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