@specific.dev/spectest 0.51.0 → 0.52.0
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/daemon.js +38 -7
- package/package.json +1 -1
- package/src/daemon.ts +45 -7
package/dist/daemon.js
CHANGED
|
@@ -46,7 +46,7 @@ import { conflict, notFound, requireString, } from "./harness/methods.js";
|
|
|
46
46
|
import { openTerminal } from "./terminal.js";
|
|
47
47
|
import { readAnnotation } from "./annotate.js";
|
|
48
48
|
import { pauseRecording, recordEmail, recordEnv, recordExec, recordFake, recordHttp, recordStep, recordTerminal, recordWait, reserveEvent, recorderEventCount, recorderMarkChildren, recorderTruncate, resumeRecording, startRecording, stopRecording, truncateUtf8, } from "./recorder.js";
|
|
49
|
-
import { deepUnwrap, wrap, wrapResponse } from "./inspect.js";
|
|
49
|
+
import { deepUnwrap, readRaw, wrap, wrapResponse } from "./inspect.js";
|
|
50
50
|
import { clearRecordSecrets, setRecordSecrets } from "./record-secrets.js";
|
|
51
51
|
import { encodeReplayBundle, replayChunk } from "./replay-bundle.js";
|
|
52
52
|
function namedServices(cfg) {
|
|
@@ -3074,17 +3074,41 @@ const TEST_DATA = new Map();
|
|
|
3074
3074
|
* test timeout instead, and a project `setup` — which routinely waits on
|
|
3075
3075
|
* rollouts — stays unbounded, so neither gets a default. */
|
|
3076
3076
|
const COMPONENT_EXEC_DEFAULT_TIMEOUT_MS = 120_000;
|
|
3077
|
-
/**
|
|
3078
|
-
*
|
|
3079
|
-
*
|
|
3080
|
-
*
|
|
3077
|
+
/** The running test case's recording `exec`, installed by `runOne`
|
|
3078
|
+
* for the length of the case and cleared again in its `finally`. It is
|
|
3079
|
+
* read at *call* time, which is the whole point: a component's context —
|
|
3080
|
+
* a `helpers` factory's, a service `setup` hook's — is built once during
|
|
3081
|
+
* bootstrap and cached, so the `exec` those hooks destructure can never
|
|
3082
|
+
* be the test's own. Without this, everything a service helper did was
|
|
3083
|
+
* invisible: `ctx.svc.<name>.<helper>()` shelling into a container
|
|
3084
|
+
* recorded no step, and a helper called inside a `ctx.poll` predicate
|
|
3085
|
+
* left the wait with nothing to render inside it (the poll keeps the
|
|
3086
|
+
* events of its last attempt — there were none). Undefined outside a
|
|
3087
|
+
* test, so bootstrap and project setup stay uninstrumented. */
|
|
3088
|
+
let RECORDING_EXEC;
|
|
3089
|
+
/** `docker exec` with optional piped stdin. Array command = exact argv
|
|
3090
|
+
* (no shell); string = `sh -lc`. Non-zero exit is reported via
|
|
3091
|
+
* `exitCode`, never thrown.
|
|
3092
|
+
*
|
|
3093
|
+
* Records a step when a test is running (through `RECORDING_EXEC`, which
|
|
3094
|
+
* also captures the asciicast), and nothing at bootstrap. Either way the
|
|
3095
|
+
* caller gets the PLAIN `ExecResult`, never the test context's
|
|
3096
|
+
* provenance-wrapped one: component code is ordinary code written against
|
|
3097
|
+
* the declared type, and a wrapper is an object — `res.exitCode !== 0`
|
|
3098
|
+
* would be true for every exit code, and a client branching on `typeof`
|
|
3099
|
+
* would take the wrong arm. Provenance has nothing to point at here
|
|
3100
|
+
* anyway, since the helper's return value is not what the step recorded. */
|
|
3081
3101
|
function componentExec(service, command, opts, defaultTimeoutMs) {
|
|
3082
3102
|
assertKnownOpts("ctx.exec", opts, EXEC_OPT_KEYS);
|
|
3083
3103
|
const timeoutMs = opts?.timeoutMs ?? defaultTimeoutMs;
|
|
3084
|
-
|
|
3104
|
+
const merged = {
|
|
3085
3105
|
...opts,
|
|
3086
3106
|
...(timeoutMs !== undefined ? { timeoutMs } : {}),
|
|
3087
|
-
}
|
|
3107
|
+
};
|
|
3108
|
+
const recording = RECORDING_EXEC;
|
|
3109
|
+
if (recording)
|
|
3110
|
+
return recording(service, command, merged).then(readRaw);
|
|
3111
|
+
return execInService(service, command, merged);
|
|
3088
3112
|
}
|
|
3089
3113
|
/** Transitive `dependsOn` closure of a service, by name. */
|
|
3090
3114
|
function transitiveDeps(service) {
|
|
@@ -3918,6 +3942,12 @@ async function runOne(testCase) {
|
|
|
3918
3942
|
testName: testCase.name,
|
|
3919
3943
|
parent,
|
|
3920
3944
|
};
|
|
3945
|
+
// Let a component's `exec` record for the length of this case. The
|
|
3946
|
+
// context a `helpers` factory or a service `setup` hook holds was built
|
|
3947
|
+
// at bootstrap and carries `componentExec`, which reads this at call
|
|
3948
|
+
// time — so from here on a service helper's `docker exec` lands on the
|
|
3949
|
+
// timeline (and in the cast) exactly like the test's own `ctx.exec`.
|
|
3950
|
+
RECORDING_EXEC = recordedExec;
|
|
3921
3951
|
const timeoutMs = testCase.timeoutMs ?? DEFAULT_TEST_TIMEOUT_MS;
|
|
3922
3952
|
let timer;
|
|
3923
3953
|
const timedOut = new Promise((_, reject) => {
|
|
@@ -3946,6 +3976,7 @@ async function runOne(testCase) {
|
|
|
3946
3976
|
finally {
|
|
3947
3977
|
if (timer)
|
|
3948
3978
|
clearTimeout(timer);
|
|
3979
|
+
RECORDING_EXEC = undefined;
|
|
3949
3980
|
restoreFetch();
|
|
3950
3981
|
restoreConsole();
|
|
3951
3982
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
package/package.json
CHANGED
package/src/daemon.ts
CHANGED
|
@@ -134,7 +134,7 @@ import {
|
|
|
134
134
|
type TestEvent,
|
|
135
135
|
type OmittedBody,
|
|
136
136
|
} from "./recorder.js";
|
|
137
|
-
import { deepUnwrap, wrap, wrapResponse } from "./inspect.js";
|
|
137
|
+
import { deepUnwrap, readRaw, wrap, wrapResponse } from "./inspect.js";
|
|
138
138
|
import type { Wrapped, WrappedResponse } from "./inspect.js";
|
|
139
139
|
import { clearRecordSecrets, setRecordSecrets } from "./record-secrets.js";
|
|
140
140
|
import { encodeReplayBundle, replayChunk } from "./replay-bundle.js";
|
|
@@ -3773,10 +3773,37 @@ const TEST_DATA = new Map<string, unknown>();
|
|
|
3773
3773
|
* rollouts — stays unbounded, so neither gets a default. */
|
|
3774
3774
|
const COMPONENT_EXEC_DEFAULT_TIMEOUT_MS = 120_000;
|
|
3775
3775
|
|
|
3776
|
-
/**
|
|
3777
|
-
*
|
|
3778
|
-
*
|
|
3779
|
-
*
|
|
3776
|
+
/** The running test case's recording `exec`, installed by `runOne`
|
|
3777
|
+
* for the length of the case and cleared again in its `finally`. It is
|
|
3778
|
+
* read at *call* time, which is the whole point: a component's context —
|
|
3779
|
+
* a `helpers` factory's, a service `setup` hook's — is built once during
|
|
3780
|
+
* bootstrap and cached, so the `exec` those hooks destructure can never
|
|
3781
|
+
* be the test's own. Without this, everything a service helper did was
|
|
3782
|
+
* invisible: `ctx.svc.<name>.<helper>()` shelling into a container
|
|
3783
|
+
* recorded no step, and a helper called inside a `ctx.poll` predicate
|
|
3784
|
+
* left the wait with nothing to render inside it (the poll keeps the
|
|
3785
|
+
* events of its last attempt — there were none). Undefined outside a
|
|
3786
|
+
* test, so bootstrap and project setup stay uninstrumented. */
|
|
3787
|
+
let RECORDING_EXEC:
|
|
3788
|
+
| ((
|
|
3789
|
+
service: string,
|
|
3790
|
+
command: string | string[],
|
|
3791
|
+
opts?: ExecOpts,
|
|
3792
|
+
) => Promise<ExecResult>)
|
|
3793
|
+
| undefined;
|
|
3794
|
+
|
|
3795
|
+
/** `docker exec` with optional piped stdin. Array command = exact argv
|
|
3796
|
+
* (no shell); string = `sh -lc`. Non-zero exit is reported via
|
|
3797
|
+
* `exitCode`, never thrown.
|
|
3798
|
+
*
|
|
3799
|
+
* Records a step when a test is running (through `RECORDING_EXEC`, which
|
|
3800
|
+
* also captures the asciicast), and nothing at bootstrap. Either way the
|
|
3801
|
+
* caller gets the PLAIN `ExecResult`, never the test context's
|
|
3802
|
+
* provenance-wrapped one: component code is ordinary code written against
|
|
3803
|
+
* the declared type, and a wrapper is an object — `res.exitCode !== 0`
|
|
3804
|
+
* would be true for every exit code, and a client branching on `typeof`
|
|
3805
|
+
* would take the wrong arm. Provenance has nothing to point at here
|
|
3806
|
+
* anyway, since the helper's return value is not what the step recorded. */
|
|
3780
3807
|
function componentExec(
|
|
3781
3808
|
service: string,
|
|
3782
3809
|
command: string | string[],
|
|
@@ -3785,10 +3812,13 @@ function componentExec(
|
|
|
3785
3812
|
): Promise<ExecResult> {
|
|
3786
3813
|
assertKnownOpts("ctx.exec", opts, EXEC_OPT_KEYS);
|
|
3787
3814
|
const timeoutMs = opts?.timeoutMs ?? defaultTimeoutMs;
|
|
3788
|
-
|
|
3815
|
+
const merged = {
|
|
3789
3816
|
...opts,
|
|
3790
3817
|
...(timeoutMs !== undefined ? { timeoutMs } : {}),
|
|
3791
|
-
}
|
|
3818
|
+
};
|
|
3819
|
+
const recording = RECORDING_EXEC;
|
|
3820
|
+
if (recording) return recording(service, command, merged).then(readRaw);
|
|
3821
|
+
return execInService(service, command, merged);
|
|
3792
3822
|
}
|
|
3793
3823
|
|
|
3794
3824
|
// ────────────────────────────────────────────────────────────────────────
|
|
@@ -4796,6 +4826,13 @@ async function runOne(testCase: TestCase<unknown>): Promise<RunResult> {
|
|
|
4796
4826
|
parent,
|
|
4797
4827
|
};
|
|
4798
4828
|
|
|
4829
|
+
// Let a component's `exec` record for the length of this case. The
|
|
4830
|
+
// context a `helpers` factory or a service `setup` hook holds was built
|
|
4831
|
+
// at bootstrap and carries `componentExec`, which reads this at call
|
|
4832
|
+
// time — so from here on a service helper's `docker exec` lands on the
|
|
4833
|
+
// timeline (and in the cast) exactly like the test's own `ctx.exec`.
|
|
4834
|
+
RECORDING_EXEC = recordedExec;
|
|
4835
|
+
|
|
4799
4836
|
const timeoutMs = testCase.timeoutMs ?? DEFAULT_TEST_TIMEOUT_MS;
|
|
4800
4837
|
let timer: NodeJS.Timeout | undefined;
|
|
4801
4838
|
const timedOut = new Promise<never>((_, reject) => {
|
|
@@ -4825,6 +4862,7 @@ async function runOne(testCase: TestCase<unknown>): Promise<RunResult> {
|
|
|
4825
4862
|
};
|
|
4826
4863
|
} finally {
|
|
4827
4864
|
if (timer) clearTimeout(timer);
|
|
4865
|
+
RECORDING_EXEC = undefined;
|
|
4828
4866
|
restoreFetch();
|
|
4829
4867
|
restoreConsole();
|
|
4830
4868
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|