diff --git a/ndk/run_tests.py b/ndk/run_tests.py index f5367e2f..e67a4db5 100755 --- a/ndk/run_tests.py +++ b/ndk/run_tests.py @@ -40,7 +40,6 @@ List, Mapping, Optional, - Sequence, Tuple, Union, ) @@ -80,18 +79,22 @@ DEVICE_TEST_BASE_DIR = '/data/local/tmp/tests' +AdbResult = tuple[int, str, str, str] + + def logger() -> logging.Logger: """Returns the module logger.""" return logging.getLogger(__name__) -def shell_nocheck_wrap_errors(device: Device, - cmd: Sequence[str]) -> Tuple[int, str, str]: +def shell_nocheck_wrap_errors(device: Device, cmd: str) -> AdbResult: """Invokes device.shell_nocheck and wraps exceptions as failed commands.""" + repro_cmd = f'adb -s {device.serial} shell {shlex.quote(cmd)}' try: - return device.shell_nocheck(cmd) + rc, stdout, stderr = device.shell_nocheck([cmd]) + return rc, stdout, stderr, repro_cmd except RuntimeError: - return 1, shlex.join(cmd), traceback.format_exc() + return 1, cmd, traceback.format_exc(), repro_cmd # TODO: Extract a common interface from this and ndk.test.types.Test for the @@ -121,9 +124,13 @@ def check_broken( self, device: Device) -> Union[Tuple[None, None], Tuple[str, str]]: raise NotImplementedError - def run(self, device: Device) -> Tuple[int, str, str]: + def run(self, device: Device) -> AdbResult: raise NotImplementedError + def run_cmd(self, device: Device, cmd: str) -> AdbResult: + logger().info('%s: shell_nocheck "%s"', device.name, cmd) + return shell_nocheck_wrap_errors(device, cmd) + class BasicTestCase(TestCase): """A test case for the standard NDK test builder. @@ -157,11 +164,10 @@ def check_broken( self, device: Device) -> Union[Tuple[None, None], Tuple[str, str]]: return self.get_test_config().run_broken(self, device) - def run(self, device: Device) -> Tuple[int, str, str]: - cmd = 'cd {} && LD_LIBRARY_PATH={} ./{} 2>&1'.format( - self.device_dir, self.device_dir, self.executable) - logger().info('%s: shell_nocheck "%s"', device.name, cmd) - return shell_nocheck_wrap_errors(device, [cmd]) + def run(self, device: Device) -> AdbResult: + return self.run_cmd( + device, 'cd {} && LD_LIBRARY_PATH={} ./{} 2>&1'.format( + self.device_dir, self.device_dir, self.executable)) class LibcxxTestCase(TestCase): @@ -212,13 +218,12 @@ def check_broken( return config, bug return None, None - def run(self, device: Device) -> Tuple[int, str, str]: + def run(self, device: Device) -> AdbResult: libcxx_so_dir = posixpath.join( DEVICE_TEST_BASE_DIR, str(self.config), 'libcxx/libc++') - cmd = 'cd {} && LD_LIBRARY_PATH={} ./{} 2>&1'.format( - self.device_dir, libcxx_so_dir, self.executable) - logger().info('%s: shell_nocheck "%s"', device.name, cmd) - return shell_nocheck_wrap_errors(device, [cmd]) + return self.run_cmd( + device, 'cd {} && LD_LIBRARY_PATH={} ./{} 2>&1'.format( + self.device_dir, libcxx_so_dir, self.executable)) class TestRun: @@ -241,15 +246,14 @@ def build_system(self) -> str: def config(self) -> BuildConfiguration: return self.test_case.config - def make_result(self, adb_result_tuple: Tuple[int, str, str], - device: Device) -> TestResult: - status, out, _ = adb_result_tuple + def make_result(self, adb_result: AdbResult, device: Device) -> TestResult: + status, out, _, cmd = adb_result result: TestResult if status == 0: result = Success(self) else: out = '\n'.join([str(device), out]) - result = Failure(self, out) + result = Failure(self, out, cmd) return self.fixup_xfail(result, device) def fixup_xfail(self, result: TestResult, device: Device) -> TestResult: diff --git a/ndk/test/result.py b/ndk/test/result.py index 1874cc56..c485c99c 100644 --- a/ndk/test/result.py +++ b/ndk/test/result.py @@ -14,7 +14,7 @@ # limitations under the License. # """Test result classes.""" -from typing import Any +from typing import Any, Optional import ndk.termcolor @@ -41,9 +41,13 @@ def to_string(self, colored: bool = False) -> str: class Failure(TestResult): - def __init__(self, test: Test, message: str) -> None: + def __init__(self, + test: Test, + message: str, + repro_cmd: Optional[str] = None) -> None: super().__init__(test) self.message = message + self.repro_cmd = repro_cmd def passed(self) -> bool: return False @@ -53,7 +57,9 @@ def failed(self) -> bool: def to_string(self, colored: bool = False) -> str: label = ndk.termcolor.maybe_color('FAIL', 'red', colored) - return f'{label} {self.test.name} [{self.test.config}]: {self.message}' + repro = f' {self.repro_cmd}' if self.repro_cmd else '' + return (f'{label} {self.test.name} [{self.test.config}]:{repro}\n' + f'{self.message}') class Success(TestResult):