Skip to content

Commit eb65adc

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

2 files changed

Lines changed: 170 additions & 153 deletions

File tree

.github/workflows/ci.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,9 @@ jobs:
125125
- name: Build test binaries
126126
run: cargo test --no-run --all-features
127127

128+
- name: Build dwarf-tool (required by tests)
129+
run: cargo build -p dwarf-tool
130+
128131
- name: Run tests with sudo
129132
run: sudo -E $(which cargo) test --all-features
130133
env:

ghostscope/tests/common/mod.rs

Lines changed: 167 additions & 153 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,145 @@ 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+
// Compile specific optimization level
144+
let output = Command::new("make")
145+
.arg(opt_level.as_make_target())
146+
.current_dir(&sample_program_dir)
147+
.output()
148+
.map_err(|e| {
149+
anyhow::anyhow!(
150+
"Failed to run make for sample_program {}: {}",
151+
opt_level.description(),
152+
e
153+
)
154+
})?;
155+
156+
if output.status.success() {
157+
println!(
158+
"✓ Successfully compiled sample_program {}",
159+
opt_level.description()
160+
);
161+
Ok(())
162+
} else {
163+
let stderr = String::from_utf8_lossy(&output.stderr);
164+
Err(anyhow::anyhow!(
165+
"Failed to compile sample_program {}: {}",
166+
opt_level.description(),
167+
stderr
168+
))
169+
}
167170
}
168171

169172
static COMPILE_COMPLEX_DEBUG: Once = Once::new();
170173
static COMPILE_COMPLEX_OPT: Once = Once::new();
171174
static COMPILE_COMPLEX_NOPIE: Once = Once::new();
172175

173176
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();
191-
}
192-
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-
};
199-
200-
let compile_output = Command::new("make")
201-
.arg(target)
202-
.current_dir(&program_dir)
203-
.output();
204-
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-
}
177+
match opt_level {
178+
OptimizationLevel::Debug => {
179+
COMPILE_COMPLEX_DEBUG.call_once(|| {
180+
let compile_result = compile_complex_program(opt_level);
181+
*COMPILE_COMPLEX_DEBUG_RESULT.lock().unwrap() = Some(compile_result);
182+
});
183+
match COMPILE_COMPLEX_DEBUG_RESULT.lock().unwrap().as_ref() {
184+
Some(Ok(())) => Ok(()),
185+
Some(Err(e)) => Err(anyhow::anyhow!("{}", e)),
186+
None => panic!("Compilation result should be set after call_once"),
220187
}
221-
Err(e) => {
222-
result = Err(anyhow::anyhow!(
223-
"Failed to run make for complex_types_program {}: {}",
224-
opt_level.description(),
225-
e
226-
));
188+
}
189+
_ => {
190+
COMPILE_COMPLEX_OPT.call_once(|| {
191+
let compile_result = compile_complex_program(opt_level);
192+
*COMPILE_COMPLEX_OPT_RESULT.lock().unwrap() = Some(compile_result);
193+
});
194+
match COMPILE_COMPLEX_OPT_RESULT.lock().unwrap().as_ref() {
195+
Some(Ok(())) => Ok(()),
196+
Some(Err(e)) => Err(anyhow::anyhow!("{}", e)),
197+
None => panic!("Compilation result should be set after call_once"),
227198
}
228199
}
200+
}
201+
}
202+
203+
fn compile_complex_program(opt_level: OptimizationLevel) -> anyhow::Result<()> {
204+
let fixtures_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures");
205+
let program_dir = fixtures_path.join("complex_types_program");
206+
207+
println!(
208+
"Compiling complex_types_program {} in {:?}",
209+
opt_level.description(),
210+
program_dir
211+
);
212+
213+
let target = match opt_level {
214+
OptimizationLevel::Debug => "complex_types_program",
215+
OptimizationLevel::O1 => "complex_types_program_o1",
216+
OptimizationLevel::O2 => "complex_types_program_o2",
217+
OptimizationLevel::O3 => "complex_types_program_o3",
229218
};
230219

231-
match opt_level {
232-
OptimizationLevel::Debug => COMPILE_COMPLEX_DEBUG.call_once(compile_fn),
233-
_ => COMPILE_COMPLEX_OPT.call_once(compile_fn),
220+
let output = Command::new("make")
221+
.arg(target)
222+
.current_dir(&program_dir)
223+
.output()
224+
.map_err(|e| {
225+
anyhow::anyhow!(
226+
"Failed to run make for complex_types_program {}: {}",
227+
opt_level.description(),
228+
e
229+
)
230+
})?;
231+
232+
if output.status.success() {
233+
println!(
234+
"✓ Successfully compiled complex_types_program {}",
235+
opt_level.description()
236+
);
237+
Ok(())
238+
} else {
239+
let stderr = String::from_utf8_lossy(&output.stderr);
240+
Err(anyhow::anyhow!(
241+
"Failed to compile complex_types_program {}: {}",
242+
opt_level.description(),
243+
stderr
244+
))
234245
}
235-
236-
result
237246
}
238247

239248
/// Test fixtures manager
@@ -289,43 +298,48 @@ impl TestFixtures {
289298

290299
/// Build and return the non-PIE variant of complex_types_program
291300
pub fn get_test_binary_complex_nopie(&self) -> anyhow::Result<PathBuf> {
292-
// Ensure build once
293301
let program_dir = self.base_path.join("complex_types_program");
294-
let mut result = Ok(());
302+
295303
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-
));
304+
let compile_result = (|| -> anyhow::Result<()> {
305+
println!(
306+
"Compiling complex_types_program Non-PIE (ET_EXEC) in {:?}",
307+
program_dir
308+
);
309+
310+
let output = Command::new("make")
311+
.arg("complex_types_program_nopie")
312+
.current_dir(&program_dir)
313+
.output()
314+
.map_err(|e| {
315+
anyhow::anyhow!(
316+
"Failed to run make for complex_types_program Non-PIE: {}",
317+
e
318+
)
319+
})?;
320+
321+
if output.status.success() {
322+
println!("✓ Successfully compiled complex_types_program Non-PIE");
323+
Ok(())
324+
} else {
325+
let stderr = String::from_utf8_lossy(&output.stderr);
326+
Err(anyhow::anyhow!(
327+
"Failed to compile complex_types_program Non-PIE: {}",
328+
stderr
329+
))
325330
}
326-
}
331+
})();
332+
333+
*COMPILE_COMPLEX_NOPIE_RESULT.lock().unwrap() = Some(compile_result);
327334
});
328-
result?;
335+
336+
// Check compilation result
337+
match COMPILE_COMPLEX_NOPIE_RESULT.lock().unwrap().as_ref() {
338+
Some(Ok(())) => {}
339+
Some(Err(e)) => return Err(anyhow::anyhow!("{}", e)),
340+
None => panic!("Compilation result should be set after call_once"),
341+
}
342+
329343
let bin_path = program_dir.join("complex_types_program_nopie");
330344
if !bin_path.exists() {
331345
anyhow::bail!("Non-PIE binary not found: {}", bin_path.display());

0 commit comments

Comments
 (0)