summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorzhanyong.wan <zhanyong.wan@861a406c-534a-0410-8894-cb66d6ee9925>2009-11-24 20:19:45 +0000
committerzhanyong.wan <zhanyong.wan@861a406c-534a-0410-8894-cb66d6ee9925>2009-11-24 20:19:45 +0000
commit89f3c263a8bb78a6a7ba2078852c2d3f1cc5ed86 (patch)
tree38f5aae68b5e0a48991fdcbd7729647c9213fdfa
parentc23fd197eec5a5bd471cc19ebadcd248dfcad16c (diff)
downloadgtest-89f3c263a8bb78a6a7ba2078852c2d3f1cc5ed86.tar.gz
gtest-89f3c263a8bb78a6a7ba2078852c2d3f1cc5ed86.tar.bz2
gtest-89f3c263a8bb78a6a7ba2078852c2d3f1cc5ed86.tar.xz
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).
git-svn-id: http://googletest.googlecode.com/svn/trunk@344 861a406c-534a-0410-8894-cb66d6ee9925
-rw-r--r--Makefile.am5
-rwxr-xr-xrun_tests.py405
-rw-r--r--test/gtest-tuple_test.cc2
-rwxr-xr-xtest/gtest_test_utils.py7
-rwxr-xr-xtest/run_tests_util.py445
-rwxr-xr-xtest/run_tests_util_test.py (renamed from test/run_tests_test.py)114
6 files changed, 552 insertions, 426 deletions
diff --git a/Makefile.am b/Makefile.am
index ec3e5ee..14880b8 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -11,6 +11,7 @@ EXTRA_DIST = \
include/gtest/internal/gtest-type-util.h.pump \
include/gtest/internal/gtest-param-util-generated.h.pump \
make/Makefile \
+ run_tests.py \
scons/SConscript \
scons/SConstruct \
scons/SConstruct.common \
@@ -432,6 +433,10 @@ test_gtest_xml_output_unittest__LDADD = lib/libgtest.la
check_SCRIPTS += test/gtest_xml_output_unittest.py
TESTS += test/gtest_xml_output_unittest.py
+check_SCRIPTS += test/run_tests_util.py \
+ test/run_tests_util_test.py
+TESTS += test/run_tests_util_test.py
+
# TODO(wan@google.com): make the build script compile and run the
# negative-compilation tests. (The test/gtest_nc* files are unfinished
# implementation of tests for verifying that certain kinds of misuse
diff --git a/run_tests.py b/run_tests.py
index 9d85b80..e108405 100755
--- a/run_tests.py
+++ b/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
-
-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.
+SCRIPT_DIR = os.path.dirname(__file__) or '.'
- 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)
diff --git a/test/gtest-tuple_test.cc b/test/gtest-tuple_test.cc
index ca5232e..532f70b 100644
--- a/test/gtest-tuple_test.cc
+++ b/test/gtest-tuple_test.cc
@@ -159,7 +159,7 @@ TEST(TupleConstructorTest, DefaultConstructorDefaultInitializesEachField) {
b3 = a3;
EXPECT_EQ(0.0, get<0>(b3));
EXPECT_EQ('\0', get<1>(b3));
- EXPECT_EQ(NULL, get<2>(b3));
+ EXPECT_TRUE(get<2>(b3) == NULL);
tuple<int, int, int, int, int, int, int, int, int, int> a10, b10;
b10 = a10;
diff --git a/test/gtest_test_utils.py b/test/gtest_test_utils.py
index 591cdb8..19b5b22 100755
--- a/test/gtest_test_utils.py
+++ b/test/gtest_test_utils.py
@@ -138,7 +138,7 @@ def GetTempDir():
return _temp_dir
-def GetTestExecutablePath(executable_name):
+def GetTestExecutablePath(executable_name, build_dir=None):
"""Returns the absolute path of the test binary given its name.
The function will print a message and abort the program if the resulting file
@@ -146,12 +146,15 @@ def GetTestExecutablePath(executable_name):
Args:
executable_name: name of the test binary that the test script runs.
+ build_dir: directory where to look for executables, by default
+ the result of GetBuildDir().
Returns:
The absolute path of the test binary.
"""
- path = os.path.abspath(os.path.join(GetBuildDir(), executable_name))
+ path = os.path.abspath(os.path.join(build_dir or GetBuildDir(),
+ executable_name))
if (IS_WINDOWS or IS_CYGWIN) and not path.endswith('.exe'):
path += '.exe'
diff --git a/test/run_tests_util.py b/test/run_tests_util.py
new file mode 100755
index 0000000..f1bb3e0
--- /dev/null
+++ b/test/run_tests_util.py
@@ -0,0 +1,445 @@
+# Copyright 2008 Google Inc. All Rights Reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+"""Provides facilities for running SCons-built Google Test/Mock tests."""
+
+
+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.
+ # Suppresses the 'Import not at the top of the file' lint complaint.
+ # pylint: disable-msg=C6204
+ import subprocess
+except ImportError:
+ subprocess = None
+
+HELP_MSG = """Runs the specified tests for %(proj)s.
+
+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
+ %(proj)s 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
+ -h, --help
+ Print this help message.
+ -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>/%(proj)s/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, %(proj)s's SConstruct file
+ defines them as follows (the default build directory is the first one
+ listed in each group):
+ On Windows:
+ <%(proj)s root>/scons/build/win-dbg8/%(proj)s/scons/
+ <%(proj)s root>/scons/build/win-opt8/%(proj)s/scons/
+ <%(proj)s root>/scons/build/win-dbg/%(proj)s/scons/
+ <%(proj)s root>/scons/build/win-opt/%(proj)s/scons/
+ On Mac:
+ <%(proj)s root>/scons/build/mac-dbg/%(proj)s/scons/
+ <%(proj)s root>/scons/build/mac-opt/%(proj)s/scons/
+ On other platforms:
+ <%(proj)s root>/scons/build/dbg/%(proj)s/scons/
+ <%(proj)s root>/scons/build/opt/%(proj)s/scons/"""
+
+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(injected_os, script_dir, config):
+ """Calculates path to the Google Test SCons build directory."""
+
+ return injected_os.path.normpath(injected_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,
+ script_dir,
+ build_dir_var_name='GTEST_BUILD_DIR',
+ injected_os=os,
+ injected_subprocess=subprocess,
+ injected_build_dir_finder=_GetGtestBuildDir):
+ """Initializes a TestRunner instance.
+
+ Args:
+ script_dir: File path to the calling script.
+ build_dir_var_name: Name of the env variable used to pass the
+ the build directory path to the invoked
+ tests.
+ injected_os: standard os module or a mock/stub for
+ testing.
+ injected_subprocess: standard subprocess module or a mock/stub
+ for testing
+ injected_build_dir_finder: function that determines the path to
+ the build directory.
+ """
+
+ 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
+ self.script_dir = 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
+
+
+def ParseArgs(project_name, argv=None, help_callback=None):
+ """Parses the options run_tests.py uses."""
+
+ # Suppresses lint warning on unused arguments. These arguments are
+ # required by optparse, even though they are unused.
+ # pylint: disable-msg=W0613
+ def PrintHelp(option, opt, value, parser):
+ print HELP_MSG % {'proj': project_name}
+ sys.exit(1)
+
+ parser = optparse.OptionParser()
+ parser.add_option('-c',
+ action='store',
+ dest='configurations',
+ default=None)
+ parser.add_option('-a',
+ action='store_const',
+ dest='configurations',
+ default=None,
+ const='all')
+ parser.add_option('-b',
+ action='store_const',
+ dest='built_configurations',
+ default=False,
+ const=True)
+ # Replaces the built-in help with ours.
+ parser.remove_option('-h')
+ parser.add_option('-h', '--help',
+ action='callback',
+ callback=help_callback or PrintHelp)
+ return parser.parse_args(argv)
diff --git a/test/run_tests_test.py b/test/run_tests_util_test.py
index a9f0b5d..9c55726 100755
--- a/test/run_tests_test.py
+++ b/test/run_tests_util_test.py
@@ -28,18 +28,16 @@
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-"""Tests for run_tests.py test runner script."""
+"""Tests for run_tests_util.py test runner script."""
__author__ = 'vladl@google.com (Vlad Losev)'
import os
import re
import sets
-import sys
import unittest
-sys.path.append(os.path.join(os.path.dirname(sys.argv[0]), os.pardir))
-import run_tests
+import run_tests_util
GTEST_DBG_DIR = 'scons/build/dbg/gtest/scons'
@@ -50,7 +48,7 @@ GTEST_OTHER_DIR = 'scons/build/other/gtest/scons'
def AddExeExtension(path):
"""Appends .exe to the path on Windows or Cygwin."""
- if run_tests.IS_WINDOWS or run_tests.IS_CYGWIN:
+ if run_tests_util.IS_WINDOWS or run_tests_util.IS_CYGWIN:
return path + '.exe'
else:
return path
@@ -109,6 +107,8 @@ class FakePath(object):
return tree
+ # Silences pylint warning about using standard names.
+ # pylint: disable-msg=C6409
def normpath(self, path):
return os.path.normpath(path)
@@ -141,6 +141,7 @@ class FakeOs(object):
# Some methods/attributes are delegated to the real os module.
self.environ = os.environ
+ # pylint: disable-msg=C6409
def listdir(self, path):
assert self.path.isdir(path)
return self.path.PathElement(path).iterkeys()
@@ -169,7 +170,7 @@ class GetTestsToRunTest(unittest.TestCase):
# On Windows and Cygwin, the test file names have the .exe extension, but
# they can be invoked either by name or by name+extension. Our test must
# accommodate both situations.
- if run_tests.IS_WINDOWS or run_tests.IS_CYGWIN:
+ if run_tests_util.IS_WINDOWS or run_tests_util.IS_CYGWIN:
executable = re.sub(r'\.exe$', '', executable)
return (directory, executable)
@@ -187,14 +188,14 @@ class GetTestsToRunTest(unittest.TestCase):
def setUp(self):
self.fake_os = FakeOs(FakePath(
- current_dir=os.path.abspath(os.path.dirname(run_tests.__file__)),
+ current_dir=os.path.abspath(os.path.dirname(run_tests_util.__file__)),
known_paths=[AddExeExtension(GTEST_DBG_DIR + '/gtest_unittest'),
AddExeExtension(GTEST_OPT_DIR + '/gtest_unittest'),
'test/gtest_color_test.py']))
self.fake_configurations = ['dbg', 'opt']
- self.test_runner = run_tests.TestRunner(injected_os=self.fake_os,
- injected_subprocess=None,
- injected_script_dir='.')
+ self.test_runner = run_tests_util.TestRunner(script_dir='.',
+ injected_os=self.fake_os,
+ injected_subprocess=None)
def testBinaryTestsOnly(self):
"""Exercises GetTestsToRun with parameters designating binary tests only."""
@@ -419,12 +420,12 @@ class GetTestsToRunTest(unittest.TestCase):
"""Verifies that GetTestsToRun ignores non-test files in the filesystem."""
self.fake_os = FakeOs(FakePath(
- current_dir=os.path.abspath(os.path.dirname(run_tests.__file__)),
+ current_dir=os.path.abspath(os.path.dirname(run_tests_util.__file__)),
known_paths=[AddExeExtension(GTEST_DBG_DIR + '/gtest_nontest'),
'test/']))
- self.test_runner = run_tests.TestRunner(injected_os=self.fake_os,
- injected_subprocess=None,
- injected_script_dir='.')
+ self.test_runner = run_tests_util.TestRunner(script_dir='.',
+ injected_os=self.fake_os,
+ injected_subprocess=None)
self.AssertResultsEqual(
self.test_runner.GetTestsToRun(
[],
@@ -446,9 +447,9 @@ class GetTestsToRunTest(unittest.TestCase):
AddExeExtension('/d/' + GTEST_OPT_DIR + '/gtest_unittest'),
'/d/test/gtest_color_test.py']))
self.fake_configurations = ['dbg', 'opt']
- self.test_runner = run_tests.TestRunner(injected_os=self.fake_os,
- injected_subprocess=None,
- injected_script_dir='/d/')
+ self.test_runner = run_tests_util.TestRunner(script_dir='/d/',
+ injected_os=self.fake_os,
+ injected_subprocess=None)
# A binary test.
self.AssertResultsEqual(
self.test_runner.GetTestsToRun(
@@ -468,7 +469,6 @@ class GetTestsToRunTest(unittest.TestCase):
available_configurations=self.fake_configurations),
([('/d/' + GTEST_DBG_DIR, '/d/test/gtest_color_test.py')], []))
-
def testNonTestBinary(self):
"""Exercises GetTestsToRun with a non-test parameter."""
@@ -489,16 +489,17 @@ class GetTestsToRunTest(unittest.TestCase):
False,
available_configurations=self.fake_configurations))
- if run_tests.IS_WINDOWS or run_tests.IS_CYGWIN:
+ if run_tests_util.IS_WINDOWS or run_tests_util.IS_CYGWIN:
+
def testDoesNotPickNonExeFilesOnWindows(self):
"""Verifies that GetTestsToRun does not find _test files on Windows."""
self.fake_os = FakeOs(FakePath(
- current_dir=os.path.abspath(os.path.dirname(run_tests.__file__)),
+ current_dir=os.path.abspath(os.path.dirname(run_tests_util.__file__)),
known_paths=['/d/' + GTEST_DBG_DIR + '/gtest_test', 'test/']))
- self.test_runner = run_tests.TestRunner(injected_os=self.fake_os,
- injected_subprocess=None,
- injected_script_dir='.')
+ self.test_runner = run_tests_util.TestRunner(script_dir='.',
+ injected_os=self.fake_os,
+ injected_subprocess=None)
self.AssertResultsEqual(
self.test_runner.GetTestsToRun(
[],
@@ -525,14 +526,16 @@ class RunTestsTest(unittest.TestCase):
def setUp(self):
self.fake_os = FakeOs(FakePath(
- current_dir=os.path.abspath(os.path.dirname(run_tests.__file__)),
+ current_dir=os.path.abspath(os.path.dirname(run_tests_util.__file__)),
known_paths=[
AddExeExtension(GTEST_DBG_DIR + '/gtest_unittest'),
AddExeExtension(GTEST_OPT_DIR + '/gtest_unittest'),
'test/gtest_color_test.py']))
self.fake_configurations = ['dbg', 'opt']
- self.test_runner = run_tests.TestRunner(injected_os=self.fake_os,
- injected_subprocess=None)
+ self.test_runner = run_tests_util.TestRunner(
+ script_dir=os.path.dirname(__file__) or '.',
+ injected_os=self.fake_os,
+ injected_subprocess=None)
self.num_spawn_calls = 0 # A number of calls to spawn.
def testRunPythonTestSuccess(self):
@@ -610,5 +613,64 @@ class RunTestsTest(unittest.TestCase):
self.assertEqual(self.num_spawn_calls, 2)
+class ParseArgsTest(unittest.TestCase):
+ """Exercises ParseArgs."""
+
+ def testNoOptions(self):
+ options, args = run_tests_util.ParseArgs('gtest', argv=['script.py'])
+ self.assertEqual(args, ['script.py'])
+ self.assert_(options.configurations is None)
+ self.assertFalse(options.built_configurations)
+
+ def testOptionC(self):
+ options, args = run_tests_util.ParseArgs(
+ 'gtest', argv=['script.py', '-c', 'dbg'])
+ self.assertEqual(args, ['script.py'])
+ self.assertEqual(options.configurations, 'dbg')
+ self.assertFalse(options.built_configurations)
+
+ def testOptionA(self):
+ options, args = run_tests_util.ParseArgs('gtest', argv=['script.py', '-a'])
+ self.assertEqual(args, ['script.py'])
+ self.assertEqual(options.configurations, 'all')
+ self.assertFalse(options.built_configurations)
+
+ def testOptionB(self):
+ options, args = run_tests_util.ParseArgs('gtest', argv=['script.py', '-b'])
+ self.assertEqual(args, ['script.py'])
+ self.assert_(options.configurations is None)
+ self.assertTrue(options.built_configurations)
+
+ def testOptionCAndOptionB(self):
+ options, args = run_tests_util.ParseArgs(
+ 'gtest', argv=['script.py', '-c', 'dbg', '-b'])
+ self.assertEqual(args, ['script.py'])
+ self.assertEqual(options.configurations, 'dbg')
+ self.assertTrue(options.built_configurations)
+
+ def testOptionH(self):
+ help_called = [False]
+
+ # Suppresses lint warning on unused arguments. These arguments are
+ # required by optparse, even though they are unused.
+ # pylint: disable-msg=W0613
+ def VerifyHelp(option, opt, value, parser):
+ help_called[0] = True
+
+ # Verifies that -h causes the help callback to be called.
+ help_called[0] = False
+ _, args = run_tests_util.ParseArgs(
+ 'gtest', argv=['script.py', '-h'], help_callback=VerifyHelp)
+ self.assertEqual(args, ['script.py'])
+ self.assertTrue(help_called[0])
+
+ # Verifies that --help causes the help callback to be called.
+ help_called[0] = False
+ _, args = run_tests_util.ParseArgs(
+ 'gtest', argv=['script.py', '--help'], help_callback=VerifyHelp)
+ self.assertEqual(args, ['script.py'])
+ self.assertTrue(help_called[0])
+
+
if __name__ == '__main__':
unittest.main()