1 |
2 |
jamieiles |
#!/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 |
|
|
|
35 |
|
|
import os
|
36 |
|
|
import gtest_test_utils
|
37 |
|
|
|
38 |
|
|
# Command to run the gtest_shuffle_test_ program.
|
39 |
|
|
COMMAND = gtest_test_utils.GetTestExecutablePath('gtest_shuffle_test_')
|
40 |
|
|
|
41 |
|
|
# The environment variables for test sharding.
|
42 |
|
|
TOTAL_SHARDS_ENV_VAR = 'GTEST_TOTAL_SHARDS'
|
43 |
|
|
SHARD_INDEX_ENV_VAR = 'GTEST_SHARD_INDEX'
|
44 |
|
|
|
45 |
|
|
TEST_FILTER = 'A*.A:A*.B:C*'
|
46 |
|
|
|
47 |
|
|
ALL_TESTS = []
|
48 |
|
|
ACTIVE_TESTS = []
|
49 |
|
|
FILTERED_TESTS = []
|
50 |
|
|
SHARDED_TESTS = []
|
51 |
|
|
|
52 |
|
|
SHUFFLED_ALL_TESTS = []
|
53 |
|
|
SHUFFLED_ACTIVE_TESTS = []
|
54 |
|
|
SHUFFLED_FILTERED_TESTS = []
|
55 |
|
|
SHUFFLED_SHARDED_TESTS = []
|
56 |
|
|
|
57 |
|
|
|
58 |
|
|
def AlsoRunDisabledTestsFlag():
|
59 |
|
|
return '--gtest_also_run_disabled_tests'
|
60 |
|
|
|
61 |
|
|
|
62 |
|
|
def FilterFlag(test_filter):
|
63 |
|
|
return '--gtest_filter=%s' % (test_filter,)
|
64 |
|
|
|
65 |
|
|
|
66 |
|
|
def RepeatFlag(n):
|
67 |
|
|
return '--gtest_repeat=%s' % (n,)
|
68 |
|
|
|
69 |
|
|
|
70 |
|
|
def ShuffleFlag():
|
71 |
|
|
return '--gtest_shuffle'
|
72 |
|
|
|
73 |
|
|
|
74 |
|
|
def RandomSeedFlag(n):
|
75 |
|
|
return '--gtest_random_seed=%s' % (n,)
|
76 |
|
|
|
77 |
|
|
|
78 |
|
|
def 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).output
|
85 |
|
|
|
86 |
|
|
|
87 |
|
|
def GetTestsForAllIterations(extra_env, args):
|
88 |
|
|
"""Runs the test program and returns a list of test lists.
|
89 |
|
|
|
90 |
|
|
Args:
|
91 |
|
|
extra_env: a map from environment variables to their values
|
92 |
|
|
args: command line flags to pass to gtest_shuffle_test_
|
93 |
|
|
|
94 |
|
|
Returns:
|
95 |
|
|
A list where the i-th element is the list of tests run in the i-th
|
96 |
|
|
test iteration.
|
97 |
|
|
"""
|
98 |
|
|
|
99 |
|
|
test_iterations = []
|
100 |
|
|
for line in RunAndReturnOutput(extra_env, args).split('\n'):
|
101 |
|
|
if line.startswith('----'):
|
102 |
|
|
tests = []
|
103 |
|
|
test_iterations.append(tests)
|
104 |
|
|
elif line.strip():
|
105 |
|
|
tests.append(line.strip()) # 'TestCaseName.TestName'
|
106 |
|
|
|
107 |
|
|
return test_iterations
|
108 |
|
|
|
109 |
|
|
|
110 |
|
|
def GetTestCases(tests):
|
111 |
|
|
"""Returns a list of test cases in the given full test names.
|
112 |
|
|
|
113 |
|
|
Args:
|
114 |
|
|
tests: a list of full test names
|
115 |
|
|
|
116 |
|
|
Returns:
|
117 |
|
|
A list of test cases from 'tests', in their original order.
|
118 |
|
|
Consecutive duplicates are removed.
|
119 |
|
|
"""
|
120 |
|
|
|
121 |
|
|
test_cases = []
|
122 |
|
|
for test in tests:
|
123 |
|
|
test_case = test.split('.')[0]
|
124 |
|
|
if not test_case in test_cases:
|
125 |
|
|
test_cases.append(test_case)
|
126 |
|
|
|
127 |
|
|
return test_cases
|
128 |
|
|
|
129 |
|
|
|
130 |
|
|
def CalculateTestLists():
|
131 |
|
|
"""Calculates the list of tests run under different flags."""
|
132 |
|
|
|
133 |
|
|
if not ALL_TESTS:
|
134 |
|
|
ALL_TESTS.extend(
|
135 |
|
|
GetTestsForAllIterations({}, [AlsoRunDisabledTestsFlag()])[0])
|
136 |
|
|
|
137 |
|
|
if not ACTIVE_TESTS:
|
138 |
|
|
ACTIVE_TESTS.extend(GetTestsForAllIterations({}, [])[0])
|
139 |
|
|
|
140 |
|
|
if not FILTERED_TESTS:
|
141 |
|
|
FILTERED_TESTS.extend(
|
142 |
|
|
GetTestsForAllIterations({}, [FilterFlag(TEST_FILTER)])[0])
|
143 |
|
|
|
144 |
|
|
if not SHARDED_TESTS:
|
145 |
|
|
SHARDED_TESTS.extend(
|
146 |
|
|
GetTestsForAllIterations({TOTAL_SHARDS_ENV_VAR: '3',
|
147 |
|
|
SHARD_INDEX_ENV_VAR: '1'},
|
148 |
|
|
[])[0])
|
149 |
|
|
|
150 |
|
|
if not SHUFFLED_ALL_TESTS:
|
151 |
|
|
SHUFFLED_ALL_TESTS.extend(GetTestsForAllIterations(
|
152 |
|
|
{}, [AlsoRunDisabledTestsFlag(), ShuffleFlag(), RandomSeedFlag(1)])[0])
|
153 |
|
|
|
154 |
|
|
if not SHUFFLED_ACTIVE_TESTS:
|
155 |
|
|
SHUFFLED_ACTIVE_TESTS.extend(GetTestsForAllIterations(
|
156 |
|
|
{}, [ShuffleFlag(), RandomSeedFlag(1)])[0])
|
157 |
|
|
|
158 |
|
|
if not SHUFFLED_FILTERED_TESTS:
|
159 |
|
|
SHUFFLED_FILTERED_TESTS.extend(GetTestsForAllIterations(
|
160 |
|
|
{}, [ShuffleFlag(), RandomSeedFlag(1), FilterFlag(TEST_FILTER)])[0])
|
161 |
|
|
|
162 |
|
|
if not SHUFFLED_SHARDED_TESTS:
|
163 |
|
|
SHUFFLED_SHARDED_TESTS.extend(
|
164 |
|
|
GetTestsForAllIterations({TOTAL_SHARDS_ENV_VAR: '3',
|
165 |
|
|
SHARD_INDEX_ENV_VAR: '1'},
|
166 |
|
|
[ShuffleFlag(), RandomSeedFlag(1)])[0])
|
167 |
|
|
|
168 |
|
|
|
169 |
|
|
class GTestShuffleUnitTest(gtest_test_utils.TestCase):
|
170 |
|
|
"""Tests test shuffling."""
|
171 |
|
|
|
172 |
|
|
def setUp(self):
|
173 |
|
|
CalculateTestLists()
|
174 |
|
|
|
175 |
|
|
def testShufflePreservesNumberOfTests(self):
|
176 |
|
|
self.assertEqual(len(ALL_TESTS), len(SHUFFLED_ALL_TESTS))
|
177 |
|
|
self.assertEqual(len(ACTIVE_TESTS), len(SHUFFLED_ACTIVE_TESTS))
|
178 |
|
|
self.assertEqual(len(FILTERED_TESTS), len(SHUFFLED_FILTERED_TESTS))
|
179 |
|
|
self.assertEqual(len(SHARDED_TESTS), len(SHUFFLED_SHARDED_TESTS))
|
180 |
|
|
|
181 |
|
|
def testShuffleChangesTestOrder(self):
|
182 |
|
|
self.assert_(SHUFFLED_ALL_TESTS != ALL_TESTS, SHUFFLED_ALL_TESTS)
|
183 |
|
|
self.assert_(SHUFFLED_ACTIVE_TESTS != ACTIVE_TESTS, SHUFFLED_ACTIVE_TESTS)
|
184 |
|
|
self.assert_(SHUFFLED_FILTERED_TESTS != FILTERED_TESTS,
|
185 |
|
|
SHUFFLED_FILTERED_TESTS)
|
186 |
|
|
self.assert_(SHUFFLED_SHARDED_TESTS != SHARDED_TESTS,
|
187 |
|
|
SHUFFLED_SHARDED_TESTS)
|
188 |
|
|
|
189 |
|
|
def testShuffleChangesTestCaseOrder(self):
|
190 |
|
|
self.assert_(GetTestCases(SHUFFLED_ALL_TESTS) != GetTestCases(ALL_TESTS),
|
191 |
|
|
GetTestCases(SHUFFLED_ALL_TESTS))
|
192 |
|
|
self.assert_(
|
193 |
|
|
GetTestCases(SHUFFLED_ACTIVE_TESTS) != GetTestCases(ACTIVE_TESTS),
|
194 |
|
|
GetTestCases(SHUFFLED_ACTIVE_TESTS))
|
195 |
|
|
self.assert_(
|
196 |
|
|
GetTestCases(SHUFFLED_FILTERED_TESTS) != GetTestCases(FILTERED_TESTS),
|
197 |
|
|
GetTestCases(SHUFFLED_FILTERED_TESTS))
|
198 |
|
|
self.assert_(
|
199 |
|
|
GetTestCases(SHUFFLED_SHARDED_TESTS) != GetTestCases(SHARDED_TESTS),
|
200 |
|
|
GetTestCases(SHUFFLED_SHARDED_TESTS))
|
201 |
|
|
|
202 |
|
|
def testShuffleDoesNotRepeatTest(self):
|
203 |
|
|
for test in SHUFFLED_ALL_TESTS:
|
204 |
|
|
self.assertEqual(1, SHUFFLED_ALL_TESTS.count(test),
|
205 |
|
|
'%s appears more than once' % (test,))
|
206 |
|
|
for test in SHUFFLED_ACTIVE_TESTS:
|
207 |
|
|
self.assertEqual(1, SHUFFLED_ACTIVE_TESTS.count(test),
|
208 |
|
|
'%s appears more than once' % (test,))
|
209 |
|
|
for test in SHUFFLED_FILTERED_TESTS:
|
210 |
|
|
self.assertEqual(1, SHUFFLED_FILTERED_TESTS.count(test),
|
211 |
|
|
'%s appears more than once' % (test,))
|
212 |
|
|
for test in SHUFFLED_SHARDED_TESTS:
|
213 |
|
|
self.assertEqual(1, SHUFFLED_SHARDED_TESTS.count(test),
|
214 |
|
|
'%s appears more than once' % (test,))
|
215 |
|
|
|
216 |
|
|
def testShuffleDoesNotCreateNewTest(self):
|
217 |
|
|
for test in SHUFFLED_ALL_TESTS:
|
218 |
|
|
self.assert_(test in ALL_TESTS, '%s is an invalid test' % (test,))
|
219 |
|
|
for test in SHUFFLED_ACTIVE_TESTS:
|
220 |
|
|
self.assert_(test in ACTIVE_TESTS, '%s is an invalid test' % (test,))
|
221 |
|
|
for test in SHUFFLED_FILTERED_TESTS:
|
222 |
|
|
self.assert_(test in FILTERED_TESTS, '%s is an invalid test' % (test,))
|
223 |
|
|
for test in SHUFFLED_SHARDED_TESTS:
|
224 |
|
|
self.assert_(test in SHARDED_TESTS, '%s is an invalid test' % (test,))
|
225 |
|
|
|
226 |
|
|
def testShuffleIncludesAllTests(self):
|
227 |
|
|
for test in ALL_TESTS:
|
228 |
|
|
self.assert_(test in SHUFFLED_ALL_TESTS, '%s is missing' % (test,))
|
229 |
|
|
for test in ACTIVE_TESTS:
|
230 |
|
|
self.assert_(test in SHUFFLED_ACTIVE_TESTS, '%s is missing' % (test,))
|
231 |
|
|
for test in FILTERED_TESTS:
|
232 |
|
|
self.assert_(test in SHUFFLED_FILTERED_TESTS, '%s is missing' % (test,))
|
233 |
|
|
for test in SHARDED_TESTS:
|
234 |
|
|
self.assert_(test in SHUFFLED_SHARDED_TESTS, '%s is missing' % (test,))
|
235 |
|
|
|
236 |
|
|
def testShuffleLeavesDeathTestsAtFront(self):
|
237 |
|
|
non_death_test_found = False
|
238 |
|
|
for test in SHUFFLED_ACTIVE_TESTS:
|
239 |
|
|
if 'DeathTest.' in test:
|
240 |
|
|
self.assert_(not non_death_test_found,
|
241 |
|
|
'%s appears after a non-death test' % (test,))
|
242 |
|
|
else:
|
243 |
|
|
non_death_test_found = True
|
244 |
|
|
|
245 |
|
|
def _VerifyTestCasesDoNotInterleave(self, tests):
|
246 |
|
|
test_cases = []
|
247 |
|
|
for test in tests:
|
248 |
|
|
[test_case, _] = test.split('.')
|
249 |
|
|
if test_cases and test_cases[-1] != test_case:
|
250 |
|
|
test_cases.append(test_case)
|
251 |
|
|
self.assertEqual(1, test_cases.count(test_case),
|
252 |
|
|
'Test case %s is not grouped together in %s' %
|
253 |
|
|
(test_case, tests))
|
254 |
|
|
|
255 |
|
|
def testShuffleDoesNotInterleaveTestCases(self):
|
256 |
|
|
self._VerifyTestCasesDoNotInterleave(SHUFFLED_ALL_TESTS)
|
257 |
|
|
self._VerifyTestCasesDoNotInterleave(SHUFFLED_ACTIVE_TESTS)
|
258 |
|
|
self._VerifyTestCasesDoNotInterleave(SHUFFLED_FILTERED_TESTS)
|
259 |
|
|
self._VerifyTestCasesDoNotInterleave(SHUFFLED_SHARDED_TESTS)
|
260 |
|
|
|
261 |
|
|
def testShuffleRestoresOrderAfterEachIteration(self):
|
262 |
|
|
# Get the test lists in all 3 iterations, using random seed 1, 2,
|
263 |
|
|
# and 3 respectively. Google Test picks a different seed in each
|
264 |
|
|
# iteration, and this test depends on the current implementation
|
265 |
|
|
# picking successive numbers. This dependency is not ideal, but
|
266 |
|
|
# makes the test much easier to write.
|
267 |
|
|
[tests_in_iteration1, tests_in_iteration2, tests_in_iteration3] = (
|
268 |
|
|
GetTestsForAllIterations(
|
269 |
|
|
{}, [ShuffleFlag(), RandomSeedFlag(1), RepeatFlag(3)]))
|
270 |
|
|
|
271 |
|
|
# Make sure running the tests with random seed 1 gets the same
|
272 |
|
|
# order as in iteration 1 above.
|
273 |
|
|
[tests_with_seed1] = GetTestsForAllIterations(
|
274 |
|
|
{}, [ShuffleFlag(), RandomSeedFlag(1)])
|
275 |
|
|
self.assertEqual(tests_in_iteration1, tests_with_seed1)
|
276 |
|
|
|
277 |
|
|
# Make sure running the tests with random seed 2 gets the same
|
278 |
|
|
# order as in iteration 2 above. Success means that Google Test
|
279 |
|
|
# correctly restores the test order before re-shuffling at the
|
280 |
|
|
# beginning of iteration 2.
|
281 |
|
|
[tests_with_seed2] = GetTestsForAllIterations(
|
282 |
|
|
{}, [ShuffleFlag(), RandomSeedFlag(2)])
|
283 |
|
|
self.assertEqual(tests_in_iteration2, tests_with_seed2)
|
284 |
|
|
|
285 |
|
|
# Make sure running the tests with random seed 3 gets the same
|
286 |
|
|
# order as in iteration 3 above. Success means that Google Test
|
287 |
|
|
# correctly restores the test order before re-shuffling at the
|
288 |
|
|
# beginning of iteration 3.
|
289 |
|
|
[tests_with_seed3] = GetTestsForAllIterations(
|
290 |
|
|
{}, [ShuffleFlag(), RandomSeedFlag(3)])
|
291 |
|
|
self.assertEqual(tests_in_iteration3, tests_with_seed3)
|
292 |
|
|
|
293 |
|
|
def testShuffleGeneratesNewOrderInEachIteration(self):
|
294 |
|
|
[tests_in_iteration1, tests_in_iteration2, tests_in_iteration3] = (
|
295 |
|
|
GetTestsForAllIterations(
|
296 |
|
|
{}, [ShuffleFlag(), RandomSeedFlag(1), RepeatFlag(3)]))
|
297 |
|
|
|
298 |
|
|
self.assert_(tests_in_iteration1 != tests_in_iteration2,
|
299 |
|
|
tests_in_iteration1)
|
300 |
|
|
self.assert_(tests_in_iteration1 != tests_in_iteration3,
|
301 |
|
|
tests_in_iteration1)
|
302 |
|
|
self.assert_(tests_in_iteration2 != tests_in_iteration3,
|
303 |
|
|
tests_in_iteration2)
|
304 |
|
|
|
305 |
|
|
def testShuffleShardedTestsPreservesPartition(self):
|
306 |
|
|
# If we run M tests on N shards, the same M tests should be run in
|
307 |
|
|
# total, regardless of the random seeds used by the shards.
|
308 |
|
|
[tests1] = GetTestsForAllIterations({TOTAL_SHARDS_ENV_VAR: '3',
|
309 |
|
|
SHARD_INDEX_ENV_VAR: '0'},
|
310 |
|
|
[ShuffleFlag(), RandomSeedFlag(1)])
|
311 |
|
|
[tests2] = GetTestsForAllIterations({TOTAL_SHARDS_ENV_VAR: '3',
|
312 |
|
|
SHARD_INDEX_ENV_VAR: '1'},
|
313 |
|
|
[ShuffleFlag(), RandomSeedFlag(20)])
|
314 |
|
|
[tests3] = GetTestsForAllIterations({TOTAL_SHARDS_ENV_VAR: '3',
|
315 |
|
|
SHARD_INDEX_ENV_VAR: '2'},
|
316 |
|
|
[ShuffleFlag(), RandomSeedFlag(25)])
|
317 |
|
|
sorted_sharded_tests = tests1 + tests2 + tests3
|
318 |
|
|
sorted_sharded_tests.sort()
|
319 |
|
|
sorted_active_tests = []
|
320 |
|
|
sorted_active_tests.extend(ACTIVE_TESTS)
|
321 |
|
|
sorted_active_tests.sort()
|
322 |
|
|
self.assertEqual(sorted_active_tests, sorted_sharded_tests)
|
323 |
|
|
|
324 |
|
|
if __name__ == '__main__':
|
325 |
|
|
gtest_test_utils.Main()
|