@@ -98,10 +98,19 @@ def run(command, *, host=None, env=None, log=True, **kwargs):
98
98
env .update (host_env )
99
99
100
100
if log :
101
- print (">" ," " . join ( map ( str , command ) ))
101
+ print (">" ,join_command ( command ))
102
102
return subprocess .run (command ,env = env ,** kwargs )
103
103
104
104
105
+ # Format a command so it can be copied into a shell. Like shlex.join, but also
106
+ # accepts arguments which are Paths, or a single string/Path outside of a list.
107
+ def join_command (args ):
108
+ if isinstance (args , (str ,Path )):
109
+ return str (args )
110
+ else :
111
+ return shlex .join (map (str ,args ))
112
+
113
+
105
114
# Format the environment so it can be pasted into a shell.
106
115
def print_env (env ):
107
116
for key ,value in sorted (env .items ()):
@@ -512,24 +521,42 @@ async def gradle_task(context):
512
521
task_prefix = "connected"
513
522
env ["ANDROID_SERIAL" ]= context .connected
514
523
524
+ hidden_output = []
525
+
526
+ def log (line ):
527
+ # Gradle may take several minutes to install SDK packages, so it's worth
528
+ # showing those messages even in non-verbose mode.
529
+ if context .verbose or line .startswith ('Preparing "Install' ):
530
+ sys .stdout .write (line )
531
+ else :
532
+ hidden_output .append (line )
533
+
534
+ if context .command :
535
+ mode = "-c"
536
+ module = context .command
537
+ else :
538
+ mode = "-m"
539
+ module = context .module or "test"
540
+
515
541
args = [
516
542
gradlew ,"--console" ,"plain" ,f"{ task_prefix } DebugAndroidTest" ,
517
- "-Pandroid.testInstrumentationRunnerArguments.pythonArgs="
518
- + shlex .join (context .args ),
543
+ ]+ [
544
+ f"-Pandroid.testInstrumentationRunnerArguments.python{ name } ={ value } "
545
+ for name ,value in [
546
+ ("Mode" ,mode ),
547
+ ("Module" ,module ),
548
+ ("Args" ,join_command (context .args )),
549
+ ]
519
550
]
520
- hidden_output = []
551
+ log ("> " + join_command (args ))
552
+
521
553
try :
522
554
async with async_process (
523
555
* args ,cwd = TESTBED_DIR ,env = env ,
524
556
stdout = subprocess .PIPE ,stderr = subprocess .STDOUT ,
525
557
)as process :
526
558
while line := (await process .stdout .readline ()).decode (* DECODE_ARGS ):
527
- # Gradle may take several minutes to install SDK packages, so
528
- # it's worth showing those messages even in non-verbose mode.
529
- if context .verbose or line .startswith ('Preparing "Install' ):
530
- sys .stdout .write (line )
531
- else :
532
- hidden_output .append (line )
559
+ log (line )
533
560
534
561
status = await wait_for (process .wait (),timeout = 1 )
535
562
if status == 0 :
@@ -701,15 +728,25 @@ def parse_args():
701
728
"-v" ,"--verbose" ,action = "count" ,default = 0 ,
702
729
help = "Show Gradle output, and non-Python logcat messages. "
703
730
"Use twice to include high-volume messages which are rarely useful." )
731
+
704
732
device_group = test .add_mutually_exclusive_group (required = True )
705
733
device_group .add_argument (
706
734
"--connected" ,metavar = "SERIAL" ,help = "Run on a connected device. "
707
735
"Connect it yourself, then get its serial from `adb devices`." )
708
736
device_group .add_argument (
709
737
"--managed" ,metavar = "NAME" ,help = "Run on a Gradle-managed device. "
710
738
"These are defined in `managedDevices` in testbed/app/build.gradle.kts." )
739
+
740
+ mode_group = test .add_mutually_exclusive_group ()
741
+ mode_group .add_argument (
742
+ "-c" ,dest = "command" ,help = "Execute the given Python code." )
743
+ mode_group .add_argument (
744
+ "-m" ,dest = "module" ,help = "Execute the module with the given name." )
745
+ test .epilog = (
746
+ "If neither -c nor -m are passed, the default is '-m test', which will "
747
+ "run Python's own test suite." )
711
748
test .add_argument (
712
- "args" ,nargs = "*" ,help = f"Argumentsfor `python -m test` . "
749
+ "args" ,nargs = "*" ,help = f"Argumentsto add to sys.argv . "
713
750
f"Separate them from{ SCRIPT_NAME } 's own arguments with `--`." )
714
751
715
752
return parser .parse_args ()
@@ -756,14 +793,9 @@ def print_called_process_error(e):
756
793
if not content .endswith ("\n " ):
757
794
stream .write ("\n " )
758
795
759
- # Format the command so it can be copied into a shell. shlex uses single
760
- # quotes, so we surround the whole command with double quotes.
761
- args_joined = (
762
- e .cmd if isinstance (e .cmd ,str )
763
- else " " .join (shlex .quote (str (arg ))for arg in e .cmd )
764
- )
796
+ # shlex uses single quotes, so we surround the command with double quotes.
765
797
print (
766
- f'Command "{ args_joined } " returned exit status{ e .returncode } '
798
+ f'Command "{ join_command ( e . cmd ) } " returned exit status{ e .returncode } '
767
799
)
768
800
769
801