162 lines
5.4 KiB
Python
162 lines
5.4 KiB
Python
# Copyright 2017 The Abseil Authors.
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
|
|
"""Tests for test sharding protocol."""
|
|
|
|
import os
|
|
import subprocess
|
|
|
|
from absl.testing import _bazelize_command
|
|
from absl.testing import absltest
|
|
from absl.testing.tests import absltest_env
|
|
|
|
|
|
NUM_TEST_METHODS = 8 # Hard-coded, based on absltest_sharding_test_helper.py
|
|
|
|
|
|
class TestShardingTest(absltest.TestCase):
|
|
"""Integration tests: Runs a test binary with sharding.
|
|
|
|
This is done by setting the sharding environment variables.
|
|
"""
|
|
|
|
def setUp(self):
|
|
super().setUp()
|
|
self._test_name = 'absl/testing/tests/absltest_sharding_test_helper'
|
|
self._shard_file = None
|
|
|
|
def tearDown(self):
|
|
super().tearDown()
|
|
if self._shard_file is not None and os.path.exists(self._shard_file):
|
|
os.unlink(self._shard_file)
|
|
|
|
def _run_sharded(self,
|
|
total_shards,
|
|
shard_index,
|
|
shard_file=None,
|
|
additional_env=None):
|
|
"""Runs the py_test binary in a subprocess.
|
|
|
|
Args:
|
|
total_shards: int, the total number of shards.
|
|
shard_index: int, the shard index.
|
|
shard_file: string, if not 'None', the path to the shard file.
|
|
This method asserts it is properly created.
|
|
additional_env: Additional environment variables to be set for the py_test
|
|
binary.
|
|
|
|
Returns:
|
|
(stdout, exit_code) tuple of (string, int).
|
|
"""
|
|
env = absltest_env.inherited_env()
|
|
if additional_env:
|
|
env.update(additional_env)
|
|
env.update({
|
|
'TEST_TOTAL_SHARDS': str(total_shards),
|
|
'TEST_SHARD_INDEX': str(shard_index)
|
|
})
|
|
if shard_file:
|
|
self._shard_file = shard_file
|
|
env['TEST_SHARD_STATUS_FILE'] = shard_file
|
|
if os.path.exists(shard_file):
|
|
os.unlink(shard_file)
|
|
|
|
proc = subprocess.Popen(
|
|
args=[_bazelize_command.get_executable_path(self._test_name)],
|
|
env=env,
|
|
stdout=subprocess.PIPE,
|
|
stderr=subprocess.STDOUT,
|
|
universal_newlines=True)
|
|
stdout = proc.communicate()[0]
|
|
|
|
if shard_file:
|
|
self.assertTrue(os.path.exists(shard_file))
|
|
|
|
return (stdout, proc.wait())
|
|
|
|
def _assert_sharding_correctness(self, total_shards):
|
|
"""Assert the primary correctness and performance of sharding.
|
|
|
|
1. Completeness (all methods are run)
|
|
2. Partition (each method run at most once)
|
|
3. Balance (for performance)
|
|
|
|
Args:
|
|
total_shards: int, total number of shards.
|
|
"""
|
|
|
|
outerr_by_shard = [] # A list of lists of strings
|
|
combined_outerr = [] # A list of strings
|
|
exit_code_by_shard = [] # A list of ints
|
|
|
|
for i in range(total_shards):
|
|
(out, exit_code) = self._run_sharded(total_shards, i)
|
|
method_list = [x for x in out.split('\n') if x.startswith('class')]
|
|
outerr_by_shard.append(method_list)
|
|
combined_outerr.extend(method_list)
|
|
exit_code_by_shard.append(exit_code)
|
|
|
|
self.assertLen([x for x in exit_code_by_shard if x != 0], 1,
|
|
'Expected exactly one failure')
|
|
|
|
# Test completeness and partition properties.
|
|
self.assertLen(combined_outerr, NUM_TEST_METHODS,
|
|
'Partition requirement not met')
|
|
self.assertLen(set(combined_outerr), NUM_TEST_METHODS,
|
|
'Completeness requirement not met')
|
|
|
|
# Test balance:
|
|
for i in range(len(outerr_by_shard)):
|
|
self.assertGreaterEqual(len(outerr_by_shard[i]),
|
|
(NUM_TEST_METHODS / total_shards) - 1,
|
|
'Shard %d of %d out of balance' %
|
|
(i, len(outerr_by_shard)))
|
|
|
|
def test_shard_file(self):
|
|
self._run_sharded(3, 1, os.path.join(
|
|
absltest.TEST_TMPDIR.value, 'shard_file'))
|
|
|
|
def test_zero_shards(self):
|
|
out, exit_code = self._run_sharded(0, 0)
|
|
self.assertEqual(1, exit_code)
|
|
self.assertGreaterEqual(out.find('Bad sharding values. index=0, total=0'),
|
|
0, 'Bad output: %s' % (out))
|
|
|
|
def test_with_four_shards(self):
|
|
self._assert_sharding_correctness(4)
|
|
|
|
def test_with_one_shard(self):
|
|
self._assert_sharding_correctness(1)
|
|
|
|
def test_with_ten_shards(self):
|
|
self._assert_sharding_correctness(10)
|
|
|
|
def test_sharding_with_randomization(self):
|
|
# If we're both sharding *and* randomizing, we need to confirm that we
|
|
# randomize within the shard; we use two seeds to confirm we're seeing the
|
|
# same tests (sharding is consistent) in a different order.
|
|
tests_seen = []
|
|
for seed in ('7', '17'):
|
|
out, exit_code = self._run_sharded(
|
|
2, 0, additional_env={'TEST_RANDOMIZE_ORDERING_SEED': seed})
|
|
self.assertEqual(0, exit_code)
|
|
tests_seen.append([x for x in out.splitlines() if x.startswith('class')])
|
|
first_tests, second_tests = tests_seen # pylint: disable=unbalanced-tuple-unpacking
|
|
self.assertEqual(set(first_tests), set(second_tests))
|
|
self.assertNotEqual(first_tests, second_tests)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
absltest.main()
|