Home | History | Annotate | Download | only in gyp
      1 #!/usr/bin/env python
      2 
      3 # Copyright (c) 2012 Google Inc. All rights reserved.
      4 # Use of this source code is governed by a BSD-style license that can be
      5 # found in the LICENSE file.
      6 
      7 __doc__ = """
      8 gyptest.py -- test runner for GYP tests.
      9 """
     10 
     11 import os
     12 import optparse
     13 import subprocess
     14 import sys
     15 
     16 class CommandRunner:
     17   """
     18   Executor class for commands, including "commands" implemented by
     19   Python functions.
     20   """
     21   verbose = True
     22   active = True
     23 
     24   def __init__(self, dictionary={}):
     25     self.subst_dictionary(dictionary)
     26 
     27   def subst_dictionary(self, dictionary):
     28     self._subst_dictionary = dictionary
     29 
     30   def subst(self, string, dictionary=None):
     31     """
     32     Substitutes (via the format operator) the values in the specified
     33     dictionary into the specified command.
     34 
     35     The command can be an (action, string) tuple.  In all cases, we
     36     perform substitution on strings and don't worry if something isn't
     37     a string.  (It's probably a Python function to be executed.)
     38     """
     39     if dictionary is None:
     40       dictionary = self._subst_dictionary
     41     if dictionary:
     42       try:
     43         string = string % dictionary
     44       except TypeError:
     45         pass
     46     return string
     47 
     48   def display(self, command, stdout=None, stderr=None):
     49     if not self.verbose:
     50       return
     51     if type(command) == type(()):
     52       func = command[0]
     53       args = command[1:]
     54       s = '%s(%s)' % (func.__name__, ', '.join(map(repr, args)))
     55     if type(command) == type([]):
     56       # TODO:  quote arguments containing spaces
     57       # TODO:  handle meta characters?
     58       s = ' '.join(command)
     59     else:
     60       s = self.subst(command)
     61     if not s.endswith('\n'):
     62       s += '\n'
     63     sys.stdout.write(s)
     64     sys.stdout.flush()
     65 
     66   def execute(self, command, stdout=None, stderr=None):
     67     """
     68     Executes a single command.
     69     """
     70     if not self.active:
     71       return 0
     72     if type(command) == type(''):
     73       command = self.subst(command)
     74       cmdargs = shlex.split(command)
     75       if cmdargs[0] == 'cd':
     76          command = (os.chdir,) + tuple(cmdargs[1:])
     77     if type(command) == type(()):
     78       func = command[0]
     79       args = command[1:]
     80       return func(*args)
     81     else:
     82       if stdout is sys.stdout:
     83         # Same as passing sys.stdout, except python2.4 doesn't fail on it.
     84         subout = None
     85       else:
     86         # Open pipe for anything else so Popen works on python2.4.
     87         subout = subprocess.PIPE
     88       if stderr is sys.stderr:
     89         # Same as passing sys.stderr, except python2.4 doesn't fail on it.
     90         suberr = None
     91       elif stderr is None:
     92         # Merge with stdout if stderr isn't specified.
     93         suberr = subprocess.STDOUT
     94       else:
     95         # Open pipe for anything else so Popen works on python2.4.
     96         suberr = subprocess.PIPE
     97       p = subprocess.Popen(command,
     98                            shell=(sys.platform == 'win32'),
     99                            stdout=subout,
    100                            stderr=suberr)
    101       p.wait()
    102       if stdout is None:
    103         self.stdout = p.stdout.read()
    104       elif stdout is not sys.stdout:
    105         stdout.write(p.stdout.read())
    106       if stderr not in (None, sys.stderr):
    107         stderr.write(p.stderr.read())
    108       return p.returncode
    109 
    110   def run(self, command, display=None, stdout=None, stderr=None):
    111     """
    112     Runs a single command, displaying it first.
    113     """
    114     if display is None:
    115       display = command
    116     self.display(display)
    117     return self.execute(command, stdout, stderr)
    118 
    119 
    120 class Unbuffered:
    121   def __init__(self, fp):
    122     self.fp = fp
    123   def write(self, arg):
    124     self.fp.write(arg)
    125     self.fp.flush()
    126   def __getattr__(self, attr):
    127     return getattr(self.fp, attr)
    128 
    129 sys.stdout = Unbuffered(sys.stdout)
    130 sys.stderr = Unbuffered(sys.stderr)
    131 
    132 
    133 def is_test_name(f):
    134   return f.startswith('gyptest') and f.endswith('.py')
    135 
    136 
    137 def find_all_gyptest_files(directory):
    138   result = []
    139   for root, dirs, files in os.walk(directory):
    140     if '.svn' in dirs:
    141       dirs.remove('.svn')
    142     result.extend([ os.path.join(root, f) for f in files if is_test_name(f) ])
    143   result.sort()
    144   return result
    145 
    146 
    147 def main(argv=None):
    148   if argv is None:
    149     argv = sys.argv
    150 
    151   usage = "gyptest.py [-ahlnq] [-f formats] [test ...]"
    152   parser = optparse.OptionParser(usage=usage)
    153   parser.add_option("-a", "--all", action="store_true",
    154             help="run all tests")
    155   parser.add_option("-C", "--chdir", action="store", default=None,
    156             help="chdir to the specified directory")
    157   parser.add_option("-f", "--format", action="store", default='',
    158             help="run tests with the specified formats")
    159   parser.add_option("-G", '--gyp_option', action="append", default=[],
    160             help="Add -G options to the gyp command line")
    161   parser.add_option("-l", "--list", action="store_true",
    162             help="list available tests and exit")
    163   parser.add_option("-n", "--no-exec", action="store_true",
    164             help="no execute, just print the command line")
    165   parser.add_option("--passed", action="store_true",
    166             help="report passed tests")
    167   parser.add_option("--path", action="append", default=[],
    168             help="additional $PATH directory")
    169   parser.add_option("-q", "--quiet", action="store_true",
    170             help="quiet, don't print test command lines")
    171   opts, args = parser.parse_args(argv[1:])
    172 
    173   if opts.chdir:
    174     os.chdir(opts.chdir)
    175 
    176   if opts.path:
    177     extra_path = [os.path.abspath(p) for p in opts.path]
    178     extra_path = os.pathsep.join(extra_path)
    179     os.environ['PATH'] = extra_path + os.pathsep + os.environ['PATH']
    180 
    181   if not args:
    182     if not opts.all:
    183       sys.stderr.write('Specify -a to get all tests.\n')
    184       return 1
    185     args = ['test']
    186 
    187   tests = []
    188   for arg in args:
    189     if os.path.isdir(arg):
    190       tests.extend(find_all_gyptest_files(os.path.normpath(arg)))
    191     else:
    192       if not is_test_name(os.path.basename(arg)):
    193         print >>sys.stderr, arg, 'is not a valid gyp test name.'
    194         sys.exit(1)
    195       tests.append(arg)
    196 
    197   if opts.list:
    198     for test in tests:
    199       print test
    200     sys.exit(0)
    201 
    202   CommandRunner.verbose = not opts.quiet
    203   CommandRunner.active = not opts.no_exec
    204   cr = CommandRunner()
    205 
    206   os.environ['PYTHONPATH'] = os.path.abspath('test/lib')
    207   if not opts.quiet:
    208     sys.stdout.write('PYTHONPATH=%s\n' % os.environ['PYTHONPATH'])
    209 
    210   passed = []
    211   failed = []
    212   no_result = []
    213 
    214   if opts.format:
    215     format_list = opts.format.split(',')
    216   else:
    217     # TODO:  not duplicate this mapping from pylib/gyp/__init__.py
    218     format_list = {
    219       'aix5':     ['make'],
    220       'freebsd7': ['make'],
    221       'freebsd8': ['make'],
    222       'openbsd5': ['make'],
    223       'cygwin':   ['msvs'],
    224       'win32':    ['msvs', 'ninja'],
    225       'linux2':   ['make', 'ninja'],
    226       'linux3':   ['make', 'ninja'],
    227       'darwin':   ['make', 'ninja', 'xcode'],
    228     }[sys.platform]
    229 
    230   for format in format_list:
    231     os.environ['TESTGYP_FORMAT'] = format
    232     if not opts.quiet:
    233       sys.stdout.write('TESTGYP_FORMAT=%s\n' % format)
    234 
    235     gyp_options = []
    236     for option in opts.gyp_option:
    237       gyp_options += ['-G', option]
    238     if gyp_options and not opts.quiet:
    239       sys.stdout.write('Extra Gyp options: %s\n' % gyp_options)
    240 
    241     for test in tests:
    242       status = cr.run([sys.executable, test] + gyp_options,
    243                       stdout=sys.stdout,
    244                       stderr=sys.stderr)
    245       if status == 2:
    246         no_result.append(test)
    247       elif status:
    248         failed.append(test)
    249       else:
    250         passed.append(test)
    251 
    252   if not opts.quiet:
    253     def report(description, tests):
    254       if tests:
    255         if len(tests) == 1:
    256           sys.stdout.write("\n%s the following test:\n" % description)
    257         else:
    258           fmt = "\n%s the following %d tests:\n"
    259           sys.stdout.write(fmt % (description, len(tests)))
    260         sys.stdout.write("\t" + "\n\t".join(tests) + "\n")
    261 
    262     if opts.passed:
    263       report("Passed", passed)
    264     report("Failed", failed)
    265     report("No result from", no_result)
    266 
    267   if failed:
    268     return 1
    269   else:
    270     return 0
    271 
    272 
    273 if __name__ == "__main__":
    274   sys.exit(main())
    275