@@ -15,6 +15,28 @@ use std::path::PathBuf;
1515use std:: process:: Command ;
1616use tempfile:: NamedTempFile ;
1717
18+ // ============================================================================
19+ // Compile Matrix Configuration
20+ // ============================================================================
21+
22+ /// Full compile matrix (enabled with --features test_matrix).
23+ /// Tests all 4 configurations: default, debug-only, optimized-only, and both.
24+ #[ cfg( feature = "test_matrix" ) ]
25+ pub const COMPILE_MATRIX : & [ ( & str , & [ & str ] ) ] = & [
26+ ( "default" , & [ ] ) ,
27+ ( "debug" , & [ "-g" ] ) ,
28+ ( "optimized" , & [ "-O" ] ) ,
29+ ( "debug_opt" , & [ "-g" , "-O" ] ) ,
30+ ] ;
31+
32+ /// Default compile config: -O -g only (fastest while still catching optimization bugs).
33+ #[ cfg( not( feature = "test_matrix" ) ) ]
34+ pub const COMPILE_MATRIX : & [ ( & str , & [ & str ] ) ] = & [ ( "debug_opt" , & [ "-g" , "-O" ] ) ] ;
35+
36+ // ============================================================================
37+ // File Path Utilities
38+ // ============================================================================
39+
1840/// Get path to a test input file in a specific test directory
1941pub fn get_test_file_path ( test_dir : & str , filename : & str ) -> PathBuf {
2042 let mut path = PathBuf :: from ( env ! ( "CARGO_MANIFEST_DIR" ) ) ;
@@ -37,23 +59,31 @@ pub fn create_c_file(name: &str, content: &str) -> NamedTempFile {
3759 file
3860}
3961
62+ // ============================================================================
63+ // Low-level Compile/Run Utilities (for backward compatibility)
64+ // ============================================================================
65+
4066/// Compile a C file using pcc and return the path to the executable
4167/// The executable is placed in temp dir and must be cleaned up by caller
68+ #[ allow( dead_code) ]
4269pub fn compile ( c_file : & PathBuf ) -> Option < PathBuf > {
70+ compile_with_opts ( c_file, & [ ] )
71+ }
72+
73+ /// Compile a C file with extra options
74+ pub fn compile_with_opts ( c_file : & PathBuf , extra_opts : & [ & str ] ) -> Option < PathBuf > {
75+ let thread_id = format ! ( "{:?}" , std:: thread:: current( ) . id( ) ) ;
4376 let exe_path = std:: env:: temp_dir ( ) . join ( format ! (
44- "pcc_exe_{}" ,
45- c_file. file_stem( ) . unwrap( ) . to_string_lossy( )
77+ "pcc_exe_{}_{}" ,
78+ c_file. file_stem( ) . unwrap( ) . to_string_lossy( ) ,
79+ thread_id. replace( |c: char | !c. is_alphanumeric( ) , "_" )
4680 ) ) ;
4781
48- let output = run_test_base (
49- "pcc" ,
50- & vec ! [
51- "-o" . to_string( ) ,
52- exe_path. to_string_lossy( ) . to_string( ) ,
53- c_file. to_string_lossy( ) . to_string( ) ,
54- ] ,
55- & [ ] ,
56- ) ;
82+ let mut args = vec ! [ "-o" . to_string( ) , exe_path. to_string_lossy( ) . to_string( ) ] ;
83+ args. extend ( extra_opts. iter ( ) . map ( |s| s. to_string ( ) ) ) ;
84+ args. push ( c_file. to_string_lossy ( ) . to_string ( ) ) ;
85+
86+ let output = run_test_base ( "pcc" , & args, & [ ] ) ;
5787
5888 if output. status . success ( ) {
5989 Some ( exe_path)
@@ -64,6 +94,7 @@ pub fn compile(c_file: &PathBuf) -> Option<PathBuf> {
6494}
6595
6696/// Compile a C file from a specific test directory
97+ #[ allow( dead_code) ]
6798pub fn compile_test_file ( test_dir : & str , filename : & str ) -> Option < PathBuf > {
6899 let c_file = get_test_file_path ( test_dir, filename) ;
69100 compile ( & c_file)
@@ -89,34 +120,33 @@ pub fn run_with_output(exe: &PathBuf) -> (i32, String) {
89120}
90121
91122/// Clean up an executable file
123+ #[ allow( dead_code) ]
92124pub fn cleanup_exe ( exe_file : & Option < PathBuf > ) {
93125 if let Some ( exe) = exe_file {
94126 let _ = std:: fs:: remove_file ( exe) ;
95127 }
96128}
97129
98- /// Compile inline C code and run, returning exit code
99- /// Temp files are cleaned up automatically via tempfile
100- pub fn compile_and_run ( name : & str , content : & str ) -> i32 {
101- compile_and_run_with_opts ( name, content, & [ ] )
102- }
103-
104- /// Compile inline C code with optimization and run, returning exit code
105- pub fn compile_and_run_optimized ( name : & str , content : & str ) -> i32 {
106- compile_and_run_with_opts ( name, content, & [ "-O1" . to_string ( ) ] )
107- }
130+ // ============================================================================
131+ // Matrix-aware Compile and Run (main API)
132+ // ============================================================================
108133
109- /// Compile inline C code with extra options and run, returning exit code
110- /// Temp files are cleaned up automatically via tempfile
111- pub fn compile_and_run_with_opts ( name : & str , content : & str , extra_opts : & [ String ] ) -> i32 {
134+ /// Internal: Compile and run with a single specific configuration (no matrix loop)
135+ fn compile_and_run_single (
136+ name : & str ,
137+ content : & str ,
138+ extra_opts : & [ String ] ,
139+ config_name : & str ,
140+ ) -> i32 {
112141 let c_file = create_c_file ( name, content) ;
113142 let c_path = c_file. path ( ) . to_path_buf ( ) ;
114143
115- // Use thread ID to make exe path unique for parallel test execution
144+ // Use thread ID and config name to make exe path unique for parallel test execution
116145 let thread_id = format ! ( "{:?}" , std:: thread:: current( ) . id( ) ) ;
117146 let exe_path = std:: env:: temp_dir ( ) . join ( format ! (
118- "pcc_exe_{}_{}" ,
147+ "pcc_exe_{}_{}_{} " ,
119148 name,
149+ config_name,
120150 thread_id. replace( |c: char | !c. is_alphanumeric( ) , "_" )
121151 ) ) ;
122152
@@ -128,8 +158,9 @@ pub fn compile_and_run_with_opts(name: &str, content: &str, extra_opts: &[String
128158
129159 if !output. status . success ( ) {
130160 eprintln ! (
131- "pcc compilation failed for {}:\n {}" ,
161+ "pcc compilation failed for {} [config: {}] :\n {}" ,
132162 name,
163+ config_name,
133164 String :: from_utf8_lossy( & output. stderr)
134165 ) ;
135166 return -1 ;
@@ -146,3 +177,100 @@ pub fn compile_and_run_with_opts(name: &str, content: &str, extra_opts: &[String
146177
147178 exit_code
148179}
180+
181+ /// Compile inline C code with extra options and run with all matrix configurations.
182+ /// Returns 0 if all configurations pass, or the first non-zero exit code on failure.
183+ pub fn compile_and_run_with_opts ( name : & str , content : & str , extra_opts : & [ String ] ) -> i32 {
184+ for ( config_name, matrix_flags) in COMPILE_MATRIX {
185+ // Combine matrix flags with caller's extra options
186+ let mut combined: Vec < String > = matrix_flags. iter ( ) . map ( |s| s. to_string ( ) ) . collect ( ) ;
187+ combined. extend ( extra_opts. iter ( ) . cloned ( ) ) ;
188+
189+ let result = compile_and_run_single ( name, content, & combined, config_name) ;
190+ if result != 0 {
191+ eprintln ! (
192+ "Test '{}' FAILED with config '{}': exit code {}" ,
193+ name, config_name, result
194+ ) ;
195+ return result;
196+ }
197+ }
198+ 0
199+ }
200+
201+ /// Compile inline C code and run with all matrix configurations.
202+ /// Returns 0 if all configurations pass, or the first non-zero exit code on failure.
203+ pub fn compile_and_run ( name : & str , content : & str ) -> i32 {
204+ compile_and_run_with_opts ( name, content, & [ ] )
205+ }
206+
207+ /// Compile inline C code with optimization and run (single config, skips matrix).
208+ /// This is used by tests that specifically test optimization behavior.
209+ pub fn compile_and_run_optimized ( name : & str , content : & str ) -> i32 {
210+ compile_and_run_single ( name, content, & [ "-O1" . to_string ( ) ] , "optimized_only" )
211+ }
212+
213+ // ============================================================================
214+ // Matrix-aware File-based Compile and Run
215+ // ============================================================================
216+
217+ /// Compile and run a fixed .c file with all matrix configurations.
218+ /// Returns the exit code (0 if all pass, first non-zero on failure).
219+ pub fn compile_and_run_file ( test_dir : & str , filename : & str ) -> i32 {
220+ let c_file = get_test_file_path ( test_dir, filename) ;
221+
222+ for ( config_name, matrix_flags) in COMPILE_MATRIX {
223+ let exe = compile_with_opts ( & c_file, matrix_flags) ;
224+ if exe. is_none ( ) {
225+ eprintln ! (
226+ "Compilation failed for {}/{} [config: {}]" ,
227+ test_dir, filename, config_name
228+ ) ;
229+ return -1 ;
230+ }
231+
232+ let exit_code = run ( exe. as_ref ( ) . unwrap ( ) ) ;
233+ cleanup_exe ( & exe) ;
234+
235+ if exit_code != 0 {
236+ eprintln ! (
237+ "Test {}/{} FAILED with config '{}': exit code {}" ,
238+ test_dir, filename, config_name, exit_code
239+ ) ;
240+ return exit_code;
241+ }
242+ }
243+ 0
244+ }
245+
246+ /// Compile and run a fixed .c file with all matrix configurations, capturing stdout.
247+ /// Returns (exit_code, stdout) where exit_code is 0 if all configs pass.
248+ /// The stdout returned is from the last successful run (or the failing run).
249+ pub fn compile_and_run_file_with_output ( test_dir : & str , filename : & str ) -> ( i32 , String ) {
250+ let c_file = get_test_file_path ( test_dir, filename) ;
251+ let mut last_stdout = String :: new ( ) ;
252+
253+ for ( config_name, matrix_flags) in COMPILE_MATRIX {
254+ let exe = compile_with_opts ( & c_file, matrix_flags) ;
255+ if exe. is_none ( ) {
256+ eprintln ! (
257+ "Compilation failed for {}/{} [config: {}]" ,
258+ test_dir, filename, config_name
259+ ) ;
260+ return ( -1 , last_stdout) ;
261+ }
262+
263+ let ( exit_code, stdout) = run_with_output ( exe. as_ref ( ) . unwrap ( ) ) ;
264+ last_stdout = stdout;
265+ cleanup_exe ( & exe) ;
266+
267+ if exit_code != 0 {
268+ eprintln ! (
269+ "Test {}/{} FAILED with config '{}': exit code {}" ,
270+ test_dir, filename, config_name, exit_code
271+ ) ;
272+ return ( exit_code, last_stdout) ;
273+ }
274+ }
275+ ( 0 , last_stdout)
276+ }
0 commit comments