UNPKG

@tapjs/run

Version:

Command-line interface for the node-tap runner

164 lines 6.74 kB
import { plugin as BeforePlugin } from '@tapjs/before'; import { TapFile } from '@tapjs/core'; import { plugin as SpawnPlugin } from '@tapjs/spawn'; import { plugin as StdinPlugin } from '@tapjs/stdin'; import { glob } from 'glob'; import { stat } from 'node:fs/promises'; import { relative, resolve } from 'node:path'; import { rimraf } from 'rimraf'; import { runAfter } from './after.js'; import { runBefore } from './before.js'; import { build } from './build.js'; import { getCoverageMap } from './coverage-map.js'; import { executeTestSuite } from './execute-test-suite.js'; import { values } from './main-config.js'; import { outputDir } from './output-dir.js'; import { readSave } from './save-list.js'; import { testArgv } from './test-argv.js'; import { testIsSerial } from './test-is-serial.js'; const regExpEscape = (s) => s.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&'); const node = process.execPath; export const run = async (args, config) => { // timeout in ms for test subprocesses, has a default // so it'll always be set to a number. /* c8 ignore start */ const timeout = (config.get('timeout') ?? 30) * 1000; /* c8 ignore stop */ // these all default to an empty array /* c8 ignore start */ const testArgs = values['test-arg'] || []; const testEnv = values['test-env'] || []; const covExcludeOpt = values['coverage-exclude'] || []; /* c8 ignore stop */ process.env._TAPJS_PROCESSINFO_CWD_ = config.projectRoot; process.env._TAPJS_PROCESSINFO_EXCLUDE_ = String( // don't consider snapshots and fixtures, or else we'll // always think that all tests are new after generating! // TODO: these come from plugins, they should be able to export // this, like with testFileExtensions. /(^(node|tapmock):|[\\\/](\.tap|tap-snapshots)[\\\/])/); const argv = testArgv(config); let env = undefined; const saveList = await readSave(config); const map = await getCoverageMap(config); const covExcludeFiles = await glob(covExcludeOpt, { cwd: config.projectRoot, absolute: true, }); let _TAPJS_PROCESSINFO_COV_EXCLUDE_FILES_ = undefined; // always exclude the root node_modules from coverage. we don't report // on it, and it just takes up space in the .tap/coverage data. const _TAPJS_PROCESSINFO_COV_EXCLUDE_ = String(new RegExp('^' + regExpEscape(resolve(config.projectRoot, 'node_modules')))); return executeTestSuite(args, config, (t) => { // generate the env here, in case any plugins updated it env = { ...process.env }; for (const e of testEnv) { if (!e.includes('=')) { delete env[e]; } else { const split = e.split('='); const k = split[0]; const v = split.slice(1).join('='); env[k] = v; } } // always set in dev, and the applyPlugin behavior is well tested /* c8 ignore start */ const a = t.pluginLoaded(SpawnPlugin) ? t : t.applyPlugin(SpawnPlugin); const b = a.pluginLoaded(BeforePlugin) ? a : a.applyPlugin(BeforePlugin); const c = b.pluginLoaded(StdinPlugin) ? b : b.applyPlugin(StdinPlugin); /* c8 ignore stop */ return c; }, async (t) => { // if the configured plugins aren't what tap is built with, rebuild it // this won't affect the tap loaded here in the runner, but will in the // the test files we're about to run. const needBuild = t.pluginSignature !== config.pluginSignature; if (needBuild) await build([], config); // have to register before doing before/after because otherwise // that will trigger a pipe to stdout. t.register(); const rb = runBefore(t, argv, config); if (rb) covExcludeFiles.push(rb); const ra = runAfter(t, argv, config); if (ra) covExcludeFiles.push(ra); // don't delete old coverage if only running subset of suites if (!config.get('changed') && !saveList.length) { await rimraf(resolve(config.projectRoot, '.tap/processinfo')); await rimraf(resolve(config.projectRoot, '.tap/coverage')); } outputDir(t, config); }, async (t, file, files, hasReporter) => { if (!_TAPJS_PROCESSINFO_COV_EXCLUDE_FILES_) { _TAPJS_PROCESSINFO_COV_EXCLUDE_FILES_ = [ ...covExcludeFiles, ...files.map(f => resolve(config.projectRoot, f)), ].join('\n'); } /* c8 ignore start */ const stdio = hasReporter ? 'pipe' : 'inherit'; /* c8 ignore end */ if (file === '/dev/stdin') { if (files.length === 1) return t.stdinOnly(); else return t.stdin(); } const name = relative(config.projectRoot, file).replace(/\\/g, '/'); const mapped = map(name); let coveredFiles = mapped === null ? null : (await glob(mapped, { cwd: config.projectRoot, absolute: true, })); if (mapped?.length && !coveredFiles?.length) { // provided a glob expression that didn't match anything. // cover nothing. coveredFiles = null; } const _TAPJS_PROCESSINFO_COVERAGE_ = coveredFiles === null ? '0' : '1'; const _TAPJS_PROCESSINFO_COV_FILES_ = (coveredFiles || []).join('\n'); const buffered = !testIsSerial(file) && t.jobs > 1; const args = [...argv, file, ...testArgs]; // support stuff like `tap <(...)` or raw .tap files. const st = await stat(file); const raw = st.isCharacterDevice() || st.isBlockDevice() || st.isFIFO() || file.toLowerCase().endsWith('.tap'); return raw ? t.sub(TapFile, { at: null, cwd: config.projectRoot, buffered, filename: file, }) : t.spawn(node, args, { at: null, stack: '', buffered, timeout, stdio, env: { ...env, _TAPJS_PROCESSINFO_COVERAGE_, _TAPJS_PROCESSINFO_COV_FILES_, _TAPJS_PROCESSINFO_COV_EXCLUDE_FILES_, _TAPJS_PROCESSINFO_COV_EXCLUDE_, }, name, cwd: config.projectRoot, externalID: name, }); }); }; //# sourceMappingURL=run.js.map