How to work with sys::process::exit in cfg(test)?

I have some code that exits the process depending on condition.

fn main() -> Result<(), Box<dyn Error>> {
    do_something();
    if gone_wrong {
        sys::process::exit(1);
    }

    if already_fine {
        sys::process::exit(0);
    }

    // otherwise
    do_something_else_with_side_effects();
    Ok(())
}

In debug/release, this works perfectly. But in cfg(test) we must hide the ::exit call, because it breaks the entire test run -- you'll see this:

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

(no, there are 34 tests in the crate, and the 33 other succeeded.)

So I put it in a block:

#[cfg(not(test))] {
    sys::process::exit(0);
}

But now do_something_else_with_side_effects gets executed, making side effects, and things work incorrectly.

How do you guys handle this?

To complicate the matters, I need to handle gone_wrong and already_fine conditions in a macro/function, so I'd need to do extra work and wrap the otherwise section in some kind of conditional, although I'd like not to do this.

In theory, I could do this

...
#[cfg(test)] {
    return Ok(())
}
// otherwise

...But it will can be done only in a macro, and imposes a strict limit on the caller's return type.

Any recepies?

If you're just using exit as a convenient way to end the process, then you can instead change your functions to return Result, ExitCode, or just a plain i32 and unwind through main when you need to exit. Result and ExitCode are nice because you can return them directly from main, and Result can be used with ?.

If you're doing it because you want to avoid unwinding or running destructors, or if you're using an API that doesn't have a built-in way of exiting, then you're pretty stuck. You could move these to integration tests instead.

3 Likes

It's kind of like what I thought of: if error, in debug/release it's exit, in tests it returns Err(...) and I bubble it up with ? operator (which means main must return Result).

Also, gone_wrong and already_fine blocks never return in debug/release -- they either exit with error, or with success. So I can expand this block:

if already_fine {
    if cfg!(test) {
        return Ok(())
    } else {
        sys::process::exit(0);
    }
}

It's doable with a macro, but also requires that main() return type Result<(), _>.

I don't recommend making your code conditional on test. Just do return Ok(()).

3 Likes

Good point.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.