Home | History | Annotate | Download | only in lib
      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 """
      6 TestGyp.py:  a testing framework for GYP integration tests.
      7 """
      8 
      9 import collections
     10 from contextlib import contextmanager
     11 import itertools
     12 import os
     13 import re
     14 import shutil
     15 import stat
     16 import subprocess
     17 import sys
     18 import tempfile
     19 
     20 import TestCmd
     21 import TestCommon
     22 from TestCommon import __all__
     23 
     24 __all__.extend([
     25   'TestGyp',
     26 ])
     27 
     28 
     29 def 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 
     43 def 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
     51 def 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 
     62 class 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 
    385 class 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 
    398 class 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 
    408 class 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 
    652 class 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 
    753 class 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 
    840 def 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 
    848 def 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 
    895 def 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 
    945 class 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 
    976 class 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 
   1039 class 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 
   1140 class 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 
   1190 class 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 
   1300 format_class_list = [
   1301   TestGypGypd,
   1302   TestGypAndroid,
   1303   TestGypCMake,
   1304   TestGypMake,
   1305   TestGypMSVS,
   1306   TestGypMSVSNinja,
   1307   TestGypNinja,
   1308   TestGypXcode,
   1309 ]
   1310 
   1311 def 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