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