• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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