@@ -2,12 +2,14 @@ import { expect, test } from 'vitest';
22import type { DeviceInfo } from '@agent-device/kernel/device' ;
33import { bindAndroidAdbHostStub } from './adb-host.fixtures.ts' ;
44import {
5+ createLocalAndroidAdbProvider ,
56 resolveAndroidAdbExecutor ,
67 resolveAndroidAdbProvider ,
78 resolveAndroidTextInjector ,
89 resolveScopedAndroidAdbBackgroundTransport ,
910 withAndroidAdbProvider ,
1011} from './adb-provider-scope.ts' ;
12+ import { runAndroidHostAdb } from './adb-host.ts' ;
1113import type { AndroidAdbExecutorResult , AndroidAdbProvider } from './adb-transport.ts' ;
1214
1315const DEVICE : DeviceInfo = {
@@ -92,3 +94,192 @@ test('the installed override routes only normalized device-scoped adb calls to t
9294 } ) ;
9395 expect ( providerCalls ) . toEqual ( [ [ 'shell' , 'ls' ] ] ) ;
9496} ) ;
97+
98+ test ( 'a managed port scope routes host adb and matching serial calls to its private server' , async ( ) => {
99+ const hostCalls : Array < { args : string [ ] ; serverPort ?: number } > = [ ] ;
100+ bindAndroidAdbHostStub ( {
101+ execHostAdb : async ( args , options ) => {
102+ hostCalls . push ( { args, serverPort : options ?. serverPort } ) ;
103+ return ok ( ) ;
104+ } ,
105+ } ) ;
106+
107+ await withAndroidAdbProvider (
108+ { exec : async ( ) => ok ( ) } ,
109+ { serial : DEVICE . id , serverPort : 15_037 } ,
110+ async ( ) => {
111+ await runAndroidHostAdb ( [ 'devices' ] ) ;
112+ await runAndroidHostAdb ( [ '-s' , DEVICE . id , 'shell' , 'getprop' ] ) ;
113+ await runAndroidHostAdb ( [ '-s' , OTHER . id , 'shell' , 'getprop' ] ) ;
114+ } ,
115+ ) ;
116+ await runAndroidHostAdb ( [ 'devices' ] ) ;
117+
118+ expect ( hostCalls ) . toEqual ( [
119+ { args : [ 'devices' ] , serverPort : 15_037 } ,
120+ { args : [ '-s' , DEVICE . id , 'shell' , 'getprop' ] , serverPort : 15_037 } ,
121+ { args : [ '-s' , OTHER . id , 'shell' , 'getprop' ] } ,
122+ { args : [ 'devices' ] } ,
123+ ] ) ;
124+ } ) ;
125+
126+ test ( 'a managed port scope classifies absolute adb commands and preserves the default boundary' , async ( ) => {
127+ const providerCalls : string [ ] [ ] = [ ] ;
128+ const hostCalls : string [ ] [ ] = [ ] ;
129+ let captured :
130+ | ( ( cmd : string , args : string [ ] , options : object ) => Promise < unknown > | undefined )
131+ | undefined ;
132+ bindAndroidAdbHostStub ( {
133+ execHostAdb : async ( args ) => {
134+ hostCalls . push ( args ) ;
135+ return ok ( ) ;
136+ } ,
137+ withAdbCommandExecutorOverride : async ( override , fn ) => {
138+ captured = override ;
139+ return await fn ( ) ;
140+ } ,
141+ } ) ;
142+
143+ await withAndroidAdbProvider (
144+ {
145+ exec : async ( args ) => {
146+ providerCalls . push ( args ) ;
147+ return ok ( ) ;
148+ } ,
149+ } ,
150+ { serial : DEVICE . id , serverPort : 15_037 } ,
151+ async ( ) => {
152+ const global = captured ?.( '/opt/android-sdk/platform-tools/adb' , [ 'devices' , '-l' ] , { } ) ;
153+ const matching = captured ?.(
154+ '/opt/android-sdk/platform-tools/adb' ,
155+ [ '-s' , DEVICE . id , 'shell' , 'ls' ] ,
156+ { } ,
157+ ) ;
158+ expect ( captured ?.( 'adb' , [ '-s' , OTHER . id , 'shell' , 'ls' ] , { } ) ) . toBeUndefined ( ) ;
159+ expect ( captured ?.( 'emulator' , [ '-list-avds' ] , { } ) ) . toBeUndefined ( ) ;
160+ expect ( global ) . toBeDefined ( ) ;
161+ expect ( matching ) . toBeDefined ( ) ;
162+ await global ;
163+ await matching ;
164+ } ,
165+ ) ;
166+
167+ expect ( hostCalls ) . toEqual ( [ [ 'devices' , '-l' ] ] ) ;
168+ expect ( providerCalls ) . toEqual ( [ [ 'shell' , 'ls' ] ] ) ;
169+ } ) ;
170+
171+ test ( 'a managed port scope keeps shell -s arguments on the private transport' , async ( ) => {
172+ const hostCalls : Array < { args : string [ ] ; serverPort ?: number } > = [ ] ;
173+ let captured :
174+ | ( ( cmd : string , args : string [ ] , options : object ) => Promise < unknown > | undefined )
175+ | undefined ;
176+ bindAndroidAdbHostStub ( {
177+ execHostAdb : async ( args , options ) => {
178+ hostCalls . push ( { args, serverPort : options ?. serverPort } ) ;
179+ return ok ( ) ;
180+ } ,
181+ withAdbCommandExecutorOverride : async ( override , fn ) => {
182+ captured = override ;
183+ return await fn ( ) ;
184+ } ,
185+ } ) ;
186+
187+ await withAndroidAdbProvider (
188+ { exec : async ( ) => ok ( ) } ,
189+ { serial : DEVICE . id , serverPort : 15_037 } ,
190+ async ( ) => {
191+ await runAndroidHostAdb ( [ 'shell' , 'echo' , '-s' , OTHER . id ] ) ;
192+ const shellCommand = captured ?.( 'adb' , [ 'shell' , 'echo' , '-s' , OTHER . id ] , { } ) ;
193+ expect ( shellCommand ) . toBeDefined ( ) ;
194+ await shellCommand ;
195+ } ,
196+ ) ;
197+
198+ expect ( hostCalls ) . toEqual ( [
199+ { args : [ 'shell' , 'echo' , '-s' , OTHER . id ] , serverPort : 15_037 } ,
200+ { args : [ 'shell' , 'echo' , '-s' , OTHER . id ] , serverPort : 15_037 } ,
201+ ] ) ;
202+ } ) ;
203+
204+ test ( 'a managed port scope restores the default transport after task failure' , async ( ) => {
205+ const ports : Array < number | undefined > = [ ] ;
206+ bindAndroidAdbHostStub ( {
207+ execHostAdb : async ( _args , options ) => {
208+ ports . push ( options ?. serverPort ) ;
209+ return ok ( ) ;
210+ } ,
211+ } ) ;
212+
213+ await expect (
214+ withAndroidAdbProvider (
215+ { exec : async ( ) => ok ( ) } ,
216+ { serial : DEVICE . id , serverPort : 15_037 } ,
217+ async ( ) => {
218+ await runAndroidHostAdb ( [ 'devices' ] ) ;
219+ throw new Error ( 'stop managed request' ) ;
220+ } ,
221+ ) ,
222+ ) . rejects . toThrow ( 'stop managed request' ) ;
223+ await runAndroidHostAdb ( [ 'devices' ] ) ;
224+
225+ expect ( ports ) . toEqual ( [ 15_037 , undefined ] ) ;
226+ } ) ;
227+
228+ test ( 'a managed port scope carries its server through the local background transport' , async ( ) => {
229+ const spawnCalls : Array < { serial : string ; args : string [ ] ; serverPort ?: number } > = [ ] ;
230+ bindAndroidAdbHostStub ( {
231+ spawnSerialAdb : ( serial , args , options ) => {
232+ spawnCalls . push ( { serial, args, serverPort : options ?. serverPort } ) ;
233+ return undefined as never ;
234+ } ,
235+ } ) ;
236+ const deviceProvider = createLocalAndroidAdbProvider ( DEVICE , { serverPort : 15_037 } ) ;
237+
238+ await withAndroidAdbProvider (
239+ deviceProvider ,
240+ { serial : DEVICE . id , serverPort : 15_037 } ,
241+ async ( ) => {
242+ const transport = resolveScopedAndroidAdbBackgroundTransport ( DEVICE ) ;
243+ expect ( transport . mode ) . toBe ( 'transport-composed' ) ;
244+ if ( transport . mode === 'transport-composed' ) {
245+ transport . spawn ?.( [ 'logcat' , '-v' , 'threadtime' ] ) ;
246+ }
247+ } ,
248+ ) ;
249+
250+ expect ( spawnCalls ) . toEqual ( [
251+ { serial : DEVICE . id , args : [ 'logcat' , '-v' , 'threadtime' ] , serverPort : 15_037 } ,
252+ ] ) ;
253+ } ) ;
254+
255+ test ( 'managed port scopes remain isolated across concurrent requests' , async ( ) => {
256+ const hostCalls : Array < { serial : string ; serverPort ?: number } > = [ ] ;
257+ bindAndroidAdbHostStub ( {
258+ execHostAdb : async ( args , options ) => {
259+ hostCalls . push ( {
260+ serial : args [ args . indexOf ( '-s' ) + 1 ] ?? 'global' ,
261+ serverPort : options ?. serverPort ,
262+ } ) ;
263+ await Promise . resolve ( ) ;
264+ return ok ( ) ;
265+ } ,
266+ } ) ;
267+
268+ await Promise . all ( [
269+ withAndroidAdbProvider (
270+ { exec : async ( ) => ok ( ) } ,
271+ { serial : DEVICE . id , serverPort : 15_037 } ,
272+ async ( ) => await runAndroidHostAdb ( [ '-s' , DEVICE . id , 'shell' , 'id' ] ) ,
273+ ) ,
274+ withAndroidAdbProvider (
275+ { exec : async ( ) => ok ( ) } ,
276+ { serial : OTHER . id , serverPort : 15_038 } ,
277+ async ( ) => await runAndroidHostAdb ( [ '-s' , OTHER . id , 'shell' , 'id' ] ) ,
278+ ) ,
279+ ] ) ;
280+
281+ expect ( hostCalls ) . toEqual ( [
282+ { serial : DEVICE . id , serverPort : 15_037 } ,
283+ { serial : OTHER . id , serverPort : 15_038 } ,
284+ ] ) ;
285+ } ) ;
0 commit comments