@react-native-harness/jest 1.4.0-rc.1 → 1.4.1
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/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/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/package.json +8 -7
- package/src/crash-monitor.ts +28 -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
- 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 -163
- 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 -166
- 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__/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 -393
- 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__/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 -51
- 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 -74
- 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 -148
- 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 -52
- 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__/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/tsconfig.lib.tsbuildinfo +0 -1
- package/src/__tests__/bridge.test.ts +0 -219
- package/src/__tests__/client-log-handler.test.ts +0 -364
- package/src/__tests__/crash-monitor.test.ts +0 -239
- package/src/__tests__/errors.test.ts +0 -157
- package/src/__tests__/execute-run.test.ts +0 -526
- package/src/__tests__/harness-session.test.ts +0 -93
- package/src/__tests__/hook-queue.test.ts +0 -77
- package/src/__tests__/index.test.ts +0 -219
- package/src/__tests__/metro-port.test.ts +0 -61
- package/src/__tests__/resource-lock.test.ts +0 -179
- package/src/__tests__/test-file-platform-filter.test.ts +0 -83
- package/tsconfig.tsbuildinfo +0 -1
package/dist/crash-monitor.d.ts
CHANGED
|
@@ -15,6 +15,13 @@ export type CrashMonitor = {
|
|
|
15
15
|
reset: () => void;
|
|
16
16
|
setAppSession: (session: AppSession | null) => void;
|
|
17
17
|
handleBridgeDisconnect: () => void;
|
|
18
|
+
/**
|
|
19
|
+
* Opens a window during which handleBridgeDisconnect() is suppressed,
|
|
20
|
+
* for use around operations (like a runtime reset) that intentionally
|
|
21
|
+
* disconnect the bridge without the app process dying. Returns a closer
|
|
22
|
+
* that ends the window; safe to call multiple times or nest.
|
|
23
|
+
*/
|
|
24
|
+
expectDisconnect: () => () => void;
|
|
18
25
|
dispose: () => Promise<void>;
|
|
19
26
|
};
|
|
20
27
|
export type CrashMonitorOptions = {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"crash-monitor.d.ts","sourceRoot":"","sources":["../src/crash-monitor.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,UAAU,EAKhB,MAAM,iCAAiC,CAAC;AACzC,OAAO,EAKL,KAAK,gBAAgB,EAEtB,MAAM,aAAa,CAAC;AAOrB,qBAAa,wBAAyB,SAAQ,KAAK;;CAKlD;AAED,MAAM,MAAM,UAAU,GAAG;IACvB,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC,KAAK,CAAC,CAAC;IACjC,MAAM,EAAE,MAAM,IAAI,CAAC;CACpB,CAAC;AAEF,MAAM,MAAM,YAAY,GAAG;IACzB,KAAK,EAAE,CAAC,YAAY,EAAE,MAAM,EAAE,KAAK,EAAE,gBAAgB,KAAK,UAAU,CAAC;IACrE,OAAO,EAAE,MAAM,OAAO,CAAC;IACvB,IAAI,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1B,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3B,KAAK,EAAE,MAAM,IAAI,CAAC;IAClB,aAAa,EAAE,CAAC,OAAO,EAAE,UAAU,GAAG,IAAI,KAAK,IAAI,CAAC;IACpD,sBAAsB,EAAE,MAAM,IAAI,CAAC;IACnC,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC9B,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG;IAChC,UAAU,CAAC,EAAE,UAAU,GAAG,IAAI,CAAC;CAChC,CAAC;AA0EF,eAAO,MAAM,kBAAkB,GAAI,qCAEhC,mBAAwB,KAAG,
|
|
1
|
+
{"version":3,"file":"crash-monitor.d.ts","sourceRoot":"","sources":["../src/crash-monitor.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,UAAU,EAKhB,MAAM,iCAAiC,CAAC;AACzC,OAAO,EAKL,KAAK,gBAAgB,EAEtB,MAAM,aAAa,CAAC;AAOrB,qBAAa,wBAAyB,SAAQ,KAAK;;CAKlD;AAED,MAAM,MAAM,UAAU,GAAG;IACvB,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC,KAAK,CAAC,CAAC;IACjC,MAAM,EAAE,MAAM,IAAI,CAAC;CACpB,CAAC;AAEF,MAAM,MAAM,YAAY,GAAG;IACzB,KAAK,EAAE,CAAC,YAAY,EAAE,MAAM,EAAE,KAAK,EAAE,gBAAgB,KAAK,UAAU,CAAC;IACrE,OAAO,EAAE,MAAM,OAAO,CAAC;IACvB,IAAI,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1B,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3B,KAAK,EAAE,MAAM,IAAI,CAAC;IAClB,aAAa,EAAE,CAAC,OAAO,EAAE,UAAU,GAAG,IAAI,KAAK,IAAI,CAAC;IACpD,sBAAsB,EAAE,MAAM,IAAI,CAAC;IACnC;;;;;OAKG;IACH,gBAAgB,EAAE,MAAM,MAAM,IAAI,CAAC;IACnC,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC9B,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG;IAChC,UAAU,CAAC,EAAE,UAAU,GAAG,IAAI,CAAC;CAChC,CAAC;AA0EF,eAAO,MAAM,kBAAkB,GAAI,qCAEhC,mBAAwB,KAAG,YA8M7B,CAAC"}
|
package/dist/crash-monitor.js
CHANGED
|
@@ -55,6 +55,7 @@ export const createCrashMonitor = ({ appSession: initialAppSession = null, } = {
|
|
|
55
55
|
let pendingTimer = null;
|
|
56
56
|
let currentTestFilePath = '';
|
|
57
57
|
let currentPhase = 'startup';
|
|
58
|
+
let expectedDisconnectDepth = 0;
|
|
58
59
|
const watchers = new Set();
|
|
59
60
|
const notifyFailure = (err) => {
|
|
60
61
|
const pending = [...watchers];
|
|
@@ -154,6 +155,17 @@ export const createCrashMonitor = ({ appSession: initialAppSession = null, } = {
|
|
|
154
155
|
};
|
|
155
156
|
return { promise, cancel };
|
|
156
157
|
};
|
|
158
|
+
const expectDisconnect = () => {
|
|
159
|
+
expectedDisconnectDepth += 1;
|
|
160
|
+
let closed = false;
|
|
161
|
+
return () => {
|
|
162
|
+
if (closed) {
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
closed = true;
|
|
166
|
+
expectedDisconnectDepth = Math.max(0, expectedDisconnectDepth - 1);
|
|
167
|
+
};
|
|
168
|
+
};
|
|
157
169
|
return {
|
|
158
170
|
watch,
|
|
159
171
|
isAlive: () => alive,
|
|
@@ -170,15 +182,22 @@ export const createCrashMonitor = ({ appSession: initialAppSession = null, } = {
|
|
|
170
182
|
watchers.clear();
|
|
171
183
|
isResolvingCrash = false;
|
|
172
184
|
currentTestFilePath = '';
|
|
185
|
+
expectedDisconnectDepth = 0;
|
|
173
186
|
clearPendingTimer();
|
|
174
187
|
},
|
|
175
188
|
setAppSession,
|
|
176
189
|
handleBridgeDisconnect: () => {
|
|
190
|
+
if (expectedDisconnectDepth > 0) {
|
|
191
|
+
crashLogger.debug('ignoring expected bridge disconnect');
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
177
194
|
startCrashResolution('bridge-disconnect');
|
|
178
195
|
},
|
|
196
|
+
expectDisconnect,
|
|
179
197
|
dispose: async () => {
|
|
180
198
|
disposed = true;
|
|
181
199
|
monitoring = false;
|
|
200
|
+
expectedDisconnectDepth = 0;
|
|
182
201
|
watchers.clear();
|
|
183
202
|
isResolvingCrash = false;
|
|
184
203
|
clearPendingTimer();
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { Diagnostics } from '@react-native-harness/tools';
|
|
2
|
+
import type { HarnessBridge } from '@react-native-harness/bridge/server';
|
|
3
|
+
import type { MetroInstance } from '@react-native-harness/bundler-metro';
|
|
4
|
+
/**
|
|
5
|
+
* Observes Metro's reporter event stream and derives:
|
|
6
|
+
* - `metro.bundle.build` spans, correlated by `buildID` across
|
|
7
|
+
* bundle_build_started -> bundle_build_done|bundle_build_failed.
|
|
8
|
+
* - `metro.bundle.request` marks from bundle_request_observed events.
|
|
9
|
+
*/
|
|
10
|
+
export declare const observeMetroEvents: (events: MetroInstance["events"], diagnostics: Diagnostics) => (() => void);
|
|
11
|
+
type MinimalBridge = Pick<HarnessBridge, 'on' | 'off'>;
|
|
12
|
+
/**
|
|
13
|
+
* Observes bridge connection lifecycle and device-reported bridge events and
|
|
14
|
+
* derives:
|
|
15
|
+
* - `bridge.connected` / `bridge.disconnected` marks.
|
|
16
|
+
* - `client.bundle.module`, `client.setup-file`, `client.collect`,
|
|
17
|
+
* `client.suite`, `client.test` completed spans, recorded via
|
|
18
|
+
* diagnostics.record() since these events arrive with a `duration` already
|
|
19
|
+
* measured on the (unsynced) device clock; we anchor them at host receipt
|
|
20
|
+
* time instead of trusting the device's absolute clock.
|
|
21
|
+
*/
|
|
22
|
+
export declare const observeBridgeEvents: (bridge: MinimalBridge, diagnostics: Diagnostics) => (() => void);
|
|
23
|
+
export {};
|
|
24
|
+
//# sourceMappingURL=derivers.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"derivers.d.ts","sourceRoot":"","sources":["../../src/diagnostics/derivers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAkB,MAAM,6BAA6B,CAAC;AAC/E,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qCAAqC,CAAC;AAEzE,OAAO,KAAK,EAAE,aAAa,EAAmB,MAAM,qCAAqC,CAAC;AAW1F;;;;;GAKG;AACH,eAAO,MAAM,kBAAkB,GAC7B,QAAQ,aAAa,CAAC,QAAQ,CAAC,EAC/B,aAAa,WAAW,KACvB,CAAC,MAAM,IAAI,CA+Cb,CAAC;AAEF,KAAK,aAAa,GAAG,IAAI,CAAC,aAAa,EAAE,IAAI,GAAG,KAAK,CAAC,CAAC;AAEvD;;;;;;;;;GASG;AACH,eAAO,MAAM,mBAAmB,GAC9B,QAAQ,aAAa,EACrB,aAAa,WAAW,KACvB,CAAC,MAAM,IAAI,CAkFb,CAAC"}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Span/mark derivers translate existing domain event streams (Metro build
|
|
3
|
+
* events, bridge/device events) into diagnostics entries. This is the only
|
|
4
|
+
* place in the jest package that maps event shapes to diagnostic labels -
|
|
5
|
+
* business logic elsewhere stays free of diagnostics calls.
|
|
6
|
+
*/
|
|
7
|
+
const noopDispose = () => undefined;
|
|
8
|
+
/**
|
|
9
|
+
* Observes Metro's reporter event stream and derives:
|
|
10
|
+
* - `metro.bundle.build` spans, correlated by `buildID` across
|
|
11
|
+
* bundle_build_started -> bundle_build_done|bundle_build_failed.
|
|
12
|
+
* - `metro.bundle.request` marks from bundle_request_observed events.
|
|
13
|
+
*/
|
|
14
|
+
export const observeMetroEvents = (events, diagnostics) => {
|
|
15
|
+
if (!diagnostics.enabled) {
|
|
16
|
+
return noopDispose;
|
|
17
|
+
}
|
|
18
|
+
const inflightBuilds = new Map();
|
|
19
|
+
const listener = (event) => {
|
|
20
|
+
switch (event.type) {
|
|
21
|
+
case 'bundle_build_started': {
|
|
22
|
+
const span = diagnostics.start('metro.bundle.build', {
|
|
23
|
+
buildID: event.buildID,
|
|
24
|
+
platform: event.bundleDetails.platform,
|
|
25
|
+
entryFile: event.bundleDetails.entryFile,
|
|
26
|
+
dev: event.bundleDetails.dev,
|
|
27
|
+
});
|
|
28
|
+
inflightBuilds.set(event.buildID, span);
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
case 'bundle_build_done': {
|
|
32
|
+
const span = inflightBuilds.get(event.buildID);
|
|
33
|
+
inflightBuilds.delete(event.buildID);
|
|
34
|
+
span?.end();
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
case 'bundle_build_failed': {
|
|
38
|
+
const span = inflightBuilds.get(event.buildID);
|
|
39
|
+
inflightBuilds.delete(event.buildID);
|
|
40
|
+
span?.fail(new Error(`Metro bundle build ${event.buildID} failed`));
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
case 'bundle_request_observed': {
|
|
44
|
+
diagnostics.mark('metro.bundle.request', {
|
|
45
|
+
requestKind: event.requestKind,
|
|
46
|
+
platform: event.platform,
|
|
47
|
+
});
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
default:
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
events.addListener(listener);
|
|
55
|
+
return () => {
|
|
56
|
+
events.removeListener(listener);
|
|
57
|
+
};
|
|
58
|
+
};
|
|
59
|
+
/**
|
|
60
|
+
* Observes bridge connection lifecycle and device-reported bridge events and
|
|
61
|
+
* derives:
|
|
62
|
+
* - `bridge.connected` / `bridge.disconnected` marks.
|
|
63
|
+
* - `client.bundle.module`, `client.setup-file`, `client.collect`,
|
|
64
|
+
* `client.suite`, `client.test` completed spans, recorded via
|
|
65
|
+
* diagnostics.record() since these events arrive with a `duration` already
|
|
66
|
+
* measured on the (unsynced) device clock; we anchor them at host receipt
|
|
67
|
+
* time instead of trusting the device's absolute clock.
|
|
68
|
+
*/
|
|
69
|
+
export const observeBridgeEvents = (bridge, diagnostics) => {
|
|
70
|
+
if (!diagnostics.enabled) {
|
|
71
|
+
return noopDispose;
|
|
72
|
+
}
|
|
73
|
+
const onConnected = () => {
|
|
74
|
+
diagnostics.mark('bridge.connected');
|
|
75
|
+
};
|
|
76
|
+
const onDisconnected = () => {
|
|
77
|
+
diagnostics.mark('bridge.disconnected');
|
|
78
|
+
};
|
|
79
|
+
const onEvent = (event) => {
|
|
80
|
+
switch (event.type) {
|
|
81
|
+
case 'module-bundling-finished':
|
|
82
|
+
diagnostics.record({
|
|
83
|
+
label: 'client.bundle.module',
|
|
84
|
+
duration: event.duration,
|
|
85
|
+
attrs: { file: event.file },
|
|
86
|
+
});
|
|
87
|
+
return;
|
|
88
|
+
case 'module-bundling-failed':
|
|
89
|
+
diagnostics.record({
|
|
90
|
+
label: 'client.bundle.module',
|
|
91
|
+
duration: event.duration,
|
|
92
|
+
attrs: { file: event.file },
|
|
93
|
+
status: 'error',
|
|
94
|
+
});
|
|
95
|
+
return;
|
|
96
|
+
case 'setup-file-bundling-finished':
|
|
97
|
+
diagnostics.record({
|
|
98
|
+
label: 'client.setup-file',
|
|
99
|
+
duration: event.duration,
|
|
100
|
+
attrs: { file: event.file, setupType: event.setupType },
|
|
101
|
+
});
|
|
102
|
+
return;
|
|
103
|
+
case 'setup-file-bundling-failed':
|
|
104
|
+
diagnostics.record({
|
|
105
|
+
label: 'client.setup-file',
|
|
106
|
+
duration: event.duration,
|
|
107
|
+
attrs: { file: event.file, setupType: event.setupType },
|
|
108
|
+
status: 'error',
|
|
109
|
+
});
|
|
110
|
+
return;
|
|
111
|
+
case 'collection-finished':
|
|
112
|
+
diagnostics.record({
|
|
113
|
+
label: 'client.collect',
|
|
114
|
+
duration: event.duration,
|
|
115
|
+
attrs: { file: event.file, totalTests: event.totalTests },
|
|
116
|
+
});
|
|
117
|
+
return;
|
|
118
|
+
case 'suite-finished':
|
|
119
|
+
diagnostics.record({
|
|
120
|
+
label: 'client.suite',
|
|
121
|
+
duration: event.duration,
|
|
122
|
+
attrs: { file: event.file, name: event.name, status: event.status },
|
|
123
|
+
status: event.status === 'failed' ? 'error' : 'ok',
|
|
124
|
+
});
|
|
125
|
+
return;
|
|
126
|
+
case 'test-finished':
|
|
127
|
+
diagnostics.record({
|
|
128
|
+
label: 'client.test',
|
|
129
|
+
duration: event.duration,
|
|
130
|
+
attrs: { file: event.file, name: event.name, status: event.status },
|
|
131
|
+
status: event.status === 'failed' ? 'error' : 'ok',
|
|
132
|
+
});
|
|
133
|
+
return;
|
|
134
|
+
default:
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
bridge.on('connected', onConnected);
|
|
139
|
+
bridge.on('disconnected', onDisconnected);
|
|
140
|
+
bridge.on('event', onEvent);
|
|
141
|
+
return () => {
|
|
142
|
+
bridge.off('connected', onConnected);
|
|
143
|
+
bridge.off('disconnected', onDisconnected);
|
|
144
|
+
bridge.off('event', onEvent);
|
|
145
|
+
};
|
|
146
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/diagnostics/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC;AACxE,OAAO,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { DiagnosticEntry } from '@react-native-harness/tools';
|
|
2
|
+
export declare const writeTraceFile: (entries: DiagnosticEntry[], options: {
|
|
3
|
+
projectRoot: string;
|
|
4
|
+
runId: string;
|
|
5
|
+
}) => Promise<string>;
|
|
6
|
+
export declare const printSummary: (entries: DiagnosticEntry[], log: (line: string) => void) => void;
|
|
7
|
+
//# sourceMappingURL=report.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"report.d.ts","sourceRoot":"","sources":["../../src/diagnostics/report.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;AAyCnE,eAAO,MAAM,cAAc,GACzB,SAAS,eAAe,EAAE,EAC1B,SAAS;IAAE,WAAW,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,KAC9C,OAAO,CAAC,MAAM,CAUhB,CAAC;AAkBF,eAAO,MAAM,YAAY,GACvB,SAAS,eAAe,EAAE,EAC1B,KAAK,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,KAC1B,IA8CF,CAAC"}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { getHarnessRootPath } from '@react-native-harness/tools';
|
|
4
|
+
const TRACE_PID = 1;
|
|
5
|
+
const TRACE_TID = 1;
|
|
6
|
+
const toTraceEvent = (entry) => {
|
|
7
|
+
if (entry.kind === 'mark') {
|
|
8
|
+
return {
|
|
9
|
+
name: entry.label,
|
|
10
|
+
ph: 'i',
|
|
11
|
+
s: 'g',
|
|
12
|
+
ts: entry.startTime * 1000,
|
|
13
|
+
pid: TRACE_PID,
|
|
14
|
+
tid: TRACE_TID,
|
|
15
|
+
args: entry.attrs,
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
return {
|
|
19
|
+
name: entry.label,
|
|
20
|
+
ph: 'X',
|
|
21
|
+
ts: entry.startTime * 1000,
|
|
22
|
+
dur: entry.duration * 1000,
|
|
23
|
+
pid: TRACE_PID,
|
|
24
|
+
tid: TRACE_TID,
|
|
25
|
+
args: entry.attrs,
|
|
26
|
+
};
|
|
27
|
+
};
|
|
28
|
+
export const writeTraceFile = async (entries, options) => {
|
|
29
|
+
const dir = path.join(getHarnessRootPath(options.projectRoot), 'diagnostics');
|
|
30
|
+
await fs.mkdir(dir, { recursive: true });
|
|
31
|
+
const filePath = path.join(dir, `${options.runId}.json`);
|
|
32
|
+
const trace = { traceEvents: entries.map(toTraceEvent) };
|
|
33
|
+
await fs.writeFile(filePath, JSON.stringify(trace), 'utf-8');
|
|
34
|
+
return filePath;
|
|
35
|
+
};
|
|
36
|
+
const formatDuration = (ms) => {
|
|
37
|
+
if (ms >= 1000) {
|
|
38
|
+
return `${(ms / 1000).toFixed(1)}s`;
|
|
39
|
+
}
|
|
40
|
+
return `${Math.round(ms)}ms`;
|
|
41
|
+
};
|
|
42
|
+
const getNamespace = (label) => label.split('.')[0] ?? label;
|
|
43
|
+
export const printSummary = (entries, log) => {
|
|
44
|
+
if (entries.length === 0) {
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
const statsByLabel = new Map();
|
|
48
|
+
for (const entry of entries) {
|
|
49
|
+
const existing = statsByLabel.get(entry.label);
|
|
50
|
+
if (existing) {
|
|
51
|
+
existing.count += 1;
|
|
52
|
+
existing.totalMs += entry.duration;
|
|
53
|
+
existing.maxMs = Math.max(existing.maxMs, entry.duration);
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
statsByLabel.set(entry.label, {
|
|
57
|
+
label: entry.label,
|
|
58
|
+
count: 1,
|
|
59
|
+
totalMs: entry.duration,
|
|
60
|
+
maxMs: entry.duration,
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
const byNamespace = new Map();
|
|
65
|
+
for (const stats of statsByLabel.values()) {
|
|
66
|
+
const namespace = getNamespace(stats.label);
|
|
67
|
+
const group = byNamespace.get(namespace);
|
|
68
|
+
if (group) {
|
|
69
|
+
group.push(stats);
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
byNamespace.set(namespace, [stats]);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
log('Diagnostics summary:');
|
|
76
|
+
const sortedNamespaces = [...byNamespace.entries()].sort(([a], [b]) => a.localeCompare(b));
|
|
77
|
+
for (const [namespace, group] of sortedNamespaces) {
|
|
78
|
+
log(` ${namespace}`);
|
|
79
|
+
group.sort((a, b) => b.totalMs - a.totalMs);
|
|
80
|
+
for (const stats of group) {
|
|
81
|
+
log(` ${stats.label}: count=${stats.count} total=${formatDuration(stats.totalMs)} max=${formatDuration(stats.maxMs)}`);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { Config as HarnessConfig } from '@react-native-harness/config';
|
|
2
|
+
export type ResetStrategyKind = 'process' | 'runtime';
|
|
3
|
+
export type EnvironmentResetStrategy = {
|
|
4
|
+
readonly kind: ResetStrategyKind;
|
|
5
|
+
reset(ctx: {
|
|
6
|
+
testFilePath: string;
|
|
7
|
+
}): Promise<void>;
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* Normalizes the `resetEnvironmentBetweenTestFiles` config value into a
|
|
11
|
+
* strategy kind, or `null` when no reset should happen between test files
|
|
12
|
+
* (i.e. the value is `false`).
|
|
13
|
+
*/
|
|
14
|
+
export declare const resolveResetStrategyKind: (value: HarnessConfig["resetEnvironmentBetweenTestFiles"]) => ResetStrategyKind | null;
|
|
15
|
+
export type ProcessResetStrategyDeps = {
|
|
16
|
+
restart: (testFilePath: string) => Promise<void>;
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* The 'process' strategy: kill and cold-restart the app process. This is
|
|
20
|
+
* the same implementation `restartApp` uses for crash/timeout recovery.
|
|
21
|
+
*/
|
|
22
|
+
export declare const createProcessResetStrategy: (deps: ProcessResetStrategyDeps) => EnvironmentResetStrategy;
|
|
23
|
+
export type RuntimeResetConnection = {
|
|
24
|
+
resetEnvironment: () => Promise<void>;
|
|
25
|
+
};
|
|
26
|
+
export type RuntimeResetStrategyDeps = {
|
|
27
|
+
getConnection: () => RuntimeResetConnection | null;
|
|
28
|
+
expectDisconnect: () => () => void;
|
|
29
|
+
waitForReconnect: (signal: AbortSignal) => Promise<void>;
|
|
30
|
+
isDisconnectError: (error: unknown) => boolean;
|
|
31
|
+
timeoutMs: number;
|
|
32
|
+
};
|
|
33
|
+
/**
|
|
34
|
+
* The 'runtime' strategy: reload the JS runtime in place (DevSettings.reload()
|
|
35
|
+
* on native, window.location.reload() on web) via the resetEnvironment RPC,
|
|
36
|
+
* and wait for the bridge to reconnect. The app process stays alive
|
|
37
|
+
* throughout, so the crash monitor's expected-disconnect window is used to
|
|
38
|
+
* avoid misclassifying the intentional disconnect as a runtime failure.
|
|
39
|
+
*/
|
|
40
|
+
export declare const createRuntimeResetStrategy: (deps: RuntimeResetStrategyDeps) => EnvironmentResetStrategy;
|
|
41
|
+
export type EscalationOptions = {
|
|
42
|
+
onEscalate?: (error: unknown) => void;
|
|
43
|
+
};
|
|
44
|
+
/**
|
|
45
|
+
* Wraps a primary strategy so that any failure (no active connection, RPC
|
|
46
|
+
* error, reconnect timeout, ...) falls back to running `fallback` instead of
|
|
47
|
+
* propagating the error. The composite reports the primary's `kind`, since
|
|
48
|
+
* that's the strategy that was requested.
|
|
49
|
+
*/
|
|
50
|
+
export declare const withEscalation: (primary: EnvironmentResetStrategy, fallback: EnvironmentResetStrategy, options?: EscalationOptions) => EnvironmentResetStrategy;
|
|
51
|
+
//# sourceMappingURL=environment-reset.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"environment-reset.d.ts","sourceRoot":"","sources":["../src/environment-reset.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,IAAI,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAI5E,MAAM,MAAM,iBAAiB,GAAG,SAAS,GAAG,SAAS,CAAC;AAEtD,MAAM,MAAM,wBAAwB,GAAG;IACrC,QAAQ,CAAC,IAAI,EAAE,iBAAiB,CAAC;IAEjC,KAAK,CAAC,GAAG,EAAE;QAAE,YAAY,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACrD,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,wBAAwB,GACnC,OAAO,aAAa,CAAC,kCAAkC,CAAC,KACvD,iBAAiB,GAAG,IAQtB,CAAC;AAEF,MAAM,MAAM,wBAAwB,GAAG;IAErC,OAAO,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CAClD,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,0BAA0B,GACrC,MAAM,wBAAwB,KAC7B,wBAGD,CAAC;AAEH,MAAM,MAAM,sBAAsB,GAAG;IACnC,gBAAgB,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CACvC,CAAC;AAEF,MAAM,MAAM,wBAAwB,GAAG;IAErC,aAAa,EAAE,MAAM,sBAAsB,GAAG,IAAI,CAAC;IAGnD,gBAAgB,EAAE,MAAM,MAAM,IAAI,CAAC;IAGnC,gBAAgB,EAAE,CAAC,MAAM,EAAE,WAAW,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAIzD,iBAAiB,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,OAAO,CAAC;IAE/C,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,0BAA0B,GACrC,MAAM,wBAAwB,KAC7B,wBAkCD,CAAC;AAEH,MAAM,MAAM,iBAAiB,GAAG;IAE9B,UAAU,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;CACvC,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,cAAc,GACzB,SAAS,wBAAwB,EACjC,UAAU,wBAAwB,EAClC,UAAS,iBAAsB,KAC9B,wBAUD,CAAC"}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { getTimeoutSignal } from '@react-native-harness/tools';
|
|
2
|
+
const ignorePromiseRejection = () => undefined;
|
|
3
|
+
/**
|
|
4
|
+
* Normalizes the `resetEnvironmentBetweenTestFiles` config value into a
|
|
5
|
+
* strategy kind, or `null` when no reset should happen between test files
|
|
6
|
+
* (i.e. the value is `false`).
|
|
7
|
+
*/
|
|
8
|
+
export const resolveResetStrategyKind = (value) => {
|
|
9
|
+
if (value === false) {
|
|
10
|
+
return null;
|
|
11
|
+
}
|
|
12
|
+
if (value === true) {
|
|
13
|
+
return 'process';
|
|
14
|
+
}
|
|
15
|
+
return value;
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* The 'process' strategy: kill and cold-restart the app process. This is
|
|
19
|
+
* the same implementation `restartApp` uses for crash/timeout recovery.
|
|
20
|
+
*/
|
|
21
|
+
export const createProcessResetStrategy = (deps) => ({
|
|
22
|
+
kind: 'process',
|
|
23
|
+
reset: ({ testFilePath }) => deps.restart(testFilePath),
|
|
24
|
+
});
|
|
25
|
+
/**
|
|
26
|
+
* The 'runtime' strategy: reload the JS runtime in place (DevSettings.reload()
|
|
27
|
+
* on native, window.location.reload() on web) via the resetEnvironment RPC,
|
|
28
|
+
* and wait for the bridge to reconnect. The app process stays alive
|
|
29
|
+
* throughout, so the crash monitor's expected-disconnect window is used to
|
|
30
|
+
* avoid misclassifying the intentional disconnect as a runtime failure.
|
|
31
|
+
*/
|
|
32
|
+
export const createRuntimeResetStrategy = (deps) => ({
|
|
33
|
+
kind: 'runtime',
|
|
34
|
+
reset: async () => {
|
|
35
|
+
const connection = deps.getConnection();
|
|
36
|
+
if (!connection) {
|
|
37
|
+
throw new Error('No active app connection to reset the runtime on');
|
|
38
|
+
}
|
|
39
|
+
const closeWindow = deps.expectDisconnect();
|
|
40
|
+
try {
|
|
41
|
+
// Start listening for the reconnect before firing the RPC, so a fast
|
|
42
|
+
// reload can't reconnect before we're watching for it.
|
|
43
|
+
const reconnected = deps.waitForReconnect(getTimeoutSignal(deps.timeoutMs));
|
|
44
|
+
// Observe the rejection even when the RPC throws first, so the timeout
|
|
45
|
+
// abort firing later can't become an unhandled rejection. (.catch()
|
|
46
|
+
// returns a new promise; the original is what gets awaited below.)
|
|
47
|
+
reconnected.catch(ignorePromiseRejection);
|
|
48
|
+
try {
|
|
49
|
+
await connection.resetEnvironment();
|
|
50
|
+
}
|
|
51
|
+
catch (error) {
|
|
52
|
+
// The reload can tear the runtime down before the RPC ack flushes.
|
|
53
|
+
// A disconnect-flavored rejection means the reload is in flight, so
|
|
54
|
+
// keep waiting for the reconnect; anything else is a real failure.
|
|
55
|
+
if (!deps.isDisconnectError(error)) {
|
|
56
|
+
throw error;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
await reconnected;
|
|
60
|
+
}
|
|
61
|
+
finally {
|
|
62
|
+
closeWindow();
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
});
|
|
66
|
+
/**
|
|
67
|
+
* Wraps a primary strategy so that any failure (no active connection, RPC
|
|
68
|
+
* error, reconnect timeout, ...) falls back to running `fallback` instead of
|
|
69
|
+
* propagating the error. The composite reports the primary's `kind`, since
|
|
70
|
+
* that's the strategy that was requested.
|
|
71
|
+
*/
|
|
72
|
+
export const withEscalation = (primary, fallback, options = {}) => ({
|
|
73
|
+
kind: primary.kind,
|
|
74
|
+
reset: async (ctx) => {
|
|
75
|
+
try {
|
|
76
|
+
await primary.reset(ctx);
|
|
77
|
+
}
|
|
78
|
+
catch (error) {
|
|
79
|
+
options.onEscalate?.(error);
|
|
80
|
+
await fallback.reset(ctx);
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"execute-run.d.ts","sourceRoot":"","sources":["../src/execute-run.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,IAAI,EACJ,WAAW,EACX,MAAM,EACN,UAAU,EACX,MAAM,aAAa,CAAC;AAGrB,OAAO,EAAE,KAAK,cAAc,EAAwB,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"execute-run.d.ts","sourceRoot":"","sources":["../src/execute-run.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,IAAI,EACJ,WAAW,EACX,MAAM,EACN,UAAU,EACX,MAAM,aAAa,CAAC;AAGrB,OAAO,EAAE,KAAK,cAAc,EAAwB,MAAM,sBAAsB,CAAC;AA6BjF,KAAK,aAAa,GAAG,CAAC,IAAI,SAAS,MAAM,UAAU,EACjD,SAAS,EAAE,IAAI,EACf,GAAG,SAAS,EAAE,UAAU,CAAC,IAAI,CAAC,KAC3B,OAAO,CAAC,IAAI,CAAC,CAAC;AA+FnB,eAAO,MAAM,UAAU,GACrB,SAAS,cAAc,EACvB,OAAO,KAAK,CAAC,IAAI,CAAC,EAClB,SAAS,WAAW,EACpB,WAAW,aAAa,EACxB,cAAc,MAAM,CAAC,YAAY,KAChC,OAAO,CAAC,IAAI,CAsOd,CAAC"}
|