@react-native-harness/platform-android 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/adb.d.ts +2 -4
- package/dist/adb.d.ts.map +1 -1
- package/dist/adb.js +2 -3
- package/dist/app-session.d.ts +3 -12
- package/dist/app-session.d.ts.map +1 -1
- package/dist/app-session.js +23 -33
- package/dist/instance.d.ts.map +1 -1
- package/dist/instance.js +2 -4
- package/package.json +4 -4
- package/src/adb.ts +5 -6
- package/src/app-session.ts +14 -31
- package/src/instance.ts +2 -4
- package/dist/__tests__/adb-id.test.d.ts +0 -2
- package/dist/__tests__/adb-id.test.d.ts.map +0 -1
- package/dist/__tests__/adb-id.test.js +0 -41
- package/dist/__tests__/adb-wait.test.d.ts +0 -2
- package/dist/__tests__/adb-wait.test.d.ts.map +0 -1
- package/dist/__tests__/adb-wait.test.js +0 -69
- package/dist/__tests__/adb.test.d.ts +0 -2
- package/dist/__tests__/adb.test.d.ts.map +0 -1
- package/dist/__tests__/adb.test.js +0 -552
- package/dist/__tests__/app-monitor.test.d.ts +0 -2
- package/dist/__tests__/app-monitor.test.d.ts.map +0 -1
- package/dist/__tests__/app-monitor.test.js +0 -257
- package/dist/__tests__/app-session.test.d.ts +0 -2
- package/dist/__tests__/app-session.test.d.ts.map +0 -1
- package/dist/__tests__/app-session.test.js +0 -57
- package/dist/__tests__/avd-config.test.d.ts +0 -2
- package/dist/__tests__/avd-config.test.d.ts.map +0 -1
- package/dist/__tests__/avd-config.test.js +0 -232
- package/dist/__tests__/ci-action.test.d.ts +0 -2
- package/dist/__tests__/ci-action.test.d.ts.map +0 -1
- package/dist/__tests__/ci-action.test.js +0 -46
- package/dist/__tests__/crash-diagnostics.test.d.ts +0 -2
- package/dist/__tests__/crash-diagnostics.test.d.ts.map +0 -1
- package/dist/__tests__/crash-diagnostics.test.js +0 -119
- package/dist/__tests__/crash-parser.test.d.ts +0 -2
- package/dist/__tests__/crash-parser.test.d.ts.map +0 -1
- package/dist/__tests__/crash-parser.test.js +0 -45
- package/dist/__tests__/crash-reporter.test.d.ts +0 -2
- package/dist/__tests__/crash-reporter.test.d.ts.map +0 -1
- package/dist/__tests__/crash-reporter.test.js +0 -150
- package/dist/__tests__/emulator-startup.test.d.ts +0 -2
- package/dist/__tests__/emulator-startup.test.d.ts.map +0 -1
- package/dist/__tests__/emulator-startup.test.js +0 -47
- package/dist/__tests__/environment.test.d.ts +0 -2
- package/dist/__tests__/environment.test.d.ts.map +0 -1
- package/dist/__tests__/environment.test.js +0 -117
- package/dist/__tests__/instance.test.d.ts +0 -2
- package/dist/__tests__/instance.test.d.ts.map +0 -1
- package/dist/__tests__/instance.test.js +0 -599
- package/dist/__tests__/shared-prefs.test.d.ts +0 -2
- package/dist/__tests__/shared-prefs.test.d.ts.map +0 -1
- package/dist/__tests__/shared-prefs.test.js +0 -87
- package/dist/__tests__/targets.test.d.ts +0 -2
- package/dist/__tests__/targets.test.d.ts.map +0 -1
- package/dist/__tests__/targets.test.js +0 -49
- package/dist/app-monitor.d.ts +0 -13
- package/dist/app-monitor.d.ts.map +0 -1
- package/dist/app-monitor.js +0 -376
- package/dist/device-state.d.ts +0 -6
- package/dist/device-state.d.ts.map +0 -1
- package/dist/device-state.js +0 -142
- package/dist/tsconfig.lib.tsbuildinfo +0 -1
- package/src/__tests__/adb-id.test.ts +0 -51
- package/src/__tests__/adb-wait.test.ts +0 -95
- package/src/__tests__/app-session.test.ts +0 -76
- package/src/__tests__/avd-config.test.ts +0 -276
- package/src/__tests__/ci-action.test.ts +0 -81
- package/src/__tests__/crash-diagnostics.test.ts +0 -144
- package/src/__tests__/crash-parser.test.ts +0 -52
- package/src/__tests__/crash-reporter.test.ts +0 -167
- package/src/__tests__/emulator-startup.test.ts +0 -82
- package/src/__tests__/instance.test.ts +0 -844
- package/src/__tests__/shared-prefs.test.ts +0 -144
- package/src/__tests__/targets.test.ts +0 -53
- package/tsconfig.tsbuildinfo +0 -1
package/dist/adb.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type AndroidAppLaunchOptions } from '@react-native-harness/platforms';
|
|
2
|
-
import { type
|
|
2
|
+
import { type OwnedProcess } from '@react-native-harness/tools';
|
|
3
3
|
import type { ChildProcessByStdio } from 'node:child_process';
|
|
4
4
|
import type { Readable } from 'node:stream';
|
|
5
5
|
import { type EmulatorBootMode } from './emulator-startup.js';
|
|
@@ -44,9 +44,7 @@ export declare const getAppPid: (adbId: string, bundleId: string) => Promise<num
|
|
|
44
44
|
export declare const getAppUid: (adbId: string, bundleId: string) => Promise<number>;
|
|
45
45
|
export declare const setHideErrorDialogs: (adbId: string, hide: boolean) => Promise<void>;
|
|
46
46
|
export declare const getLogcatTimestamp: (adbId: string) => Promise<string>;
|
|
47
|
-
export declare const startLogcat: (adbId: string, args: readonly string[]
|
|
48
|
-
signal?: AbortSignal;
|
|
49
|
-
}) => Subprocess;
|
|
47
|
+
export declare const startLogcat: (adbId: string, args: readonly string[]) => OwnedProcess;
|
|
50
48
|
export declare const DROPBOX_CRASH_TAGS: readonly ["data_app_crash", "data_app_native_crash"];
|
|
51
49
|
export declare const getDropboxPrint: (adbId: string, tags?: readonly string[]) => Promise<string>;
|
|
52
50
|
export declare const getActivityExitInfo: (adbId: string, bundleId: string) => Promise<string>;
|
package/dist/adb.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"adb.d.ts","sourceRoot":"","sources":["../src/adb.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,uBAAuB,EAAE,MAAM,iCAAiC,CAAC;AAC/E,OAAO,
|
|
1
|
+
{"version":3,"file":"adb.d.ts","sourceRoot":"","sources":["../src/adb.ts"],"names":[],"mappings":"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"}
|
package/dist/adb.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { delay, logger, spawn, SubprocessError, waitForAbort, } from '@react-native-harness/tools';
|
|
1
|
+
import { delay, logger, spawn, spawnOwnedProcess, SubprocessError, waitForAbort, } from '@react-native-harness/tools';
|
|
2
2
|
import { spawn as nodeSpawn } from 'node:child_process';
|
|
3
3
|
import { access, rm } from 'node:fs/promises';
|
|
4
4
|
import { getAvdConfigPath, getAvdDirectory, readAvdConfig, } from './avd-config.js';
|
|
@@ -505,10 +505,9 @@ export const getLogcatTimestamp = async (adbId) => {
|
|
|
505
505
|
]);
|
|
506
506
|
return stdout.trim().replace(/^'+|'+$/g, '');
|
|
507
507
|
};
|
|
508
|
-
export const startLogcat = (adbId, args
|
|
508
|
+
export const startLogcat = (adbId, args) => spawnOwnedProcess(getAdbBinaryPath(), ['-s', adbId, ...args], {
|
|
509
509
|
stdout: 'pipe',
|
|
510
510
|
stderr: 'pipe',
|
|
511
|
-
signal: options?.signal,
|
|
512
511
|
});
|
|
513
512
|
export const DROPBOX_CRASH_TAGS = [
|
|
514
513
|
'data_app_crash',
|
package/dist/app-session.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type AppSession, type CrashArtifactWriter } from '@react-native-harness/platforms';
|
|
2
|
-
import { type
|
|
2
|
+
import { type OwnedProcess } from '@react-native-harness/tools';
|
|
3
3
|
type CreateAndroidAppSessionOptions = {
|
|
4
4
|
appUid: number;
|
|
5
5
|
bundleId: string;
|
|
@@ -7,20 +7,11 @@ type CreateAndroidAppSessionOptions = {
|
|
|
7
7
|
stopApp: () => Promise<void>;
|
|
8
8
|
getAppPid: () => Promise<number | null>;
|
|
9
9
|
getLogcatTimestamp: () => Promise<string>;
|
|
10
|
-
startLogcat: (args: readonly string[]
|
|
11
|
-
signal: AbortSignal;
|
|
12
|
-
}) => Subprocess;
|
|
10
|
+
startLogcat: (args: readonly string[]) => OwnedProcess;
|
|
13
11
|
getDropboxOutput?: () => Promise<string>;
|
|
14
12
|
getExitInfo?: () => Promise<string>;
|
|
15
13
|
crashArtifactWriter?: CrashArtifactWriter;
|
|
16
|
-
/**
|
|
17
|
-
* Session-lifetime abort signal (see HarnessPlatformInitOptions.signal).
|
|
18
|
-
* Combined with the app session's own logcatAbortController so that
|
|
19
|
-
* aborting the session stops the logcat stream even if dispose() hasn't
|
|
20
|
-
* been called yet (e.g. SIGTERM during teardown of other resources).
|
|
21
|
-
*/
|
|
22
|
-
signal?: AbortSignal;
|
|
23
14
|
};
|
|
24
|
-
export declare const createAndroidAppSession: ({ appUid, bundleId, startApp, stopApp, getAppPid, getLogcatTimestamp, startLogcat, getDropboxOutput, getExitInfo, crashArtifactWriter,
|
|
15
|
+
export declare const createAndroidAppSession: ({ appUid, bundleId, startApp, stopApp, getAppPid, getLogcatTimestamp, startLogcat, getDropboxOutput, getExitInfo, crashArtifactWriter, }: CreateAndroidAppSessionOptions) => Promise<AppSession>;
|
|
25
16
|
export {};
|
|
26
17
|
//# sourceMappingURL=app-session.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"app-session.d.ts","sourceRoot":"","sources":["../src/app-session.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,UAAU,EAEf,KAAK,mBAAmB,EACzB,MAAM,iCAAiC,CAAC;AACzC,OAAO,EAGL,KAAK,
|
|
1
|
+
{"version":3,"file":"app-session.d.ts","sourceRoot":"","sources":["../src/app-session.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,UAAU,EAEf,KAAK,mBAAmB,EACzB,MAAM,iCAAiC,CAAC;AACzC,OAAO,EAGL,KAAK,YAAY,EAClB,MAAM,6BAA6B,CAAC;AA0DrC,KAAK,8BAA8B,GAAG;IACpC,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9B,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7B,SAAS,EAAE,MAAM,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IACxC,kBAAkB,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,CAAC;IAC1C,WAAW,EAAE,CACX,IAAI,EAAE,SAAS,MAAM,EAAE,KACpB,YAAY,CAAC;IAClB,gBAAgB,CAAC,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,CAAC;IACzC,WAAW,CAAC,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,CAAC;IACpC,mBAAmB,CAAC,EAAE,mBAAmB,CAAC;CAC3C,CAAC;AAEF,eAAO,MAAM,uBAAuB,GAAU,0IAW3C,8BAA8B,KAAG,OAAO,CAAC,UAAU,CA2MrD,CAAC"}
|
package/dist/app-session.js
CHANGED
|
@@ -31,7 +31,7 @@ const isCrashSignal = (line, bundleId) => {
|
|
|
31
31
|
/fatal|crash|signal 11|signal 6|backtrace/i.test(line)));
|
|
32
32
|
};
|
|
33
33
|
const isExitedState = (state) => state.status === 'exited';
|
|
34
|
-
export const createAndroidAppSession = async ({ appUid, bundleId, startApp, stopApp, getAppPid, getLogcatTimestamp, startLogcat, getDropboxOutput, getExitInfo, crashArtifactWriter,
|
|
34
|
+
export const createAndroidAppSession = async ({ appUid, bundleId, startApp, stopApp, getAppPid, getLogcatTimestamp, startLogcat, getDropboxOutput, getExitInfo, crashArtifactWriter, }) => {
|
|
35
35
|
const emitter = createAppSessionEmitter();
|
|
36
36
|
const logBuffer = createBoundedLogBuffer();
|
|
37
37
|
let state = { status: 'running' };
|
|
@@ -105,20 +105,8 @@ export const createAndroidAppSession = async ({ appUid, bundleId, startApp, stop
|
|
|
105
105
|
};
|
|
106
106
|
const logcatTimestamp = await getLogcatTimestamp();
|
|
107
107
|
const sessionStartedAt = Date.now();
|
|
108
|
-
const
|
|
109
|
-
const
|
|
110
|
-
? AbortSignal.any([signal, logcatAbortController.signal])
|
|
111
|
-
: logcatAbortController.signal;
|
|
112
|
-
const logcatProcess = startLogcat(getLogcatArgs(appUid, logcatTimestamp), {
|
|
113
|
-
signal: logcatSignal,
|
|
114
|
-
});
|
|
115
|
-
// Aborting is nano-spawn's own cancellation path, so the resulting
|
|
116
|
-
// SubprocessError is settled the same way regardless of which of the
|
|
117
|
-
// merged stdout/stderr iterators observes it first. Without this, killing
|
|
118
|
-
// the underlying process directly can make both iterators reject at once,
|
|
119
|
-
// and nano-spawn's internal `Promise.race` only reports one of them,
|
|
120
|
-
// leaving the other an unhandled rejection.
|
|
121
|
-
logcatProcess.catch(() => undefined);
|
|
108
|
+
const ownedLogcatProcess = startLogcat(getLogcatArgs(appUid, logcatTimestamp));
|
|
109
|
+
const logcatProcess = ownedLogcatProcess.subprocess;
|
|
122
110
|
const crashReporter = createAndroidCrashReporter({
|
|
123
111
|
bundleId,
|
|
124
112
|
crashArtifactWriter,
|
|
@@ -161,7 +149,7 @@ export const createAndroidAppSession = async ({ appUid, bundleId, startApp, stop
|
|
|
161
149
|
disposed = true;
|
|
162
150
|
stopPolling = true;
|
|
163
151
|
emitter.clear();
|
|
164
|
-
|
|
152
|
+
await ownedLogcatProcess.dispose();
|
|
165
153
|
await Promise.allSettled([logTask]);
|
|
166
154
|
throw error;
|
|
167
155
|
}
|
|
@@ -187,24 +175,26 @@ export const createAndroidAppSession = async ({ appUid, bundleId, startApp, stop
|
|
|
187
175
|
await waitForNextPoll();
|
|
188
176
|
}
|
|
189
177
|
})();
|
|
178
|
+
let disposePromise;
|
|
179
|
+
const dispose = () => (disposePromise ??= (async () => {
|
|
180
|
+
if (disposed) {
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
disposed = true;
|
|
184
|
+
stopPolling = true;
|
|
185
|
+
state = { status: 'disposed', occurredAt: Date.now() };
|
|
186
|
+
if (exitNotification) {
|
|
187
|
+
clearTimeout(exitNotification);
|
|
188
|
+
exitNotification = null;
|
|
189
|
+
}
|
|
190
|
+
cancelPendingPollDelay();
|
|
191
|
+
emitter.clear();
|
|
192
|
+
await ownedLogcatProcess.dispose();
|
|
193
|
+
await stopApp();
|
|
194
|
+
await Promise.allSettled([logTask, pollTask]);
|
|
195
|
+
})());
|
|
190
196
|
return {
|
|
191
|
-
dispose
|
|
192
|
-
if (disposed) {
|
|
193
|
-
return;
|
|
194
|
-
}
|
|
195
|
-
disposed = true;
|
|
196
|
-
stopPolling = true;
|
|
197
|
-
state = { status: 'disposed', occurredAt: Date.now() };
|
|
198
|
-
if (exitNotification) {
|
|
199
|
-
clearTimeout(exitNotification);
|
|
200
|
-
exitNotification = null;
|
|
201
|
-
}
|
|
202
|
-
cancelPendingPollDelay();
|
|
203
|
-
emitter.clear();
|
|
204
|
-
logcatAbortController.abort();
|
|
205
|
-
await stopApp();
|
|
206
|
-
await Promise.allSettled([logTask, pollTask]);
|
|
207
|
-
},
|
|
197
|
+
dispose,
|
|
208
198
|
getState: async () => state,
|
|
209
199
|
getLogs: () => logBuffer.getLogs(),
|
|
210
200
|
getCrashDetails: crashReporter.getCrashDetails,
|
package/dist/instance.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"instance.d.ts","sourceRoot":"","sources":["../src/instance.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,0BAA0B,EAC/B,qBAAqB,EACtB,MAAM,iCAAiC,CAAC;AACzC,OAAO,KAAK,EAAE,MAAM,IAAI,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAE5E,OAAO,EACL,qBAAqB,EAGtB,MAAM,aAAa,CAAC;AAyKrB,eAAO,MAAM,kCAAkC,GAC7C,QAAQ,qBAAqB,EAC7B,eAAe,aAAa,EAC5B,MAAM,0BAA0B,KAC/B,OAAO,CAAC,qBAAqB,
|
|
1
|
+
{"version":3,"file":"instance.d.ts","sourceRoot":"","sources":["../src/instance.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,0BAA0B,EAC/B,qBAAqB,EACtB,MAAM,iCAAiC,CAAC;AACzC,OAAO,KAAK,EAAE,MAAM,IAAI,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAE5E,OAAO,EACL,qBAAqB,EAGtB,MAAM,aAAa,CAAC;AAyKrB,eAAO,MAAM,kCAAkC,GAC7C,QAAQ,qBAAqB,EAC7B,eAAe,aAAa,EAC5B,MAAM,0BAA0B,KAC/B,OAAO,CAAC,qBAAqB,CAmI/B,CAAC;AAEF,eAAO,MAAM,wCAAwC,GACnD,QAAQ,qBAAqB,EAC7B,eAAe,aAAa,EAC5B,OAAO,0BAA0B,KAChC,OAAO,CAAC,qBAAqB,CA8D/B,CAAC"}
|
package/dist/instance.js
CHANGED
|
@@ -172,11 +172,10 @@ export const getAndroidEmulatorPlatformInstance = async (config, harnessConfig,
|
|
|
172
172
|
stopApp: () => adb.stopApp(adbId, config.bundleId),
|
|
173
173
|
getAppPid: () => adb.getAppPid(adbId, config.bundleId),
|
|
174
174
|
getLogcatTimestamp: () => adb.getLogcatTimestamp(adbId),
|
|
175
|
-
startLogcat: (args
|
|
175
|
+
startLogcat: (args) => adb.startLogcat(adbId, args),
|
|
176
176
|
getDropboxOutput: () => adb.getDropboxPrint(adbId),
|
|
177
177
|
getExitInfo: () => adb.getActivityExitInfo(adbId, config.bundleId),
|
|
178
178
|
crashArtifactWriter: init.crashArtifactWriter,
|
|
179
|
-
signal: init.signal,
|
|
180
179
|
});
|
|
181
180
|
},
|
|
182
181
|
dispose: async () => {
|
|
@@ -218,11 +217,10 @@ export const getAndroidPhysicalDevicePlatformInstance = async (config, harnessCo
|
|
|
218
217
|
stopApp: () => adb.stopApp(adbId, config.bundleId),
|
|
219
218
|
getAppPid: () => adb.getAppPid(adbId, config.bundleId),
|
|
220
219
|
getLogcatTimestamp: () => adb.getLogcatTimestamp(adbId),
|
|
221
|
-
startLogcat: (args
|
|
220
|
+
startLogcat: (args) => adb.startLogcat(adbId, args),
|
|
222
221
|
getDropboxOutput: () => adb.getDropboxPrint(adbId),
|
|
223
222
|
getExitInfo: () => adb.getActivityExitInfo(adbId, config.bundleId),
|
|
224
223
|
crashArtifactWriter: init?.crashArtifactWriter,
|
|
225
|
-
signal: init?.signal,
|
|
226
224
|
});
|
|
227
225
|
},
|
|
228
226
|
dispose: async () => {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@react-native-harness/platform-android",
|
|
3
3
|
"description": "Android platform for React Native Harness",
|
|
4
|
-
"version": "1.
|
|
4
|
+
"version": "1.5.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
7
7
|
"module": "./dist/index.js",
|
|
@@ -19,9 +19,9 @@
|
|
|
19
19
|
"vite": "^7.2.2",
|
|
20
20
|
"zod": "^3.25.67",
|
|
21
21
|
"tslib": "^2.3.0",
|
|
22
|
-
"@react-native-harness/
|
|
23
|
-
"@react-native-harness/tools": "1.
|
|
24
|
-
"@react-native-harness/
|
|
22
|
+
"@react-native-harness/platforms": "1.5.0",
|
|
23
|
+
"@react-native-harness/tools": "1.5.0",
|
|
24
|
+
"@react-native-harness/config": "1.5.0"
|
|
25
25
|
},
|
|
26
26
|
"license": "MIT"
|
|
27
27
|
}
|
package/src/adb.ts
CHANGED
|
@@ -3,9 +3,10 @@ import {
|
|
|
3
3
|
delay,
|
|
4
4
|
logger,
|
|
5
5
|
spawn,
|
|
6
|
+
spawnOwnedProcess,
|
|
6
7
|
SubprocessError,
|
|
7
8
|
waitForAbort,
|
|
8
|
-
type
|
|
9
|
+
type OwnedProcess,
|
|
9
10
|
} from '@react-native-harness/tools';
|
|
10
11
|
import { spawn as nodeSpawn } from 'node:child_process';
|
|
11
12
|
import type { ChildProcessByStdio } from 'node:child_process';
|
|
@@ -789,13 +790,11 @@ export const getLogcatTimestamp = async (adbId: string): Promise<string> => {
|
|
|
789
790
|
|
|
790
791
|
export const startLogcat = (
|
|
791
792
|
adbId: string,
|
|
792
|
-
args: readonly string[]
|
|
793
|
-
|
|
794
|
-
)
|
|
795
|
-
spawn(getAdbBinaryPath(), ['-s', adbId, ...args], {
|
|
793
|
+
args: readonly string[]
|
|
794
|
+
): OwnedProcess =>
|
|
795
|
+
spawnOwnedProcess(getAdbBinaryPath(), ['-s', adbId, ...args], {
|
|
796
796
|
stdout: 'pipe',
|
|
797
797
|
stderr: 'pipe',
|
|
798
|
-
signal: options?.signal,
|
|
799
798
|
});
|
|
800
799
|
|
|
801
800
|
export const DROPBOX_CRASH_TAGS = [
|
package/src/app-session.ts
CHANGED
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
import {
|
|
9
9
|
escapeRegExp,
|
|
10
10
|
logger,
|
|
11
|
-
type
|
|
11
|
+
type OwnedProcess,
|
|
12
12
|
} from '@react-native-harness/tools';
|
|
13
13
|
import { createAndroidCrashReporter } from './crash-reporter.js';
|
|
14
14
|
|
|
@@ -75,19 +75,11 @@ type CreateAndroidAppSessionOptions = {
|
|
|
75
75
|
getAppPid: () => Promise<number | null>;
|
|
76
76
|
getLogcatTimestamp: () => Promise<string>;
|
|
77
77
|
startLogcat: (
|
|
78
|
-
args: readonly string[]
|
|
79
|
-
|
|
80
|
-
) => Subprocess;
|
|
78
|
+
args: readonly string[]
|
|
79
|
+
) => OwnedProcess;
|
|
81
80
|
getDropboxOutput?: () => Promise<string>;
|
|
82
81
|
getExitInfo?: () => Promise<string>;
|
|
83
82
|
crashArtifactWriter?: CrashArtifactWriter;
|
|
84
|
-
/**
|
|
85
|
-
* Session-lifetime abort signal (see HarnessPlatformInitOptions.signal).
|
|
86
|
-
* Combined with the app session's own logcatAbortController so that
|
|
87
|
-
* aborting the session stops the logcat stream even if dispose() hasn't
|
|
88
|
-
* been called yet (e.g. SIGTERM during teardown of other resources).
|
|
89
|
-
*/
|
|
90
|
-
signal?: AbortSignal;
|
|
91
83
|
};
|
|
92
84
|
|
|
93
85
|
export const createAndroidAppSession = async ({
|
|
@@ -101,7 +93,6 @@ export const createAndroidAppSession = async ({
|
|
|
101
93
|
getDropboxOutput,
|
|
102
94
|
getExitInfo,
|
|
103
95
|
crashArtifactWriter,
|
|
104
|
-
signal,
|
|
105
96
|
}: CreateAndroidAppSessionOptions): Promise<AppSession> => {
|
|
106
97
|
const emitter = createAppSessionEmitter();
|
|
107
98
|
const logBuffer = createBoundedLogBuffer();
|
|
@@ -196,20 +187,8 @@ export const createAndroidAppSession = async ({
|
|
|
196
187
|
|
|
197
188
|
const logcatTimestamp = await getLogcatTimestamp();
|
|
198
189
|
const sessionStartedAt = Date.now();
|
|
199
|
-
const
|
|
200
|
-
const
|
|
201
|
-
? AbortSignal.any([signal, logcatAbortController.signal])
|
|
202
|
-
: logcatAbortController.signal;
|
|
203
|
-
const logcatProcess = startLogcat(getLogcatArgs(appUid, logcatTimestamp), {
|
|
204
|
-
signal: logcatSignal,
|
|
205
|
-
});
|
|
206
|
-
// Aborting is nano-spawn's own cancellation path, so the resulting
|
|
207
|
-
// SubprocessError is settled the same way regardless of which of the
|
|
208
|
-
// merged stdout/stderr iterators observes it first. Without this, killing
|
|
209
|
-
// the underlying process directly can make both iterators reject at once,
|
|
210
|
-
// and nano-spawn's internal `Promise.race` only reports one of them,
|
|
211
|
-
// leaving the other an unhandled rejection.
|
|
212
|
-
logcatProcess.catch(() => undefined);
|
|
190
|
+
const ownedLogcatProcess = startLogcat(getLogcatArgs(appUid, logcatTimestamp));
|
|
191
|
+
const logcatProcess = ownedLogcatProcess.subprocess;
|
|
213
192
|
const crashReporter = createAndroidCrashReporter({
|
|
214
193
|
bundleId,
|
|
215
194
|
crashArtifactWriter,
|
|
@@ -255,7 +234,7 @@ export const createAndroidAppSession = async ({
|
|
|
255
234
|
disposed = true;
|
|
256
235
|
stopPolling = true;
|
|
257
236
|
emitter.clear();
|
|
258
|
-
|
|
237
|
+
await ownedLogcatProcess.dispose();
|
|
259
238
|
await Promise.allSettled([logTask]);
|
|
260
239
|
throw error;
|
|
261
240
|
}
|
|
@@ -285,8 +264,9 @@ export const createAndroidAppSession = async ({
|
|
|
285
264
|
}
|
|
286
265
|
})();
|
|
287
266
|
|
|
288
|
-
|
|
289
|
-
|
|
267
|
+
let disposePromise: Promise<void> | undefined;
|
|
268
|
+
const dispose = () =>
|
|
269
|
+
(disposePromise ??= (async () => {
|
|
290
270
|
if (disposed) {
|
|
291
271
|
return;
|
|
292
272
|
}
|
|
@@ -303,10 +283,13 @@ export const createAndroidAppSession = async ({
|
|
|
303
283
|
cancelPendingPollDelay();
|
|
304
284
|
|
|
305
285
|
emitter.clear();
|
|
306
|
-
|
|
286
|
+
await ownedLogcatProcess.dispose();
|
|
307
287
|
await stopApp();
|
|
308
288
|
await Promise.allSettled([logTask, pollTask]);
|
|
309
|
-
}
|
|
289
|
+
})());
|
|
290
|
+
|
|
291
|
+
return {
|
|
292
|
+
dispose,
|
|
310
293
|
getState: async () => state,
|
|
311
294
|
getLogs: () => logBuffer.getLogs(),
|
|
312
295
|
getCrashDetails: crashReporter.getCrashDetails,
|
package/src/instance.ts
CHANGED
|
@@ -297,11 +297,10 @@ export const getAndroidEmulatorPlatformInstance = async (
|
|
|
297
297
|
stopApp: () => adb.stopApp(adbId, config.bundleId),
|
|
298
298
|
getAppPid: () => adb.getAppPid(adbId, config.bundleId),
|
|
299
299
|
getLogcatTimestamp: () => adb.getLogcatTimestamp(adbId),
|
|
300
|
-
startLogcat: (args
|
|
300
|
+
startLogcat: (args) => adb.startLogcat(adbId, args),
|
|
301
301
|
getDropboxOutput: () => adb.getDropboxPrint(adbId),
|
|
302
302
|
getExitInfo: () => adb.getActivityExitInfo(adbId, config.bundleId),
|
|
303
303
|
crashArtifactWriter: init.crashArtifactWriter,
|
|
304
|
-
signal: init.signal,
|
|
305
304
|
});
|
|
306
305
|
},
|
|
307
306
|
dispose: async () => {
|
|
@@ -371,11 +370,10 @@ export const getAndroidPhysicalDevicePlatformInstance = async (
|
|
|
371
370
|
stopApp: () => adb.stopApp(adbId, config.bundleId),
|
|
372
371
|
getAppPid: () => adb.getAppPid(adbId, config.bundleId),
|
|
373
372
|
getLogcatTimestamp: () => adb.getLogcatTimestamp(adbId),
|
|
374
|
-
startLogcat: (args
|
|
373
|
+
startLogcat: (args) => adb.startLogcat(adbId, args),
|
|
375
374
|
getDropboxOutput: () => adb.getDropboxPrint(adbId),
|
|
376
375
|
getExitInfo: () => adb.getActivityExitInfo(adbId, config.bundleId),
|
|
377
376
|
crashArtifactWriter: init?.crashArtifactWriter,
|
|
378
|
-
signal: init?.signal,
|
|
379
377
|
});
|
|
380
378
|
},
|
|
381
379
|
dispose: async () => {
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"adb-id.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/adb-id.test.ts"],"names":[],"mappings":""}
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
2
|
-
import { getAdbId, isAdbIdEmulator } from '../adb-id.js';
|
|
3
|
-
import * as adb from '../adb.js';
|
|
4
|
-
describe('adb id resolution', () => {
|
|
5
|
-
beforeEach(() => {
|
|
6
|
-
vi.restoreAllMocks();
|
|
7
|
-
});
|
|
8
|
-
it('identifies emulator adb ids', () => {
|
|
9
|
-
expect(isAdbIdEmulator('emulator-5554')).toBe(true);
|
|
10
|
-
expect(isAdbIdEmulator('device-serial-001')).toBe(false);
|
|
11
|
-
});
|
|
12
|
-
it('skips non-emulator ids when resolving an emulator device', async () => {
|
|
13
|
-
vi.spyOn(adb, 'getDeviceIds').mockResolvedValue([
|
|
14
|
-
'device-serial-001',
|
|
15
|
-
'emulator-5554',
|
|
16
|
-
]);
|
|
17
|
-
vi.spyOn(adb, 'getEmulatorName').mockResolvedValue('Test_AVD_API_35');
|
|
18
|
-
await expect(getAdbId({
|
|
19
|
-
type: 'emulator',
|
|
20
|
-
name: 'Test_AVD_API_35',
|
|
21
|
-
})).resolves.toBe('emulator-5554');
|
|
22
|
-
});
|
|
23
|
-
it('resolves matching physical device by manufacturer and model', async () => {
|
|
24
|
-
vi.spyOn(adb, 'getDeviceIds').mockResolvedValue([
|
|
25
|
-
'device-serial-001',
|
|
26
|
-
'emulator-5554',
|
|
27
|
-
]);
|
|
28
|
-
vi.spyOn(adb, 'getDeviceInfo').mockImplementation(async (adbId) => {
|
|
29
|
-
const results = {
|
|
30
|
-
'device-serial-001': { manufacturer: 'Acme', model: 'Model A1' },
|
|
31
|
-
'emulator-5554': { manufacturer: 'Emulator', model: 'Emulator Model' },
|
|
32
|
-
};
|
|
33
|
-
return results[adbId] || null;
|
|
34
|
-
});
|
|
35
|
-
await expect(getAdbId({
|
|
36
|
-
type: 'physical',
|
|
37
|
-
manufacturer: 'acme',
|
|
38
|
-
model: 'model a1',
|
|
39
|
-
})).resolves.toBe('device-serial-001');
|
|
40
|
-
});
|
|
41
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"adb-wait.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/adb-wait.test.ts"],"names":[],"mappings":""}
|
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
2
|
-
const mocks = vi.hoisted(() => ({
|
|
3
|
-
spawn: vi.fn(),
|
|
4
|
-
}));
|
|
5
|
-
// The global test/setup.ts mocks '../adb.js' wholesale (including
|
|
6
|
-
// waitForEmulator itself), which is right for consumers of this module but
|
|
7
|
-
// defeats the purpose here: this suite exercises the *real* polling loop
|
|
8
|
-
// (and its private waitWithSignal helper) to catch timer/listener leaks.
|
|
9
|
-
// Re-declaring the mock for this file restores the actual implementation,
|
|
10
|
-
// while `spawn` and `getAdbBinaryPath` (genuine cross-module imports) are
|
|
11
|
-
// swapped out to keep `getDeviceIds()` from touching a real adb binary.
|
|
12
|
-
vi.mock('../adb.js', async () => {
|
|
13
|
-
const actual = await vi.importActual('../adb.js');
|
|
14
|
-
return actual;
|
|
15
|
-
});
|
|
16
|
-
vi.mock('../environment.js', async () => {
|
|
17
|
-
const actual = await vi.importActual('../environment.js');
|
|
18
|
-
return { ...actual, getAdbBinaryPath: () => '/fake/sdk/platform-tools/adb' };
|
|
19
|
-
});
|
|
20
|
-
vi.mock('@react-native-harness/tools', async () => {
|
|
21
|
-
const actual = await vi.importActual('@react-native-harness/tools');
|
|
22
|
-
return { ...actual, spawn: mocks.spawn };
|
|
23
|
-
});
|
|
24
|
-
const { waitForEmulator } = await import('../adb.js');
|
|
25
|
-
afterEach(() => {
|
|
26
|
-
vi.useRealTimers();
|
|
27
|
-
vi.restoreAllMocks();
|
|
28
|
-
});
|
|
29
|
-
describe('waitForEmulator polling', () => {
|
|
30
|
-
it('does not leak an abort listener per poll iteration when the delay wins', async () => {
|
|
31
|
-
vi.useFakeTimers();
|
|
32
|
-
// No matching device on any poll: `adb devices` reports an empty list.
|
|
33
|
-
mocks.spawn.mockResolvedValue({ stdout: 'List of devices attached\n' });
|
|
34
|
-
const controller = new AbortController();
|
|
35
|
-
const addSpy = vi.spyOn(controller.signal, 'addEventListener');
|
|
36
|
-
const removeSpy = vi.spyOn(controller.signal, 'removeEventListener');
|
|
37
|
-
const resultPromise = waitForEmulator('Test_AVD', controller.signal);
|
|
38
|
-
resultPromise.catch(() => undefined);
|
|
39
|
-
for (let i = 0; i < 3; i += 1) {
|
|
40
|
-
await vi.advanceTimersByTimeAsync(1_000);
|
|
41
|
-
}
|
|
42
|
-
// Regression: each 1s poll iteration used to add an 'abort' listener
|
|
43
|
-
// to the local waitForAbort helper that was never removed when the
|
|
44
|
-
// delay (not the abort) won the race, leaking one listener per second
|
|
45
|
-
// of polling against this session-lifetime signal.
|
|
46
|
-
expect(addSpy.mock.calls.length).toBeGreaterThanOrEqual(3);
|
|
47
|
-
const addCallsBeforeAbort = addSpy.mock.calls.length;
|
|
48
|
-
controller.abort(new Error('teardown'));
|
|
49
|
-
await expect(resultPromise).rejects.toThrow('teardown');
|
|
50
|
-
// Every listener added while losing the race — including the one still
|
|
51
|
-
// pending when the abort itself won — must be removed again.
|
|
52
|
-
expect(removeSpy).toHaveBeenCalledTimes(addCallsBeforeAbort);
|
|
53
|
-
});
|
|
54
|
-
it('clears the pending 1s poll timer when the signal aborts mid-wait', async () => {
|
|
55
|
-
vi.useFakeTimers();
|
|
56
|
-
mocks.spawn.mockResolvedValue({ stdout: 'List of devices attached\n' });
|
|
57
|
-
const controller = new AbortController();
|
|
58
|
-
const resultPromise = waitForEmulator('Test_AVD', controller.signal);
|
|
59
|
-
resultPromise.catch(() => undefined);
|
|
60
|
-
// Let the loop enter its 1s wait, then abort before the timer fires.
|
|
61
|
-
await vi.advanceTimersByTimeAsync(0);
|
|
62
|
-
controller.abort(new Error('teardown'));
|
|
63
|
-
await expect(resultPromise).rejects.toThrow('teardown');
|
|
64
|
-
// Regression: the bare setTimeout-backed `wait()` used to have no
|
|
65
|
-
// handle to clear, so it stayed alive in the event loop for the rest
|
|
66
|
-
// of the 1s interval even after the abort branch won.
|
|
67
|
-
expect(vi.getTimerCount()).toBe(0);
|
|
68
|
-
});
|
|
69
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"adb.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/adb.test.ts"],"names":[],"mappings":""}
|