michael@0: #!/usr/bin/env python michael@0: # michael@0: # Copyright 2009 Google Inc. All Rights Reserved. michael@0: # michael@0: # Redistribution and use in source and binary forms, with or without michael@0: # modification, are permitted provided that the following conditions are michael@0: # met: michael@0: # michael@0: # * Redistributions of source code must retain the above copyright michael@0: # notice, this list of conditions and the following disclaimer. michael@0: # * Redistributions in binary form must reproduce the above michael@0: # copyright notice, this list of conditions and the following disclaimer michael@0: # in the documentation and/or other materials provided with the michael@0: # distribution. michael@0: # * Neither the name of Google Inc. nor the names of its michael@0: # contributors may be used to endorse or promote products derived from michael@0: # this software without specific prior written permission. michael@0: # michael@0: # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS michael@0: # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT michael@0: # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR michael@0: # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT michael@0: # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, michael@0: # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT michael@0: # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, michael@0: # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY michael@0: # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT michael@0: # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE michael@0: # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. michael@0: michael@0: """Verifies that test shuffling works.""" michael@0: michael@0: __author__ = 'wan@google.com (Zhanyong Wan)' michael@0: michael@0: import os michael@0: import gtest_test_utils michael@0: michael@0: # Command to run the gtest_shuffle_test_ program. michael@0: COMMAND = gtest_test_utils.GetTestExecutablePath('gtest_shuffle_test_') michael@0: michael@0: # The environment variables for test sharding. michael@0: TOTAL_SHARDS_ENV_VAR = 'GTEST_TOTAL_SHARDS' michael@0: SHARD_INDEX_ENV_VAR = 'GTEST_SHARD_INDEX' michael@0: michael@0: TEST_FILTER = 'A*.A:A*.B:C*' michael@0: michael@0: ALL_TESTS = [] michael@0: ACTIVE_TESTS = [] michael@0: FILTERED_TESTS = [] michael@0: SHARDED_TESTS = [] michael@0: michael@0: SHUFFLED_ALL_TESTS = [] michael@0: SHUFFLED_ACTIVE_TESTS = [] michael@0: SHUFFLED_FILTERED_TESTS = [] michael@0: SHUFFLED_SHARDED_TESTS = [] michael@0: michael@0: michael@0: def AlsoRunDisabledTestsFlag(): michael@0: return '--gtest_also_run_disabled_tests' michael@0: michael@0: michael@0: def FilterFlag(test_filter): michael@0: return '--gtest_filter=%s' % (test_filter,) michael@0: michael@0: michael@0: def RepeatFlag(n): michael@0: return '--gtest_repeat=%s' % (n,) michael@0: michael@0: michael@0: def ShuffleFlag(): michael@0: return '--gtest_shuffle' michael@0: michael@0: michael@0: def RandomSeedFlag(n): michael@0: return '--gtest_random_seed=%s' % (n,) michael@0: michael@0: michael@0: def RunAndReturnOutput(extra_env, args): michael@0: """Runs the test program and returns its output.""" michael@0: michael@0: environ_copy = os.environ.copy() michael@0: environ_copy.update(extra_env) michael@0: michael@0: return gtest_test_utils.Subprocess([COMMAND] + args, env=environ_copy, michael@0: capture_stderr=False).output michael@0: michael@0: michael@0: def GetTestsForAllIterations(extra_env, args): michael@0: """Runs the test program and returns a list of test lists. michael@0: michael@0: Args: michael@0: extra_env: a map from environment variables to their values michael@0: args: command line flags to pass to gtest_shuffle_test_ michael@0: michael@0: Returns: michael@0: A list where the i-th element is the list of tests run in the i-th michael@0: test iteration. michael@0: """ michael@0: michael@0: test_iterations = [] michael@0: for line in RunAndReturnOutput(extra_env, args).split('\n'): michael@0: if line.startswith('----'): michael@0: tests = [] michael@0: test_iterations.append(tests) michael@0: elif line.strip(): michael@0: tests.append(line.strip()) # 'TestCaseName.TestName' michael@0: michael@0: return test_iterations michael@0: michael@0: michael@0: def GetTestCases(tests): michael@0: """Returns a list of test cases in the given full test names. michael@0: michael@0: Args: michael@0: tests: a list of full test names michael@0: michael@0: Returns: michael@0: A list of test cases from 'tests', in their original order. michael@0: Consecutive duplicates are removed. michael@0: """ michael@0: michael@0: test_cases = [] michael@0: for test in tests: michael@0: test_case = test.split('.')[0] michael@0: if not test_case in test_cases: michael@0: test_cases.append(test_case) michael@0: michael@0: return test_cases michael@0: michael@0: michael@0: def CalculateTestLists(): michael@0: """Calculates the list of tests run under different flags.""" michael@0: michael@0: if not ALL_TESTS: michael@0: ALL_TESTS.extend( michael@0: GetTestsForAllIterations({}, [AlsoRunDisabledTestsFlag()])[0]) michael@0: michael@0: if not ACTIVE_TESTS: michael@0: ACTIVE_TESTS.extend(GetTestsForAllIterations({}, [])[0]) michael@0: michael@0: if not FILTERED_TESTS: michael@0: FILTERED_TESTS.extend( michael@0: GetTestsForAllIterations({}, [FilterFlag(TEST_FILTER)])[0]) michael@0: michael@0: if not SHARDED_TESTS: michael@0: SHARDED_TESTS.extend( michael@0: GetTestsForAllIterations({TOTAL_SHARDS_ENV_VAR: '3', michael@0: SHARD_INDEX_ENV_VAR: '1'}, michael@0: [])[0]) michael@0: michael@0: if not SHUFFLED_ALL_TESTS: michael@0: SHUFFLED_ALL_TESTS.extend(GetTestsForAllIterations( michael@0: {}, [AlsoRunDisabledTestsFlag(), ShuffleFlag(), RandomSeedFlag(1)])[0]) michael@0: michael@0: if not SHUFFLED_ACTIVE_TESTS: michael@0: SHUFFLED_ACTIVE_TESTS.extend(GetTestsForAllIterations( michael@0: {}, [ShuffleFlag(), RandomSeedFlag(1)])[0]) michael@0: michael@0: if not SHUFFLED_FILTERED_TESTS: michael@0: SHUFFLED_FILTERED_TESTS.extend(GetTestsForAllIterations( michael@0: {}, [ShuffleFlag(), RandomSeedFlag(1), FilterFlag(TEST_FILTER)])[0]) michael@0: michael@0: if not SHUFFLED_SHARDED_TESTS: michael@0: SHUFFLED_SHARDED_TESTS.extend( michael@0: GetTestsForAllIterations({TOTAL_SHARDS_ENV_VAR: '3', michael@0: SHARD_INDEX_ENV_VAR: '1'}, michael@0: [ShuffleFlag(), RandomSeedFlag(1)])[0]) michael@0: michael@0: michael@0: class GTestShuffleUnitTest(gtest_test_utils.TestCase): michael@0: """Tests test shuffling.""" michael@0: michael@0: def setUp(self): michael@0: CalculateTestLists() michael@0: michael@0: def testShufflePreservesNumberOfTests(self): michael@0: self.assertEqual(len(ALL_TESTS), len(SHUFFLED_ALL_TESTS)) michael@0: self.assertEqual(len(ACTIVE_TESTS), len(SHUFFLED_ACTIVE_TESTS)) michael@0: self.assertEqual(len(FILTERED_TESTS), len(SHUFFLED_FILTERED_TESTS)) michael@0: self.assertEqual(len(SHARDED_TESTS), len(SHUFFLED_SHARDED_TESTS)) michael@0: michael@0: def testShuffleChangesTestOrder(self): michael@0: self.assert_(SHUFFLED_ALL_TESTS != ALL_TESTS, SHUFFLED_ALL_TESTS) michael@0: self.assert_(SHUFFLED_ACTIVE_TESTS != ACTIVE_TESTS, SHUFFLED_ACTIVE_TESTS) michael@0: self.assert_(SHUFFLED_FILTERED_TESTS != FILTERED_TESTS, michael@0: SHUFFLED_FILTERED_TESTS) michael@0: self.assert_(SHUFFLED_SHARDED_TESTS != SHARDED_TESTS, michael@0: SHUFFLED_SHARDED_TESTS) michael@0: michael@0: def testShuffleChangesTestCaseOrder(self): michael@0: self.assert_(GetTestCases(SHUFFLED_ALL_TESTS) != GetTestCases(ALL_TESTS), michael@0: GetTestCases(SHUFFLED_ALL_TESTS)) michael@0: self.assert_( michael@0: GetTestCases(SHUFFLED_ACTIVE_TESTS) != GetTestCases(ACTIVE_TESTS), michael@0: GetTestCases(SHUFFLED_ACTIVE_TESTS)) michael@0: self.assert_( michael@0: GetTestCases(SHUFFLED_FILTERED_TESTS) != GetTestCases(FILTERED_TESTS), michael@0: GetTestCases(SHUFFLED_FILTERED_TESTS)) michael@0: self.assert_( michael@0: GetTestCases(SHUFFLED_SHARDED_TESTS) != GetTestCases(SHARDED_TESTS), michael@0: GetTestCases(SHUFFLED_SHARDED_TESTS)) michael@0: michael@0: def testShuffleDoesNotRepeatTest(self): michael@0: for test in SHUFFLED_ALL_TESTS: michael@0: self.assertEqual(1, SHUFFLED_ALL_TESTS.count(test), michael@0: '%s appears more than once' % (test,)) michael@0: for test in SHUFFLED_ACTIVE_TESTS: michael@0: self.assertEqual(1, SHUFFLED_ACTIVE_TESTS.count(test), michael@0: '%s appears more than once' % (test,)) michael@0: for test in SHUFFLED_FILTERED_TESTS: michael@0: self.assertEqual(1, SHUFFLED_FILTERED_TESTS.count(test), michael@0: '%s appears more than once' % (test,)) michael@0: for test in SHUFFLED_SHARDED_TESTS: michael@0: self.assertEqual(1, SHUFFLED_SHARDED_TESTS.count(test), michael@0: '%s appears more than once' % (test,)) michael@0: michael@0: def testShuffleDoesNotCreateNewTest(self): michael@0: for test in SHUFFLED_ALL_TESTS: michael@0: self.assert_(test in ALL_TESTS, '%s is an invalid test' % (test,)) michael@0: for test in SHUFFLED_ACTIVE_TESTS: michael@0: self.assert_(test in ACTIVE_TESTS, '%s is an invalid test' % (test,)) michael@0: for test in SHUFFLED_FILTERED_TESTS: michael@0: self.assert_(test in FILTERED_TESTS, '%s is an invalid test' % (test,)) michael@0: for test in SHUFFLED_SHARDED_TESTS: michael@0: self.assert_(test in SHARDED_TESTS, '%s is an invalid test' % (test,)) michael@0: michael@0: def testShuffleIncludesAllTests(self): michael@0: for test in ALL_TESTS: michael@0: self.assert_(test in SHUFFLED_ALL_TESTS, '%s is missing' % (test,)) michael@0: for test in ACTIVE_TESTS: michael@0: self.assert_(test in SHUFFLED_ACTIVE_TESTS, '%s is missing' % (test,)) michael@0: for test in FILTERED_TESTS: michael@0: self.assert_(test in SHUFFLED_FILTERED_TESTS, '%s is missing' % (test,)) michael@0: for test in SHARDED_TESTS: michael@0: self.assert_(test in SHUFFLED_SHARDED_TESTS, '%s is missing' % (test,)) michael@0: michael@0: def testShuffleLeavesDeathTestsAtFront(self): michael@0: non_death_test_found = False michael@0: for test in SHUFFLED_ACTIVE_TESTS: michael@0: if 'DeathTest.' in test: michael@0: self.assert_(not non_death_test_found, michael@0: '%s appears after a non-death test' % (test,)) michael@0: else: michael@0: non_death_test_found = True michael@0: michael@0: def _VerifyTestCasesDoNotInterleave(self, tests): michael@0: test_cases = [] michael@0: for test in tests: michael@0: [test_case, _] = test.split('.') michael@0: if test_cases and test_cases[-1] != test_case: michael@0: test_cases.append(test_case) michael@0: self.assertEqual(1, test_cases.count(test_case), michael@0: 'Test case %s is not grouped together in %s' % michael@0: (test_case, tests)) michael@0: michael@0: def testShuffleDoesNotInterleaveTestCases(self): michael@0: self._VerifyTestCasesDoNotInterleave(SHUFFLED_ALL_TESTS) michael@0: self._VerifyTestCasesDoNotInterleave(SHUFFLED_ACTIVE_TESTS) michael@0: self._VerifyTestCasesDoNotInterleave(SHUFFLED_FILTERED_TESTS) michael@0: self._VerifyTestCasesDoNotInterleave(SHUFFLED_SHARDED_TESTS) michael@0: michael@0: def testShuffleRestoresOrderAfterEachIteration(self): michael@0: # Get the test lists in all 3 iterations, using random seed 1, 2, michael@0: # and 3 respectively. Google Test picks a different seed in each michael@0: # iteration, and this test depends on the current implementation michael@0: # picking successive numbers. This dependency is not ideal, but michael@0: # makes the test much easier to write. michael@0: [tests_in_iteration1, tests_in_iteration2, tests_in_iteration3] = ( michael@0: GetTestsForAllIterations( michael@0: {}, [ShuffleFlag(), RandomSeedFlag(1), RepeatFlag(3)])) michael@0: michael@0: # Make sure running the tests with random seed 1 gets the same michael@0: # order as in iteration 1 above. michael@0: [tests_with_seed1] = GetTestsForAllIterations( michael@0: {}, [ShuffleFlag(), RandomSeedFlag(1)]) michael@0: self.assertEqual(tests_in_iteration1, tests_with_seed1) michael@0: michael@0: # Make sure running the tests with random seed 2 gets the same michael@0: # order as in iteration 2 above. Success means that Google Test michael@0: # correctly restores the test order before re-shuffling at the michael@0: # beginning of iteration 2. michael@0: [tests_with_seed2] = GetTestsForAllIterations( michael@0: {}, [ShuffleFlag(), RandomSeedFlag(2)]) michael@0: self.assertEqual(tests_in_iteration2, tests_with_seed2) michael@0: michael@0: # Make sure running the tests with random seed 3 gets the same michael@0: # order as in iteration 3 above. Success means that Google Test michael@0: # correctly restores the test order before re-shuffling at the michael@0: # beginning of iteration 3. michael@0: [tests_with_seed3] = GetTestsForAllIterations( michael@0: {}, [ShuffleFlag(), RandomSeedFlag(3)]) michael@0: self.assertEqual(tests_in_iteration3, tests_with_seed3) michael@0: michael@0: def testShuffleGeneratesNewOrderInEachIteration(self): michael@0: [tests_in_iteration1, tests_in_iteration2, tests_in_iteration3] = ( michael@0: GetTestsForAllIterations( michael@0: {}, [ShuffleFlag(), RandomSeedFlag(1), RepeatFlag(3)])) michael@0: michael@0: self.assert_(tests_in_iteration1 != tests_in_iteration2, michael@0: tests_in_iteration1) michael@0: self.assert_(tests_in_iteration1 != tests_in_iteration3, michael@0: tests_in_iteration1) michael@0: self.assert_(tests_in_iteration2 != tests_in_iteration3, michael@0: tests_in_iteration2) michael@0: michael@0: def testShuffleShardedTestsPreservesPartition(self): michael@0: # If we run M tests on N shards, the same M tests should be run in michael@0: # total, regardless of the random seeds used by the shards. michael@0: [tests1] = GetTestsForAllIterations({TOTAL_SHARDS_ENV_VAR: '3', michael@0: SHARD_INDEX_ENV_VAR: '0'}, michael@0: [ShuffleFlag(), RandomSeedFlag(1)]) michael@0: [tests2] = GetTestsForAllIterations({TOTAL_SHARDS_ENV_VAR: '3', michael@0: SHARD_INDEX_ENV_VAR: '1'}, michael@0: [ShuffleFlag(), RandomSeedFlag(20)]) michael@0: [tests3] = GetTestsForAllIterations({TOTAL_SHARDS_ENV_VAR: '3', michael@0: SHARD_INDEX_ENV_VAR: '2'}, michael@0: [ShuffleFlag(), RandomSeedFlag(25)]) michael@0: sorted_sharded_tests = tests1 + tests2 + tests3 michael@0: sorted_sharded_tests.sort() michael@0: sorted_active_tests = [] michael@0: sorted_active_tests.extend(ACTIVE_TESTS) michael@0: sorted_active_tests.sort() michael@0: self.assertEqual(sorted_active_tests, sorted_sharded_tests) michael@0: michael@0: if __name__ == '__main__': michael@0: gtest_test_utils.Main()