1use crate::user_config::helpers::resolve_record_setting;
7use bytesize::ByteSize;
8use serde::Deserialize;
9use std::time::Duration;
10use target_spec::{Platform, TargetSpec};
11
12pub const MIN_MAX_OUTPUT_SIZE: ByteSize = ByteSize::b(1000);
18
19pub const MAX_MAX_OUTPUT_SIZE: ByteSize = ByteSize::mib(256);
27
28#[derive(Clone, Debug, Default, Deserialize)]
33#[serde(rename_all = "kebab-case")]
34pub struct DeserializedRecordConfig {
35 #[serde(default)]
41 pub enabled: Option<bool>,
42
43 #[serde(default)]
45 pub max_records: Option<usize>,
46
47 #[serde(default)]
49 pub max_total_size: Option<ByteSize>,
50
51 #[serde(default, with = "humantime_serde")]
53 pub max_age: Option<Duration>,
54
55 #[serde(default)]
57 pub max_output_size: Option<ByteSize>,
58}
59
60#[derive(Clone, Debug, Deserialize)]
65#[serde(rename_all = "kebab-case")]
66pub struct DefaultRecordConfig {
67 pub enabled: bool,
69
70 pub max_records: usize,
72
73 pub max_total_size: ByteSize,
75
76 #[serde(with = "humantime_serde")]
78 pub max_age: Duration,
79
80 pub max_output_size: ByteSize,
82}
83
84#[derive(Clone, Debug, Default, Deserialize)]
89#[serde(rename_all = "kebab-case")]
90pub(in crate::user_config) struct DeserializedRecordOverrideData {
91 pub(in crate::user_config) enabled: Option<bool>,
93
94 pub(in crate::user_config) max_records: Option<usize>,
96
97 pub(in crate::user_config) max_total_size: Option<ByteSize>,
99
100 #[serde(default, with = "humantime_serde")]
102 pub(in crate::user_config) max_age: Option<Duration>,
103
104 pub(in crate::user_config) max_output_size: Option<ByteSize>,
106}
107
108#[derive(Clone, Debug)]
113pub(in crate::user_config) struct CompiledRecordOverride {
114 platform_spec: TargetSpec,
115 data: RecordOverrideData,
116}
117
118impl CompiledRecordOverride {
119 pub(in crate::user_config) fn new(
121 platform_spec: TargetSpec,
122 data: DeserializedRecordOverrideData,
123 ) -> Self {
124 Self {
125 platform_spec,
126 data: RecordOverrideData {
127 enabled: data.enabled,
128 max_records: data.max_records,
129 max_total_size: data.max_total_size,
130 max_age: data.max_age,
131 max_output_size: data.max_output_size,
132 },
133 }
134 }
135
136 pub(in crate::user_config) fn matches(&self, build_target: &Platform) -> bool {
141 self.platform_spec
142 .eval(build_target)
143 .unwrap_or(false)
144 }
145
146 pub(in crate::user_config) fn data(&self) -> &RecordOverrideData {
148 &self.data
149 }
150}
151
152#[derive(Clone, Debug, Default)]
154pub(in crate::user_config) struct RecordOverrideData {
155 enabled: Option<bool>,
156 max_records: Option<usize>,
157 max_total_size: Option<ByteSize>,
158 max_age: Option<Duration>,
159 max_output_size: Option<ByteSize>,
160}
161
162impl RecordOverrideData {
163 pub(in crate::user_config) fn enabled(&self) -> Option<&bool> {
165 self.enabled.as_ref()
166 }
167
168 pub(in crate::user_config) fn max_records(&self) -> Option<&usize> {
170 self.max_records.as_ref()
171 }
172
173 pub(in crate::user_config) fn max_total_size(&self) -> Option<&ByteSize> {
175 self.max_total_size.as_ref()
176 }
177
178 pub(in crate::user_config) fn max_age(&self) -> Option<&Duration> {
180 self.max_age.as_ref()
181 }
182
183 pub(in crate::user_config) fn max_output_size(&self) -> Option<&ByteSize> {
185 self.max_output_size.as_ref()
186 }
187}
188
189#[derive(Clone, Debug)]
191pub struct RecordConfig {
192 pub enabled: bool,
197
198 pub max_records: usize,
200
201 pub max_total_size: ByteSize,
203
204 pub max_age: Duration,
206
207 pub max_output_size: ByteSize,
209}
210
211impl RecordConfig {
212 pub(in crate::user_config) fn resolve(
227 default_config: &DefaultRecordConfig,
228 default_overrides: &[CompiledRecordOverride],
229 user_config: Option<&DeserializedRecordConfig>,
230 user_overrides: &[CompiledRecordOverride],
231 build_target: &Platform,
232 ) -> Self {
233 let mut max_output_size = resolve_record_setting(
234 &default_config.max_output_size,
235 default_overrides,
236 user_config.and_then(|c| c.max_output_size.as_ref()),
237 user_overrides,
238 build_target,
239 |data| data.max_output_size(),
240 );
241
242 if max_output_size < MIN_MAX_OUTPUT_SIZE {
244 tracing::warn!(
245 "max-output-size ({}) is below minimum ({}), using minimum",
246 max_output_size,
247 MIN_MAX_OUTPUT_SIZE,
248 );
249 max_output_size = MIN_MAX_OUTPUT_SIZE;
250 } else if max_output_size > MAX_MAX_OUTPUT_SIZE {
251 tracing::warn!(
252 "max-output-size ({}) exceeds maximum ({}), using maximum",
253 max_output_size,
254 MAX_MAX_OUTPUT_SIZE,
255 );
256 max_output_size = MAX_MAX_OUTPUT_SIZE;
257 }
258
259 Self {
260 enabled: resolve_record_setting(
261 &default_config.enabled,
262 default_overrides,
263 user_config.and_then(|c| c.enabled.as_ref()),
264 user_overrides,
265 build_target,
266 |data| data.enabled(),
267 ),
268 max_records: resolve_record_setting(
269 &default_config.max_records,
270 default_overrides,
271 user_config.and_then(|c| c.max_records.as_ref()),
272 user_overrides,
273 build_target,
274 |data| data.max_records(),
275 ),
276 max_total_size: resolve_record_setting(
277 &default_config.max_total_size,
278 default_overrides,
279 user_config.and_then(|c| c.max_total_size.as_ref()),
280 user_overrides,
281 build_target,
282 |data| data.max_total_size(),
283 ),
284 max_age: resolve_record_setting(
285 &default_config.max_age,
286 default_overrides,
287 user_config.and_then(|c| c.max_age.as_ref()),
288 user_overrides,
289 build_target,
290 |data| data.max_age(),
291 ),
292 max_output_size,
293 }
294 }
295}
296
297#[cfg(test)]
298mod tests {
299 use super::*;
300
301 #[test]
302 fn test_deserialized_record_config_parsing() {
303 let config: DeserializedRecordConfig = toml::from_str(
305 r#"
306 enabled = true
307 max-records = 50
308 max-total-size = "2GB"
309 max-age = "7d"
310 max-output-size = "5MB"
311 "#,
312 )
313 .unwrap();
314
315 assert_eq!(config.enabled, Some(true));
316 assert_eq!(config.max_records, Some(50));
317 assert_eq!(config.max_total_size, Some(ByteSize::gb(2)));
318 assert_eq!(config.max_age, Some(Duration::from_secs(7 * 24 * 60 * 60)));
319 assert_eq!(config.max_output_size, Some(ByteSize::mb(5)));
320
321 let config: DeserializedRecordConfig = toml::from_str(
323 r#"
324 max-records = 100
325 "#,
326 )
327 .unwrap();
328
329 assert!(config.enabled.is_none());
330 assert_eq!(config.max_records, Some(100));
331 assert!(config.max_total_size.is_none());
332 assert!(config.max_age.is_none());
333 assert!(config.max_output_size.is_none());
334
335 let config: DeserializedRecordConfig = toml::from_str("").unwrap();
337 assert!(config.enabled.is_none());
338 assert!(config.max_records.is_none());
339 assert!(config.max_total_size.is_none());
340 assert!(config.max_age.is_none());
341 assert!(config.max_output_size.is_none());
342 }
343
344 #[test]
345 fn test_default_record_config_parsing() {
346 let config: DefaultRecordConfig = toml::from_str(
347 r#"
348 enabled = true
349 max-records = 100
350 max-total-size = "1GB"
351 max-age = "30d"
352 max-output-size = "10MB"
353 "#,
354 )
355 .unwrap();
356
357 assert!(config.enabled);
358 assert_eq!(config.max_records, 100);
359 assert_eq!(config.max_total_size, ByteSize::gb(1));
360 assert_eq!(config.max_age, Duration::from_secs(30 * 24 * 60 * 60));
361 assert_eq!(config.max_output_size, ByteSize::mb(10));
362 }
363
364 #[test]
365 fn test_resolve_uses_defaults() {
366 let defaults = DefaultRecordConfig {
367 enabled: false,
368 max_records: 100,
369 max_total_size: ByteSize::gb(1),
370 max_age: Duration::from_secs(30 * 24 * 60 * 60),
371 max_output_size: ByteSize::mb(10),
372 };
373
374 let build_target =
375 Platform::build_target().expect("nextest is built for a supported platform");
376 let resolved = RecordConfig::resolve(&defaults, &[], None, &[], &build_target);
377
378 assert!(!resolved.enabled);
379 assert_eq!(resolved.max_records, 100);
380 assert_eq!(resolved.max_total_size, ByteSize::gb(1));
381 assert_eq!(resolved.max_age, Duration::from_secs(30 * 24 * 60 * 60));
382 assert_eq!(resolved.max_output_size, ByteSize::mb(10));
383 }
384
385 #[test]
386 fn test_resolve_user_overrides_defaults() {
387 let defaults = DefaultRecordConfig {
388 enabled: false,
389 max_records: 100,
390 max_total_size: ByteSize::gb(1),
391 max_age: Duration::from_secs(30 * 24 * 60 * 60),
392 max_output_size: ByteSize::mb(10),
393 };
394
395 let user_config = DeserializedRecordConfig {
396 enabled: Some(true),
397 max_records: Some(50),
398 max_total_size: None,
399 max_age: Some(Duration::from_secs(7 * 24 * 60 * 60)),
400 max_output_size: Some(ByteSize::mb(5)),
401 };
402
403 let build_target =
404 Platform::build_target().expect("nextest is built for a supported platform");
405 let resolved =
406 RecordConfig::resolve(&defaults, &[], Some(&user_config), &[], &build_target);
407
408 assert!(resolved.enabled); assert_eq!(resolved.max_records, 50); assert_eq!(resolved.max_total_size, ByteSize::gb(1)); assert_eq!(resolved.max_age, Duration::from_secs(7 * 24 * 60 * 60)); assert_eq!(resolved.max_output_size, ByteSize::mb(5)); }
414
415 #[test]
416 fn test_resolve_clamps_small_max_output_size() {
417 let defaults = DefaultRecordConfig {
418 enabled: false,
419 max_records: 100,
420 max_total_size: ByteSize::gb(1),
421 max_age: Duration::from_secs(30 * 24 * 60 * 60),
422 max_output_size: ByteSize::mb(10),
423 };
424
425 let user_config = DeserializedRecordConfig {
427 enabled: None,
428 max_records: None,
429 max_total_size: None,
430 max_age: None,
431 max_output_size: Some(ByteSize::b(100)), };
433
434 let build_target =
435 Platform::build_target().expect("nextest is built for a supported platform");
436 let resolved =
437 RecordConfig::resolve(&defaults, &[], Some(&user_config), &[], &build_target);
438
439 assert_eq!(resolved.max_output_size, MIN_MAX_OUTPUT_SIZE);
441 }
442
443 #[test]
444 fn test_resolve_accepts_value_at_minimum() {
445 let defaults = DefaultRecordConfig {
446 enabled: false,
447 max_records: 100,
448 max_total_size: ByteSize::gb(1),
449 max_age: Duration::from_secs(30 * 24 * 60 * 60),
450 max_output_size: ByteSize::mb(10),
451 };
452
453 let user_config = DeserializedRecordConfig {
455 enabled: None,
456 max_records: None,
457 max_total_size: None,
458 max_age: None,
459 max_output_size: Some(MIN_MAX_OUTPUT_SIZE),
460 };
461
462 let build_target =
463 Platform::build_target().expect("nextest is built for a supported platform");
464 let resolved =
465 RecordConfig::resolve(&defaults, &[], Some(&user_config), &[], &build_target);
466
467 assert_eq!(resolved.max_output_size, MIN_MAX_OUTPUT_SIZE);
469 }
470
471 #[test]
472 fn test_resolve_clamps_large_max_output_size() {
473 let defaults = DefaultRecordConfig {
474 enabled: false,
475 max_records: 100,
476 max_total_size: ByteSize::gb(1),
477 max_age: Duration::from_secs(30 * 24 * 60 * 60),
478 max_output_size: ByteSize::mb(10),
479 };
480
481 let user_config = DeserializedRecordConfig {
483 enabled: None,
484 max_records: None,
485 max_total_size: None,
486 max_age: None,
487 max_output_size: Some(ByteSize::gib(1)), };
489
490 let build_target =
491 Platform::build_target().expect("nextest is built for a supported platform");
492 let resolved =
493 RecordConfig::resolve(&defaults, &[], Some(&user_config), &[], &build_target);
494
495 assert_eq!(resolved.max_output_size, MAX_MAX_OUTPUT_SIZE);
497 }
498
499 #[test]
500 fn test_resolve_accepts_value_at_maximum() {
501 let defaults = DefaultRecordConfig {
502 enabled: false,
503 max_records: 100,
504 max_total_size: ByteSize::gb(1),
505 max_age: Duration::from_secs(30 * 24 * 60 * 60),
506 max_output_size: ByteSize::mb(10),
507 };
508
509 let user_config = DeserializedRecordConfig {
511 enabled: None,
512 max_records: None,
513 max_total_size: None,
514 max_age: None,
515 max_output_size: Some(MAX_MAX_OUTPUT_SIZE),
516 };
517
518 let build_target =
519 Platform::build_target().expect("nextest is built for a supported platform");
520 let resolved =
521 RecordConfig::resolve(&defaults, &[], Some(&user_config), &[], &build_target);
522
523 assert_eq!(resolved.max_output_size, MAX_MAX_OUTPUT_SIZE);
525 }
526
527 fn make_override(
529 platform: &str,
530 data: DeserializedRecordOverrideData,
531 ) -> CompiledRecordOverride {
532 let platform_spec =
533 TargetSpec::new(platform.to_string()).expect("valid platform spec in test");
534 CompiledRecordOverride::new(platform_spec, data)
535 }
536
537 #[test]
538 fn test_resolve_user_override_applies() {
539 let defaults = DefaultRecordConfig {
540 enabled: false,
541 max_records: 100,
542 max_total_size: ByteSize::gb(1),
543 max_age: Duration::from_secs(30 * 24 * 60 * 60),
544 max_output_size: ByteSize::mb(10),
545 };
546
547 let override_ = make_override(
549 "cfg(all())",
550 DeserializedRecordOverrideData {
551 enabled: Some(true),
552 max_records: Some(50),
553 ..Default::default()
554 },
555 );
556
557 let build_target =
558 Platform::build_target().expect("nextest is built for a supported platform");
559 let resolved = RecordConfig::resolve(&defaults, &[], None, &[override_], &build_target);
560
561 assert!(resolved.enabled);
562 assert_eq!(resolved.max_records, 50);
563 assert_eq!(resolved.max_total_size, ByteSize::gb(1)); assert_eq!(resolved.max_age, Duration::from_secs(30 * 24 * 60 * 60)); assert_eq!(resolved.max_output_size, ByteSize::mb(10)); }
567
568 #[test]
569 fn test_resolve_default_override_applies() {
570 let defaults = DefaultRecordConfig {
571 enabled: false,
572 max_records: 100,
573 max_total_size: ByteSize::gb(1),
574 max_age: Duration::from_secs(30 * 24 * 60 * 60),
575 max_output_size: ByteSize::mb(10),
576 };
577
578 let override_ = make_override(
580 "cfg(all())",
581 DeserializedRecordOverrideData {
582 enabled: Some(true),
583 max_records: Some(50),
584 ..Default::default()
585 },
586 );
587
588 let build_target =
589 Platform::build_target().expect("nextest is built for a supported platform");
590 let resolved = RecordConfig::resolve(&defaults, &[override_], None, &[], &build_target);
591
592 assert!(resolved.enabled);
593 assert_eq!(resolved.max_records, 50);
594 assert_eq!(resolved.max_total_size, ByteSize::gb(1)); }
596
597 #[test]
598 fn test_resolve_platform_override_no_match() {
599 let defaults = DefaultRecordConfig {
600 enabled: false,
601 max_records: 100,
602 max_total_size: ByteSize::gb(1),
603 max_age: Duration::from_secs(30 * 24 * 60 * 60),
604 max_output_size: ByteSize::mb(10),
605 };
606
607 let override_ = make_override(
610 "cfg(any())",
611 DeserializedRecordOverrideData {
612 enabled: Some(true),
613 max_records: Some(50),
614 max_total_size: Some(ByteSize::gb(2)),
615 max_age: Some(Duration::from_secs(7 * 24 * 60 * 60)),
616 max_output_size: Some(ByteSize::mb(5)),
617 },
618 );
619
620 let build_target =
621 Platform::build_target().expect("nextest is built for a supported platform");
622 let resolved = RecordConfig::resolve(&defaults, &[], None, &[override_], &build_target);
623
624 assert!(!resolved.enabled);
626 assert_eq!(resolved.max_records, 100);
627 assert_eq!(resolved.max_total_size, ByteSize::gb(1));
628 assert_eq!(resolved.max_age, Duration::from_secs(30 * 24 * 60 * 60));
629 assert_eq!(resolved.max_output_size, ByteSize::mb(10));
630 }
631
632 #[test]
633 fn test_resolve_first_matching_user_override_wins() {
634 let defaults = DefaultRecordConfig {
635 enabled: false,
636 max_records: 100,
637 max_total_size: ByteSize::gb(1),
638 max_age: Duration::from_secs(30 * 24 * 60 * 60),
639 max_output_size: ByteSize::mb(10),
640 };
641
642 let override1 = make_override(
644 "cfg(all())",
645 DeserializedRecordOverrideData {
646 enabled: Some(true),
647 ..Default::default()
648 },
649 );
650
651 let override2 = make_override(
652 "cfg(all())",
653 DeserializedRecordOverrideData {
654 enabled: Some(false), max_records: Some(50),
656 ..Default::default()
657 },
658 );
659
660 let build_target =
661 Platform::build_target().expect("nextest is built for a supported platform");
662 let resolved =
663 RecordConfig::resolve(&defaults, &[], None, &[override1, override2], &build_target);
664
665 assert!(resolved.enabled);
667 assert_eq!(resolved.max_records, 50);
669 }
670
671 #[test]
672 fn test_resolve_user_override_beats_default_override() {
673 let defaults = DefaultRecordConfig {
674 enabled: false,
675 max_records: 100,
676 max_total_size: ByteSize::gb(1),
677 max_age: Duration::from_secs(30 * 24 * 60 * 60),
678 max_output_size: ByteSize::mb(10),
679 };
680
681 let user_override = make_override(
683 "cfg(all())",
684 DeserializedRecordOverrideData {
685 enabled: Some(true),
686 ..Default::default()
687 },
688 );
689
690 let default_override = make_override(
692 "cfg(all())",
693 DeserializedRecordOverrideData {
694 enabled: Some(false), max_records: Some(50),
696 ..Default::default()
697 },
698 );
699
700 let build_target =
701 Platform::build_target().expect("nextest is built for a supported platform");
702 let resolved = RecordConfig::resolve(
703 &defaults,
704 &[default_override],
705 None,
706 &[user_override],
707 &build_target,
708 );
709
710 assert!(resolved.enabled);
712 assert_eq!(resolved.max_records, 50);
714 }
715
716 #[test]
717 fn test_resolve_override_beats_user_base() {
718 let defaults = DefaultRecordConfig {
719 enabled: false,
720 max_records: 100,
721 max_total_size: ByteSize::gb(1),
722 max_age: Duration::from_secs(30 * 24 * 60 * 60),
723 max_output_size: ByteSize::mb(10),
724 };
725
726 let user_config = DeserializedRecordConfig {
728 enabled: Some(false),
729 max_records: Some(25),
730 ..Default::default()
731 };
732
733 let default_override = make_override(
735 "cfg(all())",
736 DeserializedRecordOverrideData {
737 enabled: Some(true),
738 ..Default::default()
739 },
740 );
741
742 let build_target =
743 Platform::build_target().expect("nextest is built for a supported platform");
744 let resolved = RecordConfig::resolve(
745 &defaults,
746 &[default_override],
747 Some(&user_config),
748 &[],
749 &build_target,
750 );
751
752 assert!(resolved.enabled);
754 assert_eq!(resolved.max_records, 25);
756 }
757
758 #[test]
759 fn test_resolve_override_clamps_max_output_size() {
760 let defaults = DefaultRecordConfig {
761 enabled: false,
762 max_records: 100,
763 max_total_size: ByteSize::gb(1),
764 max_age: Duration::from_secs(30 * 24 * 60 * 60),
765 max_output_size: ByteSize::mb(10),
766 };
767
768 let override_ = make_override(
770 "cfg(all())",
771 DeserializedRecordOverrideData {
772 max_output_size: Some(ByteSize::b(100)), ..Default::default()
774 },
775 );
776
777 let build_target =
778 Platform::build_target().expect("nextest is built for a supported platform");
779 let resolved = RecordConfig::resolve(&defaults, &[], None, &[override_], &build_target);
780
781 assert_eq!(resolved.max_output_size, MIN_MAX_OUTPUT_SIZE);
783 }
784}