50
50
+ (".bat" if os .name == "nt" else "" )
51
51
)
52
52
53
- logcat_started = False
53
+ # Whether we've seen any output from Python yet.
54
+ python_started = False
55
+
56
+ # Buffer for verbose output which will be displayed only if a test fails and
57
+ # there has been no output from Python.
58
+ hidden_output = []
59
+
60
+
61
+ def log_verbose (context , line , stream = sys .stdout ):
62
+ if context .verbose :
63
+ stream .write (line )
64
+ else :
65
+ hidden_output .append ((stream , line ))
54
66
55
67
56
68
def delete_glob (pattern ):
@@ -118,7 +130,7 @@ def android_env(host):
118
130
env_script = ANDROID_DIR / "android-env.sh"
119
131
env_output = subprocess .run (
120
132
f"set -eu; "
121
- f"export HOST={ host } ; "
133
+ f"HOST={ host } ; "
122
134
f"PREFIX={ prefix } ; "
123
135
f". { env_script } ; "
124
136
f"export" ,
@@ -453,17 +465,19 @@ async def logcat_task(context, initial_devices):
453
465
454
466
# `--pid` requires API level 24 or higher.
455
467
args = [adb , "-s" , serial , "logcat" , "--pid" , pid , "--format" , "tag" ]
456
- hidden_output = []
468
+ logcat_started = False
457
469
async with async_process (
458
470
* args , stdout = subprocess .PIPE , stderr = subprocess .STDOUT ,
459
471
) as process :
460
472
while line := (await process .stdout .readline ()).decode (* DECODE_ARGS ):
461
473
if match := re .fullmatch (r"([A-Z])/(.*)" , line , re .DOTALL ):
474
+ logcat_started = True
462
475
level , message = match .groups ()
463
476
else :
464
- # If the regex doesn't match, this is probably the second or
465
- # subsequent line of a multi-line message. Python won't produce
466
- # such messages, but other components might.
477
+ # If the regex doesn't match, this is either a logcat startup
478
+ # error, or the second or subsequent line of a multi-line
479
+ # message. Python won't produce multi-line messages, but other
480
+ # components might.
467
481
level , message = None , line
468
482
469
483
# Exclude high-volume messages which are rarely useful.
@@ -483,25 +497,22 @@ async def logcat_task(context, initial_devices):
483
497
# tag indicators from Python's stdout and stderr.
484
498
for prefix in ["python.stdout: " , "python.stderr: " ]:
485
499
if message .startswith (prefix ):
486
- global logcat_started
487
- logcat_started = True
500
+ global python_started
501
+ python_started = True
488
502
stream .write (message .removeprefix (prefix ))
489
503
break
490
504
else :
491
- if context .verbose :
492
- # Non-Python messages add a lot of noise, but they may
493
- # sometimes help explain a failure.
494
- stream .write (line )
495
- else :
496
- hidden_output .append (line )
505
+ # Non-Python messages add a lot of noise, but they may
506
+ # sometimes help explain a failure.
507
+ log_verbose (context , line , stream )
497
508
498
509
# If the device disconnects while logcat is running, which always
499
510
# happens in --managed mode, some versions of adb return non-zero.
500
511
# Distinguish this from a logcat startup error by checking whether we've
501
- # received a message from Python yet.
512
+ # received any logcat messages yet.
502
513
status = await wait_for (process .wait (), timeout = 1 )
503
514
if status != 0 and not logcat_started :
504
- raise CalledProcessError (status , args , "" . join ( hidden_output ) )
515
+ raise CalledProcessError (status , args )
505
516
506
517
507
518
def stop_app (serial ):
@@ -516,16 +527,6 @@ async def gradle_task(context):
516
527
task_prefix = "connected"
517
528
env ["ANDROID_SERIAL" ] = context .connected
518
529
519
- hidden_output = []
520
-
521
- def log (line ):
522
- # Gradle may take several minutes to install SDK packages, so it's worth
523
- # showing those messages even in non-verbose mode.
524
- if context .verbose or line .startswith ('Preparing "Install' ):
525
- sys .stdout .write (line )
526
- else :
527
- hidden_output .append (line )
528
-
529
530
if context .command :
530
531
mode = "-c"
531
532
module = context .command
@@ -550,27 +551,27 @@ def log(line):
550
551
]
551
552
if context .verbose >= 2 :
552
553
args .append ("--info" )
553
- log ( "> " + join_command (args ))
554
+ log_verbose ( context , f "> { join_command (args )} \n " )
554
555
555
556
try :
556
557
async with async_process (
557
558
* args , cwd = TESTBED_DIR , env = env ,
558
559
stdout = subprocess .PIPE , stderr = subprocess .STDOUT ,
559
560
) as process :
560
561
while line := (await process .stdout .readline ()).decode (* DECODE_ARGS ):
561
- log (line )
562
+ # Gradle may take several minutes to install SDK packages, so
563
+ # it's worth showing those messages even in non-verbose mode.
564
+ if line .startswith ('Preparing "Install' ):
565
+ sys .stdout .write (line )
566
+ else :
567
+ log_verbose (context , line )
562
568
563
569
status = await wait_for (process .wait (), timeout = 1 )
564
570
if status == 0 :
565
571
exit (0 )
566
572
else :
567
573
raise CalledProcessError (status , args )
568
574
finally :
569
- # If logcat never started, then something has gone badly wrong, so the
570
- # user probably wants to see the Gradle output even in non-verbose mode.
571
- if hidden_output and not logcat_started :
572
- sys .stdout .write ("" .join (hidden_output ))
573
-
574
575
# Gradle does not stop the tests when interrupted.
575
576
if context .connected :
576
577
stop_app (context .connected )
@@ -600,6 +601,12 @@ async def run_testbed(context):
600
601
except* MySystemExit as e :
601
602
raise SystemExit (* e .exceptions [0 ].args ) from None
602
603
except* CalledProcessError as e :
604
+ # If Python produced no output, then the user probably wants to see the
605
+ # verbose output to explain why the test failed.
606
+ if not python_started :
607
+ for stream , line in hidden_output :
608
+ stream .write (line )
609
+
603
610
# Extract it from the ExceptionGroup so it can be handled by `main`.
604
611
raise e .exceptions [0 ]
605
612
0 commit comments