Skip to content

Commit f77022c

Browse files
authored
test: integration tests can now call dynamic libraries (#1690)
1 parent 38f0f0d commit f77022c

4 files changed

Lines changed: 64 additions & 8 deletions

File tree

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

wild/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ ar = { workspace = true }
2626
itertools = { workspace = true }
2727
foldhash = { workspace = true }
2828
linker-diff = { path = "../linker-diff" }
29+
libloading = { workspace = true }
2930
object = { workspace = true }
3031
os_info = { workspace = true }
3132
regex = { workspace = true }

wild/tests/integration_tests.rs

Lines changed: 61 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,11 @@
6767
//!
6868
//! RunEnabled:{bool} Defaults to true. Set to false to disable execution of the resulting binary.
6969
//!
70+
//! RunDynSym:{string} If set and RunEnabled:true, then, instead of executing the binary normally,
71+
//! the binary is loaded as a shared library and the function specified by the string is called.
72+
//! The function must return an integer to indicate status (status != 42 is an error).
73+
//! Such run is obviously skipped if the shared library is cross compiled.
74+
//!
7075
//! SkipLinker:{linker-name} Don't link with the specified linker. Mostly useful if testing a flag
7176
//! that isn't supported by GNU ld.
7277
//!
@@ -171,6 +176,7 @@
171176
mod external_tests;
172177

173178
use itertools::Itertools;
179+
use libloading::Library;
174180
use libwild::bail;
175181
use libwild::ensure;
176182
use libwild::error;
@@ -520,6 +526,7 @@ struct Config {
520526
compiler: String,
521527
should_diff: bool,
522528
should_run: bool,
529+
run_dyn_sym: Option<String>,
523530
should_error: bool,
524531
expect_messages: Vec<ErrorMatcher>,
525532
support_architectures: Vec<Architecture>,
@@ -995,6 +1002,7 @@ impl Config {
9951002
compiler: "gcc".to_owned(),
9961003
should_diff: true,
9971004
should_run: true,
1005+
run_dyn_sym: None,
9981006
should_error: false,
9991007
expect_messages: Default::default(),
10001008
cross_enabled: true,
@@ -1206,6 +1214,9 @@ fn process_directive(
12061214
config.should_diff = arg.parse().context("Invalid bool for DiffEnabled")?
12071215
}
12081216
"RunEnabled" => config.should_run = arg.parse().context("Invalid bool for RunEnabled")?,
1217+
"RunDynSym" => {
1218+
config.run_dyn_sym = Some(arg.parse().context("Invalid string for RunDynSym")?)
1219+
}
12091220
"SkipLinker" => {
12101221
config.skip_linkers.insert(arg.trim().to_owned());
12111222
}
@@ -1588,6 +1599,7 @@ impl Debug for SectionDiff {
15881599
/// the system, which can mean that the test binaries take longer to start, so we need to be
15891600
/// somewhat generous here to avoid flakes.
15901601
const TEST_BINARY_TIMEOUT: Duration = std::time::Duration::from_millis(2000);
1602+
const EXIT_SUCCESS: i32 = 42;
15911603

15921604
impl Program<'_> {
15931605
fn run(&self, cross_arch: Option<Architecture>) -> Result {
@@ -1648,12 +1660,36 @@ impl Program<'_> {
16481660
error!("Binary exited{possible_core_dumped_msg} with signal {signal}: {output}")
16491661
})?;
16501662

1651-
if exit_code != 42 {
1663+
if exit_code != EXIT_SUCCESS {
16521664
bail!("Binary exited with unexpected exit code {exit_code}: {output}");
16531665
}
16541666

16551667
Ok(())
16561668
}
1669+
1670+
fn run_as_dynlib(&self, entry_sym: &str) -> Result {
1671+
// SAFETY: It is not safe. However, we assume that our test cases do not break anything.
1672+
// In particular: All initialization, termination and entry routines of the shared library
1673+
// need to be safe and entry_sym has to be of type `extern "C" fn() -> i32`.
1674+
let exit_code = unsafe {
1675+
let lib = Library::new(&self.link_output.binary).with_context(|| {
1676+
format!(
1677+
"Cannot load shared library {}",
1678+
self.link_output.binary.to_string_lossy()
1679+
)
1680+
})?;
1681+
let entry = lib
1682+
.get::<unsafe extern "C" fn() -> i32>(entry_sym)
1683+
.with_context(|| format!("Cannot find entry point symbol {entry_sym}"))?;
1684+
entry()
1685+
};
1686+
1687+
if exit_code != EXIT_SUCCESS {
1688+
bail!("Function {entry_sym} exited with unexpected status code {exit_code}.");
1689+
}
1690+
1691+
Ok(())
1692+
}
16571693
}
16581694

16591695
/// Attempts to spawn `command`. If that fails due to ETXTBSY, then retries until we've tried
@@ -3577,9 +3613,30 @@ fn run_with_config(
35773613
}
35783614

35793615
if config.should_run {
3580-
program
3581-
.run(cross_arch)
3582-
.with_context(|| format!("Failed to run program. {program}"))?;
3616+
// If RunDynSym is set, execute our binary by loading it dynamically and calling the
3617+
// configured function.
3618+
if let Some(func) = config.run_dyn_sym.as_ref() {
3619+
// As we are loading the library directly into our process, our binary cannot be
3620+
// cross compiled. Also, if we are on a musl libc system, we cannot
3621+
// use dlopen() as the integration test is a statically linked binary.
3622+
// In those cases test execution is skipped.
3623+
3624+
// TODO: To support those other cases: a small "wrapper executable" can be cross
3625+
// compiled and dynamically linked to load the shared library instead.
3626+
if cross_arch.is_none() && !is_musl_used() {
3627+
program
3628+
.run_as_dynlib(func)
3629+
.with_context(|| format!("Failed to load shared library. {program}"))?;
3630+
}
3631+
} else {
3632+
program
3633+
.run(cross_arch)
3634+
.with_context(|| format!("Failed to run program. {program}"))?;
3635+
}
3636+
} else if config.run_dyn_sym.is_some() {
3637+
// RunEnabled is false but RunDynSym is set.
3638+
// That is definitely not intended, so just bail.
3639+
bail!("RunDynSym is set but RunEnabled:false. {program}");
35833640
}
35843641
}
35853642

wild/tests/sources/shared.c

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,11 @@
1-
// We don't currently run this, we just make sure that we can produce a shared
2-
// object and that it passes the diff test.
3-
//
41
// One notable scenario that this test tests is having a non-weak undefined
52
// symbol (baz) in a shared object and having that symbol be defined by an
63
// archive entry that we don't load.
74

85
//#Config:default
96
//#LinkArgs:-shared -z now
107
//#Mode:dynamic
11-
//#RunEnabled:false
8+
//#RunDynSym:foo
129
//#Shared:shared-s1.c
1310
//#Archive:shared-a1.c,shared-a2.c
1411
//#DiffIgnore:.dynamic.DT_RELA

0 commit comments

Comments
 (0)