@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
|
@@ -6,6 +6,9 @@ import {
|
|
|
6
6
|
resolveAvdCachingEnabled,
|
|
7
7
|
} from '../avd-config.js';
|
|
8
8
|
import { AndroidPlatformConfigSchema } from '../config.js';
|
|
9
|
+
import { getEmulatorCpuCores } from '../emulator-startup.js';
|
|
10
|
+
|
|
11
|
+
const emulatorCpuCores = getEmulatorCpuCores();
|
|
9
12
|
|
|
10
13
|
describe('AVD config helpers', () => {
|
|
11
14
|
it('parses snapshot config from Android schema', () => {
|
|
@@ -73,8 +76,11 @@ abi.type=x86_64
|
|
|
73
76
|
hw.device.name=pixel_8
|
|
74
77
|
disk.dataPartition.size=1G
|
|
75
78
|
vm.heapSize=512M
|
|
79
|
+
hw.cpu.ncore=${emulatorCpuCores}
|
|
76
80
|
`);
|
|
77
81
|
|
|
82
|
+
expect(avdConfig.hwCpuNcore).toBe(String(emulatorCpuCores));
|
|
83
|
+
|
|
78
84
|
expect(
|
|
79
85
|
isAvdCompatible({
|
|
80
86
|
emulator: {
|
|
@@ -93,6 +99,69 @@ vm.heapSize=512M
|
|
|
93
99
|
).toEqual({ compatible: true });
|
|
94
100
|
});
|
|
95
101
|
|
|
102
|
+
it('rejects AVDs missing hw.cpu.ncore', () => {
|
|
103
|
+
const avdConfig = parseAvdConfig(`
|
|
104
|
+
image.sysdir.1=system-images/android-35/default/x86_64/
|
|
105
|
+
abi.type=x86_64
|
|
106
|
+
hw.device.name=pixel_8
|
|
107
|
+
disk.dataPartition.size=1G
|
|
108
|
+
vm.heapSize=512M
|
|
109
|
+
`);
|
|
110
|
+
|
|
111
|
+
expect(
|
|
112
|
+
isAvdCompatible({
|
|
113
|
+
emulator: {
|
|
114
|
+
type: 'emulator',
|
|
115
|
+
name: 'Pixel_8_API_35',
|
|
116
|
+
avd: {
|
|
117
|
+
apiLevel: 35,
|
|
118
|
+
profile: 'pixel_8',
|
|
119
|
+
diskSize: '1G',
|
|
120
|
+
heapSize: '512M',
|
|
121
|
+
},
|
|
122
|
+
},
|
|
123
|
+
avdConfig,
|
|
124
|
+
hostArch: 'x86_64',
|
|
125
|
+
})
|
|
126
|
+
).toMatchObject({
|
|
127
|
+
compatible: false,
|
|
128
|
+
reason: `CPU core count mismatch: expected ${emulatorCpuCores}, got missing.`,
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it('rejects AVDs with a different hw.cpu.ncore', () => {
|
|
133
|
+
const avdConfig = parseAvdConfig(`
|
|
134
|
+
image.sysdir.1=system-images/android-35/default/x86_64/
|
|
135
|
+
abi.type=x86_64
|
|
136
|
+
hw.device.name=pixel_8
|
|
137
|
+
disk.dataPartition.size=1G
|
|
138
|
+
vm.heapSize=512M
|
|
139
|
+
hw.cpu.ncore=${emulatorCpuCores === 4 ? 3 : 4}
|
|
140
|
+
`);
|
|
141
|
+
|
|
142
|
+
expect(
|
|
143
|
+
isAvdCompatible({
|
|
144
|
+
emulator: {
|
|
145
|
+
type: 'emulator',
|
|
146
|
+
name: 'Pixel_8_API_35',
|
|
147
|
+
avd: {
|
|
148
|
+
apiLevel: 35,
|
|
149
|
+
profile: 'pixel_8',
|
|
150
|
+
diskSize: '1G',
|
|
151
|
+
heapSize: '512M',
|
|
152
|
+
},
|
|
153
|
+
},
|
|
154
|
+
avdConfig,
|
|
155
|
+
hostArch: 'x86_64',
|
|
156
|
+
})
|
|
157
|
+
).toMatchObject({
|
|
158
|
+
compatible: false,
|
|
159
|
+
reason: `CPU core count mismatch: expected ${emulatorCpuCores}, got ${
|
|
160
|
+
emulatorCpuCores === 4 ? 3 : 4
|
|
161
|
+
}.`,
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
|
|
96
165
|
it('accepts disk partition sizes rewritten to bytes', () => {
|
|
97
166
|
const avdConfig = parseAvdConfig(`
|
|
98
167
|
image.sysdir.1=system-images/android-35/default/x86_64/
|
|
@@ -100,6 +169,7 @@ abi.type=x86_64
|
|
|
100
169
|
hw.device.name=pixel_8
|
|
101
170
|
disk.dataPartition.size=6442450944
|
|
102
171
|
vm.heapSize=512M
|
|
172
|
+
hw.cpu.ncore=${emulatorCpuCores}
|
|
103
173
|
`);
|
|
104
174
|
|
|
105
175
|
expect(
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import {
|
|
3
|
+
collectDropboxArtifacts,
|
|
4
|
+
filterExitInfo,
|
|
5
|
+
getBestDropboxArtifact,
|
|
6
|
+
parseDropboxOutput,
|
|
7
|
+
} from '../crash-diagnostics.js';
|
|
8
|
+
|
|
9
|
+
const javaCrashDropbox = `
|
|
10
|
+
Drop box contents: 1 entries
|
|
11
|
+
Max entries: 1000
|
|
12
|
+
========================================
|
|
13
|
+
2026-03-12 10:30:45.123 data_app_crash (text, 500 bytes)
|
|
14
|
+
Process: com.harnessplayground
|
|
15
|
+
PID: 7777
|
|
16
|
+
Package: com.harnessplayground v1 (1.0)
|
|
17
|
+
java.lang.RuntimeException: boom
|
|
18
|
+
\tat com.harnessplayground.MainActivity.onCreate(MainActivity.kt:38)
|
|
19
|
+
`;
|
|
20
|
+
|
|
21
|
+
const nativeCrashDropbox = `
|
|
22
|
+
========================================
|
|
23
|
+
2026-03-12 10:31:10.456 data_app_native_crash (text, 800 bytes)
|
|
24
|
+
Process: com.harnessplayground
|
|
25
|
+
PID: 8888
|
|
26
|
+
signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0xdeadbeef
|
|
27
|
+
Abort message: 'JNI DETECTED ERROR IN APPLICATION'
|
|
28
|
+
backtrace:
|
|
29
|
+
#00 pc 00001234 /data/app/lib/libapp.so
|
|
30
|
+
`;
|
|
31
|
+
|
|
32
|
+
const deviceJavaCrashDropbox = `
|
|
33
|
+
Drop box contents: 8 entries
|
|
34
|
+
Searching for: data_app_crash
|
|
35
|
+
|
|
36
|
+
========================================
|
|
37
|
+
2026-05-29 10:22:34 data_app_crash (text, 1328 bytes)
|
|
38
|
+
SystemUptimeMs: 79467172
|
|
39
|
+
Process: com.harnessplayground
|
|
40
|
+
PID: 586
|
|
41
|
+
Package: com.harnessplayground v1 (1.0)
|
|
42
|
+
java.lang.RuntimeException: Intentional asynchronous Kotlin crash
|
|
43
|
+
\tat com.harnessplayground.PlaygroundCrashModule.crashFromKotlinAsync(PlaygroundCrashModule.kt:44)
|
|
44
|
+
`;
|
|
45
|
+
|
|
46
|
+
describe('collectDropboxArtifacts', () => {
|
|
47
|
+
it('matches harness playground crashes from merged per-tag dropbox output', async () => {
|
|
48
|
+
const artifacts = await collectDropboxArtifacts({
|
|
49
|
+
bundleId: 'com.harnessplayground',
|
|
50
|
+
getDropboxOutput: async () => deviceJavaCrashDropbox,
|
|
51
|
+
occurredAt: Date.now(),
|
|
52
|
+
pid: 586,
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
expect(artifacts).toHaveLength(1);
|
|
56
|
+
expect(artifacts[0]).toMatchObject({
|
|
57
|
+
artifactType: 'dropbox-crash',
|
|
58
|
+
pid: 586,
|
|
59
|
+
processName: 'com.harnessplayground',
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('ignores Dropbox entries older than the session start time', async () => {
|
|
64
|
+
const artifacts = await collectDropboxArtifacts({
|
|
65
|
+
bundleId: 'com.harnessplayground',
|
|
66
|
+
getDropboxOutput: async () => deviceJavaCrashDropbox,
|
|
67
|
+
minOccurredAt: Date.parse('2026-05-29T10:22:35.000Z'),
|
|
68
|
+
occurredAt: Date.parse('2026-05-29T10:22:36.000Z'),
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
expect(artifacts).toHaveLength(0);
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
describe('parseDropboxOutput', () => {
|
|
76
|
+
it('parses java and native dropbox entries', () => {
|
|
77
|
+
const entries = parseDropboxOutput(`${javaCrashDropbox}\n${nativeCrashDropbox}`);
|
|
78
|
+
|
|
79
|
+
expect(entries).toHaveLength(2);
|
|
80
|
+
expect(entries[0]).toMatchObject({
|
|
81
|
+
tag: 'data_app_crash',
|
|
82
|
+
pid: 7777,
|
|
83
|
+
});
|
|
84
|
+
expect(entries[1]).toMatchObject({
|
|
85
|
+
tag: 'data_app_native_crash',
|
|
86
|
+
pid: 8888,
|
|
87
|
+
});
|
|
88
|
+
expect(entries[1]?.content).toContain('SIGSEGV');
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
describe('getBestDropboxArtifact', () => {
|
|
93
|
+
it('prefers the highest-scored artifact', () => {
|
|
94
|
+
const entries = parseDropboxOutput(`${javaCrashDropbox}\n${nativeCrashDropbox}`);
|
|
95
|
+
const artifacts = entries.map((entry, index) => ({
|
|
96
|
+
artifactType:
|
|
97
|
+
entry.tag === 'data_app_native_crash'
|
|
98
|
+
? ('dropbox-native-crash' as const)
|
|
99
|
+
: ('dropbox-crash' as const),
|
|
100
|
+
dropboxTag: entry.tag,
|
|
101
|
+
occurredAt: Date.now(),
|
|
102
|
+
score: index === 0 ? 100 : 300,
|
|
103
|
+
summary: entry.content,
|
|
104
|
+
pid: entry.pid,
|
|
105
|
+
processName: 'com.harnessplayground',
|
|
106
|
+
}));
|
|
107
|
+
|
|
108
|
+
expect(getBestDropboxArtifact(artifacts)?.dropboxTag).toBe(
|
|
109
|
+
'data_app_native_crash'
|
|
110
|
+
);
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
describe('filterExitInfo', () => {
|
|
115
|
+
it('returns matching exit-info records for the target package and pid', () => {
|
|
116
|
+
const output = `
|
|
117
|
+
ApplicationExitInfo #0:
|
|
118
|
+
package=com.harnessplayground pid=7777 realUid=10123
|
|
119
|
+
reason=4 (APP CRASH(NATIVE))
|
|
120
|
+
timestamp=2026-03-12 10:31:10.456
|
|
121
|
+
ApplicationExitInfo #1:
|
|
122
|
+
package=com.other.app pid=9999 realUid=10199
|
|
123
|
+
reason=10 (USER REQUESTED)
|
|
124
|
+
`;
|
|
125
|
+
|
|
126
|
+
const filtered = filterExitInfo({
|
|
127
|
+
output,
|
|
128
|
+
bundleId: 'com.harnessplayground',
|
|
129
|
+
pid: 7777,
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
expect(filtered).toContain('com.harnessplayground pid=7777');
|
|
133
|
+
expect(filtered).not.toContain('com.other.app');
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it('returns null when no exit-info records exist', () => {
|
|
137
|
+
expect(
|
|
138
|
+
filterExitInfo({
|
|
139
|
+
output: 'No exit info records for com.harnessplayground',
|
|
140
|
+
bundleId: 'com.harnessplayground',
|
|
141
|
+
})
|
|
142
|
+
).toBeNull();
|
|
143
|
+
});
|
|
144
|
+
});
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
2
|
+
import { createAndroidCrashReporter } from '../crash-reporter.js';
|
|
3
|
+
|
|
4
|
+
const javaCrashDropbox = `
|
|
5
|
+
========================================
|
|
6
|
+
2026-03-12 10:30:45.123 data_app_crash (text, 500 bytes)
|
|
7
|
+
Process: com.harnessplayground
|
|
8
|
+
PID: 7777
|
|
9
|
+
Package: com.harnessplayground v1 (1.0)
|
|
10
|
+
java.lang.RuntimeException: boom
|
|
11
|
+
\tat com.harnessplayground.MainActivity.onCreate(MainActivity.kt:38)
|
|
12
|
+
`;
|
|
13
|
+
|
|
14
|
+
const nativeCrashDropbox = `
|
|
15
|
+
========================================
|
|
16
|
+
2026-03-12 10:31:10.456 data_app_native_crash (text, 800 bytes)
|
|
17
|
+
Process: com.harnessplayground
|
|
18
|
+
PID: 8888
|
|
19
|
+
signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0xdeadbeef
|
|
20
|
+
Abort message: 'JNI DETECTED ERROR IN APPLICATION'
|
|
21
|
+
backtrace:
|
|
22
|
+
#00 pc 00001234 /data/app/lib/libapp.so
|
|
23
|
+
`;
|
|
24
|
+
|
|
25
|
+
describe('createAndroidCrashReporter', () => {
|
|
26
|
+
afterEach(() => {
|
|
27
|
+
vi.useRealTimers();
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('persists the current logcat crash block with the test file path', async () => {
|
|
31
|
+
vi.useFakeTimers();
|
|
32
|
+
const persistArtifact = vi.fn(
|
|
33
|
+
() => '/tmp/.harness/crash-reports/logcat.txt'
|
|
34
|
+
);
|
|
35
|
+
const reporter = createAndroidCrashReporter({
|
|
36
|
+
bundleId: 'com.harnessplayground',
|
|
37
|
+
crashArtifactWriter: {
|
|
38
|
+
runTimestamp: '2026-03-12T11-35-08-000Z',
|
|
39
|
+
persistArtifact,
|
|
40
|
+
},
|
|
41
|
+
getLogs: () => [
|
|
42
|
+
{ line: 'ordinary crash-buffer line', occurredAt: 1_000 },
|
|
43
|
+
{ line: '--------- beginning of crash', occurredAt: 1_100 },
|
|
44
|
+
{
|
|
45
|
+
line: 'Process: com.harnessplayground, PID: 7777',
|
|
46
|
+
occurredAt: 1_100,
|
|
47
|
+
},
|
|
48
|
+
{ line: 'FATAL EXCEPTION: main', occurredAt: 1_100 },
|
|
49
|
+
],
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
const detailsPromise = reporter.getCrashDetails({
|
|
53
|
+
occurredAt: 1_100,
|
|
54
|
+
pid: 7777,
|
|
55
|
+
testFilePath: '/test/native-crash.test.ts',
|
|
56
|
+
});
|
|
57
|
+
await vi.advanceTimersByTimeAsync(100);
|
|
58
|
+
const details = await detailsPromise;
|
|
59
|
+
|
|
60
|
+
expect(details).toMatchObject({
|
|
61
|
+
artifactType: 'logcat',
|
|
62
|
+
artifactPath: '/tmp/.harness/crash-reports/logcat.txt',
|
|
63
|
+
pid: 7777,
|
|
64
|
+
processName: 'com.harnessplayground',
|
|
65
|
+
});
|
|
66
|
+
expect(persistArtifact).toHaveBeenCalledWith({
|
|
67
|
+
artifactKind: 'logcat',
|
|
68
|
+
testFilePath: '/test/native-crash.test.ts',
|
|
69
|
+
source: {
|
|
70
|
+
kind: 'text',
|
|
71
|
+
fileName: 'logcat.txt',
|
|
72
|
+
text: [
|
|
73
|
+
'--------- beginning of crash',
|
|
74
|
+
'Process: com.harnessplayground, PID: 7777',
|
|
75
|
+
'FATAL EXCEPTION: main',
|
|
76
|
+
'',
|
|
77
|
+
].join('\n'),
|
|
78
|
+
},
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it('adds dropbox artifacts as enrichment when logcat evidence is available', async () => {
|
|
83
|
+
vi.useFakeTimers();
|
|
84
|
+
const persistArtifact = vi
|
|
85
|
+
.fn()
|
|
86
|
+
.mockImplementation(({ artifactKind }: { artifactKind: string }) => {
|
|
87
|
+
return `/tmp/.harness/crash-reports/${artifactKind}.txt`;
|
|
88
|
+
});
|
|
89
|
+
const reporter = createAndroidCrashReporter({
|
|
90
|
+
bundleId: 'com.harnessplayground',
|
|
91
|
+
crashArtifactWriter: {
|
|
92
|
+
runTimestamp: '2026-03-12T11-35-08-000Z',
|
|
93
|
+
persistArtifact,
|
|
94
|
+
},
|
|
95
|
+
getLogs: () => [
|
|
96
|
+
{ line: '--------- beginning of crash', occurredAt: 1_100 },
|
|
97
|
+
{
|
|
98
|
+
line: 'Process: com.harnessplayground, PID: 7777',
|
|
99
|
+
occurredAt: 1_100,
|
|
100
|
+
},
|
|
101
|
+
{ line: 'FATAL EXCEPTION: main', occurredAt: 1_100 },
|
|
102
|
+
],
|
|
103
|
+
getDropboxOutput: async () => javaCrashDropbox,
|
|
104
|
+
getExitInfo: async () =>
|
|
105
|
+
'ApplicationExitInfo #0:\n package=com.harnessplayground pid=7777 reason=4 (APP CRASH(NATIVE))',
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
const detailsPromise = reporter.getCrashDetails({
|
|
109
|
+
occurredAt: 1_100,
|
|
110
|
+
pid: 7777,
|
|
111
|
+
testFilePath: '/test/native-crash.test.ts',
|
|
112
|
+
});
|
|
113
|
+
await vi.advanceTimersByTimeAsync(100);
|
|
114
|
+
const details = await detailsPromise;
|
|
115
|
+
|
|
116
|
+
expect(details).toMatchObject({
|
|
117
|
+
artifactType: 'logcat',
|
|
118
|
+
artifactPath: '/tmp/.harness/crash-reports/logcat.txt',
|
|
119
|
+
});
|
|
120
|
+
expect(details?.enrichmentArtifacts).toEqual(
|
|
121
|
+
expect.arrayContaining([
|
|
122
|
+
{
|
|
123
|
+
artifactType: 'dropbox-crash',
|
|
124
|
+
artifactPath: '/tmp/.harness/crash-reports/dropbox-crash.txt',
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
artifactType: 'exit-info',
|
|
128
|
+
artifactPath: '/tmp/.harness/crash-reports/exit-info.txt',
|
|
129
|
+
},
|
|
130
|
+
])
|
|
131
|
+
);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it('falls back to native dropbox evidence when logcat is empty', async () => {
|
|
135
|
+
vi.useFakeTimers();
|
|
136
|
+
const persistArtifact = vi
|
|
137
|
+
.fn()
|
|
138
|
+
.mockImplementation(({ artifactKind }: { artifactKind: string }) => {
|
|
139
|
+
return `/tmp/.harness/crash-reports/${artifactKind}.txt`;
|
|
140
|
+
});
|
|
141
|
+
const reporter = createAndroidCrashReporter({
|
|
142
|
+
bundleId: 'com.harnessplayground',
|
|
143
|
+
crashArtifactWriter: {
|
|
144
|
+
runTimestamp: '2026-03-12T11-35-08-000Z',
|
|
145
|
+
persistArtifact,
|
|
146
|
+
},
|
|
147
|
+
getLogs: () => [],
|
|
148
|
+
getDropboxOutput: async () => nativeCrashDropbox,
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
const detailsPromise = reporter.getCrashDetails({
|
|
152
|
+
occurredAt: 1_100,
|
|
153
|
+
pid: 8888,
|
|
154
|
+
testFilePath: '/test/native-crash.test.ts',
|
|
155
|
+
});
|
|
156
|
+
await vi.advanceTimersByTimeAsync(100);
|
|
157
|
+
const details = await detailsPromise;
|
|
158
|
+
|
|
159
|
+
expect(details).toMatchObject({
|
|
160
|
+
artifactType: 'dropbox-native-crash',
|
|
161
|
+
artifactPath: '/tmp/.harness/crash-reports/dropbox-native-crash.txt',
|
|
162
|
+
pid: 8888,
|
|
163
|
+
signal: 'SIGSEGV',
|
|
164
|
+
processName: 'com.harnessplayground',
|
|
165
|
+
});
|
|
166
|
+
});
|
|
167
|
+
});
|
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
import { describe, expect, it } from 'vitest';
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
getEmulatorCpuCores,
|
|
4
|
+
getEmulatorStartupArgs,
|
|
5
|
+
isAccelerationUsable,
|
|
6
|
+
} from '../emulator-startup.js';
|
|
7
|
+
import { getDeviceCoreBudget } from '@react-native-harness/tools';
|
|
8
|
+
import os from 'node:os';
|
|
9
|
+
|
|
10
|
+
describe('getEmulatorCpuCores', () => {
|
|
11
|
+
it('uses the shared device-core budget for the current host', () => {
|
|
12
|
+
expect(getEmulatorCpuCores()).toBe(
|
|
13
|
+
getDeviceCoreBudget(os.availableParallelism())
|
|
14
|
+
);
|
|
15
|
+
});
|
|
16
|
+
});
|
|
3
17
|
|
|
4
18
|
describe('emulator startup modes', () => {
|
|
5
19
|
it('builds default boot args', () => {
|
|
@@ -8,17 +22,24 @@ describe('emulator startup modes', () => {
|
|
|
8
22
|
'@Pixel_8_API_35',
|
|
9
23
|
'-no-snapshot-load',
|
|
10
24
|
'-no-snapshot-save',
|
|
25
|
+
'-no-metrics',
|
|
11
26
|
])
|
|
12
27
|
);
|
|
13
|
-
expect(
|
|
14
|
-
|
|
15
|
-
);
|
|
28
|
+
expect(
|
|
29
|
+
getEmulatorStartupArgs('Pixel_8_API_35', 'default-boot')
|
|
30
|
+
).not.toEqual(expect.arrayContaining(['-camera-back', 'none']));
|
|
16
31
|
});
|
|
17
32
|
|
|
18
33
|
it('builds clean snapshot generation args', () => {
|
|
19
34
|
expect(
|
|
20
35
|
getEmulatorStartupArgs('Pixel_8_API_35', 'clean-snapshot-generation')
|
|
21
|
-
).toEqual(
|
|
36
|
+
).toEqual(
|
|
37
|
+
expect.arrayContaining([
|
|
38
|
+
'@Pixel_8_API_35',
|
|
39
|
+
'-no-snapshot-load',
|
|
40
|
+
'-no-metrics',
|
|
41
|
+
])
|
|
42
|
+
);
|
|
22
43
|
expect(
|
|
23
44
|
getEmulatorStartupArgs('Pixel_8_API_35', 'clean-snapshot-generation')
|
|
24
45
|
).not.toContain('-no-snapshot-save');
|
|
@@ -26,10 +47,36 @@ describe('emulator startup modes', () => {
|
|
|
26
47
|
|
|
27
48
|
it('builds snapshot reuse args', () => {
|
|
28
49
|
expect(getEmulatorStartupArgs('Pixel_8_API_35', 'snapshot-reuse')).toEqual(
|
|
29
|
-
expect.arrayContaining([
|
|
50
|
+
expect.arrayContaining([
|
|
51
|
+
'@Pixel_8_API_35',
|
|
52
|
+
'-no-snapshot-save',
|
|
53
|
+
'-no-metrics',
|
|
54
|
+
])
|
|
30
55
|
);
|
|
31
56
|
expect(
|
|
32
57
|
getEmulatorStartupArgs('Pixel_8_API_35', 'snapshot-reuse')
|
|
33
58
|
).not.toContain('-no-snapshot-load');
|
|
34
59
|
});
|
|
35
60
|
});
|
|
61
|
+
|
|
62
|
+
describe('isAccelerationUsable', () => {
|
|
63
|
+
it('returns true when the emulator reports acceleration is usable', () => {
|
|
64
|
+
expect(
|
|
65
|
+
isAccelerationUsable(
|
|
66
|
+
'accel:\n0\nKVM (version 12) is installed and usable.\naccel\n'
|
|
67
|
+
)
|
|
68
|
+
).toBe(true);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('returns false when acceleration is disabled', () => {
|
|
72
|
+
expect(
|
|
73
|
+
isAccelerationUsable(
|
|
74
|
+
'accel:\n0\nKVM is not installed on this machine.\naccel\n'
|
|
75
|
+
)
|
|
76
|
+
).toBe(false);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('returns false for empty output', () => {
|
|
80
|
+
expect(isAccelerationUsable('')).toBe(false);
|
|
81
|
+
});
|
|
82
|
+
});
|