@react-native-harness/jest 1.4.0 → 1.5.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/execute-run.d.ts.map +1 -1
- package/dist/execute-run.js +2 -1
- package/dist/harness-session.d.ts.map +1 -1
- package/dist/harness-session.js +74 -30
- package/dist/resource-lock.d.ts.map +1 -1
- package/dist/resource-lock.js +12 -2
- package/dist/toJestStatus.d.ts +4 -0
- package/dist/toJestStatus.d.ts.map +1 -0
- package/dist/toJestStatus.js +2 -0
- package/dist/toTestResult.d.ts +3 -2
- package/dist/toTestResult.d.ts.map +1 -1
- package/dist/toTestResult.js +2 -1
- package/package.json +8 -8
- package/src/execute-run.ts +2 -1
- package/src/harness-session.ts +86 -27
- package/src/resource-lock.ts +15 -2
- package/src/toJestStatus.ts +6 -0
- package/src/toTestResult.ts +5 -3
- package/dist/__tests__/bridge.test.d.ts +0 -2
- package/dist/__tests__/bridge.test.d.ts.map +0 -1
- package/dist/__tests__/bridge.test.js +0 -167
- package/dist/__tests__/client-log-handler.test.d.ts +0 -2
- package/dist/__tests__/client-log-handler.test.d.ts.map +0 -1
- package/dist/__tests__/client-log-handler.test.js +0 -306
- package/dist/__tests__/crash-monitor.test.d.ts +0 -2
- package/dist/__tests__/crash-monitor.test.d.ts.map +0 -1
- package/dist/__tests__/crash-monitor.test.js +0 -230
- package/dist/__tests__/crash-supervisor.test.d.ts +0 -2
- package/dist/__tests__/crash-supervisor.test.d.ts.map +0 -1
- package/dist/__tests__/crash-supervisor.test.js +0 -151
- package/dist/__tests__/environment-reset.test.d.ts +0 -2
- package/dist/__tests__/environment-reset.test.d.ts.map +0 -1
- package/dist/__tests__/environment-reset.test.js +0 -238
- package/dist/__tests__/errors.test.d.ts +0 -2
- package/dist/__tests__/errors.test.d.ts.map +0 -1
- package/dist/__tests__/errors.test.js +0 -98
- package/dist/__tests__/execute-run.test.d.ts +0 -2
- package/dist/__tests__/execute-run.test.d.ts.map +0 -1
- package/dist/__tests__/execute-run.test.js +0 -639
- package/dist/__tests__/fixtures/mock-platform-runner.d.ts +0 -7
- package/dist/__tests__/fixtures/mock-platform-runner.d.ts.map +0 -1
- package/dist/__tests__/fixtures/mock-platform-runner.js +0 -6
- package/dist/__tests__/format-harness-error.test.d.ts +0 -2
- package/dist/__tests__/format-harness-error.test.d.ts.map +0 -1
- package/dist/__tests__/format-harness-error.test.js +0 -27
- package/dist/__tests__/harness-cache.test.d.ts +0 -2
- package/dist/__tests__/harness-cache.test.d.ts.map +0 -1
- package/dist/__tests__/harness-cache.test.js +0 -72
- package/dist/__tests__/harness-session-signal.test.d.ts +0 -2
- package/dist/__tests__/harness-session-signal.test.d.ts.map +0 -1
- package/dist/__tests__/harness-session-signal.test.js +0 -298
- package/dist/__tests__/harness-session.test.d.ts +0 -2
- package/dist/__tests__/harness-session.test.d.ts.map +0 -1
- package/dist/__tests__/harness-session.test.js +0 -233
- package/dist/__tests__/harness.test.d.ts +0 -2
- package/dist/__tests__/harness.test.d.ts.map +0 -1
- package/dist/__tests__/harness.test.js +0 -636
- package/dist/__tests__/hook-queue.test.d.ts +0 -2
- package/dist/__tests__/hook-queue.test.d.ts.map +0 -1
- package/dist/__tests__/hook-queue.test.js +0 -56
- package/dist/__tests__/index.test.d.ts +0 -2
- package/dist/__tests__/index.test.d.ts.map +0 -1
- package/dist/__tests__/index.test.js +0 -150
- package/dist/__tests__/metro-port.test.d.ts +0 -2
- package/dist/__tests__/metro-port.test.d.ts.map +0 -1
- package/dist/__tests__/metro-port.test.js +0 -54
- package/dist/__tests__/resource-lock.test.d.ts +0 -2
- package/dist/__tests__/resource-lock.test.d.ts.map +0 -1
- package/dist/__tests__/resource-lock.test.js +0 -138
- package/dist/__tests__/run.test.d.ts +0 -2
- package/dist/__tests__/run.test.d.ts.map +0 -1
- package/dist/__tests__/run.test.js +0 -48
- package/dist/__tests__/test-file-platform-filter.test.d.ts +0 -2
- package/dist/__tests__/test-file-platform-filter.test.d.ts.map +0 -1
- package/dist/__tests__/test-file-platform-filter.test.js +0 -47
- package/dist/cli-args.test.d.ts +0 -2
- package/dist/cli-args.test.d.ts.map +0 -1
- package/dist/cli-args.test.js +0 -1
- package/dist/crash-supervisor.d.ts +0 -21
- package/dist/crash-supervisor.d.ts.map +0 -1
- package/dist/crash-supervisor.js +0 -210
- package/dist/diagnostics/__tests__/derivers.test.d.ts +0 -2
- package/dist/diagnostics/__tests__/derivers.test.d.ts.map +0 -1
- package/dist/diagnostics/__tests__/derivers.test.js +0 -248
- package/dist/diagnostics/__tests__/report.test.d.ts +0 -2
- package/dist/diagnostics/__tests__/report.test.d.ts.map +0 -1
- package/dist/diagnostics/__tests__/report.test.js +0 -97
- package/dist/event-mapper.d.ts +0 -16
- package/dist/event-mapper.d.ts.map +0 -1
- package/dist/event-mapper.js +0 -120
- package/dist/global-setup.d.ts +0 -3
- package/dist/global-setup.d.ts.map +0 -1
- package/dist/global-setup.js +0 -60
- package/dist/global-teardown.d.ts +0 -3
- package/dist/global-teardown.d.ts.map +0 -1
- package/dist/global-teardown.js +0 -11
- package/dist/harness-reporter.d.ts +0 -8
- package/dist/harness-reporter.d.ts.map +0 -1
- package/dist/harness-reporter.js +0 -12
- package/dist/harness.d.ts +0 -47
- package/dist/harness.d.ts.map +0 -1
- package/dist/harness.js +0 -619
- package/dist/jest-options.d.ts +0 -2
- package/dist/jest-options.d.ts.map +0 -1
- package/dist/jest-options.js +0 -1
- package/dist/setup.d.ts +0 -3
- package/dist/setup.d.ts.map +0 -1
- package/dist/setup.js +0 -80
- package/dist/teardown.d.ts +0 -3
- package/dist/teardown.d.ts.map +0 -1
- package/dist/teardown.js +0 -10
- package/dist/tsconfig.lib.tsbuildinfo +0 -1
- package/dist/types.d.ts +0 -17
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js +0 -1
- package/src/__tests__/bridge.test.ts +0 -224
- package/src/__tests__/client-log-handler.test.ts +0 -364
- package/src/__tests__/crash-monitor.test.ts +0 -323
- package/src/__tests__/environment-reset.test.ts +0 -308
- package/src/__tests__/errors.test.ts +0 -157
- package/src/__tests__/execute-run.test.ts +0 -849
- package/src/__tests__/format-harness-error.test.ts +0 -32
- package/src/__tests__/harness-cache.test.ts +0 -91
- package/src/__tests__/harness-session.test.ts +0 -303
- package/src/__tests__/hook-queue.test.ts +0 -77
- package/src/__tests__/index.test.ts +0 -221
- package/src/__tests__/metro-port.test.ts +0 -63
- package/src/__tests__/resource-lock.test.ts +0 -179
- package/src/__tests__/run.test.ts +0 -74
- package/src/__tests__/test-file-platform-filter.test.ts +0 -83
- package/src/diagnostics/__tests__/derivers.test.ts +0 -300
- package/src/diagnostics/__tests__/report.test.ts +0 -128
- package/tsconfig.tsbuildinfo +0 -1
package/dist/crash-supervisor.js
DELETED
|
@@ -1,210 +0,0 @@
|
|
|
1
|
-
import { NativeCrashError, } from './errors.js';
|
|
2
|
-
import { logger } from '@react-native-harness/tools';
|
|
3
|
-
const crashLogger = logger.child('crash');
|
|
4
|
-
const getCrashPhase = (state) => state === 'running' ? 'execution' : 'startup';
|
|
5
|
-
const mergeCrashDetails = (phase, initialDetails, enrichedDetails, fallbackSummary) => ({
|
|
6
|
-
phase,
|
|
7
|
-
source: enrichedDetails?.source ?? initialDetails?.source,
|
|
8
|
-
summary: enrichedDetails?.summary ?? initialDetails?.summary ?? fallbackSummary,
|
|
9
|
-
signal: enrichedDetails?.signal ?? initialDetails?.signal,
|
|
10
|
-
exceptionType: enrichedDetails?.exceptionType ?? initialDetails?.exceptionType,
|
|
11
|
-
processName: enrichedDetails?.processName ?? initialDetails?.processName,
|
|
12
|
-
pid: enrichedDetails?.pid ?? initialDetails?.pid,
|
|
13
|
-
stackTrace: enrichedDetails?.stackTrace ?? initialDetails?.stackTrace,
|
|
14
|
-
rawLines: enrichedDetails?.rawLines ?? initialDetails?.rawLines,
|
|
15
|
-
artifactType: enrichedDetails?.artifactType ?? initialDetails?.artifactType,
|
|
16
|
-
artifactPath: enrichedDetails?.artifactPath ?? initialDetails?.artifactPath,
|
|
17
|
-
});
|
|
18
|
-
export const createCrashSupervisor = ({ appMonitor, platformRunner, }) => {
|
|
19
|
-
let state = 'idle';
|
|
20
|
-
let activeTestFilePath = null;
|
|
21
|
-
let crashRejectors = new Set();
|
|
22
|
-
let disposed = false;
|
|
23
|
-
let monitoring = true;
|
|
24
|
-
let isResolvingCrash = false;
|
|
25
|
-
const getCrashDetailsProvider = () => {
|
|
26
|
-
if ('getCrashDetails' in appMonitor) {
|
|
27
|
-
return appMonitor;
|
|
28
|
-
}
|
|
29
|
-
if (platformRunner.getCrashDetails) {
|
|
30
|
-
return platformRunner;
|
|
31
|
-
}
|
|
32
|
-
return null;
|
|
33
|
-
};
|
|
34
|
-
const rejectCrashWaiters = (testFilePath, details) => {
|
|
35
|
-
const error = new NativeCrashError(testFilePath, details);
|
|
36
|
-
for (const reject of crashRejectors) {
|
|
37
|
-
reject(error);
|
|
38
|
-
}
|
|
39
|
-
crashRejectors = new Set();
|
|
40
|
-
};
|
|
41
|
-
const handleCrash = async (reason, details) => {
|
|
42
|
-
if (isResolvingCrash ||
|
|
43
|
-
(state !== 'launching' && state !== 'running' && state !== 'ready')) {
|
|
44
|
-
return;
|
|
45
|
-
}
|
|
46
|
-
if (!activeTestFilePath) {
|
|
47
|
-
crashLogger.debug('ignoring crash signal without active test: %s', reason);
|
|
48
|
-
return;
|
|
49
|
-
}
|
|
50
|
-
isResolvingCrash = true;
|
|
51
|
-
crashLogger.debug('native crash detected during %s (state=%s reason=%s)', activeTestFilePath, state, reason || '(none)');
|
|
52
|
-
for (const line of details?.rawLines ?? []) {
|
|
53
|
-
crashLogger.debug('%s', line);
|
|
54
|
-
}
|
|
55
|
-
const phase = getCrashPhase(state);
|
|
56
|
-
const testFilePath = activeTestFilePath;
|
|
57
|
-
state = 'idle';
|
|
58
|
-
try {
|
|
59
|
-
const enrichedDetails = await getCrashDetailsProvider()?.getCrashDetails?.({
|
|
60
|
-
processName: details?.processName,
|
|
61
|
-
pid: details?.pid,
|
|
62
|
-
occurredAt: Date.now(),
|
|
63
|
-
});
|
|
64
|
-
const mergedDetails = mergeCrashDetails(phase, details, enrichedDetails, reason);
|
|
65
|
-
crashLogger.debug('crash details: %o', {
|
|
66
|
-
phase: mergedDetails.phase,
|
|
67
|
-
source: mergedDetails.source,
|
|
68
|
-
summary: mergedDetails.summary,
|
|
69
|
-
signal: mergedDetails.signal,
|
|
70
|
-
exceptionType: mergedDetails.exceptionType,
|
|
71
|
-
processName: mergedDetails.processName,
|
|
72
|
-
pid: mergedDetails.pid,
|
|
73
|
-
});
|
|
74
|
-
rejectCrashWaiters(testFilePath, mergedDetails);
|
|
75
|
-
}
|
|
76
|
-
finally {
|
|
77
|
-
isResolvingCrash = false;
|
|
78
|
-
}
|
|
79
|
-
};
|
|
80
|
-
const confirmCrash = async (reason, details) => {
|
|
81
|
-
if (disposed || !monitoring || state === 'disposing') {
|
|
82
|
-
return;
|
|
83
|
-
}
|
|
84
|
-
try {
|
|
85
|
-
const isRunning = await platformRunner.isAppRunning();
|
|
86
|
-
if (!isRunning) {
|
|
87
|
-
void handleCrash(reason, details);
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
catch (error) {
|
|
91
|
-
crashLogger.debug('crash confirmation failed', error);
|
|
92
|
-
}
|
|
93
|
-
};
|
|
94
|
-
const appMonitorListener = (event) => {
|
|
95
|
-
if (disposed || !monitoring) {
|
|
96
|
-
return;
|
|
97
|
-
}
|
|
98
|
-
if (event.type === 'app_started') {
|
|
99
|
-
return;
|
|
100
|
-
}
|
|
101
|
-
if (event.type === 'app_exited') {
|
|
102
|
-
const details = {
|
|
103
|
-
source: event.crashDetails?.source ?? event.source,
|
|
104
|
-
summary: event.crashDetails?.summary,
|
|
105
|
-
signal: event.crashDetails?.signal,
|
|
106
|
-
exceptionType: event.crashDetails?.exceptionType,
|
|
107
|
-
processName: event.crashDetails?.processName,
|
|
108
|
-
pid: event.crashDetails?.pid ?? event.pid,
|
|
109
|
-
stackTrace: event.crashDetails?.stackTrace,
|
|
110
|
-
rawLines: event.crashDetails?.rawLines ??
|
|
111
|
-
(event.line ? [event.line] : undefined),
|
|
112
|
-
};
|
|
113
|
-
if (event.isConfirmed ?? event.source === 'polling') {
|
|
114
|
-
void handleCrash('', details);
|
|
115
|
-
}
|
|
116
|
-
else {
|
|
117
|
-
void confirmCrash('', details);
|
|
118
|
-
}
|
|
119
|
-
return;
|
|
120
|
-
}
|
|
121
|
-
if (event.type === 'possible_crash') {
|
|
122
|
-
const details = {
|
|
123
|
-
source: event.crashDetails?.source ?? event.source,
|
|
124
|
-
summary: event.crashDetails?.summary,
|
|
125
|
-
signal: event.crashDetails?.signal,
|
|
126
|
-
exceptionType: event.crashDetails?.exceptionType,
|
|
127
|
-
processName: event.crashDetails?.processName,
|
|
128
|
-
pid: event.crashDetails?.pid ?? event.pid,
|
|
129
|
-
stackTrace: event.crashDetails?.stackTrace,
|
|
130
|
-
rawLines: event.crashDetails?.rawLines ??
|
|
131
|
-
(event.line ? [event.line] : undefined),
|
|
132
|
-
};
|
|
133
|
-
if (event.isConfirmed) {
|
|
134
|
-
void handleCrash(`possible crash signal (${event.source ?? 'unknown'})`, details);
|
|
135
|
-
}
|
|
136
|
-
else {
|
|
137
|
-
void confirmCrash(`possible crash signal (${event.source ?? 'unknown'})`, details);
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
};
|
|
141
|
-
appMonitor.addListener(appMonitorListener);
|
|
142
|
-
const setActiveTestFile = (testFilePath) => {
|
|
143
|
-
activeTestFilePath = testFilePath;
|
|
144
|
-
};
|
|
145
|
-
const beginLaunch = (testFilePath) => {
|
|
146
|
-
activeTestFilePath = testFilePath;
|
|
147
|
-
state = 'launching';
|
|
148
|
-
};
|
|
149
|
-
const markReady = () => {
|
|
150
|
-
if (state !== 'disposing') {
|
|
151
|
-
state = 'ready';
|
|
152
|
-
}
|
|
153
|
-
};
|
|
154
|
-
const beginTestRun = (testFilePath) => {
|
|
155
|
-
activeTestFilePath = testFilePath;
|
|
156
|
-
state = 'running';
|
|
157
|
-
};
|
|
158
|
-
const stop = async () => {
|
|
159
|
-
monitoring = false;
|
|
160
|
-
await appMonitor.stop();
|
|
161
|
-
};
|
|
162
|
-
const start = async () => {
|
|
163
|
-
monitoring = true;
|
|
164
|
-
await appMonitor.start();
|
|
165
|
-
};
|
|
166
|
-
const waitForCrash = (testFilePath) => new Promise((_, reject) => {
|
|
167
|
-
if (disposed) {
|
|
168
|
-
reject(new NativeCrashError(testFilePath, {
|
|
169
|
-
phase: 'startup',
|
|
170
|
-
source: 'polling',
|
|
171
|
-
summary: 'Crash supervisor disposed while waiting for app startup.',
|
|
172
|
-
}));
|
|
173
|
-
return;
|
|
174
|
-
}
|
|
175
|
-
crashRejectors.add(reject);
|
|
176
|
-
});
|
|
177
|
-
const isReady = () => state === 'ready' || state === 'running';
|
|
178
|
-
const cancelCrashWaiters = () => {
|
|
179
|
-
crashRejectors = new Set();
|
|
180
|
-
};
|
|
181
|
-
const reset = () => {
|
|
182
|
-
cancelCrashWaiters();
|
|
183
|
-
isResolvingCrash = false;
|
|
184
|
-
if (state !== 'disposing') {
|
|
185
|
-
state = 'idle';
|
|
186
|
-
}
|
|
187
|
-
};
|
|
188
|
-
const dispose = async () => {
|
|
189
|
-
disposed = true;
|
|
190
|
-
monitoring = false;
|
|
191
|
-
state = 'disposing';
|
|
192
|
-
crashRejectors = new Set();
|
|
193
|
-
isResolvingCrash = false;
|
|
194
|
-
appMonitor.removeListener(appMonitorListener);
|
|
195
|
-
await appMonitor.dispose();
|
|
196
|
-
};
|
|
197
|
-
return {
|
|
198
|
-
setActiveTestFile,
|
|
199
|
-
beginLaunch,
|
|
200
|
-
markReady,
|
|
201
|
-
beginTestRun,
|
|
202
|
-
stop,
|
|
203
|
-
start,
|
|
204
|
-
waitForCrash,
|
|
205
|
-
isReady,
|
|
206
|
-
cancelCrashWaiters,
|
|
207
|
-
reset,
|
|
208
|
-
dispose,
|
|
209
|
-
};
|
|
210
|
-
};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"derivers.test.d.ts","sourceRoot":"","sources":["../../../src/diagnostics/__tests__/derivers.test.ts"],"names":[],"mappings":""}
|
|
@@ -1,248 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest';
|
|
2
|
-
import { createDiagnostics, getEmitter } from '@react-native-harness/tools';
|
|
3
|
-
import { observeBridgeEvents, observeMetroEvents } from '../derivers.js';
|
|
4
|
-
const createFakeBridge = () => {
|
|
5
|
-
const listeners = {
|
|
6
|
-
connected: new Set(),
|
|
7
|
-
disconnected: new Set(),
|
|
8
|
-
event: new Set(),
|
|
9
|
-
};
|
|
10
|
-
return {
|
|
11
|
-
on: (event, listener) => {
|
|
12
|
-
listeners[event].add(listener);
|
|
13
|
-
},
|
|
14
|
-
off: (event, listener) => {
|
|
15
|
-
listeners[event].delete(listener);
|
|
16
|
-
},
|
|
17
|
-
emitConnected: () => listeners.connected.forEach((l) => l({})),
|
|
18
|
-
emitDisconnected: () => listeners.disconnected.forEach((l) => l()),
|
|
19
|
-
emitEvent: (event) => listeners.event.forEach((l) => l(event)),
|
|
20
|
-
listenerCounts: () => ({
|
|
21
|
-
connected: listeners.connected.size,
|
|
22
|
-
disconnected: listeners.disconnected.size,
|
|
23
|
-
event: listeners.event.size,
|
|
24
|
-
}),
|
|
25
|
-
};
|
|
26
|
-
};
|
|
27
|
-
describe('observeMetroEvents', () => {
|
|
28
|
-
it('is a no-op and does not subscribe when diagnostics is disabled', () => {
|
|
29
|
-
const events = getEmitter();
|
|
30
|
-
const diagnostics = createDiagnostics({ enabled: false });
|
|
31
|
-
const dispose = observeMetroEvents(events, diagnostics);
|
|
32
|
-
events.emit({
|
|
33
|
-
type: 'bundle_build_started',
|
|
34
|
-
buildID: '1',
|
|
35
|
-
bundleDetails: {
|
|
36
|
-
bundleType: 'bundle',
|
|
37
|
-
dev: true,
|
|
38
|
-
entryFile: 'index.js',
|
|
39
|
-
minify: false,
|
|
40
|
-
platform: 'ios',
|
|
41
|
-
},
|
|
42
|
-
});
|
|
43
|
-
expect(diagnostics.flush()).toEqual([]);
|
|
44
|
-
dispose();
|
|
45
|
-
});
|
|
46
|
-
it('correlates bundle_build_started/done via buildID into a span', () => {
|
|
47
|
-
const events = getEmitter();
|
|
48
|
-
const diagnostics = createDiagnostics({ enabled: true });
|
|
49
|
-
observeMetroEvents(events, diagnostics);
|
|
50
|
-
events.emit({
|
|
51
|
-
type: 'bundle_build_started',
|
|
52
|
-
buildID: 'build-1',
|
|
53
|
-
bundleDetails: {
|
|
54
|
-
bundleType: 'bundle',
|
|
55
|
-
dev: true,
|
|
56
|
-
entryFile: 'index.js',
|
|
57
|
-
minify: false,
|
|
58
|
-
platform: 'ios',
|
|
59
|
-
},
|
|
60
|
-
});
|
|
61
|
-
events.emit({ type: 'bundle_build_done', buildID: 'build-1' });
|
|
62
|
-
const entries = diagnostics.flush();
|
|
63
|
-
expect(entries).toHaveLength(1);
|
|
64
|
-
expect(entries[0]?.label).toBe('metro.bundle.build');
|
|
65
|
-
expect(entries[0]?.status).toBe('ok');
|
|
66
|
-
expect(entries[0]?.attrs).toMatchObject({
|
|
67
|
-
buildID: 'build-1',
|
|
68
|
-
platform: 'ios',
|
|
69
|
-
entryFile: 'index.js',
|
|
70
|
-
});
|
|
71
|
-
});
|
|
72
|
-
it('records a failed span for bundle_build_failed', () => {
|
|
73
|
-
const events = getEmitter();
|
|
74
|
-
const diagnostics = createDiagnostics({ enabled: true });
|
|
75
|
-
observeMetroEvents(events, diagnostics);
|
|
76
|
-
events.emit({
|
|
77
|
-
type: 'bundle_build_started',
|
|
78
|
-
buildID: 'build-2',
|
|
79
|
-
bundleDetails: {
|
|
80
|
-
bundleType: 'bundle',
|
|
81
|
-
dev: true,
|
|
82
|
-
entryFile: 'index.js',
|
|
83
|
-
minify: false,
|
|
84
|
-
platform: 'android',
|
|
85
|
-
},
|
|
86
|
-
});
|
|
87
|
-
events.emit({ type: 'bundle_build_failed', buildID: 'build-2' });
|
|
88
|
-
const entries = diagnostics.flush();
|
|
89
|
-
expect(entries).toHaveLength(1);
|
|
90
|
-
expect(entries[0]?.status).toBe('error');
|
|
91
|
-
});
|
|
92
|
-
it('records a bundle_request_observed mark', () => {
|
|
93
|
-
const events = getEmitter();
|
|
94
|
-
const diagnostics = createDiagnostics({ enabled: true });
|
|
95
|
-
observeMetroEvents(events, diagnostics);
|
|
96
|
-
events.emit({
|
|
97
|
-
type: 'bundle_request_observed',
|
|
98
|
-
platform: 'ios',
|
|
99
|
-
requestKind: 'app',
|
|
100
|
-
timestamp: new Date().toISOString(),
|
|
101
|
-
url: '/index.bundle',
|
|
102
|
-
});
|
|
103
|
-
const entries = diagnostics.flush();
|
|
104
|
-
expect(entries).toHaveLength(1);
|
|
105
|
-
expect(entries[0]?.label).toBe('metro.bundle.request');
|
|
106
|
-
expect(entries[0]?.kind).toBe('mark');
|
|
107
|
-
expect(entries[0]?.attrs).toEqual({ requestKind: 'app', platform: 'ios' });
|
|
108
|
-
});
|
|
109
|
-
it('dispose() unsubscribes the listener', () => {
|
|
110
|
-
const events = getEmitter();
|
|
111
|
-
const diagnostics = createDiagnostics({ enabled: true });
|
|
112
|
-
const dispose = observeMetroEvents(events, diagnostics);
|
|
113
|
-
dispose();
|
|
114
|
-
events.emit({
|
|
115
|
-
type: 'bundle_request_observed',
|
|
116
|
-
platform: 'ios',
|
|
117
|
-
requestKind: 'app',
|
|
118
|
-
timestamp: new Date().toISOString(),
|
|
119
|
-
url: '/index.bundle',
|
|
120
|
-
});
|
|
121
|
-
expect(diagnostics.flush()).toEqual([]);
|
|
122
|
-
});
|
|
123
|
-
});
|
|
124
|
-
describe('observeBridgeEvents', () => {
|
|
125
|
-
it('is a no-op and does not subscribe when diagnostics is disabled', () => {
|
|
126
|
-
const bridge = createFakeBridge();
|
|
127
|
-
const diagnostics = createDiagnostics({ enabled: false });
|
|
128
|
-
observeBridgeEvents(bridge, diagnostics);
|
|
129
|
-
expect(bridge.listenerCounts()).toEqual({ connected: 0, disconnected: 0, event: 0 });
|
|
130
|
-
});
|
|
131
|
-
it('marks bridge.connected and bridge.disconnected', () => {
|
|
132
|
-
const bridge = createFakeBridge();
|
|
133
|
-
const diagnostics = createDiagnostics({ enabled: true });
|
|
134
|
-
observeBridgeEvents(bridge, diagnostics);
|
|
135
|
-
bridge.emitConnected();
|
|
136
|
-
bridge.emitDisconnected();
|
|
137
|
-
const entries = diagnostics.flush();
|
|
138
|
-
expect(entries.map((e) => e.label)).toEqual(['bridge.connected', 'bridge.disconnected']);
|
|
139
|
-
expect(entries.every((e) => e.kind === 'mark')).toBe(true);
|
|
140
|
-
});
|
|
141
|
-
it('records client.bundle.module for module bundling events', () => {
|
|
142
|
-
const bridge = createFakeBridge();
|
|
143
|
-
const diagnostics = createDiagnostics({ enabled: true });
|
|
144
|
-
observeBridgeEvents(bridge, diagnostics);
|
|
145
|
-
bridge.emitEvent({ type: 'module-bundling-finished', file: 'a.ts', duration: 12 });
|
|
146
|
-
bridge.emitEvent({
|
|
147
|
-
type: 'module-bundling-failed',
|
|
148
|
-
file: 'b.ts',
|
|
149
|
-
duration: 5,
|
|
150
|
-
error: 'boom',
|
|
151
|
-
});
|
|
152
|
-
const entries = diagnostics.flush();
|
|
153
|
-
expect(entries).toHaveLength(2);
|
|
154
|
-
expect(entries[0]).toMatchObject({
|
|
155
|
-
label: 'client.bundle.module',
|
|
156
|
-
duration: 12,
|
|
157
|
-
status: 'ok',
|
|
158
|
-
attrs: { file: 'a.ts' },
|
|
159
|
-
});
|
|
160
|
-
expect(entries[1]).toMatchObject({
|
|
161
|
-
label: 'client.bundle.module',
|
|
162
|
-
duration: 5,
|
|
163
|
-
status: 'error',
|
|
164
|
-
attrs: { file: 'b.ts' },
|
|
165
|
-
});
|
|
166
|
-
});
|
|
167
|
-
it('records client.setup-file for setup file bundling events', () => {
|
|
168
|
-
const bridge = createFakeBridge();
|
|
169
|
-
const diagnostics = createDiagnostics({ enabled: true });
|
|
170
|
-
observeBridgeEvents(bridge, diagnostics);
|
|
171
|
-
bridge.emitEvent({
|
|
172
|
-
type: 'setup-file-bundling-finished',
|
|
173
|
-
file: 'setup.ts',
|
|
174
|
-
setupType: 'setupFiles',
|
|
175
|
-
duration: 20,
|
|
176
|
-
});
|
|
177
|
-
const entries = diagnostics.flush();
|
|
178
|
-
expect(entries).toHaveLength(1);
|
|
179
|
-
expect(entries[0]).toMatchObject({
|
|
180
|
-
label: 'client.setup-file',
|
|
181
|
-
duration: 20,
|
|
182
|
-
attrs: { file: 'setup.ts', setupType: 'setupFiles' },
|
|
183
|
-
});
|
|
184
|
-
});
|
|
185
|
-
it('records client.collect for collection-finished', () => {
|
|
186
|
-
const bridge = createFakeBridge();
|
|
187
|
-
const diagnostics = createDiagnostics({ enabled: true });
|
|
188
|
-
observeBridgeEvents(bridge, diagnostics);
|
|
189
|
-
bridge.emitEvent({
|
|
190
|
-
type: 'collection-finished',
|
|
191
|
-
file: 'a.test.ts',
|
|
192
|
-
duration: 8,
|
|
193
|
-
totalTests: 3,
|
|
194
|
-
});
|
|
195
|
-
const entries = diagnostics.flush();
|
|
196
|
-
expect(entries[0]).toMatchObject({
|
|
197
|
-
label: 'client.collect',
|
|
198
|
-
duration: 8,
|
|
199
|
-
attrs: { file: 'a.test.ts', totalTests: 3 },
|
|
200
|
-
});
|
|
201
|
-
});
|
|
202
|
-
it('records client.suite and client.test with status', () => {
|
|
203
|
-
const bridge = createFakeBridge();
|
|
204
|
-
const diagnostics = createDiagnostics({ enabled: true });
|
|
205
|
-
observeBridgeEvents(bridge, diagnostics);
|
|
206
|
-
bridge.emitEvent({
|
|
207
|
-
type: 'suite-finished',
|
|
208
|
-
file: 'a.test.ts',
|
|
209
|
-
name: 'my suite',
|
|
210
|
-
duration: 100,
|
|
211
|
-
status: 'failed',
|
|
212
|
-
});
|
|
213
|
-
bridge.emitEvent({
|
|
214
|
-
type: 'test-finished',
|
|
215
|
-
file: 'a.test.ts',
|
|
216
|
-
suite: 'my suite',
|
|
217
|
-
name: 'my test',
|
|
218
|
-
ancestorTitles: [],
|
|
219
|
-
fullName: 'my suite my test',
|
|
220
|
-
startedAt: Date.now(),
|
|
221
|
-
duration: 15,
|
|
222
|
-
status: 'passed',
|
|
223
|
-
});
|
|
224
|
-
const entries = diagnostics.flush();
|
|
225
|
-
expect(entries[0]).toMatchObject({ label: 'client.suite', status: 'error' });
|
|
226
|
-
expect(entries[1]).toMatchObject({ label: 'client.test', status: 'ok' });
|
|
227
|
-
});
|
|
228
|
-
it('anchors recorded entries near "now - duration" using the host clock', () => {
|
|
229
|
-
const bridge = createFakeBridge();
|
|
230
|
-
const diagnostics = createDiagnostics({ enabled: true });
|
|
231
|
-
observeBridgeEvents(bridge, diagnostics);
|
|
232
|
-
const before = Date.now();
|
|
233
|
-
bridge.emitEvent({ type: 'module-bundling-finished', file: 'a.ts', duration: 50 });
|
|
234
|
-
const after = Date.now();
|
|
235
|
-
const [entry] = diagnostics.flush();
|
|
236
|
-
expect(entry?.startTime).toBeGreaterThanOrEqual(before - 50 - 5);
|
|
237
|
-
expect(entry?.startTime).toBeLessThanOrEqual(after - 50 + 5);
|
|
238
|
-
});
|
|
239
|
-
it('dispose() unsubscribes all listeners', () => {
|
|
240
|
-
const bridge = createFakeBridge();
|
|
241
|
-
const diagnostics = createDiagnostics({ enabled: true });
|
|
242
|
-
const dispose = observeBridgeEvents(bridge, diagnostics);
|
|
243
|
-
dispose();
|
|
244
|
-
expect(bridge.listenerCounts()).toEqual({ connected: 0, disconnected: 0, event: 0 });
|
|
245
|
-
bridge.emitConnected();
|
|
246
|
-
expect(diagnostics.flush()).toEqual([]);
|
|
247
|
-
});
|
|
248
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"report.test.d.ts","sourceRoot":"","sources":["../../../src/diagnostics/__tests__/report.test.ts"],"names":[],"mappings":""}
|
|
@@ -1,97 +0,0 @@
|
|
|
1
|
-
import fs from 'node:fs/promises';
|
|
2
|
-
import os from 'node:os';
|
|
3
|
-
import path from 'node:path';
|
|
4
|
-
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
|
5
|
-
import { printSummary, writeTraceFile } from '../report.js';
|
|
6
|
-
const makeSpanEntry = (overrides = {}) => ({
|
|
7
|
-
label: 'metro.bundle.build',
|
|
8
|
-
startTime: 1_000,
|
|
9
|
-
duration: 250,
|
|
10
|
-
status: 'ok',
|
|
11
|
-
kind: 'span',
|
|
12
|
-
...overrides,
|
|
13
|
-
});
|
|
14
|
-
describe('writeTraceFile', () => {
|
|
15
|
-
let projectRoot;
|
|
16
|
-
beforeEach(async () => {
|
|
17
|
-
projectRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'react-native-harness-report-test-'));
|
|
18
|
-
});
|
|
19
|
-
afterEach(async () => {
|
|
20
|
-
await fs.rm(projectRoot, { recursive: true, force: true });
|
|
21
|
-
});
|
|
22
|
-
it('writes a Chrome Trace Event JSON file under .harness/diagnostics', async () => {
|
|
23
|
-
const entries = [
|
|
24
|
-
makeSpanEntry({ attrs: { file: 'a.test.ts' } }),
|
|
25
|
-
makeSpanEntry({
|
|
26
|
-
label: 'bridge.connected',
|
|
27
|
-
duration: 0,
|
|
28
|
-
kind: 'mark',
|
|
29
|
-
}),
|
|
30
|
-
];
|
|
31
|
-
const filePath = await writeTraceFile(entries, {
|
|
32
|
-
projectRoot,
|
|
33
|
-
runId: 'run-123',
|
|
34
|
-
});
|
|
35
|
-
expect(filePath).toBe(path.join(projectRoot, '.harness', 'diagnostics', 'run-123.json'));
|
|
36
|
-
const raw = await fs.readFile(filePath, 'utf-8');
|
|
37
|
-
const parsed = JSON.parse(raw);
|
|
38
|
-
expect(parsed.traceEvents).toHaveLength(2);
|
|
39
|
-
expect(parsed.traceEvents[0]).toMatchObject({
|
|
40
|
-
name: 'metro.bundle.build',
|
|
41
|
-
ph: 'X',
|
|
42
|
-
ts: 1_000 * 1000,
|
|
43
|
-
dur: 250 * 1000,
|
|
44
|
-
pid: 1,
|
|
45
|
-
tid: 1,
|
|
46
|
-
args: { file: 'a.test.ts' },
|
|
47
|
-
});
|
|
48
|
-
expect(parsed.traceEvents[1]).toMatchObject({
|
|
49
|
-
name: 'bridge.connected',
|
|
50
|
-
ph: 'i',
|
|
51
|
-
s: 'g',
|
|
52
|
-
});
|
|
53
|
-
});
|
|
54
|
-
it('creates the diagnostics directory recursively when missing', async () => {
|
|
55
|
-
const filePath = await writeTraceFile([makeSpanEntry()], {
|
|
56
|
-
projectRoot,
|
|
57
|
-
runId: 'run-456',
|
|
58
|
-
});
|
|
59
|
-
const stat = await fs.stat(filePath);
|
|
60
|
-
expect(stat.isFile()).toBe(true);
|
|
61
|
-
});
|
|
62
|
-
});
|
|
63
|
-
describe('printSummary', () => {
|
|
64
|
-
it('does nothing when there are no entries', () => {
|
|
65
|
-
const lines = [];
|
|
66
|
-
printSummary([], (line) => lines.push(line));
|
|
67
|
-
expect(lines).toEqual([]);
|
|
68
|
-
});
|
|
69
|
-
it('groups by top-level namespace and sorts labels by total duration desc', () => {
|
|
70
|
-
const lines = [];
|
|
71
|
-
printSummary([
|
|
72
|
-
makeSpanEntry({ label: 'metro.bundle.build', duration: 100 }),
|
|
73
|
-
makeSpanEntry({ label: 'metro.bundle.build', duration: 200 }),
|
|
74
|
-
makeSpanEntry({ label: 'metro.init', duration: 50 }),
|
|
75
|
-
makeSpanEntry({ label: 'client.test', duration: 5000 }),
|
|
76
|
-
], (line) => lines.push(line));
|
|
77
|
-
const metroHeaderIndex = lines.indexOf(' metro');
|
|
78
|
-
const clientHeaderIndex = lines.indexOf(' client');
|
|
79
|
-
expect(metroHeaderIndex).toBeGreaterThanOrEqual(0);
|
|
80
|
-
expect(clientHeaderIndex).toBeGreaterThanOrEqual(0);
|
|
81
|
-
const bundleLine = lines.find((l) => l.includes('metro.bundle.build'));
|
|
82
|
-
expect(bundleLine).toContain('count=2');
|
|
83
|
-
expect(bundleLine).toContain('total=300ms');
|
|
84
|
-
expect(bundleLine).toContain('max=200ms');
|
|
85
|
-
const clientLine = lines.find((l) => l.includes('client.test'));
|
|
86
|
-
expect(clientLine).toContain('total=5.0s');
|
|
87
|
-
});
|
|
88
|
-
it('formats sub-second durations in ms and second+ durations in s', () => {
|
|
89
|
-
const lines = [];
|
|
90
|
-
printSummary([
|
|
91
|
-
makeSpanEntry({ label: 'a.b', duration: 999 }),
|
|
92
|
-
makeSpanEntry({ label: 'a.c', duration: 1500 }),
|
|
93
|
-
], (line) => lines.push(line));
|
|
94
|
-
expect(lines.find((l) => l.includes('a.b'))).toContain('999ms');
|
|
95
|
-
expect(lines.find((l) => l.includes('a.c'))).toContain('1.5s');
|
|
96
|
-
});
|
|
97
|
-
});
|
package/dist/event-mapper.d.ts
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import type { TestEvents } from '@jest/test-result';
|
|
2
|
-
import type { Test } from 'jest-runner';
|
|
3
|
-
import type { BridgeEvents } from '@react-native-harness/bridge';
|
|
4
|
-
import type { TestCaseResult } from '@jest/test-result';
|
|
5
|
-
export type EventMapperState = {
|
|
6
|
-
currentTest?: Test;
|
|
7
|
-
suiteHierarchy: string[];
|
|
8
|
-
testResults: Map<string, TestCaseResult>;
|
|
9
|
-
fileStartTime: number;
|
|
10
|
-
};
|
|
11
|
-
export type HarnessEventMapper = {
|
|
12
|
-
handleBridgeEvent: (event: BridgeEvents, test: Test) => void;
|
|
13
|
-
reset: () => void;
|
|
14
|
-
};
|
|
15
|
-
export declare const getHarnessEventMapper: (emit: <T extends keyof TestEvents>(eventName: T, eventData: TestEvents[T]) => void) => HarnessEventMapper;
|
|
16
|
-
//# sourceMappingURL=event-mapper.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"event-mapper.d.ts","sourceRoot":"","sources":["../src/event-mapper.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,KAAK,EASV,YAAY,EACb,MAAM,8BAA8B,CAAC;AACtC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAGxD,MAAM,MAAM,gBAAgB,GAAG;IAC7B,WAAW,CAAC,EAAE,IAAI,CAAC;IACnB,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,WAAW,EAAE,GAAG,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;IACzC,aAAa,EAAE,MAAM,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG;IAC/B,iBAAiB,EAAE,CAAC,KAAK,EAAE,YAAY,EAAE,IAAI,EAAE,IAAI,KAAK,IAAI,CAAC;IAC7D,KAAK,EAAE,MAAM,IAAI,CAAC;CACnB,CAAC;AAEF,eAAO,MAAM,qBAAqB,GAChC,MAAM,CAAC,CAAC,SAAS,MAAM,UAAU,EAC/B,SAAS,EAAE,CAAC,EACZ,SAAS,EAAE,UAAU,CAAC,CAAC,CAAC,KACrB,IAAI,KACR,kBA8JF,CAAC"}
|