Skip to content

Commit 8a9b26c

Browse files
committed
[cc] integration test matrix
1 parent 7b5090c commit 8a9b26c

File tree

2 files changed

+159
-27
lines changed

2 files changed

+159
-27
lines changed

cc/Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@ plib = { path = "../plib" }
1616
[dev-dependencies]
1717
tempfile = "3"
1818

19+
[features]
20+
# Enable full compile matrix (4 configs) instead of default single config (-O -g)
21+
test_matrix = []
22+
1923
[lib]
2024
name = "posixutils_cc"
2125
path = "./lib.rs"

cc/tests/common/mod.rs

Lines changed: 155 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,28 @@ use std::path::PathBuf;
1515
use std::process::Command;
1616
use 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
1941
pub 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)]
4269
pub 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)]
6798
pub 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)]
92124
pub 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

Comments
 (0)