@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
package/src/resource-lock.ts
CHANGED
|
@@ -32,7 +32,7 @@ export type ResourceLease = {
|
|
|
32
32
|
export type ResourceLockManager = {
|
|
33
33
|
acquire: (
|
|
34
34
|
key: string,
|
|
35
|
-
options?: ResourceLockAcquireOptions
|
|
35
|
+
options?: ResourceLockAcquireOptions,
|
|
36
36
|
) => Promise<ResourceLease>;
|
|
37
37
|
};
|
|
38
38
|
|
|
@@ -115,6 +115,21 @@ const readJsonFile = async <T>(filePath: string): Promise<T | null> => {
|
|
|
115
115
|
}
|
|
116
116
|
};
|
|
117
117
|
|
|
118
|
+
const writeJsonFileAtomic = async <T>(
|
|
119
|
+
filePath: string,
|
|
120
|
+
value: T,
|
|
121
|
+
): Promise<void> => {
|
|
122
|
+
const tempPath = `${filePath}.${process.pid}.${crypto.randomUUID()}.tmp`;
|
|
123
|
+
|
|
124
|
+
try {
|
|
125
|
+
await fs.writeFile(tempPath, JSON.stringify(value), 'utf8');
|
|
126
|
+
await fs.rename(tempPath, filePath);
|
|
127
|
+
} catch (error) {
|
|
128
|
+
await removeFileIfPresent(tempPath);
|
|
129
|
+
throw error;
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
|
|
118
133
|
const removeFileIfPresent = async (filePath: string): Promise<void> => {
|
|
119
134
|
try {
|
|
120
135
|
await fs.rm(filePath, { force: true });
|
|
@@ -143,7 +158,7 @@ const isMetadataStale = (
|
|
|
143
158
|
metadata: ResourceLockMetadata,
|
|
144
159
|
now: number,
|
|
145
160
|
staleLockTimeoutMs: number,
|
|
146
|
-
isProcessActive: (pid: number) => boolean
|
|
161
|
+
isProcessActive: (pid: number) => boolean,
|
|
147
162
|
): boolean => {
|
|
148
163
|
if (!isProcessActive(metadata.pid)) {
|
|
149
164
|
return true;
|
|
@@ -154,14 +169,14 @@ const isMetadataStale = (
|
|
|
154
169
|
|
|
155
170
|
const isQueuedTicketStale = (
|
|
156
171
|
metadata: ResourceLockMetadata,
|
|
157
|
-
isProcessActive: (pid: number) => boolean
|
|
172
|
+
isProcessActive: (pid: number) => boolean,
|
|
158
173
|
): boolean => {
|
|
159
174
|
return !isProcessActive(metadata.pid);
|
|
160
175
|
};
|
|
161
176
|
|
|
162
177
|
const waitForPollInterval = (
|
|
163
178
|
ms: number,
|
|
164
|
-
signal?: AbortSignal
|
|
179
|
+
signal?: AbortSignal,
|
|
165
180
|
): Promise<void> => {
|
|
166
181
|
if (!signal) {
|
|
167
182
|
return wait(ms);
|
|
@@ -187,7 +202,7 @@ const waitForPollInterval = (
|
|
|
187
202
|
};
|
|
188
203
|
|
|
189
204
|
const readQueueTickets = async (
|
|
190
|
-
queueDir: string
|
|
205
|
+
queueDir: string,
|
|
191
206
|
): Promise<ResourceLockMetadata[]> => {
|
|
192
207
|
const ticketEntries = await fs.readdir(queueDir, { withFileTypes: true });
|
|
193
208
|
const tickets = await Promise.all(
|
|
@@ -196,15 +211,15 @@ const readQueueTickets = async (
|
|
|
196
211
|
.map(async (entry) => ({
|
|
197
212
|
name: entry.name,
|
|
198
213
|
metadata: await readJsonFile<ResourceLockMetadata>(
|
|
199
|
-
path.join(queueDir, entry.name)
|
|
214
|
+
path.join(queueDir, entry.name),
|
|
200
215
|
),
|
|
201
|
-
}))
|
|
216
|
+
})),
|
|
202
217
|
);
|
|
203
218
|
|
|
204
219
|
return tickets
|
|
205
220
|
.filter(
|
|
206
221
|
(entry): entry is { name: string; metadata: ResourceLockMetadata } =>
|
|
207
|
-
entry.metadata !== null
|
|
222
|
+
entry.metadata !== null,
|
|
208
223
|
)
|
|
209
224
|
.sort((left, right) => left.name.localeCompare(right.name))
|
|
210
225
|
.map((entry) => entry.metadata);
|
|
@@ -229,10 +244,10 @@ const cleanupQueue = async (options: {
|
|
|
229
244
|
logger.debug(
|
|
230
245
|
'removing stale queued ticket %s for key %s',
|
|
231
246
|
ticket.ticketId,
|
|
232
|
-
ticket.key
|
|
247
|
+
ticket.key,
|
|
233
248
|
);
|
|
234
249
|
await removeFileIfPresent(
|
|
235
|
-
path.join(paths.queueDir, `${ticket.ticketId}.json`)
|
|
250
|
+
path.join(paths.queueDir, `${ticket.ticketId}.json`),
|
|
236
251
|
);
|
|
237
252
|
continue;
|
|
238
253
|
}
|
|
@@ -265,7 +280,7 @@ const maybeClearStaleOwner = async (options: {
|
|
|
265
280
|
logger.debug(
|
|
266
281
|
'removing stale owner ticket %s for key %s',
|
|
267
282
|
owner.ticketId,
|
|
268
|
-
owner.key
|
|
283
|
+
owner.key,
|
|
269
284
|
);
|
|
270
285
|
await removeFileIfPresent(ownerFilePath);
|
|
271
286
|
return null;
|
|
@@ -273,7 +288,7 @@ const maybeClearStaleOwner = async (options: {
|
|
|
273
288
|
|
|
274
289
|
const claimOwnership = async (
|
|
275
290
|
ownerFilePath: string,
|
|
276
|
-
metadata: ResourceLockMetadata
|
|
291
|
+
metadata: ResourceLockMetadata,
|
|
277
292
|
): Promise<boolean> => {
|
|
278
293
|
try {
|
|
279
294
|
await fs.writeFile(ownerFilePath, JSON.stringify(metadata), {
|
|
@@ -291,7 +306,7 @@ const claimOwnership = async (
|
|
|
291
306
|
};
|
|
292
307
|
|
|
293
308
|
export const createResourceLockManager = (
|
|
294
|
-
options: ResourceLockManagerOptions = {}
|
|
309
|
+
options: ResourceLockManagerOptions = {},
|
|
295
310
|
): ResourceLockManager => {
|
|
296
311
|
const rootDir = options.rootDir ?? DEFAULT_ROOT_DIR;
|
|
297
312
|
const pollIntervalMs = options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
|
|
@@ -323,6 +338,7 @@ export const createResourceLockManager = (
|
|
|
323
338
|
scopedLogger.debug('queued ticket %s for key %s', ticketId, key);
|
|
324
339
|
|
|
325
340
|
let heartbeatTimer: NodeJS.Timeout | null = null;
|
|
341
|
+
let heartbeatInFlight = false;
|
|
326
342
|
let released = false;
|
|
327
343
|
let didNotifyWait = false;
|
|
328
344
|
const waitStartedAt = Date.now();
|
|
@@ -339,7 +355,7 @@ export const createResourceLockManager = (
|
|
|
339
355
|
}
|
|
340
356
|
|
|
341
357
|
const owner = await readJsonFile<ResourceLockMetadata>(
|
|
342
|
-
paths.ownerFilePath
|
|
358
|
+
paths.ownerFilePath,
|
|
343
359
|
);
|
|
344
360
|
if (owner?.ticketId === ticketId) {
|
|
345
361
|
await removeFileIfPresent(paths.ownerFilePath);
|
|
@@ -351,30 +367,36 @@ export const createResourceLockManager = (
|
|
|
351
367
|
|
|
352
368
|
const startHeartbeat = () => {
|
|
353
369
|
heartbeatTimer = setInterval(async () => {
|
|
354
|
-
|
|
355
|
-
const owner = await readJsonFile<ResourceLockMetadata>(
|
|
356
|
-
paths.ownerFilePath
|
|
357
|
-
);
|
|
358
|
-
|
|
359
|
-
if (released || owner?.ticketId !== ticketId) {
|
|
370
|
+
if (heartbeatInFlight) {
|
|
360
371
|
return;
|
|
361
372
|
}
|
|
362
373
|
|
|
363
|
-
|
|
364
|
-
...owner,
|
|
365
|
-
heartbeatAt: nextHeartbeatAt,
|
|
366
|
-
};
|
|
374
|
+
heartbeatInFlight = true;
|
|
367
375
|
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
376
|
+
try {
|
|
377
|
+
const nextHeartbeatAt = Date.now();
|
|
378
|
+
const owner = await readJsonFile<ResourceLockMetadata>(
|
|
379
|
+
paths.ownerFilePath,
|
|
380
|
+
);
|
|
371
381
|
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
382
|
+
if (released || owner?.ticketId !== ticketId) {
|
|
383
|
+
return;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
const nextMetadata: ResourceLockMetadata = {
|
|
387
|
+
...owner,
|
|
388
|
+
heartbeatAt: nextHeartbeatAt,
|
|
389
|
+
};
|
|
390
|
+
|
|
391
|
+
if (released) {
|
|
392
|
+
return;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
await writeJsonFileAtomic(paths.ownerFilePath, nextMetadata);
|
|
396
|
+
scopedLogger.debug('refreshed heartbeat for ticket %s', ticketId);
|
|
397
|
+
} finally {
|
|
398
|
+
heartbeatInFlight = false;
|
|
399
|
+
}
|
|
378
400
|
}, heartbeatIntervalMs);
|
|
379
401
|
heartbeatTimer.unref?.();
|
|
380
402
|
};
|
|
@@ -391,12 +413,12 @@ export const createResourceLockManager = (
|
|
|
391
413
|
isProcessActive,
|
|
392
414
|
});
|
|
393
415
|
const ownIndex = activeTickets.findIndex(
|
|
394
|
-
(entry) => entry.ticketId === ticketId
|
|
416
|
+
(entry) => entry.ticketId === ticketId,
|
|
395
417
|
);
|
|
396
418
|
|
|
397
419
|
if (ownIndex === -1) {
|
|
398
420
|
throw new Error(
|
|
399
|
-
`Queued ticket ${ticketId} disappeared before acquisition
|
|
421
|
+
`Queued ticket ${ticketId} disappeared before acquisition.`,
|
|
400
422
|
);
|
|
401
423
|
}
|
|
402
424
|
|
|
@@ -420,7 +442,7 @@ export const createResourceLockManager = (
|
|
|
420
442
|
scopedLogger.debug(
|
|
421
443
|
'acquired lock for key %s with ticket %s',
|
|
422
444
|
key,
|
|
423
|
-
ticketId
|
|
445
|
+
ticketId,
|
|
424
446
|
);
|
|
425
447
|
return { release };
|
|
426
448
|
}
|
|
@@ -436,7 +458,7 @@ export const createResourceLockManager = (
|
|
|
436
458
|
'waiting for key %s with ticket %s at queue position %d',
|
|
437
459
|
key,
|
|
438
460
|
ticketId,
|
|
439
|
-
ownIndex + 1
|
|
461
|
+
ownIndex + 1,
|
|
440
462
|
);
|
|
441
463
|
|
|
442
464
|
await waitForPollInterval(pollIntervalMs, acquireOptions.signal);
|
package/src/setup.ts
CHANGED
|
@@ -122,6 +122,6 @@ export const setup = async (globalConfig: JestConfig.GlobalConfig) => {
|
|
|
122
122
|
logTestEnvironmentReady(selectedRunner);
|
|
123
123
|
setupLogger.debug('Harness instance is ready');
|
|
124
124
|
|
|
125
|
-
global.HARNESS_CONFIG =
|
|
125
|
+
global.HARNESS_CONFIG = harness.config;
|
|
126
126
|
global.HARNESS = harness;
|
|
127
127
|
};
|