@elench/testkit 0.1.83 → 0.1.85
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/lib/cli/agents/investigation-interpreter.mjs +320 -0
- package/lib/cli/agents/investigation-log.mjs +37 -0
- package/lib/cli/agents/providers/codex.mjs +1 -1
- package/lib/cli/presentation/tree-reporter.mjs +33 -1
- package/lib/cli/tui/run-session-app.mjs +73 -11
- package/lib/cli/tui/run-session-state.mjs +29 -5
- package/node_modules/@elench/next-analysis/package.json +1 -1
- package/node_modules/@elench/testkit-bridge/package.json +2 -2
- package/node_modules/@elench/testkit-protocol/package.json +1 -1
- package/node_modules/@elench/ts-analysis/package.json +1 -1
- package/package.json +7 -6
- package/lib/app/configs.test.mjs +0 -34
- package/lib/app/typecheck.test.mjs +0 -24
- package/lib/bundler/index.test.mjs +0 -164
- package/lib/cli/agents/investigation-context.test.mjs +0 -144
- package/lib/cli/agents/providers/claude.test.mjs +0 -95
- package/lib/cli/agents/providers/codex.test.mjs +0 -93
- package/lib/cli/args.test.mjs +0 -110
- package/lib/cli/command-helpers.test.mjs +0 -122
- package/lib/cli/commands/investigate.test.mjs +0 -83
- package/lib/cli/presentation/code-frames.test.mjs +0 -71
- package/lib/cli/presentation/events-reporter.test.mjs +0 -73
- package/lib/cli/presentation/run-reporter.test.mjs +0 -192
- package/lib/cli/presentation/summary-box.test.mjs +0 -60
- package/lib/cli/presentation/terminal-layout.test.mjs +0 -23
- package/lib/cli/presentation/tree-reporter.test.mjs +0 -166
- package/lib/cli/tui/run-session-app.test.mjs +0 -50
- package/lib/cli/tui/run-tree-state.test.mjs +0 -324
- package/lib/config/database.test.mjs +0 -29
- package/lib/config/discovery.test.mjs +0 -276
- package/lib/config/env.test.mjs +0 -40
- package/lib/config/index.test.mjs +0 -44
- package/lib/config/paths.test.mjs +0 -27
- package/lib/config/runtime.test.mjs +0 -82
- package/lib/config/skip-config.test.mjs +0 -63
- package/lib/config-api/index.test.mjs +0 -398
- package/lib/config-api/next-runtime-tsconfig.test.mjs +0 -58
- package/lib/coverage/backend-discovery.test.mjs +0 -61
- package/lib/coverage/evidence.test.mjs +0 -87
- package/lib/coverage/index.test.mjs +0 -715
- package/lib/coverage/routing.test.mjs +0 -36
- package/lib/coverage/shared.test.mjs +0 -72
- package/lib/database/fingerprint.test.mjs +0 -99
- package/lib/database/index.test.mjs +0 -95
- package/lib/database/naming.test.mjs +0 -39
- package/lib/database/state.test.mjs +0 -66
- package/lib/database/template-steps.test.mjs +0 -43
- package/lib/discovery/file-metadata.test.mjs +0 -51
- package/lib/discovery/index.test.mjs +0 -182
- package/lib/discovery/path-policy.test.mjs +0 -65
- package/lib/drizzle/index.test.mjs +0 -33
- package/lib/env/index.test.mjs +0 -82
- package/lib/history/index.test.mjs +0 -115
- package/lib/package.test.mjs +0 -59
- package/lib/playwright/index.test.mjs +0 -43
- package/lib/regressions/github.test.mjs +0 -324
- package/lib/regressions/index.test.mjs +0 -187
- package/lib/reporters/playwright.test.mjs +0 -167
- package/lib/runner/default-runtime-errors.test.mjs +0 -49
- package/lib/runner/execution-config.test.mjs +0 -67
- package/lib/runner/failure-details.test.mjs +0 -114
- package/lib/runner/formatting.test.mjs +0 -205
- package/lib/runner/metadata.test.mjs +0 -52
- package/lib/runner/planning.test.mjs +0 -371
- package/lib/runner/playwright-config.test.mjs +0 -78
- package/lib/runner/processes.test.mjs +0 -21
- package/lib/runner/regressions.test.mjs +0 -168
- package/lib/runner/reporting.test.mjs +0 -310
- package/lib/runner/results.test.mjs +0 -376
- package/lib/runner/runtime-manager.test.mjs +0 -252
- package/lib/runner/runtime-preparation.test.mjs +0 -141
- package/lib/runner/selection.test.mjs +0 -24
- package/lib/runner/setup-operations.test.mjs +0 -94
- package/lib/runner/state.test.mjs +0 -62
- package/lib/runner/suite-selection.test.mjs +0 -49
- package/lib/runner/template.test.mjs +0 -272
- package/lib/runtime-src/k6/http-checks.test.mjs +0 -120
- package/lib/runtime-src/k6/http.test.mjs +0 -205
- package/lib/runtime-src/shared/http-parsing.test.mjs +0 -69
- package/lib/shared/build-config.test.mjs +0 -132
- package/lib/shared/configured-steps.test.mjs +0 -102
- package/lib/shared/execution-schema.test.mjs +0 -26
- package/lib/shared/file-timeout.test.mjs +0 -64
- package/lib/shared/test-context.test.mjs +0 -43
- package/lib/timing/index.test.mjs +0 -64
- package/lib/toolchains/index.test.mjs +0 -168
- package/lib/vitest/index.test.mjs +0 -20
|
@@ -1,252 +0,0 @@
|
|
|
1
|
-
import fs from "fs";
|
|
2
|
-
import os from "os";
|
|
3
|
-
import path from "path";
|
|
4
|
-
import { afterEach, describe, expect, it } from "vitest";
|
|
5
|
-
import { createRuntimeManager } from "./runtime-manager.mjs";
|
|
6
|
-
|
|
7
|
-
const cleanups = [];
|
|
8
|
-
|
|
9
|
-
afterEach(() => {
|
|
10
|
-
while (cleanups.length > 0) {
|
|
11
|
-
cleanups.pop()();
|
|
12
|
-
}
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
function makeTempDir(prefix) {
|
|
16
|
-
const dir = fs.mkdtempSync(path.join(os.tmpdir(), prefix));
|
|
17
|
-
cleanups.push(() => fs.rmSync(dir, { recursive: true, force: true }));
|
|
18
|
-
return dir;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
function makeLifecycle() {
|
|
22
|
-
return {
|
|
23
|
-
signal: new AbortController().signal,
|
|
24
|
-
isStopRequested() {
|
|
25
|
-
return false;
|
|
26
|
-
},
|
|
27
|
-
trackGraphContext() {},
|
|
28
|
-
};
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
function makeTask(id, extras = {}) {
|
|
32
|
-
return {
|
|
33
|
-
id,
|
|
34
|
-
graphKey: "api",
|
|
35
|
-
locks: [],
|
|
36
|
-
...extras,
|
|
37
|
-
};
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
function makeHooks(events) {
|
|
41
|
-
return {
|
|
42
|
-
createRuntimeInstanceContext(runtimeId, graph, productDir) {
|
|
43
|
-
const runtimeDir = path.join(productDir, ".testkit", "_graphs", graph.dirName, "runtimes", runtimeId);
|
|
44
|
-
fs.mkdirSync(runtimeDir, { recursive: true });
|
|
45
|
-
events.created.push(runtimeId);
|
|
46
|
-
return {
|
|
47
|
-
graphKey: graph.key,
|
|
48
|
-
runtimeDir,
|
|
49
|
-
runtimeId,
|
|
50
|
-
targetNames: [...graph.targetNames],
|
|
51
|
-
};
|
|
52
|
-
},
|
|
53
|
-
async ensureRuntimeInstanceReady(context) {
|
|
54
|
-
events.ready.push(context.runtimeId);
|
|
55
|
-
},
|
|
56
|
-
async cleanupRuntimeInstanceContext(context) {
|
|
57
|
-
events.cleaned.push(context.runtimeId);
|
|
58
|
-
fs.rmSync(context.runtimeDir, { recursive: true, force: true });
|
|
59
|
-
},
|
|
60
|
-
async sleep() {
|
|
61
|
-
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
62
|
-
},
|
|
63
|
-
};
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
describe("runtime-manager", () => {
|
|
67
|
-
it("spreads concurrent leases across runtime instances", async () => {
|
|
68
|
-
const productDir = makeTempDir("testkit-runtime-manager-");
|
|
69
|
-
const events = { created: [], ready: [], cleaned: [] };
|
|
70
|
-
const manager = createRuntimeManager({
|
|
71
|
-
productDir,
|
|
72
|
-
lifecycle: makeLifecycle(),
|
|
73
|
-
graphs: [
|
|
74
|
-
{
|
|
75
|
-
key: "api",
|
|
76
|
-
dirName: "api",
|
|
77
|
-
targetNames: ["api"],
|
|
78
|
-
instanceCount: 2,
|
|
79
|
-
maxConcurrentTasks: 1,
|
|
80
|
-
},
|
|
81
|
-
],
|
|
82
|
-
hooks: makeHooks(events),
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
const leaseOne = await manager.acquire(makeTask(1));
|
|
86
|
-
const leaseTwo = await manager.acquire(makeTask(2));
|
|
87
|
-
|
|
88
|
-
expect(leaseOne.slot.runtimeId).toBe("runtime-1");
|
|
89
|
-
expect(leaseTwo.slot.runtimeId).toBe("runtime-2");
|
|
90
|
-
expect(events.created).toEqual(["runtime-1", "runtime-2"]);
|
|
91
|
-
|
|
92
|
-
await manager.release(leaseOne);
|
|
93
|
-
await manager.release(leaseTwo);
|
|
94
|
-
await manager.cleanupAll();
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
it("marks conflicting locks unavailable until the first lease releases", async () => {
|
|
98
|
-
const productDir = makeTempDir("testkit-runtime-manager-");
|
|
99
|
-
const manager = createRuntimeManager({
|
|
100
|
-
productDir,
|
|
101
|
-
lifecycle: makeLifecycle(),
|
|
102
|
-
graphs: [
|
|
103
|
-
{
|
|
104
|
-
key: "api",
|
|
105
|
-
dirName: "api",
|
|
106
|
-
targetNames: ["api"],
|
|
107
|
-
instanceCount: 1,
|
|
108
|
-
maxConcurrentTasks: 1,
|
|
109
|
-
},
|
|
110
|
-
],
|
|
111
|
-
hooks: makeHooks({ created: [], ready: [], cleaned: [] }),
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
const firstLease = await manager.acquire(makeTask(1, { locks: ["shared-lock"] }));
|
|
115
|
-
expect(manager.canAcquire(makeTask(2, { locks: ["shared-lock"] }))).toBe(false);
|
|
116
|
-
|
|
117
|
-
await manager.release(firstLease);
|
|
118
|
-
const secondLease = await manager.acquire(makeTask(2, { locks: ["shared-lock"] }));
|
|
119
|
-
|
|
120
|
-
await manager.release(secondLease);
|
|
121
|
-
await manager.cleanupAll();
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
it("does not drain an invalidated runtime slot until all active leases release", async () => {
|
|
125
|
-
const productDir = makeTempDir("testkit-runtime-manager-");
|
|
126
|
-
const events = { created: [], ready: [], cleaned: [] };
|
|
127
|
-
const manager = createRuntimeManager({
|
|
128
|
-
productDir,
|
|
129
|
-
lifecycle: makeLifecycle(),
|
|
130
|
-
graphs: [
|
|
131
|
-
{
|
|
132
|
-
key: "api",
|
|
133
|
-
dirName: "api",
|
|
134
|
-
targetNames: ["api"],
|
|
135
|
-
instanceCount: 1,
|
|
136
|
-
maxConcurrentTasks: 2,
|
|
137
|
-
},
|
|
138
|
-
],
|
|
139
|
-
hooks: makeHooks(events),
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
const leaseOne = await manager.acquire(makeTask(1));
|
|
143
|
-
const leaseTwo = await manager.acquire(makeTask(2));
|
|
144
|
-
|
|
145
|
-
await manager.release(leaseOne, { invalidate: true });
|
|
146
|
-
expect(events.cleaned).toEqual([]);
|
|
147
|
-
|
|
148
|
-
await manager.release(leaseTwo);
|
|
149
|
-
expect(events.cleaned).toEqual(["runtime-1"]);
|
|
150
|
-
});
|
|
151
|
-
|
|
152
|
-
it("removes lease directories on release", async () => {
|
|
153
|
-
const productDir = makeTempDir("testkit-runtime-manager-");
|
|
154
|
-
const manager = createRuntimeManager({
|
|
155
|
-
productDir,
|
|
156
|
-
lifecycle: makeLifecycle(),
|
|
157
|
-
graphs: [
|
|
158
|
-
{
|
|
159
|
-
key: "api",
|
|
160
|
-
dirName: "api",
|
|
161
|
-
targetNames: ["api"],
|
|
162
|
-
instanceCount: 1,
|
|
163
|
-
maxConcurrentTasks: 1,
|
|
164
|
-
},
|
|
165
|
-
],
|
|
166
|
-
hooks: makeHooks({ created: [], ready: [], cleaned: [] }),
|
|
167
|
-
});
|
|
168
|
-
|
|
169
|
-
const lease = await manager.acquire(makeTask(1));
|
|
170
|
-
expect(fs.existsSync(lease.leaseDir)).toBe(true);
|
|
171
|
-
|
|
172
|
-
await manager.release(lease);
|
|
173
|
-
expect(fs.existsSync(lease.leaseDir)).toBe(false);
|
|
174
|
-
|
|
175
|
-
await manager.cleanupAll();
|
|
176
|
-
});
|
|
177
|
-
|
|
178
|
-
it("exposes runtime capacity through canAcquire", async () => {
|
|
179
|
-
const productDir = makeTempDir("testkit-runtime-manager-");
|
|
180
|
-
const manager = createRuntimeManager({
|
|
181
|
-
productDir,
|
|
182
|
-
lifecycle: makeLifecycle(),
|
|
183
|
-
graphs: [
|
|
184
|
-
{
|
|
185
|
-
key: "api",
|
|
186
|
-
dirName: "api",
|
|
187
|
-
targetNames: ["api"],
|
|
188
|
-
instanceCount: 1,
|
|
189
|
-
maxConcurrentTasks: 2,
|
|
190
|
-
},
|
|
191
|
-
],
|
|
192
|
-
hooks: makeHooks({ created: [], ready: [], cleaned: [] }),
|
|
193
|
-
});
|
|
194
|
-
|
|
195
|
-
expect(manager.canAcquire(makeTask(1))).toBe(true);
|
|
196
|
-
const leaseOne = await manager.acquire(makeTask(1));
|
|
197
|
-
expect(manager.canAcquire(makeTask(2))).toBe(true);
|
|
198
|
-
const leaseTwo = await manager.acquire(makeTask(2));
|
|
199
|
-
expect(manager.canAcquire(makeTask(3))).toBe(false);
|
|
200
|
-
|
|
201
|
-
await manager.release(leaseOne);
|
|
202
|
-
expect(manager.canAcquire(makeTask(3))).toBe(true);
|
|
203
|
-
await manager.release(leaseTwo);
|
|
204
|
-
await manager.cleanupAll();
|
|
205
|
-
});
|
|
206
|
-
|
|
207
|
-
it("blocks a graph after fatal runtime initialization failure and drains remaining tasks", async () => {
|
|
208
|
-
const productDir = makeTempDir("testkit-runtime-manager-");
|
|
209
|
-
const manager = createRuntimeManager({
|
|
210
|
-
productDir,
|
|
211
|
-
lifecycle: makeLifecycle(),
|
|
212
|
-
graphs: [
|
|
213
|
-
{
|
|
214
|
-
key: "api",
|
|
215
|
-
dirName: "api",
|
|
216
|
-
targetNames: ["api"],
|
|
217
|
-
instanceCount: 1,
|
|
218
|
-
maxConcurrentTasks: 1,
|
|
219
|
-
},
|
|
220
|
-
],
|
|
221
|
-
hooks: {
|
|
222
|
-
...makeHooks({ created: [], ready: [], cleaned: [] }),
|
|
223
|
-
async ensureRuntimeInstanceReady() {
|
|
224
|
-
throw new Error("prepare failed");
|
|
225
|
-
},
|
|
226
|
-
},
|
|
227
|
-
});
|
|
228
|
-
|
|
229
|
-
const firstTask = makeTask(1, { file: "tests/a.int.testkit.ts", targetName: "api" });
|
|
230
|
-
const secondTask = makeTask(2, { file: "tests/b.int.testkit.ts", targetName: "api" });
|
|
231
|
-
|
|
232
|
-
await expect(manager.acquire(firstTask)).rejects.toThrow(/prepare failed/);
|
|
233
|
-
expect(manager.canAcquire(secondTask)).toBe(false);
|
|
234
|
-
|
|
235
|
-
const queue = [secondTask];
|
|
236
|
-
expect(manager.drainFailedTasks(queue)).toEqual([
|
|
237
|
-
{
|
|
238
|
-
task: secondTask,
|
|
239
|
-
reason: "Not run because runtime graph failed to initialize: prepare failed",
|
|
240
|
-
message: "prepare failed",
|
|
241
|
-
graph: {
|
|
242
|
-
key: "api",
|
|
243
|
-
dirName: "api",
|
|
244
|
-
targetNames: ["api"],
|
|
245
|
-
instanceCount: 1,
|
|
246
|
-
maxConcurrentTasks: 1,
|
|
247
|
-
},
|
|
248
|
-
},
|
|
249
|
-
]);
|
|
250
|
-
expect(queue).toEqual([]);
|
|
251
|
-
});
|
|
252
|
-
});
|
|
@@ -1,141 +0,0 @@
|
|
|
1
|
-
import fs from "fs";
|
|
2
|
-
import os from "os";
|
|
3
|
-
import path from "path";
|
|
4
|
-
import { afterEach, describe, expect, it } from "vitest";
|
|
5
|
-
import { prepareRuntimeService } from "./runtime-preparation.mjs";
|
|
6
|
-
import { resolveRuntimeInstanceConfigs } from "./template.mjs";
|
|
7
|
-
|
|
8
|
-
const tempDirs = [];
|
|
9
|
-
|
|
10
|
-
afterEach(() => {
|
|
11
|
-
while (tempDirs.length > 0) {
|
|
12
|
-
fs.rmSync(tempDirs.pop(), { recursive: true, force: true });
|
|
13
|
-
}
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
function makeTempDir(prefix) {
|
|
17
|
-
const dir = fs.mkdtempSync(path.join(os.tmpdir(), prefix));
|
|
18
|
-
tempDirs.push(dir);
|
|
19
|
-
return dir;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
function writeFile(filePath, contents) {
|
|
23
|
-
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
24
|
-
fs.writeFileSync(filePath, contents);
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
function makeResolvedRuntimeConfig(productDir, prepareConfig) {
|
|
28
|
-
const rawConfig = {
|
|
29
|
-
name: "api",
|
|
30
|
-
productDir,
|
|
31
|
-
testkit: {
|
|
32
|
-
envFiles: [],
|
|
33
|
-
serviceEnv: {},
|
|
34
|
-
runtime: {
|
|
35
|
-
instances: 1,
|
|
36
|
-
maxConcurrentTasks: 1,
|
|
37
|
-
prepare: prepareConfig,
|
|
38
|
-
},
|
|
39
|
-
local: {
|
|
40
|
-
cwd: ".",
|
|
41
|
-
start: "node {prepareDir}/server.mjs",
|
|
42
|
-
port: 3010,
|
|
43
|
-
baseUrl: "http://127.0.0.1:{port}",
|
|
44
|
-
readyUrl: "http://127.0.0.1:{port}/health",
|
|
45
|
-
env: {},
|
|
46
|
-
},
|
|
47
|
-
},
|
|
48
|
-
};
|
|
49
|
-
|
|
50
|
-
const runtimeDir = path.join(productDir, ".testkit", "_graphs", "api", "runtimes", "runtime-1");
|
|
51
|
-
fs.mkdirSync(runtimeDir, { recursive: true });
|
|
52
|
-
return resolveRuntimeInstanceConfigs([rawConfig], "runtime-1", runtimeDir, {
|
|
53
|
-
graphDirName: "api",
|
|
54
|
-
portNamespaceIndex: 0,
|
|
55
|
-
portNamespaceStride: 1,
|
|
56
|
-
})[0];
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
describe("runtime preparation", () => {
|
|
60
|
-
it("runs prepare steps once and reuses the manifest until inputs change", async () => {
|
|
61
|
-
const productDir = makeTempDir("testkit-runtime-prepare-");
|
|
62
|
-
writeFile(path.join(productDir, "src", "message.txt"), "hello one\n");
|
|
63
|
-
writeFile(
|
|
64
|
-
path.join(productDir, "scripts", "prepare.mjs"),
|
|
65
|
-
[
|
|
66
|
-
'import fs from "fs";',
|
|
67
|
-
'import path from "path";',
|
|
68
|
-
'const prepareDir = process.argv[2];',
|
|
69
|
-
'const productDir = process.cwd();',
|
|
70
|
-
'const message = fs.readFileSync(path.join(productDir, "src", "message.txt"), "utf8").trim();',
|
|
71
|
-
'const counterDir = path.join(productDir, ".runtime", "prepared");',
|
|
72
|
-
'const counterFile = path.join(counterDir, "build-count.txt");',
|
|
73
|
-
'const current = fs.existsSync(counterFile) ? Number.parseInt(fs.readFileSync(counterFile, "utf8"), 10) || 0 : 0;',
|
|
74
|
-
'fs.mkdirSync(counterDir, { recursive: true });',
|
|
75
|
-
'fs.mkdirSync(prepareDir, { recursive: true });',
|
|
76
|
-
'fs.writeFileSync(counterFile, `${current + 1}\\n`);',
|
|
77
|
-
'fs.writeFileSync(path.join(prepareDir, "message.txt"), `${message}\\n`);',
|
|
78
|
-
].join("\n")
|
|
79
|
-
);
|
|
80
|
-
|
|
81
|
-
const config = makeResolvedRuntimeConfig(productDir, {
|
|
82
|
-
inputs: ["src/message.txt"],
|
|
83
|
-
steps: [
|
|
84
|
-
{
|
|
85
|
-
kind: "command",
|
|
86
|
-
cmd: "node scripts/prepare.mjs {prepareDir}",
|
|
87
|
-
},
|
|
88
|
-
],
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
await prepareRuntimeService(config);
|
|
92
|
-
expect(
|
|
93
|
-
fs.readFileSync(path.join(productDir, ".runtime", "prepared", "build-count.txt"), "utf8").trim()
|
|
94
|
-
).toBe("1");
|
|
95
|
-
expect(fs.readFileSync(path.join(config.testkit.prepareDir, "message.txt"), "utf8").trim()).toBe(
|
|
96
|
-
"hello one"
|
|
97
|
-
);
|
|
98
|
-
|
|
99
|
-
await prepareRuntimeService(config);
|
|
100
|
-
expect(
|
|
101
|
-
fs.readFileSync(path.join(productDir, ".runtime", "prepared", "build-count.txt"), "utf8").trim()
|
|
102
|
-
).toBe("1");
|
|
103
|
-
|
|
104
|
-
fs.writeFileSync(path.join(productDir, "src", "message.txt"), "hello two\n");
|
|
105
|
-
await prepareRuntimeService(config);
|
|
106
|
-
expect(
|
|
107
|
-
fs.readFileSync(path.join(productDir, ".runtime", "prepared", "build-count.txt"), "utf8").trim()
|
|
108
|
-
).toBe("2");
|
|
109
|
-
expect(fs.readFileSync(path.join(config.testkit.prepareDir, "message.txt"), "utf8").trim()).toBe(
|
|
110
|
-
"hello two"
|
|
111
|
-
);
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
it("cleans incomplete prepared output when a prepare step fails", async () => {
|
|
115
|
-
const productDir = makeTempDir("testkit-runtime-prepare-fail-");
|
|
116
|
-
writeFile(
|
|
117
|
-
path.join(productDir, "scripts", "prepare-fail.mjs"),
|
|
118
|
-
[
|
|
119
|
-
'import fs from "fs";',
|
|
120
|
-
'import path from "path";',
|
|
121
|
-
'const prepareDir = process.argv[2];',
|
|
122
|
-
'fs.mkdirSync(prepareDir, { recursive: true });',
|
|
123
|
-
'fs.writeFileSync(path.join(prepareDir, "partial.txt"), "partial\\n");',
|
|
124
|
-
'throw new Error("prepare failed");',
|
|
125
|
-
].join("\n")
|
|
126
|
-
);
|
|
127
|
-
|
|
128
|
-
const config = makeResolvedRuntimeConfig(productDir, {
|
|
129
|
-
steps: [
|
|
130
|
-
{
|
|
131
|
-
kind: "command",
|
|
132
|
-
cmd: "node scripts/prepare-fail.mjs {prepareDir}",
|
|
133
|
-
},
|
|
134
|
-
],
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
await expect(prepareRuntimeService(config)).rejects.toThrow("Command failed with exit code 1");
|
|
138
|
-
expect(fs.existsSync(config.testkit.prepareDir)).toBe(false);
|
|
139
|
-
expect(fs.existsSync(path.join(productDir, ".testkit", "_graphs", "api", "runtimes", "runtime-1", "services", "api", "prepared", "prepare-manifest.json"))).toBe(false);
|
|
140
|
-
});
|
|
141
|
-
});
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from "vitest";
|
|
2
|
-
import { findUnmatchedRequestedFiles, isFullRunSelection } from "./selection.mjs";
|
|
3
|
-
|
|
4
|
-
describe("runner selection", () => {
|
|
5
|
-
it("finds unmatched requested files", () => {
|
|
6
|
-
const unmatched = findUnmatchedRequestedFiles(
|
|
7
|
-
[{ name: "api" }],
|
|
8
|
-
["int"],
|
|
9
|
-
[],
|
|
10
|
-
["tests/a.int.testkit.ts", "tests/missing.int.testkit.ts"],
|
|
11
|
-
() => [{ files: ["tests/a.int.testkit.ts"] }],
|
|
12
|
-
(value) => value
|
|
13
|
-
);
|
|
14
|
-
|
|
15
|
-
expect(unmatched).toEqual(["tests/missing.int.testkit.ts"]);
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
it("detects a full run selection", () => {
|
|
19
|
-
expect(isFullRunSelection(["all"], [], [], null, null)).toBe(true);
|
|
20
|
-
expect(isFullRunSelection(["all"], [{ raw: "auth" }], [], null, null)).toBe(false);
|
|
21
|
-
expect(isFullRunSelection(["all"], [], ["a"], null, null)).toBe(false);
|
|
22
|
-
expect(isFullRunSelection(["int"], [], [], null, null)).toBe(false);
|
|
23
|
-
});
|
|
24
|
-
});
|
|
@@ -1,94 +0,0 @@
|
|
|
1
|
-
import fs from "fs";
|
|
2
|
-
import os from "os";
|
|
3
|
-
import path from "path";
|
|
4
|
-
import { afterEach, describe, expect, it } from "vitest";
|
|
5
|
-
import { createRunLogRegistry } from "./logs.mjs";
|
|
6
|
-
import { createSetupOperationRegistry } from "./setup-operations.mjs";
|
|
7
|
-
|
|
8
|
-
const tempDirs = [];
|
|
9
|
-
|
|
10
|
-
afterEach(() => {
|
|
11
|
-
while (tempDirs.length > 0) {
|
|
12
|
-
fs.rmSync(tempDirs.pop(), { recursive: true, force: true });
|
|
13
|
-
}
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
function makeTempDir(prefix) {
|
|
17
|
-
const dir = fs.mkdtempSync(path.join(os.tmpdir(), prefix));
|
|
18
|
-
tempDirs.push(dir);
|
|
19
|
-
return dir;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
function makeConfig(productDir) {
|
|
23
|
-
return {
|
|
24
|
-
name: "api",
|
|
25
|
-
runtimeLabel: "api",
|
|
26
|
-
productDir,
|
|
27
|
-
};
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
describe("setup operation registry", () => {
|
|
31
|
-
it("tracks running and finished setup operations with log refs", () => {
|
|
32
|
-
const productDir = makeTempDir("testkit-setup-ops-");
|
|
33
|
-
const logRegistry = createRunLogRegistry(productDir);
|
|
34
|
-
const changes = [];
|
|
35
|
-
const registry = createSetupOperationRegistry({
|
|
36
|
-
logRegistry,
|
|
37
|
-
onChange(operations) {
|
|
38
|
-
changes.push(operations);
|
|
39
|
-
},
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
const operation = registry.start({
|
|
43
|
-
config: makeConfig(productDir),
|
|
44
|
-
stage: "template:migrate:api:1",
|
|
45
|
-
kind: "setup-step",
|
|
46
|
-
summary: "command: node scripts/migrate.mjs",
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
expect(operation.logRef).toMatchObject({
|
|
50
|
-
path: ".testkit/results/setup/api__api__template-migrate-api-1.log",
|
|
51
|
-
stage: "template:migrate:api:1",
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
const finished = registry.finish(operation, {
|
|
55
|
-
status: "passed",
|
|
56
|
-
summary: "command: node scripts/migrate.mjs",
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
expect(finished.status).toBe("passed");
|
|
60
|
-
expect(finished.durationMs).toBeGreaterThanOrEqual(0);
|
|
61
|
-
expect(registry.listOperations()).toEqual([
|
|
62
|
-
expect.objectContaining({
|
|
63
|
-
stage: "template:migrate:api:1",
|
|
64
|
-
kind: "setup-step",
|
|
65
|
-
status: "passed",
|
|
66
|
-
summary: "command: node scripts/migrate.mjs",
|
|
67
|
-
logRef: operation.logRef,
|
|
68
|
-
}),
|
|
69
|
-
]);
|
|
70
|
-
expect(changes).toHaveLength(2);
|
|
71
|
-
|
|
72
|
-
logRegistry.closeAll();
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
it("records cached setup operations without logs", () => {
|
|
76
|
-
const productDir = makeTempDir("testkit-setup-ops-cached-");
|
|
77
|
-
const registry = createSetupOperationRegistry();
|
|
78
|
-
|
|
79
|
-
const operation = registry.recordCached({
|
|
80
|
-
config: makeConfig(productDir),
|
|
81
|
-
stage: "template",
|
|
82
|
-
kind: "database-template",
|
|
83
|
-
summary: "template cache hit",
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
expect(operation).toMatchObject({
|
|
87
|
-
stage: "template",
|
|
88
|
-
kind: "database-template",
|
|
89
|
-
status: "cached",
|
|
90
|
-
summary: "template cache hit",
|
|
91
|
-
logRef: null,
|
|
92
|
-
});
|
|
93
|
-
});
|
|
94
|
-
});
|
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
import fs from "fs";
|
|
2
|
-
import os from "os";
|
|
3
|
-
import path from "path";
|
|
4
|
-
import { afterEach, describe, expect, it } from "vitest";
|
|
5
|
-
import {
|
|
6
|
-
findGraphDirsForService,
|
|
7
|
-
findRuntimeStateDirs,
|
|
8
|
-
normalizePathSeparators,
|
|
9
|
-
readGraphMetadata,
|
|
10
|
-
writeGraphMetadata,
|
|
11
|
-
} from "./state.mjs";
|
|
12
|
-
|
|
13
|
-
const tempDirs = [];
|
|
14
|
-
|
|
15
|
-
afterEach(() => {
|
|
16
|
-
for (const dir of tempDirs.splice(0)) {
|
|
17
|
-
fs.rmSync(dir, { recursive: true, force: true });
|
|
18
|
-
}
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
function mkTempDir() {
|
|
22
|
-
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "testkit-runner-state-"));
|
|
23
|
-
tempDirs.push(dir);
|
|
24
|
-
return dir;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
describe("runner-state", () => {
|
|
28
|
-
it("finds runtime state directories", () => {
|
|
29
|
-
const rootDir = mkTempDir();
|
|
30
|
-
const deep = path.join(rootDir, "workers", "w1", "deps", "api");
|
|
31
|
-
fs.mkdirSync(deep, { recursive: true });
|
|
32
|
-
fs.writeFileSync(path.join(rootDir, "workers", "database_backend"), "local");
|
|
33
|
-
fs.writeFileSync(path.join(deep, "database_backend"), "local");
|
|
34
|
-
|
|
35
|
-
const found = findRuntimeStateDirs(
|
|
36
|
-
rootDir,
|
|
37
|
-
(dir) => fs.existsSync(path.join(dir, "database_backend"))
|
|
38
|
-
);
|
|
39
|
-
|
|
40
|
-
expect(found).toEqual([
|
|
41
|
-
deep,
|
|
42
|
-
path.join(rootDir, "workers"),
|
|
43
|
-
]);
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
it("writes, reads, and finds graph metadata", () => {
|
|
47
|
-
const productDir = mkTempDir();
|
|
48
|
-
const graphDir = path.join(productDir, ".testkit", "_graphs", "api__frontend");
|
|
49
|
-
writeGraphMetadata(graphDir, {
|
|
50
|
-
runtimeNames: ["api", "frontend"],
|
|
51
|
-
targetNames: ["frontend", "api"],
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
expect(readGraphMetadata(graphDir)).toEqual({
|
|
55
|
-
runtimeServices: ["api", "frontend"],
|
|
56
|
-
targetServices: ["api", "frontend"],
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
expect(findGraphDirsForService(productDir, "frontend")).toEqual([graphDir]);
|
|
60
|
-
expect(normalizePathSeparators(`a${path.sep}b${path.sep}c`)).toBe("a/b/c");
|
|
61
|
-
});
|
|
62
|
-
});
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from "vitest";
|
|
2
|
-
import {
|
|
3
|
-
isAllTypeSelection,
|
|
4
|
-
matchesSelectedTypes,
|
|
5
|
-
matchesSuiteSelectors,
|
|
6
|
-
normalizeTypeValues,
|
|
7
|
-
parseSuiteSelectors,
|
|
8
|
-
suiteSelectionType,
|
|
9
|
-
} from "./suite-selection.mjs";
|
|
10
|
-
|
|
11
|
-
describe("runner suite selection", () => {
|
|
12
|
-
it("normalizes selected type values", () => {
|
|
13
|
-
expect(normalizeTypeValues([])).toEqual(["all"]);
|
|
14
|
-
expect(normalizeTypeValues(["int,e2e,scenario", "dal"])).toEqual([
|
|
15
|
-
"int",
|
|
16
|
-
"e2e",
|
|
17
|
-
"scenario",
|
|
18
|
-
"dal",
|
|
19
|
-
]);
|
|
20
|
-
expect(() => normalizeTypeValues(["all", "int"])).toThrow("cannot be combined");
|
|
21
|
-
expect(() => normalizeTypeValues(["jest"])).toThrow("Unknown type");
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
it("parses suite selectors", () => {
|
|
25
|
-
expect(parseSuiteSelectors(["auth,scenario:journeys,dal:queries"])).toEqual([
|
|
26
|
-
{ kind: "plain", name: "auth", raw: "auth" },
|
|
27
|
-
{ kind: "typed", type: "scenario", name: "journeys", raw: "scenario:journeys" },
|
|
28
|
-
{ kind: "typed", type: "dal", name: "queries", raw: "dal:queries" },
|
|
29
|
-
]);
|
|
30
|
-
expect(() => parseSuiteSelectors(["all:auth"])).toThrow("Unknown suite selector type");
|
|
31
|
-
expect(() => parseSuiteSelectors(["int:"])).toThrow("Invalid suite selector");
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
it("matches suite types and selectors", () => {
|
|
35
|
-
expect(isAllTypeSelection(["all"])).toBe(true);
|
|
36
|
-
expect(matchesSelectedTypes("int", ["int", "dal"])).toBe(true);
|
|
37
|
-
expect(matchesSelectedTypes("pw", ["int", "dal"])).toBe(false);
|
|
38
|
-
expect(matchesSuiteSelectors("int", "auth", parseSuiteSelectors(["auth"]))).toBe(true);
|
|
39
|
-
expect(matchesSuiteSelectors("e2e", "auth", parseSuiteSelectors(["int:auth"]))).toBe(false);
|
|
40
|
-
expect(matchesSuiteSelectors("int", "auth", parseSuiteSelectors(["int:auth"]))).toBe(true);
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
it("maps discovered suites to user-facing selection types", () => {
|
|
44
|
-
expect(suiteSelectionType("integration", "k6")).toBe("int");
|
|
45
|
-
expect(suiteSelectionType("scenario", "k6")).toBe("scenario");
|
|
46
|
-
expect(suiteSelectionType("e2e", "playwright")).toBe("pw");
|
|
47
|
-
expect(suiteSelectionType("dal", "k6")).toBe("dal");
|
|
48
|
-
});
|
|
49
|
-
});
|