@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.
@@ -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
- const nextHeartbeatAt = Date.now();
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
- const nextMetadata: ResourceLockMetadata = {
364
- ...owner,
365
- heartbeatAt: nextHeartbeatAt,
366
- };
374
+ heartbeatInFlight = true;
367
375
 
368
- if (released) {
369
- return;
370
- }
376
+ try {
377
+ const nextHeartbeatAt = Date.now();
378
+ const owner = await readJsonFile<ResourceLockMetadata>(
379
+ paths.ownerFilePath,
380
+ );
371
381
 
372
- await fs.writeFile(
373
- paths.ownerFilePath,
374
- JSON.stringify(nextMetadata),
375
- 'utf8'
376
- );
377
- scopedLogger.debug('refreshed heartbeat for ticket %s', ticketId);
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 = harnessConfig;
125
+ global.HARNESS_CONFIG = harness.config;
126
126
  global.HARNESS = harness;
127
127
  };