@descryy/runtime-test-runner 0.4.12 → 0.4.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bounded-file.d.ts +18 -0
- package/dist/bounded-file.d.ts.map +1 -0
- package/dist/bounded-file.js +77 -0
- package/dist/bounded-file.js.map +1 -0
- package/dist/child-environment.d.ts +12 -0
- package/dist/child-environment.d.ts.map +1 -0
- package/dist/child-environment.js +124 -0
- package/dist/child-environment.js.map +1 -0
- package/dist/coverage-capture.d.ts +23 -0
- package/dist/coverage-capture.d.ts.map +1 -0
- package/dist/coverage-capture.js +165 -0
- package/dist/coverage-capture.js.map +1 -0
- package/dist/executed-coverage.d.ts +45 -0
- package/dist/executed-coverage.d.ts.map +1 -0
- package/dist/executed-coverage.js +143 -0
- package/dist/executed-coverage.js.map +1 -0
- package/dist/go-cover-profile.d.ts +29 -0
- package/dist/go-cover-profile.d.ts.map +1 -0
- package/dist/go-cover-profile.js +162 -0
- package/dist/go-cover-profile.js.map +1 -0
- package/dist/go-test.d.ts +29 -2
- package/dist/go-test.d.ts.map +1 -1
- package/dist/go-test.js +149 -30
- package/dist/go-test.js.map +1 -1
- package/dist/graph-mapping.d.ts +1 -0
- package/dist/graph-mapping.d.ts.map +1 -1
- package/dist/graph-mapping.js +78 -1
- package/dist/graph-mapping.js.map +1 -1
- package/dist/index.d.ts +21 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +11 -1
- package/dist/index.js.map +1 -1
- package/dist/lexers/c-family.d.ts +4 -0
- package/dist/lexers/c-family.d.ts.map +1 -0
- package/dist/lexers/c-family.js +221 -0
- package/dist/lexers/c-family.js.map +1 -0
- package/dist/lexers/chars.d.ts +7 -0
- package/dist/lexers/chars.d.ts.map +1 -0
- package/dist/lexers/chars.js +49 -0
- package/dist/lexers/chars.js.map +1 -0
- package/dist/lexers/index.d.ts +7 -0
- package/dist/lexers/index.d.ts.map +1 -0
- package/dist/lexers/index.js +5 -0
- package/dist/lexers/index.js.map +1 -0
- package/dist/lexers/python.d.ts +3 -0
- package/dist/lexers/python.d.ts.map +1 -0
- package/dist/lexers/python.js +101 -0
- package/dist/lexers/python.js.map +1 -0
- package/dist/quality/annotations.d.ts +14 -0
- package/dist/quality/annotations.d.ts.map +1 -0
- package/dist/quality/annotations.js +147 -0
- package/dist/quality/annotations.js.map +1 -0
- package/dist/quality/assertion-kinds.d.ts +18 -0
- package/dist/quality/assertion-kinds.d.ts.map +1 -0
- package/dist/quality/assertion-kinds.js +25 -0
- package/dist/quality/assertion-kinds.js.map +1 -0
- package/dist/quality/chains.d.ts +25 -0
- package/dist/quality/chains.d.ts.map +1 -0
- package/dist/quality/chains.js +129 -0
- package/dist/quality/chains.js.map +1 -0
- package/dist/quality/common.d.ts +31 -0
- package/dist/quality/common.d.ts.map +1 -0
- package/dist/quality/common.js +211 -0
- package/dist/quality/common.js.map +1 -0
- package/dist/quality/csharp-family.d.ts +9 -0
- package/dist/quality/csharp-family.d.ts.map +1 -0
- package/dist/quality/csharp-family.js +560 -0
- package/dist/quality/csharp-family.js.map +1 -0
- package/dist/quality/detect.d.ts +3 -0
- package/dist/quality/detect.d.ts.map +1 -0
- package/dist/quality/detect.js +348 -0
- package/dist/quality/detect.js.map +1 -0
- package/dist/quality/durations.d.ts +4 -0
- package/dist/quality/durations.d.ts.map +1 -0
- package/dist/quality/durations.js +177 -0
- package/dist/quality/durations.js.map +1 -0
- package/dist/quality/expressions.d.ts +7 -0
- package/dist/quality/expressions.d.ts.map +1 -0
- package/dist/quality/expressions.js +123 -0
- package/dist/quality/expressions.js.map +1 -0
- package/dist/quality/facts.d.ts +5 -0
- package/dist/quality/facts.d.ts.map +1 -0
- package/dist/quality/facts.js +183 -0
- package/dist/quality/facts.js.map +1 -0
- package/dist/quality/failure-of.d.ts +4 -0
- package/dist/quality/failure-of.d.ts.map +1 -0
- package/dist/quality/failure-of.js +233 -0
- package/dist/quality/failure-of.js.map +1 -0
- package/dist/quality/frameworks.d.ts +5 -0
- package/dist/quality/frameworks.d.ts.map +1 -0
- package/dist/quality/frameworks.js +20 -0
- package/dist/quality/frameworks.js.map +1 -0
- package/dist/quality/go-family.d.ts +6 -0
- package/dist/quality/go-family.d.ts.map +1 -0
- package/dist/quality/go-family.js +393 -0
- package/dist/quality/go-family.js.map +1 -0
- package/dist/quality/index.d.ts +10 -0
- package/dist/quality/index.d.ts.map +1 -0
- package/dist/quality/index.js +19 -0
- package/dist/quality/index.js.map +1 -0
- package/dist/quality/java-family.d.ts +12 -0
- package/dist/quality/java-family.d.ts.map +1 -0
- package/dist/quality/java-family.js +529 -0
- package/dist/quality/java-family.js.map +1 -0
- package/dist/quality/js-assertions.d.ts +11 -0
- package/dist/quality/js-assertions.d.ts.map +1 -0
- package/dist/quality/js-assertions.js +297 -0
- package/dist/quality/js-assertions.js.map +1 -0
- package/dist/quality/js-family.d.ts +3 -0
- package/dist/quality/js-family.d.ts.map +1 -0
- package/dist/quality/js-family.js +470 -0
- package/dist/quality/js-family.js.map +1 -0
- package/dist/quality/python-family.d.ts +6 -0
- package/dist/quality/python-family.d.ts.map +1 -0
- package/dist/quality/python-family.js +786 -0
- package/dist/quality/python-family.js.map +1 -0
- package/dist/quality/scope.d.ts +26 -0
- package/dist/quality/scope.d.ts.map +1 -0
- package/dist/quality/scope.js +33 -0
- package/dist/quality/scope.js.map +1 -0
- package/dist/quality/selectors.d.ts +14 -0
- package/dist/quality/selectors.d.ts.map +1 -0
- package/dist/quality/selectors.js +137 -0
- package/dist/quality/selectors.js.map +1 -0
- package/dist/quality/statuses.d.ts +7 -0
- package/dist/quality/statuses.d.ts.map +1 -0
- package/dist/quality/statuses.js +143 -0
- package/dist/quality/statuses.js.map +1 -0
- package/dist/quality/stream.d.ts +23 -0
- package/dist/quality/stream.d.ts.map +1 -0
- package/dist/quality/stream.js +194 -0
- package/dist/quality/stream.js.map +1 -0
- package/dist/quality/types.d.ts +88 -0
- package/dist/quality/types.d.ts.map +1 -0
- package/dist/quality/types.js +2 -0
- package/dist/quality/types.js.map +1 -0
- package/dist/run-timeout.d.ts +25 -0
- package/dist/run-timeout.d.ts.map +1 -0
- package/dist/run-timeout.js +52 -0
- package/dist/run-timeout.js.map +1 -0
- package/dist/runner-wrappers.d.ts +74 -0
- package/dist/runner-wrappers.d.ts.map +1 -0
- package/dist/runner-wrappers.js +604 -0
- package/dist/runner-wrappers.js.map +1 -0
- package/dist/swift-xctest.d.ts +15 -2
- package/dist/swift-xctest.d.ts.map +1 -1
- package/dist/swift-xctest.js +143 -20
- package/dist/swift-xctest.js.map +1 -1
- package/dist/test-runner-collector.d.ts +56 -6
- package/dist/test-runner-collector.d.ts.map +1 -1
- package/dist/test-runner-collector.js +265 -113
- package/dist/test-runner-collector.js.map +1 -1
- package/dist/testgen/csharp-facts.d.ts +3 -0
- package/dist/testgen/csharp-facts.d.ts.map +1 -0
- package/dist/testgen/csharp-facts.js +306 -0
- package/dist/testgen/csharp-facts.js.map +1 -0
- package/dist/testgen/csharp-profile.d.ts +14 -0
- package/dist/testgen/csharp-profile.d.ts.map +1 -0
- package/dist/testgen/csharp-profile.js +233 -0
- package/dist/testgen/csharp-profile.js.map +1 -0
- package/dist/testgen/declarations.d.ts +10 -0
- package/dist/testgen/declarations.d.ts.map +1 -0
- package/dist/testgen/declarations.js +110 -0
- package/dist/testgen/declarations.js.map +1 -0
- package/dist/testgen/facts-common.d.ts +33 -0
- package/dist/testgen/facts-common.d.ts.map +1 -0
- package/dist/testgen/facts-common.js +76 -0
- package/dist/testgen/facts-common.js.map +1 -0
- package/dist/testgen/failure.d.ts +4 -0
- package/dist/testgen/failure.d.ts.map +1 -0
- package/dist/testgen/failure.js +106 -0
- package/dist/testgen/failure.js.map +1 -0
- package/dist/testgen/frameworks.d.ts +6 -0
- package/dist/testgen/frameworks.d.ts.map +1 -0
- package/dist/testgen/frameworks.js +516 -0
- package/dist/testgen/frameworks.js.map +1 -0
- package/dist/testgen/go-facts.d.ts +11 -0
- package/dist/testgen/go-facts.d.ts.map +1 -0
- package/dist/testgen/go-facts.js +237 -0
- package/dist/testgen/go-facts.js.map +1 -0
- package/dist/testgen/go-profile.d.ts +6 -0
- package/dist/testgen/go-profile.d.ts.map +1 -0
- package/dist/testgen/go-profile.js +155 -0
- package/dist/testgen/go-profile.js.map +1 -0
- package/dist/testgen/index.d.ts +28 -0
- package/dist/testgen/index.d.ts.map +1 -0
- package/dist/testgen/index.js +21 -0
- package/dist/testgen/index.js.map +1 -0
- package/dist/testgen/java-build.d.ts +13 -0
- package/dist/testgen/java-build.d.ts.map +1 -0
- package/dist/testgen/java-build.js +250 -0
- package/dist/testgen/java-build.js.map +1 -0
- package/dist/testgen/java-facts.d.ts +3 -0
- package/dist/testgen/java-facts.d.ts.map +1 -0
- package/dist/testgen/java-facts.js +349 -0
- package/dist/testgen/java-facts.js.map +1 -0
- package/dist/testgen/java-profile.d.ts +13 -0
- package/dist/testgen/java-profile.d.ts.map +1 -0
- package/dist/testgen/java-profile.js +194 -0
- package/dist/testgen/java-profile.js.map +1 -0
- package/dist/testgen/languages.d.ts +11 -0
- package/dist/testgen/languages.d.ts.map +1 -0
- package/dist/testgen/languages.js +63 -0
- package/dist/testgen/languages.js.map +1 -0
- package/dist/testgen/lexer.d.ts +19 -0
- package/dist/testgen/lexer.d.ts.map +1 -0
- package/dist/testgen/lexer.js +257 -0
- package/dist/testgen/lexer.js.map +1 -0
- package/dist/testgen/mutant-core.d.ts +19 -0
- package/dist/testgen/mutant-core.d.ts.map +1 -0
- package/dist/testgen/mutant-core.js +39 -0
- package/dist/testgen/mutant-core.js.map +1 -0
- package/dist/testgen/mutants-lang.d.ts +6 -0
- package/dist/testgen/mutants-lang.d.ts.map +1 -0
- package/dist/testgen/mutants-lang.js +291 -0
- package/dist/testgen/mutants-lang.js.map +1 -0
- package/dist/testgen/mutants.d.ts +9 -0
- package/dist/testgen/mutants.d.ts.map +1 -0
- package/dist/testgen/mutants.js +148 -0
- package/dist/testgen/mutants.js.map +1 -0
- package/dist/testgen/paths.d.ts +32 -0
- package/dist/testgen/paths.d.ts.map +1 -0
- package/dist/testgen/paths.js +211 -0
- package/dist/testgen/paths.js.map +1 -0
- package/dist/testgen/process.d.ts +19 -0
- package/dist/testgen/process.d.ts.map +1 -0
- package/dist/testgen/process.js +89 -0
- package/dist/testgen/process.js.map +1 -0
- package/dist/testgen/profile-failure.d.ts +7 -0
- package/dist/testgen/profile-failure.d.ts.map +1 -0
- package/dist/testgen/profile-failure.js +29 -0
- package/dist/testgen/profile-failure.js.map +1 -0
- package/dist/testgen/python-facts.d.ts +11 -0
- package/dist/testgen/python-facts.d.ts.map +1 -0
- package/dist/testgen/python-facts.js +380 -0
- package/dist/testgen/python-facts.js.map +1 -0
- package/dist/testgen/python-profile.d.ts +4 -0
- package/dist/testgen/python-profile.d.ts.map +1 -0
- package/dist/testgen/python-profile.js +344 -0
- package/dist/testgen/python-profile.js.map +1 -0
- package/dist/testgen/source-facts.d.ts +30 -0
- package/dist/testgen/source-facts.d.ts.map +1 -0
- package/dist/testgen/source-facts.js +497 -0
- package/dist/testgen/source-facts.js.map +1 -0
- package/dist/testgen/stubs.d.ts +4 -0
- package/dist/testgen/stubs.d.ts.map +1 -0
- package/dist/testgen/stubs.js +66 -0
- package/dist/testgen/stubs.js.map +1 -0
- package/dist/testgen/toolchain.d.ts +23 -0
- package/dist/testgen/toolchain.d.ts.map +1 -0
- package/dist/testgen/toolchain.js +201 -0
- package/dist/testgen/toolchain.js.map +1 -0
- package/dist/testgen/types.d.ts +67 -0
- package/dist/testgen/types.d.ts.map +1 -0
- package/dist/testgen/types.js +2 -0
- package/dist/testgen/types.js.map +1 -0
- package/dist/v8-coverage.d.ts +21 -0
- package/dist/v8-coverage.d.ts.map +1 -0
- package/dist/v8-coverage.js +217 -0
- package/dist/v8-coverage.js.map +1 -0
- package/dist/vitest-reporter.d.ts +46 -13
- package/dist/vitest-reporter.d.ts.map +1 -1
- package/dist/vitest-reporter.js +119 -26
- package/dist/vitest-reporter.js.map +1 -1
- package/dist/vitest-version.d.ts +21 -0
- package/dist/vitest-version.d.ts.map +1 -0
- package/dist/vitest-version.js +98 -0
- package/dist/vitest-version.js.map +1 -0
- package/package.json +14 -8
- package/reporters/junit/classes/DescryJUnitListener.class +0 -0
- package/reporters/junit/classes/META-INF/services/org.junit.platform.launcher.TestExecutionListener +1 -0
- package/reporters/minitest/descry_minitest_reporter.rb +150 -0
- package/reporters/phpunit/DescryPhpUnitExtension.php +209 -0
- package/reporters/pytest/descry_pytest_plugin.py +397 -0
- package/reporters/rspec/descry_rspec_formatter.rb +175 -0
- package/reporters/xunit/bin/Release/netstandard2.0/Descry.TestLogger.deps.json +203 -0
- package/reporters/xunit/bin/Release/netstandard2.0/Descry.TestLogger.dll +0 -0
- package/reporters/xunit/bin/Release/netstandard2.0/Descry.TestLogger.pdb +0 -0
|
@@ -99,9 +99,16 @@
|
|
|
99
99
|
* works and is faster while iterating on one file.
|
|
100
100
|
*/
|
|
101
101
|
import { fileURLToPath } from "node:url";
|
|
102
|
-
import {
|
|
103
|
-
import {
|
|
102
|
+
import { buildChildEnvironment } from "./child-environment.js";
|
|
103
|
+
import { NO_SANDBOX_DISCLOSURES, spawnProcess, tokenizeCommand } from "@descryy/runtime-controller";
|
|
104
|
+
import { GapResynchronizer, LineSplitter } from "@descryy/runtime-backend-observation";
|
|
105
|
+
import { armRunTimeout, resolveRunTimeout } from "./run-timeout.js";
|
|
104
106
|
import { COLLECTOR_VERSION } from "./collector-version.js";
|
|
107
|
+
import { beginCoverageCapture } from "./coverage-capture.js";
|
|
108
|
+
import { unavailableCoverage, withCoverageDirectoryAllowed } from "./executed-coverage.js";
|
|
109
|
+
import { createGoTestCollector } from "./go-test.js";
|
|
110
|
+
import { locateWrapperEnvironment, unwrapRunnerCommand, wrappedSpawnArgs } from "./runner-wrappers.js";
|
|
111
|
+
import { planVitestRun, vitestLocationDegradations } from "./vitest-version.js";
|
|
105
112
|
/**
|
|
106
113
|
* The version of the NDJSON envelope every reporter speaks (RT-130).
|
|
107
114
|
*
|
|
@@ -381,10 +388,11 @@ const PROFILES = [
|
|
|
381
388
|
? "command already configures --reporter -- this collector needs to own the reporter to observe structured events, and co-existing safely with an unknown one is not attempted. Remove the existing --reporter flag."
|
|
382
389
|
: null;
|
|
383
390
|
},
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
391
|
+
withReporterFlags: (args, options) => [
|
|
392
|
+
...args,
|
|
393
|
+
`--reporter=${VITEST_REPORTER_MODULE_PATH}`,
|
|
394
|
+
...(options?.vitestTaskLocation === true ? ["--includeTaskLocation"] : []),
|
|
395
|
+
],
|
|
388
396
|
},
|
|
389
397
|
{
|
|
390
398
|
kind: "junit",
|
|
@@ -614,10 +622,6 @@ const PROFILES = [
|
|
|
614
622
|
export function supportedRunnerExamples() {
|
|
615
623
|
return PROFILES.map((profile) => profile.declaredAs);
|
|
616
624
|
}
|
|
617
|
-
/** `true` for the one command shape neither `discoverRunner` nor `matchRunner` will ever match to a profile — see `discoverRunner`'s own npx comment for why unwrapping it is not attempted. */
|
|
618
|
-
function isNpxWrapped(baseName) {
|
|
619
|
-
return baseName === "npx" || baseName === "npx.cmd";
|
|
620
|
-
}
|
|
621
625
|
/** The one place `discoverRunner` and `matchRunner` both ask "which profile matches this command" — a shared call so the two can never drift apart the way the hardcoded refusal list once did (B4). */
|
|
622
626
|
function findMatchingProfile(baseName, args) {
|
|
623
627
|
return PROFILES.find((candidate) => candidate.matches(baseName, args));
|
|
@@ -664,10 +668,11 @@ export function matchRunner(rawCommand) {
|
|
|
664
668
|
const tokenized = tokenizeCommand(rawCommand);
|
|
665
669
|
if (!tokenized.ok)
|
|
666
670
|
return null;
|
|
667
|
-
const
|
|
668
|
-
|
|
669
|
-
if (isNpxWrapped(baseName))
|
|
671
|
+
const unwrapped = unwrapRunnerCommand(tokenized.command, tokenized.args);
|
|
672
|
+
if (unwrapped.kind === "refused")
|
|
670
673
|
return null;
|
|
674
|
+
const { command, args } = unwrapped.kind === "wrapped" ? unwrapped.inner : tokenized;
|
|
675
|
+
const baseName = command.split(/[\\/]/).pop() ?? command;
|
|
671
676
|
const profile = findMatchingProfile(baseName, args);
|
|
672
677
|
return profile === undefined ? null : { kind: profile.kind, declaredAs: profile.declaredAs };
|
|
673
678
|
}
|
|
@@ -687,23 +692,16 @@ export function discoverRunner(rawCommand) {
|
|
|
687
692
|
if (!tokenized.ok) {
|
|
688
693
|
return { ok: false, reason: tokenized.reason };
|
|
689
694
|
}
|
|
690
|
-
|
|
695
|
+
// **A wrapper (`uv run`, `npx`, `poetry run`, ...) is split by its declared grammar**
|
|
696
|
+
// (`runner-wrappers.ts`), never guessed: a flag the grammar does not know is refused by
|
|
697
|
+
// name, since whether it takes a value decides which token is the runner. The profile then
|
|
698
|
+
// matches the runner's own command exactly as it would a bare one.
|
|
699
|
+
const unwrapped = unwrapRunnerCommand(tokenized.command, tokenized.args);
|
|
700
|
+
if (unwrapped.kind === "refused")
|
|
701
|
+
return { ok: false, reason: unwrapped.reason };
|
|
702
|
+
const { command, args } = unwrapped.kind === "wrapped" ? unwrapped.inner : tokenized;
|
|
703
|
+
const wrapper = unwrapped.kind === "wrapped" ? unwrapped.wrapper : undefined;
|
|
691
704
|
const baseName = command.split(/[\\/]/).pop() ?? command;
|
|
692
|
-
// **`npx jest` is refused by name, not supported and not lumped in with
|
|
693
|
-
// an unrecognised command.** Unwrapping it means deciding which tokens
|
|
694
|
-
// belong to npx and which to the runner, and `npx -p vitest@4 vitest run`
|
|
695
|
-
// makes that genuinely ambiguous -- a wrong split silently drops a flag
|
|
696
|
-
// the caller wrote. The runner's own binary is one path away
|
|
697
|
-
// (`node_modules/.bin/jest`), so the refusal names the fix rather than
|
|
698
|
-
// guessing at one.
|
|
699
|
-
if (isNpxWrapped(baseName)) {
|
|
700
|
-
return {
|
|
701
|
-
ok: false,
|
|
702
|
-
reason: `"${rawCommand}" runs the runner through npx, whose own arguments cannot be told apart from the runner's ` +
|
|
703
|
-
"without guessing (`npx -p vitest@4 vitest run`). Declare the runner binary directly -- e.g. " +
|
|
704
|
-
"`node_modules/.bin/jest ...` or `node_modules/.bin/vitest run ...`.",
|
|
705
|
-
};
|
|
706
|
-
}
|
|
707
705
|
const profile = findMatchingProfile(baseName, args);
|
|
708
706
|
if (profile === undefined) {
|
|
709
707
|
return {
|
|
@@ -719,10 +717,10 @@ export function discoverRunner(rawCommand) {
|
|
|
719
717
|
if (refusal !== null) {
|
|
720
718
|
return { ok: false, reason: refusal };
|
|
721
719
|
}
|
|
722
|
-
return { ok: true, kind: profile.kind, command, args };
|
|
720
|
+
return { ok: true, kind: profile.kind, command, args, ...(wrapper === undefined ? {} : { wrapper }) };
|
|
723
721
|
}
|
|
724
|
-
export function reporterFlagsFor(kind, args) {
|
|
725
|
-
return PROFILES.find((profile) => profile.kind === kind).withReporterFlags(args);
|
|
722
|
+
export function reporterFlagsFor(kind, args, options) {
|
|
723
|
+
return PROFILES.find((profile) => profile.kind === kind).withReporterFlags(args, options);
|
|
726
724
|
}
|
|
727
725
|
/** `{}` for every profile that has no `reporterEnv` — `start()`'s merge then adds nothing. */
|
|
728
726
|
export function reporterEnvFor(kind, args) {
|
|
@@ -824,65 +822,15 @@ function unittestSkipReason(outcome) {
|
|
|
824
822
|
return "disabled";
|
|
825
823
|
return null;
|
|
826
824
|
}
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
* output into the same `NodeTestEvent` shape every other profile's reporter
|
|
830
|
-
* emits as real NDJSON -- so `createEventTranslator` never has to know this
|
|
831
|
-
* event did not arrive as JSON. See the file-level doc comment ("unittest is
|
|
832
|
-
* the one profile...") for why a reporter is not possible here at all.
|
|
833
|
-
*
|
|
834
|
-
* **Measured directly against a real `python -m unittest -v` run**, not
|
|
835
|
-
* assumed from documentation:
|
|
836
|
-
*
|
|
837
|
-
* ```
|
|
838
|
-
* test_pass (test_sample.TestSample.test_pass) ... ok
|
|
839
|
-
* test_fail (test_sample.TestSample.test_fail) ... FAIL
|
|
840
|
-
* test_error (test_sample.TestSample.test_error) ... ERROR
|
|
841
|
-
* test_skip (test_sample.TestSample.test_skip) ... skipped 'not ready'
|
|
842
|
-
* test_xfail (test_sample.TestSample.test_xfail) ... expected failure
|
|
843
|
-
* test_xpass (test_sample.TestSample.test_xpass) ... unexpected success
|
|
844
|
-
* ```
|
|
845
|
-
*
|
|
846
|
-
* and, for a test with a docstring, split across two lines instead of one
|
|
847
|
-
* (`getDescription` returns `"%s\n%s" % (str(test), doc_first_line)` when a
|
|
848
|
-
* docstring is present, `str(test)` alone otherwise):
|
|
849
|
-
*
|
|
850
|
-
* ```
|
|
851
|
-
* test_with_doc (test_doc.TestDoc.test_with_doc)
|
|
852
|
-
* This is a docstring. ... ok
|
|
853
|
-
* ```
|
|
854
|
-
*
|
|
855
|
-
* A header-only line is trusted for at most one line of lookahead: whatever
|
|
856
|
-
* arrives next either completes it (a docstring's first line, ending in
|
|
857
|
-
* ` ... <outcome>`) or does not, and either way the header is then
|
|
858
|
-
* discarded -- never left pending indefinitely waiting for a continuation
|
|
859
|
-
* that a stray print from the test body or an unrecognised outcome word
|
|
860
|
-
* might otherwise be mistaken for. A line the parser cannot place at all
|
|
861
|
-
* (the `======`/`----` banners, a traceback body, `Ran N tests...`, `OK`,
|
|
862
|
-
* `FAILED (...)`, or the test process's own stdout) is silently ignored --
|
|
863
|
-
* the same "unparseable output is ordinary and expected" rule every
|
|
864
|
-
* NDJSON-based profile already applies to its own noise.
|
|
865
|
-
*
|
|
866
|
-
* **What this deliberately does not attempt.** unittest's per-test line
|
|
867
|
-
* carries a qualified name only, never a file or line number -- those
|
|
868
|
-
* appear solely in the traceback section printed after every test in the
|
|
869
|
-
* run has already finished, under a `======`-delimited header repeating the
|
|
870
|
-
* test's own description. Re-associating that traceback with the correct
|
|
871
|
-
* earlier result, reliably, across a run with many tests (some sharing a
|
|
872
|
-
* class or method name across modules), is exactly the kind of inference
|
|
873
|
-
* precision-over-recall exists to forbid -- so no attempt is made, every
|
|
874
|
-
* `sourceLocation` this parser produces is null, and every FAILED/ERROR
|
|
875
|
-
* carries no `error` detail either, rather than one guessed from prose.
|
|
876
|
-
* `expected failure`/`unexpected success` fold into `TEST_SKIPPED`/
|
|
877
|
-
* `TEST_PASSED` respectively -- the same choice the pytest reporter already
|
|
878
|
-
* made for `xfail`/`xpass`, not a new one invented here. An outcome word
|
|
879
|
-
* this parser has never measured (a future CPython addition) still gets a
|
|
880
|
-
* `TEST_STARTED` -- the test did start -- but no paired result, on the same
|
|
881
|
-
* "report nothing rather than guess" rule.
|
|
882
|
-
*/
|
|
825
|
+
const UNITTEST_RAN_TRAILER = /^Ran (\d+) tests? in \S+s?$/;
|
|
826
|
+
const UNITTEST_STATUS_TRAILER = /^(OK|FAILED)(?: \(.*\))?$/;
|
|
883
827
|
export function createUnittestResultParser() {
|
|
884
828
|
let pendingHeader = null;
|
|
829
|
+
let resultLines = 0;
|
|
830
|
+
let declaredCount = null;
|
|
831
|
+
let sawStatusTrailer = false;
|
|
885
832
|
function eventsFor(header, outcome) {
|
|
833
|
+
resultLines += 1;
|
|
886
834
|
const start = { type: "test:start", data: { name: header.name, qualifiedName: header.qualifiedName } };
|
|
887
835
|
const skipReason = unittestSkipReason(outcome);
|
|
888
836
|
if (skipReason !== null) {
|
|
@@ -897,7 +845,21 @@ export function createUnittestResultParser() {
|
|
|
897
845
|
return [start];
|
|
898
846
|
}
|
|
899
847
|
return {
|
|
848
|
+
finish() {
|
|
849
|
+
if (declaredCount === null || !sawStatusTrailer) {
|
|
850
|
+
return { reconciled: false, reason: "the run printed no complete `Ran N tests` and OK/FAILED trailer (it was cut short, or the output is not unittest's), so the per-test lines could not be reconciled against the runner's own count" };
|
|
851
|
+
}
|
|
852
|
+
if (declaredCount !== resultLines) {
|
|
853
|
+
return { reconciled: false, reason: `unittest reported ${String(declaredCount)} test(s) but ${String(resultLines)} per-test result line(s) were parsed, so some lines were missed or printed by the code under test` };
|
|
854
|
+
}
|
|
855
|
+
return { reconciled: true, reason: null };
|
|
856
|
+
},
|
|
900
857
|
feed(line) {
|
|
858
|
+
const ran = UNITTEST_RAN_TRAILER.exec(line);
|
|
859
|
+
if (ran !== null)
|
|
860
|
+
declaredCount = Number(ran[1]);
|
|
861
|
+
else if (declaredCount !== null && UNITTEST_STATUS_TRAILER.test(line))
|
|
862
|
+
sawStatusTrailer = true;
|
|
901
863
|
const direct = UNITTEST_RESULT_LINE.exec(line);
|
|
902
864
|
if (direct !== null) {
|
|
903
865
|
pendingHeader = null;
|
|
@@ -1097,35 +1059,72 @@ export function createEventTranslator(emit, service, processId) {
|
|
|
1097
1059
|
}
|
|
1098
1060
|
return { handle, flush, coverage: () => ({ testsObserved, idsPresent, notRun }) };
|
|
1099
1061
|
}
|
|
1062
|
+
export function runnerOutputGapReason(droppedChars) {
|
|
1063
|
+
return (`${String(droppedChars)} characters of the test runner's output were dropped before they could be read; ` +
|
|
1064
|
+
"any test result printed in that span was not observed, so the results reported for this run are incomplete.");
|
|
1065
|
+
}
|
|
1066
|
+
export function reportRunnerOutputGap(emit, details) {
|
|
1067
|
+
emit({
|
|
1068
|
+
timestamp: new Date().toISOString(),
|
|
1069
|
+
source: "test-runner",
|
|
1070
|
+
service: details.service,
|
|
1071
|
+
process: details.processId,
|
|
1072
|
+
traceId: null,
|
|
1073
|
+
requestId: null,
|
|
1074
|
+
correlationId: null,
|
|
1075
|
+
graphNodeId: null,
|
|
1076
|
+
stackTrace: null,
|
|
1077
|
+
confidence: 1,
|
|
1078
|
+
redactionStatus: "pending-redaction",
|
|
1079
|
+
collectorVersion: COLLECTOR_VERSION,
|
|
1080
|
+
eventType: "COLLECTOR_ERROR",
|
|
1081
|
+
payload: { collector: details.collector, reason: runnerOutputGapReason(details.droppedChars), droppedChars: details.droppedChars },
|
|
1082
|
+
sourceLocation: null,
|
|
1083
|
+
});
|
|
1084
|
+
}
|
|
1100
1085
|
/**
|
|
1101
|
-
* Feeds complete NDJSON lines from `managed`'s
|
|
1086
|
+
* Feeds complete NDJSON lines from `managed`'s output to
|
|
1102
1087
|
* `onLine`, exactly once each, as they arrive -- streaming-first, not
|
|
1103
1088
|
* batched until the process exits. A narrower, single-purpose relative of
|
|
1104
|
-
* `orchestrator`'s `createPollingProcessOutputSource` (same
|
|
1105
|
-
*
|
|
1089
|
+
* `orchestrator`'s `createPollingProcessOutputSource` (same cursor read and
|
|
1090
|
+
* gap handling, same "ends only once the process is finished AND the buffer
|
|
1106
1091
|
* stopped growing" ordering) rather than a shared dependency on it: this
|
|
1107
1092
|
* collector only ever needs complete lines fed to a callback, not the
|
|
1108
1093
|
* general `ProcessOutputSource` shape, and taking a dependency on
|
|
1109
1094
|
* `@descryy/runtime-orchestrator` -- the assembly layer -- from a collector
|
|
1110
1095
|
* would invert the direction every other package in this repo depends in.
|
|
1111
1096
|
*/
|
|
1112
|
-
export async function pumpLines(managed, onLine,
|
|
1097
|
+
export async function pumpLines(managed, onLine, options = {}) {
|
|
1098
|
+
const pollIntervalMs = options.pollIntervalMs ?? 20;
|
|
1113
1099
|
const splitter = new LineSplitter();
|
|
1114
|
-
|
|
1100
|
+
const resync = new GapResynchronizer();
|
|
1101
|
+
let cursor = 0;
|
|
1115
1102
|
let finished = false;
|
|
1116
1103
|
void managed.waitForExit().then(() => {
|
|
1117
1104
|
finished = true;
|
|
1118
1105
|
});
|
|
1106
|
+
function reportGap() {
|
|
1107
|
+
const gap = resync.take();
|
|
1108
|
+
if (gap !== null)
|
|
1109
|
+
options.onGap?.(gap.droppedChars);
|
|
1110
|
+
}
|
|
1119
1111
|
for (;;) {
|
|
1120
|
-
const
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1112
|
+
const exitedBeforeRead = finished;
|
|
1113
|
+
const read = managed.readOutputFrom(cursor);
|
|
1114
|
+
const advanced = read.cursor !== cursor || read.droppedChars > 0;
|
|
1115
|
+
cursor = read.cursor;
|
|
1116
|
+
resync.recordGap(read.droppedChars, splitter);
|
|
1117
|
+
if (advanced) {
|
|
1118
|
+
const lines = splitter.push(resync.admit(read.text));
|
|
1119
|
+
if (lines.length > 0)
|
|
1120
|
+
reportGap();
|
|
1121
|
+
for (const line of lines) {
|
|
1125
1122
|
onLine(line);
|
|
1126
1123
|
}
|
|
1124
|
+
continue;
|
|
1127
1125
|
}
|
|
1128
|
-
if (
|
|
1126
|
+
if (exitedBeforeRead) {
|
|
1127
|
+
reportGap();
|
|
1129
1128
|
for (const line of splitter.flush()) {
|
|
1130
1129
|
onLine(line);
|
|
1131
1130
|
}
|
|
@@ -1150,27 +1149,116 @@ function computeCapabilities() {
|
|
|
1150
1149
|
externalServiceObservation: status,
|
|
1151
1150
|
};
|
|
1152
1151
|
}
|
|
1152
|
+
function isGoTestCommand(rawCommand) {
|
|
1153
|
+
const tokenized = tokenizeCommand(rawCommand);
|
|
1154
|
+
if (!tokenized.ok)
|
|
1155
|
+
return false;
|
|
1156
|
+
const name = tokenized.command.split(/[\\/]/).pop() ?? tokenized.command;
|
|
1157
|
+
return (name === "go" || name === "go.exe") && tokenized.args.includes("test");
|
|
1158
|
+
}
|
|
1159
|
+
const IN_PROCESS_REPORTER_RUNNERS = new Set(["jest", "vitest", "playwright", "pytest", "rspec", "minitest", "phpunit", "junit", "xunit"]);
|
|
1160
|
+
function resultTestimonyDisclosures(kind) {
|
|
1161
|
+
if (kind === "unittest") {
|
|
1162
|
+
return ["unittest results are parsed from the runner's text output, not a structured report; the code under test can print lines that look like results, so they are testimony of the run, reconciled only against unittest's own `Ran N tests` count."];
|
|
1163
|
+
}
|
|
1164
|
+
if (IN_PROCESS_REPORTER_RUNNERS.has(kind)) {
|
|
1165
|
+
return [`${kind} results arrive on the same output stream as the code under test; a test that prints reporter-shaped lines could forge them, so treat them as testimony of the run, not proof.`];
|
|
1166
|
+
}
|
|
1167
|
+
return [];
|
|
1168
|
+
}
|
|
1153
1169
|
export function createTestRunnerCollector(options) {
|
|
1170
|
+
if (isGoTestCommand(options.configuration.command)) {
|
|
1171
|
+
const { command, cwd, filesystemPolicy, networkPolicy } = options.configuration;
|
|
1172
|
+
return createGoTestCollector({
|
|
1173
|
+
command,
|
|
1174
|
+
cwd,
|
|
1175
|
+
...(options.service === undefined ? {} : { service: options.service }),
|
|
1176
|
+
...(filesystemPolicy === undefined ? {} : { filesystemPolicy }),
|
|
1177
|
+
...(networkPolicy === undefined ? {} : { networkPolicy }),
|
|
1178
|
+
...(options.configuration.collectCoverage === true ? { collectCoverage: true } : {}),
|
|
1179
|
+
...(options.configuration.timeoutMs === undefined ? {} : { timeoutMs: options.configuration.timeoutMs }),
|
|
1180
|
+
...(options.inheritHostEnvironment === undefined ? {} : { inheritHostEnvironment: options.inheritHostEnvironment }),
|
|
1181
|
+
...(options.passthroughEnvironmentNames === undefined ? {} : { passthroughEnvironmentNames: options.passthroughEnvironmentNames }),
|
|
1182
|
+
});
|
|
1183
|
+
}
|
|
1184
|
+
const collectCoverage = options.configuration.collectCoverage === true;
|
|
1185
|
+
let coverage = unavailableCoverage(collectCoverage ? "no run has completed (it has not started, was refused, or is still running)" : "coverage collection was not requested (collectCoverage is not true)");
|
|
1186
|
+
let coverageCapture = null;
|
|
1154
1187
|
const capabilities = computeCapabilities();
|
|
1155
1188
|
let context = null;
|
|
1156
1189
|
let managed = null;
|
|
1157
1190
|
let completion = Promise.resolve();
|
|
1158
1191
|
let translator = null;
|
|
1159
1192
|
let exitInfo = null;
|
|
1193
|
+
let wrapperDisclosureList = [];
|
|
1194
|
+
let wrapperCleanup = null;
|
|
1195
|
+
let timeoutGuard = null;
|
|
1196
|
+
let vitestPlan = null;
|
|
1197
|
+
const locationTally = { results: 0, withoutLocation: 0 };
|
|
1198
|
+
const runWrapperCleanup = () => {
|
|
1199
|
+
const cleanup = wrapperCleanup;
|
|
1200
|
+
wrapperCleanup = null;
|
|
1201
|
+
if (cleanup === null)
|
|
1202
|
+
return;
|
|
1203
|
+
try {
|
|
1204
|
+
cleanup();
|
|
1205
|
+
}
|
|
1206
|
+
catch (error) {
|
|
1207
|
+
console.error(`[test-runner-collector] removing the per-run wrapper cache failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
1208
|
+
}
|
|
1209
|
+
};
|
|
1160
1210
|
return {
|
|
1161
1211
|
collectorId: "test-runner-collector",
|
|
1162
1212
|
async start(ctx) {
|
|
1163
1213
|
context = ctx;
|
|
1214
|
+
const timeout = resolveRunTimeout(options.configuration.timeoutMs);
|
|
1215
|
+
if (!timeout.ok)
|
|
1216
|
+
return { available: false, reason: timeout.reason };
|
|
1164
1217
|
const discovery = discoverRunner(options.configuration.command);
|
|
1165
1218
|
if (!discovery.ok) {
|
|
1166
1219
|
return { available: false, reason: discovery.reason };
|
|
1167
1220
|
}
|
|
1221
|
+
// A wrapper's environment is found before anything is spawned, and the run is refused --
|
|
1222
|
+
// naming the command that prepares it -- when there is none: creating one is an install.
|
|
1223
|
+
let wrapperEnv = {};
|
|
1224
|
+
let filesystemPolicy = options.configuration.filesystemPolicy;
|
|
1225
|
+
if (discovery.wrapper !== undefined) {
|
|
1226
|
+
const located = locateWrapperEnvironment(discovery.wrapper, options.configuration.cwd, process.env, discovery.command);
|
|
1227
|
+
if (!located.ok)
|
|
1228
|
+
return { available: false, reason: located.reason };
|
|
1229
|
+
wrapperEnv = located.env;
|
|
1230
|
+
wrapperDisclosureList = located.disclosures;
|
|
1231
|
+
wrapperCleanup = located.cleanup ?? null;
|
|
1232
|
+
if (filesystemPolicy !== undefined) {
|
|
1233
|
+
filesystemPolicy = { ...filesystemPolicy, readOnlyRoots: [...(filesystemPolicy.readOnlyRoots ?? []), ...located.readOnlyRoots] };
|
|
1234
|
+
}
|
|
1235
|
+
}
|
|
1236
|
+
const childEnvironment = buildChildEnvironment({
|
|
1237
|
+
...(options.inheritHostEnvironment === undefined ? {} : { inheritHostEnvironment: options.inheritHostEnvironment }),
|
|
1238
|
+
...(options.passthroughEnvironmentNames === undefined ? {} : { passthroughNames: options.passthroughEnvironmentNames }),
|
|
1239
|
+
});
|
|
1240
|
+
if (childEnvironment.disclosure !== null)
|
|
1241
|
+
wrapperDisclosureList = [...wrapperDisclosureList, childEnvironment.disclosure];
|
|
1242
|
+
wrapperDisclosureList = [...wrapperDisclosureList, ...resultTestimonyDisclosures(discovery.kind)];
|
|
1243
|
+
vitestPlan = discovery.kind === "vitest" ? planVitestRun(options.configuration.cwd) : null;
|
|
1244
|
+
const runnerArgs = reporterFlagsFor(discovery.kind, discovery.args, vitestPlan === null ? undefined : { vitestTaskLocation: vitestPlan.taskLocationFlag });
|
|
1245
|
+
if (collectCoverage) {
|
|
1246
|
+
const begun = beginCoverageCapture(discovery.kind, options.configuration.cwd);
|
|
1247
|
+
if (begun.ok) {
|
|
1248
|
+
coverageCapture = begun.capture;
|
|
1249
|
+
if (filesystemPolicy !== undefined)
|
|
1250
|
+
filesystemPolicy = withCoverageDirectoryAllowed(filesystemPolicy, begun.capture.directory);
|
|
1251
|
+
}
|
|
1252
|
+
else {
|
|
1253
|
+
coverage = unavailableCoverage(begun.reason);
|
|
1254
|
+
}
|
|
1255
|
+
}
|
|
1168
1256
|
const processId = `test-runner-${Date.now()}`;
|
|
1169
1257
|
try {
|
|
1170
1258
|
managed = spawnProcess({
|
|
1171
1259
|
processId,
|
|
1172
|
-
command: discovery.command,
|
|
1173
|
-
args:
|
|
1260
|
+
command: discovery.wrapper?.command ?? discovery.command,
|
|
1261
|
+
args: discovery.wrapper === undefined ? runnerArgs : wrappedSpawnArgs(discovery.wrapper, discovery.command, runnerArgs),
|
|
1174
1262
|
cwd: options.configuration.cwd,
|
|
1175
1263
|
// `NODE_TEST_CONTEXT` (set by node's own `--test` on itself, inherited
|
|
1176
1264
|
// by every child process unless cleared) is load-bearing here, and it
|
|
@@ -1190,7 +1278,7 @@ export function createTestRunnerCollector(options) {
|
|
|
1190
1278
|
// `reporterEnvFor` adds nothing for every runner but pytest, whose
|
|
1191
1279
|
// plugin is loaded by importable name and needs `PYTHONPATH` to
|
|
1192
1280
|
// resolve it (see `RunnerProfile.reporterEnv`'s own doc comment).
|
|
1193
|
-
env: { NODE_TEST_CONTEXT: undefined, ...reporterEnvFor(discovery.kind, discovery.args) },
|
|
1281
|
+
env: { ...childEnvironment.env, NODE_TEST_CONTEXT: undefined, ...reporterEnvFor(discovery.kind, discovery.args), ...wrapperEnv, ...coverageCapture?.environment },
|
|
1194
1282
|
// §21 execution boundary, threaded through exactly like
|
|
1195
1283
|
// `ExecutionController.run()`'s own spawn site -- absent means
|
|
1196
1284
|
// unconstrained, same as there. Unlike that site, this collector
|
|
@@ -1200,11 +1288,17 @@ export function createTestRunnerCollector(options) {
|
|
|
1200
1288
|
// unconstrained when a declared policy can't be enforced -- caught
|
|
1201
1289
|
// below and reported the same honest way `discoverRunner` failures
|
|
1202
1290
|
// already are, per `Collector.start()`'s "never throw" contract.
|
|
1203
|
-
...(
|
|
1291
|
+
...(filesystemPolicy === undefined ? {} : { filesystemPolicy }),
|
|
1204
1292
|
...(options.configuration.networkPolicy === undefined ? {} : { networkPolicy: options.configuration.networkPolicy }),
|
|
1205
1293
|
});
|
|
1206
1294
|
}
|
|
1207
1295
|
catch (err) {
|
|
1296
|
+
if (coverageCapture !== null) {
|
|
1297
|
+
coverageCapture.dispose();
|
|
1298
|
+
coverageCapture = null;
|
|
1299
|
+
coverage = unavailableCoverage("the runner did not start");
|
|
1300
|
+
}
|
|
1301
|
+
runWrapperCleanup();
|
|
1208
1302
|
return { available: false, reason: err instanceof Error ? err.message : String(err) };
|
|
1209
1303
|
}
|
|
1210
1304
|
// Attached independently of `pumpLines`' own `waitForExit()` -- a
|
|
@@ -1213,6 +1307,13 @@ export function createTestRunnerCollector(options) {
|
|
|
1213
1307
|
void managed.waitForExit().then((info) => {
|
|
1214
1308
|
exitInfo = info;
|
|
1215
1309
|
});
|
|
1310
|
+
timeoutGuard = armRunTimeout(managed, {
|
|
1311
|
+
runner: discovery.kind,
|
|
1312
|
+
timeoutMs: timeout.timeoutMs,
|
|
1313
|
+
emit: ctx.emit,
|
|
1314
|
+
service: options.service ?? null,
|
|
1315
|
+
processId,
|
|
1316
|
+
});
|
|
1216
1317
|
translator = createEventTranslator(context.emit, options.service, processId);
|
|
1217
1318
|
const activeTranslator = translator;
|
|
1218
1319
|
const gate = createEnvelopeGate();
|
|
@@ -1238,15 +1339,14 @@ export function createTestRunnerCollector(options) {
|
|
|
1238
1339
|
// **unittest never speaks the NDJSON envelope** (see this file's own
|
|
1239
1340
|
// top doc comment): there is no reporter to gate, only a text parser.
|
|
1240
1341
|
// Every other profile shares the one JSON-envelope path below.
|
|
1241
|
-
const
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
})()
|
|
1342
|
+
const activeCapture = coverageCapture;
|
|
1343
|
+
const unittestParser = discovery.kind === "unittest" ? createUnittestResultParser() : null;
|
|
1344
|
+
const onLine = unittestParser !== null
|
|
1345
|
+
? (line) => {
|
|
1346
|
+
for (const event of unittestParser.feed(line)) {
|
|
1347
|
+
activeTranslator.handle(event);
|
|
1348
|
+
}
|
|
1349
|
+
}
|
|
1250
1350
|
: (line) => {
|
|
1251
1351
|
if (line.trim() === "")
|
|
1252
1352
|
return;
|
|
@@ -1283,9 +1383,20 @@ export function createTestRunnerCollector(options) {
|
|
|
1283
1383
|
}
|
|
1284
1384
|
if (verdict.kind !== "event")
|
|
1285
1385
|
return;
|
|
1386
|
+
if (vitestPlan !== null && (parsed.type === "test:pass" || parsed.type === "test:fail")) {
|
|
1387
|
+
locationTally.results++;
|
|
1388
|
+
if (typeof parsed.data.line !== "number")
|
|
1389
|
+
locationTally.withoutLocation++;
|
|
1390
|
+
}
|
|
1391
|
+
if (typeof parsed.data.file === "string")
|
|
1392
|
+
activeCapture?.observeTestFile(parsed.data.file);
|
|
1286
1393
|
activeTranslator.handle(parsed);
|
|
1287
1394
|
};
|
|
1288
|
-
completion = pumpLines(managed, onLine
|
|
1395
|
+
completion = pumpLines(managed, onLine, {
|
|
1396
|
+
onGap: (droppedChars) => {
|
|
1397
|
+
reportRunnerOutputGap(ctx.emit, { collector: "test-runner", service: options.service ?? null, processId, droppedChars });
|
|
1398
|
+
},
|
|
1399
|
+
}).then(() => {
|
|
1289
1400
|
// The very last test's result has no "next test:start" or
|
|
1290
1401
|
// no-`file` diagnostic to flush it -- node's own suite-level
|
|
1291
1402
|
// summary diagnostics almost always follow, but relying on that
|
|
@@ -1293,16 +1404,37 @@ export function createTestRunnerCollector(options) {
|
|
|
1293
1404
|
// unusual stream (the process killed mid-run, `stop()` called
|
|
1294
1405
|
// early) leaves the last real result buffered and never emitted.
|
|
1295
1406
|
activeTranslator.flush();
|
|
1407
|
+
if (unittestParser !== null) {
|
|
1408
|
+
const reconciliation = unittestParser.finish();
|
|
1409
|
+
if (!reconciliation.reconciled)
|
|
1410
|
+
reportEnvelopeError(`unittest results are read from text and could not be reconciled: ${reconciliation.reason ?? "unknown"}`);
|
|
1411
|
+
}
|
|
1412
|
+
if (activeCapture !== null) {
|
|
1413
|
+
try {
|
|
1414
|
+
coverage = activeCapture.collect();
|
|
1415
|
+
}
|
|
1416
|
+
catch (error) {
|
|
1417
|
+
coverage = unavailableCoverage(`coverage data could not be read: ${error instanceof Error ? error.message : String(error)}`);
|
|
1418
|
+
}
|
|
1419
|
+
finally {
|
|
1420
|
+
activeCapture.dispose();
|
|
1421
|
+
}
|
|
1422
|
+
}
|
|
1296
1423
|
});
|
|
1297
1424
|
return { available: true };
|
|
1298
1425
|
},
|
|
1299
1426
|
async stop() {
|
|
1427
|
+
timeoutGuard?.cancel();
|
|
1300
1428
|
if (managed !== null) {
|
|
1301
1429
|
await managed.kill();
|
|
1302
1430
|
}
|
|
1303
1431
|
await completion;
|
|
1432
|
+
runWrapperCleanup();
|
|
1304
1433
|
context = null;
|
|
1305
1434
|
},
|
|
1435
|
+
timedOut() {
|
|
1436
|
+
return timeoutGuard?.timedOut() ?? false;
|
|
1437
|
+
},
|
|
1306
1438
|
capabilities() {
|
|
1307
1439
|
return capabilities;
|
|
1308
1440
|
},
|
|
@@ -1315,6 +1447,26 @@ export function createTestRunnerCollector(options) {
|
|
|
1315
1447
|
processExit() {
|
|
1316
1448
|
return exitInfo;
|
|
1317
1449
|
},
|
|
1450
|
+
wrapperDisclosures() {
|
|
1451
|
+
return wrapperDisclosureList;
|
|
1452
|
+
},
|
|
1453
|
+
degradations() {
|
|
1454
|
+
return vitestLocationDegradations(vitestPlan, locationTally);
|
|
1455
|
+
},
|
|
1456
|
+
sandboxDisclosures() {
|
|
1457
|
+
return managed?.sandboxDisclosures() ?? NO_SANDBOX_DISCLOSURES;
|
|
1458
|
+
},
|
|
1459
|
+
executedCoverage() {
|
|
1460
|
+
return coverage;
|
|
1461
|
+
},
|
|
1462
|
+
outputTail(maxBytes = 8192) {
|
|
1463
|
+
// Combined stdout+stderr, as the process wrote it. Raw: the caller redacts before it is
|
|
1464
|
+
// shown to anyone, since only the caller knows which secret values were in the environment.
|
|
1465
|
+
if (managed === null)
|
|
1466
|
+
return null;
|
|
1467
|
+
const whole = managed.readOutput();
|
|
1468
|
+
return whole.length <= maxBytes ? whole : whole.slice(whole.length - maxBytes);
|
|
1469
|
+
},
|
|
1318
1470
|
};
|
|
1319
1471
|
}
|
|
1320
1472
|
//# sourceMappingURL=test-runner-collector.js.map
|