mirror of https://github.com/mongodb/mongo
259 lines
9.9 KiB
Python
259 lines
9.9 KiB
Python
"""Unit tests for timeout_service.py."""
|
|
import random
|
|
import unittest
|
|
from datetime import datetime, timedelta
|
|
from unittest.mock import MagicMock
|
|
|
|
from requests.exceptions import HTTPError
|
|
from evergreen import EvergreenApi
|
|
|
|
import buildscripts.timeouts.timeout_service as under_test
|
|
from buildscripts.task_generation.resmoke_proxy import ResmokeProxyService
|
|
from buildscripts.util.teststats import HistoricTaskData
|
|
|
|
# pylint: disable=missing-docstring,no-self-use,invalid-name,protected-access
|
|
|
|
|
|
def build_mock_service(evg_api=None, resmoke_proxy=None):
|
|
end_date = datetime.now()
|
|
start_date = end_date - timedelta(weeks=2)
|
|
timeout_settings = under_test.TimeoutSettings(
|
|
end_date=end_date,
|
|
start_date=start_date,
|
|
)
|
|
return under_test.TimeoutService(
|
|
evg_api=evg_api if evg_api else MagicMock(spec_set=EvergreenApi),
|
|
resmoke_proxy=resmoke_proxy if resmoke_proxy else MagicMock(spec_set=ResmokeProxyService),
|
|
timeout_settings=timeout_settings)
|
|
|
|
|
|
def tst_stat_mock(file, duration, pass_count):
|
|
return MagicMock(test_file=file, avg_duration_pass=duration, num_pass=pass_count)
|
|
|
|
|
|
class TestGetTimeoutEstimate(unittest.TestCase):
|
|
def test_no_stats_should_return_default_timeout(self):
|
|
mock_evg_api = MagicMock(spec_set=EvergreenApi)
|
|
mock_evg_api.test_stats_by_project.return_value = []
|
|
timeout_service = build_mock_service(evg_api=mock_evg_api)
|
|
timeout_params = under_test.TimeoutParams(
|
|
evg_project="my project",
|
|
build_variant="bv",
|
|
task_name="my task",
|
|
suite_name="my suite",
|
|
is_asan=False,
|
|
)
|
|
|
|
timeout = timeout_service.get_timeout_estimate(timeout_params)
|
|
|
|
self.assertFalse(timeout.is_specified())
|
|
|
|
def test_a_test_with_missing_history_should_cause_a_default_timeout(self):
|
|
mock_evg_api = MagicMock(spec_set=EvergreenApi)
|
|
test_stats = [tst_stat_mock(f"test_{i}.js", 60, 1) for i in range(30)]
|
|
mock_evg_api.test_stats_by_project.return_value = test_stats
|
|
mock_resmoke_proxy = MagicMock(spec_set=ResmokeProxyService)
|
|
mock_resmoke_proxy.list_tests.return_value = ["test_with_no_stats.js"]
|
|
timeout_service = build_mock_service(evg_api=mock_evg_api, resmoke_proxy=mock_resmoke_proxy)
|
|
timeout_params = under_test.TimeoutParams(
|
|
evg_project="my project",
|
|
build_variant="bv",
|
|
task_name="my task",
|
|
suite_name="my suite",
|
|
is_asan=False,
|
|
)
|
|
|
|
timeout = timeout_service.get_timeout_estimate(timeout_params)
|
|
|
|
self.assertFalse(timeout.is_specified())
|
|
|
|
def test_a_test_with_zero_runtime_history_should_cause_a_default_timeout(self):
|
|
mock_evg_api = MagicMock(spec_set=EvergreenApi)
|
|
test_stats = [tst_stat_mock(f"test_{i}.js", 60, 1) for i in range(30)]
|
|
test_stats.append(tst_stat_mock("zero.js", 0.0, 1))
|
|
mock_evg_api.test_stats_by_project.return_value = test_stats
|
|
mock_resmoke_proxy = MagicMock(spec_set=ResmokeProxyService)
|
|
mock_resmoke_proxy.list_tests.return_value = [ts.test_file for ts in test_stats]
|
|
timeout_service = build_mock_service(evg_api=mock_evg_api, resmoke_proxy=mock_resmoke_proxy)
|
|
timeout_params = under_test.TimeoutParams(
|
|
evg_project="my project",
|
|
build_variant="bv",
|
|
task_name="my task",
|
|
suite_name="my suite",
|
|
is_asan=False,
|
|
)
|
|
|
|
timeout = timeout_service.get_timeout_estimate(timeout_params)
|
|
|
|
self.assertFalse(timeout.is_specified())
|
|
|
|
def test_all_tests_with_runtime_history_should_use_custom_timeout(self):
|
|
mock_evg_api = MagicMock(spec_set=EvergreenApi)
|
|
n_tests = 30
|
|
test_runtime = 600
|
|
test_stats = [tst_stat_mock(f"test_{i}.js", test_runtime, 1) for i in range(n_tests)]
|
|
mock_evg_api.test_stats_by_project.return_value = test_stats
|
|
mock_resmoke_proxy = MagicMock(spec_set=ResmokeProxyService)
|
|
mock_resmoke_proxy.list_tests.return_value = [ts.test_file for ts in test_stats]
|
|
timeout_service = build_mock_service(evg_api=mock_evg_api, resmoke_proxy=mock_resmoke_proxy)
|
|
timeout_params = under_test.TimeoutParams(
|
|
evg_project="my project",
|
|
build_variant="bv",
|
|
task_name="my task",
|
|
suite_name="my suite",
|
|
is_asan=False,
|
|
)
|
|
|
|
timeout = timeout_service.get_timeout_estimate(timeout_params)
|
|
|
|
self.assertTrue(timeout.is_specified())
|
|
self.assertEqual(1860, timeout.calculate_test_timeout(1))
|
|
self.assertEqual(54180, timeout.calculate_task_timeout(1))
|
|
|
|
|
|
class TestGetTaskHookOverhead(unittest.TestCase):
|
|
def test_no_stats_should_return_zero(self):
|
|
timeout_service = build_mock_service()
|
|
|
|
overhead = timeout_service.get_task_hook_overhead("suite", is_asan=False, test_count=30,
|
|
historic_stats=None)
|
|
|
|
self.assertEqual(0.0, overhead)
|
|
|
|
def test_stats_with_no_clean_every_n_should_return_zero(self):
|
|
timeout_service = build_mock_service()
|
|
test_stats = HistoricTaskData.from_stats_list(
|
|
[tst_stat_mock(f"test_{i}.js", 60, 1) for i in range(30)])
|
|
|
|
overhead = timeout_service.get_task_hook_overhead("suite", is_asan=False, test_count=30,
|
|
historic_stats=test_stats)
|
|
|
|
self.assertEqual(0.0, overhead)
|
|
|
|
def test_stats_with_clean_every_n_should_return_overhead(self):
|
|
test_count = 30
|
|
runtime = 25
|
|
timeout_service = build_mock_service()
|
|
test_stat_list = [tst_stat_mock(f"test_{i}.js", 60, 1) for i in range(test_count)]
|
|
test_stat_list.extend([
|
|
tst_stat_mock(f"test_{i}:{under_test.CLEAN_EVERY_N_HOOK}", runtime, 1)
|
|
for i in range(10)
|
|
])
|
|
random.shuffle(test_stat_list)
|
|
test_stats = HistoricTaskData.from_stats_list(test_stat_list)
|
|
|
|
overhead = timeout_service.get_task_hook_overhead(
|
|
"suite", is_asan=True, test_count=test_count, historic_stats=test_stats)
|
|
|
|
self.assertEqual(runtime * test_count, overhead)
|
|
|
|
|
|
class TestLookupHistoricStats(unittest.TestCase):
|
|
def test_no_stats_from_evergreen_should_return_none(self):
|
|
mock_evg_api = MagicMock(spec_set=EvergreenApi)
|
|
mock_evg_api.test_stats_by_project.return_value = []
|
|
timeout_service = build_mock_service(evg_api=mock_evg_api)
|
|
timeout_params = under_test.TimeoutParams(
|
|
evg_project="my project",
|
|
build_variant="bv",
|
|
task_name="my task",
|
|
suite_name="my suite",
|
|
is_asan=False,
|
|
)
|
|
|
|
stats = timeout_service.lookup_historic_stats(timeout_params)
|
|
|
|
self.assertIsNone(stats)
|
|
|
|
def test_errors_from_evergreen_should_return_none(self):
|
|
mock_evg_api = MagicMock(spec_set=EvergreenApi)
|
|
mock_evg_api.test_stats_by_project.side_effect = HTTPError("failed to connect")
|
|
timeout_service = build_mock_service(evg_api=mock_evg_api)
|
|
timeout_params = under_test.TimeoutParams(
|
|
evg_project="my project",
|
|
build_variant="bv",
|
|
task_name="my task",
|
|
suite_name="my suite",
|
|
is_asan=False,
|
|
)
|
|
|
|
stats = timeout_service.lookup_historic_stats(timeout_params)
|
|
|
|
self.assertIsNone(stats)
|
|
|
|
def test_stats_from_evergreen_should_return_the_stats(self):
|
|
mock_evg_api = MagicMock(spec_set=EvergreenApi)
|
|
test_stats = [tst_stat_mock(f"test_{i}.js", 60, 1) for i in range(100)]
|
|
mock_evg_api.test_stats_by_project.return_value = test_stats
|
|
timeout_service = build_mock_service(evg_api=mock_evg_api)
|
|
timeout_params = under_test.TimeoutParams(
|
|
evg_project="my project",
|
|
build_variant="bv",
|
|
task_name="my task",
|
|
suite_name="my suite",
|
|
is_asan=False,
|
|
)
|
|
|
|
stats = timeout_service.lookup_historic_stats(timeout_params)
|
|
|
|
self.assertIsNotNone(stats)
|
|
self.assertEqual(len(test_stats), len(stats.historic_test_results))
|
|
|
|
|
|
class TestGetCleanEveryNCadence(unittest.TestCase):
|
|
def test_clean_every_n_cadence_on_asan(self):
|
|
timeout_service = build_mock_service()
|
|
|
|
cadence = timeout_service._get_clean_every_n_cadence("suite", True)
|
|
|
|
self.assertEqual(1, cadence)
|
|
|
|
def test_clean_every_n_cadence_from_hook_config(self):
|
|
expected_n = 42
|
|
mock_resmoke_proxy = MagicMock()
|
|
mock_resmoke_proxy.read_suite_config.return_value = {
|
|
"executor": {
|
|
"hooks": [{
|
|
"class": "hook1",
|
|
}, {
|
|
"class": under_test.CLEAN_EVERY_N_HOOK,
|
|
"n": expected_n,
|
|
}]
|
|
}
|
|
}
|
|
timeout_service = build_mock_service(resmoke_proxy=mock_resmoke_proxy)
|
|
|
|
cadence = timeout_service._get_clean_every_n_cadence("suite", False)
|
|
|
|
self.assertEqual(expected_n, cadence)
|
|
|
|
def test_clean_every_n_cadence_no_n_in_hook_config(self):
|
|
mock_resmoke_proxy = MagicMock()
|
|
mock_resmoke_proxy.read_suite_config.return_value = {
|
|
"executor": {
|
|
"hooks": [{
|
|
"class": "hook1",
|
|
}, {
|
|
"class": under_test.CLEAN_EVERY_N_HOOK,
|
|
}]
|
|
}
|
|
}
|
|
timeout_service = build_mock_service(resmoke_proxy=mock_resmoke_proxy)
|
|
|
|
cadence = timeout_service._get_clean_every_n_cadence("suite", False)
|
|
|
|
self.assertEqual(1, cadence)
|
|
|
|
def test_clean_every_n_cadence_no_hook_config(self):
|
|
mock_resmoke_proxy = MagicMock()
|
|
mock_resmoke_proxy.read_suite_config.return_value = {
|
|
"executor": {"hooks": [{
|
|
"class": "hook1",
|
|
}, ]}
|
|
}
|
|
timeout_service = build_mock_service(resmoke_proxy=mock_resmoke_proxy)
|
|
|
|
cadence = timeout_service._get_clean_every_n_cadence("suite", False)
|
|
|
|
self.assertEqual(1, cadence)
|