@@ -8,12 +8,19 @@ use std::panic::catch_unwind;
88use std:: path:: { Path , PathBuf } ;
99use std:: process:: { Command , Stdio } ;
1010use 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
1320struct TestResult {
1421 name : String ,
15- passed : bool ,
16- log_path : PathBuf ,
22+ outcome : TestOutcome ,
23+ log_path : Option < PathBuf > ,
1724}
1825
1926fn get_test ( name : & str ) -> anyhow:: Result < Box < dyn Test > > {
@@ -39,28 +46,39 @@ fn start_vm(test_setup: TestSetup) -> anyhow::Result<()> {
3946}
4047
4148fn 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
95114fn 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 ! ( "\n FAIL (PASSED {num_ok }/{num_tests}) " ) ;
245+ println ! ( "\n FAIL - {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 ! ( "\n OK ({num_ok }/{num_tests } passed) " ) ;
251+ eprintln ! ( "\n OK - {num_pass }/{num_ran } passed{skip_msg} " ) ;
194252 }
195253
196254 Ok ( ( ) )
0 commit comments