run.js 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208
  1. #!/usr/bin/env node
  2. "use strict";
  3. const TEST_RELEASE_BUILD = +process.env.TEST_RELEASE_BUILD;
  4. const assert = require("assert").strict;
  5. const fs = require("fs");
  6. const path = require("path");
  7. const { spawnSync } = require("child_process");
  8. const libwabt = require("../../build/libwabt.js")();
  9. try {
  10. var V86 = require(`../../build/${TEST_RELEASE_BUILD ? "libv86" : "libv86-debug"}.js`).V86;
  11. }
  12. catch(e) {
  13. console.error(e);
  14. console.error("Failed to import build/libv86-debug.js. Run " +
  15. "`make build/libv86-debug.js` first.");
  16. process.exit(1);
  17. }
  18. const TEST_NAME = process.env.TEST_NAME;
  19. const LOG_LEVEL = 0;
  20. const MIN_MEMORY_OFFSET = 4096;
  21. const GIT_DIFF_FLAGS = ["--no-index", "--patience", "--color=always"];
  22. const TEST_DIR = path.join(__dirname, "tests");
  23. const BUILD_DIR = path.join(TEST_DIR, "build");
  24. function run_all()
  25. {
  26. const asm_files = fs.readdirSync(TEST_DIR).filter(filename => filename.endsWith(".asm"));
  27. const files = asm_files.map(asm_file => {
  28. const name = asm_file.slice(0, -4);
  29. return {
  30. name,
  31. expect_file: path.relative(".", path.join(TEST_DIR, name + ".wast")),
  32. actual_file: path.relative(".", path.join(BUILD_DIR, name + ".actual.wast")),
  33. actual_wasm: path.relative(".", path.join(BUILD_DIR, name + ".wasm")),
  34. asm_file: path.join(TEST_DIR, name + ".asm"),
  35. executable_file: path.join(BUILD_DIR, name + ".bin"),
  36. };
  37. }).filter(({ name }) => !TEST_NAME || name === TEST_NAME);
  38. next_test(0);
  39. function next_test(i)
  40. {
  41. if(files[i])
  42. {
  43. run_test(files[i], () => next_test(i + 1));
  44. }
  45. }
  46. }
  47. // Remove parts that may not be stable between multiple runs
  48. function normalise_wast(wast)
  49. {
  50. return wast.replace(/offset=(\d+)/g, function(match, offset)
  51. {
  52. offset = Number(offset);
  53. if(offset >= MIN_MEMORY_OFFSET)
  54. {
  55. return "offset={normalised output}";
  56. }
  57. else
  58. {
  59. return match;
  60. }
  61. }).replace(/memory \$[\w\.]+ \d+/g, "memory {normalised output}");
  62. }
  63. function run_test({ name, executable_file, expect_file, actual_file, actual_wasm, asm_file }, onfinished)
  64. {
  65. const emulator = new V86({
  66. autostart: false,
  67. memory_size: 2 * 1024 * 1024,
  68. log_level: LOG_LEVEL,
  69. });
  70. const executable = fs.readFileSync(executable_file);
  71. const asm = fs.readFileSync(asm_file);
  72. const is_32 = asm.includes("BITS 32\n");
  73. emulator.add_listener("emulator-loaded", function()
  74. {
  75. const cpu = emulator.v86.cpu;
  76. const hook_not_called_timeout = setTimeout(() => {
  77. throw new Error("Hook for code generation not called");
  78. }, 1000);
  79. cpu.test_hook_did_generate_wasm = function(wasm)
  80. {
  81. const wast = normalise_wast(disassemble_wasm(wasm));
  82. clearTimeout(hook_not_called_timeout);
  83. fs.writeFileSync(actual_file, wast);
  84. fs.writeFileSync(actual_wasm, wasm);
  85. cpu.test_hook_did_generate_wasm = function()
  86. {
  87. cpu.test_hook_did_generate_wasm = function() {};
  88. throw new Error("Hook for wasm generation called multiple times");
  89. };
  90. if(!fs.existsSync(expect_file))
  91. {
  92. // enhanced workflow: If file doesn't exist yet print full diff
  93. var expect_file_for_diff = "/dev/null";
  94. }
  95. else
  96. {
  97. expect_file_for_diff = expect_file;
  98. }
  99. const result = spawnSync("git",
  100. [].concat(
  101. "diff",
  102. GIT_DIFF_FLAGS,
  103. expect_file_for_diff,
  104. actual_file
  105. ),
  106. { encoding: "utf8" });
  107. if(result.status)
  108. {
  109. console.log(result.stdout);
  110. console.log(result.stderr);
  111. if(process.argv.includes("--accept-all"))
  112. {
  113. console.log(`Running: cp ${actual_file} ${expect_file}`);
  114. fs.copyFileSync(actual_file, expect_file);
  115. }
  116. else
  117. {
  118. const failure_message = `${name}.asm failed:
  119. The code generator produced different code. If you believe this change is intentional,
  120. verify the diff above and run the following command to accept the change:
  121. cp ${actual_file} ${expect_file}
  122. When done, re-run this test to confirm that all expect-tests pass.
  123. Hint: Use tests/expect/run.js --accept-all to accept all changes (use git diff to verify).
  124. `;
  125. console.log(failure_message);
  126. process.exit(1);
  127. }
  128. }
  129. else
  130. {
  131. console.log("%s ok", name);
  132. assert(!result.stdout);
  133. assert(!result.stderr);
  134. }
  135. onfinished();
  136. };
  137. if(is_32)
  138. {
  139. cpu.is_32[0] = true;
  140. cpu.stack_size_32[0] = true;
  141. }
  142. const START_ADDRESS = 0x1000;
  143. cpu.mem8.set(executable, START_ADDRESS);
  144. cpu.jit_force_generate(START_ADDRESS);
  145. });
  146. }
  147. function disassemble_wasm(wasm)
  148. {
  149. // Need to make a small copy otherwise libwabt goes nuts trying to copy
  150. // the whole underlying buffer
  151. wasm = wasm.slice();
  152. try
  153. {
  154. var module = libwabt.readWasm(wasm, { readDebugNames: false });
  155. module.generateNames();
  156. module.applyNames();
  157. return module.toText({ foldExprs: true, inlineExport: true });
  158. }
  159. catch(e)
  160. {
  161. console.error("Error while running libwabt: " + e.toString());
  162. console.error("Did you forget an ending hlt instruction?\n");
  163. throw e;
  164. }
  165. finally
  166. {
  167. module && module.destroy();
  168. }
  169. }
  170. run_all();