@react-native-harness/platform-android 1.3.0 → 1.4.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/__tests__/adb-wait.test.d.ts +2 -0
- package/dist/__tests__/adb-wait.test.d.ts.map +1 -0
- package/dist/__tests__/adb-wait.test.js +69 -0
- package/dist/__tests__/app-session.test.d.ts +2 -0
- package/dist/__tests__/app-session.test.d.ts.map +1 -0
- package/dist/__tests__/app-session.test.js +57 -0
- package/dist/__tests__/avd-config.test.js +58 -0
- package/dist/__tests__/crash-diagnostics.test.d.ts +2 -0
- package/dist/__tests__/crash-diagnostics.test.d.ts.map +1 -0
- package/dist/__tests__/crash-diagnostics.test.js +119 -0
- package/dist/__tests__/crash-reporter.test.d.ts +2 -0
- package/dist/__tests__/crash-reporter.test.d.ts.map +1 -0
- package/dist/__tests__/crash-reporter.test.js +150 -0
- package/dist/__tests__/emulator-startup.test.js +30 -3
- package/dist/__tests__/instance.test.js +139 -24
- package/dist/adb.d.ts +7 -1
- package/dist/adb.d.ts.map +1 -1
- package/dist/adb.js +72 -23
- package/dist/app-session.d.ts +26 -0
- package/dist/app-session.d.ts.map +1 -0
- package/dist/app-session.js +214 -0
- package/dist/avd-config.d.ts +1 -0
- package/dist/avd-config.d.ts.map +1 -1
- package/dist/avd-config.js +11 -0
- package/dist/crash-diagnostics.d.ts +40 -0
- package/dist/crash-diagnostics.d.ts.map +1 -0
- package/dist/crash-diagnostics.js +256 -0
- package/dist/crash-reporter.d.ts +12 -0
- package/dist/crash-reporter.d.ts.map +1 -0
- package/dist/crash-reporter.js +150 -0
- package/dist/emulator-startup.d.ts +23 -0
- package/dist/emulator-startup.d.ts.map +1 -1
- package/dist/emulator-startup.js +58 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/instance.d.ts +1 -1
- package/dist/instance.d.ts.map +1 -1
- package/dist/instance.js +51 -72
- package/dist/runner.d.ts +2 -2
- package/dist/runner.d.ts.map +1 -1
- package/dist/runner.js +1 -1
- package/dist/tsconfig.lib.tsbuildinfo +1 -1
- package/package.json +4 -4
- package/src/__tests__/adb-wait.test.ts +95 -0
- package/src/__tests__/app-session.test.ts +76 -0
- package/src/__tests__/avd-config.test.ts +70 -0
- package/src/__tests__/crash-diagnostics.test.ts +144 -0
- package/src/__tests__/crash-reporter.test.ts +167 -0
- package/src/__tests__/emulator-startup.test.ts +53 -6
- package/src/__tests__/instance.test.ts +226 -84
- package/src/adb.ts +129 -63
- package/src/app-session.ts +316 -0
- package/src/avd-config.ts +17 -0
- package/src/crash-diagnostics.ts +471 -0
- package/src/crash-reporter.ts +263 -0
- package/src/emulator-startup.ts +74 -0
- package/src/index.ts +1 -0
- package/src/instance.ts +84 -105
- package/src/runner.ts +11 -11
- package/test/setup.ts +1 -0
- package/src/app-monitor.ts +0 -577
|
@@ -5,6 +5,23 @@ import * as adb from '../adb.js';
|
|
|
5
5
|
import * as avdConfig from '../avd-config.js';
|
|
6
6
|
import * as sharedPrefs from '../shared-prefs.js';
|
|
7
7
|
import { HarnessAppPathError, HarnessEmulatorConfigError } from '../errors.js';
|
|
8
|
+
import * as emulatorStartup from '../emulator-startup.js';
|
|
9
|
+
const { getEmulatorCpuCores } = emulatorStartup;
|
|
10
|
+
const createLogcatProcess = (lines = []) => {
|
|
11
|
+
// Mirrors nano-spawn's real Subprocess, which is both a Promise and an
|
|
12
|
+
// async iterable, since app-session.ts relies on it being thenable.
|
|
13
|
+
const process = Object.assign(Promise.resolve(undefined), {
|
|
14
|
+
nodeChildProcess: Promise.resolve({
|
|
15
|
+
kill: vi.fn(),
|
|
16
|
+
}),
|
|
17
|
+
[Symbol.asyncIterator]: async function* () {
|
|
18
|
+
for (const line of lines) {
|
|
19
|
+
yield line;
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
});
|
|
23
|
+
return process;
|
|
24
|
+
};
|
|
8
25
|
const harnessConfig = {
|
|
9
26
|
metroPort: DEFAULT_METRO_PORT,
|
|
10
27
|
};
|
|
@@ -19,7 +36,9 @@ describe('Android platform instance', () => {
|
|
|
19
36
|
beforeEach(async () => {
|
|
20
37
|
vi.restoreAllMocks();
|
|
21
38
|
vi.unstubAllEnvs();
|
|
39
|
+
vi.useRealTimers();
|
|
22
40
|
vi.spyOn(await import('../environment.js'), 'ensureAndroidEmulatorAvailable').mockResolvedValue('/tmp/android-sdk');
|
|
41
|
+
vi.spyOn(emulatorStartup, 'warnIfEmulatorAccelerationUnavailable').mockResolvedValue(undefined);
|
|
23
42
|
});
|
|
24
43
|
it('reuses a running emulator and does not shut it down on dispose', async () => {
|
|
25
44
|
const ensureAndroidEmulatorEnvironment = vi
|
|
@@ -32,6 +51,7 @@ describe('Android platform instance', () => {
|
|
|
32
51
|
vi.spyOn(adb, 'reversePort').mockResolvedValue(undefined);
|
|
33
52
|
vi.spyOn(adb, 'setHideErrorDialogs').mockResolvedValue(undefined);
|
|
34
53
|
vi.spyOn(adb, 'getAppUid').mockResolvedValue(10234);
|
|
54
|
+
vi.spyOn(adb, 'getLogcatTimestamp').mockResolvedValue('01-01 00:00:00.000');
|
|
35
55
|
vi.spyOn(sharedPrefs, 'applyHarnessDebugHttpHost').mockResolvedValue(undefined);
|
|
36
56
|
vi.spyOn(sharedPrefs, 'clearHarnessDebugHttpHost').mockResolvedValue(undefined);
|
|
37
57
|
vi.spyOn(adb, 'stopApp').mockResolvedValue(undefined);
|
|
@@ -68,6 +88,7 @@ describe('Android platform instance', () => {
|
|
|
68
88
|
vi.spyOn(adb, 'reversePort').mockResolvedValue(undefined);
|
|
69
89
|
vi.spyOn(adb, 'setHideErrorDialogs').mockResolvedValue(undefined);
|
|
70
90
|
vi.spyOn(adb, 'getAppUid').mockResolvedValue(10234);
|
|
91
|
+
vi.spyOn(adb, 'getLogcatTimestamp').mockResolvedValue('01-01 00:00:00.000');
|
|
71
92
|
vi.spyOn(sharedPrefs, 'applyHarnessDebugHttpHost').mockResolvedValue(undefined);
|
|
72
93
|
vi.spyOn(sharedPrefs, 'clearHarnessDebugHttpHost').mockResolvedValue(undefined);
|
|
73
94
|
vi.spyOn(adb, 'stopApp').mockResolvedValue(undefined);
|
|
@@ -113,6 +134,7 @@ describe('Android platform instance', () => {
|
|
|
113
134
|
vi.spyOn(adb, 'reversePort').mockResolvedValue(undefined);
|
|
114
135
|
vi.spyOn(adb, 'setHideErrorDialogs').mockResolvedValue(undefined);
|
|
115
136
|
vi.spyOn(adb, 'getAppUid').mockResolvedValue(10234);
|
|
137
|
+
vi.spyOn(adb, 'getLogcatTimestamp').mockResolvedValue('01-01 00:00:00.000');
|
|
116
138
|
vi.spyOn(sharedPrefs, 'applyHarnessDebugHttpHost').mockResolvedValue(undefined);
|
|
117
139
|
await expect(getAndroidEmulatorPlatformInstance({
|
|
118
140
|
name: 'android',
|
|
@@ -145,6 +167,7 @@ describe('Android platform instance', () => {
|
|
|
145
167
|
hwDeviceName: 'pixel_8',
|
|
146
168
|
diskDataPartitionSize: '1G',
|
|
147
169
|
vmHeapSize: '1G',
|
|
170
|
+
hwCpuNcore: String(getEmulatorCpuCores()),
|
|
148
171
|
});
|
|
149
172
|
vi.spyOn(adb, 'startEmulator').mockResolvedValue(undefined);
|
|
150
173
|
vi.spyOn(adb, 'waitForBoot').mockResolvedValue('emulator-5554');
|
|
@@ -152,6 +175,7 @@ describe('Android platform instance', () => {
|
|
|
152
175
|
vi.spyOn(adb, 'reversePort').mockResolvedValue(undefined);
|
|
153
176
|
vi.spyOn(adb, 'setHideErrorDialogs').mockResolvedValue(undefined);
|
|
154
177
|
vi.spyOn(adb, 'getAppUid').mockResolvedValue(10234);
|
|
178
|
+
vi.spyOn(adb, 'getLogcatTimestamp').mockResolvedValue('01-01 00:00:00.000');
|
|
155
179
|
vi.spyOn(sharedPrefs, 'applyHarnessDebugHttpHost').mockResolvedValue(undefined);
|
|
156
180
|
await expect(getAndroidEmulatorPlatformInstance({
|
|
157
181
|
name: 'android',
|
|
@@ -196,6 +220,7 @@ describe('Android platform instance', () => {
|
|
|
196
220
|
vi.spyOn(adb, 'reversePort').mockResolvedValue(undefined);
|
|
197
221
|
vi.spyOn(adb, 'setHideErrorDialogs').mockResolvedValue(undefined);
|
|
198
222
|
vi.spyOn(adb, 'getAppUid').mockResolvedValue(10234);
|
|
223
|
+
vi.spyOn(adb, 'getLogcatTimestamp').mockResolvedValue('01-01 00:00:00.000');
|
|
199
224
|
vi.spyOn(sharedPrefs, 'applyHarnessDebugHttpHost').mockResolvedValue(undefined);
|
|
200
225
|
await expect(getAndroidEmulatorPlatformInstance({
|
|
201
226
|
name: 'android',
|
|
@@ -238,6 +263,7 @@ describe('Android platform instance', () => {
|
|
|
238
263
|
vi.spyOn(adb, 'reversePort').mockResolvedValue(undefined);
|
|
239
264
|
vi.spyOn(adb, 'setHideErrorDialogs').mockResolvedValue(undefined);
|
|
240
265
|
vi.spyOn(adb, 'getAppUid').mockResolvedValue(10234);
|
|
266
|
+
vi.spyOn(adb, 'getLogcatTimestamp').mockResolvedValue('01-01 00:00:00.000');
|
|
241
267
|
vi.spyOn(sharedPrefs, 'applyHarnessDebugHttpHost').mockResolvedValue(undefined);
|
|
242
268
|
await expect(getAndroidEmulatorPlatformInstance({
|
|
243
269
|
name: 'android',
|
|
@@ -315,7 +341,8 @@ describe('Android platform instance', () => {
|
|
|
315
341
|
activityName: '.MainActivity',
|
|
316
342
|
}, harnessConfig, init)).rejects.toBeInstanceOf(HarnessEmulatorConfigError);
|
|
317
343
|
});
|
|
318
|
-
it('
|
|
344
|
+
it('creates a real emulator app session when native crash detection is disabled', async () => {
|
|
345
|
+
vi.useFakeTimers();
|
|
319
346
|
vi.spyOn(await import('../environment.js'), 'ensureAndroidEmulatorEnvironment').mockResolvedValue('/tmp/android-sdk');
|
|
320
347
|
vi.spyOn(adb, 'getDeviceIds').mockResolvedValue(['emulator-5554']);
|
|
321
348
|
vi.spyOn(adb, 'getEmulatorName').mockResolvedValue('Pixel_8_API_35');
|
|
@@ -324,7 +351,14 @@ describe('Android platform instance', () => {
|
|
|
324
351
|
vi.spyOn(adb, 'reversePort').mockResolvedValue(undefined);
|
|
325
352
|
vi.spyOn(adb, 'setHideErrorDialogs').mockResolvedValue(undefined);
|
|
326
353
|
vi.spyOn(adb, 'getAppUid').mockResolvedValue(10234);
|
|
354
|
+
vi.spyOn(adb, 'getLogcatTimestamp').mockResolvedValue('01-01 00:00:00.000');
|
|
327
355
|
vi.spyOn(sharedPrefs, 'applyHarnessDebugHttpHost').mockResolvedValue(undefined);
|
|
356
|
+
const stopApp = vi.spyOn(adb, 'stopApp').mockResolvedValue(undefined);
|
|
357
|
+
const startApp = vi.spyOn(adb, 'startApp').mockResolvedValue(undefined);
|
|
358
|
+
const getAppPid = vi.spyOn(adb, 'getAppPid').mockResolvedValue(4321);
|
|
359
|
+
const startLogcat = vi
|
|
360
|
+
.spyOn(adb, 'startLogcat')
|
|
361
|
+
.mockReturnValue(createLogcatProcess());
|
|
328
362
|
const instance = await getAndroidEmulatorPlatformInstance({
|
|
329
363
|
name: 'android',
|
|
330
364
|
device: {
|
|
@@ -341,14 +375,84 @@ describe('Android platform instance', () => {
|
|
|
341
375
|
activityName: '.MainActivity',
|
|
342
376
|
}, harnessConfigWithoutNativeCrashDetection, init);
|
|
343
377
|
const listener = vi.fn();
|
|
344
|
-
const
|
|
345
|
-
|
|
346
|
-
await
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
378
|
+
const appSession = await instance.createAppSession();
|
|
379
|
+
appSession.addListener(listener);
|
|
380
|
+
await vi.advanceTimersByTimeAsync(0);
|
|
381
|
+
expect(startLogcat).toHaveBeenCalledWith('emulator-5554', [
|
|
382
|
+
'logcat',
|
|
383
|
+
'-v',
|
|
384
|
+
'threadtime',
|
|
385
|
+
'-b',
|
|
386
|
+
'crash',
|
|
387
|
+
'--uid=10234',
|
|
388
|
+
'-T',
|
|
389
|
+
'01-01 00:00:00.000',
|
|
390
|
+
], { signal: expect.any(AbortSignal) });
|
|
391
|
+
expect(startLogcat.mock.invocationCallOrder[0]).toBeLessThan(startApp.mock.invocationCallOrder[0]);
|
|
392
|
+
await expect(appSession.getState()).resolves.toEqual({
|
|
393
|
+
status: 'running',
|
|
394
|
+
pid: 4321,
|
|
395
|
+
});
|
|
396
|
+
expect(getAppPid).toHaveBeenCalled();
|
|
397
|
+
expect(listener).not.toHaveBeenCalled();
|
|
398
|
+
expect(appSession.removeListener(listener)).toBeUndefined();
|
|
399
|
+
await expect(appSession.dispose()).resolves.toBeUndefined();
|
|
400
|
+
expect(stopApp).toHaveBeenCalled();
|
|
350
401
|
});
|
|
351
|
-
it('
|
|
402
|
+
it('reports an early Android crash from logcat before a PID poll succeeds', async () => {
|
|
403
|
+
vi.useFakeTimers();
|
|
404
|
+
vi.spyOn(await import('../environment.js'), 'ensureAndroidEmulatorEnvironment').mockResolvedValue('/tmp/android-sdk');
|
|
405
|
+
vi.spyOn(adb, 'getDeviceIds').mockResolvedValue(['emulator-5554']);
|
|
406
|
+
vi.spyOn(adb, 'getEmulatorName').mockResolvedValue('Pixel_8_API_35');
|
|
407
|
+
vi.spyOn(adb, 'waitForBoot').mockResolvedValue('emulator-5554');
|
|
408
|
+
vi.spyOn(adb, 'isAppInstalled').mockResolvedValue(true);
|
|
409
|
+
vi.spyOn(adb, 'reversePort').mockResolvedValue(undefined);
|
|
410
|
+
vi.spyOn(adb, 'setHideErrorDialogs').mockResolvedValue(undefined);
|
|
411
|
+
vi.spyOn(adb, 'getAppUid').mockResolvedValue(10234);
|
|
412
|
+
vi.spyOn(adb, 'getLogcatTimestamp').mockResolvedValue('01-01 00:00:00.000');
|
|
413
|
+
vi.spyOn(sharedPrefs, 'applyHarnessDebugHttpHost').mockResolvedValue(undefined);
|
|
414
|
+
vi.spyOn(adb, 'stopApp').mockResolvedValue(undefined);
|
|
415
|
+
vi.spyOn(adb, 'startApp').mockResolvedValue(undefined);
|
|
416
|
+
vi.spyOn(adb, 'getAppPid').mockResolvedValue(null);
|
|
417
|
+
vi.spyOn(adb, 'startLogcat').mockReturnValue(createLogcatProcess([
|
|
418
|
+
'--------- beginning of crash',
|
|
419
|
+
'Process: com.harnessplayground, PID: 7777',
|
|
420
|
+
'FATAL EXCEPTION: main',
|
|
421
|
+
]));
|
|
422
|
+
const instance = await getAndroidEmulatorPlatformInstance({
|
|
423
|
+
name: 'android',
|
|
424
|
+
device: {
|
|
425
|
+
type: 'emulator',
|
|
426
|
+
name: 'Pixel_8_API_35',
|
|
427
|
+
avd: {
|
|
428
|
+
apiLevel: 35,
|
|
429
|
+
profile: 'pixel_8',
|
|
430
|
+
diskSize: '1G',
|
|
431
|
+
heapSize: '1G',
|
|
432
|
+
},
|
|
433
|
+
},
|
|
434
|
+
bundleId: 'com.harnessplayground',
|
|
435
|
+
activityName: '.MainActivity',
|
|
436
|
+
}, harnessConfig, init);
|
|
437
|
+
const listener = vi.fn();
|
|
438
|
+
const appSession = await instance.createAppSession();
|
|
439
|
+
appSession.addListener(listener);
|
|
440
|
+
await vi.advanceTimersByTimeAsync(0);
|
|
441
|
+
expect(appSession.getLogs().map((entry) => entry.line)).toEqual([
|
|
442
|
+
'--------- beginning of crash',
|
|
443
|
+
'Process: com.harnessplayground, PID: 7777',
|
|
444
|
+
'FATAL EXCEPTION: main',
|
|
445
|
+
]);
|
|
446
|
+
await expect(appSession.getState()).resolves.toMatchObject({
|
|
447
|
+
status: 'exited',
|
|
448
|
+
pid: 7777,
|
|
449
|
+
reason: 'observed-exit',
|
|
450
|
+
});
|
|
451
|
+
expect(listener).toHaveBeenCalledWith({ type: 'app_exited' });
|
|
452
|
+
await appSession.dispose();
|
|
453
|
+
});
|
|
454
|
+
it('creates a real physical-device app session when native crash detection is disabled', async () => {
|
|
455
|
+
vi.useFakeTimers();
|
|
352
456
|
vi.spyOn(adb, 'getDeviceIds').mockResolvedValue(['012345']);
|
|
353
457
|
vi.spyOn(adb, 'getDeviceInfo').mockResolvedValue({
|
|
354
458
|
manufacturer: 'motorola',
|
|
@@ -358,17 +462,14 @@ describe('Android platform instance', () => {
|
|
|
358
462
|
vi.spyOn(adb, 'reversePort').mockResolvedValue(undefined);
|
|
359
463
|
vi.spyOn(adb, 'setHideErrorDialogs').mockResolvedValue(undefined);
|
|
360
464
|
vi.spyOn(adb, 'getAppUid').mockResolvedValue(10234);
|
|
465
|
+
vi.spyOn(adb, 'getLogcatTimestamp').mockResolvedValue('01-01 00:00:00.000');
|
|
361
466
|
vi.spyOn(sharedPrefs, 'applyHarnessDebugHttpHost').mockResolvedValue(undefined);
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
},
|
|
369
|
-
bundleId: 'com.harnessplayground',
|
|
370
|
-
activityName: '.MainActivity',
|
|
371
|
-
}, harnessConfigWithoutNativeCrashDetection)).resolves.toBeDefined();
|
|
467
|
+
vi.spyOn(adb, 'stopApp').mockResolvedValue(undefined);
|
|
468
|
+
vi.spyOn(adb, 'startApp').mockResolvedValue(undefined);
|
|
469
|
+
vi.spyOn(adb, 'getAppPid').mockResolvedValue(8765);
|
|
470
|
+
const startLogcat = vi
|
|
471
|
+
.spyOn(adb, 'startLogcat')
|
|
472
|
+
.mockReturnValue(createLogcatProcess());
|
|
372
473
|
const instance = await getAndroidPhysicalDevicePlatformInstance({
|
|
373
474
|
name: 'android-device',
|
|
374
475
|
device: {
|
|
@@ -380,12 +481,26 @@ describe('Android platform instance', () => {
|
|
|
380
481
|
activityName: '.MainActivity',
|
|
381
482
|
}, harnessConfigWithoutNativeCrashDetection);
|
|
382
483
|
const listener = vi.fn();
|
|
383
|
-
const
|
|
384
|
-
|
|
385
|
-
await
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
484
|
+
const appSession = await instance.createAppSession();
|
|
485
|
+
appSession.addListener(listener);
|
|
486
|
+
await vi.advanceTimersByTimeAsync(0);
|
|
487
|
+
expect(startLogcat).toHaveBeenCalledWith('012345', [
|
|
488
|
+
'logcat',
|
|
489
|
+
'-v',
|
|
490
|
+
'threadtime',
|
|
491
|
+
'-b',
|
|
492
|
+
'crash',
|
|
493
|
+
'--uid=10234',
|
|
494
|
+
'-T',
|
|
495
|
+
'01-01 00:00:00.000',
|
|
496
|
+
], { signal: expect.any(AbortSignal) });
|
|
497
|
+
await expect(appSession.getState()).resolves.toEqual({
|
|
498
|
+
status: 'running',
|
|
499
|
+
pid: 8765,
|
|
500
|
+
});
|
|
501
|
+
expect(listener).not.toHaveBeenCalled();
|
|
502
|
+
expect(appSession.removeListener(listener)).toBeUndefined();
|
|
503
|
+
await expect(appSession.dispose()).resolves.toBeUndefined();
|
|
389
504
|
});
|
|
390
505
|
it('grants permissions when permissions are enabled for emulator', async () => {
|
|
391
506
|
vi.spyOn(await import('../environment.js'), 'ensureAndroidEmulatorEnvironment').mockResolvedValue('/tmp/android-sdk');
|
package/dist/adb.d.ts
CHANGED
|
@@ -40,10 +40,16 @@ export declare const waitForEmulator: (name: string, signal: AbortSignal) => Pro
|
|
|
40
40
|
export declare const waitForEmulatorDisconnect: (adbId: string, signal: AbortSignal) => Promise<void>;
|
|
41
41
|
export declare const waitForBoot: (name: string, signal: AbortSignal) => Promise<string>;
|
|
42
42
|
export declare const isAppRunning: (adbId: string, bundleId: string) => Promise<boolean>;
|
|
43
|
+
export declare const getAppPid: (adbId: string, bundleId: string) => Promise<number | null>;
|
|
43
44
|
export declare const getAppUid: (adbId: string, bundleId: string) => Promise<number>;
|
|
44
45
|
export declare const setHideErrorDialogs: (adbId: string, hide: boolean) => Promise<void>;
|
|
45
46
|
export declare const getLogcatTimestamp: (adbId: string) => Promise<string>;
|
|
46
|
-
export declare const startLogcat: (adbId: string, args: readonly string[]
|
|
47
|
+
export declare const startLogcat: (adbId: string, args: readonly string[], options?: {
|
|
48
|
+
signal?: AbortSignal;
|
|
49
|
+
}) => Subprocess;
|
|
50
|
+
export declare const DROPBOX_CRASH_TAGS: readonly ["data_app_crash", "data_app_native_crash"];
|
|
51
|
+
export declare const getDropboxPrint: (adbId: string, tags?: readonly string[]) => Promise<string>;
|
|
52
|
+
export declare const getActivityExitInfo: (adbId: string, bundleId: string) => Promise<string>;
|
|
47
53
|
export declare const getAvds: () => Promise<string[]>;
|
|
48
54
|
export type AdbDevice = {
|
|
49
55
|
id: 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,30 +1,27 @@
|
|
|
1
|
-
import { logger, spawn, SubprocessError, } from '@react-native-harness/tools';
|
|
1
|
+
import { delay, logger, spawn, 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';
|
|
5
5
|
import { ensureAndroidEmulatorAvailable, ensureAndroidSdkPackages, getAdbBinaryPath, getAndroidSystemImagePackage, getAvdManagerBinaryPath, getEmulatorBinaryPath, getHostAndroidSystemImageArch, getRequiredAndroidSdkPackages, } from './environment.js';
|
|
6
|
-
import { getEmulatorStartupArgs, } from './emulator-startup.js';
|
|
6
|
+
import { getEmulatorCpuCores, getEmulatorStartupArgs, } from './emulator-startup.js';
|
|
7
7
|
import { AdbAppNotInstalledError, AdbPermissionGrantError, } from './adb-errors.js';
|
|
8
|
-
const wait = async (ms) => {
|
|
9
|
-
await new Promise((resolve) => {
|
|
10
|
-
setTimeout(resolve, ms);
|
|
11
|
-
});
|
|
12
|
-
};
|
|
13
|
-
const waitForAbort = (signal) => {
|
|
14
|
-
if (signal.aborted) {
|
|
15
|
-
return Promise.reject(signal.reason);
|
|
16
|
-
}
|
|
17
|
-
return new Promise((_, reject) => {
|
|
18
|
-
signal.addEventListener('abort', () => {
|
|
19
|
-
reject(signal.reason);
|
|
20
|
-
}, { once: true });
|
|
21
|
-
});
|
|
22
|
-
};
|
|
23
8
|
const waitWithSignal = async (ms, signal) => {
|
|
24
9
|
if (signal.aborted) {
|
|
25
10
|
throw signal.reason;
|
|
26
11
|
}
|
|
27
|
-
|
|
12
|
+
// Both sides of this race must be torn down explicitly when they lose:
|
|
13
|
+
// an uncancelled timer keeps a ref'd Timeout alive, and an uncancelled
|
|
14
|
+
// abort wait leaks a listener on `signal`, which is a session-lifetime
|
|
15
|
+
// signal here and gets polled against once per second.
|
|
16
|
+
const timer = delay(ms);
|
|
17
|
+
const abort = waitForAbort(signal);
|
|
18
|
+
try {
|
|
19
|
+
await Promise.race([timer.promise, abort.promise]);
|
|
20
|
+
}
|
|
21
|
+
finally {
|
|
22
|
+
timer.cancel();
|
|
23
|
+
abort.cancel();
|
|
24
|
+
}
|
|
28
25
|
};
|
|
29
26
|
const EMULATOR_STARTUP_OBSERVATION_TIMEOUT_MS = 5000;
|
|
30
27
|
const EMULATOR_OUTPUT_BUFFER_LIMIT = 16 * 1024;
|
|
@@ -311,9 +308,14 @@ export const createAvd = async ({ name, apiLevel, profile, diskSize, heapSize, }
|
|
|
311
308
|
]);
|
|
312
309
|
await ensureAvdIniExists({ name, apiLevel });
|
|
313
310
|
const configPath = await ensureAvdConfigExists(name);
|
|
311
|
+
// hw.cpu.ncore is baked into config.ini (rather than passed as a `-cores`
|
|
312
|
+
// boot flag) so it's part of the AVD's persisted hardware profile: boot
|
|
313
|
+
// snapshots require an identical hardware config to load, so the vCPU
|
|
314
|
+
// count must be fixed at AVD-creation time and stay consistent across
|
|
315
|
+
// boots.
|
|
314
316
|
await spawn('bash', [
|
|
315
317
|
'-lc',
|
|
316
|
-
`printf '%s\n%s\n' 'disk.dataPartition.size=${diskSize}' 'vm.heapSize=${heapSize}' >> "${configPath}"`,
|
|
318
|
+
`printf '%s\n%s\n%s\n' 'disk.dataPartition.size=${diskSize}' 'vm.heapSize=${heapSize}' 'hw.cpu.ncore=${getEmulatorCpuCores()}' >> "${configPath}"`,
|
|
317
319
|
]);
|
|
318
320
|
};
|
|
319
321
|
export const deleteAvd = async (name) => {
|
|
@@ -375,11 +377,17 @@ export const startEmulator = async (name, mode = 'default-boot') => {
|
|
|
375
377
|
}
|
|
376
378
|
throw error;
|
|
377
379
|
});
|
|
378
|
-
const
|
|
380
|
+
const observationTimer = delay(EMULATOR_STARTUP_OBSERVATION_TIMEOUT_MS);
|
|
381
|
+
const observationTimeout = observationTimer.promise.then(() => 'timeout');
|
|
379
382
|
try {
|
|
380
383
|
await Promise.race([earlyExit, observedBoot, observationTimeout]);
|
|
381
384
|
}
|
|
382
385
|
finally {
|
|
386
|
+
// If `earlyExit` or `observedBoot` won the race, this timer would
|
|
387
|
+
// otherwise sit ref'd in the event loop for the rest of its 5s;
|
|
388
|
+
// cancelling here is a no-op on the happy "proceed detached" path
|
|
389
|
+
// where the timer itself won.
|
|
390
|
+
observationTimer.cancel();
|
|
383
391
|
cleanup();
|
|
384
392
|
}
|
|
385
393
|
childProcess.stdout?.destroy();
|
|
@@ -432,6 +440,9 @@ export const waitForBoot = async (name, signal) => {
|
|
|
432
440
|
throw signal.reason;
|
|
433
441
|
};
|
|
434
442
|
export const isAppRunning = async (adbId, bundleId) => {
|
|
443
|
+
return (await getAppPid(adbId, bundleId)) != null;
|
|
444
|
+
};
|
|
445
|
+
export const getAppPid = async (adbId, bundleId) => {
|
|
435
446
|
try {
|
|
436
447
|
const { stdout } = await spawn(getAdbBinaryPath(), [
|
|
437
448
|
'-s',
|
|
@@ -440,11 +451,15 @@ export const isAppRunning = async (adbId, bundleId) => {
|
|
|
440
451
|
'pidof',
|
|
441
452
|
bundleId,
|
|
442
453
|
]);
|
|
443
|
-
|
|
454
|
+
const pid = stdout
|
|
455
|
+
.trim()
|
|
456
|
+
.split(/\s+/)
|
|
457
|
+
.find((value) => value !== '');
|
|
458
|
+
return pid ? Number(pid) : null;
|
|
444
459
|
}
|
|
445
460
|
catch (error) {
|
|
446
461
|
if (error instanceof SubprocessError && error.exitCode === 1) {
|
|
447
|
-
return
|
|
462
|
+
return null;
|
|
448
463
|
}
|
|
449
464
|
throw error;
|
|
450
465
|
}
|
|
@@ -490,10 +505,44 @@ export const getLogcatTimestamp = async (adbId) => {
|
|
|
490
505
|
]);
|
|
491
506
|
return stdout.trim().replace(/^'+|'+$/g, '');
|
|
492
507
|
};
|
|
493
|
-
export const startLogcat = (adbId, args) => spawn(getAdbBinaryPath(), ['-s', adbId, ...args], {
|
|
508
|
+
export const startLogcat = (adbId, args, options) => spawn(getAdbBinaryPath(), ['-s', adbId, ...args], {
|
|
494
509
|
stdout: 'pipe',
|
|
495
510
|
stderr: 'pipe',
|
|
511
|
+
signal: options?.signal,
|
|
496
512
|
});
|
|
513
|
+
export const DROPBOX_CRASH_TAGS = [
|
|
514
|
+
'data_app_crash',
|
|
515
|
+
'data_app_native_crash',
|
|
516
|
+
];
|
|
517
|
+
export const getDropboxPrint = async (adbId, tags = DROPBOX_CRASH_TAGS) => {
|
|
518
|
+
// Android treats multiple args after --print as one search string, so each
|
|
519
|
+
// tag must be queried separately and merged on the host.
|
|
520
|
+
const outputs = await Promise.all(tags.map(async (tag) => {
|
|
521
|
+
const { stdout } = await spawn(getAdbBinaryPath(), [
|
|
522
|
+
'-s',
|
|
523
|
+
adbId,
|
|
524
|
+
'shell',
|
|
525
|
+
'dumpsys',
|
|
526
|
+
'dropbox',
|
|
527
|
+
'--print',
|
|
528
|
+
tag,
|
|
529
|
+
]);
|
|
530
|
+
return stdout;
|
|
531
|
+
}));
|
|
532
|
+
return outputs.join('\n');
|
|
533
|
+
};
|
|
534
|
+
export const getActivityExitInfo = async (adbId, bundleId) => {
|
|
535
|
+
const { stdout } = await spawn(getAdbBinaryPath(), [
|
|
536
|
+
'-s',
|
|
537
|
+
adbId,
|
|
538
|
+
'shell',
|
|
539
|
+
'dumpsys',
|
|
540
|
+
'activity',
|
|
541
|
+
'exit-info',
|
|
542
|
+
bundleId,
|
|
543
|
+
]);
|
|
544
|
+
return stdout;
|
|
545
|
+
};
|
|
497
546
|
export const getAvds = async () => {
|
|
498
547
|
try {
|
|
499
548
|
const emulatorBinaryPath = await ensureEmulatorInstalled();
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { type AppSession, type CrashArtifactWriter } from '@react-native-harness/platforms';
|
|
2
|
+
import { type Subprocess } from '@react-native-harness/tools';
|
|
3
|
+
type CreateAndroidAppSessionOptions = {
|
|
4
|
+
appUid: number;
|
|
5
|
+
bundleId: string;
|
|
6
|
+
startApp: () => Promise<void>;
|
|
7
|
+
stopApp: () => Promise<void>;
|
|
8
|
+
getAppPid: () => Promise<number | null>;
|
|
9
|
+
getLogcatTimestamp: () => Promise<string>;
|
|
10
|
+
startLogcat: (args: readonly string[], options: {
|
|
11
|
+
signal: AbortSignal;
|
|
12
|
+
}) => Subprocess;
|
|
13
|
+
getDropboxOutput?: () => Promise<string>;
|
|
14
|
+
getExitInfo?: () => Promise<string>;
|
|
15
|
+
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
|
+
};
|
|
24
|
+
export declare const createAndroidAppSession: ({ appUid, bundleId, startApp, stopApp, getAppPid, getLogcatTimestamp, startLogcat, getDropboxOutput, getExitInfo, crashArtifactWriter, signal, }: CreateAndroidAppSessionOptions) => Promise<AppSession>;
|
|
25
|
+
export {};
|
|
26
|
+
//# sourceMappingURL=app-session.d.ts.map
|
|
@@ -0,0 +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"}
|