diff --git a/tests/Cargo.lock b/tests/Cargo.lock index fd0a8a5..ec76426 100644 --- a/tests/Cargo.lock +++ b/tests/Cargo.lock @@ -404,6 +404,7 @@ version = "0.0.0" dependencies = [ "anyhow", "clap", + "glob", "macros", "nix", "tempdir", diff --git a/tests/runner/Cargo.toml b/tests/runner/Cargo.toml index 5694160..9e22d63 100644 --- a/tests/runner/Cargo.toml +++ b/tests/runner/Cargo.toml @@ -9,3 +9,4 @@ nix = { version = "0.29.0", features = ["resource", "fs", "signal", "process"] } macros = { path = "../macros" } clap = { version = "4.5.27", features = ["derive"] } tempdir = "0.3.7" +glob = "0.3" diff --git a/tests/runner/src/main.rs b/tests/runner/src/main.rs index 658822e..306969a 100644 --- a/tests/runner/src/main.rs +++ b/tests/runner/src/main.rs @@ -360,14 +360,37 @@ fn run_tests( let tests_to_run: Vec<_> = if test_case == "all" { all_tests } else { + let mut include: Vec = Vec::new(); + let mut exclude: Vec = Vec::new(); + for p in test_case.split(',').map(|p| p.trim()) { + if let Some(neg) = p.strip_prefix('!') { + exclude.push( + glob::Pattern::new(neg) + .with_context(|| format!("invalid glob pattern: {p}"))?, + ); + } else { + include.push( + glob::Pattern::new(p).with_context(|| format!("invalid glob pattern: {p}"))?, + ); + } + } + if include.is_empty() { + anyhow::bail!( + "No include patterns given (only exclusions). Use e.g. \"*,{test_case}\" to exclude." + ); + } + all_tests .into_iter() - .filter(|t| t.name == test_case) + .filter(|t| { + include.iter().any(|p| p.matches(t.name)) + && !exclude.iter().any(|p| p.matches(t.name)) + }) .collect() }; if tests_to_run.is_empty() { - anyhow::bail!("No such test: {test_case}"); + anyhow::bail!("No tests matched: {test_case}"); } let max_name_len = tests_to_run.iter().map(|t| t.name.len()).max().unwrap_or(0); @@ -429,7 +452,7 @@ fn run_tests( #[derive(clap::Subcommand, Clone, Debug)] enum CliCommand { Test { - /// Specify which test to run or "all" + /// Test(s) to run: "all", a name, or comma-separated glob patterns (e.g. "net-*,!net-tap") #[arg(long, default_value = "all")] test_case: String, /// Base directory for test artifacts diff --git a/tests/test_cases/src/lib.rs b/tests/test_cases/src/lib.rs index 5a1bc95..e79a915 100644 --- a/tests/test_cases/src/lib.rs +++ b/tests/test_cases/src/lib.rs @@ -334,6 +334,13 @@ mod tests { if name == "all" { panic!("test_cases() contains test named {name}, but the name is reseved") } + for c in ['*', '?', '[', ']', ',', '!'] { + if name.contains(c) { + panic!( + "test name `{name}` contains `{c}`, which is reserved for the test selection syntax" + ) + } + } } } }