@norskvideo/ctl-test-harness 0.1.15 → 0.1.17
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/package.json +1 -1
- package/smoke.d.ts +14 -3
- package/smoke.js +24 -7
package/package.json
CHANGED
package/smoke.d.ts
CHANGED
|
@@ -34,7 +34,14 @@ export interface SmokeContext {
|
|
|
34
34
|
}): Promise<CliResult>;
|
|
35
35
|
fetchStudio(path: string): Promise<Response>;
|
|
36
36
|
}
|
|
37
|
-
|
|
37
|
+
/** Set on any observation that holds with or without a source — a resilient
|
|
38
|
+
* switch that keeps a ladder fed from a slate, a control surface that is up
|
|
39
|
+
* regardless — so it runs the satisfied phase only. `components` is
|
|
40
|
+
* structural by nature and needs no flag. */
|
|
41
|
+
interface Structural {
|
|
42
|
+
structural?: boolean;
|
|
43
|
+
}
|
|
44
|
+
export type SmokeObservation = Structural & ({
|
|
38
45
|
kind: "components";
|
|
39
46
|
ids: string[];
|
|
40
47
|
} | {
|
|
@@ -60,7 +67,7 @@ export type SmokeObservation = {
|
|
|
60
67
|
kind: "custom";
|
|
61
68
|
label: string;
|
|
62
69
|
satisfied: (ctx: SmokeContext) => Promise<boolean>;
|
|
63
|
-
};
|
|
70
|
+
});
|
|
64
71
|
export interface SmokeSpec {
|
|
65
72
|
product: {
|
|
66
73
|
/** Manifest name, e.g. "norsk-probe". The registration must report it. */
|
|
@@ -125,6 +132,8 @@ export interface SmokeDeps {
|
|
|
125
132
|
/** Docker leaves root-owned bind-mount targets under the store; a non-root
|
|
126
133
|
* runner cannot rm them, so they go from a throwaway root container. */
|
|
127
134
|
nukeStoreAsRoot(storeDir: string): void;
|
|
135
|
+
/** After the nuke: is the store still there? (Then the nuke did not work.) */
|
|
136
|
+
storeExists(storeDir: string): boolean;
|
|
128
137
|
ensureNetwork(): ReturnType<typeof ensureRunnerOnNetwork>;
|
|
129
138
|
supportsNoPublish(): Promise<boolean>;
|
|
130
139
|
containerUser(): string | undefined;
|
|
@@ -140,8 +149,10 @@ export interface SmokePorts extends BaseHarnessPorts {
|
|
|
140
149
|
export declare function smokePorts(slug: string, overrides?: Partial<SmokePorts>): SmokePorts;
|
|
141
150
|
export declare const defaultSmokeDeps: SmokeDeps;
|
|
142
151
|
/** Whether an observation depends on a source being pumped. Structural ones
|
|
143
|
-
*
|
|
152
|
+
* (`components`, or anything the spec marks `structural`) run the satisfied
|
|
153
|
+
* phase only. */
|
|
144
154
|
export declare function sourceDependent(o: SmokeObservation): boolean;
|
|
145
155
|
/** One sample of an observation — the predicate the three phases poll. */
|
|
146
156
|
export declare function observationSatisfied(o: SmokeObservation, ctx: SmokeContext, readers: SmokeReaders): Promise<boolean>;
|
|
147
157
|
export declare function runProductSmoke(slug: string, spec: SmokeSpec, deps?: SmokeDeps): Promise<void>;
|
|
158
|
+
export {};
|
package/smoke.js
CHANGED
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
// not a dependency of this package (daemon.ts's cleanupDaemon has the same
|
|
19
19
|
// rule); a product wanting typed commands bridges with toArgv in its own spec.
|
|
20
20
|
import { spawnSync } from "node:child_process";
|
|
21
|
-
import { writeFileSync } from "node:fs";
|
|
21
|
+
import { existsSync, writeFileSync } from "node:fs";
|
|
22
22
|
import { basename, dirname, join } from "node:path";
|
|
23
23
|
import { ensureRunnerOnNetwork, netReachMode, STUDIO_INTERNAL_PORT, studioBaseFrom, } from "./container-net.js";
|
|
24
24
|
import { cleanupDaemon, requireLicenseFile, runCli, startDaemon, } from "./daemon.js";
|
|
@@ -77,6 +77,7 @@ export const defaultSmokeDeps = {
|
|
|
77
77
|
`rm -rf /base/${basename(storeDir)}`,
|
|
78
78
|
]);
|
|
79
79
|
},
|
|
80
|
+
storeExists: (storeDir) => existsSync(storeDir),
|
|
80
81
|
ensureNetwork: () => ensureRunnerOnNetwork(),
|
|
81
82
|
supportsNoPublish: ctlSupportsNoPublish,
|
|
82
83
|
containerUser: runnerContainerUser,
|
|
@@ -99,9 +100,10 @@ function describeObservation(o) {
|
|
|
99
100
|
}
|
|
100
101
|
}
|
|
101
102
|
/** Whether an observation depends on a source being pumped. Structural ones
|
|
102
|
-
*
|
|
103
|
+
* (`components`, or anything the spec marks `structural`) run the satisfied
|
|
104
|
+
* phase only. */
|
|
103
105
|
export function sourceDependent(o) {
|
|
104
|
-
return o.kind !== "components";
|
|
106
|
+
return o.kind !== "components" && o.structural !== true;
|
|
105
107
|
}
|
|
106
108
|
/** One sample of an observation — the predicate the three phases poll. */
|
|
107
109
|
export async function observationSatisfied(o, ctx, readers) {
|
|
@@ -186,6 +188,7 @@ export async function runProductSmoke(slug, spec, deps = defaultSmokeDeps) {
|
|
|
186
188
|
let daemon = null;
|
|
187
189
|
let launched = false;
|
|
188
190
|
let handles = [];
|
|
191
|
+
let journeyError;
|
|
189
192
|
try {
|
|
190
193
|
// The same zero point as the guides: `init` on a virgin store, not a seeded
|
|
191
194
|
// config.yaml. No http redirect (port 80 needs root) and a banded proxy port
|
|
@@ -319,9 +322,15 @@ export async function runProductSmoke(slug, spec, deps = defaultSmokeDeps) {
|
|
|
319
322
|
deps.log(`${slug}: cleared phase passed for ${phased.length} observation(s)`);
|
|
320
323
|
}
|
|
321
324
|
}
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
+
catch (e) {
|
|
326
|
+
journeyError = e;
|
|
327
|
+
}
|
|
328
|
+
if (handles.length)
|
|
329
|
+
await deps.stopSources(handles).catch(() => { });
|
|
330
|
+
// cleanupDaemon ends with rmSync(storeDir); Docker's root-owned bind-mount
|
|
331
|
+
// targets make that EACCES on a non-root runner. Expected: the root nuke
|
|
332
|
+
// below is what removes them. Only a store that survives the nuke fails.
|
|
333
|
+
try {
|
|
325
334
|
await deps.cleanup({
|
|
326
335
|
deleteInstance: (id) => cli(["instance", "delete", id, "--purge"]),
|
|
327
336
|
stopDaemon: () => cli(["shutdown", "--daemon-only"]),
|
|
@@ -330,6 +339,14 @@ export async function runProductSmoke(slug, spec, deps = defaultSmokeDeps) {
|
|
|
330
339
|
storeDir,
|
|
331
340
|
containers: [`norsk-inst-${instanceId}-studio`, `norsk-inst-${instanceId}-media`],
|
|
332
341
|
});
|
|
333
|
-
|
|
342
|
+
}
|
|
343
|
+
catch (e) {
|
|
344
|
+
deps.log(`${slug}: cleanup could not remove the store itself (${e instanceof Error ? e.message : String(e)}); nuking as root`);
|
|
345
|
+
}
|
|
346
|
+
deps.nukeStoreAsRoot(storeDir);
|
|
347
|
+
if (journeyError !== undefined)
|
|
348
|
+
throw journeyError;
|
|
349
|
+
if (deps.storeExists(storeDir)) {
|
|
350
|
+
throw new Error(`${slug}: store ${storeDir} still present after the root-container nuke`);
|
|
334
351
|
}
|
|
335
352
|
}
|