/*******************************************************************************
* Copyright (c) 2008, 2014 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.InputStream;
import java.io.OutputStream;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Random;
import java.util.UUID;
import org.eclipse.tcf.protocol.IChannel;
import org.eclipse.tcf.protocol.IToken;
import org.eclipse.tcf.protocol.Protocol;
import org.eclipse.tcf.services.IFileSystem;
import org.eclipse.tcf.services.IFileSystem.DirEntry;
import org.eclipse.tcf.services.IFileSystem.FileAttrs;
import org.eclipse.tcf.services.IFileSystem.FileSystemException;
import org.eclipse.tcf.services.IFileSystem.IFileHandle;
import org.eclipse.tcf.util.TCFFileInputStream;
import org.eclipse.tcf.util.TCFFileOutputStream;
class TestFileSystem implements ITCFTest, IFileSystem.DoneStat,
IFileSystem.DoneOpen, IFileSystem.DoneClose,
IFileSystem.DoneWrite, IFileSystem.DoneRead,
IFileSystem.DoneRename, IFileSystem.DoneRealPath,
IFileSystem.DoneRemove, IFileSystem.DoneRoots,
IFileSystem.DoneReadDir {
private final TCFTestSuite test_suite;
private final int channel_id;
private static final String client_id = UUID.randomUUID().toString();
private static final int
STATE_PRE = 0,
STATE_RD_DIR = 1,
STATE_WRITE = 2,
STATE_READ = 3,
STATE_OUT = 4,
STATE_INP = 5,
STATE_EXIT = 6;
private final IFileSystem files;
private final Random rnd = new Random();
private final LinkedList<String> tmp_files = new LinkedList<String>();
private final HashMap<IToken,Object> cmds = new HashMap<IToken,Object>();
private byte[] data;
private String root;
private String tmp_path;
private String file_name;
private IFileHandle handle;
private int state = STATE_PRE;
private boolean async_close;
private static class ReadCmd {
int offs;
int size;
}
TestFileSystem(TCFTestSuite test_suite, IChannel channel, int channel_id) {
this.test_suite = test_suite;
this.channel_id = channel_id;
files = channel.getRemoteService(IFileSystem.class);
}
public void start() {
if (files == null) {
test_suite.done(this, null);
}
else {
files.roots(this);
}
}
private void testClosedHandle(IFileHandle handle) {
int n = rnd.nextInt(3) + 1;
for (int i = 0; i < n; i++) {
switch (rnd.nextInt(7)) {
case 0:
files.close(handle, new IFileSystem.DoneClose() {
@Override
public void doneClose(IToken token, FileSystemException error) {
if (error == null) exit(new Error("Error expected"));
}
});
break;
case 1:
files.fsetstat(handle, null, new IFileSystem.DoneSetStat() {
@Override
public void doneSetStat(IToken token, FileSystemException error) {
if (error == null) exit(new Error("Error expected"));
}
});
break;
case 2:
files.fstat(handle, new IFileSystem.DoneStat() {
@Override
public void doneStat(IToken token, FileSystemException error, FileAttrs attrs) {
if (error == null) exit(new Error("Error expected"));
}
});
break;
case 3:
files.read(handle, rnd.nextBoolean() ? 0 : -1, 1, new IFileSystem.DoneRead() {
@Override
public void doneRead(IToken token, FileSystemException error, byte[] data, boolean eof) {
if (error == null) exit(new Error("Error expected"));
}
});
break;
case 4:
files.readdir(handle, new IFileSystem.DoneReadDir() {
@Override
public void doneReadDir(IToken token, FileSystemException error, DirEntry[] entries, boolean eof) {
if (error == null) exit(new Error("Error expected"));
}
});
break;
case 5:
files.write(handle, rnd.nextBoolean() ? 0 : -1, new byte[1], 0, 1, new IFileSystem.DoneWrite() {
@Override
public void doneWrite(IToken token, FileSystemException error) {
if (error == null) exit(new Error("Error expected"));
}
});
break;
}
}
}
public void doneRoots(IToken token, FileSystemException error, DirEntry[] entries) {
assert state == STATE_PRE;
if (error != null) {
exit(error);
}
else if (entries == null || entries.length == 0) {
exit(new Exception("Invalid FileSysrem.roots responce: empty roots array"));
}
else {
for (DirEntry d : entries) {
if (d.filename.startsWith("A:")) continue;
if (d.filename.startsWith("B:")) continue;
if (d.filename.startsWith("/romfs")) continue;
root = d.filename;
break;
}
if (root == null) exit(new Exception("Invalid FileSystem.roots responce: no suitable root"));
else files.opendir(root, this);
}
}
public void doneReadDir(IToken token, FileSystemException error,
DirEntry[] entries, boolean eof) {
if (error != null) {
exit(error);
}
else if (state == STATE_PRE) {
if (entries != null && tmp_path == null) {
for (DirEntry e : entries) {
if (e.filename.equals("tmp") || e.filename.equalsIgnoreCase("temp")) {
tmp_path = root;
if (!tmp_path.endsWith("/")) tmp_path += "/";
tmp_path += e.filename;
break;
}
}
}
if (eof) {
if (tmp_path == null) {
exit(new Exception("File system test failed: cannot find temporary directory"));
return;
}
files.close(handle, this);
testClosedHandle(handle);
}
else {
files.readdir(handle, this);
}
}
else if (state == STATE_RD_DIR) {
if (entries != null) {
for (DirEntry e : entries) {
if (e.filename.startsWith("tcf-test-" + client_id + "-" + channel_id + "-")) {
tmp_files.add(e.filename);
}
}
}
if (eof) {
files.close(handle, this);
testClosedHandle(handle);
}
else {
files.readdir(handle, this);
}
}
else {
assert false;
}
}
public void doneStat(IToken token, FileSystemException error, FileAttrs attrs) {
if (error != null) {
exit(error);
}
else if (state == STATE_READ) {
if (attrs.size != data.length) {
exit(new Exception("Invalid FileSysrem.fstat responce: wrong file size"));
}
else {
files.close(handle, this);
testClosedHandle(handle);
}
}
else if (state == STATE_WRITE) {
char[] bf = new char[64];
for (int i = 0; i < bf.length; i++) {
char ch = (char)(rnd.nextInt(0x4000) + 0x20);
switch (ch) {
case '<':
case '>':
case ':':
case '"':
case '/':
case '\\':
case '|':
case '?':
case '*':
case '~':
ch = '-';
break;
}
bf[i] = ch;
}
file_name = tmp_path + "/tcf-test-" + client_id + "-" + channel_id + "-" + new String(bf) + ".tmp";
files.open(file_name, IFileSystem.TCF_O_CREAT | IFileSystem.TCF_O_TRUNC | IFileSystem.TCF_O_WRITE, null, this);
}
else {
assert false;
}
}
public void doneOpen(IToken token, FileSystemException error, final IFileHandle handle) {
if (error != null) {
exit(error);
}
else {
this.handle = handle;
if (state == STATE_READ) {
for (int i = 0; i < rnd.nextInt(8); i++) {
ReadCmd cmd = new ReadCmd();
cmd.offs = rnd.nextInt(data.length);
cmd.size = rnd.nextInt(data.length - cmd.offs) + 2;
cmds.put(files.read(handle, cmd.offs, cmd.size, this), cmd);
}
if (rnd.nextBoolean()) {
ReadCmd cmd = new ReadCmd();
cmd.offs = 0;
cmd.size = data.length + 1;
cmds.put(files.read(handle, cmd.offs, cmd.size, this), cmd);
}
else {
int pos = 0;
while (pos < data.length) {
int size = rnd.nextInt(data.length - pos) + 1;
ReadCmd cmd = new ReadCmd();
cmd.offs = pos;
cmd.size = size + 1;
cmds.put(files.read(handle, cmd.offs, cmd.size, this), cmd);
pos += size;
}
}
async_close = rnd.nextBoolean();
if (async_close) {
files.close(handle, this);
testClosedHandle(handle);
}
}
else if (state == STATE_WRITE) {
data = new byte[rnd.nextInt(0x1000) + 1];
rnd.nextBytes(data);
if (rnd.nextBoolean()) {
cmds.put(files.write(handle, 0, data, 0, data.length, this), null);
}
else {
int pos = 0;
while (pos < data.length) {
int size = rnd.nextInt(data.length - pos) + 1;
cmds.put(files.write(handle, pos, data, pos, size, this), null);
pos += size;
}
}
async_close = rnd.nextBoolean();
if (async_close) {
files.close(handle, this);
testClosedHandle(handle);
}
}
else if (state == STATE_INP) {
Thread thread = new Thread() {
public void run() {
try {
int pos = 0;
int len = data.length * 16;
byte[] buf = new byte[333];
int buf_pos = 0;
int buf_len = 0;
boolean mark = true;
boolean reset = true;
int mark_pos = rnd.nextInt(len - 1);
int reset_pos = mark_pos + rnd.nextInt(len - mark_pos);
assert reset_pos >= mark_pos;
InputStream inp = new TCFFileInputStream(handle, 133);
for (;;) {
if (mark && pos == mark_pos) {
inp.mark(len);
mark = false;
}
if (reset && pos == reset_pos) {
inp.reset();
reset = false;
pos = mark_pos;
buf_pos = buf_len = 0;
}
int ch = 0;
if (buf_pos >= buf_len && (pos >= mark_pos || pos + buf.length <= mark_pos)) {
buf_len = inp.read(buf, buf_pos = 0, buf.length);
if (buf_len < 0) break;
}
if (buf_pos < buf_len) {
ch = buf[buf_pos++] & 0xff;
}
else {
ch = inp.read();
if (ch < 0) break;
}
int dt = data[pos % data.length] & 0xff;
if (ch != dt) {
error(new Exception("Invalid TCFFileInputStream.read responce: wrong data at offset " + pos +
", expected " + dt + ", actual " + ch));
}
pos++;
}
if (pos != data.length * 16) {
error(new Exception("Invalid TCFFileInputStream.read responce: wrong file length: " +
"expected " + data.length + ", actual " + pos));
}
inp.close();
Protocol.invokeLater(new Runnable() {
public void run() {
state = STATE_EXIT;
files.rename(file_name, file_name + ".rnm", TestFileSystem.this);
}
});
}
catch (Throwable x) {
error(x);
}
}
private void error(final Throwable x) {
Protocol.invokeLater(new Runnable() {
public void run() {
exit(x);
}
});
}
};
thread.setName("TCF FileSystem Test");
thread.start();
}
else if (state == STATE_OUT) {
rnd.nextBytes(data);
Thread thread = new Thread() {
public void run() {
try {
int pos = 0;
int len = data.length * 16;
OutputStream out = new TCFFileOutputStream(handle, 121);
while (pos < len) {
int m = pos % data.length;
int n = rnd.nextInt(1021);
if (n > data.length - m) n = data.length - m;
out.write(data, m, n);
pos += n;
if (pos == len) break;
out.write(data[pos % data.length] & 0xff);
pos++;
}
out.close();
Protocol.invokeLater(new Runnable() {
public void run() {
state = STATE_INP;
files.open(file_name, IFileSystem.TCF_O_READ, null, TestFileSystem.this);
}
});
}
catch (Throwable x) {
error(x);
}
}
private void error(final Throwable x) {
Protocol.invokeLater(new Runnable() {
public void run() {
exit(x);
}
});
}
};
thread.setName("TCF FileSystem Test");
thread.start();
}
else {
assert state == STATE_PRE || state == STATE_RD_DIR;
files.readdir(handle, this);
}
}
}
public void doneWrite(IToken token, FileSystemException error) {
assert cmds.containsKey(token);
cmds.remove(token);
if (error != null) {
exit(error);
}
else if (cmds.size() == 0 && !async_close) {
files.close(handle, this);
testClosedHandle(handle);
}
}
public void doneRead(IToken token, FileSystemException error, byte[] data, boolean eof) {
assert cmds.containsKey(token);
ReadCmd cmd = (ReadCmd)cmds.remove(token);
if (error != null) {
exit(error);
}
else {
if (cmd.offs + cmd.size > this.data.length && !eof) {
exit(new Exception("Invalid FileSysrem.read responce: EOF expected"));
}
else if (data.length != (eof ? cmd.size - 1 : cmd.size)) {
exit(new Exception("Invalid FileSysrem.read responce: wrong data array size"));
}
else {
for (int i = 0; i < data.length; i++) {
if (data[i] != this.data[i + cmd.offs]) {
exit(new Exception("Invalid FileSysrem.read responce: wrong data at offset " + i +
", expected " + this.data[i + cmd.offs] + ", actual " + data[i]));
return;
}
}
if (cmds.size() == 0 && !async_close) files.fstat(handle, this);
}
}
}
public void doneClose(IToken token, FileSystemException error) {
if (error != null) {
exit(error);
}
else {
handle = null;
if (state == STATE_PRE) {
state = STATE_RD_DIR;
files.opendir(tmp_path, this);
}
else if (state == STATE_RD_DIR) {
state = STATE_WRITE;
files.realpath(tmp_path, this);
}
else if (state == STATE_WRITE) {
testSetStat(new Runnable() {
@Override
public void run() {
state = STATE_READ;
files.open(file_name, IFileSystem.TCF_O_READ, null, TestFileSystem.this);
}
});
}
else if (state == STATE_READ) {
state = STATE_OUT;
files.open(file_name, IFileSystem.TCF_O_WRITE, null, this);
}
else {
assert false;
}
}
}
public void doneRename(IToken token, FileSystemException error) {
assert state == STATE_EXIT;
if (error != null) {
exit(error);
}
else {
files.realpath(file_name + ".rnm", this);
}
}
public void doneRealPath(IToken token, FileSystemException error, String path) {
if (error != null) {
exit(error);
}
else if (state == STATE_WRITE) {
tmp_path = path;
if (tmp_files.size() > 0) {
files.remove(tmp_path + "/" + tmp_files.removeFirst(), this);
}
else {
files.stat(tmp_path, this);
}
}
else if (!path.equals(file_name + ".rnm")) {
exit(new Exception("Invalid FileSysrem.realpath responce: " + path));
}
else {
files.remove(file_name + ".rnm", this);
}
}
public void doneRemove(IToken token, FileSystemException error) {
if (error != null) {
exit(error);
}
else if (state == STATE_WRITE) {
if (tmp_files.size() > 0) {
files.remove(tmp_path + "/" + tmp_files.removeFirst(), this);
}
else {
files.stat(tmp_path, this);
}
}
else if (state == STATE_EXIT) {
exit(null);
}
else {
assert false;
}
}
private void testSetStat(final Runnable done) {
files.stat(file_name, new IFileSystem.DoneStat() {
@Override
public void doneStat(IToken token, FileSystemException error, FileAttrs attrs) {
if (error != null) {
exit(error);
return;
}
FileAttrs new_attrs = new FileAttrs(
IFileSystem.ATTR_SIZE | IFileSystem.ATTR_ACMODTIME,
attrs.size, 0, 0, 0, attrs.atime, attrs.mtime, null);
files.setstat(file_name, new_attrs, new IFileSystem.DoneSetStat() {
@Override
public void doneSetStat(IToken token, FileSystemException error) {
if (error != null) {
exit(error);
return;
}
testFSetStat(done);
}
});
}
});
}
private void testFSetStat(final Runnable done) {
files.open(file_name, IFileSystem.TCF_O_WRITE, null, new IFileSystem.DoneOpen() {
@Override
public void doneOpen(IToken token, FileSystemException error, final IFileHandle handle) {
if (error != null) {
exit(error);
return;
}
files.fstat(handle, new IFileSystem.DoneStat() {
@Override
public void doneStat(IToken token, FileSystemException error, FileAttrs attrs) {
if (error != null) {
exit(error);
return;
}
FileAttrs new_attrs = new FileAttrs(
IFileSystem.ATTR_SIZE | IFileSystem.ATTR_ACMODTIME,
attrs.size, 0, 0, 0, attrs.atime, attrs.mtime, null);
files.fsetstat(handle, new_attrs, new IFileSystem.DoneSetStat() {
@Override
public void doneSetStat(IToken token, FileSystemException error) {
if (error != null) {
exit(error);
return;
}
files.close(handle, new IFileSystem.DoneClose() {
@Override
public void doneClose(IToken token, FileSystemException error) {
if (error != null) {
exit(error);
return;
}
done.run();
}
});
}
});
}
});
}
});
}
private void exit(Throwable x) {
if (!test_suite.isActive(this)) return;
test_suite.done(this, x);
}
public boolean canResume(String id) {
return true;
}
}