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