#integration-tests #process #tokio #applications #tracing #assertions #events

tokio-bin-process

run your application under a separate process with tokio tracing assertions when integration testing

8 releases (5 breaking)

0.6.0 Oct 9, 2024
0.5.1 Jul 23, 2024
0.5.0 Feb 27, 2024
0.4.1 Sep 25, 2023
0.1.0 Apr 12, 2023

#316 in Testing

Download history 312/week @ 2024-07-22 244/week @ 2024-07-29 70/week @ 2024-08-05 15/week @ 2024-08-12 202/week @ 2024-08-19 108/week @ 2024-08-26 103/week @ 2024-09-02 98/week @ 2024-09-09 91/week @ 2024-09-16 87/week @ 2024-09-23 103/week @ 2024-09-30 306/week @ 2024-10-07 230/week @ 2024-10-14 113/week @ 2024-10-21 148/week @ 2024-10-28 100/week @ 2024-11-04

604 downloads per month

Apache-2.0

41KB
624 lines

Allows your integration tests to run your application under a separate process and assert on tracing events.

To achieve this, it locates or builds the application's executable, runs it with tracing in JSON mode, and then processes the JSON logs to both assert on and display in human readable form.

It is a little opinionated and by default will fail the test when a tracing warning or error occurs. However a specific warning or error can be allowed on a per test basis.

Example usage for an imaginary database project named cooldb:

use tokio_bin_process::event::Level;
use tokio_bin_process::{BinProcess, bin_path};
use tokio_bin_process::event_matcher::EventMatcher;
use std::time::Duration;
use std::path::PathBuf;

/// you'll want a helper like this as you'll be creating this in every integration test.
async fn cooldb_process() -> BinProcess {
    // start the process
    let mut process = BinProcess::start_binary(
        // Locate the path to the cooldb binary from an integration test or benchmark
        bin_path!("cooldb"),
        "cooldb", // The name that BinProcess should prepend its forwarded logs with
        &[
            // provide any custom CLI args required
            "--foo", "bar",
            // tokio-bin-process relies on reading tracing json's output,
            // so configure the application to produce that
            "--log-format", "json"
        ],
    )
    .await;

    // block asynchrounously until the application gives an event indicating that its ready
    tokio::time::timeout(
        Duration::from_secs(30),
        process.wait_for(
            &EventMatcher::new()
                .with_level(Level::Info)
                .with_target("cooldb")
                .with_message("accepting inbound connections"),
            &[]
        ),
    )
    .await
    .unwrap();
    process
}

#[tokio::test]
async fn test_some_functionality() {
    // start the db
    let cooldb = cooldb_process().await;

    // connect to the db, do something and assert we get the expected result
    perform_test();

    // Shutdown the DB, asserting that no warnings or errors occured,
    // but allow and expect a certain warning.
    // A drop bomb ensures that the test will fail if we forget to call this method.
    cooldb
        .shutdown_and_then_consume_events(&[
            EventMatcher::new()
                .with_level(Level::Warn)
                .with_target("cooldb::internal")
                .with_message("The user did something silly that we want to warn about but is actually expected in this test case")
        ])
        .await;
}

When Cargo builds integration tests or benchmarks it provides a path to the binary under test. We can make use of that for speed and robustness with BinProcess::start_binary.

But that is not always flexible enough so as a fallback BinProcess can invoke Cargo again internally to ensure the binary we need is compiled via BinProcess::start_binary_name.

Dependencies

~7–16MB
~206K SLoC