text: #!/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.
  
  """Verifies that test shuffling works."""
  
  __author__ = 'wan@google.com (Zhanyong Wan)'
  
  import os
  import gtest_test_utils
  
  # Command to run the gtest_shuffle_test_ program.
  COMMAND = gtest_test_utils.GetTestExecutablePath('gtest_shuffle_test_')
  
  # The environment variables for test sharding.
  TOTAL_SHARDS_ENV_VAR = 'GTEST_TOTAL_SHARDS'
  SHARD_INDEX_ENV_VAR = 'GTEST_SHARD_INDEX'
  
  TEST_FILTER = 'A*.A:A*.B:C*'
  
  ALL_TESTS = []
  ACTIVE_TESTS = []
  FILTERED_TESTS = []
  SHARDED_TESTS = []
  
  SHUFFLED_ALL_TESTS = []
  SHUFFLED_ACTIVE_TESTS = []
  SHUFFLED_FILTERED_TESTS = []
  SHUFFLED_SHARDED_TESTS = []
  
  
  def AlsoRunDisabledTestsFlag():
    return '--gtest_also_run_disabled_tests'
  
  
  def FilterFlag(test_filter):
    return '--gtest_filter=%s' % (test_filter,)
  
  
  def RepeatFlag(n):
    return '--gtest_repeat=%s' % (n,)
  
  
  def ShuffleFlag():
    return '--gtest_shuffle'
  
  
  def RandomSeedFlag(n):
    return '--gtest_random_seed=%s' % (n,)
  
  
  def RunAndReturnOutput(extra_env, args):
    """Runs the test program and returns its output."""
  
    environ_copy = os.environ.copy()
    environ_copy.update(extra_env)
  
    return gtest_test_utils.Subprocess([COMMAND] + args, env=environ_copy).output
  
  
  def GetTestsForAllIterations(extra_env, args):
    """Runs the test program and returns a list of test lists.
  
    Args:
      extra_env: a map from environment variables to their values
      args: command line flags to pass to gtest_shuffle_test_
  
    Returns:
      A list where the i-th element is the list of tests run in the i-th
      test iteration.
    """
  
    test_iterations = []
    for line in RunAndReturnOutput(extra_env, args).split('\n'):
      if line.startswith('----'):
        tests = []
        test_iterations.append(tests)
      elif line.strip():
        tests.append(line.strip())  # 'TestCaseName.TestName'
  
    return test_iterations
  
  
  def GetTestCases(tests):
    """Returns a list of test cases in the given full test names.
  
    Args:
      tests: a list of full test names
  
    Returns:
      A list of test cases from 'tests', in their original order.
      Consecutive duplicates are removed.
    """
  
    test_cases = []
    for test in tests:
      test_case = test.split('.')[0]
      if not test_case in test_cases:
        test_cases.append(test_case)
  
    return test_cases
  
  
  def CalculateTestLists():
    """Calculates the list of tests run under different flags."""
  
    if not ALL_TESTS:
      ALL_TESTS.extend(
          GetTestsForAllIterations({}, [AlsoRunDisabledTestsFlag()])[0])
  
    if not ACTIVE_TESTS:
      ACTIVE_TESTS.extend(GetTestsForAllIterations({}, [])[0])
  
    if not FILTERED_TESTS:
      FILTERED_TESTS.extend(
          GetTestsForAllIterations({}, [FilterFlag(TEST_FILTER)])[0])
  
    if not SHARDED_TESTS:
      SHARDED_TESTS.extend(
          GetTestsForAllIterations({TOTAL_SHARDS_ENV_VAR: '3',
                                    SHARD_INDEX_ENV_VAR: '1'},
                                   [])[0])
  
    if not SHUFFLED_ALL_TESTS:
      SHUFFLED_ALL_TESTS.extend(GetTestsForAllIterations(
          {}, [AlsoRunDisabledTestsFlag(), ShuffleFlag(), RandomSeedFlag(1)])[0])
  
    if not SHUFFLED_ACTIVE_TESTS:
      SHUFFLED_ACTIVE_TESTS.extend(GetTestsForAllIterations(
          {}, [ShuffleFlag(), RandomSeedFlag(1)])[0])
  
    if not SHUFFLED_FILTERED_TESTS:
      SHUFFLED_FILTERED_TESTS.extend(GetTestsForAllIterations(
          {}, [ShuffleFlag(), RandomSeedFlag(1), FilterFlag(TEST_FILTER)])[0])
  
    if not SHUFFLED_SHARDED_TESTS:
      SHUFFLED_SHARDED_TESTS.extend(
          GetTestsForAllIterations({TOTAL_SHARDS_ENV_VAR: '3',
                                    SHARD_INDEX_ENV_VAR: '1'},
                                   [ShuffleFlag(), RandomSeedFlag(1)])[0])
  
  
  class GTestShuffleUnitTest(gtest_test_utils.TestCase):
    """Tests test shuffling."""
  
    def setUp(self):
      CalculateTestLists()
  
    def testShufflePreservesNumberOfTests(self):
      self.assertEqual(len(ALL_TESTS), len(SHUFFLED_ALL_TESTS))
      self.assertEqual(len(ACTIVE_TESTS), len(SHUFFLED_ACTIVE_TESTS))
      self.assertEqual(len(FILTERED_TESTS), len(SHUFFLED_FILTERED_TESTS))
      self.assertEqual(len(SHARDED_TESTS), len(SHUFFLED_SHARDED_TESTS))
  
    def testShuffleChangesTestOrder(self):
      self.assert_(SHUFFLED_ALL_TESTS != ALL_TESTS, SHUFFLED_ALL_TESTS)
      self.assert_(SHUFFLED_ACTIVE_TESTS != ACTIVE_TESTS, SHUFFLED_ACTIVE_TESTS)
      self.assert_(SHUFFLED_FILTERED_TESTS != FILTERED_TESTS,
                   SHUFFLED_FILTERED_TESTS)
      self.assert_(SHUFFLED_SHARDED_TESTS != SHARDED_TESTS,
                   SHUFFLED_SHARDED_TESTS)
  
    def testShuffleChangesTestCaseOrder(self):
      self.assert_(GetTestCases(SHUFFLED_ALL_TESTS) != GetTestCases(ALL_TESTS),
                   GetTestCases(SHUFFLED_ALL_TESTS))
      self.assert_(
          GetTestCases(SHUFFLED_ACTIVE_TESTS) != GetTestCases(ACTIVE_TESTS),
          GetTestCases(SHUFFLED_ACTIVE_TESTS))
      self.assert_(
          GetTestCases(SHUFFLED_FILTERED_TESTS) != GetTestCases(FILTERED_TESTS),
          GetTestCases(SHUFFLED_FILTERED_TESTS))
      self.assert_(
          GetTestCases(SHUFFLED_SHARDED_TESTS) != GetTestCases(SHARDED_TESTS),
          GetTestCases(SHUFFLED_SHARDED_TESTS))
  
    def testShuffleDoesNotRepeatTest(self):
      for test in SHUFFLED_ALL_TESTS:
        self.assertEqual(1, SHUFFLED_ALL_TESTS.count(test),
                         '%s appears more than once' % (test,))
      for test in SHUFFLED_ACTIVE_TESTS:
        self.assertEqual(1, SHUFFLED_ACTIVE_TESTS.count(test),
                         '%s appears more than once' % (test,))
      for test in SHUFFLED_FILTERED_TESTS:
        self.assertEqual(1, SHUFFLED_FILTERED_TESTS.count(test),
                         '%s appears more than once' % (test,))
      for test in SHUFFLED_SHARDED_TESTS:
        self.assertEqual(1, SHUFFLED_SHARDED_TESTS.count(test),
                         '%s appears more than once' % (test,))
  
    def testShuffleDoesNotCreateNewTest(self):
      for test in SHUFFLED_ALL_TESTS:
        self.assert_(test in ALL_TESTS, '%s is an invalid test' % (test,))
      for test in SHUFFLED_ACTIVE_TESTS:
        self.assert_(test in ACTIVE_TESTS, '%s is an invalid test' % (test,))
      for test in SHUFFLED_FILTERED_TESTS:
        self.assert_(test in FILTERED_TESTS, '%s is an invalid test' % (test,))
      for test in SHUFFLED_SHARDED_TESTS:
        self.assert_(test in SHARDED_TESTS, '%s is an invalid test' % (test,))
  
    def testShuffleIncludesAllTests(self):
      for test in ALL_TESTS:
        self.assert_(test in SHUFFLED_ALL_TESTS, '%s is missing' % (test,))
      for test in ACTIVE_TESTS:
        self.assert_(test in SHUFFLED_ACTIVE_TESTS, '%s is missing' % (test,))
      for test in FILTERED_TESTS:
        self.assert_(test in SHUFFLED_FILTERED_TESTS, '%s is missing' % (test,))
      for test in SHARDED_TESTS:
        self.assert_(test in SHUFFLED_SHARDED_TESTS, '%s is missing' % (test,))
  
    def testShuffleLeavesDeathTestsAtFront(self):
      non_death_test_found = False
      for test in SHUFFLED_ACTIVE_TESTS:
        if 'DeathTest.' in test:
          self.assert_(not non_death_test_found,
                       '%s appears after a non-death test' % (test,))
        else:
          non_death_test_found = True
  
    def _VerifyTestCasesDoNotInterleave(self, tests):
      test_cases = []
      for test in tests:
        [test_case, _] = test.split('.')
        if test_cases and test_cases[-1] != test_case:
          test_cases.append(test_case)
          self.assertEqual(1, test_cases.count(test_case),
                           'Test case %s is not grouped together in %s' %
                           (test_case, tests))
  
    def testShuffleDoesNotInterleaveTestCases(self):
      self._VerifyTestCasesDoNotInterleave(SHUFFLED_ALL_TESTS)
      self._VerifyTestCasesDoNotInterleave(SHUFFLED_ACTIVE_TESTS)
      self._VerifyTestCasesDoNotInterleave(SHUFFLED_FILTERED_TESTS)
      self._VerifyTestCasesDoNotInterleave(SHUFFLED_SHARDED_TESTS)
  
    def testShuffleRestoresOrderAfterEachIteration(self):
      # Get the test lists in all 3 iterations, using random seed 1, 2,
      # and 3 respectively.  Google Test picks a different seed in each
      # iteration, and this test depends on the current implementation
      # picking successive numbers.  This dependency is not ideal, but
      # makes the test much easier to write.
      [tests_in_iteration1, tests_in_iteration2, tests_in_iteration3] = (
          GetTestsForAllIterations(
              {}, [ShuffleFlag(), RandomSeedFlag(1), RepeatFlag(3)]))
  
      # Make sure running the tests with random seed 1 gets the same
      # order as in iteration 1 above.
      [tests_with_seed1] = GetTestsForAllIterations(
          {}, [ShuffleFlag(), RandomSeedFlag(1)])
      self.assertEqual(tests_in_iteration1, tests_with_seed1)
  
      # Make sure running the tests with random seed 2 gets the same
      # order as in iteration 2 above.  Success means that Google Test
      # correctly restores the test order before re-shuffling at the
      # beginning of iteration 2.
      [tests_with_seed2] = GetTestsForAllIterations(
          {}, [ShuffleFlag(), RandomSeedFlag(2)])
      self.assertEqual(tests_in_iteration2, tests_with_seed2)
  
      # Make sure running the tests with random seed 3 gets the same
      # order as in iteration 3 above.  Success means that Google Test
      # correctly restores the test order before re-shuffling at the
      # beginning of iteration 3.
      [tests_with_seed3] = GetTestsForAllIterations(
          {}, [ShuffleFlag(), RandomSeedFlag(3)])
      self.assertEqual(tests_in_iteration3, tests_with_seed3)
  
    def testShuffleGeneratesNewOrderInEachIteration(self):
      [tests_in_iteration1, tests_in_iteration2, tests_in_iteration3] = (
          GetTestsForAllIterations(
              {}, [ShuffleFlag(), RandomSeedFlag(1), RepeatFlag(3)]))
  
      self.assert_(tests_in_iteration1 != tests_in_iteration2,
                   tests_in_iteration1)
      self.assert_(tests_in_iteration1 != tests_in_iteration3,
                   tests_in_iteration1)
      self.assert_(tests_in_iteration2 != tests_in_iteration3,
                   tests_in_iteration2)
  
    def testShuffleShardedTestsPreservesPartition(self):
      # If we run M tests on N shards, the same M tests should be run in
      # total, regardless of the random seeds used by the shards.
      [tests1] = GetTestsForAllIterations({TOTAL_SHARDS_ENV_VAR: '3',
                                           SHARD_INDEX_ENV_VAR: '0'},
                                          [ShuffleFlag(), RandomSeedFlag(1)])
      [tests2] = GetTestsForAllIterations({TOTAL_SHARDS_ENV_VAR: '3',
                                           SHARD_INDEX_ENV_VAR: '1'},
                                          [ShuffleFlag(), RandomSeedFlag(20)])
      [tests3] = GetTestsForAllIterations({TOTAL_SHARDS_ENV_VAR: '3',
                                           SHARD_INDEX_ENV_VAR: '2'},
                                          [ShuffleFlag(), RandomSeedFlag(25)])
      sorted_sharded_tests = tests1 + tests2 + tests3
      sorted_sharded_tests.sort()
      sorted_active_tests = []
      sorted_active_tests.extend(ACTIVE_TESTS)
      sorted_active_tests.sort()
      self.assertEqual(sorted_active_tests, sorted_sharded_tests)
  
  if __name__ == '__main__':
    gtest_test_utils.Main()
  