run.js 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903
  1. #!/usr/bin/env node
  2. "use strict";
  3. process.on("unhandledRejection", exn => { throw exn; });
  4. var TIMEOUT_EXTRA_FACTOR = +process.env.TIMEOUT_EXTRA_FACTOR || 1;
  5. var MAX_PARALLEL_TESTS = +process.env.MAX_PARALLEL_TESTS || 4;
  6. var TEST_NAME = process.env.TEST_NAME;
  7. const TEST_RELEASE_BUILD = +process.env.TEST_RELEASE_BUILD;
  8. const VERBOSE = false;
  9. const RUN_SLOW_TESTS = false;
  10. try
  11. {
  12. var V86 = require(`../../build/${TEST_RELEASE_BUILD ? "libv86" : "libv86-debug"}.js`).V86;
  13. }
  14. catch(e)
  15. {
  16. console.error("Failed to import build/libv86-debug.js. Run `make build/libv86-debug.js first.");
  17. process.exit(1);
  18. }
  19. const assert = require("assert").strict;
  20. var cluster = require("cluster");
  21. var os = require("os");
  22. var fs = require("fs");
  23. var root_path = __dirname + "/../..";
  24. var SCREEN_WIDTH = 80;
  25. function get_line(screen, y)
  26. {
  27. return screen.subarray(y * SCREEN_WIDTH, (y + 1) * SCREEN_WIDTH);
  28. }
  29. function line_to_text(screen, y)
  30. {
  31. return bytearray_to_string(get_line(screen, y));
  32. }
  33. function string_to_bytearray(str)
  34. {
  35. return new Uint8Array(str.split("").map(chr => chr.charCodeAt(0)));
  36. }
  37. function bytearray_to_string(arr)
  38. {
  39. return String.fromCharCode.apply(String, arr);
  40. }
  41. function screen_to_text(s)
  42. {
  43. var result = [];
  44. result.push("+==================================== SCREEN ====================================+");
  45. for(var i = 0; i < 25; i++)
  46. {
  47. var line = line_to_text(s, i);
  48. result.push("|" + line + "|");
  49. }
  50. result.push("+================================================================================+");
  51. return result.join("\n");
  52. }
  53. function send_work_to_worker(worker, message)
  54. {
  55. if(current_test < tests.length)
  56. {
  57. worker.send(tests[current_test]);
  58. current_test++;
  59. }
  60. else
  61. {
  62. worker.disconnect();
  63. }
  64. }
  65. if(cluster.isMaster)
  66. {
  67. var tests = [
  68. {
  69. name: "FreeDOS boot",
  70. fda: root_path + "/images/freedos722.img",
  71. timeout: 20,
  72. expected_texts: [
  73. "Welcome to FreeDOS",
  74. ],
  75. },
  76. {
  77. name: "FreeDOS boot with Bochs BIOS",
  78. fda: root_path + "/images/freedos722.img",
  79. timeout: 20,
  80. alternative_bios: true,
  81. expected_texts: [
  82. "Welcome to FreeDOS",
  83. ],
  84. },
  85. {
  86. name: "Windows 1.01 boot",
  87. fda: root_path + "/images/windows101.img",
  88. timeout: 10,
  89. expect_graphical_mode: true,
  90. expect_mouse_registered: true,
  91. },
  92. {
  93. name: "Sol OS",
  94. fda: root_path + "/images/os8.dsk",
  95. timeout: 20,
  96. expect_graphical_mode: true,
  97. expect_mouse_registered: true,
  98. actions: [
  99. {
  100. on_text: " or press",
  101. run: "\n"
  102. },
  103. ],
  104. },
  105. {
  106. name: "Linux",
  107. cdrom: root_path + "/images/linux.iso",
  108. timeout: 90,
  109. expected_texts: [
  110. "/root%",
  111. "test passed",
  112. ],
  113. actions: [
  114. {
  115. on_text: "/root%",
  116. run: "cd tests; ./test-i386 > emu.test; diff emu.test reference.test && echo test pas''sed || echo failed\n",
  117. },
  118. ],
  119. },
  120. //{
  121. // name: "Windows 98",
  122. // hda: root_path + "/images/windows98.img",
  123. // timeout: 60,
  124. // expect_graphical_mode: true,
  125. // expect_graphical_size: [800, 600],
  126. // expect_mouse_registered: true,
  127. // skip_if_disk_image_missing: true,
  128. //},
  129. {
  130. name: "Windows 95",
  131. hda: root_path + "/images/w95.img",
  132. timeout: 60,
  133. expect_graphical_mode: true,
  134. expect_graphical_size: [1024, 768],
  135. expect_mouse_registered: true,
  136. skip_if_disk_image_missing: true,
  137. },
  138. {
  139. name: "Oberon",
  140. hda: root_path + "/images/oberon.img",
  141. timeout: 30,
  142. expect_graphical_mode: true,
  143. expect_mouse_registered: true,
  144. },
  145. {
  146. name: "Linux 3",
  147. cdrom: root_path + "/images/linux3.iso",
  148. timeout: 200,
  149. expected_texts: [
  150. "test passed",
  151. ],
  152. actions: [
  153. {
  154. on_text: "~%",
  155. run: "head -c 10000 /dev/urandom > rand; echo test pas''sed\n",
  156. after: 1000,
  157. },
  158. ],
  159. },
  160. {
  161. name: "KolibriOS",
  162. fda: root_path + "/images/kolibri.img",
  163. timeout: 120,
  164. expect_graphical_mode: true,
  165. expect_mouse_registered: true,
  166. },
  167. {
  168. name: "Linux with Bochs BIOS",
  169. cdrom: root_path + "/images/linux.iso",
  170. timeout: 90,
  171. expected_texts: [
  172. "/root%",
  173. "test passed",
  174. ],
  175. alternative_bios: true,
  176. actions: [
  177. {
  178. on_text: "/root%",
  179. run: "cd tests; ./test-i386 > emu.test; diff emu.test reference.test && echo test pas''sed || echo failed\n",
  180. },
  181. ],
  182. },
  183. {
  184. name: "MS-DOS",
  185. skip_if_disk_image_missing: true,
  186. hda: root_path + "/images/msdos.img",
  187. timeout: 90,
  188. expected_texts: [
  189. "C:\\>",
  190. ],
  191. },
  192. {
  193. name: "Linux 4",
  194. skip_if_disk_image_missing: true,
  195. cdrom: root_path + "/images/linux4.iso",
  196. timeout: 200,
  197. expected_texts: [
  198. "~%",
  199. ],
  200. expected_serial_text: [
  201. "Files send via emulator appear in",
  202. ],
  203. expect_mouse_registered: true,
  204. },
  205. {
  206. name: "Linux 4 bzImage",
  207. bzimage: root_path + "/images/bzImage",
  208. cmdline: "auto",
  209. timeout: 200,
  210. expected_texts: [
  211. "~%",
  212. ],
  213. expected_serial_text: [
  214. "Files send via emulator appear in",
  215. ],
  216. expect_mouse_registered: true,
  217. },
  218. {
  219. name: "Linux 4 with bzImage from filesystem",
  220. bzimage_initrd_from_filesystem: true,
  221. filesystem: {
  222. basefs: root_path + "/build/integration-test-fs/fs.json",
  223. baseurl: root_path + "/build/integration-test-fs/flat/",
  224. },
  225. cmdline: "auto",
  226. timeout: 200,
  227. expected_texts: [
  228. "~%",
  229. ],
  230. expected_serial_text: [
  231. "Files send via emulator appear in",
  232. ],
  233. expect_mouse_registered: true,
  234. },
  235. {
  236. name: "OpenBSD Floppy",
  237. fda: root_path + "/images/openbsd-floppy.img",
  238. timeout: 180,
  239. expected_texts: ["(I)nstall, (U)pgrade or (S)hell"],
  240. },
  241. {
  242. name: "OpenBSD",
  243. hda: root_path + "/images/openbsd.img",
  244. timeout: 300,
  245. actions: [
  246. {
  247. on_text: "boot>",
  248. run: "boot -c\n",
  249. },
  250. {
  251. on_text: "UKC>",
  252. run: "disable mpbios\nexit\n",
  253. },
  254. {
  255. on_text: "login:",
  256. run: "root\n",
  257. },
  258. {
  259. on_text: "Password:",
  260. run: "root\n",
  261. },
  262. ],
  263. expected_texts: ["nyu# "],
  264. },
  265. {
  266. name: "Windows 3.0",
  267. slow: 1,
  268. skip_if_disk_image_missing: true,
  269. timeout: 10 * 60,
  270. cdrom: root_path + "/images/experimental/os/Win30.iso",
  271. expected_texts: [
  272. "Press any key to continue",
  273. " **************************************************",
  274. ],
  275. expect_graphical_mode: true,
  276. expect_mouse_registered: true,
  277. actions: [
  278. {
  279. on_text: "Press any key to continue . . .",
  280. after: 1000,
  281. run: "x",
  282. },
  283. {
  284. on_text: " **************************************************",
  285. after: 1000,
  286. run: "x",
  287. },
  288. {
  289. on_text: "C> ",
  290. after: 1000,
  291. run: "win\n",
  292. },
  293. ],
  294. },
  295. {
  296. name: "FreeBSD",
  297. skip_if_disk_image_missing: true,
  298. timeout: 10 * 60,
  299. hda: root_path + "/images/internal/freebsd/freebsd.img",
  300. expected_texts: [
  301. "FreeBSD/i386 (nyu) (ttyv0)",
  302. "root@nyu:~ #",
  303. ],
  304. actions: [
  305. {
  306. on_text: " Autoboot in",
  307. run: "\n",
  308. },
  309. {
  310. // workaround for freebsd not accepting key inputs just before the boot prompt
  311. // (probably needs delay between keydown and keyup)
  312. on_text: "FreeBSD/i386 (nyu) (ttyv0)",
  313. run: "\x08", // backspace to avoid messing with login prompt
  314. },
  315. {
  316. on_text: "login:",
  317. after: 1000,
  318. run: "root\n",
  319. },
  320. {
  321. on_text: "Password:",
  322. after: 1000,
  323. run: "\n",
  324. },
  325. ],
  326. },
  327. {
  328. name: "FreeBSD cdrom",
  329. skip_if_disk_image_missing: true,
  330. slow: 1,
  331. timeout: 10 * 60,
  332. cdrom: root_path + "/images/experimental/os/FreeBSD-11.0-RELEASE-i386-bootonly.iso",
  333. expected_texts: ["Welcome to FreeBSD!"],
  334. actions: [
  335. {
  336. on_text: " Autoboot in ",
  337. run: "\n",
  338. }
  339. ],
  340. },
  341. {
  342. name: "FreeGEM",
  343. skip_if_disk_image_missing: true,
  344. timeout: 60,
  345. hda: root_path + "/images/experimental/os/freegem.bin",
  346. expect_graphical_mode: true,
  347. expect_mouse_registered: true,
  348. actions: [
  349. {
  350. on_text: " Select from Menu",
  351. run: "3",
  352. }
  353. ],
  354. },
  355. {
  356. name: "Haiku",
  357. skip_if_disk_image_missing: true,
  358. timeout: 15 * 60,
  359. memory_size: 512 * 1024 * 1024,
  360. hda: root_path + "/images/experimental/haiku-master-hrev53609-x86_gcc2h-anyboot.iso",
  361. expected_serial_text: [
  362. "cx23882: init_hardware()",
  363. "Running post install script /boot/system/boot/post-install/sshd_keymaker.sh",
  364. // After pressing enter in the boot dialog:
  365. "Running first login script /boot/system/boot/first-login/default_deskbar_items.sh",
  366. ],
  367. expect_graphical_mode: true,
  368. expect_graphical_size: [1024, 768],
  369. expect_mouse_registered: true,
  370. actions: [
  371. { after: 1 * 60 * 1000, run: "\n" },
  372. { after: 2 * 60 * 1000, run: "\n" },
  373. { after: 3 * 60 * 1000, run: "\n" },
  374. { after: 4 * 60 * 1000, run: "\n" },
  375. { after: 5 * 60 * 1000, run: "\n" },
  376. { after: 6 * 60 * 1000, run: "\n" },
  377. { after: 7 * 60 * 1000, run: "\n" },
  378. { after: 8 * 60 * 1000, run: "\n" },
  379. ],
  380. },
  381. {
  382. name: "ReactOS",
  383. timeout: 15 * 60,
  384. hda: root_path + "/images/experimental/reactos-livecd-0.4.15-dev-73-g03c09c9-x86-gcc-lin-dbg.iso",
  385. expect_graphical_mode: true,
  386. expect_graphical_size: [800, 600],
  387. expect_mouse_registered: true,
  388. actions: [
  389. { after: 1 * 60 * 1000, run: "\n" },
  390. { after: 2 * 60 * 1000, run: "\n" },
  391. { after: 3 * 60 * 1000, run: "\n" },
  392. { after: 4 * 60 * 1000, run: "\n" },
  393. { after: 5 * 60 * 1000, run: "\n" },
  394. { after: 6 * 60 * 1000, run: "\n" },
  395. { after: 7 * 60 * 1000, run: "\n" },
  396. { after: 8 * 60 * 1000, run: "\n" },
  397. ],
  398. expected_serial_text: [
  399. "DnsIntCacheInitialize()",
  400. // when desktop is rendered:
  401. "err: Attempted to close thread desktop",
  402. ],
  403. },
  404. {
  405. name: "ReactOS CD",
  406. timeout: 15 * 60,
  407. cdrom: root_path + "/images/experimental/reactos-livecd-0.4.15-dev-73-g03c09c9-x86-gcc-lin-dbg.iso",
  408. expect_graphical_mode: true,
  409. expect_graphical_size: [800, 600],
  410. expect_mouse_registered: true,
  411. expected_serial_text: ["DnsIntCacheInitialize()"],
  412. },
  413. {
  414. name: "HelenOS",
  415. skip_if_disk_image_missing: true,
  416. timeout: 3 * 60,
  417. cdrom: root_path + "/images/experimental/os/HelenOS-0.5.0-ia32.iso",
  418. expect_graphical_mode: true,
  419. expect_mouse_registered: true,
  420. },
  421. {
  422. name: "Minix",
  423. skip_if_disk_image_missing: true,
  424. timeout: 60,
  425. hda: root_path + "/images/experimental/os/minix2hd.img",
  426. actions: [
  427. {
  428. on_text: " = Start Minix",
  429. run: "=",
  430. },
  431. {
  432. on_text: "noname login:",
  433. run: "root\n",
  434. },
  435. ],
  436. expected_texts: ["noname login:", "# "],
  437. },
  438. {
  439. name: "Mobius",
  440. skip_if_disk_image_missing: true,
  441. timeout: 2 * 60,
  442. fda: root_path + "/images/experimental/os/mobius-fd-release5.img",
  443. expect_graphical_mode: true,
  444. actions: [
  445. {
  446. on_text: " The highlighted entry will be booted automatically",
  447. run: "\n",
  448. },
  449. ],
  450. },
  451. {
  452. name: "Tiny Core 11 CD",
  453. skip_if_disk_image_missing: 1,
  454. timeout: 5 * 60,
  455. cdrom: root_path + "/images/experimental/TinyCore-11.0.iso",
  456. expect_graphical_mode: true,
  457. expect_mouse_registered: true,
  458. actions: [{ on_text: "boot:", run: "\n" }],
  459. },
  460. {
  461. name: "Tiny Core 11 HD",
  462. skip_if_disk_image_missing: 1,
  463. timeout: 5 * 60,
  464. cdrom: root_path + "/images/experimental/TinyCore-11.0.iso",
  465. expect_graphical_mode: true,
  466. expect_mouse_registered: true,
  467. actions: [{ on_text: "boot:", run: "\n" }],
  468. },
  469. {
  470. name: "Core 9",
  471. skip_if_disk_image_missing: 1,
  472. timeout: 5 * 60,
  473. cdrom: root_path + "/images/experimental/os/Core-9.0.iso",
  474. expected_texts: ["tc@box"],
  475. actions: [{ on_text: "boot:", run: "\n" }],
  476. },
  477. {
  478. name: "Core 8",
  479. skip_if_disk_image_missing: 1,
  480. timeout: 5 * 60,
  481. cdrom: root_path + "/images/experimental/os/Core-8.0.iso",
  482. expected_texts: ["tc@box"],
  483. actions: [{ on_text: "boot:", run: "\n" }],
  484. },
  485. {
  486. name: "Core 7",
  487. skip_if_disk_image_missing: 1,
  488. timeout: 5 * 60,
  489. cdrom: root_path + "/images/experimental/os/Core-7.2.iso",
  490. expected_texts: ["tc@box"],
  491. actions: [{ on_text: "boot:", run: "\n" }],
  492. },
  493. {
  494. name: "Core 6",
  495. skip_if_disk_image_missing: 1,
  496. timeout: 5 * 60,
  497. cdrom: root_path + "/images/experimental/os/Core-6.4.1.iso",
  498. expected_texts: ["tc@box"],
  499. actions: [{ on_text: "boot:", run: "\n" }],
  500. },
  501. {
  502. name: "Core 5",
  503. skip_if_disk_image_missing: 1,
  504. timeout: 5 * 60,
  505. cdrom: root_path + "/images/experimental/os/Core-5.4.iso",
  506. expected_texts: ["tc@box"],
  507. actions: [{ on_text: "boot:", run: "\n" }],
  508. },
  509. {
  510. name: "Core 4",
  511. skip_if_disk_image_missing: 1,
  512. timeout: 5 * 60,
  513. cdrom: root_path + "/images/experimental/os/Core-4.7.7.iso",
  514. expected_texts: ["tc@box"],
  515. actions: [{ on_text: "boot:", run: "\n" }],
  516. },
  517. {
  518. name: "Damn Small Linux",
  519. skip_if_disk_image_missing: 1,
  520. timeout: 5 * 60,
  521. cdrom: root_path + "/images/dsl-4.11.rc2.iso",
  522. expect_graphical_mode: true,
  523. expect_graphical_size: [1024, 768],
  524. expect_mouse_registered: true,
  525. },
  526. ];
  527. if(TEST_NAME)
  528. {
  529. tests = tests.filter(test => test.name === TEST_NAME);
  530. }
  531. var nr_of_cpus = Math.min(Math.round(os.cpus().length / 2) || 1, tests.length, MAX_PARALLEL_TESTS);
  532. console.log("Using %d cpus", nr_of_cpus);
  533. var current_test = 0;
  534. for(var i = 0; i < nr_of_cpus; i++)
  535. {
  536. var worker = cluster.fork();
  537. worker.on("message", send_work_to_worker.bind(null, worker));
  538. worker.on("online", send_work_to_worker.bind(null, worker));
  539. worker.on("exit", function(code, signal)
  540. {
  541. if(signal)
  542. {
  543. console.warn("Worker killed by signal " + signal);
  544. process.exit(1);
  545. }
  546. else if(code !== 0)
  547. {
  548. process.exit(code);
  549. }
  550. });
  551. worker.on("error", function(error)
  552. {
  553. console.error("Worker error: ", error.toString(), error);
  554. process.exit(1);
  555. });
  556. }
  557. }
  558. else
  559. {
  560. cluster.worker.on("message", function(test_case)
  561. {
  562. run_test(test_case, function()
  563. {
  564. process.send("I'm done");
  565. });
  566. });
  567. }
  568. function bytearray_starts_with(arr, search)
  569. {
  570. for(var i = 0; i < search.length; i++)
  571. {
  572. if(arr[i] !== search[i])
  573. {
  574. return false;
  575. }
  576. }
  577. return true;
  578. }
  579. function run_test(test, done)
  580. {
  581. console.log("Starting test: %s", test.name);
  582. let image = test.fda || test.hda || test.cdrom || test.bzimage || test.filesystem.basefs;
  583. assert(image, "Bootable drive expected");
  584. if(!fs.existsSync(image))
  585. {
  586. if(test.skip_if_disk_image_missing)
  587. {
  588. console.warn("Missing disk image: " + image + ", test skipped");
  589. console.warn();
  590. done();
  591. return;
  592. }
  593. else
  594. {
  595. console.warn("Missing disk image: " + image);
  596. process.exit(1);
  597. }
  598. }
  599. if(test.slow && !RUN_SLOW_TESTS)
  600. {
  601. console.warn("Slow test: " + test.name + ", skipped");
  602. console.warn();
  603. done();
  604. return;
  605. }
  606. if(test.alternative_bios)
  607. {
  608. var bios = root_path + "/bios/bochs-bios.bin";
  609. var vga_bios = root_path + "/bios/bochs-vgabios.bin";
  610. }
  611. else if(TEST_RELEASE_BUILD)
  612. {
  613. var bios = root_path + "/bios/seabios.bin";
  614. var vga_bios = root_path + "/bios/vgabios.bin";
  615. }
  616. else
  617. {
  618. var bios = root_path + "/bios/seabios-debug.bin";
  619. var vga_bios = root_path + "/bios/vgabios-debug.bin";
  620. }
  621. var settings = {
  622. bios: { url: bios },
  623. vga_bios: { url: vga_bios },
  624. autostart: true,
  625. memory_size: test.memory_size || 128 * 1024 * 1024,
  626. log_level: 0,
  627. };
  628. if(test.cdrom)
  629. {
  630. settings.cdrom = { url: test.cdrom };
  631. }
  632. if(test.fda)
  633. {
  634. settings.fda = { url: test.fda };
  635. }
  636. if(test.hda)
  637. {
  638. settings.hda = { url: test.hda, async: true };
  639. }
  640. if(test.bzimage)
  641. {
  642. settings.bzimage = { url: test.bzimage };
  643. }
  644. if(test.filesystem)
  645. {
  646. settings.filesystem = test.filesystem;
  647. }
  648. settings.cmdline = test.cmdline;
  649. settings.bzimage_initrd_from_filesystem = test.bzimage_initrd_from_filesystem;
  650. if(test.expected_texts)
  651. {
  652. test.expected_texts = test.expected_texts.map(string_to_bytearray);
  653. }
  654. else
  655. {
  656. test.expected_texts = [];
  657. }
  658. if(!test.expected_serial_text)
  659. {
  660. test.expected_serial_text = [];
  661. }
  662. var emulator = new V86(settings);
  663. var screen = new Uint8Array(SCREEN_WIDTH * 25);
  664. function check_text_test_done()
  665. {
  666. return test.expected_texts.length === 0;
  667. }
  668. function check_serial_test_done()
  669. {
  670. return test.expected_serial_text.length === 0;
  671. }
  672. var mouse_test_done = false;
  673. function check_mouse_test_done()
  674. {
  675. return !test.expect_mouse_registered || mouse_test_done;
  676. }
  677. var graphical_test_done = false;
  678. var size_test_done = false;
  679. function check_grapical_test_done()
  680. {
  681. return !test.expect_graphical_mode || (graphical_test_done && (!test.expect_graphical_size || size_test_done));
  682. }
  683. var test_start = Date.now();
  684. var timeout_seconds = test.timeout * TIMEOUT_EXTRA_FACTOR;
  685. var timeout = setTimeout(check_test_done, (timeout_seconds + 1) * 1000);
  686. var timeouts = [timeout];
  687. var on_text = [];
  688. var stopped = false;
  689. function check_test_done()
  690. {
  691. if(stopped)
  692. {
  693. return;
  694. }
  695. if(check_text_test_done() &&
  696. check_mouse_test_done() &&
  697. check_grapical_test_done() &&
  698. check_serial_test_done())
  699. {
  700. var end = Date.now();
  701. for(let timeout of timeouts) clearTimeout(timeout);
  702. stopped = true;
  703. emulator.stop();
  704. console.warn("Passed test: %s (took %ds)", test.name, (end - test_start) / 1000);
  705. console.warn();
  706. done();
  707. }
  708. else if(Date.now() >= test_start + timeout_seconds * 1000)
  709. {
  710. for(let timeout of timeouts) clearTimeout(timeout);
  711. stopped = true;
  712. emulator.stop();
  713. emulator.destroy();
  714. console.warn(screen_to_text(screen));
  715. console.warn("Test failed: %s\n", test.name);
  716. if(!check_text_test_done())
  717. {
  718. console.warn('Expected text "%s" after %d seconds.', bytearray_to_string(test.expected_texts[0]), timeout_seconds);
  719. }
  720. if(!check_grapical_test_done())
  721. {
  722. console.warn("Expected graphical mode after %d seconds.", timeout_seconds);
  723. }
  724. if(!check_mouse_test_done())
  725. {
  726. console.warn("Expected mouse activation after %d seconds.", timeout_seconds);
  727. }
  728. if(on_text.length)
  729. {
  730. console.warn(`Note: Expected text "${bytearray_to_string(on_text[0].text)}" to run "${on_text[0].run}"`);
  731. }
  732. process.exit(1);
  733. }
  734. }
  735. emulator.add_listener("mouse-enable", function()
  736. {
  737. mouse_test_done = true;
  738. check_test_done();
  739. });
  740. emulator.add_listener("screen-set-mode", function(is_graphical)
  741. {
  742. graphical_test_done = is_graphical;
  743. check_test_done();
  744. });
  745. emulator.add_listener("screen-set-size-graphical", function(size)
  746. {
  747. if(test.expect_graphical_size)
  748. {
  749. size_test_done = size[0] === test.expect_graphical_size[0] &&
  750. size[1] === test.expect_graphical_size[1];
  751. check_test_done();
  752. }
  753. });
  754. emulator.add_listener("screen-put-char", function(chr)
  755. {
  756. var y = chr[0];
  757. var x = chr[1];
  758. var code = chr[2];
  759. screen[x + SCREEN_WIDTH * y] = code;
  760. var line = get_line(screen, y);
  761. if(!check_text_test_done())
  762. {
  763. let expected = test.expected_texts[0];
  764. if(x < expected.length && bytearray_starts_with(line, expected))
  765. {
  766. test.expected_texts.shift();
  767. if(VERBOSE) console.log(`Passed: "${bytearray_to_string(expected)}" on screen (${test.name})`);
  768. check_test_done();
  769. }
  770. }
  771. if(on_text.length)
  772. {
  773. let expected = on_text[0].text;
  774. if(x < expected.length && bytearray_starts_with(line, expected))
  775. {
  776. var action = on_text.shift();
  777. timeouts.push(
  778. setTimeout(() => {
  779. if(VERBOSE) console.error("Sending '%s'", action.run);
  780. emulator.keyboard_send_text(action.run);
  781. }, action.after || 0)
  782. );
  783. }
  784. }
  785. });
  786. //if(VERBOSE)
  787. //{
  788. // setInterval(() => {
  789. // console.warn(screen_to_text(screen));
  790. // }, 10000);
  791. //}
  792. let serial_line = "";
  793. emulator.add_listener("serial0-output-char", function(c)
  794. {
  795. if(c === "\n")
  796. {
  797. if(VERBOSE)
  798. {
  799. console.log(`Serial (${test.name}):`, serial_line);
  800. }
  801. if(test.expected_serial_text.length)
  802. {
  803. const expected = test.expected_serial_text[0];
  804. if(serial_line.includes(expected))
  805. {
  806. test.expected_serial_text.shift();
  807. if(VERBOSE) console.log(`Passed: "${expected}" on serial (${test.name})`);
  808. check_test_done();
  809. }
  810. }
  811. serial_line = "";
  812. }
  813. else if(c >= " " && c <= "~")
  814. {
  815. serial_line += c;
  816. }
  817. });
  818. test.actions && test.actions.forEach(function(action)
  819. {
  820. if(action.on_text)
  821. {
  822. on_text.push({ text: string_to_bytearray(action.on_text), run: action.run, after: action.after });
  823. }
  824. else
  825. {
  826. timeouts.push(
  827. setTimeout(() => {
  828. if(VERBOSE) console.error("Sending '%s'", action.run);
  829. emulator.keyboard_send_text(action.run);
  830. }, action.after || 0)
  831. );
  832. }
  833. });
  834. }