1#!/usr/bin/env python
2#
3# Copyright 2009 Google Inc. All Rights Reserved.
4#
5# Redistribution and use in source and binary forms, with or without
6# modification, are permitted provided that the following conditions are
7# met:
8#
9#     * Redistributions of source code must retain the above copyright
10# notice, this list of conditions and the following disclaimer.
11#     * Redistributions in binary form must reproduce the above
12# copyright notice, this list of conditions and the following disclaimer
13# in the documentation and/or other materials provided with the
14# distribution.
15#     * Neither the name of Google Inc. nor the names of its
16# contributors may be used to endorse or promote products derived from
17# this software without specific prior written permission.
18#
19# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30
31"""Verifies that test shuffling works."""
32
33__author__ = 'wan@google.com (Zhanyong Wan)'
34
35import os
36import gtest_test_utils
37
38# Command to run the gtest_shuffle_test_ program.
39COMMAND = gtest_test_utils.GetTestExecutablePath('gtest_shuffle_test_')
40
41# The environment variables for test sharding.
42TOTAL_SHARDS_ENV_VAR = 'GTEST_TOTAL_SHARDS'
43SHARD_INDEX_ENV_VAR = 'GTEST_SHARD_INDEX'
44
45TEST_FILTER = 'A*.A:A*.B:C*'
46
47ALL_TESTS = []
48ACTIVE_TESTS = []
49FILTERED_TESTS = []
50SHARDED_TESTS = []
51
52SHUFFLED_ALL_TESTS = []
53SHUFFLED_ACTIVE_TESTS = []
54SHUFFLED_FILTERED_TESTS = []
55SHUFFLED_SHARDED_TESTS = []
56
57
58def AlsoRunDisabledTestsFlag():
59  return '--gtest_also_run_disabled_tests'
60
61
62def FilterFlag(test_filter):
63  return '--gtest_filter=%s' % (test_filter,)
64
65
66def RepeatFlag(n):
67  return '--gtest_repeat=%s' % (n,)
68
69
70def ShuffleFlag():
71  return '--gtest_shuffle'
72
73
74def RandomSeedFlag(n):
75  return '--gtest_random_seed=%s' % (n,)
76
77
78def RunAndReturnOutput(extra_env, args):
79  """Runs the test program and returns its output."""
80
81  environ_copy = os.environ.copy()
82  environ_copy.update(extra_env)
83
84  return gtest_test_utils.Subprocess([COMMAND] + args, env=environ_copy,
85                                     capture_stderr=False).output
86
87
88def GetTestsForAllIterations(extra_env, args):
89  """Runs the test program and returns a list of test lists.
90
91  Args:
92    extra_env: a map from environment variables to their values
93    args: command line flags to pass to gtest_shuffle_test_
94
95  Returns:
96    A list where the i-th element is the list of tests run in the i-th
97    test iteration.
98  """
99
100  test_iterations = []
101  for line in RunAndReturnOutput(extra_env, args).split('\n'):
102    if line.startswith('----'):
103      tests = []
104      test_iterations.append(tests)
105    elif line.strip():
106      tests.append(line.strip())  # 'TestCaseName.TestName'
107
108  return test_iterations
109
110
111def GetTestCases(tests):
112  """Returns a list of test cases in the given full test names.
113
114  Args:
115    tests: a list of full test names
116
117  Returns:
118    A list of test cases from 'tests', in their original order.
119    Consecutive duplicates are removed.
120  """
121
122  test_cases = []
123  for test in tests:
124    test_case = test.split('.')[0]
125    if not test_case in test_cases:
126      test_cases.append(test_case)
127
128  return test_cases
129
130
131def CalculateTestLists():
132  """Calculates the list of tests run under different flags."""
133
134  if not ALL_TESTS:
135    ALL_TESTS.extend(
136        GetTestsForAllIterations({}, [AlsoRunDisabledTestsFlag()])[0])
137
138  if not ACTIVE_TESTS:
139    ACTIVE_TESTS.extend(GetTestsForAllIterations({}, [])[0])
140
141  if not FILTERED_TESTS:
142    FILTERED_TESTS.extend(
143        GetTestsForAllIterations({}, [FilterFlag(TEST_FILTER)])[0])
144
145  if not SHARDED_TESTS:
146    SHARDED_TESTS.extend(
147        GetTestsForAllIterations({TOTAL_SHARDS_ENV_VAR: '3',
148                                  SHARD_INDEX_ENV_VAR: '1'},
149                                 [])[0])
150
151  if not SHUFFLED_ALL_TESTS:
152    SHUFFLED_ALL_TESTS.extend(GetTestsForAllIterations(
153        {}, [AlsoRunDisabledTestsFlag(), ShuffleFlag(), RandomSeedFlag(1)])[0])
154
155  if not SHUFFLED_ACTIVE_TESTS:
156    SHUFFLED_ACTIVE_TESTS.extend(GetTestsForAllIterations(
157        {}, [ShuffleFlag(), RandomSeedFlag(1)])[0])
158
159  if not SHUFFLED_FILTERED_TESTS:
160    SHUFFLED_FILTERED_TESTS.extend(GetTestsForAllIterations(
161        {}, [ShuffleFlag(), RandomSeedFlag(1), FilterFlag(TEST_FILTER)])[0])
162
163  if not SHUFFLED_SHARDED_TESTS:
164    SHUFFLED_SHARDED_TESTS.extend(
165        GetTestsForAllIterations({TOTAL_SHARDS_ENV_VAR: '3',
166                                  SHARD_INDEX_ENV_VAR: '1'},
167                                 [ShuffleFlag(), RandomSeedFlag(1)])[0])
168
169
170class GTestShuffleUnitTest(gtest_test_utils.TestCase):
171  """Tests test shuffling."""
172
173  def setUp(self):
174    CalculateTestLists()
175
176  def testShufflePreservesNumberOfTests(self):
177    self.assertEqual(len(ALL_TESTS), len(SHUFFLED_ALL_TESTS))
178    self.assertEqual(len(ACTIVE_TESTS), len(SHUFFLED_ACTIVE_TESTS))
179    self.assertEqual(len(FILTERED_TESTS), len(SHUFFLED_FILTERED_TESTS))
180    self.assertEqual(len(SHARDED_TESTS), len(SHUFFLED_SHARDED_TESTS))
181
182  def testShuffleChangesTestOrder(self):
183    self.assert_(SHUFFLED_ALL_TESTS != ALL_TESTS, SHUFFLED_ALL_TESTS)
184    self.assert_(SHUFFLED_ACTIVE_TESTS != ACTIVE_TESTS, SHUFFLED_ACTIVE_TESTS)
185    self.assert_(SHUFFLED_FILTERED_TESTS != FILTERED_TESTS,
186                 SHUFFLED_FILTERED_TESTS)
187    self.assert_(SHUFFLED_SHARDED_TESTS != SHARDED_TESTS,
188                 SHUFFLED_SHARDED_TESTS)
189
190  def testShuffleChangesTestCaseOrder(self):
191    self.assert_(GetTestCases(SHUFFLED_ALL_TESTS) != GetTestCases(ALL_TESTS),
192                 GetTestCases(SHUFFLED_ALL_TESTS))
193    self.assert_(
194        GetTestCases(SHUFFLED_ACTIVE_TESTS) != GetTestCases(ACTIVE_TESTS),
195        GetTestCases(SHUFFLED_ACTIVE_TESTS))
196    self.assert_(
197        GetTestCases(SHUFFLED_FILTERED_TESTS) != GetTestCases(FILTERED_TESTS),
198        GetTestCases(SHUFFLED_FILTERED_TESTS))
199    self.assert_(
200        GetTestCases(SHUFFLED_SHARDED_TESTS) != GetTestCases(SHARDED_TESTS),
201        GetTestCases(SHUFFLED_SHARDED_TESTS))
202
203  def testShuffleDoesNotRepeatTest(self):
204    for test in SHUFFLED_ALL_TESTS:
205      self.assertEqual(1, SHUFFLED_ALL_TESTS.count(test),
206                       '%s appears more than once' % (test,))
207    for test in SHUFFLED_ACTIVE_TESTS:
208      self.assertEqual(1, SHUFFLED_ACTIVE_TESTS.count(test),
209                       '%s appears more than once' % (test,))
210    for test in SHUFFLED_FILTERED_TESTS:
211      self.assertEqual(1, SHUFFLED_FILTERED_TESTS.count(test),
212                       '%s appears more than once' % (test,))
213    for test in SHUFFLED_SHARDED_TESTS:
214      self.assertEqual(1, SHUFFLED_SHARDED_TESTS.count(test),
215                       '%s appears more than once' % (test,))
216
217  def testShuffleDoesNotCreateNewTest(self):
218    for test in SHUFFLED_ALL_TESTS:
219      self.assert_(test in ALL_TESTS, '%s is an invalid test' % (test,))
220    for test in SHUFFLED_ACTIVE_TESTS:
221      self.assert_(test in ACTIVE_TESTS, '%s is an invalid test' % (test,))
222    for test in SHUFFLED_FILTERED_TESTS:
223      self.assert_(test in FILTERED_TESTS, '%s is an invalid test' % (test,))
224    for test in SHUFFLED_SHARDED_TESTS:
225      self.assert_(test in SHARDED_TESTS, '%s is an invalid test' % (test,))
226
227  def testShuffleIncludesAllTests(self):
228    for test in ALL_TESTS:
229      self.assert_(test in SHUFFLED_ALL_TESTS, '%s is missing' % (test,))
230    for test in ACTIVE_TESTS:
231      self.assert_(test in SHUFFLED_ACTIVE_TESTS, '%s is missing' % (test,))
232    for test in FILTERED_TESTS:
233      self.assert_(test in SHUFFLED_FILTERED_TESTS, '%s is missing' % (test,))
234    for test in SHARDED_TESTS:
235      self.assert_(test in SHUFFLED_SHARDED_TESTS, '%s is missing' % (test,))
236
237  def testShuffleLeavesDeathTestsAtFront(self):
238    non_death_test_found = False
239    for test in SHUFFLED_ACTIVE_TESTS:
240      if 'DeathTest.' in test:
241        self.assert_(not non_death_test_found,
242                     '%s appears after a non-death test' % (test,))
243      else:
244        non_death_test_found = True
245
246  def _VerifyTestCasesDoNotInterleave(self, tests):
247    test_cases = []
248    for test in tests:
249      [test_case, _] = test.split('.')
250      if test_cases and test_cases[-1] != test_case:
251        test_cases.append(test_case)
252        self.assertEqual(1, test_cases.count(test_case),
253                         'Test case %s is not grouped together in %s' %
254                         (test_case, tests))
255
256  def testShuffleDoesNotInterleaveTestCases(self):
257    self._VerifyTestCasesDoNotInterleave(SHUFFLED_ALL_TESTS)
258    self._VerifyTestCasesDoNotInterleave(SHUFFLED_ACTIVE_TESTS)
259    self._VerifyTestCasesDoNotInterleave(SHUFFLED_FILTERED_TESTS)
260    self._VerifyTestCasesDoNotInterleave(SHUFFLED_SHARDED_TESTS)
261
262  def testShuffleRestoresOrderAfterEachIteration(self):
263    # Get the test lists in all 3 iterations, using random seed 1, 2,
264    # and 3 respectively.  Google Test picks a different seed in each
265    # iteration, and this test depends on the current implementation
266    # picking successive numbers.  This dependency is not ideal, but
267    # makes the test much easier to write.
268    [tests_in_iteration1, tests_in_iteration2, tests_in_iteration3] = (
269        GetTestsForAllIterations(
270            {}, [ShuffleFlag(), RandomSeedFlag(1), RepeatFlag(3)]))
271
272    # Make sure running the tests with random seed 1 gets the same
273    # order as in iteration 1 above.
274    [tests_with_seed1] = GetTestsForAllIterations(
275        {}, [ShuffleFlag(), RandomSeedFlag(1)])
276    self.assertEqual(tests_in_iteration1, tests_with_seed1)
277
278    # Make sure running the tests with random seed 2 gets the same
279    # order as in iteration 2 above.  Success means that Google Test
280    # correctly restores the test order before re-shuffling at the
281    # beginning of iteration 2.
282    [tests_with_seed2] = GetTestsForAllIterations(
283        {}, [ShuffleFlag(), RandomSeedFlag(2)])
284    self.assertEqual(tests_in_iteration2, tests_with_seed2)
285
286    # Make sure running the tests with random seed 3 gets the same
287    # order as in iteration 3 above.  Success means that Google Test
288    # correctly restores the test order before re-shuffling at the
289    # beginning of iteration 3.
290    [tests_with_seed3] = GetTestsForAllIterations(
291        {}, [ShuffleFlag(), RandomSeedFlag(3)])
292    self.assertEqual(tests_in_iteration3, tests_with_seed3)
293
294  def testShuffleGeneratesNewOrderInEachIteration(self):
295    [tests_in_iteration1, tests_in_iteration2, tests_in_iteration3] = (
296        GetTestsForAllIterations(
297            {}, [ShuffleFlag(), RandomSeedFlag(1), RepeatFlag(3)]))
298
299    self.assert_(tests_in_iteration1 != tests_in_iteration2,
300                 tests_in_iteration1)
301    self.assert_(tests_in_iteration1 != tests_in_iteration3,
302                 tests_in_iteration1)
303    self.assert_(tests_in_iteration2 != tests_in_iteration3,
304                 tests_in_iteration2)
305
306  def testShuffleShardedTestsPreservesPartition(self):
307    # If we run M tests on N shards, the same M tests should be run in
308    # total, regardless of the random seeds used by the shards.
309    [tests1] = GetTestsForAllIterations({TOTAL_SHARDS_ENV_VAR: '3',
310                                         SHARD_INDEX_ENV_VAR: '0'},
311                                        [ShuffleFlag(), RandomSeedFlag(1)])
312    [tests2] = GetTestsForAllIterations({TOTAL_SHARDS_ENV_VAR: '3',
313                                         SHARD_INDEX_ENV_VAR: '1'},
314                                        [ShuffleFlag(), RandomSeedFlag(20)])
315    [tests3] = GetTestsForAllIterations({TOTAL_SHARDS_ENV_VAR: '3',
316                                         SHARD_INDEX_ENV_VAR: '2'},
317                                        [ShuffleFlag(), RandomSeedFlag(25)])
318    sorted_sharded_tests = tests1 + tests2 + tests3
319    sorted_sharded_tests.sort()
320    sorted_active_tests = []
321    sorted_active_tests.extend(ACTIVE_TESTS)
322    sorted_active_tests.sort()
323    self.assertEqual(sorted_active_tests, sorted_sharded_tests)
324
325if __name__ == '__main__':
326  gtest_test_utils.Main()
327