diff --git a/.github/workflows/CI.yaml b/.github/workflows/CI.yaml index f5d2c6ae8..9f9650758 100644 --- a/.github/workflows/CI.yaml +++ b/.github/workflows/CI.yaml @@ -51,8 +51,12 @@ jobs: node: 16.x script: npm run test-nolint + # use macos-12 instead of macos-latest + # as proxy for Intel instead of ARM + # because the older Puppeteer/Chromr for Node 10 compat + # didn't support ARM yet. - name: "macOS: Node 16" - os: macos-latest + os: macos-12 node: 16.x script: npm run test-nolint diff --git a/test/cli/cli-main.js b/test/cli/cli-main.js index f7151f05d..92b7de7b9 100644 --- a/test/cli/cli-main.js +++ b/test/cli/cli-main.js @@ -17,33 +17,10 @@ QUnit.module('CLI Main', () => { concurrentMapKeys(readFixtures(FIXTURES_DIR), 0, (runFixture) => runFixture()), async (assert, fixture) => { const result = await fixture; - assert.equal(result.snapshot, result.expected); + assert.equal(result.snapshot, result.expected, result.name); } ); - // TODO: Figure out why trace isn't trimmed on Windows. https://github.com/qunitjs/qunit/issues/1359 - QUnit[skipOnWinTest]('report assert.throws() failures properly', async assert => { - const command = ['qunit', 'assert-throws-failure.js']; - const execution = await execute(command); - assert.equal(execution.snapshot, `TAP version 13 -not ok 1 Throws match > bad - --- - message: match error - severity: failed - actual : Error: Match me with a pattern - expected: "/incorrect pattern/" - stack: | - at /qunit/test/cli/fixtures/assert-throws-failure.js:3:12 - ... -1..1 -# pass 0 -# skip 0 -# todo 0 -# fail 1 - -# exit code: 1`); - }); - QUnit.test('callbacks', async assert => { const expected = `CALLBACK: begin1 CALLBACK: begin2 @@ -237,8 +214,7 @@ ok 1 ESM test suite > sum() // https://nodejs.org/dist/v12.12.0/docs/api/cli.html#cli_enable_source_maps if (semver.gte(process.versions.node, '14.0.0')) { - // TODO: Figure out why trace isn't trimmed on Windows. https://github.com/qunitjs/qunit/issues/1359 - QUnit[skipOnWinTest]('normal trace with native source map', async assert => { + QUnit.test('normal trace with native source map', async assert => { const command = ['qunit', 'sourcemap/source.js']; const execution = await execute(command); @@ -264,9 +240,7 @@ not ok 2 Example > bad // skip if running in code coverage mode, // as that leads to conflicting maps-on-maps that invalidate this test - // - // TODO: Figure out why trace isn't trimmed on Windows. https://github.com/qunitjs/qunit/issues/1359 - QUnit[process.env.NYC_PROCESS_ID ? 'skip' : skipOnWinTest]( + QUnit[process.env.NYC_PROCESS_ID ? 'skip' : 'test']( 'mapped trace with native source map', async function (assert) { const command = ['qunit', 'sourcemap/source.min.js']; const execution = await execute(command, { @@ -397,143 +371,4 @@ not ok 1 global failure actual: execution.stdout + '\n' + execution.stderr }); }); - - // TODO: Figure out why trace isn't trimmed on Windows. https://github.com/qunitjs/qunit/issues/1359 - QUnit[skipOnWinTest]('assert.async() handled after fail in other test', async assert => { - const command = ['qunit', 'drooling-done.js']; - const execution = await execute(command); - - assert.equal(execution.snapshot, `TAP version 13 -not ok 1 Test A - --- - message: |+ - Died on test #2: this is an intentional error - at /qunit/test/cli/fixtures/drooling-done.js:5:7 - at internal - severity: failed - actual : null - expected: undefined - stack: | - Error: this is an intentional error - at /qunit/test/cli/fixtures/drooling-done.js:8:9 - ... -ok 2 Test B -1..2 -# pass 1 -# skip 0 -# todo 0 -# fail 1 - -# exit code: 1`); - }); - - // TODO: Figure out why trace isn't trimmed on Windows. https://github.com/qunitjs/qunit/issues/1359 - QUnit[skipOnWinTest]('assert.async() handled again in other test', async assert => { - const command = ['qunit', 'drooling-extra-done.js']; - const execution = await execute(command); - - assert.equal(execution.snapshot, `TAP version 13 -ok 1 Test A -not ok 2 Test B - --- - message: |+ - Died on test #2: Unexpected release of async pause during a different test. - > Test: Test A [async #1] - at /qunit/test/cli/fixtures/drooling-extra-done.js:13:7 - at internal - severity: failed - actual : null - expected: undefined - stack: | - Error: Unexpected release of async pause during a different test. - > Test: Test A [async #1] - ... -1..2 -# pass 1 -# skip 0 -# todo 0 -# fail 1 - -# exit code: 1`); - }); - - // TODO: Figure out why trace isn't trimmed on Windows. https://github.com/qunitjs/qunit/issues/1359 - QUnit[skipOnWinTest]('assert.async() handled too often', async assert => { - const command = ['qunit', 'too-many-done-calls.js']; - const execution = await execute(command); - - assert.equal(execution.snapshot, `TAP version 13 -not ok 1 Test A - --- - message: |+ - Died on test #2: Tried to release async pause that was already released. - > Test: Test A [async #1] - at /qunit/test/cli/fixtures/too-many-done-calls.js:1:7 - at internal - severity: failed - actual : null - expected: undefined - stack: | - Error: Tried to release async pause that was already released. - > Test: Test A [async #1] - ... -1..1 -# pass 0 -# skip 0 -# todo 0 -# fail 1 - -# exit code: 1`); - }); - - // TODO: Figure out why trace isn't trimmed on Windows. https://github.com/qunitjs/qunit/issues/1359 - QUnit[skipOnWinTest]('module.only() nested', async assert => { - const command = ['qunit', 'only-module.js']; - const execution = await execute(command); - - assert.equal(execution.snapshot, `TAP version 13 -not ok 1 # TODO module B > Only this module should run > a todo test - --- - message: not implemented yet - severity: todo - actual : false - expected: true - stack: | - at /qunit/test/cli/fixtures/only-module.js:17:18 - ... -ok 2 # SKIP module B > Only this module should run > implicitly skipped test -ok 3 module B > Only this module should run > normal test -ok 4 module D > test D -ok 5 module E > module F > test F -ok 6 module E > test E -1..8 -# pass 6 -# skip 1 -# todo 1 -# fail 0`); - }); - - // TODO: Figure out why trace isn't trimmed on Windows. https://github.com/qunitjs/qunit/issues/1359 - QUnit[skipOnWinTest]('module.only() flat', async assert => { - const command = ['qunit', 'only-module-flat.js']; - const execution = await execute(command); - - assert.equal(execution.snapshot, `TAP version 13 -not ok 1 # TODO module B > test B - --- - message: not implemented yet - severity: todo - actual : false - expected: true - stack: | - at /qunit/test/cli/fixtures/only-module-flat.js:8:14 - ... -ok 2 # SKIP module B > test C -ok 3 module B > test D -1..4 -# pass 2 -# skip 1 -# todo 1 -# fail 0`); - }); }); diff --git a/test/cli/fixtures/assert-throws-failure.tap.txt b/test/cli/fixtures/assert-throws-failure.tap.txt new file mode 100644 index 000000000..3405fcbcd --- /dev/null +++ b/test/cli/fixtures/assert-throws-failure.tap.txt @@ -0,0 +1,20 @@ +# name: report assert.throws() failures properly +# command: ["qunit", "assert-throws-failure.js"] + +TAP version 13 +not ok 1 Throws match > bad + --- + message: match error + severity: failed + actual : Error: Match me with a pattern + expected: "/incorrect pattern/" + stack: | + at /qunit/test/cli/fixtures/assert-throws-failure.js:3:12 + ... +1..1 +# pass 0 +# skip 0 +# todo 0 +# fail 1 + +# exit code: 1 diff --git a/test/cli/fixtures/drooling-done.tap.txt b/test/cli/fixtures/drooling-done.tap.txt new file mode 100644 index 000000000..082583c9e --- /dev/null +++ b/test/cli/fixtures/drooling-done.tap.txt @@ -0,0 +1,26 @@ +# name: assert.async() handled after fail in other test +# command: ["qunit", "drooling-done.js"] + +TAP version 13 +not ok 1 Test A + --- + message: |+ + Died on test #2: this is an intentional error + at /qunit/test/cli/fixtures/drooling-done.js:5:7 + at internal + severity: failed + actual : null + expected: undefined + stack: | + Error: this is an intentional error + at /qunit/test/cli/fixtures/drooling-done.js:8:9 + ... +ok 2 Test B +1..2 +# pass 1 +# skip 0 +# todo 0 +# fail 1 + +# exit code: 1 + diff --git a/test/cli/fixtures/drooling-extra-done.tap.txt b/test/cli/fixtures/drooling-extra-done.tap.txt new file mode 100644 index 000000000..154359cb5 --- /dev/null +++ b/test/cli/fixtures/drooling-extra-done.tap.txt @@ -0,0 +1,26 @@ +# name: assert.async() handled again in other test +# command: ["qunit", "drooling-extra-done.js"] + +TAP version 13 +ok 1 Test A +not ok 2 Test B + --- + message: |+ + Died on test #2: Unexpected release of async pause during a different test. + > Test: Test A [async #1] + at /qunit/test/cli/fixtures/drooling-extra-done.js:13:7 + at internal + severity: failed + actual : null + expected: undefined + stack: | + Error: Unexpected release of async pause during a different test. + > Test: Test A [async #1] + ... +1..2 +# pass 1 +# skip 0 +# todo 0 +# fail 1 + +# exit code: 1 diff --git a/test/cli/fixtures/filter-modulename-insensitive.tap.txt b/test/cli/fixtures/filter-modulename-insensitive.tap.txt index cb6188bce..05b2c94d3 100644 --- a/test/cli/fixtures/filter-modulename-insensitive.tap.txt +++ b/test/cli/fixtures/filter-modulename-insensitive.tap.txt @@ -7,4 +7,4 @@ ok 1 Second > 1 # pass 1 # skip 0 # todo 0 -# fail 0 \ No newline at end of file +# fail 0 diff --git a/test/cli/fixtures/only-module-flat.tap.txt b/test/cli/fixtures/only-module-flat.tap.txt new file mode 100644 index 000000000..572c377a0 --- /dev/null +++ b/test/cli/fixtures/only-module-flat.tap.txt @@ -0,0 +1,19 @@ +# command: ["qunit", "only-module-flat.js"] + +TAP version 13 +not ok 1 # TODO module B > test B + --- + message: not implemented yet + severity: todo + actual : false + expected: true + stack: | + at /qunit/test/cli/fixtures/only-module-flat.js:8:14 + ... +ok 2 # SKIP module B > test C +ok 3 module B > test D +1..4 +# pass 2 +# skip 1 +# todo 1 +# fail 0 diff --git a/test/cli/fixtures/only-module.tap.txt b/test/cli/fixtures/only-module.tap.txt new file mode 100644 index 000000000..bf5803fc7 --- /dev/null +++ b/test/cli/fixtures/only-module.tap.txt @@ -0,0 +1,23 @@ +# name: module.only() nested +# command: ["qunit", "only-module.js"] + +TAP version 13 +not ok 1 # TODO module B > Only this module should run > a todo test + --- + message: not implemented yet + severity: todo + actual : false + expected: true + stack: | + at /qunit/test/cli/fixtures/only-module.js:17:18 + ... +ok 2 # SKIP module B > Only this module should run > implicitly skipped test +ok 3 module B > Only this module should run > normal test +ok 4 module D > test D +ok 5 module E > module F > test F +ok 6 module E > test E +1..8 +# pass 6 +# skip 1 +# todo 1 +# fail 0 diff --git a/test/cli/fixtures/too-many-done-calls.tap.txt b/test/cli/fixtures/too-many-done-calls.tap.txt new file mode 100644 index 000000000..a2c18b528 --- /dev/null +++ b/test/cli/fixtures/too-many-done-calls.tap.txt @@ -0,0 +1,25 @@ +# name: assert.async() handled too often +# command: ["qunit", "too-many-done-calls.js"] + +TAP version 13 +not ok 1 Test A + --- + message: |+ + Died on test #2: Tried to release async pause that was already released. + > Test: Test A [async #1] + at /qunit/test/cli/fixtures/too-many-done-calls.js:1:7 + at internal + severity: failed + actual : null + expected: undefined + stack: | + Error: Tried to release async pause that was already released. + > Test: Test A [async #1] + ... +1..1 +# pass 0 +# skip 0 +# todo 0 +# fail 1 + +# exit code: 1 diff --git a/test/cli/helpers/execute.js b/test/cli/helpers/execute.js index f73711f62..bdf15797e 100644 --- a/test/cli/helpers/execute.js +++ b/test/cli/helpers/execute.js @@ -12,11 +12,12 @@ const reEscape = /([\\{}()|.?*+\-^$[\]])/g; function normalize (actual) { const dir = path.join(__dirname, '..', '..', '..'); const reDir = new RegExp(dir.replace(reEscape, '\\$1'), 'g'); - const reSep = new RegExp(path.sep.replace(reEscape, '\\$1'), 'g'); + // Replace backslashes (\) in stack traces on Windows to POSIX + // but leave quoted/escaped shell arguments like \"foo\" unchanged. + const reSep = new RegExp(path.sep.replace(reEscape, '\\$1') + '(?!")', 'g'); return actual .replace(reDir, '/qunit') - // Replace backslashes (\) in stack traces on Windows to POSIX .replace(reSep, '/') // Convert "at processModule (/qunit/qunit/qunit.js:1:2)" to "at qunit.js" // Convert "at /qunit/qunit/qunit.js:1:2" to "at qunit.js" @@ -169,7 +170,7 @@ function concurrentMap (input, concurrency, asyncFn) { for (let i = 0; i < input.length; i++) { const val = input[i]; if (i < concurrency) { - ret[i] = Promise.resolve(asyncFn(val)).finally(next); + ret[i] = Promise.resolve(asyncFn(val)); } else { let trigger; const promise = new Promise((resolve) => { @@ -177,8 +178,10 @@ function concurrentMap (input, concurrency, asyncFn) { }); queue.push(trigger); - ret[i] = promise.then(asyncFn.bind(null, val)).finally(next); + ret[i] = promise.then(asyncFn.bind(null, val)); } + // Avoid premature UnhandledPromiseRejectionWarning + ret[i].catch(() => null).finally(next); } return ret; } diff --git a/test/cli/helpers/fixtures.js b/test/cli/helpers/fixtures.js index 6bbbdbe5c..d64185280 100644 --- a/test/cli/helpers/fixtures.js +++ b/test/cli/helpers/fixtures.js @@ -11,18 +11,18 @@ const CMD_DIRECTIVE = '# command: '; async function parseFixture (file) { const contents = await fsPromises.readFile(file, 'utf8'); const lines = contents.split('\n'); - if (!lines[0].startsWith(NAME_DIRECTIVE)) { - throw new Error('Name declaration missing'); + const name = (lines[0].startsWith(NAME_DIRECTIVE)) + ? lines.shift().slice(NAME_DIRECTIVE.length) + : path.basename(file); + if (!lines[0].startsWith(CMD_DIRECTIVE)) { + throw new Error(`Missing command declaration in ${path.basename(file)}`); } - const name = lines[0].slice(NAME_DIRECTIVE.length); - if (!lines[1].startsWith(CMD_DIRECTIVE)) { - throw new Error('Command declaration missing'); - } - const command = JSON.parse(lines[1].slice(CMD_DIRECTIVE.length)); + const command = JSON.parse(lines[0].slice(CMD_DIRECTIVE.length)); + return { name, command, - expected: lines.slice(2).join('\n').trim() + expected: lines.slice(1).join('\n').trim() }; } @@ -34,6 +34,7 @@ function readFixtures (dir) { const fixture = await parseFixture(file); const result = await execute(fixture.command); return { + name: fixture.name, expected: fixture.expected, snapshot: result.snapshot };