/*******************************************************************************
* Copyright (c) 2010, 2013 Wind River Systems, Inc. and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Wind River Systems - initial API and implementation
*******************************************************************************/
package org.eclipse.tcf.internal.debug.tests;
import java.io.IOException;
import java.util.HashSet;
import java.util.Random;
import org.eclipse.tcf.protocol.IChannel;
import org.eclipse.tcf.protocol.IToken;
import org.eclipse.tcf.protocol.Protocol;
import org.eclipse.tcf.services.IDiagnostics;
import org.eclipse.tcf.services.IStreams;
import org.eclipse.tcf.util.TCFVirtualInputStream;
import org.eclipse.tcf.util.TCFVirtualOutputStream;
class TestStreams implements ITCFTest, IStreams.StreamsListener {
private final TCFTestSuite test_suite;
private final IChannel channel;
private final IDiagnostics diag;
private final IStreams streams;
private final Random rnd = new Random();
private final HashSet<String> stream_ids = new HashSet<String>();
private String inp_id;
private String out_id;
private int test_count;
private long start_time;
TestStreams(TCFTestSuite test_suite, IChannel channel) {
this.test_suite = test_suite;
this.channel = channel;
diag = channel.getRemoteService(IDiagnostics.class);
streams = channel.getRemoteService(IStreams.class);
}
public void start() {
if (diag == null ||streams == null) {
test_suite.done(this, null);
}
else {
start_time = System.currentTimeMillis();
connect();
}
}
private void connect() {
diag.createTestStreams(1001, 771, new IDiagnostics.DoneCreateTestStreams() {
public void doneCreateTestStreams(IToken token, Throwable error, final String inp_id, final String out_id) {
if (error != null) {
exit(error);
}
else {
TestStreams.this.inp_id = inp_id;
TestStreams.this.out_id = out_id;
if (stream_ids.size() != 0) {
exit(new Exception("Stream events without subscription"));
return;
}
streams.connect(inp_id, new IStreams.DoneConnect() {
public void doneConnect(IToken token, Exception error) {
if (error != null) {
exit(error);
}
else {
// write some data (zeros)
// this data can be dropped by Streams since we are not connected yet
final byte[] data_out = new byte[rnd.nextInt(10000) + 1000];
IStreams.DoneWrite done_write = new IStreams.DoneWrite() {
public void doneWrite(IToken token, Exception error) {
if (error != null) exit(error);
}
};
int offs = 0;
while (offs < data_out.length) {
int size = rnd.nextInt(400);
if (size > data_out.length - offs) size = data_out.length - offs;
streams.write(inp_id, data_out, offs, size, done_write);
offs += size;
}
streams.connect(out_id, new IStreams.DoneConnect() {
public void doneConnect(IToken token, Exception error) {
if (error != null) {
exit(error);
}
else {
testReadWrite(true, new Runnable() {
public void run() {
TestStreams.this.inp_id = null;
TestStreams.this.out_id = null;
subscribe();
}
});
}
}
});
}
}
});
}
}
});
}
private void subscribe() {
streams.subscribe(IDiagnostics.NAME, this, new IStreams.DoneSubscribe() {
public void doneSubscribe(IToken token, Exception error) {
if (error != null) {
exit(error);
}
else {
createStreams();
}
}
});
}
private void createStreams() {
diag.createTestStreams(1153, 947, new IDiagnostics.DoneCreateTestStreams() {
public void doneCreateTestStreams(IToken token, Throwable error, String inp_id, String out_id) {
if (error != null) {
exit(error);
}
else {
TestStreams.this.inp_id = inp_id;
TestStreams.this.out_id = out_id;
for (String id : stream_ids) {
if (id.equals(inp_id)) continue;
if (id.equals(out_id)) continue;
streams.disconnect(id, new IStreams.DoneDisconnect() {
public void doneDisconnect(IToken token, Exception error) {
if (error != null) exit(error);
}
});
}
testReadWrite(false, new Runnable() {
public void run() {
unsubscribe();
}
});
}
}
});
}
private void testReadWrite(boolean skip_zeros, final Runnable done) {
if (rnd.nextBoolean()) testReadWriteSync(skip_zeros, done);
else testReadWriteAsync(skip_zeros, done);
}
private void testReadWriteAsync(final boolean skip_zeros, final Runnable done) {
final byte[] data_out = new byte[rnd.nextInt(10000) + 1000];
rnd.nextBytes(data_out);
if (skip_zeros) data_out[0] = 1;
final HashSet<IToken> cmds = new HashSet<IToken>();
IStreams.DoneRead done_read = new IStreams.DoneRead() {
private int offs = 0;
private boolean eos;
public void doneRead(IToken token, Exception error, int lost_size, byte[] data, boolean eos) {
cmds.remove(token);
if (error != null) {
if (!this.eos) exit(error);
}
else if (lost_size != 0) {
exit(new Exception("Streams service: unexpected data loss"));
}
else if (this.eos) {
if (!eos || data != null && data.length > 0) {
exit(new Exception("Streams service: unexpected successful read after EOS"));
}
}
else {
if (data != null) {
if (offs + data.length > data_out.length) {
exit(new Exception("Streams service: read returns more data then expected"));
return;
}
for (int n = 0; n < data.length; n++) {
if (!skip_zeros || offs > 0 || data[n] != 0) {
if (data[n] != data_out[offs]) {
exit(new Exception("Streams service: data error: " + data[n] + " != " + data_out[offs]));
return;
}
offs++;
}
}
}
if (eos) {
if (offs != data_out.length) {
exit(new Exception("Streams service: unexpected EOS"));
}
this.eos = true;
}
else if (cmds.size() < 8) {
cmds.add(streams.read(out_id, 241, this));
}
}
if (cmds.isEmpty()) disposeStreams(true, done);
}
};
cmds.add(streams.read(out_id, 223, done_read));
cmds.add(streams.read(out_id, 227, done_read));
cmds.add(streams.read(out_id, 229, done_read));
cmds.add(streams.read(out_id, 233, done_read));
IStreams.DoneWrite done_write = new IStreams.DoneWrite() {
public void doneWrite(IToken token, Exception error) {
if (error != null) exit(error);
}
};
int offs = 0;
while (offs < data_out.length) {
int size = rnd.nextInt(400);
if (size > data_out.length - offs) size = data_out.length - offs;
streams.write(inp_id, data_out, offs, size, done_write);
offs += size;
}
streams.eos(inp_id, new IStreams.DoneEOS() {
public void doneEOS(IToken token, Exception error) {
if (error != null) exit(error);
}
});
}
private void testReadWriteSync(final boolean skip_zeros, final Runnable done) {
Runnable on_close = new Runnable() {
int cnt;
@Override
public void run() {
cnt++;
if (cnt == 2) disposeStreams(false, done);
if (cnt > 2) exit(new Exception("Invalid invocation of on_close"));
}
};
try {
final TCFVirtualInputStream inp = new TCFVirtualInputStream(channel, out_id, on_close);
final TCFVirtualOutputStream out = new TCFVirtualOutputStream(channel, inp_id, true, on_close);
final byte[] data_out = new byte[rnd.nextInt(10000) + 1000];
final int buf_cnt = 64;
rnd.nextBytes(data_out);
if (skip_zeros) data_out[0] = 1;
new Thread() {
@Override
public void run() {
try {
for (int i = 0; i < buf_cnt; i++) {
out.write(data_out);
if (rnd.nextInt(32) == 0) out.flush();
}
out.close();
}
catch (final IOException x) {
Protocol.invokeLater(new Runnable() {
@Override
public void run() {
exit(x);
}
});
}
}
}.start();
new Thread() {
@Override
public void run() {
try {
int pos = 0;
for (;;) {
byte[] data_inp = new byte[rnd.nextInt(10000) + 1000];
int rd = inp.read(data_inp);
if (rd < 0) {
if (pos != data_out.length * buf_cnt) throw new Exception("Invalid byte count");
break;
}
for (int i = 0; i < rd; i++) {
byte b = data_inp[i];
if (skip_zeros && pos == 0 && b == 0) continue;
if (b != data_out[pos % data_out.length]) throw new Exception("Data error");
pos++;
}
}
inp.close();
}
catch (final Exception x) {
Protocol.invokeLater(new Runnable() {
@Override
public void run() {
exit(x);
}
});
}
}
}.start();
}
catch (IOException x) {
exit(x);
}
}
private void disposeStreams(boolean disconnect, final Runnable done) {
final HashSet<IToken> cmds = new HashSet<IToken>();
IStreams.DoneDisconnect done_disconnect = new IStreams.DoneDisconnect() {
public void doneDisconnect(IToken token, Exception error) {
if (error != null) {
exit(error);
}
else {
cmds.remove(token);
if (cmds.isEmpty() && test_suite.isActive(TestStreams.this)) done.run();
}
}
};
IDiagnostics.DoneDisposeTestStream done_dispose = new IDiagnostics.DoneDisposeTestStream() {
public void doneDisposeTestStream(IToken token, Throwable error) {
if (error != null) {
exit(error);
}
else {
cmds.remove(token);
if (cmds.isEmpty() && test_suite.isActive(TestStreams.this)) done.run();
}
}
};
if (disconnect) cmds.add(streams.disconnect(inp_id, done_disconnect));
cmds.add(diag.disposeTestStream(inp_id, done_dispose));
cmds.add(diag.disposeTestStream(out_id, done_dispose));
if (disconnect) cmds.add(streams.disconnect(out_id, done_disconnect));
}
private void unsubscribe() {
streams.unsubscribe(IDiagnostics.NAME, this, new IStreams.DoneUnsubscribe() {
public void doneUnsubscribe(IToken token, Exception error) {
if (error != null || test_count >= 10 || System.currentTimeMillis() - start_time >= 4000) {
exit(error);
}
else {
test_count++;
stream_ids.clear();
inp_id = null;
out_id = null;
connect();
}
}
});
}
private void exit(Throwable x) {
if (!test_suite.isActive(this)) return;
test_suite.done(this, x);
}
/************************** StreamsListener **************************/
public void created(String stream_type, String stream_id, String context_id) {
if (!IDiagnostics.NAME.equals(stream_type)) exit(new Exception("Invalid stream type in Streams.created event"));
if (stream_ids.contains(stream_id)) exit(new Exception("Invalid stream ID in Streams.created event"));
stream_ids.add(stream_id);
if (inp_id != null) {
if (inp_id.equals(stream_id)) exit(new Exception("Invalid stream ID in Streams.created event"));
if (out_id.equals(stream_id)) exit(new Exception("Invalid stream ID in Streams.created event"));
streams.disconnect(stream_id, new IStreams.DoneDisconnect() {
public void doneDisconnect(IToken token, Exception error) {
if (error != null) {
exit(error);
}
}
});
}
}
public void disposed(String stream_type, String stream_id) {
if (!IDiagnostics.NAME.equals(stream_type)) exit(new Exception("Invalid stream type in Streams.disposed event"));
if (!stream_ids.remove(stream_id)) exit(new Exception("Invalid stream ID in Streams.disposed event"));
}
public boolean canResume(String id) {
return true;
}
}