run.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449
  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. };
  258. if(test.cdrom)
  259. {
  260. settings.cdrom = { url: test.cdrom };
  261. }
  262. if(test.fda)
  263. {
  264. settings.fda = { url: test.fda };
  265. }
  266. if(test.hda)
  267. {
  268. settings.hda = { url: test.hda };
  269. }
  270. if(test.expected_texts)
  271. {
  272. test.expected_texts = test.expected_texts.map(string_to_bytearray);
  273. }
  274. else
  275. {
  276. test.expected_texts = [];
  277. }
  278. var emulator = new V86(settings);
  279. var screen = new Uint8Array(SCREEN_WIDTH * 25);
  280. function check_text_test_done()
  281. {
  282. return test.expected_texts.length === 0;
  283. }
  284. var mouse_test_done = false;
  285. function check_mouse_test_done()
  286. {
  287. return !test.expect_mouse_registered || mouse_test_done;
  288. }
  289. var graphical_test_done = false;
  290. var size_test_done = false;
  291. function check_grapical_test_done()
  292. {
  293. return !test.expect_graphical_mode || (graphical_test_done && (!test.expect_graphical_size || size_test_done));
  294. }
  295. var test_start = Date.now();
  296. var timeout_seconds = test.timeout * TIMEOUT_EXTRA_FACTOR;
  297. var timeout = setTimeout(check_test_done, (timeout_seconds + 1) * 1000);
  298. var on_text = [];
  299. var stopped = false;
  300. function check_test_done()
  301. {
  302. if(stopped)
  303. {
  304. return;
  305. }
  306. if(check_text_test_done() && check_mouse_test_done() && check_grapical_test_done())
  307. {
  308. var end = Date.now();
  309. clearTimeout(timeout);
  310. stopped = true;
  311. emulator.stop();
  312. console.warn("Passed test: %s (took %ds)", test.name, (end - test_start) / 1000);
  313. console.warn();
  314. done();
  315. }
  316. else if(Date.now() >= test_start + timeout_seconds * 1000)
  317. {
  318. clearTimeout(timeout);
  319. stopped = true;
  320. emulator.stop();
  321. emulator.destroy();
  322. console.warn(screen_to_text(screen));
  323. console.warn("Test failed: %s\n", test.name);
  324. if(!check_text_test_done())
  325. {
  326. console.warn('Expected text "%s" after %d seconds.', bytearray_to_string(test.expected_texts[0]), timeout_seconds);
  327. }
  328. if(!check_grapical_test_done())
  329. {
  330. console.warn("Expected graphical mode after %d seconds.", timeout_seconds);
  331. }
  332. if(!check_mouse_test_done())
  333. {
  334. console.warn("Expected mouse activation after %d seconds.", timeout_seconds);
  335. }
  336. process.exit(1);
  337. }
  338. }
  339. emulator.add_listener("mouse-enable", function()
  340. {
  341. mouse_test_done = true;
  342. check_test_done();
  343. });
  344. emulator.add_listener("screen-set-mode", function(is_graphical)
  345. {
  346. graphical_test_done = is_graphical;
  347. check_test_done();
  348. });
  349. emulator.add_listener("screen-set-size-graphical", function(size)
  350. {
  351. if(test.expect_graphical_size)
  352. {
  353. size_test_done = size[0] === test.expect_graphical_size[0] &&
  354. size[1] === test.expect_graphical_size[1];
  355. check_test_done();
  356. }
  357. });
  358. emulator.add_listener("screen-put-char", function(chr)
  359. {
  360. var y = chr[0];
  361. var x = chr[1];
  362. var code = chr[2];
  363. screen[x + SCREEN_WIDTH * y] = code;
  364. var line = get_line(screen, y);
  365. if(!check_text_test_done())
  366. {
  367. let expected = test.expected_texts[0];
  368. if(x < expected.length && bytearray_starts_with(line, expected))
  369. {
  370. test.expected_texts.shift();
  371. check_test_done();
  372. }
  373. }
  374. if(on_text.length)
  375. {
  376. let expected = on_text[0].text;
  377. if(x < expected.length && bytearray_starts_with(line, expected))
  378. {
  379. var action = on_text.shift();
  380. emulator.keyboard_send_text(action.run);
  381. }
  382. }
  383. });
  384. test.actions && test.actions.forEach(function(action)
  385. {
  386. if(action.on_text)
  387. {
  388. on_text.push({ text: string_to_bytearray(action.on_text), run: action.run, });
  389. }
  390. });
  391. }