diff options
author | Mikhail Shchatko <mikhail.shchatko@mongodb.com> | 2022-07-29 08:35:33 +0000 |
---|---|---|
committer | Evergreen Agent <no-reply@evergreen.mongodb.com> | 2022-07-29 09:04:37 +0000 |
commit | 368542c6aa8a129b461e54582ed0913fc72cdfb8 (patch) | |
tree | ca0c5c9b2a8cd9c6f57198bff9a57f818080f40a /buildscripts/tests | |
parent | 35978285c4be21c80e4781a827fda307e4b040a0 (diff) | |
download | mongo-368542c6aa8a129b461e54582ed0913fc72cdfb8.tar.gz |
SERVER-66369 Generate burn_in_tests and burn_in_tags using mongo-task-generator
Diffstat (limited to 'buildscripts/tests')
-rw-r--r-- | buildscripts/tests/test_burn_in_tags.py | 261 | ||||
-rw-r--r-- | buildscripts/tests/test_burn_in_tags_evergreen.yml | 138 | ||||
-rw-r--r-- | buildscripts/tests/test_evergreen_activate_gen_tasks.py | 113 | ||||
-rw-r--r-- | buildscripts/tests/test_evergreen_burn_in_tests.py | 408 |
4 files changed, 98 insertions, 822 deletions
diff --git a/buildscripts/tests/test_burn_in_tags.py b/buildscripts/tests/test_burn_in_tags.py deleted file mode 100644 index ec53a02d161..00000000000 --- a/buildscripts/tests/test_burn_in_tags.py +++ /dev/null @@ -1,261 +0,0 @@ -"""Unit tests for the burn_in_tags.py script.""" -from collections import defaultdict -import json -import os -import sys -import unittest -from unittest.mock import MagicMock, patch - -from shrub.v2 import ShrubProject - -import buildscripts.ciconfig.evergreen as _evergreen -from buildscripts.burn_in_tests import TaskInfo -from buildscripts.tests.test_burn_in_tests import ns as burn_in_tests_ns -from buildscripts.ciconfig.evergreen import EvergreenProjectConfig - -import buildscripts.burn_in_tags as under_test - -# pylint: disable=missing-docstring,invalid-name,unused-argument,no-self-use,protected-access - -EMPTY_PROJECT = { - "buildvariants": [], - "tasks": [], -} -TEST_FILE_PATH = os.path.join(os.path.dirname(__file__), "test_burn_in_tags_evergreen.yml") - -NS = "buildscripts.burn_in_tags" - - -def ns(relative_name): # pylint: disable-invalid-name - """Return a full name from a name relative to the test module"s name space.""" - return NS + "." + relative_name - - -def get_expansions_data(): - return { - "branch_name": "fake_branch", - "build_variant": "enterprise-rhel-80-64-bit-suggested", - "check_evergreen": 2, - "distro_id": "rhel80-small", - "is_patch": "true", - "max_revisions": 25, - "repeat_tests_max": 1000, - "repeat_tests_min": 2, - "repeat_tests_secs": 600, - "revision": "fake_sha", - "project": "fake_project", - "task_id": "task id", - } # yapf: disable - - -def get_evergreen_config() -> EvergreenProjectConfig: - return _evergreen.parse_evergreen_file(TEST_FILE_PATH, evergreen_binary=None) - - -class TestCreateEvgBuildVariantMap(unittest.TestCase): - def test_create_evg_buildvariant_map(self): - expansions_file_data = { - "build_variant": "variant1", "burn_in_tag_buildvariants": "variant2 variant3" - } - buildvariant_map = under_test._create_evg_build_variant_map(expansions_file_data) - - expected_buildvariant_map = { - "variant2": "variant2-required", "variant3": "variant3-required" - } - self.assertEqual(buildvariant_map, expected_buildvariant_map) - - -class TestGenerateEvgBuildVariants(unittest.TestCase): - def test_generate_evg_buildvariant_one_base_variant(self): - evg_conf_mock = get_evergreen_config() - base_variant = "enterprise-rhel-80-64-bit-inmem" - generated_variant = "enterprise-rhel-80-64-bit-inmem-required" - burn_in_tags_gen_variant = "enterprise-rhel-80-64-bit" - variant = evg_conf_mock.get_variant(base_variant) - - build_variant = under_test._generate_evg_build_variant(variant, generated_variant, - burn_in_tags_gen_variant) - - generated_build_variant = build_variant.as_dict() - self.assertEqual(generated_build_variant["name"], generated_variant) - self.assertEqual(generated_build_variant["modules"], variant.modules) - generated_expansions = generated_build_variant["expansions"] - burn_in_bypass_expansion_value = generated_expansions.pop("burn_in_bypass") - self.assertEqual(burn_in_bypass_expansion_value, burn_in_tags_gen_variant) - self.assertEqual(generated_expansions, variant.expansions) - - -class TestGenerateEvgTasks(unittest.TestCase): - @patch(ns("create_tests_by_task")) - def test_generate_evg_tasks_no_tests_changed(self, create_tests_by_task_mock): - evg_conf_mock = get_evergreen_config() - create_tests_by_task_mock.return_value = {} - expansions_file_data = get_expansions_data() - buildvariant_map = { - "enterprise-rhel-80-64-bit-inmem": "enterprise-rhel-80-64-bit-inmem-required", - "enterprise-rhel-80-64-bit-majority-read-concern-off": - "enterprise-rhel-80-64-bit-majority-read-concern-off-required", - } # yapf: disable - shrub_config = ShrubProject() - evergreen_api = MagicMock() - repo = MagicMock(working_dir=os.getcwd()) - under_test._generate_evg_tasks(evergreen_api, shrub_config, expansions_file_data, - buildvariant_map, [repo], evg_conf_mock, 'install-dir/bin') - - self.assertEqual(shrub_config.as_dict(), EMPTY_PROJECT) - - @patch(ns("create_tests_by_task")) - def test_generate_evg_tasks_one_test_changed(self, create_tests_by_task_mock): - evg_conf_mock = get_evergreen_config() - create_tests_by_task_mock.return_value = { - "aggregation_mongos_passthrough": TaskInfo( - display_task_name="aggregation_mongos_passthrough", - suite="aggregation_mongos_passthrough", - resmoke_args="--suites=aggregation_mongos_passthrough --storageEngine=wiredTiger", - tests=["jstests/aggregation/ifnull.js"], - require_multiversion_setup=False, - distro="", - build_variant="enterprise-rhel-80-64-bit-inmem" - ) - } # yapf: disable - expansions_file_data = get_expansions_data() - buildvariant_map = { - "enterprise-rhel-80-64-bit-inmem": "enterprise-rhel-80-64-bit-inmem-required", - "enterprise-rhel-80-64-bit-majority-read-concern-off": - "enterprise-rhel-80-64-bit-majority-read-concern-off-required", - } # yapf: disable - shrub_config = ShrubProject.empty() - evergreen_api = MagicMock() - repo = MagicMock(working_dir=os.getcwd()) - evergreen_api.test_stats_by_project.return_value = [ - MagicMock(test_file="dir/test2.js", avg_duration_pass=10) - ] - under_test._generate_evg_tasks(evergreen_api, shrub_config, expansions_file_data, - buildvariant_map, [repo], evg_conf_mock, 'install-dir/bin') - - generated_config = shrub_config.as_dict() - self.assertEqual(len(generated_config["buildvariants"]), 2) - first_generated_build_variant = generated_config["buildvariants"][0] - self.assertIn(first_generated_build_variant["name"], buildvariant_map.values()) - self.assertEqual(first_generated_build_variant["display_tasks"][0]["name"], "burn_in_tests") - self.assertEqual( - first_generated_build_variant["display_tasks"][0]["execution_tasks"][0], - f"burn_in:aggregation_mongos_passthrough_0_{first_generated_build_variant['name']}") - - -EXPANSIONS_FILE_DATA = { - "build_variant": "enterprise-rhel-80-64-bit", - "revision": "badf00d000000000000000000000000000000000", "max_revisions": "1000", - "branch_name": "mongodb-mongo-master", "is_patch": "false", "distro_id": "rhel62-small", - "repeat_tests_min": "2", "repeat_tests_max": "1000", "repeat_tests_secs": "600", "project": - "mongodb-mongo-master", "task_id": "task id" -} - -CREATE_EVG_BUILD_VARIANT_MAP = { - 'enterprise-rhel-80-64-bit-majority-read-concern-off': - 'enterprise-rhel-80-64-bit-majority-read-concern-off-required', - 'enterprise-rhel-80-64-bit-inmem': - 'enterprise-rhel-80-64-bit-inmem-required' -} - -CREATE_TEST_MEMBERSHIP_MAP = { - "jstests/aggregation/accumulators/accumulator_js.js": [ - "aggregation", "aggregation_auth", "aggregation_disabled_optimization", "aggregation_ese", - "aggregation_ese_gcm", "aggregation_facet_unwind_passthrough", - "aggregation_mongos_passthrough", "aggregation_one_shard_sharded_collections", - "aggregation_read_concern_majority_passthrough", "aggregation_secondary_reads", - "aggregation_sharded_collections_passthrough" - ], "jstests/core/create_collection.js": [ - "core", "core_auth", "core_ese", "core_ese_gcm", "core_minimum_batch_size", "core_op_query", - "cwrwc_passthrough", "cwrwc_rc_majority_passthrough", "cwrwc_wc_majority_passthrough", - "logical_session_cache_replication_100ms_refresh_jscore_passthrough", - "logical_session_cache_replication_10sec_refresh_jscore_passthrough", - "logical_session_cache_replication_1sec_refresh_jscore_passthrough", - "logical_session_cache_replication_default_refresh_jscore_passthrough", - "logical_session_cache_standalone_100ms_refresh_jscore_passthrough", - "logical_session_cache_standalone_10sec_refresh_jscore_passthrough", - "logical_session_cache_standalone_1sec_refresh_jscore_passthrough", - "logical_session_cache_standalone_default_refresh_jscore_passthrough", - "read_concern_linearizable_passthrough", "read_concern_majority_passthrough", - "causally_consistent_read_concern_snapshot_passthrough", - "replica_sets_initsync_jscore_passthrough", "replica_sets_fcbis_jscore_passthrough", - "replica_sets_initsync_static_jscore_passthrough", "replica_sets_jscore_passthrough", - "replica_sets_kill_primary_jscore_passthrough", - "replica_sets_kill_secondaries_jscore_passthrough", - "replica_sets_reconfig_jscore_passthrough", - "replica_sets_terminate_primary_jscore_passthrough", "retryable_writes_jscore_passthrough", - "retryable_writes_jscore_stepdown_passthrough", "secondary_reads_passthrough", - "session_jscore_passthrough", "write_concern_majority_passthrough" - ] -} - - -class TestAcceptance(unittest.TestCase): - @patch(ns("write_file_to_dir")) - @patch(ns("_create_evg_build_variant_map")) - @patch(ns("EvergreenFileChangeDetector")) - def test_no_tests_run_if_none_changed(self, find_changed_tests_mock, - create_evg_build_variant_map_mock, write_to_file_mock): - """ - Given a git repository with no changes, - When burn_in_tags is run, - Then no tests are discovered to run. - """ - repos = [MagicMock(working_dir=os.getcwd())] - evg_conf_mock = MagicMock() - find_changed_tests_mock.return_value.find_changed_tests.return_value = {} - - create_evg_build_variant_map_mock.return_value = CREATE_EVG_BUILD_VARIANT_MAP - - under_test.burn_in(EXPANSIONS_FILE_DATA, evg_conf_mock, MagicMock(), repos, - 'install_dir/bin') - - write_to_file_mock.assert_called_once() - shrub_config = write_to_file_mock.call_args[0][2] - self.assertEqual(EMPTY_PROJECT, json.loads(shrub_config)) - - @unittest.skipIf(sys.platform.startswith("win"), "not supported on windows") - @patch(ns("write_file_to_dir")) - @patch(ns("_create_evg_build_variant_map")) - @patch(ns("EvergreenFileChangeDetector")) - @patch(burn_in_tests_ns("create_test_membership_map")) - def test_tests_generated_if_a_file_changed( - self, create_test_membership_map_mock, find_changed_tests_mock, - create_evg_build_variant_map_mock, write_to_file_mock): - """ - Given a git repository with changes, - When burn_in_tags is run, - Then some tags are discovered to run. - """ - create_test_membership_map_mock.return_value = defaultdict(list, CREATE_TEST_MEMBERSHIP_MAP) - - repos = [MagicMock(working_dir=os.getcwd())] - evg_conf = get_evergreen_config() - create_evg_build_variant_map_mock.return_value = CREATE_EVG_BUILD_VARIANT_MAP - find_changed_tests_mock.return_value.find_changed_tests.return_value = { - 'jstests/slow1/large_role_chain.js', - 'jstests/aggregation/accumulators/accumulator_js.js' - } - - under_test.burn_in(EXPANSIONS_FILE_DATA, evg_conf, MagicMock(), repos, 'install_dir/bin') - - write_to_file_mock.assert_called_once() - written_config = write_to_file_mock.call_args[0][2] - written_config_map = json.loads(written_config) - - n_tasks = len(written_config_map["tasks"]) - # Ensure we are generating at least one task for the test. - self.assertGreaterEqual(n_tasks, 1) - - written_build_variants = written_config_map["buildvariants"] - written_build_variants_name = [variant['name'] for variant in written_build_variants] - self.assertEqual( - set(CREATE_EVG_BUILD_VARIANT_MAP.values()), set(written_build_variants_name)) - - tasks = written_config_map["tasks"] - self.assertGreaterEqual(len(tasks), len(CREATE_EVG_BUILD_VARIANT_MAP)) - - self.assertTrue( - all( - len(display_tasks) == 1 for display_tasks in - [build_variant["display_tasks"] for build_variant in written_build_variants])) diff --git a/buildscripts/tests/test_burn_in_tags_evergreen.yml b/buildscripts/tests/test_burn_in_tags_evergreen.yml deleted file mode 100644 index 2db458fff05..00000000000 --- a/buildscripts/tests/test_burn_in_tags_evergreen.yml +++ /dev/null @@ -1,138 +0,0 @@ -functions: - "fetch source": - - command: git.get_project - params: - directory: src - - command: shell.exec - params: - working_dir: src - script: | - echo "this is a 2nd command in the function!" - ls - -tasks: -- name: compile - depends_on: [] - commands: - - func: "fetch source" -- name: burn_in_tags_gen - depends_on: [] - commands: - - func: "fake command" -- name: compile_all_run_unittests_TG - depends_on: [] - commands: - - func: "fake command" -- name: clang_tidy_TG - depends_on: [] - commands: - - func: "fake command" -- name: stitch_support_lib_build_and_archive - depends_on: [] - commands: - - func: "fake command" -- name: lint_pylinters - depends_on: [] - commands: - - func: "fake command" -- name: lint_clang_format - depends_on: [] - commands: - - func: "fake command" -- name: burn_in_tests_gen - depends_on: [] - commands: - - func: "fake command" -- name: aggregation_multiversion_fuzzer_gen - depends_on: [] - commands: - - func: "generate resmoke tasks" -- name: aggregation_expression_multiversion_fuzzer_gen - depends_on: [] - commands: - - func: "generate resmoke tasks" -- name: aggregation - depends_on: - - name: compile - commands: - - func: run tests - vars: - resmoke_args: --suites=aggregation --storageEngine=wiredTiger - -buildvariants: -- name: enterprise-rhel-80-64-bit - display_name: "! Enterprise RHEL 8.0" - expansions: - multiversion_platform: rhel80 - burn_in_tag_buildvariants: enterprise-rhel-80-64-bit-majority-read-concern-off enterprise-rhel-80-64-bit-inmem - tasks: - - name: compile_all_run_unittests_TG - distros: - - rhel80-large - - name: lint_pylinters - - name: burn_in_tests_gen - - name: aggregation_multiversion_fuzzer_gen - - name: aggregation - - name: burn_in_tags_gen -- name: buildvariant-without-burn-in-tag-buildvariants - display_name: "Buildvariant without burn in tag buildvariants expansion" - expansions: - multiversion_platform: rhel80 - tasks: - - name: burn_in_tags_gen -- name: enterprise-rhel-80-64-bit-majority-read-concern-off - display_name: "Enterprise RHEL 8.0 (majority read concern off)" - modules: ["enterprise"] - run_on: - - rhel80-small - expansions: &enterprise-rhel-80-64-bit-majority-read-concern-off-expansions - multiversion_edition: enterprise - tasks: - - name: compile_all_run_unittests_TG - distros: - - rhel80-large - - name: aggregation_multiversion_fuzzer_gen - - name: aggregation -- name: enterprise-rhel-80-64-bit-inmem - display_name: Enterprise RHEL 8.0 (inMemory) - modules: ["enterprise"] - run_on: - - rhel80-small - expansions: &enterprise-rhel-80-64-bit-inmem-expansions - test_flags: >- - --majorityReadConcern=off - --excludeWithAnyTags=requires_majority_read_concern,uses_prepare_transaction,uses_multi_shard_transaction,uses_atclustertime - compile_flags: >- - -j$(grep -c ^processor /proc/cpuinfo) - --ssl - --release - --variables-files=etc/scons/mongodbtoolchain_v3_gcc.vars - MONGO_DISTMOD=rhel80 - multiversion_platform: rhel80 - multiversion_edition: enterprise - scons_cache_scope: shared - tooltags: "ssl sasl gssapi" - large_distro_name: rhel80-large - tasks: - - name: compile -- name: enterprise-rhel-80-64-bit-inmem - display_name: Enterprise RHEL 8.0 (inMemory) - expansions: - additional_targets: archive-mongocryptd archive-mongocryptd-debug - compile_flags: --ssl MONGO_DISTMOD=rhel80 -j$(grep -c ^processor /proc/cpuinfo) - --variables-files=etc/scons/mongodbtoolchain_v3_gcc.vars - large_distro_name: rhel80-large - multiversion_edition: enterprise - multiversion_platform: rhel80 - scons_cache_scope: shared - test_flags: --storageEngine=inMemory --excludeWithAnyTags=requires_persistence - modules: - - enterprise - run_on: - - rhel80-small - tasks: - - name: compile_all_run_unittests_TG - distros: - - rhel80-large - - name: aggregation_multiversion_fuzzer_gen - - name: aggregation diff --git a/buildscripts/tests/test_evergreen_activate_gen_tasks.py b/buildscripts/tests/test_evergreen_activate_gen_tasks.py index dbb64380838..cc7e85661c0 100644 --- a/buildscripts/tests/test_evergreen_activate_gen_tasks.py +++ b/buildscripts/tests/test_evergreen_activate_gen_tasks.py @@ -1,42 +1,125 @@ """Unit tests for the generate_resmoke_suite script.""" import unittest -from mock import MagicMock +from mock import MagicMock, mock from buildscripts import evergreen_activate_gen_tasks as under_test +from evergreen import Build, EvergreenApi, Task, Version # pylint: disable=missing-docstring,invalid-name,unused-argument,no-self-use,protected-access # pylint: disable=too-many-locals,too-many-lines,too-many-public-methods,no-value-for-parameter -def build_mock_task(name, task_id): - mock_task = MagicMock(display_name=name, task_id=task_id) +def build_mock_task(display_name, task_id): + mock_task = MagicMock(spec_set=Task, display_name=display_name, task_id=task_id) return mock_task -def build_mock_evg_api(mock_task_list): - mock_build = MagicMock() +def build_mock_task_list(num_tasks): + return [build_mock_task(f"task_{i}", f"id_{i}") for i in range(num_tasks)] + + +def build_mock_build(mock_task_list): + mock_build = MagicMock(spec_set=Build) mock_build.get_tasks.return_value = mock_task_list - mock_evg_api = MagicMock() - mock_evg_api.build_by_id.return_value = mock_build + return mock_build + + +def build_mock_evg_api(mock_current_build, mock_other_builds_list): + mock_version = MagicMock(spec_set=Version) + mock_version.build_by_variant.side_effect = mock_other_builds_list + mock_evg_api = MagicMock(spec_set=EvergreenApi) + mock_evg_api.version_by_id.return_value = mock_version + mock_evg_api.build_by_id.return_value = mock_current_build return mock_evg_api class TestActivateTask(unittest.TestCase): def test_task_with_display_name_is_activated(self): - n_tasks = 5 - mock_task_list = [build_mock_task(f"task_{i}", f"id_{i}") for i in range(n_tasks)] - mock_evg_api = build_mock_evg_api(mock_task_list) + expansions = under_test.EvgExpansions(**{ + "build_id": "build_id", + "version_id": "version_id", + "task_name": "task_3_gen", + }) + mock_task_list = build_mock_task_list(5) + mock_evg_api = build_mock_evg_api(build_mock_build(mock_task_list), []) - under_test.activate_task("build_id", "task_3", mock_evg_api) + under_test.activate_task(expansions, mock_evg_api) mock_evg_api.configure_task.assert_called_with("id_3", activated=True) def test_task_with_no_matching_name(self): - n_tasks = 5 - mock_task_list = [build_mock_task(f"task_{i}", f"id_{i}") for i in range(n_tasks)] - mock_evg_api = build_mock_evg_api(mock_task_list) + expansions = under_test.EvgExpansions(**{ + "build_id": "build_id", + "version_id": "version_id", + "task_name": "not_an_existing_task", + }) + mock_task_list = build_mock_task_list(5) + mock_evg_api = build_mock_evg_api(build_mock_build(mock_task_list), []) + + under_test.activate_task(expansions, mock_evg_api) + + mock_evg_api.configure_task.assert_not_called() + + def test_burn_in_tags_tasks_are_activated(self): + expansions = under_test.EvgExpansions( + **{ + "build_id": "build_id", + "version_id": "version_id", + "task_name": "burn_in_tags_gen", + "burn_in_tag_buildvariants": "build_variant_2 build_variant_3", + }) + mock_task_list_1 = build_mock_task_list(5) + mock_task_list_1.append(build_mock_task("burn_in_tags_gen", "burn_in_tags_gen_id_1")) + mock_task_list_2 = build_mock_task_list(5) + mock_task_list_2.append(build_mock_task("burn_in_tests", "burn_in_tests_id_2")) + mock_task_list_3 = build_mock_task_list(5) + mock_task_list_3.append(build_mock_task("burn_in_tests", "burn_in_tests_id_3")) + mock_evg_api = build_mock_evg_api( + build_mock_build(mock_task_list_1), [ + build_mock_build(mock_task_list_2), + build_mock_build(mock_task_list_3), + ]) + + under_test.activate_task(expansions, mock_evg_api) + + mock_evg_api.configure_task.assert_has_calls([ + mock.call("burn_in_tests_id_2", activated=True), + mock.call("burn_in_tests_id_3", activated=True) + ]) + + def test_burn_in_tags_task_skips_non_existing_build_variant(self): + expansions = under_test.EvgExpansions( + **{ + "build_id": "build_id", + "version_id": "version_id", + "task_name": "burn_in_tags_gen", + "burn_in_tag_buildvariants": "not_an_existing_build_variant build_variant_2", + }) + mock_task_list_1 = build_mock_task_list(5) + mock_task_list_1.append(build_mock_task("burn_in_tags_gen", "burn_in_tags_gen_id_1")) + mock_task_list_2 = build_mock_task_list(5) + mock_task_list_2.append(build_mock_task("burn_in_tests", "burn_in_tests_id_2")) + mock_evg_api = build_mock_evg_api( + build_mock_build(mock_task_list_1), [ + KeyError, + build_mock_build(mock_task_list_2), + ]) + + under_test.activate_task(expansions, mock_evg_api) + + mock_evg_api.configure_task.assert_called_once_with("burn_in_tests_id_2", activated=True) + + def test_burn_in_tags_task_with_missing_burn_in_tag_buildvariants_expansion(self): + expansions = under_test.EvgExpansions(**{ + "build_id": "build_id", + "version_id": "version_id", + "task_name": "burn_in_tags_gen", + }) + mock_task_list_1 = build_mock_task_list(5) + mock_task_list_1.append(build_mock_task("burn_in_tags_gen", "burn_in_tags_gen_id_1")) + mock_evg_api = build_mock_evg_api(build_mock_build(mock_task_list_1), []) - under_test.activate_task("build_id", "not_an_existing_task", mock_evg_api) + under_test.activate_task(expansions, mock_evg_api) mock_evg_api.configure_task.assert_not_called() diff --git a/buildscripts/tests/test_evergreen_burn_in_tests.py b/buildscripts/tests/test_evergreen_burn_in_tests.py deleted file mode 100644 index ee77ced2579..00000000000 --- a/buildscripts/tests/test_evergreen_burn_in_tests.py +++ /dev/null @@ -1,408 +0,0 @@ -"""Unit tests for buildscripts/burn_in_tests.py.""" - -from __future__ import absolute_import - -import json -import os -import sys -import unittest -from datetime import datetime, timedelta -from math import ceil - -import requests -from mock import patch, MagicMock -from shrub.v2 import BuildVariant, ShrubProject -from evergreen.api import EvergreenApi - -import buildscripts.evergreen_burn_in_tests as under_test -from buildscripts.ciconfig.evergreen import parse_evergreen_file -import buildscripts.resmokelib.parser as _parser -import buildscripts.resmokelib.config as _config -import buildscripts.util.teststats as teststats_utils -_parser.set_run_options() - -# pylint: disable=missing-docstring,invalid-name,unused-argument,no-self-use,protected-access - -NS = "buildscripts.evergreen_burn_in_tests" - - -def ns(relative_name): # pylint: disable=invalid-name - """Return a full name from a name relative to the test module"s name space.""" - return NS + "." + relative_name - - -def mock_a_file(filename): - change = MagicMock(a_path=filename) - return change - - -def mock_git_diff(change_list): - diff = MagicMock() - diff.iter_change_type.return_value = change_list - return diff - - -def mock_changed_git_files(add_files): - repo = MagicMock() - repo.index.diff.return_value = mock_git_diff([mock_a_file(f) for f in add_files]) - repo.working_dir = "." - return repo - - -def get_evergreen_config(config_file_path): - evergreen_home = os.path.expanduser(os.path.join("~", "evergreen")) - if os.path.exists(evergreen_home): - return parse_evergreen_file(config_file_path, evergreen_home) - return parse_evergreen_file(config_file_path) - - -class TestAcceptance(unittest.TestCase): - def tearDown(self): - _parser.set_run_options() - - @patch(ns("write_file")) - def test_no_tests_run_if_none_changed(self, write_json_mock): - """ - Given a git repository with no changes, - When burn_in_tests is run, - Then no tests are discovered to run. - """ - variant = "build_variant" - repos = [mock_changed_git_files([])] - repeat_config = under_test.RepeatConfig() - gen_config = under_test.GenerateConfig( - variant, - "project", - ) # yapf: disable - mock_evg_conf = MagicMock() - mock_evg_conf.get_task_names_by_tag.return_value = set() - mock_evg_api = MagicMock() - - under_test.burn_in("task_id", variant, gen_config, repeat_config, mock_evg_api, - mock_evg_conf, repos, "testfile.json", "install-dir/bin") - - write_json_mock.assert_called_once() - written_config = json.loads(write_json_mock.call_args[0][1]) - display_task = written_config["buildvariants"][0]["display_tasks"][0] - self.assertEqual(1, len(display_task["execution_tasks"])) - self.assertEqual(under_test.BURN_IN_TESTS_GEN_TASK, display_task["execution_tasks"][0]) - - @unittest.skipIf(sys.platform.startswith("win"), "not supported on windows") - @patch(ns("write_file")) - def test_tests_generated_if_a_file_changed(self, write_json_mock): - """ - Given a git repository with changes, - When burn_in_tests is run, - Then tests are discovered to run. - """ - # Note: this test is using actual tests and suites. So changes to those suites could - # introduce failures and require this test to be updated. - # You can see the test file it is using below. This test is used in the 'auth' and - # 'auth_audit' test suites. It needs to be in at least one of those for the test to pass. - variant = "enterprise-rhel-80-64-bit-inmem" - repos = [mock_changed_git_files(["jstests/auth/auth1.js"])] - repeat_config = under_test.RepeatConfig() - gen_config = under_test.GenerateConfig( - variant, - "project", - ) # yapf: disable - mock_evg_conf = get_evergreen_config("etc/evergreen.yml") - mock_evg_api = MagicMock() - - under_test.burn_in("task_id", variant, gen_config, repeat_config, mock_evg_api, - mock_evg_conf, repos, "testfile.json", 'install-dir/bin') - - write_json_mock.assert_called_once() - written_config = json.loads(write_json_mock.call_args[0][1]) - n_tasks = len(written_config["tasks"]) - # Ensure we are generating at least one task for the test. - self.assertGreaterEqual(n_tasks, 1) - - written_build_variant = written_config["buildvariants"][0] - self.assertEqual(variant, written_build_variant["name"]) - self.assertEqual(n_tasks, len(written_build_variant["tasks"])) - - display_task = written_build_variant["display_tasks"][0] - # The display task should contain all the generated tasks as well as 1 extra task for - # the burn_in_test_gen task. - self.assertEqual(n_tasks + 1, len(display_task["execution_tasks"])) - - -class TestGenerateConfig(unittest.TestCase): - def test_run_build_variant_with_no_run_build_variant(self): - gen_config = under_test.GenerateConfig("build_variant", "project") - - self.assertEqual(gen_config.build_variant, gen_config.run_build_variant) - - def test_run_build_variant_with_run_build_variant(self): - gen_config = under_test.GenerateConfig("build_variant", "project", "run_build_variant") - - self.assertNotEqual(gen_config.build_variant, gen_config.run_build_variant) - self.assertEqual(gen_config.run_build_variant, "run_build_variant") - - def test_validate_non_existing_build_variant(self): - evg_conf_mock = MagicMock() - evg_conf_mock.get_variant.return_value = None - - gen_config = under_test.GenerateConfig("build_variant", "project", "run_build_variant") - - with self.assertRaises(ValueError): - gen_config.validate(evg_conf_mock) - - def test_validate_existing_build_variant(self): - evg_conf_mock = MagicMock() - - gen_config = under_test.GenerateConfig("build_variant", "project", "run_build_variant") - gen_config.validate(evg_conf_mock) - - def test_validate_non_existing_run_build_variant(self): - evg_conf_mock = MagicMock() - - gen_config = under_test.GenerateConfig("build_variant", "project") - gen_config.validate(evg_conf_mock) - - -class TestParseAvgTestRuntime(unittest.TestCase): - def test__parse_avg_test_runtime(self): - task_avg_test_runtime_stats = [ - teststats_utils.TestRuntime(test_name="dir/test1.js", runtime=30.2), - teststats_utils.TestRuntime(test_name="dir/test2.js", runtime=455.1) - ] - result = under_test._parse_avg_test_runtime("dir/test2.js", task_avg_test_runtime_stats) - self.assertEqual(result, 455.1) - - -class TestCalculateTimeout(unittest.TestCase): - def test__calculate_timeout(self): - avg_test_runtime = 455.1 - expected_result = ceil(avg_test_runtime * under_test.AVG_TEST_TIME_MULTIPLIER) - self.assertEqual(expected_result, under_test._calculate_timeout(avg_test_runtime)) - - def test__calculate_timeout_avg_is_less_than_min(self): - avg_test_runtime = 10 - self.assertEqual(under_test.MIN_AVG_TEST_TIME_SEC, - under_test._calculate_timeout(avg_test_runtime)) - - -class TestCalculateExecTimeout(unittest.TestCase): - def test__calculate_exec_timeout(self): - repeat_config = under_test.RepeatConfig(repeat_tests_secs=600) - avg_test_runtime = 455.1 - - exec_timeout = under_test._calculate_exec_timeout(repeat_config, avg_test_runtime) - - self.assertEqual(1771, exec_timeout) - - def test_average_timeout_greater_than_execution_time(self): - repeat_config = under_test.RepeatConfig(repeat_tests_secs=600, repeat_tests_min=2) - avg_test_runtime = 750 - - exec_timeout = under_test._calculate_exec_timeout(repeat_config, avg_test_runtime) - - # The timeout needs to be greater than the number of the test * the minimum number of runs. - minimum_expected_timeout = avg_test_runtime * repeat_config.repeat_tests_min - - self.assertGreater(exec_timeout, minimum_expected_timeout) - - -class TestGenerateTimeouts(unittest.TestCase): - def test__generate_timeouts(self): - repeat_config = under_test.RepeatConfig(repeat_tests_secs=600) - runtime_stats = [teststats_utils.TestRuntime(test_name="dir/test2.js", runtime=455.1)] - test_name = "dir/test2.js" - - task_generator = under_test.BurnInGenTaskService(MagicMock(), repeat_config, runtime_stats) - timeout_info = task_generator.generate_timeouts(test_name) - - self.assertEqual(timeout_info.exec_timeout, 1771) - self.assertEqual(timeout_info.timeout, 1366) - - def test__generate_timeouts_no_results(self): - repeat_config = under_test.RepeatConfig(repeat_tests_secs=600) - runtime_stats = [] - test_name = "dir/new_test.js" - - task_generator = under_test.BurnInGenTaskService(MagicMock(), repeat_config, runtime_stats) - timeout_info = task_generator.generate_timeouts(test_name) - - self.assertIsNone(timeout_info.cmd) - - def test__generate_timeouts_avg_runtime_is_zero(self): - repeat_config = under_test.RepeatConfig(repeat_tests_secs=600) - runtime_stats = [ - teststats_utils.TestRuntime(test_name="dir/test_with_zero_runtime.js", runtime=0) - ] - test_name = "dir/test_with_zero_runtime.js" - - task_generator = under_test.BurnInGenTaskService(MagicMock(), repeat_config, runtime_stats) - timeout_info = task_generator.generate_timeouts(test_name) - - self.assertIsNone(timeout_info.cmd) - - -class TestGetTaskRuntimeHistory(unittest.TestCase): - def test_get_task_runtime_history(self): - mock_evg_api = MagicMock() - mock_evg_api.test_stats_by_project.return_value = [ - MagicMock( - test_file="dir/test2.js", - task_name="task1", - variant="variant1", - distro="distro1", - date=datetime.utcnow().date(), - num_pass=1, - num_fail=0, - avg_duration_pass=10.1, - ) - ] - analysis_duration = under_test.AVG_TEST_RUNTIME_ANALYSIS_DAYS - end_date = datetime.utcnow().replace(microsecond=0) - start_date = end_date - timedelta(days=analysis_duration) - mock_gen_config = MagicMock(project="project1", build_variant="variant1") - - executor = under_test.GenerateBurnInExecutor(mock_gen_config, MagicMock(), mock_evg_api, - history_end_date=end_date) - result = executor.get_task_runtime_history("task1") - - self.assertEqual(result, [("dir/test2.js", 10.1)]) - mock_evg_api.test_stats_by_project.assert_called_with( - "project1", after_date=start_date, before_date=end_date, group_by="test", - group_num_days=14, tasks=["task1"], variants=["variant1"]) - - def test_get_task_runtime_history_evg_degraded_mode_error(self): - mock_response = MagicMock(status_code=requests.codes.SERVICE_UNAVAILABLE) - mock_evg_api = MagicMock() - mock_evg_api.test_stats_by_project.side_effect = requests.HTTPError(response=mock_response) - mock_gen_config = MagicMock(project="project1", build_variant="variant1") - - executor = under_test.GenerateBurnInExecutor(mock_gen_config, MagicMock(), mock_evg_api) - result = executor.get_task_runtime_history("task1") - - self.assertEqual(result, []) - - -TESTS_BY_TASK = { - "task1": { - "resmoke_args": "--suites=suite1", - "tests": ["jstests/test1.js", "jstests/test2.js"]}, - "task2": { - "resmoke_args": "--suites=suite1", - "tests": ["jstests/test1.js", "jstests/test3.js"]}, - "task3": { - "resmoke_args": "--suites=suite3", - "tests": ["jstests/test4.js", "jstests/test5.js"]}, - "task4": { - "resmoke_args": "--suites=suite4", "tests": []}, -} # yapf: disable - - -def create_tests_by_task_mock(n_tasks, n_tests): - return { - f"task_{i}_gen": under_test.TaskInfo(display_task_name=f"task_{i}", resmoke_args="", tests=[ - f"jstests/tests_{j}" for j in range(n_tests) - ], require_multiversion_setup=False, distro=f"distro_{i}", build_variant="variant", - suite=f"suite_{i}") - for i in range(n_tasks) - } - - -class TestCreateGenerateTasksConfig(unittest.TestCase): - @unittest.skipIf(sys.platform.startswith("win"), "not supported on windows") - def test_no_tasks_given(self): - build_variant = BuildVariant("build variant") - gen_config = MagicMock(run_build_variant="variant") - repeat_config = MagicMock() - mock_evg_api = MagicMock() - - executor = under_test.GenerateBurnInExecutor(gen_config, repeat_config, mock_evg_api) - executor.generate_tasks_for_variant({}, build_variant) - - evg_config_dict = build_variant.as_dict() - self.assertEqual(0, len(evg_config_dict["tasks"])) - - @unittest.skipIf(sys.platform.startswith("win"), "not supported on windows") - def test_one_task_one_test(self): - n_tasks = 1 - n_tests = 1 - resmoke_options = "options for resmoke" - build_variant = BuildVariant("build variant") - gen_config = MagicMock(run_build_variant="variant", distro=None) - repeat_config = MagicMock() - repeat_config.generate_resmoke_options.return_value = resmoke_options - mock_evg_api = MagicMock() - tests_by_task = create_tests_by_task_mock(n_tasks, n_tests) - - executor = under_test.GenerateBurnInExecutor(gen_config, repeat_config, mock_evg_api) - executor.generate_tasks_for_variant(tests_by_task, build_variant) - - shrub_config = ShrubProject.empty().add_build_variant(build_variant) - evg_config_dict = shrub_config.as_dict() - tasks = evg_config_dict["tasks"] - self.assertEqual(n_tasks * n_tests, len(tasks)) - cmd = tasks[0]["commands"] - self.assertIn(resmoke_options, cmd[2]["vars"]["resmoke_args"]) - self.assertEqual("suite_0", cmd[2]["vars"]["suite"]) - self.assertIn("tests_0", cmd[2]["vars"]["resmoke_args"]) - - @unittest.skipIf(sys.platform.startswith("win"), "not supported on windows") - def test_n_task_m_test(self): - n_tasks = 3 - n_tests = 5 - build_variant = BuildVariant("build variant") - gen_config = MagicMock(run_build_variant="variant", distro=None) - repeat_config = MagicMock() - tests_by_task = create_tests_by_task_mock(n_tasks, n_tests) - mock_evg_api = MagicMock() - - executor = under_test.GenerateBurnInExecutor(gen_config, repeat_config, mock_evg_api) - executor.generate_tasks_for_variant(tests_by_task, build_variant) - - evg_config_dict = build_variant.as_dict() - self.assertEqual(n_tasks * n_tests, len(evg_config_dict["tasks"])) - - -class TestCreateGenerateTasksFile(unittest.TestCase): - @unittest.skipIf(sys.platform.startswith("win"), "not supported on windows") - @patch(ns("sys.exit")) - @patch(ns("validate_task_generation_limit")) - def test_cap_on_task_generate(self, validate_mock, exit_mock): - gen_config = MagicMock(require_multiversion_setup=False) - repeat_config = MagicMock() - tests_by_task = MagicMock() - mock_evg_api = MagicMock() - - validate_mock.return_value = False - - exit_mock.side_effect = ValueError("exiting") - with self.assertRaises(ValueError): - executor = under_test.GenerateBurnInExecutor(gen_config, repeat_config, mock_evg_api, - "gen_file.json") - executor.execute(tests_by_task) - - exit_mock.assert_called_once() - - -class TestFindChangedTests(unittest.TestCase): - def test_manual_tests_should_be_specifiable_via_env_vars(self): - mock_evg_api = MagicMock(spec_set=EvergreenApi) - mock_repo = MagicMock() - mock_env = { - "BURN_IN_TESTS": "jstests/auth/auth1.js,jstests/core/where1.js", - } - change_detector = under_test.EvergreenFileChangeDetector("task_id", mock_evg_api, mock_env) - - test_set = change_detector.find_changed_tests([mock_repo]) - - self.assertEqual(2, len(test_set)) - self.assertIn("jstests/auth/auth1.js", test_set) - self.assertIn("jstests/core/where1.js", test_set) - - def test_empty_env_should_not_add_extra_tests(self): - mock_evg_api = MagicMock(spec_set=EvergreenApi) - mock_repo = MagicMock() - mock_env = {} - change_detector = under_test.EvergreenFileChangeDetector("task_id", mock_evg_api, mock_env) - - test_set = change_detector.find_changed_tests([mock_repo]) - - self.assertEqual(set(), test_set) |