#!/usr/bin/env python # Copyright (c) 2012 Google Inc. All rights reserved. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. """gyptest.py -- test runner for GYP tests.""" from __future__ import print_function import argparse import math import os import platform import subprocess import sys import time def is_test_name(f): return f.startswith('gyptest') and f.endswith('.py') def find_all_gyptest_files(directory): result = [] for root, dirs, files in os.walk(directory): result.extend([ os.path.join(root, f) for f in files if is_test_name(f) ]) result.sort() return result def main(argv=None): if argv is None: argv = sys.argv parser = argparse.ArgumentParser() parser.add_argument("-a", "--all", action="store_true", help="run all tests") parser.add_argument("-C", "--chdir", action="store", help="change to directory") parser.add_argument("-f", "--format", action="store", default='', help="run tests with the specified formats") parser.add_argument("-G", '--gyp_option', action="append", default=[], help="Add -G options to the gyp command line") parser.add_argument("-l", "--list", action="store_true", help="list available tests and exit") parser.add_argument("-n", "--no-exec", action="store_true", help="no execute, just print the command line") parser.add_argument("--path", action="append", default=[], help="additional $PATH directory") parser.add_argument("-q", "--quiet", action="store_true", help="quiet, don't print anything unless there are failures") parser.add_argument("-v", "--verbose", action="store_true", help="print configuration info and test results.") parser.add_argument('tests', nargs='*') args = parser.parse_args(argv[1:]) if args.chdir: os.chdir(args.chdir) if args.path: extra_path = [os.path.abspath(p) for p in args.path] extra_path = os.pathsep.join(extra_path) os.environ['PATH'] = extra_path + os.pathsep + os.environ['PATH'] if not args.tests: if not args.all: sys.stderr.write('Specify -a to get all tests.\n') return 1 args.tests = ['test'] tests = [] for arg in args.tests: if os.path.isdir(arg): tests.extend(find_all_gyptest_files(os.path.normpath(arg))) else: if not is_test_name(os.path.basename(arg)): print(arg, 'is not a valid gyp test name.', file=sys.stderr) sys.exit(1) tests.append(arg) if args.list: for test in tests: print(test) sys.exit(0) os.environ['PYTHONPATH'] = os.path.abspath('test/lib') if args.verbose: print_configuration_info() if args.gyp_option and not args.quiet: print('Extra Gyp options: %s\n' % args.gyp_option) if args.format: format_list = args.format.split(',') else: format_list = { 'aix5': ['make'], 'freebsd7': ['make'], 'freebsd8': ['make'], 'openbsd5': ['make'], 'cygwin': ['msvs'], 'win32': ['msvs', 'ninja'], 'linux': ['make', 'ninja'], 'linux2': ['make', 'ninja'], 'linux3': ['make', 'ninja'], # TODO: Re-enable xcode-ninja. # https://bugs.chromium.org/p/gyp/issues/detail?id=530 # 'darwin': ['make', 'ninja', 'xcode', 'xcode-ninja'], 'darwin': ['make', 'ninja', 'xcode'], }[sys.platform] gyp_options = [] for option in args.gyp_option: gyp_options += ['-G', option] runner = Runner(format_list, tests, gyp_options, args.verbose) runner.run() if not args.quiet: runner.print_results() if runner.failures: return 1 else: return 0 def print_configuration_info(): print('Test configuration:') if sys.platform == 'darwin': sys.path.append(os.path.abspath('test/lib')) import TestMac print(' Mac %s %s' % (platform.mac_ver()[0], platform.mac_ver()[2])) print(' Xcode %s' % TestMac.Xcode.Version()) elif sys.platform == 'win32': sys.path.append(os.path.abspath('pylib')) import gyp.MSVSVersion print(' Win %s %s\n' % platform.win32_ver()[0:2]) print(' MSVS %s' % gyp.MSVSVersion.SelectVisualStudioVersion().Description()) elif sys.platform in ('linux', 'linux2'): print(' Linux %s' % ' '.join(platform.linux_distribution())) print(' Python %s' % platform.python_version()) print(' PYTHONPATH=%s' % os.environ['PYTHONPATH']) print() class Runner(object): def __init__(self, formats, tests, gyp_options, verbose): self.formats = formats self.tests = tests self.verbose = verbose self.gyp_options = gyp_options self.failures = [] self.num_tests = len(formats) * len(tests) num_digits = len(str(self.num_tests)) self.fmt_str = '[%%%dd/%%%dd] (%%s) %%s' % (num_digits, num_digits) self.isatty = sys.stdout.isatty() and not self.verbose self.env = os.environ.copy() self.hpos = 0 def run(self): run_start = time.time() i = 1 for fmt in self.formats: for test in self.tests: self.run_test(test, fmt, i) i += 1 if self.isatty: self.erase_current_line() self.took = time.time() - run_start def run_test(self, test, fmt, i): if self.isatty: self.erase_current_line() msg = self.fmt_str % (i, self.num_tests, fmt, test) self.print_(msg) start = time.time() cmd = [sys.executable, test] + self.gyp_options self.env['TESTGYP_FORMAT'] = fmt proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, env=self.env) proc.wait() took = time.time() - start stdout = proc.stdout.read().decode('utf8') if proc.returncode == 2: res = 'skipped' elif proc.returncode: res = 'failed' self.failures.append('(%s) %s' % (test, fmt)) else: res = 'passed' res_msg = ' %s %.3fs' % (res, took) self.print_(res_msg) if (stdout and not stdout.endswith('PASSED\n') and not (stdout.endswith('NO RESULT\n'))): print() for l in stdout.splitlines(): print(' %s' % l) elif not self.isatty: print() def print_(self, msg): print(msg, end='') index = msg.rfind('\n') if index == -1: self.hpos += len(msg) else: self.hpos = len(msg) - index sys.stdout.flush() def erase_current_line(self): print('\b' * self.hpos + ' ' * self.hpos + '\b' * self.hpos, end='') sys.stdout.flush() self.hpos = 0 def print_results(self): num_failures = len(self.failures) if num_failures: print() if num_failures == 1: print("Failed the following test:") else: print("Failed the following %d tests:" % num_failures) print("\t" + "\n\t".join(sorted(self.failures))) print() print('Ran %d tests in %.3fs, %d failed.' % (self.num_tests, self.took, num_failures)) print() if __name__ == "__main__": sys.exit(main())