@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.
Files changed (77) hide show
  1. package/dist/adb.d.ts +2 -4
  2. package/dist/adb.d.ts.map +1 -1
  3. package/dist/adb.js +2 -3
  4. package/dist/app-session.d.ts +3 -12
  5. package/dist/app-session.d.ts.map +1 -1
  6. package/dist/app-session.js +23 -33
  7. package/dist/instance.d.ts.map +1 -1
  8. package/dist/instance.js +2 -4
  9. package/package.json +4 -4
  10. package/src/adb.ts +5 -6
  11. package/src/app-session.ts +14 -31
  12. package/src/instance.ts +2 -4
  13. package/dist/__tests__/adb-id.test.d.ts +0 -2
  14. package/dist/__tests__/adb-id.test.d.ts.map +0 -1
  15. package/dist/__tests__/adb-id.test.js +0 -41
  16. package/dist/__tests__/adb-wait.test.d.ts +0 -2
  17. package/dist/__tests__/adb-wait.test.d.ts.map +0 -1
  18. package/dist/__tests__/adb-wait.test.js +0 -69
  19. package/dist/__tests__/adb.test.d.ts +0 -2
  20. package/dist/__tests__/adb.test.d.ts.map +0 -1
  21. package/dist/__tests__/adb.test.js +0 -552
  22. package/dist/__tests__/app-monitor.test.d.ts +0 -2
  23. package/dist/__tests__/app-monitor.test.d.ts.map +0 -1
  24. package/dist/__tests__/app-monitor.test.js +0 -257
  25. package/dist/__tests__/app-session.test.d.ts +0 -2
  26. package/dist/__tests__/app-session.test.d.ts.map +0 -1
  27. package/dist/__tests__/app-session.test.js +0 -57
  28. package/dist/__tests__/avd-config.test.d.ts +0 -2
  29. package/dist/__tests__/avd-config.test.d.ts.map +0 -1
  30. package/dist/__tests__/avd-config.test.js +0 -232
  31. package/dist/__tests__/ci-action.test.d.ts +0 -2
  32. package/dist/__tests__/ci-action.test.d.ts.map +0 -1
  33. package/dist/__tests__/ci-action.test.js +0 -46
  34. package/dist/__tests__/crash-diagnostics.test.d.ts +0 -2
  35. package/dist/__tests__/crash-diagnostics.test.d.ts.map +0 -1
  36. package/dist/__tests__/crash-diagnostics.test.js +0 -119
  37. package/dist/__tests__/crash-parser.test.d.ts +0 -2
  38. package/dist/__tests__/crash-parser.test.d.ts.map +0 -1
  39. package/dist/__tests__/crash-parser.test.js +0 -45
  40. package/dist/__tests__/crash-reporter.test.d.ts +0 -2
  41. package/dist/__tests__/crash-reporter.test.d.ts.map +0 -1
  42. package/dist/__tests__/crash-reporter.test.js +0 -150
  43. package/dist/__tests__/emulator-startup.test.d.ts +0 -2
  44. package/dist/__tests__/emulator-startup.test.d.ts.map +0 -1
  45. package/dist/__tests__/emulator-startup.test.js +0 -47
  46. package/dist/__tests__/environment.test.d.ts +0 -2
  47. package/dist/__tests__/environment.test.d.ts.map +0 -1
  48. package/dist/__tests__/environment.test.js +0 -117
  49. package/dist/__tests__/instance.test.d.ts +0 -2
  50. package/dist/__tests__/instance.test.d.ts.map +0 -1
  51. package/dist/__tests__/instance.test.js +0 -599
  52. package/dist/__tests__/shared-prefs.test.d.ts +0 -2
  53. package/dist/__tests__/shared-prefs.test.d.ts.map +0 -1
  54. package/dist/__tests__/shared-prefs.test.js +0 -87
  55. package/dist/__tests__/targets.test.d.ts +0 -2
  56. package/dist/__tests__/targets.test.d.ts.map +0 -1
  57. package/dist/__tests__/targets.test.js +0 -49
  58. package/dist/app-monitor.d.ts +0 -13
  59. package/dist/app-monitor.d.ts.map +0 -1
  60. package/dist/app-monitor.js +0 -376
  61. package/dist/device-state.d.ts +0 -6
  62. package/dist/device-state.d.ts.map +0 -1
  63. package/dist/device-state.js +0 -142
  64. package/dist/tsconfig.lib.tsbuildinfo +0 -1
  65. package/src/__tests__/adb-id.test.ts +0 -51
  66. package/src/__tests__/adb-wait.test.ts +0 -95
  67. package/src/__tests__/app-session.test.ts +0 -76
  68. package/src/__tests__/avd-config.test.ts +0 -276
  69. package/src/__tests__/ci-action.test.ts +0 -81
  70. package/src/__tests__/crash-diagnostics.test.ts +0 -144
  71. package/src/__tests__/crash-parser.test.ts +0 -52
  72. package/src/__tests__/crash-reporter.test.ts +0 -167
  73. package/src/__tests__/emulator-startup.test.ts +0 -82
  74. package/src/__tests__/instance.test.ts +0 -844
  75. package/src/__tests__/shared-prefs.test.ts +0 -144
  76. package/src/__tests__/targets.test.ts +0 -53
  77. 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 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.0",
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.0",
23
- "@react-native-harness/tools": "1.4.0",
24
- "@react-native-harness/platforms": "1.4.0"
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,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=adb-id.test.d.ts.map
@@ -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,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=adb-wait.test.d.ts.map
@@ -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,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=adb.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"adb.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/adb.test.ts"],"names":[],"mappings":""}