#!/usr/bin/env python # # Copyright 2009 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. """Tests for run_tests_util.py test runner script.""" __author__ = 'vladl@google.com (Vlad Losev)' import os import re import sets import unittest import run_tests_util GTEST_DBG_DIR = 'scons/build/dbg/gtest/scons' GTEST_OPT_DIR = 'scons/build/opt/gtest/scons' GTEST_OTHER_DIR = 'scons/build/other/gtest/scons' def AddExeExtension(path): """Appends .exe to the path on Windows or Cygwin.""" if run_tests_util.IS_WINDOWS or run_tests_util.IS_CYGWIN: return path + '.exe' else: return path class FakePath(object): """A fake os.path module for testing.""" def __init__(self, current_dir=os.getcwd(), known_paths=None): self.current_dir = current_dir self.tree = {} self.path_separator = os.sep # known_paths contains either absolute or relative paths. Relative paths # are absolutized with self.current_dir. if known_paths: self._AddPaths(known_paths) def _AddPath(self, path): ends_with_slash = path.endswith('/') path = self.abspath(path) if ends_with_slash: path += self.path_separator name_list = path.split(self.path_separator) tree = self.tree for name in name_list[:-1]: if not name: continue if name in tree: tree = tree[name] else: tree[name] = {} tree = tree[name] name = name_list[-1] if name: if name in tree: assert tree[name] == 1 else: tree[name] = 1 def _AddPaths(self, paths): for path in paths: self._AddPath(path) def PathElement(self, path): """Returns an internal representation of directory tree entry for path.""" tree = self.tree name_list = self.abspath(path).split(self.path_separator) for name in name_list: if not name: continue tree = tree.get(name, None) if tree is None: break return tree # Silences pylint warning about using standard names. # pylint: disable-msg=C6409 def normpath(self, path): return os.path.normpath(path) def abspath(self, path): return self.normpath(os.path.join(self.current_dir, path)) def isfile(self, path): return self.PathElement(self.abspath(path)) == 1 def isdir(self, path): return type(self.PathElement(self.abspath(path))) == type(dict()) def basename(self, path): return os.path.basename(path) def dirname(self, path): return os.path.dirname(path) def join(self, *kargs): return os.path.join(*kargs) class FakeOs(object): """A fake os module for testing.""" P_WAIT = os.P_WAIT def __init__(self, fake_path_module): self.path = fake_path_module # 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() def spawnv(self, wait, executable, *kargs): assert wait == FakeOs.P_WAIT return self.spawn_impl(executable, kargs) class GetTestsToRunTest(unittest.TestCase): """Exercises TestRunner.GetTestsToRun.""" def NormalizeGetTestsToRunResults(self, results): """Normalizes path data returned from GetTestsToRun for comparison.""" def NormalizePythonTestPair(pair): """Normalizes path data in the (directory, python_script) pair.""" return (os.path.normpath(pair[0]), os.path.normpath(pair[1])) def NormalizeBinaryTestPair(pair): """Normalizes path data in the (directory, binary_executable) pair.""" directory, executable = map(os.path.normpath, pair) # 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_util.IS_WINDOWS or run_tests_util.IS_CYGWIN: executable = re.sub(r'\.exe$', '', executable) return (directory, executable) python_tests = sets.Set(map(NormalizePythonTestPair, results[0])) binary_tests = sets.Set(map(NormalizeBinaryTestPair, results[1])) return (python_tests, binary_tests) def AssertResultsEqual(self, results, expected): """Asserts results returned by GetTestsToRun equal to expected results.""" self.assertEqual(self.NormalizeGetTestsToRunResults(results), self.NormalizeGetTestsToRunResults(expected), 'Incorrect set of tests returned:\n%s\nexpected:\n%s' % (results, expected)) def setUp(self): self.fake_os = FakeOs(FakePath( 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_util.TestRunner(script_dir='.', injected_os=self.fake_os, injected_subprocess=None) def testBinaryTestsOnly(self): """Exercises GetTestsToRun with parameters designating binary tests only.""" # A default build. self.AssertResultsEqual( self.test_runner.GetTestsToRun( ['gtest_unittest'], '', False, available_configurations=self.fake_configurations), ([], [(GTEST_DBG_DIR, GTEST_DBG_DIR + '/gtest_unittest')])) # An explicitly specified directory. self.AssertResultsEqual( self.test_runner.GetTestsToRun( [GTEST_DBG_DIR, 'gtest_unittest'], '', False, available_configurations=self.fake_configurations), ([], [(GTEST_DBG_DIR, GTEST_DBG_DIR + '/gtest_unittest')])) # A particular configuration. self.AssertResultsEqual( self.test_runner.GetTestsToRun( ['gtest_unittest'], 'other', False, available_configurations=self.fake_configurations), ([], [(GTEST_OTHER_DIR, GTEST_OTHER_DIR + '/gtest_unittest')])) # All available configurations self.AssertResultsEqual( self.test_runner.GetTestsToRun( ['gtest_unittest'], 'all', False, available_configurations=self.fake_configurations), ([], [(GTEST_DBG_DIR, GTEST_DBG_DIR + '/gtest_unittest'), (GTEST_OPT_DIR, GTEST_OPT_DIR + '/gtest_unittest')])) # All built configurations (unbuilt don't cause failure). self.AssertResultsEqual( self.test_runner.GetTestsToRun( ['gtest_unittest'], '', True, available_configurations=self.fake_configurations + ['unbuilt']), ([], [(GTEST_DBG_DIR, GTEST_DBG_DIR + '/gtest_unittest'), (GTEST_OPT_DIR, GTEST_OPT_DIR + '/gtest_unittest')])) # A combination of an explicit directory and a configuration. self.AssertResultsEqual( self.test_runner.GetTestsToRun( [GTEST_DBG_DIR, 'gtest_unittest'], 'opt', False, available_configurations=self.fake_configurations), ([], [(GTEST_DBG_DIR, GTEST_DBG_DIR + '/gtest_unittest'), (GTEST_OPT_DIR, GTEST_OPT_DIR + '/gtest_unittest')])) # Same test specified in an explicit directory and via a configuration. self.AssertResultsEqual( self.test_runner.GetTestsToRun( [GTEST_DBG_DIR, 'gtest_unittest'], 'dbg', False, available_configurations=self.fake_configurations), ([], [(GTEST_DBG_DIR, GTEST_DBG_DIR + '/gtest_unittest')])) # All built configurations + explicit directory + explicit configuration. self.AssertResultsEqual( self.test_runner.GetTestsToRun( [GTEST_DBG_DIR, 'gtest_unittest'], 'opt', True, available_configurations=self.fake_configurations), ([], [(GTEST_DBG_DIR, GTEST_DBG_DIR + '/gtest_unittest'), (GTEST_OPT_DIR, GTEST_OPT_DIR + '/gtest_unittest')])) def testPythonTestsOnly(self): """Exercises GetTestsToRun with parameters designating Python tests only.""" # A default build. self.AssertResultsEqual( self.test_runner.GetTestsToRun( ['gtest_color_test.py'], '', False, available_configurations=self.fake_configurations), ([(GTEST_DBG_DIR, 'test/gtest_color_test.py')], [])) # An explicitly specified directory. self.AssertResultsEqual( self.test_runner.GetTestsToRun( [GTEST_DBG_DIR, 'test/gtest_color_test.py'], '', False, available_configurations=self.fake_configurations), ([(GTEST_DBG_DIR, 'test/gtest_color_test.py')], [])) # A particular configuration. self.AssertResultsEqual( self.test_runner.GetTestsToRun( ['gtest_color_test.py'], 'other', False, available_configurations=self.fake_configurations), ([(GTEST_OTHER_DIR, 'test/gtest_color_test.py')], [])) # All available configurations self.AssertResultsEqual( self.test_runner.GetTestsToRun( ['test/gtest_color_test.py'], 'all', False, available_configurations=self.fake_configurations), ([(GTEST_DBG_DIR, 'test/gtest_color_test.py'), (GTEST_OPT_DIR, 'test/gtest_color_test.py')], [])) # All built configurations (unbuilt don't cause failure). self.AssertResultsEqual( self.test_runner.GetTestsToRun( ['gtest_color_test.py'], '', True, available_configurations=self.fake_configurations + ['unbuilt']), ([(GTEST_DBG_DIR, 'test/gtest_color_test.py'), (GTEST_OPT_DIR, 'test/gtest_color_test.py')], [])) # A combination of an explicit directory and a configuration. self.AssertResultsEqual( self.test_runner.GetTestsToRun( [GTEST_DBG_DIR, 'gtest_color_test.py'], 'opt', False, available_configurations=self.fake_configurations), ([(GTEST_DBG_DIR, 'test/gtest_color_test.py'), (GTEST_OPT_DIR, 'test/gtest_color_test.py')], [])) # Same test specified in an explicit directory and via a configuration. self.AssertResultsEqual( self.test_runner.GetTestsToRun( [GTEST_DBG_DIR, 'gtest_color_test.py'], 'dbg', False, available_configurations=self.fake_configurations), ([(GTEST_DBG_DIR, 'test/gtest_color_test.py')], [])) # All built configurations + explicit directory + explicit configuration. self.AssertResultsEqual( self.test_runner.GetTestsToRun( [GTEST_DBG_DIR, 'gtest_color_test.py'], 'opt', True, available_configurations=self.fake_configurations), ([(GTEST_DBG_DIR, 'test/gtest_color_test.py'), (GTEST_OPT_DIR, 'test/gtest_color_test.py')], [])) def testCombinationOfBinaryAndPythonTests(self): """Exercises GetTestsToRun with mixed binary/Python tests.""" # Use only default configuration for this test. # Neither binary nor Python tests are specified so find all. self.AssertResultsEqual( self.test_runner.GetTestsToRun( [], '', False, available_configurations=self.fake_configurations), ([(GTEST_DBG_DIR, 'test/gtest_color_test.py')], [(GTEST_DBG_DIR, GTEST_DBG_DIR + '/gtest_unittest')])) # Specifying both binary and Python tests. self.AssertResultsEqual( self.test_runner.GetTestsToRun( ['gtest_unittest', 'gtest_color_test.py'], '', False, available_configurations=self.fake_configurations), ([(GTEST_DBG_DIR, 'test/gtest_color_test.py')], [(GTEST_DBG_DIR, GTEST_DBG_DIR + '/gtest_unittest')])) # Specifying binary tests suppresses Python tests. self.AssertResultsEqual( self.test_runner.GetTestsToRun( ['gtest_unittest'], '', False, available_configurations=self.fake_configurations), ([], [(GTEST_DBG_DIR, GTEST_DBG_DIR + '/gtest_unittest')])) # Specifying Python tests suppresses binary tests. self.AssertResultsEqual( self.test_runner.GetTestsToRun( ['gtest_color_test.py'], '', False, available_configurations=self.fake_configurations), ([(GTEST_DBG_DIR, 'test/gtest_color_test.py')], [])) def testIgnoresNonTestFiles(self): """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_util.__file__)), known_paths=[AddExeExtension(GTEST_DBG_DIR + '/gtest_nontest'), 'test/'])) self.test_runner = run_tests_util.TestRunner(script_dir='.', injected_os=self.fake_os, injected_subprocess=None) self.AssertResultsEqual( self.test_runner.GetTestsToRun( [], '', True, available_configurations=self.fake_configurations), ([], [])) def testWorksFromDifferentDir(self): """Exercises GetTestsToRun from a directory different from run_test.py's.""" # Here we simulate an test script in directory /d/ called from the # directory /a/b/c/. self.fake_os = FakeOs(FakePath( current_dir=os.path.abspath('/a/b/c'), known_paths=[ '/a/b/c/', AddExeExtension('/d/' + GTEST_DBG_DIR + '/gtest_unittest'), AddExeExtension('/d/' + GTEST_OPT_DIR + '/gtest_unittest'), '/d/test/gtest_color_test.py'])) self.fake_configurations = ['dbg', 'opt'] 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( ['gtest_unittest'], '', False, available_configurations=self.fake_configurations), ([], [('/d/' + GTEST_DBG_DIR, '/d/' + GTEST_DBG_DIR + '/gtest_unittest')])) # A Python test. self.AssertResultsEqual( self.test_runner.GetTestsToRun( ['gtest_color_test.py'], '', False, 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.""" self.assert_( not self.test_runner.GetTestsToRun( ['gtest_unittest_not_really'], '', False, available_configurations=self.fake_configurations)) def testNonExistingPythonTest(self): """Exercises GetTestsToRun with a non-existent Python test parameter.""" self.assert_( not self.test_runner.GetTestsToRun( ['nonexistent_test.py'], '', False, available_configurations=self.fake_configurations)) 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_util.__file__)), known_paths=['/d/' + GTEST_DBG_DIR + '/gtest_test', 'test/'])) self.test_runner = run_tests_util.TestRunner(script_dir='.', injected_os=self.fake_os, injected_subprocess=None) self.AssertResultsEqual( self.test_runner.GetTestsToRun( [], '', True, available_configurations=self.fake_configurations), ([], [])) class RunTestsTest(unittest.TestCase): """Exercises TestRunner.RunTests.""" def SpawnSuccess(self, unused_executable, unused_argv): """Fakes test success by returning 0 as an exit code.""" self.num_spawn_calls += 1 return 0 def SpawnFailure(self, unused_executable, unused_argv): """Fakes test success by returning 1 as an exit code.""" self.num_spawn_calls += 1 return 1 def setUp(self): self.fake_os = FakeOs(FakePath( 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_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): """Exercises RunTests to handle a Python test success.""" self.fake_os.spawn_impl = self.SpawnSuccess self.assertEqual( self.test_runner.RunTests( [(GTEST_DBG_DIR, 'test/gtest_color_test.py')], []), 0) self.assertEqual(self.num_spawn_calls, 1) def testRunBinaryTestSuccess(self): """Exercises RunTests to handle a binary test success.""" self.fake_os.spawn_impl = self.SpawnSuccess self.assertEqual( self.test_runner.RunTests( [], [(GTEST_DBG_DIR, GTEST_DBG_DIR + '/gtest_unittest')]), 0) self.assertEqual(self.num_spawn_calls, 1) def testRunPythonTestFauilure(self): """Exercises RunTests to handle a Python test failure.""" self.fake_os.spawn_impl = self.SpawnFailure self.assertEqual( self.test_runner.RunTests( [(GTEST_DBG_DIR, 'test/gtest_color_test.py')], []), 1) self.assertEqual(self.num_spawn_calls, 1) def testRunBinaryTestFailure(self): """Exercises RunTests to handle a binary test failure.""" self.fake_os.spawn_impl = self.SpawnFailure self.assertEqual( self.test_runner.RunTests( [], [(GTEST_DBG_DIR, GTEST_DBG_DIR + '/gtest_unittest')]), 1) self.assertEqual(self.num_spawn_calls, 1) def testCombinedTestSuccess(self): """Exercises RunTests to handle a success of both Python and binary test.""" self.fake_os.spawn_impl = self.SpawnSuccess self.assertEqual( self.test_runner.RunTests( [(GTEST_DBG_DIR, GTEST_DBG_DIR + '/gtest_unittest')], [(GTEST_DBG_DIR, GTEST_DBG_DIR + '/gtest_unittest')]), 0) self.assertEqual(self.num_spawn_calls, 2) def testCombinedTestSuccessAndFailure(self): """Exercises RunTests to handle a success of both Python and binary test.""" def SpawnImpl(executable, argv): self.num_spawn_calls += 1 # Simulates failure of a Python test and success of a binary test. if '.py' in executable or '.py' in argv[0]: return 1 else: return 0 self.fake_os.spawn_impl = SpawnImpl self.assertEqual( self.test_runner.RunTests( [(GTEST_DBG_DIR, GTEST_DBG_DIR + '/gtest_unittest')], [(GTEST_DBG_DIR, GTEST_DBG_DIR + '/gtest_unittest')]), 0) 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()