def main(expansions_file: str = "expansions.yml", output_file: str = "powercycle_tasks.json") -> None: """Generate multiple powercycle tasks to run in evergreen.""" config = make_config(expansions_file) build_variant = BuildVariant(config.build_variant) for task_name in config.task_names: if "skip_compile" in task_name: commands, task_dependency = get_skip_compile_setup_commands() else: commands, task_dependency = get_setup_commands() commands.extend([ FunctionCall("set up remote credentials", config.remote_credentials_vars), BuiltInCommand("timeout.update", config.timeout_params), FunctionCall("set up EC2 instance", config.set_up_ec2_instance_vars), FunctionCall("run powercycle test", config.run_powercycle_vars), ]) build_variant.display_task( task_name, { Task( name_generated_task(task_name, index, config.num_tasks, config.build_variant), commands, task_dependency) for index in range(config.num_tasks) }, distros=[config.distro]) shrub_project = ShrubProject.empty() shrub_project.add_build_variant(build_variant) write_file(output_file, shrub_project.json())
def create_generate_tasks_config(build_variant: BuildVariant, tests_by_task: Dict, generate_config: GenerateConfig, repeat_config: RepeatConfig, evg_api: Optional[EvergreenApi], include_gen_task: bool = True, task_prefix: str = "burn_in") -> None: # pylint: disable=too-many-arguments,too-many-locals """ Create the config for the Evergreen generate.tasks file. :param build_variant: Shrub configuration to add to. :param tests_by_task: Dictionary of tests to generate tasks for. :param generate_config: Configuration of what to generate. :param repeat_config: Configuration of how to repeat tests. :param evg_api: Evergreen API. :param include_gen_task: Should generating task be include in display task. :param task_prefix: Prefix all task names with this. """ tasks = create_generated_tasks(tests_by_task, task_prefix, generate_config, repeat_config, evg_api) existing_tasks = {ExistingTask(BURN_IN_TESTS_GEN_TASK) } if include_gen_task else None build_variant.display_task(BURN_IN_TESTS_TASK, tasks, execution_existing_tasks=existing_tasks)
def _generate_fuzzer_tasks(self, build_variant: BuildVariant, version_configs: List[str], is_sharded: bool) -> None: """ Generate fuzzer tasks and add them to the given build variant. :param build_variant: Build variant to add tasks to. :param version_configs: Version configurations to generate. :param is_sharded: Should configuration be generated for sharding. """ tasks = set() for version_config in version_configs: fuzzer_config = self._get_fuzzer_options(version_config, is_sharded) task_name = f"{fuzzer_config.name}_{version_config}" sub_tasks = gen_fuzzer.generate_fuzzer_sub_tasks( task_name, fuzzer_config) tasks = tasks.union(sub_tasks) existing_tasks = { ExistingTask(f"{self.options.suite}_multiversion_gen") } build_variant.display_task(self.task, tasks, execution_existing_tasks=existing_tasks)
def generate_tasks_for_variant(self, tests_by_task: Dict[str, TaskInfo], variant: BuildVariant): """Add tasks to the passed in variant.""" tasks = set() for task in sorted(tests_by_task): task_info = tests_by_task[task] task_history = self.get_task_runtime_history( task_info.display_task_name) gen_suite = _tests_dict_to_generated_suites( task_info, task_history) task_generator = BurnInGenTaskService(self.generate_config, self.repeat_config, task_history) params = BurnInGenTaskParams( resmoke_args=task_info.resmoke_args, require_multiversion_setup=task_info. require_multiversion_setup, distro=task_info.distro, ) new_tasks = task_generator.generate_tasks(gen_suite, params) tasks = tasks.union(new_tasks) variant.display_task( BURN_IN_TESTS_TASK, tasks, execution_existing_tasks=self._get_existing_tasks())
def _generate_evg_build_variant( source_build_variant: Variant, run_build_variant: str, bypass_build_variant: str, ) -> BuildVariant: """ Generate a shrub build variant for the given run build variant. :param source_build_variant: The build variant to base configuration on. :param run_build_variant: The build variant to generate. :param bypass_build_variant: The build variant to get compile artifacts from. :return: Shrub build variant configuration. """ display_name = f"! {source_build_variant.display_name}" run_on = source_build_variant.run_on modules = source_build_variant.modules expansions = source_build_variant.expansions expansions["burn_in_bypass"] = bypass_build_variant build_variant = BuildVariant(run_build_variant, display_name, expansions=expansions, modules=modules, run_on=run_on) build_variant.add_existing_task(ExistingTask(COMPILE_TASK)) return build_variant
def test_no_tasks_given(self): build_variant = BuildVariant("build variant") gen_config = MagicMock(run_build_variant="variant") repeat_config = MagicMock() under_test.create_generate_tasks_config(build_variant, {}, gen_config, repeat_config, None) evg_config_dict = build_variant.as_dict() self.assertEqual(0, len(evg_config_dict["tasks"]))
def create_multiversion_generate_tasks_config( tests_by_task: Dict[str, TaskInfo], evg_api: EvergreenApi, generate_config: GenerateConfig) -> BuildVariant: """ Create the multiversion config for the Evergreen generate.tasks file. :param tests_by_task: Dictionary of tests to generate tasks for. :param evg_api: Evergreen API. :param generate_config: Configuration of what to generate. :return: Shrub configuration with added tasks. """ build_variant = BuildVariant(generate_config.build_variant) tasks = set() if tests_by_task: # Get the multiversion suites that will run in as part of burn_in_multiversion. multiversion_suites = get_named_suites_with_root_level_key( MULTIVERSION_CONFIG_KEY) for suite in multiversion_suites: idx = 0 if suite["origin"] not in tests_by_task.keys(): # Only generate burn in multiversion tasks for suites that would run the detected # changed tests. continue LOGGER.debug("Generating multiversion suite", suite=suite["multiversion_name"]) # We hardcode the number of fallback sub suites and the target resmoke time here # since burn_in_tests cares about individual tests and not entire suites. The config # options here are purely used to generate the proper multiversion suites to run # tests against. config_options = { "suite": suite["origin"], "fallback_num_sub_suites": 1, "project": generate_config.project, "build_variant": generate_config.build_variant, "task_id": generate_config.task_id, "task_name": suite["multiversion_name"], "target_resmoke_time": 60, } config_options.update(gen_resmoke.DEFAULT_CONFIG_VALUES) config_generator = gen_multiversion.EvergreenMultiversionConfigGenerator( evg_api, gen_resmoke.ConfigOptions(config_options)) test_list = tests_by_task[suite["origin"]].tests for test in test_list: # Generate the multiversion tasks for each test. sub_tasks = config_generator.get_burn_in_tasks(test, idx) tasks = tasks.union(sub_tasks) idx += 1 existing_tasks = {ExistingTask(f"{BURN_IN_MULTIVERSION_TASK}_gen")} build_variant.display_task(BURN_IN_MULTIVERSION_TASK, tasks, execution_existing_tasks=existing_tasks) return build_variant
def test_fuzzer_for_large_distro_tasks_should_be_generated_on_large(self): mock_distro = "my large distro" mock_params = build_mock_fuzzer_params(use_large_distro=True, large_distro_name=mock_distro) build_variant = BuildVariant("mock build variant") service = build_mocked_service() service.gen_task_options.large_distro_name = mock_distro service.generate_fuzzer_task(mock_params, build_variant) fuzzer_config = build_variant.as_dict() self.assertTrue(all(mock_distro in task["distros"] for task in fuzzer_config["tasks"]))
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"]))
def add_config_for_build_variant(self, build_variant: BuildVariant, tests_by_task: Dict[str, TaskInfo]) -> None: """ Add configuration for generating tasks to the given build variant. :param build_variant: Build variant to update. :param tests_by_task: Tasks and tests to update. """ tasks = self.create_generated_tasks(tests_by_task) build_variant.display_task(BURN_IN_TESTS_TASK, tasks, execution_existing_tasks=self.get_existing_tasks())
def test_evg_config_does_not_fails_if_timeout_too_high_on_mainline(self): options = self.generate_mock_options() options.is_patch = False options.repeat_suites = under_test.MAX_EXPECTED_TIMEOUT suites = self.generate_mock_suites(3) build_variant = BuildVariant("variant") generator = under_test.EvergreenConfigGenerator(suites, options, MagicMock()) generator.generate_config(build_variant) config = build_variant.as_dict() self.assertEqual(len(config["tasks"]), len(suites) + 1)
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) under_test.create_generate_tasks_config(build_variant, tests_by_task, gen_config, repeat_config, None) evg_config_dict = build_variant.as_dict() self.assertEqual(n_tasks * n_tests, len(evg_config_dict["tasks"]))
def generate_config(self, build_variant: BuildVariant) -> None: """ Generate evergreen configuration. :param build_variant: Build variant to add generated configuration to. """ self.build_tasks = self.evg_api.tasks_by_build(self.options.build_id) tasks = self._generate_all_tasks() generating_task = {ExistingTask(task_name) for task_name in self.options.gen_task_set} distros = self._get_distro(build_variant.name) build_variant.display_task(self.options.display_task_name, execution_tasks=tasks, execution_existing_tasks=generating_task, distros=distros)
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"]))
def create_fuzzer_task(options: ConfigOptions, build_variant: BuildVariant) -> None: """ Generate an evergreen configuration for fuzzers and add it to the given build variant. :param options: task options. :param build_variant: Build variant to add tasks to. """ task_name = options.name sub_tasks = generate_fuzzer_sub_tasks(task_name, options) build_variant.display_task( task_name, sub_tasks, execution_existing_tasks={ExistingTask(f"{options.name}_gen")})
def test_evg_config_is_created_without_multiversion(self): build_variant = BuildVariant("build variant") options = self._create_options_mock() under_test.create_fuzzer_task(options, build_variant) shrub_project = ShrubProject.empty().add_build_variant(build_variant) config = shrub_project.as_dict() self.assertEqual(options.num_tasks, len(config["tasks"])) self.assertEqual("setup jstestfuzz", config["tasks"][0]["commands"][1]["func"]) command1 = config["tasks"][0]["commands"][2] self.assertIn(str(options.num_files), command1["vars"]["jstestfuzz_vars"]) self.assertIn(options.npm_command, command1["vars"]["npm_command"]) self.assertEqual("run jstestfuzz", command1["func"]) buildvariant = config["buildvariants"][0] self.assertEqual(options.variant, buildvariant["name"]) self.assertEqual(options.num_tasks, len(buildvariant["tasks"])) self.assertEqual( 1, len(buildvariant["display_tasks"][0]["execution_tasks"])) self.assertEqual(under_test.GEN_PARENT_TASK, buildvariant["display_tasks"][0]["name"]) self.assertIn(options.name + "_gen", buildvariant["display_tasks"][0]["execution_tasks"]) self.assertEqual( options.num_tasks, len(buildvariant["display_tasks"][1]["execution_tasks"])) self.assertEqual(options.name, buildvariant["display_tasks"][1]["name"])
def gen_lint_config(): targets = ["src", "test"] max_hosts = 5 task_group_name = "lint group" variant_name = "lint variant" def define_task(target): name = f"make-lint-{target}" return Task(name, [FunctionCall("run-make", {"target": name})]) task_group = TaskGroup( task_group_name, [define_task(target) for target in targets], max_hosts=max_hosts, setup_group=[ git_get_project("src").set_type(CommandType.SYSTEM), FunctionCall("set-up-credentials"), ], teardown_group=[ FunctionCall("attach-test-results"), FunctionCall("remove-test-results"), ], ) variant = BuildVariant(variant_name).add_task_group(task_group) project = ShrubProject({variant}) return project
def generate_fuzzer_task(self, params: FuzzerGenTaskParams, build_variant: BuildVariant) -> FuzzerTask: """ Generate evergreen configuration for the given fuzzer and add it to the build_variant. :param params: Parameters for how fuzzer should be generated. :param build_variant: Build variant to add generated configuration to. """ fuzzer_task = self.fuzzer_gen_task_service.generate_tasks(params) distros = self._get_distro(build_variant.name, params.use_large_distro, params.large_distro_name) build_variant.display_task(fuzzer_task.task_name, fuzzer_task.sub_tasks, distros=distros, activate=False) return fuzzer_task
def test_evg_config_has_timeouts_for_repeated_suites(self): options = self.generate_mock_options() options.repeat_suites = 5 suites = self.generate_mock_suites(3) build_variant = BuildVariant("variant") generator = under_test.EvergreenConfigGenerator( suites, options, MagicMock()) generator.generate_config(build_variant) shrub_project = ShrubProject.empty().add_build_variant(build_variant) config = shrub_project.as_dict() self.assertEqual(len(config["tasks"]), len(suites) + 1) command1 = config["tasks"][0]["commands"][2] self.assertIn(" --repeatSuites=5 ", command1["vars"]["resmoke_args"]) self.assertIn(options.resmoke_args, command1["vars"]["resmoke_args"]) timeout_cmd = config["tasks"][0]["commands"][0] self.assertEqual("timeout.update", timeout_cmd["command"]) expected_timeout = under_test.calculate_timeout( suites[0].max_runtime, 3) * 5 self.assertEqual(expected_timeout, timeout_cmd["params"]["timeout_secs"]) expected_exec_timeout = under_test.calculate_timeout( suites[0].get_runtime(), 3) * 5 self.assertEqual(expected_exec_timeout, timeout_cmd["params"]["exec_timeout_secs"])
def create_generate_tasks_file(tests_by_task: Dict, generate_config: GenerateConfig, repeat_config: RepeatConfig, evg_api: Optional[EvergreenApi], task_prefix: str = 'burn_in', include_gen_task: bool = True) -> str: """ Create an Evergreen generate.tasks file to run the given tasks and tests. :param tests_by_task: Dictionary of tests and tasks to run. :param generate_config: Information about how burn_in should generate tasks. :param repeat_config: Information about how burn_in should repeat tests. :param evg_api: Evergreen api. :param task_prefix: Prefix to start generated task's name with. :param include_gen_task: Should the generating task be included in the display task. :returns: Configuration to pass to 'generate.tasks'. """ build_variant = BuildVariant(generate_config.run_build_variant) create_generate_tasks_config(build_variant, tests_by_task, generate_config, repeat_config, evg_api, include_gen_task=include_gen_task, task_prefix=task_prefix) shrub_project = ShrubProject.empty() shrub_project.add_build_variant(build_variant) if not validate_task_generation_limit(shrub_project): sys.exit(1) return shrub_project.json()
def generate_task(self, generated_suite: GeneratedSuite, build_variant: BuildVariant, gen_params: ResmokeGenTaskParams) -> None: """ Generate evergreen configuration for the given suite and add it to the build_variant. :param generated_suite: Suite to add. :param build_variant: Build variant to add generated configuration to. :param gen_params: Parameters to configuration how tasks are generated. """ # self.build_tasks = self.evg_api.tasks_by_build(self.options.build_id) execution_tasks = self.resmoke_gen_task_service.generate_tasks(generated_suite, gen_params) distros = self._get_distro(build_variant.name, gen_params.use_large_distro, gen_params.large_distro_name) build_variant.display_task(generated_suite.display_task_name(), execution_tasks=execution_tasks, distros=distros)
def run(evg_api: EvergreenApi, evg_conf: EvergreenProjectConfig, selected_tests_service: SelectedTestsService, selected_tests_variant_expansions: Dict[str, str], repos: List[Repo]) -> Dict[str, str]: # pylint: disable=too-many-locals """ Run code to select tasks to run based on test and task mappings for each of the build variants. :param evg_api: Evergreen API object. :param evg_conf: Evergreen configuration. :param selected_tests_service: Selected-tests service. :param selected_tests_variant_expansions: Expansions of the selected-tests variant. :param repos: List of repos containing changed files. :return: Dict of files and file contents for generated tasks. """ config_dict_of_suites_and_tasks = {} task_id = selected_tests_variant_expansions[TASK_ID_EXPANSION] revision_map = generate_revision_map_from_manifest(repos, task_id, evg_api) changed_files = find_changed_files_in_repos(repos, revision_map) changed_files = { _remove_repo_path_prefix(file_path) for file_path in changed_files } LOGGER.info("Found changed files", files=changed_files) shrub_project = ShrubProject() for build_variant_config in evg_conf.get_required_variants(): shrub_build_variant = BuildVariant(build_variant_config.name) origin_variant_expansions = build_variant_config.expansions task_configs = _get_task_configs(evg_conf, selected_tests_service, selected_tests_variant_expansions, build_variant_config, changed_files) remove_task_configs_already_in_build( task_configs, evg_api, build_variant_config, selected_tests_variant_expansions["version_id"]) for task_config in task_configs.values(): Suite.reset_current_index() config_options = SelectedTestsConfigOptions.from_file( origin_variant_expansions, selected_tests_variant_expansions, task_config, REQUIRED_CONFIG_KEYS, DEFAULT_CONFIG_VALUES, CONFIG_FORMAT_FN, ) _update_config_with_task(evg_api, shrub_build_variant, config_options, config_dict_of_suites_and_tasks) shrub_project.add_build_variant(shrub_build_variant) config_dict_of_suites_and_tasks[ "selected_tests_config.json"] = shrub_project.json() return config_dict_of_suites_and_tasks
def generate_task_config(self, suites: List[Suite]) -> BuildVariant: """ Generate the evergreen configuration for the new suite. :param suites: The suite the generated Evergreen config will be generated for. """ build_variant = BuildVariant(self.config_options.variant) self.add_suites_to_build_variant(suites, build_variant) return build_variant
def generate_multiversion_burnin_task(self, generated_suite: GeneratedSuite, gen_params: MultiversionGenTaskParams, build_variant: BuildVariant) -> Set[Task]: """ Generate burn_in configuration for the given suite and add it to the build_variant. :param generated_suite: Suite to add. :param build_variant: Build variant to add generated configuration to. :param gen_params: Parameters to configuration how tasks are generated. :return: Set of tasks that were generated. """ tasks = self.multiversion_gen_task_service.generate_tasks(generated_suite, gen_params) distros = self._get_distro(build_variant.name, gen_params.use_large_distro, gen_params.large_distro_name) if gen_params.add_to_display_task: build_variant.display_task(generated_suite.task_name, tasks, distros=distros) else: build_variant.add_tasks(tasks, distros=distros) return tasks
def test_build_variant_without_large_distro_defined_fails(self): options = self.generate_mock_options() options.use_large_distro = "true" options.large_distro_name = None suites = self.generate_mock_suites(3) build_variant = BuildVariant("variant") generator = under_test.EvergreenConfigGenerator(suites, options, MagicMock()) with self.assertRaises(ValueError): generator.generate_config(build_variant)
def generate_multiversion_task( self, generated_suite: GeneratedSuite, build_variant: BuildVariant, gen_params: MultiversionGenTaskParams) -> None: """ Generate evergreen configuration for the given suite and add it to the build_variant. :param generated_suite: Suite to add. :param build_variant: Build variant to add generated configuration to. :param gen_params: Parameters to configuration how tasks are generated. """ execution_tasks = self.multiversion_gen_task_service.generate_tasks( generated_suite, gen_params) distros = self._get_distro(build_variant.name, gen_params.use_large_distro, gen_params.large_distro_name) build_variant.display_task(generated_suite.display_task_name(), execution_tasks=execution_tasks, distros=distros, activate=False)
def generate_evg_tasks(self, build_variant: BuildVariant) -> None: # pylint: disable=too-many-locals """ Generate evergreen tasks for multiversion tests. The number of tasks generated equals (the number of version configs) * (the number of generated suites). :param build_variant: Build variant to add generated configuration to. """ is_sharded = is_suite_sharded(TEST_SUITE_DIR, self.options.suite) version_configs = get_version_configs(is_sharded) if self.options.is_jstestfuzz: self._generate_fuzzer_tasks(build_variant, version_configs, is_sharded) return suites = self.generate_resmoke_suites() sub_tasks = set() for version_config in version_configs: idx = 0 for suite in suites: # Generate the newly divided test suites source_suite = os.path.join(CONFIG_DIR, suite.name + ".yml") sub_tasks.add( self._generate_sub_task(version_config, self.task, idx, source_suite, len(suites), is_sharded)) idx += 1 # Also generate the misc task. misc_suite_name = "{0}_misc".format(self.options.suite) misc_suite = os.path.join(CONFIG_DIR, misc_suite_name + ".yml") sub_tasks.add( self._generate_sub_task(version_config, self.task, idx, misc_suite, 1, is_sharded)) idx += 1 build_variant.display_task( self.task, sub_tasks, execution_existing_tasks={ExistingTask(f"{self.task}_gen")})
def get_build_variant(self, build_variant: str) -> BuildVariant: """ Get the build variant object, creating it if it doesn't exist. NOTE: The `lock` should be held by any functions calling this one. :param build_variant: Name of build variant. :return: BuildVariant object being created. """ if build_variant not in self.build_variants: self.build_variants[build_variant] = BuildVariant(build_variant) return self.build_variants[build_variant]
def run(self) -> None: """Generate multiversion suites that run within a specified target execution time.""" if not generate_resmoke.should_tasks_be_generated(self.evg_api, self.options.task_id): LOGGER.info("Not generating configuration due to previous successful generation.") return build_variant = BuildVariant(self.options.variant) self.generate_evg_tasks(build_variant) shrub_project = ShrubProject.empty() shrub_project.add_build_variant(build_variant) write_file_to_dir(CONFIG_DIR, f"{self.task}.json", shrub_project.json())
def test_evg_config_is_created_with_multiversion(self): build_variant = BuildVariant("build variant") options = self._create_options_mock() options.use_multiversion = "/data/multiversion" under_test.create_fuzzer_task(options, build_variant) shrub_project = ShrubProject.empty().add_build_variant(build_variant) config = shrub_project.as_dict() self.assertEqual("do multiversion setup", config["tasks"][0]["commands"][2]["func"]) self.assertEqual("/data/multiversion", config["tasks"][0]["commands"][5]["vars"]["task_path_suffix"])