• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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__ = """
8gyptest.py -- test runner for GYP tests.
9"""
10
11import os
12import optparse
13import subprocess
14import sys
15
16class CommandRunner(object):
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
120class Unbuffered(object):
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
129sys.stdout = Unbuffered(sys.stdout)
130sys.stderr = Unbuffered(sys.stderr)
131
132
133def is_test_name(f):
134  return f.startswith('gyptest') and f.endswith('.py')
135
136
137def 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
147def 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', 'xcode-ninja'],
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
273if __name__ == "__main__":
274  sys.exit(main())
275