michael@0: #!/usr/bin/env python michael@0: michael@0: # Copyright (c) 2012 Google Inc. All rights reserved. michael@0: # Use of this source code is governed by a BSD-style license that can be michael@0: # found in the LICENSE file. michael@0: michael@0: __doc__ = """ michael@0: gyptest.py -- test runner for GYP tests. michael@0: """ michael@0: michael@0: import os michael@0: import optparse michael@0: import subprocess michael@0: import sys michael@0: michael@0: class CommandRunner: michael@0: """ michael@0: Executor class for commands, including "commands" implemented by michael@0: Python functions. michael@0: """ michael@0: verbose = True michael@0: active = True michael@0: michael@0: def __init__(self, dictionary={}): michael@0: self.subst_dictionary(dictionary) michael@0: michael@0: def subst_dictionary(self, dictionary): michael@0: self._subst_dictionary = dictionary michael@0: michael@0: def subst(self, string, dictionary=None): michael@0: """ michael@0: Substitutes (via the format operator) the values in the specified michael@0: dictionary into the specified command. michael@0: michael@0: The command can be an (action, string) tuple. In all cases, we michael@0: perform substitution on strings and don't worry if something isn't michael@0: a string. (It's probably a Python function to be executed.) michael@0: """ michael@0: if dictionary is None: michael@0: dictionary = self._subst_dictionary michael@0: if dictionary: michael@0: try: michael@0: string = string % dictionary michael@0: except TypeError: michael@0: pass michael@0: return string michael@0: michael@0: def display(self, command, stdout=None, stderr=None): michael@0: if not self.verbose: michael@0: return michael@0: if type(command) == type(()): michael@0: func = command[0] michael@0: args = command[1:] michael@0: s = '%s(%s)' % (func.__name__, ', '.join(map(repr, args))) michael@0: if type(command) == type([]): michael@0: # TODO: quote arguments containing spaces michael@0: # TODO: handle meta characters? michael@0: s = ' '.join(command) michael@0: else: michael@0: s = self.subst(command) michael@0: if not s.endswith('\n'): michael@0: s += '\n' michael@0: sys.stdout.write(s) michael@0: sys.stdout.flush() michael@0: michael@0: def execute(self, command, stdout=None, stderr=None): michael@0: """ michael@0: Executes a single command. michael@0: """ michael@0: if not self.active: michael@0: return 0 michael@0: if type(command) == type(''): michael@0: command = self.subst(command) michael@0: cmdargs = shlex.split(command) michael@0: if cmdargs[0] == 'cd': michael@0: command = (os.chdir,) + tuple(cmdargs[1:]) michael@0: if type(command) == type(()): michael@0: func = command[0] michael@0: args = command[1:] michael@0: return func(*args) michael@0: else: michael@0: if stdout is sys.stdout: michael@0: # Same as passing sys.stdout, except python2.4 doesn't fail on it. michael@0: subout = None michael@0: else: michael@0: # Open pipe for anything else so Popen works on python2.4. michael@0: subout = subprocess.PIPE michael@0: if stderr is sys.stderr: michael@0: # Same as passing sys.stderr, except python2.4 doesn't fail on it. michael@0: suberr = None michael@0: elif stderr is None: michael@0: # Merge with stdout if stderr isn't specified. michael@0: suberr = subprocess.STDOUT michael@0: else: michael@0: # Open pipe for anything else so Popen works on python2.4. michael@0: suberr = subprocess.PIPE michael@0: p = subprocess.Popen(command, michael@0: shell=(sys.platform == 'win32'), michael@0: stdout=subout, michael@0: stderr=suberr) michael@0: p.wait() michael@0: if stdout is None: michael@0: self.stdout = p.stdout.read() michael@0: elif stdout is not sys.stdout: michael@0: stdout.write(p.stdout.read()) michael@0: if stderr not in (None, sys.stderr): michael@0: stderr.write(p.stderr.read()) michael@0: return p.returncode michael@0: michael@0: def run(self, command, display=None, stdout=None, stderr=None): michael@0: """ michael@0: Runs a single command, displaying it first. michael@0: """ michael@0: if display is None: michael@0: display = command michael@0: self.display(display) michael@0: return self.execute(command, stdout, stderr) michael@0: michael@0: michael@0: class Unbuffered: michael@0: def __init__(self, fp): michael@0: self.fp = fp michael@0: def write(self, arg): michael@0: self.fp.write(arg) michael@0: self.fp.flush() michael@0: def __getattr__(self, attr): michael@0: return getattr(self.fp, attr) michael@0: michael@0: sys.stdout = Unbuffered(sys.stdout) michael@0: sys.stderr = Unbuffered(sys.stderr) michael@0: michael@0: michael@0: def find_all_gyptest_files(directory): michael@0: result = [] michael@0: for root, dirs, files in os.walk(directory): michael@0: if '.svn' in dirs: michael@0: dirs.remove('.svn') michael@0: result.extend([ os.path.join(root, f) for f in files michael@0: if f.startswith('gyptest') and f.endswith('.py') ]) michael@0: result.sort() michael@0: return result michael@0: michael@0: michael@0: def main(argv=None): michael@0: if argv is None: michael@0: argv = sys.argv michael@0: michael@0: usage = "gyptest.py [-ahlnq] [-f formats] [test ...]" michael@0: parser = optparse.OptionParser(usage=usage) michael@0: parser.add_option("-a", "--all", action="store_true", michael@0: help="run all tests") michael@0: parser.add_option("-C", "--chdir", action="store", default=None, michael@0: help="chdir to the specified directory") michael@0: parser.add_option("-f", "--format", action="store", default='', michael@0: help="run tests with the specified formats") michael@0: parser.add_option("-G", '--gyp_option', action="append", default=[], michael@0: help="Add -G options to the gyp command line") michael@0: parser.add_option("-l", "--list", action="store_true", michael@0: help="list available tests and exit") michael@0: parser.add_option("-n", "--no-exec", action="store_true", michael@0: help="no execute, just print the command line") michael@0: parser.add_option("--passed", action="store_true", michael@0: help="report passed tests") michael@0: parser.add_option("--path", action="append", default=[], michael@0: help="additional $PATH directory") michael@0: parser.add_option("-q", "--quiet", action="store_true", michael@0: help="quiet, don't print test command lines") michael@0: opts, args = parser.parse_args(argv[1:]) michael@0: michael@0: if opts.chdir: michael@0: os.chdir(opts.chdir) michael@0: michael@0: if opts.path: michael@0: extra_path = [os.path.abspath(p) for p in opts.path] michael@0: extra_path = os.pathsep.join(extra_path) michael@0: os.environ['PATH'] += os.pathsep + extra_path michael@0: michael@0: if not args: michael@0: if not opts.all: michael@0: sys.stderr.write('Specify -a to get all tests.\n') michael@0: return 1 michael@0: args = ['test'] michael@0: michael@0: tests = [] michael@0: for arg in args: michael@0: if os.path.isdir(arg): michael@0: tests.extend(find_all_gyptest_files(os.path.normpath(arg))) michael@0: else: michael@0: tests.append(arg) michael@0: michael@0: if opts.list: michael@0: for test in tests: michael@0: print test michael@0: sys.exit(0) michael@0: michael@0: CommandRunner.verbose = not opts.quiet michael@0: CommandRunner.active = not opts.no_exec michael@0: cr = CommandRunner() michael@0: michael@0: os.environ['PYTHONPATH'] = os.path.abspath('test/lib') michael@0: if not opts.quiet: michael@0: sys.stdout.write('PYTHONPATH=%s\n' % os.environ['PYTHONPATH']) michael@0: michael@0: passed = [] michael@0: failed = [] michael@0: no_result = [] michael@0: michael@0: if opts.format: michael@0: format_list = opts.format.split(',') michael@0: else: michael@0: # TODO: not duplicate this mapping from pylib/gyp/__init__.py michael@0: format_list = { michael@0: 'freebsd7': ['make'], michael@0: 'freebsd8': ['make'], michael@0: 'cygwin': ['msvs'], michael@0: 'win32': ['msvs', 'ninja'], michael@0: 'linux2': ['make', 'ninja'], michael@0: 'linux3': ['make', 'ninja'], michael@0: 'darwin': ['make', 'ninja', 'xcode'], michael@0: }[sys.platform] michael@0: michael@0: for format in format_list: michael@0: os.environ['TESTGYP_FORMAT'] = format michael@0: if not opts.quiet: michael@0: sys.stdout.write('TESTGYP_FORMAT=%s\n' % format) michael@0: michael@0: gyp_options = [] michael@0: for option in opts.gyp_option: michael@0: gyp_options += ['-G', option] michael@0: if gyp_options and not opts.quiet: michael@0: sys.stdout.write('Extra Gyp options: %s\n' % gyp_options) michael@0: michael@0: for test in tests: michael@0: status = cr.run([sys.executable, test] + gyp_options, michael@0: stdout=sys.stdout, michael@0: stderr=sys.stderr) michael@0: if status == 2: michael@0: no_result.append(test) michael@0: elif status: michael@0: failed.append(test) michael@0: else: michael@0: passed.append(test) michael@0: michael@0: if not opts.quiet: michael@0: def report(description, tests): michael@0: if tests: michael@0: if len(tests) == 1: michael@0: sys.stdout.write("\n%s the following test:\n" % description) michael@0: else: michael@0: fmt = "\n%s the following %d tests:\n" michael@0: sys.stdout.write(fmt % (description, len(tests))) michael@0: sys.stdout.write("\t" + "\n\t".join(tests) + "\n") michael@0: michael@0: if opts.passed: michael@0: report("Passed", passed) michael@0: report("Failed", failed) michael@0: report("No result from", no_result) michael@0: michael@0: if failed: michael@0: return 1 michael@0: else: michael@0: return 0 michael@0: michael@0: michael@0: if __name__ == "__main__": michael@0: sys.exit(main())