@specific.dev/spectest 0.31.0 → 0.32.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/components/index.d.ts +1 -1
- package/dist/components/k3s.d.ts +175 -12
- package/dist/components/k3s.js +403 -62
- package/dist/daemon.js +182 -109
- package/dist/index.d.ts +251 -217
- package/package.json +1 -1
package/dist/daemon.js
CHANGED
|
@@ -2553,7 +2553,11 @@ async function bootstrapInner() {
|
|
|
2553
2553
|
if (svc.setup) {
|
|
2554
2554
|
progressService(svc.name, { status: "probing", detail: "running setup" });
|
|
2555
2555
|
const helpers = await ensureHelpers(svc.name, svc);
|
|
2556
|
-
await svc.setup({
|
|
2556
|
+
await svc.setup({
|
|
2557
|
+
name: svc.name,
|
|
2558
|
+
helpers,
|
|
2559
|
+
...(await spectestContext({ service: svc.name, includeSelf: true })),
|
|
2560
|
+
});
|
|
2557
2561
|
}
|
|
2558
2562
|
progressService(svc.name, { status: "ready", detail: undefined });
|
|
2559
2563
|
const ti = timings.get(svc.name);
|
|
@@ -2622,25 +2626,15 @@ async function runProjectSetupInner() {
|
|
|
2622
2626
|
if (!proj.setup)
|
|
2623
2627
|
return { ran: false, durationMs: 0 };
|
|
2624
2628
|
const start = Date.now();
|
|
2625
|
-
// Build the same `svc` handles tests see, so setup and tests share
|
|
2626
|
-
// helper instances (e.g. a Bun.SQL pool created here is reused later).
|
|
2627
|
-
const svc = (await buildServiceHandles(proj.environment));
|
|
2628
|
-
const fakes = await buildFakeHandles();
|
|
2629
2629
|
// Install the fetch wrapper for the duration of setup so `ctx.fetch` (and any
|
|
2630
2630
|
// client routed through `globalThis.fetch`) returns a wrapped Response, same
|
|
2631
2631
|
// as in a test. No recorder is active here, so it wraps without provenance —
|
|
2632
2632
|
// but the wrapped type stays honest at runtime (`.unwrap()` works).
|
|
2633
2633
|
const restoreFetch = installFetchWrapper();
|
|
2634
|
-
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
|
|
2638
|
-
fakes,
|
|
2639
|
-
dnsName: registerDnsName,
|
|
2640
|
-
certificate: mintCertificate,
|
|
2641
|
-
startService: startRuntimeService,
|
|
2642
|
-
stopService: stopRuntimeService,
|
|
2643
|
-
};
|
|
2634
|
+
// The full context, unscoped: every service is up by project-setup time,
|
|
2635
|
+
// so `ctx.svc` is the same map tests see (and shares helper instances with
|
|
2636
|
+
// them — a Bun.SQL pool created here is reused later).
|
|
2637
|
+
const ctx = await spectestContext();
|
|
2644
2638
|
try {
|
|
2645
2639
|
await proj.setup(ctx);
|
|
2646
2640
|
}
|
|
@@ -3013,63 +3007,131 @@ const TEST_DATA = new Map();
|
|
|
3013
3007
|
// so components don't hand-roll child_process docker execs or hard-code
|
|
3014
3008
|
// control-plane paths like /workspace.
|
|
3015
3009
|
// ────────────────────────────────────────────────────────────────────────
|
|
3010
|
+
/** Default `ctx.exec` timeout inside a service-level `setup`/`helpers`
|
|
3011
|
+
* hook. Those run during boot where nothing else bounds them, so a wedged
|
|
3012
|
+
* command would hang the whole env start. A test's exec inherits the
|
|
3013
|
+
* test timeout instead, and a project `setup` — which routinely waits on
|
|
3014
|
+
* rollouts — stays unbounded, so neither gets a default. */
|
|
3016
3015
|
const COMPONENT_EXEC_DEFAULT_TIMEOUT_MS = 120_000;
|
|
3017
3016
|
/** Raw `docker exec` with optional piped stdin. Array command = exact
|
|
3018
3017
|
* argv (no shell); string = `sh -lc`. Non-zero exit is reported via
|
|
3019
|
-
* `exitCode`, never thrown. Unlike `
|
|
3020
|
-
* setup/helpers-factory time has no test timeline. */
|
|
3021
|
-
function componentExec(service, command, opts) {
|
|
3018
|
+
* `exitCode`, never thrown. Unlike the test-context `exec` this records
|
|
3019
|
+
* nothing — setup/helpers-factory time has no test timeline. */
|
|
3020
|
+
function componentExec(service, command, opts, defaultTimeoutMs) {
|
|
3022
3021
|
assertKnownOpts("ctx.exec", opts, EXEC_OPT_KEYS);
|
|
3023
|
-
const
|
|
3024
|
-
|
|
3025
|
-
|
|
3026
|
-
|
|
3027
|
-
|
|
3028
|
-
|
|
3029
|
-
|
|
3030
|
-
|
|
3031
|
-
const
|
|
3032
|
-
|
|
3033
|
-
|
|
3034
|
-
|
|
3035
|
-
|
|
3036
|
-
|
|
3037
|
-
|
|
3038
|
-
|
|
3039
|
-
|
|
3040
|
-
|
|
3041
|
-
|
|
3042
|
-
|
|
3043
|
-
|
|
3044
|
-
|
|
3045
|
-
|
|
3046
|
-
|
|
3047
|
-
|
|
3048
|
-
|
|
3049
|
-
|
|
3050
|
-
|
|
3051
|
-
|
|
3052
|
-
|
|
3053
|
-
|
|
3054
|
-
|
|
3055
|
-
|
|
3056
|
-
|
|
3057
|
-
|
|
3058
|
-
|
|
3059
|
-
|
|
3060
|
-
|
|
3061
|
-
|
|
3062
|
-
|
|
3063
|
-
|
|
3022
|
+
const timeoutMs = opts?.timeoutMs ?? defaultTimeoutMs;
|
|
3023
|
+
return execInService(service, command, {
|
|
3024
|
+
...opts,
|
|
3025
|
+
...(timeoutMs !== undefined ? { timeoutMs } : {}),
|
|
3026
|
+
});
|
|
3027
|
+
}
|
|
3028
|
+
/** Transitive `dependsOn` closure of a service, by name. */
|
|
3029
|
+
function transitiveDeps(service) {
|
|
3030
|
+
const services = loaded?.project.environment.services ?? {};
|
|
3031
|
+
const seen = new Set();
|
|
3032
|
+
const walk = (name) => {
|
|
3033
|
+
for (const dep of services[name]?.dependsOn ?? []) {
|
|
3034
|
+
if (seen.has(dep) || !services[dep])
|
|
3035
|
+
continue;
|
|
3036
|
+
seen.add(dep);
|
|
3037
|
+
walk(dep);
|
|
3038
|
+
}
|
|
3039
|
+
};
|
|
3040
|
+
walk(service);
|
|
3041
|
+
return seen;
|
|
3042
|
+
}
|
|
3043
|
+
/** Property reads that are language plumbing, not a service lookup —
|
|
3044
|
+
* `then` above all: a context object holding this map gets awaited, and a
|
|
3045
|
+
* throwing `then` read would turn `await ctx` into a hard failure. */
|
|
3046
|
+
const SVC_PLUMBING_PROPS = ["then", "toJSON", "constructor"];
|
|
3047
|
+
/**
|
|
3048
|
+
* `svc` for a service-level hook: the handles of everything the DAG says
|
|
3049
|
+
* is up, behind a Proxy that turns a read of anything else into an error
|
|
3050
|
+
* naming the fix rather than a `undefined is not an object` further down.
|
|
3051
|
+
*/
|
|
3052
|
+
function scopedServiceHandles(built, scope) {
|
|
3053
|
+
const allowed = transitiveDeps(scope.service);
|
|
3054
|
+
if (scope.includeSelf)
|
|
3055
|
+
allowed.add(scope.service);
|
|
3056
|
+
return new Proxy(built.handles, {
|
|
3057
|
+
get(target, prop, receiver) {
|
|
3058
|
+
if (typeof prop === "string" &&
|
|
3059
|
+
!SVC_PLUMBING_PROPS.includes(prop) &&
|
|
3060
|
+
!(prop in target)) {
|
|
3061
|
+
// A dependency whose own `helpers` factory blew up: surface THAT
|
|
3062
|
+
// error, and only if someone actually reads the handle — a hook
|
|
3063
|
+
// that never touches `ctx.svc` shouldn't fail over a sibling's
|
|
3064
|
+
// broken factory.
|
|
3065
|
+
const failure = built.failures.get(prop);
|
|
3066
|
+
if (failure)
|
|
3067
|
+
throw failure;
|
|
3068
|
+
if (!allowed.has(prop)) {
|
|
3069
|
+
const known = loaded?.project.environment.services ?? {};
|
|
3070
|
+
throw new Error(known[prop]
|
|
3071
|
+
? `ctx.svc.${prop} is not available in ${scope.service}'s ${scope.includeSelf ? "setup" : "helpers"} hook: "${prop}" is not a dependency of "${scope.service}", so it may not be running yet. Add it to ${scope.service}'s \`dependsOn\`.`
|
|
3072
|
+
: `ctx.svc.${prop}: no service named "${prop}" in this environment.`);
|
|
3073
|
+
}
|
|
3074
|
+
}
|
|
3075
|
+
return Reflect.get(target, prop, receiver);
|
|
3076
|
+
},
|
|
3064
3077
|
});
|
|
3065
3078
|
}
|
|
3066
|
-
|
|
3079
|
+
/**
|
|
3080
|
+
* Build the context every hook receives. `scope` decides which services'
|
|
3081
|
+
* handles are reachable and whether `exec` gets the boot-time default
|
|
3082
|
+
* timeout; everything else is identical in every context.
|
|
3083
|
+
*/
|
|
3084
|
+
async function spectestContext(scope = {}) {
|
|
3085
|
+
const handles = scope.service
|
|
3086
|
+
? scopedServiceHandles(await buildScopedHandles(scope.service, scope.includeSelf === true), { ...scope, service: scope.service })
|
|
3087
|
+
: loaded
|
|
3088
|
+
? await buildServiceHandles(loaded.project.environment)
|
|
3089
|
+
: {};
|
|
3090
|
+
const fakes = loaded ? await buildFakeHandles() : {};
|
|
3091
|
+
const execTimeoutMs = scope.service ? COMPONENT_EXEC_DEFAULT_TIMEOUT_MS : undefined;
|
|
3067
3092
|
return {
|
|
3068
3093
|
projectRoot: WORKSPACE,
|
|
3069
3094
|
readProjectFile: (p) => fs.readFile(path.isAbsolute(p) ? p : path.join(WORKSPACE, p), "utf8"),
|
|
3070
|
-
exec: componentExec,
|
|
3095
|
+
exec: (service, command, opts) => componentExec(service, command, opts, execTimeoutMs),
|
|
3096
|
+
// Read `globalThis.fetch` at call time: the wrapper is installed for
|
|
3097
|
+
// the duration of a test / eval / project setup, so a context built
|
|
3098
|
+
// before it lands still gets the wrapped one.
|
|
3099
|
+
fetch: ((input, init) => globalThis.fetch(input, init)),
|
|
3100
|
+
svc: handles,
|
|
3101
|
+
fakes: fakes,
|
|
3102
|
+
poll: pollCall,
|
|
3103
|
+
dnsName: registerDnsName,
|
|
3104
|
+
certificate: mintCertificate,
|
|
3105
|
+
startService: startRuntimeService,
|
|
3106
|
+
stopService: stopRuntimeService,
|
|
3071
3107
|
};
|
|
3072
3108
|
}
|
|
3109
|
+
/** Handles for a service hook: its dependencies (always up by the time the
|
|
3110
|
+
* hook runs) plus, for a `setup` hook, the service itself. Built eagerly —
|
|
3111
|
+
* the factories are cached, so a later test reuses these instances, and
|
|
3112
|
+
* building them here puts them in the warm-template snapshot. A factory
|
|
3113
|
+
* that throws is recorded rather than propagated: only a hook that
|
|
3114
|
+
* actually reads that handle should fail (see `scopedServiceHandles`). */
|
|
3115
|
+
async function buildScopedHandles(service, includeSelf) {
|
|
3116
|
+
const services = loaded?.project.environment.services ?? {};
|
|
3117
|
+
const names = transitiveDeps(service);
|
|
3118
|
+
if (includeSelf)
|
|
3119
|
+
names.add(service);
|
|
3120
|
+
const handles = {};
|
|
3121
|
+
const failures = new Map();
|
|
3122
|
+
for (const name of names) {
|
|
3123
|
+
const def = services[name];
|
|
3124
|
+
if (!def?.helpers)
|
|
3125
|
+
continue;
|
|
3126
|
+
try {
|
|
3127
|
+
handles[name] = await ensureHelpers(name, def);
|
|
3128
|
+
}
|
|
3129
|
+
catch (err) {
|
|
3130
|
+
failures.set(name, err);
|
|
3131
|
+
}
|
|
3132
|
+
}
|
|
3133
|
+
return { handles, failures };
|
|
3134
|
+
}
|
|
3073
3135
|
// Cached helper namespaces produced by `ServiceDefinition.helpers`
|
|
3074
3136
|
// factories. Built lazily on first access and reused for the daemon's
|
|
3075
3137
|
// lifetime — Bun.SQL pools and similar resources are happy to live a
|
|
@@ -3086,10 +3148,30 @@ async function ensureHelpers(name, def) {
|
|
|
3086
3148
|
if (!def.helpers)
|
|
3087
3149
|
return {};
|
|
3088
3150
|
if (!HELPERS_CACHE.has(name)) {
|
|
3089
|
-
|
|
3151
|
+
// A factory can now read `ctx.svc` (its dependencies'), so it can
|
|
3152
|
+
// re-enter this function. Deps are a DAG, but a factory reaching a
|
|
3153
|
+
// NON-dependency it happens to be reachable from would spin forever —
|
|
3154
|
+
// report the cycle instead of hanging the boot.
|
|
3155
|
+
if (HELPERS_BUILDING.has(name)) {
|
|
3156
|
+
throw new Error(`helpers factory cycle: building ctx.svc.${name} re-entered itself ` +
|
|
3157
|
+
`(via ${[...HELPERS_BUILDING].join(" → ")}). A helpers factory can read its ` +
|
|
3158
|
+
`dependencies' handles, but they must not read back.`);
|
|
3159
|
+
}
|
|
3160
|
+
HELPERS_BUILDING.add(name);
|
|
3161
|
+
try {
|
|
3162
|
+
HELPERS_CACHE.set(name, await def.helpers({
|
|
3163
|
+
name,
|
|
3164
|
+
...(await spectestContext({ service: name, includeSelf: false })),
|
|
3165
|
+
}));
|
|
3166
|
+
}
|
|
3167
|
+
finally {
|
|
3168
|
+
HELPERS_BUILDING.delete(name);
|
|
3169
|
+
}
|
|
3090
3170
|
}
|
|
3091
3171
|
return HELPERS_CACHE.get(name);
|
|
3092
3172
|
}
|
|
3173
|
+
/** Names whose `helpers` factory is mid-flight — see the cycle check above. */
|
|
3174
|
+
const HELPERS_BUILDING = new Set();
|
|
3093
3175
|
/**
|
|
3094
3176
|
* Build the `svc` map for one test/eval. The value at `svc[name]` is
|
|
3095
3177
|
* exactly the record the service's `helpers` factory returned (e.g.
|
|
@@ -3209,21 +3291,32 @@ function installFetchWrapper() {
|
|
|
3209
3291
|
globalThis.fetch = original;
|
|
3210
3292
|
};
|
|
3211
3293
|
}
|
|
3212
|
-
/** Build the `docker exec` argv for a service command.
|
|
3213
|
-
*
|
|
3214
|
-
*
|
|
3215
|
-
*
|
|
3216
|
-
*
|
|
3217
|
-
*
|
|
3294
|
+
/** Build the `docker exec` argv for a service command. A string command runs
|
|
3295
|
+
* through `sh -lc`; an array is exact argv with no shell (the
|
|
3296
|
+
* `["psql", "-f", "-"]` shape). An optional `cwd` becomes `-w <cwd>` (the
|
|
3297
|
+
* working-directory option of `ctx.exec`), so the command runs from that
|
|
3298
|
+
* directory without it being baked into the command string. `-i` is passed
|
|
3299
|
+
* whenever stdin will be piped — without it docker attaches no stdin and the
|
|
3300
|
+
* payload is silently discarded. Used by both the buffered and streaming
|
|
3301
|
+
* variants so they stay in lockstep. */
|
|
3218
3302
|
function dockerExecArgs(service, command, opts) {
|
|
3219
3303
|
const args = ["exec"];
|
|
3220
3304
|
if (opts?.stdin !== undefined)
|
|
3221
3305
|
args.push("-i");
|
|
3222
3306
|
if (opts?.cwd)
|
|
3223
3307
|
args.push("-w", opts.cwd);
|
|
3224
|
-
args.push(service
|
|
3308
|
+
args.push(service);
|
|
3309
|
+
if (typeof command === "string")
|
|
3310
|
+
args.push("sh", "-lc", command);
|
|
3311
|
+
else
|
|
3312
|
+
args.push(...command);
|
|
3225
3313
|
return args;
|
|
3226
3314
|
}
|
|
3315
|
+
/** How an argv-array command is shown in the timeline / asciicast prompt.
|
|
3316
|
+
* Display only — the real invocation never goes through a shell. */
|
|
3317
|
+
function displayCommand(command) {
|
|
3318
|
+
return typeof command === "string" ? command : command.join(" ");
|
|
3319
|
+
}
|
|
3227
3320
|
/** Exit code reported when an exec is killed by its own `timeoutMs`
|
|
3228
3321
|
* (the `timeout(1)` convention, matching `shx`). */
|
|
3229
3322
|
const EXEC_TIMEOUT_EXIT_CODE = 124;
|
|
@@ -3515,14 +3608,17 @@ async function runOne(testCase) {
|
|
|
3515
3608
|
const t = Date.now();
|
|
3516
3609
|
const cwd = opts?.cwd;
|
|
3517
3610
|
const resv = reserveEvent();
|
|
3518
|
-
|
|
3611
|
+
// An argv array has no shell form to show; render it space-joined for
|
|
3612
|
+
// the prompt line and the timeline row (display only).
|
|
3613
|
+
const shown = displayCommand(command);
|
|
3614
|
+
const session = newTerminalSession(start, service, shown, EXEC_CAST_COLS, EXEC_CAST_ROWS, testCase.id);
|
|
3519
3615
|
terminalSessions.push(session.record);
|
|
3520
3616
|
// Synthetic prompt frame so the replay is self-describing — the
|
|
3521
3617
|
// program's own output starts on the next line, like a real shell. A
|
|
3522
3618
|
// working directory rides in the prompt sigil (`svc:/dir $`) the way a
|
|
3523
3619
|
// real shell prompt shows it, so the recording stays self-describing.
|
|
3524
3620
|
const sigil = cwd ? `${service}:${cwd}` : service;
|
|
3525
|
-
session.pushFrame(0, `\x1b[32m${sigil} $\x1b[0m \x1b[1m${
|
|
3621
|
+
session.pushFrame(0, `\x1b[32m${sigil} $\x1b[0m \x1b[1m${shown}\x1b[0m\r\n`);
|
|
3526
3622
|
let frameBytes = 0;
|
|
3527
3623
|
let frameCapped = false;
|
|
3528
3624
|
const res = await execInServiceStreaming(service, command, (_stream, data) => {
|
|
@@ -3551,7 +3647,7 @@ async function runOne(testCase) {
|
|
|
3551
3647
|
const stdin = opts?.stdin !== undefined ? truncateUtf8(opts.stdin) : undefined;
|
|
3552
3648
|
const seq = recordExec({
|
|
3553
3649
|
service,
|
|
3554
|
-
command,
|
|
3650
|
+
command: shown,
|
|
3555
3651
|
cwd,
|
|
3556
3652
|
...(stdin
|
|
3557
3653
|
? { stdin: stdin.value, stdinTruncated: stdin.truncated }
|
|
@@ -3713,20 +3809,16 @@ async function runOne(testCase) {
|
|
|
3713
3809
|
sharedMobiles.set(app.url, mobile);
|
|
3714
3810
|
return mobile;
|
|
3715
3811
|
};
|
|
3716
|
-
//
|
|
3717
|
-
//
|
|
3718
|
-
//
|
|
3719
|
-
|
|
3720
|
-
const
|
|
3812
|
+
// The shared context (svc/fakes handles, poll, project files, dnsName,
|
|
3813
|
+
// certificate, runtime services) — identical to what a `setup` hook gets.
|
|
3814
|
+
// Built before installing the timeout so a slow client factory surfaces as
|
|
3815
|
+
// a real error rather than getting attributed to the test.
|
|
3816
|
+
const base = await spectestContext();
|
|
3721
3817
|
const ctx = {
|
|
3722
|
-
|
|
3723
|
-
// version, so capturing it here gets us instrumentation on ctx.fetch
|
|
3724
|
-
// for free. The recorder is active for the test, so responses come
|
|
3725
|
-
// back wrapped — hence the SpectestFetch type.
|
|
3726
|
-
fetch: globalThis.fetch,
|
|
3818
|
+
...base,
|
|
3727
3819
|
// exec/terminal/poll wrap their results at runtime (the recorder is
|
|
3728
|
-
// active), so the ctx interface types them wrapped
|
|
3729
|
-
//
|
|
3820
|
+
// active), so the ctx interface types them wrapped. The impls' own
|
|
3821
|
+
// return types stay raw — hence the bridging casts.
|
|
3730
3822
|
exec: recordedExec,
|
|
3731
3823
|
terminal: recordedTerminal,
|
|
3732
3824
|
openTerminal: recordedOpenTerminal,
|
|
@@ -3734,13 +3826,6 @@ async function runOne(testCase) {
|
|
|
3734
3826
|
mobile: trackedOpenMobile,
|
|
3735
3827
|
testName: testCase.name,
|
|
3736
3828
|
parent,
|
|
3737
|
-
svc,
|
|
3738
|
-
fakes,
|
|
3739
|
-
poll: pollCall,
|
|
3740
|
-
dnsName: registerDnsName,
|
|
3741
|
-
certificate: mintCertificate,
|
|
3742
|
-
startService: startRuntimeService,
|
|
3743
|
-
stopService: stopRuntimeService,
|
|
3744
3829
|
};
|
|
3745
3830
|
const timeoutMs = testCase.timeoutMs ?? DEFAULT_TEST_TIMEOUT_MS;
|
|
3746
3831
|
let timer;
|
|
@@ -4072,19 +4157,14 @@ async function evalCode(code, secrets) {
|
|
|
4072
4157
|
const evalOpenTerminal = async (service, opts) => {
|
|
4073
4158
|
return openInstrumentedTerminal(service, opts, start, terminalSessions, false, "eval");
|
|
4074
4159
|
};
|
|
4075
|
-
//
|
|
4076
|
-
// loaded
|
|
4160
|
+
// The shared context. Handles are best-effort for eval — with no project
|
|
4161
|
+
// loaded `spectestContext()` yields empty maps, so quick `await fetch(...)`
|
|
4077
4162
|
// snippets don't require a /load round-trip first.
|
|
4078
|
-
const
|
|
4079
|
-
? await buildServiceHandles(loaded.project.environment)
|
|
4080
|
-
: {};
|
|
4081
|
-
const fakes = loaded ? await buildFakeHandles() : {};
|
|
4163
|
+
const base = await spectestContext();
|
|
4082
4164
|
const ctx = {
|
|
4083
|
-
|
|
4084
|
-
//
|
|
4085
|
-
|
|
4086
|
-
// Wraps its result the same way (no recorder under eval, so no provenance —
|
|
4087
|
-
// but the wrapped type is honest at runtime, so `.unwrap()` works).
|
|
4165
|
+
...base,
|
|
4166
|
+
// Wraps its result (no recorder under eval, so no provenance — but the
|
|
4167
|
+
// wrapped type is honest at runtime, so `.unwrap()` works).
|
|
4088
4168
|
exec: execInServiceWrapped,
|
|
4089
4169
|
terminal: evalTerminal,
|
|
4090
4170
|
openTerminal: evalOpenTerminal,
|
|
@@ -4092,13 +4172,6 @@ async function evalCode(code, secrets) {
|
|
|
4092
4172
|
mobile: trackedOpenMobile,
|
|
4093
4173
|
testName: "eval",
|
|
4094
4174
|
parent: undefined,
|
|
4095
|
-
svc,
|
|
4096
|
-
fakes,
|
|
4097
|
-
poll: pollCall,
|
|
4098
|
-
dnsName: registerDnsName,
|
|
4099
|
-
certificate: mintCertificate,
|
|
4100
|
-
startService: startRuntimeService,
|
|
4101
|
-
stopService: stopRuntimeService,
|
|
4102
4175
|
};
|
|
4103
4176
|
// Expose the test context, matchers, and persistent state as globals
|
|
4104
4177
|
// so the snippet can use them without an explicit import. The user code
|