Skip to content

Commit 87ab264

Browse files
committed
[cargo-nextest] add a "bench" command
See #2125 -- it can be helpful to run benchmarks with setup scripts and such configured.
1 parent 6ca837c commit 87ab264

File tree

18 files changed

+776
-172
lines changed

18 files changed

+776
-172
lines changed

cargo-nextest/src/dispatch.rs

Lines changed: 294 additions & 33 deletions
Large diffs are not rendered by default.

cargo-nextest/src/errors.rs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use indent_write::indentable::Indented;
77
use itertools::Itertools;
88
use nextest_filtering::errors::FiltersetParseErrors;
99
use nextest_metadata::NextestExitCode;
10-
use nextest_runner::{errors::*, helpers::plural, redact::Redactor};
10+
use nextest_runner::{errors::*, helpers::plural, redact::Redactor, run_mode::NextestRunMode};
1111
use owo_colors::OwoColorize;
1212
use semver::Version;
1313
use std::{error::Error, io, path::PathBuf, process::ExitStatus, string::FromUtf8Error};
@@ -212,6 +212,7 @@ pub enum ExpectedError {
212212
TestRunFailed,
213213
#[error("no tests to run")]
214214
NoTestsRun {
215+
mode: NextestRunMode,
215216
/// The no-tests-run error was chosen because it was the default (we show a hint in this
216217
/// case)
217218
is_default: bool,
@@ -870,13 +871,16 @@ impl ExpectedError {
870871
error!("test run failed");
871872
None
872873
}
873-
Self::NoTestsRun { is_default } => {
874+
Self::NoTestsRun { mode, is_default } => {
874875
let hint_str = if *is_default {
875876
"\n(hint: use `--no-tests` to customize)"
876877
} else {
877878
""
878879
};
879-
error!("no tests to run{hint_str}");
880+
error!(
881+
"no {} to run{hint_str}",
882+
plural::tests_plural_if(*mode, true),
883+
);
880884
None
881885
}
882886
Self::ShowTestGroupsError { err } => {

nextest-metadata/src/test_list.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -812,6 +812,9 @@ impl FilterMatch {
812812
#[serde(rename_all = "kebab-case")]
813813
#[non_exhaustive]
814814
pub enum MismatchReason {
815+
/// Nextest is running in benchmark mode and this test is not a benchmark.
816+
NotBenchmark,
817+
815818
/// This test does not match the run-ignored option in the filter.
816819
Ignored,
817820

@@ -833,6 +836,7 @@ pub enum MismatchReason {
833836
impl fmt::Display for MismatchReason {
834837
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
835838
match self {
839+
MismatchReason::NotBenchmark => write!(f, "is not a benchmark"),
836840
MismatchReason::Ignored => write!(f, "does not match the run-ignored option"),
837841
MismatchReason::String => write!(f, "does not match the provided string filters"),
838842
MismatchReason::Expression => {

nextest-runner/src/helpers.rs

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ use std::{fmt, io, path::PathBuf, process::ExitStatus, time::Duration};
1515

1616
/// Utilities for pluralizing various words based on count or plurality.
1717
pub mod plural {
18+
use crate::run_mode::NextestRunMode;
19+
1820
/// Returns "were" if `plural` is true, otherwise "was".
1921
pub fn were_plural_if(plural: bool) -> &'static str {
2022
if plural { "were" } else { "was" }
@@ -29,14 +31,33 @@ pub mod plural {
2931
}
3032
}
3133

32-
/// Returns "test" if `count` is 1, otherwise "tests".
33-
pub fn tests_str(count: usize) -> &'static str {
34-
tests_plural_if(count != 1)
34+
/// Returns:
35+
///
36+
/// * If `mode` is `Test`: "test" if `count` is 1, otherwise "tests".
37+
/// * If `mode` is `Benchmark`: "benchmark" if `count` is 1, otherwise "benchmarks".
38+
pub fn tests_str(mode: NextestRunMode, count: usize) -> &'static str {
39+
tests_plural_if(mode, count != 1)
3540
}
3641

37-
/// Returns "tests" if `plural` is true, otherwise "test".
38-
pub fn tests_plural_if(plural: bool) -> &'static str {
39-
if plural { "tests" } else { "test" }
42+
/// Returns:
43+
///
44+
/// * If `mode` is `Test`: "tests" if `plural` is true, otherwise "test".
45+
/// * If `mode` is `Benchmark`: "benchmarks" if `plural` is true, otherwise "benchmark".
46+
pub fn tests_plural_if(mode: NextestRunMode, plural: bool) -> &'static str {
47+
match (mode, plural) {
48+
(NextestRunMode::Test, true) => "tests",
49+
(NextestRunMode::Test, false) => "test",
50+
(NextestRunMode::Benchmark, true) => "benchmarks",
51+
(NextestRunMode::Benchmark, false) => "benchmark",
52+
}
53+
}
54+
55+
/// Returns "tests" or "benchmarks" based on the run mode.
56+
pub fn tests_plural(mode: NextestRunMode) -> &'static str {
57+
match mode {
58+
NextestRunMode::Test => "tests",
59+
NextestRunMode::Benchmark => "benchmarks",
60+
}
4061
}
4162

4263
/// Returns "binary" if `count` is 1, otherwise "binaries".

nextest-runner/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ pub mod runner;
2828
// TODO: move this module to the cargo-nextest crate and make it a private module once we get rid of
2929
// the tests in nextest-runner/tests/integration which depend on this to provide correct host and
3030
// target libdir.
31+
pub mod run_mode;
3132
mod rustc_cli;
3233
pub mod show_config;
3334
pub mod signal;

nextest-runner/src/list/test_list.rs

Lines changed: 54 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ use crate::{
1414
indenter::indented,
1515
list::{BinaryList, OutputFormat, RustBuildMeta, Styles, TestListState},
1616
reuse_build::PathMapper,
17+
run_mode::NextestRunMode,
1718
target_runner::{PlatformRunner, TargetRunner},
1819
test_command::{LocalExecuteContext, TestCommand, TestCommandPhase},
1920
test_filter::{BinaryMismatchReason, FilterBinaryMatch, FilterBound, TestFilterBuilder},
@@ -42,7 +43,7 @@ use std::{
4243
sync::{Arc, OnceLock},
4344
};
4445
use tokio::runtime::Runtime;
45-
use tracing::debug;
46+
use tracing::{debug, trace};
4647

4748
/// A Rust test binary built by Cargo. This artifact hasn't been run yet so there's no information
4849
/// about the tests within it.
@@ -201,6 +202,13 @@ pub struct SkipCounts {
201202
/// The number of skipped tests.
202203
pub skipped_tests: usize,
203204

205+
/// The number of tests skipped due to not being benchmarks.
206+
///
207+
/// This is the highest-priority reason for skipping tests.
208+
///
209+
/// This is non-zero only when running in benchmark mode.
210+
pub skipped_tests_non_benchmark: usize,
211+
204212
/// The number of tests skipped due to not being in the default set.
205213
pub skipped_tests_default_filter: usize,
206214

@@ -215,6 +223,7 @@ pub struct SkipCounts {
215223
#[derive(Clone, Debug)]
216224
pub struct TestList<'g> {
217225
test_count: usize,
226+
mode: NextestRunMode,
218227
rust_build_meta: RustBuildMeta<TestListState>,
219228
rust_suites: BTreeMap<RustBinaryId, RustTestSuite<'g>>,
220229
workspace_root: Utf8PathBuf,
@@ -312,6 +321,7 @@ impl<'g> TestList<'g> {
312321

313322
Ok(Self {
314323
rust_suites,
324+
mode: filter.mode(),
315325
workspace_root,
316326
env,
317327
rust_build_meta,
@@ -370,6 +380,7 @@ impl<'g> TestList<'g> {
370380

371381
Ok(Self {
372382
rust_suites,
383+
mode: filter.mode(),
373384
workspace_root,
374385
env,
375386
rust_build_meta,
@@ -384,6 +395,11 @@ impl<'g> TestList<'g> {
384395
self.test_count
385396
}
386397

398+
/// Returns the mode nextest is running it.
399+
pub fn mode(&self) -> NextestRunMode {
400+
self.mode
401+
}
402+
387403
/// Returns the Rust build-related metadata for this test list.
388404
pub fn rust_build_meta(&self) -> &RustBuildMeta<TestListState> {
389405
&self.rust_build_meta
@@ -392,10 +408,19 @@ impl<'g> TestList<'g> {
392408
/// Returns the total number of skipped tests.
393409
pub fn skip_counts(&self) -> &SkipCounts {
394410
self.skip_counts.get_or_init(|| {
411+
let mut skipped_tests_non_benchmark = 0;
395412
let mut skipped_tests_default_filter = 0;
396413
let skipped_tests = self
397414
.iter_tests()
398415
.filter(|instance| match instance.test_info.filter_match {
416+
FilterMatch::Mismatch {
417+
reason: MismatchReason::NotBenchmark,
418+
} => {
419+
// If we're running in benchmark mode, we track but
420+
// don't show skip counts for non-benchmark tests.
421+
skipped_tests_non_benchmark += 1;
422+
true
423+
}
399424
FilterMatch::Mismatch {
400425
reason: MismatchReason::DefaultFilter,
401426
} => {
@@ -425,6 +450,7 @@ impl<'g> TestList<'g> {
425450

426451
SkipCounts {
427452
skipped_tests,
453+
skipped_tests_non_benchmark,
428454
skipped_tests_default_filter,
429455
skipped_binaries,
430456
skipped_binaries_default_filter,
@@ -548,6 +574,7 @@ impl<'g> TestList<'g> {
548574
pub(crate) fn empty() -> Self {
549575
Self {
550576
test_count: 0,
577+
mode: NextestRunMode::Test,
551578
workspace_root: Utf8PathBuf::new(),
552579
rust_build_meta: RustBuildMeta::empty(),
553580
env: EnvironmentMap::empty(),
@@ -605,18 +632,18 @@ impl<'g> TestList<'g> {
605632
// based on one doesn't affect the other.
606633
let mut non_ignored_filter = filter.build();
607634
for (test_name, kind) in Self::parse(&test_binary.binary_id, non_ignored.as_ref())? {
635+
let filter_match =
636+
non_ignored_filter.filter_match(&test_binary, test_name, &kind, ecx, bound, false);
637+
trace!(
638+
"test binary {}: test {} ({kind:?}) matches non-ignored filter: {filter_match:?}",
639+
test_binary.binary_id, test_name,
640+
);
608641
test_cases.insert(
609642
test_name.into(),
610643
RustTestCaseSummary {
611644
kind: Some(kind),
612645
ignored: false,
613-
filter_match: non_ignored_filter.filter_match(
614-
&test_binary,
615-
test_name,
616-
ecx,
617-
bound,
618-
false,
619-
),
646+
filter_match,
620647
},
621648
);
622649
}
@@ -627,18 +654,18 @@ impl<'g> TestList<'g> {
627654
// * just ignored tests if --ignored is passed in
628655
// * all tests, both ignored and non-ignored, if --ignored is not passed in
629656
// Adding ignored tests after non-ignored ones makes everything resolve correctly.
657+
let filter_match =
658+
ignored_filter.filter_match(&test_binary, test_name, &kind, ecx, bound, true);
659+
trace!(
660+
"test binary {}: test {} ({kind:?}) matches ignored filter: {filter_match:?}",
661+
test_binary.binary_id, test_name,
662+
);
630663
test_cases.insert(
631664
test_name.into(),
632665
RustTestCaseSummary {
633666
kind: Some(kind),
634667
ignored: true,
635-
filter_match: ignored_filter.filter_match(
636-
&test_binary,
637-
test_name,
638-
ecx,
639-
bound,
640-
true,
641-
),
668+
filter_match,
642669
},
643670
);
644671
}
@@ -1127,6 +1154,15 @@ impl<'a> TestInstance<'a> {
11271154
if self.test_info.ignored {
11281155
cli.push("--ignored");
11291156
}
1157+
match test_list.mode() {
1158+
NextestRunMode::Benchmark => cli.push("--bench"),
1159+
NextestRunMode::Test => {
1160+
// We don't pass in additional arguments like "--test" here
1161+
// because our ad-hoc custom harness protocol doesn't document
1162+
// that as a requirement. This doesn't seem to be an issue in
1163+
// practice, though.
1164+
}
1165+
}
11301166
cli.extend(extra_args.iter().map(String::as_str));
11311167

11321168
let lctx = LocalExecuteContext {
@@ -1301,6 +1337,7 @@ mod tests {
13011337
let cx = ParseContext::new(&PACKAGE_GRAPH_FIXTURE);
13021338

13031339
let test_filter = TestFilterBuilder::new(
1340+
NextestRunMode::Test,
13041341
RunIgnored::Default,
13051342
None,
13061343
TestFilterPatterns::default(),
@@ -1410,7 +1447,7 @@ mod tests {
14101447
filter_match: FilterMatch::Mismatch { reason: MismatchReason::Ignored },
14111448
},
14121449
"tests::baz::test_ignored".to_owned() => RustTestCaseSummary {
1413-
kind: None,
1450+
kind: Some(RustTestKind::TEST),
14141451
ignored: true,
14151452
filter_match: FilterMatch::Mismatch { reason: MismatchReason::Ignored },
14161453
},
@@ -1539,6 +1576,7 @@ mod tests {
15391576
}
15401577
},
15411578
"tests::baz::test_ignored": {
1579+
"kind": "test",
15421580
"ignored": true,
15431581
"filter-match": {
15441582
"status": "mismatch",

nextest-runner/src/reporter/aggregator/imp.rs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@
22
// SPDX-License-Identifier: MIT OR Apache-2.0
33

44
use super::junit::MetadataJunit;
5-
use crate::{config::EvaluatableProfile, errors::WriteEventError, reporter::events::TestEvent};
5+
use crate::{
6+
config::EvaluatableProfile, errors::WriteEventError, reporter::events::TestEvent,
7+
run_mode::NextestRunMode,
8+
};
69
use camino::Utf8PathBuf;
710

811
#[derive(Clone, Debug)]
@@ -15,10 +18,10 @@ pub(crate) struct EventAggregator<'cfg> {
1518
}
1619

1720
impl<'cfg> EventAggregator<'cfg> {
18-
pub(crate) fn new(profile: &EvaluatableProfile<'cfg>) -> Self {
21+
pub(crate) fn new(mode: NextestRunMode, profile: &EvaluatableProfile<'cfg>) -> Self {
1922
Self {
2023
store_dir: profile.store_dir().to_owned(),
21-
junit: profile.junit().map(MetadataJunit::new),
24+
junit: profile.junit().map(|cfg| MetadataJunit::new(mode, cfg)),
2225
}
2326
}
2427

0 commit comments

Comments
 (0)