Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
012acb0
feat: verbous error when entire test tree is canceled
MoLow Aug 2, 2022
d885ee2
feat: support programmatically running `--test`
MoLow Aug 15, 2022
27241c3
fix: fix `duration_ms` to be milliseconds
MoLow Sep 4, 2022
6755536
feat: support using `--inspect` with `--test`
MoLow Sep 10, 2022
c50f844
fix: include stack of uncaught exceptions
MoLow Sep 14, 2022
1950b38
test: fix test-runner-inspect
MoLow Sep 14, 2022
c5fd64c
feat: add --test-name-pattern CLI flag
cjihrig Sep 1, 2022
46dce07
feat: add extra fields in AssertionError YAML
bengl Oct 26, 2022
0bfdb77
fix: call {before,after}Each() on suites
cjihrig Oct 27, 2022
08269c5
fix: report tap subtest in order
MoLow Oct 28, 2022
f2815af
fix: fix afterEach not running on test failures
MrJithil Nov 7, 2022
cff397a
fix: avoid swallowing of asynchronously thrown errors
fossamagna Nov 7, 2022
2e499ee
feat: support function mocking
cjihrig Apr 4, 2022
5f8ce61
feat: add initial TAP parser
manekinekko Nov 21, 2022
5ba2500
fix: remove stdout and stderr from error
cjihrig Nov 25, 2022
b942f93
feat: add getter and setter to MockTracker
fossamagna Nov 18, 2022
b3b384e
fix: don't use a symbol for runHook()
cjihrig Dec 6, 2022
71b659e
feat: add t.after() hook
cjihrig Dec 8, 2022
c0854ac
test: fix invalid output TAP if there newline in test name
pulkit-30 Dec 11, 2022
9b49978
chore: refactor `tap_parser` to use more primordials
aduh95 Dec 11, 2022
4e778bc
chore: refactor `tap_lexer` to use more primordials
aduh95 Dec 12, 2022
d1343a7
feat: parse yaml
MoLow Dec 13, 2022
c80e426
fix: run t.after() if test body throws
cjihrig Dec 17, 2022
9fa496b
test: fix mock.method to support class instances
ErickWendel Dec 17, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
498 changes: 496 additions & 2 deletions README.md

Large diffs are not rendered by default.

7 changes: 7 additions & 0 deletions bin/node--test-name-pattern.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#!/usr/bin/env node

const { argv } = require('#internal/options')

argv['test-name-pattern'] = true

require('./node-core-test.js')
5 changes: 5 additions & 0 deletions bin/node-core-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,14 @@ const { argv } = require('#internal/options')

Object.assign(argv, minimist(process.argv.slice(2), {
boolean: ['test', 'test-only'],
string: ['test-name-pattern'],
default: Object.prototype.hasOwnProperty.call(argv, 'test') ? { test: argv.test } : undefined
}))

if (typeof argv['test-name-pattern'] === 'string') {
argv['test-name-pattern'] = [argv['test-name-pattern']]
}

process.argv.splice(1, Infinity, ...argv._)
if (argv.test) {
require('#internal/main/test_runner')
Expand Down
17 changes: 16 additions & 1 deletion lib/internal/errors.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// https://github.com/nodejs/node/blob/1aab13cad9c800f4121c1d35b554b78c1b17bdbd/lib/internal/errors.js
// https://github.com/nodejs/node/blob/f8ce9117b19702487eb600493d941f7876e00e01/lib/internal/errors.js

'use strict'

Expand Down Expand Up @@ -346,6 +346,21 @@ module.exports = {
kIsNodeError
}

E('ERR_TAP_LEXER_ERROR', function (errorMsg) {
hideInternalStackFrames(this)
return errorMsg
}, Error)
E('ERR_TAP_PARSER_ERROR', function (errorMsg, details, tokenCausedError, source) {
hideInternalStackFrames(this)
this.cause = tokenCausedError
const { column, line, start, end } = tokenCausedError.location
const errorDetails = `${details} at line ${line}, column ${column} (start ${start}, end ${end})`
return errorMsg + errorDetails
}, SyntaxError)
E('ERR_TAP_VALIDATION_ERROR', function (errorMsg) {
hideInternalStackFrames(this)
return errorMsg
}, Error)
E('ERR_TEST_FAILURE', function (error, failureType) {
hideInternalStackFrames(this)
assert(typeof failureType === 'string',
Expand Down
154 changes: 16 additions & 138 deletions lib/internal/main/test_runner.js
Original file line number Diff line number Diff line change
@@ -1,148 +1,26 @@
// https://github.com/nodejs/node/blob/2fd4c013c221653da2a7921d08fe1aa96aaba504/lib/internal/main/test_runner.js
// https://github.com/nodejs/node/blob/a165193c5c8e4bcfbd12b2c3f6e55a81a251c258/lib/internal/main/test_runner.js
'use strict'
const {
ArrayFrom,
ArrayPrototypeFilter,
ArrayPrototypeIncludes,
ArrayPrototypeJoin,
ArrayPrototypePush,
ArrayPrototypeSlice,
ArrayPrototypeSort,
SafePromiseAll,
SafeSet
} = require('#internal/per_context/primordials')
const {
prepareMainThreadExecution
} = require('#internal/bootstrap/pre_execution')
const { spawn } = require('child_process')
const { readdirSync, statSync } = require('fs')
const {
codes: {
ERR_TEST_FAILURE
}
} = require('#internal/errors')
const { toArray } = require('#internal/streams/operators').promiseReturningOperators
const { test } = require('#internal/test_runner/harness')
const { kSubtestsFailed } = require('#internal/test_runner/test')
const {
isSupportedFileType,
doesPathMatchFilter
} = require('#internal/test_runner/utils')
const { basename, join, resolve } = require('path')
const { once } = require('events')
const kFilterArgs = ['--test']
} = require('#internal/process/pre_execution')
const { isUsingInspector } = require('#internal/util/inspector')
const { run } = require('#internal/test_runner/runner')

prepareMainThreadExecution(false)
// markBootstrapComplete();

// TODO(cjihrig): Replace this with recursive readdir once it lands.
function processPath (path, testFiles, options) {
const stats = statSync(path)

if (stats.isFile()) {
if (options.userSupplied ||
(options.underTestDir && isSupportedFileType(path)) ||
doesPathMatchFilter(path)) {
testFiles.add(path)
}
} else if (stats.isDirectory()) {
const name = basename(path)

if (!options.userSupplied && name === 'node_modules') {
return
}

// 'test' directories get special treatment. Recursively add all .js,
// .cjs, and .mjs files in the 'test' directory.
const isTestDir = name === 'test'
const { underTestDir } = options
const entries = readdirSync(path)

if (isTestDir) {
options.underTestDir = true
}
let concurrency = true
let inspectPort

options.userSupplied = false

for (let i = 0; i < entries.length; i++) {
processPath(join(path, entries[i]), testFiles, options)
}

options.underTestDir = underTestDir
}
if (isUsingInspector()) {
process.emitWarning('Using the inspector with --test forces running at a concurrency of 1. ' +
'Use the inspectPort option to run with concurrency')
concurrency = 1
inspectPort = process.debugPort
}

function createTestFileList () {
const cwd = process.cwd()
const hasUserSuppliedPaths = process.argv.length > 1
const testPaths = hasUserSuppliedPaths
? ArrayPrototypeSlice(process.argv, 1)
: [cwd]
const testFiles = new SafeSet()

try {
for (let i = 0; i < testPaths.length; i++) {
const absolutePath = resolve(testPaths[i])

processPath(absolutePath, testFiles, { userSupplied: true })
}
} catch (err) {
if (err?.code === 'ENOENT') {
console.error(`Could not find '${err.path}'`)
process.exit(1)
}

throw err
}

return ArrayPrototypeSort(ArrayFrom(testFiles))
}

function filterExecArgv (arg) {
return !ArrayPrototypeIncludes(kFilterArgs, arg)
}

function runTestFile (path) {
return test(path, async (t) => {
const args = ArrayPrototypeFilter(process.execArgv, filterExecArgv)
ArrayPrototypePush(args, path)

const child = spawn(process.execPath, args, { signal: t.signal, encoding: 'utf8' })
// TODO(cjihrig): Implement a TAP parser to read the child's stdout
// instead of just displaying it all if the child fails.
let err

child.on('error', (error) => {
err = error
})

const { 0: { 0: code, 1: signal }, 1: stdout, 2: stderr } = await SafePromiseAll([
once(child, 'exit', { signal: t.signal }),
toArray.call(child.stdout, { signal: t.signal }),
toArray.call(child.stderr, { signal: t.signal })
])

if (code !== 0 || signal !== null) {
if (!err) {
err = new ERR_TEST_FAILURE('test failed', kSubtestsFailed)
err.exitCode = code
err.signal = signal
err.stdout = ArrayPrototypeJoin(stdout, '')
err.stderr = ArrayPrototypeJoin(stderr, '')
// The stack will not be useful since the failures came from tests
// in a child process.
err.stack = undefined
}

throw err
}
})
}

;(async function main () {
const testFiles = createTestFileList()

for (let i = 0; i < testFiles.length; i++) {
runTestFile(testFiles[i])
}
})()
const tapStream = run({ concurrency, inspectPort })
tapStream.pipe(process.stdout)
tapStream.once('test:fail', () => {
process.exitCode = 1
})
27 changes: 27 additions & 0 deletions lib/internal/per_context/primordials.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,29 +3,44 @@
const replaceAll = require('string.prototype.replaceall')

exports.ArrayFrom = (it, mapFn) => Array.from(it, mapFn)
exports.ArrayIsArray = Array.isArray
exports.ArrayPrototypeConcat = (arr, ...el) => arr.concat(...el)
exports.ArrayPrototypeFilter = (arr, fn) => arr.filter(fn)
exports.ArrayPrototypeFind = (arr, fn) => arr.find(fn)
exports.ArrayPrototypeForEach = (arr, fn, thisArg) => arr.forEach(fn, thisArg)
exports.ArrayPrototypeIncludes = (arr, el, fromIndex) => arr.includes(el, fromIndex)
exports.ArrayPrototypeJoin = (arr, str) => arr.join(str)
exports.ArrayPrototypeMap = (arr, mapFn) => arr.map(mapFn)
exports.ArrayPrototypePop = arr => arr.pop()
exports.ArrayPrototypePush = (arr, ...el) => arr.push(...el)
exports.ArrayPrototypeReduce = (arr, fn, originalVal) => arr.reduce(fn, originalVal)
exports.ArrayPrototypeShift = arr => arr.shift()
exports.ArrayPrototypeSlice = (arr, offset) => arr.slice(offset)
exports.ArrayPrototypeSome = (arr, fn) => arr.some(fn)
exports.ArrayPrototypeSort = (arr, fn) => arr.sort(fn)
exports.ArrayPrototypeSplice = (arr, offset, len, ...el) => arr.splice(offset, len, ...el)
exports.ArrayPrototypeUnshift = (arr, ...el) => arr.unshift(...el)
exports.Boolean = Boolean
exports.Error = Error
exports.ErrorCaptureStackTrace = (...args) => Error.captureStackTrace(...args)
exports.FunctionPrototype = Function.prototype
exports.FunctionPrototypeBind = (fn, obj, ...args) => fn.bind(obj, ...args)
exports.FunctionPrototypeCall = (fn, obj, ...args) => fn.call(obj, ...args)
exports.MathMax = (...args) => Math.max(...args)
exports.Number = Number
exports.NumberIsInteger = Number.isInteger
exports.NumberIsNaN = Number.isNaN
exports.NumberParseInt = (str, radix) => Number.parseInt(str, radix)
exports.NumberMIN_SAFE_INTEGER = Number.MIN_SAFE_INTEGER
exports.NumberMAX_SAFE_INTEGER = Number.MAX_SAFE_INTEGER
exports.ObjectAssign = (target, ...sources) => Object.assign(target, ...sources)
exports.ObjectCreate = obj => Object.create(obj)
exports.ObjectDefineProperties = (obj, props) => Object.defineProperties(obj, props)
exports.ObjectDefineProperty = (obj, key, descr) => Object.defineProperty(obj, key, descr)
exports.ObjectEntries = obj => Object.entries(obj)
exports.ObjectFreeze = obj => Object.freeze(obj)
exports.ObjectGetOwnPropertyDescriptor = (obj, key) => Object.getOwnPropertyDescriptor(obj, key)
exports.ObjectGetPrototypeOf = obj => Object.getPrototypeOf(obj)
exports.ObjectIsExtensible = obj => Object.isExtensible(obj)
exports.ObjectPrototypeHasOwnProperty = (obj, property) => Object.prototype.hasOwnProperty.call(obj, property)
exports.ObjectSeal = (obj) => Object.seal(obj)
Expand All @@ -35,22 +50,34 @@ exports.PromiseAll = iterator => Promise.all(iterator)
exports.PromisePrototypeThen = (promise, thenFn, catchFn) => promise.then(thenFn, catchFn)
exports.PromiseResolve = val => Promise.resolve(val)
exports.PromiseRace = val => Promise.race(val)
exports.Proxy = Proxy
exports.RegExpPrototypeSymbolSplit = (reg, str) => reg[Symbol.split](str)
exports.SafeArrayIterator = class ArrayIterator {constructor (array) { this.array = array }[Symbol.iterator] () { return this.array.values() }}
exports.SafeMap = Map
exports.SafePromiseAll = (array, mapFn) => Promise.all(mapFn ? array.map(mapFn) : array)
exports.SafePromiseRace = (array, mapFn) => Promise.race(mapFn ? array.map(mapFn) : array)
exports.SafeSet = Set
exports.SafeWeakMap = WeakMap
exports.SafeWeakSet = WeakSet
exports.String = String
exports.StringPrototypeEndsWith = (haystack, needle, index) => haystack.endsWith(needle, index)
exports.StringPrototypeIncludes = (str, needle) => str.includes(needle)
exports.StringPrototypeMatch = (str, reg) => str.match(reg)
exports.StringPrototypeRepeat = (str, times) => str.repeat(times)
exports.StringPrototypeReplace = (str, search, replacement) =>
str.replace(search, replacement)
exports.StringPrototypeReplaceAll = replaceAll
exports.StringPrototypeStartsWith = (haystack, needle, index) => haystack.startsWith(needle, index)
exports.StringPrototypeSlice = (str, ...args) => str.slice(...args)
exports.StringPrototypeSplit = (str, search, limit) => str.split(search, limit)
exports.StringPrototypeSubstring = (str, ...args) => str.substring(...args)
exports.StringPrototypeToUpperCase = str => str.toUpperCase()
exports.StringPrototypeTrim = str => str.trim()
exports.Symbol = Symbol
exports.SymbolFor = repr => Symbol.for(repr)
exports.ReflectApply = (target, self, args) => Reflect.apply(target, self, args)
exports.ReflectConstruct = (target, args, newTarget) => Reflect.construct(target, args, newTarget)
exports.ReflectGet = (target, property, receiver) => Reflect.get(target, property, receiver)
exports.RegExpPrototypeExec = (reg, str) => reg.exec(str)
exports.RegExpPrototypeSymbolReplace = (regexp, str, replacement) =>
regexp[Symbol.replace](str, replacement)
Loading