#testing #macro #utility

subprocess-test

Convenience macro for creating tests which run in child process

2 releases

Uses new Rust 2024

new 0.1.1 Apr 21, 2025
0.1.0 Apr 20, 2025

#2070 in Rust patterns

Download history 66/week @ 2025-04-14

76 downloads per month

MIT license

14KB
212 lines

Subprocess test

Introduces macro and some infrastructure that allows running your test code in separate subprocess and verify it, if needed, inside test invocation, like normal. See crate documentation for details.

Some small examples:

subprocess_test::subprocess_test! {
    #[test]
    fn just_success() {
        let value = 1;
        assert_eq!(value + 1, 2);
    }
}
subprocess_test::subprocess_test! {
    #[test]
    fn one_plus_one() {
        println!("{}", 1 + 1);
    }
    verify |success, output| {
        assert!(success);
        assert_eq!(output, "2\n");
    }
}

lib.rs:

This crate exposes single utility macro subprocess_test

Macro generates test function code in such a way that first test code block is executed in separate subprocess by re-invoking current test executable, its output is captured, filtered a bit and then fed to verification function. Test decides whether it's in normal or subprocess mode through marker environment variable

Used when one needs to either run some test in isolation or validate test output regardless of its proper completion, i.e. even if it aborts

Small examples

subprocess_test::subprocess_test! {
    #[test]
    fn just_success() {
        let value = 1;
        assert_eq!(value + 1, 2);
    }
}
subprocess_test::subprocess_test! {
    #[test]
    fn one_plus_one() {
        println!("{}", 1 + 1);
    }
    verify |success, output| {
        assert!(success);
        assert_eq!(output, "2\n");
    }
}

Usage

subprocess_test::subprocess_test! {
    // Mandatory test marker attribute; psrens are needed
    // only if some attribute parameters are specified.
    //
    // Please also note that this attribute must be first,
    // and its optional parameters must maintain order.
    // This is due to limitations of Rust's macro-by-example.
    #[test(     
        // Optionally specify name of environment variable used to mark subprocess mode.
        // Default name is "__TEST_RUN_SUBPROCESS__", so in very unprobable case case
        // you're getting name collision here, you can change it.
        env_var_name = "RUN_SUBPROCESS_ENV_VAR",
        // While subprocess is executed using `cargo test -q -- --nocapture`,
        // there's still some output from test harness.
        // To filter it out, test prints two boundary lines, in the beginning
        // and in the end of test's output, regardless if it succeeds or panics.
        // The default boundary line is "========================================",
        // so in rare case you expect conflict with actual test output, you can use
        // this parameter to set custom output boundary.
        output_boundary = "<><><><><><><><>",
    )]
    // Any other attributes are allowed, yet are optional
    #[ignore]
    // Test can have any valid name, same as normal test function
    fn dummy() {
        // This block is intended to generate test output,
        // although it can be used as normal test body
        println!("Foo");
        eprintln!("Bar");
    }
    // `verify` block is optional;
    // if absent, it's substituted with block which just asserts that subprocess succeeded
    // and prints test output in case of failure
    //
    // Parameters can be any names. Their meanings:
    // * `success` - boolean which is `true` if subprocess succeeded
    // * `output` - subprocess output collected into string, both `stdout` and `stderr`
    verify |success, output| {
        // This block is run as normal part of test and in general must succeed
        assert!(success);
        assert_eq!(output, "Foo\nBar\n");
    }
}

Limitations

Macro doesn't work well with #[should_panic] attribute because there's only one test function which runs in two modes. If subprocess test panics as expected, subprocess succeeds, and verify block must panic too. Just use verify block and do any checks you need there.

Dependencies

~2–11MB
~130K SLoC