@react-native-harness/jest 1.1.0-rc.1 → 1.1.0-rc.3
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__/errors.test.js +6 -1
- package/dist/__tests__/harness-cache.test.d.ts +2 -0
- package/dist/__tests__/harness-cache.test.d.ts.map +1 -0
- package/dist/__tests__/harness-cache.test.js +51 -0
- package/dist/__tests__/harness.test.js +484 -47
- package/dist/__tests__/index.test.d.ts +2 -0
- package/dist/__tests__/index.test.d.ts.map +1 -0
- package/dist/__tests__/index.test.js +42 -0
- package/dist/__tests__/resource-lock.test.d.ts +2 -0
- package/dist/__tests__/resource-lock.test.d.ts.map +1 -0
- package/dist/__tests__/resource-lock.test.js +103 -0
- package/dist/action-hooks.d.ts +9 -0
- package/dist/action-hooks.d.ts.map +1 -0
- package/dist/action-hooks.js +36 -0
- package/dist/cli-args.d.ts +1 -0
- package/dist/cli-args.d.ts.map +1 -1
- package/dist/cli-args.js +5 -0
- package/dist/crash-supervisor.d.ts.map +1 -1
- package/dist/crash-supervisor.js +7 -6
- package/dist/errors.d.ts +5 -0
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +10 -2
- package/dist/harness.d.ts +23 -1
- package/dist/harness.d.ts.map +1 -1
- package/dist/harness.js +574 -136
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +205 -58
- package/dist/logs.d.ts +4 -0
- package/dist/logs.d.ts.map +1 -1
- package/dist/logs.js +12 -0
- package/dist/resource-lock.d.ts +25 -0
- package/dist/resource-lock.d.ts.map +1 -0
- package/dist/resource-lock.js +278 -0
- package/dist/run.d.ts +7 -1
- package/dist/run.d.ts.map +1 -1
- package/dist/run.js +13 -8
- package/dist/setup.d.ts.map +1 -1
- package/dist/setup.js +29 -2
- package/dist/tsconfig.lib.tsbuildinfo +1 -1
- package/package.json +7 -6
- package/src/__tests__/errors.test.ts +18 -6
- package/src/__tests__/harness-cache.test.ts +74 -0
- package/src/__tests__/harness.test.ts +702 -49
- package/src/__tests__/index.test.ts +61 -0
- package/src/__tests__/resource-lock.test.ts +138 -0
- package/src/action-hooks.ts +66 -0
- package/src/cli-args.ts +6 -0
- package/src/crash-supervisor.ts +13 -6
- package/src/env.d.ts +1 -1
- package/src/errors.ts +15 -4
- package/src/harness.ts +743 -157
- package/src/index.ts +276 -64
- package/src/logs.ts +17 -3
- package/src/resource-lock.ts +450 -0
- package/src/run.ts +20 -10
- package/src/setup.ts +42 -1
- package/tsconfig.json +3 -3
- package/tsconfig.lib.json +6 -6
package/src/index.ts
CHANGED
|
@@ -11,13 +11,40 @@ import type {
|
|
|
11
11
|
import pLimit from 'p-limit';
|
|
12
12
|
import { runHarnessTestFile } from './run.js';
|
|
13
13
|
import { Config as HarnessConfig } from '@react-native-harness/config';
|
|
14
|
-
import { type Harness } from './harness.js';
|
|
14
|
+
import { type Harness, type HarnessRunState } from './harness.js';
|
|
15
15
|
import { setup } from './setup.js';
|
|
16
16
|
import { teardown } from './teardown.js';
|
|
17
17
|
import { HarnessError } from '@react-native-harness/tools';
|
|
18
18
|
import { getErrorMessage } from './logs.js';
|
|
19
19
|
import { DeviceNotRespondingError } from '@react-native-harness/bridge/server';
|
|
20
|
-
import { NativeCrashError } from './errors.js';
|
|
20
|
+
import { NativeCrashError, StartupStallError } from './errors.js';
|
|
21
|
+
import { logger } from '@react-native-harness/tools';
|
|
22
|
+
import { randomUUID } from 'node:crypto';
|
|
23
|
+
import path from 'node:path';
|
|
24
|
+
|
|
25
|
+
const runLogger = logger.child('run');
|
|
26
|
+
|
|
27
|
+
const createRunSummary = () => ({
|
|
28
|
+
passed: 0,
|
|
29
|
+
failed: 0,
|
|
30
|
+
skipped: 0,
|
|
31
|
+
todo: 0,
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
const applyJestResultToSummary = (
|
|
35
|
+
summary: ReturnType<typeof createRunSummary>,
|
|
36
|
+
result: {
|
|
37
|
+
numPassingTests: number;
|
|
38
|
+
numFailingTests: number;
|
|
39
|
+
numPendingTests: number;
|
|
40
|
+
numTodoTests: number;
|
|
41
|
+
}
|
|
42
|
+
) => {
|
|
43
|
+
summary.passed += result.numPassingTests;
|
|
44
|
+
summary.failed += result.numFailingTests;
|
|
45
|
+
summary.skipped += result.numPendingTests;
|
|
46
|
+
summary.todo += result.numTodoTests;
|
|
47
|
+
};
|
|
21
48
|
|
|
22
49
|
class CancelRun extends Error {
|
|
23
50
|
constructor(message?: string) {
|
|
@@ -87,81 +114,266 @@ export default class JestHarness implements CallbackTestRunnerInterface {
|
|
|
87
114
|
): Promise<void> {
|
|
88
115
|
const mutex = pLimit(1);
|
|
89
116
|
let isFirstTest = true;
|
|
117
|
+
const startTime = Date.now();
|
|
118
|
+
const runId = randomUUID();
|
|
119
|
+
const watchMode = this.#globalConfig.watch || this.#globalConfig.watchAll;
|
|
120
|
+
const rootDir = this.#globalConfig.rootDir ?? process.cwd();
|
|
121
|
+
const testFiles = tests.map((test) =>
|
|
122
|
+
path.relative(rootDir, test.path)
|
|
123
|
+
);
|
|
124
|
+
const summary = createRunSummary();
|
|
125
|
+
const updateRunState = (overrides: Partial<HarnessRunState> = {}) => {
|
|
126
|
+
const nextRunState: HarnessRunState = {
|
|
127
|
+
runId,
|
|
128
|
+
startTime,
|
|
129
|
+
testFiles,
|
|
130
|
+
watchMode,
|
|
131
|
+
coverageEnabled: this.#globalConfig.collectCoverage,
|
|
132
|
+
summary,
|
|
133
|
+
status: summary.failed > 0 ? 'failed' : 'passed',
|
|
134
|
+
...overrides,
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
harness.setRunState(nextRunState);
|
|
138
|
+
return nextRunState;
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
updateRunState();
|
|
142
|
+
runLogger.debug(
|
|
143
|
+
'run started: runId=%s files=%d watchMode=%s coverage=%s',
|
|
144
|
+
runId,
|
|
145
|
+
testFiles.length,
|
|
146
|
+
watchMode,
|
|
147
|
+
this.#globalConfig.collectCoverage
|
|
148
|
+
);
|
|
149
|
+
await harness.callHook('run:started', {
|
|
150
|
+
runId,
|
|
151
|
+
startTime,
|
|
152
|
+
testFiles,
|
|
153
|
+
watchMode,
|
|
154
|
+
coverageEnabled: this.#globalConfig.collectCoverage,
|
|
155
|
+
});
|
|
156
|
+
await harness.callHook('metro:initialized', {
|
|
157
|
+
runId,
|
|
158
|
+
port: harnessConfig.metroPort,
|
|
159
|
+
host: harnessConfig.host?.trim() || undefined,
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
try {
|
|
163
|
+
await tests.reduce(
|
|
164
|
+
(promise, test) =>
|
|
165
|
+
mutex(() => {
|
|
166
|
+
let didApplySummary = false;
|
|
167
|
+
|
|
168
|
+
return promise
|
|
169
|
+
.then(async () => {
|
|
170
|
+
const relativeTestPath = path.relative(
|
|
171
|
+
rootDir,
|
|
172
|
+
test.path
|
|
173
|
+
);
|
|
174
|
+
const testFileStartedAt = Date.now();
|
|
175
|
+
let didEmitTestFileFinished = false;
|
|
176
|
+
|
|
177
|
+
await harness.callHook('test-file:started', {
|
|
178
|
+
runId,
|
|
179
|
+
file: relativeTestPath,
|
|
180
|
+
});
|
|
181
|
+
runLogger.debug('test file started: %s', relativeTestPath);
|
|
182
|
+
|
|
183
|
+
const emitTestFileFinished = async (options: {
|
|
184
|
+
status: 'passed' | 'failed' | 'skipped' | 'todo';
|
|
185
|
+
duration: number;
|
|
186
|
+
result: Awaited<ReturnType<typeof runHarnessTestFile>>['harnessResult'] | null;
|
|
187
|
+
}) => {
|
|
188
|
+
didEmitTestFileFinished = true;
|
|
189
|
+
runLogger.debug(
|
|
190
|
+
'test file finished: %s status=%s duration=%dms',
|
|
191
|
+
relativeTestPath,
|
|
192
|
+
options.status,
|
|
193
|
+
options.duration
|
|
194
|
+
);
|
|
195
|
+
await harness.callHook('test-file:finished', {
|
|
196
|
+
runId,
|
|
197
|
+
file: relativeTestPath,
|
|
198
|
+
duration: options.duration,
|
|
199
|
+
status: options.status,
|
|
200
|
+
result: options.result,
|
|
201
|
+
});
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
try {
|
|
205
|
+
if (watcher.isInterrupted()) {
|
|
206
|
+
throw new CancelRun();
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
if (
|
|
210
|
+
harnessConfig.resetEnvironmentBetweenTestFiles &&
|
|
211
|
+
!isFirstTest
|
|
212
|
+
) {
|
|
213
|
+
runLogger.debug(
|
|
214
|
+
'resetting environment before %s',
|
|
215
|
+
relativeTestPath
|
|
216
|
+
);
|
|
217
|
+
await harness.restart(test.path);
|
|
218
|
+
}
|
|
219
|
+
isFirstTest = false;
|
|
220
|
+
|
|
221
|
+
const result = await onStart(test).then(async () => {
|
|
222
|
+
if (!harnessConfig.detectNativeCrashes) {
|
|
223
|
+
runLogger.debug(
|
|
224
|
+
'native crash detection disabled for %s',
|
|
225
|
+
relativeTestPath
|
|
226
|
+
);
|
|
227
|
+
await harness.ensureAppReady(test.path);
|
|
228
|
+
return runHarnessTestFile({
|
|
229
|
+
testPath: test.path,
|
|
230
|
+
harness,
|
|
231
|
+
globalConfig: this.#globalConfig,
|
|
232
|
+
projectConfig: test.context.config,
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
await harness.ensureAppReady(test.path);
|
|
237
|
+
harness.crashSupervisor.beginTestRun(test.path);
|
|
238
|
+
runLogger.debug(
|
|
239
|
+
'native crash detection armed for %s',
|
|
240
|
+
relativeTestPath
|
|
241
|
+
);
|
|
242
|
+
const crashPromise =
|
|
243
|
+
harness.crashSupervisor.waitForCrash(test.path);
|
|
244
|
+
|
|
245
|
+
try {
|
|
246
|
+
return await Promise.race([
|
|
247
|
+
runHarnessTestFile({
|
|
248
|
+
testPath: test.path,
|
|
249
|
+
harness,
|
|
250
|
+
globalConfig: this.#globalConfig,
|
|
251
|
+
projectConfig: test.context.config,
|
|
252
|
+
}),
|
|
253
|
+
crashPromise,
|
|
254
|
+
]);
|
|
255
|
+
} finally {
|
|
256
|
+
harness.crashSupervisor.cancelCrashWaiters();
|
|
257
|
+
}
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
applyJestResultToSummary(summary, result.jestResult);
|
|
261
|
+
didApplySummary = true;
|
|
262
|
+
updateRunState();
|
|
263
|
+
await emitTestFileFinished({
|
|
264
|
+
status: result.harnessResult.status,
|
|
265
|
+
duration: result.duration,
|
|
266
|
+
result: result.harnessResult,
|
|
267
|
+
});
|
|
90
268
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
269
|
+
return result;
|
|
270
|
+
} catch (error) {
|
|
271
|
+
if (!didEmitTestFileFinished) {
|
|
272
|
+
await emitTestFileFinished({
|
|
273
|
+
status: 'failed',
|
|
274
|
+
duration: Date.now() - testFileStartedAt,
|
|
275
|
+
result: null,
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
throw error;
|
|
280
|
+
}
|
|
281
|
+
})
|
|
282
|
+
.then(async (result) => {
|
|
283
|
+
if (!result) {
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
99
286
|
|
|
287
|
+
await onResult(test, result.jestResult);
|
|
288
|
+
})
|
|
289
|
+
.catch(async (err) => {
|
|
100
290
|
if (
|
|
101
|
-
|
|
102
|
-
|
|
291
|
+
err instanceof NativeCrashError ||
|
|
292
|
+
err instanceof StartupStallError ||
|
|
293
|
+
err instanceof DeviceNotRespondingError
|
|
103
294
|
) {
|
|
104
|
-
|
|
295
|
+
runLogger.debug(
|
|
296
|
+
'classified runtime failure for %s: %s',
|
|
297
|
+
test.path,
|
|
298
|
+
err.name
|
|
299
|
+
);
|
|
300
|
+
summary.failed += 1;
|
|
301
|
+
updateRunState();
|
|
105
302
|
}
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
testPath: test.path,
|
|
113
|
-
harness,
|
|
114
|
-
globalConfig: this.#globalConfig,
|
|
115
|
-
projectConfig: test.context.config,
|
|
303
|
+
|
|
304
|
+
if (err instanceof NativeCrashError) {
|
|
305
|
+
harness.crashSupervisor.reset();
|
|
306
|
+
onFailure(test, {
|
|
307
|
+
message: err.message,
|
|
308
|
+
stack: '',
|
|
116
309
|
});
|
|
117
|
-
}
|
|
118
310
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
const crashPromise = harness.crashSupervisor.waitForCrash(test.path);
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
122
313
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
globalConfig: this.#globalConfig,
|
|
129
|
-
projectConfig: test.context.config,
|
|
130
|
-
}),
|
|
131
|
-
crashPromise,
|
|
132
|
-
]);
|
|
314
|
+
if (err instanceof StartupStallError) {
|
|
315
|
+
onFailure(test, {
|
|
316
|
+
message: err.message,
|
|
317
|
+
stack: '',
|
|
318
|
+
});
|
|
133
319
|
|
|
134
|
-
return
|
|
135
|
-
} finally {
|
|
136
|
-
harness.crashSupervisor.cancelCrashWaiters();
|
|
320
|
+
return;
|
|
137
321
|
}
|
|
138
|
-
});
|
|
139
|
-
})
|
|
140
|
-
.then((result) => onResult(test, result))
|
|
141
|
-
.catch(async (err) => {
|
|
142
|
-
if (err instanceof NativeCrashError) {
|
|
143
|
-
harness.crashSupervisor.reset();
|
|
144
|
-
onFailure(test, {
|
|
145
|
-
message: err.message,
|
|
146
|
-
stack: '',
|
|
147
|
-
});
|
|
148
322
|
|
|
149
|
-
|
|
150
|
-
|
|
323
|
+
if (err instanceof DeviceNotRespondingError) {
|
|
324
|
+
onFailure(test, {
|
|
325
|
+
message: err.message,
|
|
326
|
+
stack: '',
|
|
327
|
+
});
|
|
151
328
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
message: err.message,
|
|
155
|
-
stack: '',
|
|
156
|
-
});
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
157
331
|
|
|
158
|
-
|
|
159
|
-
|
|
332
|
+
if (!(err instanceof CancelRun) && !didApplySummary) {
|
|
333
|
+
summary.failed += 1;
|
|
334
|
+
}
|
|
335
|
+
updateRunState({ error: err });
|
|
336
|
+
onFailure(test, err);
|
|
337
|
+
});
|
|
338
|
+
}),
|
|
339
|
+
Promise.resolve()
|
|
340
|
+
);
|
|
160
341
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
342
|
+
const runState = updateRunState();
|
|
343
|
+
runLogger.debug(
|
|
344
|
+
'run finished: runId=%s status=%s duration=%dms',
|
|
345
|
+
runId,
|
|
346
|
+
runState.status ?? 'passed',
|
|
347
|
+
Date.now() - startTime
|
|
348
|
+
);
|
|
349
|
+
await harness.callHook('run:finished', {
|
|
350
|
+
runId,
|
|
351
|
+
startTime,
|
|
352
|
+
duration: Date.now() - startTime,
|
|
353
|
+
testFiles,
|
|
354
|
+
summary,
|
|
355
|
+
status: runState.status ?? 'passed',
|
|
356
|
+
});
|
|
357
|
+
} catch (error) {
|
|
358
|
+
const runState = updateRunState({
|
|
359
|
+
error,
|
|
360
|
+
status: 'failed',
|
|
361
|
+
});
|
|
362
|
+
runLogger.debug(
|
|
363
|
+
'run failed: runId=%s status=%s',
|
|
364
|
+
runId,
|
|
365
|
+
runState.status ?? 'failed'
|
|
366
|
+
);
|
|
367
|
+
await harness.callHook('run:finished', {
|
|
368
|
+
runId,
|
|
369
|
+
startTime,
|
|
370
|
+
duration: Date.now() - startTime,
|
|
371
|
+
testFiles,
|
|
372
|
+
summary,
|
|
373
|
+
status: runState.status ?? 'failed',
|
|
374
|
+
error,
|
|
375
|
+
});
|
|
376
|
+
throw error;
|
|
377
|
+
}
|
|
166
378
|
}
|
|
167
379
|
}
|
package/src/logs.ts
CHANGED
|
@@ -25,10 +25,24 @@ export const logTestEnvironmentReady = (runner: HarnessPlatform): void => {
|
|
|
25
25
|
log(`${TAG} Runner ${chalk.bold(runner.name)} ready\n`);
|
|
26
26
|
};
|
|
27
27
|
|
|
28
|
+
export const logRunnerWaitingInQueue = (runner: HarnessPlatform): void => {
|
|
29
|
+
log(`${TAG} Runner ${chalk.bold(runner.name)} is busy, waiting in queue\n`);
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export const logRunnerStillWaitingInQueue = (runner: HarnessPlatform): void => {
|
|
33
|
+
log(`${TAG} Still waiting in queue for ${chalk.bold(runner.name)} runner\n`);
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export const logRunnerStarting = (runner: HarnessPlatform): void => {
|
|
37
|
+
log(`${TAG} Runner ${chalk.bold(runner.name)} is starting\n`);
|
|
38
|
+
};
|
|
39
|
+
|
|
28
40
|
export const logMetroPrewarmCompleted = (runner: HarnessPlatform): void => {
|
|
29
|
-
log(
|
|
30
|
-
|
|
31
|
-
|
|
41
|
+
log(`${TAG} Metro pre-warm for ${chalk.bold(runner.name)} completed\n`);
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export const logMetroCacheReused = (runner: HarnessPlatform): void => {
|
|
45
|
+
log(`${TAG} Reusing Metro cache for ${chalk.bold(runner.name)}\n`);
|
|
32
46
|
};
|
|
33
47
|
|
|
34
48
|
export const getErrorMessage = (error: HarnessError): string => {
|