Radish alpha
h
Radicle Heartwood Protocol & Stack
Radicle
Git (anonymous pull)
Log in to clone via SSH
REVIEW: Add broken pipe handler test and panic simulator
✗ CI failure Adrian Duke committed 23 days ago
commit 2f9a2bc8e8862d35a82036c40b06070edc3539a1
parent 4e39faa51387363d128018303c7f3564d0727745
1 failed (1 total) View logs
2 files changed +57 -0
modified crates/radicle-cli/src/main.rs
@@ -137,6 +137,9 @@ fn main() {
        }));
    }

+
    #[cfg(all(unix, debug_assertions))]
+
    broken_pipe_panic_simulator();
+

    if let Some(lvl) = radicle::logger::env_level() {
        let logger = Box::new(radicle::logger::Logger::new(lvl));
        log::set_boxed_logger(logger).expect("no other logger should have been set already");
@@ -149,6 +152,31 @@ fn main() {
    run(command, term::DefaultContext)
}

+
/// Simulate a panic caused by a broken pipe for testing purposes.
+
///
+
/// Testing the broken pipe panic handler directly in a unit test is difficult
+
/// because `handle_broken_pipe` calls `std::process::exit(0)`, which would
+
/// immediately terminate the test runner. Furthermore, we cannot easily
+
/// construct a `std::panic::PanicHookInfo` manually.
+
///
+
/// Instead, we spawn the `rad` binary with a hidden environment variable
+
/// (`RAD_DEBUG_PANIC_BROKEN_PIPE`). This function intercepts that variable
+
/// and triggers a panic with the payloads `String` and `&str` that the
+
/// standard library produces when `println!` or `print!` fails due to
+
/// a closed standard output.
+
///
+
/// See: <https://github.com/rust-lang/rust/issues/62569>
+
#[cfg(all(unix, debug_assertions))]
+
fn broken_pipe_panic_simulator() {
+
    if let Ok(val) = std::env::var("RAD_DEBUG_PANIC_BROKEN_PIPE") {
+
        match val.as_str() {
+
            "string" => std::panic::panic_any("failed printing to stdout: Broken pipe".to_string()),
+
            "str" => std::panic::panic_any("failed printing to stdout: Broken pipe"),
+
            _ => {}
+
        }
+
    }
+
}
+

fn write_version(as_json: bool) -> anyhow::Result<()> {
    let mut stdout = io::stdout();
    if as_json {
modified crates/radicle-cli/tests/commands/sigpipe.rs
@@ -154,3 +154,32 @@ fn rad_self() {
        "rad panicked on broken pipe.\nstderr:\n{stderr}"
    );
}
+

+
#[test]
+
fn test_panic_broken_pipe_handled_cleanly() {
+
    let rad = env!("CARGO_BIN_EXE_rad");
+

+
    // Test with a `String` payload
+
    let output_string = Command::new(rad)
+
        .env("RAD_DEBUG_PANIC_BROKEN_PIPE", "string")
+
        .output()
+
        .expect("failed to run rad");
+

+
    assert_eq!(
+
        output_string.status.code(),
+
        Some(0),
+
        "rad should exit with 0 when panicking with a Broken pipe String payload"
+
    );
+

+
    // Test with a `&str` payload
+
    let output_str = Command::new(rad)
+
        .env("RAD_DEBUG_PANIC_BROKEN_PIPE", "str")
+
        .output()
+
        .expect("failed to run rad");
+

+
    assert_eq!(
+
        output_str.status.code(),
+
        Some(0),
+
        "rad should exit with 0 when panicking with a Broken pipe &str payload"
+
    );
+
}