1use cuenv_core::ci::{PipelineMode, PipelineTask};
11use serde::{Deserialize, Serialize};
12use std::collections::BTreeMap;
13
14pub const IR_VERSION: &str = "1.5";
16
17#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
19pub struct IntermediateRepresentation {
20 pub version: String,
22
23 pub pipeline: PipelineMetadata,
25
26 #[serde(default)]
28 pub runtimes: Vec<Runtime>,
29
30 pub tasks: Vec<Task>,
32}
33
34impl IntermediateRepresentation {
35 pub fn new(pipeline_name: impl Into<String>) -> Self {
37 Self {
38 version: IR_VERSION.to_string(),
39 pipeline: PipelineMetadata {
40 name: pipeline_name.into(),
41 mode: PipelineMode::default(),
42 environment: None,
43 requires_onepassword: false,
44 project_name: None,
45 trigger: None,
46 pipeline_tasks: Vec::new(),
47 pipeline_task_defs: Vec::new(),
48 },
49 runtimes: Vec::new(),
50 tasks: Vec::new(),
51 }
52 }
53
54 pub fn phase_tasks(&self, stage: BuildStage) -> impl Iterator<Item = &Task> {
59 self.tasks.iter().filter(move |t| t.phase == Some(stage))
60 }
61
62 pub fn regular_tasks(&self) -> impl Iterator<Item = &Task> {
67 self.tasks.iter().filter(|t| t.phase.is_none())
68 }
69
70 #[must_use]
75 pub fn sorted_phase_tasks(&self, stage: BuildStage) -> Vec<&Task> {
76 let mut tasks: Vec<_> = self.phase_tasks(stage).collect();
77 tasks.sort_by_key(|t| t.priority.unwrap_or(10));
78 tasks
79 }
80}
81
82#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
84pub struct PipelineMetadata {
85 pub name: String,
87
88 #[serde(default)]
90 pub mode: PipelineMode,
91
92 #[serde(skip_serializing_if = "Option::is_none")]
94 pub environment: Option<String>,
95
96 #[serde(default, skip_serializing_if = "std::ops::Not::not")]
98 pub requires_onepassword: bool,
99
100 #[serde(skip_serializing_if = "Option::is_none")]
102 pub project_name: Option<String>,
103
104 #[serde(skip_serializing_if = "Option::is_none")]
106 pub trigger: Option<TriggerCondition>,
107
108 #[serde(default, skip_serializing_if = "Vec::is_empty")]
110 pub pipeline_tasks: Vec<String>,
111
112 #[serde(default, skip_serializing_if = "Vec::is_empty")]
114 pub pipeline_task_defs: Vec<PipelineTask>,
115}
116
117#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
119#[serde(rename_all = "snake_case")]
120pub struct TriggerCondition {
121 #[serde(default, skip_serializing_if = "Vec::is_empty")]
123 pub branches: Vec<String>,
124
125 #[serde(skip_serializing_if = "Option::is_none")]
127 pub pull_request: Option<bool>,
128
129 #[serde(default, skip_serializing_if = "Vec::is_empty")]
131 pub scheduled: Vec<String>,
132
133 #[serde(default, skip_serializing_if = "Vec::is_empty")]
135 pub release: Vec<String>,
136
137 #[serde(skip_serializing_if = "Option::is_none")]
139 pub manual: Option<ManualTriggerConfig>,
140
141 #[serde(default, skip_serializing_if = "Vec::is_empty")]
143 pub paths: Vec<String>,
144
145 #[serde(default, skip_serializing_if = "Vec::is_empty")]
147 pub paths_ignore: Vec<String>,
148}
149
150#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
152pub struct ManualTriggerConfig {
153 pub enabled: bool,
155
156 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
158 pub inputs: BTreeMap<String, WorkflowDispatchInputDef>,
159}
160
161#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
163#[serde(rename_all = "snake_case")]
164pub struct WorkflowDispatchInputDef {
165 pub description: String,
167
168 #[serde(default)]
170 pub required: bool,
171
172 #[serde(skip_serializing_if = "Option::is_none")]
174 pub default: Option<String>,
175
176 #[serde(skip_serializing_if = "Option::is_none")]
178 pub input_type: Option<String>,
179
180 #[serde(default, skip_serializing_if = "Vec::is_empty")]
182 pub options: Vec<String>,
183}
184
185#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
187pub struct Runtime {
188 pub id: String,
190
191 pub flake: String,
193
194 pub output: String,
196
197 pub system: String,
199
200 pub digest: String,
202
203 #[serde(default)]
205 pub purity: PurityMode,
206}
207
208#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Default)]
210#[serde(rename_all = "lowercase")]
211pub enum PurityMode {
212 Strict,
214
215 #[default]
217 Warning,
218
219 Override,
221}
222
223#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
225pub struct Task {
226 pub id: String,
228
229 #[serde(skip_serializing_if = "Option::is_none")]
231 pub runtime: Option<String>,
232
233 pub command: Vec<String>,
235
236 #[serde(default)]
238 pub shell: bool,
239
240 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
242 pub env: BTreeMap<String, String>,
243
244 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
246 pub secrets: BTreeMap<String, SecretConfig>,
247
248 #[serde(skip_serializing_if = "Option::is_none")]
250 pub resources: Option<ResourceRequirements>,
251
252 #[serde(skip_serializing_if = "Option::is_none")]
254 pub concurrency_group: Option<String>,
255
256 #[serde(default)]
258 pub inputs: Vec<String>,
259
260 #[serde(default)]
262 pub outputs: Vec<OutputDeclaration>,
263
264 #[serde(default, skip_serializing_if = "Vec::is_empty")]
266 pub depends_on: Vec<String>,
267
268 #[serde(default)]
270 pub cache_policy: CachePolicy,
271
272 #[serde(default)]
274 pub deployment: bool,
275
276 #[serde(default)]
278 pub manual_approval: bool,
279
280 #[serde(skip_serializing_if = "Option::is_none")]
282 pub matrix: Option<MatrixConfig>,
283
284 #[serde(default, skip_serializing_if = "Vec::is_empty")]
286 pub artifact_downloads: Vec<ArtifactDownload>,
287
288 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
290 pub params: BTreeMap<String, String>,
291
292 #[serde(skip_serializing_if = "Option::is_none")]
300 pub phase: Option<BuildStage>,
301
302 #[serde(skip_serializing_if = "Option::is_none")]
304 pub label: Option<String>,
305
306 #[serde(default, skip_serializing_if = "Option::is_none")]
311 pub priority: Option<i32>,
312
313 #[serde(skip_serializing_if = "Option::is_none")]
317 pub contributor: Option<String>,
318
319 #[serde(skip_serializing_if = "Option::is_none")]
326 pub condition: Option<TaskCondition>,
327
328 #[serde(default, skip_serializing_if = "Option::is_none")]
333 pub provider_hints: Option<serde_json::Value>,
334}
335
336impl Task {
337 #[must_use]
341 pub fn label(&self) -> String {
342 self.label.clone().unwrap_or_else(|| self.id.clone())
343 }
344
345 #[must_use]
349 pub fn command_string(&self) -> String {
350 self.command.join(" ")
351 }
352
353 #[must_use]
358 pub fn synthetic_aggregation(
359 id: impl Into<String>,
360 artifact_downloads: Vec<ArtifactDownload>,
361 params: BTreeMap<String, String>,
362 ) -> Self {
363 Self {
364 id: id.into(),
365 runtime: None,
366 command: vec![],
367 shell: false,
368 env: BTreeMap::new(),
369 secrets: BTreeMap::new(),
370 resources: None,
371 concurrency_group: None,
372 inputs: vec![],
373 outputs: vec![],
374 depends_on: vec![],
375 cache_policy: CachePolicy::Normal,
376 deployment: false,
377 manual_approval: false,
378 matrix: None,
379 artifact_downloads,
380 params,
381 phase: None,
383 label: None,
384 priority: None,
385 contributor: None,
386 condition: None,
387 provider_hints: None,
388 }
389 }
390
391 #[must_use]
396 pub fn synthetic_matrix(id: impl Into<String>, matrix: MatrixConfig) -> Self {
397 Self {
398 id: id.into(),
399 runtime: None,
400 command: vec![],
401 shell: false,
402 env: BTreeMap::new(),
403 secrets: BTreeMap::new(),
404 resources: None,
405 concurrency_group: None,
406 inputs: vec![],
407 outputs: vec![],
408 depends_on: vec![],
409 cache_policy: CachePolicy::Normal,
410 deployment: false,
411 manual_approval: false,
412 matrix: Some(matrix),
413 artifact_downloads: vec![],
414 params: BTreeMap::new(),
415 phase: None,
417 label: None,
418 priority: None,
419 contributor: None,
420 condition: None,
421 provider_hints: None,
422 }
423 }
424}
425
426#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
428pub struct MatrixConfig {
429 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
431 pub dimensions: BTreeMap<String, Vec<String>>,
432
433 #[serde(default, skip_serializing_if = "Vec::is_empty")]
435 pub exclude: Vec<BTreeMap<String, String>>,
436
437 #[serde(default, skip_serializing_if = "Vec::is_empty")]
439 pub include: Vec<BTreeMap<String, String>>,
440
441 #[serde(default)]
443 pub max_parallel: usize,
444
445 #[serde(default = "default_fail_fast")]
447 pub fail_fast: bool,
448}
449
450const fn default_fail_fast() -> bool {
451 true
452}
453
454#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
456pub struct ArtifactDownload {
457 pub name: String,
459
460 pub path: String,
462
463 #[serde(default, skip_serializing_if = "String::is_empty")]
465 pub filter: String,
466}
467
468#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
470pub struct SecretConfig {
471 pub source: String,
473
474 #[serde(default)]
476 pub cache_key: bool,
477}
478
479#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
481pub struct ResourceRequirements {
482 #[serde(skip_serializing_if = "Option::is_none")]
484 pub cpu: Option<String>,
485
486 #[serde(skip_serializing_if = "Option::is_none")]
488 pub memory: Option<String>,
489
490 #[serde(default, skip_serializing_if = "Vec::is_empty")]
492 pub tags: Vec<String>,
493}
494
495#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
497pub struct OutputDeclaration {
498 pub path: String,
500
501 #[serde(rename = "type")]
503 pub output_type: OutputType,
504}
505
506#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Default)]
508#[serde(rename_all = "lowercase")]
509pub enum OutputType {
510 #[default]
512 Cas,
513
514 Orchestrator,
516}
517
518#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Default)]
520#[serde(rename_all = "lowercase")]
521pub enum CachePolicy {
522 #[default]
524 Normal,
525
526 Readonly,
528
529 Writeonly,
531
532 Disabled,
534}
535
536#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
542#[serde(rename_all = "lowercase")]
543pub enum BuildStage {
544 Bootstrap,
546
547 Setup,
549
550 Success,
552
553 Failure,
555}
556
557#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
562#[serde(rename_all = "snake_case")]
563pub enum TaskCondition {
564 OnSuccess,
566
567 OnFailure,
569
570 Always,
572}
573
574#[cfg(test)]
575mod tests {
576 use super::*;
577
578 #[test]
579 fn test_ir_version() {
580 let ir = IntermediateRepresentation::new("test-pipeline");
581 assert_eq!(ir.version, "1.5");
582 assert_eq!(ir.pipeline.name, "test-pipeline");
583 assert!(ir.runtimes.is_empty());
584 assert!(ir.tasks.is_empty());
585 }
586
587 #[test]
588 fn test_purity_mode_serialization() {
589 let strict = PurityMode::Strict;
590 let json = serde_json::to_string(&strict).unwrap();
591 assert_eq!(json, r#""strict""#);
592
593 let warning = PurityMode::Warning;
594 let json = serde_json::to_string(&warning).unwrap();
595 assert_eq!(json, r#""warning""#);
596
597 let override_mode = PurityMode::Override;
598 let json = serde_json::to_string(&override_mode).unwrap();
599 assert_eq!(json, r#""override""#);
600 }
601
602 #[test]
603 fn test_cache_policy_serialization() {
604 let normal = CachePolicy::Normal;
605 assert_eq!(serde_json::to_string(&normal).unwrap(), r#""normal""#);
606
607 let readonly = CachePolicy::Readonly;
608 assert_eq!(serde_json::to_string(&readonly).unwrap(), r#""readonly""#);
609
610 let writeonly = CachePolicy::Writeonly;
611 assert_eq!(serde_json::to_string(&writeonly).unwrap(), r#""writeonly""#);
612
613 let disabled = CachePolicy::Disabled;
614 assert_eq!(serde_json::to_string(&disabled).unwrap(), r#""disabled""#);
615 }
616
617 #[test]
618 fn test_output_type_serialization() {
619 let cas = OutputType::Cas;
620 assert_eq!(serde_json::to_string(&cas).unwrap(), r#""cas""#);
621
622 let orchestrator = OutputType::Orchestrator;
623 assert_eq!(
624 serde_json::to_string(&orchestrator).unwrap(),
625 r#""orchestrator""#
626 );
627 }
628
629 #[test]
630 fn test_task_minimal() {
631 let task = Task {
632 id: "test-task".to_string(),
633 runtime: None,
634 command: vec!["echo".to_string(), "hello".to_string()],
635 shell: false,
636 env: BTreeMap::new(),
637 secrets: BTreeMap::new(),
638 resources: None,
639 concurrency_group: None,
640 inputs: vec![],
641 outputs: vec![],
642 depends_on: vec![],
643 cache_policy: CachePolicy::Normal,
644 deployment: false,
645 manual_approval: false,
646 matrix: None,
647 artifact_downloads: vec![],
648 params: BTreeMap::new(),
649 phase: None,
650 label: None,
651 priority: None,
652 contributor: None,
653 condition: None,
654 provider_hints: None,
655 };
656
657 let json = serde_json::to_value(&task).unwrap();
658 assert_eq!(json["id"], "test-task");
659 assert_eq!(json["command"], serde_json::json!(["echo", "hello"]));
660 assert_eq!(json["shell"], false);
661 }
662
663 #[test]
664 fn test_task_with_deployment() {
665 let task = Task {
666 id: "deploy-prod".to_string(),
667 runtime: None,
668 command: vec!["deploy".to_string()],
669 shell: false,
670 env: BTreeMap::new(),
671 secrets: BTreeMap::new(),
672 resources: None,
673 concurrency_group: Some("production".to_string()),
674 inputs: vec![],
675 outputs: vec![],
676 depends_on: vec!["build".to_string()],
677 cache_policy: CachePolicy::Disabled,
678 deployment: true,
679 manual_approval: true,
680 matrix: None,
681 artifact_downloads: vec![],
682 params: BTreeMap::new(),
683 phase: None,
684 label: None,
685 priority: None,
686 contributor: None,
687 condition: None,
688 provider_hints: None,
689 };
690
691 let json = serde_json::to_value(&task).unwrap();
692 assert_eq!(json["deployment"], true);
693 assert_eq!(json["manual_approval"], true);
694 assert_eq!(json["cache_policy"], "disabled");
695 assert_eq!(json["concurrency_group"], "production");
696 }
697
698 #[test]
699 fn test_task_with_matrix() {
700 let task = Task {
701 id: "build-matrix".to_string(),
702 runtime: None,
703 command: vec!["cargo".to_string(), "build".to_string()],
704 shell: false,
705 env: BTreeMap::new(),
706 secrets: BTreeMap::new(),
707 resources: None,
708 concurrency_group: None,
709 inputs: vec![],
710 outputs: vec![],
711 depends_on: vec![],
712 cache_policy: CachePolicy::Normal,
713 deployment: false,
714 manual_approval: false,
715 matrix: Some(MatrixConfig {
716 dimensions: [(
717 "arch".to_string(),
718 vec!["x64".to_string(), "arm64".to_string()],
719 )]
720 .into_iter()
721 .collect(),
722 ..Default::default()
723 }),
724 artifact_downloads: vec![],
725 params: BTreeMap::new(),
726 phase: None,
727 label: None,
728 priority: None,
729 contributor: None,
730 condition: None,
731 provider_hints: None,
732 };
733
734 let json = serde_json::to_value(&task).unwrap();
735 assert_eq!(
736 json["matrix"]["dimensions"]["arch"],
737 serde_json::json!(["x64", "arm64"])
738 );
739 }
740
741 #[test]
742 fn test_artifact_download() {
743 let artifact = ArtifactDownload {
744 name: "build-${{ matrix.arch }}".to_string(),
745 path: "./artifacts".to_string(),
746 filter: "*stable".to_string(),
747 };
748
749 let json = serde_json::to_value(&artifact).unwrap();
750 assert_eq!(json["name"], "build-${{ matrix.arch }}");
751 assert_eq!(json["path"], "./artifacts");
752 assert_eq!(json["filter"], "*stable");
753 }
754
755 #[test]
756 fn test_secret_config() {
757 let secret = SecretConfig {
758 source: "CI_API_KEY".to_string(),
759 cache_key: true,
760 };
761
762 let json = serde_json::to_value(&secret).unwrap();
763 assert_eq!(json["source"], "CI_API_KEY");
764 assert_eq!(json["cache_key"], true);
765 }
766
767 #[test]
768 fn test_runtime() {
769 let runtime = Runtime {
770 id: "nix-rust".to_string(),
771 flake: "github:NixOS/nixpkgs/nixos-unstable".to_string(),
772 output: "devShells.x86_64-linux.default".to_string(),
773 system: "x86_64-linux".to_string(),
774 digest: "sha256:abc123".to_string(),
775 purity: PurityMode::Strict,
776 };
777
778 let json = serde_json::to_value(&runtime).unwrap();
779 assert_eq!(json["id"], "nix-rust");
780 assert_eq!(json["purity"], "strict");
781 }
782
783 #[test]
784 fn test_full_ir_serialization() {
785 let mut ir = IntermediateRepresentation::new("my-pipeline");
786 ir.pipeline.trigger = Some(TriggerCondition {
787 branches: vec!["main".to_string()],
788 ..Default::default()
789 });
790
791 ir.runtimes.push(Runtime {
792 id: "default".to_string(),
793 flake: "github:NixOS/nixpkgs/nixos-unstable".to_string(),
794 output: "devShells.x86_64-linux.default".to_string(),
795 system: "x86_64-linux".to_string(),
796 digest: "sha256:def456".to_string(),
797 purity: PurityMode::Warning,
798 });
799
800 ir.tasks.push(Task {
801 id: "build".to_string(),
802 runtime: Some("default".to_string()),
803 command: vec!["cargo".to_string(), "build".to_string()],
804 shell: false,
805 env: BTreeMap::new(),
806 secrets: BTreeMap::new(),
807 resources: Some(ResourceRequirements {
808 cpu: Some("2".to_string()),
809 memory: Some("4Gi".to_string()),
810 tags: vec!["rust".to_string()],
811 }),
812 concurrency_group: None,
813 inputs: vec!["src/**/*.rs".to_string(), "Cargo.toml".to_string()],
814 outputs: vec![OutputDeclaration {
815 path: "target/release/binary".to_string(),
816 output_type: OutputType::Cas,
817 }],
818 depends_on: vec![],
819 cache_policy: CachePolicy::Normal,
820 deployment: false,
821 manual_approval: false,
822 matrix: None,
823 artifact_downloads: vec![],
824 params: BTreeMap::new(),
825 phase: None,
826 label: None,
827 priority: None,
828 contributor: None,
829 condition: None,
830 provider_hints: None,
831 });
832
833 let json = serde_json::to_string_pretty(&ir).unwrap();
834 assert!(json.contains(r#""version": "1.5""#));
835 assert!(json.contains(r#""name": "my-pipeline""#));
836 assert!(json.contains(r#""id": "build""#));
837 }
838
839 #[test]
844 fn test_build_stage_serialization() {
845 assert_eq!(
846 serde_json::to_string(&BuildStage::Bootstrap).unwrap(),
847 r#""bootstrap""#
848 );
849 assert_eq!(
850 serde_json::to_string(&BuildStage::Setup).unwrap(),
851 r#""setup""#
852 );
853 assert_eq!(
854 serde_json::to_string(&BuildStage::Success).unwrap(),
855 r#""success""#
856 );
857 assert_eq!(
858 serde_json::to_string(&BuildStage::Failure).unwrap(),
859 r#""failure""#
860 );
861 }
862
863 fn make_test_task(id: &str) -> Task {
869 Task {
870 id: id.to_string(),
871 runtime: None,
872 command: vec!["echo".to_string()],
873 shell: false,
874 env: BTreeMap::new(),
875 secrets: BTreeMap::new(),
876 resources: None,
877 concurrency_group: None,
878 inputs: vec![],
879 outputs: vec![],
880 depends_on: vec![],
881 cache_policy: CachePolicy::Disabled,
882 deployment: false,
883 manual_approval: false,
884 matrix: None,
885 artifact_downloads: vec![],
886 params: BTreeMap::new(),
887 phase: None,
888 label: None,
889 priority: None,
890 contributor: None,
891 condition: None,
892 provider_hints: None,
893 }
894 }
895
896 #[test]
897 fn test_phase_tasks_filters_by_phase() {
898 let mut ir = IntermediateRepresentation::new("test");
899
900 ir.tasks.push(make_test_task("regular-task"));
902
903 let mut bootstrap_task = make_test_task("install-nix");
905 bootstrap_task.phase = Some(BuildStage::Bootstrap);
906 ir.tasks.push(bootstrap_task);
907
908 let mut setup_task = make_test_task("setup-cuenv");
910 setup_task.phase = Some(BuildStage::Setup);
911 ir.tasks.push(setup_task);
912
913 let bootstrap_tasks: Vec<_> = ir.phase_tasks(BuildStage::Bootstrap).collect();
915 assert_eq!(bootstrap_tasks.len(), 1);
916 assert_eq!(bootstrap_tasks[0].id, "install-nix");
917
918 let setup_tasks: Vec<_> = ir.phase_tasks(BuildStage::Setup).collect();
919 assert_eq!(setup_tasks.len(), 1);
920 assert_eq!(setup_tasks[0].id, "setup-cuenv");
921
922 let success_tasks: Vec<_> = ir.phase_tasks(BuildStage::Success).collect();
924 assert!(success_tasks.is_empty());
925 }
926
927 #[test]
928 fn test_regular_tasks_excludes_phase_tasks() {
929 let mut ir = IntermediateRepresentation::new("test");
930
931 ir.tasks.push(make_test_task("build"));
933 ir.tasks.push(make_test_task("test"));
934
935 let mut phase_task = make_test_task("install-nix");
937 phase_task.phase = Some(BuildStage::Bootstrap);
938 ir.tasks.push(phase_task);
939
940 let regular: Vec<_> = ir.regular_tasks().collect();
942 assert_eq!(regular.len(), 2);
943 assert!(regular.iter().any(|t| t.id == "build"));
944 assert!(regular.iter().any(|t| t.id == "test"));
945 assert!(!regular.iter().any(|t| t.id == "install-nix"));
946 }
947
948 #[test]
949 fn test_sorted_phase_tasks_orders_by_priority() {
950 let mut ir = IntermediateRepresentation::new("test");
951
952 let mut task_high_priority = make_test_task("first");
954 task_high_priority.phase = Some(BuildStage::Setup);
955 task_high_priority.priority = Some(1);
956 ir.tasks.push(task_high_priority);
957
958 let mut task_low_priority = make_test_task("last");
959 task_low_priority.phase = Some(BuildStage::Setup);
960 task_low_priority.priority = Some(100);
961 ir.tasks.push(task_low_priority);
962
963 let mut task_medium_priority = make_test_task("middle");
964 task_medium_priority.phase = Some(BuildStage::Setup);
965 task_medium_priority.priority = Some(50);
966 ir.tasks.push(task_medium_priority);
967
968 let sorted = ir.sorted_phase_tasks(BuildStage::Setup);
970 assert_eq!(sorted.len(), 3);
971 assert_eq!(sorted[0].id, "first");
972 assert_eq!(sorted[1].id, "middle");
973 assert_eq!(sorted[2].id, "last");
974 }
975
976 #[test]
977 fn test_sorted_phase_tasks_uses_default_priority() {
978 let mut ir = IntermediateRepresentation::new("test");
979
980 let mut explicit_task = make_test_task("explicit");
982 explicit_task.phase = Some(BuildStage::Setup);
983 explicit_task.priority = Some(5);
984 ir.tasks.push(explicit_task);
985
986 let mut default_task = make_test_task("default");
988 default_task.phase = Some(BuildStage::Setup);
989 ir.tasks.push(default_task);
990
991 let mut high_task = make_test_task("high");
993 high_task.phase = Some(BuildStage::Setup);
994 high_task.priority = Some(20);
995 ir.tasks.push(high_task);
996
997 let sorted = ir.sorted_phase_tasks(BuildStage::Setup);
999 assert_eq!(sorted[0].id, "explicit");
1000 assert_eq!(sorted[1].id, "default");
1001 assert_eq!(sorted[2].id, "high");
1002 }
1003}