@elench/testkit 0.1.40 → 0.1.41
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 +25 -13
- package/lib/cli/args.mjs +0 -4
- package/lib/cli/args.test.mjs +0 -5
- package/lib/cli/index.mjs +0 -9
- package/lib/config/index.mjs +67 -24
- package/lib/database/index.mjs +19 -7
- package/lib/database/naming.mjs +2 -2
- package/lib/database/naming.test.mjs +2 -2
- package/lib/runner/default-runtime-runner.mjs +31 -53
- package/lib/runner/execution-config.mjs +14 -70
- package/lib/runner/execution-config.test.mjs +22 -74
- package/lib/runner/formatting.mjs +0 -15
- package/lib/runner/formatting.test.mjs +0 -18
- package/lib/runner/lifecycle.mjs +7 -7
- package/lib/runner/orchestrator.mjs +9 -10
- package/lib/runner/planning.mjs +42 -136
- package/lib/runner/planning.test.mjs +70 -174
- package/lib/runner/playwright-config.mjs +8 -2
- package/lib/runner/playwright-config.test.mjs +20 -5
- package/lib/runner/playwright-runner.mjs +32 -54
- package/lib/runner/readiness.mjs +2 -2
- package/lib/runner/reporting.mjs +2 -3
- package/lib/runner/reporting.test.mjs +2 -5
- package/lib/runner/results.mjs +1 -1
- package/lib/runner/results.test.mjs +1 -1
- package/lib/runner/runtime-contexts.mjs +20 -24
- package/lib/runner/runtime-manager.mjs +181 -0
- package/lib/runner/runtime-manager.test.mjs +181 -0
- package/lib/runner/services.mjs +4 -4
- package/lib/runner/state.mjs +1 -2
- package/lib/runner/state.test.mjs +2 -4
- package/lib/runner/template.mjs +90 -60
- package/lib/runner/template.test.mjs +59 -27
- package/lib/runner/worker-loop.mjs +29 -32
- package/lib/setup/index.d.ts +14 -10
- package/package.json +1 -1
- package/lib/runner/stack-manager.mjs +0 -146
package/lib/setup/index.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import type { AuthAdapter, HeaderBuilder, HttpSuiteConfig } from "../index";
|
|
|
2
2
|
|
|
3
3
|
export interface LocalDatabaseConfig {
|
|
4
4
|
provider: "local";
|
|
5
|
+
binding?: "shared" | "per-runtime";
|
|
5
6
|
image?: string;
|
|
6
7
|
password?: string;
|
|
7
8
|
reset?: boolean;
|
|
@@ -33,26 +34,28 @@ export interface SkipConfig {
|
|
|
33
34
|
suites?: SkipSuiteRule[];
|
|
34
35
|
}
|
|
35
36
|
|
|
36
|
-
export interface
|
|
37
|
+
export interface RuntimeConfig {
|
|
38
|
+
instances?: number;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface SuiteRequirementRule {
|
|
37
42
|
selector: string;
|
|
38
|
-
|
|
43
|
+
locks?: string[];
|
|
39
44
|
}
|
|
40
45
|
|
|
41
|
-
export interface
|
|
46
|
+
export interface FileRequirementRule {
|
|
42
47
|
path: string;
|
|
43
|
-
|
|
48
|
+
locks?: string[];
|
|
44
49
|
}
|
|
45
50
|
|
|
46
|
-
export interface
|
|
47
|
-
suites?:
|
|
48
|
-
files?:
|
|
51
|
+
export interface ServiceRequirementConfig {
|
|
52
|
+
suites?: SuiteRequirementRule[];
|
|
53
|
+
files?: FileRequirementRule[];
|
|
49
54
|
}
|
|
50
55
|
|
|
51
56
|
export interface TestkitExecutionConfig {
|
|
52
57
|
workers?: number;
|
|
53
58
|
fileTimeoutSeconds?: number;
|
|
54
|
-
stackMode?: "shared" | "pooled" | "isolated";
|
|
55
|
-
stackCount?: number;
|
|
56
59
|
}
|
|
57
60
|
|
|
58
61
|
export interface ServiceConfig {
|
|
@@ -76,8 +79,9 @@ export interface ServiceConfig {
|
|
|
76
79
|
};
|
|
77
80
|
migrate?: LifecycleConfig;
|
|
78
81
|
seed?: LifecycleConfig;
|
|
82
|
+
runtime?: RuntimeConfig;
|
|
83
|
+
requirements?: ServiceRequirementConfig;
|
|
79
84
|
skip?: SkipConfig;
|
|
80
|
-
execution?: ServiceExecutionConfig;
|
|
81
85
|
}
|
|
82
86
|
|
|
83
87
|
export interface TestkitSetup {
|
package/package.json
CHANGED
|
@@ -1,146 +0,0 @@
|
|
|
1
|
-
import { buildStackIds } from "./execution-config.mjs";
|
|
2
|
-
import {
|
|
3
|
-
cleanupStackContext,
|
|
4
|
-
createStackContext,
|
|
5
|
-
ensureStackContextReady,
|
|
6
|
-
} from "./runtime-contexts.mjs";
|
|
7
|
-
|
|
8
|
-
export function createStackManager({ productDir, graphs, execution, lifecycle }) {
|
|
9
|
-
const graphByKey = new Map(graphs.map((graph) => [graph.key, graph]));
|
|
10
|
-
const pools = new Map();
|
|
11
|
-
|
|
12
|
-
return {
|
|
13
|
-
async acquire(batch) {
|
|
14
|
-
const pool = getPool(pools, graphByKey, batch, execution, productDir, lifecycle);
|
|
15
|
-
while (true) {
|
|
16
|
-
if (lifecycle.isStopRequested()) {
|
|
17
|
-
throw lifecycle.signal.reason || new Error("testkit run interrupted");
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
const slot = claimSlot(pool, batch.accessMode);
|
|
21
|
-
if (!slot) {
|
|
22
|
-
await sleep(25);
|
|
23
|
-
continue;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
try {
|
|
27
|
-
const context = await getReadyContext(slot, graphByKey, productDir, batch, lifecycle);
|
|
28
|
-
return {
|
|
29
|
-
slot,
|
|
30
|
-
context,
|
|
31
|
-
};
|
|
32
|
-
} catch (error) {
|
|
33
|
-
releaseSlot(slot, batch.accessMode);
|
|
34
|
-
await invalidateSlot(slot, lifecycle);
|
|
35
|
-
throw error;
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
},
|
|
39
|
-
async release(lease, options = {}) {
|
|
40
|
-
if (!lease?.slot) return;
|
|
41
|
-
releaseSlot(lease.slot, options.accessMode || lease.slot.lastAccessMode);
|
|
42
|
-
if (options.invalidate) {
|
|
43
|
-
await invalidateSlot(lease.slot, lifecycle);
|
|
44
|
-
}
|
|
45
|
-
},
|
|
46
|
-
async cleanupAll() {
|
|
47
|
-
for (const pool of pools.values()) {
|
|
48
|
-
for (const slot of pool.slots) {
|
|
49
|
-
await invalidateSlot(slot, lifecycle);
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
},
|
|
53
|
-
};
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
function getPool(pools, graphByKey, batch, execution, productDir, lifecycle) {
|
|
57
|
-
const key = `${batch.graphKey}:${batch.stackMode}`;
|
|
58
|
-
const existing = pools.get(key);
|
|
59
|
-
if (existing) return existing;
|
|
60
|
-
|
|
61
|
-
const graph = graphByKey.get(batch.graphKey);
|
|
62
|
-
if (!graph) {
|
|
63
|
-
throw new Error(`Unknown graph "${batch.graphKey}"`);
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
const slots = buildPoolStackIds(batch.stackMode, execution).map((stackId) => ({
|
|
67
|
-
graph,
|
|
68
|
-
stackId,
|
|
69
|
-
context: null,
|
|
70
|
-
activeSharedCount: 0,
|
|
71
|
-
exclusiveActive: false,
|
|
72
|
-
lastAccessMode: null,
|
|
73
|
-
contextPromise: null,
|
|
74
|
-
}));
|
|
75
|
-
const pool = {
|
|
76
|
-
productDir,
|
|
77
|
-
lifecycle,
|
|
78
|
-
slots,
|
|
79
|
-
};
|
|
80
|
-
pools.set(key, pool);
|
|
81
|
-
return pool;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
function buildPoolStackIds(stackMode, execution) {
|
|
85
|
-
if (stackMode === "isolated") {
|
|
86
|
-
return Array.from({ length: execution.workers }, (_unused, index) => `isolated-${index + 1}`);
|
|
87
|
-
}
|
|
88
|
-
if (stackMode === "shared") {
|
|
89
|
-
return ["shared"];
|
|
90
|
-
}
|
|
91
|
-
return buildStackIds({
|
|
92
|
-
stackMode: "pooled",
|
|
93
|
-
stackCount: execution.stackCount,
|
|
94
|
-
});
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
function claimSlot(pool, accessMode) {
|
|
98
|
-
if (accessMode === "shared") {
|
|
99
|
-
const candidates = pool.slots.filter((slot) => !slot.exclusiveActive);
|
|
100
|
-
if (candidates.length === 0) return null;
|
|
101
|
-
const slot = [...candidates].sort((left, right) => left.activeSharedCount - right.activeSharedCount)[0];
|
|
102
|
-
slot.activeSharedCount += 1;
|
|
103
|
-
slot.lastAccessMode = "shared";
|
|
104
|
-
return slot;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
const slot = pool.slots.find((candidate) => !candidate.exclusiveActive && candidate.activeSharedCount === 0);
|
|
108
|
-
if (!slot) return null;
|
|
109
|
-
slot.exclusiveActive = true;
|
|
110
|
-
slot.lastAccessMode = "exclusive";
|
|
111
|
-
return slot;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
async function getReadyContext(slot, graphByKey, productDir, batch, lifecycle) {
|
|
115
|
-
if (!slot.context) {
|
|
116
|
-
slot.context = createStackContext(slot.stackId, slot.graph, productDir);
|
|
117
|
-
lifecycle.trackGraphContext(slot.context);
|
|
118
|
-
}
|
|
119
|
-
if (!slot.contextPromise) {
|
|
120
|
-
slot.contextPromise = Promise.resolve(slot.context);
|
|
121
|
-
}
|
|
122
|
-
await slot.contextPromise;
|
|
123
|
-
await ensureStackContextReady(slot.context, batch, lifecycle);
|
|
124
|
-
return slot.context;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
function releaseSlot(slot, accessMode) {
|
|
128
|
-
if (accessMode === "shared") {
|
|
129
|
-
slot.activeSharedCount = Math.max(0, slot.activeSharedCount - 1);
|
|
130
|
-
return;
|
|
131
|
-
}
|
|
132
|
-
slot.exclusiveActive = false;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
async function invalidateSlot(slot, lifecycle) {
|
|
136
|
-
if (!slot.context) return;
|
|
137
|
-
await cleanupStackContext(slot.context, lifecycle);
|
|
138
|
-
slot.context = null;
|
|
139
|
-
slot.contextPromise = null;
|
|
140
|
-
slot.activeSharedCount = 0;
|
|
141
|
-
slot.exclusiveActive = false;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
function sleep(ms) {
|
|
145
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
146
|
-
}
|