1use super::clap_error::EarlyArgs;
7use crate::{
8 ExpectedError, Result,
9 cargo_cli::{CargoCli, CargoOptions},
10 output::OutputContext,
11 reuse_build::{ArchiveFormatOpt, ReuseBuildOpts, make_path_mapper},
12};
13use camino::{Utf8Path, Utf8PathBuf};
14use clap::{ArgAction, Args, Subcommand, ValueEnum, builder::BoolishValueParser};
15use guppy::graph::PackageGraph;
16use nextest_filtering::ParseContext;
17use nextest_metadata::BuildPlatform;
18use nextest_runner::{
19 cargo_config::EnvironmentMap,
20 config::{
21 core::{
22 ConfigExperimental, EvaluatableProfile, NextestConfig, ToolConfigFile,
23 VersionOnlyConfig, get_num_cpus,
24 },
25 elements::{MaxFail, RetryPolicy, TestThreads},
26 },
27 list::{
28 BinaryList, OutputFormat, RustTestArtifact, SerializableFormat, TestExecuteContext,
29 TestList,
30 },
31 partition::PartitionerBuilder,
32 platform::BuildPlatforms,
33 reporter::{
34 FinalStatusLevel, MaxProgressRunning, ReporterBuilder, StatusLevel, TestOutputDisplay,
35 },
36 reuse_build::ReuseBuildInfo,
37 run_mode::NextestRunMode,
38 runner::{
39 DebuggerCommand, Interceptor, StressCondition, StressCount, TestRunnerBuilder,
40 TracerCommand,
41 },
42 test_filter::{FilterBound, RunIgnored, TestFilterBuilder, TestFilterPatterns},
43 test_output::CaptureStrategy,
44 user_config::elements::{PagerSetting, PaginateSetting, UiConfig, UiShowProgress},
45};
46use std::{collections::BTreeSet, io::Cursor, sync::Arc, time::Duration};
47use tracing::{debug, warn};
48
49#[derive(Debug, Args)]
51pub(super) struct CommonOpts {
52 #[arg(
54 long,
55 global = true,
56 value_name = "PATH",
57 help_heading = "Manifest options"
58 )]
59 pub(super) manifest_path: Option<Utf8PathBuf>,
60
61 #[clap(flatten)]
62 pub(super) output: crate::output::OutputOpts,
63
64 #[clap(flatten)]
65 pub(super) config_opts: ConfigOpts,
66}
67
68#[derive(Debug, Args)]
69#[command(next_help_heading = "Config options")]
70pub(super) struct ConfigOpts {
71 #[arg(long, global = true, value_name = "PATH")]
73 pub config_file: Option<Utf8PathBuf>,
74
75 #[arg(long = "tool-config-file", global = true, value_name = "TOOL:ABS_PATH")]
89 pub tool_config_files: Vec<ToolConfigFile>,
90
91 #[arg(long, global = true)]
96 pub override_version_check: bool,
97
98 #[arg(
104 long,
105 short = 'P',
106 env = "NEXTEST_PROFILE",
107 global = true,
108 help_heading = "Config options"
109 )]
110 pub(super) profile: Option<String>,
111}
112
113impl ConfigOpts {
114 pub(super) fn make_version_only_config(
116 &self,
117 workspace_root: &Utf8Path,
118 ) -> Result<VersionOnlyConfig> {
119 VersionOnlyConfig::from_sources(
120 workspace_root,
121 self.config_file.as_deref(),
122 &self.tool_config_files,
123 )
124 .map_err(ExpectedError::config_parse_error)
125 }
126
127 pub(super) fn make_config(
129 &self,
130 workspace_root: &Utf8Path,
131 pcx: &ParseContext<'_>,
132 experimental: &BTreeSet<ConfigExperimental>,
133 ) -> Result<NextestConfig> {
134 NextestConfig::from_sources(
135 workspace_root,
136 pcx,
137 self.config_file.as_deref(),
138 &self.tool_config_files,
139 experimental,
140 )
141 .map_err(ExpectedError::config_parse_error)
142 }
143}
144
145#[derive(Debug, Subcommand)]
146pub(super) enum Command {
147 List(Box<ListOpts>),
158 #[command(visible_alias = "r")]
165 Run(Box<RunOpts>),
166 #[command(visible_alias = "b")]
174 Bench(Box<BenchOpts>),
175 Archive(Box<ArchiveOpts>),
183 ShowConfig {
190 #[clap(subcommand)]
191 command: super::commands::ShowConfigCommand,
192 },
193 #[clap(name = "self")]
195 Self_ {
196 #[clap(subcommand)]
197 command: super::commands::SelfCommand,
198 },
199 #[clap(hide = true)]
204 Debug {
205 #[clap(subcommand)]
206 command: super::commands::DebugCommand,
207 },
208}
209
210#[derive(Debug, Args)]
211pub(super) struct ArchiveOpts {
212 #[clap(flatten)]
213 pub(super) cargo_options: CargoOptions,
214
215 #[arg(
217 long,
218 name = "archive-file",
219 help_heading = "Archive options",
220 value_name = "PATH"
221 )]
222 pub(super) archive_file: Utf8PathBuf,
223
224 #[arg(
229 long,
230 value_enum,
231 help_heading = "Archive options",
232 value_name = "FORMAT",
233 default_value_t
234 )]
235 pub(super) archive_format: ArchiveFormatOpt,
236
237 #[clap(flatten)]
238 pub(super) archive_build_filter: ArchiveBuildFilter,
239
240 #[arg(
242 long,
243 help_heading = "Archive options",
244 value_name = "LEVEL",
245 default_value_t = 0,
246 allow_negative_numbers = true
247 )]
248 pub(super) zstd_level: i32,
249 }
251
252#[derive(Debug, Args)]
253pub(super) struct ListOpts {
254 #[clap(flatten)]
255 pub(super) cargo_options: CargoOptions,
256
257 #[clap(flatten)]
258 pub(super) build_filter: TestBuildFilter,
259
260 #[arg(
262 short = 'T',
263 long,
264 value_enum,
265 default_value_t,
266 help_heading = "Output options",
267 value_name = "FMT"
268 )]
269 pub(super) message_format: MessageFormatOpts,
270
271 #[arg(
273 long,
274 value_enum,
275 default_value_t,
276 help_heading = "Output options",
277 value_name = "TYPE"
278 )]
279 pub(super) list_type: ListType,
280
281 #[clap(flatten)]
282 pub(super) pager_opts: PagerOpts,
283
284 #[clap(flatten)]
285 pub(super) reuse_build: ReuseBuildOpts,
286}
287
288#[derive(Debug, Default, Args)]
290#[command(next_help_heading = "Pager options")]
291pub(super) struct PagerOpts {
292 #[arg(long)]
294 no_pager: bool,
295}
296
297impl PagerOpts {
298 pub(super) fn resolve(&self, resolved_ui: &UiConfig) -> (PagerSetting, PaginateSetting) {
304 if self.no_pager {
305 return (resolved_ui.pager.clone(), PaginateSetting::Never);
307 }
308
309 (resolved_ui.pager.clone(), resolved_ui.paginate)
311 }
312}
313
314#[derive(Debug, Args)]
315pub(super) struct RunOpts {
316 #[clap(flatten)]
317 pub(super) cargo_options: CargoOptions,
318
319 #[clap(flatten)]
320 pub(super) build_filter: TestBuildFilter,
321
322 #[clap(flatten)]
323 pub(super) runner_opts: TestRunnerOpts,
324
325 #[arg(
327 long,
328 name = "no-capture",
329 alias = "nocapture",
330 help_heading = "Runner options",
331 display_order = 100
332 )]
333 pub(super) no_capture: bool,
334
335 #[clap(flatten)]
336 pub(super) reporter_opts: ReporterOpts,
337
338 #[clap(flatten)]
339 pub(super) reuse_build: ReuseBuildOpts,
340}
341
342#[derive(Debug, Args)]
343pub(super) struct BenchOpts {
344 #[clap(flatten)]
345 pub(super) cargo_options: CargoOptions,
346
347 #[clap(flatten)]
348 pub(super) build_filter: TestBuildFilter,
349
350 #[clap(flatten)]
351 pub(super) runner_opts: BenchRunnerOpts,
352
353 #[arg(
358 long,
359 name = "no-capture",
360 alias = "nocapture",
361 help_heading = "Runner options",
362 display_order = 100
363 )]
364 pub(super) no_capture: bool,
365
366 #[clap(flatten)]
367 pub(super) reporter_opts: BenchReporterOpts,
368 }
370
371#[derive(Debug, Default, Args)]
373#[command(next_help_heading = "Runner options")]
374pub(super) struct BenchRunnerOpts {
375 #[arg(long, name = "no-run")]
377 pub(super) no_run: bool,
378
379 #[arg(
381 long,
382 visible_alias = "ff",
383 name = "fail-fast",
384 conflicts_with = "no-run"
390 )]
391 fail_fast: bool,
392
393 #[arg(
395 long,
396 visible_alias = "nff",
397 name = "no-fail-fast",
398 conflicts_with = "no-run",
399 overrides_with = "fail-fast"
400 )]
401 no_fail_fast: bool,
402
403 #[arg(
406 long,
407 name = "max-fail",
408 value_name = "N",
409 conflicts_with_all = &["no-run", "fail-fast", "no-fail-fast"],
410 )]
411 max_fail: Option<MaxFail>,
412
413 #[arg(long, value_enum, value_name = "ACTION", env = "NEXTEST_NO_TESTS")]
415 pub(super) no_tests: Option<NoTestsBehavior>,
416
417 #[clap(flatten)]
419 pub(super) stress: StressOptions,
420
421 #[clap(flatten)]
422 pub(super) interceptor: InterceptorOpt,
423}
424
425impl BenchRunnerOpts {
426 pub(super) fn to_builder(&self, cap_strat: CaptureStrategy) -> Option<TestRunnerBuilder> {
427 if self.no_tests.is_some() && self.no_run {
428 warn!("ignoring --no-tests because --no-run is specified");
429 }
430
431 if self.no_run {
434 return None;
435 }
436 let mut builder = TestRunnerBuilder::default();
437 builder.set_capture_strategy(cap_strat);
438 if let Some(max_fail) = self.max_fail {
439 builder.set_max_fail(max_fail);
440 debug!(max_fail = ?max_fail, "set max fail");
441 } else if self.no_fail_fast {
442 builder.set_max_fail(MaxFail::from_fail_fast(false));
443 debug!("set max fail via from_fail_fast(false)");
444 } else if self.fail_fast {
445 builder.set_max_fail(MaxFail::from_fail_fast(true));
446 debug!("set max fail via from_fail_fast(true)");
447 }
448
449 builder.set_test_threads(TestThreads::Count(1));
451
452 if let Some(condition) = self.stress.condition.as_ref() {
453 builder.set_stress_condition(condition.stress_condition());
454 }
455
456 builder.set_interceptor(self.interceptor.to_interceptor());
457
458 Some(builder)
459 }
460}
461
462#[derive(Debug, Default, Args)]
464#[command(next_help_heading = "Reporter options")]
465pub(super) struct BenchReporterOpts {
466 #[arg(long, env = "NEXTEST_SHOW_PROGRESS")]
474 show_progress: Option<ShowProgressOpt>,
475
476 #[arg(long, env = "NEXTEST_NO_INPUT_HANDLER", value_parser = BoolishValueParser::new())]
481 pub(super) no_input_handler: bool,
482}
483
484impl BenchReporterOpts {
485 pub(super) fn to_builder(
486 &self,
487 should_colorize: bool,
488 resolved_ui: &UiConfig,
489 ) -> ReporterBuilder {
490 let mut builder = ReporterBuilder::default();
491 builder.set_no_capture(true);
492 builder.set_colorize(should_colorize);
493 let ui_show_progress = self
495 .show_progress
496 .map(UiShowProgress::from)
497 .unwrap_or(resolved_ui.show_progress);
498 if ui_show_progress == UiShowProgress::Only {
499 builder.set_status_level(StatusLevel::Slow);
501 builder.set_final_status_level(FinalStatusLevel::None);
502 }
503 builder.set_show_progress(ui_show_progress.into());
504 builder
505 }
506}
507
508#[derive(Copy, Clone, Debug, ValueEnum, Default)]
509pub(crate) enum PlatformFilterOpts {
510 Target,
511 Host,
512 #[default]
513 Any,
514}
515
516impl From<PlatformFilterOpts> for Option<BuildPlatform> {
517 fn from(opt: PlatformFilterOpts) -> Self {
518 match opt {
519 PlatformFilterOpts::Target => Some(BuildPlatform::Target),
520 PlatformFilterOpts::Host => Some(BuildPlatform::Host),
521 PlatformFilterOpts::Any => None,
522 }
523 }
524}
525
526#[derive(Copy, Clone, Debug, ValueEnum, Default)]
527pub(super) enum ListType {
528 #[default]
529 Full,
530 BinariesOnly,
531}
532
533#[derive(Copy, Clone, Debug, ValueEnum, Default)]
534pub(super) enum MessageFormatOpts {
535 #[default]
538 Auto,
539 Human,
541 Oneline,
543 Json,
545 JsonPretty,
547}
548
549impl MessageFormatOpts {
550 pub(super) fn to_output_format(self, verbose: bool, is_terminal: bool) -> OutputFormat {
551 match self {
552 Self::Auto => {
553 if is_terminal {
554 OutputFormat::Human { verbose }
555 } else {
556 OutputFormat::Oneline { verbose }
557 }
558 }
559 Self::Human => OutputFormat::Human { verbose },
560 Self::Oneline => OutputFormat::Oneline { verbose },
561 Self::Json => OutputFormat::Serializable(SerializableFormat::Json),
562 Self::JsonPretty => OutputFormat::Serializable(SerializableFormat::JsonPretty),
563 }
564 }
565
566 pub(super) fn is_human_readable(self) -> bool {
570 match self {
571 Self::Auto | Self::Human | Self::Oneline => true,
572 Self::Json | Self::JsonPretty => false,
573 }
574 }
575}
576
577#[derive(Debug, Args)]
578#[command(next_help_heading = "Filter options")]
579pub(super) struct TestBuildFilter {
580 #[arg(long, value_enum, value_name = "WHICH")]
582 run_ignored: Option<RunIgnoredOpt>,
583
584 #[arg(long)]
586 partition: Option<PartitionerBuilder>,
587
588 #[arg(
592 long,
593 hide_short_help = true,
594 value_enum,
595 value_name = "PLATFORM",
596 default_value_t
597 )]
598 pub(crate) platform_filter: PlatformFilterOpts,
599
600 #[arg(
602 long,
603 alias = "filter-expr",
604 short = 'E',
605 value_name = "EXPR",
606 action(ArgAction::Append)
607 )]
608 pub(super) filterset: Vec<String>,
609
610 #[arg(long)]
617 ignore_default_filter: bool,
618
619 #[arg(help_heading = None, name = "FILTERS")]
621 pre_double_dash_filters: Vec<String>,
622
623 #[arg(help_heading = None, value_name = "FILTERS_AND_ARGS", last = true)]
632 filters: Vec<String>,
633}
634
635impl TestBuildFilter {
636 #[expect(clippy::too_many_arguments)]
637 pub(super) fn compute_test_list<'g>(
638 &self,
639 ctx: &TestExecuteContext<'_>,
640 graph: &'g PackageGraph,
641 workspace_root: Utf8PathBuf,
642 binary_list: Arc<BinaryList>,
643 test_filter_builder: TestFilterBuilder,
644 env: EnvironmentMap,
645 profile: &EvaluatableProfile<'_>,
646 reuse_build: &ReuseBuildInfo,
647 ) -> Result<TestList<'g>> {
648 let path_mapper = make_path_mapper(
649 reuse_build,
650 graph,
651 &binary_list.rust_build_meta.target_directory,
652 )?;
653
654 let rust_build_meta = binary_list.rust_build_meta.map_paths(&path_mapper);
655 let test_artifacts = RustTestArtifact::from_binary_list(
656 graph,
657 binary_list,
658 &rust_build_meta,
659 &path_mapper,
660 self.platform_filter.into(),
661 )?;
662 TestList::new(
663 ctx,
664 test_artifacts,
665 rust_build_meta,
666 &test_filter_builder,
667 workspace_root,
668 env,
669 profile,
670 if self.ignore_default_filter {
671 FilterBound::All
672 } else {
673 FilterBound::DefaultSet
674 },
675 get_num_cpus(),
677 )
678 .map_err(|err| ExpectedError::CreateTestListError { err })
679 }
680
681 pub(super) fn make_test_filter_builder(
682 &self,
683 mode: NextestRunMode,
684 filter_exprs: Vec<nextest_filtering::Filterset>,
685 ) -> Result<TestFilterBuilder> {
686 let mut run_ignored = self.run_ignored.map(Into::into);
688 let mut patterns = TestFilterPatterns::new(self.pre_double_dash_filters.clone());
689 self.merge_test_binary_args(&mut run_ignored, &mut patterns)?;
690
691 Ok(TestFilterBuilder::new(
692 mode,
693 run_ignored.unwrap_or_default(),
694 self.partition.clone(),
695 patterns,
696 filter_exprs,
697 )?)
698 }
699
700 fn merge_test_binary_args(
701 &self,
702 run_ignored: &mut Option<RunIgnored>,
703 patterns: &mut TestFilterPatterns,
704 ) -> Result<()> {
705 let mut is_exact = false;
708 for arg in &self.filters {
709 if arg == "--" {
710 break;
711 }
712 if arg == "--exact" {
713 if is_exact {
714 return Err(ExpectedError::test_binary_args_parse_error(
715 "duplicated",
716 vec![arg.clone()],
717 ));
718 }
719 is_exact = true;
720 }
721 }
722
723 let mut ignore_filters = Vec::new();
724 let mut read_trailing_filters = false;
725
726 let mut unsupported_args = Vec::new();
727
728 let mut it = self.filters.iter();
729 while let Some(arg) = it.next() {
730 if read_trailing_filters || !arg.starts_with('-') {
731 if is_exact {
732 patterns.add_exact_pattern(arg.clone());
733 } else {
734 patterns.add_substring_pattern(arg.clone());
735 }
736 } else if arg == "--include-ignored" {
737 ignore_filters.push((arg.clone(), RunIgnored::All));
738 } else if arg == "--ignored" {
739 ignore_filters.push((arg.clone(), RunIgnored::Only));
740 } else if arg == "--" {
741 read_trailing_filters = true;
742 } else if arg == "--skip" {
743 let skip_arg = it.next().ok_or_else(|| {
744 ExpectedError::test_binary_args_parse_error(
745 "missing required argument",
746 vec![arg.clone()],
747 )
748 })?;
749
750 if is_exact {
751 patterns.add_skip_exact_pattern(skip_arg.clone());
752 } else {
753 patterns.add_skip_pattern(skip_arg.clone());
754 }
755 } else if arg == "--exact" {
756 } else {
758 unsupported_args.push(arg.clone());
759 }
760 }
761
762 for (s, f) in ignore_filters {
763 if let Some(run_ignored) = run_ignored {
764 if *run_ignored != f {
765 return Err(ExpectedError::test_binary_args_parse_error(
766 "mutually exclusive",
767 vec![s],
768 ));
769 } else {
770 return Err(ExpectedError::test_binary_args_parse_error(
771 "duplicated",
772 vec![s],
773 ));
774 }
775 } else {
776 *run_ignored = Some(f);
777 }
778 }
779
780 if !unsupported_args.is_empty() {
781 return Err(ExpectedError::test_binary_args_parse_error(
782 "unsupported",
783 unsupported_args,
784 ));
785 }
786
787 Ok(())
788 }
789}
790
791#[derive(Debug, Args)]
792#[command(next_help_heading = "Filter options")]
793pub(super) struct ArchiveBuildFilter {
794 #[arg(long, short = 'E', value_name = "EXPR", action(ArgAction::Append))]
798 pub(super) filterset: Vec<String>,
799}
800
801#[derive(Copy, Clone, Debug, ValueEnum)]
802enum RunIgnoredOpt {
803 Default,
805
806 #[clap(alias = "ignored-only")]
808 Only,
809
810 All,
812}
813
814impl From<RunIgnoredOpt> for RunIgnored {
815 fn from(opt: RunIgnoredOpt) -> Self {
816 match opt {
817 RunIgnoredOpt::Default => RunIgnored::Default,
818 RunIgnoredOpt::Only => RunIgnored::Only,
819 RunIgnoredOpt::All => RunIgnored::All,
820 }
821 }
822}
823
824impl CargoOptions {
825 pub(super) fn compute_binary_list(
826 &self,
827 cargo_command: &str,
828 graph: &PackageGraph,
829 manifest_path: Option<&Utf8Path>,
830 output: OutputContext,
831 build_platforms: BuildPlatforms,
832 ) -> Result<BinaryList> {
833 let mut cargo_cli = CargoCli::new(cargo_command, manifest_path, output);
836
837 cargo_cli.add_args(["--no-run", "--message-format", "json-render-diagnostics"]);
839 cargo_cli.add_options(self);
840
841 let expression = cargo_cli.to_expression();
842 let output = expression
843 .stdout_capture()
844 .unchecked()
845 .run()
846 .map_err(|err| ExpectedError::build_exec_failed(cargo_cli.all_args(), err))?;
847 if !output.status.success() {
848 return Err(ExpectedError::build_failed(
849 cargo_cli.all_args(),
850 output.status.code(),
851 ));
852 }
853
854 let test_binaries =
855 BinaryList::from_messages(Cursor::new(output.stdout), graph, build_platforms)?;
856 Ok(test_binaries)
857 }
858}
859
860#[derive(Debug, Default, Args)]
862#[command(next_help_heading = "Runner options")]
863pub struct TestRunnerOpts {
864 #[arg(long, name = "no-run")]
866 pub(super) no_run: bool,
867
868 #[arg(
871 long,
872 short = 'j',
873 visible_alias = "jobs",
874 value_name = "N",
875 env = "NEXTEST_TEST_THREADS",
876 allow_negative_numbers = true
877 )]
878 test_threads: Option<TestThreads>,
879
880 #[arg(long, env = "NEXTEST_RETRIES", value_name = "N")]
882 retries: Option<u32>,
883
884 #[arg(
886 long,
887 visible_alias = "ff",
888 name = "fail-fast",
889 conflicts_with = "no-run"
895 )]
896 fail_fast: bool,
897
898 #[arg(
900 long,
901 visible_alias = "nff",
902 name = "no-fail-fast",
903 conflicts_with = "no-run",
904 overrides_with = "fail-fast"
905 )]
906 no_fail_fast: bool,
907
908 #[arg(
916 long,
917 name = "max-fail",
918 value_name = "N[:MODE]",
919 conflicts_with_all = &["no-run", "fail-fast", "no-fail-fast"],
920 )]
921 max_fail: Option<MaxFail>,
922
923 #[clap(flatten)]
925 pub(super) interceptor: InterceptorOpt,
926
927 #[arg(long, value_enum, value_name = "ACTION", env = "NEXTEST_NO_TESTS")]
929 pub(super) no_tests: Option<NoTestsBehavior>,
930
931 #[clap(flatten)]
933 pub(super) stress: StressOptions,
934}
935
936#[derive(Debug, Default, Args)]
937#[group(id = "interceptor", multiple = false)]
938pub(super) struct InterceptorOpt {
939 #[arg(long, value_name = "DEBUGGER", conflicts_with_all = ["stress_condition", "no-run"])]
949 pub(super) debugger: Option<DebuggerCommand>,
950
951 #[arg(long, value_name = "TRACER", conflicts_with_all = ["stress_condition", "no-run"])]
962 pub(super) tracer: Option<TracerCommand>,
963}
964
965impl InterceptorOpt {
966 pub(super) fn is_active(&self) -> bool {
968 self.debugger.is_some() || self.tracer.is_some()
969 }
970
971 pub(super) fn to_interceptor(&self) -> Interceptor {
973 match (&self.debugger, &self.tracer) {
974 (Some(debugger), None) => Interceptor::Debugger(debugger.clone()),
975 (None, Some(tracer)) => Interceptor::Tracer(tracer.clone()),
976 (None, None) => Interceptor::None,
977 (Some(_), Some(_)) => {
978 unreachable!("clap group ensures debugger and tracer are mutually exclusive")
979 }
980 }
981 }
982}
983
984#[derive(Clone, Copy, Debug, ValueEnum)]
985pub(super) enum NoTestsBehavior {
986 Pass,
988
989 Warn,
991
992 #[clap(alias = "error")]
994 Fail,
995}
996
997impl TestRunnerOpts {
998 pub(super) fn to_builder(&self, cap_strat: CaptureStrategy) -> Option<TestRunnerBuilder> {
999 if self.test_threads.is_some()
1003 && let Some(reasons) =
1004 no_run_no_capture_reasons(self.no_run, cap_strat == CaptureStrategy::None)
1005 {
1006 warn!("ignoring --test-threads because {reasons}");
1007 }
1008
1009 if self.retries.is_some() && self.no_run {
1010 warn!("ignoring --retries because --no-run is specified");
1011 }
1012 if self.no_tests.is_some() && self.no_run {
1013 warn!("ignoring --no-tests because --no-run is specified");
1014 }
1015
1016 if self.no_run {
1019 return None;
1020 }
1021
1022 let mut builder = TestRunnerBuilder::default();
1023 builder.set_capture_strategy(cap_strat);
1024 if let Some(retries) = self.retries {
1025 builder.set_retries(RetryPolicy::new_without_delay(retries));
1026 }
1027
1028 if let Some(max_fail) = self.max_fail {
1029 builder.set_max_fail(max_fail);
1030 debug!(max_fail = ?max_fail, "set max fail");
1031 } else if self.no_fail_fast {
1032 builder.set_max_fail(MaxFail::from_fail_fast(false));
1033 debug!("set max fail via from_fail_fast(false)");
1034 } else if self.fail_fast {
1035 builder.set_max_fail(MaxFail::from_fail_fast(true));
1036 debug!("set max fail via from_fail_fast(true)");
1037 }
1038
1039 if let Some(test_threads) = self.test_threads {
1040 builder.set_test_threads(test_threads);
1041 }
1042
1043 if let Some(condition) = self.stress.condition.as_ref() {
1044 builder.set_stress_condition(condition.stress_condition());
1045 }
1046
1047 builder.set_interceptor(self.interceptor.to_interceptor());
1048
1049 Some(builder)
1050 }
1051}
1052
1053fn no_run_no_capture_reasons(no_run: bool, no_capture: bool) -> Option<&'static str> {
1054 match (no_run, no_capture) {
1055 (true, true) => Some("--no-run and --no-capture are specified"),
1056 (true, false) => Some("--no-run is specified"),
1057 (false, true) => Some("--no-capture is specified"),
1058 (false, false) => None,
1059 }
1060}
1061
1062#[derive(Clone, Copy, Debug, ValueEnum)]
1063pub(super) enum IgnoreOverridesOpt {
1064 Retries,
1065 All,
1066}
1067
1068#[derive(Clone, Copy, Debug, ValueEnum, Default)]
1069pub(super) enum MessageFormat {
1070 #[default]
1072 Human,
1073 LibtestJson,
1075 LibtestJsonPlus,
1078}
1079
1080#[derive(Debug, Default, Args)]
1081#[command(next_help_heading = "Stress testing options")]
1082pub(super) struct StressOptions {
1083 #[clap(flatten)]
1085 pub(super) condition: Option<StressConditionOpt>,
1086 }
1088
1089#[derive(Clone, Debug, Default, Args)]
1090#[group(id = "stress_condition", multiple = false)]
1091pub(super) struct StressConditionOpt {
1092 #[arg(long, value_name = "COUNT")]
1094 stress_count: Option<StressCount>,
1095
1096 #[arg(long, value_name = "DURATION", value_parser = non_zero_duration)]
1098 stress_duration: Option<Duration>,
1099}
1100
1101impl StressConditionOpt {
1102 fn stress_condition(&self) -> StressCondition {
1103 if let Some(count) = self.stress_count {
1104 StressCondition::Count(count)
1105 } else if let Some(duration) = self.stress_duration {
1106 StressCondition::Duration(duration)
1107 } else {
1108 unreachable!(
1109 "if StressOptions::condition is Some, \
1110 one of these should be set"
1111 )
1112 }
1113 }
1114}
1115
1116fn non_zero_duration(input: &str) -> std::result::Result<Duration, String> {
1117 let duration = humantime::parse_duration(input).map_err(|error| error.to_string())?;
1118 if duration.is_zero() {
1119 Err("duration must be non-zero".to_string())
1120 } else {
1121 Ok(duration)
1122 }
1123}
1124
1125#[derive(Debug, Default, Args)]
1126#[command(next_help_heading = "Reporter options")]
1127pub(super) struct ReporterOpts {
1128 #[arg(long, value_enum, value_name = "WHEN", env = "NEXTEST_FAILURE_OUTPUT")]
1130 failure_output: Option<TestOutputDisplayOpt>,
1131
1132 #[arg(long, value_enum, value_name = "WHEN", env = "NEXTEST_SUCCESS_OUTPUT")]
1134 success_output: Option<TestOutputDisplayOpt>,
1135
1136 #[arg(long, value_enum, value_name = "LEVEL", env = "NEXTEST_STATUS_LEVEL")]
1139 status_level: Option<StatusLevelOpt>,
1140
1141 #[arg(
1143 long,
1144 value_enum,
1145 value_name = "LEVEL",
1146 env = "NEXTEST_FINAL_STATUS_LEVEL"
1147 )]
1148 final_status_level: Option<FinalStatusLevelOpt>,
1149
1150 #[arg(long, env = "NEXTEST_SHOW_PROGRESS")]
1155 show_progress: Option<ShowProgressOpt>,
1156
1157 #[arg(long, env = "NEXTEST_HIDE_PROGRESS_BAR", value_parser = BoolishValueParser::new())]
1159 hide_progress_bar: bool,
1160
1161 #[arg(long, env = "NEXTEST_NO_OUTPUT_INDENT", value_parser = BoolishValueParser::new())]
1170 no_output_indent: bool,
1171
1172 #[arg(long, env = "NEXTEST_NO_INPUT_HANDLER", value_parser = BoolishValueParser::new())]
1177 pub(super) no_input_handler: bool,
1178
1179 #[arg(
1190 long = "max-progress-running",
1191 value_name = "N",
1192 env = "NEXTEST_MAX_PROGRESS_RUNNING"
1193 )]
1194 max_progress_running: Option<MaxProgressRunning>,
1195
1196 #[arg(
1198 long,
1199 name = "message-format",
1200 value_enum,
1201 value_name = "FORMAT",
1202 env = "NEXTEST_MESSAGE_FORMAT"
1203 )]
1204 pub(super) message_format: Option<MessageFormat>,
1205
1206 #[arg(
1211 long,
1212 requires = "message-format",
1213 value_name = "VERSION",
1214 env = "NEXTEST_MESSAGE_FORMAT_VERSION"
1215 )]
1216 pub(super) message_format_version: Option<String>,
1217}
1218
1219impl ReporterOpts {
1220 pub(super) fn to_builder(
1221 &self,
1222 no_run: bool,
1223 no_capture: bool,
1224 should_colorize: bool,
1225 resolved_ui: &UiConfig,
1226 ) -> ReporterBuilder {
1227 if no_run && no_capture {
1231 warn!("ignoring --no-capture because --no-run is specified");
1232 }
1233
1234 let reasons = no_run_no_capture_reasons(no_run, no_capture);
1235
1236 if self.failure_output.is_some()
1237 && let Some(reasons) = reasons
1238 {
1239 warn!("ignoring --failure-output because {}", reasons);
1240 }
1241 if self.success_output.is_some()
1242 && let Some(reasons) = reasons
1243 {
1244 warn!("ignoring --success-output because {}", reasons);
1245 }
1246 if self.status_level.is_some() && no_run {
1247 warn!("ignoring --status-level because --no-run is specified");
1248 }
1249 if self.final_status_level.is_some() && no_run {
1250 warn!("ignoring --final-status-level because --no-run is specified");
1251 }
1252 if self.message_format.is_some() && no_run {
1253 warn!("ignoring --message-format because --no-run is specified");
1254 }
1255 if self.message_format_version.is_some() && no_run {
1256 warn!("ignoring --message-format-version because --no-run is specified");
1257 }
1258
1259 let ui_show_progress = match (self.show_progress, self.hide_progress_bar) {
1262 (Some(show_progress), true) => {
1263 warn!("ignoring --hide-progress-bar because --show-progress is specified");
1264 show_progress.into()
1265 }
1266 (Some(show_progress), false) => show_progress.into(),
1267 (None, true) => UiShowProgress::None,
1268 (None, false) => resolved_ui.show_progress,
1269 };
1270
1271 let max_progress_running = self
1273 .max_progress_running
1274 .unwrap_or(resolved_ui.max_progress_running);
1275
1276 let no_output_indent = self.no_output_indent || !resolved_ui.output_indent;
1279
1280 debug!(
1281 ?ui_show_progress,
1282 ?max_progress_running,
1283 ?no_output_indent,
1284 "resolved reporter UI settings"
1285 );
1286
1287 let mut builder = ReporterBuilder::default();
1290 builder.set_no_capture(no_capture);
1291 builder.set_colorize(should_colorize);
1292
1293 if ui_show_progress == UiShowProgress::Only {
1294 builder.set_status_level(StatusLevel::Slow);
1297 builder.set_final_status_level(FinalStatusLevel::None);
1298 }
1299 if let Some(failure_output) = self.failure_output {
1300 builder.set_failure_output(failure_output.into());
1301 }
1302 if let Some(success_output) = self.success_output {
1303 builder.set_success_output(success_output.into());
1304 }
1305 if let Some(status_level) = self.status_level {
1306 builder.set_status_level(status_level.into());
1307 }
1308 if let Some(final_status_level) = self.final_status_level {
1309 builder.set_final_status_level(final_status_level.into());
1310 }
1311 builder.set_show_progress(ui_show_progress.into());
1312 builder.set_no_output_indent(no_output_indent);
1313 builder.set_max_progress_running(max_progress_running);
1314 builder
1315 }
1316}
1317
1318#[derive(Clone, Copy, Debug, ValueEnum)]
1319enum TestOutputDisplayOpt {
1320 Immediate,
1321 ImmediateFinal,
1322 Final,
1323 Never,
1324}
1325
1326impl From<TestOutputDisplayOpt> for TestOutputDisplay {
1327 fn from(opt: TestOutputDisplayOpt) -> Self {
1328 match opt {
1329 TestOutputDisplayOpt::Immediate => TestOutputDisplay::Immediate,
1330 TestOutputDisplayOpt::ImmediateFinal => TestOutputDisplay::ImmediateFinal,
1331 TestOutputDisplayOpt::Final => TestOutputDisplay::Final,
1332 TestOutputDisplayOpt::Never => TestOutputDisplay::Never,
1333 }
1334 }
1335}
1336
1337#[derive(Clone, Copy, Debug, ValueEnum)]
1338enum StatusLevelOpt {
1339 None,
1340 Fail,
1341 Retry,
1342 Slow,
1343 Leak,
1344 Pass,
1345 Skip,
1346 All,
1347}
1348
1349impl From<StatusLevelOpt> for StatusLevel {
1350 fn from(opt: StatusLevelOpt) -> Self {
1351 match opt {
1352 StatusLevelOpt::None => StatusLevel::None,
1353 StatusLevelOpt::Fail => StatusLevel::Fail,
1354 StatusLevelOpt::Retry => StatusLevel::Retry,
1355 StatusLevelOpt::Slow => StatusLevel::Slow,
1356 StatusLevelOpt::Leak => StatusLevel::Leak,
1357 StatusLevelOpt::Pass => StatusLevel::Pass,
1358 StatusLevelOpt::Skip => StatusLevel::Skip,
1359 StatusLevelOpt::All => StatusLevel::All,
1360 }
1361 }
1362}
1363
1364#[derive(Clone, Copy, Debug, ValueEnum)]
1365enum FinalStatusLevelOpt {
1366 None,
1367 Fail,
1368 #[clap(alias = "retry")]
1369 Flaky,
1370 Slow,
1371 Skip,
1372 Pass,
1373 All,
1374}
1375
1376impl From<FinalStatusLevelOpt> for FinalStatusLevel {
1377 fn from(opt: FinalStatusLevelOpt) -> Self {
1378 match opt {
1379 FinalStatusLevelOpt::None => FinalStatusLevel::None,
1380 FinalStatusLevelOpt::Fail => FinalStatusLevel::Fail,
1381 FinalStatusLevelOpt::Flaky => FinalStatusLevel::Flaky,
1382 FinalStatusLevelOpt::Slow => FinalStatusLevel::Slow,
1383 FinalStatusLevelOpt::Skip => FinalStatusLevel::Skip,
1384 FinalStatusLevelOpt::Pass => FinalStatusLevel::Pass,
1385 FinalStatusLevelOpt::All => FinalStatusLevel::All,
1386 }
1387 }
1388}
1389
1390#[derive(Default, Clone, Copy, Debug, ValueEnum)]
1391enum ShowProgressOpt {
1392 #[default]
1395 Auto,
1396
1397 None,
1399
1400 #[clap(alias = "running")]
1403 Bar,
1404
1405 Counter,
1407
1408 Only,
1412}
1413
1414impl From<ShowProgressOpt> for UiShowProgress {
1415 fn from(opt: ShowProgressOpt) -> Self {
1416 match opt {
1417 ShowProgressOpt::Auto => UiShowProgress::Auto,
1418 ShowProgressOpt::None => UiShowProgress::None,
1419 ShowProgressOpt::Bar => UiShowProgress::Bar,
1420 ShowProgressOpt::Counter => UiShowProgress::Counter,
1421 ShowProgressOpt::Only => UiShowProgress::Only,
1422 }
1423 }
1424}
1425
1426#[derive(Debug, clap::Parser)]
1431#[command(
1432 version = crate::version::short(),
1433 long_version = crate::version::long(),
1434 bin_name = "cargo",
1435 styles = crate::output::clap_styles::style(),
1436 max_term_width = 100,
1437)]
1438pub struct CargoNextestApp {
1439 #[clap(flatten)]
1441 early_args: EarlyArgs,
1442
1443 #[clap(subcommand)]
1444 subcommand: NextestSubcommand,
1445}
1446
1447impl CargoNextestApp {
1448 pub fn init_output(&self) -> OutputContext {
1450 match &self.subcommand {
1451 NextestSubcommand::Nextest(args) => args.common.output.init(self.early_args),
1452 NextestSubcommand::Ntr(args) => args.common.output.init(self.early_args),
1453 #[cfg(unix)]
1454 NextestSubcommand::DoubleSpawn(_) => OutputContext::color_never_init(),
1456 }
1457 }
1458
1459 pub fn exec(
1461 self,
1462 cli_args: Vec<String>,
1463 output: OutputContext,
1464 output_writer: &mut crate::output::OutputWriter,
1465 ) -> Result<i32> {
1466 if let Err(err) = nextest_runner::usdt::register_probes() {
1467 tracing::warn!("failed to register USDT probes: {}", err);
1468 }
1469
1470 match self.subcommand {
1471 NextestSubcommand::Nextest(app) => app.exec(cli_args, output, output_writer),
1472 NextestSubcommand::Ntr(opts) => opts.exec(cli_args, output, output_writer),
1473 #[cfg(unix)]
1474 NextestSubcommand::DoubleSpawn(opts) => opts.exec(output),
1475 }
1476 }
1477}
1478
1479#[derive(Debug, Subcommand)]
1480enum NextestSubcommand {
1481 Nextest(Box<AppOpts>),
1483 Ntr(Box<NtrOpts>),
1485 #[cfg(unix)]
1487 #[command(name = nextest_runner::double_spawn::DoubleSpawnInfo::SUBCOMMAND_NAME, hide = true)]
1488 DoubleSpawn(crate::double_spawn::DoubleSpawnOpts),
1489}
1490
1491#[derive(Debug, Args)]
1492#[clap(
1493 version = crate::version::short(),
1494 long_version = crate::version::long(),
1495 display_name = "cargo-nextest",
1496)]
1497pub(super) struct AppOpts {
1498 #[clap(flatten)]
1499 common: CommonOpts,
1500
1501 #[clap(subcommand)]
1502 command: Command,
1503}
1504
1505impl AppOpts {
1506 fn exec(
1510 self,
1511 cli_args: Vec<String>,
1512 output: OutputContext,
1513 output_writer: &mut crate::output::OutputWriter,
1514 ) -> Result<i32> {
1515 match self.command {
1516 Command::List(list_opts) => {
1517 let base = super::execution::BaseApp::new(
1518 output,
1519 list_opts.reuse_build,
1520 list_opts.cargo_options,
1521 self.common.config_opts,
1522 self.common.manifest_path,
1523 output_writer,
1524 )?;
1525 let app = super::execution::App::new(base, list_opts.build_filter)?;
1526 app.exec_list(
1527 list_opts.message_format,
1528 list_opts.list_type,
1529 &list_opts.pager_opts,
1530 )?;
1531 Ok(0)
1532 }
1533 Command::Run(run_opts) => {
1534 let base = super::execution::BaseApp::new(
1535 output,
1536 run_opts.reuse_build,
1537 run_opts.cargo_options,
1538 self.common.config_opts,
1539 self.common.manifest_path,
1540 output_writer,
1541 )?;
1542 let app = super::execution::App::new(base, run_opts.build_filter)?;
1543 app.exec_run(
1544 run_opts.no_capture,
1545 &run_opts.runner_opts,
1546 &run_opts.reporter_opts,
1547 cli_args,
1548 output_writer,
1549 )?;
1550 Ok(0)
1551 }
1552 Command::Bench(bench_opts) => {
1553 let base = super::execution::BaseApp::new(
1554 output,
1555 ReuseBuildOpts::default(),
1556 bench_opts.cargo_options,
1557 self.common.config_opts,
1558 self.common.manifest_path,
1559 output_writer,
1560 )?;
1561 let app = super::execution::App::new(base, bench_opts.build_filter)?;
1562 app.exec_bench(
1563 &bench_opts.runner_opts,
1564 &bench_opts.reporter_opts,
1565 cli_args,
1566 output_writer,
1567 )?;
1568 Ok(0)
1569 }
1570 Command::Archive(archive_opts) => {
1571 let app = super::execution::BaseApp::new(
1572 output,
1573 ReuseBuildOpts::default(),
1574 archive_opts.cargo_options,
1575 self.common.config_opts,
1576 self.common.manifest_path,
1577 output_writer,
1578 )?;
1579
1580 let app =
1581 super::execution::ArchiveApp::new(app, archive_opts.archive_build_filter)?;
1582 app.exec_archive(
1583 &archive_opts.archive_file,
1584 archive_opts.archive_format,
1585 archive_opts.zstd_level,
1586 output_writer,
1587 )?;
1588 Ok(0)
1589 }
1590 Command::ShowConfig { command } => command.exec(
1591 self.common.manifest_path,
1592 self.common.config_opts,
1593 output,
1594 output_writer,
1595 ),
1596 Command::Self_ { command } => command.exec(output),
1597 Command::Debug { command } => command.exec(output),
1598 }
1599 }
1600}
1601
1602#[derive(Debug, Args)]
1603struct NtrOpts {
1604 #[clap(flatten)]
1605 common: CommonOpts,
1606
1607 #[clap(flatten)]
1608 run_opts: RunOpts,
1609}
1610
1611impl NtrOpts {
1612 fn exec(
1613 self,
1614 cli_args: Vec<String>,
1615 output: OutputContext,
1616 output_writer: &mut crate::output::OutputWriter,
1617 ) -> Result<i32> {
1618 let base = super::execution::BaseApp::new(
1619 output,
1620 self.run_opts.reuse_build,
1621 self.run_opts.cargo_options,
1622 self.common.config_opts,
1623 self.common.manifest_path,
1624 output_writer,
1625 )?;
1626 let app = super::execution::App::new(base, self.run_opts.build_filter)?;
1627 app.exec_run(
1628 self.run_opts.no_capture,
1629 &self.run_opts.runner_opts,
1630 &self.run_opts.reporter_opts,
1631 cli_args,
1632 output_writer,
1633 )?;
1634 Ok(0)
1635 }
1636}
1637
1638#[cfg(test)]
1639mod tests {
1640 use super::*;
1641 use clap::Parser;
1642 use nextest_runner::run_mode::NextestRunMode;
1643
1644 #[test]
1645 fn test_argument_parsing() {
1646 use clap::error::ErrorKind::{self, *};
1647
1648 let valid: &[&'static str] = &[
1649 "cargo nextest list",
1653 "cargo nextest run",
1654 "cargo nextest list --list-type binaries-only",
1658 "cargo nextest list --list-type full",
1659 "cargo nextest list --message-format json-pretty",
1660 "cargo nextest list --message-format oneline",
1661 "cargo nextest list --message-format auto",
1662 "cargo nextest list -T oneline",
1663 "cargo nextest list -T auto",
1664 "cargo nextest run --failure-output never",
1665 "cargo nextest run --success-output=immediate",
1666 "cargo nextest run --status-level=all",
1667 "cargo nextest run --no-capture",
1668 "cargo nextest run --nocapture",
1669 "cargo nextest run --no-run",
1670 "cargo nextest run --final-status-level flaky",
1671 "cargo nextest run --max-fail 3",
1672 "cargo nextest run --max-fail=all",
1673 "cargo nextest run --final-status-level retry",
1675 "NEXTEST_HIDE_PROGRESS_BAR=1 cargo nextest run",
1676 "NEXTEST_HIDE_PROGRESS_BAR=true cargo nextest run",
1677 "cargo nextest run --no-run -j8",
1681 "cargo nextest run --no-run --retries 3",
1682 "NEXTEST_TEST_THREADS=8 cargo nextest run --no-run",
1683 "cargo nextest run --no-run --success-output never",
1684 "NEXTEST_SUCCESS_OUTPUT=never cargo nextest run --no-run",
1685 "cargo nextest run --no-run --failure-output immediate",
1686 "NEXTEST_FAILURE_OUTPUT=immediate cargo nextest run --no-run",
1687 "cargo nextest run --no-run --status-level pass",
1688 "NEXTEST_STATUS_LEVEL=pass cargo nextest run --no-run",
1689 "cargo nextest run --no-run --final-status-level skip",
1690 "NEXTEST_FINAL_STATUS_LEVEL=skip cargo nextest run --no-run",
1691 "cargo nextest run --no-capture --test-threads=24",
1695 "NEXTEST_NO_CAPTURE=1 cargo nextest run --test-threads=24",
1696 "cargo nextest run --no-capture --failure-output=never",
1697 "NEXTEST_NO_CAPTURE=1 cargo nextest run --failure-output=never",
1698 "cargo nextest run --no-capture --success-output=final",
1699 "NEXTEST_SUCCESS_OUTPUT=final cargo nextest run --no-capture",
1700 "cargo nextest list --lib --bins",
1704 "cargo nextest run --ignore-rust-version --unit-graph",
1705 "cargo nextest list --no-pager",
1709 "cargo nextest show-config test-groups --no-pager",
1710 "cargo nextest list --binaries-metadata=foo",
1714 "cargo nextest run --binaries-metadata=foo --target-dir-remap=bar",
1715 "cargo nextest list --cargo-metadata path",
1716 "cargo nextest run --cargo-metadata=path --workspace-remap remapped-path",
1717 "cargo nextest archive --archive-file my-archive.tar.zst --zstd-level -1",
1718 "cargo nextest archive --archive-file my-archive.foo --archive-format tar-zst",
1719 "cargo nextest archive --archive-file my-archive.foo --archive-format tar-zstd",
1720 "cargo nextest list --archive-file my-archive.tar.zst",
1721 "cargo nextest list --archive-file my-archive.tar.zst --archive-format tar-zst",
1722 "cargo nextest list --archive-file my-archive.tar.zst --extract-to my-path",
1723 "cargo nextest list --archive-file my-archive.tar.zst --extract-to my-path --extract-overwrite",
1724 "cargo nextest list --archive-file my-archive.tar.zst --persist-extract-tempdir",
1725 "cargo nextest list --archive-file my-archive.tar.zst --workspace-remap foo",
1726 "cargo nextest list --archive-file my-archive.tar.zst --config target.'cfg(all())'.runner=\"my-runner\"",
1727 "cargo nextest list -E deps(foo)",
1731 "cargo nextest run --filterset 'test(bar)' --package=my-package test-filter",
1732 "cargo nextest run --filter-expr 'test(bar)' --package=my-package test-filter",
1733 "cargo nextest list -E 'deps(foo)' --ignore-default-filter",
1734 "cargo nextest run --stress-count 4",
1738 "cargo nextest run --stress-count infinite",
1739 "cargo nextest run --stress-duration 60m",
1740 "cargo nextest run --stress-duration 24h",
1741 "cargo nextest run -- --a an arbitrary arg",
1745 "cargo nextest run --jobs -3",
1747 "cargo nextest run --jobs 3",
1748 "cargo nextest run --build-jobs -1",
1750 "cargo nextest run --build-jobs 1",
1751 "cargo nextest self update",
1755 "cargo nextest self update --beta",
1756 "cargo nextest self update --rc",
1757 "cargo nextest self update --version 0.9.100",
1758 "cargo nextest self update --version latest",
1759 "cargo nextest self update --check",
1760 "cargo nextest self update --beta --check",
1761 "cargo nextest self update --rc --force",
1762 "cargo nextest bench",
1766 "cargo nextest bench --no-run",
1767 "cargo nextest bench --fail-fast",
1768 "cargo nextest bench --no-fail-fast",
1769 "cargo nextest bench --max-fail 3",
1770 "cargo nextest bench --max-fail=all",
1771 "cargo nextest bench --stress-count 4",
1772 "cargo nextest bench --stress-count infinite",
1773 "cargo nextest bench --stress-duration 60m",
1774 "cargo nextest bench --debugger gdb",
1775 "cargo nextest bench --tracer strace",
1776 ];
1777
1778 let invalid: &[(&'static str, ErrorKind)] = &[
1779 ("cargo nextest run --no-run --fail-fast", ArgumentConflict),
1783 (
1784 "cargo nextest run --no-run --no-fail-fast",
1785 ArgumentConflict,
1786 ),
1787 ("cargo nextest run --no-run --max-fail=3", ArgumentConflict),
1788 (
1792 "cargo nextest run --max-fail=3 --no-fail-fast",
1793 ArgumentConflict,
1794 ),
1795 (
1799 "cargo nextest run --manifest-path foo --cargo-metadata bar",
1802 ArgumentConflict,
1803 ),
1804 (
1805 "cargo nextest run --binaries-metadata=foo --lib",
1806 ArgumentConflict,
1807 ),
1808 (
1812 "cargo nextest run --workspace-remap foo",
1813 MissingRequiredArgument,
1814 ),
1815 (
1819 "cargo nextest run --target-dir-remap bar",
1820 MissingRequiredArgument,
1821 ),
1822 (
1826 "cargo nextest run --archive-format tar-zst",
1827 MissingRequiredArgument,
1828 ),
1829 (
1830 "cargo nextest run --archive-file foo --archive-format no",
1831 InvalidValue,
1832 ),
1833 (
1834 "cargo nextest run --extract-to foo",
1835 MissingRequiredArgument,
1836 ),
1837 (
1838 "cargo nextest run --archive-file foo --extract-overwrite",
1839 MissingRequiredArgument,
1840 ),
1841 (
1842 "cargo nextest run --extract-to foo --extract-overwrite",
1843 MissingRequiredArgument,
1844 ),
1845 (
1846 "cargo nextest run --persist-extract-tempdir",
1847 MissingRequiredArgument,
1848 ),
1849 (
1850 "cargo nextest run --archive-file foo --extract-to bar --persist-extract-tempdir",
1851 ArgumentConflict,
1852 ),
1853 (
1854 "cargo nextest run --archive-file foo --cargo-metadata bar",
1855 ArgumentConflict,
1856 ),
1857 (
1858 "cargo nextest run --archive-file foo --binaries-metadata bar",
1859 ArgumentConflict,
1860 ),
1861 (
1862 "cargo nextest run --archive-file foo --target-dir-remap bar",
1863 ArgumentConflict,
1864 ),
1865 ("cargo nextest run --jobs 0", ValueValidation),
1867 ("cargo nextest run --jobs -twenty", UnknownArgument),
1869 ("cargo nextest run --build-jobs -inf1", UnknownArgument),
1870 ("cargo nextest run --stress-count 0", ValueValidation),
1872 ("cargo nextest run --stress-duration 0m", ValueValidation),
1874 (
1878 "cargo nextest run --debugger gdb --stress-count 4",
1879 ArgumentConflict,
1880 ),
1881 (
1882 "cargo nextest run --debugger gdb --stress-duration 1h",
1883 ArgumentConflict,
1884 ),
1885 (
1886 "cargo nextest run --debugger gdb --no-run",
1887 ArgumentConflict,
1888 ),
1889 ("cargo nextest bench --no-run --fail-fast", ArgumentConflict),
1893 (
1894 "cargo nextest bench --no-run --no-fail-fast",
1895 ArgumentConflict,
1896 ),
1897 (
1898 "cargo nextest bench --no-run --max-fail=3",
1899 ArgumentConflict,
1900 ),
1901 (
1902 "cargo nextest bench --max-fail=3 --no-fail-fast",
1903 ArgumentConflict,
1904 ),
1905 (
1906 "cargo nextest bench --debugger gdb --stress-count 4",
1907 ArgumentConflict,
1908 ),
1909 (
1910 "cargo nextest bench --debugger gdb --stress-duration 1h",
1911 ArgumentConflict,
1912 ),
1913 (
1914 "cargo nextest bench --debugger gdb --no-run",
1915 ArgumentConflict,
1916 ),
1917 (
1918 "cargo nextest bench --tracer strace --stress-count 4",
1919 ArgumentConflict,
1920 ),
1921 ("cargo nextest bench --stress-count 0", ValueValidation),
1923 ("cargo nextest bench --stress-duration 0m", ValueValidation),
1925 ("cargo nextest self update --beta --rc", ArgumentConflict),
1929 (
1930 "cargo nextest self update --beta --version 0.9.100",
1931 ArgumentConflict,
1932 ),
1933 (
1934 "cargo nextest self update --rc --version 0.9.100",
1935 ArgumentConflict,
1936 ),
1937 ];
1938
1939 for (k, _) in std::env::vars() {
1941 if k.starts_with("NEXTEST_") {
1942 unsafe { std::env::remove_var(k) };
1945 }
1946 }
1947
1948 for valid_args in valid {
1949 let cmd = shell_words::split(valid_args).expect("valid command line");
1950 let env_vars: Vec<_> = cmd
1952 .iter()
1953 .take_while(|arg| arg.contains('='))
1954 .cloned()
1955 .collect();
1956
1957 let mut env_keys = Vec::with_capacity(env_vars.len());
1958 for k_v in &env_vars {
1959 let (k, v) = k_v.split_once('=').expect("valid env var");
1960 unsafe { std::env::set_var(k, v) };
1963 env_keys.push(k);
1964 }
1965
1966 let cmd = cmd.iter().skip(env_vars.len());
1967
1968 if let Err(error) = CargoNextestApp::try_parse_from(cmd) {
1969 panic!("{valid_args} should have successfully parsed, but didn't: {error}");
1970 }
1971
1972 for &k in &env_keys {
1975 unsafe { std::env::remove_var(k) };
1978 }
1979 }
1980
1981 for &(invalid_args, kind) in invalid {
1982 match CargoNextestApp::try_parse_from(
1983 shell_words::split(invalid_args).expect("valid command"),
1984 ) {
1985 Ok(_) => {
1986 panic!("{invalid_args} should have errored out but successfully parsed");
1987 }
1988 Err(error) => {
1989 let actual_kind = error.kind();
1990 if kind != actual_kind {
1991 panic!(
1992 "{invalid_args} should error with kind {kind:?}, but actual kind was {actual_kind:?}",
1993 );
1994 }
1995 }
1996 }
1997 }
1998 }
1999
2000 #[derive(Debug, clap::Parser)]
2001 struct TestCli {
2002 #[structopt(flatten)]
2003 build_filter: TestBuildFilter,
2004 }
2005
2006 #[test]
2007 fn test_test_binary_argument_parsing() {
2008 fn get_test_filter_builder(cmd: &str) -> Result<TestFilterBuilder> {
2009 let app = TestCli::try_parse_from(shell_words::split(cmd).expect("valid command line"))
2010 .unwrap_or_else(|_| panic!("{cmd} should have successfully parsed"));
2011 app.build_filter
2012 .make_test_filter_builder(NextestRunMode::Test, vec![])
2013 }
2014
2015 let valid = &[
2016 ("foo -- str1", "foo str1"),
2020 ("foo -- str2 str3", "foo str2 str3"),
2021 ("foo -- --ignored", "foo --run-ignored only"),
2025 ("foo -- --ignored", "foo --run-ignored ignored-only"),
2026 ("foo -- --include-ignored", "foo --run-ignored all"),
2027 (
2031 "foo -- --ignored -- str --- --ignored",
2032 "foo --run-ignored ignored-only str -- -- --- --ignored",
2033 ),
2034 ("foo -- -- str1 str2 --", "foo str1 str2 -- -- --"),
2035 ];
2036 let skip_exact = &[
2037 ("foo -- --skip my-pattern --skip your-pattern", {
2041 let mut patterns = TestFilterPatterns::default();
2042 patterns.add_skip_pattern("my-pattern".to_owned());
2043 patterns.add_skip_pattern("your-pattern".to_owned());
2044 patterns
2045 }),
2046 ("foo -- pattern1 --skip my-pattern --skip your-pattern", {
2047 let mut patterns = TestFilterPatterns::default();
2048 patterns.add_substring_pattern("pattern1".to_owned());
2049 patterns.add_skip_pattern("my-pattern".to_owned());
2050 patterns.add_skip_pattern("your-pattern".to_owned());
2051 patterns
2052 }),
2053 (
2057 "foo -- --skip my-pattern --skip your-pattern exact1 --exact pattern2",
2058 {
2059 let mut patterns = TestFilterPatterns::default();
2060 patterns.add_skip_exact_pattern("my-pattern".to_owned());
2061 patterns.add_skip_exact_pattern("your-pattern".to_owned());
2062 patterns.add_exact_pattern("exact1".to_owned());
2063 patterns.add_exact_pattern("pattern2".to_owned());
2064 patterns
2065 },
2066 ),
2067 ];
2068 let invalid = &[
2069 ("foo -- --include-ignored --include-ignored", "duplicated"),
2073 ("foo -- --ignored --ignored", "duplicated"),
2074 ("foo -- --exact --exact", "duplicated"),
2075 ("foo -- --ignored --include-ignored", "mutually exclusive"),
2079 ("foo --run-ignored all -- --ignored", "mutually exclusive"),
2080 ("foo -- --skip", "missing required argument"),
2084 ("foo -- --bar", "unsupported"),
2088 ];
2089
2090 for (a, b) in valid {
2091 let a_str = format!(
2092 "{:?}",
2093 get_test_filter_builder(a).unwrap_or_else(|_| panic!("failed to parse {a}"))
2094 );
2095 let b_str = format!(
2096 "{:?}",
2097 get_test_filter_builder(b).unwrap_or_else(|_| panic!("failed to parse {b}"))
2098 );
2099 assert_eq!(a_str, b_str);
2100 }
2101
2102 for (args, patterns) in skip_exact {
2103 let builder =
2104 get_test_filter_builder(args).unwrap_or_else(|_| panic!("failed to parse {args}"));
2105
2106 let builder2 = TestFilterBuilder::new(
2107 NextestRunMode::Test,
2108 RunIgnored::Default,
2109 None,
2110 patterns.clone(),
2111 Vec::new(),
2112 )
2113 .unwrap_or_else(|_| panic!("failed to build TestFilterBuilder"));
2114
2115 assert_eq!(builder, builder2, "{args} matches expected");
2116 }
2117
2118 for (s, r) in invalid {
2119 let res = get_test_filter_builder(s);
2120 if let Err(ExpectedError::TestBinaryArgsParseError { reason, .. }) = &res {
2121 assert_eq!(reason, r);
2122 } else {
2123 panic!(
2124 "{s} should have errored out with TestBinaryArgsParseError, actual: {res:?}",
2125 );
2126 }
2127 }
2128 }
2129}