|
67 | 67 | //! |
68 | 68 | //! RunEnabled:{bool} Defaults to true. Set to false to disable execution of the resulting binary. |
69 | 69 | //! |
| 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 | +//! |
70 | 75 | //! SkipLinker:{linker-name} Don't link with the specified linker. Mostly useful if testing a flag |
71 | 76 | //! that isn't supported by GNU ld. |
72 | 77 | //! |
|
171 | 176 | mod external_tests; |
172 | 177 |
|
173 | 178 | use itertools::Itertools; |
| 179 | +use libloading::Library; |
174 | 180 | use libwild::bail; |
175 | 181 | use libwild::ensure; |
176 | 182 | use libwild::error; |
@@ -520,6 +526,7 @@ struct Config { |
520 | 526 | compiler: String, |
521 | 527 | should_diff: bool, |
522 | 528 | should_run: bool, |
| 529 | + run_dyn_sym: Option<String>, |
523 | 530 | should_error: bool, |
524 | 531 | expect_messages: Vec<ErrorMatcher>, |
525 | 532 | support_architectures: Vec<Architecture>, |
@@ -995,6 +1002,7 @@ impl Config { |
995 | 1002 | compiler: "gcc".to_owned(), |
996 | 1003 | should_diff: true, |
997 | 1004 | should_run: true, |
| 1005 | + run_dyn_sym: None, |
998 | 1006 | should_error: false, |
999 | 1007 | expect_messages: Default::default(), |
1000 | 1008 | cross_enabled: true, |
@@ -1206,6 +1214,9 @@ fn process_directive( |
1206 | 1214 | config.should_diff = arg.parse().context("Invalid bool for DiffEnabled")? |
1207 | 1215 | } |
1208 | 1216 | "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 | + } |
1209 | 1220 | "SkipLinker" => { |
1210 | 1221 | config.skip_linkers.insert(arg.trim().to_owned()); |
1211 | 1222 | } |
@@ -1588,6 +1599,7 @@ impl Debug for SectionDiff { |
1588 | 1599 | /// the system, which can mean that the test binaries take longer to start, so we need to be |
1589 | 1600 | /// somewhat generous here to avoid flakes. |
1590 | 1601 | const TEST_BINARY_TIMEOUT: Duration = std::time::Duration::from_millis(2000); |
| 1602 | +const EXIT_SUCCESS: i32 = 42; |
1591 | 1603 |
|
1592 | 1604 | impl Program<'_> { |
1593 | 1605 | fn run(&self, cross_arch: Option<Architecture>) -> Result { |
@@ -1648,12 +1660,36 @@ impl Program<'_> { |
1648 | 1660 | error!("Binary exited{possible_core_dumped_msg} with signal {signal}: {output}") |
1649 | 1661 | })?; |
1650 | 1662 |
|
1651 | | - if exit_code != 42 { |
| 1663 | + if exit_code != EXIT_SUCCESS { |
1652 | 1664 | bail!("Binary exited with unexpected exit code {exit_code}: {output}"); |
1653 | 1665 | } |
1654 | 1666 |
|
1655 | 1667 | Ok(()) |
1656 | 1668 | } |
| 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 | + } |
1657 | 1693 | } |
1658 | 1694 |
|
1659 | 1695 | /// Attempts to spawn `command`. If that fails due to ETXTBSY, then retries until we've tried |
@@ -3577,9 +3613,30 @@ fn run_with_config( |
3577 | 3613 | } |
3578 | 3614 |
|
3579 | 3615 | 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}"); |
3583 | 3640 | } |
3584 | 3641 | } |
3585 | 3642 |
|
|
0 commit comments