@elench/testkit 0.1.37 → 0.1.39
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/README.md +12 -3
- package/lib/cli/args.mjs +6 -7
- package/lib/cli/args.test.mjs +9 -4
- package/lib/cli/index.mjs +15 -4
- package/lib/config/index.mjs +126 -2
- package/lib/runner/default-runtime-runner.mjs +4 -4
- package/lib/runner/execution-config.mjs +108 -0
- package/lib/runner/execution-config.test.mjs +101 -0
- package/lib/runner/lifecycle.mjs +7 -7
- package/lib/runner/orchestrator.mjs +51 -24
- package/lib/runner/planning.mjs +42 -2
- package/lib/runner/planning.test.mjs +175 -3
- package/lib/runner/playwright-config.test.mjs +1 -1
- package/lib/runner/playwright-runner.mjs +2 -2
- package/lib/runner/readiness.mjs +2 -2
- package/lib/runner/reporting.mjs +5 -2
- package/lib/runner/reporting.test.mjs +12 -1
- package/lib/runner/runtime-contexts.mjs +38 -47
- package/lib/runner/services.mjs +4 -4
- package/lib/runner/stack-manager.mjs +146 -0
- package/lib/runner/state-io.mjs +23 -0
- package/lib/runner/template.mjs +107 -39
- package/lib/runner/template.test.mjs +68 -41
- package/lib/runner/worker-loop.mjs +17 -14
- package/lib/setup/index.d.ts +25 -1
- package/package.json +1 -1
|
@@ -1,15 +1,18 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import os from "os";
|
|
3
|
+
import path from "path";
|
|
1
4
|
import { describe, expect, it } from "vitest";
|
|
2
5
|
import {
|
|
3
6
|
buildExecutionEnv,
|
|
4
7
|
buildPlaywrightEnv,
|
|
5
8
|
buildPortMap,
|
|
6
9
|
finalizeString,
|
|
7
|
-
|
|
10
|
+
getStackServiceStateDir,
|
|
8
11
|
normalizeSocketHost,
|
|
9
12
|
numericPortFromUrl,
|
|
10
13
|
resolveServiceStateDir,
|
|
14
|
+
resolveStackRuntimeConfigs,
|
|
11
15
|
resolveTemplateString,
|
|
12
|
-
resolveWorkerRuntimeConfigs,
|
|
13
16
|
rewriteUrlPort,
|
|
14
17
|
socketFromUrl,
|
|
15
18
|
} from "./template.mjs";
|
|
@@ -34,7 +37,7 @@ describe("runner-template", () => {
|
|
|
34
37
|
makeRuntimeConfig("frontend", { port: 3001, baseUrl: "http://127.0.0.1:{port}" }),
|
|
35
38
|
];
|
|
36
39
|
|
|
37
|
-
expect([...buildPortMap(configs, 2).entries()]).toEqual([
|
|
40
|
+
expect([...buildPortMap(configs, "stack-2").entries()]).toEqual([
|
|
38
41
|
["api", 3100],
|
|
39
42
|
["frontend", 3101],
|
|
40
43
|
]);
|
|
@@ -45,14 +48,22 @@ describe("runner-template", () => {
|
|
|
45
48
|
makeRuntimeConfig("api", { port: 3000, baseUrl: "http://127.0.0.1:{port}" }),
|
|
46
49
|
makeRuntimeConfig("other", { port: 3000, baseUrl: "http://127.0.0.1:{port}" }),
|
|
47
50
|
],
|
|
48
|
-
|
|
51
|
+
"shared"
|
|
49
52
|
)
|
|
50
|
-
).toThrow("
|
|
53
|
+
).toThrow("Stack port collision");
|
|
51
54
|
});
|
|
52
55
|
|
|
53
56
|
it("resolves template strings and URL rewrites", () => {
|
|
57
|
+
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "testkit-template-"));
|
|
58
|
+
const apiStateDir = path.join(tmpDir, "api");
|
|
59
|
+
fs.mkdirSync(apiStateDir, { recursive: true });
|
|
60
|
+
fs.writeFileSync(
|
|
61
|
+
path.join(apiStateDir, "database_url"),
|
|
62
|
+
"postgres://testkit:testkit@127.0.0.1:55432/onix_db"
|
|
63
|
+
);
|
|
64
|
+
|
|
54
65
|
const context = {
|
|
55
|
-
|
|
66
|
+
stackId: "stack-2",
|
|
56
67
|
targetName: "frontend",
|
|
57
68
|
serviceName: "frontend",
|
|
58
69
|
serviceStateDir: "/tmp/state",
|
|
@@ -62,11 +73,17 @@ describe("runner-template", () => {
|
|
|
62
73
|
]),
|
|
63
74
|
baseUrlByService: new Map([["api", "http://127.0.0.1:3100"]]),
|
|
64
75
|
readyUrlByService: new Map([["api", "http://127.0.0.1:3100/health"]]),
|
|
76
|
+
stateDirByService: new Map([["api", apiStateDir]]),
|
|
65
77
|
urlMappings: [["http://api:3000", "http://127.0.0.1:3100"]],
|
|
66
78
|
};
|
|
67
79
|
|
|
68
|
-
expect(resolveTemplateString("{
|
|
80
|
+
expect(resolveTemplateString("{stack}:{target}:{service}", context)).toBe(
|
|
81
|
+
"stack-2:frontend:frontend"
|
|
82
|
+
);
|
|
69
83
|
expect(resolveTemplateString("{baseUrl:api}", context)).toBe("http://127.0.0.1:3100");
|
|
84
|
+
expect(resolveTemplateString("{dbHost:api}:{dbPort:api}/{dbName:api}", context)).toBe(
|
|
85
|
+
"127.0.0.1:55432/onix_db"
|
|
86
|
+
);
|
|
70
87
|
expect(finalizeString("API={baseUrl:api} OLD=http://api:3000", context)).toBe(
|
|
71
88
|
"API=http://127.0.0.1:3100 OLD=http://127.0.0.1:3100"
|
|
72
89
|
);
|
|
@@ -75,21 +92,26 @@ describe("runner-template", () => {
|
|
|
75
92
|
);
|
|
76
93
|
});
|
|
77
94
|
|
|
78
|
-
it("builds
|
|
79
|
-
const
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
95
|
+
it("builds stack runtime configs and execution env", () => {
|
|
96
|
+
const stackStateDir = fs.mkdtempSync(path.join(os.tmpdir(), "testkit-stack-"));
|
|
97
|
+
const api = makeRuntimeConfig(
|
|
98
|
+
"api",
|
|
99
|
+
{
|
|
100
|
+
cwd: ".",
|
|
101
|
+
start: "npm run api",
|
|
102
|
+
port: 3000,
|
|
103
|
+
baseUrl: "http://127.0.0.1:{port}",
|
|
104
|
+
readyUrl: "http://127.0.0.1:{port}/health",
|
|
105
|
+
env: {
|
|
106
|
+
PORT: "{port}",
|
|
107
|
+
},
|
|
91
108
|
},
|
|
92
|
-
|
|
109
|
+
{
|
|
110
|
+
serviceEnv: {
|
|
111
|
+
API_KEY: "secret",
|
|
112
|
+
},
|
|
113
|
+
}
|
|
114
|
+
);
|
|
93
115
|
const frontend = makeRuntimeConfig("frontend", {
|
|
94
116
|
cwd: "frontend",
|
|
95
117
|
start: "npm run web",
|
|
@@ -98,26 +120,29 @@ describe("runner-template", () => {
|
|
|
98
120
|
readyUrl: "http://127.0.0.1:{port}",
|
|
99
121
|
env: {
|
|
100
122
|
NEXT_PUBLIC_API_URL: "{baseUrl:api}",
|
|
123
|
+
ONIX_DB_HOST: "{dbHost:api}",
|
|
101
124
|
},
|
|
102
125
|
});
|
|
103
126
|
|
|
104
|
-
const resolved =
|
|
127
|
+
const resolved = resolveStackRuntimeConfigs(frontend, [api, frontend], "stack-2", stackStateDir);
|
|
105
128
|
expect(resolved[0].testkit.local.port).toBe(3100);
|
|
106
|
-
expect(resolved[1].testkit.local.env.NEXT_PUBLIC_API_URL).toBe("
|
|
107
|
-
expect(resolveServiceStateDir(
|
|
108
|
-
|
|
129
|
+
expect(resolved[1].testkit.local.env.NEXT_PUBLIC_API_URL).toBe("{baseUrl:api}");
|
|
130
|
+
expect(resolveServiceStateDir(stackStateDir, "frontend", api)).toBe(
|
|
131
|
+
`${stackStateDir}/deps/api`
|
|
132
|
+
);
|
|
133
|
+
expect(getStackServiceStateDir(stackStateDir, "frontend", "frontend")).toBe(stackStateDir);
|
|
134
|
+
|
|
135
|
+
fs.mkdirSync(path.join(stackStateDir, "deps", "api"), { recursive: true });
|
|
136
|
+
fs.writeFileSync(
|
|
137
|
+
path.join(stackStateDir, "deps", "api", "database_url"),
|
|
138
|
+
"postgres://testkit:testkit@127.0.0.1:55432/onix_db"
|
|
139
|
+
);
|
|
109
140
|
|
|
110
141
|
expect(
|
|
111
142
|
buildExecutionEnv(
|
|
143
|
+
resolved[1],
|
|
112
144
|
{
|
|
113
|
-
|
|
114
|
-
testkit: {
|
|
115
|
-
serviceEnv: {
|
|
116
|
-
API_KEY: "secret",
|
|
117
|
-
},
|
|
118
|
-
},
|
|
119
|
-
},
|
|
120
|
-
{
|
|
145
|
+
...resolved[1].testkit.local.env,
|
|
121
146
|
DATABASE_URL: "gone",
|
|
122
147
|
},
|
|
123
148
|
{
|
|
@@ -126,17 +151,19 @@ describe("runner-template", () => {
|
|
|
126
151
|
)
|
|
127
152
|
).toEqual({
|
|
128
153
|
PATH: "/usr/bin",
|
|
129
|
-
|
|
154
|
+
NEXT_PUBLIC_API_URL: "http://127.0.0.1:3100",
|
|
155
|
+
ONIX_DB_HOST: "127.0.0.1",
|
|
130
156
|
TESTKIT_ACTIVE: "1",
|
|
131
|
-
|
|
157
|
+
TESTKIT_STACK_ID: "stack-2",
|
|
132
158
|
});
|
|
133
159
|
|
|
134
|
-
expect(
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
160
|
+
expect(
|
|
161
|
+
buildPlaywrightEnv({ stackId: "shared", testkit: { serviceEnv: {} } }, "http://localhost:3000", {})
|
|
162
|
+
).toMatchObject({
|
|
163
|
+
BASE_URL: "http://localhost:3000",
|
|
164
|
+
TESTKIT_STACK_ID: "shared",
|
|
165
|
+
PLAYWRIGHT_HTML_OPEN: "never",
|
|
166
|
+
});
|
|
140
167
|
});
|
|
141
168
|
|
|
142
169
|
it("parses runtime sockets", () => {
|
|
@@ -1,11 +1,6 @@
|
|
|
1
1
|
import { formatError } from "./formatting.mjs";
|
|
2
2
|
import { runDalBatch, runHttpK6Batch } from "./default-runtime-runner.mjs";
|
|
3
3
|
import { runPlaywrightBatch } from "./playwright-runner.mjs";
|
|
4
|
-
import {
|
|
5
|
-
cleanupWorker,
|
|
6
|
-
ensureWorkerGraph,
|
|
7
|
-
resetCurrentGraph,
|
|
8
|
-
} from "./runtime-contexts.mjs";
|
|
9
4
|
|
|
10
5
|
const HTTP_K6_TYPES = new Set(["integration", "e2e", "load"]);
|
|
11
6
|
|
|
@@ -14,7 +9,6 @@ export function createWorker(workerId, productDir) {
|
|
|
14
9
|
workerId,
|
|
15
10
|
productDir,
|
|
16
11
|
currentGraphKey: null,
|
|
17
|
-
graphContexts: new Map(),
|
|
18
12
|
graphSwitches: 0,
|
|
19
13
|
taskCount: 0,
|
|
20
14
|
};
|
|
@@ -23,7 +17,7 @@ export function createWorker(workerId, productDir) {
|
|
|
23
17
|
export async function runWorker(
|
|
24
18
|
worker,
|
|
25
19
|
queue,
|
|
26
|
-
|
|
20
|
+
stackManager,
|
|
27
21
|
trackers,
|
|
28
22
|
timingUpdates,
|
|
29
23
|
lifecycle,
|
|
@@ -32,7 +26,7 @@ export async function runWorker(
|
|
|
32
26
|
recordGraphError
|
|
33
27
|
) {
|
|
34
28
|
const startedAt = Date.now();
|
|
35
|
-
console.log(`\n══
|
|
29
|
+
console.log(`\n══ worker ${worker.workerId} ══`);
|
|
36
30
|
const errors = [];
|
|
37
31
|
|
|
38
32
|
try {
|
|
@@ -41,9 +35,14 @@ export async function runWorker(
|
|
|
41
35
|
const batch = claimNextBatch(queue, worker.currentGraphKey);
|
|
42
36
|
if (!batch) break;
|
|
43
37
|
|
|
38
|
+
let lease = null;
|
|
44
39
|
try {
|
|
45
|
-
|
|
46
|
-
|
|
40
|
+
if (worker.currentGraphKey && worker.currentGraphKey !== batch.graphKey) {
|
|
41
|
+
worker.graphSwitches += 1;
|
|
42
|
+
}
|
|
43
|
+
worker.currentGraphKey = batch.graphKey;
|
|
44
|
+
lease = await stackManager.acquire(batch);
|
|
45
|
+
const outcomes = await runBatch(lease.context, batch, lifecycle);
|
|
47
46
|
for (const outcome of outcomes) {
|
|
48
47
|
recordTaskOutcome(trackers, outcome.task, outcome);
|
|
49
48
|
timingUpdates.push({
|
|
@@ -52,15 +51,19 @@ export async function runWorker(
|
|
|
52
51
|
});
|
|
53
52
|
worker.taskCount += 1;
|
|
54
53
|
}
|
|
54
|
+
await stackManager.release(lease, { accessMode: batch.accessMode });
|
|
55
55
|
} catch (error) {
|
|
56
56
|
const message = formatError(error);
|
|
57
57
|
errors.push(message);
|
|
58
|
-
recordGraphError(trackers,
|
|
59
|
-
await
|
|
58
|
+
recordGraphError(trackers, { assignedTargets: lease?.context?.assignedTargets || [batch.targetName] }, message);
|
|
59
|
+
await stackManager.release(lease, {
|
|
60
|
+
accessMode: batch.accessMode,
|
|
61
|
+
invalidate: lease !== null,
|
|
62
|
+
});
|
|
60
63
|
}
|
|
61
64
|
}
|
|
62
65
|
} finally {
|
|
63
|
-
|
|
66
|
+
worker.currentGraphKey = null;
|
|
64
67
|
}
|
|
65
68
|
|
|
66
69
|
return {
|
|
@@ -76,7 +79,7 @@ export async function runWorker(
|
|
|
76
79
|
async function runBatch(context, batch, lifecycle) {
|
|
77
80
|
const targetConfig = context.configByName.get(batch.targetName);
|
|
78
81
|
if (!targetConfig) {
|
|
79
|
-
throw new Error(`
|
|
82
|
+
throw new Error(`Stack is missing target config "${batch.targetName}"`);
|
|
80
83
|
}
|
|
81
84
|
|
|
82
85
|
if (batch.framework === "playwright") {
|
package/lib/setup/index.d.ts
CHANGED
|
@@ -33,6 +33,27 @@ export interface SkipConfig {
|
|
|
33
33
|
suites?: SkipSuiteRule[];
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
+
export interface SuiteExecutionRule {
|
|
37
|
+
selector: string;
|
|
38
|
+
stackMode: "shared" | "pooled" | "isolated";
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface FileExecutionRule {
|
|
42
|
+
path: string;
|
|
43
|
+
stackMode: "shared" | "pooled" | "isolated";
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface ServiceExecutionConfig {
|
|
47
|
+
suites?: SuiteExecutionRule[];
|
|
48
|
+
files?: FileExecutionRule[];
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface TestkitExecutionConfig {
|
|
52
|
+
workers?: number;
|
|
53
|
+
stackMode?: "shared" | "pooled" | "isolated";
|
|
54
|
+
stackCount?: number;
|
|
55
|
+
}
|
|
56
|
+
|
|
36
57
|
export interface ServiceConfig {
|
|
37
58
|
database?: LocalDatabaseConfig;
|
|
38
59
|
databaseFrom?: string;
|
|
@@ -40,9 +61,10 @@ export interface ServiceConfig {
|
|
|
40
61
|
discovery?: {
|
|
41
62
|
roots?: string[];
|
|
42
63
|
};
|
|
64
|
+
env?: Record<string, string>;
|
|
43
65
|
envFile?: string;
|
|
44
66
|
envFiles?: string[];
|
|
45
|
-
local?: {
|
|
67
|
+
local?: false | {
|
|
46
68
|
baseUrl: string;
|
|
47
69
|
cwd?: string;
|
|
48
70
|
env?: Record<string, string>;
|
|
@@ -54,9 +76,11 @@ export interface ServiceConfig {
|
|
|
54
76
|
migrate?: LifecycleConfig;
|
|
55
77
|
seed?: LifecycleConfig;
|
|
56
78
|
skip?: SkipConfig;
|
|
79
|
+
execution?: ServiceExecutionConfig;
|
|
57
80
|
}
|
|
58
81
|
|
|
59
82
|
export interface TestkitSetup {
|
|
83
|
+
execution?: TestkitExecutionConfig;
|
|
60
84
|
profiles?: {
|
|
61
85
|
http?: Record<string, HttpSuiteConfig>;
|
|
62
86
|
};
|