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