Skip to content

Commit b49e2e0

Browse files
committed
[cargo-nextest] add a new --no-tests option
Default to `warn` currently, but can also be `fail` or `pass`. The plan is to default to `fail` in the future (see #1646).
1 parent 4f5c89a commit b49e2e0

File tree

6 files changed

+178
-31
lines changed

6 files changed

+178
-31
lines changed

cargo-nextest/src/dispatch.rs

Lines changed: 52 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ use camino::{Utf8Path, Utf8PathBuf};
1111
use clap::{builder::BoolishValueParser, ArgAction, Args, Parser, Subcommand, ValueEnum};
1212
use guppy::graph::PackageGraph;
1313
use itertools::Itertools;
14+
use log::warn;
1415
use nextest_filtering::FilteringExpr;
1516
use nextest_metadata::BuildPlatform;
1617
use nextest_runner::{
@@ -406,8 +407,7 @@ impl NtrOpts {
406407
&self.run_opts.reporter_opts,
407408
cli_args,
408409
output_writer,
409-
)?;
410-
Ok(0)
410+
)
411411
}
412412
}
413413

@@ -754,6 +754,33 @@ pub struct TestRunnerOpts {
754754
/// Run all tests regardless of failure
755755
#[arg(long, conflicts_with = "no-run", overrides_with = "fail-fast")]
756756
no_fail_fast: bool,
757+
758+
/// Behavior if there are no tests to run.
759+
///
760+
/// The default is currently `warn`, but it will change to `fail` in the future.
761+
#[arg(
762+
long,
763+
value_enum,
764+
conflicts_with = "no-run",
765+
value_name = "ACTION",
766+
require_equals = true,
767+
env = "NEXTEST_NO_TESTS"
768+
)]
769+
no_tests: Option<NoTestsBehavior>,
770+
}
771+
772+
#[derive(Clone, Copy, Debug, Default, ValueEnum)]
773+
enum NoTestsBehavior {
774+
/// Silently exit with code 0.
775+
Pass,
776+
777+
/// Produce a warning and exit with code 0.
778+
#[default]
779+
Warn,
780+
781+
/// Produce an error message and exit with code 4.
782+
#[clap(alias = "error")]
783+
Fail,
757784
}
758785

759786
impl TestRunnerOpts {
@@ -1563,7 +1590,7 @@ impl App {
15631590
reporter_opts: &TestReporterOpts,
15641591
cli_args: Vec<String>,
15651592
output_writer: &mut OutputWriter,
1566-
) -> Result<()> {
1593+
) -> Result<i32> {
15671594
let (version_only_config, config) = self.base.load_config()?;
15681595
let profile = self.base.load_profile(profile_name, &config)?;
15691596

@@ -1638,7 +1665,7 @@ impl App {
16381665
Some(runner_builder) => runner_builder,
16391666
None => {
16401667
// This means --no-run was passed in. Exit.
1641-
return Ok(());
1668+
return Ok(0);
16421669
}
16431670
};
16441671

@@ -1656,15 +1683,32 @@ impl App {
16561683
// Write and flush the event.
16571684
reporter.report_event(event)
16581685
})?;
1686+
reporter.finish();
16591687
self.base
16601688
.check_version_config_final(version_only_config.nextest_version())?;
16611689

16621690
match run_stats.summarize_final() {
1663-
FinalRunStats::Success => Ok(()),
1691+
FinalRunStats::Success => Ok(0),
16641692
FinalRunStats::NoTestsRun => {
1665-
// This currently does not exit with a non-zero code, but will in the future:
1666-
// https://github.com/nextest-rs/nextest/issues/1639
1667-
Ok(())
1693+
match runner_opts.no_tests {
1694+
Some(NoTestsBehavior::Pass) => Ok(0),
1695+
Some(NoTestsBehavior::Warn) => {
1696+
warn!("no tests to run");
1697+
Ok(0)
1698+
}
1699+
Some(NoTestsBehavior::Fail) => {
1700+
Err(ExpectedError::NoTestsRun { is_default: false })
1701+
}
1702+
None => {
1703+
// This currently does not exit with a non-zero code, but will in the
1704+
// future: https://github.com/nextest-rs/nextest/issues/1639
1705+
warn!(
1706+
"no tests to run -- this will become an error in the future\n\
1707+
(hint: use `--no-tests` to customize)"
1708+
);
1709+
Ok(0)
1710+
}
1711+
}
16681712
}
16691713
FinalRunStats::Canceled(RunStatsFailureKind::SetupScript)
16701714
| FinalRunStats::Failed(RunStatsFailureKind::SetupScript) => {

cargo-nextest/src/errors.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,12 @@ pub enum ExpectedError {
184184
SetupScriptFailed,
185185
#[error("test run failed")]
186186
TestRunFailed,
187+
#[error("no tests to run")]
188+
NoTestsRun {
189+
/// The no-tests-run error was chosen because it was the default (we show a hint in this
190+
/// case)
191+
is_default: bool,
192+
},
187193
#[cfg(feature = "self-update")]
188194
#[error("failed to parse --version")]
189195
UpdateVersionParseError {
@@ -406,6 +412,7 @@ impl ExpectedError {
406412
}
407413
Self::SetupScriptFailed => NextestExitCode::SETUP_SCRIPT_FAILED,
408414
Self::TestRunFailed => NextestExitCode::TEST_RUN_FAILED,
415+
Self::NoTestsRun { .. } => NextestExitCode::NO_TESTS_RUN,
409416
Self::ArchiveCreateError { .. } => NextestExitCode::ARCHIVE_CREATION_FAILED,
410417
Self::WriteTestListError { .. } | Self::WriteEventError { .. } => {
411418
NextestExitCode::WRITE_OUTPUT_ERROR
@@ -731,6 +738,15 @@ impl ExpectedError {
731738
log::error!("test run failed");
732739
None
733740
}
741+
Self::NoTestsRun { is_default } => {
742+
let hint_str = if *is_default {
743+
"\n(hint: use `--no-tests` to customize)"
744+
} else {
745+
""
746+
};
747+
log::error!("no tests to run{hint_str}");
748+
None
749+
}
734750
Self::ShowTestGroupsError { err } => {
735751
log::error!("{err}");
736752
err.source()

integration-tests/tests/integration/main.rs

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,87 @@ fn test_list_target_after_build() {
228228
check_list_full_output(&output.stdout, Some(BuildPlatform::Target));
229229
}
230230

231+
#[test]
232+
fn test_run_no_tests() {
233+
set_env_vars();
234+
235+
let p = TempProject::new().unwrap();
236+
build_tests(&p);
237+
238+
let output = CargoNextestCli::new()
239+
.args([
240+
"--manifest-path",
241+
p.manifest_path().as_str(),
242+
"run",
243+
"-E",
244+
"none()",
245+
])
246+
.output();
247+
248+
let stderr = output.stderr_as_str();
249+
assert!(
250+
stderr.contains("warning: no tests to run -- this will become an error in the future"),
251+
"stderr contains no tests message"
252+
);
253+
254+
let output = CargoNextestCli::new()
255+
.args([
256+
"--manifest-path",
257+
p.manifest_path().as_str(),
258+
"run",
259+
"-E",
260+
"none()",
261+
"--no-tests=warn",
262+
])
263+
.output();
264+
265+
let stderr = output.stderr_as_str();
266+
assert!(
267+
stderr.contains("warning: no tests to run"),
268+
"stderr contains no tests message"
269+
);
270+
271+
let output = CargoNextestCli::new()
272+
.args([
273+
"--manifest-path",
274+
p.manifest_path().as_str(),
275+
"run",
276+
"-E",
277+
"none()",
278+
"--no-tests=fail",
279+
])
280+
.unchecked(true)
281+
.output();
282+
assert_eq!(
283+
output.exit_status.code(),
284+
Some(NextestExitCode::NO_TESTS_RUN),
285+
"correct exit code for command\n{output}"
286+
);
287+
288+
let stderr = output.stderr_as_str();
289+
assert!(
290+
stderr.contains("error: no tests to run"),
291+
"stderr contains no tests message: {output}"
292+
);
293+
294+
let output = CargoNextestCli::new()
295+
.args([
296+
"--manifest-path",
297+
p.manifest_path().as_str(),
298+
"run",
299+
"-E",
300+
"none()",
301+
"--no-tests=pass",
302+
])
303+
.output();
304+
305+
let stderr = output.stderr_as_str();
306+
assert!(
307+
!stderr.contains("no tests to run"),
308+
"no tests message does not error out"
309+
);
310+
}
311+
231312
#[test]
232313
fn test_run() {
233314
set_env_vars();

nextest-metadata/src/exit_codes.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,14 @@ impl NextestExitCode {
1919
/// An error was encountered while attempting to double-spawn a nextest process.
2020
pub const DOUBLE_SPAWN_ERROR: i32 = 70;
2121

22+
/// No tests were selected to run, but no other errors occurred.
23+
///
24+
/// This is an advisory exit code generated if nextest is run with `--no-tests=fail` (soon to
25+
/// become the default). See [discussion #1646] for more.
26+
///
27+
/// [discussion #1646]: https://github.com/nextest-rs/nextest/discussions/1646
28+
pub const NO_TESTS_RUN: i32 = 4;
29+
2230
/// One or more tests failed.
2331
pub const TEST_RUN_FAILED: i32 = 100;
2432

nextest-runner/src/reporter.rs

Lines changed: 20 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -342,6 +342,17 @@ enum ReporterStderrImpl<'a> {
342342
Buffer(&'a mut Vec<u8>),
343343
}
344344

345+
impl ReporterStderrImpl<'_> {
346+
fn finish_and_clear_bar(&self) {
347+
match self {
348+
ReporterStderrImpl::TerminalWithBar(bar) => {
349+
bar.finish_and_clear();
350+
}
351+
ReporterStderrImpl::TerminalWithoutBar | ReporterStderrImpl::Buffer(_) => {}
352+
}
353+
}
354+
}
355+
345356
/// Functionality to report test results to stderr, JUnit, and/or structured,
346357
/// machine-readable results to stdout
347358
pub struct TestReporter<'a> {
@@ -364,6 +375,11 @@ impl<'a> TestReporter<'a> {
364375
self.write_event(event)
365376
}
366377

378+
/// Mark the reporter done.
379+
pub fn finish(&mut self) {
380+
self.stderr.finish_and_clear_bar();
381+
}
382+
367383
// ---
368384
// Helper methods
369385
// ---
@@ -1021,8 +1037,7 @@ impl<'a> TestReporterImpl<'a> {
10211037
}
10221038
}
10231039

1024-
// Print out warnings at the end, if any. We currently print out warnings in two
1025-
// cases:
1040+
// Print out warnings at the end, if any.
10261041
write_final_warnings(stats_summary, self.cancel_status, &self.styles, writer)?;
10271042
}
10281043
}
@@ -1634,7 +1649,6 @@ fn write_final_warnings(
16341649
writer: &mut dyn Write,
16351650
) -> io::Result<()> {
16361651
match final_stats {
1637-
// 1. When some tests are not run due to a test failure.
16381652
FinalRunStats::Failed(RunStatsFailureKind::Test {
16391653
initial_run_count,
16401654
not_run,
@@ -1674,16 +1688,6 @@ fn write_final_warnings(
16741688
)?;
16751689
}
16761690
}
1677-
1678-
// 2. When no tests are run at all.
1679-
FinalRunStats::NoTestsRun => {
1680-
writeln!(
1681-
writer,
1682-
"{}: no tests were run (this will exit with a \
1683-
non-zero code in the future)",
1684-
"warning".style(styles.skip)
1685-
)?;
1686-
}
16871691
_ => {}
16881692
}
16891693

@@ -2406,17 +2410,11 @@ mod tests {
24062410
);
24072411
assert_eq!(warnings, "warning: 1/1 test was not run due to interrupt\n");
24082412

2413+
// These warnings are taken care of by cargo-nextest.
24092414
let warnings = final_warnings_for(FinalRunStats::NoTestsRun, None);
2410-
assert_eq!(
2411-
warnings,
2412-
"warning: no tests were run (this will exit with a non-zero code in the future)\n"
2413-
);
2414-
2415+
assert_eq!(warnings, "");
24152416
let warnings = final_warnings_for(FinalRunStats::NoTestsRun, Some(CancelReason::Signal));
2416-
assert_eq!(
2417-
warnings,
2418-
"warning: no tests were run (this will exit with a non-zero code in the future)\n"
2419-
);
2417+
assert_eq!(warnings, "");
24202418

24212419
// No warnings for success.
24222420
let warnings = final_warnings_for(FinalRunStats::Success, None);

nextest-runner/src/runner.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1649,7 +1649,7 @@ pub enum FinalRunStats {
16491649
/// The test run was successful, or is successful so far.
16501650
Success,
16511651

1652-
/// The test run was successful, or is successful so far, but no tests were run.
1652+
/// The test run was successful, or is successful so far, but no tests were selected to run.
16531653
NoTestsRun,
16541654

16551655
/// The test run was canceled.

0 commit comments

Comments
 (0)