1#!/usr/bin/env python3 2 3from typing import List, Optional, Tuple 4 5import argparse 6import os 7import pipes 8import subprocess 9import sys 10import unittest 11 12ANDROID_RUNNER_REQUIRED_VERBOSITY = 2 13 14 15def parse_args(args: Optional[List[str]] = None) -> argparse.Namespace: 16 parser = argparse.ArgumentParser() 17 parser.add_argument('test_name', type=str, help="Name of the test") 18 parser.add_argument('binary_path', type=str, 19 help="Full path to the binary on device") 20 parser.add_argument('--subtests', type=str, nargs='*', 21 help="Specific subtests to run") 22 parser.add_argument('--test_args', type=str, nargs='*', 23 help="Unfiltered arguments to pass to the run command") 24 25 args = parser.parse_args(args) 26 args.subtests = args.subtests or [] 27 args.test_args = args.test_args or [] 28 29 return args 30 31 32def run_command(command: str) -> Tuple[int, str, str]: 33 serial_number = os.environ.get("ANDROID_SERIAL", "") 34 if not serial_number: 35 raise "$ANDROID_SERIAL is empty, device must be specified" 36 37 full_command = ["adb", "-s", serial_number, "shell", command] 38 ret = subprocess.run( 39 full_command, capture_output=True, universal_newlines=True) 40 return ret.returncode, ret.stdout, ret.stderr 41 42 43def get_all_subtests(binary_path: str) -> List[str]: 44 retcode, output, _ = run_command(f'{binary_path} --help') 45 46 test_name_line = "Test names" 47 index = output.find(test_name_line) 48 if index == -1: 49 return [] 50 51 test_names_output = output[index:] 52 test_names = [] 53 # Skip the first line which starts with "Test names" 54 for test_name in test_names_output.splitlines()[1:]: 55 if not test_name.startswith((" ", "\t")): 56 break 57 test_names.append(test_name.strip()) 58 59 return test_names 60 61 62def get_subtests(binary_path: str, subtests: List[str]) -> List[str]: 63 all_subtests = set(get_all_subtests(binary_path)) 64 if not subtests: 65 return all_subtests 66 67 subtests = set(subtests) 68 selected_subtests = subtests & all_subtests 69 remaining_subtests = subtests - all_subtests 70 71 if remaining_subtests: 72 print("Could not find subtests: {}".format(', '.join(remaining_subtests)), 73 file=sys.stderr) 74 75 return sorted(list(selected_subtests)) 76 77 78class OpenCLTest(unittest.TestCase): 79 80 def __init__(self, test_name: str, binary_path: str, args: List[str]): 81 82 self._test_name = test_name 83 self._binary_path = binary_path 84 self._args = args 85 86 self.command = " ".join( 87 [self._binary_path, self._test_name] + 88 list(map(pipes.quote, self._args)) 89 ) 90 91 self.test_func_name = self._test_name 92 setattr(self, self.test_func_name, self.genericTest) 93 94 super().__init__(methodName=self.test_func_name) 95 96 def genericTest(self): 97 retcode, output, oerror = run_command(self.command) 98 99 # TODO(layog): CTS currently return non-zero return code if the 100 # implementation is missing for some API even if the API is not supported by 101 # the version reported by the driver. Need to patch upstream. 102 missing_line = f"ERROR: Test '{self._test_name}' is missing implementation" 103 if missing_line in output or missing_line in oerror: 104 self.skipTest(f"{self._test_name} API not available in the driver") 105 106 self.assertFalse(retcode, "Test exited with non-zero status") 107 108 # TODO(b/158646251): Update upstream to exit with proper error code 109 passed_line = "PASSED test." 110 self.assertTrue(passed_line in output) 111 112 113def main(): 114 """main entrypoint for test runner""" 115 args = parse_args(sys.argv[1:]) 116 117 # HACK: Name hack to report the actual test name 118 OpenCLTest.__name__ = args.test_name 119 OpenCLTest.__qualname__ = args.test_name 120 121 suite = unittest.TestSuite() 122 subtests = get_subtests(args.binary_path, args.subtests) 123 for subtest in subtests: 124 suite.addTest(OpenCLTest(subtest, args.binary_path, args.test_args)) 125 126 runner = unittest.TextTestRunner( 127 stream=sys.stderr, verbosity=ANDROID_RUNNER_REQUIRED_VERBOSITY) 128 runner.run(suite) 129 130 131if __name__ == "__main__": 132 main() 133