1use serde::{Deserialize, Serialize};
8use std::collections::HashMap;
9use std::path::PathBuf;
10use std::time::Duration;
11
12#[derive(Debug, Clone, Serialize, Deserialize)]
14pub struct CiCdAutomationConfig {
15 pub enable_automation: bool,
17 pub platform: CiCdPlatform,
19 pub test_execution: TestExecutionConfig,
21 pub baseline_management: BaselineManagementConfig,
23 pub reporting: ReportingConfig,
25 pub artifact_storage: ArtifactStorageConfig,
27 pub integrations: IntegrationConfig,
29 pub performance_gates: PerformanceGatesConfig,
31}
32
33#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
35pub enum CiCdPlatform {
36 GitHubActions,
38 GitLabCI,
40 Jenkins,
42 AzureDevOps,
44 CircleCI,
46 TravisCI,
48 TeamCity,
50 Buildkite,
52 Generic,
54}
55
56#[derive(Debug, Clone, Serialize, Deserialize)]
58pub struct TestExecutionConfig {
59 pub run_on_commit: bool,
61 pub run_on_pr: bool,
63 pub run_on_release: bool,
65 pub run_on_schedule: Option<CronSchedule>,
67 pub test_timeout: u64,
69 pub test_iterations: usize,
71 pub warmup_iterations: usize,
73 pub parallel_execution: bool,
75 pub isolation_level: TestIsolationLevel,
77 pub max_concurrent_tests: usize,
79 pub resource_limits: ResourceLimits,
81 pub environment_variables: HashMap<String, String>,
83 pub custom_commands: HashMap<String, String>,
85}
86
87#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
89pub enum TestIsolationLevel {
90 Process,
92 ProcessIsolated,
94 Container,
96 VirtualMachine,
98}
99
100#[derive(Debug, Clone, Serialize, Deserialize)]
102pub struct ResourceLimits {
103 pub max_memory_mb: Option<usize>,
105 pub max_cpu_percent: Option<f64>,
107 pub max_execution_time_sec: Option<u64>,
109 pub max_disk_mb: Option<usize>,
111 pub max_network_mbps: Option<f64>,
113}
114
115#[derive(Debug, Clone, Serialize, Deserialize)]
117pub struct CronSchedule {
118 pub expression: String,
120 pub timezone: String,
122 pub enabled: bool,
124}
125
126#[derive(Debug, Clone, Serialize, Deserialize)]
128pub struct BaselineManagementConfig {
129 pub auto_update_main: bool,
131 pub update_on_release: bool,
133 pub min_improvement_threshold: f64,
135 pub max_degradation_threshold: f64,
137 pub storage: BaselineStorageConfig,
139 pub retention: BaselineRetentionPolicy,
141 pub validation: BaselineValidationConfig,
143}
144
145#[derive(Debug, Clone, Serialize, Deserialize)]
147pub struct BaselineStorageConfig {
148 pub provider: BaselineStorageProvider,
150 pub location: String,
152 pub encryption: Option<EncryptionConfig>,
154 pub compression: Option<CompressionConfig>,
156}
157
158#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
160pub enum BaselineStorageProvider {
161 Local,
163 S3,
165 GCS,
167 AzureBlob,
169 Git,
171 Database,
173}
174
175#[derive(Debug, Clone, Serialize, Deserialize)]
177pub struct EncryptionConfig {
178 pub algorithm: EncryptionAlgorithm,
180 pub key_management: KeyManagementConfig,
182 pub encrypt_at_rest: bool,
184 pub encrypt_in_transit: bool,
186}
187
188#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
190pub enum EncryptionAlgorithm {
191 AES256,
193 ChaCha20Poly1305,
195 RSA,
197}
198
199#[derive(Debug, Clone, Serialize, Deserialize)]
201pub struct KeyManagementConfig {
202 pub provider: KeyProvider,
204 pub rotation_interval_days: Option<u32>,
206 pub derivation: Option<KeyDerivationConfig>,
208}
209
210#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
212pub enum KeyProvider {
213 Environment,
215 AWSKMS,
217 AzureKeyVault,
219 GoogleCloudKMS,
221 Vault,
223}
224
225#[derive(Debug, Clone, Serialize, Deserialize)]
227pub struct KeyDerivationConfig {
228 pub function: KeyDerivationFunction,
230 pub salt_length: usize,
232 pub iterations: u32,
234}
235
236#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
238pub enum KeyDerivationFunction {
239 PBKDF2SHA256,
241 Argon2id,
243 Scrypt,
245}
246
247#[derive(Debug, Clone, Serialize, Deserialize)]
249pub struct CompressionConfig {
250 pub algorithm: CompressionAlgorithm,
252 pub level: u8,
254 pub compress_baselines: bool,
256 pub compress_reports: bool,
258 pub min_size_bytes: usize,
260}
261
262#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
264pub enum CompressionAlgorithm {
265 Gzip,
267 Zstd,
269 LZ4,
271 Brotli,
273}
274
275#[derive(Debug, Clone, Serialize, Deserialize)]
277pub struct BaselineRetentionPolicy {
278 pub retention_days: u32,
280 pub max_baselines: usize,
282 pub keep_release_baselines: bool,
284 pub keep_milestone_baselines: bool,
286 pub cleanup_frequency_hours: u32,
288}
289
290#[derive(Debug, Clone, Serialize, Deserialize)]
292pub struct BaselineValidationConfig {
293 pub enable_integrity_checks: bool,
295 pub enable_statistical_validation: bool,
297 pub min_sample_size: usize,
299 pub confidence_level: f64,
301 pub validation_timeout_sec: u64,
303}
304
305#[derive(Debug, Clone, Serialize, Deserialize)]
307pub struct ReportingConfig {
308 pub generate_html: bool,
310 pub generate_json: bool,
312 pub generate_junit: bool,
314 pub generate_markdown: bool,
316 pub generate_pdf: bool,
318 pub include_detailed_metrics: bool,
320 pub include_graphs: bool,
322 pub include_regression_analysis: bool,
324 pub templates: ReportTemplateConfig,
326 pub styling: ReportStylingConfig,
328 pub distribution: ReportDistributionConfig,
330}
331
332#[derive(Debug, Clone, Serialize, Deserialize)]
334pub struct ReportTemplateConfig {
335 pub html_template_path: Option<PathBuf>,
337 pub markdown_template_path: Option<PathBuf>,
339 pub template_variables: HashMap<String, String>,
341 pub enable_caching: bool,
343}
344
345#[derive(Debug, Clone, Serialize, Deserialize)]
347pub struct ReportStylingConfig {
348 pub css_path: Option<PathBuf>,
350 pub color_theme: ColorTheme,
352 pub font_family: String,
354 pub chart_style: ChartStyleConfig,
356}
357
358#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
360pub enum ColorTheme {
361 Light,
363 Dark,
365 HighContrast,
367 Custom,
369}
370
371#[derive(Debug, Clone, Serialize, Deserialize)]
373pub struct ChartStyleConfig {
374 pub width: u32,
376 pub height: u32,
378 pub enable_animations: bool,
380 pub color_palette: Vec<String>,
382 pub grid_style: GridStyle,
384}
385
386#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
388pub enum GridStyle {
389 Solid,
391 Dashed,
393 Dotted,
395 None,
397}
398
399#[derive(Debug, Clone, Serialize, Deserialize)]
401pub struct ReportDistributionConfig {
402 pub email: Option<EmailDistributionConfig>,
404 pub slack: Option<SlackDistributionConfig>,
406 pub webhooks: Vec<WebhookDistributionConfig>,
408 pub filesystem: Option<FilesystemDistributionConfig>,
410}
411
412#[derive(Debug, Clone, Serialize, Deserialize)]
414pub struct EmailDistributionConfig {
415 pub recipients: Vec<String>,
417 pub subject_template: String,
419 pub body_template: String,
421 pub attach_reports: bool,
423 pub priority: EmailPriority,
425}
426
427#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
429pub enum EmailPriority {
430 Low,
432 Normal,
434 High,
436 Urgent,
438}
439
440#[derive(Debug, Clone, Serialize, Deserialize)]
442pub struct SlackDistributionConfig {
443 pub channels: Vec<String>,
445 pub message_template: String,
447 pub include_attachments: bool,
449 pub mention_on_failure: Vec<String>,
451}
452
453#[derive(Debug, Clone, Serialize, Deserialize)]
455pub struct WebhookDistributionConfig {
456 pub url: String,
458 pub method: HttpMethod,
460 pub headers: HashMap<String, String>,
462 pub payload_template: String,
464 pub auth: Option<WebhookAuth>,
466 pub timeout_sec: u64,
468 pub retry: WebhookRetryConfig,
470}
471
472#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
474pub enum HttpMethod {
475 GET,
476 POST,
477 PUT,
478 PATCH,
479 DELETE,
480}
481
482#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
484pub enum WebhookAuth {
485 Bearer { token: String },
487 Basic { username: String, password: String },
489 ApiKey { key: String, header: String },
491 Custom { headers: HashMap<String, String> },
493}
494
495#[derive(Debug, Clone, Serialize, Deserialize)]
497pub struct WebhookRetryConfig {
498 pub max_retries: u32,
500 pub initial_delay_sec: u64,
502 pub backoff_multiplier: f64,
504 pub max_delay_sec: u64,
506}
507
508#[derive(Debug, Clone, Serialize, Deserialize)]
510pub struct FilesystemDistributionConfig {
511 pub output_directory: PathBuf,
513 pub file_naming_pattern: String,
515 pub create_date_subdirs: bool,
517 pub file_permissions: Option<u32>,
519}
520
521#[derive(Debug, Clone, Serialize, Deserialize)]
523pub struct ArtifactStorageConfig {
524 pub enabled: bool,
526 pub provider: ArtifactStorageProvider,
528 pub storage_config: HashMap<String, String>,
530 pub retention: ArtifactRetentionPolicy,
532 pub upload: ArtifactUploadConfig,
534 pub download: ArtifactDownloadConfig,
536}
537
538#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
540pub enum ArtifactStorageProvider {
541 Local(PathBuf),
543 S3 {
545 bucket: String,
546 region: String,
547 prefix: Option<String>,
548 },
549 GCS {
551 bucket: String,
552 prefix: Option<String>,
553 },
554 AzureBlob {
556 account: String,
557 container: String,
558 prefix: Option<String>,
559 },
560 FTP {
562 host: String,
563 port: u16,
564 path: String,
565 secure: bool,
566 },
567 HTTP {
569 base_url: String,
570 auth: Option<WebhookAuth>,
571 },
572}
573
574#[derive(Debug, Clone, Serialize, Deserialize)]
576pub struct ArtifactRetentionPolicy {
577 pub retention_days: u32,
579 pub max_artifacts: Option<usize>,
581 pub keep_release_artifacts: bool,
583 pub keep_failed_build_artifacts: bool,
585 pub cleanup_schedule: Option<CronSchedule>,
587}
588
589#[derive(Debug, Clone, Serialize, Deserialize)]
591pub struct ArtifactUploadConfig {
592 pub compress: bool,
594 pub compression_level: u8,
596 pub encrypt: bool,
598 pub timeout_sec: u64,
600 pub max_file_size_mb: usize,
602 pub parallel_uploads: ParallelUploadConfig,
604}
605
606#[derive(Debug, Clone, Serialize, Deserialize)]
608pub struct ParallelUploadConfig {
609 pub enabled: bool,
611 pub max_concurrent: usize,
613 pub chunk_size_mb: usize,
615}
616
617#[derive(Debug, Clone, Serialize, Deserialize)]
619pub struct ArtifactDownloadConfig {
620 pub timeout_sec: u64,
622 pub enable_caching: bool,
624 pub cache_directory: Option<PathBuf>,
626 pub cache_size_limit_mb: Option<usize>,
628 pub verify_checksums: bool,
630}
631
632#[derive(Debug, Clone, Serialize, Deserialize, Default)]
634pub struct IntegrationConfig {
635 pub github: Option<GitHubIntegration>,
637 pub slack: Option<SlackIntegration>,
639 pub email: Option<EmailIntegration>,
641 pub webhooks: Vec<WebhookIntegration>,
643 pub custom: HashMap<String, CustomIntegrationConfig>,
645}
646
647#[derive(Debug, Clone, Serialize, Deserialize)]
649pub struct GitHubIntegration {
650 pub token: String,
652 pub owner: String,
654 pub repository: String,
656 pub create_status_checks: bool,
658 pub create_pr_comments: bool,
660 pub create_regression_issues: bool,
662 pub labels: GitHubLabelConfig,
664 pub status_checks: GitHubStatusCheckConfig,
666}
667
668#[derive(Debug, Clone, Serialize, Deserialize)]
670pub struct GitHubLabelConfig {
671 pub performance_regression: String,
673 pub performance_improvement: String,
675 pub test_failure: String,
677 pub automated: String,
679}
680
681#[derive(Debug, Clone, Serialize, Deserialize)]
683pub struct GitHubStatusCheckConfig {
684 pub context: String,
686 pub success_description: String,
688 pub failure_description: String,
690 pub pending_description: String,
692}
693
694#[derive(Debug, Clone, Serialize, Deserialize)]
696pub struct SlackIntegration {
697 pub webhook_url: String,
699 pub default_channel: String,
701 pub username: Option<String>,
703 pub icon_emoji: Option<String>,
705 pub notifications: SlackNotificationConfig,
707}
708
709#[derive(Debug, Clone, Serialize, Deserialize)]
711pub struct SlackNotificationConfig {
712 pub notify_on_completion: bool,
714 pub notify_on_regression: bool,
716 pub notify_on_failure: bool,
718 pub notify_on_improvement: bool,
720 pub mention_users: Vec<String>,
722}
723
724#[derive(Debug, Clone, Serialize, Deserialize)]
726pub struct EmailIntegration {
727 pub smtp: SmtpConfig,
729 pub from_email: String,
731 pub default_recipients: Vec<String>,
733 pub templates: EmailTemplateConfig,
735}
736
737#[derive(Debug, Clone, Serialize, Deserialize)]
739pub struct SmtpConfig {
740 pub host: String,
742 pub port: u16,
744 pub use_tls: bool,
746 pub username: Option<String>,
748 pub password: Option<String>,
750 pub timeout_sec: u64,
752}
753
754#[derive(Debug, Clone, Serialize, Deserialize)]
756pub struct EmailTemplateConfig {
757 pub success_subject: String,
759 pub failure_subject: String,
761 pub success_body: String,
763 pub failure_body: String,
765 pub use_html: bool,
767}
768
769#[derive(Debug, Clone, Serialize, Deserialize)]
771pub struct WebhookIntegration {
772 pub name: String,
774 pub url: String,
776 pub method: HttpMethod,
778 pub headers: HashMap<String, String>,
780 pub auth: Option<WebhookAuth>,
782 pub triggers: WebhookTriggerConfig,
784 pub payload: WebhookPayloadConfig,
786}
787
788#[derive(Debug, Clone, Serialize, Deserialize)]
790pub struct WebhookTriggerConfig {
791 pub on_completion: bool,
793 pub on_regression: bool,
795 pub on_failure: bool,
797 pub on_improvement: bool,
799 pub custom_conditions: Vec<String>,
801}
802
803#[derive(Debug, Clone, Serialize, Deserialize)]
805pub struct WebhookPayloadConfig {
806 pub format: PayloadFormat,
808 pub include_results: bool,
810 pub include_metrics: bool,
812 pub include_environment: bool,
814 pub custom_template: Option<String>,
816}
817
818#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
820pub enum PayloadFormat {
821 JSON,
823 XML,
825 FormData,
827 Custom,
829}
830
831#[derive(Debug, Clone, Serialize, Deserialize)]
833pub struct CustomIntegrationConfig {
834 pub integration_type: String,
836 pub parameters: HashMap<String, String>,
838 pub enabled: bool,
840}
841
842#[derive(Debug, Clone, Serialize, Deserialize)]
844pub struct PerformanceGatesConfig {
845 pub enabled: bool,
847 pub metric_gates: HashMap<MetricType, MetricGate>,
849 pub evaluation_strategy: GateEvaluationStrategy,
851 pub failure_handling: GateFailureHandling,
853}
854
855#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
857pub enum MetricType {
858 ExecutionTime,
860 MemoryUsage,
862 CpuUsage,
864 Throughput,
866 Latency,
868 ErrorRate,
870 Custom(String),
872}
873
874#[derive(Debug, Clone, Serialize, Deserialize)]
876pub struct MetricGate {
877 pub gate_type: GateType,
879 pub threshold: f64,
881 pub operator: ComparisonOperator,
883 pub severity: GateSeverity,
885 pub enabled: bool,
887}
888
889#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
891pub enum GateType {
892 Absolute,
894 Relative,
896 Statistical,
898 Trend,
900}
901
902#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
904pub enum ComparisonOperator {
905 LessThan,
907 LessThanOrEqual,
909 GreaterThan,
911 GreaterThanOrEqual,
913 Equal,
915 NotEqual,
917}
918
919#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
921pub enum GateSeverity {
922 Info,
924 Warning,
926 Error,
928 Critical,
930}
931
932#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
934pub enum GateEvaluationStrategy {
935 FailFast,
937 EvaluateAll,
939 Weighted,
941}
942
943#[derive(Debug, Clone, Serialize, Deserialize)]
945pub struct GateFailureHandling {
946 pub failure_action: GateFailureAction,
948 pub allow_manual_override: bool,
950 pub override_timeout_hours: Option<u32>,
952 pub notifications: GateFailureNotificationConfig,
954}
955
956#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
958pub enum GateFailureAction {
959 FailBuild,
961 MarkUnstable,
963 LogWarning,
965 NotifyOnly,
967}
968
969#[derive(Debug, Clone, Serialize, Deserialize, Default)]
971pub struct GateFailureNotificationConfig {
972 pub send_email: bool,
974 pub send_slack: bool,
976 pub send_webhooks: bool,
978 pub create_github_issues: bool,
980 pub escalation: Option<NotificationEscalationConfig>,
982}
983
984#[derive(Debug, Clone, Serialize, Deserialize)]
986pub struct NotificationEscalationConfig {
987 pub delay_minutes: u32,
989 pub max_levels: u32,
991 pub recipients_by_level: HashMap<u32, Vec<String>>,
993}
994
995impl Default for CiCdAutomationConfig {
998 fn default() -> Self {
999 Self {
1000 enable_automation: true,
1001 platform: CiCdPlatform::Generic,
1002 test_execution: TestExecutionConfig::default(),
1003 baseline_management: BaselineManagementConfig::default(),
1004 reporting: ReportingConfig::default(),
1005 artifact_storage: ArtifactStorageConfig::default(),
1006 integrations: IntegrationConfig::default(),
1007 performance_gates: PerformanceGatesConfig::default(),
1008 }
1009 }
1010}
1011
1012impl Default for TestExecutionConfig {
1013 fn default() -> Self {
1014 Self {
1015 run_on_commit: true,
1016 run_on_pr: true,
1017 run_on_release: true,
1018 run_on_schedule: None,
1019 test_timeout: 3600, test_iterations: 5,
1021 warmup_iterations: 2,
1022 parallel_execution: true,
1023 isolation_level: TestIsolationLevel::Process,
1024 max_concurrent_tests: num_cpus::get(),
1025 resource_limits: ResourceLimits::default(),
1026 environment_variables: HashMap::new(),
1027 custom_commands: HashMap::new(),
1028 }
1029 }
1030}
1031
1032impl Default for ResourceLimits {
1033 fn default() -> Self {
1034 Self {
1035 max_memory_mb: Some(4096), max_cpu_percent: Some(80.0), max_execution_time_sec: Some(1800), max_disk_mb: Some(10240), max_network_mbps: Some(100.0), }
1041 }
1042}
1043
1044impl Default for BaselineManagementConfig {
1045 fn default() -> Self {
1046 Self {
1047 auto_update_main: true,
1048 update_on_release: true,
1049 min_improvement_threshold: 0.05, max_degradation_threshold: 0.10, storage: BaselineStorageConfig::default(),
1052 retention: BaselineRetentionPolicy::default(),
1053 validation: BaselineValidationConfig::default(),
1054 }
1055 }
1056}
1057
1058impl Default for BaselineStorageConfig {
1059 fn default() -> Self {
1060 Self {
1061 provider: BaselineStorageProvider::Local,
1062 location: "./baselines".to_string(),
1063 encryption: None,
1064 compression: None,
1065 }
1066 }
1067}
1068
1069impl Default for BaselineRetentionPolicy {
1070 fn default() -> Self {
1071 Self {
1072 retention_days: 90,
1073 max_baselines: 100,
1074 keep_release_baselines: true,
1075 keep_milestone_baselines: true,
1076 cleanup_frequency_hours: 24,
1077 }
1078 }
1079}
1080
1081impl Default for BaselineValidationConfig {
1082 fn default() -> Self {
1083 Self {
1084 enable_integrity_checks: true,
1085 enable_statistical_validation: true,
1086 min_sample_size: 10,
1087 confidence_level: 0.95,
1088 validation_timeout_sec: 300,
1089 }
1090 }
1091}
1092
1093impl Default for ReportingConfig {
1094 fn default() -> Self {
1095 Self {
1096 generate_html: true,
1097 generate_json: true,
1098 generate_junit: false,
1099 generate_markdown: false,
1100 generate_pdf: false,
1101 include_detailed_metrics: true,
1102 include_graphs: true,
1103 include_regression_analysis: true,
1104 templates: ReportTemplateConfig::default(),
1105 styling: ReportStylingConfig::default(),
1106 distribution: ReportDistributionConfig::default(),
1107 }
1108 }
1109}
1110
1111impl Default for ReportTemplateConfig {
1112 fn default() -> Self {
1113 Self {
1114 html_template_path: None,
1115 markdown_template_path: None,
1116 template_variables: HashMap::new(),
1117 enable_caching: true,
1118 }
1119 }
1120}
1121
1122impl Default for ReportStylingConfig {
1123 fn default() -> Self {
1124 Self {
1125 css_path: None,
1126 color_theme: ColorTheme::Light,
1127 font_family: "Arial, sans-serif".to_string(),
1128 chart_style: ChartStyleConfig::default(),
1129 }
1130 }
1131}
1132
1133impl Default for ChartStyleConfig {
1134 fn default() -> Self {
1135 Self {
1136 width: 800,
1137 height: 600,
1138 enable_animations: true,
1139 color_palette: vec![
1140 "#1f77b4".to_string(),
1141 "#ff7f0e".to_string(),
1142 "#2ca02c".to_string(),
1143 "#d62728".to_string(),
1144 "#9467bd".to_string(),
1145 ],
1146 grid_style: GridStyle::Solid,
1147 }
1148 }
1149}
1150
1151impl Default for ReportDistributionConfig {
1152 fn default() -> Self {
1153 Self {
1154 email: None,
1155 slack: None,
1156 webhooks: Vec::new(),
1157 filesystem: Some(FilesystemDistributionConfig::default()),
1158 }
1159 }
1160}
1161
1162impl Default for FilesystemDistributionConfig {
1163 fn default() -> Self {
1164 Self {
1165 output_directory: PathBuf::from("./reports"),
1166 file_naming_pattern: "{timestamp}_{test_name}_{format}".to_string(),
1167 create_date_subdirs: true,
1168 file_permissions: Some(0o644),
1169 }
1170 }
1171}
1172
1173impl Default for ArtifactStorageConfig {
1174 fn default() -> Self {
1175 Self {
1176 enabled: true,
1177 provider: ArtifactStorageProvider::Local(PathBuf::from("./artifacts")),
1178 storage_config: HashMap::new(),
1179 retention: ArtifactRetentionPolicy::default(),
1180 upload: ArtifactUploadConfig::default(),
1181 download: ArtifactDownloadConfig::default(),
1182 }
1183 }
1184}
1185
1186impl Default for ArtifactRetentionPolicy {
1187 fn default() -> Self {
1188 Self {
1189 retention_days: 30,
1190 max_artifacts: Some(1000),
1191 keep_release_artifacts: true,
1192 keep_failed_build_artifacts: false,
1193 cleanup_schedule: None,
1194 }
1195 }
1196}
1197
1198impl Default for ArtifactUploadConfig {
1199 fn default() -> Self {
1200 Self {
1201 compress: true,
1202 compression_level: 6,
1203 encrypt: false,
1204 timeout_sec: 300,
1205 max_file_size_mb: 1024, parallel_uploads: ParallelUploadConfig::default(),
1207 }
1208 }
1209}
1210
1211impl Default for ParallelUploadConfig {
1212 fn default() -> Self {
1213 Self {
1214 enabled: true,
1215 max_concurrent: 4,
1216 chunk_size_mb: 100, }
1218 }
1219}
1220
1221impl Default for ArtifactDownloadConfig {
1222 fn default() -> Self {
1223 Self {
1224 timeout_sec: 300,
1225 enable_caching: true,
1226 cache_directory: Some(PathBuf::from("./cache")),
1227 cache_size_limit_mb: Some(5120), verify_checksums: true,
1229 }
1230 }
1231}
1232
1233impl Default for PerformanceGatesConfig {
1234 fn default() -> Self {
1235 let mut metric_gates = HashMap::new();
1236
1237 metric_gates.insert(
1239 MetricType::ExecutionTime,
1240 MetricGate {
1241 gate_type: GateType::Relative,
1242 threshold: 0.20, operator: ComparisonOperator::LessThanOrEqual,
1244 severity: GateSeverity::Error,
1245 enabled: true,
1246 },
1247 );
1248
1249 metric_gates.insert(
1251 MetricType::MemoryUsage,
1252 MetricGate {
1253 gate_type: GateType::Relative,
1254 threshold: 0.30, operator: ComparisonOperator::LessThanOrEqual,
1256 severity: GateSeverity::Warning,
1257 enabled: true,
1258 },
1259 );
1260
1261 Self {
1262 enabled: true,
1263 metric_gates,
1264 evaluation_strategy: GateEvaluationStrategy::EvaluateAll,
1265 failure_handling: GateFailureHandling::default(),
1266 }
1267 }
1268}
1269
1270impl Default for GateFailureHandling {
1271 fn default() -> Self {
1272 Self {
1273 failure_action: GateFailureAction::FailBuild,
1274 allow_manual_override: true,
1275 override_timeout_hours: Some(24),
1276 notifications: GateFailureNotificationConfig::default(),
1277 }
1278 }
1279}
1280
1281impl CiCdAutomationConfig {
1284 pub fn validate(&self) -> Result<(), String> {
1286 self.test_execution.validate()?;
1288
1289 self.baseline_management.validate()?;
1291
1292 self.reporting.validate()?;
1294
1295 self.artifact_storage.validate()?;
1297
1298 self.performance_gates.validate()?;
1300
1301 Ok(())
1302 }
1303
1304 pub fn get_platform_config(&self) -> PlatformSpecificConfig {
1306 match self.platform {
1307 CiCdPlatform::GitHubActions => PlatformSpecificConfig::github_actions(),
1308 CiCdPlatform::GitLabCI => PlatformSpecificConfig::gitlab_ci(),
1309 CiCdPlatform::Jenkins => PlatformSpecificConfig::jenkins(),
1310 CiCdPlatform::AzureDevOps => PlatformSpecificConfig::azure_devops(),
1311 CiCdPlatform::CircleCI => PlatformSpecificConfig::circle_ci(),
1312 CiCdPlatform::TravisCI => PlatformSpecificConfig::travis_ci(),
1313 CiCdPlatform::TeamCity => PlatformSpecificConfig::team_city(),
1314 CiCdPlatform::Buildkite => PlatformSpecificConfig::buildkite(),
1315 CiCdPlatform::Generic => PlatformSpecificConfig::generic(),
1316 }
1317 }
1318}
1319
1320impl TestExecutionConfig {
1321 fn validate(&self) -> Result<(), String> {
1322 if self.test_timeout == 0 {
1323 return Err("Test timeout cannot be zero".to_string());
1324 }
1325
1326 if self.test_iterations == 0 {
1327 return Err("Test iterations cannot be zero".to_string());
1328 }
1329
1330 if self.max_concurrent_tests == 0 {
1331 return Err("Max concurrent tests cannot be zero".to_string());
1332 }
1333
1334 Ok(())
1335 }
1336}
1337
1338impl BaselineManagementConfig {
1339 fn validate(&self) -> Result<(), String> {
1340 if self.min_improvement_threshold < 0.0 || self.min_improvement_threshold > 1.0 {
1341 return Err("Min improvement threshold must be between 0.0 and 1.0".to_string());
1342 }
1343
1344 if self.max_degradation_threshold < 0.0 || self.max_degradation_threshold > 1.0 {
1345 return Err("Max degradation threshold must be between 0.0 and 1.0".to_string());
1346 }
1347
1348 Ok(())
1349 }
1350}
1351
1352impl ReportingConfig {
1353 fn validate(&self) -> Result<(), String> {
1354 if !self.generate_html
1355 && !self.generate_json
1356 && !self.generate_junit
1357 && !self.generate_markdown
1358 && !self.generate_pdf
1359 {
1360 return Err("At least one report format must be enabled".to_string());
1361 }
1362
1363 Ok(())
1364 }
1365}
1366
1367impl ArtifactStorageConfig {
1368 fn validate(&self) -> Result<(), String> {
1369 if self.enabled {
1370 match &self.provider {
1371 ArtifactStorageProvider::Local(path) => {
1372 if path.as_os_str().is_empty() {
1373 return Err("Local storage path cannot be empty".to_string());
1374 }
1375 }
1376 ArtifactStorageProvider::S3 { bucket, .. } => {
1377 if bucket.is_empty() {
1378 return Err("S3 bucket name cannot be empty".to_string());
1379 }
1380 }
1381 ArtifactStorageProvider::GCS { bucket, .. } => {
1382 if bucket.is_empty() {
1383 return Err("GCS bucket name cannot be empty".to_string());
1384 }
1385 }
1386 ArtifactStorageProvider::AzureBlob {
1387 account, container, ..
1388 } => {
1389 if account.is_empty() || container.is_empty() {
1390 return Err("Azure Blob account and container cannot be empty".to_string());
1391 }
1392 }
1393 ArtifactStorageProvider::FTP { host, .. } => {
1394 if host.is_empty() {
1395 return Err("FTP host cannot be empty".to_string());
1396 }
1397 }
1398 ArtifactStorageProvider::HTTP { base_url, .. } => {
1399 if base_url.is_empty() {
1400 return Err("HTTP base URL cannot be empty".to_string());
1401 }
1402 }
1403 }
1404 }
1405
1406 Ok(())
1407 }
1408}
1409
1410impl PerformanceGatesConfig {
1411 fn validate(&self) -> Result<(), String> {
1412 if self.enabled && self.metric_gates.is_empty() {
1413 return Err("Performance gates are enabled but no gates are configured".to_string());
1414 }
1415
1416 for (metric_type, gate) in &self.metric_gates {
1417 gate.validate(metric_type)?;
1418 }
1419
1420 Ok(())
1421 }
1422}
1423
1424impl MetricGate {
1425 fn validate(&self, _metric_type: &MetricType) -> Result<(), String> {
1426 if self.threshold < 0.0 {
1427 return Err("Gate threshold cannot be negative".to_string());
1428 }
1429
1430 if self.gate_type == GateType::Relative && self.threshold > 10.0 {
1431 return Err("Relative gate threshold seems unreasonably high".to_string());
1433 }
1434
1435 Ok(())
1436 }
1437}
1438
1439#[derive(Debug, Clone)]
1441pub struct PlatformSpecificConfig {
1442 pub env_vars: HashMap<String, String>,
1444 pub artifact_paths: Vec<String>,
1446 pub commands: HashMap<String, String>,
1448 pub limitations: PlatformLimitations,
1450}
1451
1452#[derive(Debug, Clone)]
1454pub struct PlatformLimitations {
1455 pub max_job_runtime_minutes: Option<u32>,
1457 pub max_artifact_size_mb: Option<usize>,
1459 pub max_parallel_jobs: Option<usize>,
1461 pub supported_os: Vec<String>,
1463}
1464
1465impl PlatformSpecificConfig {
1466 fn github_actions() -> Self {
1467 let mut env_vars = HashMap::new();
1468 env_vars.insert("CI".to_string(), "GITHUB_ACTIONS".to_string());
1469 env_vars.insert("BUILD_ID".to_string(), "GITHUB_RUN_ID".to_string());
1470 env_vars.insert("BRANCH".to_string(), "GITHUB_REF_NAME".to_string());
1471 env_vars.insert("COMMIT".to_string(), "GITHUB_SHA".to_string());
1472
1473 let mut commands = HashMap::new();
1474 commands.insert(
1475 "cache_key".to_string(),
1476 "echo \"::set-output name=cache-key::${{ hashFiles('**/Cargo.lock') }}\"".to_string(),
1477 );
1478
1479 Self {
1480 env_vars,
1481 artifact_paths: vec!["./target/criterion".to_string()],
1482 commands,
1483 limitations: PlatformLimitations {
1484 max_job_runtime_minutes: Some(360), max_artifact_size_mb: Some(2048), max_parallel_jobs: Some(20),
1487 supported_os: vec![
1488 "ubuntu-latest".to_string(),
1489 "windows-latest".to_string(),
1490 "macos-latest".to_string(),
1491 ],
1492 },
1493 }
1494 }
1495
1496 fn gitlab_ci() -> Self {
1497 let mut env_vars = HashMap::new();
1498 env_vars.insert("CI".to_string(), "GITLAB_CI".to_string());
1499 env_vars.insert("BUILD_ID".to_string(), "CI_JOB_ID".to_string());
1500 env_vars.insert("BRANCH".to_string(), "CI_COMMIT_REF_NAME".to_string());
1501 env_vars.insert("COMMIT".to_string(), "CI_COMMIT_SHA".to_string());
1502
1503 Self {
1504 env_vars,
1505 artifact_paths: vec!["./target/criterion".to_string()],
1506 commands: HashMap::new(),
1507 limitations: PlatformLimitations {
1508 max_job_runtime_minutes: None, max_artifact_size_mb: Some(1024), max_parallel_jobs: None, supported_os: vec![
1512 "linux".to_string(),
1513 "windows".to_string(),
1514 "macos".to_string(),
1515 ],
1516 },
1517 }
1518 }
1519
1520 fn jenkins() -> Self {
1521 let mut env_vars = HashMap::new();
1522 env_vars.insert("CI".to_string(), "JENKINS".to_string());
1523 env_vars.insert("BUILD_ID".to_string(), "BUILD_NUMBER".to_string());
1524 env_vars.insert("BRANCH".to_string(), "BRANCH_NAME".to_string());
1525 env_vars.insert("COMMIT".to_string(), "GIT_COMMIT".to_string());
1526
1527 Self {
1528 env_vars,
1529 artifact_paths: vec!["./target/criterion".to_string()],
1530 commands: HashMap::new(),
1531 limitations: PlatformLimitations {
1532 max_job_runtime_minutes: None, max_artifact_size_mb: None, max_parallel_jobs: None, supported_os: vec![
1536 "linux".to_string(),
1537 "windows".to_string(),
1538 "macos".to_string(),
1539 ],
1540 },
1541 }
1542 }
1543
1544 fn azure_devops() -> Self {
1545 let mut env_vars = HashMap::new();
1546 env_vars.insert("CI".to_string(), "AZURE_DEVOPS".to_string());
1547 env_vars.insert("BUILD_ID".to_string(), "BUILD_BUILDID".to_string());
1548 env_vars.insert("BRANCH".to_string(), "BUILD_SOURCEBRANCHNAME".to_string());
1549 env_vars.insert("COMMIT".to_string(), "BUILD_SOURCEVERSION".to_string());
1550
1551 Self {
1552 env_vars,
1553 artifact_paths: vec!["./target/criterion".to_string()],
1554 commands: HashMap::new(),
1555 limitations: PlatformLimitations {
1556 max_job_runtime_minutes: Some(360), max_artifact_size_mb: Some(100), max_parallel_jobs: Some(10),
1559 supported_os: vec![
1560 "ubuntu-latest".to_string(),
1561 "windows-latest".to_string(),
1562 "macos-latest".to_string(),
1563 ],
1564 },
1565 }
1566 }
1567
1568 fn circle_ci() -> Self {
1569 let mut env_vars = HashMap::new();
1570 env_vars.insert("CI".to_string(), "CIRCLECI".to_string());
1571 env_vars.insert("BUILD_ID".to_string(), "CIRCLE_BUILD_NUM".to_string());
1572 env_vars.insert("BRANCH".to_string(), "CIRCLE_BRANCH".to_string());
1573 env_vars.insert("COMMIT".to_string(), "CIRCLE_SHA1".to_string());
1574
1575 Self {
1576 env_vars,
1577 artifact_paths: vec!["./target/criterion".to_string()],
1578 commands: HashMap::new(),
1579 limitations: PlatformLimitations {
1580 max_job_runtime_minutes: Some(300), max_artifact_size_mb: Some(3072), max_parallel_jobs: Some(16),
1583 supported_os: vec![
1584 "linux".to_string(),
1585 "macos".to_string(),
1586 "windows".to_string(),
1587 ],
1588 },
1589 }
1590 }
1591
1592 fn travis_ci() -> Self {
1593 let mut env_vars = HashMap::new();
1594 env_vars.insert("CI".to_string(), "TRAVIS".to_string());
1595 env_vars.insert("BUILD_ID".to_string(), "TRAVIS_BUILD_ID".to_string());
1596 env_vars.insert("BRANCH".to_string(), "TRAVIS_BRANCH".to_string());
1597 env_vars.insert("COMMIT".to_string(), "TRAVIS_COMMIT".to_string());
1598
1599 Self {
1600 env_vars,
1601 artifact_paths: vec!["./target/criterion".to_string()],
1602 commands: HashMap::new(),
1603 limitations: PlatformLimitations {
1604 max_job_runtime_minutes: Some(50), max_artifact_size_mb: Some(100), max_parallel_jobs: Some(5),
1607 supported_os: vec![
1608 "linux".to_string(),
1609 "osx".to_string(),
1610 "windows".to_string(),
1611 ],
1612 },
1613 }
1614 }
1615
1616 fn team_city() -> Self {
1617 let mut env_vars = HashMap::new();
1618 env_vars.insert("CI".to_string(), "TEAMCITY".to_string());
1619 env_vars.insert("BUILD_ID".to_string(), "BUILD_NUMBER".to_string());
1620 env_vars.insert("BRANCH".to_string(), "teamcity.build.branch".to_string());
1621 env_vars.insert("COMMIT".to_string(), "BUILD_VCS_NUMBER".to_string());
1622
1623 Self {
1624 env_vars,
1625 artifact_paths: vec!["./target/criterion".to_string()],
1626 commands: HashMap::new(),
1627 limitations: PlatformLimitations {
1628 max_job_runtime_minutes: None, max_artifact_size_mb: None, max_parallel_jobs: None, supported_os: vec![
1632 "linux".to_string(),
1633 "windows".to_string(),
1634 "macos".to_string(),
1635 ],
1636 },
1637 }
1638 }
1639
1640 fn buildkite() -> Self {
1641 let mut env_vars = HashMap::new();
1642 env_vars.insert("CI".to_string(), "BUILDKITE".to_string());
1643 env_vars.insert("BUILD_ID".to_string(), "BUILDKITE_BUILD_ID".to_string());
1644 env_vars.insert("BRANCH".to_string(), "BUILDKITE_BRANCH".to_string());
1645 env_vars.insert("COMMIT".to_string(), "BUILDKITE_COMMIT".to_string());
1646
1647 Self {
1648 env_vars,
1649 artifact_paths: vec!["./target/criterion".to_string()],
1650 commands: HashMap::new(),
1651 limitations: PlatformLimitations {
1652 max_job_runtime_minutes: None, max_artifact_size_mb: None, max_parallel_jobs: None, supported_os: vec![
1656 "linux".to_string(),
1657 "windows".to_string(),
1658 "macos".to_string(),
1659 ],
1660 },
1661 }
1662 }
1663
1664 fn generic() -> Self {
1665 let mut env_vars = HashMap::new();
1666 env_vars.insert("CI".to_string(), "true".to_string());
1667 env_vars.insert("BUILD_ID".to_string(), "BUILD_ID".to_string());
1668 env_vars.insert("BRANCH".to_string(), "BRANCH".to_string());
1669 env_vars.insert("COMMIT".to_string(), "COMMIT_SHA".to_string());
1670
1671 Self {
1672 env_vars,
1673 artifact_paths: vec!["./target/criterion".to_string()],
1674 commands: HashMap::new(),
1675 limitations: PlatformLimitations {
1676 max_job_runtime_minutes: None,
1677 max_artifact_size_mb: None,
1678 max_parallel_jobs: None,
1679 supported_os: vec![
1680 "linux".to_string(),
1681 "windows".to_string(),
1682 "macos".to_string(),
1683 ],
1684 },
1685 }
1686 }
1687}
1688
1689#[cfg(test)]
1690mod tests {
1691 use super::*;
1692
1693 #[test]
1694 fn test_default_config_validation() {
1695 let config = CiCdAutomationConfig::default();
1696 assert!(config.validate().is_ok());
1697 }
1698
1699 #[test]
1700 fn test_invalid_test_execution_config() {
1701 let config = TestExecutionConfig {
1702 test_timeout: 0,
1703 ..Default::default()
1704 };
1705 assert!(config.validate().is_err());
1706
1707 let config = TestExecutionConfig {
1708 test_timeout: 3600,
1709 test_iterations: 0,
1710 ..Default::default()
1711 };
1712 assert!(config.validate().is_err());
1713 }
1714
1715 #[test]
1716 fn test_platform_specific_configs() {
1717 let github_config = PlatformSpecificConfig::github_actions();
1718 assert!(github_config.env_vars.contains_key("CI"));
1719 assert_eq!(github_config.env_vars["CI"], "GITHUB_ACTIONS");
1720
1721 let gitlab_config = PlatformSpecificConfig::gitlab_ci();
1722 assert_eq!(gitlab_config.env_vars["CI"], "GITLAB_CI");
1723 }
1724
1725 #[test]
1726 fn test_metric_gate_validation() {
1727 let gate = MetricGate {
1728 gate_type: GateType::Relative,
1729 threshold: -1.0,
1730 operator: ComparisonOperator::LessThanOrEqual,
1731 severity: GateSeverity::Error,
1732 enabled: true,
1733 };
1734
1735 assert!(gate.validate(&MetricType::ExecutionTime).is_err());
1736 }
1737
1738 #[test]
1739 fn test_platform_enum_serialization() {
1740 let platform = CiCdPlatform::GitHubActions;
1741 let serialized = serde_json::to_string(&platform).expect("unwrap failed");
1742 let deserialized: CiCdPlatform = serde_json::from_str(&serialized).expect("unwrap failed");
1743 assert_eq!(platform, deserialized);
1744 }
1745
1746 #[test]
1747 fn test_comprehensive_config() {
1748 let mut config = CiCdAutomationConfig::default();
1749
1750 config.reporting.generate_html = true;
1752 config.reporting.generate_json = true;
1753 config.reporting.generate_markdown = true;
1754 config.reporting.generate_pdf = true;
1755 config.artifact_storage.enabled = true;
1756 config.performance_gates.enabled = true;
1757
1758 assert!(config.validate().is_ok());
1759 }
1760}