diffs-rs 0.1.1

Fast terminal diff viewer and embeddable Rust diff engine
Documentation
use super::*;

fn tokens(ranges: &[(usize, usize, Color)]) -> Vec<Token> {
    ranges
        .iter()
        .map(|&(start, end, color)| Token {
            range: start..end,
            color,
        })
        .collect()
}

#[test]
fn coverage_respects_partitions_gaps_empty_spans_and_invalid_ranges() {
    let red = Color::Red;
    let blue = Color::Blue;
    let a = tokens(&[(0, 2, red), (2, 2, blue), (2, 5, red), (7, 9, red)]);
    let b = tokens(&[(0, 5, red), (7, 9, red)]);
    assert!(ordered(&a) && ordered(&b));
    let mut counts = Tokens::default();
    assert!(counts.observe(&a, a.capacity()));
    assert_eq!(counts.empty_ranges, 1);
    assert_eq!(counts.eligible_nonempty_tokens, 3);
    assert_eq!(counts.hypothetical_merged_tokens, 2);
    assert!(runs(&a).eq(runs(&b)));
    assert_eq!(runs(&a).collect::<Vec<_>>(), vec![(0..5, red), (7..9, red)]);
    for other in [
        tokens(&[(0, 9, red)]),
        tokens(&[(1, 5, red), (7, 9, red)]),
        tokens(&[(0, 5, red), (7, 10, red)]),
        tokens(&[(0, 5, blue), (7, 9, red)]),
        Vec::new(),
    ] {
        assert!(ordered(&other));
        assert!(!runs(&a).eq(runs(&other)));
    }
    assert!(runs(&tokens(&[(9, 9, blue)])).eq(runs(&[])));
    for invalid in [
        tokens(&[(2, 1, red)]),
        tokens(&[(0, 3, red), (2, 5, red)]),
        tokens(&[(7, 9, red), (0, 5, red)]),
    ] {
        let mut counts = Tokens::default();
        assert!(!counts.observe(&invalid, invalid.capacity()));
        assert_eq!(counts.invalid_vectors, 1);
        assert_eq!(counts.eligible_nonempty_tokens, 0);
        assert_eq!(counts.hypothetical_merged_tokens, 0);
    }
    let end = usize::MAX;
    let large = tokens(&[(end - 4, end - 2, red), (end - 2, end, red)]);
    assert!(ordered(&large));
    assert_eq!(runs(&large).collect::<Vec<_>>(), vec![(end - 4..end, red)]);
}

#[test]
fn cache_accounting_is_disjoint_and_inspection_preserves_storage() {
    let mut primary = Vec::with_capacity(11);
    primary.extend(tokens(&[(0, 2, Color::Red), (2, 5, Color::Red)]));
    let mut old = Vec::with_capacity(7);
    old.extend(tokens(&[(0, 5, Color::Red)]));
    let mut changed = Vec::with_capacity(5);
    changed.push(1..3);
    let styles = [
        LineStyle {
            tokens: primary,
            old_tokens: Some(old),
            changed,
            fallback: false,
            fallback_reason: None,
        },
        LineStyle {
            tokens: tokens(&[(0, 1, Color::Blue)]),
            old_tokens: Some(Vec::new()),
            ..LineStyle::default()
        },
        LineStyle::default(),
    ];
    let expected_dynamic: usize = styles
        .iter()
        .map(|style| {
            (style.tokens.capacity() + style.old_tokens.as_ref().map_or(0, Vec::capacity))
                * size_of::<Token>()
                + style.changed.capacity() * size_of::<Range<usize>>()
        })
        .sum();
    let expected_old_capacity =
        styles[0].old_tokens.as_ref().unwrap().capacity() * size_of::<Token>();
    let mut cache = Cache::default();
    for (id, style) in styles.into_iter().enumerate() {
        cache.insert(id, style);
    }
    let values = cache.lines.clone();
    let addresses: Vec<_> = cache
        .lines
        .iter()
        .map(|(&id, style)| {
            (
                id,
                style.tokens.as_ptr(),
                style.tokens.capacity(),
                style.changed.as_ptr(),
                style.changed.capacity(),
                style
                    .old_tokens
                    .as_ref()
                    .map(|old| (old.as_ptr(), old.capacity())),
            )
        })
        .collect();
    let order = cache.order.clone();
    let before = (cache.lines.capacity(), cache.order.capacity(), cache.bytes);
    let census = cache.census();
    crate::highlight::cache_tests::assert_storage(&cache);
    assert_eq!(cache.census(), census);
    assert_eq!(census["fifo_accounting"], "recomputed-from-styles");
    assert_eq!(
        census["fifo_element_size_bytes"],
        size_of::<crate::view::LineId>()
    );
    assert_eq!(
        census["fifo_capacity_bytes"],
        cache.order.capacity() * size_of::<crate::view::LineId>()
    );
    assert_eq!(
        census["fifo_accounted_bytes"],
        expected_dynamic + 3 * size_of::<LineStyle>()
    );
    let observed = &census["style_storage"];
    assert_eq!(observed["dynamic_capacity_bytes"], expected_dynamic);
    let used = 4 * size_of::<Token>() + size_of::<Range<usize>>();
    assert_eq!(observed["dynamic_used_bytes"], used);
    assert_eq!(observed["dynamic_spare_bytes"], expected_dynamic - used);
    assert_eq!(observed["inline_value_bytes"], 3 * size_of::<LineStyle>());
    assert_eq!(
        census["payload_counter_bytes"],
        expected_dynamic + 3 * size_of::<LineStyle>()
    );
    assert_eq!(observed["primary"]["vectors"]["count"], 3);
    assert_eq!(observed["primary"]["vectors"]["length"], 3);
    assert_eq!(observed["primary"]["hypothetical_merged_tokens"], 2);
    assert_eq!(observed["old"]["vectors"]["count"], 2);
    assert_eq!(observed["old"]["vectors"]["length"], 1);
    assert_eq!(observed["equivalent_old_vectors"], 1);
    assert_eq!(
        observed["equivalent_old_capacity_bytes"],
        expected_old_capacity
    );
    assert_eq!(observed["identical_old_vectors"], 0);
    assert_eq!(cache.lines.len(), values.len());
    for (id, expected) in values {
        let actual = cache.lines.get(&id).unwrap();
        assert_eq!(actual.tokens, expected.tokens);
        assert_eq!(actual.old_tokens, expected.old_tokens);
        assert_eq!(actual.changed, expected.changed);
        assert_eq!(actual.fallback, expected.fallback);
    }
    assert_eq!(cache.order, order);
    assert_eq!(
        (cache.lines.capacity(), cache.order.capacity(), cache.bytes),
        before
    );
    for (id, pointer, capacity, changed_pointer, changed_capacity, old) in addresses {
        let style = cache.lines.get(&id).unwrap();
        assert_eq!(
            (style.tokens.as_ptr(), style.tokens.capacity()),
            (pointer, capacity)
        );
        assert_eq!(
            (style.changed.as_ptr(), style.changed.capacity()),
            (changed_pointer, changed_capacity)
        );
        assert_eq!(
            style
                .old_tokens
                .as_ref()
                .map(|value| (value.as_ptr(), value.capacity())),
            old
        );
    }
}