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