Refactors run_tests.py s.t. it can be shared by gmock (by Vlad Losev); Fixes a warning in gtest-tuple_test.cc on Cygwin (by Vlad Losev).
This commit is contained in:
		
							
								
								
									
										405
									
								
								run_tests.py
									
									
									
									
									
								
							
							
						
						
									
										405
									
								
								run_tests.py
									
									
									
									
									
								
							@@ -28,415 +28,26 @@
 | 
			
		||||
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 | 
			
		||||
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 | 
			
		||||
 | 
			
		||||
"""Runs specified tests for Google Test.
 | 
			
		||||
"""Runs the specified tests for Google Test.
 | 
			
		||||
 | 
			
		||||
SYNOPSIS
 | 
			
		||||
       run_tests.py [OPTION]... [BUILD_DIR]... [TEST]...
 | 
			
		||||
 | 
			
		||||
DESCRIPTION
 | 
			
		||||
       Runs the specified tests (either binary or Python), and prints a
 | 
			
		||||
       summary of the results. BUILD_DIRS will be used to search for the
 | 
			
		||||
       binaries. If no TESTs are specified, all binary tests found in
 | 
			
		||||
       BUILD_DIRs and all Python tests found in the directory test/ (in the
 | 
			
		||||
       gtest root) are run.
 | 
			
		||||
 | 
			
		||||
       TEST is a name of either a binary or a Python test. A binary test is
 | 
			
		||||
       an executable file named *_test or *_unittest (with the .exe
 | 
			
		||||
       extension on Windows) A Python test is a script named *_test.py or
 | 
			
		||||
       *_unittest.py.
 | 
			
		||||
 | 
			
		||||
OPTIONS
 | 
			
		||||
       -c CONFIGURATIONS
 | 
			
		||||
              Specify build directories via build configurations.
 | 
			
		||||
              CONFIGURATIONS is either a comma-separated list of build
 | 
			
		||||
              configurations or 'all'. Each configuration is equivalent to
 | 
			
		||||
              adding 'scons/build/<configuration>/gtest/scons' to BUILD_DIRs.
 | 
			
		||||
              Specifying -c=all is equivalent to providing all directories
 | 
			
		||||
              listed in KNOWN BUILD DIRECTORIES section below.
 | 
			
		||||
 | 
			
		||||
       -a
 | 
			
		||||
             Equivalent to -c=all
 | 
			
		||||
 | 
			
		||||
       -b
 | 
			
		||||
             Equivalent to -c=all with the exception that the script will not
 | 
			
		||||
             fail if some of the KNOWN BUILD DIRECTORIES do not exists; the
 | 
			
		||||
             script will simply not run the tests there. 'b' stands for
 | 
			
		||||
             'built directories'.
 | 
			
		||||
 | 
			
		||||
RETURN VALUE
 | 
			
		||||
       Returns 0 if all tests are successful; otherwise returns 1.
 | 
			
		||||
 | 
			
		||||
EXAMPLES
 | 
			
		||||
       run_tests.py
 | 
			
		||||
              Runs all tests for the default build configuration.
 | 
			
		||||
 | 
			
		||||
       run_tests.py -a
 | 
			
		||||
              Runs all tests with binaries in KNOWN BUILD DIRECTORIES.
 | 
			
		||||
 | 
			
		||||
       run_tests.py -b
 | 
			
		||||
              Runs all tests in KNOWN BUILD DIRECTORIES that have been
 | 
			
		||||
              built.
 | 
			
		||||
 | 
			
		||||
       run_tests.py foo/
 | 
			
		||||
              Runs all tests in the foo/ directory and all Python tests in
 | 
			
		||||
              the directory test. The Python tests are instructed to look
 | 
			
		||||
              for binaries in foo/.
 | 
			
		||||
 | 
			
		||||
       run_tests.py bar_test.exe test/baz_test.exe foo/ bar/
 | 
			
		||||
              Runs foo/bar_test.exe, bar/bar_test.exe, foo/baz_test.exe, and
 | 
			
		||||
              bar/baz_test.exe.
 | 
			
		||||
 | 
			
		||||
       run_tests.py foo bar test/foo_test.py
 | 
			
		||||
              Runs test/foo_test.py twice instructing it to look for its
 | 
			
		||||
              test binaries in the directories foo and bar,
 | 
			
		||||
              correspondingly.
 | 
			
		||||
 | 
			
		||||
KNOWN BUILD DIRECTORIES
 | 
			
		||||
      run_tests.py knows about directories where the SCons build script
 | 
			
		||||
      deposits its products. These are the directories where run_tests.py
 | 
			
		||||
      will be looking for its binaries. Currently, gtest's SConstruct file
 | 
			
		||||
      defines them as follows (the default build directory is the first one
 | 
			
		||||
      listed in each group):
 | 
			
		||||
      On Windows:
 | 
			
		||||
              <gtest root>/scons/build/win-dbg8/gtest/scons/
 | 
			
		||||
              <gtest root>/scons/build/win-opt8/gtest/scons/
 | 
			
		||||
              <gtest root>/scons/build/win-dbg/gtest/scons/
 | 
			
		||||
              <gtest root>/scons/build/win-opt/gtest/scons/
 | 
			
		||||
      On Mac:
 | 
			
		||||
              <gtest root>/scons/build/mac-dbg/gtest/scons/
 | 
			
		||||
              <gtest root>/scons/build/mac-opt/gtest/scons/
 | 
			
		||||
      On other platforms:
 | 
			
		||||
              <gtest root>/scons/build/dbg/gtest/scons/
 | 
			
		||||
              <gtest root>/scons/build/opt/gtest/scons/
 | 
			
		||||
 | 
			
		||||
AUTHOR
 | 
			
		||||
       Written by Zhanyong Wan (wan@google.com)
 | 
			
		||||
       and Vlad Losev(vladl@google.com).
 | 
			
		||||
 | 
			
		||||
REQUIREMENTS
 | 
			
		||||
       This script requires Python 2.3 or higher.
 | 
			
		||||
This script requires Python 2.3 or higher.  To learn the usage, run it
 | 
			
		||||
with -h.
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
import optparse
 | 
			
		||||
import os
 | 
			
		||||
import re
 | 
			
		||||
import sets
 | 
			
		||||
import sys
 | 
			
		||||
 | 
			
		||||
try:
 | 
			
		||||
  # subrocess module is a preferable way to invoke subprocesses but it may
 | 
			
		||||
  # not be available on MacOS X 10.4.
 | 
			
		||||
  import subprocess
 | 
			
		||||
except ImportError:
 | 
			
		||||
  subprocess = None
 | 
			
		||||
SCRIPT_DIR = os.path.dirname(__file__) or '.'
 | 
			
		||||
 | 
			
		||||
IS_WINDOWS = os.name == 'nt'
 | 
			
		||||
IS_MAC = os.name == 'posix' and os.uname()[0] == 'Darwin'
 | 
			
		||||
IS_CYGWIN = os.name == 'posix' and 'CYGWIN' in os.uname()[0]
 | 
			
		||||
 | 
			
		||||
# Definition of CONFIGS must match that of the build directory names in the
 | 
			
		||||
# SConstruct script. The first list item is the default build configuration.
 | 
			
		||||
if IS_WINDOWS:
 | 
			
		||||
  CONFIGS = ('win-dbg8', 'win-opt8', 'win-dbg', 'win-opt')
 | 
			
		||||
elif IS_MAC:
 | 
			
		||||
  CONFIGS = ('mac-dbg', 'mac-opt')
 | 
			
		||||
else:
 | 
			
		||||
  CONFIGS = ('dbg', 'opt')
 | 
			
		||||
 | 
			
		||||
if IS_WINDOWS or IS_CYGWIN:
 | 
			
		||||
  PYTHON_TEST_REGEX = re.compile(r'_(unit)?test\.py$', re.IGNORECASE)
 | 
			
		||||
  BINARY_TEST_REGEX = re.compile(r'_(unit)?test(\.exe)?$', re.IGNORECASE)
 | 
			
		||||
  BINARY_TEST_SEARCH_REGEX = re.compile(r'_(unit)?test\.exe$', re.IGNORECASE)
 | 
			
		||||
else:
 | 
			
		||||
  PYTHON_TEST_REGEX = re.compile(r'_(unit)?test\.py$')
 | 
			
		||||
  BINARY_TEST_REGEX = re.compile(r'_(unit)?test$')
 | 
			
		||||
  BINARY_TEST_SEARCH_REGEX = BINARY_TEST_REGEX
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def _GetGtestBuildDir(os, script_dir, config):
 | 
			
		||||
  """Calculates path to the Google Test SCons build directory."""
 | 
			
		||||
 | 
			
		||||
  return os.path.normpath(os.path.join(script_dir,
 | 
			
		||||
                                       'scons/build',
 | 
			
		||||
                                       config,
 | 
			
		||||
                                       'gtest/scons'))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# All paths in this script are either absolute or relative to the current
 | 
			
		||||
# working directory, unless otherwise specified.
 | 
			
		||||
class TestRunner(object):
 | 
			
		||||
  """Provides facilities for running Python and binary tests for Google Test."""
 | 
			
		||||
 | 
			
		||||
  def __init__(self,
 | 
			
		||||
               build_dir_var_name='GTEST_BUILD_DIR',
 | 
			
		||||
               injected_os=os,
 | 
			
		||||
               injected_subprocess=subprocess,
 | 
			
		||||
               injected_script_dir=os.path.dirname(__file__),
 | 
			
		||||
               injected_build_dir_finder=_GetGtestBuildDir):
 | 
			
		||||
    self.os = injected_os
 | 
			
		||||
    self.subprocess = injected_subprocess
 | 
			
		||||
    self.build_dir_finder = injected_build_dir_finder
 | 
			
		||||
    self.build_dir_var_name = build_dir_var_name
 | 
			
		||||
    # If a program using this file is invoked via a relative path, the
 | 
			
		||||
    # script directory will be relative to the path of the main program
 | 
			
		||||
    # file.  It may be '.' when this script is invoked directly or '..' when
 | 
			
		||||
    # it is imported for testing.  To simplify testing we inject the script
 | 
			
		||||
    # directory into TestRunner.
 | 
			
		||||
    self.script_dir = injected_script_dir
 | 
			
		||||
 | 
			
		||||
  def _GetBuildDirForConfig(self, config):
 | 
			
		||||
    """Returns the build directory for a given configuration."""
 | 
			
		||||
 | 
			
		||||
    return self.build_dir_finder(self.os, self.script_dir, config)
 | 
			
		||||
 | 
			
		||||
  def _Run(self, args):
 | 
			
		||||
    """Runs the executable with given args (args[0] is the executable name).
 | 
			
		||||
 | 
			
		||||
    Args:
 | 
			
		||||
      args: Command line arguments for the process.
 | 
			
		||||
 | 
			
		||||
    Returns:
 | 
			
		||||
      Process's exit code if it exits normally, or -signal if the process is
 | 
			
		||||
      killed by a signal.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    if self.subprocess:
 | 
			
		||||
      return self.subprocess.Popen(args).wait()
 | 
			
		||||
    else:
 | 
			
		||||
      return self.os.spawnv(self.os.P_WAIT, args[0], args)
 | 
			
		||||
 | 
			
		||||
  def _RunBinaryTest(self, test):
 | 
			
		||||
    """Runs the binary test given its path.
 | 
			
		||||
 | 
			
		||||
    Args:
 | 
			
		||||
      test: Path to the test binary.
 | 
			
		||||
 | 
			
		||||
    Returns:
 | 
			
		||||
      Process's exit code if it exits normally, or -signal if the process is
 | 
			
		||||
      killed by a signal.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    return self._Run([test])
 | 
			
		||||
 | 
			
		||||
  def _RunPythonTest(self, test, build_dir):
 | 
			
		||||
    """Runs the Python test script with the specified build directory.
 | 
			
		||||
 | 
			
		||||
    Args:
 | 
			
		||||
      test: Path to the test's Python script.
 | 
			
		||||
      build_dir: Path to the directory where the test binary is to be found.
 | 
			
		||||
 | 
			
		||||
    Returns:
 | 
			
		||||
      Process's exit code if it exits normally, or -signal if the process is
 | 
			
		||||
      killed by a signal.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    old_build_dir = self.os.environ.get(self.build_dir_var_name)
 | 
			
		||||
 | 
			
		||||
    try:
 | 
			
		||||
      self.os.environ[self.build_dir_var_name] = build_dir
 | 
			
		||||
 | 
			
		||||
      # If this script is run on a Windows machine that has no association
 | 
			
		||||
      # between the .py extension and a python interpreter, simply passing
 | 
			
		||||
      # the script name into subprocess.Popen/os.spawn will not work.
 | 
			
		||||
      print 'Running %s . . .' % (test,)
 | 
			
		||||
      return self._Run([sys.executable, test])
 | 
			
		||||
 | 
			
		||||
    finally:
 | 
			
		||||
      if old_build_dir is None:
 | 
			
		||||
        del self.os.environ[self.build_dir_var_name]
 | 
			
		||||
      else:
 | 
			
		||||
        self.os.environ[self.build_dir_var_name] = old_build_dir
 | 
			
		||||
 | 
			
		||||
  def _FindFilesByRegex(self, directory, regex):
 | 
			
		||||
    """Returns files in a directory whose names match a regular expression.
 | 
			
		||||
 | 
			
		||||
    Args:
 | 
			
		||||
      directory: Path to the directory to search for files.
 | 
			
		||||
      regex: Regular expression to filter file names.
 | 
			
		||||
 | 
			
		||||
    Returns:
 | 
			
		||||
      The list of the paths to the files in the directory.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    return [self.os.path.join(directory, file_name)
 | 
			
		||||
            for file_name in self.os.listdir(directory)
 | 
			
		||||
            if re.search(regex, file_name)]
 | 
			
		||||
 | 
			
		||||
  # TODO(vladl@google.com): Implement parsing of scons/SConscript to run all
 | 
			
		||||
  # tests defined there when no tests are specified.
 | 
			
		||||
  # TODO(vladl@google.com): Update the docstring after the code is changed to
 | 
			
		||||
  # try to test all builds defined in scons/SConscript.
 | 
			
		||||
  def GetTestsToRun(self,
 | 
			
		||||
                    args,
 | 
			
		||||
                    named_configurations,
 | 
			
		||||
                    built_configurations,
 | 
			
		||||
                    available_configurations=CONFIGS):
 | 
			
		||||
    """Determines what tests should be run.
 | 
			
		||||
 | 
			
		||||
    Args:
 | 
			
		||||
      args: The list of non-option arguments from the command line.
 | 
			
		||||
      named_configurations: The list of configurations specified via -c or -a.
 | 
			
		||||
      built_configurations: True if -b has been specified.
 | 
			
		||||
      available_configurations: a list of configurations available on the
 | 
			
		||||
                                current platform, injectable for testing.
 | 
			
		||||
 | 
			
		||||
    Returns:
 | 
			
		||||
      A tuple with 2 elements: the list of Python tests to run and the list of
 | 
			
		||||
      binary tests to run.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    if named_configurations == 'all':
 | 
			
		||||
      named_configurations = ','.join(available_configurations)
 | 
			
		||||
 | 
			
		||||
    normalized_args = [self.os.path.normpath(arg) for arg in args]
 | 
			
		||||
 | 
			
		||||
    # A final list of build directories which will be searched for the test
 | 
			
		||||
    # binaries. First, add directories specified directly on the command
 | 
			
		||||
    # line.
 | 
			
		||||
    build_dirs = filter(self.os.path.isdir, normalized_args)
 | 
			
		||||
 | 
			
		||||
    # Adds build directories specified via their build configurations using
 | 
			
		||||
    # the -c or -a options.
 | 
			
		||||
    if named_configurations:
 | 
			
		||||
      build_dirs += [self._GetBuildDirForConfig(config)
 | 
			
		||||
                     for config in named_configurations.split(',')]
 | 
			
		||||
 | 
			
		||||
    # Adds KNOWN BUILD DIRECTORIES if -b is specified.
 | 
			
		||||
    if built_configurations:
 | 
			
		||||
      build_dirs += [self._GetBuildDirForConfig(config)
 | 
			
		||||
                     for config in available_configurations
 | 
			
		||||
                     if self.os.path.isdir(self._GetBuildDirForConfig(config))]
 | 
			
		||||
 | 
			
		||||
    # If no directories were specified either via -a, -b, -c, or directly, use
 | 
			
		||||
    # the default configuration.
 | 
			
		||||
    elif not build_dirs:
 | 
			
		||||
      build_dirs = [self._GetBuildDirForConfig(available_configurations[0])]
 | 
			
		||||
 | 
			
		||||
    # Makes sure there are no duplications.
 | 
			
		||||
    build_dirs = sets.Set(build_dirs)
 | 
			
		||||
 | 
			
		||||
    errors_found = False
 | 
			
		||||
    listed_python_tests = []  # All Python tests listed on the command line.
 | 
			
		||||
    listed_binary_tests = []  # All binary tests listed on the command line.
 | 
			
		||||
 | 
			
		||||
    test_dir = self.os.path.normpath(self.os.path.join(self.script_dir, 'test'))
 | 
			
		||||
 | 
			
		||||
    # Sifts through non-directory arguments fishing for any Python or binary
 | 
			
		||||
    # tests and detecting errors.
 | 
			
		||||
    for argument in sets.Set(normalized_args) - build_dirs:
 | 
			
		||||
      if re.search(PYTHON_TEST_REGEX, argument):
 | 
			
		||||
        python_path = self.os.path.join(test_dir,
 | 
			
		||||
                                        self.os.path.basename(argument))
 | 
			
		||||
        if self.os.path.isfile(python_path):
 | 
			
		||||
          listed_python_tests.append(python_path)
 | 
			
		||||
        else:
 | 
			
		||||
          sys.stderr.write('Unable to find Python test %s' % argument)
 | 
			
		||||
          errors_found = True
 | 
			
		||||
      elif re.search(BINARY_TEST_REGEX, argument):
 | 
			
		||||
        # This script also accepts binary test names prefixed with test/ for
 | 
			
		||||
        # the convenience of typing them (can use path completions in the
 | 
			
		||||
        # shell).  Strips test/ prefix from the binary test names.
 | 
			
		||||
        listed_binary_tests.append(self.os.path.basename(argument))
 | 
			
		||||
      else:
 | 
			
		||||
        sys.stderr.write('%s is neither test nor build directory' % argument)
 | 
			
		||||
        errors_found = True
 | 
			
		||||
 | 
			
		||||
    if errors_found:
 | 
			
		||||
      return None
 | 
			
		||||
 | 
			
		||||
    user_has_listed_tests = listed_python_tests or listed_binary_tests
 | 
			
		||||
 | 
			
		||||
    if user_has_listed_tests:
 | 
			
		||||
      selected_python_tests = listed_python_tests
 | 
			
		||||
    else:
 | 
			
		||||
      selected_python_tests = self._FindFilesByRegex(test_dir,
 | 
			
		||||
                                                     PYTHON_TEST_REGEX)
 | 
			
		||||
 | 
			
		||||
    # TODO(vladl@google.com): skip unbuilt Python tests when -b is specified.
 | 
			
		||||
    python_test_pairs = []
 | 
			
		||||
    for directory in build_dirs:
 | 
			
		||||
      for test in selected_python_tests:
 | 
			
		||||
        python_test_pairs.append((directory, test))
 | 
			
		||||
 | 
			
		||||
    binary_test_pairs = []
 | 
			
		||||
    for directory in build_dirs:
 | 
			
		||||
      if user_has_listed_tests:
 | 
			
		||||
        binary_test_pairs.extend(
 | 
			
		||||
            [(directory, self.os.path.join(directory, test))
 | 
			
		||||
             for test in listed_binary_tests])
 | 
			
		||||
      else:
 | 
			
		||||
        tests = self._FindFilesByRegex(directory, BINARY_TEST_SEARCH_REGEX)
 | 
			
		||||
        binary_test_pairs.extend([(directory, test) for test in tests])
 | 
			
		||||
 | 
			
		||||
    return (python_test_pairs, binary_test_pairs)
 | 
			
		||||
 | 
			
		||||
  def RunTests(self, python_tests, binary_tests):
 | 
			
		||||
    """Runs Python and binary tests and reports results to the standard output.
 | 
			
		||||
 | 
			
		||||
    Args:
 | 
			
		||||
      python_tests: List of Python tests to run in the form of tuples
 | 
			
		||||
                    (build directory, Python test script).
 | 
			
		||||
      binary_tests: List of binary tests to run in the form of tuples
 | 
			
		||||
                    (build directory, binary file).
 | 
			
		||||
 | 
			
		||||
    Returns:
 | 
			
		||||
      The exit code the program should pass into sys.exit().
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    if python_tests or binary_tests:
 | 
			
		||||
      results = []
 | 
			
		||||
      for directory, test in python_tests:
 | 
			
		||||
        results.append((directory,
 | 
			
		||||
                        test,
 | 
			
		||||
                        self._RunPythonTest(test, directory) == 0))
 | 
			
		||||
      for directory, test in binary_tests:
 | 
			
		||||
        results.append((directory,
 | 
			
		||||
                        self.os.path.basename(test),
 | 
			
		||||
                        self._RunBinaryTest(test) == 0))
 | 
			
		||||
 | 
			
		||||
      failed = [(directory, test)
 | 
			
		||||
                for (directory, test, success) in results
 | 
			
		||||
                if not success]
 | 
			
		||||
      print
 | 
			
		||||
      print '%d tests run.' % len(results)
 | 
			
		||||
      if failed:
 | 
			
		||||
        print 'The following %d tests failed:' % len(failed)
 | 
			
		||||
        for (directory, test) in failed:
 | 
			
		||||
          print '%s in %s' % (test, directory)
 | 
			
		||||
        return 1
 | 
			
		||||
      else:
 | 
			
		||||
        print 'All tests passed!'
 | 
			
		||||
    else:  # No tests defined
 | 
			
		||||
      print 'Nothing to test - no tests specified!'
 | 
			
		||||
 | 
			
		||||
    return 0
 | 
			
		||||
sys.path.append(os.path.join(SCRIPT_DIR, 'test'))
 | 
			
		||||
import run_tests_util
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def _Main():
 | 
			
		||||
  """Runs all tests for Google Test."""
 | 
			
		||||
 | 
			
		||||
  parser = optparse.OptionParser()
 | 
			
		||||
  parser.add_option('-c',
 | 
			
		||||
                    action='store',
 | 
			
		||||
                    dest='configurations',
 | 
			
		||||
                    default=None,
 | 
			
		||||
                    help='Test in the specified build directories')
 | 
			
		||||
  parser.add_option('-a',
 | 
			
		||||
                    action='store_const',
 | 
			
		||||
                    dest='configurations',
 | 
			
		||||
                    default=None,
 | 
			
		||||
                    const='all',
 | 
			
		||||
                    help='Test in all default build directories')
 | 
			
		||||
  parser.add_option('-b',
 | 
			
		||||
                    action='store_const',
 | 
			
		||||
                    dest='built_configurations',
 | 
			
		||||
                    default=False,
 | 
			
		||||
                    const=True,
 | 
			
		||||
                    help=('Test in all default build directories, do not fail'
 | 
			
		||||
                          'if some of them do not exist'))
 | 
			
		||||
  (options, args) = parser.parse_args()
 | 
			
		||||
 | 
			
		||||
  test_runner = TestRunner()
 | 
			
		||||
  options, args = run_tests_util.ParseArgs('gtest')
 | 
			
		||||
  test_runner = run_tests_util.TestRunner(script_dir=SCRIPT_DIR)
 | 
			
		||||
  tests = test_runner.GetTestsToRun(args,
 | 
			
		||||
                                    options.configurations,
 | 
			
		||||
                                    options.built_configurations)
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user