1#![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
68pub struct ProjectInfo {
70 pub evg_project_location: PathBuf,
72
73 pub evg_project: String,
75
76 pub gen_sub_tasks_config_file: Option<PathBuf>,
78}
79
80impl ProjectInfo {
81 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 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 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
121pub struct ExecutionConfiguration<'a> {
123 pub project_info: &'a ProjectInfo,
125 pub evg_auth_file: &'a Path,
127 pub use_task_split_fallback: bool,
129 pub resmoke_command: &'a str,
131 pub target_directory: &'a Path,
133 pub generating_task: &'a str,
135 pub config_location: &'a str,
137 pub gen_burn_in: bool,
139 pub burn_in_tests_command: &'a str,
141}
142
143#[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 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#[derive(Debug, Clone)]
240struct GeneratedConfig {
241 pub gen_task_specs: Vec<TaskRef>,
243 pub display_tasks: Vec<DisplayTask>,
245}
246
247impl GeneratedConfig {
248 pub fn new() -> Self {
250 Self {
251 gen_task_specs: vec![],
252 display_tasks: vec![],
253 }
254 }
255}
256
257pub 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 let generated_tasks = generate_tasks_service.build_generated_tasks(deps).await?;
271
272 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#[async_trait]
307trait GenerateTasksService: Sync + Send {
308 async fn build_generated_tasks(
318 &self,
319 deps: &Dependencies,
320 ) -> Result<Arc<Mutex<GenTaskCollection>>>;
321
322 fn generate_build_variants(
333 &self,
334 deps: &Dependencies,
335 generated_tasks: Arc<Mutex<GenTaskCollection>>,
336 ) -> Result<Vec<BuildVariant>>;
337
338 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 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 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 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#[async_trait]
465impl GenerateTasksService for GenerateTasksServiceImpl {
466 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 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 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 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 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(¶ms)?)
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(¶ms, &build_variant.name)
610 .await?,
611 )
612 };
613
614 Ok(generated_task)
615 }
616
617 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 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 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
812fn 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
834fn 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
875fn 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 #[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 #[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 #[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}