@react-native-harness/jest 1.1.0 → 1.2.0
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__/bridge.test.d.ts +2 -0
- package/dist/__tests__/bridge.test.d.ts.map +1 -0
- package/dist/__tests__/bridge.test.js +142 -0
- package/dist/__tests__/client-log-handler.test.js +116 -72
- package/dist/__tests__/crash-monitor.test.d.ts +2 -0
- package/dist/__tests__/crash-monitor.test.d.ts.map +1 -0
- package/dist/__tests__/crash-monitor.test.js +212 -0
- package/dist/__tests__/execute-run.test.d.ts +2 -0
- package/dist/__tests__/execute-run.test.d.ts.map +1 -0
- package/dist/__tests__/execute-run.test.js +229 -0
- package/dist/__tests__/harness.test.js +35 -0
- package/dist/__tests__/hook-queue.test.d.ts +2 -0
- package/dist/__tests__/hook-queue.test.d.ts.map +1 -0
- package/dist/__tests__/hook-queue.test.js +56 -0
- package/dist/__tests__/index.test.js +94 -37
- package/dist/__tests__/resource-lock.test.js +24 -24
- package/dist/cli-args.test.d.ts +2 -0
- package/dist/cli-args.test.d.ts.map +1 -0
- package/dist/cli-args.test.js +1 -0
- package/dist/client-log-handler.d.ts +10 -11
- package/dist/client-log-handler.d.ts.map +1 -1
- package/dist/client-log-handler.js +36 -58
- package/dist/crash-monitor.d.ts +23 -0
- package/dist/crash-monitor.d.ts.map +1 -0
- package/dist/crash-monitor.js +176 -0
- package/dist/event-mapper.d.ts +16 -0
- package/dist/event-mapper.d.ts.map +1 -0
- package/dist/event-mapper.js +120 -0
- package/dist/execute-run.d.ts +4 -0
- package/dist/execute-run.d.ts.map +1 -0
- package/dist/execute-run.js +151 -0
- package/dist/global-setup.d.ts +3 -0
- package/dist/global-setup.d.ts.map +1 -0
- package/dist/global-setup.js +60 -0
- package/dist/global-teardown.d.ts +3 -0
- package/dist/global-teardown.d.ts.map +1 -0
- package/dist/global-teardown.js +11 -0
- package/dist/harness-reporter.d.ts +8 -0
- package/dist/harness-reporter.d.ts.map +1 -0
- package/dist/harness-reporter.js +12 -0
- package/dist/harness-session.d.ts +34 -0
- package/dist/harness-session.d.ts.map +1 -0
- package/dist/harness-session.js +523 -0
- package/dist/harness.d.ts.map +1 -1
- package/dist/harness.js +6 -1
- package/dist/hook-queue.d.ts +6 -0
- package/dist/hook-queue.d.ts.map +1 -0
- package/dist/hook-queue.js +18 -0
- package/dist/index.d.ts +0 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +12 -230
- package/dist/jest-options.d.ts +2 -0
- package/dist/jest-options.d.ts.map +1 -0
- package/dist/jest-options.js +1 -0
- package/dist/logs.d.ts +1 -0
- package/dist/logs.d.ts.map +1 -1
- package/dist/logs.js +3 -0
- package/dist/resource-lock.d.ts +0 -1
- package/dist/resource-lock.d.ts.map +1 -1
- package/dist/resource-lock.js +1 -1
- package/dist/run.d.ts +2 -2
- package/dist/run.d.ts.map +1 -1
- package/dist/run.js +3 -3
- package/dist/tsconfig.lib.tsbuildinfo +1 -1
- package/dist/types.d.ts +17 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +1 -0
- package/package.json +7 -8
- package/src/__tests__/bridge.test.ts +184 -0
- package/src/__tests__/client-log-handler.test.ts +117 -81
- package/src/__tests__/crash-monitor.test.ts +288 -0
- package/src/__tests__/execute-run.test.ts +325 -0
- package/src/__tests__/hook-queue.test.ts +77 -0
- package/src/__tests__/index.test.ts +135 -55
- package/src/__tests__/resource-lock.test.ts +29 -42
- package/src/client-log-handler.ts +48 -64
- package/src/crash-monitor.ts +248 -0
- package/src/execute-run.ts +199 -0
- package/src/harness-session.ts +734 -0
- package/src/hook-queue.ts +26 -0
- package/src/index.ts +17 -330
- package/src/logs.ts +4 -0
- package/src/resource-lock.ts +1 -1
- package/src/run.ts +5 -5
- package/tsconfig.tsbuildinfo +1 -0
- package/src/__tests__/crash-supervisor.test.ts +0 -200
- package/src/__tests__/harness-cache.test.ts +0 -74
- package/src/__tests__/harness.test.ts +0 -870
- package/src/crash-supervisor.ts +0 -321
- package/src/env.d.ts +0 -9
- package/src/harness.ts +0 -864
- package/src/setup.ts +0 -127
- package/src/teardown.ts +0 -14
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export type HookQueue = {
|
|
2
|
+
schedule: (work: () => Promise<void>) => void;
|
|
3
|
+
drain: () => Promise<void>;
|
|
4
|
+
};
|
|
5
|
+
|
|
6
|
+
export const createHookQueue = (): HookQueue => {
|
|
7
|
+
let tail: Promise<void> = Promise.resolve();
|
|
8
|
+
let firstError: unknown;
|
|
9
|
+
|
|
10
|
+
const schedule = (work: () => Promise<void>): void => {
|
|
11
|
+
tail = tail.then(work).catch((err) => {
|
|
12
|
+
firstError ??= err;
|
|
13
|
+
});
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const drain = async (): Promise<void> => {
|
|
17
|
+
await tail;
|
|
18
|
+
if (firstError !== undefined) {
|
|
19
|
+
const err = firstError;
|
|
20
|
+
firstError = undefined;
|
|
21
|
+
throw err;
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
return { schedule, drain };
|
|
26
|
+
};
|
package/src/index.ts
CHANGED
|
@@ -8,55 +8,16 @@ import type {
|
|
|
8
8
|
TestRunnerOptions,
|
|
9
9
|
TestWatcher,
|
|
10
10
|
} from 'jest-runner';
|
|
11
|
-
import
|
|
12
|
-
import {
|
|
13
|
-
import { Config as HarnessConfig } from '@react-native-harness/config';
|
|
14
|
-
import { type Harness, type HarnessRunState } from './harness.js';
|
|
15
|
-
import { setup } from './setup.js';
|
|
16
|
-
import { teardown } from './teardown.js';
|
|
11
|
+
import { createHarnessSession, type HarnessSession } from './harness-session.js';
|
|
12
|
+
import { executeRun } from './execute-run.js';
|
|
17
13
|
import { HarnessError } from '@react-native-harness/tools';
|
|
18
14
|
import { getErrorMessage } from './logs.js';
|
|
19
|
-
import { DeviceNotRespondingError } from '@react-native-harness/bridge/server';
|
|
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
|
-
};
|
|
48
|
-
|
|
49
|
-
class CancelRun extends Error {
|
|
50
|
-
constructor(message?: string) {
|
|
51
|
-
super(message);
|
|
52
|
-
this.name = 'CancelRun';
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
15
|
|
|
56
16
|
export default class JestHarness implements CallbackTestRunnerInterface {
|
|
57
17
|
readonly isSerial = true;
|
|
58
18
|
|
|
59
19
|
#globalConfig: Config.GlobalConfig;
|
|
20
|
+
#session: HarnessSession | null = null;
|
|
60
21
|
|
|
61
22
|
constructor(globalConfig: Config.GlobalConfig) {
|
|
62
23
|
this.#globalConfig = globalConfig;
|
|
@@ -68,312 +29,38 @@ export default class JestHarness implements CallbackTestRunnerInterface {
|
|
|
68
29
|
onStart: OnTestStart,
|
|
69
30
|
onResult: OnTestSuccess,
|
|
70
31
|
onFailure: OnTestFailure,
|
|
71
|
-
options: TestRunnerOptions
|
|
32
|
+
options: TestRunnerOptions,
|
|
72
33
|
): Promise<void> {
|
|
73
34
|
if (!options.serial) {
|
|
74
35
|
throw new Error('Parallel test running is not supported');
|
|
75
36
|
}
|
|
76
37
|
|
|
77
|
-
|
|
78
|
-
// This is necessary as Harness may throw and we want to catch it and display a helpful error message.
|
|
79
|
-
await setup(this.#globalConfig);
|
|
38
|
+
const isWatchMode = this.#globalConfig.watch || this.#globalConfig.watchAll;
|
|
80
39
|
|
|
81
|
-
|
|
82
|
-
|
|
40
|
+
try {
|
|
41
|
+
if (!this.#session) {
|
|
42
|
+
this.#session = await createHarnessSession(this.#globalConfig);
|
|
43
|
+
}
|
|
83
44
|
|
|
84
|
-
|
|
45
|
+
await executeRun(
|
|
46
|
+
this.#session,
|
|
85
47
|
tests,
|
|
86
48
|
watcher,
|
|
87
|
-
harness,
|
|
88
|
-
harnessConfig,
|
|
89
49
|
onStart,
|
|
90
50
|
onResult,
|
|
91
|
-
onFailure
|
|
51
|
+
onFailure,
|
|
52
|
+
this.#globalConfig,
|
|
92
53
|
);
|
|
93
54
|
} catch (error) {
|
|
94
55
|
if (error instanceof HarnessError) {
|
|
95
|
-
// Jest will print strings as they are, without processing them further.
|
|
96
56
|
throw getErrorMessage(error);
|
|
97
57
|
}
|
|
98
|
-
|
|
99
58
|
throw error;
|
|
100
59
|
} finally {
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
async _createInBandTestRun(
|
|
107
|
-
tests: Array<Test>,
|
|
108
|
-
watcher: TestWatcher,
|
|
109
|
-
harness: Harness,
|
|
110
|
-
harnessConfig: HarnessConfig,
|
|
111
|
-
onStart: OnTestStart,
|
|
112
|
-
onResult: OnTestSuccess,
|
|
113
|
-
onFailure: OnTestFailure
|
|
114
|
-
): Promise<void> {
|
|
115
|
-
const mutex = pLimit(1);
|
|
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
|
-
});
|
|
268
|
-
|
|
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
|
-
}
|
|
286
|
-
|
|
287
|
-
await onResult(test, result.jestResult);
|
|
288
|
-
})
|
|
289
|
-
.catch(async (err) => {
|
|
290
|
-
if (
|
|
291
|
-
err instanceof NativeCrashError ||
|
|
292
|
-
err instanceof StartupStallError ||
|
|
293
|
-
err instanceof DeviceNotRespondingError
|
|
294
|
-
) {
|
|
295
|
-
runLogger.debug(
|
|
296
|
-
'classified runtime failure for %s: %s',
|
|
297
|
-
test.path,
|
|
298
|
-
err.name
|
|
299
|
-
);
|
|
300
|
-
summary.failed += 1;
|
|
301
|
-
updateRunState();
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
if (err instanceof NativeCrashError) {
|
|
305
|
-
harness.crashSupervisor.reset();
|
|
306
|
-
onFailure(test, {
|
|
307
|
-
message: err.message,
|
|
308
|
-
stack: '',
|
|
309
|
-
});
|
|
310
|
-
|
|
311
|
-
return;
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
if (err instanceof StartupStallError) {
|
|
315
|
-
onFailure(test, {
|
|
316
|
-
message: err.message,
|
|
317
|
-
stack: '',
|
|
318
|
-
});
|
|
319
|
-
|
|
320
|
-
return;
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
if (err instanceof DeviceNotRespondingError) {
|
|
324
|
-
onFailure(test, {
|
|
325
|
-
message: err.message,
|
|
326
|
-
stack: '',
|
|
327
|
-
});
|
|
328
|
-
|
|
329
|
-
return;
|
|
330
|
-
}
|
|
331
|
-
|
|
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
|
-
);
|
|
341
|
-
|
|
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;
|
|
60
|
+
if (!isWatchMode) {
|
|
61
|
+
await this.#session?.dispose();
|
|
62
|
+
this.#session = null;
|
|
63
|
+
}
|
|
377
64
|
}
|
|
378
65
|
}
|
|
379
66
|
}
|
package/src/logs.ts
CHANGED
|
@@ -56,6 +56,10 @@ export const logMetroPortFallback = (
|
|
|
56
56
|
);
|
|
57
57
|
};
|
|
58
58
|
|
|
59
|
+
export const logNativeCoverageCollected = (lcovPath: string): void => {
|
|
60
|
+
log(`${TAG} Native coverage written to ${chalk.bold(lcovPath)}\n`);
|
|
61
|
+
};
|
|
62
|
+
|
|
59
63
|
export const getErrorMessage = (error: HarnessError): string => {
|
|
60
64
|
return `${ERROR_TAG} ${error.message}\n`;
|
|
61
65
|
};
|
package/src/resource-lock.ts
CHANGED
|
@@ -60,7 +60,7 @@ const wait = async (ms: number): Promise<void> => {
|
|
|
60
60
|
const createAbortError = () =>
|
|
61
61
|
new DOMException('The operation was aborted', 'AbortError');
|
|
62
62
|
|
|
63
|
-
|
|
63
|
+
const hashResourceLockKey = (key: string): string => {
|
|
64
64
|
return crypto.createHash('sha256').update(key).digest('hex');
|
|
65
65
|
};
|
|
66
66
|
|
package/src/run.ts
CHANGED
|
@@ -5,7 +5,7 @@ import type {
|
|
|
5
5
|
TestSuiteResult as HarnessTestSuiteResult,
|
|
6
6
|
TestResult as HarnessTestResult,
|
|
7
7
|
} from '@react-native-harness/bridge';
|
|
8
|
-
import type {
|
|
8
|
+
import type { HarnessSession } from './harness-session.js';
|
|
9
9
|
import { formatResultsErrors } from 'jest-message-util';
|
|
10
10
|
import { toTestResult } from './toTestResult.js';
|
|
11
11
|
|
|
@@ -62,7 +62,7 @@ const calculateStats = (tests: HarnessTestResult[]) => {
|
|
|
62
62
|
|
|
63
63
|
export type RunHarnessTestFile = (options: {
|
|
64
64
|
testPath: string;
|
|
65
|
-
|
|
65
|
+
session: HarnessSession;
|
|
66
66
|
projectConfig: JestConfig.ProjectConfig;
|
|
67
67
|
globalConfig: JestConfig.GlobalConfig;
|
|
68
68
|
}) => Promise<{
|
|
@@ -76,7 +76,7 @@ export const runHarnessTestFile: RunHarnessTestFile = async ({
|
|
|
76
76
|
testPath,
|
|
77
77
|
globalConfig,
|
|
78
78
|
projectConfig,
|
|
79
|
-
|
|
79
|
+
session,
|
|
80
80
|
}) => {
|
|
81
81
|
const start = Date.now();
|
|
82
82
|
const relativeTestPath = path.relative(globalConfig.rootDir, testPath);
|
|
@@ -89,11 +89,11 @@ export const runHarnessTestFile: RunHarnessTestFile = async ({
|
|
|
89
89
|
(setupFile) => path.relative(globalConfig.rootDir, setupFile)
|
|
90
90
|
);
|
|
91
91
|
|
|
92
|
-
const harnessResult = await
|
|
92
|
+
const harnessResult = await session.runTestFile(relativeTestPath, {
|
|
93
93
|
testNamePattern: globalConfig.testNamePattern,
|
|
94
94
|
setupFiles,
|
|
95
95
|
setupFilesAfterEnv,
|
|
96
|
-
runner:
|
|
96
|
+
runner: session.context.platform.runner,
|
|
97
97
|
});
|
|
98
98
|
const end = Date.now();
|
|
99
99
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"fileNames":[],"fileInfos":[],"root":[],"options":{"composite":true,"declarationMap":true,"emitDeclarationOnly":true,"importHelpers":true,"module":199,"noEmitOnError":true,"noFallthroughCasesInSwitch":true,"noImplicitOverride":true,"noImplicitReturns":true,"noUnusedLocals":true,"skipLibCheck":true,"strict":true,"target":9},"version":"5.9.3"}
|
|
@@ -1,200 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it, vi } from 'vitest';
|
|
2
|
-
import {
|
|
3
|
-
type AppMonitor,
|
|
4
|
-
type AppCrashDetails,
|
|
5
|
-
type AppMonitorEvent,
|
|
6
|
-
type AppMonitorListener,
|
|
7
|
-
type HarnessPlatformRunner,
|
|
8
|
-
} from '@react-native-harness/platforms';
|
|
9
|
-
import { createCrashSupervisor } from '../crash-supervisor.js';
|
|
10
|
-
|
|
11
|
-
type MockCrashDetailsGetter = (
|
|
12
|
-
options: {
|
|
13
|
-
processName?: string;
|
|
14
|
-
pid?: number;
|
|
15
|
-
occurredAt: number;
|
|
16
|
-
}
|
|
17
|
-
) => Promise<AppCrashDetails | null>;
|
|
18
|
-
|
|
19
|
-
const createMockAppMonitor = (options?: {
|
|
20
|
-
getCrashDetails?: MockCrashDetailsGetter;
|
|
21
|
-
}) => {
|
|
22
|
-
const listeners = new Set<AppMonitorListener>();
|
|
23
|
-
const noop = async () => undefined;
|
|
24
|
-
|
|
25
|
-
const appMonitor: AppMonitor & {
|
|
26
|
-
getCrashDetails?: MockCrashDetailsGetter;
|
|
27
|
-
} = {
|
|
28
|
-
start: noop,
|
|
29
|
-
stop: noop,
|
|
30
|
-
dispose: noop,
|
|
31
|
-
addListener: (listener) => {
|
|
32
|
-
listeners.add(listener);
|
|
33
|
-
},
|
|
34
|
-
removeListener: (listener) => {
|
|
35
|
-
listeners.delete(listener);
|
|
36
|
-
},
|
|
37
|
-
};
|
|
38
|
-
|
|
39
|
-
if (options?.getCrashDetails) {
|
|
40
|
-
appMonitor.getCrashDetails = options.getCrashDetails;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
const emit = (event: AppMonitorEvent) => {
|
|
44
|
-
for (const listener of listeners) {
|
|
45
|
-
listener(event);
|
|
46
|
-
}
|
|
47
|
-
};
|
|
48
|
-
|
|
49
|
-
return {
|
|
50
|
-
appMonitor,
|
|
51
|
-
emit,
|
|
52
|
-
};
|
|
53
|
-
};
|
|
54
|
-
|
|
55
|
-
const createPlatformRunner = ({
|
|
56
|
-
isAppRunning,
|
|
57
|
-
getCrashDetails,
|
|
58
|
-
}: {
|
|
59
|
-
isAppRunning: () => Promise<boolean>;
|
|
60
|
-
getCrashDetails?: HarnessPlatformRunner['getCrashDetails'];
|
|
61
|
-
}): HarnessPlatformRunner => ({
|
|
62
|
-
startApp: async () => undefined,
|
|
63
|
-
restartApp: async () => undefined,
|
|
64
|
-
stopApp: async () => undefined,
|
|
65
|
-
dispose: async () => undefined,
|
|
66
|
-
isAppRunning,
|
|
67
|
-
createAppMonitor: () => {
|
|
68
|
-
throw new Error('Not used in unit tests');
|
|
69
|
-
},
|
|
70
|
-
getCrashDetails,
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
describe('crash-supervisor', () => {
|
|
74
|
-
it('rejects the active test when the app exits during launch', async () => {
|
|
75
|
-
const monitor = createMockAppMonitor();
|
|
76
|
-
const supervisor = createCrashSupervisor({
|
|
77
|
-
appMonitor: monitor.appMonitor,
|
|
78
|
-
platformRunner: createPlatformRunner({
|
|
79
|
-
isAppRunning: vi.fn().mockResolvedValue(false),
|
|
80
|
-
}),
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
supervisor.beginLaunch('/tmp/startup.harness.ts');
|
|
84
|
-
|
|
85
|
-
const crashPromise = supervisor.waitForCrash('/tmp/startup.harness.ts');
|
|
86
|
-
const expectation =
|
|
87
|
-
expect(crashPromise).rejects.toMatchObject({
|
|
88
|
-
name: 'NativeCrashError',
|
|
89
|
-
phase: 'startup',
|
|
90
|
-
details: {
|
|
91
|
-
signal: 'SIGSEGV',
|
|
92
|
-
processName: 'com.harnessplayground',
|
|
93
|
-
},
|
|
94
|
-
});
|
|
95
|
-
monitor.emit({
|
|
96
|
-
type: 'app_exited',
|
|
97
|
-
source: 'logs',
|
|
98
|
-
crashDetails: {
|
|
99
|
-
source: 'logs',
|
|
100
|
-
signal: 'SIGSEGV',
|
|
101
|
-
processName: 'com.harnessplayground',
|
|
102
|
-
rawLines: ['Fatal signal 11 (SIGSEGV)'],
|
|
103
|
-
},
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
await expectation;
|
|
107
|
-
await supervisor.dispose();
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
it('treats polling exit events as immediately confirmed crashes', async () => {
|
|
111
|
-
const monitor = createMockAppMonitor();
|
|
112
|
-
const isAppRunning = vi.fn().mockResolvedValue(true);
|
|
113
|
-
const supervisor = createCrashSupervisor({
|
|
114
|
-
appMonitor: monitor.appMonitor,
|
|
115
|
-
platformRunner: createPlatformRunner({
|
|
116
|
-
isAppRunning,
|
|
117
|
-
}),
|
|
118
|
-
});
|
|
119
|
-
|
|
120
|
-
supervisor.beginLaunch('/tmp/polling-exit.harness.ts');
|
|
121
|
-
|
|
122
|
-
const crashPromise = supervisor.waitForCrash('/tmp/polling-exit.harness.ts');
|
|
123
|
-
monitor.emit({ type: 'app_exited', source: 'polling' });
|
|
124
|
-
|
|
125
|
-
await expect(crashPromise).rejects.toMatchObject({
|
|
126
|
-
name: 'NativeCrashError',
|
|
127
|
-
phase: 'startup',
|
|
128
|
-
});
|
|
129
|
-
expect(isAppRunning).not.toHaveBeenCalled();
|
|
130
|
-
await supervisor.dispose();
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
it('does not reject when monitoring is stopped', async () => {
|
|
134
|
-
const monitor = createMockAppMonitor();
|
|
135
|
-
const supervisor = createCrashSupervisor({
|
|
136
|
-
appMonitor: monitor.appMonitor,
|
|
137
|
-
platformRunner: createPlatformRunner({
|
|
138
|
-
isAppRunning: vi.fn().mockResolvedValue(false),
|
|
139
|
-
}),
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
supervisor.beginTestRun('/tmp/restart.harness.ts');
|
|
143
|
-
await supervisor.stop();
|
|
144
|
-
|
|
145
|
-
const reject = vi.fn();
|
|
146
|
-
void supervisor.waitForCrash('/tmp/restart.harness.ts').catch(reject);
|
|
147
|
-
|
|
148
|
-
monitor.emit({ type: 'app_exited', source: 'polling' });
|
|
149
|
-
await Promise.resolve();
|
|
150
|
-
|
|
151
|
-
expect(reject).not.toHaveBeenCalled();
|
|
152
|
-
await supervisor.dispose();
|
|
153
|
-
});
|
|
154
|
-
|
|
155
|
-
it('prefers crash details from the started app monitor over the runner', async () => {
|
|
156
|
-
const monitorGetCrashDetails = vi.fn().mockResolvedValue({
|
|
157
|
-
source: 'logs',
|
|
158
|
-
summary: 'full crash block',
|
|
159
|
-
rawLines: ['full crash block'],
|
|
160
|
-
});
|
|
161
|
-
const runnerGetCrashDetails = vi.fn().mockResolvedValue({
|
|
162
|
-
source: 'logs',
|
|
163
|
-
summary: 'runner details',
|
|
164
|
-
rawLines: ['runner details'],
|
|
165
|
-
});
|
|
166
|
-
const monitor = createMockAppMonitor({
|
|
167
|
-
getCrashDetails: monitorGetCrashDetails,
|
|
168
|
-
});
|
|
169
|
-
const supervisor = createCrashSupervisor({
|
|
170
|
-
appMonitor: monitor.appMonitor,
|
|
171
|
-
platformRunner: createPlatformRunner({
|
|
172
|
-
isAppRunning: vi.fn().mockResolvedValue(false),
|
|
173
|
-
getCrashDetails: runnerGetCrashDetails,
|
|
174
|
-
}),
|
|
175
|
-
});
|
|
176
|
-
|
|
177
|
-
supervisor.beginTestRun('/tmp/monitor-details.harness.ts');
|
|
178
|
-
|
|
179
|
-
const crashPromise = supervisor.waitForCrash('/tmp/monitor-details.harness.ts');
|
|
180
|
-
monitor.emit({
|
|
181
|
-
type: 'possible_crash',
|
|
182
|
-
source: 'logs',
|
|
183
|
-
crashDetails: {
|
|
184
|
-
source: 'logs',
|
|
185
|
-
processName: 'com.harnessplayground',
|
|
186
|
-
pid: 1234,
|
|
187
|
-
rawLines: ['partial line'],
|
|
188
|
-
},
|
|
189
|
-
});
|
|
190
|
-
|
|
191
|
-
await expect(crashPromise).rejects.toMatchObject({
|
|
192
|
-
details: {
|
|
193
|
-
summary: 'full crash block',
|
|
194
|
-
},
|
|
195
|
-
});
|
|
196
|
-
expect(monitorGetCrashDetails).toHaveBeenCalled();
|
|
197
|
-
expect(runnerGetCrashDetails).not.toHaveBeenCalled();
|
|
198
|
-
await supervisor.dispose();
|
|
199
|
-
});
|
|
200
|
-
});
|