Dan Albert | 6f56ab7 | 2014-05-06 14:12:12 -0700 | [diff] [blame] | 1 | # Copyright (C) 2014 The Android Open Source Project |
| 2 | # |
| 3 | # Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 | # you may not use this file except in compliance with the License. |
| 5 | # You may obtain a copy of the License at |
| 6 | # |
| 7 | # http://www.apache.org/licenses/LICENSE-2.0 |
| 8 | # |
| 9 | # Unless required by applicable law or agreed to in writing, software |
| 10 | # distributed under the License is distributed on an "AS IS" BASIS, |
| 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 12 | # See the License for the specific language governing permissions and |
| 13 | # limitations under the License. |
| 14 | import getopt |
| 15 | import multiprocessing |
| 16 | import os |
| 17 | import re |
| 18 | import subprocess |
| 19 | import sys |
| 20 | |
| 21 | |
| 22 | class ProgressBarWrapper(object): |
| 23 | def __init__(self, maxval): |
| 24 | try: |
| 25 | import progressbar |
| 26 | self.pb = progressbar.ProgressBar(maxval=maxval) |
| 27 | except ImportError: |
| 28 | self.pb = None |
| 29 | |
| 30 | def start(self): |
| 31 | if self.pb: |
| 32 | self.pb.start() |
| 33 | |
| 34 | def update(self, value): |
| 35 | if self.pb: |
| 36 | self.pb.update(value) |
| 37 | |
| 38 | def finish(self): |
| 39 | if self.pb: |
| 40 | self.pb.finish() |
| 41 | |
| 42 | |
| 43 | class HostTest(object): |
| 44 | def __init__(self, path): |
| 45 | self.src_path = re.sub(r'\.pass\.cpp', '', path) |
| 46 | self.name = '{0}'.format(self.src_path) |
| 47 | self.path = '{0}/bin/libc++tests/{1}'.format( |
| 48 | os.getenv('ANDROID_HOST_OUT'), self.name) |
| 49 | |
| 50 | def run(self): |
| 51 | return subprocess.call(['timeout', '30', self.path], |
| 52 | stdout=subprocess.PIPE, stderr=subprocess.PIPE) |
| 53 | |
| 54 | |
| 55 | class DeviceTest(object): |
| 56 | def __init__(self, path): |
| 57 | self.src_path = re.sub(r'\.pass\.cpp', '', path) |
| 58 | self.name = '{0}'.format(self.src_path) |
| 59 | self.path = '/system/bin/libc++tests/{0}'.format(self.name) |
| 60 | |
| 61 | def run(self): |
| 62 | return adb_shell(self.path) |
| 63 | |
| 64 | |
| 65 | def adb_shell(command): |
| 66 | proc = subprocess.Popen(['timeout', '30', |
| 67 | 'adb', 'shell', '{0}; echo $? 2>&1'.format(command)], |
| 68 | stdout=subprocess.PIPE, stderr=subprocess.PIPE) |
| 69 | out, err = proc.communicate() |
| 70 | proc.wait() |
| 71 | if proc.returncode: |
| 72 | return proc.returncode |
| 73 | out = [x for x in out.split('\r\n') if x] |
| 74 | return int(out[-1]) |
| 75 | |
| 76 | |
| 77 | def get_all_tests(subdir): |
| 78 | tests = {'host': [], 'device': []} |
| 79 | for path, _dirs, files in os.walk(subdir): |
| 80 | path = os.path.normpath(path) |
| 81 | if path == '.': |
| 82 | path = '' |
| 83 | for test in [t for t in files if t.endswith('.pass.cpp')]: |
| 84 | tests['host'].append(HostTest(os.path.join(path, test))) |
| 85 | tests['device'].append(DeviceTest(os.path.join(path, test))) |
| 86 | return tests |
| 87 | |
| 88 | |
| 89 | def get_tests_in_subdirs(subdirs): |
| 90 | tests = {'host': [], 'device': []} |
| 91 | for subdir in subdirs: |
| 92 | subdir_tests = get_all_tests(subdir=subdir) |
| 93 | tests['host'].extend(subdir_tests['host']) |
| 94 | tests['device'].extend(subdir_tests['device']) |
| 95 | return tests |
| 96 | |
| 97 | |
| 98 | def run_tests(tests, num_threads): |
| 99 | pb = ProgressBarWrapper(maxval=len(tests)) |
| 100 | pool = multiprocessing.Pool(num_threads) |
| 101 | |
| 102 | pb.start() |
| 103 | results = pool.imap(pool_task, tests) |
| 104 | num_run = {'host': 0, 'device': 0} |
| 105 | failures = {'host': [], 'device': []} |
| 106 | for name, status, target in results: |
| 107 | num_run[target] += 1 |
| 108 | if status: |
| 109 | failures[target].append(name) |
| 110 | pb.update(sum(num_run.values())) |
| 111 | pb.finish() |
| 112 | return {'num_run': num_run, 'failures': failures} |
| 113 | |
| 114 | |
| 115 | def report_results(results): |
| 116 | num_run = results['num_run'] |
| 117 | failures = results['failures'] |
| 118 | failed_both = sorted(filter( |
| 119 | lambda x: x in failures['host'], |
| 120 | failures['device'])) |
| 121 | |
| 122 | for target, failed in failures.iteritems(): |
| 123 | failed = [x for x in failed if x not in failed_both] |
| 124 | print '{0} tests run: {1}'.format(target, num_run[target]) |
| 125 | print '{0} tests failed: {1}'.format(target, len(failed)) |
| 126 | for failure in sorted(failed): |
| 127 | print '\t{0}'.format(failure) |
| 128 | print |
| 129 | |
| 130 | if len(failed_both): |
| 131 | print '{0} tests failed in both environments'.format(len(failed_both)) |
| 132 | for failure in failed_both: |
| 133 | print '\t{0}'.format(failure) |
| 134 | |
| 135 | |
| 136 | def pool_task(test): |
| 137 | target = 'host' if isinstance(test, HostTest) else 'device' |
| 138 | #print '{0} run {1}'.format(target, test.name) |
| 139 | return (test.name, test.run(), target) |
| 140 | |
| 141 | |
| 142 | def main(): |
| 143 | try: |
| 144 | opts, args = getopt.getopt( |
| 145 | sys.argv[1:], 'n:t:', ['threads=', 'target=']) |
| 146 | except getopt.GetoptError as err: |
| 147 | sys.exit(str(err)) |
| 148 | |
| 149 | subdirs = ['.'] |
| 150 | target = 'both' |
| 151 | num_threads = multiprocessing.cpu_count() * 2 |
| 152 | for opt, arg in opts: |
| 153 | if opt in ('-n', '--threads'): |
| 154 | num_threads = int(arg) |
| 155 | elif opt in ('-t', '--target'): |
| 156 | target = arg |
| 157 | else: |
| 158 | sys.exit('Unknown option {0}'.format(opt)) |
| 159 | |
| 160 | if len(args): |
| 161 | subdirs = args |
| 162 | |
| 163 | tests = get_tests_in_subdirs(subdirs) |
| 164 | if target == 'both': |
| 165 | tests = tests['host'] + tests['device'] |
| 166 | else: |
| 167 | tests = tests[target] |
| 168 | |
| 169 | results = run_tests(tests, num_threads) |
| 170 | report_results(results) |
| 171 | |
| 172 | |
| 173 | if __name__ == '__main__': |
| 174 | main() |