@@ -11,6 +11,96 @@ import {
1111 resolveAndroidAvdName ,
1212} from '../devices.ts' ;
1313
14+ const MOCK_ANDROID_ADB_SCRIPT = [
15+ '#!/bin/sh' ,
16+ 'if [ "$1" = "devices" ] && [ "$2" = "-l" ]; then' ,
17+ ' echo "List of devices attached"' ,
18+ ' if [ -f "$AGENT_DEVICE_TEST_EMU_BOOTED_FILE" ]; then' ,
19+ ' echo "emulator-5554 device product:sdk_gphone64 model:Pixel_9_Pro_XL device:emu64a transport_id:2"' ,
20+ ' fi' ,
21+ ' exit 0' ,
22+ 'fi' ,
23+ 'if [ "$1" = "-s" ] && [ "$2" = "emulator-5554" ] && [ "$3" = "emu" ] && [ "$4" = "avd" ] && [ "$5" = "name" ]; then' ,
24+ ' echo "Pixel_9_Pro_XL"' ,
25+ ' exit 0' ,
26+ 'fi' ,
27+ 'if [ "$1" = "-s" ] && [ "$2" = "emulator-5554" ] && [ "$3" = "shell" ] && [ "$4" = "getprop" ] && [ "$5" = "ro.boot.qemu.avd_name" ]; then' ,
28+ ' echo "Pixel_9_Pro_XL"' ,
29+ ' exit 0' ,
30+ 'fi' ,
31+ 'if [ "$1" = "-s" ] && [ "$2" = "emulator-5554" ] && [ "$3" = "shell" ] && [ "$4" = "getprop" ] && [ "$5" = "persist.sys.avd_name" ]; then' ,
32+ ' echo "Pixel_9_Pro_XL"' ,
33+ ' exit 0' ,
34+ 'fi' ,
35+ 'if [ "$1" = "-s" ] && [ "$2" = "emulator-5554" ] && [ "$3" = "shell" ] && [ "$4" = "getprop" ] && [ "$5" = "sys.boot_completed" ]; then' ,
36+ ' if [ -f "$AGENT_DEVICE_TEST_EMU_BOOTED_FILE" ]; then' ,
37+ ' echo "1"' ,
38+ ' else' ,
39+ ' echo "0"' ,
40+ ' fi' ,
41+ ' exit 0' ,
42+ 'fi' ,
43+ 'if [ "$1" = "-s" ] && [ "$2" = "emulator-5554" ] && [ "$3" = "shell" ] && [ "$4" = "getprop" ] && [ "$5" = "ro.build.characteristics" ]; then' ,
44+ ' echo "phone"' ,
45+ ' exit 0' ,
46+ 'fi' ,
47+ 'if [ "$1" = "-s" ] && [ "$2" = "emulator-5554" ] && [ "$3" = "shell" ] && [ "$4" = "cmd" ] && [ "$5" = "package" ] && [ "$6" = "has-feature" ]; then' ,
48+ ' echo "false"' ,
49+ ' exit 0' ,
50+ 'fi' ,
51+ 'if [ "$1" = "-s" ] && [ "$2" = "emulator-5554" ] && [ "$3" = "shell" ] && [ "$4" = "pm" ] && [ "$5" = "list" ] && [ "$6" = "features" ]; then' ,
52+ ' echo ""' ,
53+ ' exit 0' ,
54+ 'fi' ,
55+ 'echo "unexpected adb args: $@" >> "$AGENT_DEVICE_TEST_EMU_LOG_FILE"' ,
56+ 'exit 1' ,
57+ '' ,
58+ ] ;
59+
60+ const MOCK_ANDROID_EMULATOR_SCRIPT = [
61+ '#!/bin/sh' ,
62+ 'if [ "$1" = "-list-avds" ]; then' ,
63+ ' echo "Pixel_9_Pro_XL"' ,
64+ ' exit 0' ,
65+ 'fi' ,
66+ 'if [ "$1" = "-avd" ]; then' ,
67+ ' echo "$@" >> "$AGENT_DEVICE_TEST_EMU_LOG_FILE"' ,
68+ ' touch "$AGENT_DEVICE_TEST_EMU_BOOTED_FILE"' ,
69+ ' exit 0' ,
70+ 'fi' ,
71+ 'echo "unexpected emulator args: $@" >> "$AGENT_DEVICE_TEST_EMU_LOG_FILE"' ,
72+ 'exit 1' ,
73+ '' ,
74+ ] ;
75+
76+ async function writeExecutable ( filePath : string , lines : readonly string [ ] ) : Promise < void > {
77+ await fs . writeFile ( filePath , lines . join ( '\n' ) , 'utf8' ) ;
78+ await fs . chmod ( filePath , 0o755 ) ;
79+ }
80+
81+ async function withEnv (
82+ overrides : Record < string , string | undefined > ,
83+ run : ( ) => Promise < void > ,
84+ ) : Promise < void > {
85+ const saved = Object . fromEntries (
86+ Object . keys ( overrides ) . map ( ( key ) => [ key , process . env [ key ] ] ) ,
87+ ) as Record < string , string | undefined > ;
88+
89+ for ( const [ key , value ] of Object . entries ( overrides ) ) {
90+ if ( value === undefined ) delete process . env [ key ] ;
91+ else process . env [ key ] = value ;
92+ }
93+
94+ try {
95+ await run ( ) ;
96+ } finally {
97+ for ( const [ key , value ] of Object . entries ( saved ) ) {
98+ if ( value === undefined ) delete process . env [ key ] ;
99+ else process . env [ key ] = value ;
100+ }
101+ }
102+ }
103+
14104test ( 'parseAndroidTargetFromCharacteristics detects tv markers' , ( ) => {
15105 assert . equal ( parseAndroidTargetFromCharacteristics ( 'tv,nosdcard' ) , 'tv' ) ;
16106 assert . equal ( parseAndroidTargetFromCharacteristics ( 'watch,leanback' ) , 'tv' ) ;
@@ -47,92 +137,56 @@ async function withMockedAndroidTools(
47137 const adbPath = path . join ( tmpDir , 'adb' ) ;
48138 const emulatorPath = path . join ( tmpDir , 'emulator' ) ;
49139
50- await fs . writeFile (
51- adbPath ,
52- [
53- '#!/bin/sh' ,
54- 'if [ "$1" = "devices" ] && [ "$2" = "-l" ]; then' ,
55- ' echo "List of devices attached"' ,
56- ' if [ -f "$AGENT_DEVICE_TEST_EMU_BOOTED_FILE" ]; then' ,
57- ' echo "emulator-5554 device product:sdk_gphone64 model:Pixel_9_Pro_XL device:emu64a transport_id:2"' ,
58- ' fi' ,
59- ' exit 0' ,
60- 'fi' ,
61- 'if [ "$1" = "-s" ] && [ "$2" = "emulator-5554" ] && [ "$3" = "emu" ] && [ "$4" = "avd" ] && [ "$5" = "name" ]; then' ,
62- ' echo "Pixel_9_Pro_XL"' ,
63- ' exit 0' ,
64- 'fi' ,
65- 'if [ "$1" = "-s" ] && [ "$2" = "emulator-5554" ] && [ "$3" = "shell" ] && [ "$4" = "getprop" ] && [ "$5" = "ro.boot.qemu.avd_name" ]; then' ,
66- ' echo "Pixel_9_Pro_XL"' ,
67- ' exit 0' ,
68- 'fi' ,
69- 'if [ "$1" = "-s" ] && [ "$2" = "emulator-5554" ] && [ "$3" = "shell" ] && [ "$4" = "getprop" ] && [ "$5" = "persist.sys.avd_name" ]; then' ,
70- ' echo "Pixel_9_Pro_XL"' ,
71- ' exit 0' ,
72- 'fi' ,
73- 'if [ "$1" = "-s" ] && [ "$2" = "emulator-5554" ] && [ "$3" = "shell" ] && [ "$4" = "getprop" ] && [ "$5" = "sys.boot_completed" ]; then' ,
74- ' if [ -f "$AGENT_DEVICE_TEST_EMU_BOOTED_FILE" ]; then' ,
75- ' echo "1"' ,
76- ' else' ,
77- ' echo "0"' ,
78- ' fi' ,
79- ' exit 0' ,
80- 'fi' ,
81- 'if [ "$1" = "-s" ] && [ "$2" = "emulator-5554" ] && [ "$3" = "shell" ] && [ "$4" = "getprop" ] && [ "$5" = "ro.build.characteristics" ]; then' ,
82- ' echo "phone"' ,
83- ' exit 0' ,
84- 'fi' ,
85- 'if [ "$1" = "-s" ] && [ "$2" = "emulator-5554" ] && [ "$3" = "shell" ] && [ "$4" = "cmd" ] && [ "$5" = "package" ] && [ "$6" = "has-feature" ]; then' ,
86- ' echo "false"' ,
87- ' exit 0' ,
88- 'fi' ,
89- 'if [ "$1" = "-s" ] && [ "$2" = "emulator-5554" ] && [ "$3" = "shell" ] && [ "$4" = "pm" ] && [ "$5" = "list" ] && [ "$6" = "features" ]; then' ,
90- ' echo ""' ,
91- ' exit 0' ,
92- 'fi' ,
93- 'echo "unexpected adb args: $@" >> "$AGENT_DEVICE_TEST_EMU_LOG_FILE"' ,
94- 'exit 1' ,
95- '' ,
96- ] . join ( '\n' ) ,
97- 'utf8' ,
98- ) ;
99- await fs . writeFile (
100- emulatorPath ,
101- [
102- '#!/bin/sh' ,
103- 'if [ "$1" = "-list-avds" ]; then' ,
104- ' echo "Pixel_9_Pro_XL"' ,
105- ' exit 0' ,
106- 'fi' ,
107- 'if [ "$1" = "-avd" ]; then' ,
108- ' echo "$@" >> "$AGENT_DEVICE_TEST_EMU_LOG_FILE"' ,
109- ' touch "$AGENT_DEVICE_TEST_EMU_BOOTED_FILE"' ,
110- ' exit 0' ,
111- 'fi' ,
112- 'echo "unexpected emulator args: $@" >> "$AGENT_DEVICE_TEST_EMU_LOG_FILE"' ,
113- 'exit 1' ,
114- '' ,
115- ] . join ( '\n' ) ,
116- 'utf8' ,
117- ) ;
118- await fs . chmod ( adbPath , 0o755 ) ;
119- await fs . chmod ( emulatorPath , 0o755 ) ;
120-
121- const previousPath = process . env . PATH ;
122- const previousBooted = process . env . AGENT_DEVICE_TEST_EMU_BOOTED_FILE ;
123- const previousLog = process . env . AGENT_DEVICE_TEST_EMU_LOG_FILE ;
124- process . env . PATH = `${ tmpDir } ${ path . delimiter } ${ previousPath ?? '' } ` ;
125- process . env . AGENT_DEVICE_TEST_EMU_BOOTED_FILE = emulatorBootedPath ;
126- process . env . AGENT_DEVICE_TEST_EMU_LOG_FILE = emulatorLogPath ;
140+ await writeExecutable ( adbPath , MOCK_ANDROID_ADB_SCRIPT ) ;
141+ await writeExecutable ( emulatorPath , MOCK_ANDROID_EMULATOR_SCRIPT ) ;
142+
143+ try {
144+ await withEnv (
145+ {
146+ PATH : `${ tmpDir } ${ path . delimiter } ${ process . env . PATH ?? '' } ` ,
147+ AGENT_DEVICE_TEST_EMU_BOOTED_FILE : emulatorBootedPath ,
148+ AGENT_DEVICE_TEST_EMU_LOG_FILE : emulatorLogPath ,
149+ HOME : tmpDir ,
150+ ANDROID_SDK_ROOT : undefined ,
151+ ANDROID_HOME : undefined ,
152+ } ,
153+ async ( ) => await run ( { emulatorLogPath, emulatorBootedPath } ) ,
154+ ) ;
155+ } finally {
156+ await fs . rm ( tmpDir , { recursive : true , force : true } ) ;
157+ }
158+ }
159+
160+ async function withMockedAndroidSdkRoot (
161+ run : ( ctx : { emulatorLogPath : string ; emulatorBootedPath : string ; sdkRoot : string } ) => Promise < void > ,
162+ ) : Promise < void > {
163+ const tmpDir = await fs . mkdtemp ( path . join ( os . tmpdir ( ) , 'agent-device-android-sdk-root-' ) ) ;
164+ const sdkRoot = path . join ( tmpDir , 'Android' , 'Sdk' ) ;
165+ const platformToolsDir = path . join ( sdkRoot , 'platform-tools' ) ;
166+ const emulatorDir = path . join ( sdkRoot , 'emulator' ) ;
167+ const emulatorLogPath = path . join ( tmpDir , 'emulator.log' ) ;
168+ const emulatorBootedPath = path . join ( tmpDir , 'emulator.booted' ) ;
169+ const adbPath = path . join ( platformToolsDir , 'adb' ) ;
170+ const emulatorPath = path . join ( emulatorDir , 'emulator' ) ;
171+
172+ await fs . mkdir ( platformToolsDir , { recursive : true } ) ;
173+ await fs . mkdir ( emulatorDir , { recursive : true } ) ;
174+
175+ await writeExecutable ( adbPath , MOCK_ANDROID_ADB_SCRIPT ) ;
176+ await writeExecutable ( emulatorPath , MOCK_ANDROID_EMULATOR_SCRIPT ) ;
127177
128178 try {
129- await run ( { emulatorLogPath, emulatorBootedPath } ) ;
179+ await withEnv (
180+ {
181+ PATH : process . env . PATH ?? '' ,
182+ AGENT_DEVICE_TEST_EMU_BOOTED_FILE : emulatorBootedPath ,
183+ AGENT_DEVICE_TEST_EMU_LOG_FILE : emulatorLogPath ,
184+ ANDROID_SDK_ROOT : sdkRoot ,
185+ ANDROID_HOME : undefined ,
186+ } ,
187+ async ( ) => await run ( { emulatorLogPath, emulatorBootedPath, sdkRoot } ) ,
188+ ) ;
130189 } finally {
131- process . env . PATH = previousPath ;
132- if ( previousBooted === undefined ) delete process . env . AGENT_DEVICE_TEST_EMU_BOOTED_FILE ;
133- else process . env . AGENT_DEVICE_TEST_EMU_BOOTED_FILE = previousBooted ;
134- if ( previousLog === undefined ) delete process . env . AGENT_DEVICE_TEST_EMU_LOG_FILE ;
135- else process . env . AGENT_DEVICE_TEST_EMU_LOG_FILE = previousLog ;
136190 await fs . rm ( tmpDir , { recursive : true , force : true } ) ;
137191 }
138192}
@@ -180,3 +234,19 @@ test('ensureAndroidEmulatorBooted launches emulator with GUI by default', async
180234 assert . doesNotMatch ( log , / - n o - w i n d o w / ) ;
181235 } ) ;
182236} ) ;
237+
238+ test ( 'ensureAndroidEmulatorBooted falls back to ANDROID_SDK_ROOT when PATH is incomplete' , async ( ) => {
239+ await withMockedAndroidSdkRoot ( async ( { emulatorLogPath, sdkRoot } ) => {
240+ const device = await ensureAndroidEmulatorBooted ( {
241+ avdName : 'Pixel 9 Pro XL' ,
242+ timeoutMs : 5_000 ,
243+ headless : true ,
244+ } ) ;
245+ assert . equal ( device . id , 'emulator-5554' ) ;
246+ const log = await fs . readFile ( emulatorLogPath , 'utf8' ) ;
247+ assert . match ( log , / - a v d P i x e l _ 9 _ P r o _ X L - n o - w i n d o w - n o - a u d i o / ) ;
248+ assert . ok ( ( process . env . PATH ?? '' ) . includes ( path . join ( sdkRoot , 'platform-tools' ) ) ) ;
249+ assert . ok ( ( process . env . PATH ?? '' ) . includes ( path . join ( sdkRoot , 'emulator' ) ) ) ;
250+ assert . equal ( process . env . ANDROID_HOME , sdkRoot ) ;
251+ } ) ;
252+ } ) ;
0 commit comments