1#!/usr/bin/env python 2# Copyright (c) 2012 Google Inc. All rights reserved. 3# Use of this source code is governed by a BSD-style license that can be 4# found in the LICENSE file. 5 6"""gyptest.py -- test runner for GYP tests.""" 7 8from __future__ import print_function 9 10import argparse 11import os 12import platform 13import subprocess 14import sys 15import time 16 17 18def is_test_name(f): 19 return f.startswith("gyptest") and f.endswith(".py") 20 21 22def find_all_gyptest_files(directory): 23 result = [] 24 for root, dirs, files in os.walk(directory): 25 result.extend([os.path.join(root, f) for f in files if is_test_name(f)]) 26 result.sort() 27 return result 28 29 30def main(argv=None): 31 if argv is None: 32 argv = sys.argv 33 34 parser = argparse.ArgumentParser() 35 parser.add_argument("-a", "--all", action="store_true", help="run all tests") 36 parser.add_argument("-C", "--chdir", action="store", help="change to directory") 37 parser.add_argument( 38 "-f", 39 "--format", 40 action="store", 41 default="", 42 help="run tests with the specified formats", 43 ) 44 parser.add_argument( 45 "-G", 46 "--gyp_option", 47 action="append", 48 default=[], 49 help="Add -G options to the gyp command line", 50 ) 51 parser.add_argument( 52 "-l", "--list", action="store_true", help="list available tests and exit" 53 ) 54 parser.add_argument( 55 "-n", 56 "--no-exec", 57 action="store_true", 58 help="no execute, just print the command line", 59 ) 60 parser.add_argument( 61 "--path", action="append", default=[], help="additional $PATH directory" 62 ) 63 parser.add_argument( 64 "-q", 65 "--quiet", 66 action="store_true", 67 help="quiet, don't print anything unless there are failures", 68 ) 69 parser.add_argument( 70 "-v", 71 "--verbose", 72 action="store_true", 73 help="print configuration info and test results.", 74 ) 75 parser.add_argument("tests", nargs="*") 76 args = parser.parse_args(argv[1:]) 77 78 if args.chdir: 79 os.chdir(args.chdir) 80 81 if args.path: 82 extra_path = [os.path.abspath(p) for p in args.path] 83 extra_path = os.pathsep.join(extra_path) 84 os.environ["PATH"] = extra_path + os.pathsep + os.environ["PATH"] 85 86 if not args.tests: 87 if not args.all: 88 sys.stderr.write("Specify -a to get all tests.\n") 89 return 1 90 args.tests = ["test"] 91 92 tests = [] 93 for arg in args.tests: 94 if os.path.isdir(arg): 95 tests.extend(find_all_gyptest_files(os.path.normpath(arg))) 96 else: 97 if not is_test_name(os.path.basename(arg)): 98 print(arg, "is not a valid gyp test name.", file=sys.stderr) 99 sys.exit(1) 100 tests.append(arg) 101 102 if args.list: 103 for test in tests: 104 print(test) 105 sys.exit(0) 106 107 os.environ["PYTHONPATH"] = os.path.abspath("test/lib") 108 109 if args.verbose: 110 print_configuration_info() 111 112 if args.gyp_option and not args.quiet: 113 print("Extra Gyp options: %s\n" % args.gyp_option) 114 115 if args.format: 116 format_list = args.format.split(",") 117 else: 118 format_list = { 119 "aix5": ["make"], 120 "freebsd7": ["make"], 121 "freebsd8": ["make"], 122 "openbsd5": ["make"], 123 "cygwin": ["msvs"], 124 "win32": ["msvs", "ninja"], 125 "linux": ["make", "ninja"], 126 "linux2": ["make", "ninja"], 127 "linux3": ["make", "ninja"], 128 # TODO: Re-enable xcode-ninja. 129 # https://bugs.chromium.org/p/gyp/issues/detail?id=530 130 # 'darwin': ['make', 'ninja', 'xcode', 'xcode-ninja'], 131 "darwin": ["make", "ninja", "xcode"], 132 }[sys.platform] 133 134 gyp_options = [] 135 for option in args.gyp_option: 136 gyp_options += ["-G", option] 137 138 runner = Runner(format_list, tests, gyp_options, args.verbose) 139 runner.run() 140 141 if not args.quiet: 142 runner.print_results() 143 144 if runner.failures: 145 return 1 146 else: 147 return 0 148 149 150def print_configuration_info(): 151 print("Test configuration:") 152 if sys.platform == "darwin": 153 sys.path.append(os.path.abspath("test/lib")) 154 import TestMac 155 156 print(" Mac %s %s" % (platform.mac_ver()[0], platform.mac_ver()[2])) 157 print(" Xcode %s" % TestMac.Xcode.Version()) 158 elif sys.platform == "win32": 159 sys.path.append(os.path.abspath("pylib")) 160 import gyp.MSVSVersion 161 162 print(" Win %s %s\n" % platform.win32_ver()[0:2]) 163 print(" MSVS %s" % gyp.MSVSVersion.SelectVisualStudioVersion().Description()) 164 elif sys.platform in ("linux", "linux2"): 165 print(" Linux %s" % " ".join(platform.linux_distribution())) 166 print(" Python %s" % platform.python_version()) 167 print(" PYTHONPATH=%s" % os.environ["PYTHONPATH"]) 168 print() 169 170 171class Runner(object): 172 def __init__(self, formats, tests, gyp_options, verbose): 173 self.formats = formats 174 self.tests = tests 175 self.verbose = verbose 176 self.gyp_options = gyp_options 177 self.failures = [] 178 self.num_tests = len(formats) * len(tests) 179 num_digits = len(str(self.num_tests)) 180 self.fmt_str = "[%%%dd/%%%dd] (%%s) %%s" % (num_digits, num_digits) 181 self.isatty = sys.stdout.isatty() and not self.verbose 182 self.env = os.environ.copy() 183 self.hpos = 0 184 185 def run(self): 186 run_start = time.time() 187 188 i = 1 189 for fmt in self.formats: 190 for test in self.tests: 191 self.run_test(test, fmt, i) 192 i += 1 193 194 if self.isatty: 195 self.erase_current_line() 196 197 self.took = time.time() - run_start 198 199 def run_test(self, test, fmt, i): 200 if self.isatty: 201 self.erase_current_line() 202 203 msg = self.fmt_str % (i, self.num_tests, fmt, test) 204 self.print_(msg) 205 206 start = time.time() 207 cmd = [sys.executable, test] + self.gyp_options 208 self.env["TESTGYP_FORMAT"] = fmt 209 proc = subprocess.Popen( 210 cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, env=self.env 211 ) 212 proc.wait() 213 took = time.time() - start 214 215 stdout = proc.stdout.read().decode("utf8") 216 if proc.returncode == 2: 217 res = "skipped" 218 elif proc.returncode: 219 res = "failed" 220 self.failures.append("(%s) %s" % (test, fmt)) 221 else: 222 res = "passed" 223 res_msg = " %s %.3fs" % (res, took) 224 self.print_(res_msg) 225 226 if ( 227 stdout 228 and not stdout.endswith("PASSED\n") 229 and not (stdout.endswith("NO RESULT\n")) 230 ): 231 print() 232 print("\n".join(" %s" % line for line in stdout.splitlines())) 233 elif not self.isatty: 234 print() 235 236 def print_(self, msg): 237 print(msg, end="") 238 index = msg.rfind("\n") 239 if index == -1: 240 self.hpos += len(msg) 241 else: 242 self.hpos = len(msg) - index 243 sys.stdout.flush() 244 245 def erase_current_line(self): 246 print("\b" * self.hpos + " " * self.hpos + "\b" * self.hpos, end="") 247 sys.stdout.flush() 248 self.hpos = 0 249 250 def print_results(self): 251 num_failures = len(self.failures) 252 if num_failures: 253 print() 254 if num_failures == 1: 255 print("Failed the following test:") 256 else: 257 print("Failed the following %d tests:" % num_failures) 258 print("\t" + "\n\t".join(sorted(self.failures))) 259 print() 260 print( 261 "Ran %d tests in %.3fs, %d failed." 262 % (self.num_tests, self.took, num_failures) 263 ) 264 print() 265 266 267if __name__ == "__main__": 268 sys.exit(main()) 269