spec-test.js 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122
  1. /**
  2. * CommonMark 0.31.2 spec conformance runner.
  3. *
  4. * Loads js/statum.js through the DOM shim, renders every example in
  5. * spec-tests.json and compares (whitespace-normalised) against the expected
  6. * HTML. Sections listed in OUT_OF_SCOPE are counted separately: they cover
  7. * raw HTML pass-through and schemes we deliberately neutralise, so they are
  8. * excluded from the pass-rate denominator.
  9. *
  10. * Usage: qjs tests/markdown/spec-test.js [section-filter]
  11. */
  12. import * as std from 'std';
  13. import { installShim } from './shim.js';
  14. var ROOT = import.meta.url ? '.' : '.';
  15. function fail(msg) {
  16. std.err.puts(msg + '\n');
  17. std.exit(1);
  18. }
  19. installShim();
  20. // Load order matters: statum-core.js creates `window.statum` (and the module
  21. // seam), statum-markdown.js registers the engine + its bindings through it.
  22. // Loading exactly these two proves the core+markdown cherry-pick.
  23. (0, eval)(std.loadFile('js/statum-core.js'));
  24. (0, eval)(std.loadFile('js/statum-markdown.js'));
  25. var render = globalThis.statum._testMarkdown;
  26. if (typeof render !== 'function') {
  27. fail('statum._testMarkdown is not exposed — was __STATUM_TEST__ set before loading?');
  28. }
  29. /** cmark-style normalisation: collapse whitespace runs, trim the ends. */
  30. function normalize(html) {
  31. return String(html).replace(/[ \t\r\n]+/g, ' ').trim();
  32. }
  33. var OUT_OF_SCOPE = {
  34. 'HTML blocks': 'raw HTML blocks are escaped, not passed through',
  35. 'Raw HTML': 'inline raw HTML is escaped, not passed through'
  36. };
  37. var data;
  38. try {
  39. data = JSON.parse(std.loadFile('tests/markdown/spec-tests.json'));
  40. } catch (e) {
  41. fail('cannot read tests/markdown/spec-tests.json: ' + e);
  42. }
  43. var filter = scriptArgs.length > 1 ? scriptArgs[1] : null;
  44. var sections = {};
  45. var order = [];
  46. var pass = 0, failCount = 0, skipped = 0;
  47. var failures = [];
  48. for (var i = 0; i < data.length; i++) {
  49. var t = data[i];
  50. if (filter && t.section.indexOf(filter) < 0) continue;
  51. if (Object.prototype.hasOwnProperty.call(OUT_OF_SCOPE, t.section)) { skipped++; continue; }
  52. var got;
  53. try {
  54. got = normalize(render(t.markdown));
  55. } catch (e) {
  56. got = '<<threw ' + e + '>>';
  57. }
  58. var expected = normalize(t.html);
  59. var sec = sections[t.section];
  60. if (!sec) {
  61. sec = sections[t.section] = { pass: 0, fail: 0 };
  62. order.push(t.section);
  63. }
  64. if (got === expected) {
  65. pass++; sec.pass++;
  66. } else {
  67. failCount++; sec.fail++;
  68. failures.push({ example: t.example, section: t.section, markdown: t.markdown, html: t.html, got: got });
  69. }
  70. }
  71. print('CommonMark 0.31.2 spec suite (in scope: ' + (pass + failCount) + ', out of scope: ' + skipped + ')');
  72. print('');
  73. for (var s = 0; s < order.length; s++) {
  74. var st = sections[order[s]];
  75. var total = st.pass + st.fail;
  76. print(' ' + pad(order[s], 38) + ' ' + pad(String(st.pass) + '/' + total, 8) +
  77. bar(st.pass, total));
  78. }
  79. print('');
  80. var rate = (pass + failCount) ? (100 * pass / (pass + failCount)) : 0;
  81. print(' ' + pad('TOTAL', 38) + ' ' + pad(pass + '/' + (pass + failCount), 8) + rate.toFixed(1) + '%');
  82. print('');
  83. if (failures.length && (!filter || scriptArgs.indexOf('-v') >= 0)) {
  84. print('failures:');
  85. for (var f = 0; f < failures.length; f++) {
  86. var x = failures[f];
  87. print(' #' + x.example + ' [' + x.section + '] ' + JSON.stringify(x.markdown));
  88. print(' got: ' + x.got);
  89. print(' exp: ' + normalize(x.html));
  90. }
  91. print('');
  92. }
  93. function pad(s, n) {
  94. while (s.length < n) s += ' ';
  95. return s;
  96. }
  97. function bar(p, total) {
  98. if (!total) return '';
  99. var filled = Math.round(20 * p / total);
  100. var s = '';
  101. for (var i = 0; i < 20; i++) s += i < filled ? '#' : '.';
  102. return ' |' + s + '|';
  103. }
  104. // Exit status is decided by the caller (run.sh) based on the target rate; we
  105. // print the numbers and fail only on hard errors above.
  106. std.exit(0);