@react-native-harness/platform-android 1.4.1 → 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 CHANGED
@@ -1,5 +1,5 @@
1
1
  import { type AndroidAppLaunchOptions } from '@react-native-harness/platforms';
2
- import { type Subprocess } from '@react-native-harness/tools';
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[], options?: {
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":"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"}
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, options) => spawn(getAdbBinaryPath(), ['-s', 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',
@@ -1,5 +1,5 @@
1
1
  import { type AppSession, type CrashArtifactWriter } from '@react-native-harness/platforms';
2
- import { type Subprocess } from '@react-native-harness/tools';
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[], options: {
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, signal, }: CreateAndroidAppSessionOptions) => Promise<AppSession>;
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,UAAU,EAChB,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,EACvB,OAAO,EAAE;QAAE,MAAM,EAAE,WAAW,CAAA;KAAE,KAC7B,UAAU,CAAC;IAChB,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;IAC1C;;;;;OAKG;IACH,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB,CAAC;AAEF,eAAO,MAAM,uBAAuB,GAAU,kJAY3C,8BAA8B,KAAG,OAAO,CAAC,UAAU,CAmNrD,CAAC"}
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"}
@@ -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, signal, }) => {
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 logcatAbortController = new AbortController();
109
- const logcatSignal = signal
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
- logcatAbortController.abort();
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: async () => {
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,
@@ -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,CAoI/B,CAAC;AAEF,eAAO,MAAM,wCAAwC,GACnD,QAAQ,qBAAqB,EAC7B,eAAe,aAAa,EAC5B,OAAO,0BAA0B,KAChC,OAAO,CAAC,qBAAqB,CA+D/B,CAAC"}
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, options) => adb.startLogcat(adbId, args, options),
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, options) => adb.startLogcat(adbId, args, options),
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.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/config": "1.4.1",
23
- "@react-native-harness/platforms": "1.4.1",
24
- "@react-native-harness/tools": "1.4.1"
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 Subprocess,
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
- options?: { signal?: AbortSignal }
794
- ): Subprocess =>
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 = [
@@ -8,7 +8,7 @@ import {
8
8
  import {
9
9
  escapeRegExp,
10
10
  logger,
11
- type Subprocess,
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
- options: { signal: AbortSignal }
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 logcatAbortController = new AbortController();
200
- const logcatSignal = signal
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
- logcatAbortController.abort();
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
- return {
289
- dispose: async () => {
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
- logcatAbortController.abort();
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, options) => adb.startLogcat(adbId, args, options),
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, options) => adb.startLogcat(adbId, args, options),
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,13 +0,0 @@
1
- import { type AppMonitor, type AppCrashDetails, type CrashArtifactWriter, type CrashDetailsLookupOptions, type AppMonitorEvent } from '@react-native-harness/platforms';
2
- declare const createAndroidLogEvent: (line: string, bundleId: string) => AppMonitorEvent | null;
3
- export declare const createAndroidAppMonitor: ({ adbId, bundleId, appUid, crashArtifactWriter, }: {
4
- adbId: string;
5
- bundleId: string;
6
- appUid: number;
7
- crashArtifactWriter?: CrashArtifactWriter;
8
- }) => AndroidAppMonitor;
9
- export { createAndroidLogEvent };
10
- export type AndroidAppMonitor = AppMonitor & {
11
- getCrashDetails: (options: CrashDetailsLookupOptions) => Promise<AppCrashDetails | null>;
12
- };
13
- //# sourceMappingURL=app-monitor.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"app-monitor.d.ts","sourceRoot":"","sources":["../src/app-monitor.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,UAAU,EACf,KAAK,eAAe,EACpB,KAAK,mBAAmB,EACxB,KAAK,yBAAyB,EAC9B,KAAK,eAAe,EAErB,MAAM,iCAAiC,CAAC;AA+SzC,QAAA,MAAM,qBAAqB,GACzB,MAAM,MAAM,EACZ,UAAU,MAAM,KACf,eAAe,GAAG,IAwEpB,CAAC;AAEF,eAAO,MAAM,uBAAuB,GAAI,mDAKrC;IACD,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,mBAAmB,CAAC,EAAE,mBAAmB,CAAC;CAC3C,KAAG,iBA4KH,CAAC;AAEF,OAAO,EAAE,qBAAqB,EAAE,CAAC;AACjC,MAAM,MAAM,iBAAiB,GAAG,UAAU,GAAG;IAC3C,eAAe,EAAE,CACf,OAAO,EAAE,yBAAyB,KAC/B,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC,CAAC;CACtC,CAAC"}
@@ -1,376 +0,0 @@
1
- import { escapeRegExp, getEmitter, logger, SubprocessError, } from '@react-native-harness/tools';
2
- import * as adb from './adb.js';
3
- import { androidCrashParser } from './crash-parser.js';
4
- const androidAppMonitorLogger = logger.child('android-app-monitor');
5
- const getLogcatArgs = (uid, fromTime) => [
6
- 'logcat',
7
- '-v',
8
- 'threadtime',
9
- '-b',
10
- 'crash',
11
- `--uid=${uid}`,
12
- '-T',
13
- fromTime,
14
- ];
15
- const MAX_RECENT_LOG_LINES = 200;
16
- const MAX_RECENT_CRASH_ARTIFACTS = 10;
17
- const CRASH_ARTIFACT_SETTLE_DELAY_MS = 100;
18
- const startProcPattern = (bundleId) => new RegExp(`Start proc (\\d+):${escapeRegExp(bundleId)}(?:/|\\s)`);
19
- const processPattern = (bundleId) => new RegExp(`Process:\\s*${escapeRegExp(bundleId)},\\s*PID:\\s*(\\d+)`);
20
- const nativeCrashPattern = (bundleId) => new RegExp(`>>>\\s*${escapeRegExp(bundleId)}\\s*<<<`);
21
- const processDiedPattern = (bundleId) => new RegExp(`Process\\s+${escapeRegExp(bundleId)}\\s+\\(pid\\s+(\\d+)\\)\\s+has\\s+died`, 'i');
22
- const getSignal = (line) => {
23
- const namedSignalMatch = line.match(/\b(SIG[A-Z0-9]+)\b/);
24
- if (namedSignalMatch) {
25
- return namedSignalMatch[1];
26
- }
27
- const signalNumberMatch = line.match(/signal\s+(\d+)/i);
28
- if (signalNumberMatch) {
29
- return `signal ${signalNumberMatch[1]}`;
30
- }
31
- return undefined;
32
- };
33
- const getAndroidLogLineCrashDetails = ({ line, bundleId, pid, }) => {
34
- const fatalExceptionMatch = line.match(/FATAL EXCEPTION:\s*(.+)$/i);
35
- const processMatch = line.match(processPattern(bundleId));
36
- return {
37
- source: 'logs',
38
- summary: line.trim(),
39
- signal: getSignal(line),
40
- exceptionType: fatalExceptionMatch?.[1]?.trim(),
41
- processName: processMatch
42
- ? bundleId
43
- : line.includes(bundleId)
44
- ? bundleId
45
- : undefined,
46
- pid: pid ?? (processMatch ? Number(processMatch[1]) : undefined),
47
- rawLines: [line],
48
- };
49
- };
50
- const CRASH_BLOCK_HEADER = '--------- beginning of crash';
51
- const getLatestCrashBlock = (recentLogLines) => {
52
- const lines = recentLogLines.map(({ line }) => line);
53
- let latestCrashHeaderIndex = -1;
54
- for (let index = lines.length - 1; index >= 0; index -= 1) {
55
- if (/FATAL EXCEPTION:|Process:\s+.+,\s+PID:/i.test(lines[index])) {
56
- latestCrashHeaderIndex = index;
57
- break;
58
- }
59
- }
60
- const blockStartIndex = Math.max(lines.lastIndexOf(CRASH_BLOCK_HEADER), latestCrashHeaderIndex);
61
- if (blockStartIndex === -1) {
62
- return lines;
63
- }
64
- return lines.slice(blockStartIndex);
65
- };
66
- const getCrashBlockForArtifact = ({ artifact, recentLogLines, }) => {
67
- const targetIndex = recentLogLines.findIndex(({ line, occurredAt }) => line === artifact.triggerLine &&
68
- (artifact.triggerOccurredAt === undefined ||
69
- occurredAt === artifact.triggerOccurredAt));
70
- if (targetIndex === -1) {
71
- return artifact.rawLines ?? [];
72
- }
73
- let blockStartIndex = targetIndex;
74
- for (let index = targetIndex; index >= 0; index -= 1) {
75
- const { line } = recentLogLines[index];
76
- if (line === CRASH_BLOCK_HEADER) {
77
- blockStartIndex = index;
78
- break;
79
- }
80
- }
81
- let blockEndIndex = recentLogLines.length;
82
- for (let index = targetIndex + 1; index < recentLogLines.length; index += 1) {
83
- if (recentLogLines[index].line === CRASH_BLOCK_HEADER) {
84
- blockEndIndex = index;
85
- break;
86
- }
87
- }
88
- return recentLogLines
89
- .slice(blockStartIndex, blockEndIndex)
90
- .map(({ line }) => line);
91
- };
92
- const hydrateCrashArtifact = ({ artifact, recentLogLines, }) => {
93
- const rawLines = getCrashBlockForArtifact({ artifact, recentLogLines });
94
- if (rawLines.length === 0) {
95
- return artifact;
96
- }
97
- const parsedDetails = androidCrashParser.parse({
98
- contents: rawLines.join('\n'),
99
- bundleId: artifact.processName ?? '',
100
- pid: artifact.pid,
101
- });
102
- return {
103
- ...artifact,
104
- ...parsedDetails,
105
- artifactType: artifact.artifactType,
106
- artifactPath: artifact.artifactPath,
107
- rawLines,
108
- };
109
- };
110
- const createCrashArtifact = ({ details, recentLogLines, }) => {
111
- const occurredAt = Date.now();
112
- const rawLines = getLatestCrashBlock(recentLogLines);
113
- const triggerOccurredAt = [...recentLogLines]
114
- .reverse()
115
- .find(({ line }) => line === details.summary)?.occurredAt;
116
- const contents = rawLines.length > 0
117
- ? rawLines.join('\n')
118
- : (details.rawLines ?? []).join('\n');
119
- const parsedDetails = details.processName !== undefined
120
- ? androidCrashParser.parse({
121
- contents,
122
- bundleId: details.processName,
123
- pid: details.pid,
124
- })
125
- : details;
126
- return {
127
- ...parsedDetails,
128
- occurredAt,
129
- triggerLine: details.summary ?? '',
130
- triggerOccurredAt,
131
- artifactType: 'logcat',
132
- rawLines: rawLines.length > 0
133
- ? rawLines
134
- : parsedDetails.rawLines ?? details.rawLines,
135
- };
136
- };
137
- const persistCrashArtifact = ({ details, crashArtifactWriter, }) => {
138
- if (!crashArtifactWriter || details.artifactType !== 'logcat') {
139
- return details;
140
- }
141
- const artifactBody = details.rawLines?.join('\n');
142
- if (!artifactBody) {
143
- return details;
144
- }
145
- return {
146
- ...details,
147
- artifactPath: crashArtifactWriter.persistArtifact({
148
- artifactKind: details.artifactType,
149
- source: {
150
- kind: 'text',
151
- fileName: 'logcat.txt',
152
- text: `${artifactBody}\n`,
153
- },
154
- }),
155
- };
156
- };
157
- const getLatestCrashArtifact = ({ crashArtifacts, recentLogLines, processName, pid, occurredAt, }) => {
158
- const matchingByPid = pid
159
- ? crashArtifacts.filter((artifact) => artifact.pid === pid)
160
- : [];
161
- const matchingByProcess = processName
162
- ? crashArtifacts.filter((artifact) => artifact.processName === processName)
163
- : [];
164
- const candidates = matchingByPid.length > 0
165
- ? matchingByPid
166
- : matchingByProcess.length > 0
167
- ? matchingByProcess
168
- : crashArtifacts;
169
- const sortedCandidates = [...candidates].sort((left, right) => Math.abs(left.occurredAt - occurredAt) -
170
- Math.abs(right.occurredAt - occurredAt));
171
- const artifact = sortedCandidates[0];
172
- if (!artifact) {
173
- return null;
174
- }
175
- return hydrateCrashArtifact({
176
- artifact,
177
- recentLogLines,
178
- });
179
- };
180
- const createAndroidLogEvent = (line, bundleId) => {
181
- const startMatch = line.match(startProcPattern(bundleId));
182
- if (startMatch) {
183
- return {
184
- type: 'app_started',
185
- pid: Number(startMatch[1]),
186
- source: 'logs',
187
- line,
188
- };
189
- }
190
- const processMatch = line.match(processPattern(bundleId));
191
- if (processMatch) {
192
- return {
193
- type: 'possible_crash',
194
- pid: Number(processMatch[1]),
195
- source: 'logs',
196
- line,
197
- crashDetails: getAndroidLogLineCrashDetails({
198
- line,
199
- bundleId,
200
- pid: Number(processMatch[1]),
201
- }),
202
- };
203
- }
204
- if (nativeCrashPattern(bundleId).test(line)) {
205
- return {
206
- type: 'possible_crash',
207
- source: 'logs',
208
- line,
209
- crashDetails: getAndroidLogLineCrashDetails({
210
- line,
211
- bundleId,
212
- }),
213
- };
214
- }
215
- const diedMatch = line.match(processDiedPattern(bundleId));
216
- if (diedMatch) {
217
- return {
218
- type: 'app_exited',
219
- pid: Number(diedMatch[1]),
220
- source: 'logs',
221
- line,
222
- crashDetails: getAndroidLogLineCrashDetails({
223
- line,
224
- bundleId,
225
- pid: Number(diedMatch[1]),
226
- }),
227
- };
228
- }
229
- if (line.includes(bundleId) &&
230
- /fatal|crash|signal 11|signal 6|backtrace/i.test(line)) {
231
- return {
232
- type: 'possible_crash',
233
- source: 'logs',
234
- line,
235
- crashDetails: getAndroidLogLineCrashDetails({
236
- line,
237
- bundleId,
238
- }),
239
- };
240
- }
241
- return null;
242
- };
243
- export const createAndroidAppMonitor = ({ adbId, bundleId, appUid, crashArtifactWriter, }) => {
244
- const emitter = getEmitter();
245
- let isStarted = false;
246
- let logcatProcess = null;
247
- let logTask = null;
248
- let recentLogLines = [];
249
- let recentCrashArtifacts = [];
250
- const emit = (event) => {
251
- emitter.emit(event);
252
- };
253
- const recordLogLine = (line) => {
254
- recentLogLines = [
255
- ...recentLogLines,
256
- { line, occurredAt: Date.now() },
257
- ].slice(-MAX_RECENT_LOG_LINES);
258
- };
259
- const recordCrashArtifact = (details) => {
260
- if (!details) {
261
- return;
262
- }
263
- recentCrashArtifacts = [
264
- ...recentCrashArtifacts,
265
- createCrashArtifact({
266
- details,
267
- recentLogLines,
268
- }),
269
- ].slice(-MAX_RECENT_CRASH_ARTIFACTS);
270
- };
271
- const stopProcess = async (child) => {
272
- if (!child) {
273
- return;
274
- }
275
- try {
276
- (await child.nodeChildProcess).kill();
277
- }
278
- catch {
279
- // Ignore termination failures for background monitors.
280
- }
281
- };
282
- const startLogcat = async () => {
283
- const logcatTimestamp = await adb.getLogcatTimestamp(adbId);
284
- logcatProcess = adb.startLogcat(adbId, getLogcatArgs(appUid, logcatTimestamp));
285
- const currentProcess = logcatProcess;
286
- if (!currentProcess) {
287
- return;
288
- }
289
- logTask = (async () => {
290
- try {
291
- for await (const line of currentProcess) {
292
- recordLogLine(line);
293
- emit({ type: 'log', source: 'logs', line });
294
- const event = createAndroidLogEvent(line, bundleId);
295
- if (event) {
296
- if (event.type === 'possible_crash' ||
297
- event.type === 'app_exited') {
298
- recordCrashArtifact(event.crashDetails);
299
- }
300
- emit(event);
301
- }
302
- }
303
- }
304
- catch (error) {
305
- if (!(error instanceof SubprocessError && error.signalName === 'SIGTERM')) {
306
- androidAppMonitorLogger.debug('Android logcat monitor stopped', error);
307
- }
308
- }
309
- })();
310
- };
311
- const start = async () => {
312
- if (isStarted) {
313
- return;
314
- }
315
- try {
316
- await startLogcat();
317
- isStarted = true;
318
- }
319
- catch (error) {
320
- const currentProcess = logcatProcess;
321
- const currentTask = logTask;
322
- logcatProcess = null;
323
- logTask = null;
324
- await stopProcess(currentProcess);
325
- await currentTask;
326
- throw error;
327
- }
328
- };
329
- const stop = async () => {
330
- if (!isStarted) {
331
- return;
332
- }
333
- isStarted = false;
334
- const currentProcess = logcatProcess;
335
- const currentTask = logTask;
336
- logcatProcess = null;
337
- logTask = null;
338
- await stopProcess(currentProcess);
339
- await currentTask;
340
- };
341
- const dispose = async () => {
342
- await stop();
343
- emitter.clearAllListeners();
344
- recentLogLines = [];
345
- recentCrashArtifacts = [];
346
- };
347
- const addListener = (listener) => {
348
- emitter.addListener(listener);
349
- };
350
- const removeListener = (listener) => {
351
- emitter.removeListener(listener);
352
- };
353
- return {
354
- start,
355
- stop,
356
- dispose,
357
- addListener,
358
- removeListener,
359
- getCrashDetails: async (options) => {
360
- await new Promise((resolve) => setTimeout(resolve, CRASH_ARTIFACT_SETTLE_DELAY_MS));
361
- const details = getLatestCrashArtifact({
362
- crashArtifacts: recentCrashArtifacts,
363
- recentLogLines,
364
- ...options,
365
- });
366
- if (!details) {
367
- return null;
368
- }
369
- return persistCrashArtifact({
370
- details,
371
- crashArtifactWriter,
372
- });
373
- },
374
- };
375
- };
376
- export { createAndroidLogEvent };
@@ -1,6 +0,0 @@
1
- import type { DeviceStateController } from '@react-native-harness/platforms';
2
- export declare const createAndroidDeviceState: (options: {
3
- adbId: string;
4
- bundleId: string;
5
- }) => DeviceStateController;
6
- //# sourceMappingURL=device-state.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"device-state.d.ts","sourceRoot":"","sources":["../src/device-state.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAEV,qBAAqB,EAEtB,MAAM,iCAAiC,CAAC;AA0HzC,eAAO,MAAM,wBAAwB,GAAI,SAAS;IAChD,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;CAClB,KAAG,qBAqFH,CAAC"}
@@ -1,142 +0,0 @@
1
- import * as adb from './adb.js';
2
- const ANDROID_PERMISSION_ALIASES = {
3
- bluetooth: [
4
- 'android.permission.BLUETOOTH_CONNECT',
5
- 'android.permission.BLUETOOTH_SCAN',
6
- ],
7
- calendar: [
8
- 'android.permission.READ_CALENDAR',
9
- 'android.permission.WRITE_CALENDAR',
10
- ],
11
- camera: ['android.permission.CAMERA'],
12
- contacts: [
13
- 'android.permission.READ_CONTACTS',
14
- 'android.permission.WRITE_CONTACTS',
15
- ],
16
- location: [
17
- 'android.permission.ACCESS_FINE_LOCATION',
18
- 'android.permission.ACCESS_COARSE_LOCATION',
19
- ],
20
- mediaLibrary: [
21
- 'android.permission.READ_MEDIA_IMAGES',
22
- 'android.permission.READ_MEDIA_VIDEO',
23
- 'android.permission.READ_MEDIA_AUDIO',
24
- ],
25
- microphone: ['android.permission.RECORD_AUDIO'],
26
- notifications: ['android.permission.POST_NOTIFICATIONS'],
27
- phone: [
28
- 'android.permission.CALL_PHONE',
29
- 'android.permission.ANSWER_PHONE_CALLS',
30
- ],
31
- sms: [
32
- 'android.permission.READ_SMS',
33
- 'android.permission.RECEIVE_SMS',
34
- 'android.permission.SEND_SMS',
35
- ],
36
- storage: [
37
- 'android.permission.READ_EXTERNAL_STORAGE',
38
- 'android.permission.WRITE_EXTERNAL_STORAGE',
39
- ],
40
- };
41
- const normalizePermissionName = (name) => {
42
- return name.trim();
43
- };
44
- const resolvePermissions = async (options) => {
45
- if (options.command.kind === 'permission-all') {
46
- return adb.getDeclaredDangerousPermissions(options.adbId, options.bundleId);
47
- }
48
- const normalized = normalizePermissionName(options.command.permission);
49
- const aliases = ANDROID_PERMISSION_ALIASES[normalized];
50
- if (aliases) {
51
- return aliases;
52
- }
53
- return [normalized];
54
- };
55
- const snapshotPermissions = async (options) => {
56
- const granted = await Promise.all(options.permissions.map(async (permission) => {
57
- const isGranted = await adb.isPermissionGranted(options.adbId, options.bundleId, permission);
58
- return isGranted ? permission : null;
59
- }));
60
- return granted.filter((permission) => permission != null);
61
- };
62
- const applyPermissionSnapshot = async (options) => {
63
- const grantedSet = new Set(options.snapshot.previousGranted);
64
- const toGrant = options.snapshot.permissions.filter((permission) => grantedSet.has(permission));
65
- const toRevoke = options.snapshot.permissions.filter((permission) => !grantedSet.has(permission));
66
- if (toGrant.length > 0) {
67
- await adb.setPermissions(options.adbId, options.bundleId, toGrant, 'grant', {
68
- bestEffort: true,
69
- });
70
- }
71
- if (toRevoke.length > 0) {
72
- await adb.setPermissions(options.adbId, options.bundleId, toRevoke, 'reset', {
73
- bestEffort: true,
74
- });
75
- }
76
- };
77
- export const createAndroidDeviceState = (options) => {
78
- const outstandingMutations = new Map();
79
- let mutationCounter = 0;
80
- const rememberMutation = (snapshot) => {
81
- const id = `android-permissions-${++mutationCounter}`;
82
- const mutation = {
83
- id,
84
- snapshot,
85
- revert: async () => {
86
- await controller.permissions.revert(id);
87
- },
88
- };
89
- outstandingMutations.set(id, mutation);
90
- return mutation;
91
- };
92
- const controller = {
93
- permissions: {
94
- apply: async (command) => {
95
- const permissions = await resolvePermissions({
96
- adbId: options.adbId,
97
- bundleId: options.bundleId,
98
- command,
99
- });
100
- if (permissions.length === 0) {
101
- return { mutation: null };
102
- }
103
- const previousGranted = await snapshotPermissions({
104
- adbId: options.adbId,
105
- bundleId: options.bundleId,
106
- permissions,
107
- });
108
- await adb.setPermissions(options.adbId, options.bundleId, permissions, command.decision, { bestEffort: command.kind === 'permission-all' });
109
- const mutation = rememberMutation({
110
- kind: 'permission-set',
111
- permissions,
112
- previousGranted,
113
- });
114
- return { mutation };
115
- },
116
- revert: async (mutationId) => {
117
- const mutation = outstandingMutations.get(mutationId);
118
- if (!mutation) {
119
- return;
120
- }
121
- outstandingMutations.delete(mutationId);
122
- await applyPermissionSnapshot({
123
- adbId: options.adbId,
124
- bundleId: options.bundleId,
125
- snapshot: mutation.snapshot,
126
- });
127
- },
128
- resetOutstanding: async () => {
129
- const mutations = [...outstandingMutations.values()].reverse();
130
- outstandingMutations.clear();
131
- for (const mutation of mutations) {
132
- await applyPermissionSnapshot({
133
- adbId: options.adbId,
134
- bundleId: options.bundleId,
135
- snapshot: mutation.snapshot,
136
- });
137
- }
138
- },
139
- },
140
- };
141
- return controller;
142
- };