Skip to content

Commit 899059d

Browse files
committed
fix: resolve test fixture compilation race condition
1 parent b37d3d8 commit 899059d

1 file changed

Lines changed: 169 additions & 150 deletions

File tree

ghostscope/tests/common/mod.rs

Lines changed: 169 additions & 150 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,20 @@ use std::path::PathBuf;
88
use std::process::Command;
99
use std::sync::Once;
1010

11+
use std::sync::Mutex;
12+
1113
static INIT: Once = Once::new();
1214
static COMPILE: Once = Once::new();
1315
static REGISTER_CLEANUP: Once = Once::new();
1416

17+
lazy_static! {
18+
static ref COMPILE_DEBUG_RESULT: Mutex<Option<anyhow::Result<()>>> = Mutex::new(None);
19+
static ref COMPILE_OPT_RESULT: Mutex<Option<anyhow::Result<()>>> = Mutex::new(None);
20+
static ref COMPILE_COMPLEX_DEBUG_RESULT: Mutex<Option<anyhow::Result<()>>> = Mutex::new(None);
21+
static ref COMPILE_COMPLEX_OPT_RESULT: Mutex<Option<anyhow::Result<()>>> = Mutex::new(None);
22+
static ref COMPILE_COMPLEX_NOPIE_RESULT: Mutex<Option<anyhow::Result<()>>> = Mutex::new(None);
23+
}
24+
1525
/// Initialize logging for tests (call once per test)
1626
pub fn init() {
1727
INIT.call_once(|| {
@@ -94,146 +104,151 @@ pub fn ensure_test_program_compiled() -> anyhow::Result<()> {
94104

95105
/// Compile test program with specific optimization level
96106
pub fn ensure_test_program_compiled_with_opt(opt_level: OptimizationLevel) -> anyhow::Result<()> {
97-
let mut result = Ok(());
98-
99-
let compile_fn = || {
100-
let fixtures_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures");
101-
let sample_program_dir = fixtures_path.join("sample_program");
102-
103-
println!(
104-
"Compiling sample_program {} in {:?}",
105-
opt_level.description(),
106-
sample_program_dir
107-
);
108-
109-
// Clean first (only for debug builds to avoid conflicts)
110-
if opt_level == OptimizationLevel::Debug {
111-
let clean_output = Command::new("make")
112-
.arg("clean")
113-
.current_dir(&sample_program_dir)
114-
.output();
115-
116-
match clean_output {
117-
Ok(_) => println!("✓ Cleaned sample_program build directory"),
118-
Err(e) => {
119-
result = Err(anyhow::anyhow!("Failed to clean sample_program: {}", e));
120-
return;
121-
}
122-
}
123-
}
124-
125-
// Compile specific optimization level
126-
let compile_output = Command::new("make")
127-
.arg(opt_level.as_make_target())
128-
.current_dir(&sample_program_dir)
129-
.output();
130-
131-
match compile_output {
132-
Ok(output) => {
133-
if output.status.success() {
134-
println!(
135-
"✓ Successfully compiled sample_program {}",
136-
opt_level.description()
137-
);
138-
} else {
139-
let stderr = String::from_utf8_lossy(&output.stderr);
140-
result = Err(anyhow::anyhow!(
141-
"Failed to compile sample_program {}: {}",
142-
opt_level.description(),
143-
stderr
144-
));
145-
}
146-
}
147-
Err(e) => {
148-
result = Err(anyhow::anyhow!(
149-
"Failed to run make for sample_program {}: {}",
150-
opt_level.description(),
151-
e
152-
));
153-
}
154-
}
155-
};
156-
157107
match opt_level {
158108
OptimizationLevel::Debug => {
159-
COMPILE.call_once(compile_fn);
109+
COMPILE.call_once(|| {
110+
let compile_result = compile_sample_program(opt_level);
111+
*COMPILE_DEBUG_RESULT.lock().unwrap() = Some(compile_result);
112+
});
113+
match COMPILE_DEBUG_RESULT.lock().unwrap().as_ref() {
114+
Some(Ok(())) => Ok(()),
115+
Some(Err(e)) => Err(anyhow::anyhow!("{}", e)),
116+
None => panic!("Compilation result should be set after call_once"),
117+
}
160118
}
161119
_ => {
162-
COMPILE_OPTIMIZED.call_once(compile_fn);
120+
COMPILE_OPTIMIZED.call_once(|| {
121+
let compile_result = compile_sample_program(opt_level);
122+
*COMPILE_OPT_RESULT.lock().unwrap() = Some(compile_result);
123+
});
124+
match COMPILE_OPT_RESULT.lock().unwrap().as_ref() {
125+
Some(Ok(())) => Ok(()),
126+
Some(Err(e)) => Err(anyhow::anyhow!("{}", e)),
127+
None => panic!("Compilation result should be set after call_once"),
128+
}
163129
}
164130
}
131+
}
165132

166-
result
133+
fn compile_sample_program(opt_level: OptimizationLevel) -> anyhow::Result<()> {
134+
let fixtures_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures");
135+
let sample_program_dir = fixtures_path.join("sample_program");
136+
137+
println!(
138+
"Compiling sample_program {} in {:?}",
139+
opt_level.description(),
140+
sample_program_dir
141+
);
142+
143+
// Clean first (only for debug builds to avoid conflicts)
144+
if opt_level == OptimizationLevel::Debug {
145+
Command::new("make")
146+
.arg("clean")
147+
.current_dir(&sample_program_dir)
148+
.output()
149+
.map_err(|e| anyhow::anyhow!("Failed to clean sample_program: {}", e))?;
150+
println!("✓ Cleaned sample_program build directory");
151+
}
152+
153+
// Compile specific optimization level
154+
let output = Command::new("make")
155+
.arg(opt_level.as_make_target())
156+
.current_dir(&sample_program_dir)
157+
.output()
158+
.map_err(|e| anyhow::anyhow!("Failed to run make for sample_program {}: {}", opt_level.description(), e))?;
159+
160+
if output.status.success() {
161+
println!(
162+
"✓ Successfully compiled sample_program {}",
163+
opt_level.description()
164+
);
165+
Ok(())
166+
} else {
167+
let stderr = String::from_utf8_lossy(&output.stderr);
168+
Err(anyhow::anyhow!(
169+
"Failed to compile sample_program {}: {}",
170+
opt_level.description(),
171+
stderr
172+
))
173+
}
167174
}
168175

169176
static COMPILE_COMPLEX_DEBUG: Once = Once::new();
170177
static COMPILE_COMPLEX_OPT: Once = Once::new();
171178
static COMPILE_COMPLEX_NOPIE: Once = Once::new();
172179

173180
fn ensure_complex_program_compiled_with_opt(opt_level: OptimizationLevel) -> anyhow::Result<()> {
174-
let mut result = Ok(());
175-
let compile_fn = || {
176-
let fixtures_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures");
177-
let program_dir = fixtures_path.join("complex_types_program");
178-
179-
println!(
180-
"Compiling complex_types_program {} in {:?}",
181-
opt_level.description(),
182-
program_dir
183-
);
184-
185-
// Clean first for debug builds
186-
if opt_level == OptimizationLevel::Debug {
187-
let _ = Command::new("make")
188-
.arg("clean")
189-
.current_dir(&program_dir)
190-
.output();
181+
match opt_level {
182+
OptimizationLevel::Debug => {
183+
COMPILE_COMPLEX_DEBUG.call_once(|| {
184+
let compile_result = compile_complex_program(opt_level);
185+
*COMPILE_COMPLEX_DEBUG_RESULT.lock().unwrap() = Some(compile_result);
186+
});
187+
match COMPILE_COMPLEX_DEBUG_RESULT.lock().unwrap().as_ref() {
188+
Some(Ok(())) => Ok(()),
189+
Some(Err(e)) => Err(anyhow::anyhow!("{}", e)),
190+
None => panic!("Compilation result should be set after call_once"),
191+
}
192+
}
193+
_ => {
194+
COMPILE_COMPLEX_OPT.call_once(|| {
195+
let compile_result = compile_complex_program(opt_level);
196+
*COMPILE_COMPLEX_OPT_RESULT.lock().unwrap() = Some(compile_result);
197+
});
198+
match COMPILE_COMPLEX_OPT_RESULT.lock().unwrap().as_ref() {
199+
Some(Ok(())) => Ok(()),
200+
Some(Err(e)) => Err(anyhow::anyhow!("{}", e)),
201+
None => panic!("Compilation result should be set after call_once"),
202+
}
191203
}
204+
}
205+
}
192206

193-
let target = match opt_level {
194-
OptimizationLevel::Debug => "complex_types_program",
195-
OptimizationLevel::O1 => "complex_types_program_o1",
196-
OptimizationLevel::O2 => "complex_types_program_o2",
197-
OptimizationLevel::O3 => "complex_types_program_o3",
198-
};
207+
fn compile_complex_program(opt_level: OptimizationLevel) -> anyhow::Result<()> {
208+
let fixtures_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures");
209+
let program_dir = fixtures_path.join("complex_types_program");
210+
211+
println!(
212+
"Compiling complex_types_program {} in {:?}",
213+
opt_level.description(),
214+
program_dir
215+
);
199216

200-
let compile_output = Command::new("make")
201-
.arg(target)
217+
// Clean first for debug builds
218+
if opt_level == OptimizationLevel::Debug {
219+
let _ = Command::new("make")
220+
.arg("clean")
202221
.current_dir(&program_dir)
203222
.output();
223+
}
204224

205-
match compile_output {
206-
Ok(output) => {
207-
if output.status.success() {
208-
println!(
209-
"✓ Successfully compiled complex_types_program {}",
210-
opt_level.description()
211-
);
212-
} else {
213-
let stderr = String::from_utf8_lossy(&output.stderr);
214-
result = Err(anyhow::anyhow!(
215-
"Failed to compile complex_types_program {}: {}",
216-
opt_level.description(),
217-
stderr
218-
));
219-
}
220-
}
221-
Err(e) => {
222-
result = Err(anyhow::anyhow!(
223-
"Failed to run make for complex_types_program {}: {}",
224-
opt_level.description(),
225-
e
226-
));
227-
}
228-
}
225+
let target = match opt_level {
226+
OptimizationLevel::Debug => "complex_types_program",
227+
OptimizationLevel::O1 => "complex_types_program_o1",
228+
OptimizationLevel::O2 => "complex_types_program_o2",
229+
OptimizationLevel::O3 => "complex_types_program_o3",
229230
};
230231

231-
match opt_level {
232-
OptimizationLevel::Debug => COMPILE_COMPLEX_DEBUG.call_once(compile_fn),
233-
_ => COMPILE_COMPLEX_OPT.call_once(compile_fn),
234-
}
232+
let output = Command::new("make")
233+
.arg(target)
234+
.current_dir(&program_dir)
235+
.output()
236+
.map_err(|e| anyhow::anyhow!("Failed to run make for complex_types_program {}: {}", opt_level.description(), e))?;
235237

236-
result
238+
if output.status.success() {
239+
println!(
240+
"✓ Successfully compiled complex_types_program {}",
241+
opt_level.description()
242+
);
243+
Ok(())
244+
} else {
245+
let stderr = String::from_utf8_lossy(&output.stderr);
246+
Err(anyhow::anyhow!(
247+
"Failed to compile complex_types_program {}: {}",
248+
opt_level.description(),
249+
stderr
250+
))
251+
}
237252
}
238253

239254
/// Test fixtures manager
@@ -289,43 +304,47 @@ impl TestFixtures {
289304

290305
/// Build and return the non-PIE variant of complex_types_program
291306
pub fn get_test_binary_complex_nopie(&self) -> anyhow::Result<PathBuf> {
292-
// Ensure build once
293307
let program_dir = self.base_path.join("complex_types_program");
294-
let mut result = Ok(());
308+
295309
COMPILE_COMPLEX_NOPIE.call_once(|| {
296-
println!(
297-
"Compiling complex_types_program Non-PIE (ET_EXEC) in {:?}",
298-
program_dir
299-
);
300-
let _ = Command::new("make")
301-
.arg("clean")
302-
.current_dir(&program_dir)
303-
.output();
304-
let compile_output = Command::new("make")
305-
.arg("complex_types_program_nopie")
306-
.current_dir(&program_dir)
307-
.output();
308-
match compile_output {
309-
Ok(out) => {
310-
if out.status.success() {
311-
println!("✓ Successfully compiled complex_types_program Non-PIE");
312-
} else {
313-
let stderr = String::from_utf8_lossy(&out.stderr);
314-
result = Err(anyhow::anyhow!(
315-
"Failed to compile complex_types_program Non-PIE: {}",
316-
stderr
317-
));
318-
}
319-
}
320-
Err(e) => {
321-
result = Err(anyhow::anyhow!(
322-
"Failed to run make for complex_types_program Non-PIE: {}",
323-
e
324-
));
310+
let compile_result = (|| -> anyhow::Result<()> {
311+
println!(
312+
"Compiling complex_types_program Non-PIE (ET_EXEC) in {:?}",
313+
program_dir
314+
);
315+
let _ = Command::new("make")
316+
.arg("clean")
317+
.current_dir(&program_dir)
318+
.output();
319+
320+
let output = Command::new("make")
321+
.arg("complex_types_program_nopie")
322+
.current_dir(&program_dir)
323+
.output()
324+
.map_err(|e| anyhow::anyhow!("Failed to run make for complex_types_program Non-PIE: {}", e))?;
325+
326+
if output.status.success() {
327+
println!("✓ Successfully compiled complex_types_program Non-PIE");
328+
Ok(())
329+
} else {
330+
let stderr = String::from_utf8_lossy(&output.stderr);
331+
Err(anyhow::anyhow!(
332+
"Failed to compile complex_types_program Non-PIE: {}",
333+
stderr
334+
))
325335
}
326-
}
336+
})();
337+
338+
*COMPILE_COMPLEX_NOPIE_RESULT.lock().unwrap() = Some(compile_result);
327339
});
328-
result?;
340+
341+
// Check compilation result
342+
match COMPILE_COMPLEX_NOPIE_RESULT.lock().unwrap().as_ref() {
343+
Some(Ok(())) => {},
344+
Some(Err(e)) => return Err(anyhow::anyhow!("{}", e)),
345+
None => panic!("Compilation result should be set after call_once"),
346+
}
347+
329348
let bin_path = program_dir.join("complex_types_program_nopie");
330349
if !bin_path.exists() {
331350
anyhow::bail!("Non-PIE binary not found: {}", bin_path.display());

0 commit comments

Comments
 (0)