@react-native-harness/jest 1.4.0-rc.1 → 1.4.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.js +6 -2
- package/dist/__tests__/crash-monitor.test.js +64 -0
- package/dist/__tests__/environment-reset.test.d.ts +2 -0
- package/dist/__tests__/environment-reset.test.d.ts.map +1 -0
- package/dist/__tests__/environment-reset.test.js +238 -0
- package/dist/__tests__/execute-run.test.js +250 -4
- package/dist/__tests__/format-harness-error.test.d.ts +2 -0
- package/dist/__tests__/format-harness-error.test.d.ts.map +1 -0
- package/dist/__tests__/format-harness-error.test.js +27 -0
- package/dist/__tests__/harness-cache.test.js +62 -41
- package/dist/__tests__/harness-session-signal.test.d.ts +2 -0
- package/dist/__tests__/harness-session-signal.test.d.ts.map +1 -0
- package/dist/__tests__/harness-session-signal.test.js +298 -0
- package/dist/__tests__/harness-session.test.js +161 -2
- package/dist/__tests__/index.test.js +3 -1
- package/dist/__tests__/metro-port.test.js +2 -0
- package/dist/__tests__/run.test.d.ts +2 -0
- package/dist/__tests__/run.test.d.ts.map +1 -0
- package/dist/__tests__/run.test.js +48 -0
- package/dist/crash-monitor.d.ts +7 -0
- package/dist/crash-monitor.d.ts.map +1 -1
- package/dist/crash-monitor.js +19 -0
- package/dist/diagnostics/__tests__/derivers.test.d.ts +2 -0
- package/dist/diagnostics/__tests__/derivers.test.d.ts.map +1 -0
- package/dist/diagnostics/__tests__/derivers.test.js +248 -0
- package/dist/diagnostics/__tests__/report.test.d.ts +2 -0
- package/dist/diagnostics/__tests__/report.test.d.ts.map +1 -0
- package/dist/diagnostics/__tests__/report.test.js +97 -0
- package/dist/diagnostics/derivers.d.ts +24 -0
- package/dist/diagnostics/derivers.d.ts.map +1 -0
- package/dist/diagnostics/derivers.js +146 -0
- package/dist/diagnostics/index.d.ts +3 -0
- package/dist/diagnostics/index.d.ts.map +1 -0
- package/dist/diagnostics/index.js +2 -0
- package/dist/diagnostics/report.d.ts +7 -0
- package/dist/diagnostics/report.d.ts.map +1 -0
- package/dist/diagnostics/report.js +84 -0
- package/dist/environment-reset.d.ts +51 -0
- package/dist/environment-reset.d.ts.map +1 -0
- package/dist/environment-reset.js +83 -0
- package/dist/execute-run.d.ts.map +1 -1
- package/dist/execute-run.js +121 -69
- package/dist/format-harness-error.d.ts +5 -0
- package/dist/format-harness-error.d.ts.map +1 -0
- package/dist/format-harness-error.js +43 -0
- package/dist/harness-session.d.ts +26 -1
- package/dist/harness-session.d.ts.map +1 -1
- package/dist/harness-session.js +260 -87
- package/dist/run.d.ts.map +1 -1
- package/dist/run.js +21 -4
- package/dist/tsconfig.lib.tsbuildinfo +1 -1
- package/package.json +8 -7
- package/src/__tests__/bridge.test.ts +8 -3
- package/src/__tests__/crash-monitor.test.ts +84 -0
- package/src/__tests__/environment-reset.test.ts +308 -0
- package/src/__tests__/execute-run.test.ts +327 -4
- package/src/__tests__/format-harness-error.test.ts +32 -0
- package/src/__tests__/harness-cache.test.ts +91 -0
- package/src/__tests__/harness-session.test.ts +211 -1
- package/src/__tests__/index.test.ts +3 -1
- package/src/__tests__/metro-port.test.ts +2 -0
- package/src/__tests__/run.test.ts +74 -0
- package/src/crash-monitor.ts +28 -0
- package/src/diagnostics/__tests__/derivers.test.ts +300 -0
- package/src/diagnostics/__tests__/report.test.ts +128 -0
- package/src/diagnostics/derivers.ts +170 -0
- package/src/diagnostics/index.ts +2 -0
- package/src/diagnostics/report.ts +124 -0
- package/src/environment-reset.ts +138 -0
- package/src/execute-run.ts +136 -77
- package/src/format-harness-error.ts +73 -0
- package/src/harness-session.ts +369 -131
- package/src/run.ts +23 -4
- package/tsconfig.json +3 -0
- package/tsconfig.lib.json +3 -0
|
@@ -1,10 +1,15 @@
|
|
|
1
|
-
import { describe, expect, it, vi } from 'vitest';
|
|
1
|
+
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
2
2
|
import type { AppConnection } from '@react-native-harness/bridge/server';
|
|
3
3
|
import {
|
|
4
|
+
getSignalExitCodeForRunState,
|
|
4
5
|
waitForBridgeDisconnectOrTimeout,
|
|
6
|
+
waitForNextConnected,
|
|
5
7
|
waitForStartupCrash,
|
|
8
|
+
withPlatformReadyTimeout,
|
|
9
|
+
type HarnessRunState,
|
|
6
10
|
} from '../harness-session.js';
|
|
7
11
|
import type { CrashMonitor } from '../crash-monitor.js';
|
|
12
|
+
import { PlatformReadyTimeoutError } from '../errors.js';
|
|
8
13
|
|
|
9
14
|
const createConnection = (): AppConnection => ({
|
|
10
15
|
device: {
|
|
@@ -14,6 +19,7 @@ const createConnection = (): AppConnection => ({
|
|
|
14
19
|
osVersion: '18.0',
|
|
15
20
|
},
|
|
16
21
|
runTests: vi.fn(),
|
|
22
|
+
resetEnvironment: vi.fn(),
|
|
17
23
|
});
|
|
18
24
|
|
|
19
25
|
const createBridge = (connection: AppConnection | null) => {
|
|
@@ -71,6 +77,65 @@ describe('waitForBridgeDisconnectOrTimeout', () => {
|
|
|
71
77
|
});
|
|
72
78
|
});
|
|
73
79
|
|
|
80
|
+
describe('waitForNextConnected', () => {
|
|
81
|
+
const createConnectableBridge = () => {
|
|
82
|
+
let listener: (() => void) | null = null;
|
|
83
|
+
const bridge: Pick<
|
|
84
|
+
import('@react-native-harness/bridge/server').HarnessBridge,
|
|
85
|
+
'on' | 'off'
|
|
86
|
+
> = {
|
|
87
|
+
on: vi.fn((event, l) => {
|
|
88
|
+
if (event === 'connected') listener = l as () => void;
|
|
89
|
+
}),
|
|
90
|
+
off: vi.fn((event, l) => {
|
|
91
|
+
if (event === 'connected' && listener === l) listener = null;
|
|
92
|
+
}),
|
|
93
|
+
};
|
|
94
|
+
return { bridge, emitConnected: () => listener?.() };
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
it('resolves when the bridge emits a connected event', async () => {
|
|
98
|
+
const { bridge, emitConnected } = createConnectableBridge();
|
|
99
|
+
const controller = new AbortController();
|
|
100
|
+
|
|
101
|
+
const waitPromise = waitForNextConnected({ bridge, signal: controller.signal });
|
|
102
|
+
emitConnected();
|
|
103
|
+
|
|
104
|
+
await expect(waitPromise).resolves.toBeUndefined();
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it('rejects when the signal aborts before a connected event', async () => {
|
|
108
|
+
const { bridge } = createConnectableBridge();
|
|
109
|
+
const controller = new AbortController();
|
|
110
|
+
|
|
111
|
+
const waitPromise = waitForNextConnected({ bridge, signal: controller.signal });
|
|
112
|
+
controller.abort(new DOMException('Aborted', 'AbortError'));
|
|
113
|
+
|
|
114
|
+
await expect(waitPromise).rejects.toThrow('Aborted');
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it('rejects immediately when the signal is already aborted', async () => {
|
|
118
|
+
const { bridge } = createConnectableBridge();
|
|
119
|
+
const controller = new AbortController();
|
|
120
|
+
controller.abort(new DOMException('Aborted', 'AbortError'));
|
|
121
|
+
|
|
122
|
+
await expect(
|
|
123
|
+
waitForNextConnected({ bridge, signal: controller.signal }),
|
|
124
|
+
).rejects.toThrow('Aborted');
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it('unsubscribes the connected listener once settled', async () => {
|
|
128
|
+
const { bridge, emitConnected } = createConnectableBridge();
|
|
129
|
+
const controller = new AbortController();
|
|
130
|
+
|
|
131
|
+
const waitPromise = waitForNextConnected({ bridge, signal: controller.signal });
|
|
132
|
+
emitConnected();
|
|
133
|
+
await waitPromise;
|
|
134
|
+
|
|
135
|
+
expect(bridge.off).toHaveBeenCalledOnce();
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
|
|
74
139
|
describe('waitForStartupCrash', () => {
|
|
75
140
|
it('does not install a startup crash watch when native crash detection is disabled', async () => {
|
|
76
141
|
const watch = vi.fn();
|
|
@@ -90,4 +155,149 @@ describe('waitForStartupCrash', () => {
|
|
|
90
155
|
await expect(waitPromise).rejects.toThrow('Aborted');
|
|
91
156
|
expect(watch).not.toHaveBeenCalled();
|
|
92
157
|
});
|
|
158
|
+
|
|
159
|
+
it('removes its abort listener on the session signal once the crash watch wins', async () => {
|
|
160
|
+
let rejectWatch!: (error: unknown) => void;
|
|
161
|
+
const cancel = vi.fn();
|
|
162
|
+
const watch = vi.fn(() => ({
|
|
163
|
+
promise: new Promise<never>((_, reject) => {
|
|
164
|
+
rejectWatch = reject;
|
|
165
|
+
}),
|
|
166
|
+
cancel,
|
|
167
|
+
}));
|
|
168
|
+
const crashMonitor = { watch } as unknown as CrashMonitor;
|
|
169
|
+
const controller = new AbortController();
|
|
170
|
+
const addSpy = vi.spyOn(controller.signal, 'addEventListener');
|
|
171
|
+
const removeSpy = vi.spyOn(controller.signal, 'removeEventListener');
|
|
172
|
+
|
|
173
|
+
const waitPromise = waitForStartupCrash({
|
|
174
|
+
crashMonitor,
|
|
175
|
+
detectNativeCrashes: true,
|
|
176
|
+
testFilePath: '/test.harness.ts',
|
|
177
|
+
signal: controller.signal,
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
// The crash watch wins the race, not the session signal.
|
|
181
|
+
rejectWatch(new Error('native crash detected'));
|
|
182
|
+
|
|
183
|
+
await expect(waitPromise).rejects.toThrow('native crash detected');
|
|
184
|
+
expect(cancel).toHaveBeenCalledTimes(1);
|
|
185
|
+
|
|
186
|
+
// Regression: forgetting to cancel the losing waitForAbort() left its
|
|
187
|
+
// listener attached to this session-lifetime signal for the rest of
|
|
188
|
+
// the session — one leaked listener per test file.
|
|
189
|
+
expect(addSpy).toHaveBeenCalledTimes(1);
|
|
190
|
+
expect(removeSpy).toHaveBeenCalledTimes(1);
|
|
191
|
+
});
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
describe('withPlatformReadyTimeout', () => {
|
|
195
|
+
afterEach(() => {
|
|
196
|
+
vi.useRealTimers();
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
it('passes the real session signal to work, unmodified by the timeout', async () => {
|
|
200
|
+
const controller = new AbortController();
|
|
201
|
+
const work = vi.fn(async (signal: AbortSignal) => {
|
|
202
|
+
expect(signal).toBe(controller.signal);
|
|
203
|
+
return 'ready';
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
await expect(
|
|
207
|
+
withPlatformReadyTimeout({
|
|
208
|
+
timeout: 1_000,
|
|
209
|
+
signal: controller.signal,
|
|
210
|
+
work,
|
|
211
|
+
}),
|
|
212
|
+
).resolves.toBe('ready');
|
|
213
|
+
expect(work).toHaveBeenCalledTimes(1);
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
it('throws PlatformReadyTimeoutError on timeout without aborting the session signal', async () => {
|
|
217
|
+
vi.useFakeTimers();
|
|
218
|
+
const controller = new AbortController();
|
|
219
|
+
// Never resolves within the test; simulates a hung platform init.
|
|
220
|
+
const work = () => new Promise<void>(() => undefined);
|
|
221
|
+
|
|
222
|
+
const resultPromise = withPlatformReadyTimeout({
|
|
223
|
+
timeout: 1_000,
|
|
224
|
+
signal: controller.signal,
|
|
225
|
+
work,
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
await vi.advanceTimersByTimeAsync(1_000);
|
|
229
|
+
|
|
230
|
+
await expect(resultPromise).rejects.toBeInstanceOf(PlatformReadyTimeoutError);
|
|
231
|
+
expect(controller.signal.aborted).toBe(false);
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
it('propagates a genuine abort from the session signal as-is', async () => {
|
|
235
|
+
const controller = new AbortController();
|
|
236
|
+
const abortReason = new DOMException('Aborted', 'AbortError');
|
|
237
|
+
const work = (signal: AbortSignal) =>
|
|
238
|
+
new Promise<never>((_, reject) => {
|
|
239
|
+
signal.addEventListener('abort', () => reject(signal.reason));
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
const resultPromise = withPlatformReadyTimeout({
|
|
243
|
+
timeout: 1_000,
|
|
244
|
+
signal: controller.signal,
|
|
245
|
+
work,
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
controller.abort(abortReason);
|
|
249
|
+
|
|
250
|
+
await expect(resultPromise).rejects.toBe(abortReason);
|
|
251
|
+
});
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
describe('getSignalExitCodeForRunState', () => {
|
|
255
|
+
const createRunState = (
|
|
256
|
+
overrides: Partial<HarnessRunState> = {}
|
|
257
|
+
): HarnessRunState => ({
|
|
258
|
+
completed: true,
|
|
259
|
+
coverageEnabled: false,
|
|
260
|
+
runId: 'run-1',
|
|
261
|
+
startTime: 0,
|
|
262
|
+
status: 'passed',
|
|
263
|
+
summary: {
|
|
264
|
+
failed: 0,
|
|
265
|
+
passed: 1,
|
|
266
|
+
skipped: 0,
|
|
267
|
+
todo: 0,
|
|
268
|
+
},
|
|
269
|
+
testFiles: ['test.harness.ts'],
|
|
270
|
+
watchMode: false,
|
|
271
|
+
...overrides,
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
it('preserves a successful exit after a completed passing run', () => {
|
|
275
|
+
expect(getSignalExitCodeForRunState(createRunState())).toBe(0);
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
it('fails when the run has not completed yet', () => {
|
|
279
|
+
expect(
|
|
280
|
+
getSignalExitCodeForRunState(createRunState({ completed: false }))
|
|
281
|
+
).toBe(1);
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
it('fails when the completed run has failures', () => {
|
|
285
|
+
expect(
|
|
286
|
+
getSignalExitCodeForRunState(
|
|
287
|
+
createRunState({
|
|
288
|
+
status: 'failed',
|
|
289
|
+
summary: {
|
|
290
|
+
failed: 1,
|
|
291
|
+
passed: 0,
|
|
292
|
+
skipped: 0,
|
|
293
|
+
todo: 0,
|
|
294
|
+
},
|
|
295
|
+
})
|
|
296
|
+
)
|
|
297
|
+
).toBe(1);
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
it('fails when no run state is available', () => {
|
|
301
|
+
expect(getSignalExitCodeForRunState(null)).toBe(1);
|
|
302
|
+
});
|
|
93
303
|
});
|
|
@@ -3,7 +3,7 @@ import type { Config, Test, TestEvents, TestRunnerOptions, TestWatcher } from 'j
|
|
|
3
3
|
import type { TestSuiteResult } from '@react-native-harness/bridge';
|
|
4
4
|
import type { HarnessSession } from '../harness-session.js';
|
|
5
5
|
import type { executeRun as ExecuteRun } from '../execute-run.js';
|
|
6
|
-
import { HarnessError } from '@react-native-harness/tools';
|
|
6
|
+
import { createDiagnostics, HarnessError } from '@react-native-harness/tools';
|
|
7
7
|
import JestHarness from '../index.js';
|
|
8
8
|
|
|
9
9
|
const resolveUndefined = async () => undefined;
|
|
@@ -15,6 +15,7 @@ const resolveUndefined = async () => undefined;
|
|
|
15
15
|
const mockSession: HarnessSession = {
|
|
16
16
|
config: { metroPort: 8081 } as HarnessSession['config'],
|
|
17
17
|
context: {} as HarnessSession['context'],
|
|
18
|
+
diagnostics: createDiagnostics({ enabled: false }),
|
|
18
19
|
onTestRunnerEvent: vi.fn(() => () => undefined),
|
|
19
20
|
ensureAppReady: vi.fn(resolveUndefined),
|
|
20
21
|
runTestFile: vi.fn(async (): Promise<TestSuiteResult> => ({
|
|
@@ -25,6 +26,7 @@ const mockSession: HarnessSession = {
|
|
|
25
26
|
duration: 0,
|
|
26
27
|
})),
|
|
27
28
|
restartApp: vi.fn(resolveUndefined),
|
|
29
|
+
resetEnvironment: vi.fn(resolveUndefined),
|
|
28
30
|
resetCrashState: vi.fn(),
|
|
29
31
|
flushClientLogs: vi.fn(() => []),
|
|
30
32
|
callHook: vi.fn(resolveUndefined),
|
|
@@ -22,11 +22,13 @@ const createConfig = (overrides: Partial<HarnessConfig> = {}): HarnessConfig =>
|
|
|
22
22
|
disableViewFlattening: false,
|
|
23
23
|
entryPoint: 'index.js',
|
|
24
24
|
forwardClientLogs: false,
|
|
25
|
+
diagnostics: false,
|
|
25
26
|
maxAppRestarts: 2,
|
|
26
27
|
metroPort: 8081,
|
|
27
28
|
platformReadyTimeout: 300_000,
|
|
28
29
|
resetEnvironmentBetweenTestFiles: true,
|
|
29
30
|
runners: [],
|
|
31
|
+
testTimeout: 5_000,
|
|
30
32
|
unstable__enableMetroCache: false,
|
|
31
33
|
unstable__skipAlreadyIncludedModules: false,
|
|
32
34
|
...overrides,
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
2
|
+
import type { Config as JestConfig } from 'jest-runner';
|
|
3
|
+
import type { TestSuiteResult } from '@react-native-harness/bridge';
|
|
4
|
+
import type { HarnessSession } from '../harness-session.js';
|
|
5
|
+
import { runHarnessTestFile } from '../run.js';
|
|
6
|
+
|
|
7
|
+
vi.mock('jest-message-util', () => ({
|
|
8
|
+
formatResultsErrors: vi.fn(() => ''),
|
|
9
|
+
}));
|
|
10
|
+
|
|
11
|
+
const createHarnessResult = (): TestSuiteResult => ({
|
|
12
|
+
name: 'root',
|
|
13
|
+
tests: [],
|
|
14
|
+
suites: [],
|
|
15
|
+
status: 'passed',
|
|
16
|
+
duration: 0,
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
const createSession = (testTimeout: number): HarnessSession =>
|
|
20
|
+
({
|
|
21
|
+
config: { testTimeout },
|
|
22
|
+
context: {
|
|
23
|
+
platform: {
|
|
24
|
+
runner: './runner.js',
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
runTestFile: vi.fn(async () => createHarnessResult()),
|
|
28
|
+
}) as unknown as HarnessSession;
|
|
29
|
+
|
|
30
|
+
const createGlobalConfig = (
|
|
31
|
+
overrides: Partial<JestConfig.GlobalConfig> = {},
|
|
32
|
+
): JestConfig.GlobalConfig =>
|
|
33
|
+
({
|
|
34
|
+
rootDir: '/project',
|
|
35
|
+
...overrides,
|
|
36
|
+
}) as JestConfig.GlobalConfig;
|
|
37
|
+
|
|
38
|
+
const createProjectConfig = (
|
|
39
|
+
overrides: Partial<JestConfig.ProjectConfig> = {},
|
|
40
|
+
): JestConfig.ProjectConfig => overrides as JestConfig.ProjectConfig;
|
|
41
|
+
|
|
42
|
+
describe('runHarnessTestFile', () => {
|
|
43
|
+
it('uses Harness config testTimeout when Jest config does not set one', async () => {
|
|
44
|
+
const session = createSession(15000);
|
|
45
|
+
|
|
46
|
+
await runHarnessTestFile({
|
|
47
|
+
testPath: '/project/example.harness.ts',
|
|
48
|
+
session,
|
|
49
|
+
globalConfig: createGlobalConfig(),
|
|
50
|
+
projectConfig: createProjectConfig(),
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
expect(session.runTestFile).toHaveBeenCalledWith(
|
|
54
|
+
'example.harness.ts',
|
|
55
|
+
expect.objectContaining({ testTimeout: 15000 }),
|
|
56
|
+
);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('keeps Harness config testTimeout above Jest config testTimeout', async () => {
|
|
60
|
+
const session = createSession(15000);
|
|
61
|
+
|
|
62
|
+
await runHarnessTestFile({
|
|
63
|
+
testPath: '/project/example.harness.ts',
|
|
64
|
+
session,
|
|
65
|
+
globalConfig: createGlobalConfig({ testTimeout: 20000 } as never),
|
|
66
|
+
projectConfig: createProjectConfig({ testTimeout: 30000 } as never),
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
expect(session.runTestFile).toHaveBeenCalledWith(
|
|
70
|
+
'example.harness.ts',
|
|
71
|
+
expect.objectContaining({ testTimeout: 15000 }),
|
|
72
|
+
);
|
|
73
|
+
});
|
|
74
|
+
});
|
package/src/crash-monitor.ts
CHANGED
|
@@ -40,6 +40,13 @@ export type CrashMonitor = {
|
|
|
40
40
|
reset: () => void;
|
|
41
41
|
setAppSession: (session: AppSession | null) => void;
|
|
42
42
|
handleBridgeDisconnect: () => void;
|
|
43
|
+
/**
|
|
44
|
+
* Opens a window during which handleBridgeDisconnect() is suppressed,
|
|
45
|
+
* for use around operations (like a runtime reset) that intentionally
|
|
46
|
+
* disconnect the bridge without the app process dying. Returns a closer
|
|
47
|
+
* that ends the window; safe to call multiple times or nest.
|
|
48
|
+
*/
|
|
49
|
+
expectDisconnect: () => () => void;
|
|
43
50
|
dispose: () => Promise<void>;
|
|
44
51
|
};
|
|
45
52
|
|
|
@@ -131,6 +138,7 @@ export const createCrashMonitor = ({
|
|
|
131
138
|
|
|
132
139
|
let currentTestFilePath = '';
|
|
133
140
|
let currentPhase: NativeCrashPhase = 'startup';
|
|
141
|
+
let expectedDisconnectDepth = 0;
|
|
134
142
|
const watchers = new Set<(err: HarnessRuntimeFailure) => void>();
|
|
135
143
|
|
|
136
144
|
const notifyFailure = (err: HarnessRuntimeFailure) => {
|
|
@@ -273,6 +281,19 @@ export const createCrashMonitor = ({
|
|
|
273
281
|
return { promise, cancel };
|
|
274
282
|
};
|
|
275
283
|
|
|
284
|
+
const expectDisconnect = (): (() => void) => {
|
|
285
|
+
expectedDisconnectDepth += 1;
|
|
286
|
+
let closed = false;
|
|
287
|
+
|
|
288
|
+
return () => {
|
|
289
|
+
if (closed) {
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
closed = true;
|
|
293
|
+
expectedDisconnectDepth = Math.max(0, expectedDisconnectDepth - 1);
|
|
294
|
+
};
|
|
295
|
+
};
|
|
296
|
+
|
|
276
297
|
return {
|
|
277
298
|
watch,
|
|
278
299
|
isAlive: () => alive,
|
|
@@ -289,15 +310,22 @@ export const createCrashMonitor = ({
|
|
|
289
310
|
watchers.clear();
|
|
290
311
|
isResolvingCrash = false;
|
|
291
312
|
currentTestFilePath = '';
|
|
313
|
+
expectedDisconnectDepth = 0;
|
|
292
314
|
clearPendingTimer();
|
|
293
315
|
},
|
|
294
316
|
setAppSession,
|
|
295
317
|
handleBridgeDisconnect: () => {
|
|
318
|
+
if (expectedDisconnectDepth > 0) {
|
|
319
|
+
crashLogger.debug('ignoring expected bridge disconnect');
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
296
322
|
startCrashResolution('bridge-disconnect');
|
|
297
323
|
},
|
|
324
|
+
expectDisconnect,
|
|
298
325
|
dispose: async () => {
|
|
299
326
|
disposed = true;
|
|
300
327
|
monitoring = false;
|
|
328
|
+
expectedDisconnectDepth = 0;
|
|
301
329
|
watchers.clear();
|
|
302
330
|
isResolvingCrash = false;
|
|
303
331
|
clearPendingTimer();
|