From 1150991ba1921cbf888438d46350894484356078 Mon Sep 17 00:00:00 2001 From: Jonathan Samines Date: Mon, 3 Aug 2020 00:33:38 -0600 Subject: [PATCH] Self-host the built-in Node.js assert tests Also make `test-ava` work from inside the `test` directory. Co-authored-by: Mark Wubben --- test-tap/helper/cli.js | 5 +--- test-tap/integration/node-assertions.js | 29 ------------------- test/assertions/test.js | 2 +- .../fixtures}/assert-failure.js | 2 +- .../fixtures/package.json | 7 +++++ test/builtin-nodejs-assert/test.js | 25 ++++++++++++++++ test/helpers/exec.js | 12 +++++--- .../helper => test/helpers}/simulate-tty.js | 16 +++++----- test/hook-restrictions/test.js | 4 +-- test/node_modules/.bin/test-ava | 1 + test/snapshot-updates/test.js | 8 ++--- test/test-timeouts/test.js | 4 +-- 12 files changed, 60 insertions(+), 55 deletions(-) delete mode 100644 test-tap/integration/node-assertions.js rename {test-tap/fixture/node-assertions => test/builtin-nodejs-assert/fixtures}/assert-failure.js (69%) create mode 100644 test/builtin-nodejs-assert/fixtures/package.json create mode 100644 test/builtin-nodejs-assert/test.js rename {test-tap/helper => test/helpers}/simulate-tty.js (67%) create mode 120000 test/node_modules/.bin/test-ava diff --git a/test-tap/helper/cli.js b/test-tap/helper/cli.js index c1dd2c949..29eb69f0f 100644 --- a/test-tap/helper/cli.js +++ b/test-tap/helper/cli.js @@ -4,7 +4,6 @@ const childProcess = require('child_process'); const getStream = require('get-stream'); const cliPath = path.join(__dirname, '../../cli.js'); -const ttySimulator = path.join(__dirname, 'simulate-tty.js'); function execCli(args, options, cb) { let dirname; @@ -24,9 +23,7 @@ function execCli(args, options, cb) { let stderr; const processPromise = new Promise(resolve => { - // Spawning a child with piped IO means that the CLI will never see a TTY. - // Inserting a shim here allows us to fake a TTY. - child = childProcess.spawn(process.execPath, ['--require', ttySimulator, cliPath].concat(args), { + child = childProcess.spawn(process.execPath, [cliPath].concat(args), { cwd: dirname, env: {AVA_FORCE_CI: 'ci', ...env}, // Force CI to ensure the correct reporter is selected // env, diff --git a/test-tap/integration/node-assertions.js b/test-tap/integration/node-assertions.js deleted file mode 100644 index 1b37e9bb9..000000000 --- a/test-tap/integration/node-assertions.js +++ /dev/null @@ -1,29 +0,0 @@ -'use strict'; -const {test} = require('tap'); -const {execCli} = require('../helper/cli'); - -// The AssertionError constructor in Node 10 depends on the TTY interface -test('node assertion failures are reported to the console when running in a terminal', t => { - const options = { - dirname: 'fixture/node-assertions', - env: { - AVA_SIMULATE_TTY: true, - AVA_TTY_COLOR_DEPTH: 8, - AVA_TTY_HAS_COLORS: typeof process.stderr.hasColors === 'function' - } - }; - - execCli('assert-failure.js', options, (err, stdout) => { - t.ok(err); - t.match(stdout, /AssertionError/); - t.end(); - }); -}); - -test('node assertion failures are reported to the console when not running in a terminal', t => { - execCli('assert-failure.js', {dirname: 'fixture/node-assertions'}, (err, stdout) => { - t.ok(err); - t.match(stdout, /AssertionError/); - t.end(); - }); -}); diff --git a/test/assertions/test.js b/test/assertions/test.js index c0207d423..426d7a67e 100644 --- a/test/assertions/test.js +++ b/test/assertions/test.js @@ -2,6 +2,6 @@ const test = require('@ava/test'); const exec = require('../helpers/exec'); test('happy path', async t => { - const result = await exec.fixture('happy-path.js'); + const result = await exec.fixture(['happy-path.js']); t.snapshot(result.stats.passed.map(({title}) => title)); }); diff --git a/test-tap/fixture/node-assertions/assert-failure.js b/test/builtin-nodejs-assert/fixtures/assert-failure.js similarity index 69% rename from test-tap/fixture/node-assertions/assert-failure.js rename to test/builtin-nodejs-assert/fixtures/assert-failure.js index 9d8ab50df..a7e0562bf 100644 --- a/test-tap/fixture/node-assertions/assert-failure.js +++ b/test/builtin-nodejs-assert/fixtures/assert-failure.js @@ -1,5 +1,5 @@ +const test = require('ava'); const assert = require('assert'); -const test = require('../../..'); test('test', () => { assert(false); diff --git a/test/builtin-nodejs-assert/fixtures/package.json b/test/builtin-nodejs-assert/fixtures/package.json new file mode 100644 index 000000000..f9b9cb835 --- /dev/null +++ b/test/builtin-nodejs-assert/fixtures/package.json @@ -0,0 +1,7 @@ +{ + "ava": { + "files": [ + "*.js" + ] + } +} diff --git a/test/builtin-nodejs-assert/test.js b/test/builtin-nodejs-assert/test.js new file mode 100644 index 000000000..c18c34468 --- /dev/null +++ b/test/builtin-nodejs-assert/test.js @@ -0,0 +1,25 @@ +const test = require('@ava/test'); +const exec = require('../helpers/exec'); + +test('node assertion failures are reported to the console when running in a terminal', async t => { + const options = { + env: { + // The AssertionError constructor in Node.js 10 depends on the TTY interface, so opt-in + // to it being simulated. + AVA_SIMULATE_TTY: true, + AVA_TTY_COLOR_DEPTH: 8 + } + }; + + const result = await t.throwsAsync(exec.fixture(['assert-failure.js'], options)); + const error = result.stats.getError(result.stats.failed[0]); + + t.true(error.values.every(value => value.formatted.includes('AssertionError'))); +}); + +test('node assertion failures are reported to the console when not running in a terminal', async t => { + const result = await t.throwsAsync(exec.fixture(['assert-failure.js'])); + const error = result.stats.getError(result.stats.failed[0]); + + t.true(error.values.every(value => value.formatted.includes('AssertionError'))); +}); diff --git a/test/helpers/exec.js b/test/helpers/exec.js index 907a5429e..cc85c1761 100644 --- a/test/helpers/exec.js +++ b/test/helpers/exec.js @@ -3,8 +3,11 @@ const v8 = require('v8'); const test = require('@ava/test'); const execa = require('execa'); +const defaultsDeep = require('lodash/defaultsDeep'); const cliPath = path.resolve(__dirname, '../../cli.js'); +const ttySimulator = path.join(__dirname, './simulate-tty.js'); + const serialization = process.versions.node >= '12.16.0' ? 'advanced' : 'json'; const normalizePath = (root, file) => path.posix.normalize(path.relative(root, file)); @@ -25,15 +28,16 @@ const compareStatObjects = (a, b) => { return 1; }; -exports.fixture = async (...args) => { +exports.fixture = async (args, options = {}) => { const cwd = path.join(path.dirname(test.meta.file), 'fixtures'); - const running = execa.node(cliPath, args, { + const running = execa.node(cliPath, args, defaultsDeep({ env: { AVA_EMIT_RUN_STATUS_OVER_IPC: 'I\'ll find a payphone baby / Take some time to talk to you' }, cwd, - serialization - }); + serialization, + nodeOptions: ['--require', ttySimulator] + }, options)); // Besides buffering stderr, if this environment variable is set, also pipe // to stderr. This can be useful when debugging the tests. diff --git a/test-tap/helper/simulate-tty.js b/test/helpers/simulate-tty.js similarity index 67% rename from test-tap/helper/simulate-tty.js rename to test/helpers/simulate-tty.js index 406aa50dd..59f674518 100644 --- a/test-tap/helper/simulate-tty.js +++ b/test/helpers/simulate-tty.js @@ -5,7 +5,7 @@ const assertHasColorsArguments = count => { tty.WriteStream.prototype.hasColors(count); }; -const makeHasColors = colorDepth => (count = 16, env = undefined) => { +const makeHasColors = colorDepth => (count = 16, env) => { // eslint-disable-line default-param-last // `count` is optional too, so make sure it's not an env object. if (env === undefined && typeof count === 'object' && count !== null) { count = 16; @@ -15,7 +15,7 @@ const makeHasColors = colorDepth => (count = 16, env = undefined) => { return count <= 2 ** colorDepth; }; -const simulateTTY = (stream, colorDepth, hasColors) => { +const simulateTTY = (stream, colorDepth) => { stream.isTTY = true; stream.columns = 80; stream.rows = 24; @@ -24,9 +24,10 @@ const simulateTTY = (stream, colorDepth, hasColors) => { stream.getColorDepth = () => colorDepth; } - if (hasColors) { - stream.hasColors = makeHasColors(colorDepth); - } + stream.hasColors = makeHasColors(colorDepth); + stream.clearLine = tty.WriteStream.prototype.clearLine; + stream.cursorTo = tty.WriteStream.prototype.cursorTo; + stream.moveCursor = tty.WriteStream.prototype.moveCursor; }; // The execCli helper spawns tests in a child process. This means that stdout is @@ -36,8 +37,7 @@ if (process.env.AVA_SIMULATE_TTY) { const colorDepth = process.env.AVA_TTY_COLOR_DEPTH ? Number.parseInt(process.env.AVA_TTY_COLOR_DEPTH, 10) : undefined; - const hasColors = process.env.AVA_TTY_HAS_COLORS !== undefined; - simulateTTY(process.stderr, colorDepth, hasColors); - simulateTTY(process.stdout, colorDepth, hasColors); + simulateTTY(process.stderr, colorDepth); + simulateTTY(process.stdout, colorDepth); } diff --git a/test/hook-restrictions/test.js b/test/hook-restrictions/test.js index 7e32f4abb..7601c238a 100644 --- a/test/hook-restrictions/test.js +++ b/test/hook-restrictions/test.js @@ -2,13 +2,13 @@ const test = require('@ava/test'); const exec = require('../helpers/exec'); test('snapshots cannot be used in hooks', async t => { - const result = await t.throwsAsync(exec.fixture('invalid-snapshots-in-hooks.js')); + const result = await t.throwsAsync(exec.fixture(['invalid-snapshots-in-hooks.js'])); const error = result.stats.getError(result.stats.failedHooks[0]); t.snapshot(error.message, 'error message'); }); test('`t.try()` cannot be used in hooks', async t => { - const result = await t.throwsAsync(exec.fixture('invalid-t-try-in-hooks.js')); + const result = await t.throwsAsync(exec.fixture(['invalid-t-try-in-hooks.js'])); const error = result.stats.getError(result.stats.failedHooks[0]); t.snapshot(error.message, 'error message'); }); diff --git a/test/node_modules/.bin/test-ava b/test/node_modules/.bin/test-ava new file mode 120000 index 000000000..bd4f1e8fb --- /dev/null +++ b/test/node_modules/.bin/test-ava @@ -0,0 +1 @@ +/Users/jsamines/dev/personal/ava/node_modules/.bin/test-ava \ No newline at end of file diff --git a/test/snapshot-updates/test.js b/test/snapshot-updates/test.js index 465d8e6ed..91c2686e3 100644 --- a/test/snapshot-updates/test.js +++ b/test/snapshot-updates/test.js @@ -2,14 +2,14 @@ const test = require('@ava/test'); const exec = require('../helpers/exec'); test('cannot update snapshots when file contains skipped tests', async t => { - const result = await t.throwsAsync(exec.fixture('contains-skip.js', '-u')); + const result = await t.throwsAsync(exec.fixture(['contains-skip.js', '-u'])); t.snapshot(result.stats.failed, 'failed tests'); t.snapshot(result.stats.skipped, 'skipped tests'); t.snapshot(result.stats.unsavedSnapshots, 'files where snapshots could not be updated'); }); test('cannot update snapshots when file contains exclusive tests', async t => { - const result = await exec.fixture('contains-only.js', '-u'); + const result = await exec.fixture(['contains-only.js', '-u']); t.snapshot(result.stats.failed, 'failed tests'); t.snapshot(result.stats.passed, 'passed tests'); t.snapshot(result.stats.unsavedSnapshots, 'files where snapshots could not be updated'); @@ -18,11 +18,11 @@ test('cannot update snapshots when file contains exclusive tests', async t => { const stripLeadingFigures = string => string.replace(/^\W+/, ''); test('cannot update snapshots when matching test titles', async t => { - const result = await t.throwsAsync(exec.fixture('contains-skip.js', '-u', '-m=snapshot')); + const result = await t.throwsAsync(exec.fixture(['contains-skip.js', '-u', '-m=snapshot'])); t.snapshot(stripLeadingFigures(result.stderr.trim())); }); test('cannot update snapshots when selecting tests by line number', async t => { - const result = await t.throwsAsync(exec.fixture('contains-skip.js:4', '-u')); + const result = await t.throwsAsync(exec.fixture(['contains-skip.js:4', '-u'])); t.snapshot(stripLeadingFigures(result.stderr.trim())); }); diff --git a/test/test-timeouts/test.js b/test/test-timeouts/test.js index 5971cbe77..2fdd67e2d 100644 --- a/test/test-timeouts/test.js +++ b/test/test-timeouts/test.js @@ -2,13 +2,13 @@ const test = require('@ava/test'); const exec = require('../helpers/exec'); test('timeout message can be specified', async t => { - const result = await t.throwsAsync(exec.fixture('custom-message.js')); + const result = await t.throwsAsync(exec.fixture(['custom-message.js'])); const error = result.stats.getError(result.stats.failed[0]); t.is(error.message, 'time budget exceeded'); }); test('timeout messages must be strings', async t => { - const result = await t.throwsAsync(exec.fixture('invalid-message.js')); + const result = await t.throwsAsync(exec.fixture(['invalid-message.js'])); const error = result.stats.getError(result.stats.failed[0]); t.snapshot(error.message, 'error message'); t.snapshot(error.values, 'formatted values');