@@ -27,10 +27,10 @@ public class AdbRunner
27
27
private readonly string _absoluteAdbExePath ;
28
28
private readonly ILogger _log ;
29
29
private readonly IAdbProcessManager _processManager ;
30
- private readonly Dictionary < string , string > _commandList = new ( )
30
+ private readonly Dictionary < string , string [ ] > _commandList = new ( )
31
31
{
32
- { "architecture" , "shell getprop ro.product.cpu.abilist" } ,
33
- { "app" , "shell pm list packages -3" }
32
+ { "architecture" , new [ ] { "shell" , " getprop" , " ro.product.cpu.abilist" } } ,
33
+ { "app" , new [ ] { "shell" , "pm" , " list" , " packages" , " -3" } } ,
34
34
} ;
35
35
36
36
public int APIVersion => _api ?? GetAPIVersion ( ) ;
@@ -85,7 +85,7 @@ public void SetActiveDevice(string? deviceSerialNumber)
85
85
86
86
private int GetAPIVersion ( )
87
87
{
88
- var output = RunAdbCommand ( "shell getprop ro.build.version.sdk" ) ;
88
+ var output = RunAdbCommand ( new [ ] { "shell" , " getprop" , " ro.build.version.sdk" } ) ;
89
89
return int . Parse ( output . StandardOutput ) ;
90
90
}
91
91
@@ -113,22 +113,22 @@ private static string GetCliAdbExePath()
113
113
114
114
public TimeSpan TimeToWaitForBootCompletion { get ; set ; } = TimeSpan . FromMinutes ( 5 ) ;
115
115
116
- public string GetAdbVersion ( ) => RunAdbCommand ( "version" ) . StandardOutput ;
116
+ public string GetAdbVersion ( ) => RunAdbCommand ( new [ ] { "version" } ) . StandardOutput ;
117
117
118
- public string GetAdbState ( ) => RunAdbCommand ( "get-state" ) . StandardOutput ;
118
+ public string GetAdbState ( ) => RunAdbCommand ( new [ ] { "get-state" } ) . StandardOutput ;
119
119
120
- public string RebootAndroidDevice ( ) => RunAdbCommand ( "reboot" ) . StandardOutput ;
120
+ public string RebootAndroidDevice ( ) => RunAdbCommand ( new [ ] { "reboot" } ) . StandardOutput ;
121
121
122
- public void ClearAdbLog ( ) => RunAdbCommand ( "logcat -c" ) ;
122
+ public void ClearAdbLog ( ) => RunAdbCommand ( new [ ] { "logcat" , " -c" } ) ;
123
123
124
- public void EnableWifi ( bool enable ) => RunAdbCommand ( $ "shell svc wifi { ( enable ? "enable" : "disable" ) } " ) ;
124
+ public void EnableWifi ( bool enable ) => RunAdbCommand ( new [ ] { "shell" , " svc" , " wifi" , enable ? "enable" : "disable" } ) ;
125
125
126
126
public void DumpAdbLog ( string outputFilePath , string filterSpec = "" )
127
127
{
128
128
// Workaround: Doesn't seem to have a flush() function and sometimes it doesn't have the full log on emulators.
129
129
Thread . Sleep ( 3000 ) ;
130
130
131
- var result = RunAdbCommand ( $ "logcat -d { filterSpec } " , TimeSpan . FromMinutes ( 2 ) ) ;
131
+ var result = RunAdbCommand ( new [ ] { "logcat" , "-d" , filterSpec } , TimeSpan . FromMinutes ( 2 ) ) ;
132
132
if ( result . ExitCode != 0 )
133
133
{
134
134
// Could throw here, but it would tear down a possibly otherwise acceptable execution.
@@ -155,7 +155,7 @@ public void WaitForDevice()
155
155
// (Returns instantly if device is ready)
156
156
// This can fail if _currentDevice is unset if there are multiple devices.
157
157
_log . LogInformation ( "Waiting for device to be available (max 5 minutes)" ) ;
158
- var result = RunAdbCommand ( "wait-for-device" , TimeSpan . FromMinutes ( 5 ) ) ;
158
+ var result = RunAdbCommand ( new [ ] { "wait-for-device" } , TimeSpan . FromMinutes ( 5 ) ) ;
159
159
_log . LogDebug ( $ "{ result . StandardOutput } ") ;
160
160
if ( result . ExitCode != 0 )
161
161
{
@@ -167,11 +167,11 @@ public void WaitForDevice()
167
167
// to be '1' (as opposed to empty) to make subsequent automation happy.
168
168
var began = DateTimeOffset . UtcNow ;
169
169
var waitingUntil = began . Add ( TimeToWaitForBootCompletion ) ;
170
- var bootCompleted = RunAdbCommand ( $ "shell getprop { AdbShellPropertyForBootCompletion } " ) ;
170
+ var bootCompleted = RunAdbCommand ( new [ ] { "shell" , " getprop" , AdbShellPropertyForBootCompletion } ) ;
171
171
172
172
while ( ! bootCompleted . StandardOutput . Trim ( ) . StartsWith ( "1" ) && DateTimeOffset . UtcNow < waitingUntil )
173
173
{
174
- bootCompleted = RunAdbCommand ( $ "shell getprop { AdbShellPropertyForBootCompletion } " ) ;
174
+ bootCompleted = RunAdbCommand ( new [ ] { "shell" , " getprop" , AdbShellPropertyForBootCompletion } ) ;
175
175
_log . LogDebug ( $ "{ AdbShellPropertyForBootCompletion } = '{ bootCompleted . StandardOutput . Trim ( ) } '") ;
176
176
Thread . Sleep ( ( int ) TimeSpan . FromSeconds ( 10 ) . TotalMilliseconds ) ;
177
177
}
@@ -188,7 +188,7 @@ public void WaitForDevice()
188
188
189
189
public void StartAdbServer ( )
190
190
{
191
- var result = RunAdbCommand ( "start-server" ) ;
191
+ var result = RunAdbCommand ( new [ ] { "start-server" } ) ;
192
192
_log . LogDebug ( $ "{ result . StandardOutput } ") ;
193
193
if ( result . ExitCode != 0 )
194
194
{
@@ -198,7 +198,7 @@ public void StartAdbServer()
198
198
199
199
public void KillAdbServer ( )
200
200
{
201
- var result = RunAdbCommand ( "kill-server" ) ;
201
+ var result = RunAdbCommand ( new [ ] { "kill-server" } ) ;
202
202
if ( result . ExitCode != 0 )
203
203
{
204
204
throw new Exception ( $ "Error killing ADB Server. Std out:{ result . StandardOutput } Std. Err: { result . StandardError } ") ;
@@ -217,7 +217,7 @@ public int InstallApk(string apkPath)
217
217
throw new FileNotFoundException ( $ "Could not find { apkPath } ", apkPath ) ;
218
218
}
219
219
220
- var result = RunAdbCommand ( $ "install \" { apkPath } \" " ) ;
220
+ var result = RunAdbCommand ( new [ ] { "install" , apkPath } ) ;
221
221
222
222
// Two possible retry scenarios, theoretically both can happen on the same run:
223
223
@@ -227,7 +227,7 @@ public int InstallApk(string apkPath)
227
227
_log . LogWarning ( $ "Hit broken pipe error; Will make one attempt to restart ADB server, then retry the install") ;
228
228
KillAdbServer ( ) ;
229
229
StartAdbServer ( ) ;
230
- result = RunAdbCommand ( $ "install \" { apkPath } \" " ) ;
230
+ result = RunAdbCommand ( new [ ] { "install" , apkPath } ) ;
231
231
}
232
232
233
233
// 2. Installation cache on device is messed up; restarting the device reliably seems to unblock this (unless the device is actually full, if so this will error the same)
@@ -236,7 +236,7 @@ public int InstallApk(string apkPath)
236
236
_log . LogWarning ( $ "It seems the package installation cache may be full on the device. We'll try to reboot it before trying one more time.{ Environment . NewLine } Output:{ result } ") ;
237
237
RebootAndroidDevice ( ) ;
238
238
WaitForDevice ( ) ;
239
- result = RunAdbCommand ( $ "install \" { apkPath } \" " ) ;
239
+ result = RunAdbCommand ( new [ ] { "install" , apkPath } ) ;
240
240
}
241
241
242
242
// 3. Installation timed out or failed with exception; restarting the ADB server, reboot the device and give more time for installation
@@ -248,7 +248,7 @@ public int InstallApk(string apkPath)
248
248
StartAdbServer ( ) ;
249
249
RebootAndroidDevice ( ) ;
250
250
WaitForDevice ( ) ;
251
- result = RunAdbCommand ( $ "install \" { apkPath } \" " , TimeSpan . FromMinutes ( 10 ) ) ;
251
+ result = RunAdbCommand ( new [ ] { "install" , apkPath } , TimeSpan . FromMinutes ( 10 ) ) ;
252
252
}
253
253
254
254
if ( result . ExitCode != 0 )
@@ -271,7 +271,7 @@ public int UninstallApk(string apkName)
271
271
}
272
272
273
273
_log . LogInformation ( $ "Attempting to remove apk '{ apkName } ': ") ;
274
- var result = RunAdbCommand ( $ "uninstall { apkName } " ) ;
274
+ var result = RunAdbCommand ( new [ ] { "uninstall" , apkName } ) ;
275
275
276
276
// See note above in install()
277
277
if ( result . ExitCode == ( int ) AdbExitCodes . ADB_BROKEN_PIPE )
@@ -280,7 +280,7 @@ public int UninstallApk(string apkName)
280
280
281
281
KillAdbServer ( ) ;
282
282
StartAdbServer ( ) ;
283
- result = RunAdbCommand ( $ "uninstall { apkName } " ) ;
283
+ result = RunAdbCommand ( new [ ] { "uninstall" , apkName } ) ;
284
284
}
285
285
286
286
if ( result . ExitCode == ( int ) AdbExitCodes . SUCCESS )
@@ -303,7 +303,7 @@ public int UninstallApk(string apkName)
303
303
public int KillApk ( string apkName )
304
304
{
305
305
_log . LogInformation ( $ "Killing all running processes for '{ apkName } ': ") ;
306
- var result = RunAdbCommand ( $ "shell am kill --user all { apkName } " ) ;
306
+ var result = RunAdbCommand ( new [ ] { "shell" , "am" , " kill" , " --user" , " all" , apkName } ) ;
307
307
if ( result . ExitCode != ( int ) AdbExitCodes . SUCCESS )
308
308
{
309
309
_log . LogError ( $ "Error:{ Environment . NewLine } { result } ") ;
@@ -331,7 +331,7 @@ public List<string> PullFiles(string devicePath, string localPath)
331
331
_log . LogInformation ( $ "Attempting to pull contents of { devicePath } to { localPath } ") ;
332
332
var copiedFiles = new List < string > ( ) ;
333
333
334
- var result = RunAdbCommand ( $ "pull { devicePath } { tempFolder } " ) ;
334
+ var result = RunAdbCommand ( new [ ] { "pull" , devicePath , tempFolder } ) ;
335
335
336
336
if ( result . ExitCode != ( int ) AdbExitCodes . SUCCESS )
337
337
{
@@ -372,9 +372,20 @@ public List<string> PullFiles(string devicePath, string localPath)
372
372
{
373
373
var devicesAndProperties = new Dictionary < string , string ? > ( ) ;
374
374
375
- string command = _commandList [ property ] ;
375
+ IEnumerable < string > GetAdbArguments ( string deviceSerial )
376
+ {
377
+ var args = new List < string >
378
+ {
379
+ "-s" ,
380
+ deviceSerial ,
381
+ } ;
382
+
383
+ args . AddRange ( _commandList [ property ] ) ;
376
384
377
- var result = RunAdbCommand ( "devices -l" , TimeSpan . FromSeconds ( 30 ) ) ;
385
+ return args ;
386
+ }
387
+
388
+ var result = RunAdbCommand ( new [ ] { "devices" , "-l" } , TimeSpan . FromSeconds ( 30 ) ) ;
378
389
string [ ] standardOutputLines = result . StandardOutput . Split ( Environment . NewLine , StringSplitOptions . RemoveEmptyEntries ) ;
379
390
380
391
// Retry up to 3 mins til we get output; if the ADB server isn't started the output will come from a child process and we'll miss it.
@@ -386,7 +397,7 @@ public List<string> PullFiles(string devicePath, string localPath)
386
397
{
387
398
_log . LogDebug ( $ "Unexpected response from adb devices -l:{ Environment . NewLine } Exit code={ result . ExitCode } { Environment . NewLine } Std. Output: { result . StandardOutput } { Environment . NewLine } Std. Error: { result . StandardError } ") ;
388
399
Thread . Sleep ( 10000 ) ;
389
- result = RunAdbCommand ( "devices -l" , TimeSpan . FromSeconds ( 30 ) ) ;
400
+ result = RunAdbCommand ( new [ ] { "devices" , " -l" } , TimeSpan . FromSeconds ( 30 ) ) ;
390
401
standardOutputLines = result . StandardOutput . Split ( Environment . NewLine , StringSplitOptions . RemoveEmptyEntries ) ;
391
402
}
392
403
@@ -402,7 +413,7 @@ public List<string> PullFiles(string devicePath, string localPath)
402
413
{
403
414
var deviceSerial = lineParts [ 0 ] ;
404
415
405
- var shellResult = RunAdbCommand ( $ "-s { deviceSerial } { command } " , TimeSpan . FromSeconds ( 30 ) ) ;
416
+ var shellResult = RunAdbCommand ( GetAdbArguments ( deviceSerial ) , TimeSpan . FromSeconds ( 30 ) ) ;
406
417
407
418
// Assumption: All Devices on a machine running Xharness should attempt to be online or disconnected.
408
419
retriesLeft = 30 ; // Max 5 minutes (30 attempts * 10 second waits)
@@ -411,7 +422,7 @@ public List<string> PullFiles(string devicePath, string localPath)
411
422
_log . LogWarning ( $ "Device '{ deviceSerial } ' is offline; retrying up to one minute.") ;
412
423
Thread . Sleep ( 10000 ) ;
413
424
414
- shellResult = RunAdbCommand ( $ "-s { deviceSerial } { command } " , TimeSpan . FromSeconds ( 30 ) ) ;
425
+ shellResult = RunAdbCommand ( GetAdbArguments ( deviceSerial ) , TimeSpan . FromSeconds ( 30 ) ) ;
415
426
}
416
427
417
428
if ( shellResult . ExitCode == ( int ) AdbExitCodes . SUCCESS )
@@ -502,30 +513,28 @@ public Dictionary<string, string> GetAllDevicesToUse(ILogger logger, IEnumerable
502
513
public ProcessExecutionResults RunApkInstrumentation ( string apkName , string ? instrumentationClassName , Dictionary < string , string > args , TimeSpan timeout )
503
514
{
504
515
string displayName = string . IsNullOrEmpty ( instrumentationClassName ) ? "{default}" : instrumentationClassName ;
505
- string appArguments = "" ;
506
- if ( args . Count > 0 )
516
+
517
+ var adbArgs = new List < string >
507
518
{
508
- foreach ( string key in args . Keys )
509
- {
510
- appArguments = $ " { appArguments } -e { key } { args [ key ] } " ;
511
- }
512
- }
519
+ "shell" , "am" , "instrument"
520
+ } ;
521
+
522
+ adbArgs . AddRange ( args . SelectMany ( arg => new [ ] { "-e" , arg . Key , arg . Value } ) ) ;
523
+ adbArgs . Add ( "-w" ) ;
513
524
514
- string command = $ "shell am instrument { appArguments } -w { apkName } ";
515
525
if ( string . IsNullOrEmpty ( instrumentationClassName ) )
516
526
{
517
527
_log . LogInformation ( $ "Starting default instrumentation class on { apkName } (exit code 0 == success)") ;
528
+ adbArgs . Add ( apkName ) ;
518
529
}
519
530
else
520
531
{
521
532
_log . LogInformation ( $ "Starting instrumentation class '{ instrumentationClassName } ' on { apkName } ") ;
522
- command = $ "{ command } /{ instrumentationClassName } ";
533
+ adbArgs . Add ( $ "{ apkName } /{ instrumentationClassName } ") ;
523
534
}
524
- _log . LogDebug ( $ "Raw command: '{ command } '") ;
525
535
526
- var stopWatch = new Stopwatch ( ) ;
527
- stopWatch . Start ( ) ;
528
- var result = RunAdbCommand ( command , timeout ) ;
536
+ var stopWatch = Stopwatch . StartNew ( ) ;
537
+ var result = RunAdbCommand ( adbArgs , timeout ) ;
529
538
stopWatch . Stop ( ) ;
530
539
531
540
if ( result . ExitCode == ( int ) AdbExitCodes . INSTRUMENTATION_TIMEOUT )
@@ -536,24 +545,26 @@ public ProcessExecutionResults RunApkInstrumentation(string apkName, string? ins
536
545
{
537
546
_log . LogInformation ( $ "Running instrumentation class { displayName } took { stopWatch . Elapsed . TotalSeconds } seconds") ;
538
547
}
548
+
539
549
_log . LogDebug ( result . ToString ( ) ) ;
550
+
540
551
return result ;
541
552
}
542
553
543
554
#endregion
544
555
545
556
#region Process runner helpers
546
557
547
- public ProcessExecutionResults RunAdbCommand ( string command ) => RunAdbCommand ( command , TimeSpan . FromMinutes ( 5 ) ) ;
558
+ public ProcessExecutionResults RunAdbCommand ( IEnumerable < string > arguments ) => RunAdbCommand ( arguments , TimeSpan . FromMinutes ( 5 ) ) ;
548
559
549
- public ProcessExecutionResults RunAdbCommand ( string command , TimeSpan timeOut )
560
+ public ProcessExecutionResults RunAdbCommand ( IEnumerable < string > arguments , TimeSpan timeOut )
550
561
{
551
562
if ( ! File . Exists ( _absoluteAdbExePath ) )
552
563
{
553
564
throw new FileNotFoundException ( $ "Provided path for adb.exe was not valid ('{ _absoluteAdbExePath } ')", _absoluteAdbExePath ) ;
554
565
}
555
566
556
- return _processManager . Run ( _absoluteAdbExePath , command , timeOut ) ;
567
+ return _processManager . Run ( _absoluteAdbExePath , arguments , timeOut ) ;
557
568
}
558
569
559
570
#endregion
0 commit comments