@react-native-harness/jest 1.1.0-rc.3 → 1.1.0-rc.4
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/dist/__tests__/harness.test.js +59 -1
- package/dist/__tests__/metro-port.test.d.ts +2 -0
- package/dist/__tests__/metro-port.test.d.ts.map +1 -0
- package/dist/__tests__/metro-port.test.js +52 -0
- package/dist/__tests__/resource-lock.test.js +36 -1
- package/dist/errors.d.ts +5 -0
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +11 -0
- package/dist/harness.d.ts +1 -0
- package/dist/harness.d.ts.map +1 -1
- package/dist/harness.js +26 -13
- package/dist/logs.d.ts +1 -0
- package/dist/logs.d.ts.map +1 -1
- package/dist/logs.js +3 -0
- package/dist/metro-port.d.ts +16 -0
- package/dist/metro-port.d.ts.map +1 -0
- package/dist/metro-port.js +48 -0
- package/dist/resource-lock.d.ts.map +1 -1
- package/dist/resource-lock.js +32 -11
- package/dist/setup.js +1 -1
- package/dist/tsconfig.lib.tsbuildinfo +1 -1
- package/package.json +7 -7
- package/src/__tests__/harness.test.ts +89 -1
- package/src/__tests__/metro-port.test.ts +61 -0
- package/src/__tests__/resource-lock.test.ts +58 -4
- package/src/errors.ts +13 -0
- package/src/harness.ts +34 -12
- package/src/logs.ts +11 -0
- package/src/metro-port.ts +76 -0
- package/src/resource-lock.ts +59 -37
- package/src/setup.ts +1 -1
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { EventEmitter } from 'node:events';
|
|
2
2
|
import { HARNESS_BRIDGE_PATH } from '@react-native-harness/bridge';
|
|
3
3
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
4
|
+
import { MetroPortRangeExhaustedError } from '../errors.js';
|
|
4
5
|
import { definePlugin } from '@react-native-harness/plugins';
|
|
5
6
|
import { createCrashSupervisor } from '../crash-supervisor.js';
|
|
6
7
|
const mocks = vi.hoisted(() => ({
|
|
@@ -9,16 +10,19 @@ const mocks = vi.hoisted(() => ({
|
|
|
9
10
|
getMetroInstance: vi.fn(),
|
|
10
11
|
isMetroCacheReusable: vi.fn(() => false),
|
|
11
12
|
logMetroCacheReused: vi.fn(),
|
|
13
|
+
logMetroPortFallback: vi.fn(),
|
|
12
14
|
logRunnerStarting: vi.fn(),
|
|
13
15
|
logRunnerStillWaitingInQueue: vi.fn(),
|
|
14
16
|
logRunnerWaitingInQueue: vi.fn(),
|
|
15
17
|
waitForMetroBackedAppReady: vi.fn(),
|
|
18
|
+
isPortAvailable: vi.fn(async () => true),
|
|
16
19
|
}));
|
|
17
20
|
vi.mock('@react-native-harness/bundler-metro', async () => {
|
|
18
21
|
const actual = await vi.importActual('@react-native-harness/bundler-metro');
|
|
19
22
|
return {
|
|
20
23
|
...actual,
|
|
21
24
|
getMetroInstance: mocks.getMetroInstance,
|
|
25
|
+
isPortAvailable: mocks.isPortAvailable,
|
|
22
26
|
isMetroCacheReusable: mocks.isMetroCacheReusable,
|
|
23
27
|
waitForMetroBackedAppReady: mocks.waitForMetroBackedAppReady,
|
|
24
28
|
};
|
|
@@ -28,6 +32,7 @@ vi.mock('@react-native-harness/bridge/server', () => ({
|
|
|
28
32
|
}));
|
|
29
33
|
vi.mock('../logs.js', () => ({
|
|
30
34
|
logMetroCacheReused: mocks.logMetroCacheReused,
|
|
35
|
+
logMetroPortFallback: mocks.logMetroPortFallback,
|
|
31
36
|
logRunnerStarting: mocks.logRunnerStarting,
|
|
32
37
|
logRunnerStillWaitingInQueue: mocks.logRunnerStillWaitingInQueue,
|
|
33
38
|
logRunnerWaitingInQueue: mocks.logRunnerWaitingInQueue,
|
|
@@ -148,6 +153,8 @@ const createHarnessConfig = (overrides = {}) => ({
|
|
|
148
153
|
});
|
|
149
154
|
beforeEach(() => {
|
|
150
155
|
vi.clearAllMocks();
|
|
156
|
+
mocks.isPortAvailable.mockReset();
|
|
157
|
+
mocks.isPortAvailable.mockResolvedValue(true);
|
|
151
158
|
});
|
|
152
159
|
afterEach(() => {
|
|
153
160
|
delete globalThis.__HARNESS_PLATFORM_RUNNER__;
|
|
@@ -271,11 +278,62 @@ describe('getHarness', () => {
|
|
|
271
278
|
runner: `data:text/javascript,${encodeURIComponent('export default (...args) => globalThis.__HARNESS_PLATFORM_RUNNER__(...args);')}`,
|
|
272
279
|
};
|
|
273
280
|
const harness = await getHarness(createHarnessConfig(), platform, '/tmp/project');
|
|
274
|
-
expect(runner).toHaveBeenCalledWith(platform.config, expect.
|
|
281
|
+
expect(runner).toHaveBeenCalledWith(platform.config, expect.objectContaining({
|
|
282
|
+
metroPort: 8081,
|
|
283
|
+
}), expect.objectContaining({
|
|
275
284
|
signal: expect.any(AbortSignal),
|
|
276
285
|
}));
|
|
277
286
|
await harness.dispose();
|
|
278
287
|
});
|
|
288
|
+
it('resolves and exposes a fallback Metro port before platform init', async () => {
|
|
289
|
+
const { serverBridge } = createBridgeServer();
|
|
290
|
+
const appMonitor = createAppMonitor();
|
|
291
|
+
const platformInstance = createPlatformRunner({
|
|
292
|
+
createAppMonitor: () => appMonitor.appMonitor,
|
|
293
|
+
});
|
|
294
|
+
const metroInstance = createMetroInstance();
|
|
295
|
+
mocks.getBridgeServer.mockResolvedValue(serverBridge);
|
|
296
|
+
mocks.getMetroInstance.mockResolvedValue(metroInstance);
|
|
297
|
+
mocks.isPortAvailable
|
|
298
|
+
.mockResolvedValueOnce(false)
|
|
299
|
+
.mockResolvedValueOnce(true);
|
|
300
|
+
const runner = vi.fn(async () => platformInstance);
|
|
301
|
+
globalThis.__HARNESS_PLATFORM_RUNNER__ = runner;
|
|
302
|
+
const platform = {
|
|
303
|
+
config: {},
|
|
304
|
+
getResourceLockKey: () => 'android:emulator:Pixel_8_API_35',
|
|
305
|
+
name: 'android',
|
|
306
|
+
platformId: 'android',
|
|
307
|
+
runner: `data:text/javascript,${encodeURIComponent('export default (...args) => globalThis.__HARNESS_PLATFORM_RUNNER__(...args);')}`,
|
|
308
|
+
};
|
|
309
|
+
const harness = await getHarness(createHarnessConfig(), platform, '/tmp/project');
|
|
310
|
+
expect(harness.config.metroPort).toBe(8082);
|
|
311
|
+
expect(mocks.getMetroInstance).toHaveBeenCalledWith(expect.objectContaining({
|
|
312
|
+
harnessConfig: expect.objectContaining({
|
|
313
|
+
metroPort: 8082,
|
|
314
|
+
}),
|
|
315
|
+
}), expect.any(AbortSignal));
|
|
316
|
+
expect(runner).toHaveBeenCalledWith(platform.config, expect.objectContaining({
|
|
317
|
+
metroPort: 8082,
|
|
318
|
+
}), expect.objectContaining({
|
|
319
|
+
signal: expect.any(AbortSignal),
|
|
320
|
+
}));
|
|
321
|
+
expect(mocks.logMetroPortFallback).toHaveBeenCalledWith(8081, 8082);
|
|
322
|
+
await harness.dispose();
|
|
323
|
+
});
|
|
324
|
+
it('fails when no Metro port is available in the retry window', async () => {
|
|
325
|
+
mocks.isPortAvailable.mockResolvedValue(false);
|
|
326
|
+
const platform = {
|
|
327
|
+
config: {},
|
|
328
|
+
getResourceLockKey: () => 'android:emulator:Pixel_8_API_35',
|
|
329
|
+
name: 'android',
|
|
330
|
+
platformId: 'android',
|
|
331
|
+
runner: 'data:text/javascript,export default async () => ({})',
|
|
332
|
+
};
|
|
333
|
+
await expect(getHarness(createHarnessConfig(), platform, '/tmp/project')).rejects.toBeInstanceOf(MetroPortRangeExhaustedError);
|
|
334
|
+
expect(mocks.getBridgeServer).not.toHaveBeenCalled();
|
|
335
|
+
expect(mocks.getMetroInstance).not.toHaveBeenCalled();
|
|
336
|
+
});
|
|
279
337
|
it('falls back to a default resource lock key for platforms without getResourceLockKey', async () => {
|
|
280
338
|
const { serverBridge } = createBridgeServer();
|
|
281
339
|
const appMonitor = createAppMonitor();
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"metro-port.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/metro-port.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
2
|
+
import { resolveHarnessMetroPort } from '../metro-port.js';
|
|
3
|
+
const mocks = vi.hoisted(() => ({
|
|
4
|
+
isPortAvailable: vi.fn(async () => true),
|
|
5
|
+
}));
|
|
6
|
+
vi.mock('@react-native-harness/bundler-metro', () => ({
|
|
7
|
+
isPortAvailable: mocks.isPortAvailable,
|
|
8
|
+
}));
|
|
9
|
+
const createConfig = (overrides = {}) => ({
|
|
10
|
+
appRegistryComponentName: 'App',
|
|
11
|
+
bridgeTimeout: 60_000,
|
|
12
|
+
bundleStartTimeout: 60_000,
|
|
13
|
+
crashDetectionInterval: 500,
|
|
14
|
+
defaultRunner: 'ios-device',
|
|
15
|
+
detectNativeCrashes: true,
|
|
16
|
+
disableViewFlattening: false,
|
|
17
|
+
entryPoint: 'index.js',
|
|
18
|
+
forwardClientLogs: false,
|
|
19
|
+
maxAppRestarts: 2,
|
|
20
|
+
metroPort: 8081,
|
|
21
|
+
platformReadyTimeout: 300_000,
|
|
22
|
+
resetEnvironmentBetweenTestFiles: true,
|
|
23
|
+
runners: [],
|
|
24
|
+
unstable__enableMetroCache: false,
|
|
25
|
+
unstable__skipAlreadyIncludedModules: false,
|
|
26
|
+
...overrides,
|
|
27
|
+
});
|
|
28
|
+
describe('resolveHarnessMetroPort', () => {
|
|
29
|
+
it('skips fallback allocation for iOS physical device runners', async () => {
|
|
30
|
+
const acquire = vi.fn();
|
|
31
|
+
const config = createConfig();
|
|
32
|
+
const platform = {
|
|
33
|
+
config: {},
|
|
34
|
+
name: 'ios-device',
|
|
35
|
+
platformId: 'ios',
|
|
36
|
+
runner: 'unused',
|
|
37
|
+
};
|
|
38
|
+
const result = await resolveHarnessMetroPort({
|
|
39
|
+
config,
|
|
40
|
+
platform,
|
|
41
|
+
resourceLockManager: {
|
|
42
|
+
acquire,
|
|
43
|
+
},
|
|
44
|
+
signal: new AbortController().signal,
|
|
45
|
+
});
|
|
46
|
+
expect(result.config).toBe(config);
|
|
47
|
+
expect(result.metroPortLease).toBeNull();
|
|
48
|
+
expect(result.didFallback).toBe(false);
|
|
49
|
+
expect(acquire).not.toHaveBeenCalled();
|
|
50
|
+
expect(mocks.isPortAvailable).not.toHaveBeenCalled();
|
|
51
|
+
});
|
|
52
|
+
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import fs from 'node:fs/promises';
|
|
2
2
|
import os from 'node:os';
|
|
3
3
|
import path from 'node:path';
|
|
4
|
-
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
|
4
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
5
5
|
import { createResourceLockManager, hashResourceLockKey, } from '../resource-lock.js';
|
|
6
6
|
describe('resource lock manager', () => {
|
|
7
7
|
let rootDir;
|
|
@@ -100,4 +100,39 @@ describe('resource lock manager', () => {
|
|
|
100
100
|
expect(owner.ticketId).not.toBe('stale-owner');
|
|
101
101
|
await lease.release();
|
|
102
102
|
});
|
|
103
|
+
it('keeps owner metadata valid when heartbeat writes overlap', async () => {
|
|
104
|
+
const manager = createResourceLockManager({
|
|
105
|
+
rootDir,
|
|
106
|
+
pollIntervalMs: 5,
|
|
107
|
+
heartbeatIntervalMs: 10,
|
|
108
|
+
staleLockTimeoutMs: 200,
|
|
109
|
+
});
|
|
110
|
+
const key = 'ios:simulator:iPhone 17 Pro:26.2';
|
|
111
|
+
const ownerFilePath = path.join(rootDir, hashResourceLockKey(key), 'owner.json');
|
|
112
|
+
const actualWriteFile = fs.writeFile.bind(fs);
|
|
113
|
+
const writeFileSpy = vi
|
|
114
|
+
.spyOn(fs, 'writeFile')
|
|
115
|
+
.mockImplementation(async (file, data, options) => {
|
|
116
|
+
if (typeof file === 'string' &&
|
|
117
|
+
file.startsWith(ownerFilePath) &&
|
|
118
|
+
file !== ownerFilePath) {
|
|
119
|
+
await new Promise((resolve) => setTimeout(resolve, 25));
|
|
120
|
+
}
|
|
121
|
+
return await actualWriteFile(file, data, options);
|
|
122
|
+
});
|
|
123
|
+
try {
|
|
124
|
+
const lease = await manager.acquire(key);
|
|
125
|
+
const initialOwner = JSON.parse(await fs.readFile(ownerFilePath, 'utf8'));
|
|
126
|
+
await new Promise((resolve) => setTimeout(resolve, 80));
|
|
127
|
+
for (let index = 0; index < 5; index += 1) {
|
|
128
|
+
const owner = JSON.parse(await fs.readFile(ownerFilePath, 'utf8'));
|
|
129
|
+
expect(owner.ticketId).toBe(initialOwner.ticketId);
|
|
130
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
131
|
+
}
|
|
132
|
+
await lease.release();
|
|
133
|
+
}
|
|
134
|
+
finally {
|
|
135
|
+
writeFileSpy.mockRestore();
|
|
136
|
+
}
|
|
137
|
+
});
|
|
103
138
|
});
|
package/dist/errors.d.ts
CHANGED
|
@@ -15,6 +15,11 @@ export declare class PlatformReadyTimeoutError extends HarnessError {
|
|
|
15
15
|
readonly timeout: number;
|
|
16
16
|
constructor(timeout: number);
|
|
17
17
|
}
|
|
18
|
+
export declare class MetroPortRangeExhaustedError extends HarnessError {
|
|
19
|
+
readonly initialPort: number;
|
|
20
|
+
readonly attempts: number;
|
|
21
|
+
constructor(initialPort: number, attempts: number);
|
|
22
|
+
}
|
|
18
23
|
export type NativeCrashPhase = 'startup' | 'execution';
|
|
19
24
|
export type NativeCrashDetails = AppCrashDetails & {
|
|
20
25
|
phase: NativeCrashPhase;
|
package/dist/errors.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAC3D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iCAAiC,CAAC;AACvE,OAAO,EACL,iBAAiB,EACjB,KAAK,gBAAgB,EACrB,KAAK,mBAAmB,GACzB,MAAM,qCAAqC,CAAC;AAE7C,qBAAa,sBAAuB,SAAQ,YAAY;;CAKvD;AAED,qBAAa,mBAAoB,SAAQ,YAAY;aACvB,UAAU,EAAE,MAAM;gBAAlB,UAAU,EAAE,MAAM;CAI/C;AAED,qBAAa,0BAA2B,SAAQ,YAAY;;CAK3D;AAED,qBAAa,yBAA0B,SAAQ,YAAY;aAC7B,OAAO,EAAE,MAAM;gBAAf,OAAO,EAAE,MAAM;CAM5C;AAED,MAAM,MAAM,gBAAgB,GAAG,SAAS,GAAG,WAAW,CAAC;AAEvD,MAAM,MAAM,kBAAkB,GAAG,eAAe,GAAG;IACjD,KAAK,EAAE,gBAAgB,CAAC;CACzB,CAAC;AAmDF,qBAAa,gBAAiB,SAAQ,YAAY;aAE9B,YAAY,EAAE,MAAM;aACpB,OAAO,EAAE,kBAAkB;aAC3B,aAAa,CAAC,EAAE,MAAM;gBAFtB,YAAY,EAAE,MAAM,EACpB,OAAO,EAAE,kBAAkB,EAC3B,aAAa,CAAC,EAAE,MAAM,YAAA;IAOxC,IAAI,KAAK,qBAER;CACF"}
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAC3D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iCAAiC,CAAC;AACvE,OAAO,EACL,iBAAiB,EACjB,KAAK,gBAAgB,EACrB,KAAK,mBAAmB,GACzB,MAAM,qCAAqC,CAAC;AAE7C,qBAAa,sBAAuB,SAAQ,YAAY;;CAKvD;AAED,qBAAa,mBAAoB,SAAQ,YAAY;aACvB,UAAU,EAAE,MAAM;gBAAlB,UAAU,EAAE,MAAM;CAI/C;AAED,qBAAa,0BAA2B,SAAQ,YAAY;;CAK3D;AAED,qBAAa,yBAA0B,SAAQ,YAAY;aAC7B,OAAO,EAAE,MAAM;gBAAf,OAAO,EAAE,MAAM;CAM5C;AAED,qBAAa,4BAA6B,SAAQ,YAAY;aAE1C,WAAW,EAAE,MAAM;aACnB,QAAQ,EAAE,MAAM;gBADhB,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,MAAM;CAQnC;AAED,MAAM,MAAM,gBAAgB,GAAG,SAAS,GAAG,WAAW,CAAC;AAEvD,MAAM,MAAM,kBAAkB,GAAG,eAAe,GAAG;IACjD,KAAK,EAAE,gBAAgB,CAAC;CACzB,CAAC;AAmDF,qBAAa,gBAAiB,SAAQ,YAAY;aAE9B,YAAY,EAAE,MAAM;aACpB,OAAO,EAAE,kBAAkB;aAC3B,aAAa,CAAC,EAAE,MAAM;gBAFtB,YAAY,EAAE,MAAM,EACpB,OAAO,EAAE,kBAAkB,EAC3B,aAAa,CAAC,EAAE,MAAM,YAAA;IAOxC,IAAI,KAAK,qBAER;CACF"}
|
package/dist/errors.js
CHANGED
|
@@ -28,6 +28,17 @@ export class PlatformReadyTimeoutError extends HarnessError {
|
|
|
28
28
|
this.name = 'PlatformReadyTimeoutError';
|
|
29
29
|
}
|
|
30
30
|
}
|
|
31
|
+
export class MetroPortRangeExhaustedError extends HarnessError {
|
|
32
|
+
initialPort;
|
|
33
|
+
attempts;
|
|
34
|
+
constructor(initialPort, attempts) {
|
|
35
|
+
const finalPort = initialPort + attempts - 1;
|
|
36
|
+
super(`Harness could not find an available Metro port in the range ${initialPort}-${finalPort}.`);
|
|
37
|
+
this.initialPort = initialPort;
|
|
38
|
+
this.attempts = attempts;
|
|
39
|
+
this.name = 'MetroPortRangeExhaustedError';
|
|
40
|
+
}
|
|
41
|
+
}
|
|
31
42
|
const buildNativeCrashMessage = ({ phase, summary, signal, exceptionType, processName, pid, stackTrace, artifactType, }) => {
|
|
32
43
|
const lines = [
|
|
33
44
|
phase === 'startup'
|
package/dist/harness.d.ts
CHANGED
|
@@ -18,6 +18,7 @@ export type HarnessRunState = {
|
|
|
18
18
|
};
|
|
19
19
|
export type Harness = {
|
|
20
20
|
context: HarnessContext;
|
|
21
|
+
config: HarnessConfig;
|
|
21
22
|
runTests: (path: string, options: HarnessRunTestsOptions) => Promise<TestSuiteResult>;
|
|
22
23
|
ensureAppReady: (testFilePath: string) => Promise<void>;
|
|
23
24
|
restart: (testFilePath?: string) => Promise<void>;
|
package/dist/harness.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"harness.d.ts","sourceRoot":"","sources":["../src/harness.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,YAAY,EACb,MAAM,qCAAqC,CAAC;AAC7C,OAAO,EAEL,cAAc,EAGd,oBAAoB,EACpB,eAAe,EAChB,MAAM,8BAA8B,CAAC;AACtC,OAAO,EAEL,KAAK,gBAAgB,EACrB,eAAe,EAEf,qBAAqB,EACtB,MAAM,iCAAiC,CAAC;AACzC,OAAO,EAIL,KAAK,aAAa,EAGnB,MAAM,qCAAqC,CAAC;AAC7C,OAAO,EAIL,KAAK,oBAAoB,EACzB,KAAK,gBAAgB,EACrB,KAAK,iBAAiB,EACvB,MAAM,+BAA+B,CAAC;AAQvC,OAAO,EAAE,MAAM,IAAI,aAAa,EAAE,MAAM,8BAA8B,CAAC;AACvE,OAAO,EAEL,KAAK,eAAe,EACrB,MAAM,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"harness.d.ts","sourceRoot":"","sources":["../src/harness.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,YAAY,EACb,MAAM,qCAAqC,CAAC;AAC7C,OAAO,EAEL,cAAc,EAGd,oBAAoB,EACpB,eAAe,EAChB,MAAM,8BAA8B,CAAC;AACtC,OAAO,EAEL,KAAK,gBAAgB,EACrB,eAAe,EAEf,qBAAqB,EACtB,MAAM,iCAAiC,CAAC;AACzC,OAAO,EAIL,KAAK,aAAa,EAGnB,MAAM,qCAAqC,CAAC;AAC7C,OAAO,EAIL,KAAK,oBAAoB,EACzB,KAAK,gBAAgB,EACrB,KAAK,iBAAiB,EACvB,MAAM,+BAA+B,CAAC;AAQvC,OAAO,EAAE,MAAM,IAAI,aAAa,EAAE,MAAM,8BAA8B,CAAC;AACvE,OAAO,EAEL,KAAK,eAAe,EACrB,MAAM,uBAAuB,CAAC;AAgB/B,MAAM,MAAM,sBAAsB,GAAG,OAAO,CAAC,oBAAoB,EAAE,UAAU,CAAC,CAAC;AAE/E,MAAM,MAAM,eAAe,GAAG;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,SAAS,EAAE,OAAO,CAAC;IACnB,eAAe,EAAE,OAAO,CAAC;IACzB,OAAO,CAAC,EAAE,iBAAiB,CAAC;IAC5B,MAAM,CAAC,EAAE,gBAAgB,CAAC;IAC1B,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB,CAAC;AAEF,MAAM,MAAM,OAAO,GAAG;IACpB,OAAO,EAAE,cAAc,CAAC;IACxB,MAAM,EAAE,aAAa,CAAC;IACtB,QAAQ,EAAE,CACR,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,sBAAsB,KAC5B,OAAO,CAAC,eAAe,CAAC,CAAC;IAC9B,cAAc,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACxD,OAAO,EAAE,CAAC,YAAY,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAClD,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7B,eAAe,EAAE,eAAe,CAAC;IACjC,QAAQ,EAAE,oBAAoB,CAAC,aAAa,EAAE,eAAe,CAAC,CAAC,UAAU,CAAC,CAAC;IAC3E,WAAW,EAAE,CAAC,QAAQ,EAAE,eAAe,GAAG,IAAI,KAAK,IAAI,CAAC;IACxD,WAAW,EAAE,MAAM,eAAe,GAAG,IAAI,CAAC;CAC3C,CAAC;AAEF,eAAO,MAAM,uBAAuB,GAClC,QAAQ,aAAa,EACrB,UAAU,eAAe,EACzB,aAAa,MAAM,KAClB,IAIF,CAAC;AAgDF,eAAO,MAAM,eAAe,GAAU,SAAS;IAC7C,aAAa,EAAE,aAAa,CAAC;IAC7B,YAAY,EAAE,YAAY,CAAC;IAC3B,gBAAgB,EAAE,qBAAqB,CAAC;IACxC,UAAU,EAAE,MAAM,CAAC;IACnB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,YAAY,EAAE,MAAM,CAAC;IACrB,cAAc,EAAE,MAAM,CAAC;IACvB,YAAY,EAAE,MAAM,CAAC;IACrB,eAAe,EAAE,eAAe,CAAC;IACjC,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,gBAAgB,CAAC,EAAE,gBAAgB,CAAC;IACpC,SAAS,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CACjC,KAAG,OAAO,CAAC,IAAI,CA2Ef,CAAC;AAomBF,eAAO,MAAM,UAAU,GACrB,QAAQ,aAAa,EACrB,UAAU,eAAe,EACzB,aAAa,MAAM,KAClB,OAAO,CAAC,OAAO,CAYjB,CAAC"}
|
package/dist/harness.js
CHANGED
|
@@ -7,8 +7,9 @@ import { PlatformReadyTimeoutError } from './errors.js';
|
|
|
7
7
|
import { createCrashSupervisor, } from './crash-supervisor.js';
|
|
8
8
|
import { createClientLogListener } from './client-log-handler.js';
|
|
9
9
|
import path from 'node:path';
|
|
10
|
-
import { logMetroCacheReused, logRunnerStarting, logRunnerStillWaitingInQueue, logRunnerWaitingInQueue, } from './logs.js';
|
|
10
|
+
import { logMetroCacheReused, logMetroPortFallback, logRunnerStarting, logRunnerStillWaitingInQueue, logRunnerWaitingInQueue, } from './logs.js';
|
|
11
11
|
import { createResourceLockManager } from './resource-lock.js';
|
|
12
|
+
import { resolveHarnessMetroPort } from './metro-port.js';
|
|
12
13
|
const harnessLogger = logger.child('runtime');
|
|
13
14
|
const resourceLockManager = createResourceLockManager();
|
|
14
15
|
export const maybeLogMetroCacheReuse = (config, platform, projectRoot) => {
|
|
@@ -137,12 +138,21 @@ const getHarnessInternal = async (config, platform, projectRoot, signal) => {
|
|
|
137
138
|
}
|
|
138
139
|
harnessLogger.debug('resource lock acquired for runner=%s key=%s', platform.name, resourceLockKey);
|
|
139
140
|
try {
|
|
140
|
-
|
|
141
|
+
const { config: runtimeConfig, metroPortLease, initialMetroPort, didFallback, } = await resolveHarnessMetroPort({
|
|
142
|
+
config,
|
|
143
|
+
platform,
|
|
144
|
+
resourceLockManager,
|
|
145
|
+
signal,
|
|
146
|
+
});
|
|
147
|
+
if (didFallback) {
|
|
148
|
+
logMetroPortFallback(initialMetroPort, runtimeConfig.metroPort);
|
|
149
|
+
}
|
|
150
|
+
maybeLogMetroCacheReuse(runtimeConfig, platform, projectRoot);
|
|
141
151
|
const pluginAbortController = new AbortController();
|
|
142
152
|
const pluginManager = createHarnessPluginManager({
|
|
143
|
-
plugins: (
|
|
153
|
+
plugins: (runtimeConfig.plugins ?? []),
|
|
144
154
|
projectRoot,
|
|
145
|
-
config,
|
|
155
|
+
config: runtimeConfig,
|
|
146
156
|
runner: platform,
|
|
147
157
|
abortSignal: pluginAbortController.signal,
|
|
148
158
|
});
|
|
@@ -182,7 +192,7 @@ const getHarnessInternal = async (config, platform, projectRoot, signal) => {
|
|
|
182
192
|
};
|
|
183
193
|
const serverBridge = await getBridgeServer({
|
|
184
194
|
noServer: true,
|
|
185
|
-
timeout:
|
|
195
|
+
timeout: runtimeConfig.bridgeTimeout,
|
|
186
196
|
context,
|
|
187
197
|
});
|
|
188
198
|
harnessLogger.debug('starting Metro, platform runner, and bridge initialization');
|
|
@@ -192,7 +202,7 @@ const getHarnessInternal = async (config, platform, projectRoot, signal) => {
|
|
|
192
202
|
return await Promise.all([
|
|
193
203
|
getMetroInstance({
|
|
194
204
|
projectRoot,
|
|
195
|
-
harnessConfig:
|
|
205
|
+
harnessConfig: runtimeConfig,
|
|
196
206
|
websocketEndpoints: {
|
|
197
207
|
[HARNESS_BRIDGE_PATH]: serverBridge.ws,
|
|
198
208
|
},
|
|
@@ -201,11 +211,11 @@ const getHarnessInternal = async (config, platform, projectRoot, signal) => {
|
|
|
201
211
|
return instance;
|
|
202
212
|
}),
|
|
203
213
|
withPlatformReadyTimeout({
|
|
204
|
-
timeout:
|
|
214
|
+
timeout: runtimeConfig.platformReadyTimeout,
|
|
205
215
|
signal,
|
|
206
216
|
work: async () => {
|
|
207
217
|
return await import(platform.runner)
|
|
208
|
-
.then((module) => module.default(platform.config,
|
|
218
|
+
.then((module) => module.default(platform.config, runtimeConfig, {
|
|
209
219
|
signal,
|
|
210
220
|
}))
|
|
211
221
|
.then((instance) => {
|
|
@@ -219,6 +229,7 @@ const getHarnessInternal = async (config, platform, projectRoot, signal) => {
|
|
|
219
229
|
catch (error) {
|
|
220
230
|
await Promise.allSettled([
|
|
221
231
|
resourceLease.release(),
|
|
232
|
+
metroPortLease?.release(),
|
|
222
233
|
serverBridge.dispose(),
|
|
223
234
|
]);
|
|
224
235
|
throw error;
|
|
@@ -422,7 +433,7 @@ const getHarnessInternal = async (config, platform, projectRoot, signal) => {
|
|
|
422
433
|
metroInstance.events.addListener(onMetroEvent);
|
|
423
434
|
appMonitor.addListener(onAppMonitorEvent);
|
|
424
435
|
harnessLogger.debug('registered runtime, bridge, and Metro listeners');
|
|
425
|
-
if (
|
|
436
|
+
if (runtimeConfig.forwardClientLogs) {
|
|
426
437
|
metroInstance.events.addListener(clientLogListener);
|
|
427
438
|
harnessLogger.debug('client log forwarding enabled');
|
|
428
439
|
}
|
|
@@ -451,7 +462,7 @@ const getHarnessInternal = async (config, platform, projectRoot, signal) => {
|
|
|
451
462
|
catch (error) {
|
|
452
463
|
hookError = error;
|
|
453
464
|
}
|
|
454
|
-
if (
|
|
465
|
+
if (runtimeConfig.forwardClientLogs) {
|
|
455
466
|
metroInstance.events.removeListener(clientLogListener);
|
|
456
467
|
}
|
|
457
468
|
metroInstance.events.removeListener(onMetroEvent);
|
|
@@ -466,6 +477,7 @@ const getHarnessInternal = async (config, platform, projectRoot, signal) => {
|
|
|
466
477
|
serverBridge.dispose(),
|
|
467
478
|
platformInstance.dispose(),
|
|
468
479
|
metroInstance.dispose(),
|
|
480
|
+
metroPortLease?.release(),
|
|
469
481
|
]);
|
|
470
482
|
}
|
|
471
483
|
catch (error) {
|
|
@@ -527,9 +539,9 @@ const getHarnessInternal = async (config, platform, projectRoot, signal) => {
|
|
|
527
539
|
serverBridge,
|
|
528
540
|
platformInstance: platformInstance,
|
|
529
541
|
platformId: platform.platformId,
|
|
530
|
-
bundleStartTimeout:
|
|
531
|
-
readyTimeout:
|
|
532
|
-
maxAppRestarts:
|
|
542
|
+
bundleStartTimeout: runtimeConfig.bundleStartTimeout ?? 60000,
|
|
543
|
+
readyTimeout: runtimeConfig.bridgeTimeout,
|
|
544
|
+
maxAppRestarts: runtimeConfig.maxAppRestarts ?? 2,
|
|
533
545
|
testFilePath,
|
|
534
546
|
crashSupervisor,
|
|
535
547
|
appLaunchOptions,
|
|
@@ -560,6 +572,7 @@ const getHarnessInternal = async (config, platform, projectRoot, signal) => {
|
|
|
560
572
|
};
|
|
561
573
|
return {
|
|
562
574
|
context,
|
|
575
|
+
config: runtimeConfig,
|
|
563
576
|
runTests: async (path, options) => {
|
|
564
577
|
await flushPendingHooks();
|
|
565
578
|
activeTestFilePath = path;
|
package/dist/logs.d.ts
CHANGED
|
@@ -8,5 +8,6 @@ export declare const logRunnerStillWaitingInQueue: (runner: HarnessPlatform) =>
|
|
|
8
8
|
export declare const logRunnerStarting: (runner: HarnessPlatform) => void;
|
|
9
9
|
export declare const logMetroPrewarmCompleted: (runner: HarnessPlatform) => void;
|
|
10
10
|
export declare const logMetroCacheReused: (runner: HarnessPlatform) => void;
|
|
11
|
+
export declare const logMetroPortFallback: (initialPort: number, selectedPort: number) => void;
|
|
11
12
|
export declare const getErrorMessage: (error: HarnessError) => string;
|
|
12
13
|
//# sourceMappingURL=logs.d.ts.map
|
package/dist/logs.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"logs.d.ts","sourceRoot":"","sources":["../src/logs.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,iCAAiC,CAAC;AAClE,OAAO,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAY3D,eAAO,MAAM,GAAG,GAAI,SAAS,MAAM,KAAG,IAErC,CAAC;AAEF,eAAO,MAAM,gBAAgB,GAAI,QAAQ,eAAe,KAAG,IAI1D,CAAC;AAEF,eAAO,MAAM,uBAAuB,GAAI,QAAQ,eAAe,KAAG,IAEjE,CAAC;AAEF,eAAO,MAAM,uBAAuB,GAAI,QAAQ,eAAe,KAAG,IAEjE,CAAC;AAEF,eAAO,MAAM,4BAA4B,GAAI,QAAQ,eAAe,KAAG,IAEtE,CAAC;AAEF,eAAO,MAAM,iBAAiB,GAAI,QAAQ,eAAe,KAAG,IAE3D,CAAC;AAEF,eAAO,MAAM,wBAAwB,GAAI,QAAQ,eAAe,KAAG,IAElE,CAAC;AAEF,eAAO,MAAM,mBAAmB,GAAI,QAAQ,eAAe,KAAG,IAE7D,CAAC;AAEF,eAAO,MAAM,eAAe,GAAI,OAAO,YAAY,KAAG,MAErD,CAAC"}
|
|
1
|
+
{"version":3,"file":"logs.d.ts","sourceRoot":"","sources":["../src/logs.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,iCAAiC,CAAC;AAClE,OAAO,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAY3D,eAAO,MAAM,GAAG,GAAI,SAAS,MAAM,KAAG,IAErC,CAAC;AAEF,eAAO,MAAM,gBAAgB,GAAI,QAAQ,eAAe,KAAG,IAI1D,CAAC;AAEF,eAAO,MAAM,uBAAuB,GAAI,QAAQ,eAAe,KAAG,IAEjE,CAAC;AAEF,eAAO,MAAM,uBAAuB,GAAI,QAAQ,eAAe,KAAG,IAEjE,CAAC;AAEF,eAAO,MAAM,4BAA4B,GAAI,QAAQ,eAAe,KAAG,IAEtE,CAAC;AAEF,eAAO,MAAM,iBAAiB,GAAI,QAAQ,eAAe,KAAG,IAE3D,CAAC;AAEF,eAAO,MAAM,wBAAwB,GAAI,QAAQ,eAAe,KAAG,IAElE,CAAC;AAEF,eAAO,MAAM,mBAAmB,GAAI,QAAQ,eAAe,KAAG,IAE7D,CAAC;AAEF,eAAO,MAAM,oBAAoB,GAC/B,aAAa,MAAM,EACnB,cAAc,MAAM,KACnB,IAMF,CAAC;AAEF,eAAO,MAAM,eAAe,GAAI,OAAO,YAAY,KAAG,MAErD,CAAC"}
|
package/dist/logs.js
CHANGED
|
@@ -30,6 +30,9 @@ export const logMetroPrewarmCompleted = (runner) => {
|
|
|
30
30
|
export const logMetroCacheReused = (runner) => {
|
|
31
31
|
log(`${TAG} Reusing Metro cache for ${chalk.bold(runner.name)}\n`);
|
|
32
32
|
};
|
|
33
|
+
export const logMetroPortFallback = (initialPort, selectedPort) => {
|
|
34
|
+
log(`${TAG} Harness could not use Metro port ${chalk.bold(String(initialPort))} and is using ${chalk.bold(String(selectedPort))} instead.\n`);
|
|
35
|
+
};
|
|
33
36
|
export const getErrorMessage = (error) => {
|
|
34
37
|
return `${ERROR_TAG} ${error.message}\n`;
|
|
35
38
|
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { Config as HarnessConfig } from '@react-native-harness/config';
|
|
2
|
+
import type { HarnessPlatform } from '@react-native-harness/platforms';
|
|
3
|
+
import type { ResourceLease, ResourceLockManager } from './resource-lock.js';
|
|
4
|
+
export declare const METRO_PORT_SCAN_ATTEMPTS = 10;
|
|
5
|
+
export declare const resolveHarnessMetroPort: (options: {
|
|
6
|
+
config: HarnessConfig;
|
|
7
|
+
platform: HarnessPlatform;
|
|
8
|
+
resourceLockManager: ResourceLockManager;
|
|
9
|
+
signal: AbortSignal;
|
|
10
|
+
}) => Promise<{
|
|
11
|
+
config: HarnessConfig;
|
|
12
|
+
metroPortLease: ResourceLease | null;
|
|
13
|
+
initialMetroPort: number;
|
|
14
|
+
didFallback: boolean;
|
|
15
|
+
}>;
|
|
16
|
+
//# sourceMappingURL=metro-port.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"metro-port.d.ts","sourceRoot":"","sources":["../src/metro-port.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,IAAI,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAE5E,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iCAAiC,CAAC;AAEvE,OAAO,KAAK,EAAE,aAAa,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAE7E,eAAO,MAAM,wBAAwB,KAAK,CAAC;AAU3C,eAAO,MAAM,uBAAuB,GAAU,SAAS;IACrD,MAAM,EAAE,aAAa,CAAC;IACtB,QAAQ,EAAE,eAAe,CAAC;IAC1B,mBAAmB,EAAE,mBAAmB,CAAC;IACzC,MAAM,EAAE,WAAW,CAAC;CACrB,KAAG,OAAO,CAAC;IACV,MAAM,EAAE,aAAa,CAAC;IACtB,cAAc,EAAE,aAAa,GAAG,IAAI,CAAC;IACrC,gBAAgB,EAAE,MAAM,CAAC;IACzB,WAAW,EAAE,OAAO,CAAC;CACtB,CAiDA,CAAC"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { isPortAvailable } from '@react-native-harness/bundler-metro';
|
|
2
|
+
import { MetroPortRangeExhaustedError } from './errors.js';
|
|
3
|
+
export const METRO_PORT_SCAN_ATTEMPTS = 10;
|
|
4
|
+
const getPortLockKey = (host, port) => {
|
|
5
|
+
return `metro-port:${host?.trim() || '*'}:${port}`;
|
|
6
|
+
};
|
|
7
|
+
const isIosPhysicalRunner = (platform) => {
|
|
8
|
+
return platform.platformId === 'ios' && platform.name.includes('device');
|
|
9
|
+
};
|
|
10
|
+
export const resolveHarnessMetroPort = async (options) => {
|
|
11
|
+
const { config, platform, resourceLockManager, signal } = options;
|
|
12
|
+
const initialMetroPort = config.metroPort;
|
|
13
|
+
const host = config.host?.trim();
|
|
14
|
+
if (isIosPhysicalRunner(platform)) {
|
|
15
|
+
return {
|
|
16
|
+
config,
|
|
17
|
+
metroPortLease: null,
|
|
18
|
+
initialMetroPort,
|
|
19
|
+
didFallback: false,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
for (let attempt = 0; attempt < METRO_PORT_SCAN_ATTEMPTS; attempt += 1) {
|
|
23
|
+
const candidatePort = initialMetroPort + attempt;
|
|
24
|
+
const metroPortLease = await resourceLockManager.acquire(getPortLockKey(host, candidatePort), { signal });
|
|
25
|
+
try {
|
|
26
|
+
if (!(await isPortAvailable(candidatePort, host))) {
|
|
27
|
+
await metroPortLease.release();
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
return {
|
|
31
|
+
config: candidatePort === initialMetroPort
|
|
32
|
+
? config
|
|
33
|
+
: {
|
|
34
|
+
...config,
|
|
35
|
+
metroPort: candidatePort,
|
|
36
|
+
},
|
|
37
|
+
metroPortLease,
|
|
38
|
+
initialMetroPort,
|
|
39
|
+
didFallback: candidatePort !== initialMetroPort,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
catch (error) {
|
|
43
|
+
await metroPortLease.release();
|
|
44
|
+
throw error;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
throw new MetroPortRangeExhaustedError(initialMetroPort, METRO_PORT_SCAN_ATTEMPTS);
|
|
48
|
+
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"resource-lock.d.ts","sourceRoot":"","sources":["../src/resource-lock.ts"],"names":[],"mappings":"AAIA,OAAO,EAAU,KAAK,aAAa,EAAE,MAAM,6BAA6B,CAAC;AAiBzE,MAAM,MAAM,0BAA0B,GAAG;IACvC,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;IACpB,cAAc,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;CAC9C,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG;IAC1B,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC9B,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG;IAChC,OAAO,EAAE,CACP,GAAG,EAAE,MAAM,EACX,OAAO,CAAC,EAAE,0BAA0B,KACjC,OAAO,CAAC,aAAa,CAAC,CAAC;CAC7B,CAAC;AAEF,KAAK,0BAA0B,GAAG;IAChC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,aAAa,CAAC;IACvB,eAAe,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC;CAC5C,CAAC;AAgBF,eAAO,MAAM,mBAAmB,GAAI,KAAK,MAAM,KAAG,MAEjD,CAAC;
|
|
1
|
+
{"version":3,"file":"resource-lock.d.ts","sourceRoot":"","sources":["../src/resource-lock.ts"],"names":[],"mappings":"AAIA,OAAO,EAAU,KAAK,aAAa,EAAE,MAAM,6BAA6B,CAAC;AAiBzE,MAAM,MAAM,0BAA0B,GAAG;IACvC,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;IACpB,cAAc,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;CAC9C,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG;IAC1B,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC9B,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG;IAChC,OAAO,EAAE,CACP,GAAG,EAAE,MAAM,EACX,OAAO,CAAC,EAAE,0BAA0B,KACjC,OAAO,CAAC,aAAa,CAAC,CAAC;CAC7B,CAAC;AAEF,KAAK,0BAA0B,GAAG;IAChC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,aAAa,CAAC;IACvB,eAAe,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC;CAC5C,CAAC;AAgBF,eAAO,MAAM,mBAAmB,GAAI,KAAK,MAAM,KAAG,MAEjD,CAAC;AAmPF,eAAO,MAAM,yBAAyB,GACpC,UAAS,0BAA+B,KACvC,mBAkKF,CAAC"}
|
package/dist/resource-lock.js
CHANGED
|
@@ -56,6 +56,17 @@ const readJsonFile = async (filePath) => {
|
|
|
56
56
|
throw error;
|
|
57
57
|
}
|
|
58
58
|
};
|
|
59
|
+
const writeJsonFileAtomic = async (filePath, value) => {
|
|
60
|
+
const tempPath = `${filePath}.${process.pid}.${crypto.randomUUID()}.tmp`;
|
|
61
|
+
try {
|
|
62
|
+
await fs.writeFile(tempPath, JSON.stringify(value), 'utf8');
|
|
63
|
+
await fs.rename(tempPath, filePath);
|
|
64
|
+
}
|
|
65
|
+
catch (error) {
|
|
66
|
+
await removeFileIfPresent(tempPath);
|
|
67
|
+
throw error;
|
|
68
|
+
}
|
|
69
|
+
};
|
|
59
70
|
const removeFileIfPresent = async (filePath) => {
|
|
60
71
|
try {
|
|
61
72
|
await fs.rm(filePath, { force: true });
|
|
@@ -189,6 +200,7 @@ export const createResourceLockManager = (options = {}) => {
|
|
|
189
200
|
await fs.writeFile(ticketPath, JSON.stringify(metadata), 'utf8');
|
|
190
201
|
scopedLogger.debug('queued ticket %s for key %s', ticketId, key);
|
|
191
202
|
let heartbeatTimer = null;
|
|
203
|
+
let heartbeatInFlight = false;
|
|
192
204
|
let released = false;
|
|
193
205
|
let didNotifyWait = false;
|
|
194
206
|
const waitStartedAt = Date.now();
|
|
@@ -210,20 +222,29 @@ export const createResourceLockManager = (options = {}) => {
|
|
|
210
222
|
};
|
|
211
223
|
const startHeartbeat = () => {
|
|
212
224
|
heartbeatTimer = setInterval(async () => {
|
|
213
|
-
|
|
214
|
-
const owner = await readJsonFile(paths.ownerFilePath);
|
|
215
|
-
if (released || owner?.ticketId !== ticketId) {
|
|
225
|
+
if (heartbeatInFlight) {
|
|
216
226
|
return;
|
|
217
227
|
}
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
228
|
+
heartbeatInFlight = true;
|
|
229
|
+
try {
|
|
230
|
+
const nextHeartbeatAt = Date.now();
|
|
231
|
+
const owner = await readJsonFile(paths.ownerFilePath);
|
|
232
|
+
if (released || owner?.ticketId !== ticketId) {
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
const nextMetadata = {
|
|
236
|
+
...owner,
|
|
237
|
+
heartbeatAt: nextHeartbeatAt,
|
|
238
|
+
};
|
|
239
|
+
if (released) {
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
await writeJsonFileAtomic(paths.ownerFilePath, nextMetadata);
|
|
243
|
+
scopedLogger.debug('refreshed heartbeat for ticket %s', ticketId);
|
|
244
|
+
}
|
|
245
|
+
finally {
|
|
246
|
+
heartbeatInFlight = false;
|
|
224
247
|
}
|
|
225
|
-
await fs.writeFile(paths.ownerFilePath, JSON.stringify(nextMetadata), 'utf8');
|
|
226
|
-
scopedLogger.debug('refreshed heartbeat for ticket %s', ticketId);
|
|
227
248
|
}, heartbeatIntervalMs);
|
|
228
249
|
heartbeatTimer.unref?.();
|
|
229
250
|
};
|
package/dist/setup.js
CHANGED
|
@@ -75,6 +75,6 @@ export const setup = async (globalConfig) => {
|
|
|
75
75
|
const harness = await getHarness(harnessConfig, selectedRunner, globalConfig.rootDir);
|
|
76
76
|
logTestEnvironmentReady(selectedRunner);
|
|
77
77
|
setupLogger.debug('Harness instance is ready');
|
|
78
|
-
global.HARNESS_CONFIG =
|
|
78
|
+
global.HARNESS_CONFIG = harness.config;
|
|
79
79
|
global.HARNESS = harness;
|
|
80
80
|
};
|