cabloy 5.1.216 → 5.1.218
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/.cabloy-version +1 -1
- package/.github/workflows/create-cabloy.yml +137 -0
- package/.github/workflows/playwright-e2e.yml +163 -13
- package/.github/workflows/vona-test-sqlite3.yml +77 -7
- package/CHANGELOG.md +22 -0
- package/CONTRIBUTING.md +4 -2
- package/package.json +1 -1
- package/repo-docs/frontend/scripts.md +2 -0
- package/repo-docs/reference/repo-scripts.md +27 -1
- package/repo-e2e/scripts/e2e.test.ts +1 -1
- package/repo-e2e/scripts/managedTest.test.ts +115 -3
- package/repo-e2e/scripts/managedTest.ts +69 -20
- package/repo-e2e/scripts/managedTestImports.test.ts +55 -5
- package/repo-e2e/scripts/runE2e.test.ts +109 -0
- package/repo-e2e/scripts/runE2e.ts +58 -39
- package/repo-e2e/scripts/runE2eArgs.test.ts +16 -1
- package/repo-e2e/scripts/runE2eArgs.ts +5 -0
- package/repo-e2e/specs/a-commerce.spec.ts +10 -2
- package/repo-e2e/specs/home-user-account.spec.ts +4 -0
- package/repo-e2e/specs/markdown.spec.ts +14 -3
- package/scripts/generateOpenapiIsolated.test.ts +5 -1
- package/scripts/runIsolatedTest.test.ts +403 -36
- package/scripts/runIsolatedTest.ts +139 -12
- package/scripts/testBrowserGuardianWindows.test.ts +327 -0
- package/scripts/testClaimCleanup.test.ts +25 -9
- package/scripts/testClaimCleanup.ts +14 -45
- package/scripts/testClaimProcesses.test.ts +81 -2
- package/scripts/testClaimProcesses.ts +46 -10
- package/scripts/testProcessGuardianWindows.test.ts +602 -0
- package/scripts/testProcessGuardianWindows.ts +155 -0
- package/vona/packages-cli/cli/package.json +1 -1
- package/vona/packages-cli/cli-set-api/package.json +1 -1
- package/vona/packages-cli/cli-set-api/src/lib/bean/cli.bin.build.ts +4 -1
- package/vona/packages-cli/cli-set-api/test/binBuildEntries.test.ts +159 -0
- package/vona/packages-vona/vona/package.json +1 -1
- package/vona/packages-vona/vona-core/package.json +1 -1
- package/vona/packages-vona/vona-core/src/lib/core/testClaim.test.ts +298 -17
- package/vona/packages-vona/vona-core/src/lib/core/testClaim.ts +205 -97
- package/vona/packages-vona/vona-core/src/lib/core/testClaimFilesystem.test.ts +247 -0
- package/vona/packages-vona/vona-core/src/lib/core/testClaimFilesystem.ts +224 -0
- package/vona/packages-vona/vona-core/src/lib/core/testClaimFilesystemWindows.cs +229 -0
- package/vona/packages-vona/vona-core/src/lib/core/testClaimFilesystemWindows.ps1 +20 -0
- package/vona/packages-vona/vona-core/src/lib/core/testClaimFilesystemWindows.ts +200 -0
- package/vona/packages-vona/vona-core/src/lib/core/testProcessWindowsNative.cs +336 -0
- package/vona/packages-vona/vona-core/src/lib/core/testProcessWindowsNative.ps1 +20 -0
- package/vona/packages-vona/vona-core/src/lib/core/testProcessWindowsNative.ts +531 -0
- package/vona/packages-vona/vona-core/src/lib/framework/bootstrap.test.ts +61 -0
- package/vona/packages-vona/vona-core/src/lib/framework/index.ts +1 -0
- package/vona/packages-vona/vona-core/src/lib/framework/processWorker.test.ts +59 -0
- package/vona/packages-vona/vona-core/src/lib/framework/processWorker.ts +8 -5
- package/vona/packages-vona/vona-core/src/lib/framework/useApp.test.ts +84 -0
- package/vona/packages-vona/vona-core/src/lib/framework/useApp.ts +2 -2
- package/vona/packages-vona/vona-core/src/lib/utils/util.ts +1 -1
- package/vona/packages-vona/vona-mock/package.json +1 -1
- package/vona/pnpm-lock.yaml +42 -181
- package/vona/src/suite-vendor/a-cabloy/modules/a-rbac/package.json +1 -1
- package/vona/src/suite-vendor/a-cabloy/package.json +2 -2
- package/vona/src/suite-vendor/a-vona/modules/a-core/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-executor/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-executor/src/bean/bean.executor.ts +2 -1
- package/vona/src/suite-vendor/a-vona/modules/a-logger/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-logger/src/bean/middlewareSystem.httpLog.ts +2 -2
- package/vona/src/suite-vendor/a-vona/modules/a-logger/src/lib/redactIsolatedReadyHeader.test.ts +16 -0
- package/vona/src/suite-vendor/a-vona/modules/a-logger/src/lib/redactIsolatedReadyHeader.ts +6 -3
- package/vona/src/suite-vendor/a-vona/modules/a-logger/test/middlewareSystem.httpLog.test.ts +80 -0
- package/vona/src/suite-vendor/a-vona/modules/a-metrics/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-metrics/src/bean/middlewareSystem.health.ts +73 -2
- package/vona/src/suite-vendor/a-vona/modules/a-metrics/test/middlewareSystem.health.test.ts +309 -0
- package/vona/src/suite-vendor/a-vona/modules/a-orm/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-orm/src/service/transactionAsyncLocalStorage_.ts +3 -3
- package/vona/src/suite-vendor/a-vona/modules/a-permission/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-static/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-static/src/bean/middlewareSystem.static.ts +2 -2
- package/vona/src/suite-vendor/a-vona/modules/a-static/test/middlewareSystem.static.test.ts +101 -0
- package/vona/src/suite-vendor/a-vona/package.json +1 -1
- package/zova/package.original.json +1 -1
- package/zova/packages-cli/cli/package.json +2 -2
- package/zova/packages-cli/cli-set-front/package.json +4 -4
- package/zova/packages-utils/zova-jsx/package.json +2 -2
- package/zova/packages-utils/zova-vite/package.json +2 -2
- package/zova/packages-utils/zova-vite/src/configUtils.ts +1 -1
- package/zova/packages-utils/zova-vite/test/configUtils.test.ts +55 -0
- package/zova/packages-zova/zova/package.json +3 -3
- package/zova/packages-zova/zova-core/package.json +2 -2
- package/zova/pnpm-lock.yaml +324 -63
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/model/cart.ts +8 -4
- package/zova/src/suite/a-commerce/modules/commerce-trade/test/lib/cartModel.test.ts +71 -0
- package/zova/src/suite-vendor/a-zova/modules/a-zova/package.json +2 -2
- package/zova/src/suite-vendor/a-zova/package.json +2 -2
|
@@ -1,8 +1,5 @@
|
|
|
1
1
|
import { spawn } from 'node:child_process';
|
|
2
|
-
import {
|
|
3
|
-
import { constants } from 'node:fs';
|
|
4
|
-
import { lstat, open, readdir, rm, unlink } from 'node:fs/promises';
|
|
5
|
-
import os from 'node:os';
|
|
2
|
+
import { lstat, readdir, rm } from 'node:fs/promises';
|
|
6
3
|
import path from 'node:path';
|
|
7
4
|
import { fileURLToPath } from 'node:url';
|
|
8
5
|
|
|
@@ -18,6 +15,11 @@ import {
|
|
|
18
15
|
TEST_CLAIM_DIRECTORY,
|
|
19
16
|
writeTestClaim,
|
|
20
17
|
} from '../vona/packages-vona/vona-core/src/lib/core/testClaim.ts';
|
|
18
|
+
import {
|
|
19
|
+
acquireTestClaimCleanupLock,
|
|
20
|
+
assertTestClaimPrivateDirectory,
|
|
21
|
+
deleteTestClaimFile,
|
|
22
|
+
} from '../vona/packages-vona/vona-core/src/lib/core/testClaimFilesystem.ts';
|
|
21
23
|
import { assertTestRunOwner } from '../vona/packages-vona/vona-core/src/lib/core/testIsolation.ts';
|
|
22
24
|
|
|
23
25
|
export interface TestClaimCleanupSession {
|
|
@@ -52,24 +54,6 @@ export function parseTestClaimTtlSeconds(
|
|
|
52
54
|
return Number(value);
|
|
53
55
|
}
|
|
54
56
|
|
|
55
|
-
async function assertSharedDirectory(directory: string, missingAllowed = true) {
|
|
56
|
-
assertTestClaimSafePath(directory, missingAllowed);
|
|
57
|
-
let stat;
|
|
58
|
-
try {
|
|
59
|
-
stat = await lstat(directory);
|
|
60
|
-
} catch (error) {
|
|
61
|
-
if (missingAllowed && (error as NodeJS.ErrnoException).code === 'ENOENT') return false;
|
|
62
|
-
throw error;
|
|
63
|
-
}
|
|
64
|
-
if (
|
|
65
|
-
!stat.isDirectory() ||
|
|
66
|
-
(stat.mode & 0o077) !== 0 ||
|
|
67
|
-
(process.getuid && stat.uid !== process.getuid())
|
|
68
|
-
) {
|
|
69
|
-
throw new Error('Unsafe shared test claim directory');
|
|
70
|
-
}
|
|
71
|
-
return true;
|
|
72
|
-
}
|
|
73
57
|
async function exists(file: string) {
|
|
74
58
|
try {
|
|
75
59
|
await lstat(file);
|
|
@@ -84,7 +68,9 @@ async function validateLocalRoots(claim: TestClaimManifest) {
|
|
|
84
68
|
assertTestClaimSafePath(local.path);
|
|
85
69
|
if (!(await exists(local.path))) continue;
|
|
86
70
|
if (local.stage === 'released') throw new Error('Released local test resource reappeared');
|
|
87
|
-
const
|
|
71
|
+
const markerPath = path.join(local.path, '.vona-test-owner');
|
|
72
|
+
assertTestClaimSafePath(markerPath, false);
|
|
73
|
+
const marker = await lstat(markerPath);
|
|
88
74
|
if (marker.isSymbolicLink() || !marker.isFile() || marker.nlink !== 1)
|
|
89
75
|
throw new Error('Unsafe test owner marker');
|
|
90
76
|
assertTestRunOwner(local.path, claim.runId);
|
|
@@ -97,7 +83,7 @@ export async function cleanTestClaim(
|
|
|
97
83
|
): Promise<TestClaimCleanupResult> {
|
|
98
84
|
claimPath = path.resolve(claimPath);
|
|
99
85
|
// An unsafe shared namespace is fatal to the invocation, not a skippable claim.
|
|
100
|
-
await
|
|
86
|
+
await assertTestClaimPrivateDirectory(path.dirname(claimPath), false);
|
|
101
87
|
const warn = options.warn ?? (() => {});
|
|
102
88
|
const skip = (reason: string): TestClaimCleanupResult => {
|
|
103
89
|
warn(`Retained test claim ${path.basename(claimPath)}: ${reason}`);
|
|
@@ -114,25 +100,15 @@ export async function cleanTestClaim(
|
|
|
114
100
|
const lockPath = `${claimPath}.cleanup.lock`;
|
|
115
101
|
let lock;
|
|
116
102
|
try {
|
|
117
|
-
|
|
118
|
-
lock = await open(
|
|
119
|
-
lockPath,
|
|
120
|
-
constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | constants.O_NOFOLLOW,
|
|
121
|
-
0o600,
|
|
122
|
-
);
|
|
103
|
+
lock = await acquireTestClaimCleanupLock(lockPath);
|
|
123
104
|
} catch (error) {
|
|
124
105
|
if ((error as NodeJS.ErrnoException).code === 'EEXIST')
|
|
125
106
|
return skip('cleanup lock present; inspect manually, age never permits takeover');
|
|
126
107
|
return skip('cannot acquire safe cleanup lock');
|
|
127
108
|
}
|
|
128
|
-
const token = randomBytes(24).toString('hex');
|
|
129
109
|
let session: TestClaimCleanupSession | undefined;
|
|
130
110
|
let sequenceStarted = false;
|
|
131
111
|
try {
|
|
132
|
-
await lock.writeFile(
|
|
133
|
-
`${JSON.stringify({ pid: process.pid, hostname: os.hostname(), startTime: String(Date.now() - process.uptime() * 1000), token })}\n`,
|
|
134
|
-
);
|
|
135
|
-
await lock.sync();
|
|
136
112
|
// Re-read after locking; the runner must have closed its single inventory broker.
|
|
137
113
|
claim = await loadTestClaim(claimPath);
|
|
138
114
|
await validateLocalRoots(claim);
|
|
@@ -194,7 +170,7 @@ export async function cleanTestClaim(
|
|
|
194
170
|
await writeTestClaim(claimPath, claim);
|
|
195
171
|
// Validate once more before releasing the inventory, always last.
|
|
196
172
|
await loadTestClaim(claimPath);
|
|
197
|
-
await
|
|
173
|
+
await deleteTestClaimFile(claimPath);
|
|
198
174
|
return { status: 'cleaned' };
|
|
199
175
|
} catch {
|
|
200
176
|
const reason = sequenceStarted
|
|
@@ -204,16 +180,9 @@ export async function cleanTestClaim(
|
|
|
204
180
|
return { status: sequenceStarted ? 'failed' : 'skipped', reason };
|
|
205
181
|
} finally {
|
|
206
182
|
if (session) await session.close().catch(() => {});
|
|
207
|
-
await lock.close();
|
|
208
183
|
// Do not remove a replacement lock. No automatic orphan-lock takeover.
|
|
209
184
|
try {
|
|
210
|
-
|
|
211
|
-
try {
|
|
212
|
-
const value = JSON.parse(await handle.readFile('utf8'));
|
|
213
|
-
if (value.token === token) await unlink(lockPath);
|
|
214
|
-
} finally {
|
|
215
|
-
await handle.close();
|
|
216
|
-
}
|
|
185
|
+
await lock.release();
|
|
217
186
|
} catch {
|
|
218
187
|
warn(`Retained cleanup lock ${path.basename(lockPath)}: manual inspection required`);
|
|
219
188
|
}
|
|
@@ -240,7 +209,7 @@ export async function reapExpiredTestClaims(
|
|
|
240
209
|
if (legacyRun && !(await exists(path.join(directory, `${legacyRun[1]}.json`))))
|
|
241
210
|
warn(`Legacy inventory-less test root retained: ${item.name}`);
|
|
242
211
|
}
|
|
243
|
-
if (!(await
|
|
212
|
+
if (!(await assertTestClaimPrivateDirectory(directory))) return [];
|
|
244
213
|
const results: TestClaimCleanupResult[] = [];
|
|
245
214
|
for (const name of await readdir(directory)) {
|
|
246
215
|
if (name.endsWith('.cleanup.lock')) {
|
|
@@ -96,11 +96,90 @@ test('inactivity fails closed for active, reused, pending and uninspectable proc
|
|
|
96
96
|
);
|
|
97
97
|
});
|
|
98
98
|
|
|
99
|
+
test('Windows job inactivity requires durable drainage and absence of the exact owner PID', async () => {
|
|
100
|
+
const owner = { pid: 100, startTime: 'owner-start', host: hostname() };
|
|
101
|
+
const registered = {
|
|
102
|
+
id: 'guardian',
|
|
103
|
+
role: 'guardian',
|
|
104
|
+
stage: 'registered' as const,
|
|
105
|
+
containment: 'windows-job' as const,
|
|
106
|
+
jobId: '69a52c48-6bc4-46ce-9655-b0379117ad61',
|
|
107
|
+
pid: 200,
|
|
108
|
+
startTime: '134000000000000000',
|
|
109
|
+
host: hostname(),
|
|
110
|
+
};
|
|
111
|
+
assert.equal(
|
|
112
|
+
(await isTestClaimInactive({ owner, processes: [registered] }, false, async () => [])).inactive,
|
|
113
|
+
false,
|
|
114
|
+
);
|
|
115
|
+
const drained = { ...registered, stage: 'drained' as const };
|
|
116
|
+
assert.equal(
|
|
117
|
+
(await isTestClaimInactive({ owner, processes: [drained] }, false, async () => [])).inactive,
|
|
118
|
+
true,
|
|
119
|
+
);
|
|
120
|
+
for (const [pid, reason] of [
|
|
121
|
+
[owner.pid, 'claim owner PID is still present'],
|
|
122
|
+
[drained.pid, 'registered Windows job owner is still present'],
|
|
123
|
+
] as const) {
|
|
124
|
+
let inspections = 0;
|
|
125
|
+
const inactivity = await isTestClaimInactive(
|
|
126
|
+
{ owner, processes: [drained] },
|
|
127
|
+
false,
|
|
128
|
+
async () => {
|
|
129
|
+
inspections++;
|
|
130
|
+
return [
|
|
131
|
+
{
|
|
132
|
+
pid,
|
|
133
|
+
startTime: 'different-created-time',
|
|
134
|
+
host: hostname(),
|
|
135
|
+
state: 'running',
|
|
136
|
+
args: '',
|
|
137
|
+
},
|
|
138
|
+
];
|
|
139
|
+
},
|
|
140
|
+
);
|
|
141
|
+
assert.deepEqual(inactivity, { inactive: false, reason });
|
|
142
|
+
assert.equal(inspections, 1);
|
|
143
|
+
}
|
|
144
|
+
assert.equal(
|
|
145
|
+
(
|
|
146
|
+
await isTestClaimInactive(
|
|
147
|
+
{ owner, processes: [{ ...drained, jobId: undefined }] },
|
|
148
|
+
false,
|
|
149
|
+
async () => [],
|
|
150
|
+
)
|
|
151
|
+
).inactive,
|
|
152
|
+
false,
|
|
153
|
+
);
|
|
154
|
+
assert.equal(
|
|
155
|
+
(
|
|
156
|
+
await isTestClaimInactive(
|
|
157
|
+
{ owner, processes: [{ ...drained, pgid: 200 }] },
|
|
158
|
+
false,
|
|
159
|
+
async () => [],
|
|
160
|
+
)
|
|
161
|
+
).inactive,
|
|
162
|
+
false,
|
|
163
|
+
);
|
|
164
|
+
await assert.rejects(
|
|
165
|
+
isTestClaimInactive({ owner, processes: [drained] }, false, async () => {
|
|
166
|
+
throw new Error('native inspection unavailable');
|
|
167
|
+
}),
|
|
168
|
+
/native inspection unavailable/,
|
|
169
|
+
);
|
|
170
|
+
});
|
|
171
|
+
|
|
99
172
|
test('identifies the current process without publishing environment values', async () => {
|
|
100
173
|
const identity = await inspectTestProcess(process.pid);
|
|
101
174
|
assert.equal(identity.pid, process.pid);
|
|
102
|
-
assert.ok(identity.pgid > 0);
|
|
103
175
|
assert.ok(identity.startTime);
|
|
104
176
|
assert.ok(identity.host);
|
|
105
|
-
|
|
177
|
+
if (process.platform === 'win32') {
|
|
178
|
+
assert.equal(identity.pgid, undefined);
|
|
179
|
+
assert.match(identity.startTime, /^[1-9]\d+$/);
|
|
180
|
+
await assert.rejects(waitForTestProcessGroupExit(process.pid, 0), /unsupported/);
|
|
181
|
+
} else {
|
|
182
|
+
assert.ok(identity.pgid! > 0);
|
|
183
|
+
await assert.rejects(waitForTestProcessGroupExit(identity.pgid!, 0), /did not drain/);
|
|
184
|
+
}
|
|
106
185
|
});
|
|
@@ -2,11 +2,16 @@ import { execFile } from 'node:child_process';
|
|
|
2
2
|
import { hostname } from 'node:os';
|
|
3
3
|
import { promisify } from 'node:util';
|
|
4
4
|
|
|
5
|
+
import {
|
|
6
|
+
inspectWindowsProcess,
|
|
7
|
+
inspectWindowsProcesses,
|
|
8
|
+
} from '../vona/packages-vona/vona-core/src/lib/core/testProcessWindowsNative.ts';
|
|
9
|
+
|
|
5
10
|
const execFileAsync = promisify(execFile);
|
|
6
11
|
|
|
7
12
|
export interface TestProcessIdentity {
|
|
8
13
|
pid: number;
|
|
9
|
-
pgid
|
|
14
|
+
pgid?: number;
|
|
10
15
|
startTime: string;
|
|
11
16
|
host: string;
|
|
12
17
|
}
|
|
@@ -39,7 +44,7 @@ export function parseTestProcesses(output: string): TestProcessSnapshot[] {
|
|
|
39
44
|
|
|
40
45
|
export async function inspectTestProcesses(): Promise<TestProcessSnapshot[]> {
|
|
41
46
|
if (process.platform === 'win32') {
|
|
42
|
-
throw new Error('
|
|
47
|
+
throw new Error('Windows process inspection requires explicit owned PIDs');
|
|
43
48
|
}
|
|
44
49
|
const { stdout } = await execFileAsync(
|
|
45
50
|
'ps',
|
|
@@ -50,6 +55,11 @@ export async function inspectTestProcesses(): Promise<TestProcessSnapshot[]> {
|
|
|
50
55
|
}
|
|
51
56
|
|
|
52
57
|
export async function inspectTestProcess(pid: number): Promise<TestProcessSnapshot> {
|
|
58
|
+
if (process.platform === 'win32') {
|
|
59
|
+
const item = await inspectWindowsProcess(pid);
|
|
60
|
+
if (!item) throw new Error('Cannot identify isolated test process');
|
|
61
|
+
return item;
|
|
62
|
+
}
|
|
53
63
|
const item = (await inspectTestProcesses()).find(
|
|
54
64
|
item => item.pid === pid && !item.state.startsWith('Z'),
|
|
55
65
|
);
|
|
@@ -63,6 +73,8 @@ export interface TestClaimProcessRecord {
|
|
|
63
73
|
stage: 'pending' | 'registered' | 'drained';
|
|
64
74
|
pid?: number;
|
|
65
75
|
pgid?: number;
|
|
76
|
+
containment?: 'windows-job';
|
|
77
|
+
jobId?: string;
|
|
66
78
|
startTime?: string;
|
|
67
79
|
host?: string;
|
|
68
80
|
profilePath?: string;
|
|
@@ -74,12 +86,27 @@ export async function isTestClaimInactive(
|
|
|
74
86
|
processes: TestClaimProcessRecord[];
|
|
75
87
|
},
|
|
76
88
|
allowCurrentOwner = false,
|
|
77
|
-
inspect
|
|
89
|
+
inspect?: () => Promise<TestProcessSnapshot[]>,
|
|
78
90
|
): Promise<{ inactive: boolean; reason?: string }> {
|
|
79
91
|
if (claim.owner.host !== hostname()) {
|
|
80
92
|
return { inactive: false, reason: 'claim belongs to another or unidentified host' };
|
|
81
93
|
}
|
|
82
|
-
|
|
94
|
+
if (
|
|
95
|
+
!inspect &&
|
|
96
|
+
process.platform === 'win32' &&
|
|
97
|
+
claim.processes.some(item => item.containment !== 'windows-job')
|
|
98
|
+
) {
|
|
99
|
+
return { inactive: false, reason: 'POSIX process ownership cannot be inspected on Windows' };
|
|
100
|
+
}
|
|
101
|
+
const snapshots = inspect
|
|
102
|
+
? await inspect()
|
|
103
|
+
: process.platform === 'win32'
|
|
104
|
+
? await inspectWindowsProcesses([
|
|
105
|
+
claim.owner.pid,
|
|
106
|
+
...claim.processes.flatMap(item => (item.pid ? [item.pid] : [])),
|
|
107
|
+
])
|
|
108
|
+
: await inspectTestProcesses();
|
|
109
|
+
const processes = snapshots.filter(item => !item.state.startsWith('Z'));
|
|
83
110
|
const owner = processes.find(item => item.pid === claim.owner.pid);
|
|
84
111
|
if (
|
|
85
112
|
owner &&
|
|
@@ -91,17 +118,24 @@ export async function isTestClaimInactive(
|
|
|
91
118
|
if (registered.stage === 'pending') {
|
|
92
119
|
return { inactive: false, reason: 'process launch registration is incomplete' };
|
|
93
120
|
}
|
|
94
|
-
if (
|
|
95
|
-
!registered.pid ||
|
|
96
|
-
!registered.pgid ||
|
|
97
|
-
!registered.startTime ||
|
|
98
|
-
registered.host !== hostname()
|
|
99
|
-
) {
|
|
121
|
+
if (!registered.pid || !registered.startTime || registered.host !== hostname()) {
|
|
100
122
|
return {
|
|
101
123
|
inactive: false,
|
|
102
124
|
reason: 'process identity is incomplete or belongs to another host',
|
|
103
125
|
};
|
|
104
126
|
}
|
|
127
|
+
if (registered.containment === 'windows-job') {
|
|
128
|
+
if (!registered.jobId || registered.pgid !== undefined || registered.stage !== 'drained') {
|
|
129
|
+
return { inactive: false, reason: 'Windows job drainage is unproven' };
|
|
130
|
+
}
|
|
131
|
+
if (processes.some(item => item.pid === registered.pid)) {
|
|
132
|
+
return { inactive: false, reason: 'registered Windows job owner is still present' };
|
|
133
|
+
}
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
if (!registered.pgid || registered.jobId !== undefined) {
|
|
137
|
+
return { inactive: false, reason: 'POSIX process group identity is incomplete' };
|
|
138
|
+
}
|
|
105
139
|
if (processes.some(item => item.pid === registered.pid || item.pgid === registered.pgid)) {
|
|
106
140
|
return { inactive: false, reason: 'registered process or group is still present' };
|
|
107
141
|
}
|
|
@@ -116,6 +150,8 @@ export async function isTestClaimInactive(
|
|
|
116
150
|
}
|
|
117
151
|
|
|
118
152
|
export async function waitForTestProcessGroupExit(pgid: number, timeoutMs = 10_000) {
|
|
153
|
+
if (process.platform === 'win32')
|
|
154
|
+
throw new Error('POSIX browser process groups are unsupported on Windows');
|
|
119
155
|
if (!Number.isSafeInteger(pgid) || pgid <= 0) throw new Error('Invalid isolated process group');
|
|
120
156
|
const deadline = Date.now() + timeoutMs;
|
|
121
157
|
while (true) {
|