Skip to content

Commit 495e288

Browse files
committed
tests: Add support for skipping tests
Signed-off-by: Matej Hrica <mhrica@redhat.com>
1 parent afa1f0a commit 495e288

File tree

3 files changed

+146
-61
lines changed

3 files changed

+146
-61
lines changed

tests/guest-agent/src/main.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ fn run_guest_agent(test_name: &str) -> anyhow::Result<()> {
88
.into_iter()
99
.find(|t| t.name() == test_name)
1010
.context("No such test!")?;
11-
let TestCase { test, name: _ } = test_case;
11+
let TestCase { test, .. } = test_case;
1212
test.in_guest();
1313
Ok(())
1414
}

tests/runner/src/main.rs

Lines changed: 118 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,19 @@ use std::panic::catch_unwind;
88
use std::path::{Path, PathBuf};
99
use std::process::{Command, Stdio};
1010
use tempdir::TempDir;
11-
use test_cases::{test_cases, Test, TestCase, TestSetup};
11+
use test_cases::{test_cases, ShouldRun, Test, TestCase, TestSetup};
12+
13+
#[derive(Clone)]
14+
enum TestOutcome {
15+
Pass,
16+
Fail,
17+
Skip(&'static str),
18+
}
1219

1320
struct TestResult {
1421
name: String,
15-
passed: bool,
16-
log_path: PathBuf,
22+
outcome: TestOutcome,
23+
log_path: Option<PathBuf>,
1724
}
1825

1926
fn get_test(name: &str) -> anyhow::Result<Box<dyn Test>> {
@@ -39,28 +46,39 @@ fn start_vm(test_setup: TestSetup) -> anyhow::Result<()> {
3946
}
4047

4148
fn run_single_test(
42-
test_case: &str,
49+
test_case: &TestCase,
4350
base_dir: &Path,
4451
keep_all: bool,
4552
max_name_len: usize,
4653
) -> anyhow::Result<TestResult> {
54+
eprint!(
55+
"[{}] {:.<width$} ",
56+
test_case.name,
57+
"",
58+
width = max_name_len - test_case.name.len() + 3
59+
);
60+
61+
// Check if test should run
62+
if let ShouldRun::No(reason) = test_case.should_run() {
63+
eprintln!("SKIP ({})", reason);
64+
return Ok(TestResult {
65+
name: test_case.name.to_string(),
66+
outcome: TestOutcome::Skip(reason),
67+
log_path: None,
68+
});
69+
}
70+
4771
let executable = env::current_exe().context("Failed to detect current executable")?;
48-
let test_dir = base_dir.join(test_case);
72+
let test_dir = base_dir.join(test_case.name);
4973
fs::create_dir(&test_dir).context("Failed to create test directory")?;
5074

5175
let log_path = test_dir.join("log.txt");
5276
let log_file = File::create(&log_path).context("Failed to create log file")?;
5377

54-
eprint!(
55-
"[{test_case}] {:.<width$} ",
56-
"",
57-
width = max_name_len - test_case.len() + 3
58-
);
59-
6078
let child = Command::new(&executable)
6179
.arg("start-vm")
6280
.arg("--test-case")
63-
.arg(test_case)
81+
.arg(test_case.name)
6482
.arg("--tmp-dir")
6583
.arg(&test_dir)
6684
.stdin(Stdio::piped())
@@ -69,33 +87,35 @@ fn run_single_test(
6987
.spawn()
7088
.context("Failed to start subprocess for test")?;
7189

72-
let _ = get_test(test_case)?;
90+
let test_name = test_case.name.to_string();
7391
let result = catch_unwind(|| {
74-
let test = get_test(test_case).unwrap();
92+
let test = get_test(&test_name).unwrap();
7593
test.check(child);
7694
});
7795

78-
let passed = result.is_ok();
79-
if passed {
96+
let outcome = if result.is_ok() {
8097
eprintln!("OK");
8198
if !keep_all {
8299
let _ = fs::remove_dir_all(&test_dir);
83100
}
101+
TestOutcome::Pass
84102
} else {
85103
eprintln!("FAIL");
86-
}
104+
TestOutcome::Fail
105+
};
87106

88107
Ok(TestResult {
89-
name: test_case.to_string(),
90-
passed,
91-
log_path,
108+
name: test_case.name.to_string(),
109+
outcome,
110+
log_path: Some(log_path),
92111
})
93112
}
94113

95114
fn write_github_summary(
96115
results: &[TestResult],
97-
num_ok: usize,
98-
num_tests: usize,
116+
num_pass: usize,
117+
num_fail: usize,
118+
num_skip: usize,
99119
) -> anyhow::Result<()> {
100120
let summary_path = env::var("GITHUB_STEP_SUMMARY")
101121
.context("GITHUB_STEP_SUMMARY environment variable not set")?;
@@ -106,33 +126,50 @@ fn write_github_summary(
106126
.open(&summary_path)
107127
.context("Failed to open GITHUB_STEP_SUMMARY")?;
108128

109-
let all_passed = num_ok == num_tests;
110-
let status = if all_passed { "✅" } else { "❌" };
129+
let num_ran = num_pass + num_fail;
130+
let status = if num_fail == 0 { "✅" } else { "❌" };
131+
let skip_msg = if num_skip > 0 {
132+
format!(" ({num_skip} skipped)")
133+
} else {
134+
String::new()
135+
};
111136

112137
writeln!(
113138
file,
114-
"## {status} Integration Tests ({num_ok}/{num_tests} passed)\n"
139+
"## {status} Integration Tests - {num_pass}/{num_ran} passed{skip_msg}\n"
115140
)?;
116141

117142
for result in results {
118-
let icon = if result.passed { "✅" } else { "❌" };
119-
let log_content = fs::read_to_string(&result.log_path).unwrap_or_default();
143+
let (icon, status_text) = match &result.outcome {
144+
TestOutcome::Pass => ("✅", String::new()),
145+
TestOutcome::Fail => ("❌", String::new()),
146+
TestOutcome::Skip(reason) => ("⏭️", format!(" - {}", reason)),
147+
};
120148

121149
writeln!(file, "<details>")?;
122-
writeln!(file, "<summary>{icon} {}</summary>\n", result.name)?;
123-
writeln!(file, "```")?;
124-
// Limit log size to avoid huge summaries (2 MiB limit)
125-
const MAX_LOG_SIZE: usize = 2 * 1024 * 1024;
126-
let truncated = if log_content.len() > MAX_LOG_SIZE {
127-
format!(
128-
"... (truncated, showing last 1 MiB) ...\n{}",
129-
&log_content[log_content.len() - MAX_LOG_SIZE..]
130-
)
131-
} else {
132-
log_content
133-
};
134-
writeln!(file, "{truncated}")?;
135-
writeln!(file, "```")?;
150+
writeln!(
151+
file,
152+
"<summary>{icon} {}{}</summary>\n",
153+
result.name, status_text
154+
)?;
155+
156+
if let Some(log_path) = &result.log_path {
157+
let log_content = fs::read_to_string(log_path).unwrap_or_default();
158+
writeln!(file, "```")?;
159+
// Limit log size to avoid huge summaries (2 MiB limit)
160+
const MAX_LOG_SIZE: usize = 2 * 1024 * 1024;
161+
let truncated = if log_content.len() > MAX_LOG_SIZE {
162+
format!(
163+
"... (truncated, showing last 1 MiB) ...\n{}",
164+
&log_content[log_content.len() - MAX_LOG_SIZE..]
165+
)
166+
} else {
167+
log_content
168+
};
169+
writeln!(file, "{truncated}")?;
170+
writeln!(file, "```")?;
171+
}
172+
136173
writeln!(file, "</details>\n")?;
137174
}
138175

@@ -157,40 +194,61 @@ fn run_tests(
157194
};
158195

159196
let mut results: Vec<TestResult> = Vec::new();
197+
let all_tests = test_cases();
160198

161-
if test_case == "all" {
162-
let all_tests = test_cases();
163-
let max_name_len = all_tests.iter().map(|t| t.name.len()).max().unwrap_or(0);
164-
165-
for TestCase { name, test: _ } in all_tests {
166-
results.push(run_single_test(name, &base_dir, keep_all, max_name_len).context(name)?);
167-
}
199+
let tests_to_run: Vec<_> = if test_case == "all" {
200+
all_tests
168201
} else {
169-
let max_name_len = test_case.len();
170-
results.push(
171-
run_single_test(test_case, &base_dir, keep_all, max_name_len)
172-
.context(test_case.to_string())?,
173-
);
202+
all_tests
203+
.into_iter()
204+
.filter(|t| t.name == test_case)
205+
.collect()
206+
};
207+
208+
if tests_to_run.is_empty() {
209+
anyhow::bail!("No such test: {test_case}");
210+
}
211+
212+
let max_name_len = tests_to_run.iter().map(|t| t.name.len()).max().unwrap_or(0);
213+
214+
for tc in &tests_to_run {
215+
results.push(run_single_test(tc, &base_dir, keep_all, max_name_len).context(tc.name)?);
174216
}
175217

176-
let num_tests = results.len();
177-
let num_ok = results.iter().filter(|r| r.passed).count();
218+
let num_pass = results
219+
.iter()
220+
.filter(|r| matches!(r.outcome, TestOutcome::Pass))
221+
.count();
222+
let num_fail = results
223+
.iter()
224+
.filter(|r| matches!(r.outcome, TestOutcome::Fail))
225+
.count();
226+
let num_skip = results
227+
.iter()
228+
.filter(|r| matches!(r.outcome, TestOutcome::Skip(_)))
229+
.count();
230+
let num_ran = num_pass + num_fail;
178231

179232
// Write GitHub Actions summary if requested
180233
if github_summary {
181-
write_github_summary(&results, num_ok, num_tests)?;
234+
write_github_summary(&results, num_pass, num_fail, num_skip)?;
182235
}
183236

184-
let num_failures = num_tests - num_ok;
185-
if num_failures > 0 {
237+
let skip_msg = if num_skip > 0 {
238+
format!(" ({num_skip} skipped)")
239+
} else {
240+
String::new()
241+
};
242+
243+
if num_fail > 0 {
186244
eprintln!("(See test artifacts at: {})", base_dir.display());
187-
println!("\nFAIL (PASSED {num_ok}/{num_tests})");
245+
println!("\nFAIL - {num_pass}/{num_ran} passed{skip_msg}");
188246
anyhow::bail!("")
189247
} else {
190248
if keep_all {
191249
eprintln!("(See test artifacts at: {})", base_dir.display());
192250
}
193-
eprintln!("\nOK ({num_ok}/{num_tests} passed)");
251+
eprintln!("\nOK - {num_pass}/{num_ran} passed{skip_msg}");
194252
}
195253

196254
Ok(())

tests/test_cases/src/lib.rs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,22 @@ use test_tsi_tcp_guest_listen::TestTsiTcpGuestListen;
1313
mod test_multiport_console;
1414
use test_multiport_console::TestMultiportConsole;
1515

16+
pub enum ShouldRun {
17+
Yes,
18+
No(&'static str),
19+
}
20+
21+
impl ShouldRun {
22+
/// Returns Yes unless on macOS, in which case returns No with the given reason.
23+
pub fn yes_unless_macos(reason: &'static str) -> Self {
24+
if cfg!(target_os = "macos") {
25+
ShouldRun::No(reason)
26+
} else {
27+
ShouldRun::Yes
28+
}
29+
}
30+
}
31+
1632
pub fn test_cases() -> Vec<TestCase> {
1733
// Register your test here:
1834
vec![
@@ -80,6 +96,11 @@ pub trait Test {
8096
let output = child.wait_with_output().unwrap();
8197
assert_eq!(String::from_utf8(output.stdout).unwrap(), "OK\n");
8298
}
99+
100+
/// Check if this test should run on this platform.
101+
fn should_run(&self) -> ShouldRun {
102+
ShouldRun::Yes
103+
}
83104
}
84105

85106
#[guest]
@@ -100,6 +121,12 @@ impl TestCase {
100121
Self { name, test }
101122
}
102123

124+
/// Check if this test should run on this platform.
125+
#[host]
126+
pub fn should_run(&self) -> ShouldRun {
127+
self.test.should_run()
128+
}
129+
103130
#[allow(dead_code)]
104131
pub fn name(&self) -> &'static str {
105132
self.name

0 commit comments

Comments
 (0)