diff --git a/starboard/xb1/tools/xb1_launcher.py b/starboard/xb1/tools/xb1_launcher.py index ab54abd45660..cf252793fa1c 100644 --- a/starboard/xb1/tools/xb1_launcher.py +++ b/starboard/xb1/tools/xb1_launcher.py @@ -95,6 +95,14 @@ _XB1_NET_LOG_PORT = 49353 _XB1_NET_ARG_PORT = 49355 +# Number of times a test will try or retry. +_TEST_MAX_TRIES = 4 +# Seconds to wait between retries (scales with backoff factor). +_TEST_RETRY_WAIT = 8 +# Amount to multiply retry time with each failed attempt (i.e. 2 doubles the +# amount of time to wait between retries). +_TEST_RETRY_BACKOFF_FACTOR = 2 + _PROCESS_TIMEOUT = 60 * 5.0 _PROCESS_KILL_TIMEOUT_SECONDS = 5.0 @@ -448,6 +456,31 @@ def CheckPackageIsDeployed(self): return False return package_list[package_index:].split('\n')[0].strip() + def RunTest(self, appx_name: str): + self.net_args_thread = None + attempt_num = 0 + retry_wait_s = _TEST_RETRY_WAIT + while attempt_num < _TEST_MAX_TRIES: + if not self.net_args_thread or not self.net_args_thread.is_alive(): + # This thread must start before the app executes or else it is possible + # the app will hang at _network_api.ExecuteBinary() + self.net_args_thread = net_args.NetArgsThread(self.device_id, + _XB1_NET_ARG_PORT, + self._target_args) + if self._network_api.ExecuteBinary(_DEFAULT_PACKAGE_NAME, appx_name): + break + + if not self.net_args_thread.ArgsSent(): + self._LogLn( + 'Net Args were not sent to the test! This will likely cause ' + 'the test to fail!') + attempt_num += 1 + self._LogLn(f'Retry attempt {attempt_num}.') + time.sleep(retry_wait_s) + retry_wait_s *= _TEST_RETRY_BACKOFF_FACTOR + if hasattr(self, 'net_args_thread'): + self.net_args_thread.join() + def Run(self): # Only upload and install Appx on the first run. if FirstRun(): @@ -474,12 +507,6 @@ def Run(self): try: self.Kill() # Kill existing running app. - - # These threads must start before the app executes or else it is possible - # the app will hang at _network_api.ExecuteBinary() - self.net_args_thread = net_args.NetArgsThread(self.device_id, - _XB1_NET_ARG_PORT, - self._target_args) # While binary is running, extract the net log and stream it to # the output. self.net_log_thread = net_log.NetLogThread(self.device_id, @@ -487,7 +514,7 @@ def Run(self): appx_name = ToAppxFriendlyName(self.target_name) - self._network_api.ExecuteBinary(_DEFAULT_PACKAGE_NAME, appx_name) + self.RunTest(appx_name) while self._network_api.IsBinaryRunning(self.target_name): self._Log(self.net_log_thread.GetLog()) diff --git a/starboard/xb1/tools/xb1_network_api.py b/starboard/xb1/tools/xb1_network_api.py index 093218e662f7..2d8834e9e363 100644 --- a/starboard/xb1/tools/xb1_network_api.py +++ b/starboard/xb1/tools/xb1_network_api.py @@ -421,7 +421,8 @@ def FetchPackageFile(self, return None return None - def ExecuteBinary(self, partial_package_name, app_alias_name): + def ExecuteBinary(self, partial_package_name: str, + app_alias_name: str) -> bool: default_relative_name = self._GetDefaultRelativeId(partial_package_name) if not default_relative_name or not '!' in default_relative_name: raise IOError('Could not resolve package name "' + partial_package_name + @@ -432,33 +433,25 @@ def ExecuteBinary(self, partial_package_name, app_alias_name): appid_64 = base64.b64encode(package_relative_id.encode('UTF-8')) package_64 = base64.b64encode(default_relative_name.encode('UTF-8')) - retry_count = 4 - # Time to wait between tries. - retry_wait_s = 8 try: - while retry_count > 0: - self.LogLn('Executing: ' + package_relative_id) - response = self._DoJsonRequest( - 'POST', - _TASKMANAGER_ENDPOINT, - params={ - 'appid': appid_64, - 'package': package_64 - }, - raise_on_failure=False) - if not response or response == requests.codes.OK: - self.LogLn('Execution successful') - break - self.LogLn('Execution not successful: ' + str(response)) - self.LogLn('Retrying with ' + str(retry_count) + ' attempts remaining.') - time.sleep(retry_wait_s) - retry_count -= 1 - # Double the wait time until the next attempt. - retry_wait_s *= 2 + self.LogLn('Executing: ' + package_relative_id) + response = self._DoJsonRequest( + 'POST', + _TASKMANAGER_ENDPOINT, + params={ + 'appid': appid_64, + 'package': package_64 + }, + raise_on_failure=False) + if not response or response == requests.codes.OK: + self.LogLn('Execution successful') + return True + self.LogLn('Execution not successful: ' + str(response)) except Exception as err: err_msg = '\n Failed to run:\n ' + package_relative_id + \ '\n because of:\n' + str(err) raise IOError(err_msg) from err + return False # Given a package name, return all files + directories. # Throws IOError if the app is locked.