• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright (c) 2012 Google Inc. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5"""
6TestGyp.py:  a testing framework for GYP integration tests.
7"""
8
9import collections
10from contextlib import contextmanager
11import itertools
12import os
13import re
14import shutil
15import stat
16import subprocess
17import sys
18import tempfile
19
20import TestCmd
21import TestCommon
22from TestCommon import __all__
23
24__all__.extend([
25  'TestGyp',
26])
27
28
29def remove_debug_line_numbers(contents):
30  """Function to remove the line numbers from the debug output
31  of gyp and thus reduce the extreme fragility of the stdout
32  comparison tests.
33  """
34  lines = contents.splitlines()
35  # split each line on ":"
36  lines = [l.split(":", 3) for l in lines]
37  # join each line back together while ignoring the
38  # 3rd column which is the line number
39  lines = [len(l) > 3 and ":".join(l[3:]) or l for l in lines]
40  return "\n".join(lines)
41
42
43def match_modulo_line_numbers(contents_a, contents_b):
44  """File contents matcher that ignores line numbers."""
45  contents_a = remove_debug_line_numbers(contents_a)
46  contents_b = remove_debug_line_numbers(contents_b)
47  return TestCommon.match_exact(contents_a, contents_b)
48
49
50@contextmanager
51def LocalEnv(local_env):
52  """Context manager to provide a local OS environment."""
53  old_env = os.environ.copy()
54  os.environ.update(local_env)
55  try:
56    yield
57  finally:
58    os.environ.clear()
59    os.environ.update(old_env)
60
61
62class TestGypBase(TestCommon.TestCommon):
63  """
64  Class for controlling end-to-end tests of gyp generators.
65
66  Instantiating this class will create a temporary directory and
67  arrange for its destruction (via the TestCmd superclass) and
68  copy all of the non-gyptest files in the directory hierarchy of the
69  executing script.
70
71  The default behavior is to test the 'gyp' or 'gyp.bat' file in the
72  current directory.  An alternative may be specified explicitly on
73  instantiation, or by setting the TESTGYP_GYP environment variable.
74
75  This class should be subclassed for each supported gyp generator
76  (format).  Various abstract methods below define calling signatures
77  used by the test scripts to invoke builds on the generated build
78  configuration and to run executables generated by those builds.
79  """
80
81  formats = []
82  build_tool = None
83  build_tool_list = []
84
85  _exe = TestCommon.exe_suffix
86  _obj = TestCommon.obj_suffix
87  shobj_ = TestCommon.shobj_prefix
88  _shobj = TestCommon.shobj_suffix
89  lib_ = TestCommon.lib_prefix
90  _lib = TestCommon.lib_suffix
91  dll_ = TestCommon.dll_prefix
92  _dll = TestCommon.dll_suffix
93
94  # Constants to represent different targets.
95  ALL = '__all__'
96  DEFAULT = '__default__'
97
98  # Constants for different target types.
99  EXECUTABLE = '__executable__'
100  STATIC_LIB = '__static_lib__'
101  SHARED_LIB = '__shared_lib__'
102
103  def __init__(self, gyp=None, *args, **kw):
104    self.origin_cwd = os.path.abspath(os.path.dirname(sys.argv[0]))
105    self.extra_args = sys.argv[1:]
106
107    if not gyp:
108      gyp = os.environ.get('TESTGYP_GYP')
109      if not gyp:
110        if sys.platform == 'win32':
111          gyp = 'gyp.bat'
112        else:
113          gyp = 'gyp'
114    self.gyp = os.path.abspath(gyp)
115    self.no_parallel = False
116
117    self.formats = [self.format]
118
119    self.initialize_build_tool()
120
121    kw.setdefault('match', TestCommon.match_exact)
122
123    # Put test output in out/testworkarea by default.
124    # Use temporary names so there are no collisions.
125    workdir = os.path.join('out', kw.get('workdir', 'testworkarea'))
126    # Create work area if it doesn't already exist.
127    if not os.path.isdir(workdir):
128      os.makedirs(workdir)
129
130    kw['workdir'] = tempfile.mktemp(prefix='testgyp.', dir=workdir)
131
132    formats = kw.pop('formats', [])
133
134    super(TestGypBase, self).__init__(*args, **kw)
135
136    real_format = self.format.split('-')[-1]
137    excluded_formats = set([f for f in formats if f[0] == '!'])
138    included_formats = set(formats) - excluded_formats
139    if ('!'+real_format in excluded_formats or
140        included_formats and real_format not in included_formats):
141      msg = 'Invalid test for %r format; skipping test.\n'
142      self.skip_test(msg % self.format)
143
144    self.copy_test_configuration(self.origin_cwd, self.workdir)
145    self.set_configuration(None)
146
147    # Set $HOME so that gyp doesn't read the user's actual
148    # ~/.gyp/include.gypi file, which may contain variables
149    # and other settings that would change the output.
150    os.environ['HOME'] = self.workpath()
151    # Clear $GYP_DEFINES for the same reason.
152    if 'GYP_DEFINES' in os.environ:
153      del os.environ['GYP_DEFINES']
154    # Override the user's language settings, which could
155    # otherwise make the output vary from what is expected.
156    os.environ['LC_ALL'] = 'C'
157
158  def built_file_must_exist(self, name, type=None, **kw):
159    """
160    Fails the test if the specified built file name does not exist.
161    """
162    return self.must_exist(self.built_file_path(name, type, **kw))
163
164  def built_file_must_not_exist(self, name, type=None, **kw):
165    """
166    Fails the test if the specified built file name exists.
167    """
168    return self.must_not_exist(self.built_file_path(name, type, **kw))
169
170  def built_file_must_match(self, name, contents, **kw):
171    """
172    Fails the test if the contents of the specified built file name
173    do not match the specified contents.
174    """
175    return self.must_match(self.built_file_path(name, **kw), contents)
176
177  def built_file_must_not_match(self, name, contents, **kw):
178    """
179    Fails the test if the contents of the specified built file name
180    match the specified contents.
181    """
182    return self.must_not_match(self.built_file_path(name, **kw), contents)
183
184  def built_file_must_not_contain(self, name, contents, **kw):
185    """
186    Fails the test if the specified built file name contains the specified
187    contents.
188    """
189    return self.must_not_contain(self.built_file_path(name, **kw), contents)
190
191  def copy_test_configuration(self, source_dir, dest_dir):
192    """
193    Copies the test configuration from the specified source_dir
194    (the directory in which the test script lives) to the
195    specified dest_dir (a temporary working directory).
196
197    This ignores all files and directories that begin with
198    the string 'gyptest', and all '.svn' subdirectories.
199    """
200    for root, dirs, files in os.walk(source_dir):
201      if '.svn' in dirs:
202        dirs.remove('.svn')
203      dirs = [ d for d in dirs if not d.startswith('gyptest') ]
204      files = [ f for f in files if not f.startswith('gyptest') ]
205      for dirname in dirs:
206        source = os.path.join(root, dirname)
207        destination = source.replace(source_dir, dest_dir)
208        os.mkdir(destination)
209        if sys.platform != 'win32':
210          shutil.copystat(source, destination)
211      for filename in files:
212        source = os.path.join(root, filename)
213        destination = source.replace(source_dir, dest_dir)
214        shutil.copy2(source, destination)
215
216  def initialize_build_tool(self):
217    """
218    Initializes the .build_tool attribute.
219
220    Searches the .build_tool_list for an executable name on the user's
221    $PATH.  The first tool on the list is used as-is if nothing is found
222    on the current $PATH.
223    """
224    for build_tool in self.build_tool_list:
225      if not build_tool:
226        continue
227      if os.path.isabs(build_tool):
228        self.build_tool = build_tool
229        return
230      build_tool = self.where_is(build_tool)
231      if build_tool:
232        self.build_tool = build_tool
233        return
234
235    if self.build_tool_list:
236      self.build_tool = self.build_tool_list[0]
237
238  def relocate(self, source, destination):
239    """
240    Renames (relocates) the specified source (usually a directory)
241    to the specified destination, creating the destination directory
242    first if necessary.
243
244    Note:  Don't use this as a generic "rename" operation.  In the
245    future, "relocating" parts of a GYP tree may affect the state of
246    the test to modify the behavior of later method calls.
247    """
248    destination_dir = os.path.dirname(destination)
249    if not os.path.exists(destination_dir):
250      self.subdir(destination_dir)
251    os.rename(source, destination)
252
253  def report_not_up_to_date(self):
254    """
255    Reports that a build is not up-to-date.
256
257    This provides common reporting for formats that have complicated
258    conditions for checking whether a build is up-to-date.  Formats
259    that expect exact output from the command (make) can
260    just set stdout= when they call the run_build() method.
261    """
262    print "Build is not up-to-date:"
263    print self.banner('STDOUT ')
264    print self.stdout()
265    stderr = self.stderr()
266    if stderr:
267      print self.banner('STDERR ')
268      print stderr
269
270  def run_gyp(self, gyp_file, *args, **kw):
271    """
272    Runs gyp against the specified gyp_file with the specified args.
273    """
274
275    # When running gyp, and comparing its output we use a comparitor
276    # that ignores the line numbers that gyp logs in its debug output.
277    if kw.pop('ignore_line_numbers', False):
278      kw.setdefault('match', match_modulo_line_numbers)
279
280    # TODO:  --depth=. works around Chromium-specific tree climbing.
281    depth = kw.pop('depth', '.')
282    run_args = ['--depth='+depth]
283    run_args.extend(['--format='+f for f in self.formats]);
284    run_args.append(gyp_file)
285    if self.no_parallel:
286      run_args += ['--no-parallel']
287    # TODO: if extra_args contains a '--build' flag
288    # we really want that to only apply to the last format (self.format).
289    run_args.extend(self.extra_args)
290    run_args.extend(args)
291    return self.run(program=self.gyp, arguments=run_args, **kw)
292
293  def run(self, *args, **kw):
294    """
295    Executes a program by calling the superclass .run() method.
296
297    This exists to provide a common place to filter out keyword
298    arguments implemented in this layer, without having to update
299    the tool-specific subclasses or clutter the tests themselves
300    with platform-specific code.
301    """
302    if kw.has_key('SYMROOT'):
303      del kw['SYMROOT']
304    super(TestGypBase, self).run(*args, **kw)
305
306  def set_configuration(self, configuration):
307    """
308    Sets the configuration, to be used for invoking the build
309    tool and testing potential built output.
310    """
311    self.configuration = configuration
312
313  def configuration_dirname(self):
314    if self.configuration:
315      return self.configuration.split('|')[0]
316    else:
317      return 'Default'
318
319  def configuration_buildname(self):
320    if self.configuration:
321      return self.configuration
322    else:
323      return 'Default'
324
325  #
326  # Abstract methods to be defined by format-specific subclasses.
327  #
328
329  def build(self, gyp_file, target=None, **kw):
330    """
331    Runs a build of the specified target against the configuration
332    generated from the specified gyp_file.
333
334    A 'target' argument of None or the special value TestGyp.DEFAULT
335    specifies the default argument for the underlying build tool.
336    A 'target' argument of TestGyp.ALL specifies the 'all' target
337    (if any) of the underlying build tool.
338    """
339    raise NotImplementedError
340
341  def built_file_path(self, name, type=None, **kw):
342    """
343    Returns a path to the specified file name, of the specified type.
344    """
345    raise NotImplementedError
346
347  def built_file_basename(self, name, type=None, **kw):
348    """
349    Returns the base name of the specified file name, of the specified type.
350
351    A bare=True keyword argument specifies that prefixes and suffixes shouldn't
352    be applied.
353    """
354    if not kw.get('bare'):
355      if type == self.EXECUTABLE:
356        name = name + self._exe
357      elif type == self.STATIC_LIB:
358        name = self.lib_ + name + self._lib
359      elif type == self.SHARED_LIB:
360        name = self.dll_ + name + self._dll
361    return name
362
363  def run_built_executable(self, name, *args, **kw):
364    """
365    Runs an executable program built from a gyp-generated configuration.
366
367    The specified name should be independent of any particular generator.
368    Subclasses should find the output executable in the appropriate
369    output build directory, tack on any necessary executable suffix, etc.
370    """
371    raise NotImplementedError
372
373  def up_to_date(self, gyp_file, target=None, **kw):
374    """
375    Verifies that a build of the specified target is up to date.
376
377    The subclass should implement this by calling build()
378    (or a reasonable equivalent), checking whatever conditions
379    will tell it the build was an "up to date" null build, and
380    failing if it isn't.
381    """
382    raise NotImplementedError
383
384
385class TestGypGypd(TestGypBase):
386  """
387  Subclass for testing the GYP 'gypd' generator (spit out the
388  internal data structure as pretty-printed Python).
389  """
390  format = 'gypd'
391  def __init__(self, gyp=None, *args, **kw):
392    super(TestGypGypd, self).__init__(*args, **kw)
393    # gypd implies the use of 'golden' files, so parallelizing conflicts as it
394    # causes ordering changes.
395    self.no_parallel = True
396
397
398class TestGypCustom(TestGypBase):
399  """
400  Subclass for testing the GYP with custom generator
401  """
402
403  def __init__(self, gyp=None, *args, **kw):
404    self.format = kw.pop("format")
405    super(TestGypCustom, self).__init__(*args, **kw)
406
407
408class TestGypAndroid(TestGypBase):
409  """
410  Subclass for testing the GYP Android makefile generator. Note that
411  build/envsetup.sh and lunch must have been run before running tests.
412  """
413  format = 'android'
414
415  # Note that we can't use mmm as the build tool because ...
416  # - it builds all targets, whereas we need to pass a target
417  # - it is a function, whereas the test runner assumes the build tool is a file
418  # Instead we use make and duplicate the logic from mmm.
419  build_tool_list = ['make']
420
421  # We use our custom target 'gyp_all_modules', as opposed to the 'all_modules'
422  # target used by mmm, to build only those targets which are part of the gyp
423  # target 'all'.
424  ALL = 'gyp_all_modules'
425
426  def __init__(self, gyp=None, *args, **kw):
427    # Android requires build and test output to be inside its source tree.
428    # We use the following working directory for the test's source, but the
429    # test's build output still goes to $ANDROID_PRODUCT_OUT.
430    # Note that some tests explicitly set format='gypd' to invoke the gypd
431    # backend. This writes to the source tree, but there's no way around this.
432    kw['workdir'] = os.path.join('/tmp', 'gyptest',
433                                 kw.get('workdir', 'testworkarea'))
434    # We need to remove all gyp outputs from out/. Ths is because some tests
435    # don't have rules to regenerate output, so they will simply re-use stale
436    # output if present. Since the test working directory gets regenerated for
437    # each test run, this can confuse things.
438    # We don't have a list of build outputs because we don't know which
439    # dependent targets were built. Instead we delete all gyp-generated output.
440    # This may be excessive, but should be safe.
441    out_dir = os.environ['ANDROID_PRODUCT_OUT']
442    obj_dir = os.path.join(out_dir, 'obj')
443    shutil.rmtree(os.path.join(obj_dir, 'GYP'), ignore_errors = True)
444    for x in ['EXECUTABLES', 'STATIC_LIBRARIES', 'SHARED_LIBRARIES']:
445      for d in os.listdir(os.path.join(obj_dir, x)):
446        if d.endswith('_gyp_intermediates'):
447          shutil.rmtree(os.path.join(obj_dir, x, d), ignore_errors = True)
448    for x in [os.path.join('obj', 'lib'), os.path.join('system', 'lib')]:
449      for d in os.listdir(os.path.join(out_dir, x)):
450        if d.endswith('_gyp.so'):
451          os.remove(os.path.join(out_dir, x, d))
452
453    super(TestGypAndroid, self).__init__(*args, **kw)
454    self._adb_path = os.path.join(os.environ['ANDROID_HOST_OUT'], 'bin', 'adb')
455    self._device_serial = None
456    adb_devices_out = self._call_adb(['devices'])
457    devices = [l.split()[0] for l in adb_devices_out.splitlines()[1:-1]
458               if l.split()[1] == 'device']
459    if len(devices) == 0:
460      self._device_serial = None
461    else:
462      if len(devices) > 1:
463        self._device_serial = random.choice(devices)
464      else:
465        self._device_serial = devices[0]
466      self._call_adb(['root'])
467    self._to_install = set()
468
469  def target_name(self, target):
470    if target == self.ALL:
471      return self.ALL
472    # The default target is 'droid'. However, we want to use our special target
473    # to build only the gyp target 'all'.
474    if target in (None, self.DEFAULT):
475      return self.ALL
476    return target
477
478  _INSTALLABLE_PREFIX = 'Install: '
479
480  def build(self, gyp_file, target=None, **kw):
481    """
482    Runs a build using the Android makefiles generated from the specified
483    gyp_file. This logic is taken from Android's mmm.
484    """
485    arguments = kw.get('arguments', [])[:]
486    arguments.append(self.target_name(target))
487    arguments.append('-C')
488    arguments.append(os.environ['ANDROID_BUILD_TOP'])
489    kw['arguments'] = arguments
490    chdir = kw.get('chdir', '')
491    makefile = os.path.join(self.workdir, chdir, 'GypAndroid.mk')
492    os.environ['ONE_SHOT_MAKEFILE'] = makefile
493    result = self.run(program=self.build_tool, **kw)
494    for l in self.stdout().splitlines():
495      if l.startswith(TestGypAndroid._INSTALLABLE_PREFIX):
496        self._to_install.add(os.path.abspath(os.path.join(
497            os.environ['ANDROID_BUILD_TOP'],
498            l[len(TestGypAndroid._INSTALLABLE_PREFIX):])))
499    del os.environ['ONE_SHOT_MAKEFILE']
500    return result
501
502  def android_module(self, group, name, subdir):
503    if subdir:
504      name = '%s_%s' % (subdir, name)
505    if group == 'SHARED_LIBRARIES':
506      name = 'lib_%s' % name
507    return '%s_gyp' % name
508
509  def intermediates_dir(self, group, module_name):
510    return os.path.join(os.environ['ANDROID_PRODUCT_OUT'], 'obj', group,
511                        '%s_intermediates' % module_name)
512
513  def built_file_path(self, name, type=None, **kw):
514    """
515    Returns a path to the specified file name, of the specified type,
516    as built by Android. Note that we don't support the configuration
517    parameter.
518    """
519    # Built files are in $ANDROID_PRODUCT_OUT. This requires copying logic from
520    # the Android build system.
521    if type == None or type == self.EXECUTABLE:
522      return os.path.join(os.environ['ANDROID_PRODUCT_OUT'], 'obj', 'GYP',
523                          'shared_intermediates', name)
524    subdir = kw.get('subdir')
525    if type == self.STATIC_LIB:
526      group = 'STATIC_LIBRARIES'
527      module_name = self.android_module(group, name, subdir)
528      return os.path.join(self.intermediates_dir(group, module_name),
529                          '%s.a' % module_name)
530    if type == self.SHARED_LIB:
531      group = 'SHARED_LIBRARIES'
532      module_name = self.android_module(group, name, subdir)
533      return os.path.join(self.intermediates_dir(group, module_name), 'LINKED',
534                          '%s.so' % module_name)
535    assert False, 'Unhandled type'
536
537  def _adb_failure(self, command, msg, stdout, stderr):
538    """ Reports a failed adb command and fails the containing test.
539
540    Args:
541      command: The adb command that failed.
542      msg: The error description.
543      stdout: The standard output.
544      stderr: The standard error.
545    """
546    print '%s failed%s' % (' '.join(command), ': %s' % msg if msg else '')
547    print self.banner('STDOUT ')
548    stdout.seek(0)
549    print stdout.read()
550    print self.banner('STDERR ')
551    stderr.seek(0)
552    print stderr.read()
553    self.fail_test()
554
555  def _call_adb(self, command):
556    """ Calls the provided adb command.
557
558    If the command fails, the test fails.
559
560    Args:
561      command: The adb command to call.
562    Returns:
563      The command's output.
564    """
565    with tempfile.TemporaryFile(bufsize=0) as adb_out:
566      with tempfile.TemporaryFile(bufsize=0) as adb_err:
567        adb_command = [self._adb_path]
568        if self._device_serial:
569          adb_command += ['-s', self._device_serial]
570        is_shell = (command[0] == 'shell')
571        if is_shell:
572          command = [command[0], '%s; echo "\n$?";' % ' '.join(command[1:])]
573        adb_command += command
574        if subprocess.call(adb_command, stdout=adb_out, stderr=adb_err) != 0:
575          self._adb_failure(adb_command, None, adb_out, adb_err)
576        else:
577          adb_out.seek(0)
578          output = adb_out.read()
579          if is_shell:
580            output = output.splitlines(True)
581            try:
582              output[-2] = output[-2].rstrip('\r\n')
583              output, rc = (''.join(output[:-1]), int(output[-1]))
584            except ValueError:
585              self._adb_failure(adb_command, 'unexpected output format',
586                                adb_out, adb_err)
587            if rc != 0:
588              self._adb_failure(adb_command, 'exited with %d' % rc, adb_out,
589                                adb_err)
590          return output
591
592  def run_built_executable(self, name, *args, **kw):
593    """
594    Runs an executable program built from a gyp-generated configuration.
595    """
596    match = kw.pop('match', self.match)
597
598    executable_file = self.built_file_path(name, type=self.EXECUTABLE, **kw)
599    if executable_file not in self._to_install:
600      self.fail_test()
601
602    if not self._device_serial:
603      self.skip_test(message='No devices attached.\n')
604
605    storage = self._call_adb(['shell', 'echo', '$ANDROID_DATA']).strip()
606    if not len(storage):
607      self.fail_test()
608
609    installed = set()
610    try:
611      for i in self._to_install:
612        a = os.path.abspath(
613            os.path.join(os.environ['ANDROID_BUILD_TOP'], i))
614        dest = '%s/%s' % (storage, os.path.basename(a))
615        self._call_adb(['push', os.path.abspath(a), dest])
616        installed.add(dest)
617        if i == executable_file:
618          device_executable = dest
619          self._call_adb(['shell', 'chmod', '755', device_executable])
620
621      out = self._call_adb(
622          ['shell', 'LD_LIBRARY_PATH=$LD_LIBRARY_PATH:%s' % storage,
623           device_executable])
624      out = out.replace('\r\n', '\n')
625      self._complete(out, kw.pop('stdout', None), None, None, None, match)
626    finally:
627      if len(installed):
628        self._call_adb(['shell', 'rm'] + list(installed))
629
630  def match_single_line(self, lines = None, expected_line = None):
631    """
632    Checks that specified line appears in the text.
633    """
634    for line in lines.split('\n'):
635        if line == expected_line:
636            return 1
637    return
638
639  def up_to_date(self, gyp_file, target=None, **kw):
640    """
641    Verifies that a build of the specified target is up to date.
642    """
643    kw['stdout'] = ("make: Nothing to be done for `%s'." %
644                    self.target_name(target))
645
646    # We need to supply a custom matcher, since we don't want to depend on the
647    # exact stdout string.
648    kw['match'] = self.match_single_line
649    return self.build(gyp_file, target, **kw)
650
651
652class TestGypCMake(TestGypBase):
653  """
654  Subclass for testing the GYP CMake generator, using cmake's ninja backend.
655  """
656  format = 'cmake'
657  build_tool_list = ['cmake']
658  ALL = 'all'
659
660  def cmake_build(self, gyp_file, target=None, **kw):
661    arguments = kw.get('arguments', [])[:]
662
663    self.build_tool_list = ['cmake']
664    self.initialize_build_tool()
665
666    chdir = os.path.join(kw.get('chdir', '.'),
667                         'out',
668                         self.configuration_dirname())
669    kw['chdir'] = chdir
670
671    arguments.append('-G')
672    arguments.append('Ninja')
673
674    kw['arguments'] = arguments
675
676    stderr = kw.get('stderr', None)
677    if stderr:
678      kw['stderr'] = stderr.split('$$$')[0]
679
680    self.run(program=self.build_tool, **kw)
681
682  def ninja_build(self, gyp_file, target=None, **kw):
683    arguments = kw.get('arguments', [])[:]
684
685    self.build_tool_list = ['ninja']
686    self.initialize_build_tool()
687
688    # Add a -C output/path to the command line.
689    arguments.append('-C')
690    arguments.append(os.path.join('out', self.configuration_dirname()))
691
692    if target not in (None, self.DEFAULT):
693      arguments.append(target)
694
695    kw['arguments'] = arguments
696
697    stderr = kw.get('stderr', None)
698    if stderr:
699      stderrs = stderr.split('$$$')
700      kw['stderr'] = stderrs[1] if len(stderrs) > 1 else ''
701
702    return self.run(program=self.build_tool, **kw)
703
704  def build(self, gyp_file, target=None, status=0, **kw):
705    # Two tools must be run to build, cmake and the ninja.
706    # Allow cmake to succeed when the overall expectation is to fail.
707    if status is None:
708      kw['status'] = None
709    else:
710      if not isinstance(status, collections.Iterable): status = (status,)
711      kw['status'] = list(itertools.chain((0,), status))
712    self.cmake_build(gyp_file, target, **kw)
713    kw['status'] = status
714    self.ninja_build(gyp_file, target, **kw)
715
716  def run_built_executable(self, name, *args, **kw):
717    # Enclosing the name in a list avoids prepending the original dir.
718    program = [self.built_file_path(name, type=self.EXECUTABLE, **kw)]
719    if sys.platform == 'darwin':
720      configuration = self.configuration_dirname()
721      os.environ['DYLD_LIBRARY_PATH'] = os.path.join('out', configuration)
722    return self.run(program=program, *args, **kw)
723
724  def built_file_path(self, name, type=None, **kw):
725    result = []
726    chdir = kw.get('chdir')
727    if chdir:
728      result.append(chdir)
729    result.append('out')
730    result.append(self.configuration_dirname())
731    if type == self.STATIC_LIB:
732      if sys.platform != 'darwin':
733        result.append('obj.target')
734    elif type == self.SHARED_LIB:
735      if sys.platform != 'darwin' and sys.platform != 'win32':
736        result.append('lib.target')
737    subdir = kw.get('subdir')
738    if subdir and type != self.SHARED_LIB:
739      result.append(subdir)
740    result.append(self.built_file_basename(name, type, **kw))
741    return self.workpath(*result)
742
743  def up_to_date(self, gyp_file, target=None, **kw):
744    result = self.ninja_build(gyp_file, target, **kw)
745    if not result:
746      stdout = self.stdout()
747      if 'ninja: no work to do' not in stdout:
748        self.report_not_up_to_date()
749        self.fail_test()
750    return result
751
752
753class TestGypMake(TestGypBase):
754  """
755  Subclass for testing the GYP Make generator.
756  """
757  format = 'make'
758  build_tool_list = ['make']
759  ALL = 'all'
760  def build(self, gyp_file, target=None, **kw):
761    """
762    Runs a Make build using the Makefiles generated from the specified
763    gyp_file.
764    """
765    arguments = kw.get('arguments', [])[:]
766    if self.configuration:
767      arguments.append('BUILDTYPE=' + self.configuration)
768    if target not in (None, self.DEFAULT):
769      arguments.append(target)
770    # Sub-directory builds provide per-gyp Makefiles (i.e.
771    # Makefile.gyp_filename), so use that if there is no Makefile.
772    chdir = kw.get('chdir', '')
773    if not os.path.exists(os.path.join(chdir, 'Makefile')):
774      print "NO Makefile in " + os.path.join(chdir, 'Makefile')
775      arguments.insert(0, '-f')
776      arguments.insert(1, os.path.splitext(gyp_file)[0] + '.Makefile')
777    kw['arguments'] = arguments
778    return self.run(program=self.build_tool, **kw)
779  def up_to_date(self, gyp_file, target=None, **kw):
780    """
781    Verifies that a build of the specified Make target is up to date.
782    """
783    if target in (None, self.DEFAULT):
784      message_target = 'all'
785    else:
786      message_target = target
787    kw['stdout'] = "make: Nothing to be done for `%s'.\n" % message_target
788    return self.build(gyp_file, target, **kw)
789  def run_built_executable(self, name, *args, **kw):
790    """
791    Runs an executable built by Make.
792    """
793    configuration = self.configuration_dirname()
794    libdir = os.path.join('out', configuration, 'lib')
795    # TODO(piman): when everything is cross-compile safe, remove lib.target
796    if sys.platform == 'darwin':
797      # Mac puts target shared libraries right in the product directory.
798      configuration = self.configuration_dirname()
799      os.environ['DYLD_LIBRARY_PATH'] = (
800          libdir + '.host:' + os.path.join('out', configuration))
801    else:
802      os.environ['LD_LIBRARY_PATH'] = libdir + '.host:' + libdir + '.target'
803    # Enclosing the name in a list avoids prepending the original dir.
804    program = [self.built_file_path(name, type=self.EXECUTABLE, **kw)]
805    return self.run(program=program, *args, **kw)
806  def built_file_path(self, name, type=None, **kw):
807    """
808    Returns a path to the specified file name, of the specified type,
809    as built by Make.
810
811    Built files are in the subdirectory 'out/{configuration}'.
812    The default is 'out/Default'.
813
814    A chdir= keyword argument specifies the source directory
815    relative to which  the output subdirectory can be found.
816
817    "type" values of STATIC_LIB or SHARED_LIB append the necessary
818    prefixes and suffixes to a platform-independent library base name.
819
820    A subdir= keyword argument specifies a library subdirectory within
821    the default 'obj.target'.
822    """
823    result = []
824    chdir = kw.get('chdir')
825    if chdir:
826      result.append(chdir)
827    configuration = self.configuration_dirname()
828    result.extend(['out', configuration])
829    if type == self.STATIC_LIB and sys.platform != 'darwin':
830      result.append('obj.target')
831    elif type == self.SHARED_LIB and sys.platform != 'darwin':
832      result.append('lib.target')
833    subdir = kw.get('subdir')
834    if subdir and type != self.SHARED_LIB:
835      result.append(subdir)
836    result.append(self.built_file_basename(name, type, **kw))
837    return self.workpath(*result)
838
839
840def ConvertToCygpath(path):
841  """Convert to cygwin path if we are using cygwin."""
842  if sys.platform == 'cygwin':
843    p = subprocess.Popen(['cygpath', path], stdout=subprocess.PIPE)
844    path = p.communicate()[0].strip()
845  return path
846
847
848def FindMSBuildInstallation(msvs_version = 'auto'):
849  """Returns path to MSBuild for msvs_version or latest available.
850
851  Looks in the registry to find install location of MSBuild.
852  MSBuild before v4.0 will not build c++ projects, so only use newer versions.
853  """
854  import TestWin
855  registry = TestWin.Registry()
856
857  msvs_to_msbuild = {
858      '2013': r'12.0',
859      '2012': r'4.0',  # Really v4.0.30319 which comes with .NET 4.5.
860      '2010': r'4.0'}
861
862  msbuild_basekey = r'HKLM\SOFTWARE\Microsoft\MSBuild\ToolsVersions'
863  if not registry.KeyExists(msbuild_basekey):
864    print 'Error: could not find MSBuild base registry entry'
865    return None
866
867  msbuild_version = None
868  if msvs_version in msvs_to_msbuild:
869    msbuild_test_version = msvs_to_msbuild[msvs_version]
870    if registry.KeyExists(msbuild_basekey + '\\' + msbuild_test_version):
871      msbuild_version = msbuild_test_version
872    else:
873      print ('Warning: Environment variable GYP_MSVS_VERSION specifies "%s" '
874             'but corresponding MSBuild "%s" was not found.' %
875             (msvs_version, msbuild_version))
876  if not msbuild_version:
877    for msvs_version in sorted(msvs_to_msbuild, reverse=True):
878      msbuild_test_version = msvs_to_msbuild[msvs_version]
879      if registry.KeyExists(msbuild_basekey + '\\' + msbuild_test_version):
880        msbuild_version = msbuild_test_version
881        break
882  if not msbuild_version:
883    print 'Error: could not find MSBuild registry entry'
884    return None
885
886  msbuild_path = registry.GetValue(msbuild_basekey + '\\' + msbuild_version,
887                                   'MSBuildToolsPath')
888  if not msbuild_path:
889    print 'Error: could not get MSBuild registry entry value'
890    return None
891
892  return os.path.join(msbuild_path, 'MSBuild.exe')
893
894
895def FindVisualStudioInstallation():
896  """Returns appropriate values for .build_tool and .uses_msbuild fields
897  of TestGypBase for Visual Studio.
898
899  We use the value specified by GYP_MSVS_VERSION.  If not specified, we
900  search %PATH% and %PATHEXT% for a devenv.{exe,bat,...} executable.
901  Failing that, we search for likely deployment paths.
902  """
903  possible_roots = ['%s:\\Program Files%s' % (chr(drive), suffix)
904                    for drive in range(ord('C'), ord('Z') + 1)
905                    for suffix in ['', ' (x86)']]
906  possible_paths = {
907      '2013': r'Microsoft Visual Studio 12.0\Common7\IDE\devenv.com',
908      '2012': r'Microsoft Visual Studio 11.0\Common7\IDE\devenv.com',
909      '2010': r'Microsoft Visual Studio 10.0\Common7\IDE\devenv.com',
910      '2008': r'Microsoft Visual Studio 9.0\Common7\IDE\devenv.com',
911      '2005': r'Microsoft Visual Studio 8\Common7\IDE\devenv.com'}
912
913  possible_roots = [ConvertToCygpath(r) for r in possible_roots]
914
915  msvs_version = 'auto'
916  for flag in (f for f in sys.argv if f.startswith('msvs_version=')):
917    msvs_version = flag.split('=')[-1]
918  msvs_version = os.environ.get('GYP_MSVS_VERSION', msvs_version)
919
920  if msvs_version in possible_paths:
921    # Check that the path to the specified GYP_MSVS_VERSION exists.
922    path = possible_paths[msvs_version]
923    for r in possible_roots:
924      build_tool = os.path.join(r, path)
925      if os.path.exists(build_tool):
926        uses_msbuild = msvs_version >= '2010'
927        msbuild_path = FindMSBuildInstallation(msvs_version)
928        return build_tool, uses_msbuild, msbuild_path
929    else:
930      print ('Warning: Environment variable GYP_MSVS_VERSION specifies "%s" '
931              'but corresponding "%s" was not found.' % (msvs_version, path))
932  # Neither GYP_MSVS_VERSION nor the path help us out.  Iterate through
933  # the choices looking for a match.
934  for version in sorted(possible_paths, reverse=True):
935    path = possible_paths[version]
936    for r in possible_roots:
937      build_tool = os.path.join(r, path)
938      if os.path.exists(build_tool):
939        uses_msbuild = msvs_version >= '2010'
940        msbuild_path = FindMSBuildInstallation(msvs_version)
941        return build_tool, uses_msbuild, msbuild_path
942  print 'Error: could not find devenv'
943  sys.exit(1)
944
945class TestGypOnMSToolchain(TestGypBase):
946  """
947  Common subclass for testing generators that target the Microsoft Visual
948  Studio toolchain (cl, link, dumpbin, etc.)
949  """
950  @staticmethod
951  def _ComputeVsvarsPath(devenv_path):
952    devenv_dir = os.path.split(devenv_path)[0]
953    vsvars_path = os.path.join(devenv_path, '../../Tools/vsvars32.bat')
954    return vsvars_path
955
956  def initialize_build_tool(self):
957    super(TestGypOnMSToolchain, self).initialize_build_tool()
958    if sys.platform in ('win32', 'cygwin'):
959      build_tools = FindVisualStudioInstallation()
960      self.devenv_path, self.uses_msbuild, self.msbuild_path = build_tools
961      self.vsvars_path = TestGypOnMSToolchain._ComputeVsvarsPath(
962          self.devenv_path)
963
964  def run_dumpbin(self, *dumpbin_args):
965    """Run the dumpbin tool with the specified arguments, and capturing and
966    returning stdout."""
967    assert sys.platform in ('win32', 'cygwin')
968    cmd = os.environ.get('COMSPEC', 'cmd.exe')
969    arguments = [cmd, '/c', self.vsvars_path, '&&', 'dumpbin']
970    arguments.extend(dumpbin_args)
971    proc = subprocess.Popen(arguments, stdout=subprocess.PIPE)
972    output = proc.communicate()[0]
973    assert not proc.returncode
974    return output
975
976class TestGypNinja(TestGypOnMSToolchain):
977  """
978  Subclass for testing the GYP Ninja generator.
979  """
980  format = 'ninja'
981  build_tool_list = ['ninja']
982  ALL = 'all'
983  DEFAULT = 'all'
984
985  def run_gyp(self, gyp_file, *args, **kw):
986    TestGypBase.run_gyp(self, gyp_file, *args, **kw)
987
988  def build(self, gyp_file, target=None, **kw):
989    arguments = kw.get('arguments', [])[:]
990
991    # Add a -C output/path to the command line.
992    arguments.append('-C')
993    arguments.append(os.path.join('out', self.configuration_dirname()))
994
995    if target is None:
996      target = 'all'
997    arguments.append(target)
998
999    kw['arguments'] = arguments
1000    return self.run(program=self.build_tool, **kw)
1001
1002  def run_built_executable(self, name, *args, **kw):
1003    # Enclosing the name in a list avoids prepending the original dir.
1004    program = [self.built_file_path(name, type=self.EXECUTABLE, **kw)]
1005    if sys.platform == 'darwin':
1006      configuration = self.configuration_dirname()
1007      os.environ['DYLD_LIBRARY_PATH'] = os.path.join('out', configuration)
1008    return self.run(program=program, *args, **kw)
1009
1010  def built_file_path(self, name, type=None, **kw):
1011    result = []
1012    chdir = kw.get('chdir')
1013    if chdir:
1014      result.append(chdir)
1015    result.append('out')
1016    result.append(self.configuration_dirname())
1017    if type == self.STATIC_LIB:
1018      if sys.platform != 'darwin':
1019        result.append('obj')
1020    elif type == self.SHARED_LIB:
1021      if sys.platform != 'darwin' and sys.platform != 'win32':
1022        result.append('lib')
1023    subdir = kw.get('subdir')
1024    if subdir and type != self.SHARED_LIB:
1025      result.append(subdir)
1026    result.append(self.built_file_basename(name, type, **kw))
1027    return self.workpath(*result)
1028
1029  def up_to_date(self, gyp_file, target=None, **kw):
1030    result = self.build(gyp_file, target, **kw)
1031    if not result:
1032      stdout = self.stdout()
1033      if 'ninja: no work to do' not in stdout:
1034        self.report_not_up_to_date()
1035        self.fail_test()
1036    return result
1037
1038
1039class TestGypMSVS(TestGypOnMSToolchain):
1040  """
1041  Subclass for testing the GYP Visual Studio generator.
1042  """
1043  format = 'msvs'
1044
1045  u = r'=== Build: 0 succeeded, 0 failed, (\d+) up-to-date, 0 skipped ==='
1046  up_to_date_re = re.compile(u, re.M)
1047
1048  # Initial None element will indicate to our .initialize_build_tool()
1049  # method below that 'devenv' was not found on %PATH%.
1050  #
1051  # Note:  we must use devenv.com to be able to capture build output.
1052  # Directly executing devenv.exe only sends output to BuildLog.htm.
1053  build_tool_list = [None, 'devenv.com']
1054
1055  def initialize_build_tool(self):
1056    super(TestGypMSVS, self).initialize_build_tool()
1057    self.build_tool = self.devenv_path
1058
1059  def build(self, gyp_file, target=None, rebuild=False, clean=False, **kw):
1060    """
1061    Runs a Visual Studio build using the configuration generated
1062    from the specified gyp_file.
1063    """
1064    configuration = self.configuration_buildname()
1065    if clean:
1066      build = '/Clean'
1067    elif rebuild:
1068      build = '/Rebuild'
1069    else:
1070      build = '/Build'
1071    arguments = kw.get('arguments', [])[:]
1072    arguments.extend([gyp_file.replace('.gyp', '.sln'),
1073                      build, configuration])
1074    # Note:  the Visual Studio generator doesn't add an explicit 'all'
1075    # target, so we just treat it the same as the default.
1076    if target not in (None, self.ALL, self.DEFAULT):
1077      arguments.extend(['/Project', target])
1078    if self.configuration:
1079      arguments.extend(['/ProjectConfig', self.configuration])
1080    kw['arguments'] = arguments
1081    return self.run(program=self.build_tool, **kw)
1082  def up_to_date(self, gyp_file, target=None, **kw):
1083    """
1084    Verifies that a build of the specified Visual Studio target is up to date.
1085
1086    Beware that VS2010 will behave strangely if you build under
1087    C:\USERS\yourname\AppData\Local. It will cause needless work.  The ouptut
1088    will be "1 succeeded and 0 up to date".  MSBuild tracing reveals that:
1089    "Project 'C:\Users\...\AppData\Local\...vcxproj' not up to date because
1090    'C:\PROGRAM FILES (X86)\MICROSOFT VISUAL STUDIO 10.0\VC\BIN\1033\CLUI.DLL'
1091    was modified at 02/21/2011 17:03:30, which is newer than '' which was
1092    modified at 01/01/0001 00:00:00.
1093
1094    The workaround is to specify a workdir when instantiating the test, e.g.
1095    test = TestGyp.TestGyp(workdir='workarea')
1096    """
1097    result = self.build(gyp_file, target, **kw)
1098    if not result:
1099      stdout = self.stdout()
1100
1101      m = self.up_to_date_re.search(stdout)
1102      up_to_date = m and int(m.group(1)) > 0
1103      if not up_to_date:
1104        self.report_not_up_to_date()
1105        self.fail_test()
1106    return result
1107  def run_built_executable(self, name, *args, **kw):
1108    """
1109    Runs an executable built by Visual Studio.
1110    """
1111    configuration = self.configuration_dirname()
1112    # Enclosing the name in a list avoids prepending the original dir.
1113    program = [self.built_file_path(name, type=self.EXECUTABLE, **kw)]
1114    return self.run(program=program, *args, **kw)
1115  def built_file_path(self, name, type=None, **kw):
1116    """
1117    Returns a path to the specified file name, of the specified type,
1118    as built by Visual Studio.
1119
1120    Built files are in a subdirectory that matches the configuration
1121    name.  The default is 'Default'.
1122
1123    A chdir= keyword argument specifies the source directory
1124    relative to which  the output subdirectory can be found.
1125
1126    "type" values of STATIC_LIB or SHARED_LIB append the necessary
1127    prefixes and suffixes to a platform-independent library base name.
1128    """
1129    result = []
1130    chdir = kw.get('chdir')
1131    if chdir:
1132      result.append(chdir)
1133    result.append(self.configuration_dirname())
1134    if type == self.STATIC_LIB:
1135      result.append('lib')
1136    result.append(self.built_file_basename(name, type, **kw))
1137    return self.workpath(*result)
1138
1139
1140class TestGypMSVSNinja(TestGypNinja):
1141  """
1142  Subclass for testing the GYP Visual Studio Ninja generator.
1143  """
1144  format = 'msvs-ninja'
1145
1146  def initialize_build_tool(self):
1147    super(TestGypMSVSNinja, self).initialize_build_tool()
1148    # When using '--build', make sure ninja is first in the format list.
1149    self.formats.insert(0, 'ninja')
1150
1151  def build(self, gyp_file, target=None, rebuild=False, clean=False, **kw):
1152    """
1153    Runs a Visual Studio build using the configuration generated
1154    from the specified gyp_file.
1155    """
1156    arguments = kw.get('arguments', [])[:]
1157    if target in (None, self.ALL, self.DEFAULT):
1158      # Note: the Visual Studio generator doesn't add an explicit 'all' target.
1159      # This will build each project. This will work if projects are hermetic,
1160      # but may fail if they are not (a project may run more than once).
1161      # It would be nice to supply an all.metaproj for MSBuild.
1162      arguments.extend([gyp_file.replace('.gyp', '.sln')])
1163    else:
1164      # MSBuild documentation claims that one can specify a sln but then build a
1165      # project target like 'msbuild a.sln /t:proj:target' but this format only
1166      # supports 'Clean', 'Rebuild', and 'Publish' (with none meaning Default).
1167      # This limitation is due to the .sln -> .sln.metaproj conversion.
1168      # The ':' is not special, 'proj:target' is a target in the metaproj.
1169      arguments.extend([target+'.vcxproj'])
1170
1171    if clean:
1172      build = 'Clean'
1173    elif rebuild:
1174      build = 'Rebuild'
1175    else:
1176      build = 'Build'
1177    arguments.extend(['/target:'+build])
1178    configuration = self.configuration_buildname()
1179    config = configuration.split('|')
1180    arguments.extend(['/property:Configuration='+config[0]])
1181    if len(config) > 1:
1182      arguments.extend(['/property:Platform='+config[1]])
1183    arguments.extend(['/property:BuildInParallel=false'])
1184    arguments.extend(['/verbosity:minimal'])
1185
1186    kw['arguments'] = arguments
1187    return self.run(program=self.msbuild_path, **kw)
1188
1189
1190class TestGypXcode(TestGypBase):
1191  """
1192  Subclass for testing the GYP Xcode generator.
1193  """
1194  format = 'xcode'
1195  build_tool_list = ['xcodebuild']
1196
1197  phase_script_execution = ("\n"
1198                            "PhaseScriptExecution /\\S+/Script-[0-9A-F]+\\.sh\n"
1199                            "    cd /\\S+\n"
1200                            "    /bin/sh -c /\\S+/Script-[0-9A-F]+\\.sh\n"
1201                            "(make: Nothing to be done for `all'\\.\n)?")
1202
1203  strip_up_to_date_expressions = [
1204    # Various actions or rules can run even when the overall build target
1205    # is up to date.  Strip those phases' GYP-generated output.
1206    re.compile(phase_script_execution, re.S),
1207
1208    # The message from distcc_pump can trail the "BUILD SUCCEEDED"
1209    # message, so strip that, too.
1210    re.compile('__________Shutting down distcc-pump include server\n', re.S),
1211  ]
1212
1213  up_to_date_endings = (
1214    'Checking Dependencies...\n** BUILD SUCCEEDED **\n', # Xcode 3.0/3.1
1215    'Check dependencies\n** BUILD SUCCEEDED **\n\n',     # Xcode 3.2
1216    'Check dependencies\n\n\n** BUILD SUCCEEDED **\n\n', # Xcode 4.2
1217    'Check dependencies\n\n** BUILD SUCCEEDED **\n\n',   # Xcode 5.0
1218  )
1219
1220  def build(self, gyp_file, target=None, **kw):
1221    """
1222    Runs an xcodebuild using the .xcodeproj generated from the specified
1223    gyp_file.
1224    """
1225    # Be sure we're working with a copy of 'arguments' since we modify it.
1226    # The caller may not be expecting it to be modified.
1227    arguments = kw.get('arguments', [])[:]
1228    arguments.extend(['-project', gyp_file.replace('.gyp', '.xcodeproj')])
1229    if target == self.ALL:
1230      arguments.append('-alltargets',)
1231    elif target not in (None, self.DEFAULT):
1232      arguments.extend(['-target', target])
1233    if self.configuration:
1234      arguments.extend(['-configuration', self.configuration])
1235    symroot = kw.get('SYMROOT', '$SRCROOT/build')
1236    if symroot:
1237      arguments.append('SYMROOT='+symroot)
1238    kw['arguments'] = arguments
1239
1240    # Work around spurious stderr output from Xcode 4, http://crbug.com/181012
1241    match = kw.pop('match', self.match)
1242    def match_filter_xcode(actual, expected):
1243      if actual:
1244        if not TestCmd.is_List(actual):
1245          actual = actual.split('\n')
1246        if not TestCmd.is_List(expected):
1247          expected = expected.split('\n')
1248        actual = [a for a in actual
1249                    if 'No recorder, buildTask: <Xcode3BuildTask:' not in a]
1250      return match(actual, expected)
1251    kw['match'] = match_filter_xcode
1252
1253    return self.run(program=self.build_tool, **kw)
1254  def up_to_date(self, gyp_file, target=None, **kw):
1255    """
1256    Verifies that a build of the specified Xcode target is up to date.
1257    """
1258    result = self.build(gyp_file, target, **kw)
1259    if not result:
1260      output = self.stdout()
1261      for expression in self.strip_up_to_date_expressions:
1262        output = expression.sub('', output)
1263      if not output.endswith(self.up_to_date_endings):
1264        self.report_not_up_to_date()
1265        self.fail_test()
1266    return result
1267  def run_built_executable(self, name, *args, **kw):
1268    """
1269    Runs an executable built by xcodebuild.
1270    """
1271    configuration = self.configuration_dirname()
1272    os.environ['DYLD_LIBRARY_PATH'] = os.path.join('build', configuration)
1273    # Enclosing the name in a list avoids prepending the original dir.
1274    program = [self.built_file_path(name, type=self.EXECUTABLE, **kw)]
1275    return self.run(program=program, *args, **kw)
1276  def built_file_path(self, name, type=None, **kw):
1277    """
1278    Returns a path to the specified file name, of the specified type,
1279    as built by Xcode.
1280
1281    Built files are in the subdirectory 'build/{configuration}'.
1282    The default is 'build/Default'.
1283
1284    A chdir= keyword argument specifies the source directory
1285    relative to which  the output subdirectory can be found.
1286
1287    "type" values of STATIC_LIB or SHARED_LIB append the necessary
1288    prefixes and suffixes to a platform-independent library base name.
1289    """
1290    result = []
1291    chdir = kw.get('chdir')
1292    if chdir:
1293      result.append(chdir)
1294    configuration = self.configuration_dirname()
1295    result.extend(['build', configuration])
1296    result.append(self.built_file_basename(name, type, **kw))
1297    return self.workpath(*result)
1298
1299
1300format_class_list = [
1301  TestGypGypd,
1302  TestGypAndroid,
1303  TestGypCMake,
1304  TestGypMake,
1305  TestGypMSVS,
1306  TestGypMSVSNinja,
1307  TestGypNinja,
1308  TestGypXcode,
1309]
1310
1311def TestGyp(*args, **kw):
1312  """
1313  Returns an appropriate TestGyp* instance for a specified GYP format.
1314  """
1315  format = kw.pop('format', os.environ.get('TESTGYP_FORMAT'))
1316  for format_class in format_class_list:
1317    if format == format_class.format:
1318      return format_class(*args, **kw)
1319  raise Exception, "unknown format %r" % format
1320