trellis-testing 0.1.1

Companion testing support for Trellis graph invariants.
Documentation
use trellis_core::{Graph, TransactionPhase};
use trellis_testing::{
    ConformanceCheckResult, ConformanceLevel, ConformanceSuite, Scenario, conformance,
};

fn deterministic_graph_dump() -> String {
    let mut graph = Graph::<(), usize>::new_with_output_type();
    let mut tx = graph.begin_transaction().unwrap();
    let scope = tx.create_scope("screen").unwrap();
    let input = tx.input::<usize>("count").unwrap();
    tx.set_input(input, 1).unwrap();
    tx.attach_node_to_scope(input, scope).unwrap();
    tx.commit().unwrap();
    drop(tx);
    graph.debug_dump()
}

#[test]
fn executable_conformance_runs_app_owned_checks() {
    let report = conformance()
        .check(
            ConformanceLevel::DeterministicTrace,
            "debug dump is deterministic",
            || {
                if deterministic_graph_dump() == deterministic_graph_dump() {
                    ConformanceCheckResult::passed()
                } else {
                    ConformanceCheckResult::failed("debug dump differed between fresh graphs")
                }
            },
        )
        .unsupported(
            ConformanceLevel::GeneratedModelSequences,
            "app has not opted into generated sequence hooks",
        )
        .run()
        .unwrap();

    assert!(report.supports(ConformanceLevel::DeterministicTrace));
    assert!(
        report
            .unsupported_levels()
            .contains(&ConformanceLevel::GeneratedModelSequences)
    );
    assert_eq!(report.check_results().len(), 1);
}

#[test]
fn required_levels_without_hooks_are_reported_unsupported() {
    let report = ConformanceSuite::all()
        .runner()
        .check(
            ConformanceLevel::DeterministicTrace,
            "phase order is stable",
            || {
                let mut graph = Graph::<(), ()>::new();
                let mut tx = graph.begin_transaction().unwrap();
                let result = tx.commit().unwrap();
                drop(tx);
                if result
                    .phase_trace
                    .contains(&TransactionPhase::ReturnTransactionResult)
                {
                    ConformanceCheckResult::passed()
                } else {
                    ConformanceCheckResult::failed("missing return phase")
                }
            },
        )
        .run()
        .unwrap();

    assert!(report.supports(ConformanceLevel::DeterministicTrace));
    assert!(
        report
            .unsupported_levels()
            .contains(&ConformanceLevel::MaterializedOutput)
    );
    assert!(
        report
            .unsupported_reasons()
            .get(&ConformanceLevel::MaterializedOutput)
            .unwrap()
            .iter()
            .any(|reason| reason.contains("no conformance check registered"))
    );
}

#[test]
fn failures_include_invariant_and_trace_context() {
    let mut scenario = Scenario::new();
    let mut graph = Graph::<(), ()>::new();
    let mut tx = graph.begin_transaction().unwrap();
    let result = tx.commit().unwrap();
    drop(tx);
    scenario.record("empty", &result);

    let failure = conformance()
        .check(
            ConformanceLevel::DeterministicTrace,
            "same input sequence produces same trace",
            move || {
                ConformanceCheckResult::failed(format!(
                    "scenario empty trace: {:#?}",
                    scenario.step("empty").unwrap().trace
                ))
            },
        )
        .run()
        .unwrap_err();

    assert_eq!(
        failure.invariant,
        "same input sequence produces same trace".to_owned()
    );
    assert!(failure.detail.contains("scenario empty trace"));
}