From 1d80a2bae5420b52b8c18f4e0d316f3426ea1fe1 Mon Sep 17 00:00:00 2001 From: Raz Luvaton <16746759+rluvaton@users.noreply.github.com> Date: Wed, 28 Jun 2023 16:27:23 +0300 Subject: [PATCH 01/19] test_runner: run tests as soon as find them --- lib/internal/fs/glob.js | 306 +++++++++++++++++++++++++---- lib/internal/test_runner/runner.js | 62 ++++-- test/parallel/test-fs-glob.mjs | 47 ++++- test/parallel/test-runner-run.mjs | 4 + 4 files changed, 370 insertions(+), 49 deletions(-) diff --git a/lib/internal/fs/glob.js b/lib/internal/fs/glob.js index 323ef2a25d434c..fd89385b6e59ce 100644 --- a/lib/internal/fs/glob.js +++ b/lib/internal/fs/glob.js @@ -1,5 +1,6 @@ 'use strict'; const { lstatSync, readdirSync } = require('fs'); +const { lstat, readdir } = require('fs/promises'); const { join, resolve } = require('path'); const { @@ -21,6 +22,8 @@ const { SafeMap, SafeSet, StringPrototypeEndsWith, + SymbolIterator, + SymbolAsyncIterator, } = primordials; let minimatch; @@ -51,6 +54,20 @@ class Cache { this.#statsCache.set(path, val); return val; } + async stat(path) { + const cached = this.#statsCache.get(path); + if (cached) { + return cached; + } + let val; + try { + val = await lstat(path); + } catch { + val = null; + } + this.#statsCache.set(path, val); + return val; + } addToStatCache(path, val) { this.#statsCache.set(path, val); } @@ -68,6 +85,21 @@ class Cache { this.#readdirCache.set(path, val); return val; } + + async readdir(path) { + const cached = this.#readdirCache.get(path); + if (cached) { + return cached; + } + let val; + try { + val = await readdir(path, { __proto__: null, withFileTypes: true }); + } catch { + val = []; + } + this.#readdirCache.set(path, val); + return val; + } add(path, pattern) { let cache = this.#cache.get(path); if (!cache) { @@ -148,7 +180,6 @@ class Glob { #root; #exclude; #cache = new Cache(); - #results = []; #queue = []; #subpatterns = new SafeMap(); constructor(patterns, options = kEmptyObject) { @@ -172,29 +203,9 @@ class Glob { } globSync() { - ArrayPrototypePush(this.#queue, { - __proto__: null, - path: '.', - patterns: ArrayPrototypeFlatMap(this.matchers, (matcher) => ArrayPrototypeMap(matcher.set, - (pattern, i) => new Pattern( - pattern, - matcher.globParts[i], - new SafeSet([0]), - new SafeSet(), - ))), - }); - - while (this.#queue.length > 0) { - const item = ArrayPrototypePop(this.#queue); - for (let i = 0; i < item.patterns.length; i++) { - this.#addSubpatterns(item.path, item.patterns[i]); - } - this.#subpatterns - .forEach((patterns, path) => ArrayPrototypePush(this.#queue, { __proto__: null, path, patterns })); - this.#subpatterns.clear(); - } - return this.#results; + return ArrayFrom(this); } + #addSubpattern(path, pattern) { if (!this.#subpatterns.has(path)) { this.#subpatterns.set(path, [pattern]); @@ -202,7 +213,8 @@ class Glob { ArrayPrototypePush(this.#subpatterns.get(path), pattern); } } - #addSubpatterns(path, pattern) { + + *#addSubpatternsSync(path, pattern) { const seen = this.#cache.add(path, pattern); if (seen) { return; @@ -240,16 +252,16 @@ class Glob { const p = pattern.at(-1); const stat = this.#cache.statSync(join(fullpath, p)); if (stat && (p || isDirectory)) { - ArrayPrototypePush(this.#results, join(path, p)); + yield join(path, p); } if (pattern.indexes.size === 1 && pattern.indexes.has(last)) { return; } } else if (isLast && pattern.at(-1) === lazyMinimatch().GLOBSTAR && - (path !== '.' || pattern.at(0) === '.' || (last === 0 && stat))) { + (path !== '.' || pattern.at(0) === '.' || (last === 0 && stat))) { // If pattern ends with **, add to results // if path is ".", add it only if pattern starts with "." or pattern is exactly "**" - ArrayPrototypePush(this.#results, path); + yield path; } if (!isDirectory) { @@ -296,7 +308,182 @@ class Glob { subPatterns.add(index); } else if (!fromSymlink && index === last) { // If ** is last, add to results - ArrayPrototypePush(this.#results, entryPath); + yield entryPath; + } + + // Any pattern after ** is also a potential pattern + // so we can already test it here + const nextMatches = pattern.test(nextIndex, entry.name); + if (nextMatches && nextIndex === last && !isLast) { + // If next pattern is the last one, add to results + yield entryPath; + } else if (nextMatches && entry.isDirectory()) { + // Pattern mached, meaning two patterns forward + // are also potential patterns + // e.g **/b/c when entry is a/b - add c to potential patterns + subPatterns.add(index + 2); + } + if ((nextMatches || pattern.at(0) === '.') && + (entry.isDirectory() || entry.isSymbolicLink()) && !fromSymlink) { + // If pattern after ** matches, or pattern starts with "." + // and entry is a directory or symlink, add to potential patterns + subPatterns.add(nextIndex); + } + + if (entry.isSymbolicLink()) { + nSymlinks.add(index); + } + + if (next === '..' && entry.isDirectory()) { + // In case pattern is "**/..", + // both parent and current directory should be added to the queue + // if this is the last pattern, add to results instead + const parent = join(path, '..'); + if (nextIndex < last) { + if (!this.#subpatterns.has(path) && !this.#cache.seen(path, pattern, nextIndex + 1)) { + this.#subpatterns.set(path, [pattern.child(new SafeSet([nextIndex + 1]))]); + } + if (!this.#subpatterns.has(parent) && !this.#cache.seen(parent, pattern, nextIndex + 1)) { + this.#subpatterns.set(parent, [pattern.child(new SafeSet([nextIndex + 1]))]); + } + } else { + if (!this.#cache.seen(path, pattern, nextIndex)) { + this.#cache.add(path, pattern.child(new SafeSet([nextIndex]))); + yield path; + } + if (!this.#cache.seen(path, pattern, nextIndex) || !this.#cache.seen(parent, pattern, nextIndex)) { + this.#cache.add(parent, pattern.child(new SafeSet([nextIndex]))); + yield parent; + } + } + } + } + if (typeof current === 'string') { + if (pattern.test(index, entry.name) && index !== last) { + // If current pattern matches entry name + // the next pattern is a potential pattern + subPatterns.add(nextIndex); + } else if (current === '.' && pattern.test(nextIndex, entry.name)) { + // If current pattern is ".", proceed to test next pattern + if (nextIndex === last) { + yield entryPath; + } else { + subPatterns.add(nextIndex + 1); + } + } + } + if (typeof current === 'object' && pattern.test(index, entry.name)) { + // If current pattern is a regex that matches entry name (e.g *.js) + // add next pattern to potential patterns, or to results if it's the last pattern + if (index === last) { + yield entryPath; + } else if (entry.isDirectory()) { + subPatterns.add(nextIndex); + } + } + } + if (subPatterns.size > 0) { + // If there are potential patterns, add to queue + this.#addSubpattern(entryPath, pattern.child(subPatterns, nSymlinks)); + } + } + } + + async *#addSubpatterns(path, pattern) { + const seen = this.#cache.add(path, pattern); + if (seen) { + return; + } + const fullpath = resolve(this.#root, path); + const stat = await this.#cache.stat(fullpath); + const last = pattern.last; + const isDirectory = stat?.isDirectory() || (stat?.isSymbolicLink() && pattern.hasSeenSymlinks); + const isLast = pattern.isLast(isDirectory); + const isFirst = pattern.isFirst(); + + if (isFirst && isWindows && typeof pattern.at(0) === 'string' && StringPrototypeEndsWith(pattern.at(0), ':')) { + // Absolute path, go to root + this.#addSubpattern(`${pattern.at(0)}\\`, pattern.child(new SafeSet([1]))); + return; + } + if (isFirst && pattern.at(0) === '') { + // Absolute path, go to root + this.#addSubpattern('/', pattern.child(new SafeSet([1]))); + return; + } + if (isFirst && pattern.at(0) === '..') { + // Start with .., go to parent + this.#addSubpattern('../', pattern.child(new SafeSet([1]))); + return; + } + if (isFirst && pattern.at(0) === '.') { + // Start with ., proceed + this.#addSubpattern('.', pattern.child(new SafeSet([1]))); + return; + } + + if (isLast && typeof pattern.at(-1) === 'string') { + // Add result if it exists + const p = pattern.at(-1); + const stat = await this.#cache.stat(join(fullpath, p)); + if (stat && (p || isDirectory)) { + yield join(path, p); + } + if (pattern.indexes.size === 1 && pattern.indexes.has(last)) { + return; + } + } else if (isLast && pattern.at(-1) === lazyMinimatch().GLOBSTAR && + (path !== '.' || pattern.at(0) === '.' || (last === 0 && stat))) { + // If pattern ends with **, add to results + // if path is ".", add it only if pattern starts with "." or pattern is exactly "**" + yield path; + } + + if (!isDirectory) { + return; + } + + let children; + const firstPattern = pattern.indexes.size === 1 && pattern.at(pattern.indexes.values().next().value); + if (typeof firstPattern === 'string') { + const stat = await this.#cache.stat(join(fullpath, firstPattern)); + if (stat) { + stat.name = firstPattern; + children = [stat]; + } else { + children = []; + } + } else { + children = await this.#cache.readdir(fullpath); + } + + for (let i = 0; i < children.length; i++) { + const entry = children[i]; + const entryPath = join(path, entry.name); + this.#cache.addToStatCache(join(fullpath, entry.name), entry); + + const subPatterns = new SafeSet(); + const nSymlinks = new SafeSet(); + for (const index of pattern.indexes) { + // For each child, chek potential patterns + if (this.#cache.seen(entryPath, pattern, index) || this.#cache.seen(entryPath, pattern, index + 1)) { + return; + } + const current = pattern.at(index); + const nextIndex = index + 1; + const next = pattern.at(nextIndex); + const fromSymlink = pattern.symlinks.has(index); + + if (current === lazyMinimatch().GLOBSTAR) { + if (entry.name[0] === '.' || (this.#exclude && this.#exclude(entry.name))) { + continue; + } + if (!fromSymlink && entry.isDirectory()) { + // If directory, add ** to its potential patterns + subPatterns.add(index); + } else if (!fromSymlink && index === last) { + // If ** is last, add to results + yield entryPath; } // Any pattern after ** is also a potential pattern @@ -304,7 +491,7 @@ class Glob { const nextMatches = pattern.test(nextIndex, entry.name); if (nextMatches && nextIndex === last && !isLast) { // If next pattern is the last one, add to results - ArrayPrototypePush(this.#results, entryPath); + yield entryPath; } else if (nextMatches && entry.isDirectory()) { // Pattern mached, meaning two patterns forward // are also potential patterns @@ -312,7 +499,7 @@ class Glob { subPatterns.add(index + 2); } if ((nextMatches || pattern.at(0) === '.') && - (entry.isDirectory() || entry.isSymbolicLink()) && !fromSymlink) { + (entry.isDirectory() || entry.isSymbolicLink()) && !fromSymlink) { // If pattern after ** matches, or pattern starts with "." // and entry is a directory or symlink, add to potential patterns subPatterns.add(nextIndex); @@ -337,11 +524,11 @@ class Glob { } else { if (!this.#cache.seen(path, pattern, nextIndex)) { this.#cache.add(path, pattern.child(new SafeSet([nextIndex]))); - ArrayPrototypePush(this.#results, path); + yield path; } if (!this.#cache.seen(path, pattern, nextIndex) || !this.#cache.seen(parent, pattern, nextIndex)) { this.#cache.add(parent, pattern.child(new SafeSet([nextIndex]))); - ArrayPrototypePush(this.#results, parent); + yield parent; } } } @@ -354,7 +541,7 @@ class Glob { } else if (current === '.' && pattern.test(nextIndex, entry.name)) { // If current pattern is ".", proceed to test next pattern if (nextIndex === last) { - ArrayPrototypePush(this.#results, entryPath); + yield entryPath; } else { subPatterns.add(nextIndex + 1); } @@ -364,7 +551,7 @@ class Glob { // If current pattern is a regex that matches entry name (e.g *.js) // add next pattern to potential patterns, or to results if it's the last pattern if (index === last) { - ArrayPrototypePush(this.#results, entryPath); + yield entryPath; } else if (entry.isDirectory()) { subPatterns.add(nextIndex); } @@ -376,6 +563,57 @@ class Glob { } } } + + *[SymbolIterator]() { + ArrayPrototypePush(this.#queue, { + __proto__: null, + path: '.', + patterns: ArrayPrototypeFlatMap(this.matchers, (matcher) => ArrayPrototypeMap(matcher.set, + (pattern, i) => new Pattern( + pattern, + matcher.globParts[i], + new SafeSet([0]), + new SafeSet(), + ))), + }); + + while (this.#queue.length > 0) { + const item = ArrayPrototypePop(this.#queue); + for (let i = 0; i < item.patterns.length; i++) { + yield *this.#addSubpatternsSync(item.path, item.patterns[i]); + } + this.#subpatterns + .forEach((patterns, path) => ArrayPrototypePush(this.#queue, { __proto__: null, path, patterns })); + this.#subpatterns.clear(); + } + } + + + // TODO - add signal + // TODO - avoid copy + async *[SymbolAsyncIterator]() { + ArrayPrototypePush(this.#queue, { + __proto__: null, + path: '.', + patterns: ArrayPrototypeFlatMap(this.matchers, (matcher) => ArrayPrototypeMap(matcher.set, + (pattern, i) => new Pattern( + pattern, + matcher.globParts[i], + new SafeSet([0]), + new SafeSet(), + ))), + }); + + while (this.#queue.length > 0) { + const item = ArrayPrototypePop(this.#queue); + for (let i = 0; i < item.patterns.length; i++) { + yield* await this.#addSubpatterns(item.path, item.patterns[i]); + } + this.#subpatterns + .forEach((patterns, path) => ArrayPrototypePush(this.#queue, { __proto__: null, path, patterns })); + this.#subpatterns.clear(); + } + } } module.exports = { diff --git a/lib/internal/test_runner/runner.js b/lib/internal/test_runner/runner.js index af121273c9e638..e9d8db551d46ab 100644 --- a/lib/internal/test_runner/runner.js +++ b/lib/internal/test_runner/runner.js @@ -11,7 +11,6 @@ const { ArrayPrototypeShift, ArrayPrototypeSlice, ArrayPrototypeSome, - ArrayPrototypeSort, ObjectAssign, PromisePrototypeThen, SafePromiseAll, @@ -28,6 +27,7 @@ const { } = primordials; const { spawn } = require('child_process'); +const { Readable } = require('stream'); const { finished } = require('internal/streams/end-of-stream'); const { DefaultDeserializer, DefaultSerializer } = require('v8'); // TODO(aduh95): switch to internal/readline/interface when backporting to Node.js 16.x is no longer a concern. @@ -87,14 +87,25 @@ function createTestFileList() { cwd, exclude: (name) => name === 'node_modules', }); - const results = glob.globSync(); - if (hasUserSuppliedPattern && results.length === 0 && ArrayPrototypeEvery(glob.matchers, (m) => !m.hasMagic())) { - console.error(`Could not find '${ArrayPrototypeJoin(patterns, ', ')}'`); - process.exit(kGenericUserError); + + if (hasUserSuppliedPattern && ArrayPrototypeEvery(glob.matchers, (m) => !m.hasMagic())) { + return Readable.from((async function*() { + let hasFile = false; + for await (const file of glob) { + hasFile = true; + yield file; + } + + if (!hasFile) { + console.error(`Could not find '${ArrayPrototypeJoin(patterns, ', ')}'`); + process.exit(kGenericUserError); + } + })()); } - return ArrayPrototypeSort(results); + // TODO - make sure using the async... + return Readable.from(glob); } function filterExecArgv(arg, i, arr) { @@ -373,7 +384,7 @@ function runTestFile(path, root, inspectPort, filesWatcher, testNamePatterns) { return subtest.start(); } -function watchFiles(testFiles, root, inspectPort, signal, testNamePatterns) { +function watchFiles(testFilesPromise, root, inspectPort, signal, testNamePatterns) { const runningProcesses = new SafeMap(); const runningSubtests = new SafeMap(); const watcher = new FilesWatcher({ throttle: 500, mode: 'filter', signal }); @@ -381,7 +392,7 @@ function watchFiles(testFiles, root, inspectPort, signal, testNamePatterns) { watcher.on('changed', ({ owners }) => { watcher.unfilterFilesOwnedBy(owners); - PromisePrototypeThen(SafePromiseAllReturnVoid(testFiles, async (file) => { + PromisePrototypeThen(testFilesPromise, (testFiles) => SafePromiseAllReturnVoid(testFiles, async (file) => { if (!owners.has(file)) { return; } @@ -446,25 +457,48 @@ function run(options) { } const root = createTestTree({ concurrency, timeout, signal }); - const testFiles = files ?? createTestFileList(); + let testFiles = files ?? createTestFileList(); let postRun = () => root.postRun(); let filesWatcher; if (watch) { + // Overriding testFiles so we won't try to consume again the stream + testFiles = ArrayIsArray(testFiles) ? testFiles : testFiles.toArray(); + + // TODO - should not pass a promise filesWatcher = watchFiles(testFiles, root, inspectPort, signal, testNamePatterns); postRun = undefined; } + + const runSingleFile = (path) => { + const subtest = runTestFile(path, root, inspectPort, filesWatcher, testNamePatterns); + filesWatcher?.runningSubtests.set(path, subtest); + return subtest; + }; + const runFiles = () => { root.harness.bootstrapComplete = true; - return SafePromiseAllSettledReturnVoid(testFiles, (path) => { - const subtest = runTestFile(path, root, inspectPort, filesWatcher, testNamePatterns); - filesWatcher?.runningSubtests.set(path, subtest); - return subtest; - }); + + if (ArrayIsArray(testFiles)) { + testFiles = PromiseResolve(testFiles); + } + + // TODO - find better way to check if promise + if (typeof testFiles.then === 'function') { + return PromisePrototypeThen( + testFiles, + (testFilesArray) => SafePromiseAllSettledReturnVoid(testFilesArray, runSingleFile), + ); + } + + return testFiles + .map(runSingleFile, { signal, concurrency }) + .toArray(); }; PromisePrototypeThen(PromisePrototypeThen(PromiseResolve(setup?.(root)), runFiles), postRun); + return root.reporter; } diff --git a/test/parallel/test-fs-glob.mjs b/test/parallel/test-fs-glob.mjs index b1420fec272923..119949b1a3e025 100644 --- a/test/parallel/test-fs-glob.mjs +++ b/test/parallel/test-fs-glob.mjs @@ -300,10 +300,55 @@ const patterns = { ], }; + +test('Glob should be an iterator', () => { + const pattern = Object.keys(patterns)[0]; + const globInstance = new glob.Glob([pattern], { cwd: fixtureDir }); + + assert.strictEqual( + typeof globInstance[Symbol.iterator], + 'function', + new TypeError('Glob instance should be iterable') + ); +}); +test('Glob should be an async iterator', () => { + const pattern = Object.keys(patterns)[0]; + const globInstance = new glob.Glob([pattern], { cwd: fixtureDir }); + + assert.strictEqual( + typeof globInstance[Symbol.asyncIterator], + 'function', + new TypeError('Glob instance should be iterable') + ); +}); + for (const [pattern, expected] of Object.entries(patterns)) { - test(pattern, () => { + test(`${pattern} globSync`, () => { const actual = new glob.Glob([pattern], { cwd: fixtureDir }).globSync().sort(); const normalized = expected.filter(Boolean).map((item) => item.replaceAll('/', sep)).sort(); assert.deepStrictEqual(actual, normalized); }); + + test(`${pattern} iterable`, () => { + const globInstance = new glob.Glob([pattern], { cwd: fixtureDir }); + const actual = Array.from(globInstance).sort(); + const normalized = expected.filter(Boolean).map((item) => item.replaceAll('/', sep)).sort(); + + assert.deepStrictEqual(actual, normalized); + }); + + test(`${pattern} async iterable`, async () => { + const actual = new glob.Glob([pattern], { cwd: fixtureDir }); + + const normalizedArr = expected.filter(Boolean).map((item) => item.replaceAll('/', sep)); + const normalized = new Set(normalizedArr); + + let matchesCount = 0; + for await (const matchItem of actual) { + matchesCount++; + assert.ok(normalized.has(matchItem), new Error(`${matchItem} not suppose to be in the glob matches (should be one of ${normalizedArr})`)); + } + + assert.strictEqual(normalized.size, matchesCount); + }); } diff --git a/test/parallel/test-runner-run.mjs b/test/parallel/test-runner-run.mjs index 0e92abd92bb5a2..657b9e6c69ccf4 100644 --- a/test/parallel/test-runner-run.mjs +++ b/test/parallel/test-runner-run.mjs @@ -7,6 +7,10 @@ import assert from 'node:assert'; const testFixtures = fixtures.path('test-runner'); +// TODO - fix this warning that got when running this test file: +// (node:61297) MaxListenersExceededWarning: Possible EventEmitter memory leak detected. +// 11 uncaughtException listeners added to [process]. Use emitter.setMaxListeners() to increase limit + describe('require(\'node:test\').run', { concurrency: true }, () => { it('should run with no tests', async () => { From 20e7ad9eff99bd7878c9e69819054a058b0a148a Mon Sep 17 00:00:00 2001 From: Raz Luvaton <16746759+rluvaton@users.noreply.github.com> Date: Wed, 28 Jun 2023 19:39:13 +0300 Subject: [PATCH 02/19] test-runner: fix tests as files not run in consistent order... --- lib/internal/fs/glob.js | 4 ++- lib/internal/test_runner/runner.js | 2 -- test/parallel/test-runner-cli.js | 44 ++++++++++++++++-------------- 3 files changed, 26 insertions(+), 24 deletions(-) diff --git a/lib/internal/fs/glob.js b/lib/internal/fs/glob.js index fd89385b6e59ce..bfda2f3e7a9fd3 100644 --- a/lib/internal/fs/glob.js +++ b/lib/internal/fs/glob.js @@ -100,6 +100,7 @@ class Cache { this.#readdirCache.set(path, val); return val; } + add(path, pattern) { let cache = this.#cache.get(path); if (!cache) { @@ -110,6 +111,7 @@ class Cache { pattern.indexes.forEach((index) => cache.add(pattern.cacheKey(index))); return cache.size !== originalSize + pattern.indexes.size; } + seen(path, pattern, index) { return this.#cache.get(path)?.has(pattern.cacheKey(index)); } @@ -557,6 +559,7 @@ class Glob { } } } + if (subPatterns.size > 0) { // If there are potential patterns, add to queue this.#addSubpattern(entryPath, pattern.child(subPatterns, nSymlinks)); @@ -588,7 +591,6 @@ class Glob { } } - // TODO - add signal // TODO - avoid copy async *[SymbolAsyncIterator]() { diff --git a/lib/internal/test_runner/runner.js b/lib/internal/test_runner/runner.js index e9d8db551d46ab..95ddcea6b3bbbb 100644 --- a/lib/internal/test_runner/runner.js +++ b/lib/internal/test_runner/runner.js @@ -88,7 +88,6 @@ function createTestFileList() { exclude: (name) => name === 'node_modules', }); - if (hasUserSuppliedPattern && ArrayPrototypeEvery(glob.matchers, (m) => !m.hasMagic())) { return Readable.from((async function*() { let hasFile = false; @@ -498,7 +497,6 @@ function run(options) { PromisePrototypeThen(PromisePrototypeThen(PromiseResolve(setup?.(root)), runFiles), postRun); - return root.reporter; } diff --git a/test/parallel/test-runner-cli.js b/test/parallel/test-runner-cli.js index 704e72b2df49d6..4a980afa94d728 100644 --- a/test/parallel/test-runner-cli.js +++ b/test/parallel/test-runner-cli.js @@ -14,7 +14,7 @@ const testFixtures = fixtures.path('test-runner'); assert.strictEqual(child.status, 1); assert.strictEqual(child.signal, null); - assert.strictEqual(child.stdout.toString(), ''); + assert.strictEqual(child.stdout.toString(), 'TAP version 13\n'); assert.match(child.stderr.toString(), /^Could not find/); } @@ -28,10 +28,12 @@ const testFixtures = fixtures.path('test-runner'); assert.strictEqual(child.signal, null); assert.strictEqual(child.stderr.toString(), ''); const stdout = child.stdout.toString(); - assert.match(stdout, /ok 1 - this should pass/); - assert.match(stdout, /not ok 2 - this should fail/); - assert.match(stdout, /ok 3 - subdir.+subdir_test\.js/); - assert.match(stdout, /ok 4 - this should pass/); + + // TODO - Make sure not the same number + assert.match(stdout, /ok \d+ - this should pass/); + assert.match(stdout, /not ok \d+ - this should fail/); + assert.match(stdout, /ok \d+ - subdir.+subdir_test\.js/); + assert.match(stdout, /ok \d+ - this should pass/); } { @@ -40,10 +42,10 @@ const testFixtures = fixtures.path('test-runner'); const child = spawnSync(process.execPath, args, { cwd: testFixtures }); const stdout = child.stdout.toString(); - assert.match(stdout, /ok 1 - this should pass/); - assert.match(stdout, /not ok 2 - this should fail/); - assert.match(stdout, /ok 3 - subdir.+subdir_test\.js/); - assert.match(stdout, /ok 4 - this should pass/); + assert.match(stdout, /ok \d+ - this should pass/); + assert.match(stdout, /not ok \d+ - this should fail/); + assert.match(stdout, /ok \d+ - subdir.+subdir_test\.js/); + assert.match(stdout, /ok \d+ - this should pass/); assert.strictEqual(child.status, 1); assert.strictEqual(child.signal, null); assert.strictEqual(child.stderr.toString(), ''); @@ -83,10 +85,10 @@ const testFixtures = fixtures.path('test-runner'); assert.strictEqual(child.signal, null); assert.strictEqual(child.stderr.toString(), ''); const stdout = child.stdout.toString(); - assert.match(stdout, /ok 1 - this should pass/); - assert.match(stdout, /not ok 2 - this should fail/); - assert.match(stdout, /ok 3 - subdir.+subdir_test\.js/); - assert.match(stdout, /ok 4 - this should pass/); + assert.match(stdout, /ok \d+ - this should pass/); + assert.match(stdout, /not ok \d+ - this should fail/); + assert.match(stdout, /ok \d+ - subdir.+subdir_test\.js/); + assert.match(stdout, /ok \d+ - this should pass/); } { @@ -125,31 +127,31 @@ const testFixtures = fixtures.path('test-runner'); assert.strictEqual(child.stderr.toString(), ''); const stdout = child.stdout.toString(); assert.match(stdout, /# Subtest: this should pass/); - assert.match(stdout, /ok 1 - this should pass/); + assert.match(stdout, /ok \d+ - this should pass/); assert.match(stdout, / {2}---/); assert.match(stdout, / {2}duration_ms: .*/); assert.match(stdout, / {2}\.\.\./); assert.match(stdout, /# Subtest: .+invalid-tap\.js/); assert.match(stdout, /# invalid tap output/); - assert.match(stdout, /ok 2 - .+invalid-tap\.js/); + assert.match(stdout, /ok \d+ - .+invalid-tap\.js/); assert.match(stdout, /# Subtest: level 0a/); assert.match(stdout, / {4}# Subtest: level 1a/); - assert.match(stdout, / {4}ok 1 - level 1a/); + assert.match(stdout, / {4}ok \d+ - level 1a/); assert.match(stdout, / {4}# Subtest: level 1b/); - assert.match(stdout, / {4}not ok 2 - level 1b/); + assert.match(stdout, / {4}not ok \d+ - level 1b/); assert.match(stdout, / {6}code: 'ERR_TEST_FAILURE'/); assert.match(stdout, / {6}stack: |-'/); assert.match(stdout, / {8}TestContext\. .*/); assert.match(stdout, / {4}# Subtest: level 1c/); - assert.match(stdout, / {4}ok 3 - level 1c # SKIP aaa/); + assert.match(stdout, / {4}ok \d+ - level 1c # SKIP aaa/); assert.match(stdout, / {4}# Subtest: level 1d/); - assert.match(stdout, / {4}ok 4 - level 1d/); - assert.match(stdout, /not ok 3 - level 0a/); + assert.match(stdout, / {4}ok \d+ - level 1d/); + assert.match(stdout, /not ok \d+ - level 0a/); assert.match(stdout, / {2}error: '1 subtest failed'/); assert.match(stdout, /# Subtest: level 0b/); - assert.match(stdout, /not ok 4 - level 0b/); + assert.match(stdout, /not ok \d+ - level 0b/); assert.match(stdout, / {2}error: 'level 0b error'/); assert.match(stdout, /# tests 8/); assert.match(stdout, /# pass 4/); From 299cd6d1b32a95d75729b3a590b95789a82b78c2 Mon Sep 17 00:00:00 2001 From: Raz Luvaton <16746759+rluvaton@users.noreply.github.com> Date: Wed, 28 Jun 2023 19:52:00 +0300 Subject: [PATCH 03/19] test: use hard-coded pattern --- test/parallel/test-fs-glob.mjs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/test/parallel/test-fs-glob.mjs b/test/parallel/test-fs-glob.mjs index 119949b1a3e025..c0c7c7c7e7f215 100644 --- a/test/parallel/test-fs-glob.mjs +++ b/test/parallel/test-fs-glob.mjs @@ -302,8 +302,7 @@ const patterns = { test('Glob should be an iterator', () => { - const pattern = Object.keys(patterns)[0]; - const globInstance = new glob.Glob([pattern], { cwd: fixtureDir }); + const globInstance = new glob.Glob(['a/b/**'], { cwd: fixtureDir }); assert.strictEqual( typeof globInstance[Symbol.iterator], @@ -312,8 +311,7 @@ test('Glob should be an iterator', () => { ); }); test('Glob should be an async iterator', () => { - const pattern = Object.keys(patterns)[0]; - const globInstance = new glob.Glob([pattern], { cwd: fixtureDir }); + const globInstance = new glob.Glob(['a/b/**'], { cwd: fixtureDir }); assert.strictEqual( typeof globInstance[Symbol.asyncIterator], From 13f003ee58ba36f633d067c6e7ff5a879ac6bf97 Mon Sep 17 00:00:00 2001 From: Raz Luvaton <16746759+rluvaton@users.noreply.github.com> Date: Wed, 28 Jun 2023 19:54:53 +0300 Subject: [PATCH 04/19] Update lib/internal/test_runner/runner.js Co-authored-by: Jordan Harband --- lib/internal/test_runner/runner.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/internal/test_runner/runner.js b/lib/internal/test_runner/runner.js index 95ddcea6b3bbbb..3ebdb10d444cad 100644 --- a/lib/internal/test_runner/runner.js +++ b/lib/internal/test_runner/runner.js @@ -491,7 +491,7 @@ function run(options) { } return testFiles - .map(runSingleFile, { signal, concurrency }) + .map(runSingleFile, { __proto__: null, signal, concurrency }) .toArray(); }; From a3f75752f87748ef4187707bd3f4ce56b4c6ebdf Mon Sep 17 00:00:00 2001 From: Raz Luvaton <16746759+rluvaton@users.noreply.github.com> Date: Wed, 28 Jun 2023 19:55:02 +0300 Subject: [PATCH 05/19] Update lib/internal/test_runner/runner.js Co-authored-by: Jordan Harband --- lib/internal/test_runner/runner.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/internal/test_runner/runner.js b/lib/internal/test_runner/runner.js index 3ebdb10d444cad..c1cdccc6d2ad3f 100644 --- a/lib/internal/test_runner/runner.js +++ b/lib/internal/test_runner/runner.js @@ -89,7 +89,7 @@ function createTestFileList() { }); if (hasUserSuppliedPattern && ArrayPrototypeEvery(glob.matchers, (m) => !m.hasMagic())) { - return Readable.from((async function*() { + return Readable.from((async * () => { let hasFile = false; for await (const file of glob) { hasFile = true; From 00259ee3cf0b67417b3c518f5b33bd2d006456b8 Mon Sep 17 00:00:00 2001 From: Raz Luvaton <16746759+rluvaton@users.noreply.github.com> Date: Wed, 28 Jun 2023 20:03:39 +0300 Subject: [PATCH 06/19] test_runner: check node stream --- lib/internal/test_runner/runner.js | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/lib/internal/test_runner/runner.js b/lib/internal/test_runner/runner.js index 95ddcea6b3bbbb..f2f9c09c5aa0d4 100644 --- a/lib/internal/test_runner/runner.js +++ b/lib/internal/test_runner/runner.js @@ -29,6 +29,7 @@ const { const { spawn } = require('child_process'); const { Readable } = require('stream'); const { finished } = require('internal/streams/end-of-stream'); +const { isNodeStream } = require('internal/streams/utils'); const { DefaultDeserializer, DefaultSerializer } = require('v8'); // TODO(aduh95): switch to internal/readline/interface when backporting to Node.js 16.x is no longer a concern. const { createInterface } = require('readline'); @@ -478,21 +479,20 @@ function run(options) { const runFiles = () => { root.harness.bootstrapComplete = true; - if (ArrayIsArray(testFiles)) { - testFiles = PromiseResolve(testFiles); + if(isNodeStream(testFiles)) { + return testFiles + .map(runSingleFile, { signal, concurrency }) + .toArray(); } - // TODO - find better way to check if promise - if (typeof testFiles.then === 'function') { - return PromisePrototypeThen( - testFiles, - (testFilesArray) => SafePromiseAllSettledReturnVoid(testFilesArray, runSingleFile), - ); + if (ArrayIsArray(testFiles)) { + testFiles = PromiseResolve(testFiles); } - return testFiles - .map(runSingleFile, { signal, concurrency }) - .toArray(); + return PromisePrototypeThen( + testFiles, + (testFilesArray) => SafePromiseAllSettledReturnVoid(testFilesArray, runSingleFile), + ); }; PromisePrototypeThen(PromisePrototypeThen(PromiseResolve(setup?.(root)), runFiles), postRun); From afd06589b44eb424aa89eb68580e1dacb488017f Mon Sep 17 00:00:00 2001 From: Raz Luvaton <16746759+rluvaton@users.noreply.github.com> Date: Wed, 28 Jun 2023 20:09:27 +0300 Subject: [PATCH 07/19] test_runner: lint --- lib/internal/test_runner/runner.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/internal/test_runner/runner.js b/lib/internal/test_runner/runner.js index 81b7ff9cf5ce17..5bdf1dbad97552 100644 --- a/lib/internal/test_runner/runner.js +++ b/lib/internal/test_runner/runner.js @@ -479,7 +479,7 @@ function run(options) { const runFiles = () => { root.harness.bootstrapComplete = true; - if(isNodeStream(testFiles)) { + if (isNodeStream(testFiles)) { return testFiles .map(runSingleFile, { __proto__: null, signal, concurrency }) .toArray(); From cebf7267d52acce5540e0c7e3b2fe10281e6823a Mon Sep 17 00:00:00 2001 From: Raz Luvaton <16746759+rluvaton@users.noreply.github.com> Date: Wed, 28 Jun 2023 20:24:28 +0300 Subject: [PATCH 08/19] test_runner: add comment --- test/parallel/test-runner-concurrency.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/parallel/test-runner-concurrency.js b/test/parallel/test-runner-concurrency.js index 5a9ac8ec44beef..be569e18ce94df 100644 --- a/test/parallel/test-runner-concurrency.js +++ b/test/parallel/test-runner-concurrency.js @@ -88,6 +88,9 @@ describe('concurrency: true implies Infinity', { concurrency: true }, () => { } test('--test multiple files', { skip: os.availableParallelism() < 3 }, async () => { + + // TODO - fix this test, it failed due to concurrency not being set you + // waiting for first to finish before starting other await fs.writeFile(path.resolve(tmpdir.path, 'test-runner-concurrency'), ''); const { code, stderr } = await common.spawnPromisified(process.execPath, [ '--test', From 1d68c63602061d37fe916db2bab7a444d8b19c29 Mon Sep 17 00:00:00 2001 From: Raz Luvaton <16746759+rluvaton@users.noreply.github.com> Date: Wed, 28 Jun 2023 20:43:21 +0300 Subject: [PATCH 09/19] test_runner: fix concurrent test --- lib/internal/test_runner/runner.js | 3 ++- test/parallel/test-runner-concurrency.js | 3 --- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/lib/internal/test_runner/runner.js b/lib/internal/test_runner/runner.js index 5bdf1dbad97552..3e56cff835242c 100644 --- a/lib/internal/test_runner/runner.js +++ b/lib/internal/test_runner/runner.js @@ -24,6 +24,7 @@ const { StringPrototypeStartsWith, TypedArrayPrototypeGetLength, TypedArrayPrototypeSubarray, + NumberMAX_SAFE_INTEGER } = primordials; const { spawn } = require('child_process'); @@ -481,7 +482,7 @@ function run(options) { if (isNodeStream(testFiles)) { return testFiles - .map(runSingleFile, { __proto__: null, signal, concurrency }) + .map(runSingleFile, { __proto__: null, signal, concurrency: NumberMAX_SAFE_INTEGER }) .toArray(); } diff --git a/test/parallel/test-runner-concurrency.js b/test/parallel/test-runner-concurrency.js index be569e18ce94df..5a9ac8ec44beef 100644 --- a/test/parallel/test-runner-concurrency.js +++ b/test/parallel/test-runner-concurrency.js @@ -88,9 +88,6 @@ describe('concurrency: true implies Infinity', { concurrency: true }, () => { } test('--test multiple files', { skip: os.availableParallelism() < 3 }, async () => { - - // TODO - fix this test, it failed due to concurrency not being set you - // waiting for first to finish before starting other await fs.writeFile(path.resolve(tmpdir.path, 'test-runner-concurrency'), ''); const { code, stderr } = await common.spawnPromisified(process.execPath, [ '--test', From 0ea199fd4f58a31b812ea00892c81aa3c7ed6e05 Mon Sep 17 00:00:00 2001 From: Raz Luvaton <16746759+rluvaton@users.noreply.github.com> Date: Wed, 28 Jun 2023 20:47:16 +0300 Subject: [PATCH 10/19] test_runner: format --- lib/internal/test_runner/runner.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/internal/test_runner/runner.js b/lib/internal/test_runner/runner.js index 3e56cff835242c..da08c9d66f320b 100644 --- a/lib/internal/test_runner/runner.js +++ b/lib/internal/test_runner/runner.js @@ -24,7 +24,7 @@ const { StringPrototypeStartsWith, TypedArrayPrototypeGetLength, TypedArrayPrototypeSubarray, - NumberMAX_SAFE_INTEGER + NumberMAX_SAFE_INTEGER, } = primordials; const { spawn } = require('child_process'); From 98a9614467d5f3875ef63ca455b4f137e83fe5e2 Mon Sep 17 00:00:00 2001 From: Raz Luvaton <16746759+rluvaton@users.noreply.github.com> Date: Wed, 28 Jun 2023 20:52:57 +0300 Subject: [PATCH 11/19] test_runner: improve error message in test --- test/parallel/test-runner-coverage.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/parallel/test-runner-coverage.js b/test/parallel/test-runner-coverage.js index 9377f1bb509328..9b471a6772a71b 100644 --- a/test/parallel/test-runner-coverage.js +++ b/test/parallel/test-runner-coverage.js @@ -89,7 +89,7 @@ test('test tap coverage reporter', skipIfNoInspector, async (t) => { const result = spawnSync(process.execPath, args, options); const report = getTapCoverageFixtureReport(); - assert(result.stdout.toString().includes(report)); + assert.ok(result.stdout.toString().includes(report), new Error(`test stdout should include:\n${report}\n\nBut instead got:\n${result.stdout.toString()}`)); assert.strictEqual(result.stderr.toString(), ''); assert.strictEqual(result.status, 0); assert(findCoverageFileForPid(result.pid)); From 004e9159a892148addcfd7399dc49a60cdc0c110 Mon Sep 17 00:00:00 2001 From: Raz Luvaton <16746759+rluvaton@users.noreply.github.com> Date: Wed, 28 Jun 2023 21:31:05 +0300 Subject: [PATCH 12/19] test_runner: fix exit code test --- lib/internal/test_runner/harness.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/internal/test_runner/harness.js b/lib/internal/test_runner/harness.js index f150a8f5ed85c2..e3ab5711453d88 100644 --- a/lib/internal/test_runner/harness.js +++ b/lib/internal/test_runner/harness.js @@ -152,7 +152,7 @@ function setup(root) { const terminationHandler = async () => { await exitHandler(); - process.exit(); + process.exit(1); }; process.on('uncaughtException', exceptionHandler); From 0cb655f06d37edd3e0b5b0d0f6b8a1f2d11f37af Mon Sep 17 00:00:00 2001 From: Raz Luvaton <16746759+rluvaton@users.noreply.github.com> Date: Wed, 28 Jun 2023 21:36:08 +0300 Subject: [PATCH 13/19] test_runner: revert termination handler process.exit(1) --- lib/internal/test_runner/harness.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/internal/test_runner/harness.js b/lib/internal/test_runner/harness.js index e3ab5711453d88..f150a8f5ed85c2 100644 --- a/lib/internal/test_runner/harness.js +++ b/lib/internal/test_runner/harness.js @@ -152,7 +152,7 @@ function setup(root) { const terminationHandler = async () => { await exitHandler(); - process.exit(1); + process.exit(); }; process.on('uncaughtException', exceptionHandler); From 2062f53e407c7eaf7574eb779a823a84cbc643ce Mon Sep 17 00:00:00 2001 From: Raz Luvaton <16746759+rluvaton@users.noreply.github.com> Date: Wed, 28 Jun 2023 21:39:49 +0300 Subject: [PATCH 14/19] test_runner: termination handler process.exit(1) --- lib/internal/test_runner/harness.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/internal/test_runner/harness.js b/lib/internal/test_runner/harness.js index f150a8f5ed85c2..e3ab5711453d88 100644 --- a/lib/internal/test_runner/harness.js +++ b/lib/internal/test_runner/harness.js @@ -152,7 +152,7 @@ function setup(root) { const terminationHandler = async () => { await exitHandler(); - process.exit(); + process.exit(1); }; process.on('uncaughtException', exceptionHandler); From 517bdbcb88ac6391fbe1c8dbf4f6f4141fc5a264 Mon Sep 17 00:00:00 2001 From: Raz Luvaton <16746759+rluvaton@users.noreply.github.com> Date: Wed, 28 Jun 2023 21:43:21 +0300 Subject: [PATCH 15/19] test_runner: remove todo --- lib/internal/test_runner/runner.js | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/internal/test_runner/runner.js b/lib/internal/test_runner/runner.js index da08c9d66f320b..4ceb3c53e6b436 100644 --- a/lib/internal/test_runner/runner.js +++ b/lib/internal/test_runner/runner.js @@ -105,7 +105,6 @@ function createTestFileList() { })()); } - // TODO - make sure using the async... return Readable.from(glob); } From 5d34f67f892ec5203179137246528d9abb36dc90 Mon Sep 17 00:00:00 2001 From: Raz Luvaton <16746759+rluvaton@users.noreply.github.com> Date: Wed, 28 Jun 2023 22:27:20 +0300 Subject: [PATCH 16/19] test_runner: add signal support for glob --- lib/internal/fs/glob.js | 18 +++++++++++--- test/parallel/test-fs-glob.mjs | 43 ++++++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+), 3 deletions(-) diff --git a/lib/internal/fs/glob.js b/lib/internal/fs/glob.js index bfda2f3e7a9fd3..200d6df10a2751 100644 --- a/lib/internal/fs/glob.js +++ b/lib/internal/fs/glob.js @@ -9,7 +9,9 @@ const { const { validateFunction, validateObject, + validateAbortSignal, } = require('internal/validators'); +const { AbortError } = require('internal/errors'); const { ArrayFrom, @@ -186,10 +188,13 @@ class Glob { #subpatterns = new SafeMap(); constructor(patterns, options = kEmptyObject) { validateObject(options, 'options'); - const { exclude, cwd } = options; + const { exclude, cwd, signal } = options; if (exclude != null) { validateFunction(exclude, 'options.exclude'); } + if (signal != null) { + validateAbortSignal(options.signal, 'options.signal'); + } this.#root = cwd ?? '.'; this.#exclude = exclude; this.matchers = ArrayPrototypeMap(patterns, (pattern) => new (lazyMinimatch().Minimatch)(pattern, { @@ -202,6 +207,7 @@ class Glob { platform: process.platform, nocaseMagicOnly: true, })); + this.signal = signal; } globSync() { @@ -591,9 +597,11 @@ class Glob { } } - // TODO - add signal - // TODO - avoid copy async *[SymbolAsyncIterator]() { + if (this.signal?.aborted) { + throw new AbortError(); + } + ArrayPrototypePush(this.#queue, { __proto__: null, path: '.', @@ -609,6 +617,10 @@ class Glob { while (this.#queue.length > 0) { const item = ArrayPrototypePop(this.#queue); for (let i = 0; i < item.patterns.length; i++) { + if (this.signal?.aborted) { + throw new AbortError(); + } + yield* await this.#addSubpatterns(item.path, item.patterns[i]); } this.#subpatterns diff --git a/test/parallel/test-fs-glob.mjs b/test/parallel/test-fs-glob.mjs index c0c7c7c7e7f215..773ee6763d6186 100644 --- a/test/parallel/test-fs-glob.mjs +++ b/test/parallel/test-fs-glob.mjs @@ -310,6 +310,7 @@ test('Glob should be an iterator', () => { new TypeError('Glob instance should be iterable') ); }); + test('Glob should be an async iterator', () => { const globInstance = new glob.Glob(['a/b/**'], { cwd: fixtureDir }); @@ -320,6 +321,48 @@ test('Glob should be an async iterator', () => { ); }); +test('should throw an abort error when signal is already aborted', async () => { + const ac = new AbortController(); + + const actual = new glob.Glob(['a/b/**'], { cwd: fixtureDir, signal: ac.signal }); + + ac.abort(); + try { + // eslint-disable-next-line no-unused-vars + for await (const _ of actual) { + assert.strictEqual('', 'should not get any item'); + } + assert.strictEqual('', 'unreachable'); + } catch (err) { + assert.strictEqual(err.name, 'AbortError'); + } +}); + +test('should not continue after aborting', async () => { + const ac = new AbortController(); + const pattern = '**/a/**'; + + const actual = new glob.Glob([pattern], { cwd: fixtureDir, signal: ac.signal }); + + const matches = []; + try { + for await (const match of actual) { + matches.push(match); + + if (matches.length === 2) { + ac.abort(); + } + + if (matches.length > 2) { + assert.strictEqual('', 'should not get any more items after aborting'); + } + } + assert.strictEqual('', 'unreachable'); + } catch (err) { + assert.strictEqual(err.name, 'AbortError'); + } +}); + for (const [pattern, expected] of Object.entries(patterns)) { test(`${pattern} globSync`, () => { const actual = new glob.Glob([pattern], { cwd: fixtureDir }).globSync().sort(); From 4461789772cbd3b112dce7b60eaaa499c2ddb9a0 Mon Sep 17 00:00:00 2001 From: Raz Luvaton <16746759+rluvaton@users.noreply.github.com> Date: Wed, 28 Jun 2023 22:32:28 +0300 Subject: [PATCH 17/19] test_runner: simplify runner --- lib/internal/test_runner/runner.js | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/lib/internal/test_runner/runner.js b/lib/internal/test_runner/runner.js index 4ceb3c53e6b436..45d9f7eed31516 100644 --- a/lib/internal/test_runner/runner.js +++ b/lib/internal/test_runner/runner.js @@ -476,7 +476,7 @@ function run(options) { return subtest; }; - const runFiles = () => { + const runFiles = async () => { root.harness.bootstrapComplete = true; if (isNodeStream(testFiles)) { @@ -485,14 +485,7 @@ function run(options) { .toArray(); } - if (ArrayIsArray(testFiles)) { - testFiles = PromiseResolve(testFiles); - } - - return PromisePrototypeThen( - testFiles, - (testFilesArray) => SafePromiseAllSettledReturnVoid(testFilesArray, runSingleFile), - ); + return SafePromiseAllSettledReturnVoid(await testFiles, runSingleFile); }; PromisePrototypeThen(PromisePrototypeThen(PromiseResolve(setup?.(root)), runFiles), postRun); From 773f3cf2b8bdda4f3c042c576aae939c6a2be70b Mon Sep 17 00:00:00 2001 From: Raz Luvaton <16746759+rluvaton@users.noreply.github.com> Date: Wed, 28 Jun 2023 23:05:10 +0300 Subject: [PATCH 18/19] test_runner: remove comment --- test/parallel/test-runner-run.mjs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/test/parallel/test-runner-run.mjs b/test/parallel/test-runner-run.mjs index 657b9e6c69ccf4..0e92abd92bb5a2 100644 --- a/test/parallel/test-runner-run.mjs +++ b/test/parallel/test-runner-run.mjs @@ -7,10 +7,6 @@ import assert from 'node:assert'; const testFixtures = fixtures.path('test-runner'); -// TODO - fix this warning that got when running this test file: -// (node:61297) MaxListenersExceededWarning: Possible EventEmitter memory leak detected. -// 11 uncaughtException listeners added to [process]. Use emitter.setMaxListeners() to increase limit - describe('require(\'node:test\').run', { concurrency: true }, () => { it('should run with no tests', async () => { From dd09109b0d40a0a396d4dc81afee5509719ae12b Mon Sep 17 00:00:00 2001 From: Raz Luvaton <16746759+rluvaton@users.noreply.github.com> Date: Wed, 28 Jun 2023 23:45:24 +0300 Subject: [PATCH 19/19] test_runner: remove todo --- test/parallel/test-runner-cli.js | 51 +++++++++++++++++--------------- 1 file changed, 27 insertions(+), 24 deletions(-) diff --git a/test/parallel/test-runner-cli.js b/test/parallel/test-runner-cli.js index 4a980afa94d728..47bda19d827736 100644 --- a/test/parallel/test-runner-cli.js +++ b/test/parallel/test-runner-cli.js @@ -29,11 +29,11 @@ const testFixtures = fixtures.path('test-runner'); assert.strictEqual(child.stderr.toString(), ''); const stdout = child.stdout.toString(); - // TODO - Make sure not the same number - assert.match(stdout, /ok \d+ - this should pass/); - assert.match(stdout, /not ok \d+ - this should fail/); - assert.match(stdout, /ok \d+ - subdir.+subdir_test\.js/); - assert.match(stdout, /ok \d+ - this should pass/); + assert.match(stdout, /ok 1 - this should pass/); + assert.match(stdout, /not ok 2 - this should fail/); + assert.match(stdout, /ok 3 - this should pass/); + assert.match(stdout, /ok 4 - this should be skipped/); + assert.match(stdout, /ok 5 - subdir.+subdir_test\.js/); } { @@ -42,10 +42,11 @@ const testFixtures = fixtures.path('test-runner'); const child = spawnSync(process.execPath, args, { cwd: testFixtures }); const stdout = child.stdout.toString(); - assert.match(stdout, /ok \d+ - this should pass/); - assert.match(stdout, /not ok \d+ - this should fail/); - assert.match(stdout, /ok \d+ - subdir.+subdir_test\.js/); - assert.match(stdout, /ok \d+ - this should pass/); + assert.match(stdout, /ok 1 - this should pass/); + assert.match(stdout, /not ok 2 - this should fail/); + assert.match(stdout, /ok 3 - this should pass/); + assert.match(stdout, /ok 4 - this should be skipped/); + assert.match(stdout, /ok 5 - subdir.+subdir_test\.js/); assert.strictEqual(child.status, 1); assert.strictEqual(child.signal, null); assert.strictEqual(child.stderr.toString(), ''); @@ -85,10 +86,11 @@ const testFixtures = fixtures.path('test-runner'); assert.strictEqual(child.signal, null); assert.strictEqual(child.stderr.toString(), ''); const stdout = child.stdout.toString(); - assert.match(stdout, /ok \d+ - this should pass/); - assert.match(stdout, /not ok \d+ - this should fail/); - assert.match(stdout, /ok \d+ - subdir.+subdir_test\.js/); - assert.match(stdout, /ok \d+ - this should pass/); + assert.match(stdout, /ok 1 - this should pass/); + assert.match(stdout, /not ok 2 - this should fail/); + assert.match(stdout, /ok 3 - this should pass/); + assert.match(stdout, /ok 4 - this should be skipped/); + assert.match(stdout, /ok 5 - subdir.+subdir_test\.js/); } { @@ -126,33 +128,34 @@ const testFixtures = fixtures.path('test-runner'); assert.strictEqual(child.signal, null); assert.strictEqual(child.stderr.toString(), ''); const stdout = child.stdout.toString(); + assert.match(stdout, /# Subtest: this should pass/); - assert.match(stdout, /ok \d+ - this should pass/); + assert.match(stdout, /ok 1 - this should pass/); assert.match(stdout, / {2}---/); assert.match(stdout, / {2}duration_ms: .*/); assert.match(stdout, / {2}\.\.\./); - assert.match(stdout, /# Subtest: .+invalid-tap\.js/); - assert.match(stdout, /# invalid tap output/); - assert.match(stdout, /ok \d+ - .+invalid-tap\.js/); - assert.match(stdout, /# Subtest: level 0a/); assert.match(stdout, / {4}# Subtest: level 1a/); - assert.match(stdout, / {4}ok \d+ - level 1a/); + assert.match(stdout, / {4}ok 1 - level 1a/); assert.match(stdout, / {4}# Subtest: level 1b/); - assert.match(stdout, / {4}not ok \d+ - level 1b/); + assert.match(stdout, / {4}not ok 2 - level 1b/); assert.match(stdout, / {6}code: 'ERR_TEST_FAILURE'/); assert.match(stdout, / {6}stack: |-'/); assert.match(stdout, / {8}TestContext\. .*/); assert.match(stdout, / {4}# Subtest: level 1c/); - assert.match(stdout, / {4}ok \d+ - level 1c # SKIP aaa/); + assert.match(stdout, / {4}ok 3 - level 1c # SKIP aaa/); assert.match(stdout, / {4}# Subtest: level 1d/); - assert.match(stdout, / {4}ok \d+ - level 1d/); - assert.match(stdout, /not ok \d+ - level 0a/); + assert.match(stdout, / {4}ok 4 - level 1d/); + assert.match(stdout, /not ok 2 - level 0a/); assert.match(stdout, / {2}error: '1 subtest failed'/); assert.match(stdout, /# Subtest: level 0b/); - assert.match(stdout, /not ok \d+ - level 0b/); + assert.match(stdout, /not ok 3 - level 0b/); assert.match(stdout, / {2}error: 'level 0b error'/); + + assert.match(stdout, /# Subtest: .+invalid-tap\.js/); + assert.match(stdout, /# invalid tap output/); + assert.match(stdout, /ok 3 - .+invalid-tap\.js/); assert.match(stdout, /# tests 8/); assert.match(stdout, /# pass 4/); assert.match(stdout, /# fail 3/);