Skip to content

Commit ccffb23

Browse files
committed
[nextest-filtering] reject unknown binary IDs and binary names
We would previously accept binary IDs and names that were not present in the graph. That is definitely not right -- we should reject any that weren't known. This may cause some minor breakage, but hopefully that will alert folks about mistakes rather than be disruptive.
1 parent c32589e commit ccffb23

File tree

15 files changed

+244
-38
lines changed

15 files changed

+244
-38
lines changed

Cargo.lock

Lines changed: 5 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ fs-err = "3.1.0"
6161
future-queue = "0.4.0"
6262
futures = "0.3.31"
6363
globset = "0.4.15"
64-
guppy = "0.17.14"
64+
guppy = "0.17.16"
6565
hex = "0.4.3"
6666
home = "0.5.11"
6767
http = "1.2.0"
@@ -157,6 +157,7 @@ nextest-workspace-hack = { path = "workspace-hack" }
157157

158158
# Uncomment for testing.
159159
# cargo_metadata = { path = "../cargo_metadata" }
160+
# guppy = { path = "../../guppy/guppy" }
160161
# future-queue = { path = "../future-queue" }
161162
# target-spec = { path = "../guppy/target-spec" }
162163
# target-spec-miette = { path = "../guppy/target-spec-miette" }

fixtures/nextest-tests/Cargo.toml

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,10 @@ test = true
2424

2525
[[example]]
2626
name = "other"
27-
test = true
27+
# Note that setting test = false does not mean that tests are disabled. It just
28+
# means that `cargo nextest run` won't run tests by default. It will run tests
29+
# when `cargo nextest run --all-targets` is used.
30+
test = false
2831

2932
# Make this crate its own workspace.
3033
[workspace]
@@ -34,7 +37,7 @@ members = [
3437
"derive",
3538
"dylib-test",
3639
"with-build-script",
37-
"proc-macro-test"
40+
"proc-macro-test",
3841
]
3942

4043
[dependencies]

integration-tests/tests/integration/main.rs

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,38 @@ fn test_list_binaries_only() {
164164
.output();
165165

166166
check_list_binaries_output(&output.stdout);
167+
168+
// Check error messages for unknown binary IDs.
169+
let output = CargoNextestCli::for_test()
170+
.args([
171+
"--manifest-path",
172+
p.manifest_path().as_str(),
173+
"list",
174+
"--cargo-quiet",
175+
"--workspace",
176+
"--all-targets",
177+
"--message-format",
178+
"json",
179+
"--list-type",
180+
"binaries-only",
181+
// Doesn't exist.
182+
"-E",
183+
"binary(unknown) & binary_id(unknown) & binary(=unknown) | binary_id(=unknown)",
184+
// Does exist.
185+
"-E",
186+
"binary_id(with-build-script) | binary_id(=with-build-script) | \
187+
binary(nextest-tests) | binary(=nextest-tests)",
188+
// First one doesn't exist, second one does.
189+
"-E",
190+
"binary_id(nextest-tests::does_not_exist) | binary_id(=nextest-tests::basic)",
191+
// First one exists, second one doesn't.
192+
"-E",
193+
"binary_id(nextest-tests::example/*) | binary_id(dylib-test::example/*)",
194+
])
195+
.unchecked(true)
196+
.output();
197+
198+
insta::assert_snapshot!(output.stderr_as_str());
167199
}
168200

169201
#[test]
@@ -321,7 +353,7 @@ fn test_run_no_tests() {
321353

322354
let stderr = output.stderr_as_str();
323355
assert!(
324-
stderr.contains("Starting 0 tests across 0 binaries (8 binaries skipped)"),
356+
stderr.contains("Starting 0 tests across 0 binaries (7 binaries skipped)"),
325357
"stderr contains 'Starting' message: {output}"
326358
);
327359
assert!(

integration-tests/tests/integration/snapshots/integration__archive_includes.snap

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
---
22
source: integration-tests/tests/integration/main.rs
33
expression: output.stderr_as_str()
4+
snapshot_kind: text
45
---
56
Archiving 17 binaries (including 2 non-test binaries), 2 build script output directories, 2 linked paths, 7 extra paths, and 1 standard library to <archive-file>
67
Warning ignoring extra path `<target-dir>/excluded-dir` because it does not exist

integration-tests/tests/integration/snapshots/integration__archive_missing_includes.snap

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
---
22
source: integration-tests/tests/integration/main.rs
33
expression: output.stderr_as_str()
4+
snapshot_kind: text
45
---
56
Archiving 17 binaries (including 2 non-test binaries), 2 build script output directories, 2 linked paths, 1 extra path, and 1 standard library to <archive-file>
67
error: error creating archive `<archive-file>`

integration-tests/tests/integration/snapshots/integration__archive_no_includes.snap

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
---
22
source: integration-tests/tests/integration/main.rs
33
expression: output.stderr_as_str()
4+
snapshot_kind: text
45
---
56
Archiving 17 binaries (including 2 non-test binaries), 2 build script output directories, 2 linked paths, and 1 standard library to <archive-file>
67
Warning linked path `<target-dir>/debug/build/<cdylib-link-hash>/does-not-exist` not found, requested by: cdylib-link v0.1.0
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
---
2+
source: integration-tests/tests/integration/main.rs
3+
expression: output.stderr_as_str()
4+
snapshot_kind: text
5+
---
6+
info: experimental features enabled: setup-scripts
7+
error: operator didn't match any binary names
8+
╭────
9+
1binary(unknown) & binary_id(unknown) & binary(=unknown) | binary_id(=unknown)
10+
· ───┬───
11+
· ╰── no binary names matched this
12+
╰────
13+
14+
error: operator didn't match any binary IDs
15+
╭────
16+
1binary(unknown) & binary_id(unknown) & binary(=unknown) | binary_id(=unknown)
17+
· ───┬───
18+
· ╰── no binary IDs matched this
19+
╰────
20+
21+
error: operator didn't match any binary names
22+
╭────
23+
1binary(unknown) & binary_id(unknown) & binary(=unknown) | binary_id(=unknown)
24+
· ────┬───
25+
· ╰── no binary names matched this
26+
╰────
27+
28+
error: operator didn't match any binary IDs
29+
╭────
30+
1binary(unknown) & binary_id(unknown) & binary(=unknown) | binary_id(=unknown)
31+
· ────┬───
32+
· ╰── no binary IDs matched this
33+
╰────
34+
35+
error: operator didn't match any binary IDs
36+
╭────
37+
1binary_id(nextest-tests::does_not_exist) | binary_id(=nextest-tests::basic)
38+
· ──────────────┬──────────────
39+
· ╰── no binary IDs matched this
40+
╰────
41+
42+
error: operator didn't match any binary IDs
43+
╭────
44+
1binary_id(nextest-tests::example/*) | binary_id(dylib-test::example/*)
45+
· ──────────┬──────────
46+
· ╰── no binary IDs matched this
47+
╰────
48+
49+
error: failed to parse filterset

nextest-filtering/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ proptest = { workspace = true, optional = true }
3535
recursion.workspace = true
3636
regex-syntax.workspace = true
3737
regex.workspace = true
38+
smol_str.workspace = true
3839
test-strategy = { workspace = true, optional = true }
3940
thiserror.workspace = true
4041
winnow.workspace = true

nextest-filtering/src/compile.rs

Lines changed: 65 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ use guppy::{
1212
};
1313
use miette::SourceSpan;
1414
use recursion::CollapsibleExt;
15+
use smol_str::SmolStr;
1516
use std::collections::HashSet;
1617

1718
pub(crate) fn compile(
@@ -22,9 +23,9 @@ pub(crate) fn compile(
2223
let mut errors = vec![];
2324
check_banned_predicates(expr, kind, &mut errors);
2425

25-
let workspace_packages = &cx.make_cache().workspace_packages;
26+
let cx_cache = cx.make_cache();
2627
let mut cache = cx.graph().new_depends_cache();
27-
let expr = compile_expr(expr, workspace_packages, &mut cache, &mut errors);
28+
let expr = compile_expr(expr, cx_cache, &mut cache, &mut errors);
2829

2930
if errors.is_empty() {
3031
Ok(expr)
@@ -110,29 +111,35 @@ fn rdependencies_packages(
110111

111112
fn compile_set_def(
112113
set: &SetDef,
113-
packages: &[PackageMetadata<'_>],
114+
cx_cache: &ParseContextCache<'_>,
114115
cache: &mut DependsCache<'_>,
115116
errors: &mut Vec<ParseSingleError>,
116117
) -> FiltersetLeaf {
117118
match set {
118-
SetDef::Package(matcher, span) => FiltersetLeaf::Packages(expect_non_empty(
119-
matching_packages(matcher, packages),
119+
SetDef::Package(matcher, span) => FiltersetLeaf::Packages(expect_non_empty_packages(
120+
matching_packages(matcher, &cx_cache.workspace_packages),
120121
*span,
121122
errors,
122123
)),
123-
SetDef::Deps(matcher, span) => FiltersetLeaf::Packages(expect_non_empty(
124-
dependencies_packages(matcher, packages, cache),
124+
SetDef::Deps(matcher, span) => FiltersetLeaf::Packages(expect_non_empty_packages(
125+
dependencies_packages(matcher, &cx_cache.workspace_packages, cache),
125126
*span,
126127
errors,
127128
)),
128-
SetDef::Rdeps(matcher, span) => FiltersetLeaf::Packages(expect_non_empty(
129-
rdependencies_packages(matcher, packages, cache),
129+
SetDef::Rdeps(matcher, span) => FiltersetLeaf::Packages(expect_non_empty_packages(
130+
rdependencies_packages(matcher, &cx_cache.workspace_packages, cache),
130131
*span,
131132
errors,
132133
)),
133134
SetDef::Kind(matcher, span) => FiltersetLeaf::Kind(matcher.clone(), *span),
134-
SetDef::Binary(matcher, span) => FiltersetLeaf::Binary(matcher.clone(), *span),
135-
SetDef::BinaryId(matcher, span) => FiltersetLeaf::BinaryId(matcher.clone(), *span),
135+
SetDef::Binary(matcher, span) => FiltersetLeaf::Binary(
136+
expect_non_empty_binary_names(matcher, &cx_cache.binary_names, *span, errors),
137+
*span,
138+
),
139+
SetDef::BinaryId(matcher, span) => FiltersetLeaf::BinaryId(
140+
expect_non_empty_binary_ids(matcher, &cx_cache.binary_ids, *span, errors),
141+
*span,
142+
),
136143
SetDef::Platform(platform, span) => FiltersetLeaf::Platform(*platform, *span),
137144
SetDef::Test(matcher, span) => FiltersetLeaf::Test(matcher.clone(), *span),
138145
SetDef::Default(_) => FiltersetLeaf::Default,
@@ -141,7 +148,7 @@ fn compile_set_def(
141148
}
142149
}
143150

144-
fn expect_non_empty(
151+
fn expect_non_empty_packages(
145152
packages: HashSet<PackageId>,
146153
span: SourceSpan,
147154
errors: &mut Vec<ParseSingleError>,
@@ -152,16 +159,60 @@ fn expect_non_empty(
152159
packages
153160
}
154161

162+
fn expect_non_empty_binary_names(
163+
matcher: &NameMatcher,
164+
all_binary_names: &HashSet<&str>,
165+
span: SourceSpan,
166+
errors: &mut Vec<ParseSingleError>,
167+
) -> NameMatcher {
168+
let any_matches = match matcher {
169+
NameMatcher::Equal { value, .. } => all_binary_names.contains(value.as_str()),
170+
_ => {
171+
// For anything more complex than equals, iterate over all the binary names.
172+
all_binary_names
173+
.iter()
174+
.any(|binary_name| matcher.is_match(binary_name))
175+
}
176+
};
177+
178+
if !any_matches {
179+
errors.push(ParseSingleError::NoBinaryNameMatch(span));
180+
}
181+
matcher.clone()
182+
}
183+
184+
fn expect_non_empty_binary_ids(
185+
matcher: &NameMatcher,
186+
all_binary_ids: &HashSet<SmolStr>,
187+
span: SourceSpan,
188+
errors: &mut Vec<ParseSingleError>,
189+
) -> NameMatcher {
190+
let any_matches = match matcher {
191+
NameMatcher::Equal { value, .. } => all_binary_ids.contains(value.as_str()),
192+
_ => {
193+
// For anything more complex than equals, iterate over all the binary IDs.
194+
all_binary_ids
195+
.iter()
196+
.any(|binary_id| matcher.is_match(binary_id))
197+
}
198+
};
199+
200+
if !any_matches {
201+
errors.push(ParseSingleError::NoBinaryIdMatch(span));
202+
}
203+
matcher.clone()
204+
}
205+
155206
fn compile_expr(
156207
expr: &ParsedExpr,
157-
packages: &[PackageMetadata<'_>],
208+
cx_cache: &ParseContextCache<'_>,
158209
cache: &mut DependsCache<'_>,
159210
errors: &mut Vec<ParseSingleError>,
160211
) -> CompiledExpr {
161212
use crate::expression::ExprFrame::*;
162213

163214
Wrapped(expr).collapse_frames(|layer: ExprFrame<&SetDef, CompiledExpr>| match layer {
164-
Set(set) => CompiledExpr::Set(compile_set_def(set, packages, cache, errors)),
215+
Set(set) => CompiledExpr::Set(compile_set_def(set, cx_cache, cache, errors)),
165216
Not(expr) => CompiledExpr::Not(Box::new(expr)),
166217
Union(expr_1, expr_2) => CompiledExpr::Union(Box::new(expr_1), Box::new(expr_2)),
167218
Intersection(expr_1, expr_2) => {

nextest-filtering/src/errors.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,14 @@ pub enum ParseSingleError {
118118
#[error("operator didn't match any packages")]
119119
NoPackageMatch(#[label("no packages matched this")] SourceSpan),
120120

121+
/// This matcher didn't match any binary IDs.
122+
#[error("operator didn't match any binary IDs")]
123+
NoBinaryIdMatch(#[label("no binary IDs matched this")] SourceSpan),
124+
125+
/// This matcher didn't match any binary names.
126+
#[error("operator didn't match any binary names")]
127+
NoBinaryNameMatch(#[label("no binary names matched this")] SourceSpan),
128+
121129
/// Expected "host" or "target" for a `platform()` predicate.
122130
#[error("invalid argument for platform")]
123131
InvalidPlatformArgument(#[label("expected \"target\" or \"host\"")] SourceSpan),

0 commit comments

Comments
 (0)