@react-native-harness/platform-android 1.1.0-rc.2 → 1.1.0-rc.4
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/README.md +9 -2
- package/dist/__tests__/adb.test.js +283 -10
- package/dist/__tests__/avd-config.test.d.ts +2 -0
- package/dist/__tests__/avd-config.test.d.ts.map +1 -0
- package/dist/__tests__/avd-config.test.js +174 -0
- package/dist/__tests__/ci-action.test.d.ts +2 -0
- package/dist/__tests__/ci-action.test.d.ts.map +1 -0
- package/dist/__tests__/ci-action.test.js +46 -0
- package/dist/__tests__/emulator-startup.test.d.ts +2 -0
- package/dist/__tests__/emulator-startup.test.d.ts.map +1 -0
- package/dist/__tests__/emulator-startup.test.js +19 -0
- package/dist/__tests__/environment.test.d.ts +2 -0
- package/dist/__tests__/environment.test.d.ts.map +1 -0
- package/dist/__tests__/environment.test.js +117 -0
- package/dist/__tests__/instance.test.d.ts +2 -0
- package/dist/__tests__/instance.test.d.ts.map +1 -0
- package/dist/__tests__/instance.test.js +423 -0
- package/dist/__tests__/targets.test.d.ts +2 -0
- package/dist/__tests__/targets.test.d.ts.map +1 -0
- package/dist/__tests__/targets.test.js +49 -0
- package/dist/adb.d.ts +23 -0
- package/dist/adb.d.ts.map +1 -1
- package/dist/adb.js +259 -16
- package/dist/app-monitor.d.ts.map +1 -1
- package/dist/app-monitor.js +27 -7
- package/dist/assertions.d.ts +5 -0
- package/dist/assertions.d.ts.map +1 -0
- package/dist/assertions.js +6 -0
- package/dist/avd-config.d.ts +41 -0
- package/dist/avd-config.d.ts.map +1 -0
- package/dist/avd-config.js +173 -0
- package/dist/config.d.ts +77 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +5 -0
- package/dist/emulator-startup.d.ts +3 -0
- package/dist/emulator-startup.d.ts.map +1 -0
- package/dist/emulator-startup.js +17 -0
- package/dist/emulator.d.ts +6 -0
- package/dist/emulator.d.ts.map +1 -0
- package/dist/emulator.js +27 -0
- package/dist/environment.d.ts +31 -0
- package/dist/environment.d.ts.map +1 -0
- package/dist/environment.js +317 -0
- package/dist/errors.d.ts +7 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +14 -0
- package/dist/factory.d.ts.map +1 -1
- package/dist/factory.js +3 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/instance.d.ts +6 -0
- package/dist/instance.d.ts.map +1 -0
- package/dist/instance.js +232 -0
- package/dist/reader.d.ts +6 -0
- package/dist/reader.d.ts.map +1 -0
- package/dist/reader.js +57 -0
- package/dist/runner.d.ts +2 -2
- package/dist/runner.d.ts.map +1 -1
- package/dist/runner.js +9 -52
- package/dist/targets.d.ts +1 -1
- package/dist/targets.d.ts.map +1 -1
- package/dist/targets.js +4 -0
- package/dist/tsconfig.lib.tsbuildinfo +1 -1
- package/dist/types.d.ts +381 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +107 -0
- package/package.json +4 -4
- package/src/__tests__/adb.test.ts +419 -15
- package/src/__tests__/avd-config.test.ts +206 -0
- package/src/__tests__/ci-action.test.ts +81 -0
- package/src/__tests__/emulator-startup.test.ts +32 -0
- package/src/__tests__/environment.test.ts +212 -0
- package/src/__tests__/instance.test.ts +610 -0
- package/src/__tests__/targets.test.ts +53 -0
- package/src/adb.ts +430 -28
- package/src/app-monitor.ts +56 -18
- package/src/avd-config.ts +290 -0
- package/src/config.ts +8 -0
- package/src/emulator-startup.ts +28 -0
- package/src/environment.ts +554 -0
- package/src/errors.ts +19 -0
- package/src/factory.ts +4 -0
- package/src/index.ts +7 -1
- package/src/instance.ts +380 -0
- package/src/runner.ts +19 -70
- package/src/targets.ts +18 -8
package/src/app-monitor.ts
CHANGED
|
@@ -6,14 +6,30 @@ import {
|
|
|
6
6
|
type AppMonitorEvent,
|
|
7
7
|
type AppMonitorListener,
|
|
8
8
|
} from '@react-native-harness/platforms';
|
|
9
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
escapeRegExp,
|
|
11
|
+
getEmitter,
|
|
12
|
+
logger,
|
|
13
|
+
spawn,
|
|
14
|
+
SubprocessError,
|
|
15
|
+
type Subprocess,
|
|
16
|
+
} from '@react-native-harness/tools';
|
|
10
17
|
import * as adb from './adb.js';
|
|
11
18
|
import { androidCrashParser } from './crash-parser.js';
|
|
12
19
|
|
|
13
20
|
const androidAppMonitorLogger = logger.child('android-app-monitor');
|
|
14
21
|
|
|
15
22
|
const getLogcatArgs = (uid: number, fromTime: string) =>
|
|
16
|
-
[
|
|
23
|
+
[
|
|
24
|
+
'logcat',
|
|
25
|
+
'-v',
|
|
26
|
+
'threadtime',
|
|
27
|
+
'-b',
|
|
28
|
+
'crash',
|
|
29
|
+
`--uid=${uid}`,
|
|
30
|
+
'-T',
|
|
31
|
+
fromTime,
|
|
32
|
+
] as const;
|
|
17
33
|
const MAX_RECENT_LOG_LINES = 200;
|
|
18
34
|
const MAX_RECENT_CRASH_ARTIFACTS = 10;
|
|
19
35
|
const CRASH_ARTIFACT_SETTLE_DELAY_MS = 100;
|
|
@@ -29,7 +45,9 @@ const nativeCrashPattern = (bundleId: string) =>
|
|
|
29
45
|
|
|
30
46
|
const processDiedPattern = (bundleId: string) =>
|
|
31
47
|
new RegExp(
|
|
32
|
-
`Process\\s+${escapeRegExp(
|
|
48
|
+
`Process\\s+${escapeRegExp(
|
|
49
|
+
bundleId
|
|
50
|
+
)}\\s+\\(pid\\s+(\\d+)\\)\\s+has\\s+died`,
|
|
33
51
|
'i'
|
|
34
52
|
);
|
|
35
53
|
|
|
@@ -66,7 +84,11 @@ const getAndroidLogLineCrashDetails = ({
|
|
|
66
84
|
summary: line.trim(),
|
|
67
85
|
signal: getSignal(line),
|
|
68
86
|
exceptionType: fatalExceptionMatch?.[1]?.trim(),
|
|
69
|
-
processName: processMatch
|
|
87
|
+
processName: processMatch
|
|
88
|
+
? bundleId
|
|
89
|
+
: line.includes(bundleId)
|
|
90
|
+
? bundleId
|
|
91
|
+
: undefined,
|
|
70
92
|
pid: pid ?? (processMatch ? Number(processMatch[1]) : undefined),
|
|
71
93
|
rawLines: [line],
|
|
72
94
|
};
|
|
@@ -211,7 +233,9 @@ const createCrashArtifact = ({
|
|
|
211
233
|
triggerOccurredAt,
|
|
212
234
|
artifactType: 'logcat',
|
|
213
235
|
rawLines:
|
|
214
|
-
rawLines.length > 0
|
|
236
|
+
rawLines.length > 0
|
|
237
|
+
? rawLines
|
|
238
|
+
: parsedDetails.rawLines ?? details.rawLines,
|
|
215
239
|
};
|
|
216
240
|
};
|
|
217
241
|
|
|
@@ -265,11 +289,12 @@ const getLatestCrashArtifact = ({
|
|
|
265
289
|
matchingByPid.length > 0
|
|
266
290
|
? matchingByPid
|
|
267
291
|
: matchingByProcess.length > 0
|
|
268
|
-
|
|
269
|
-
|
|
292
|
+
? matchingByProcess
|
|
293
|
+
: crashArtifacts;
|
|
270
294
|
const sortedCandidates = [...candidates].sort(
|
|
271
295
|
(left, right) =>
|
|
272
|
-
Math.abs(left.occurredAt - occurredAt) -
|
|
296
|
+
Math.abs(left.occurredAt - occurredAt) -
|
|
297
|
+
Math.abs(right.occurredAt - occurredAt)
|
|
273
298
|
);
|
|
274
299
|
|
|
275
300
|
const artifact = sortedCandidates[0];
|
|
@@ -385,9 +410,10 @@ export const createAndroidAppMonitor = ({
|
|
|
385
410
|
};
|
|
386
411
|
|
|
387
412
|
const recordLogLine = (line: string) => {
|
|
388
|
-
recentLogLines = [
|
|
389
|
-
|
|
390
|
-
|
|
413
|
+
recentLogLines = [
|
|
414
|
+
...recentLogLines,
|
|
415
|
+
{ line, occurredAt: Date.now() },
|
|
416
|
+
].slice(-MAX_RECENT_LOG_LINES);
|
|
391
417
|
};
|
|
392
418
|
|
|
393
419
|
const recordCrashArtifact = (details?: AppCrashDetails) => {
|
|
@@ -419,10 +445,14 @@ export const createAndroidAppMonitor = ({
|
|
|
419
445
|
const startLogcat = async () => {
|
|
420
446
|
const logcatTimestamp = await adb.getLogcatTimestamp(adbId);
|
|
421
447
|
|
|
422
|
-
logcatProcess = spawn(
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
448
|
+
logcatProcess = spawn(
|
|
449
|
+
'adb',
|
|
450
|
+
['-s', adbId, ...getLogcatArgs(appUid, logcatTimestamp)],
|
|
451
|
+
{
|
|
452
|
+
stdout: 'pipe',
|
|
453
|
+
stderr: 'pipe',
|
|
454
|
+
}
|
|
455
|
+
);
|
|
426
456
|
|
|
427
457
|
const currentProcess = logcatProcess;
|
|
428
458
|
|
|
@@ -439,15 +469,23 @@ export const createAndroidAppMonitor = ({
|
|
|
439
469
|
const event = createAndroidLogEvent(line, bundleId);
|
|
440
470
|
|
|
441
471
|
if (event) {
|
|
442
|
-
if (
|
|
472
|
+
if (
|
|
473
|
+
event.type === 'possible_crash' ||
|
|
474
|
+
event.type === 'app_exited'
|
|
475
|
+
) {
|
|
443
476
|
recordCrashArtifact(event.crashDetails);
|
|
444
477
|
}
|
|
445
478
|
emit(event);
|
|
446
479
|
}
|
|
447
480
|
}
|
|
448
481
|
} catch (error) {
|
|
449
|
-
if (
|
|
450
|
-
|
|
482
|
+
if (
|
|
483
|
+
!(error instanceof SubprocessError && error.signalName === 'SIGTERM')
|
|
484
|
+
) {
|
|
485
|
+
androidAppMonitorLogger.debug(
|
|
486
|
+
'Android logcat monitor stopped',
|
|
487
|
+
error
|
|
488
|
+
);
|
|
451
489
|
}
|
|
452
490
|
}
|
|
453
491
|
})();
|
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
import { access, readFile } from 'node:fs/promises';
|
|
2
|
+
import type { AndroidSystemImageArch } from './environment.js';
|
|
3
|
+
import type { AndroidEmulator, AndroidEmulatorAVDConfig } from './config.js';
|
|
4
|
+
|
|
5
|
+
export type AvdConfig = {
|
|
6
|
+
imageSysdir1?: string;
|
|
7
|
+
abiType?: string;
|
|
8
|
+
hwDeviceName?: string;
|
|
9
|
+
diskDataPartitionSize?: string;
|
|
10
|
+
vmHeapSize?: string;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export type AvdCompatibilityResult =
|
|
14
|
+
| { compatible: true }
|
|
15
|
+
| { compatible: false; reason: string };
|
|
16
|
+
|
|
17
|
+
export const getAvdDirectory = (name: string): string => {
|
|
18
|
+
return `${
|
|
19
|
+
process.env.ANDROID_AVD_HOME ?? `${process.env.HOME}/.android/avd`
|
|
20
|
+
}/${name}.avd`;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export const getAvdConfigPath = (name: string): string => {
|
|
24
|
+
return `${getAvdDirectory(name)}/config.ini`;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const normalizeAvdValue = (value: string | undefined): string | undefined => {
|
|
28
|
+
if (!value) {
|
|
29
|
+
return undefined;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return value.trim();
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const normalizeConfigValue = (value: string): string => {
|
|
36
|
+
return value.trim().toLowerCase();
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const parseSizeInBytes = (value: string | undefined): number | null => {
|
|
40
|
+
if (!value) {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const normalizedValue = value.trim().toLowerCase();
|
|
45
|
+
|
|
46
|
+
if (/^\d+$/.test(normalizedValue)) {
|
|
47
|
+
return Number(normalizedValue);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const match = normalizedValue.match(/^(\d+)([kmgt])$/i);
|
|
51
|
+
|
|
52
|
+
if (!match) {
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const size = Number(match[1]);
|
|
57
|
+
const unit = match[2]?.toLowerCase();
|
|
58
|
+
|
|
59
|
+
const multiplier =
|
|
60
|
+
unit === 'k'
|
|
61
|
+
? 1024
|
|
62
|
+
: unit === 'm'
|
|
63
|
+
? 1024 ** 2
|
|
64
|
+
: unit === 'g'
|
|
65
|
+
? 1024 ** 3
|
|
66
|
+
: unit === 't'
|
|
67
|
+
? 1024 ** 4
|
|
68
|
+
: null;
|
|
69
|
+
|
|
70
|
+
return multiplier == null ? null : size * multiplier;
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
const getApiLevelFromImageSysdir = (
|
|
74
|
+
value: string | undefined
|
|
75
|
+
): number | null => {
|
|
76
|
+
const match = value?.match(/android-(\d+)/i);
|
|
77
|
+
return match ? Number(match[1]) : null;
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const normalizeProfile = (value: string | undefined): string | undefined => {
|
|
81
|
+
if (!value) {
|
|
82
|
+
return undefined;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return value
|
|
86
|
+
.trim()
|
|
87
|
+
.replace(/[\r\n]+/g, ' ')
|
|
88
|
+
.toLowerCase();
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
export const parseAvdConfig = (contents: string): AvdConfig => {
|
|
92
|
+
const config: AvdConfig = {};
|
|
93
|
+
|
|
94
|
+
for (const line of contents.split(/\r?\n/)) {
|
|
95
|
+
const trimmedLine = line.trim();
|
|
96
|
+
|
|
97
|
+
if (trimmedLine === '' || trimmedLine.startsWith('#')) {
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const separatorIndex = trimmedLine.indexOf('=');
|
|
102
|
+
|
|
103
|
+
if (separatorIndex === -1) {
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const key = trimmedLine.slice(0, separatorIndex).trim();
|
|
108
|
+
const value = trimmedLine.slice(separatorIndex + 1).trim();
|
|
109
|
+
|
|
110
|
+
switch (key) {
|
|
111
|
+
case 'image.sysdir.1':
|
|
112
|
+
config.imageSysdir1 = value;
|
|
113
|
+
break;
|
|
114
|
+
case 'abi.type':
|
|
115
|
+
config.abiType = value;
|
|
116
|
+
break;
|
|
117
|
+
case 'hw.device.name':
|
|
118
|
+
config.hwDeviceName = value;
|
|
119
|
+
break;
|
|
120
|
+
case 'disk.dataPartition.size':
|
|
121
|
+
config.diskDataPartitionSize = value;
|
|
122
|
+
break;
|
|
123
|
+
case 'vm.heapSize':
|
|
124
|
+
config.vmHeapSize = value;
|
|
125
|
+
break;
|
|
126
|
+
default:
|
|
127
|
+
break;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return config;
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
export const readAvdConfig = async (
|
|
135
|
+
name: string
|
|
136
|
+
): Promise<AvdConfig | null> => {
|
|
137
|
+
const configPath = getAvdConfigPath(name);
|
|
138
|
+
|
|
139
|
+
try {
|
|
140
|
+
await access(configPath);
|
|
141
|
+
} catch {
|
|
142
|
+
return null;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return parseAvdConfig(await readFile(configPath, 'utf8'));
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
export const isAvdCompatible = ({
|
|
149
|
+
emulator,
|
|
150
|
+
avdConfig,
|
|
151
|
+
hostArch,
|
|
152
|
+
}: {
|
|
153
|
+
emulator: AndroidEmulator;
|
|
154
|
+
avdConfig: AvdConfig;
|
|
155
|
+
hostArch: AndroidSystemImageArch;
|
|
156
|
+
}): AvdCompatibilityResult => {
|
|
157
|
+
const requestedAvdConfig = emulator.avd;
|
|
158
|
+
|
|
159
|
+
if (!requestedAvdConfig) {
|
|
160
|
+
return { compatible: false, reason: 'AVD config is required.' };
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (emulator.name.trim() === '') {
|
|
164
|
+
return { compatible: false, reason: 'AVD name is required.' };
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const apiLevel = getApiLevelFromImageSysdir(avdConfig.imageSysdir1);
|
|
168
|
+
|
|
169
|
+
if (apiLevel !== requestedAvdConfig.apiLevel) {
|
|
170
|
+
return {
|
|
171
|
+
compatible: false,
|
|
172
|
+
reason: `API level mismatch: expected ${
|
|
173
|
+
requestedAvdConfig.apiLevel
|
|
174
|
+
}, got ${apiLevel ?? 'missing'}.`,
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if (normalizeAvdValue(avdConfig.abiType) !== hostArch) {
|
|
179
|
+
return {
|
|
180
|
+
compatible: false,
|
|
181
|
+
reason: `ABI mismatch: expected ${hostArch}, got ${
|
|
182
|
+
normalizeAvdValue(avdConfig.abiType) ?? 'missing'
|
|
183
|
+
}.`,
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if (
|
|
188
|
+
normalizeProfile(avdConfig.hwDeviceName) !==
|
|
189
|
+
normalizeProfile(requestedAvdConfig.profile)
|
|
190
|
+
) {
|
|
191
|
+
return {
|
|
192
|
+
compatible: false,
|
|
193
|
+
reason: `Profile mismatch: expected ${requestedAvdConfig.profile}, got ${
|
|
194
|
+
avdConfig.hwDeviceName ?? 'missing'
|
|
195
|
+
}.`,
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if (
|
|
200
|
+
(() => {
|
|
201
|
+
const configuredDiskSizeBytes = parseSizeInBytes(
|
|
202
|
+
avdConfig.diskDataPartitionSize
|
|
203
|
+
);
|
|
204
|
+
const requestedDiskSizeBytes = parseSizeInBytes(
|
|
205
|
+
requestedAvdConfig.diskSize
|
|
206
|
+
);
|
|
207
|
+
|
|
208
|
+
if (configuredDiskSizeBytes != null && requestedDiskSizeBytes != null) {
|
|
209
|
+
return configuredDiskSizeBytes < requestedDiskSizeBytes;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
return (
|
|
213
|
+
normalizeConfigValue(avdConfig.diskDataPartitionSize ?? '') !==
|
|
214
|
+
normalizeConfigValue(requestedAvdConfig.diskSize)
|
|
215
|
+
);
|
|
216
|
+
})()
|
|
217
|
+
) {
|
|
218
|
+
return {
|
|
219
|
+
compatible: false,
|
|
220
|
+
reason: `Disk size mismatch: expected ${
|
|
221
|
+
requestedAvdConfig.diskSize
|
|
222
|
+
}, got ${avdConfig.diskDataPartitionSize ?? 'missing'}.`,
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
if (
|
|
227
|
+
normalizeConfigValue(avdConfig.vmHeapSize ?? '') !==
|
|
228
|
+
normalizeConfigValue(requestedAvdConfig.heapSize)
|
|
229
|
+
) {
|
|
230
|
+
return {
|
|
231
|
+
compatible: false,
|
|
232
|
+
reason: `Heap size mismatch: expected ${
|
|
233
|
+
requestedAvdConfig.heapSize
|
|
234
|
+
}, got ${avdConfig.vmHeapSize ?? 'missing'}.`,
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
return { compatible: true };
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
export const getNormalizedAvdCacheConfig = ({
|
|
242
|
+
emulator,
|
|
243
|
+
hostArch,
|
|
244
|
+
}: {
|
|
245
|
+
emulator: AndroidEmulator;
|
|
246
|
+
hostArch: AndroidSystemImageArch;
|
|
247
|
+
}): {
|
|
248
|
+
name: string;
|
|
249
|
+
apiLevel: number;
|
|
250
|
+
arch: AndroidSystemImageArch;
|
|
251
|
+
profile: string;
|
|
252
|
+
diskSize: string;
|
|
253
|
+
heapSize: string;
|
|
254
|
+
} | null => {
|
|
255
|
+
const avd = emulator.avd;
|
|
256
|
+
|
|
257
|
+
if (!avd) {
|
|
258
|
+
return null;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
return {
|
|
262
|
+
name: emulator.name,
|
|
263
|
+
apiLevel: avd.apiLevel,
|
|
264
|
+
arch: hostArch,
|
|
265
|
+
profile: avd.profile.trim().toLowerCase(),
|
|
266
|
+
diskSize: avd.diskSize.trim().toLowerCase(),
|
|
267
|
+
heapSize: avd.heapSize.trim().toLowerCase(),
|
|
268
|
+
};
|
|
269
|
+
};
|
|
270
|
+
|
|
271
|
+
export const resolveAvdCachingEnabled = ({
|
|
272
|
+
avd,
|
|
273
|
+
isInteractive,
|
|
274
|
+
env = process.env,
|
|
275
|
+
}: {
|
|
276
|
+
avd?: AndroidEmulatorAVDConfig;
|
|
277
|
+
isInteractive: boolean;
|
|
278
|
+
env?: NodeJS.ProcessEnv;
|
|
279
|
+
}): boolean => {
|
|
280
|
+
const override = env.HARNESS_AVD_CACHING;
|
|
281
|
+
const configValue = avd?.snapshot?.enabled;
|
|
282
|
+
const requestedValue =
|
|
283
|
+
override == null ? configValue : override.toLowerCase() === 'true';
|
|
284
|
+
|
|
285
|
+
if (!requestedValue) {
|
|
286
|
+
return false;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
return !isInteractive;
|
|
290
|
+
};
|
package/src/config.ts
CHANGED
|
@@ -11,6 +11,11 @@ export const AndroidEmulatorAVDConfigSchema = z.object({
|
|
|
11
11
|
profile: z.string().min(1, 'Profile is required'),
|
|
12
12
|
diskSize: z.string().min(1, 'Disk size is required').default('1G'),
|
|
13
13
|
heapSize: z.string().min(1, 'Heap size is required').default('1G'),
|
|
14
|
+
snapshot: z
|
|
15
|
+
.object({
|
|
16
|
+
enabled: z.boolean().optional(),
|
|
17
|
+
})
|
|
18
|
+
.optional(),
|
|
14
19
|
});
|
|
15
20
|
|
|
16
21
|
export const AndroidEmulatorSchema = z.object({
|
|
@@ -51,6 +56,9 @@ export type AndroidAppLaunchOptions = z.infer<
|
|
|
51
56
|
export type AndroidEmulatorAVDConfig = z.infer<
|
|
52
57
|
typeof AndroidEmulatorAVDConfigSchema
|
|
53
58
|
>;
|
|
59
|
+
export type AndroidEmulatorAVDSnapshotConfig = NonNullable<
|
|
60
|
+
AndroidEmulatorAVDConfig['snapshot']
|
|
61
|
+
>;
|
|
54
62
|
|
|
55
63
|
export const isAndroidDeviceEmulator = (
|
|
56
64
|
device: AndroidDevice
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export type EmulatorBootMode =
|
|
2
|
+
| 'default-boot'
|
|
3
|
+
| 'clean-snapshot-generation'
|
|
4
|
+
| 'snapshot-reuse';
|
|
5
|
+
|
|
6
|
+
const COMMON_EMULATOR_ARGS = [
|
|
7
|
+
'-no-window',
|
|
8
|
+
'-gpu',
|
|
9
|
+
'swiftshader_indirect',
|
|
10
|
+
'-noaudio',
|
|
11
|
+
'-no-boot-anim',
|
|
12
|
+
'-camera-back',
|
|
13
|
+
'none',
|
|
14
|
+
] as const;
|
|
15
|
+
|
|
16
|
+
export const getEmulatorStartupArgs = (
|
|
17
|
+
name: string,
|
|
18
|
+
mode: EmulatorBootMode
|
|
19
|
+
): string[] => {
|
|
20
|
+
const modeArgs =
|
|
21
|
+
mode === 'clean-snapshot-generation'
|
|
22
|
+
? ['-no-snapshot-load']
|
|
23
|
+
: mode === 'snapshot-reuse'
|
|
24
|
+
? ['-no-snapshot-save']
|
|
25
|
+
: ['-no-snapshot-load', '-no-snapshot-save'];
|
|
26
|
+
|
|
27
|
+
return [`@${name}`, ...modeArgs, ...COMMON_EMULATOR_ARGS];
|
|
28
|
+
};
|