7979import json
8080import os
8181import pathlib
82+ import platform
8283import shutil
8384import signal
8485import subprocess
86+ import tempfile
8587import time
8688
8789from absl import app
113115}
114116
115117_RESULT_FILE = "Results1.json"
116- _TEST_RETRY = 3
118+ _MAX_ATTEMPTS = 3
117119_CMD_TIMEOUT = 300
118120
119121_DEVICE_NONE = "None"
@@ -251,7 +253,7 @@ def main(argv):
251253
252254 for app_path in ios_testapps :
253255 bundle_id = _get_bundle_id (app_path , config )
254- logs = _run_apple_test (testapp_dir , bundle_id , app_path , ios_helper_app , device_id , _TEST_RETRY )
256+ logs = _run_apple_test (testapp_dir , bundle_id , app_path , ios_helper_app , device_id )
255257 tests .append (Test (testapp_path = app_path , logs = logs ))
256258
257259 _shutdown_simulator ()
@@ -291,7 +293,7 @@ def main(argv):
291293
292294 for app_path in tvos_testapps :
293295 bundle_id = _get_bundle_id (app_path , config )
294- logs = _run_apple_test (testapp_dir , bundle_id , app_path , tvos_helper_app , device_id , _TEST_RETRY )
296+ logs = _run_apple_test (testapp_dir , bundle_id , app_path , tvos_helper_app , device_id , record_video_display = "external" )
295297 tests .append (Test (testapp_path = app_path , logs = logs ))
296298
297299 _shutdown_simulator ()
@@ -326,7 +328,7 @@ def main(argv):
326328
327329 for app_path in android_testapps :
328330 package_name = _get_package_name (app_path )
329- logs = _run_android_test (testapp_dir , package_name , app_path , android_helper_project , _TEST_RETRY )
331+ logs = _run_android_test (testapp_dir , package_name , app_path , android_helper_project )
330332 tests .append (Test (testapp_path = app_path , logs = logs ))
331333
332334 _shutdown_emulator ()
@@ -387,23 +389,77 @@ def _build_tvos_helper(helper_project, device_name, device_os):
387389 return os .path .join (file_dir , file_name )
388390
389391
390- def _record_apple_tests (video_name ):
391- command = "xcrun simctl io booted recordVideo -f --codec=h264 %s" % video_name
392- logging .info ("Recording test: %s" , command )
393- return subprocess .Popen (command , stdout = subprocess .PIPE , stderr = subprocess .PIPE , shell = True , preexec_fn = os .setsid )
394-
395-
396- def _stop_recording (record_process ):
397- logging .info ("Stop recording test" )
398- try :
399- os .killpg (record_process .pid , signal .SIGINT )
400- except :
401- logging .info ("Stop recording test failed!!!" )
392+ def _record_apple_tests (video_name , display ):
393+ args = [
394+ "xcrun" ,
395+ "simctl" ,
396+ "io" ,
397+ "booted" ,
398+ "recordVideo" ,
399+ "-f" ,
400+ "--codec=h264" ,
401+ ]
402+ if display is not None :
403+ args .append ("--display=" + display )
404+ args .append (video_name )
405+ return _start_recording (args )
406+
407+
408+ def _start_recording (args ):
409+ logging .info ("Starting screen recording: %s" , subprocess .list2cmdline (args ))
410+
411+ output_file = tempfile .TemporaryFile ()
412+
413+ # Specify CREATE_NEW_PROCESS_GROUP on Windows because it is required for
414+ # sending CTRL_C_SIGNAL, which is done by _stop_recording().
415+ proc = subprocess .Popen (
416+ args ,
417+ stdout = output_file ,
418+ stderr = subprocess .STDOUT ,
419+ creationflags =
420+ subprocess .CREATE_NEW_PROCESS_GROUP
421+ if platform .system () == "Windows"
422+ else 0 ,
423+ )
424+ logging .info ("Started screen recording with PID %s" , proc .pid )
425+ return (proc , output_file )
426+
427+
428+ def _stop_recording (start_recording_retval ):
429+ (proc , output_file ) = start_recording_retval
430+ logging .info ("Stopping screen recording with PID %s by sending CTRL+C" ,
431+ proc .pid )
432+
433+ # Make sure that `returncode` is set on the `Popen` object.
434+ proc .poll ()
435+
436+ if proc .returncode is not None :
437+ logging .warning (
438+ "WARNING: Screen recording process ended prematurely with exit code %s" ,
439+ proc .returncode
440+ )
441+ output_file .seek (0 )
442+ logging .warning ("==== BEGIN screen recording output ====" )
443+ for line in output_file .read ().decode ("utf8" , errors = "replace" ).splitlines ():
444+ logging .warning ("%s" , line .rstrip ())
445+ logging .warning ("==== END screen recording output ====" )
446+
447+ output_file .close ()
448+
449+ proc .send_signal (
450+ signal .CTRL_C_SIGNAL
451+ if platform .system () == "Windows"
452+ else signal .SIGINT
453+ )
454+ proc .wait ()
402455 time .sleep (5 )
403456
404457
405458def _save_recorded_apple_video (video_name , summary_dir ):
406- logging .info ("Save test video to: %s" , summary_dir )
459+ logging .info ("Save test video %s to: %s" , video_name , summary_dir )
460+ if not os .path .exists (video_name ):
461+ logging .warning ("Save test video failed: file not found: %s" , video_name )
462+ return
407463 shutil .move (video_name , os .path .join (summary_dir , video_name ))
408464
409465
@@ -502,23 +558,24 @@ def _has_uitests(app_path, config):
502558 return api .get ("has_uitests" , False )
503559
504560
505- def _run_apple_test (testapp_dir , bundle_id , app_path , helper_app , device_id , retry = 1 ):
561+ def _run_apple_test (testapp_dir , bundle_id , app_path , helper_app , device_id , max_attempts = _MAX_ATTEMPTS , record_video_display = None ):
506562 """Run helper test and collect test result."""
507- logging .info ("Running apple helper test: %s, %s, %s, %s" , bundle_id , app_path , helper_app , device_id )
508- _install_apple_app (app_path , device_id )
509- video_name = "video-%s-%s-%s.mp4" % (bundle_id , retry , FLAGS .logfile_name )
510- record_process = _record_apple_tests (video_name )
511- _run_xctest (helper_app , device_id )
512- _stop_recording (record_process )
513- log = _get_apple_test_log (bundle_id , app_path , device_id )
514- _uninstall_apple_app (bundle_id , device_id )
515- result = test_validation .validate_results (log , test_validation .CPP )
516- if not result .complete or (FLAGS .test_type == "uitest" and result .fails > 0 ):
563+ attempt_num = 1
564+ while attempt_num <= max_attempts :
565+ logging .info ("Running apple helper test (attempt %s of %s): %s, %s, %s, %s" , attempt_num , max_attempts , bundle_id , app_path , helper_app , device_id )
566+ _install_apple_app (app_path , device_id )
567+ video_name = "video-%s-%s-%s.mp4" % (bundle_id , attempt_num , FLAGS .logfile_name )
568+ record_process = _record_apple_tests (video_name , display = record_video_display )
569+ _run_xctest (helper_app , device_id )
570+ _stop_recording (record_process )
571+ log = _get_apple_test_log (bundle_id , app_path , device_id )
572+ _uninstall_apple_app (bundle_id , device_id )
573+ result = test_validation .validate_results (log , test_validation .CPP )
574+ if result .complete and (FLAGS .test_type != "uitest" or result .fails == 0 ):
575+ break
517576 _save_recorded_apple_video (video_name , testapp_dir )
518- if retry > 1 :
519- logging .info ("Retry _run_apple_test. Remaining retry: %s" , retry - 1 )
520- return _run_apple_test (testapp_dir , bundle_id , app_path , helper_app , device_id , retry = retry - 1 )
521-
577+ attempt_num += 1
578+
522579 return log
523580
524581
@@ -629,7 +686,7 @@ def _create_and_boot_emulator(sdk_id):
629686 if not FLAGS .ci :
630687 command = "$ANDROID_HOME/emulator/emulator -avd test_emulator &"
631688 else :
632- command = "$ANDROID_HOME/emulator/emulator -avd test_emulator -no-audio -no-boot-anim -gpu auto &"
689+ command = "$ANDROID_HOME/emulator/emulator -avd test_emulator -no-window -no- audio -no-boot-anim -gpu auto &"
633690 logging .info ("Boot test emulator: %s" , command )
634691 subprocess .Popen (command , universal_newlines = True , shell = True , stdout = subprocess .PIPE )
635692
@@ -673,25 +730,27 @@ def _get_package_name(app_path):
673730 return package_name
674731
675732
676- def _run_android_test (testapp_dir , package_name , app_path , helper_project , retry = 1 ):
677- logging .info ("Running android helper test: %s, %s, %s" , package_name , app_path , helper_project )
678- _install_android_app (app_path )
679- video_name = "video-%s-%s-%s.mp4" % (package_name , retry , FLAGS .logfile_name )
680- logcat_name = "logcat-%s-%s-%s.txt" % (package_name , retry , FLAGS .logfile_name )
681- record_process = _record_android_tests (video_name )
682- _clear_android_logcat ()
683- _run_instrumented_test ()
684- _stop_recording (record_process )
685- log = _get_android_test_log (package_name )
686- _uninstall_android_app (package_name )
687-
688- result = test_validation .validate_results (log , test_validation .CPP )
689- if not result .complete or (FLAGS .test_type == "uitest" and result .fails > 0 ):
733+ def _run_android_test (testapp_dir , package_name , app_path , helper_project , max_attempts = _MAX_ATTEMPTS ):
734+ attempt_num = 1
735+ while attempt_num <= max_attempts :
736+ logging .info ("Running android helper test (attempt %s of %s): %s, %s, %s" , attempt_num , max_attempts , package_name , app_path , helper_project )
737+ _install_android_app (app_path )
738+ video_name = "video-%s-%s-%s.mp4" % (package_name , attempt_num , FLAGS .logfile_name )
739+ logcat_name = "logcat-%s-%s-%s.txt" % (package_name , attempt_num , FLAGS .logfile_name )
740+ record_process = _record_android_tests (video_name )
741+ _clear_android_logcat ()
742+ _run_instrumented_test ()
743+ _stop_recording (record_process )
744+ log = _get_android_test_log (package_name )
745+ _uninstall_android_app (package_name )
746+
747+ result = test_validation .validate_results (log , test_validation .CPP )
748+ if result .complete and (FLAGS .test_type != "uitest" or result .fails == 0 ):
749+ break
750+
690751 _save_recorded_android_video (video_name , testapp_dir )
691752 _save_android_logcat (logcat_name , testapp_dir )
692- if retry > 1 :
693- logging .info ("Retry _run_android_test. Remaining retry: %s" , retry - 1 )
694- return _run_android_test (testapp_dir , package_name , app_path , helper_project , retry = retry - 1 )
753+ attempt_num += 1
695754
696755 return log
697756
@@ -726,9 +785,13 @@ def _install_android_helper_app(helper_project):
726785
727786
728787def _record_android_tests (video_name ):
729- command = "adb shell screenrecord /sdcard/%s" % video_name
730- logging .info ("Recording test: %s" , command )
731- return subprocess .Popen (command , stdout = subprocess .PIPE , stderr = subprocess .PIPE , shell = True , preexec_fn = os .setsid )
788+ return _start_recording ([
789+ "adb" ,
790+ "shell" ,
791+ "screenrecord" ,
792+ "--bugreport" ,
793+ "/sdcard/%s" % video_name ,
794+ ])
732795
733796
734797def _save_recorded_android_video (video_name , summary_dir ):
@@ -774,12 +837,20 @@ def _get_android_test_log(test_package):
774837 return result .stdout
775838
776839
777- def _run_with_retry (args , shell = False , check = True , timeout = _CMD_TIMEOUT , retry_time = _TEST_RETRY , device = _DEVICE_NONE , type = _RESET_TYPE_REBOOT ):
778- logging .info ("run_with_retry: %s; remaining retry: %s" , args , retry_time )
779- if retry_time > 1 :
840+ def _run_with_retry (args , shell = False , check = True , timeout = _CMD_TIMEOUT , max_attempts = _MAX_ATTEMPTS , device = _DEVICE_NONE , type = _RESET_TYPE_REBOOT ):
841+ attempt_num = 1
842+ while attempt_num <= max_attempts :
843+ logging .info ("run_with_retry: %s (attempt %s of %s)" , args , attempt_num , max_attempts )
780844 try :
781845 subprocess .run (args , shell = shell , check = check , timeout = timeout )
782- except :
846+ except subprocess .SubprocessError :
847+ logging .exception ("run_with_retry: %s (attempt %s of %s) FAILED" , args , attempt_num , max_attempts )
848+
849+ # If retries have been exhausted, just raise the exception
850+ if attempt_num >= max_attempts :
851+ raise
852+
853+ # Otherwise, reset the emulator/simulator and try again.
783854 if device == _DEVICE_NONE :
784855 pass
785856 elif device == _DEVICE_ANDROID :
@@ -788,9 +859,10 @@ def _run_with_retry(args, shell=False, check=True, timeout=_CMD_TIMEOUT, retry_t
788859 else :
789860 # Apple
790861 _reset_simulator_on_error (device , type )
791- _run_with_retry (args , shell , check , timeout , retry_time - 1 , device , type )
792- else :
793- subprocess .run (args , shell = shell , check = False , timeout = timeout )
862+ else :
863+ break
864+
865+ attempt_num += 1
794866
795867
796868if __name__ == '__main__' :
0 commit comments