mongo_task_generator/
lib.rs

1//! Entry point into the task generation logic.
2//!
3//! This code will go through the entire evergreen configuration and create task definitions
4//! for any tasks that need to be generated. It will then add references to those generated
5//! tasks to any build variants to expect to run them.
6#![cfg_attr(feature = "strict", deny(missing_docs))]
7
8use core::panic;
9use std::{
10    collections::{HashMap, HashSet},
11    path::{Path, PathBuf},
12    sync::{Arc, Mutex},
13    vec,
14};
15
16use crate::evergreen_names::{COMPILE_VARIANT, VERSION_GEN_TASK, VERSION_GEN_VARIANT};
17use anyhow::{bail, Result};
18use async_trait::async_trait;
19use evergreen::{
20    evg_config::{EvgConfigService, EvgProjectConfig},
21    evg_config_utils::{EvgConfigUtils, EvgConfigUtilsImpl},
22    evg_task_history::TaskHistoryServiceImpl,
23};
24use evergreen_names::{
25    BURN_IN_TAGS, BURN_IN_TAG_BUILD_VARIANTS, BURN_IN_TAG_COMPILE_TASK_GROUP_NAME, BURN_IN_TESTS,
26    ENTERPRISE_MODULE, GENERATOR_TASKS, LARGE_DISTRO_EXPANSION,
27};
28use evg_api_rs::EvgClient;
29use generate_sub_tasks_config::GenerateSubTasksConfig;
30use resmoke::{burn_in_proxy::BurnInProxy, resmoke_proxy::ResmokeProxy};
31use services::config_extraction::{ConfigExtractionService, ConfigExtractionServiceImpl};
32use shrub_rs::models::task::TaskDependency;
33use shrub_rs::models::{
34    project::EvgProject,
35    task::{EvgTask, TaskRef},
36    variant::{BuildVariant, DisplayTask},
37};
38use task_types::{
39    burn_in_tests::{BurnInService, BurnInServiceImpl},
40    fuzzer_tasks::{GenFuzzerService, GenFuzzerServiceImpl},
41    generated_suite::GeneratedSuite,
42    multiversion::MultiversionServiceImpl,
43    resmoke_config_writer::{ResmokeConfigActor, ResmokeConfigActorService},
44    resmoke_tasks::{GenResmokeConfig, GenResmokeTaskService, GenResmokeTaskServiceImpl},
45};
46use tracing::{event, Level};
47use utils::fs_service::FsServiceImpl;
48
49mod evergreen;
50mod evergreen_names;
51mod generate_sub_tasks_config;
52mod resmoke;
53mod services;
54mod task_types;
55mod utils;
56
57const BURN_IN_PREFIX: &str = "burn_in_tests";
58const HISTORY_LOOKBACK_DAYS: u64 = 14;
59const MAX_SUB_TASKS_PER_TASK: usize = 5;
60
61type GenTaskCollection = HashMap<String, Box<dyn GeneratedSuite>>;
62
63pub struct BurnInTagBuildVariantInfo {
64    pub compile_task_group_name: String,
65    pub compile_variant: String,
66}
67
68/// Information about the Evergreen project being run against.
69pub struct ProjectInfo {
70    /// Path to the evergreen project configuration yaml.
71    pub evg_project_location: PathBuf,
72
73    /// Evergreen project being run.
74    pub evg_project: String,
75
76    /// Path to the sub-tasks configuration file.
77    pub gen_sub_tasks_config_file: Option<PathBuf>,
78}
79
80impl ProjectInfo {
81    /// Create a new ProjectInfo struct.
82    ///
83    /// # Arguments
84    ///
85    /// * `evg_project_location` - Path to the evergreen project configuration yaml.
86    /// * `evg_project` - Evergreen project being run.
87    /// * `gen_sub_tasks_config_file` - Path to the sub-tasks configuration file.
88    ///
89    /// # Returns
90    ///
91    /// Instance of ProjectInfo with provided info.
92    pub fn new<P: AsRef<Path>>(
93        evg_project_location: P,
94        evg_project: &str,
95        gen_sub_tasks_config_file: Option<P>,
96    ) -> Self {
97        Self {
98            evg_project_location: evg_project_location.as_ref().to_path_buf(),
99            evg_project: evg_project.to_string(),
100            gen_sub_tasks_config_file: gen_sub_tasks_config_file.map(|p| p.as_ref().to_path_buf()),
101        }
102    }
103
104    /// Get the project configuration for this project.
105    pub fn get_project_config(&self) -> Result<EvgProjectConfig> {
106        Ok(EvgProjectConfig::new(&self.evg_project_location).expect("Could not find evg project"))
107    }
108
109    /// Get the generate sub-task configuration for this project.
110    pub fn get_generate_sub_tasks_config(&self) -> Result<Option<GenerateSubTasksConfig>> {
111        if let Some(gen_sub_tasks_config_file) = &self.gen_sub_tasks_config_file {
112            Ok(Some(GenerateSubTasksConfig::from_yaml_file(
113                gen_sub_tasks_config_file,
114            )?))
115        } else {
116            Ok(None)
117        }
118    }
119}
120
121/// Configuration required to execute generating tasks.
122pub struct ExecutionConfiguration<'a> {
123    /// Information about the project being generated under.
124    pub project_info: &'a ProjectInfo,
125    /// Path to the evergreen API authentication file.
126    pub evg_auth_file: &'a Path,
127    /// Should task splitting use the fallback method by default.
128    pub use_task_split_fallback: bool,
129    /// Command to execute resmoke.
130    pub resmoke_command: &'a str,
131    /// Directory to place generated configuration files.
132    pub target_directory: &'a Path,
133    /// Task generating the configuration.
134    pub generating_task: &'a str,
135    /// Location in S3 where generated configuration will be uploaded.
136    pub config_location: &'a str,
137    /// Should burn_in tasks be generated.
138    pub gen_burn_in: bool,
139    /// Command to execute burn_in_tests.
140    pub burn_in_tests_command: &'a str,
141}
142
143/// Collection of services needed to execution.
144#[derive(Clone)]
145pub struct Dependencies {
146    evg_config_utils: Arc<dyn EvgConfigUtils>,
147    gen_task_service: Arc<dyn GenerateTasksService>,
148    resmoke_config_actor: Arc<tokio::sync::Mutex<dyn ResmokeConfigActor>>,
149    burn_in_service: Arc<dyn BurnInService>,
150}
151
152impl Dependencies {
153    /// Create a new set of dependency instances.
154    ///
155    /// # Arguments
156    ///
157    /// * `execution_config` - Information about how generation to take place.
158    ///
159    /// # Returns
160    ///
161    /// A set of dependencies to run against.
162    pub fn new(execution_config: ExecutionConfiguration) -> Result<Self> {
163        let fs_service = Arc::new(FsServiceImpl::new());
164        let discovery_service = Arc::new(ResmokeProxy::new(execution_config.resmoke_command));
165        let multiversion_service =
166            Arc::new(MultiversionServiceImpl::new(discovery_service.clone())?);
167        let evg_config_service = Arc::new(execution_config.project_info.get_project_config()?);
168        let evg_config_utils = Arc::new(EvgConfigUtilsImpl::new());
169        let gen_fuzzer_service = Arc::new(GenFuzzerServiceImpl::new(multiversion_service.clone()));
170        let config_extraction_service = Arc::new(ConfigExtractionServiceImpl::new(
171            evg_config_utils.clone(),
172            execution_config.generating_task.to_string(),
173            execution_config.config_location.to_string(),
174        ));
175        let evg_client = Arc::new(
176            EvgClient::from_file(execution_config.evg_auth_file)
177                .expect("Cannot find evergreen auth file"),
178        );
179        let task_history_service = Arc::new(TaskHistoryServiceImpl::new(
180            evg_client,
181            HISTORY_LOOKBACK_DAYS,
182            execution_config.project_info.evg_project.clone(),
183        ));
184        let resmoke_config_actor =
185            Arc::new(tokio::sync::Mutex::new(ResmokeConfigActorService::new(
186                discovery_service.clone(),
187                fs_service.clone(),
188                execution_config
189                    .target_directory
190                    .to_str()
191                    .expect("Unexpected target directory"),
192                32,
193            )));
194        let enterprise_dir = evg_config_service.get_module_dir(ENTERPRISE_MODULE);
195        let gen_resmoke_config = GenResmokeConfig::new(
196            MAX_SUB_TASKS_PER_TASK,
197            execution_config.use_task_split_fallback,
198            enterprise_dir,
199        );
200        let gen_resmoke_task_service = Arc::new(GenResmokeTaskServiceImpl::new(
201            task_history_service,
202            discovery_service,
203            resmoke_config_actor.clone(),
204            multiversion_service.clone(),
205            fs_service,
206            gen_resmoke_config,
207        ));
208        let gen_sub_tasks_config = execution_config
209            .project_info
210            .get_generate_sub_tasks_config()?;
211        let gen_task_service = Arc::new(GenerateTasksServiceImpl::new(
212            evg_config_service,
213            evg_config_utils.clone(),
214            gen_fuzzer_service,
215            gen_resmoke_task_service.clone(),
216            config_extraction_service.clone(),
217            gen_sub_tasks_config,
218            execution_config.gen_burn_in,
219        ));
220
221        let burn_in_discovery = Arc::new(BurnInProxy::new(execution_config.burn_in_tests_command));
222        let burn_in_service = Arc::new(BurnInServiceImpl::new(
223            burn_in_discovery,
224            gen_resmoke_task_service,
225            config_extraction_service,
226            multiversion_service,
227        ));
228
229        Ok(Self {
230            evg_config_utils,
231            gen_task_service,
232            resmoke_config_actor,
233            burn_in_service,
234        })
235    }
236}
237
238/// A container for configuration generated for a build variant.
239#[derive(Debug, Clone)]
240struct GeneratedConfig {
241    /// References to generated tasks that should be included.
242    pub gen_task_specs: Vec<TaskRef>,
243    /// Display tasks that should be created.
244    pub display_tasks: Vec<DisplayTask>,
245}
246
247impl GeneratedConfig {
248    /// Create an empty instance of generated configuration.
249    pub fn new() -> Self {
250        Self {
251            gen_task_specs: vec![],
252            display_tasks: vec![],
253        }
254    }
255}
256
257/// Create 'generate.tasks' configuration for all generated tasks in the provided evergreen
258/// project configuration.
259///
260/// # Arguments
261///
262/// * `deps` - Dependencies needed to perform generation.
263/// * `target_directory` - Directory to store generated configuration.
264pub async fn generate_configuration(deps: &Dependencies, target_directory: &Path) -> Result<()> {
265    let generate_tasks_service = deps.gen_task_service.clone();
266    std::fs::create_dir_all(target_directory)?;
267
268    // We are going to do 2 passes through the project build variants. In this first pass, we
269    // are actually going to create all the generated tasks that we discover.
270    let generated_tasks = generate_tasks_service.build_generated_tasks(deps).await?;
271
272    // Now that we have generated all the tasks we want to make another pass through all the
273    // build variants and add references to the generated tasks that each build variant includes.
274    let generated_build_variants =
275        generate_tasks_service.generate_build_variants(deps, generated_tasks.clone())?;
276
277    let task_defs: Vec<EvgTask> = {
278        let generated_tasks = generated_tasks.lock().unwrap();
279        generated_tasks
280            .values()
281            .flat_map(|g| g.sub_tasks())
282            .collect()
283    };
284
285    let gen_evg_project = EvgProject {
286        buildvariants: generated_build_variants.to_vec(),
287        tasks: task_defs,
288        ..Default::default()
289    };
290
291    let mut config_file = target_directory.to_path_buf();
292    config_file.push("evergreen_config.json");
293    std::fs::write(config_file, serde_json::to_string_pretty(&gen_evg_project)?)?;
294    let mut resmoke_config_actor = deps.resmoke_config_actor.lock().await;
295    let failures = resmoke_config_actor.flush().await?;
296    if !failures.is_empty() {
297        bail!(format!(
298            "Encountered errors writing resmoke configuration files: {:?}",
299            failures
300        ));
301    }
302    Ok(())
303}
304
305/// A service for generating tasks.
306#[async_trait]
307trait GenerateTasksService: Sync + Send {
308    /// Build task definition for all tasks
309    ///
310    /// # Arguments
311    ///
312    /// * `deps` - Service dependencies.
313    ///
314    /// Returns
315    ///
316    /// Map of task names to generated task definitions.
317    async fn build_generated_tasks(
318        &self,
319        deps: &Dependencies,
320    ) -> Result<Arc<Mutex<GenTaskCollection>>>;
321
322    /// Create build variants definitions containing all the generated tasks for each build variant.
323    ///
324    /// # Arguments
325    ///
326    /// * `deps` - Service dependencies.
327    /// * `generated_tasks` - Map of task names and their generated configuration.
328    ///
329    /// # Returns
330    ///
331    /// Vector of shrub build variants with generated task information.
332    fn generate_build_variants(
333        &self,
334        deps: &Dependencies,
335        generated_tasks: Arc<Mutex<GenTaskCollection>>,
336    ) -> Result<Vec<BuildVariant>>;
337
338    /// Generate the burn_in build variant information for a build variant.
339    ///
340    /// # Arguments
341    ///
342    /// * `burn_in_tag_build_variant_info` - A map of burn_in build variants to config information about them.
343    /// * `build_variant` - The original build variant to generate burn_in information from.
344    /// * `build_variant_map` - A map of build variant names to their definitions.
345    ///
346    /// # Returns
347    ///
348    /// Nothing, modifies the burn_in_tag_build_variant_info with new values.
349    fn generate_burn_in_build_variant_info(
350        &self,
351        burn_in_tag_build_variant_info: &mut HashMap<String, BurnInTagBuildVariantInfo>,
352        build_variant: &BuildVariant,
353        build_variant_map: &HashMap<String, &BuildVariant>,
354    );
355
356    /// Generate a task for the given task definition.
357    ///
358    /// # Arguments
359    ///
360    /// * `task_def` - Task definition to base generated task on.
361    /// * `build_variant` - Build Variant to base generated task on.
362    ///
363    /// # Returns
364    ///
365    /// Configuration for a generated task.
366    async fn generate_task(
367        &self,
368        task_def: &EvgTask,
369        build_variant: &BuildVariant,
370    ) -> Result<Option<Box<dyn GeneratedSuite>>>;
371}
372
373struct GenerateTasksServiceImpl {
374    evg_config_service: Arc<dyn EvgConfigService>,
375    evg_config_utils: Arc<dyn EvgConfigUtils>,
376    gen_fuzzer_service: Arc<dyn GenFuzzerService>,
377    gen_resmoke_service: Arc<dyn GenResmokeTaskService>,
378    config_extraction_service: Arc<dyn ConfigExtractionService>,
379    gen_sub_tasks_config: Option<GenerateSubTasksConfig>,
380    gen_burn_in: bool,
381}
382
383impl GenerateTasksServiceImpl {
384    /// Create an instance of GenerateTasksServiceImpl.
385    ///
386    /// # Arguments
387    ///
388    /// * `evg_config_service` - Service to work with evergreen project configuration.
389    /// * `evg_config_utils` - Utilities to work with evergreen project configuration.
390    /// * `gen_fuzzer_service` - Service to generate fuzzer tasks.
391    /// * `gen_resmoke_service` - Service for generating resmoke tasks.
392    /// * `config_extraction_service` - Service to extraction configuration from evergreen config.
393    /// * `gen_sub_tasks_config` - Configuration for generating sub-tasks.
394    pub fn new(
395        evg_config_service: Arc<dyn EvgConfigService>,
396        evg_config_utils: Arc<dyn EvgConfigUtils>,
397        gen_fuzzer_service: Arc<dyn GenFuzzerService>,
398        gen_resmoke_service: Arc<dyn GenResmokeTaskService>,
399        config_extraction_service: Arc<dyn ConfigExtractionService>,
400        gen_sub_tasks_config: Option<GenerateSubTasksConfig>,
401        gen_burn_in: bool,
402    ) -> Self {
403        Self {
404            evg_config_service,
405            evg_config_utils,
406            gen_fuzzer_service,
407            gen_resmoke_service,
408            config_extraction_service,
409            gen_sub_tasks_config,
410            gen_burn_in,
411        }
412    }
413
414    /// Determine which distro the given sub-tasks should run on.
415    ///
416    /// By default, we won't specify a distro and they will just use the default for the build
417    /// variant. If they specify `use_large_distro` then we should instead use the large distro
418    /// configured for the build variant. If that is not defined, then throw an error unless
419    /// the build variant is configured to be ignored.
420    ///
421    /// # Arguments
422    ///
423    /// * `large_distro_name` - Name
424    fn determine_distro(
425        &self,
426        large_distro_name: &Option<String>,
427        generated_task: &dyn GeneratedSuite,
428        build_variant_name: &str,
429    ) -> Result<Option<String>> {
430        if generated_task.use_large_distro() {
431            if large_distro_name.is_some() {
432                return Ok(large_distro_name.clone());
433            }
434
435            if let Some(gen_task_config) = &self.gen_sub_tasks_config {
436                if gen_task_config.ignore_missing_large_distro(build_variant_name) {
437                    return Ok(None);
438                }
439            }
440
441            bail!(
442                r#"
443***************************************************************************************
444It appears we are trying to generate a task marked as requiring a large distro, but the
445build variant has not specified a large build variant. In order to resolve this error,
446you need to:
447
448(1) add a 'large_distro_name' expansion to this build variant ('{build_variant_name}').
449
450-- or --
451
452(2) add this build variant ('{build_variant_name}') to the 'build_variant_large_distro_exception'
453list in the 'etc/generate_subtasks_config.yml' file.
454***************************************************************************************
455"#
456            );
457        }
458
459        Ok(None)
460    }
461}
462
463/// An implementation of GeneratorTasksService.
464#[async_trait]
465impl GenerateTasksService for GenerateTasksServiceImpl {
466    /// Build task definition for all tasks
467    ///
468    /// # Arguments
469    ///
470    /// * `deps` - Service dependencies.
471    ///
472    /// Returns
473    ///
474    /// Map of task names to generated task definitions.
475    async fn build_generated_tasks(
476        &self,
477        deps: &Dependencies,
478    ) -> Result<Arc<Mutex<GenTaskCollection>>> {
479        let build_variant_list = self.evg_config_service.sort_build_variants_by_required();
480        let build_variant_map = self.evg_config_service.get_build_variant_map();
481        let task_map = Arc::new(self.evg_config_service.get_task_def_map());
482
483        let mut thread_handles = vec![];
484
485        let generated_tasks = Arc::new(Mutex::new(HashMap::new()));
486        let mut seen_tasks = HashSet::new();
487        for build_variant in &build_variant_list {
488            let build_variant = build_variant_map.get(build_variant).unwrap();
489            let is_enterprise = self
490                .evg_config_utils
491                .is_enterprise_build_variant(build_variant);
492            let platform = self
493                .evg_config_utils
494                .infer_build_variant_platform(build_variant);
495            for task in &build_variant.tasks {
496                // Burn in tasks could be different for each build variant, so we will always
497                // handle them.
498                if self.gen_burn_in {
499                    if task.name == BURN_IN_TESTS {
500                        thread_handles.push(create_burn_in_worker(
501                            deps,
502                            task_map.clone(),
503                            build_variant,
504                            build_variant.name.clone(),
505                            generated_tasks.clone(),
506                        ));
507                    }
508
509                    if task.name == BURN_IN_TAGS {
510                        for base_bv_name in self
511                            .evg_config_utils
512                            .lookup_and_split_by_whitespace_build_variant_expansion(
513                                BURN_IN_TAG_BUILD_VARIANTS,
514                                build_variant,
515                            )
516                        {
517                            let base_build_variant = build_variant_map.get(&base_bv_name).unwrap();
518                            let run_build_variant_name =
519                                format!("{}-required", base_build_variant.name);
520                            thread_handles.push(create_burn_in_worker(
521                                deps,
522                                task_map.clone(),
523                                base_build_variant,
524                                run_build_variant_name,
525                                generated_tasks.clone(),
526                            ));
527                        }
528                    }
529
530                    continue;
531                }
532
533                if task.name == BURN_IN_TESTS || task.name == BURN_IN_TAGS {
534                    continue;
535                }
536
537                // Skip tasks that have already been seen.
538                let task_name = lookup_task_name(is_enterprise, &task.name, &platform);
539                if seen_tasks.contains(&task_name) {
540                    continue;
541                }
542
543                seen_tasks.insert(task_name);
544                if let Some(task_def) = task_map.get(&task.name) {
545                    if self.evg_config_utils.is_task_generated(task_def) {
546                        // Spawn off a tokio task to do the actual generation work.
547                        thread_handles.push(create_task_worker(
548                            deps,
549                            task_def,
550                            build_variant,
551                            generated_tasks.clone(),
552                        ));
553                    }
554                }
555            }
556        }
557
558        for handle in thread_handles {
559            handle.await.unwrap();
560        }
561
562        Ok(generated_tasks)
563    }
564
565    /// Generate a task for the given task definition.
566    ///
567    /// # Arguments
568    ///
569    /// * `task_def` - Task definition to base generated task on.
570    /// * `build_variant` - Build Variant to base generated task on.
571    ///
572    /// # Returns
573    ///
574    /// Configuration for a generated task.
575    async fn generate_task(
576        &self,
577        task_def: &EvgTask,
578        build_variant: &BuildVariant,
579    ) -> Result<Option<Box<dyn GeneratedSuite>>> {
580        let generated_task = if self.evg_config_utils.is_task_fuzzer(task_def) {
581            event!(Level::INFO, "Generating fuzzer: {}", task_def.name);
582
583            let params = self
584                .config_extraction_service
585                .task_def_to_fuzzer_params(task_def, build_variant)?;
586
587            Some(self.gen_fuzzer_service.generate_fuzzer_task(&params)?)
588        } else {
589            let is_enterprise = self
590                .evg_config_utils
591                .is_enterprise_build_variant(build_variant);
592            let platform = self
593                .evg_config_utils
594                .infer_build_variant_platform(build_variant);
595            event!(
596                Level::INFO,
597                "Generating resmoke task: {}, is_enterprise: {}, platform: {}",
598                task_def.name,
599                is_enterprise,
600                platform
601            );
602            let params = self.config_extraction_service.task_def_to_resmoke_params(
603                task_def,
604                is_enterprise,
605                Some(platform),
606            )?;
607            Some(
608                self.gen_resmoke_service
609                    .generate_resmoke_task(&params, &build_variant.name)
610                    .await?,
611            )
612        };
613
614        Ok(generated_task)
615    }
616
617    /// Generate the burn_in build variant information for a build variant.
618    ///
619    /// # Arguments
620    ///
621    /// * `burn_in_tag_build_variant_info` - A map of burn_in build variants to config information about them.
622    /// * `build_variant` - The original build variant to generate burn_in information from.
623    /// * `build_variant_map` - A map of build variant names to their definitions.
624    ///
625    /// # Returns
626    ///
627    /// Nothing, modifies the burn_in_tag_build_variant_info with new values.
628    fn generate_burn_in_build_variant_info(
629        &self,
630        burn_in_tag_build_variant_info: &mut HashMap<String, BurnInTagBuildVariantInfo>,
631        build_variant: &BuildVariant,
632        build_variant_map: &HashMap<String, &BuildVariant>,
633    ) {
634        let burn_in_tag_build_variants = self
635            .evg_config_utils
636            .lookup_and_split_by_whitespace_build_variant_expansion(
637                BURN_IN_TAG_BUILD_VARIANTS,
638                build_variant,
639            );
640        if burn_in_tag_build_variants.is_empty() {
641            panic!(
642            "`{}` build variant is either missing or has an empty list for the `{}` expansion. Set the expansion in your project's config to run {}.",
643            build_variant.name, BURN_IN_TAG_BUILD_VARIANTS, BURN_IN_TAGS
644        )
645        }
646
647        let compile_variant = self
648            .evg_config_utils
649            .lookup_build_variant_expansion(COMPILE_VARIANT, build_variant)
650            .unwrap_or_else(|| build_variant.name.clone());
651
652        let compile_task_group_name = self
653        .evg_config_utils
654        .lookup_build_variant_expansion(
655            BURN_IN_TAG_COMPILE_TASK_GROUP_NAME,
656            build_variant,
657        ).unwrap_or_else(|| {
658            panic!(
659                "`{}` build variant is missing the `{}` expansion to run `{}`. Set the expansion in your project's config to continue.",
660                build_variant.name, BURN_IN_TAG_COMPILE_TASK_GROUP_NAME, BURN_IN_TAGS
661            )
662        });
663        for variant in burn_in_tag_build_variants {
664            if !build_variant_map.contains_key(&variant) {
665                panic!("`{}` is trying to create a build variant that does not exist: {}. Check the {} expansion in this variant.",
666                build_variant.name, variant, BURN_IN_TAG_BUILD_VARIANTS)
667            }
668            let bv_info = burn_in_tag_build_variant_info
669                .entry(variant.clone())
670                .or_insert(BurnInTagBuildVariantInfo {
671                    compile_task_group_name: compile_task_group_name.clone(),
672                    compile_variant: compile_variant.clone(),
673                });
674            if bv_info.compile_task_group_name != compile_task_group_name {
675                panic!(
676                    "`{}` is trying to set a different compile task group name than already exists for `{}`. Check the `{}` expansions in your config.",
677                build_variant.name, variant, BURN_IN_TAG_COMPILE_TASK_GROUP_NAME
678            )
679            }
680            if bv_info.compile_variant != compile_variant {
681                panic!(
682                    "`{}` is trying to set a different compile variant than already exists for `{}`. Check the `{}` expansions in your config.",
683                    build_variant.name, variant, COMPILE_VARIANT
684                )
685            }
686        }
687    }
688
689    /// Create build variants definitions containing all the generated tasks for each build variant.
690    ///
691    /// # Arguments
692    ///
693    /// * `deps` - Service dependencies.
694    /// * `generated_tasks` - Map of task names and their generated configuration.
695    ///
696    /// # Returns
697    ///
698    /// Vector of shrub build variants with generated task information.
699    fn generate_build_variants(
700        &self,
701        deps: &Dependencies,
702        generated_tasks: Arc<Mutex<GenTaskCollection>>,
703    ) -> Result<Vec<BuildVariant>> {
704        let mut generated_build_variants = vec![];
705        let mut burn_in_tag_build_variant_info: HashMap<String, BurnInTagBuildVariantInfo> =
706            HashMap::new();
707
708        let build_variant_map = self.evg_config_service.get_build_variant_map();
709        for (bv_name, build_variant) in &build_variant_map {
710            let is_enterprise = self
711                .evg_config_utils
712                .is_enterprise_build_variant(build_variant);
713            let platform = self
714                .evg_config_utils
715                .infer_build_variant_platform(build_variant);
716            let mut gen_config = GeneratedConfig::new();
717            let mut generating_tasks = vec![];
718            let large_distro_name = self
719                .evg_config_utils
720                .lookup_build_variant_expansion(LARGE_DISTRO_EXPANSION, build_variant);
721            for task in &build_variant.tasks {
722                if task.name == BURN_IN_TAGS {
723                    if self.gen_burn_in {
724                        self.generate_burn_in_build_variant_info(
725                            &mut burn_in_tag_build_variant_info,
726                            build_variant,
727                            &build_variant_map,
728                        )
729                    }
730                    continue;
731                }
732
733                let generated_tasks = generated_tasks.lock().unwrap();
734
735                let task_name = if task.name == BURN_IN_TESTS {
736                    format!("{}-{}", BURN_IN_PREFIX, build_variant.name)
737                } else {
738                    lookup_task_name(is_enterprise, &task.name, &platform)
739                };
740
741                if let Some(generated_task) = generated_tasks.get(&task_name) {
742                    let distro = self.determine_distro(
743                        &large_distro_name,
744                        generated_task.as_ref(),
745                        bv_name,
746                    )?;
747
748                    generating_tasks.push(&task.name);
749                    gen_config
750                        .display_tasks
751                        .push(generated_task.build_display_task());
752                    gen_config
753                        .gen_task_specs
754                        .extend(generated_task.build_task_ref(distro));
755                }
756            }
757
758            if !generating_tasks.is_empty() {
759                // Put all the "_gen" tasks into a display task to hide them from view.
760                gen_config.display_tasks.push(DisplayTask {
761                    name: GENERATOR_TASKS.to_string(),
762                    execution_tasks: generating_tasks
763                        .into_iter()
764                        .map(|s| s.to_string())
765                        .collect(),
766                });
767
768                let gen_build_variant = BuildVariant {
769                    name: build_variant.name.clone(),
770                    tasks: gen_config.gen_task_specs.clone(),
771                    display_tasks: Some(gen_config.display_tasks.clone()),
772                    activate: Some(false),
773                    ..Default::default()
774                };
775                generated_build_variants.push(gen_build_variant);
776            }
777        }
778
779        for (base_bv_name, bv_info) in burn_in_tag_build_variant_info {
780            let generated_tasks = generated_tasks.lock().unwrap();
781            let base_build_variant = build_variant_map.get(&base_bv_name).unwrap();
782            let run_build_variant_name = format!("{}-required", base_build_variant.name);
783            let task_name = format!("{}-{}", BURN_IN_PREFIX, run_build_variant_name);
784            let variant_task_dependencies = vec![
785                TaskDependency {
786                    name: bv_info.compile_task_group_name,
787                    variant: Some(bv_info.compile_variant.clone()),
788                },
789                TaskDependency {
790                    name: VERSION_GEN_TASK.to_string(),
791                    variant: Some(VERSION_GEN_VARIANT.to_string()),
792                },
793            ];
794
795            if let Some(generated_task) = generated_tasks.get(&task_name) {
796                generated_build_variants.push(
797                    deps.burn_in_service.generate_burn_in_tags_build_variant(
798                        base_build_variant,
799                        bv_info.compile_variant,
800                        run_build_variant_name,
801                        generated_task.as_ref(),
802                        &variant_task_dependencies,
803                    ),
804                );
805            }
806        }
807
808        Ok(generated_build_variants)
809    }
810}
811
812/// Determine the task name to use.
813///
814/// We append "enterprise" to tasks run on enterprise module build variants, so they don't
815/// conflict with the normal tasks.
816///
817/// # Arguments
818///
819/// * `is_enterprise` - Whether the task is for an enterprise build variant.
820/// * `task` - Name of task.
821/// * `platform` - Platform that task will run on.
822///
823/// # Returns
824///
825/// Name to use for task.
826fn lookup_task_name(is_enterprise: bool, task_name: &str, platform: &str) -> String {
827    if is_enterprise {
828        format!("{}-{}-{}", task_name, platform, ENTERPRISE_MODULE)
829    } else {
830        format!("{}-{}", task_name, platform)
831    }
832}
833
834/// Spawn a tokio task to perform the task generation work.
835///
836/// # Arguments
837///
838/// * `deps` - Service dependencies.
839/// * `task_def` - Evergreen task definition to base generated task off.
840/// * `build_variant` - Build variant to query timing information from.
841/// * `generated_tasks` - Map to stored generated to in.
842///
843/// # Returns
844///
845/// Handle to created tokio worker.
846fn create_task_worker(
847    deps: &Dependencies,
848    task_def: &EvgTask,
849    build_variant: &BuildVariant,
850    generated_tasks: Arc<Mutex<GenTaskCollection>>,
851) -> tokio::task::JoinHandle<()> {
852    let generate_task_service = deps.gen_task_service.clone();
853    let evg_config_utils = deps.evg_config_utils.clone();
854    let task_def = task_def.clone();
855    let build_variant = build_variant.clone();
856    let generated_tasks = generated_tasks.clone();
857
858    tokio::spawn(async move {
859        let generated_task = generate_task_service
860            .generate_task(&task_def, &build_variant)
861            .await
862            .unwrap();
863
864        let is_enterprise = evg_config_utils.is_enterprise_build_variant(&build_variant);
865        let platform = evg_config_utils.infer_build_variant_platform(&build_variant);
866        let task_name = lookup_task_name(is_enterprise, &task_def.name, &platform);
867
868        if let Some(generated_task) = generated_task {
869            let mut generated_tasks = generated_tasks.lock().unwrap();
870            generated_tasks.insert(task_name, generated_task);
871        }
872    })
873}
874
875/// Spawn a tokio task to perform the burn_in_test generation work.
876///
877/// # Arguments
878///
879/// * `deps` - Service dependencies.
880/// * `task_map` - Map of task definitions in evergreen project configuration.
881/// * `build_variant` - Build variant to query timing information from.
882/// * `run_build_variant_name` - Build variant name to run burn_in_tests task on.
883/// * `generated_tasks` - Map to stored generated tasks in.
884///
885/// # Returns
886///
887/// Handle to created tokio worker.
888fn create_burn_in_worker(
889    deps: &Dependencies,
890    task_map: Arc<HashMap<String, EvgTask>>,
891    build_variant: &BuildVariant,
892    run_build_variant_name: String,
893    generated_tasks: Arc<Mutex<GenTaskCollection>>,
894) -> tokio::task::JoinHandle<()> {
895    let burn_in_service = deps.burn_in_service.clone();
896    let build_variant = build_variant.clone();
897    let generated_tasks = generated_tasks.clone();
898
899    tokio::spawn(async move {
900        let generated_task = burn_in_service
901            .generate_burn_in_suite(&build_variant.name, &run_build_variant_name, task_map)
902            .unwrap();
903
904        let task_name = format!("{}-{}", BURN_IN_PREFIX, run_build_variant_name);
905
906        if !generated_task.sub_tasks().is_empty() {
907            let mut generated_tasks = generated_tasks.lock().unwrap();
908            generated_tasks.insert(task_name, generated_task);
909        }
910    })
911}
912
913#[cfg(test)]
914mod tests {
915    use maplit::hashset;
916    use rstest::rstest;
917
918    use crate::{
919        resmoke::burn_in_proxy::{BurnInDiscovery, DiscoveredTask},
920        task_types::{
921            fuzzer_tasks::FuzzerGenTaskParams,
922            multiversion::{MultiversionIterator, MultiversionService},
923            resmoke_tasks::{
924                GeneratedResmokeSuite, ResmokeGenParams, ResmokeSuiteGenerationInfo, SubSuite,
925            },
926        },
927    };
928
929    use super::*;
930
931    struct MockConfigService {}
932    impl EvgConfigService for MockConfigService {
933        fn get_build_variant_map(&self) -> HashMap<String, &BuildVariant> {
934            todo!()
935        }
936
937        fn get_task_def_map(&self) -> HashMap<String, EvgTask> {
938            todo!()
939        }
940
941        fn sort_build_variants_by_required(&self) -> Vec<String> {
942            todo!()
943        }
944
945        fn get_module_dir(&self, _module_name: &str) -> Option<String> {
946            todo!()
947        }
948    }
949
950    struct MockGenFuzzerService {}
951    impl GenFuzzerService for MockGenFuzzerService {
952        fn generate_fuzzer_task(
953            &self,
954            _params: &FuzzerGenTaskParams,
955        ) -> Result<Box<dyn GeneratedSuite>> {
956            todo!()
957        }
958    }
959    struct MockGenResmokeTasksService {}
960    #[async_trait]
961    impl GenResmokeTaskService for MockGenResmokeTasksService {
962        async fn generate_resmoke_task(
963            &self,
964            _params: &ResmokeGenParams,
965            _build_variant: &str,
966        ) -> Result<Box<dyn GeneratedSuite>> {
967            todo!()
968        }
969
970        fn build_resmoke_sub_task(
971            &self,
972            _sub_suite: &SubSuite,
973            _total_sub_suites: usize,
974            _params: &ResmokeGenParams,
975            _suite_override: Option<String>,
976        ) -> EvgTask {
977            todo!()
978        }
979    }
980
981    fn build_mock_generate_tasks_service() -> GenerateTasksServiceImpl {
982        let evg_config_utils = Arc::new(EvgConfigUtilsImpl::new());
983        GenerateTasksServiceImpl::new(
984            Arc::new(MockConfigService {}),
985            evg_config_utils.clone(),
986            Arc::new(MockGenFuzzerService {}),
987            Arc::new(MockGenResmokeTasksService {}),
988            Arc::new(ConfigExtractionServiceImpl::new(
989                evg_config_utils,
990                "generating_task".to_string(),
991                "config_location".to_string(),
992            )),
993            None,
994            false,
995        )
996    }
997
998    // Tests for determine_distro.
999    #[rstest]
1000    #[case(false, None, None)]
1001    #[case(false, Some("large_distro".to_string()), None)]
1002    fn test_valid_determine_distros_should_work(
1003        #[case] use_large_distro: bool,
1004        #[case] large_distro_name: Option<String>,
1005        #[case] expected_distro: Option<String>,
1006    ) {
1007        let gen_task_service = build_mock_generate_tasks_service();
1008        let generated_task = Box::new(task_types::resmoke_tasks::GeneratedResmokeSuite {
1009            task_name: "my task".to_string(),
1010            sub_suites: vec![],
1011            use_large_distro,
1012        });
1013
1014        let distro = gen_task_service
1015            .determine_distro(
1016                &large_distro_name,
1017                generated_task.as_ref(),
1018                "my_build_variant",
1019            )
1020            .unwrap();
1021
1022        assert_eq!(distro, expected_distro);
1023    }
1024
1025    #[test]
1026    fn test_determine_distros_should_fail_if_no_large_distro() {
1027        let gen_task_service = build_mock_generate_tasks_service();
1028        let generated_task = Box::new(task_types::resmoke_tasks::GeneratedResmokeSuite {
1029            task_name: "my task".to_string(),
1030            sub_suites: vec![],
1031            use_large_distro: true,
1032        });
1033
1034        let distro =
1035            gen_task_service.determine_distro(&None, generated_task.as_ref(), "my_build_variant");
1036
1037        assert!(distro.is_err());
1038    }
1039
1040    #[test]
1041    fn test_determine_distros_should_no_large_distro_can_be_ignored() {
1042        let mut gen_task_service = build_mock_generate_tasks_service();
1043        gen_task_service.gen_sub_tasks_config = Some(GenerateSubTasksConfig {
1044            build_variant_large_distro_exceptions: hashset! {
1045                "build_variant_0".to_string(),
1046                "my_build_variant".to_string(),
1047                "build_variant_1".to_string(),
1048            },
1049        });
1050        let generated_task = Box::new(task_types::resmoke_tasks::GeneratedResmokeSuite {
1051            task_name: "my task".to_string(),
1052            sub_suites: vec![],
1053            use_large_distro: true,
1054        });
1055
1056        let distro =
1057            gen_task_service.determine_distro(&None, generated_task.as_ref(), "my_build_variant");
1058
1059        assert!(distro.is_ok());
1060    }
1061
1062    // tests for lookup_task_name.
1063    #[rstest]
1064    #[case(false, "my_task", "my_platform", "my_task-my_platform")]
1065    #[case(true, "my_task", "my_platform", "my_task-my_platform-enterprise")]
1066    fn test_lookup_task_name_should_use_enterprise_when_specified(
1067        #[case] is_enterprise: bool,
1068        #[case] task_name: &str,
1069        #[case] platform: &str,
1070        #[case] expected_task_name: &str,
1071    ) {
1072        assert_eq!(
1073            lookup_task_name(is_enterprise, task_name, platform),
1074            expected_task_name.to_string()
1075        );
1076    }
1077
1078    struct MockEvgConfigUtils {}
1079    impl EvgConfigUtils for MockEvgConfigUtils {
1080        fn is_task_generated(&self, _task: &EvgTask) -> bool {
1081            todo!()
1082        }
1083
1084        fn is_task_fuzzer(&self, _task: &EvgTask) -> bool {
1085            todo!()
1086        }
1087
1088        fn find_suite_name<'a>(&self, _task: &'a EvgTask) -> &'a str {
1089            todo!()
1090        }
1091
1092        fn get_task_tags(&self, _task: &EvgTask) -> HashSet<String> {
1093            todo!()
1094        }
1095
1096        fn get_task_dependencies(&self, _task: &EvgTask) -> Vec<String> {
1097            todo!()
1098        }
1099
1100        fn get_gen_task_var<'a>(&self, _task: &'a EvgTask, _var: &str) -> Option<&'a str> {
1101            todo!()
1102        }
1103
1104        fn get_gen_task_vars(
1105            &self,
1106            _task: &EvgTask,
1107        ) -> Option<HashMap<String, shrub_rs::models::params::ParamValue>> {
1108            todo!()
1109        }
1110
1111        fn translate_run_var(
1112            &self,
1113            _run_var: &str,
1114            _build_variantt: &BuildVariant,
1115        ) -> Option<String> {
1116            todo!()
1117        }
1118
1119        fn lookup_build_variant_expansion(
1120            &self,
1121            _name: &str,
1122            _build_variant: &BuildVariant,
1123        ) -> Option<String> {
1124            todo!()
1125        }
1126
1127        fn lookup_and_split_by_whitespace_build_variant_expansion(
1128            &self,
1129            _name: &str,
1130            _build_variant: &BuildVariant,
1131        ) -> Vec<String> {
1132            todo!()
1133        }
1134
1135        fn lookup_required_param_str(
1136            &self,
1137            _task_def: &EvgTask,
1138            _run_varr: &str,
1139        ) -> Result<String> {
1140            todo!()
1141        }
1142
1143        fn lookup_required_param_u64(&self, _task_def: &EvgTask, _run_varr: &str) -> Result<u64> {
1144            todo!()
1145        }
1146
1147        fn lookup_required_param_bool(&self, _task_def: &EvgTask, _run_var: &str) -> Result<bool> {
1148            todo!()
1149        }
1150
1151        fn lookup_default_param_bool(
1152            &self,
1153            _task_def: &EvgTask,
1154            _run_var: &str,
1155            _default: bool,
1156        ) -> Result<bool> {
1157            todo!()
1158        }
1159
1160        fn lookup_default_param_str(
1161            &self,
1162            _task_def: &EvgTask,
1163            _run_var: &str,
1164            _default: &str,
1165        ) -> String {
1166            todo!()
1167        }
1168
1169        fn lookup_optional_param_u64(
1170            &self,
1171            _task_def: &EvgTask,
1172            _run_var: &str,
1173        ) -> Result<Option<u64>> {
1174            todo!()
1175        }
1176
1177        fn is_enterprise_build_variant(&self, _build_variant: &BuildVariant) -> bool {
1178            todo!()
1179        }
1180
1181        fn infer_build_variant_platform(&self, _build_variant: &BuildVariant) -> String {
1182            todo!()
1183        }
1184    }
1185
1186    struct MockResmokeConfigActorService {}
1187    #[async_trait]
1188    impl ResmokeConfigActor for MockResmokeConfigActorService {
1189        async fn write_sub_suite(&mut self, _gen_suite: &ResmokeSuiteGenerationInfo) {
1190            todo!()
1191        }
1192
1193        async fn flush(&mut self) -> Result<Vec<String>> {
1194            todo!()
1195        }
1196    }
1197
1198    struct MockBurnInDiscovery {}
1199    impl BurnInDiscovery for MockBurnInDiscovery {
1200        fn discover_tasks(&self, _build_variant: &str) -> Result<Vec<DiscoveredTask>> {
1201            todo!()
1202        }
1203    }
1204
1205    struct MockConfigExtractionService {}
1206    impl ConfigExtractionService for MockConfigExtractionService {
1207        fn task_def_to_fuzzer_params(
1208            &self,
1209            _task_def: &EvgTask,
1210            _build_variant: &BuildVariant,
1211        ) -> Result<FuzzerGenTaskParams> {
1212            todo!()
1213        }
1214
1215        fn task_def_to_resmoke_params(
1216            &self,
1217            _task_def: &EvgTask,
1218            _is_enterprise: bool,
1219            _platform: Option<String>,
1220        ) -> Result<ResmokeGenParams> {
1221            todo!()
1222        }
1223    }
1224
1225    struct MockMultiversionService {}
1226    impl MultiversionService for MockMultiversionService {
1227        fn get_version_combinations(&self, _suite_name: &str) -> Result<Vec<String>> {
1228            todo!()
1229        }
1230
1231        fn multiversion_iter(&self, _suite_name: &str) -> Result<MultiversionIterator> {
1232            todo!()
1233        }
1234
1235        fn name_multiversion_suite(
1236            &self,
1237            _base_name: &str,
1238            _old_version: &str,
1239            _version_combination: &str,
1240        ) -> String {
1241            todo!()
1242        }
1243
1244        fn exclude_tags_for_task(&self, _task_name: &str, _mv_mode: Option<String>) -> String {
1245            todo!()
1246        }
1247    }
1248
1249    struct MockBurnInService {
1250        sub_suites: Vec<EvgTask>,
1251    }
1252    impl BurnInService for MockBurnInService {
1253        fn generate_burn_in_suite(
1254            &self,
1255            _build_variant: &str,
1256            _run_build_variant_name: &str,
1257            _task_map: Arc<HashMap<String, EvgTask>>,
1258        ) -> Result<Box<dyn GeneratedSuite>> {
1259            Ok(Box::new(GeneratedResmokeSuite {
1260                task_name: "burn_in_tests".to_string(),
1261                sub_suites: self.sub_suites.clone(),
1262                use_large_distro: false,
1263            }))
1264        }
1265
1266        fn generate_burn_in_tags_build_variant(
1267            &self,
1268            _base_build_variant: &BuildVariant,
1269            _compile_build_variant_name: String,
1270            _run_build_variant_name: String,
1271            _generated_task: &dyn GeneratedSuite,
1272            _variant_task_dependencies: &[TaskDependency],
1273        ) -> BuildVariant {
1274            todo!()
1275        }
1276    }
1277
1278    fn build_mocked_burn_in_service(sub_suites: Vec<EvgTask>) -> MockBurnInService {
1279        MockBurnInService {
1280            sub_suites: sub_suites.clone(),
1281        }
1282    }
1283
1284    fn build_mocked_dependencies(burn_in_service: MockBurnInService) -> Dependencies {
1285        Dependencies {
1286            evg_config_utils: Arc::new(MockEvgConfigUtils {}),
1287            gen_task_service: Arc::new(build_mock_generate_tasks_service()),
1288            resmoke_config_actor: Arc::new(tokio::sync::Mutex::new(
1289                MockResmokeConfigActorService {},
1290            )),
1291            burn_in_service: Arc::new(burn_in_service),
1292        }
1293    }
1294
1295    // tests for create_burn_in_worker.
1296    #[tokio::test]
1297    async fn test_create_burn_in_worker_should_add_task_when_burn_in_suites_are_present() {
1298        let mock_burn_in_service = build_mocked_burn_in_service(vec![EvgTask {
1299            ..Default::default()
1300        }]);
1301        let mock_deps = build_mocked_dependencies(mock_burn_in_service);
1302        let task_map = Arc::new(HashMap::new());
1303        let generated_tasks = Arc::new(Mutex::new(HashMap::new()));
1304
1305        let thread_handle = create_burn_in_worker(
1306            &mock_deps,
1307            task_map.clone(),
1308            &BuildVariant {
1309                ..Default::default()
1310            },
1311            "run_bv_name".to_string(),
1312            generated_tasks.clone(),
1313        );
1314        thread_handle.await.unwrap();
1315
1316        assert_eq!(
1317            generated_tasks
1318                .lock()
1319                .unwrap()
1320                .contains_key(&format!("{}-{}", BURN_IN_PREFIX, "run_bv_name")),
1321            true
1322        );
1323    }
1324
1325    #[tokio::test]
1326    async fn test_create_burn_in_worker_should_not_add_task_when_burn_in_suites_are_absent() {
1327        let mock_burn_in_service = build_mocked_burn_in_service(vec![]);
1328        let mock_deps = build_mocked_dependencies(mock_burn_in_service);
1329        let task_map = Arc::new(HashMap::new());
1330        let generated_tasks = Arc::new(Mutex::new(HashMap::new()));
1331
1332        let thread_handle = create_burn_in_worker(
1333            &mock_deps,
1334            task_map.clone(),
1335            &BuildVariant {
1336                ..Default::default()
1337            },
1338            "run_bv_name".to_string(),
1339            generated_tasks.clone(),
1340        );
1341        thread_handle.await.unwrap();
1342
1343        assert_eq!(
1344            generated_tasks
1345                .lock()
1346                .unwrap()
1347                .contains_key(&format!("{}-{}", BURN_IN_PREFIX, "run_bv_name")),
1348            false
1349        );
1350    }
1351}