run.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459
  1. #!/usr/bin/env node
  2. "use strict";
  3. var TIMEOUT_EXTRA_FACTOR = +process.env.TIMEOUT_EXTRA_FACTOR || 1;
  4. var MAX_PARALLEL_TESTS = +process.env.MAX_PARALLEL_TESTS || 4;
  5. var TEST_NAME = process.env.TEST_NAME;
  6. try
  7. {
  8. var V86 = require("../../build/libv86.js").V86Starter;
  9. }
  10. catch(e)
  11. {
  12. console.error("Failed to import build/libv86.js. Run `make build/libv86.js first.");
  13. process.exit(1);
  14. }
  15. var cluster = require("cluster");
  16. var os = require("os");
  17. var fs = require("fs");
  18. var root_path = __dirname + "/../..";
  19. var SCREEN_WIDTH = 80;
  20. function get_line(screen, y)
  21. {
  22. return screen.subarray(y * SCREEN_WIDTH, (y + 1) * SCREEN_WIDTH);
  23. }
  24. function line_to_text(screen, y)
  25. {
  26. return bytearray_to_string(get_line(screen, y));
  27. }
  28. function string_to_bytearray(str)
  29. {
  30. return new Uint8Array(str.split("").map(chr => chr.charCodeAt(0)));
  31. }
  32. function bytearray_to_string(arr)
  33. {
  34. return String.fromCharCode.apply(String, arr);
  35. }
  36. function screen_to_text(s)
  37. {
  38. var result = [];
  39. result.push("+==================================== SCREEN ====================================+");
  40. for(var i = 0; i < 25; i++)
  41. {
  42. var line = line_to_text(s, i);
  43. result.push("|" + line + "|");
  44. }
  45. result.push("+================================================================================+");
  46. return result.join("\n");
  47. }
  48. function send_work_to_worker(worker, message)
  49. {
  50. if(current_test < tests.length)
  51. {
  52. worker.send(tests[current_test]);
  53. current_test++;
  54. }
  55. else
  56. {
  57. worker.disconnect();
  58. }
  59. }
  60. if(cluster.isMaster)
  61. {
  62. var tests = [
  63. {
  64. name: "FreeDOS boot",
  65. fda: root_path + "/images/freedos722.img",
  66. timeout: 10,
  67. expected_texts: [
  68. "Welcome to FreeDOS",
  69. ],
  70. },
  71. {
  72. name: "FreeDOS boot with Bochs BIOS",
  73. fda: root_path + "/images/freedos722.img",
  74. timeout: 10,
  75. alternative_bios: true,
  76. expected_texts: [
  77. "Welcome to FreeDOS",
  78. ],
  79. },
  80. {
  81. name: "Windows 1.01 boot",
  82. fda: root_path + "/images/windows101.img",
  83. timeout: 10,
  84. expect_graphical_mode: true,
  85. expect_mouse_registered: true,
  86. },
  87. {
  88. name: "Sol OS",
  89. fda: root_path + "/images/os8.dsk",
  90. timeout: 20,
  91. expect_graphical_mode: true,
  92. expect_mouse_registered: true,
  93. actions: [
  94. {
  95. on_text: " or press",
  96. run: "\n"
  97. },
  98. ],
  99. },
  100. {
  101. name: "Linux",
  102. cdrom: root_path + "/images/linux.iso",
  103. timeout: 90,
  104. expected_texts: [
  105. "/root%",
  106. "test passed",
  107. ],
  108. actions: [
  109. {
  110. on_text: "/root%",
  111. run: "cd tests; ./test-i386 > emu.test; diff emu.test reference.test > /dev/null && echo test pas''sed || echo failed\n",
  112. },
  113. ],
  114. },
  115. //{
  116. // name: "Windows 98",
  117. // hda: root_path + "/images/windows98.img",
  118. // timeout: 60,
  119. // expect_graphical_mode: true,
  120. // expect_graphical_size: [800, 600],
  121. // expect_mouse_registered: true,
  122. // skip_if_disk_image_missing: true,
  123. //},
  124. //{
  125. // name: "Oberon",
  126. // hda: root_path + "/images/oberon.dsk",
  127. // fda: root_path + "/images/oberon-boot.dsk",
  128. // timeout: 30,
  129. // expect_graphical_mode: true,
  130. // expect_mouse_registered: true,
  131. //},
  132. {
  133. name: "Linux 3",
  134. cdrom: root_path + "/images/linux3.iso",
  135. timeout: 200,
  136. expected_texts: [
  137. "test passed",
  138. ],
  139. actions: [
  140. {
  141. on_text: "~%",
  142. run: "echo test pas''sed\n"
  143. },
  144. ],
  145. },
  146. {
  147. name: "KolibriOS",
  148. fda: root_path + "/images/kolibri.img",
  149. timeout: 120,
  150. expect_graphical_mode: true,
  151. expect_mouse_registered: true,
  152. },
  153. {
  154. name: "Linux with Bochs BIOS",
  155. cdrom: root_path + "/images/linux.iso",
  156. timeout: 90,
  157. expected_texts: [
  158. "/root%",
  159. "test passed",
  160. ],
  161. alternative_bios: true,
  162. actions: [
  163. {
  164. on_text: "/root%",
  165. run: "cd tests; ./test-i386 > emu.test; diff emu.test reference.test > /dev/null && echo test pas''sed || echo failed\n",
  166. },
  167. ],
  168. },
  169. {
  170. name: "OpenBSD",
  171. fda: root_path + "/images/openbsd.img",
  172. timeout: 180,
  173. expected_texts: ["(I)nstall, (U)pgrade or (S)hell"],
  174. },
  175. ];
  176. if(TEST_NAME)
  177. {
  178. tests = tests.filter(test => test.name === TEST_NAME);
  179. }
  180. var nr_of_cpus = Math.min(Math.round(os.cpus().length / 2) || 1, tests.length, MAX_PARALLEL_TESTS);
  181. console.log("Using %d cpus", nr_of_cpus);
  182. var current_test = 0;
  183. for(var i = 0; i < nr_of_cpus; i++)
  184. {
  185. var worker = cluster.fork();
  186. worker.on("message", send_work_to_worker.bind(null, worker));
  187. worker.on("online", send_work_to_worker.bind(null, worker));
  188. worker.on("exit", function(code, signal)
  189. {
  190. if(code !== 0)
  191. {
  192. process.exit(code);
  193. }
  194. });
  195. worker.on("error", function(error)
  196. {
  197. console.error("Worker error: ", error.toString(), error);
  198. process.exit(1);
  199. });
  200. }
  201. }
  202. else
  203. {
  204. cluster.worker.on("message", function(test_case)
  205. {
  206. run_test(test_case, function()
  207. {
  208. process.send("I'm done");
  209. });
  210. });
  211. }
  212. function bytearray_starts_with(arr, search)
  213. {
  214. for(var i = 0; i < search.length; i++)
  215. {
  216. if(arr[i] !== search[i])
  217. {
  218. return false;
  219. }
  220. }
  221. return true;
  222. }
  223. function run_test(test, done)
  224. {
  225. console.log("Starting test: %s", test.name);
  226. let image = test.fda || test.hda || test.cdrom;
  227. console.assert(image, "Bootable drive expected");
  228. if(!fs.existsSync(image))
  229. {
  230. if(test.skip_if_disk_image_missing)
  231. {
  232. console.warn("Missing disk image: " + image + ", test skipped");
  233. console.warn();
  234. done();
  235. return;
  236. }
  237. else
  238. {
  239. console.warn("Missing disk image: " + image);
  240. process.exit(1);
  241. }
  242. }
  243. if(test.alternative_bios)
  244. {
  245. var bios = root_path + "/bios/bochs-bios.bin";
  246. var vga_bios = root_path + "/bios/bochs-vgabios.bin";
  247. }
  248. else
  249. {
  250. var bios = root_path + "/bios/seabios.bin";
  251. var vga_bios = root_path + "/bios/vgabios.bin";
  252. }
  253. var settings = {
  254. bios: { url: bios },
  255. vga_bios: { url: vga_bios },
  256. autostart: true,
  257. memory_size: 128 * 1024 * 1024,
  258. };
  259. if(test.cdrom)
  260. {
  261. settings.cdrom = { url: test.cdrom };
  262. }
  263. if(test.fda)
  264. {
  265. settings.fda = { url: test.fda };
  266. }
  267. if(test.hda)
  268. {
  269. settings.hda = { url: test.hda, async: true, };
  270. }
  271. if(test.expected_texts)
  272. {
  273. test.expected_texts = test.expected_texts.map(string_to_bytearray);
  274. }
  275. else
  276. {
  277. test.expected_texts = [];
  278. }
  279. var emulator = new V86(settings);
  280. var screen = new Uint8Array(SCREEN_WIDTH * 25);
  281. function check_text_test_done()
  282. {
  283. return test.expected_texts.length === 0;
  284. }
  285. var mouse_test_done = false;
  286. function check_mouse_test_done()
  287. {
  288. return !test.expect_mouse_registered || mouse_test_done;
  289. }
  290. var graphical_test_done = false;
  291. var size_test_done = false;
  292. function check_grapical_test_done()
  293. {
  294. return !test.expect_graphical_mode || (graphical_test_done && (!test.expect_graphical_size || size_test_done));
  295. }
  296. var test_start = Date.now();
  297. var timeout_seconds = test.timeout * TIMEOUT_EXTRA_FACTOR;
  298. var timeout = setTimeout(check_test_done, (timeout_seconds + 1) * 1000);
  299. var on_text = [];
  300. var stopped = false;
  301. function check_test_done()
  302. {
  303. if(stopped)
  304. {
  305. return;
  306. }
  307. if(check_text_test_done() && check_mouse_test_done() && check_grapical_test_done())
  308. {
  309. var end = Date.now();
  310. clearTimeout(timeout);
  311. stopped = true;
  312. emulator.stop();
  313. console.warn("Passed test: %s (took %ds)", test.name, (end - test_start) / 1000);
  314. console.warn();
  315. done();
  316. }
  317. else if(Date.now() >= test_start + timeout_seconds * 1000)
  318. {
  319. clearTimeout(timeout);
  320. stopped = true;
  321. emulator.stop();
  322. emulator.destroy();
  323. console.warn(screen_to_text(screen));
  324. console.warn("Test failed: %s\n", test.name);
  325. if(!check_text_test_done())
  326. {
  327. console.warn('Expected text "%s" after %d seconds.', bytearray_to_string(test.expected_texts[0]), timeout_seconds);
  328. }
  329. if(!check_grapical_test_done())
  330. {
  331. console.warn("Expected graphical mode after %d seconds.", timeout_seconds);
  332. }
  333. if(!check_mouse_test_done())
  334. {
  335. console.warn("Expected mouse activation after %d seconds.", timeout_seconds);
  336. }
  337. process.exit(1);
  338. }
  339. }
  340. emulator.add_listener("mouse-enable", function()
  341. {
  342. mouse_test_done = true;
  343. check_test_done();
  344. });
  345. emulator.add_listener("screen-set-mode", function(is_graphical)
  346. {
  347. graphical_test_done = is_graphical;
  348. check_test_done();
  349. });
  350. emulator.add_listener("screen-set-size-graphical", function(size)
  351. {
  352. if(test.expect_graphical_size)
  353. {
  354. size_test_done = size[0] === test.expect_graphical_size[0] &&
  355. size[1] === test.expect_graphical_size[1];
  356. check_test_done();
  357. }
  358. });
  359. emulator.add_listener("screen-put-char", function(chr)
  360. {
  361. var y = chr[0];
  362. var x = chr[1];
  363. var code = chr[2];
  364. screen[x + SCREEN_WIDTH * y] = code;
  365. var line = get_line(screen, y);
  366. if(!check_text_test_done())
  367. {
  368. let expected = test.expected_texts[0];
  369. if(x < expected.length && bytearray_starts_with(line, expected))
  370. {
  371. test.expected_texts.shift();
  372. check_test_done();
  373. }
  374. }
  375. if(on_text.length)
  376. {
  377. let expected = on_text[0].text;
  378. if(x < expected.length && bytearray_starts_with(line, expected))
  379. {
  380. var action = on_text.shift();
  381. if(action.after)
  382. {
  383. setTimeout(() => emulator.keyboard_send_text(action.run), action.after);
  384. }
  385. else
  386. {
  387. emulator.keyboard_send_text(action.run);
  388. }
  389. }
  390. }
  391. });
  392. test.actions && test.actions.forEach(function(action)
  393. {
  394. if(action.on_text)
  395. {
  396. on_text.push({ text: string_to_bytearray(action.on_text), run: action.run, after: action.after, });
  397. }
  398. });
  399. }