@@ -3,12 +3,19 @@ use clap::Parser;
33use nix:: sys:: resource:: { getrlimit, setrlimit, Resource } ;
44use std:: env;
55use std:: fs:: { self , File } ;
6+ use std:: io:: Write ;
67use std:: panic:: catch_unwind;
78use std:: path:: { Path , PathBuf } ;
89use std:: process:: { Command , Stdio } ;
910use tempdir:: TempDir ;
1011use test_cases:: { test_cases, Test , TestCase , TestSetup } ;
1112
13+ struct TestResult {
14+ name : String ,
15+ passed : bool ,
16+ log_path : PathBuf ,
17+ }
18+
1219fn get_test ( name : & str ) -> anyhow:: Result < Box < dyn Test > > {
1320 let tests = test_cases ( ) ;
1421 tests
@@ -36,7 +43,7 @@ fn run_single_test(
3643 base_dir : & Path ,
3744 keep_all : bool ,
3845 max_name_len : usize ,
39- ) -> anyhow:: Result < bool > {
46+ ) -> anyhow:: Result < TestResult > {
4047 let executable = env:: current_exe ( ) . context ( "Failed to detect current executable" ) ?;
4148 let test_dir = base_dir. join ( test_case) ;
4249 fs:: create_dir ( & test_dir) . context ( "Failed to create test directory" ) ?;
@@ -68,22 +75,76 @@ fn run_single_test(
6875 test. check ( child) ;
6976 } ) ;
7077
71- match result {
72- Ok ( ( ) ) => {
73- eprintln ! ( "OK" ) ;
74- if !keep_all {
75- let _ = fs:: remove_dir_all ( & test_dir) ;
76- }
77- Ok ( true )
78- }
79- Err ( _e) => {
80- eprintln ! ( "FAIL" ) ;
81- Ok ( false )
78+ let passed = result. is_ok ( ) ;
79+ if passed {
80+ eprintln ! ( "OK" ) ;
81+ if !keep_all {
82+ let _ = fs:: remove_dir_all ( & test_dir) ;
8283 }
84+ } else {
85+ eprintln ! ( "FAIL" ) ;
86+ }
87+
88+ Ok ( TestResult {
89+ name : test_case. to_string ( ) ,
90+ passed,
91+ log_path,
92+ } )
93+ }
94+
95+ fn write_github_summary (
96+ results : & [ TestResult ] ,
97+ num_ok : usize ,
98+ num_tests : usize ,
99+ ) -> anyhow:: Result < ( ) > {
100+ let summary_path = env:: var ( "GITHUB_STEP_SUMMARY" )
101+ . context ( "GITHUB_STEP_SUMMARY environment variable not set" ) ?;
102+
103+ let mut file = fs:: OpenOptions :: new ( )
104+ . create ( true )
105+ . append ( true )
106+ . open ( & summary_path)
107+ . context ( "Failed to open GITHUB_STEP_SUMMARY" ) ?;
108+
109+ let all_passed = num_ok == num_tests;
110+ let status = if all_passed { "✅" } else { "❌" } ;
111+
112+ writeln ! (
113+ file,
114+ "## {status} Integration Tests ({num_ok}/{num_tests} passed)\n "
115+ ) ?;
116+
117+ 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 ( ) ;
120+
121+ 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, "```" ) ?;
136+ writeln ! ( file, "</details>\n " ) ?;
83137 }
138+
139+ Ok ( ( ) )
84140}
85141
86- fn run_tests ( test_case : & str , base_dir : Option < PathBuf > , keep_all : bool ) -> anyhow:: Result < ( ) > {
142+ fn run_tests (
143+ test_case : & str ,
144+ base_dir : Option < PathBuf > ,
145+ keep_all : bool ,
146+ github_summary : bool ,
147+ ) -> anyhow:: Result < ( ) > {
87148 // Create the base directory - either use provided path or create a temp one
88149 let base_dir = match base_dir {
89150 Some ( path) => {
@@ -95,22 +156,29 @@ fn run_tests(test_case: &str, base_dir: Option<PathBuf>, keep_all: bool) -> anyh
95156 . into_path ( ) ,
96157 } ;
97158
98- let mut num_tests = 1 ;
99- let mut num_ok: usize = 0 ;
159+ let mut results: Vec < TestResult > = Vec :: new ( ) ;
100160
101161 if test_case == "all" {
102162 let all_tests = test_cases ( ) ;
103- num_tests = all_tests. len ( ) ;
104163 let max_name_len = all_tests. iter ( ) . map ( |t| t. name . len ( ) ) . max ( ) . unwrap_or ( 0 ) ;
105164
106165 for TestCase { name, test : _ } in all_tests {
107- num_ok +=
108- run_single_test ( name, & base_dir, keep_all, max_name_len) . context ( name) ? as usize ;
166+ results. push ( run_single_test ( name, & base_dir, keep_all, max_name_len) . context ( name) ?) ;
109167 }
110168 } else {
111169 let max_name_len = test_case. len ( ) ;
112- num_ok += run_single_test ( test_case, & base_dir, keep_all, max_name_len)
113- . context ( test_case. to_string ( ) ) ? as usize ;
170+ results. push (
171+ run_single_test ( test_case, & base_dir, keep_all, max_name_len)
172+ . context ( test_case. to_string ( ) ) ?,
173+ ) ;
174+ }
175+
176+ let num_tests = results. len ( ) ;
177+ let num_ok = results. iter ( ) . filter ( |r| r. passed ) . count ( ) ;
178+
179+ // Write GitHub Actions summary if requested
180+ if github_summary {
181+ write_github_summary ( & results, num_ok, num_tests) ?;
114182 }
115183
116184 let num_failures = num_tests - num_ok;
@@ -140,6 +208,9 @@ enum CliCommand {
140208 /// Keep all test artifacts even on success
141209 #[ arg( long) ]
142210 keep_all : bool ,
211+ /// Write test results to GitHub Actions job summary ($GITHUB_STEP_SUMMARY)
212+ #[ arg( long) ]
213+ github_summary : bool ,
143214 } ,
144215 StartVm {
145216 #[ arg( long) ]
@@ -155,6 +226,7 @@ impl Default for CliCommand {
155226 test_case : "all" . to_string ( ) ,
156227 base_dir : None ,
157228 keep_all : false ,
229+ github_summary : false ,
158230 }
159231 }
160232}
@@ -175,6 +247,7 @@ fn main() -> anyhow::Result<()> {
175247 test_case,
176248 base_dir,
177249 keep_all,
178- } => run_tests ( & test_case, base_dir, keep_all) ,
250+ github_summary,
251+ } => run_tests ( & test_case, base_dir, keep_all, github_summary) ,
179252 }
180253}
0 commit comments