1use crate::{
5 config::{
6 core::{
7 EvaluatableProfile, FinalConfig, NextestConfig, NextestConfigImpl, PreBuildPlatform,
8 },
9 elements::{
10 FlakyResult, JunitFlakyFailStatus, LeakTimeout, RetryPolicy, SlowTimeout, TestGroup,
11 TestPriority, ThreadsRequired,
12 },
13 scripts::{
14 CompiledProfileScripts, DeserializedProfileScriptConfig, ScriptId, WrapperScriptConfig,
15 },
16 },
17 errors::{
18 ConfigCompileError, ConfigCompileErrorKind, ConfigCompileSection, ConfigParseErrorKind,
19 },
20 platform::BuildPlatforms,
21 reporter::TestOutputDisplay,
22 run_mode::NextestRunMode,
23};
24use guppy::graph::cargo::BuildPlatform;
25use nextest_filtering::{
26 BinaryQuery, CompiledExpr, Filterset, FiltersetKind, KnownGroups, ParseContext, TestQuery,
27};
28use owo_colors::{OwoColorize, Style};
29use serde::{Deserialize, Deserializer};
30use smol_str::SmolStr;
31use std::collections::HashMap;
32use target_spec::{Platform, TargetSpec};
33
34#[derive(Clone, Debug)]
36pub struct ListSettings<'p, Source = ()> {
37 list_wrapper: Option<(&'p WrapperScriptConfig, Source)>,
38}
39
40impl<'p, Source: Copy> ListSettings<'p, Source> {
41 pub(in crate::config) fn new(
42 profile: &'p EvaluatableProfile<'_>,
43 query: &BinaryQuery<'_>,
44 ) -> Self
45 where
46 Source: TrackSource<'p>,
47 {
48 let ecx = profile.filterset_ecx();
49
50 let mut list_wrapper = None;
51
52 for override_ in &profile.compiled_data.scripts {
53 if let Some(wrapper) = &override_.list_wrapper
54 && list_wrapper.is_none()
55 {
56 let (wrapper, source) =
57 map_wrapper_script(profile, Source::track_script(wrapper.clone(), override_));
58
59 if !override_
60 .is_enabled_binary(query, &ecx)
61 .expect("test() in list-time scripts should have been rejected")
62 {
63 continue;
64 }
65
66 list_wrapper = Some((wrapper, source));
67 }
68 }
69
70 Self { list_wrapper }
71 }
72}
73
74impl<'p> ListSettings<'p> {
75 pub fn debug_empty() -> Self {
79 Self { list_wrapper: None }
80 }
81
82 pub fn debug_set_list_wrapper(&mut self, wrapper: &'p WrapperScriptConfig) -> &mut Self {
86 self.list_wrapper = Some((wrapper, ()));
87 self
88 }
89
90 pub fn list_wrapper(&self) -> Option<&'p WrapperScriptConfig> {
92 self.list_wrapper.as_ref().map(|(wrapper, _)| *wrapper)
93 }
94}
95
96#[derive(Clone, Debug)]
103pub struct TestSettings<'p, Source = ()> {
104 priority: (TestPriority, Source),
105 threads_required: (ThreadsRequired, Source),
106 run_wrapper: Option<(&'p WrapperScriptConfig, Source)>,
107 run_extra_args: (&'p [String], Source),
108 retries: (RetryPolicy, Source),
109 flaky_result: (FlakyResult, Source),
110 slow_timeout: (SlowTimeout, Source),
111 leak_timeout: (LeakTimeout, Source),
112 test_group: (TestGroup, Source),
113 success_output: (TestOutputDisplay, Source),
114 failure_output: (TestOutputDisplay, Source),
115 junit_store_success_output: (bool, Source),
116 junit_store_failure_output: (bool, Source),
117 junit_flaky_fail_status: (JunitFlakyFailStatus, Source),
118}
119
120pub(crate) trait TrackSource<'p>: Sized {
121 fn track_default<T>(value: T) -> (T, Self);
122 fn track_profile<T>(value: T) -> (T, Self);
123 fn track_override<T>(value: T, source: &'p CompiledOverride<FinalConfig>) -> (T, Self);
124 fn track_script<T>(value: T, source: &'p CompiledProfileScripts<FinalConfig>) -> (T, Self);
125}
126
127impl<'p> TrackSource<'p> for () {
128 fn track_default<T>(value: T) -> (T, Self) {
129 (value, ())
130 }
131
132 fn track_profile<T>(value: T) -> (T, Self) {
133 (value, ())
134 }
135
136 fn track_override<T>(value: T, _source: &'p CompiledOverride<FinalConfig>) -> (T, Self) {
137 (value, ())
138 }
139
140 fn track_script<T>(value: T, _source: &'p CompiledProfileScripts<FinalConfig>) -> (T, Self) {
141 (value, ())
142 }
143}
144
145#[derive(Copy, Clone, Debug)]
146pub(crate) enum SettingSource<'p> {
147 Default,
150
151 Profile,
153
154 Override(&'p CompiledOverride<FinalConfig>),
156
157 #[expect(dead_code)]
159 Script(&'p CompiledProfileScripts<FinalConfig>),
160}
161
162impl<'p> TrackSource<'p> for SettingSource<'p> {
163 fn track_default<T>(value: T) -> (T, Self) {
164 (value, SettingSource::Default)
165 }
166
167 fn track_profile<T>(value: T) -> (T, Self) {
168 (value, SettingSource::Profile)
169 }
170
171 fn track_override<T>(value: T, source: &'p CompiledOverride<FinalConfig>) -> (T, Self) {
172 (value, SettingSource::Override(source))
173 }
174
175 fn track_script<T>(value: T, source: &'p CompiledProfileScripts<FinalConfig>) -> (T, Self) {
176 (value, SettingSource::Script(source))
177 }
178}
179
180impl<'p> TestSettings<'p> {
181 pub fn priority(&self) -> TestPriority {
183 self.priority.0
184 }
185
186 pub fn threads_required(&self) -> ThreadsRequired {
188 self.threads_required.0
189 }
190
191 pub fn run_wrapper(&self) -> Option<&'p WrapperScriptConfig> {
193 self.run_wrapper.map(|(script, _)| script)
194 }
195
196 pub fn run_extra_args(&self) -> &'p [String] {
198 self.run_extra_args.0
199 }
200
201 pub fn retries(&self) -> RetryPolicy {
203 self.retries.0
204 }
205
206 pub fn flaky_result(&self) -> FlakyResult {
208 self.flaky_result.0
209 }
210
211 pub fn slow_timeout(&self) -> SlowTimeout {
213 self.slow_timeout.0
214 }
215
216 pub fn leak_timeout(&self) -> LeakTimeout {
218 self.leak_timeout.0
219 }
220
221 pub fn test_group(&self) -> &TestGroup {
223 &self.test_group.0
224 }
225
226 pub fn success_output(&self) -> TestOutputDisplay {
228 self.success_output.0
229 }
230
231 pub fn failure_output(&self) -> TestOutputDisplay {
233 self.failure_output.0
234 }
235
236 pub fn junit_store_success_output(&self) -> bool {
238 self.junit_store_success_output.0
239 }
240
241 pub fn junit_store_failure_output(&self) -> bool {
243 self.junit_store_failure_output.0
244 }
245
246 pub fn junit_flaky_fail_status(&self) -> JunitFlakyFailStatus {
248 self.junit_flaky_fail_status.0
249 }
250}
251
252#[expect(dead_code)]
253impl<'p, Source: Copy> TestSettings<'p, Source> {
254 pub(in crate::config) fn new(
255 profile: &'p EvaluatableProfile<'_>,
256 run_mode: NextestRunMode,
257 query: &TestQuery<'_>,
258 ) -> Self
259 where
260 Source: TrackSource<'p>,
261 {
262 let ecx = profile.filterset_ecx();
263
264 let mut priority = None;
265 let mut threads_required = None;
266 let mut run_wrapper = None;
267 let mut run_extra_args = None;
268 let mut retries = None;
269 let mut flaky_result = None;
270 let mut slow_timeout = None;
271 let mut leak_timeout = None;
272 let mut test_group = None;
273 let mut success_output = None;
274 let mut failure_output = None;
275 let mut junit_store_success_output = None;
276 let mut junit_store_failure_output = None;
277 let mut junit_flaky_fail_status = None;
278
279 for override_ in &profile.compiled_data.overrides {
280 if !override_.matches_test_query(query, &ecx) {
281 continue;
282 }
283
284 if priority.is_none()
285 && let Some(p) = override_.data.priority
286 {
287 priority = Some(Source::track_override(p, override_));
288 }
289 if threads_required.is_none()
290 && let Some(t) = override_.data.threads_required
291 {
292 threads_required = Some(Source::track_override(t, override_));
293 }
294 if run_extra_args.is_none()
295 && let Some(r) = override_.data.run_extra_args.as_deref()
296 {
297 run_extra_args = Some(Source::track_override(r, override_));
298 }
299 if retries.is_none()
300 && let Some(r) = override_.data.retries
301 {
302 retries = Some(Source::track_override(r, override_));
303 }
304 if flaky_result.is_none()
305 && let Some(fr) = override_.data.flaky_result
306 {
307 flaky_result = Some(Source::track_override(fr, override_));
308 }
309 if slow_timeout.is_none() {
310 let timeout_for_mode = match run_mode {
313 NextestRunMode::Test => override_.data.slow_timeout,
314 NextestRunMode::Benchmark => override_.data.bench_slow_timeout,
315 };
316 if let Some(s) = timeout_for_mode {
317 slow_timeout = Some(Source::track_override(s, override_));
318 }
319 }
320 if leak_timeout.is_none()
321 && let Some(l) = override_.data.leak_timeout
322 {
323 leak_timeout = Some(Source::track_override(l, override_));
324 }
325 if test_group.is_none()
326 && let Some(t) = &override_.data.test_group
327 {
328 test_group = Some(Source::track_override(t.clone(), override_));
329 }
330 if success_output.is_none()
331 && let Some(s) = override_.data.success_output
332 {
333 success_output = Some(Source::track_override(s, override_));
334 }
335 if failure_output.is_none()
336 && let Some(f) = override_.data.failure_output
337 {
338 failure_output = Some(Source::track_override(f, override_));
339 }
340 if junit_store_success_output.is_none()
341 && let Some(s) = override_.data.junit.store_success_output
342 {
343 junit_store_success_output = Some(Source::track_override(s, override_));
344 }
345 if junit_store_failure_output.is_none()
346 && let Some(f) = override_.data.junit.store_failure_output
347 {
348 junit_store_failure_output = Some(Source::track_override(f, override_));
349 }
350 if junit_flaky_fail_status.is_none()
351 && let Some(s) = override_.data.junit.flaky_fail_status
352 {
353 junit_flaky_fail_status = Some(Source::track_override(s, override_));
354 }
355 }
356
357 for override_ in &profile.compiled_data.scripts {
358 if !override_.is_enabled(query, &ecx) {
359 continue;
360 }
361
362 if run_wrapper.is_none()
363 && let Some(wrapper) = &override_.run_wrapper
364 {
365 run_wrapper = Some(Source::track_script(wrapper.clone(), override_));
366 }
367 }
368
369 let priority = priority.unwrap_or_else(|| Source::track_default(TestPriority::default()));
371 let threads_required =
372 threads_required.unwrap_or_else(|| Source::track_profile(profile.threads_required()));
373 let run_wrapper = run_wrapper.map(|wrapper| map_wrapper_script(profile, wrapper));
374 let run_extra_args =
375 run_extra_args.unwrap_or_else(|| Source::track_profile(profile.run_extra_args()));
376 let retries = retries.unwrap_or_else(|| Source::track_profile(profile.retries()));
377 let flaky_result =
378 flaky_result.unwrap_or_else(|| Source::track_profile(profile.flaky_result()));
379 let slow_timeout =
380 slow_timeout.unwrap_or_else(|| Source::track_profile(profile.slow_timeout(run_mode)));
381 let leak_timeout =
382 leak_timeout.unwrap_or_else(|| Source::track_profile(profile.leak_timeout()));
383 let test_group = test_group.unwrap_or_else(|| Source::track_profile(TestGroup::Global));
384 let success_output =
385 success_output.unwrap_or_else(|| Source::track_profile(profile.success_output()));
386 let failure_output =
387 failure_output.unwrap_or_else(|| Source::track_profile(profile.failure_output()));
388 let junit_store_success_output = junit_store_success_output.unwrap_or_else(|| {
389 Source::track_profile(profile.junit().is_some_and(|j| j.store_success_output()))
391 });
392 let junit_store_failure_output = junit_store_failure_output.unwrap_or_else(|| {
393 Source::track_profile(profile.junit().is_some_and(|j| j.store_failure_output()))
395 });
396 let junit_flaky_fail_status = junit_flaky_fail_status.unwrap_or_else(|| {
397 Source::track_profile(
398 profile
399 .junit()
400 .map_or(JunitFlakyFailStatus::default(), |j| j.flaky_fail_status()),
401 )
402 });
403
404 TestSettings {
405 threads_required,
406 run_extra_args,
407 run_wrapper,
408 retries,
409 flaky_result,
410 priority,
411 slow_timeout,
412 leak_timeout,
413 test_group,
414 success_output,
415 failure_output,
416 junit_store_success_output,
417 junit_store_failure_output,
418 junit_flaky_fail_status,
419 }
420 }
421
422 pub(crate) fn threads_required_with_source(&self) -> (ThreadsRequired, Source) {
424 self.threads_required
425 }
426
427 pub(crate) fn retries_with_source(&self) -> (RetryPolicy, Source) {
429 self.retries
430 }
431
432 pub(crate) fn slow_timeout_with_source(&self) -> (SlowTimeout, Source) {
434 self.slow_timeout
435 }
436
437 pub(crate) fn leak_timeout_with_source(&self) -> (LeakTimeout, Source) {
439 self.leak_timeout
440 }
441
442 pub(crate) fn test_group_with_source(&self) -> &(TestGroup, Source) {
444 &self.test_group
445 }
446}
447
448fn map_wrapper_script<'p, Source>(
449 profile: &'p EvaluatableProfile<'_>,
450 (script, source): (ScriptId, Source),
451) -> (&'p WrapperScriptConfig, Source)
452where
453 Source: TrackSource<'p>,
454{
455 let wrapper_config = profile
456 .script_config()
457 .wrapper
458 .get(&script)
459 .unwrap_or_else(|| {
460 panic!(
461 "wrapper script {script} not found \
462 (should have been checked while reading config)"
463 )
464 });
465 (wrapper_config, source)
466}
467
468#[derive(Clone, Debug)]
469pub(in crate::config) struct CompiledByProfile {
470 pub(in crate::config) default: CompiledData<PreBuildPlatform>,
471 pub(in crate::config) other: HashMap<String, CompiledData<PreBuildPlatform>>,
472}
473
474impl CompiledByProfile {
475 pub(in crate::config) fn new(
476 pcx: &ParseContext<'_>,
477 config: &NextestConfigImpl,
478 ) -> Result<Self, ConfigParseErrorKind> {
479 let mut errors = vec![];
480 let default = CompiledData::new(
481 pcx,
482 "default",
483 Some(config.default_profile().default_filter()),
484 config.default_profile().overrides(),
485 config.default_profile().setup_scripts(),
486 &mut errors,
487 );
488 let other: HashMap<_, _> = config
489 .other_profiles()
490 .map(|(profile_name, profile)| {
491 (
492 profile_name.to_owned(),
493 CompiledData::new(
494 pcx,
495 profile_name,
496 profile.default_filter(),
497 profile.overrides(),
498 profile.scripts(),
499 &mut errors,
500 ),
501 )
502 })
503 .collect();
504
505 if errors.is_empty() {
506 Ok(Self { default, other })
507 } else {
508 Err(ConfigParseErrorKind::CompileErrors(errors))
509 }
510 }
511
512 pub(in crate::config) fn for_default_config() -> Self {
518 Self {
519 default: CompiledData {
520 profile_default_filter: Some(CompiledDefaultFilter::for_default_config()),
521 overrides: vec![],
522 scripts: vec![],
523 },
524 other: HashMap::new(),
525 }
526 }
527}
528
529#[derive(Clone, Debug)]
533pub struct CompiledDefaultFilter {
534 pub expr: CompiledExpr,
545
546 pub profile: String,
548
549 pub section: CompiledDefaultFilterSection,
551}
552
553impl CompiledDefaultFilter {
554 pub(crate) fn for_default_config() -> Self {
555 Self {
556 expr: CompiledExpr::ALL,
557 profile: NextestConfig::DEFAULT_PROFILE.to_owned(),
558 section: CompiledDefaultFilterSection::Profile,
559 }
560 }
561
562 pub fn display_config(&self, bold_style: Style) -> String {
564 match &self.section {
565 CompiledDefaultFilterSection::Profile => {
566 format!("profile.{}.default-filter", self.profile)
567 .style(bold_style)
568 .to_string()
569 }
570 CompiledDefaultFilterSection::Override(_) => {
571 format!(
572 "default-filter in {}",
573 format!("profile.{}.overrides", self.profile).style(bold_style)
574 )
575 }
576 }
577 }
578}
579
580#[derive(Clone, Copy, Debug)]
583pub enum CompiledDefaultFilterSection {
584 Profile,
586
587 Override(usize),
589}
590
591#[derive(Clone, Debug)]
592pub(in crate::config) struct CompiledData<State> {
593 pub(in crate::config) profile_default_filter: Option<CompiledDefaultFilter>,
598 pub(in crate::config) overrides: Vec<CompiledOverride<State>>,
599 pub(in crate::config) scripts: Vec<CompiledProfileScripts<State>>,
600}
601
602impl CompiledData<PreBuildPlatform> {
603 fn new(
604 pcx: &ParseContext<'_>,
605 profile_name: &str,
606 profile_default_filter: Option<&str>,
607 overrides: &[DeserializedOverride],
608 scripts: &[DeserializedProfileScriptConfig],
609 errors: &mut Vec<ConfigCompileError>,
610 ) -> Self {
611 let profile_default_filter =
612 profile_default_filter.and_then(|filter| {
613 match Filterset::parse(
614 filter.to_owned(),
615 pcx,
616 FiltersetKind::DefaultFilter,
617 &KnownGroups::Unavailable,
618 ) {
619 Ok(expr) => Some(CompiledDefaultFilter {
620 expr: expr.compiled,
621 profile: profile_name.to_owned(),
622 section: CompiledDefaultFilterSection::Profile,
623 }),
624 Err(err) => {
625 errors.push(ConfigCompileError {
626 profile_name: profile_name.to_owned(),
627 section: ConfigCompileSection::DefaultFilter,
628 kind: ConfigCompileErrorKind::Parse {
629 host_parse_error: None,
630 target_parse_error: None,
631 filter_parse_errors: vec![err],
632 },
633 });
634 None
635 }
636 }
637 });
638
639 let overrides = overrides
640 .iter()
641 .enumerate()
642 .filter_map(|(index, source)| {
643 CompiledOverride::new(pcx, profile_name, index, source, errors)
644 })
645 .collect();
646 let scripts = scripts
647 .iter()
648 .enumerate()
649 .filter_map(|(index, source)| {
650 CompiledProfileScripts::new(pcx, profile_name, index, source, errors)
651 })
652 .collect();
653 Self {
654 profile_default_filter,
655 overrides,
656 scripts,
657 }
658 }
659
660 pub(in crate::config) fn extend_reverse(&mut self, other: Self) {
661 if other.profile_default_filter.is_some() {
663 self.profile_default_filter = other.profile_default_filter;
664 }
665 self.overrides.extend(other.overrides.into_iter().rev());
666 self.scripts.extend(other.scripts.into_iter().rev());
667 }
668
669 pub(in crate::config) fn reverse(&mut self) {
670 self.overrides.reverse();
671 self.scripts.reverse();
672 }
673
674 pub(in crate::config) fn chain(self, other: Self) -> Self {
676 let profile_default_filter = self.profile_default_filter.or(other.profile_default_filter);
677 let mut overrides = self.overrides;
678 let mut scripts = self.scripts;
679 overrides.extend(other.overrides);
680 scripts.extend(other.scripts);
681 Self {
682 profile_default_filter,
683 overrides,
684 scripts,
685 }
686 }
687
688 pub(in crate::config) fn apply_build_platforms(
689 self,
690 build_platforms: &BuildPlatforms,
691 ) -> CompiledData<FinalConfig> {
692 let profile_default_filter = self.profile_default_filter;
693 let overrides = self
694 .overrides
695 .into_iter()
696 .map(|override_| override_.apply_build_platforms(build_platforms))
697 .collect();
698 let setup_scripts = self
699 .scripts
700 .into_iter()
701 .map(|setup_script| setup_script.apply_build_platforms(build_platforms))
702 .collect();
703 CompiledData {
704 profile_default_filter,
705 overrides,
706 scripts: setup_scripts,
707 }
708 }
709}
710
711#[derive(Clone, Debug)]
712pub(crate) struct CompiledOverride<State> {
713 id: OverrideId,
714 state: State,
715 pub(in crate::config) data: ProfileOverrideData,
716}
717
718impl<State> CompiledOverride<State> {
719 pub(crate) fn id(&self) -> &OverrideId {
720 &self.id
721 }
722}
723
724#[derive(Clone, Debug, Eq, Hash, PartialEq)]
725pub(crate) struct OverrideId {
726 pub(crate) profile_name: SmolStr,
727 index: usize,
728}
729
730#[derive(Clone, Debug)]
731pub(in crate::config) struct ProfileOverrideData {
732 host_spec: MaybeTargetSpec,
733 target_spec: MaybeTargetSpec,
734 filter: Option<FilterOrDefaultFilter>,
735 priority: Option<TestPriority>,
736 threads_required: Option<ThreadsRequired>,
737 run_extra_args: Option<Vec<String>>,
738 retries: Option<RetryPolicy>,
739 flaky_result: Option<FlakyResult>,
740 slow_timeout: Option<SlowTimeout>,
741 bench_slow_timeout: Option<SlowTimeout>,
742 leak_timeout: Option<LeakTimeout>,
743 pub(in crate::config) test_group: Option<TestGroup>,
744 success_output: Option<TestOutputDisplay>,
745 failure_output: Option<TestOutputDisplay>,
746 junit: DeserializedJunitOutput,
747}
748
749impl CompiledOverride<PreBuildPlatform> {
750 fn new(
751 pcx: &ParseContext<'_>,
752 profile_name: &str,
753 index: usize,
754 source: &DeserializedOverride,
755 errors: &mut Vec<ConfigCompileError>,
756 ) -> Option<Self> {
757 if source.platform.host.is_none()
758 && source.platform.target.is_none()
759 && source.filter.is_none()
760 {
761 errors.push(ConfigCompileError {
762 profile_name: profile_name.to_owned(),
763 section: ConfigCompileSection::Override(index),
764 kind: ConfigCompileErrorKind::ConstraintsNotSpecified {
765 default_filter_specified: source.default_filter.is_some(),
766 },
767 });
768 return None;
769 }
770
771 let host_spec = MaybeTargetSpec::new(source.platform.host.as_deref());
772 let target_spec = MaybeTargetSpec::new(source.platform.target.as_deref());
773 let filter = source.filter.as_ref().map_or(Ok(None), |filter| {
774 Some(Filterset::parse(
775 filter.clone(),
776 pcx,
777 FiltersetKind::OverrideFilter,
778 &KnownGroups::Unavailable,
779 ))
780 .transpose()
781 });
782 let default_filter = source.default_filter.as_ref().map_or(Ok(None), |filter| {
783 Some(Filterset::parse(
784 filter.clone(),
785 pcx,
786 FiltersetKind::DefaultFilter,
787 &KnownGroups::Unavailable,
788 ))
789 .transpose()
790 });
791
792 match (host_spec, target_spec, filter, default_filter) {
793 (Ok(host_spec), Ok(target_spec), Ok(filter), Ok(default_filter)) => {
794 let filter = match (filter, default_filter) {
796 (Some(_), Some(_)) => {
797 errors.push(ConfigCompileError {
798 profile_name: profile_name.to_owned(),
799 section: ConfigCompileSection::Override(index),
800 kind: ConfigCompileErrorKind::FilterAndDefaultFilterSpecified,
801 });
802 return None;
803 }
804 (Some(filter), None) => Some(FilterOrDefaultFilter::Filter(filter)),
805 (None, Some(default_filter)) => {
806 let compiled = CompiledDefaultFilter {
807 expr: default_filter.compiled,
808 profile: profile_name.to_owned(),
809 section: CompiledDefaultFilterSection::Override(index),
810 };
811 Some(FilterOrDefaultFilter::DefaultFilter(compiled))
812 }
813 (None, None) => None,
814 };
815
816 Some(Self {
817 id: OverrideId {
818 profile_name: profile_name.into(),
819 index,
820 },
821 state: PreBuildPlatform {},
822 data: ProfileOverrideData {
823 host_spec,
824 target_spec,
825 filter,
826 priority: source.priority,
827 threads_required: source.threads_required,
828 run_extra_args: source.run_extra_args.clone(),
829 retries: source.retries,
830 flaky_result: source.flaky_result,
831 slow_timeout: source.slow_timeout,
832 bench_slow_timeout: source.bench.slow_timeout,
833 leak_timeout: source.leak_timeout,
834 test_group: source.test_group.clone(),
835 success_output: source.success_output,
836 failure_output: source.failure_output,
837 junit: source.junit,
838 },
839 })
840 }
841 (maybe_host_err, maybe_target_err, maybe_filter_err, maybe_default_filter_err) => {
842 let host_parse_error = maybe_host_err.err();
843 let target_parse_error = maybe_target_err.err();
844 let filter_parse_errors = maybe_filter_err
845 .err()
846 .into_iter()
847 .chain(maybe_default_filter_err.err())
848 .collect();
849
850 errors.push(ConfigCompileError {
851 profile_name: profile_name.to_owned(),
852 section: ConfigCompileSection::Override(index),
853 kind: ConfigCompileErrorKind::Parse {
854 host_parse_error,
855 target_parse_error,
856 filter_parse_errors,
857 },
858 });
859 None
860 }
861 }
862 }
863
864 pub(in crate::config) fn apply_build_platforms(
865 self,
866 build_platforms: &BuildPlatforms,
867 ) -> CompiledOverride<FinalConfig> {
868 let host_eval = self.data.host_spec.eval(&build_platforms.host.platform);
869 let host_test_eval = self.data.target_spec.eval(&build_platforms.host.platform);
870 let target_eval = build_platforms
871 .target
872 .as_ref()
873 .map_or(host_test_eval, |target| {
874 self.data.target_spec.eval(&target.triple.platform)
875 });
876
877 CompiledOverride {
878 id: self.id,
879 state: FinalConfig {
880 host_eval,
881 host_test_eval,
882 target_eval,
883 },
884 data: self.data,
885 }
886 }
887}
888
889impl CompiledOverride<FinalConfig> {
890 pub(crate) fn target_spec(&self) -> &MaybeTargetSpec {
892 &self.data.target_spec
893 }
894
895 pub(crate) fn filter(&self) -> Option<&Filterset> {
897 match self.data.filter.as_ref() {
898 Some(FilterOrDefaultFilter::Filter(filter)) => Some(filter),
899 _ => None,
900 }
901 }
902
903 pub(in crate::config) fn matches_test_query(
906 &self,
907 query: &TestQuery<'_>,
908 ecx: &nextest_filtering::EvalContext<'_>,
909 ) -> bool {
910 if !self.state.host_eval {
911 return false;
912 }
913 if query.binary_query.platform == BuildPlatform::Host && !self.state.host_test_eval {
914 return false;
915 }
916 if query.binary_query.platform == BuildPlatform::Target && !self.state.target_eval {
917 return false;
918 }
919 if let Some(expr) = self.filter()
921 && !expr.matches_test(query, ecx)
922 {
923 return false;
924 }
925 true
926 }
927
928 pub(crate) fn default_filter_if_matches_platform(&self) -> Option<&CompiledDefaultFilter> {
930 match self.data.filter.as_ref() {
931 Some(FilterOrDefaultFilter::DefaultFilter(filter)) => {
932 (self.state.host_eval && self.state.target_eval).then_some(filter)
939 }
940 _ => None,
941 }
942 }
943}
944
945#[derive(Clone, Debug, Default)]
947pub(crate) enum MaybeTargetSpec {
948 Provided(TargetSpec),
949 #[default]
950 Any,
951}
952
953impl MaybeTargetSpec {
954 pub(in crate::config) fn new(platform_str: Option<&str>) -> Result<Self, target_spec::Error> {
955 Ok(match platform_str {
956 Some(platform_str) => {
957 MaybeTargetSpec::Provided(TargetSpec::new(platform_str.to_owned())?)
958 }
959 None => MaybeTargetSpec::Any,
960 })
961 }
962
963 pub(in crate::config) fn eval(&self, platform: &Platform) -> bool {
964 match self {
965 MaybeTargetSpec::Provided(spec) => spec
966 .eval(platform)
967 .unwrap_or(true),
968 MaybeTargetSpec::Any => true,
969 }
970 }
971}
972
973#[derive(Clone, Debug)]
977pub(crate) enum FilterOrDefaultFilter {
978 Filter(Filterset),
979 DefaultFilter(CompiledDefaultFilter),
980}
981
982#[derive(Clone, Debug, Deserialize)]
984#[cfg_attr(feature = "config-schema", derive(schemars::JsonSchema))]
985#[cfg_attr(feature = "config-schema", schemars(deny_unknown_fields))]
986#[serde(rename_all = "kebab-case")]
987pub(in crate::config) struct DeserializedOverride {
988 #[serde(default)]
990 platform: PlatformStrings,
991 #[serde(default)]
993 filter: Option<String>,
994 #[serde(default)]
1001 priority: Option<TestPriority>,
1002 #[serde(default)]
1005 default_filter: Option<String>,
1006 #[serde(default)]
1008 threads_required: Option<ThreadsRequired>,
1009 #[serde(default)]
1011 run_extra_args: Option<Vec<String>>,
1012 #[serde(
1014 default,
1015 deserialize_with = "crate::config::elements::deserialize_retry_policy"
1016 )]
1017 retries: Option<RetryPolicy>,
1018 #[serde(default)]
1020 flaky_result: Option<FlakyResult>,
1021 #[serde(
1023 default,
1024 deserialize_with = "crate::config::elements::deserialize_slow_timeout"
1025 )]
1026 slow_timeout: Option<SlowTimeout>,
1027 #[serde(
1029 default,
1030 deserialize_with = "crate::config::elements::deserialize_leak_timeout"
1031 )]
1032 leak_timeout: Option<LeakTimeout>,
1033 #[serde(default)]
1035 test_group: Option<TestGroup>,
1036 #[serde(default)]
1038 success_output: Option<TestOutputDisplay>,
1039 #[serde(default)]
1041 failure_output: Option<TestOutputDisplay>,
1042 #[serde(default)]
1044 junit: DeserializedJunitOutput,
1045 #[serde(default)]
1047 bench: DeserializedOverrideBench,
1048}
1049
1050#[derive(Copy, Clone, Debug, Default, Deserialize)]
1051#[cfg_attr(feature = "config-schema", derive(schemars::JsonSchema))]
1052#[cfg_attr(feature = "config-schema", schemars(deny_unknown_fields))]
1053#[serde(rename_all = "kebab-case")]
1054pub(in crate::config) struct DeserializedJunitOutput {
1055 store_success_output: Option<bool>,
1058 store_failure_output: Option<bool>,
1061 flaky_fail_status: Option<JunitFlakyFailStatus>,
1063}
1064
1065#[derive(Clone, Debug, Default, Deserialize)]
1067#[cfg_attr(feature = "config-schema", derive(schemars::JsonSchema))]
1068#[cfg_attr(feature = "config-schema", schemars(deny_unknown_fields))]
1069#[serde(rename_all = "kebab-case")]
1070pub(in crate::config) struct DeserializedOverrideBench {
1071 #[serde(
1073 default,
1074 deserialize_with = "crate::config::elements::deserialize_slow_timeout"
1075 )]
1076 slow_timeout: Option<SlowTimeout>,
1077}
1078
1079#[derive(Clone, Debug, Default)]
1080pub(in crate::config) struct PlatformStrings {
1081 pub(in crate::config) host: Option<String>,
1082 pub(in crate::config) target: Option<String>,
1083}
1084
1085#[cfg(feature = "config-schema")]
1086impl schemars::JsonSchema for PlatformStrings {
1087 fn schema_name() -> std::borrow::Cow<'static, str> {
1088 "PlatformStrings".into()
1089 }
1090
1091 fn json_schema(generator: &mut schemars::SchemaGenerator) -> schemars::Schema {
1092 schemars::json_schema!({
1093 "oneOf": [
1094 generator.subschema_for::<String>(),
1095 {
1096 "type": "object",
1097 "properties": {
1098 "host": {
1099 "type": ["string", "null"],
1100 },
1101 "target": {
1102 "type": ["string", "null"],
1103 },
1104 },
1105 "additionalProperties": false,
1106 }
1107 ]
1108 })
1109 }
1110}
1111
1112impl<'de> Deserialize<'de> for PlatformStrings {
1113 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
1114 struct V;
1115
1116 impl<'de2> serde::de::Visitor<'de2> for V {
1117 type Value = PlatformStrings;
1118
1119 fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
1120 formatter.write_str(
1121 "a table ({ host = \"x86_64-apple-darwin\", \
1122 target = \"cfg(windows)\" }) \
1123 or a string (\"x86_64-unknown-gnu-linux\")",
1124 )
1125 }
1126
1127 fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
1128 where
1129 E: serde::de::Error,
1130 {
1131 Ok(PlatformStrings {
1132 host: None,
1133 target: Some(v.to_owned()),
1134 })
1135 }
1136
1137 fn visit_map<A>(self, map: A) -> Result<Self::Value, A::Error>
1138 where
1139 A: serde::de::MapAccess<'de2>,
1140 {
1141 #[derive(Deserialize)]
1142 struct PlatformStringsInner {
1143 #[serde(default)]
1144 host: Option<String>,
1145 #[serde(default)]
1146 target: Option<String>,
1147 }
1148
1149 let inner = PlatformStringsInner::deserialize(
1150 serde::de::value::MapAccessDeserializer::new(map),
1151 )?;
1152 Ok(PlatformStrings {
1153 host: inner.host,
1154 target: inner.target,
1155 })
1156 }
1157 }
1158
1159 deserializer.deserialize_any(V)
1160 }
1161}
1162
1163#[cfg(test)]
1164mod tests {
1165 use super::*;
1166 use crate::config::{
1167 core::NextestConfig,
1168 elements::{LeakTimeoutResult, SlowTimeoutResult},
1169 utils::test_helpers::*,
1170 };
1171 use camino_tempfile::tempdir;
1172 use indoc::indoc;
1173 use nextest_metadata::TestCaseName;
1174 use std::{num::NonZeroUsize, time::Duration};
1175 use test_case::test_case;
1176
1177 #[test]
1179 fn test_overrides_basic() {
1180 let config_contents = indoc! {r#"
1181 # Override 1
1182 [[profile.default.overrides]]
1183 platform = 'aarch64-apple-darwin' # this is the target platform
1184 filter = "test(test)"
1185 retries = { backoff = "exponential", count = 20, delay = "1s", max-delay = "20s" }
1186 slow-timeout = { period = "120s", terminate-after = 1, grace-period = "0s" }
1187 success-output = "immediate-final"
1188 junit = { store-success-output = true }
1189
1190 # Override 2
1191 [[profile.default.overrides]]
1192 filter = "test(test)"
1193 threads-required = 8
1194 retries = 3
1195 slow-timeout = "60s"
1196 leak-timeout = "300ms"
1197 test-group = "my-group"
1198 failure-output = "final"
1199 junit = { store-failure-output = false }
1200
1201 # Override 3
1202 [[profile.default.overrides]]
1203 platform = { host = "cfg(unix)" }
1204 filter = "test(override3)"
1205 retries = 5
1206
1207 # Override 4 -- host not matched
1208 [[profile.default.overrides]]
1209 platform = { host = 'aarch64-apple-darwin' }
1210 retries = 10
1211
1212 # Override 5 -- no filter provided, just platform
1213 [[profile.default.overrides]]
1214 platform = { host = 'cfg(target_os = "linux")', target = 'aarch64-apple-darwin' }
1215 filter = "test(override5)"
1216 retries = 8
1217
1218 # Override 6 -- timeout result success
1219 [[profile.default.overrides]]
1220 filter = "test(timeout_success)"
1221 slow-timeout = { period = "30s", on-timeout = "pass" }
1222
1223 [profile.default.junit]
1224 path = "my-path.xml"
1225
1226 [test-groups.my-group]
1227 max-threads = 20
1228 "#};
1229
1230 let workspace_dir = tempdir().unwrap();
1231
1232 let graph = temp_workspace(&workspace_dir, config_contents);
1233 let package_id = graph.workspace().iter().next().unwrap().id();
1234
1235 let pcx = ParseContext::new(&graph);
1236
1237 let nextest_config_result = NextestConfig::from_sources(
1238 graph.workspace().root(),
1239 &pcx,
1240 None,
1241 &[][..],
1242 &Default::default(),
1243 )
1244 .expect("config is valid");
1245 let profile = nextest_config_result
1246 .profile("default")
1247 .expect("valid profile name")
1248 .apply_build_platforms(&build_platforms());
1249
1250 let host_binary_query =
1252 binary_query(&graph, package_id, "lib", "my-binary", BuildPlatform::Host);
1253 let test_name = TestCaseName::new("test");
1254 let query = TestQuery {
1255 binary_query: host_binary_query.to_query(),
1256 test_name: &test_name,
1257 };
1258 let overrides = profile.settings_for(NextestRunMode::Test, &query);
1259
1260 assert_eq!(overrides.threads_required(), ThreadsRequired::Count(8));
1261 assert_eq!(overrides.retries(), RetryPolicy::new_without_delay(3));
1262 assert_eq!(
1263 overrides.slow_timeout(),
1264 SlowTimeout {
1265 period: Duration::from_secs(60),
1266 on_timeout: SlowTimeoutResult::default(),
1267 terminate_after: None,
1268 grace_period: Duration::from_secs(10),
1269 }
1270 );
1271 assert_eq!(
1272 overrides.leak_timeout(),
1273 LeakTimeout {
1274 period: Duration::from_millis(300),
1275 result: LeakTimeoutResult::Pass,
1276 }
1277 );
1278 assert_eq!(overrides.test_group(), &test_group("my-group"));
1279 assert_eq!(overrides.success_output(), TestOutputDisplay::Never);
1280 assert_eq!(overrides.failure_output(), TestOutputDisplay::Final);
1281 #[expect(clippy::bool_assert_comparison)]
1283 {
1284 assert_eq!(overrides.junit_store_success_output(), false);
1285 assert_eq!(overrides.junit_store_failure_output(), false);
1286 }
1287
1288 let target_binary_query = binary_query(
1290 &graph,
1291 package_id,
1292 "lib",
1293 "my-binary",
1294 BuildPlatform::Target,
1295 );
1296 let test_name = TestCaseName::new("test");
1297 let query = TestQuery {
1298 binary_query: target_binary_query.to_query(),
1299 test_name: &test_name,
1300 };
1301 let overrides = profile.settings_for(NextestRunMode::Test, &query);
1302
1303 assert_eq!(overrides.threads_required(), ThreadsRequired::Count(8));
1304 assert_eq!(
1305 overrides.retries(),
1306 RetryPolicy::Exponential {
1307 count: 20,
1308 delay: Duration::from_secs(1),
1309 jitter: false,
1310 max_delay: Some(Duration::from_secs(20)),
1311 }
1312 );
1313 assert_eq!(
1314 overrides.slow_timeout(),
1315 SlowTimeout {
1316 period: Duration::from_secs(120),
1317 terminate_after: Some(NonZeroUsize::new(1).unwrap()),
1318 grace_period: Duration::ZERO,
1319 on_timeout: SlowTimeoutResult::default(),
1320 }
1321 );
1322 assert_eq!(
1323 overrides.leak_timeout(),
1324 LeakTimeout {
1325 period: Duration::from_millis(300),
1326 result: LeakTimeoutResult::Pass,
1327 }
1328 );
1329 assert_eq!(overrides.test_group(), &test_group("my-group"));
1330 assert_eq!(
1331 overrides.success_output(),
1332 TestOutputDisplay::ImmediateFinal
1333 );
1334 assert_eq!(overrides.failure_output(), TestOutputDisplay::Final);
1335 #[expect(clippy::bool_assert_comparison)]
1337 {
1338 assert_eq!(overrides.junit_store_success_output(), true);
1339 assert_eq!(overrides.junit_store_failure_output(), false);
1340 }
1341
1342 let test_name = TestCaseName::new("override3");
1344 let query = TestQuery {
1345 binary_query: target_binary_query.to_query(),
1346 test_name: &test_name,
1347 };
1348 let overrides = profile.settings_for(NextestRunMode::Test, &query);
1349 assert_eq!(overrides.retries(), RetryPolicy::new_without_delay(5));
1350
1351 let test_name = TestCaseName::new("override5");
1353 let query = TestQuery {
1354 binary_query: target_binary_query.to_query(),
1355 test_name: &test_name,
1356 };
1357 let overrides = profile.settings_for(NextestRunMode::Test, &query);
1358 assert_eq!(overrides.retries(), RetryPolicy::new_without_delay(8));
1359
1360 let test_name = TestCaseName::new("timeout_success");
1362 let query = TestQuery {
1363 binary_query: target_binary_query.to_query(),
1364 test_name: &test_name,
1365 };
1366 let overrides = profile.settings_for(NextestRunMode::Test, &query);
1367 assert_eq!(
1368 overrides.slow_timeout(),
1369 SlowTimeout {
1370 period: Duration::from_secs(30),
1371 on_timeout: SlowTimeoutResult::Pass,
1372 terminate_after: None,
1373 grace_period: Duration::from_secs(10),
1374 }
1375 );
1376
1377 let test_name = TestCaseName::new("no_match");
1379 let query = TestQuery {
1380 binary_query: target_binary_query.to_query(),
1381 test_name: &test_name,
1382 };
1383 let overrides = profile.settings_for(NextestRunMode::Test, &query);
1384 assert_eq!(overrides.retries(), RetryPolicy::new_without_delay(0));
1385 }
1386
1387 #[test]
1389 fn test_overrides_bench_slow_timeout() {
1390 let config_contents = indoc! {r#"
1391 # Profile-level benchmark slow-timeout (used as fallback).
1392 [profile.default]
1393 bench.slow-timeout = { period = "30y" }
1394
1395 # Override 1: Both test and bench slow-timeout specified.
1396 [[profile.default.overrides]]
1397 filter = "test(both_specified)"
1398 slow-timeout = "60s"
1399 bench.slow-timeout = { period = "5m", terminate-after = 2 }
1400
1401 # Override 2: Only test slow-timeout specified.
1402 [[profile.default.overrides]]
1403 filter = "test(test_only)"
1404 slow-timeout = "90s"
1405
1406 # Override 3: Only bench slow-timeout specified.
1407 [[profile.default.overrides]]
1408 filter = "test(bench_only)"
1409 bench.slow-timeout = "10m"
1410 "#};
1411
1412 let workspace_dir = tempdir().unwrap();
1413 let graph = temp_workspace(&workspace_dir, config_contents);
1414 let package_id = graph.workspace().iter().next().unwrap().id();
1415 let pcx = ParseContext::new(&graph);
1416
1417 let nextest_config_result = NextestConfig::from_sources(
1418 graph.workspace().root(),
1419 &pcx,
1420 None,
1421 &[][..],
1422 &Default::default(),
1423 )
1424 .expect("config is valid");
1425 let profile = nextest_config_result
1426 .profile("default")
1427 .expect("valid profile name")
1428 .apply_build_platforms(&build_platforms());
1429
1430 let host_binary_query =
1431 binary_query(&graph, package_id, "lib", "my-binary", BuildPlatform::Host);
1432
1433 let test_name = TestCaseName::new("both_specified");
1436 let query = TestQuery {
1437 binary_query: host_binary_query.to_query(),
1438 test_name: &test_name,
1439 };
1440
1441 let test_settings = profile.settings_for(NextestRunMode::Test, &query);
1442 assert_eq!(test_settings.slow_timeout().period, Duration::from_secs(60));
1443
1444 let bench_settings = profile.settings_for(NextestRunMode::Benchmark, &query);
1445 assert_eq!(
1446 bench_settings.slow_timeout(),
1447 SlowTimeout {
1448 period: Duration::from_secs(5 * 60),
1449 terminate_after: Some(NonZeroUsize::new(2).unwrap()),
1450 grace_period: Duration::from_secs(10),
1451 on_timeout: SlowTimeoutResult::default(),
1452 }
1453 );
1454
1455 let test_name = TestCaseName::new("test_only");
1459 let query = TestQuery {
1460 binary_query: host_binary_query.to_query(),
1461 test_name: &test_name,
1462 };
1463
1464 let test_settings = profile.settings_for(NextestRunMode::Test, &query);
1465 assert_eq!(test_settings.slow_timeout().period, Duration::from_secs(90));
1466
1467 let bench_settings = profile.settings_for(NextestRunMode::Benchmark, &query);
1468 assert!(
1472 bench_settings.slow_timeout().period >= SlowTimeout::VERY_LARGE.period,
1473 "should be >= VERY_LARGE, got {:?}",
1474 bench_settings.slow_timeout().period
1475 );
1476
1477 let test_name = TestCaseName::new("bench_only");
1480 let query = TestQuery {
1481 binary_query: host_binary_query.to_query(),
1482 test_name: &test_name,
1483 };
1484
1485 let test_settings = profile.settings_for(NextestRunMode::Test, &query);
1486 assert_eq!(test_settings.slow_timeout().period, Duration::from_secs(60));
1488
1489 let bench_settings = profile.settings_for(NextestRunMode::Benchmark, &query);
1490 assert_eq!(
1491 bench_settings.slow_timeout().period,
1492 Duration::from_secs(10 * 60)
1493 );
1494 }
1495
1496 #[test_case(
1497 indoc! {r#"
1498 [[profile.default.overrides]]
1499 retries = 2
1500 "#},
1501 "default",
1502 &[MietteJsonReport {
1503 message: "at least one of `platform` and `filter` must be specified".to_owned(),
1504 labels: vec![],
1505 }]
1506
1507 ; "neither platform nor filter specified"
1508 )]
1509 #[test_case(
1510 indoc! {r#"
1511 [[profile.default.overrides]]
1512 default-filter = "test(test1)"
1513 retries = 2
1514 "#},
1515 "default",
1516 &[MietteJsonReport {
1517 message: "for override with `default-filter`, `platform` must also be specified".to_owned(),
1518 labels: vec![],
1519 }]
1520
1521 ; "default-filter without platform"
1522 )]
1523 #[test_case(
1524 indoc! {r#"
1525 [[profile.default.overrides]]
1526 platform = 'cfg(unix)'
1527 default-filter = "not default()"
1528 retries = 2
1529 "#},
1530 "default",
1531 &[MietteJsonReport {
1532 message: "predicate not allowed in `default-filter` expressions".to_owned(),
1533 labels: vec![
1534 MietteJsonLabel {
1535 label: "default() causes infinite recursion".to_owned(),
1536 span: MietteJsonSpan { offset: 4, length: 9 },
1537 },
1538 ],
1539 }]
1540
1541 ; "default filterset in default-filter"
1542 )]
1543 #[test_case(
1544 indoc! {r#"
1545 [[profile.default.overrides]]
1546 filter = 'test(test1)'
1547 default-filter = "test(test2)"
1548 retries = 2
1549 "#},
1550 "default",
1551 &[MietteJsonReport {
1552 message: "at most one of `filter` and `default-filter` must be specified".to_owned(),
1553 labels: vec![],
1554 }]
1555
1556 ; "both filter and default-filter specified"
1557 )]
1558 #[test_case(
1559 indoc! {r#"
1560 [[profile.default.overrides]]
1561 filter = 'test(test1)'
1562 platform = 'cfg(unix)'
1563 default-filter = "test(test2)"
1564 retries = 2
1565 "#},
1566 "default",
1567 &[MietteJsonReport {
1568 message: "at most one of `filter` and `default-filter` must be specified".to_owned(),
1569 labels: vec![],
1570 }]
1571
1572 ; "both filter and default-filter specified with platform"
1573 )]
1574 #[test_case(
1575 indoc! {r#"
1576 [[profile.default.overrides]]
1577 platform = {}
1578 retries = 2
1579 "#},
1580 "default",
1581 &[MietteJsonReport {
1582 message: "at least one of `platform` and `filter` must be specified".to_owned(),
1583 labels: vec![],
1584 }]
1585
1586 ; "empty platform map"
1587 )]
1588 #[test_case(
1589 indoc! {r#"
1590 [[profile.ci.overrides]]
1591 platform = 'cfg(target_os = "macos)'
1592 retries = 2
1593 "#},
1594 "ci",
1595 &[MietteJsonReport {
1596 message: "error parsing cfg() expression".to_owned(),
1597 labels: vec![
1598 MietteJsonLabel { label: "unclosed quotes".to_owned(), span: MietteJsonSpan { offset: 16, length: 6 } }
1599 ]
1600 }]
1601
1602 ; "invalid platform expression"
1603 )]
1604 #[test_case(
1605 indoc! {r#"
1606 [[profile.ci.overrides]]
1607 filter = 'test(/foo)'
1608 retries = 2
1609 "#},
1610 "ci",
1611 &[MietteJsonReport {
1612 message: "expected close regex".to_owned(),
1613 labels: vec![
1614 MietteJsonLabel { label: "missing `/`".to_owned(), span: MietteJsonSpan { offset: 9, length: 0 } }
1615 ]
1616 }]
1617
1618 ; "invalid filterset"
1619 )]
1620 #[test_case(
1621 indoc! {r#"
1623 [profile.ci]
1624 default-filter = "test(foo) or default()"
1625 "#},
1626 "ci",
1627 &[MietteJsonReport {
1628 message: "predicate not allowed in `default-filter` expressions".to_owned(),
1629 labels: vec![
1630 MietteJsonLabel { label: "default() causes infinite recursion".to_owned(), span: MietteJsonSpan { offset: 13, length: 9 } }
1631 ]
1632 }]
1633
1634 ; "default-filter with default"
1635 )]
1636 fn parse_overrides_invalid(
1637 config_contents: &str,
1638 faulty_profile: &str,
1639 expected_reports: &[MietteJsonReport],
1640 ) {
1641 let workspace_dir = tempdir().unwrap();
1642
1643 let graph = temp_workspace(&workspace_dir, config_contents);
1644 let pcx = ParseContext::new(&graph);
1645
1646 let err = NextestConfig::from_sources(
1647 graph.workspace().root(),
1648 &pcx,
1649 None,
1650 [],
1651 &Default::default(),
1652 )
1653 .expect_err("config is invalid");
1654 match err.kind() {
1655 ConfigParseErrorKind::CompileErrors(compile_errors) => {
1656 assert_eq!(
1657 compile_errors.len(),
1658 1,
1659 "exactly one override error must be produced"
1660 );
1661 let error = compile_errors.first().unwrap();
1662 assert_eq!(
1663 error.profile_name, faulty_profile,
1664 "compile error profile matches"
1665 );
1666 let handler = miette::JSONReportHandler::new();
1667 let reports = error
1668 .kind
1669 .reports()
1670 .map(|report| {
1671 let mut out = String::new();
1672 handler.render_report(&mut out, report.as_ref()).unwrap();
1673
1674 let json_report: MietteJsonReport = serde_json::from_str(&out)
1675 .unwrap_or_else(|err| {
1676 panic!(
1677 "failed to deserialize JSON message produced by miette: {err}"
1678 )
1679 });
1680 json_report
1681 })
1682 .collect::<Vec<_>>();
1683 assert_eq!(&reports, expected_reports, "reports match");
1684 }
1685 other => {
1686 panic!(
1687 "for config error {other:?}, expected ConfigParseErrorKind::FiltersetOrCfgParseError"
1688 );
1689 }
1690 };
1691 }
1692
1693 #[test]
1697 fn cfg_unix_with_custom_platform() {
1698 let config_contents = indoc! {r#"
1699 [[profile.default.overrides]]
1700 platform = { host = "cfg(unix)" }
1701 filter = "test(test)"
1702 retries = 5
1703 "#};
1704
1705 let workspace_dir = tempdir().unwrap();
1706
1707 let graph = temp_workspace(&workspace_dir, config_contents);
1708 let package_id = graph.workspace().iter().next().unwrap().id();
1709 let pcx = ParseContext::new(&graph);
1710
1711 let nextest_config = NextestConfig::from_sources(
1712 graph.workspace().root(),
1713 &pcx,
1714 None,
1715 &[][..],
1716 &Default::default(),
1717 )
1718 .expect("config is valid");
1719
1720 let build_platforms = custom_build_platforms(workspace_dir.path());
1721
1722 let profile = nextest_config
1723 .profile("default")
1724 .expect("valid profile name")
1725 .apply_build_platforms(&build_platforms);
1726
1727 let target_binary_query = binary_query(
1729 &graph,
1730 package_id,
1731 "lib",
1732 "my-binary",
1733 BuildPlatform::Target,
1734 );
1735 let test_name = TestCaseName::new("test");
1736 let query = TestQuery {
1737 binary_query: target_binary_query.to_query(),
1738 test_name: &test_name,
1739 };
1740 let overrides = profile.settings_for(NextestRunMode::Test, &query);
1741 assert_eq!(
1742 overrides.retries(),
1743 RetryPolicy::new_without_delay(5),
1744 "retries applied to custom platform"
1745 );
1746 }
1747}