#!/usr/bin/env python3 from typing import List, Optional, Tuple import argparse import os import pipes import subprocess import sys import unittest ANDROID_RUNNER_REQUIRED_VERBOSITY = 2 def parse_args(args: Optional[List[str]] = None) -> argparse.Namespace: parser = argparse.ArgumentParser() parser.add_argument('test_name', type=str, help="Name of the test") parser.add_argument('binary_path', type=str, help="Full path to the binary on device") parser.add_argument('--subtests', type=str, nargs='*', help="Specific subtests to run") parser.add_argument('--test_args', type=str, nargs='*', help="Unfiltered arguments to pass to the run command") args = parser.parse_args(args) args.subtests = args.subtests or [] args.test_args = args.test_args or [] return args def run_command(command: str) -> Tuple[int, str, str]: serial_number = os.environ.get("ANDROID_SERIAL", "") if not serial_number: raise "$ANDROID_SERIAL is empty, device must be specified" full_command = ["adb", "-s", serial_number, "shell", command] ret = subprocess.run( full_command, capture_output=True, universal_newlines=True) return ret.returncode, ret.stdout, ret.stderr def get_all_subtests(binary_path: str) -> List[str]: retcode, output, _ = run_command(f'{binary_path} --help') test_name_line = "Test names" index = output.find(test_name_line) if index == -1: return [] test_names_output = output[index:] test_names = [] # Skip the first line which starts with "Test names" for test_name in test_names_output.splitlines()[1:]: if not test_name.startswith((" ", "\t")): break test_names.append(test_name.strip()) return test_names def get_subtests(binary_path: str, subtests: List[str]) -> List[str]: all_subtests = set(get_all_subtests(binary_path)) if not subtests: return all_subtests subtests = set(subtests) selected_subtests = subtests & all_subtests remaining_subtests = subtests - all_subtests if remaining_subtests: print("Could not find subtests: {}".format(', '.join(remaining_subtests)), file=sys.stderr) return sorted(list(selected_subtests)) class OpenCLTest(unittest.TestCase): def __init__(self, test_name: str, binary_path: str, args: List[str]): self._test_name = test_name self._binary_path = binary_path self._args = args self.command = " ".join( [self._binary_path, self._test_name] + list(map(pipes.quote, self._args)) ) self.test_func_name = self._test_name setattr(self, self.test_func_name, self.genericTest) super().__init__(methodName=self.test_func_name) def genericTest(self): retcode, output, oerror = run_command(self.command) # TODO(layog): CTS currently return non-zero return code if the # implementation is missing for some API even if the API is not supported by # the version reported by the driver. Need to patch upstream. missing_line = f"ERROR: Test '{self._test_name}' is missing implementation" if missing_line in output or missing_line in oerror: self.skipTest(f"{self._test_name} API not available in the driver") self.assertFalse(retcode, "Test exited with non-zero status") # TODO(b/158646251): Update upstream to exit with proper error code passed_line = "PASSED test." self.assertTrue(passed_line in output) def main(): """main entrypoint for test runner""" args = parse_args(sys.argv[1:]) # HACK: Name hack to report the actual test name OpenCLTest.__name__ = args.test_name OpenCLTest.__qualname__ = args.test_name suite = unittest.TestSuite() subtests = get_subtests(args.binary_path, args.subtests) for subtest in subtests: suite.addTest(OpenCLTest(subtest, args.binary_path, args.test_args)) runner = unittest.TextTestRunner( stream=sys.stderr, verbosity=ANDROID_RUNNER_REQUIRED_VERBOSITY) runner.run(suite) if __name__ == "__main__": main()