Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/edda

crates/gunnar-delta/tests/limits.rs

//! An absurd request is refused, not attempted.
//!
//! A test in this tree took a 499 GB machine out on 2026-08-03 by crossing a
//! degenerate parameter with a large input, and a sliding window over a large
//! object set is exactly that shape. Every one of these assertions is that the
//! crate came back with [`gunnar_delta::Error::Refused`] naming the field —
//! *before* allocating — rather than succeeding slowly, clamping quietly or
//! being killed.
//!
//! Silent clamping is the failure mode worth naming: a caller that asks for a
//! window of 100,000 and gets 1,024 without being told has been given wrong
//! output under a right-looking API. Nothing here clamps.

use std::sync::atomic::AtomicBool;

use gix_object::Kind;
use gunnar_delta::{delta_into, deltify, Encode, Error, Limits, Object, Options};

fn objects(n: usize, size: usize) -> Vec<Vec<u8>> {
    (0..n)
        .map(|i| {
            (0..size)
                .map(|k| ((k * 31 + i * 7) % 251) as u8)
                .collect::<Vec<u8>>()
        })
        .collect()
}

fn borrow(v: &[Vec<u8>]) -> Vec<Object<'_>> {
    v.iter()
        .enumerate()
        .map(|(i, c)| {
            let mut id = [0u8; 20];
            id[..8].copy_from_slice(&(i as u64).to_be_bytes());
            Object {
                id: gix_hash::ObjectId::from(id),
                kind: Kind::Blob,
                name_hash: 0,
                epoch: 0,
                data: c.as_slice(),
            }
        })
        .collect()
}

#[track_caller]
fn expect_refusal(r: Result<impl std::fmt::Debug, Error>, field: &str) {
    match r {
        Err(Error::Refused { what, .. }) => assert_eq!(what, field),
        Err(other) => panic!("expected a refusal naming {field}, got {other}"),
        Ok(v) => panic!("expected a refusal naming {field}, the call SUCCEEDED with {v:?}"),
    }
}

#[test]
fn a_window_past_the_cap_is_refused() {
    let data = objects(4, 128);
    let opts = Options {
        window: 100_000,
        ..Options::default()
    };
    expect_refusal(
        deltify(
            &borrow(&data),
            &opts,
            &mut gix_utils::progress::Discard,
            &AtomicBool::default(),
        ),
        "window",
    );
}

#[test]
fn a_depth_past_the_cap_is_refused() {
    let data = objects(4, 128);
    let opts = Options {
        depth: 10_000_000,
        ..Options::default()
    };
    expect_refusal(
        deltify(
            &borrow(&data),
            &opts,
            &mut gix_utils::progress::Discard,
            &AtomicBool::default(),
        ),
        "depth",
    );
}

#[test]
fn too_many_objects_is_refused() {
    let data = objects(40, 64);
    let opts = Options {
        limits: Limits {
            max_objects: 10,
            ..Limits::default()
        },
        ..Options::default()
    };
    expect_refusal(
        deltify(
            &borrow(&data),
            &opts,
            &mut gix_utils::progress::Discard,
            &AtomicBool::default(),
        ),
        "objects",
    );
}

#[test]
fn too_much_total_input_is_refused() {
    let data = objects(20, 4_096);
    let opts = Options {
        limits: Limits {
            max_total_input: 1_000,
            ..Limits::default()
        },
        ..Options::default()
    };
    expect_refusal(
        deltify(
            &borrow(&data),
            &opts,
            &mut gix_utils::progress::Discard,
            &AtomicBool::default(),
        ),
        "total input bytes",
    );
}

#[test]
fn an_index_above_the_byte_cap_is_refused_rather_than_built() {
    let base = vec![0x11u8; 2 * 1024 * 1024];
    let mut out = Vec::new();
    let opts = Encode {
        limits: Limits {
            max_index_bytes: 4_096,
            ..Limits::default()
        },
        ..Encode::default()
    };
    expect_refusal(delta_into(&base, &base, &mut out, &opts), "base index size");
    assert!(
        out.is_empty(),
        "a refused call still wrote {} bytes",
        out.len()
    );
}

/// A base too large to *index* is skipped as a candidate, not refused: a repack
/// must not fail because one blob is big. The object still comes back, whole.
#[test]
fn an_oversized_base_is_skipped_as_a_candidate_and_the_object_still_appears() {
    let big = vec![7u8; 300_000];
    let data = vec![big.clone(), big[..299_000].to_vec()];
    let opts = Options {
        limits: Limits {
            max_base_size: 1_024,
            ..Limits::default()
        },
        ..Options::default()
    };
    let out = deltify(
        &borrow(&data),
        &opts,
        &mut gix_utils::progress::Discard,
        &AtomicBool::default(),
    )
    .expect("an oversized base is a skip, not an error");
    assert_eq!(out.len(), 2);
    assert!(
        out.iter().all(|d| !d.is_delta()),
        "an object above max_base_size was used as a base anyway"
    );

    // Red-first: with the cap raised, the very same input DOES delta. Without
    // this the assertion above would also pass on an encoder that never deltas.
    let out = deltify(
        &borrow(&data),
        &Options::default(),
        &mut gix_utils::progress::Discard,
        &AtomicBool::default(),
    )
    .expect("deltify");
    assert!(
        out.iter().any(|d| d.is_delta()),
        "the same input does not delta even with the default cap, so the skip test is vacuous"
    );
}

#[test]
fn an_oversized_target_is_emitted_whole_rather_than_searched() {
    let data = vec![vec![3u8; 200_000], vec![3u8; 199_000]];
    let opts = Options {
        limits: Limits {
            max_target_size: 1_024,
            ..Limits::default()
        },
        ..Options::default()
    };
    let out = deltify(
        &borrow(&data),
        &opts,
        &mut gix_utils::progress::Discard,
        &AtomicBool::default(),
    )
    .expect("deltify");
    assert!(out.iter().all(|d| !d.is_delta()));
}

#[test]
fn a_degenerate_all_identical_input_finishes_under_the_chain_cap() {
    // Every 16-byte anchor of this base hashes to the same bucket, which without
    // `Encode::max_chain` is a quadratic scan. The assertion is that the call
    // returns at all, and correctly, in a test that would otherwise hang.
    let base = vec![0xa5u8; 4 * 1024 * 1024];
    let started = std::time::Instant::now();
    let d = gunnar_delta::delta(&base, &base).expect("encode");
    assert!(
        started.elapsed() < std::time::Duration::from_secs(30),
        "a degenerate all-identical base took {:?}, so the candidate chain is not bounded",
        started.elapsed()
    );
    assert!(
        d.len() < 64,
        "delta of an identical 4 MiB object is {} bytes",
        d.len()
    );
}

#[test]
fn an_interrupted_run_returns_interrupted_rather_than_a_partial_result() {
    let data = objects(4_000, 8_192);
    let flag = AtomicBool::new(true);
    match deltify(
        &borrow(&data),
        &Options::default(),
        &mut gix_utils::progress::Discard,
        &flag,
    ) {
        Err(Error::Interrupted) => {}
        Err(other) => panic!("expected Interrupted, got {other}"),
        Ok(v) => panic!("expected Interrupted, got {} entries", v.len()),
    }
}