#![allow(clippy::unwrap_used, clippy::expect_used)]
use std::alloc::{GlobalAlloc, Layout, System};
use std::sync::Mutex;
use std::sync::atomic::{AtomicUsize, Ordering};
use test_lang::{Change, Diff, Snapshot};
struct Counting;
static ALLOCATIONS: AtomicUsize = AtomicUsize::new(0);
static LIVE: AtomicUsize = AtomicUsize::new(0);
static PEAK: AtomicUsize = AtomicUsize::new(0);
static SERIAL: Mutex<()> = Mutex::new(());
unsafe impl GlobalAlloc for Counting {
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
ALLOCATIONS.fetch_add(1, Ordering::Relaxed);
let live = LIVE.fetch_add(layout.size(), Ordering::Relaxed) + layout.size();
PEAK.fetch_max(live, Ordering::Relaxed);
unsafe { System.alloc(layout) }
}
unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
LIVE.fetch_sub(layout.size(), Ordering::Relaxed);
unsafe { System.dealloc(ptr, layout) }
}
}
#[global_allocator]
static GLOBAL: Counting = Counting;
fn measure<T>(f: impl FnOnce() -> T) -> (T, usize, usize) {
let start_count = ALLOCATIONS.load(Ordering::Relaxed);
let start_live = LIVE.load(Ordering::Relaxed);
PEAK.store(start_live, Ordering::Relaxed);
let out = f();
let count = ALLOCATIONS.load(Ordering::Relaxed) - start_count;
let peak = PEAK.load(Ordering::Relaxed).saturating_sub(start_live);
(out, count, peak)
}
fn numbered(lines: usize, offset: usize) -> String {
use std::fmt::Write;
let mut text = String::new();
for i in 0..lines {
writeln!(text, "tok {}", i + offset).unwrap();
}
text
}
#[test]
fn test_matching_check_does_not_copy_every_line() {
let _serial = SERIAL
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
let text = numbered(10_000, 0);
let snap = Snapshot::new(&text);
let (result, allocations, _) = measure(|| snap.check(&text));
assert!(result.is_ok());
assert!(allocations < 100, "{allocations} allocations");
}
#[test]
fn test_large_diff_has_bounded_memory() {
let _serial = SERIAL
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
let expected = numbered(8_000, 0);
let actual = numbered(8_000, 1);
let (diff, _, peak) = measure(|| Diff::lines(&expected, &actual));
assert!(peak < 64 << 20, "peak {} MiB", peak >> 20);
let old: Vec<&str> = diff
.changes()
.filter(|(c, _)| *c != Change::Insert)
.map(|(_, l)| l)
.collect();
let new: Vec<&str> = diff
.changes()
.filter(|(c, _)| *c != Change::Delete)
.map(|(_, l)| l)
.collect();
assert_eq!(old.join("\n"), expected);
assert_eq!(new.join("\n"), actual);
assert!(!diff.is_empty());
}
#[test]
fn test_small_diff_is_still_minimal() {
let _serial = SERIAL
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
let expected = numbered(500, 0);
let actual = numbered(500, 1);
let diff = Diff::lines(&expected, &actual);
let edits = diff.changes().filter(|(c, _)| *c != Change::Equal).count();
assert_eq!(edits, 2);
}