Spaces:
Runtime error
Runtime error
| import url from "node:url"; | |
| import fs from "node:fs"; | |
| process.on("unhandledRejection", exn => { throw exn; }); | |
| const __dirname = url.fileURLToPath(new URL(".", import.meta.url)); | |
| const TEST_RELEASE_BUILD = +process.env.TEST_RELEASE_BUILD; | |
| const { V86 } = await import(TEST_RELEASE_BUILD ? "../../build/libv86.mjs" : "../../src/main.js"); | |
| const testfsjson = JSON.parse(fs.readFileSync(__dirname + "/testfs.json", "utf-8")); | |
| const SHOW_LOGS = false; | |
| const STOP_ON_FIRST_FAILURE = false; | |
| function assert_equal(actual, expected, message) | |
| { | |
| if(actual !== expected) | |
| { | |
| console.warn("Failed assert equal (Test: %s). %s", tests[test_num].name, message || ""); | |
| console.warn("Expected:\n" + expected); | |
| console.warn("Actual:\n" + actual); | |
| test_fail(); | |
| } | |
| } | |
| function assert_not_equal(actual, expected, message) | |
| { | |
| if(actual === expected) | |
| { | |
| console.warn("Failed assert not equal (Test: %s). %s", tests[test_num].name, message || ""); | |
| console.warn("Expected something different than:\n" + expected); | |
| test_fail(); | |
| } | |
| } | |
| // Random printable characters. | |
| const test_file = new Uint8Array(512).map(v => 0x20 + Math.random() * 0x5e); | |
| const test_file_string = Buffer.from(test_file).toString(); | |
| const test_file_small = new Uint8Array(16).map(v => 0x20 + Math.random() * 0x5e); | |
| const test_file_small_string = Buffer.from(test_file_small).toString(); | |
| const tests = | |
| [ | |
| { | |
| name: "Read Existing", | |
| timeout: 60, | |
| start: () => | |
| { | |
| emulator.serial0_send("cp /etc/profile /mnt/read-existing\n"); | |
| emulator.serial0_send("echo start-capture; cat /etc/profile; echo done-read-existing\n"); | |
| }, | |
| capture_trigger: "start-capture", | |
| end_trigger: "done-read-existing", | |
| end: async (capture, done) => | |
| { | |
| const data = await emulator.read_file("read-existing"); | |
| assert_equal(capture, Buffer.from(data).toString()); | |
| done(); | |
| }, | |
| }, | |
| { | |
| name: "Read New", | |
| timeout: 60, | |
| start: () => | |
| { | |
| emulator.serial0_send("dd if=/dev/zero of=/mnt/read-new bs=1k count=512\n"); | |
| emulator.serial0_send("echo done-read-new\n"); | |
| }, | |
| end_trigger: "done-read-new", | |
| end: async (capture, done) => | |
| { | |
| const data = await emulator.read_file("read-new"); | |
| assert_equal(data.length, 512 * 1024); | |
| if(data.find(v => v !== 0)) | |
| { | |
| console.warn("Fail: Incorrect data. Expected all zeros."); | |
| test_fail(); | |
| } | |
| done(); | |
| }, | |
| }, | |
| { | |
| name: "Read Async", | |
| use_fsjson: true, | |
| timeout: 60, | |
| start: () => | |
| { | |
| emulator.serial0_send("echo start-capture;"); | |
| // "foo" is from ./testfs/foo | |
| emulator.serial0_send("cat /mnt/foo;"); | |
| emulator.serial0_send("echo done-read-async\n"); | |
| }, | |
| capture_trigger: "start-capture", | |
| end_trigger: "done-read-async", | |
| end: async (capture, done) => | |
| { | |
| assert_equal(capture, "bar\n"); | |
| const data = await emulator.read_file("foo"); | |
| assert_equal(Buffer.from(data).toString(), "bar\n"); | |
| done(); | |
| }, | |
| }, | |
| { | |
| name: "Write New", | |
| timeout: 60, | |
| files: | |
| [ | |
| { | |
| file: "write-new", | |
| data: test_file, | |
| }, | |
| ], | |
| start: () => | |
| { | |
| emulator.serial0_send("echo start-capture; cat /mnt/write-new; echo; echo done-write-new\n"); | |
| }, | |
| capture_trigger: "start-capture", | |
| end_trigger: "done-write-new", | |
| end: (capture, done) => | |
| { | |
| // Handle word wrapping. | |
| const lines = capture.split("\n"); | |
| let pos = 0; | |
| for(const line of lines) | |
| { | |
| assert_equal(line, test_file_string.slice(pos, line.length)); | |
| pos += line.length; | |
| } | |
| done(); | |
| }, | |
| }, | |
| { | |
| name: "New file time", | |
| timeout: 10, | |
| start: () => | |
| { | |
| emulator.serial0_send("echo start-capture; echo foo > /mnt/bar; ls -l --full-time --color=never /mnt/bar; echo; echo done-write-new\n"); | |
| }, | |
| capture_trigger: "start-capture", | |
| end_trigger: "done-write-new", | |
| end: (capture, done) => | |
| { | |
| const outputs = capture.split("\n").map(output => output.split(/\s+/)); | |
| // atime: Should be fresh | |
| const [year, month, day] = outputs[0][5].split("-"); | |
| assert_not_equal(year, "1970"); | |
| done(); | |
| }, | |
| }, | |
| { | |
| name: "Move", | |
| timeout: 60, | |
| files: | |
| [ | |
| { | |
| file: "test-file", | |
| data: test_file, | |
| }, | |
| ], | |
| start: () => | |
| { | |
| emulator.serial0_send("echo start-capture;"); | |
| emulator.serial0_send("cat /mnt/test-file;"); | |
| emulator.serial0_send("find /mnt;"); | |
| // Rename. Verify updated directory. | |
| emulator.serial0_send("mv /mnt/test-file /mnt/renamed;"); | |
| emulator.serial0_send("cat /mnt/renamed;"); | |
| emulator.serial0_send("find /mnt;"); | |
| // Move between folders. Verify directories. | |
| emulator.serial0_send("mkdir /mnt/somedir;"); | |
| emulator.serial0_send("mv /mnt/renamed /mnt/somedir/file;"); | |
| emulator.serial0_send("cat /mnt/somedir/file;"); | |
| emulator.serial0_send("find /mnt;"); | |
| // Rename folder. | |
| emulator.serial0_send("mv /mnt/somedir /mnt/otherdir;"); | |
| emulator.serial0_send("cat /mnt/otherdir/file;"); | |
| emulator.serial0_send("find /mnt;"); | |
| // Move folder. | |
| emulator.serial0_send("mkdir /mnt/thirddir;"); | |
| emulator.serial0_send("mv /mnt/otherdir /mnt/thirddir;"); | |
| emulator.serial0_send("cat /mnt/thirddir/otherdir/file;"); | |
| emulator.serial0_send("find /mnt;"); | |
| // Move folder outside /mnt. Should be removed from 9p filesystem. | |
| emulator.serial0_send("mv /mnt/thirddir/otherdir /root/movedoutside;"); | |
| emulator.serial0_send("cat /root/movedoutside/file;"); | |
| emulator.serial0_send("find /mnt;"); | |
| // Cleanup. | |
| emulator.serial0_send("rm -rf /root/movedoutside;"); | |
| emulator.serial0_send("echo done-move\n"); | |
| }, | |
| capture_trigger: "start-capture", | |
| end_trigger: "done-move", | |
| end: (capture, done) => | |
| { | |
| assert_equal(capture, | |
| test_file_string + | |
| "/mnt\n" + | |
| "/mnt/test-file\n" + | |
| test_file_string + | |
| "/mnt\n" + | |
| "/mnt/renamed\n" + | |
| test_file_string + | |
| "/mnt\n" + | |
| "/mnt/somedir\n" + | |
| "/mnt/somedir/file\n" + | |
| test_file_string + | |
| "/mnt\n" + | |
| "/mnt/otherdir\n" + | |
| "/mnt/otherdir/file\n" + | |
| test_file_string + | |
| "/mnt\n" + | |
| "/mnt/thirddir\n" + | |
| "/mnt/thirddir/otherdir\n" + | |
| "/mnt/thirddir/otherdir/file\n" + | |
| test_file_string + | |
| "/mnt\n" + | |
| "/mnt/thirddir\n"); | |
| done(); | |
| }, | |
| }, | |
| { | |
| name: "Unlink", | |
| timeout: 60, | |
| files: | |
| [ | |
| { | |
| file: "existing-file", | |
| data: test_file, | |
| }, | |
| ], | |
| start: () => | |
| { | |
| emulator.serial0_send("touch /mnt/new-file\n"); | |
| emulator.serial0_send("mkdir /mnt/new-dir\n"); | |
| emulator.serial0_send("touch /mnt/new-dir/file\n"); | |
| emulator.serial0_send("echo start-capture;"); | |
| emulator.serial0_send("rm /mnt/new-file;"); | |
| emulator.serial0_send("test ! -e /mnt/new-file && echo new-file-unlinked;"); | |
| emulator.serial0_send("cat /mnt/new-file 2>/dev/null || echo read-failed;"); | |
| emulator.serial0_send("rm /mnt/existing-file;"); | |
| emulator.serial0_send("test ! -e /mnt/existing-file && echo existing-file-unlinked;"); | |
| emulator.serial0_send("cat /mnt/existing-file 2>/dev/null || echo read-failed;"); | |
| emulator.serial0_send("rmdir /mnt/new-dir 2>/dev/null || echo rmdir-failed;"); | |
| emulator.serial0_send("test -e /mnt/new-dir && echo new-dir-exist;"); | |
| emulator.serial0_send("rm /mnt/new-dir/file;"); | |
| emulator.serial0_send("rmdir /mnt/new-dir;"); | |
| emulator.serial0_send("test ! -e /mnt/new-dir/file && echo new-dir-file-unlinked;"); | |
| emulator.serial0_send("test ! -e /mnt/new-dir && echo new-dir-unlinked;"); | |
| emulator.serial0_send("ls /mnt/new-dir 2>/dev/null || echo read-failed;"); | |
| emulator.serial0_send("echo done-unlink\n"); | |
| }, | |
| capture_trigger: "start-capture", | |
| end_trigger: "done-unlink", | |
| end: (capture, done) => | |
| { | |
| assert_equal(capture, | |
| "new-file-unlinked\n" + | |
| "read-failed\n" + | |
| "existing-file-unlinked\n" + | |
| "read-failed\n" + | |
| "rmdir-failed\n" + | |
| "new-dir-exist\n" + | |
| "new-dir-file-unlinked\n" + | |
| "new-dir-unlinked\n" + | |
| "read-failed\n"); | |
| done(); | |
| }, | |
| }, | |
| { | |
| name: "Hard Links", | |
| timeout: 60, | |
| files: | |
| [ | |
| { | |
| file: "target", | |
| data: test_file_small, | |
| }, | |
| ], | |
| start: () => | |
| { | |
| // Helper that prints filename followed by nlinks. | |
| emulator.serial0_send("nlinks() {\n"); | |
| emulator.serial0_send(` ls -dli $@ | awk '{ print "'$@' "$3 }'\n`); | |
| emulator.serial0_send("}\n"); | |
| // Check nlinks before mkdir. | |
| emulator.serial0_send("nlinks /mnt | tee -a /mnt/target\n"); | |
| emulator.serial0_send("mkdir /mnt/dir\n"); | |
| emulator.serial0_send("echo other > /mnt/target2\n"); | |
| // Check nlinks after mkdir. | |
| emulator.serial0_send("nlinks /mnt | tee -a /mnt/target\n"); | |
| emulator.serial0_send("nlinks /mnt/dir | tee -a /mnt/target\n"); | |
| emulator.serial0_send("nlinks /mnt/target | tee -a /mnt/target\n"); | |
| // Create hard links. | |
| emulator.serial0_send("ln /mnt/target /mnt/link1\n"); | |
| emulator.serial0_send("ln /mnt/link1 /mnt/dir/link2\n"); | |
| emulator.serial0_send("ln /mnt/dir/link2 /mnt/dir/link3\n"); | |
| emulator.serial0_send("ln /mnt/target2 /mnt/link-other\n"); | |
| // Test inode numbers. | |
| emulator.serial0_send("{ test /mnt/target -ef /mnt/link1 && \n"); | |
| emulator.serial0_send(" test /mnt/link1 -ef /mnt/dir/link2 && \n"); | |
| emulator.serial0_send(" test /mnt/target -ef /mnt/dir/link3 && \n"); | |
| emulator.serial0_send(" echo same inode | tee -a /mnt/target; }\n"); | |
| emulator.serial0_send("{ test /mnt/link-other -ef /mnt/dir/link3 || \n"); | |
| emulator.serial0_send(" echo different inode | tee -a /mnt/link1; }\n"); | |
| // Check nlinks after hard links. | |
| emulator.serial0_send("nlinks /mnt | tee -a /mnt/dir/link2\n"); | |
| emulator.serial0_send("nlinks /mnt/dir | tee -a /mnt/dir/link2\n"); | |
| emulator.serial0_send("nlinks /mnt/target | tee -a /mnt/dir/link2\n"); | |
| emulator.serial0_send("nlinks /mnt/dir/link2 | tee -a /mnt/dir/link2\n"); | |
| emulator.serial0_send("nlinks /mnt/target2 | tee -a /mnt/dir/link2\n"); | |
| emulator.serial0_send("nlinks /mnt/link-other | tee -a /mnt/dir/link2\n"); | |
| // Movement and unlink. | |
| emulator.serial0_send("mv /mnt/link1 /mnt/link1-renamed\n"); | |
| emulator.serial0_send("echo renamed | tee -a /mnt/link1-renamed\n"); | |
| emulator.serial0_send("mv /mnt/dir/link2 /mnt/link2-moved\n"); | |
| emulator.serial0_send("echo moved | tee -a /mnt/link2-moved\n"); | |
| emulator.serial0_send("rm /mnt/target\n"); | |
| emulator.serial0_send("echo unlinked original | tee -a /mnt/dir/link3\n"); | |
| // Test inode numbers after movement and unlinking. | |
| emulator.serial0_send("{ test /mnt/link1-renamed -ef /mnt/link2-moved && \n"); | |
| emulator.serial0_send(" test /mnt/link2-moved -ef /mnt/dir/link3 && \n"); | |
| emulator.serial0_send(" echo same inode after mv | tee -a /mnt/link1-renamed; }\n"); | |
| // Check nlinks after movement and unlinking. | |
| emulator.serial0_send("nlinks /mnt | tee -a /mnt/link2-moved\n"); | |
| emulator.serial0_send("nlinks /mnt/dir | tee -a /mnt/link2-moved\n"); | |
| emulator.serial0_send("nlinks /mnt/link1-renamed | tee -a /mnt/link2-moved\n"); | |
| emulator.serial0_send("echo start-capture;\\\n"); | |
| // Unlink the rest and output the above messages. | |
| emulator.serial0_send("rm /mnt/link1-renamed;\\\n"); | |
| emulator.serial0_send("echo unlinked link1 >> /mnt/link2-moved;\\\n"); | |
| emulator.serial0_send("nlinks /mnt/link2-moved >> /mnt/link2-moved;\\\n"); | |
| emulator.serial0_send("rm /mnt/link2-moved;\\\n"); | |
| emulator.serial0_send("echo unlinked link2 >> /mnt/dir/link3;\\\n"); | |
| emulator.serial0_send("nlinks /mnt/dir/link3 >> /mnt/dir/link3;\\\n"); | |
| emulator.serial0_send("cat /mnt/dir/link3;\\\n"); | |
| emulator.serial0_send("rm /mnt/dir/link3;\\\n"); | |
| // Verify nlinks of directories after unlinking hardlinks. | |
| emulator.serial0_send("nlinks /mnt;\\\n"); | |
| emulator.serial0_send("nlinks /mnt/dir;\\\n"); | |
| // Verify nlinks of root directory after subdirectory is unlinked. | |
| emulator.serial0_send("rmdir /mnt/dir;\\\n"); | |
| emulator.serial0_send("nlinks /mnt;\\\n"); | |
| emulator.serial0_send("echo done-hard-links\n"); | |
| }, | |
| capture_trigger: "start-capture", | |
| end_trigger: "done-hard-links", | |
| end: (capture, done) => | |
| { | |
| assert_equal(capture, | |
| test_file_small_string + | |
| "/mnt 2\n" + | |
| "/mnt 3\n" + | |
| "/mnt/dir 2\n" + | |
| "/mnt/target 1\n" + | |
| "same inode\n" + | |
| "different inode\n" + | |
| "/mnt 3\n" + | |
| "/mnt/dir 2\n" + | |
| "/mnt/target 4\n" + | |
| "/mnt/dir/link2 4\n" + | |
| "/mnt/target2 2\n" + | |
| "/mnt/link-other 2\n" + | |
| "renamed\n" + | |
| "moved\n" + | |
| "unlinked original\n" + | |
| "same inode after mv\n" + | |
| "/mnt 3\n" + | |
| "/mnt/dir 2\n" + | |
| "/mnt/link1-renamed 3\n" + | |
| "unlinked link1\n" + | |
| "/mnt/link2-moved 2\n" + | |
| "unlinked link2\n" + | |
| "/mnt/dir/link3 1\n" + | |
| "/mnt 3\n" + | |
| "/mnt/dir 2\n" + | |
| "/mnt 2\n"); | |
| done(); | |
| }, | |
| }, | |
| { | |
| name: "Symlinks", | |
| timeout: 60, | |
| files: | |
| [ | |
| { | |
| file: "target", | |
| data: test_file_small, | |
| }, | |
| ], | |
| start: () => | |
| { | |
| emulator.serial0_send("echo otherdata > /mnt/target2\n"); | |
| emulator.serial0_send("ln -s /mnt/target /mnt/symlink\n"); | |
| emulator.serial0_send("echo appended >> /mnt/symlink\n"); | |
| emulator.serial0_send("echo start-capture;"); | |
| // Should output same file data. | |
| emulator.serial0_send("cat /mnt/target;"); | |
| emulator.serial0_send("cat /mnt/symlink;"); | |
| // Swap target with the other file. | |
| emulator.serial0_send("rm /mnt/target;"); | |
| emulator.serial0_send("mv /mnt/target2 /mnt/target;"); | |
| // Symlink should now read from that file. | |
| emulator.serial0_send("cat /mnt/symlink;"); | |
| emulator.serial0_send("rm /mnt/target;"); | |
| emulator.serial0_send("rm /mnt/symlink;"); | |
| emulator.serial0_send("echo done-symlinks\n"); | |
| }, | |
| capture_trigger: "start-capture", | |
| end_trigger: "done-symlinks", | |
| end: (capture, done) => | |
| { | |
| assert_equal(capture, | |
| test_file_small_string + "appended\n" + | |
| test_file_small_string + "appended\n" + | |
| "otherdata\n"); | |
| done(); | |
| }, | |
| }, | |
| { | |
| name: "Mknod - fifo", | |
| timeout: 60, | |
| start: () => | |
| { | |
| emulator.serial0_send("mkfifo /mnt/testfifo\n"); | |
| emulator.serial0_send('(cat /mnt/testfifo > /mnt/testfifo-output;echo "\ndone-fifo") &\n'); | |
| emulator.serial0_send("echo fifomessage > /mnt/testfifo\n"); | |
| }, | |
| end_trigger: "done-fifo", | |
| end: async (capture, done) => | |
| { | |
| const data = await emulator.read_file("testfifo-output"); | |
| assert_equal(Buffer.from(data).toString(), "fifomessage\n"); | |
| done(); | |
| }, | |
| }, | |
| { | |
| name: "Readlink", | |
| timeout: 60, | |
| start: () => | |
| { | |
| emulator.serial0_send("touch /mnt/target\n"); | |
| emulator.serial0_send("ln -s /mnt/target /mnt/link\n"); | |
| emulator.serial0_send("echo start-capture;"); | |
| emulator.serial0_send("readlink /mnt/link;"); | |
| emulator.serial0_send("echo done-readlink\n"); | |
| }, | |
| capture_trigger: "start-capture", | |
| end_trigger: "done-readlink", | |
| end: (capture, done) => | |
| { | |
| assert_equal(capture, "/mnt/target\n"); | |
| done(); | |
| }, | |
| }, | |
| { | |
| name: "Mkdir", | |
| timeout: 60, | |
| start: () => | |
| { | |
| emulator.serial0_send("echo notfoobar > /mnt/e-file\n"); | |
| emulator.serial0_send("mkdir /mnt/a-dir\n"); | |
| emulator.serial0_send("mkdir /mnt/a-dir/b-dir\n"); | |
| emulator.serial0_send("mkdir /mnt/a-dir/c-dir\n"); | |
| emulator.serial0_send("touch /mnt/a-dir/d-file\n"); | |
| emulator.serial0_send("echo mkdirfoobar > /mnt/a-dir/e-file\n"); | |
| emulator.serial0_send("echo done-mkdir\n"); | |
| }, | |
| end_trigger: "done-mkdir", | |
| end: async (capture, done) => | |
| { | |
| const data = await emulator.read_file("a-dir/e-file"); | |
| assert_equal(Buffer.from(data).toString(), "mkdirfoobar\n"); | |
| done(); | |
| }, | |
| }, | |
| { | |
| name: "Walk", | |
| timeout: 60, | |
| start: () => | |
| { | |
| emulator.serial0_send("mkdir -p /mnt/walk/a/aa/aaa/aaaa\n"); | |
| emulator.serial0_send("mkdir -p /mnt/walk/a/aa/aaa/aaaa\n"); | |
| emulator.serial0_send("mkdir -p /mnt/walk/b/ba\n"); | |
| emulator.serial0_send("mkdir -p /mnt/walk/a/aa/aab\n"); | |
| emulator.serial0_send("mkdir -p /mnt/walk/a/aa/aac\n"); | |
| emulator.serial0_send("touch /mnt/walk/a/aa/aab/aabfile\n"); | |
| emulator.serial0_send("touch /mnt/walk/b/bfile\n"); | |
| emulator.serial0_send("echo start-capture;"); | |
| emulator.serial0_send("find /mnt/walk | sort;"); // order agnostic | |
| emulator.serial0_send("echo done-walk\n"); | |
| }, | |
| capture_trigger: "start-capture", | |
| end_trigger: "done-walk", | |
| end: (capture, done) => | |
| { | |
| const actual = capture; | |
| const expected = | |
| "/mnt/walk\n" + | |
| "/mnt/walk/a\n" + | |
| "/mnt/walk/a/aa\n" + | |
| "/mnt/walk/a/aa/aaa\n" + | |
| "/mnt/walk/a/aa/aaa/aaaa\n" + | |
| "/mnt/walk/a/aa/aab\n" + | |
| "/mnt/walk/a/aa/aab/aabfile\n" + | |
| "/mnt/walk/a/aa/aac\n" + | |
| "/mnt/walk/b\n" + | |
| "/mnt/walk/b/ba\n" + | |
| "/mnt/walk/b/bfile\n"; | |
| assert_equal(actual, expected); | |
| done(); | |
| }, | |
| }, | |
| { | |
| name: "Statfs", | |
| timeout: 60, | |
| allow_failure: true, | |
| start: () => | |
| { | |
| emulator.serial0_send("echo start-capture;"); | |
| emulator.serial0_send("touch /mnt/file;"); | |
| emulator.serial0_send("df -PTk /mnt | tail -n 1;"); | |
| // Grow file and verify space usage. | |
| emulator.serial0_send("dd if=/dev/zero of=/mnt/file bs=1k count=4 status=none;"); | |
| emulator.serial0_send("df -PTk /mnt | tail -n 1;"); | |
| // Shrink file and verify space usage. | |
| emulator.serial0_send("truncate -s 0 /mnt/file;"); | |
| emulator.serial0_send("df -PTk /mnt | tail -n 1;"); | |
| emulator.serial0_send("echo done-statfs\n"); | |
| }, | |
| capture_trigger: "start-capture", | |
| end_trigger: "done-statfs", | |
| end: (capture, done) => | |
| { | |
| const outputs = capture.split("\n").map(output => output.split(/\s+/)); | |
| if(outputs.length < 3) | |
| { | |
| console.warn("Wrong format: %s", capture); | |
| test_fail(); | |
| done(); | |
| return; | |
| } | |
| const before = outputs[0]; | |
| const after_add = outputs[1]; | |
| const after_rm = outputs[2]; | |
| // mount tag | |
| assert_equal(before[0], "host9p"); | |
| // fs type | |
| assert_equal(before[1], "9p"); | |
| // total size in 1024 blocks | |
| assert_equal(after_add[2], before[2]); | |
| assert_equal(after_rm[2], before[2]); | |
| // used size in 1024 blocks | |
| assert_equal(+after_add[3], (+before[3]) + 4); | |
| assert_equal(after_rm[3], before[3]); | |
| // free size in 1024 blocks | |
| assert_equal(+after_add[4], (+before[4]) - 4); | |
| assert_equal(after_rm[4], before[4]); | |
| // Entry [5] is percentage used. | |
| // mount path | |
| assert_equal(before[6], "/mnt"); | |
| done(); | |
| }, | |
| }, | |
| { | |
| name: "File Attributes", | |
| timeout: 60, | |
| start: () => | |
| { | |
| emulator.serial0_send("echo start-capture;"); | |
| emulator.serial0_send("dd if=/dev/zero of=/mnt/file bs=1 count=137 status=none;"); | |
| emulator.serial0_send("touch -t 200002022222 /mnt/file;"); | |
| emulator.serial0_send("chmod =rw /mnt/file;"); | |
| emulator.serial0_send("ls -l --full-time --color=never /mnt/file;"); | |
| emulator.serial0_send("chmod +x /mnt/file;"); | |
| emulator.serial0_send("chmod -w /mnt/file;"); | |
| emulator.serial0_send("ln /mnt/file /mnt/file-link;"); | |
| emulator.serial0_send("ls -l --full-time --color=never /mnt/file;"); | |
| emulator.serial0_send("chmod -x /mnt/file;"); | |
| emulator.serial0_send("truncate -s 100 /mnt/file;"); | |
| emulator.serial0_send("touch -t 201011220344 /mnt/file;"); | |
| emulator.serial0_send("rm /mnt/file-link;"); | |
| emulator.serial0_send("ls -l --full-time --color=never /mnt/file;"); | |
| emulator.serial0_send("echo done-file-attr\n"); | |
| }, | |
| capture_trigger: "start-capture", | |
| end_trigger: "done-file-attr", | |
| end: (capture, done) => | |
| { | |
| const outputs = capture.split("\n").map(output => output.split(/\s+/)); | |
| if(outputs.length < 3) | |
| { | |
| console.warn("Wrong format (expected 3 rows): %s", capture); | |
| test_fail(); | |
| done(); | |
| return; | |
| } | |
| // mode | |
| assert_equal(outputs[0][0], "-rw-r--r--"); | |
| // nlinks | |
| assert_equal(outputs[0][1], "1"); | |
| // user | |
| assert_equal(outputs[0][2], "root"); | |
| // group | |
| assert_equal(outputs[0][3], "root"); | |
| // size | |
| assert_equal(outputs[0][4], "137"); | |
| // atime | |
| assert_equal(outputs[0][5], "2000-02-02"); | |
| assert_equal(outputs[0][6], "22:22:00"); | |
| assert_equal(outputs[0][7], "+0000"); | |
| // pathname | |
| assert_equal(outputs[0][8], "/mnt/file"); | |
| // mode | |
| assert_equal(outputs[1][0], "-r-xr-xr-x"); | |
| // nlinks | |
| assert_equal(outputs[1][1], "2"); | |
| // user | |
| assert_equal(outputs[1][2], "root"); | |
| // group | |
| assert_equal(outputs[1][3], "root"); | |
| // size | |
| assert_equal(outputs[1][4], "137"); | |
| // atime | |
| assert_equal(outputs[1][5], "2000-02-02"); | |
| assert_equal(outputs[1][6], "22:22:00"); | |
| assert_equal(outputs[1][7], "+0000"); | |
| // pathname | |
| assert_equal(outputs[1][8], "/mnt/file"); | |
| // mode | |
| assert_equal(outputs[2][0], "-r--r--r--"); | |
| // nlinks | |
| assert_equal(outputs[2][1], "1"); | |
| // user | |
| assert_equal(outputs[2][2], "root"); | |
| // group | |
| assert_equal(outputs[2][3], "root"); | |
| // size | |
| assert_equal(outputs[2][4], "100"); | |
| // atime | |
| assert_equal(outputs[2][5], "2010-11-22"); | |
| assert_equal(outputs[2][6], "03:44:00"); | |
| assert_equal(outputs[2][7], "+0000"); | |
| // pathname | |
| assert_equal(outputs[2][8], "/mnt/file"); | |
| done(); | |
| }, | |
| }, | |
| { | |
| name: "Xattrwalk and Listxattr", | |
| timeout: 60, | |
| allow_failure: true, | |
| start: () => | |
| { | |
| emulator.serial0_send("echo originalvalue > /mnt/file\n"); | |
| emulator.serial0_send("echo start-capture;"); | |
| emulator.serial0_send('setfattr --name=user.attr1 --value="val1" /mnt/file;'); | |
| emulator.serial0_send('setfattr --name=user.attr2 --value="val2" /mnt/file;'); | |
| emulator.serial0_send('setfattr --name=user.mime_type --value="text/plain" /mnt/file;'); | |
| emulator.serial0_send('setfattr --name=user.nested.attr --value="foobar" /mnt/file;'); | |
| // Unrecognized attribute name under other namespaces should be allowed. | |
| emulator.serial0_send('setfattr --name=security.not_an_attr --value="val3" /mnt/file;'); | |
| // Remove the caps attribute we've automatically put in. Tested later. | |
| emulator.serial0_send("setfattr --remove=security.capability /mnt/file;"); | |
| emulator.serial0_send("getfattr --encoding=text --absolute-names --dump /mnt/file | sort;"); | |
| emulator.serial0_send("getfattr --encoding=text --absolute-names --name=user.nested.attr /mnt/file;"); | |
| emulator.serial0_send("getfattr --encoding=text --absolute-names --name=security.not_an_attr /mnt/file;"); | |
| emulator.serial0_send("getfattr --encoding=text --absolute-names --name=user.attr2 /mnt/file;"); | |
| emulator.serial0_send("echo done-listxattr\n"); | |
| }, | |
| capture_trigger: "start-capture", | |
| end_trigger: "done-listxattr", | |
| end: (capture, done) => | |
| { | |
| assert_equal(capture, | |
| "# file: /mnt/file\n" + | |
| 'security.not_an_attr="val3"\n' + | |
| 'user.attr1="val1"\n' + | |
| 'user.attr2="val2"\n' + | |
| 'user.mime_type="text/plain"\n' + | |
| 'user.nested.attr="foobar"\n' + | |
| "\n" + | |
| "# file: /mnt/file\n" + | |
| 'user.nested.attr="foobar"\n' + | |
| "\n" + | |
| "# file: /mnt/file\n" + | |
| 'security.not_an_attr="val3"\n' + | |
| "\n" + | |
| "# file: /mnt/file\n" + | |
| 'user.attr2="val2"\n'); | |
| done(); | |
| }, | |
| }, | |
| { | |
| name: "Xattrcreate", | |
| timeout: 60, | |
| allow_failure: true, | |
| start: () => | |
| { | |
| emulator.serial0_send("echo originalvalue > /mnt/file\n"); | |
| // Remove the caps attribute we've automatically put in. Tested later. | |
| emulator.serial0_send("setfattr --remove=security.capability /mnt/file\n"); | |
| emulator.serial0_send("echo start-capture;"); | |
| // Creation of new xattr using xattrcreate. | |
| emulator.serial0_send("setfattr --name=user.foo --value=bar /mnt/file;"); | |
| // File contents should not be overriden. | |
| emulator.serial0_send("cat /mnt/file;"); | |
| emulator.serial0_send("getfattr --encoding=hex --absolute-names --name=user.foo /mnt/file;"); | |
| // Overwriting of xattr using xattrcreate. | |
| emulator.serial0_send("setfattr --name=user.foo --value=baz /mnt/file;"); | |
| // File contents should not be overriden. | |
| emulator.serial0_send("cat /mnt/file;"); | |
| emulator.serial0_send("getfattr --encoding=hex --absolute-names --name=user.foo /mnt/file;"); | |
| emulator.serial0_send("echo done-xattrcreate\n"); | |
| }, | |
| capture_trigger: "start-capture", | |
| end_trigger: "done-xattrcreate", | |
| end: (capture, done) => | |
| { | |
| assert_equal(capture, | |
| "originalvalue\n" + | |
| "# file: /mnt/file\n" + | |
| 'user.foo="bar"\n' + | |
| "\n" + | |
| "originalvalue\n" + | |
| "# file: /mnt/file\n" + | |
| 'user.foo="baz"\n' + | |
| "\n"); | |
| done(); | |
| }, | |
| }, | |
| { | |
| name: "Report All Security Capabilities", | |
| timeout: 60, | |
| allow_failure: true, | |
| start: () => | |
| { | |
| emulator.serial0_send("touch /mnt/file\n"); | |
| emulator.serial0_send("echo start-capture;"); | |
| emulator.serial0_send("getfattr --encoding=hex --absolute-names --name=security.capability /mnt/file;"); | |
| emulator.serial0_send("echo done-xattr\n"); | |
| }, | |
| capture_trigger: "start-capture", | |
| end_trigger: "done-xattr", | |
| end: (capture, done) => | |
| { | |
| assert_equal(capture, | |
| "# file: /mnt/file\n" + | |
| "security.capability=0x" + | |
| // magic and revision number | |
| "00000002" + | |
| // lower permitted | |
| "ffffffff" + | |
| // lower inheritable | |
| "ffffffff" + | |
| // higher permitted | |
| "3f000000" + | |
| // higher inheritable | |
| "3f000000" + | |
| "\n\n"); | |
| done(); | |
| }, | |
| }, | |
| { | |
| name: "File Locks", | |
| timeout: 60, | |
| start: () => | |
| { | |
| emulator.serial0_send("touch /mnt/file\n"); | |
| emulator.serial0_send("touch /mnt/logs\n"); | |
| emulator.serial0_send("mkfifo /mnt/fifo1\n"); | |
| emulator.serial0_send("mkfifo /mnt/fifo2\n"); | |
| emulator.serial0_send("flock -s /mnt/file -c 'cat /mnt/fifo1 >> /mnt/file' &\n"); | |
| emulator.serial0_send("flock -s /mnt/file -c 'echo lock-shared-2 >> /mnt/file' \n"); | |
| emulator.serial0_send("flock -xn /mnt/file -c 'echo lock unblocked! >> /mnt/logs' \n"); | |
| emulator.serial0_send("echo lock-shared-1 > /mnt/fifo1\n"); | |
| emulator.serial0_send("flock -x /mnt/file -c 'cat /mnt/fifo1 >> /mnt/file' &\n"); | |
| emulator.serial0_send("flock -x /mnt/file -c 'echo lock-exclusive-2 >> /mnt/file' &\n"); | |
| emulator.serial0_send("flock -sn /mnt/file -c 'echo lock unblocked! >> /mnt/logs' \n"); | |
| emulator.serial0_send("echo lock-exclusive-1 > /mnt/fifo1\n"); | |
| emulator.serial0_send("flock -sn /mnt/file -c 'cat /mnt/fifo1 >> /mnt/file' &\n"); | |
| emulator.serial0_send("flock -s /mnt/file -c 'cat /mnt/fifo2 >> /mnt/file' &\n"); | |
| emulator.serial0_send("flock -x /mnt/file -c 'echo lock-exclusive-3 >> /mnt/file' &\n"); | |
| emulator.serial0_send("echo lock-shared-4 > /mnt/fifo2\n"); | |
| emulator.serial0_send("sleep 0.1\n"); | |
| emulator.serial0_send("echo lock-shared-3 > /mnt/fifo1\n"); | |
| emulator.serial0_send("echo start-capture;\\\n"); | |
| emulator.serial0_send("cat /mnt/file;\\\n"); | |
| emulator.serial0_send("cat /mnt/logs;\\\n"); | |
| emulator.serial0_send("echo done-locks\n"); | |
| }, | |
| capture_trigger: "start-capture", | |
| end_trigger: "done-locks", | |
| end: (capture, done) => | |
| { | |
| assert_equal(capture, | |
| "lock-shared-2\n" + | |
| "lock-shared-1\n" + | |
| "lock-exclusive-1\n" + | |
| "lock-exclusive-2\n" + | |
| "lock-shared-4\n" + | |
| "lock-shared-3\n" + | |
| "lock-exclusive-3\n"); | |
| const idx = emulator.fs9p.Search(0, "file"); | |
| const P9_LOCK_TYPE_RDLCK = 0; | |
| const P9_LOCK_TYPE_WRLCK = 1; | |
| const P9_LOCK_TYPE_UNLCK = 2; | |
| const P9_LOCK_SUCCESS = 0; | |
| const P9_LOCK_BLOCKED = 1; | |
| const CLIENT_ID = "under test"; | |
| function test_getlock(num, type, pos, proc_id, locked) | |
| { | |
| const lock = emulator.fs9p.DescribeLock(type, pos, 1, proc_id, CLIENT_ID); | |
| const ret = emulator.fs9p.GetLock(idx, lock, 0); | |
| assert_equal(ret !== null, locked, | |
| `getlock ${num}: type=${type}, pos=${pos}, proc_id=${proc_id}. Wrong state:`); | |
| } | |
| function test_lock(num, type, start, length, proc_id, status, lock_state) | |
| { | |
| console.log(` Lock ${num}: type=${type}, start=${start}, length=${length} ` + | |
| ` proc_id=${proc_id}, expected state=${lock_state}`); | |
| const lock = emulator.fs9p.DescribeLock(type, start, length, proc_id, CLIENT_ID); | |
| assert_equal(emulator.fs9p.Lock(idx, lock, 0), status, "Wrong status:"); | |
| for(const [i, state] of [...lock_state].entries()) | |
| { | |
| switch(state) | |
| { | |
| case "1": | |
| test_getlock(num, P9_LOCK_TYPE_WRLCK, i, 1, false); | |
| test_getlock(num, P9_LOCK_TYPE_RDLCK, i, 2, false); | |
| test_getlock(num, P9_LOCK_TYPE_WRLCK, i, 2, true); | |
| break; | |
| case "2": | |
| test_getlock(num, P9_LOCK_TYPE_WRLCK, i, 2, false); | |
| test_getlock(num, P9_LOCK_TYPE_RDLCK, i, 1, false); | |
| test_getlock(num, P9_LOCK_TYPE_WRLCK, i, 1, true); | |
| break; | |
| case "3": | |
| test_getlock(num, P9_LOCK_TYPE_RDLCK, i, 1, false); | |
| test_getlock(num, P9_LOCK_TYPE_WRLCK, i, 1, true); | |
| test_getlock(num, P9_LOCK_TYPE_RDLCK, i, 2, false); | |
| test_getlock(num, P9_LOCK_TYPE_WRLCK, i, 2, true); | |
| break; | |
| case "e": | |
| test_getlock(num, P9_LOCK_TYPE_RDLCK, i, 1, false); | |
| test_getlock(num, P9_LOCK_TYPE_RDLCK, i, 2, true); | |
| break; | |
| case "E": | |
| test_getlock(num, P9_LOCK_TYPE_RDLCK, i, 1, true); | |
| test_getlock(num, P9_LOCK_TYPE_RDLCK, i, 2, false); | |
| break; | |
| case "-": | |
| test_getlock(num, P9_LOCK_TYPE_WRLCK, i, 1, false); | |
| test_getlock(num, P9_LOCK_TYPE_WRLCK, i, 2, false); | |
| break; | |
| } | |
| } | |
| } | |
| // Key: | |
| // 1/2/3 = shared lock by process 1/2/both | |
| // e/E = exclusive lock by process 1/2 | |
| // - = no locks | |
| const I = Infinity; | |
| test_lock(0, P9_LOCK_TYPE_RDLCK, 0, 1, 1, P9_LOCK_SUCCESS, "1-------"); // First lock. | |
| test_lock(1, P9_LOCK_TYPE_RDLCK, 0, 2, 1, P9_LOCK_SUCCESS, "11------"); // Replace. | |
| test_lock(2, P9_LOCK_TYPE_RDLCK, 1, 1, 2, P9_LOCK_SUCCESS, "13------"); | |
| test_lock(3, P9_LOCK_TYPE_RDLCK, 2, 2, 1, P9_LOCK_SUCCESS, "1311----"); // Skip. Merge before. | |
| test_lock(4, P9_LOCK_TYPE_WRLCK, 0, 1, 1, P9_LOCK_SUCCESS, "e311----"); // Shrink left. | |
| test_lock(5, P9_LOCK_TYPE_WRLCK, 1, 1, 1, P9_LOCK_BLOCKED, "e311----"); | |
| test_lock(6, P9_LOCK_TYPE_UNLCK, 0, 4, 1, P9_LOCK_SUCCESS, "-2------"); // Delete. | |
| test_lock(7, P9_LOCK_TYPE_WRLCK, 1, 2, 1, P9_LOCK_BLOCKED, "-2------"); | |
| test_lock(8, P9_LOCK_TYPE_UNLCK, 1, 3, 2, P9_LOCK_SUCCESS, "--------"); // Delete. | |
| test_lock(9, P9_LOCK_TYPE_WRLCK, 1, 1, 1, P9_LOCK_SUCCESS, "-e------"); | |
| test_lock(10, P9_LOCK_TYPE_RDLCK, 3, 3, 1, P9_LOCK_SUCCESS, "-e-111--"); // Skip. | |
| test_lock(11, P9_LOCK_TYPE_RDLCK, 2, 1, 2, P9_LOCK_SUCCESS, "-e2111--"); // Skip past. | |
| test_lock(12, P9_LOCK_TYPE_UNLCK, 2, 1, 2, P9_LOCK_SUCCESS, "-e-111--"); // Delete. | |
| test_lock(13, P9_LOCK_TYPE_WRLCK, 0, 1, 1, P9_LOCK_SUCCESS, "ee-111--"); | |
| test_lock(14, P9_LOCK_TYPE_WRLCK, 1, 4, 1, P9_LOCK_SUCCESS, "eeeee1--"); // Merge before. Shrink both ways. | |
| test_lock(15, P9_LOCK_TYPE_WRLCK, 1, 2, 2, P9_LOCK_BLOCKED, "eeeee1--"); | |
| test_lock(16, P9_LOCK_TYPE_RDLCK, 4, 5, 2, P9_LOCK_BLOCKED, "eeeee1--"); | |
| test_lock(17, P9_LOCK_TYPE_RDLCK, 5, I, 2, P9_LOCK_SUCCESS, "eeeee322"); | |
| test_lock(18, P9_LOCK_TYPE_UNLCK, 0, I, 1, P9_LOCK_SUCCESS, "-----222"); // Replace. | |
| test_lock(19, P9_LOCK_TYPE_RDLCK, 4, I, 2, P9_LOCK_SUCCESS, "----2222"); // Replace. | |
| test_lock(20, P9_LOCK_TYPE_WRLCK, 2, I, 2, P9_LOCK_SUCCESS, "--EEEEEE"); // Replace. | |
| test_lock(21, P9_LOCK_TYPE_WRLCK, 0, 1, 2, P9_LOCK_SUCCESS, "E-EEEEEE"); | |
| test_lock(22, P9_LOCK_TYPE_WRLCK, 1, 3, 2, P9_LOCK_SUCCESS, "EEEEEEEE"); // Merge both. Shrink left. | |
| test_lock(23, P9_LOCK_TYPE_RDLCK, 3, 4, 2, P9_LOCK_SUCCESS, "EEE2222E"); // Split. | |
| test_lock(24, P9_LOCK_TYPE_RDLCK, 1, 2, 2, P9_LOCK_SUCCESS, "E222222E"); // Merge after. Shrink right. | |
| test_lock(25, P9_LOCK_TYPE_RDLCK, 2, 3, 2, P9_LOCK_SUCCESS, "E222222E"); // No-op. | |
| done(); | |
| }, | |
| }, | |
| { | |
| name: "Stress Files", | |
| timeout: 360, | |
| start: () => | |
| { | |
| emulator.serial0_send("mkdir /mnt/stress-files\n"); | |
| emulator.serial0_send('cat << "EOF" | sh\n'); | |
| // Create files. | |
| // Ensure directory inode data exceeds maximum message size for 9p. | |
| emulator.serial0_send("for f in $(seq -w 0 999)\n"); | |
| emulator.serial0_send("do\n"); | |
| emulator.serial0_send(' echo "$f" > "/mnt/stress-files/file-$f"\n'); | |
| emulator.serial0_send("done\n"); | |
| emulator.serial0_send("echo start-capture\n"); | |
| // Read some of them. | |
| emulator.serial0_send("for f in $(seq -w 0 31 999)\n"); | |
| emulator.serial0_send("do\n"); | |
| emulator.serial0_send(' cat "/mnt/stress-files/file-$f"\n'); | |
| emulator.serial0_send("done\n"); | |
| // Walk. | |
| emulator.serial0_send("find /mnt/stress-files | sort\n"); | |
| // Delete and verify. | |
| // Using glob checks readdir. | |
| emulator.serial0_send("rm /mnt/stress-files/file-*\n"); | |
| emulator.serial0_send('test -z "$(ls /mnt/stress-files)" && echo delete-success\n'); | |
| emulator.serial0_send("echo done-stress-files\n"); | |
| emulator.serial0_send("EOF\n"); | |
| }, | |
| capture_trigger: "start-capture", | |
| end_trigger: "done-stress-files", | |
| end: (capture, done) => | |
| { | |
| let expected = ""; | |
| for(let i = 0; i < 1000; i += 31) | |
| { | |
| expected += i.toString().padStart(3, "0") + "\n"; | |
| } | |
| expected += "/mnt/stress-files\n"; | |
| for(let i = 0; i < 1000; i ++) | |
| { | |
| expected += "/mnt/stress-files/file-" + i.toString().padStart(3, "0") + "\n"; | |
| } | |
| expected += "delete-success\n"; | |
| assert_equal(capture, expected); | |
| done(); | |
| }, | |
| }, | |
| { | |
| name: "Stress Directories", | |
| timeout: 360, | |
| start: () => | |
| { | |
| emulator.serial0_send('cat << "EOF" | sh\n'); | |
| emulator.serial0_send("p=/mnt/stress-dirs\n"); | |
| emulator.serial0_send('mkdir "$p"\n'); | |
| // Create deep folder structure | |
| emulator.serial0_send("for i in $(seq 0 99)\n"); | |
| emulator.serial0_send("do\n"); | |
| emulator.serial0_send(' p="$p/$i"\n'); | |
| emulator.serial0_send(' mkdir "$p"\n'); | |
| emulator.serial0_send(' echo "$i" > "$p/file"\n'); | |
| emulator.serial0_send("done\n"); | |
| // Try accessing deep files | |
| emulator.serial0_send("p=/mnt/stress-dirs\n"); | |
| emulator.serial0_send("echo start-capture\n"); | |
| // Skip first 80 - otherwise too slow | |
| emulator.serial0_send("for i in $(seq 0 79)\n"); | |
| emulator.serial0_send("do\n"); | |
| emulator.serial0_send(' p="$p/$i"\n'); | |
| emulator.serial0_send("done\n"); | |
| emulator.serial0_send("for i in $(seq 80 99)\n"); | |
| emulator.serial0_send("do\n"); | |
| emulator.serial0_send(' p="$p/$i"\n'); | |
| emulator.serial0_send(' cat "$p/file"\n'); | |
| emulator.serial0_send("done\n"); | |
| // Delete and verify | |
| emulator.serial0_send("rm -rf /mnt/stress-dirs/0\n"); | |
| emulator.serial0_send('test -z "$(ls /mnt/stress-dirs)" && echo delete-success\n'); | |
| emulator.serial0_send("echo done-stress-dirs\n"); | |
| emulator.serial0_send("EOF\n"); | |
| }, | |
| capture_trigger: "start-capture", | |
| end_trigger: "done-stress-dirs", | |
| end: (capture, done) => | |
| { | |
| const outputs = capture.split("\n"); | |
| for(let i = 0; i < 20; i++) | |
| { | |
| assert_equal(outputs[i], `${i + 80}`); | |
| } | |
| assert_equal(outputs[20], "delete-success"); | |
| done(); | |
| }, | |
| }, | |
| { | |
| name: "Read Past Available", | |
| timeout: 60, | |
| start: () => | |
| { | |
| emulator.serial0_send("echo a > /mnt/small-file\n"); | |
| emulator.serial0_send("echo start-capture;"); | |
| // Reading from offsets > size of file should not read anything. | |
| emulator.serial0_send("dd if=/mnt/small-file bs=1 count=1 skip=10;"); | |
| emulator.serial0_send("dd if=/mnt/small-file bs=1 count=1 skip=100;"); | |
| emulator.serial0_send("dd if=/mnt/small-file bs=1 count=1 skip=1000;"); | |
| emulator.serial0_send("echo done-read-exceed\n"); | |
| }, | |
| capture_trigger: "start-capture", | |
| end_trigger: "done-read-exceed", | |
| end: (capture, done) => | |
| { | |
| const outputs = capture.split("\n"); | |
| assert_equal(outputs[0], "0+0 records in"); | |
| assert_equal(outputs[1], "0+0 records out"); | |
| assert_equal(outputs[2], "0+0 records in"); | |
| assert_equal(outputs[3], "0+0 records out"); | |
| assert_equal(outputs[4], "0+0 records in"); | |
| assert_equal(outputs[5], "0+0 records out"); | |
| done(); | |
| }, | |
| }, | |
| ]; | |
| let test_num = 0; | |
| let test_timeout = 0; | |
| let test_has_failed = false; | |
| const failed_tests = []; | |
| function test_fail() | |
| { | |
| if(!test_has_failed) | |
| { | |
| test_has_failed = true; | |
| failed_tests.push(test_num); | |
| } | |
| } | |
| const emulator = new V86({ | |
| bios: { url: __dirname + "/../../bios/seabios.bin" }, | |
| vga_bios: { url: __dirname + "/../../bios/vgabios.bin" }, | |
| cdrom: { url: __dirname + "/../../images/linux4.iso" }, | |
| autostart: true, | |
| memory_size: 64 * 1024 * 1024, | |
| filesystem: { | |
| baseurl: __dirname + "/testfs/", | |
| }, | |
| disable_jit: +process.env.DISABLE_JIT, | |
| log_level: SHOW_LOGS ? 0x400000 : 0, | |
| }); | |
| let ran_command = false; | |
| let line = ""; | |
| let capturing = false; | |
| let capture = ""; | |
| let next_trigger; | |
| let next_trigger_handler; | |
| async function prepare_test() | |
| { | |
| console.log("\nPreparing test #%d: %s", test_num, tests[test_num].name); | |
| if(tests[test_num].timeout) | |
| { | |
| test_timeout = setTimeout(() => | |
| { | |
| console.error("[-] Test #%d (%s) took longer than %s sec. Timing out and terminating.", test_num, tests[test_num].name, tests[test_num].timeout); | |
| process.exit(1); | |
| }, tests[test_num].timeout * 1000); | |
| } | |
| console.log(" Nuking /mnt"); | |
| emulator.fs9p.RecursiveDelete(""); | |
| if(tests[test_num].use_fsjson) | |
| { | |
| console.log(" Reloading files from json"); | |
| emulator.fs9p.load_from_json(testfsjson); | |
| } | |
| console.log(" Loading additional files"); | |
| if(tests[test_num].files) | |
| { | |
| let remaining = tests[test_num].files.length; | |
| for(const f of tests[test_num].files) | |
| { | |
| await emulator.create_file(f.file, f.data); | |
| } | |
| } | |
| console.log("Starting test #%d: %s", test_num, tests[test_num].name); | |
| capture = ""; | |
| tests[test_num].start(); | |
| if(tests[test_num].capture_trigger) | |
| { | |
| next_trigger = tests[test_num].capture_trigger; | |
| next_trigger_handler = start_capture; | |
| } | |
| else | |
| { | |
| next_trigger = tests[test_num].end_trigger; | |
| next_trigger_handler = end_test; | |
| } | |
| } | |
| function start_capture() | |
| { | |
| console.log("Capturing..."); | |
| capture = ""; | |
| capturing = true; | |
| next_trigger = tests[test_num].end_trigger; | |
| next_trigger_handler = end_test; | |
| } | |
| function end_test() | |
| { | |
| capturing = false; | |
| if(tests[test_num].timeout) | |
| { | |
| clearTimeout(test_timeout); | |
| } | |
| tests[test_num].end(capture, report_test); | |
| } | |
| function report_test() | |
| { | |
| if(!test_has_failed) | |
| { | |
| console.log("[+] Test #%d passed: %s", test_num, tests[test_num].name); | |
| } | |
| else | |
| { | |
| if(tests[test_num].allow_failure) | |
| { | |
| console.warn("Test #%d failed: %s (failure allowed)", test_num, tests[test_num].name); | |
| } | |
| else | |
| { | |
| console.error("[-] Test #%d failed: %s", test_num, tests[test_num].name); | |
| if(STOP_ON_FIRST_FAILURE) | |
| { | |
| finish_tests(); | |
| } | |
| } | |
| test_has_failed = false; | |
| } | |
| test_num++; | |
| if(test_num < tests.length) | |
| { | |
| prepare_test(); | |
| } | |
| else | |
| { | |
| finish_tests(); | |
| } | |
| } | |
| function finish_tests() | |
| { | |
| emulator.stop(); | |
| console.log("\nTests finished."); | |
| if(failed_tests.length === 0) | |
| { | |
| console.log("All tests passed"); | |
| } | |
| else | |
| { | |
| let unallowed_failure = false; | |
| console.error("[-] Failed %d out of %d tests:", failed_tests.length, tests.length); | |
| for(const num of failed_tests) | |
| { | |
| if(tests[num].allow_failure) | |
| { | |
| console.warn("#%d %s (failure allowed)", num, tests[num].name); | |
| } | |
| else | |
| { | |
| unallowed_failure = true; | |
| console.error("[-] #%d %s", num, tests[num].name); | |
| } | |
| } | |
| if(unallowed_failure) | |
| { | |
| process.exit(1); | |
| } | |
| } | |
| } | |
| emulator.bus.register("emulator-started", function() | |
| { | |
| console.log("Booting now, please stand by"); | |
| }); | |
| emulator.add_listener("serial0-output-byte", function(byte) | |
| { | |
| var chr = String.fromCharCode(byte); | |
| if(chr < " " && chr !== "\n" && chr !== "\t" || chr > "~") | |
| { | |
| return; | |
| } | |
| let new_line = ""; | |
| let is_new_line = false; | |
| if(chr === "\n") | |
| { | |
| is_new_line = true; | |
| new_line = line; | |
| line = ""; | |
| } | |
| else | |
| { | |
| line += chr; | |
| } | |
| if(!ran_command && line.endsWith("~% ")) | |
| { | |
| ran_command = true; | |
| prepare_test(); | |
| } | |
| else if(new_line === next_trigger) | |
| { | |
| next_trigger_handler(); | |
| } | |
| else if(is_new_line && capturing) | |
| { | |
| capture += new_line + "\n"; | |
| console.log(" Captured: %s", new_line); | |
| } | |
| else if(is_new_line) | |
| { | |
| console.log(" Serial: %s", new_line); | |
| } | |
| }); | |