def _RunSimulatorTest(args): """The function of running test with new simulator.""" with xctest_session.XctestSession( sdk=ios_constants.SDK.IPHONESIMULATOR, device_arch=ios_constants.ARCH.X86_64, work_dir=args.work_dir, output_dir=args.output_dir) as session: simulator_id, _, os_version, _ = simulator_util.CreateNewSimulator( device_type=args.device_type, os_version=args.os_version, name_prefix=args.new_simulator_name_prefix) simulator_obj = simulator_util.Simulator(simulator_id) hostless = args.app_under_test_path is None try: if not hostless: simulator_obj.Boot() session.Prepare(app_under_test=args.app_under_test_path, test_bundle=args.test_bundle_path, xctestrun_file_path=args.xctestrun, test_type=args.test_type, signing_options=_GetJson( args.signing_options_json_path)) session.SetLaunchOptions( _GetJson(args.launch_options_json_path)) if not hostless: try: simulator_obj.BootStatus().wait(timeout=60) except subprocess.TimeoutExpired: logging.warning( 'The simulator %s could not be booted in 60s. Will try to run ' 'test directly.', simulator_id) return session.RunTest(simulator_id, os_version=os_version) finally: simulator_obj.Delete()
def Execute(self, return_output=True): """Executes the xcodebuild test command. Args: return_output: bool, whether save output in the execution result. Returns: a tuple of two fields: exit_code: A value of type runner_exit_codes.EXITCODE. output: the output of xcodebuild test command or None if return_output is False. """ run_env = dict(os.environ) run_env['NSUnbufferedIO'] = 'YES' max_attempts = 1 sim_log_path = None if self._sdk == ios_constants.SDK.IPHONESIMULATOR: max_attempts = _SIM_TEST_MAX_ATTEMPTS if self._device_id: sim_log_path = simulator_util.Simulator( self._device_id).simulator_system_log_path elif self._sdk == ios_constants.SDK.IPHONEOS: max_attempts = _DEVICE_TEST_MAX_ATTEMPTS test_started = False test_succeeded = False test_failed = False for i in range(max_attempts): process = subprocess.Popen(self._command, env=run_env, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) check_xcodebuild_stuck = CheckXcodebuildStuckThread( process, self._startup_timeout_sec) check_xcodebuild_stuck.start() output = io.BytesIO() for stdout_line in iter(process.stdout.readline, ''): if not test_started: # Terminates the CheckXcodebuildStuckThread when test has started # or XCTRunner.app has started. # But XCTRunner.app start does not mean test start. if ios_constants.TEST_STARTED_SIGNAL in stdout_line: test_started = True check_xcodebuild_stuck.Terminate() # Only terminate the check_xcodebuild_stuck thread when running on # iphonesimulator device. When running on iphoneos device, the # XCTRunner.app may not launch the test session sometimes # (error rate < 1%). if (self._test_type == ios_constants.TestType.XCUITEST and ios_constants.XCTRUNNER_STARTED_SIGNAL in stdout_line and self._sdk == ios_constants.SDK.IPHONESIMULATOR): check_xcodebuild_stuck.Terminate() else: if self._succeeded_signal and self._succeeded_signal in stdout_line: test_succeeded = True if self._failed_signal and self._failed_signal in stdout_line: test_failed = True sys.stdout.write(stdout_line) sys.stdout.flush() # If return_output is false, the output is only used for checking error # cause and deleting cached files (_DeleteTestCacheFileDirs method). if return_output or not test_started: output.write(stdout_line) try: if test_started: if test_succeeded: exit_code = runner_exit_codes.EXITCODE.SUCCEEDED elif test_failed: exit_code = runner_exit_codes.EXITCODE.FAILED else: exit_code = runner_exit_codes.EXITCODE.ERROR return exit_code, output.getvalue( ) if return_output else None check_xcodebuild_stuck.Terminate() if check_xcodebuild_stuck.is_xcodebuild_stuck: return self._GetResultForXcodebuildStuck( output, return_output) output_str = output.getvalue() if self._sdk == ios_constants.SDK.IPHONEOS: if ((re.search(_DEVICE_TYPE_WAS_NULL_PATTERN, output_str) or _LOST_CONNECTION_ERROR in output_str) and i < max_attempts - 1): logging.warning( 'Failed to launch test on the device. Will relaunch again.' ) continue if _TOO_MANY_INSTANCES_ALREADY_RUNNING in output_str: return (runner_exit_codes.EXITCODE.NEED_REBOOT_DEVICE, output_str if return_output else None) if self._sdk == ios_constants.SDK.IPHONESIMULATOR: if self._NeedRebootSim(output_str): return (runner_exit_codes.EXITCODE.NEED_REBOOT_DEVICE, output_str if return_output else None) if self._NeedRecreateSim(output_str): return (runner_exit_codes.EXITCODE.NEED_RECREATE_SIM, output_str if return_output else None) # The following error can be fixed by relaunching the test again. try: if sim_log_path and os.path.exists(sim_log_path): # Sleeps short time. Then the tail simulator log can get more log. time.sleep(0.5) tail_sim_log = _ReadFileTailInShell( sim_log_path, _TAIL_SIM_LOG_LINE) if (self._test_type == ios_constants.TestType.LOGIC_TEST and simulator_util. IsXctestFailedToLaunchOnSim(tail_sim_log) or self._test_type != ios_constants.TestType.LOGIC_TEST and simulator_util. IsAppFailedToLaunchOnSim(tail_sim_log) or simulator_util.IsCoreSimulatorCrash( tail_sim_log)): raise ios_errors.SimError('') if _PROCESS_EXISTED_OR_CRASHED_ERROR in output_str: raise ios_errors.SimError('') if ios_constants.CORESIMULATOR_INTERRUPTED_ERROR in output_str: # Sleep random[0,2] seconds to avoid race condition. It is a known # issue that CoreSimulatorService connection will be interrupted # if two simulators are booting at the same time. time.sleep(random.uniform(0, 2)) raise ios_errors.SimError('') if (self._app_bundle_id and not simulator_util.Simulator( self._device_id).IsAppInstalled( self._app_bundle_id)): raise ios_errors.SimError('') except ios_errors.SimError: if i < max_attempts - 1: logging.warning( 'Failed to launch test on simulator. Will relaunch again.' ) continue return (runner_exit_codes.EXITCODE.TEST_NOT_START, output_str if return_output else None) finally: _DeleteTestCacheFileDirs(output.getvalue(), self._sdk, self._test_type)
def _RunSimulatorTest(args): """The function of running test with new simulator.""" with xctest_session.XctestSession( sdk=ios_constants.SDK.IPHONESIMULATOR, work_dir=args.work_dir, output_dir=args.output_dir) as session: session.Prepare(app_under_test=args.app_under_test_path, test_bundle=args.test_bundle_path, xctestrun_file_path=args.xctestrun, test_type=args.test_type, signing_options=_GetJson( args.signing_options_json_path)) session.SetLaunchOptions(_GetJson(args.launch_options_json_path)) # In prior of Xcode 9, `xcodebuild test` will launch the Simulator.app # process. If there is Simulator.app before running test, it will cause # error later. if xcode_info_util.GetXcodeVersionNumber() < 900: simulator_util.QuitSimulatorApp() max_attempts = 3 reboot_sim = False for i in range(max_attempts): if not reboot_sim: simulator_id, _, _, _ = simulator_util.CreateNewSimulator( device_type=args.device_type, os_version=args.os_version, name=args.new_simulator_name) reboot_sim = False try: # Don't use command "{Xcode_developer_dir}Applications/ \ # Simulator.app/Contents/MacOS/Simulator" to launch the Simulator.app. # 1) `xcodebuild test` will handle the launch Simulator. # 2) If there are two Simulator.app processes launched by command line # and `xcodebuild test` starts to run on one of Simulator, the another # Simulator.app will popup 'Unable to boot device in current state: \ # Booted' dialog and may cause potential error. exit_code = session.RunTest(simulator_id) if i < max_attempts - 1: if exit_code == runner_exit_codes.EXITCODE.NEED_RECREATE_SIM: logging.warning( 'Will create a new simulator to retry running test.' ) continue if exit_code == runner_exit_codes.EXITCODE.NEED_REBOOT_DEVICE: reboot_sim = True logging.warning( 'Will reboot the simulator to retry running test.' ) continue return exit_code finally: # 1. In prior of Xcode 9, `xcodebuild test` will launch the # Simulator.app process. Quit the Simulator.app to avoid side effect. # 2. Quit Simulator.app can also shutdown the simulator. To make sure # the Simulator state to be SHUTDOWN, still call shutdown command # later. if xcode_info_util.GetXcodeVersionNumber() < 900: simulator_util.QuitSimulatorApp() simulator_obj = simulator_util.Simulator(simulator_id) if reboot_sim: simulator_obj.Shutdown() else: # In Xcode 9+, simctl can delete the Booted simulator. # In prior of Xcode 9, we have to shutdown the simulator first # before deleting it. if xcode_info_util.GetXcodeVersionNumber() < 900: simulator_obj.Shutdown() simulator_obj.Delete()