warble-eval-compare 0.19.0

Warble eval comparator: compare a result-set table against a golden expectation (scalar/set/ordered match, with tolerance).
Documentation
use warble_eval_compare::{compare, CompareRequest};

fn run(json: &str) -> (bool, String) {
    let req: CompareRequest = serde_json::from_str(json).expect("valid test input");
    let result = compare(&req);
    (result.pass, result.reason)
}

#[test]
fn scalar_exact_match() {
    let (pass, _) = run(r#"{
            "match": "scalar",
            "tolerance": {"numeric": 0.0},
            "expected": {"columns": ["n"], "rows": [[42]]},
            "actual": {"columns": ["count"], "rows": [[42]]}
        }"#);
    assert!(pass);
}

#[test]
fn scalar_numeric_tolerance_pass() {
    let (pass, _) = run(r#"{
            "match": "scalar",
            "tolerance": {"numeric": 0.01},
            "expected": {"columns": ["n"], "rows": [[1.00]]},
            "actual": {"columns": ["n"], "rows": [[1.005]]}
        }"#);
    assert!(pass);
}

#[test]
fn scalar_numeric_tolerance_fail_outside() {
    let (pass, reason) = run(r#"{
            "match": "scalar",
            "tolerance": {"numeric": 0.01},
            "expected": {"columns": ["n"], "rows": [[1.00]]},
            "actual": {"columns": ["n"], "rows": [[1.05]]}
        }"#);
    assert!(!pass);
    assert!(reason.contains("scalar mismatch"));
}

#[test]
fn scalar_wrong_value_fails() {
    let (pass, reason) = run(r#"{
            "match": "scalar",
            "tolerance": {"numeric": 0.0},
            "expected": {"columns": ["n"], "rows": [["hello"]]},
            "actual": {"columns": ["n"], "rows": [["world"]]}
        }"#);
    assert!(!pass);
    assert!(reason.contains("scalar mismatch"));
}

#[test]
fn scalar_actual_reduces_from_extra_null_columns() {
    let (pass, _) = run(r#"{
            "match": "scalar",
            "tolerance": {"numeric": 0.0},
            "expected": {"columns": ["n"], "rows": [[7]]},
            "actual": {"columns": ["a", "b"], "rows": [[null, 7]]}
        }"#);
    assert!(pass);
}

#[test]
fn scalar_actual_too_many_non_null_cells_fails() {
    let (pass, reason) = run(r#"{
            "match": "scalar",
            "tolerance": {"numeric": 0.0},
            "expected": {"columns": ["n"], "rows": [[7]]},
            "actual": {"columns": ["a", "b"], "rows": [[7, 8]]}
        }"#);
    assert!(!pass);
    assert!(reason.contains("reduce to one non-null cell"));
}

#[test]
fn set_match_passes_under_row_permutation() {
    let (pass, _) = run(r#"{
            "match": "set",
            "tolerance": {"numeric": 0.0},
            "expected": {"columns": ["a", "b"], "rows": [[1, "x"], [2, "y"]]},
            "actual": {"columns": ["a", "b"], "rows": [[2, "y"], [1, "x"]]}
        }"#);
    assert!(pass);
}

#[test]
fn set_match_ignores_column_order_within_row() {
    let (pass, _) = run(r#"{
            "match": "set",
            "tolerance": {"numeric": 0.0},
            "expected": {"columns": ["a", "b"], "rows": [[1, "x"]]},
            "actual": {"columns": ["b", "a"], "rows": [["x", 1]]}
        }"#);
    assert!(pass);
}

#[test]
fn ordered_match_fails_under_same_permutation_that_passes_set() {
    let (pass, reason) = run(r#"{
            "match": "ordered",
            "tolerance": {"numeric": 0.0},
            "expected": {"columns": ["a", "b"], "rows": [[1, "x"], [2, "y"]]},
            "actual": {"columns": ["a", "b"], "rows": [[2, "y"], [1, "x"]]}
        }"#);
    assert!(!pass);
    assert!(reason.contains("row index 0"));
}

#[test]
fn ordered_match_passes_when_positions_align() {
    let (pass, _) = run(r#"{
            "match": "ordered",
            "tolerance": {"numeric": 0.01},
            "expected": {"columns": ["a"], "rows": [[1.0], [2.0]]},
            "actual": {"columns": ["a"], "rows": [[1.004], [2.0]]}
        }"#);
    assert!(pass);
}

#[test]
fn empty_vs_nonempty_fails() {
    let (pass, reason) = run(r#"{
            "match": "set",
            "tolerance": {"numeric": 0.0},
            "expected": {"columns": ["a"], "rows": []},
            "actual": {"columns": ["a"], "rows": [[1]]}
        }"#);
    assert!(!pass);
    assert!(reason.contains("set mismatch"));
}

#[test]
fn empty_vs_empty_passes() {
    let (pass, _) = run(r#"{
            "match": "set",
            "tolerance": {"numeric": 0.0},
            "expected": {"columns": ["a"], "rows": []},
            "actual": {"columns": ["a"], "rows": []}
        }"#);
    assert!(pass);
}

#[test]
fn row_count_mismatch_fails_for_ordered() {
    let (pass, reason) = run(r#"{
            "match": "ordered",
            "tolerance": {"numeric": 0.0},
            "expected": {"columns": ["a"], "rows": [[1], [2]]},
            "actual": {"columns": ["a"], "rows": [[1]]}
        }"#);
    assert!(!pass);
    assert!(reason.contains("row count differs"));
}

#[test]
fn duplicates_are_significant_for_set_match() {
    let (pass, reason) = run(r#"{
            "match": "set",
            "tolerance": {"numeric": 0.0},
            "expected": {"columns": ["a"], "rows": [[1], [1], [2]]},
            "actual": {"columns": ["a"], "rows": [[1], [2], [2]]}
        }"#);
    assert!(!pass);
    assert!(reason.contains("set mismatch"));
}

#[test]
fn duplicates_matching_counts_pass_for_set_match() {
    let (pass, _) = run(r#"{
            "match": "set",
            "tolerance": {"numeric": 0.0},
            "expected": {"columns": ["a"], "rows": [[1], [1], [2]]},
            "actual": {"columns": ["a"], "rows": [[2], [1], [1]]}
        }"#);
    assert!(pass);
}

#[test]
fn null_cells_only_equal_null() {
    let (pass, _) = run(r#"{
            "match": "ordered",
            "tolerance": {"numeric": 0.0},
            "expected": {"columns": ["a"], "rows": [[null]]},
            "actual": {"columns": ["a"], "rows": [[null]]}
        }"#);
    assert!(pass);

    let (pass2, reason2) = run(r#"{
            "match": "ordered",
            "tolerance": {"numeric": 0.0},
            "expected": {"columns": ["a"], "rows": [[null]]},
            "actual": {"columns": ["a"], "rows": [[0]]}
        }"#);
    assert!(!pass2);
    assert!(reason2.contains("row index 0"));
}