1use super::{
7 cli::{ConfigOpts, TestBuildFilter},
8 execution::{App, BaseApp},
9 helpers::{detect_build_platforms, display_output_slice, extract_slice_from_output},
10};
11use crate::{
12 ExpectedError, Result,
13 cargo_cli::{CargoCli, CargoOptions},
14 output::{OutputContext, OutputOpts, OutputWriter},
15 reuse_build::ReuseBuildOpts,
16};
17use camino::{Utf8Path, Utf8PathBuf};
18use clap::{Subcommand, ValueEnum};
19use nextest_runner::{
20 cargo_config::CargoConfigs, config::core::NextestVersionEval, errors::WriteTestListError,
21};
22use std::fmt;
23use tracing::Level;
24
25#[derive(Debug, Subcommand)]
26pub(super) enum ShowConfigCommand {
27 Version {},
29 TestGroups {
31 #[arg(long)]
33 show_default: bool,
34
35 #[arg(long)]
37 groups: Vec<nextest_runner::config::elements::TestGroup>,
38
39 #[clap(flatten)]
40 cargo_options: Box<CargoOptions>,
41
42 #[clap(flatten)]
43 build_filter: TestBuildFilter,
44
45 #[clap(flatten)]
46 reuse_build: Box<ReuseBuildOpts>,
47 },
48}
49
50impl ShowConfigCommand {
51 pub(super) fn exec(
52 self,
53 manifest_path: Option<Utf8PathBuf>,
54 config_opts: ConfigOpts,
55 output: OutputContext,
56 output_writer: &mut OutputWriter,
57 ) -> Result<i32> {
58 match self {
59 Self::Version {} => {
60 let mut cargo_cli =
61 CargoCli::new("locate-project", manifest_path.as_deref(), output);
62 cargo_cli.add_args(["--workspace", "--message-format=plain"]);
63 let locate_project_output = cargo_cli
64 .to_expression()
65 .stdout_capture()
66 .unchecked()
67 .run()
68 .map_err(|error| {
69 ExpectedError::cargo_locate_project_exec_failed(cargo_cli.all_args(), error)
70 })?;
71 if !locate_project_output.status.success() {
72 return Err(ExpectedError::cargo_locate_project_failed(
73 cargo_cli.all_args(),
74 locate_project_output.status,
75 ));
76 }
77 let workspace_root = String::from_utf8(locate_project_output.stdout)
78 .map_err(|err| ExpectedError::WorkspaceRootInvalidUtf8 { err })?;
79 let workspace_root = Utf8Path::new(workspace_root.trim_end());
81 let workspace_root =
83 workspace_root
84 .parent()
85 .ok_or_else(|| ExpectedError::WorkspaceRootInvalid {
86 workspace_root: workspace_root.to_owned(),
87 })?;
88
89 let config = config_opts.make_version_only_config(workspace_root)?;
90 let current_version = super::execution::current_version();
91
92 let show = nextest_runner::show_config::ShowNextestVersion::new(
93 config.nextest_version(),
94 ¤t_version,
95 config_opts.override_version_check,
96 );
97 show.write_human(
98 &mut output_writer.stdout_writer(),
99 output.color.should_colorize(supports_color::Stream::Stdout),
100 )
101 .map_err(WriteTestListError::Io)?;
102
103 match config
104 .nextest_version()
105 .eval(¤t_version, config_opts.override_version_check)
106 {
107 NextestVersionEval::Satisfied => Ok(0),
108 NextestVersionEval::Error { .. } => {
109 crate::helpers::log_needs_update(
110 Level::ERROR,
111 crate::helpers::BYPASS_VERSION_TEXT,
112 &output.stderr_styles(),
113 );
114 Ok(nextest_metadata::NextestExitCode::REQUIRED_VERSION_NOT_MET)
115 }
116 NextestVersionEval::Warn { .. } => {
117 crate::helpers::log_needs_update(
118 Level::WARN,
119 crate::helpers::BYPASS_VERSION_TEXT,
120 &output.stderr_styles(),
121 );
122 Ok(nextest_metadata::NextestExitCode::RECOMMENDED_VERSION_NOT_MET)
123 }
124 NextestVersionEval::ErrorOverride { .. }
125 | NextestVersionEval::WarnOverride { .. } => Ok(0),
126 }
127 }
128 Self::TestGroups {
129 show_default,
130 groups,
131 cargo_options,
132 build_filter,
133 reuse_build,
134 } => {
135 let base = BaseApp::new(
136 output,
137 *reuse_build,
138 *cargo_options,
139 config_opts,
140 manifest_path,
141 output_writer,
142 )?;
143 let app = App::new(base, build_filter)?;
144
145 app.exec_show_test_groups(show_default, groups, output_writer)?;
146
147 Ok(0)
148 }
149 }
150 }
151}
152
153#[derive(Debug, Subcommand)]
154pub(super) enum SelfCommand {
155 #[clap(hide = true)]
156 Setup {
158 #[arg(long, value_enum, default_value_t = SetupSource::User)]
160 source: SetupSource,
161 },
162 #[cfg_attr(
163 not(feature = "self-update"),
164 doc = "This version of nextest does not have self-update enabled\n\
165 \n\
166 Always exits with code 93 (SELF_UPDATE_UNAVAILABLE).
167 "
168 )]
169 #[cfg_attr(
170 feature = "self-update",
171 doc = "Download and install updates to nextest\n\
172 \n\
173 This command checks the internet for updates to nextest, then downloads and
174 installs them if an update is available."
175 )]
176 Update {
177 #[arg(long, default_value = "latest")]
179 version: String,
180
181 #[arg(short = 'n', long)]
186 check: bool,
187
188 #[arg(short = 'y', long, conflicts_with = "check")]
190 yes: bool,
191
192 #[arg(short, long)]
194 force: bool,
195
196 #[arg(long)]
198 releases_url: Option<String>,
199 },
200}
201
202#[derive(Clone, Copy, Debug, ValueEnum)]
203pub(super) enum SetupSource {
204 User,
205 SelfUpdate,
206 PackageManager,
207}
208
209impl SelfCommand {
210 #[cfg_attr(not(feature = "self-update"), expect(unused_variables))]
211 pub(super) fn exec(self, output: OutputOpts) -> Result<i32> {
212 let output = output.init();
213
214 match self {
215 Self::Setup { source: _source } => {
216 Ok(0)
218 }
219 Self::Update {
220 version,
221 check,
222 yes,
223 force,
224 releases_url,
225 } => {
226 cfg_if::cfg_if! {
227 if #[cfg(feature = "self-update")] {
228 crate::update::perform_update(
229 &version,
230 check,
231 yes,
232 force,
233 releases_url,
234 output,
235 )
236 } else {
237 tracing::info!(
238 "this version of cargo-nextest cannot perform self-updates\n\
239 (hint: this usually means nextest was installed by a package manager)"
240 );
241 Ok(nextest_metadata::NextestExitCode::SELF_UPDATE_UNAVAILABLE)
242 }
243 }
244 }
245 }
246 }
247}
248
249#[derive(Debug, Subcommand)]
250pub(super) enum DebugCommand {
251 Extract {
256 #[arg(long, required_unless_present_any = ["stderr", "combined"])]
258 stdout: Option<Utf8PathBuf>,
259
260 #[arg(long, required_unless_present_any = ["stdout", "combined"])]
262 stderr: Option<Utf8PathBuf>,
263
264 #[arg(long, conflicts_with_all = ["stdout", "stderr"])]
266 combined: Option<Utf8PathBuf>,
267
268 #[arg(value_enum)]
270 output_format: ExtractOutputFormat,
271 },
272
273 CurrentExe,
275
276 BuildPlatforms {
278 #[arg(long)]
280 target: Option<String>,
281
282 #[arg(long, value_name = "KEY=VALUE")]
284 config: Vec<String>,
285
286 #[arg(long, value_enum, default_value_t)]
288 output_format: BuildPlatformsOutputFormat,
289 },
290}
291
292impl DebugCommand {
293 pub(super) fn exec(self, output: OutputOpts) -> Result<i32> {
294 let _ = output.init();
295
296 match self {
297 DebugCommand::Extract {
298 stdout,
299 stderr,
300 combined,
301 output_format,
302 } => {
303 if let Some(combined) = combined {
305 let combined = std::fs::read(&combined).map_err(|err| {
306 ExpectedError::DebugExtractReadError {
307 kind: "combined",
308 path: combined,
309 err,
310 }
311 })?;
312
313 let description_kind = extract_slice_from_output(&combined, &combined);
314 display_output_slice(description_kind, output_format)?;
315 } else {
316 let stdout = stdout
317 .map(|path| {
318 std::fs::read(&path).map_err(|err| {
319 ExpectedError::DebugExtractReadError {
320 kind: "stdout",
321 path,
322 err,
323 }
324 })
325 })
326 .transpose()?
327 .unwrap_or_default();
328 let stderr = stderr
329 .map(|path| {
330 std::fs::read(&path).map_err(|err| {
331 ExpectedError::DebugExtractReadError {
332 kind: "stderr",
333 path,
334 err,
335 }
336 })
337 })
338 .transpose()?
339 .unwrap_or_default();
340
341 let output_slice = extract_slice_from_output(&stdout, &stderr);
342 display_output_slice(output_slice, output_format)?;
343 }
344 }
345 DebugCommand::CurrentExe => {
346 let exe = std::env::current_exe()
347 .map_err(|err| ExpectedError::GetCurrentExeFailed { err })?;
348 println!("{}", exe.display());
349 }
350 DebugCommand::BuildPlatforms {
351 target,
352 config,
353 output_format,
354 } => {
355 let cargo_configs = CargoConfigs::new(&config).map_err(Box::new)?;
356 let build_platforms = detect_build_platforms(&cargo_configs, target.as_deref())?;
357 match output_format {
358 BuildPlatformsOutputFormat::Debug => {
359 println!("{build_platforms:#?}");
360 }
361 BuildPlatformsOutputFormat::Triple => {
362 println!(
363 "host triple: {}",
364 build_platforms.host.platform.triple().as_str()
365 );
366 if let Some(target) = &build_platforms.target {
367 println!(
368 "target triple: {}",
369 target.triple.platform.triple().as_str()
370 );
371 } else {
372 println!("target triple: (none)");
373 }
374 }
375 }
376 }
377 }
378
379 Ok(0)
380 }
381}
382
383#[derive(Clone, Copy, Debug, ValueEnum)]
385pub enum ExtractOutputFormat {
386 Raw,
388
389 JunitDescription,
394
395 Highlight,
397}
398
399impl fmt::Display for ExtractOutputFormat {
400 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
401 match self {
402 Self::Raw => write!(f, "raw"),
403 Self::JunitDescription => write!(f, "junit-description"),
404 Self::Highlight => write!(f, "highlight"),
405 }
406 }
407}
408
409#[derive(Clone, Copy, Debug, Default, ValueEnum)]
411pub enum BuildPlatformsOutputFormat {
412 #[default]
414 Debug,
415
416 Triple,
418}