Oregami
Repositories/oxedyne/fe2o3

oxedyne/fe2o3/fe2o3_ore/src/diff.rs

62.8 KiB, 278 runs

created by r1870400018:18774, which is this file's identity for as long as the history lasts, whatever it is later renamed to

download · who wrote it · its history

1//! What has to be done to one version of a file's bytes to obtain another.
2//!
3//! The operation vocabulary records intent, and an author working in an editor
4//! supplies it directly. An author working in a filesystem does not: what is
5//! available is the file as it stood and the file as it now stands, and the
6//! edit between them has to be recovered. That recovery is this module, and it
7//! is the one place in the crate where a diff is a guess rather than a record.
8//!
9//! # Division of labour
10//!
11//! The splices returned here are **positional**, and every position is an
12//! offset into the *old* bytes. That is deliberate. Content anchors are minted
13//! from a render, and the module that holds the render --
14//! [`crate::seq::render::Rendered`] -- already knows how to turn an offset into
15//! the names of the bytes around it. So the split is:
16//!
17//! - here: old bytes and new bytes in, an ordered list of positional splices
18//! out, with no knowledge of operation identifiers, anchors or history;
19//! - the caller: for each splice in turn, `rendered.splice(at, delete, insert)`,
20//! which resolves the offsets against the render and yields a content
21//! anchored [`crate::op::Op`].
22//!
23//! Because every offset is in the old bytes, and the render the caller holds is
24//! the old bytes, the whole list is resolved against that one render. The
25//! splices are ascending and non-overlapping, and consecutive splices are
26//! separated by at least [`MIN_SPLICE_GAP`] unchanged bytes, so no splice
27//! anchors itself on content another one removes.
28//!
29//! # How the diff is found
30//!
31//! Two levels, over pieces and then over bytes.
32//!
33//! **Pieces first.** The bytes are cut into pieces, each distinct piece is given
34//! a number, and Myers' O(ND) algorithm runs over the numbers. This is what keeps
35//! a large file affordable, since the sequences it compares are as long as the
36//! file has pieces rather than bytes, and the cost is driven by the size of the
37//! difference rather than the size of the file.
38//!
39//! **Bytes second.** Each run of changed pieces is trimmed of the bytes its two
40//! sides share at either end, and, where what remains is small, Myers runs again
41//! over those bytes. A one character change in the middle of a line therefore
42//! stores one character, not the line and not the file.
43//!
44//! # Where the pieces come from
45//!
46//! There are two ways of cutting, and which is used is [`Route`].
47//!
48//! For text, a piece is a **line**: the bytes are cut at every `\n`. That is the
49//! right unit for anything a person edits a line at a time, and it costs one pass
50//! to find.
51//!
52//! For everything else, a piece is a **content-defined chunk**. A gear rolling
53//! hash reads the bytes, and a boundary is declared wherever the hash of the last
54//! few dozen bytes hits a mask, so a boundary follows the content rather than an
55//! offset: an edit perturbs only the chunks around it, and every chunk beyond the
56//! disturbance re-synchronises on the same bytes and keeps the number it had.
57//! That is what makes a small edit to a large binary cost the region it touched
58//! rather than everything between the first edit and the last. Boundaries are
59//! held between [`MIN_CHUNK`] and [`MAX_CHUNK`] and steered towards [`AVG_CHUNK`]
60//! by normalised chunking, all three tuned well below what a storage layer would
61//! use: a diff wants to localise an edit to a few kilobytes, not to a quarter of
62//! a megabyte.
63//!
64//! The route is chosen by asking whether the line pass has anything to work with.
65//! Content whose average line is longer than [`MAX_TEXT_LINE`] is one long line as
66//! far as that pass is concerned, and where such content is also longer than
67//! [`MIN_CHUNKED_LEN`] the line pass is skipped and the chunk pass runs instead.
68//! Below that length nothing is chunked, because a single splice over so few bytes
69//! costs less than the operations that would replace it.
70//!
71//! # The bound on cost
72//!
73//! Myers costs O(ND) in time and, as written here, O(D²) in memory, where D is
74//! the length of the edit script. Both are fine while D is small and neither is
75//! acceptable when it is not, so D is capped at [`MAX_PIECE_SCRIPT`] insertions
76//! and deletions over pieces, and [`MAX_BYTE_SCRIPT`] within a refined run. A
77//! piece script that would exceed its cap means the two versions have little left
78//! in common. The line pass then hands over to the chunk pass where the content is
79//! long enough to chunk, and beyond that the answer falls back to a single splice
80//! covering everything between the shared prefix and the shared suffix -- which is
81//! the whole of what a diff-free frontend would have recorded anyway, arrived at
82//! in linear time. The cap on the piece script bounds the working memory at
83//! roughly four megabytes.
84//!
85//! # Bytes, not text
86//!
87//! Nothing here decodes. A piece is compared to another by its bytes, so an
88//! encoding this module has never heard of costs it nothing, and no route is
89//! chosen by guessing what the content is: the question asked is only whether the
90//! newlines are frequent enough to cut on, which is a fact about the bytes.
91//!
92//! [Written with AI entirely](https://need2know.ai/entirely-ai/code)\
93//! Anthropic Claude
94
95use oxedyne_fe2o3_core::prelude::*;
96
97use std::collections::HashMap;
98use std::ops::Range;
99
100
101// Greatest piece level edit script the diff will compute on one route before
102// handing on to the next. A piece is a line on one route and a content-defined
103// chunk on the other, and the cap is the same for both: what it bounds is the
104// working memory of the Myers pass, which does not care what it is comparing.
105pub const MAX_PIECE_SCRIPT: usize = 1024;
106
107// Greatest byte level edit script the refinement of one changed run will
108// compute before leaving the run as a single splice.
109pub const MAX_BYTE_SCRIPT: usize = 256;
110
111// Greatest changed run, in bytes on either side, the byte level refinement is
112// attempted on at all.
113pub const MAX_REFINE_LEN: usize = 4096;
114
115// Fewest unchanged bytes that may separate two splices. Two operations each
116// carry their own identity, parents and origins, which costs more than a
117// handful of bytes that did not change; splices closer together than this are
118// merged into one. Keeping the gap non-zero is also what stops one splice
119// anchoring on content that another removes.
120pub const MIN_SPLICE_GAP: usize = 16;
121
122// A floor stops a run of unlucky hash hits producing a swarm of tiny pieces,
123// each of which costs an entry in the sequence Myers runs over.
124pub const MIN_CHUNK: usize = 2 * 1024;
125
126// The granularity at which an edit is localised before the byte level
127// refinement narrows it further, so it is the bound on what a diff re-stores
128// when the refinement declines. A storage layer chunks an order of magnitude
129// larger, because its cost is one address per chunk in every manifest; here the
130// cost is one integer per chunk in one comparison, which buys the finer cut.
131pub const AVG_CHUNK: usize = 8 * 1024;
132
133// A ceiling bounds the damage where the hash finds no boundary at all, which is
134// what a long run of identical bytes does to it.
135pub const MAX_CHUNK: usize = 64 * 1024;
136
137// Shortest input the chunk route is used on. Below this a single splice
138// covering everything between the shared prefix and the shared suffix stores at
139// most sixteen kilobytes, which is of the same order as the operations that
140// would replace it, so there is nothing to win.
141pub const MIN_CHUNKED_LEN: usize = 16 * 1024;
142
143// Longest average line at which the line route is still worth taking. Above it
144// the newlines are too sparse to cut on: the pieces are so long that a changed
145// piece is most of the file, which is the trimmed splice with extra steps.
146pub const MAX_TEXT_LINE: usize = 1024;
147
148// Seed for the gear table's generator. The table has to be the same in every
149// build, because a changed table changes every boundary and so which bytes a
150// diff decides to re-store. Nothing here is content-addressed, so a changed
151// table would cost efficiency rather than correctness -- the applier checks the
152// answer either way -- but a diff that depends on which binary produced it is
153// not a diff anyone can reason about. Fixing the seed here, and deriving the
154// table from it rather than shipping a literal, is what pins it. The value is
155// the golden-ratio constant splitmix64 conventionally uses.
156const GEAR_SEED: u64 = 0x9e37_79b9_7f4a_7c15;
157
158// Normalisation level: how many bits the mask tightens below the average chunk
159// size and loosens above it. Plain gear chunking gives an exponential spread of
160// chunk sizes, so short chunks dominate and the tail is long. Cutting less
161// readily before the average and more readily after it pulls the spread in
162// towards the average without forcing boundaries at fixed offsets. Two is the
163// level the FastCDC paper settles on.
164const NORM_LEVEL: u32 = 2;
165
166// The 256-entry gear table, one pseudorandom `u64` per byte value, generated at
167// compile time from `GEAR_SEED` by splitmix64 so there is no dependency to pull
168// in and no table to keep in the source.
169static GEAR: [u64; 256] = gear_table();
170
171// The masks a boundary must hit: the stricter below the average chunk size, the
172// looser at and above it.
173const MASK_S: u64 = high_mask(log2_floor(AVG_CHUNK) + NORM_LEVEL);
174const MASK_L: u64 = high_mask(log2_floor(AVG_CHUNK) - NORM_LEVEL);
175
176
177/// Which of the routes through the diff was taken.
178///
179/// Returned by [`diff_routed`] so that a caller measuring what a diff cost, or a
180/// test asserting which pass ran, does not have to infer it from the shape of the
181/// answer.
182#[derive(Clone, Copy, Debug, Eq, PartialEq)]
183pub enum Route {
184 Same, // the two versions are the same bytes and there is nothing to do
185 Line, // pieces cut at the newlines, then bytes within each changed run
186 Chunk, // pieces cut where a gear hash says the content changes, then bytes
187 Whole, // between the shared prefix and the shared suffix, where both give up
188}
189
190impl Route {
191 pub fn name(&self) -> &'static str {
192 match self {
193 Self::Same => "Same",
194 Self::Line => "Line",
195 Self::Chunk => "Chunk",
196 Self::Whole => "Whole",
197 }
198 }
199}
200
201
202/// One positional edit against the old bytes: remove `delete` bytes at `at`,
203/// and put `insert` in their place.
204///
205/// The three fields are independent -- any offset, any length and any payload
206/// name an edit, even where the file cannot supply them -- so there is no
207/// invariant here for a struct literal to break. What the offsets have to agree
208/// with is the file, and [`apply`] is what says whether they do.
209#[derive(Clone, Debug, Default, Eq, PartialEq)]
210pub struct Splice {
211 pub at: usize,
212 pub delete: usize,
213 pub insert: Vec<u8>,
214}
215
216impl Splice {
217 pub fn insert_len(&self) -> usize {
218 self.insert.len()
219 }
220}
221
222
223/// The list is ascending by offset and non-overlapping, and is empty when the
224/// two are already the same.
225pub fn diff(old: &[u8], new: &[u8]) -> Vec<Splice> {
226 diff_with_budget(old, new, MAX_PIECE_SCRIPT)
227}
228
229/// As [`diff`], with the piece level edit script capped at `budget` rather than
230/// at [`MAX_PIECE_SCRIPT`].
231///
232/// A budget of zero forces the single trimmed splice: no piece route can produce
233/// a script of no steps for two versions that differ, so both give up and the
234/// fallback is what is left.
235pub fn diff_with_budget(old: &[u8], new: &[u8], budget: usize) -> Vec<Splice> {
236 diff_routed(old, new, budget).0
237}
238
239/// The routes are tried in the order the content deserves. Where the newlines
240/// are frequent enough to cut on, lines are tried first, because a line is the
241/// unit an author edits in and cutting there gives the finest changed runs. Where
242/// they are not, and the content is long enough for it to be worth the pass,
243/// content-defined chunks are tried instead, and are tried anyway when the line
244/// route exhausts its budget. What is left is the single trimmed splice.
245pub fn diff_routed(old: &[u8], new: &[u8], budget: usize)
246 -> (Vec<Splice>, Route)
247{
248 if old == new {
249 return (Vec::new(), Route::Same);
250 }
251 let old_lines = line_starts(old);
252 let new_lines = line_starts(new);
253 // Long enough that chunking has room to localise an edit within it.
254 let chunkable = old.len().max(new.len()) >= MIN_CHUNKED_LEN;
255 // The line route is skipped only where it has nothing to work with *and*
256 // there is a chunk route to skip it in favour of.
257 let texty = texty(old, &old_lines) && texty(new, &new_lines);
258 if texty || !chunkable {
259 if let Some(sp) = diff_over(old, new, &old_lines, &new_lines, budget) {
260 return (sp, Route::Line);
261 }
262 }
263 if chunkable {
264 let old_chunks = chunk_starts(old);
265 let new_chunks = chunk_starts(new);
266 if let Some(sp) = diff_over(old, new, &old_chunks, &new_chunks, budget) {
267 return (sp, Route::Chunk);
268 }
269 }
270 (vec![trimmed_splice(old, new)], Route::Whole)
271}
272
273/// Fails where the list is not ascending, where two splices overlap, or where
274/// one reaches past the end of the file: all three mean the list was not
275/// produced against these bytes, and a caller had better hear so rather than
276/// receive a plausible wrong answer.
277pub fn apply(old: &[u8], splices: &[Splice])
278 -> Outcome<Vec<u8>>
279{
280 let mut out: Vec<u8> = Vec::with_capacity(old.len());
281 let mut at = 0usize;
282 for (i, sp) in splices.iter().enumerate() {
283 if sp.at < at {
284 return Err(err!(
285 "Splice {} begins at {}, behind the {} bytes of the old file already \
286 consumed; splices must ascend and may not overlap.", i, sp.at, at;
287 Invalid, Input, Order));
288 }
289 let end = match sp.at.checked_add(sp.delete) {
290 Some(e) if e <= old.len() => e,
291 _ => return Err(err!(
292 "Splice {} removes {} bytes at {}, reaching past the {} bytes the old \
293 file holds.", i, sp.delete, sp.at, old.len();
294 Invalid, Input, Range)),
295 };
296 out.extend_from_slice(&old[at..sp.at]);
297 out.extend_from_slice(&sp.insert);
298 at = end;
299 }
300 out.extend_from_slice(&old[at..]);
301 Ok(out)
302}
303
304
305// ┌───────────────────────────────────────────────────────────────┐
306// │ Stored patches │
307// └───────────────────────────────────────────────────────────────┘
308//
309// A splice list is only worth storing if the bytes it was made against can be
310// named again months later. `apply` refuses a list that is structurally
311// impossible against the bytes in hand -- one that overlaps itself, or reaches
312// past the end -- and that is as far as offsets alone can go: a list made
313// against a DIFFERENT old file of a compatible shape applies perfectly well and
314// returns something nobody wrote. So a stored patch carries the length and the
315// checksum of both sides, and the reader is told which side is wrong rather
316// than handed a plausible answer.
317
318// Leading bytes of a stored patch, so a reader given some other file says so
319// instead of decoding whatever it was given.
320pub const PATCH_MAGIC: [u8; 4] = *b"ORDF";
321
322// The only layout understood here. A later one takes the next number, and a
323// reader that does not know a number refuses rather than guesses at it.
324pub const PATCH_FORMAT: u32 = 1;
325
326// Magic, format, the two lengths, the two checksums and the splice count.
327pub const PATCH_HEADER: usize = 28;
328
329// Offset, deletion count and insertion length, ahead of the inserted bytes.
330const SPLICE_HEADER: usize = 12;
331
332
333/// The splices from `old` to `new`, encoded as bytes that carry what they were
334/// made against.
335///
336/// **What comes back has already been decoded and applied again, and the result
337/// compared with `new`.** Nothing else here can promise that. `diff` is a guess
338/// by construction, and `apply` sees only whether a list fits the bytes it is
339/// given, so a caller who stores a patch and reads it back a year later has
340/// nobody to appeal to. One `apply` at record time is what turns a silent
341/// reconstruction failure into a refusal at the one moment the correct bytes
342/// are still in hand -- and it runs over the ENCODED form, through the same
343/// door a reader comes in by, so an encoder fault is caught here too.
344pub fn make_patch(old: &[u8], new: &[u8])
345 -> Outcome<Vec<u8>>
346{
347 if old.len() > u32::MAX as usize || new.len() > u32::MAX as usize {
348 return Err(err!(
349 "A patch addresses its bytes with 32 bit offsets, so neither side may be \
350 longer than {} bytes; the old side is {} and the new side {}.",
351 u32::MAX, old.len(), new.len(); Invalid, Input, Size));
352 }
353 let splices = diff(old, new);
354 let patch = encode_patch(old, new, &splices);
355 let back = res!(apply_patch(old, &patch), Invalid, Data);
356 if back != new {
357 return Err(err!(
358 "A patch from {} bytes to {} reconstructed {} bytes that are not them, so it \
359 is refused rather than stored.", old.len(), new.len(), back.len();
360 Invalid, Data, Mismatch));
361 }
362 Ok(patch)
363}
364
365/// The bytes a patch was made towards, given the bytes it was made from.
366///
367/// Four things are checked before a single splice is applied: that this is a
368/// patch at all, that its layout is one this build knows, and that `old` is the
369/// length and the checksum the patch was made against. The last is what stops a
370/// patch recorded against a different parent from returning a wrong file
371/// quietly; the checksum of the result is then confirmed as well, which catches
372/// a patch whose own bytes have decayed on disk.
373pub fn apply_patch(old: &[u8], patch: &[u8])
374 -> Outcome<Vec<u8>>
375{
376 if patch.len() < PATCH_HEADER {
377 return Err(err!(
378 "A patch is at least {} bytes of header; this one is {}.",
379 PATCH_HEADER, patch.len(); Invalid, Input, Decode));
380 }
381 if patch[0..4] != PATCH_MAGIC {
382 return Err(err!(
383 "These {} bytes do not begin with a patch's mark {:?}, so they are some other \
384 file.", patch.len(), PATCH_MAGIC; Invalid, Input, Decode));
385 }
386 let format = u32_at(patch, 4);
387 if format != PATCH_FORMAT {
388 return Err(err!(
389 "This patch is written in format {}, and this build reads format {}.",
390 format, PATCH_FORMAT; Invalid, Input, Decode));
391 }
392 let old_len = u32_at(patch, 8) as usize;
393 let old_sum = u32_at(patch, 12);
394 let new_len = u32_at(patch, 16) as usize;
395 let new_sum = u32_at(patch, 20);
396 let count = u32_at(patch, 24) as usize;
397 if old.len() != old_len {
398 return Err(err!(
399 "This patch was made against {} bytes and has been given {}, so it is not this \
400 file's patch.", old_len, old.len(); Invalid, Input, Mismatch));
401 }
402 let sum = checksum(old);
403 if sum != old_sum {
404 return Err(err!(
405 "This patch was made against bytes checksumming {:08x}, and the {} bytes given \
406 checksum {:08x}, so it is not this file's patch.", old_sum, old.len(), sum;
407 Invalid, Input, Checksum));
408 }
409 let splices = res!(decode_splices(patch, count));
410 let out = res!(apply(old, &splices));
411 if out.len() != new_len {
412 return Err(err!(
413 "This patch says it makes {} bytes and made {}.", new_len, out.len();
414 Invalid, Data, Mismatch));
415 }
416 let sum = checksum(&out);
417 if sum != new_sum {
418 return Err(err!(
419 "This patch says it makes bytes checksumming {:08x} and made {} bytes \
420 checksumming {:08x}.", new_sum, out.len(), sum; Invalid, Data, Checksum));
421 }
422 Ok(out)
423}
424
425/// The length of the bytes a patch was made from, and of the bytes it makes,
426/// read from its header without applying it.
427///
428/// For a caller weighing a patch against a full copy before it has either file
429/// in hand.
430pub fn patch_lengths(patch: &[u8])
431 -> Outcome<(usize, usize)>
432{
433 if patch.len() < PATCH_HEADER || patch[0..4] != PATCH_MAGIC {
434 return Err(err!(
435 "These {} bytes are not a patch.", patch.len(); Invalid, Input, Decode));
436 }
437 Ok((u32_at(patch, 8) as usize, u32_at(patch, 16) as usize))
438}
439
440fn encode_patch(old: &[u8], new: &[u8], splices: &[Splice])
441 -> Vec<u8>
442{
443 let body: usize = splices.iter().map(|s| SPLICE_HEADER + s.insert.len()).sum();
444 let mut out = Vec::with_capacity(PATCH_HEADER + body);
445 out.extend_from_slice(&PATCH_MAGIC);
446 out.extend_from_slice(&PATCH_FORMAT.to_le_bytes());
447 out.extend_from_slice(&(old.len() as u32).to_le_bytes());
448 out.extend_from_slice(&checksum(old).to_le_bytes());
449 out.extend_from_slice(&(new.len() as u32).to_le_bytes());
450 out.extend_from_slice(&checksum(new).to_le_bytes());
451 out.extend_from_slice(&(splices.len() as u32).to_le_bytes());
452 for sp in splices {
453 out.extend_from_slice(&(sp.at as u32).to_le_bytes());
454 out.extend_from_slice(&(sp.delete as u32).to_le_bytes());
455 out.extend_from_slice(&(sp.insert.len() as u32).to_le_bytes());
456 out.extend_from_slice(&sp.insert);
457 }
458 out
459}
460
461/// Every length is read before it is trusted, so a truncated or corrupted patch
462/// is a refusal rather than a panic on a slice.
463fn decode_splices(patch: &[u8], count: usize)
464 -> Outcome<Vec<Splice>>
465{
466 let mut out = Vec::with_capacity(count.min(1024));
467 let mut at = PATCH_HEADER;
468 for i in 0..count {
469 if at + SPLICE_HEADER > patch.len() {
470 return Err(err!(
471 "Splice {} of {} begins at byte {} of a patch {} bytes long, so the patch is \
472 truncated.", i, count, at, patch.len(); Invalid, Input, Decode));
473 }
474 let off = u32_at(patch, at) as usize;
475 let del = u32_at(patch, at + 4) as usize;
476 let ins = u32_at(patch, at + 8) as usize;
477 at += SPLICE_HEADER;
478 let end = match at.checked_add(ins) {
479 Some(e) if e <= patch.len() => e,
480 _ => return Err(err!(
481 "Splice {} of {} inserts {} bytes from byte {} of a patch {} bytes long, so \
482 the patch is truncated.", i, count, ins, at, patch.len();
483 Invalid, Input, Decode)),
484 };
485 out.push(Splice { at: off, delete: del, insert: patch[at..end].to_vec() });
486 at = end;
487 }
488 if at != patch.len() {
489 return Err(err!(
490 "A patch of {} splices ends at byte {} of {}, so it carries {} bytes nothing \
491 accounts for.", count, at, patch.len(), patch.len() - at; Invalid, Input, Decode));
492 }
493 Ok(out)
494}
495
496/// CRC-32, from the one already in the dependency graph for the deflate the
497/// packed record kind uses. It is here to catch a patch meeting bytes it was not
498/// made against and a patch that has decayed on disk, neither of which is an
499/// adversary, and a second hash implementation would be two to keep in step.
500fn checksum(buf: &[u8]) -> u32 {
501 let mut crc = flate2::Crc::new();
502 crc.update(buf);
503 crc.sum()
504}
505
506fn u32_at(buf: &[u8], at: usize) -> u32 {
507 u32::from_le_bytes([buf[at], buf[at + 1], buf[at + 2], buf[at + 3]])
508}
509
510
511
512
513fn common_prefix(a: &[u8], b: &[u8]) -> usize {
514 let limit = a.len().min(b.len());
515 let mut n = 0;
516 while n < limit && a[n] == b[n] {
517 n += 1;
518 }
519 n
520}
521
522/// The `floor` bytes already accounted for at the front are never run back past.
523fn common_suffix(a: &[u8], b: &[u8], floor: usize) -> usize {
524 let limit = (a.len() - floor).min(b.len() - floor);
525 let mut n = 0;
526 while n < limit && a[a.len() - 1 - n] == b[b.len() - 1 - n] {
527 n += 1;
528 }
529 n
530}
531
532/// The single splice covering everything between the shared prefix and the
533/// shared suffix, which is what a frontend with no diff at all records.
534fn trimmed_splice(old: &[u8], new: &[u8]) -> Splice {
535 let front = common_prefix(old, new);
536 let back = common_suffix(old, new, front);
537 Splice {
538 at: front,
539 delete: old.len() - front - back,
540 insert: new[front..new.len() - back].to_vec().into(),
541 }
542}
543
544/// Runs one piece level pass and the byte level refinement beneath it, or gives
545/// `None` where the edit script over the pieces would exceed `budget`.
546///
547/// Both routes are this function with different cut points, which is the whole of
548/// what separates them: what a piece is changes, and nothing else does.
549fn diff_over(
550 old: &[u8],
551 new: &[u8],
552 old_starts: &[usize],
553 new_starts: &[usize],
554 budget: usize,
555)
556 -> Option<Vec<Splice>>
557{
558 let mut names: HashMap<&[u8], u32> = HashMap::new();
559 let old_ids = piece_ids(old, old_starts, &mut names);
560 let new_ids = piece_ids(new, new_starts, &mut names);
561 let changes = match changed_runs(&old_ids, &new_ids, budget) {
562 Some(c) => c,
563 None => return None,
564 };
565 let mut out: Vec<Splice> = Vec::with_capacity(changes.len());
566 for c in &changes {
567 refine(
568 old,
569 new,
570 old_starts[c.a.start]..old_starts[c.a.end],
571 new_starts[c.b.start]..new_starts[c.b.end],
572 &mut out,
573 );
574 }
575 coalesce(old, &mut out);
576 Some(out)
577}
578
579/// Are the newlines frequent enough for the line route to have anything to cut
580/// on?
581///
582/// An empty side is no obstacle: it has no lines, and it is the other side that
583/// decides. A side of one line has nothing for the pass to match against, and a
584/// side whose lines average more than [`MAX_TEXT_LINE`] bytes has pieces so
585/// coarse that a changed piece is most of the file.
586fn texty(bytes: &[u8], starts: &[usize]) -> bool {
587 if bytes.is_empty() {
588 return true;
589 }
590 let lines = starts.len() - 1;
591 if lines < 2 {
592 return false;
593 }
594 bytes.len() / lines <= MAX_TEXT_LINE
595}
596
597/// Returns the offset at which each line begins, with the length of the input
598/// as a final entry, so that line `i` occupies `starts[i]..starts[i + 1]`.
599///
600/// A line carries its own terminating newline. A file whose last line has none
601/// still ends in a line, and an empty file has no lines at all.
602fn line_starts(bytes: &[u8]) -> Vec<usize> {
603 let mut starts = vec![0usize];
604 for (i, b) in bytes.iter().enumerate() {
605 if *b == b'\n' {
606 starts.push(i + 1);
607 }
608 }
609 if starts[starts.len() - 1] != bytes.len() {
610 starts.push(bytes.len());
611 }
612 starts
613}
614
615/// Returns the offset at which each content-defined chunk begins, under the same
616/// contract [`line_starts`] keeps: the length of the input is the final entry,
617/// and an empty input has no chunks.
618fn chunk_starts(bytes: &[u8]) -> Vec<usize> {
619 let mut starts = vec![0usize];
620 let mut at = 0usize;
621 while at < bytes.len() {
622 at += cut(&bytes[at..]);
623 starts.push(at);
624 }
625 starts
626}
627
628/// Returns the length of the first chunk of `data`, always at least one byte so
629/// that [`chunk_starts`] terminates.
630///
631/// The gear hash reads one byte at a time, keeping a value that depends only on
632/// the last few dozen bytes, and a boundary is declared where that value hits the
633/// mask. The bytes before [`MIN_CHUNK`] are not hashed at all, no boundary being
634/// acceptable there, which is the cut-point skipping that makes the scan cheap.
635/// The mask is the stricter of the two below [`AVG_CHUNK`] and the looser at and
636/// above it, which pulls the spread of chunk sizes in towards the average.
637fn cut(data: &[u8]) -> usize {
638 let n = data.len();
639 if n <= MIN_CHUNK {
640 return n; // too short to cut: the whole remainder is one chunk
641 }
642 let end = MAX_CHUNK.min(n); // forced boundary
643 let mid = AVG_CHUNK.min(end); // where the mask loosens
644 let mut fp = 0u64; // the rolling gear hash
645 let mut i = MIN_CHUNK; // skip: no boundary may land below
646 while i < mid {
647 fp = (fp << 1).wrapping_add(GEAR[data[i] as usize]);
648 if fp & MASK_S == 0 {
649 return i + 1;
650 }
651 i += 1;
652 }
653 while i < end {
654 fp = (fp << 1).wrapping_add(GEAR[data[i] as usize]);
655 if fp & MASK_L == 0 {
656 return i + 1;
657 }
658 i += 1;
659 }
660 end
661}
662
663/// Splitmix64 is a handful of multiplies and shifts, which is why it can run in
664/// a `const` context and save the crate a dependency and a 2 KiB literal.
665const fn gear_table() -> [u64; 256] {
666 let mut table = [0u64; 256];
667 let mut state = GEAR_SEED;
668 let mut i = 0;
669 while i < 256 {
670 state = state.wrapping_add(GEAR_SEED);
671 let mut z = state;
672 z = (z ^ (z >> 30)).wrapping_mul(0xbf58_476d_1ce4_e5b9);
673 z = (z ^ (z >> 27)).wrapping_mul(0x94d0_49bb_1331_11eb);
674 table[i] = z ^ (z >> 31);
675 i += 1;
676 }
677 table
678}
679
680/// Builds a mask over the top `bits` bits of a `u64`, for `1 <= bits <= 63`.
681///
682/// The top bits are the ones to test. A gear hash shifts left by one per byte,
683/// so bit `k` of the hash depends on the last `k + 1` bytes; testing the high
684/// bits therefore tests a window dozens of bytes wide, while testing the low bits
685/// would decide a boundary on almost nothing.
686const fn high_mask(bits: u32) -> u64 {
687 ((1u64 << bits) - 1) << (64 - bits)
688}
689
690/// Floor of the base-2 logarithm of a non-zero value.
691const fn log2_floor(n: usize) -> u32 {
692 (usize::BITS - 1) - n.leading_zeros()
693}
694
695/// Numbers the pieces, giving equal pieces equal numbers across both files.
696///
697/// The numbering is what the piece level pass compares, so that a comparison
698/// costs one integer rather than the length of a piece. Equality is over the
699/// bytes themselves rather than over a digest of them, so two pieces that differ
700/// can never be given one number and no route can be led into a wrong answer by
701/// a collision.
702fn piece_ids<'a>(bytes: &'a [u8], starts: &[usize], names: &mut HashMap<&'a [u8], u32>)
703 -> Vec<u32>
704{
705 let mut out = Vec::with_capacity(starts.len() - 1);
706 for w in starts.windows(2) {
707 let next = names.len() as u32;
708 out.push(*names.entry(&bytes[w[0]..w[1]]).or_insert(next));
709 }
710 out
711}
712
713
714/// A run of `a` that has become a run of `b`. At least one of the two is
715/// non-empty.
716#[derive(Clone, Debug, Eq, PartialEq)]
717struct Change {
718 a: Range<usize>, // what the run was, in the old sequence
719 b: Range<usize>, // what it has become, in the new sequence
720}
721
722
723/// Returns the changed runs turning `a` into `b`, or `None` where the edit
724/// script would exceed `budget`.
725///
726/// The shared head and tail are taken off first, since neither costs anything
727/// to find and both shrink what Myers is asked to do.
728fn changed_runs<T: PartialEq>(a: &[T], b: &[T], budget: usize)
729 -> Option<Vec<Change>>
730{
731 let limit = a.len().min(b.len());
732 let mut head = 0;
733 while head < limit && a[head] == b[head] {
734 head += 1;
735 }
736 let mut tail = 0;
737 while tail < limit - head
738 && a[a.len() - 1 - tail] == b[b.len() - 1 - tail]
739 {
740 tail += 1;
741 }
742 let (an, bn) = (a.len() - tail, b.len() - tail);
743 if head == an && head == bn {
744 return Some(Vec::new());
745 }
746 // Where one side has nothing left, every remaining element of the other is
747 // the whole of the change and Myers has nothing to decide.
748 if head == an || head == bn {
749 return Some(vec![Change { a: head..an, b: head..bn }]);
750 }
751 let matched = match myers(&a[head..an], &b[head..bn], budget) {
752 Some(m) => m,
753 None => return None,
754 };
755 Some(runs_between(&matched, an - head, bn - head, head))
756}
757
758fn runs_between(matched: &[(usize, usize)], n: usize, m: usize, off: usize)
759 -> Vec<Change>
760{
761 let mut out: Vec<Change> = Vec::new();
762 let (mut i, mut j) = (0usize, 0usize);
763 for (mi, mj) in matched {
764 if *mi > i || *mj > j {
765 out.push(Change {
766 a: off + i..off + mi,
767 b: off + j..off + mj,
768 });
769 }
770 i = mi + 1;
771 j = mj + 1;
772 }
773 if i < n || j < m {
774 out.push(Change {
775 a: off + i..off + n,
776 b: off + j..off + m,
777 });
778 }
779 out
780}
781
782/// Returns the pairs of positions a shortest edit script leaves matched,
783/// ascending, or `None` where that script would be longer than `budget`.
784///
785/// This is Myers' greedy algorithm: for each script length `d` in turn, the
786/// furthest point reachable on each diagonal is extended along whatever run of
787/// equal elements follows it, and the first `d` that reaches the far corner is
788/// the length of a shortest script. Keeping the furthest points of every `d`
789/// makes the path itself recoverable by walking back through them, which is
790/// where the matched pairs come from, and is also what costs O(D²) memory and
791/// so what the budget is bounding.
792fn myers<T: PartialEq>(a: &[T], b: &[T], budget: usize)
793 -> Option<Vec<(usize, usize)>>
794{
795 let (n, m) = (a.len() as isize, b.len() as isize);
796 let max = (a.len() + b.len()) as isize;
797 let cap = budget.min(a.len() + b.len());
798 // Furthest x reached on each diagonal k, shifted so that k = 0 sits at max.
799 let mut v: Vec<isize> = vec![0; (2 * max + 1) as usize];
800 let mut trace: Vec<Vec<isize>> = Vec::with_capacity(cap + 1);
801 for d in 0..=cap {
802 let dd = d as isize;
803 // The state before this step, which is what the walk back reads.
804 trace.push(v[(max - dd) as usize..=(max + dd) as usize].to_vec());
805 let mut k = -dd;
806 while k <= dd {
807 let mut x = if k == -dd
808 || (k != dd && v[(max + k - 1) as usize] < v[(max + k + 1) as usize])
809 {
810 v[(max + k + 1) as usize]
811 } else {
812 v[(max + k - 1) as usize] + 1
813 };
814 let mut y = x - k;
815 while x < n && y < m && a[x as usize] == b[y as usize] {
816 x += 1;
817 y += 1;
818 }
819 v[(max + k) as usize] = x;
820 if x >= n && y >= m {
821 return Some(walk_back(&trace, n, m, d));
822 }
823 k += 2;
824 }
825 }
826 None
827}
828
829/// Walks the furthest points back from the far corner, collecting the pairs
830/// matched on the way.
831fn walk_back(trace: &[Vec<isize>], n: isize, m: isize, found: usize)
832 -> Vec<(usize, usize)>
833{
834 let mut matched: Vec<(usize, usize)> = Vec::new();
835 let (mut x, mut y) = (n, m);
836 for d in (0..=found).rev() {
837 let (prev_x, prev_y) = if d == 0 {
838 (0, 0)
839 } else {
840 let dd = d as isize;
841 let w = &trace[d];
842 let at = |k: isize| w[(k + dd) as usize];
843 let k = x - y;
844 let prev_k = if k == -dd || (k != dd && at(k - 1) < at(k + 1)) {
845 k + 1
846 } else {
847 k - 1
848 };
849 let px = at(prev_k);
850 (px, px - prev_k)
851 };
852 while x > prev_x && y > prev_y {
853 x -= 1;
854 y -= 1;
855 matched.push((x as usize, y as usize));
856 }
857 x = prev_x;
858 y = prev_y;
859 }
860 matched.reverse();
861 matched
862}
863
864/// Turns one changed run of lines into the splices that describe it, trimming
865/// what its two sides share and, where the remainder is small enough to be
866/// worth the second pass, splitting it further at the byte level.
867fn refine(
868 old: &[u8],
869 new: &[u8],
870 o: Range<usize>,
871 n: Range<usize>,
872 out: &mut Vec<Splice>,
873) {
874 let front = common_prefix(&old[o.clone()], &new[n.clone()]);
875 let back = common_suffix(&old[o.clone()], &new[n.clone()], front);
876 let (oa, ob) = (o.start + front, o.end - back);
877 let (na, nb) = (n.start + front, n.end - back);
878 if oa == ob && na == nb {
879 return;
880 }
881 if oa < ob && na < nb
882 && ob - oa <= MAX_REFINE_LEN
883 && nb - na <= MAX_REFINE_LEN
884 {
885 if let Some(inner) = changed_runs(&old[oa..ob], &new[na..nb], MAX_BYTE_SCRIPT) {
886 for c in inner {
887 out.push(Splice {
888 at: oa + c.a.start,
889 delete: c.a.end - c.a.start,
890 insert: new[na + c.b.start..na + c.b.end].to_vec().into(),
891 });
892 }
893 return;
894 }
895 }
896 out.push(Splice {
897 at: oa,
898 delete: ob - oa,
899 insert: new[na..nb].to_vec().into(),
900 });
901}
902
903/// Merges splices lying closer together than [`MIN_SPLICE_GAP`], carrying the
904/// unchanged bytes between them into the payload.
905fn coalesce(old: &[u8], splices: &mut Vec<Splice>) {
906 if splices.len() < 2 {
907 return;
908 }
909 let mut out: Vec<Splice> = Vec::with_capacity(splices.len());
910 for sp in splices.drain(..) {
911 let near = match out.last() {
912 Some(prev) => sp.at - (prev.at + prev.delete) < MIN_SPLICE_GAP,
913 None => false,
914 };
915 match out.last_mut() {
916 Some(prev) if near => {
917 let end = prev.at + prev.delete;
918 prev.insert.extend_from_slice(&old[end..sp.at]);
919 prev.insert.extend_from_slice(&sp.insert);
920 prev.delete = sp.at + sp.delete - prev.at;
921 },
922 _ => out.push(sp),
923 }
924 }
925 *splices = out;
926}
927
928
929#[cfg(test)]
930mod tests {
931 use super::*;
932
933 /// Asserts the property everything else here rests on: the splices, applied
934 /// to the old bytes, produce the new bytes exactly.
935 fn diffed(old: &[u8], new: &[u8])
936 -> Outcome<Vec<Splice>>
937 {
938 let splices = diff(old, new);
939 let got = res!(apply(old, &splices));
940 assert_eq!(
941 got, new,
942 "applying {} splices to {:?} gave {:?}",
943 splices.len(), Bytes(old), Bytes(&got),
944 );
945 Ok(splices)
946 }
947
948 /// A readable form for the byte strings in a failure message.
949 struct Bytes<'a>(&'a [u8]);
950
951 impl std::fmt::Debug for Bytes<'_> {
952 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
953 match std::str::from_utf8(self.0) {
954 Ok(s) => write!(f, "{:?}", s),
955 Err(_) => write!(f, "{:?}", self.0),
956 }
957 }
958 }
959
960 fn inserted(splices: &[Splice]) -> usize {
961 splices.iter().map(|s| s.insert_len()).sum()
962 }
963
964 fn deleted(splices: &[Splice]) -> usize {
965 splices.iter().map(|s| s.delete).sum()
966 }
967
968 #[test]
969 fn identical_inputs_diff_to_nothing() -> Outcome<()> {
970 for s in [&b""[..], b"a", b"a\n", b"one\ntwo\nthree\n", b"no trailing newline"] {
971 assert!(res!(diffed(s, s)).is_empty(), "{:?}", Bytes(s));
972 }
973 Ok(())
974 }
975
976 #[test]
977 fn empty_sides_are_one_splice() -> Outcome<()> {
978 let text = b"one\ntwo\nthree\n";
979 let born = res!(diffed(b"", text));
980 assert_eq!(born.len(), 1);
981 assert_eq!(born[0].at, 0);
982 assert_eq!(born[0].delete, 0);
983 assert_eq!(born[0].insert, text.to_vec());
984 let died = res!(diffed(text, b""));
985 assert_eq!(died.len(), 1);
986 assert_eq!(died[0].at, 0);
987 assert_eq!(died[0].delete, text.len());
988 assert!(died[0].insert.is_empty());
989 // Two empty files are the same file.
990 assert!(res!(diffed(b"", b"")).is_empty());
991 Ok(())
992 }
993
994 #[test]
995 fn an_inserted_line_costs_one_line() -> Outcome<()> {
996 let old = b"alpha\nbeta\ngamma\n";
997 let new = b"alpha\nbeta\ndelta\ngamma\n";
998 let sp = res!(diffed(old, new));
999 assert_eq!(sp.len(), 1);
1000 assert_eq!(sp[0].at, 11, "the gap before gamma");
1001 assert_eq!(sp[0].delete, 0);
1002 assert_eq!(sp[0].insert, b"delta\n".to_vec());
1003 Ok(())
1004 }
1005
1006 #[test]
1007 fn a_deleted_line_costs_one_line() -> Outcome<()> {
1008 let old = b"alpha\nbeta\ngamma\n";
1009 let new = b"alpha\ngamma\n";
1010 let sp = res!(diffed(old, new));
1011 assert_eq!(sp.len(), 1);
1012 assert_eq!(sp[0].at, 6, "the start of beta");
1013 assert_eq!(sp[0].delete, 5, "beta and its newline");
1014 assert!(sp[0].insert.is_empty());
1015 Ok(())
1016 }
1017
1018 #[test]
1019 fn an_edit_within_a_line_costs_the_changed_bytes() -> Outcome<()> {
1020 let old = b"alpha\nthe quick brown fox\ngamma\n";
1021 let new = b"alpha\nthe quick brawn fox\ngamma\n";
1022 let sp = res!(diffed(old, new));
1023 assert_eq!(sp.len(), 1);
1024 assert_eq!(sp[0].delete, 1);
1025 assert_eq!(sp[0].insert, b"a".to_vec());
1026 Ok(())
1027 }
1028
1029 #[test]
1030 fn nearby_changes_merge_and_distant_ones_do_not() -> Outcome<()> {
1031 let old = b"X-------------------------X\n";
1032 let new = b"Y-------------------------Y\n";
1033 let apart = res!(diffed(old, new));
1034 assert_eq!(apart.len(), 2, "twenty five unchanged bytes keep them apart");
1035 assert_eq!(inserted(&apart), 2);
1036 let old = b"X---X\n";
1037 let new = b"Y---Y\n";
1038 let close = res!(diffed(old, new));
1039 assert_eq!(close.len(), 1, "three unchanged bytes do not");
1040 assert_eq!(close[0].insert, b"Y---Y".to_vec());
1041 Ok(())
1042 }
1043
1044 /// The module makes no claim to notice a move; the operation vocabulary has
1045 /// one, and finding it is not this module's business.
1046 #[test]
1047 fn a_move_shaped_edit_becomes_a_delete_and_an_insert() -> Outcome<()> {
1048 let old = b"moved\nalpha\nbeta\ngamma\ndelta\nepsilon\n";
1049 let new = b"alpha\nbeta\ngamma\ndelta\nepsilon\nmoved\n";
1050 let sp = res!(diffed(old, new));
1051 assert_eq!(sp.len(), 2);
1052 assert_eq!(sp[0].at, 0);
1053 assert_eq!(sp[0].delete, 6);
1054 assert!(sp[0].insert.is_empty());
1055 assert_eq!(sp[1].delete, 0);
1056 assert_eq!(sp[1].insert, b"moved\n".to_vec());
1057 Ok(())
1058 }
1059
1060 #[test]
1061 fn a_whole_file_rewrite_is_one_splice() -> Outcome<()> {
1062 let old = b"alpha\nbeta\ngamma\n";
1063 let new = b"one\ntwo\nthree\n";
1064 let sp = res!(diffed(old, new));
1065 assert_eq!(sp.len(), 1);
1066 assert_eq!(sp[0].at, 0);
1067 // The final newline is the one thing the two versions still share.
1068 assert_eq!(sp[0].delete, old.len() - 1);
1069 assert_eq!(sp[0].insert, new[..new.len() - 1].to_vec());
1070 Ok(())
1071 }
1072
1073 #[test]
1074 fn a_trailing_newline_is_one_byte() -> Outcome<()> {
1075 let bare = b"alpha\nbeta";
1076 let ended = b"alpha\nbeta\n";
1077 let gained = res!(diffed(bare, ended));
1078 assert_eq!(gained.len(), 1);
1079 assert_eq!(gained[0].at, bare.len());
1080 assert_eq!(gained[0].delete, 0);
1081 assert_eq!(gained[0].insert, b"\n".to_vec());
1082 let lost = res!(diffed(ended, bare));
1083 assert_eq!(lost.len(), 1);
1084 assert_eq!(lost[0].at, bare.len());
1085 assert_eq!(lost[0].delete, 1);
1086 assert!(lost[0].insert.is_empty());
1087 Ok(())
1088 }
1089
1090 #[test]
1091 fn newline_free_content_is_one_trimmed_splice() -> Outcome<()> {
1092 let old: Vec<u8> = (0u8..=255).chain(0u8..=255).collect();
1093 let mut new = old.clone();
1094 new[300] = 0xff;
1095 new[301] = 0xff;
1096 let sp = res!(diffed(&old, &new));
1097 assert_eq!(sp.len(), 1);
1098 assert_eq!(sp[0].at, 300);
1099 assert_eq!(sp[0].delete, 2);
1100 assert_eq!(sp[0].insert, vec![0xff, 0xff]);
1101 Ok(())
1102 }
1103
1104 /// Two distant edits in a large file cost the two edits, which is the whole
1105 /// point of the exercise: a single trimmed splice would have stored
1106 /// everything between them.
1107 #[test]
1108 fn two_distant_edits_in_a_large_file_cost_two_edits() -> Outcome<()> {
1109 let mut lines: Vec<String> = Vec::with_capacity(1000);
1110 for i in 0..1000 {
1111 lines.push(fmt!("line {} of the file, with some words on it\n", i));
1112 }
1113 let old: Vec<u8> = lines.concat().into_bytes();
1114 lines[99] = fmt!("line 99 has been rewritten entirely\n");
1115 lines[899] = fmt!("line 899 of the file, with other words on it\n");
1116 let new: Vec<u8> = lines.concat().into_bytes();
1117 let sp = res!(diffed(&old, &new));
1118 assert_eq!(sp.len(), 2, "one splice per changed line");
1119 // One line was rewritten, so it is stored; the other lost a word, so
1120 // only the word is. Neither is anywhere near the size of the file.
1121 assert!(inserted(&sp) <= 40, "inserted {} bytes", inserted(&sp));
1122 assert!(deleted(&sp) <= 40, "deleted {} bytes", deleted(&sp));
1123 // What the frontend does without a diff, for comparison: everything
1124 // between the first change and the last.
1125 let bare = trimmed_splice(&old, &new);
1126 assert!(bare.insert.len() > 30_000, "the whole point of the exercise");
1127 Ok(())
1128 }
1129
1130 #[test]
1131 fn an_exhausted_budget_falls_back_to_one_splice() -> Outcome<()> {
1132 let mut old = String::new();
1133 let mut new = String::new();
1134 for i in 0..200 {
1135 // Every other line changes, so the edit script is two hundred long
1136 // and the changes are spread across two hundred runs.
1137 if i % 2 == 0 {
1138 old.push_str(&fmt!("old line {}, with words after it\n", i));
1139 new.push_str(&fmt!("new line {}, with words after it\n", i));
1140 } else {
1141 let same = fmt!("line {} is left exactly as it was\n", i);
1142 old.push_str(&same);
1143 new.push_str(&same);
1144 }
1145 }
1146 let (old, new) = (old.into_bytes(), new.into_bytes());
1147 assert!(diff_with_budget(&old, &new, 512).len() > 1, "a full budget diffs");
1148 for budget in [0usize, 1, 8, 64] {
1149 let sp = diff_with_budget(&old, &new, budget);
1150 assert_eq!(sp.len(), 1, "budget {}", budget);
1151 assert_eq!(res!(apply(&old, &sp)), new, "budget {}", budget);
1152 assert_eq!(sp[0], trimmed_splice(&old, &new), "budget {}", budget);
1153 }
1154 Ok(())
1155 }
1156
1157 #[test]
1158 fn a_long_changed_run_is_left_whole() -> Outcome<()> {
1159 // One line, longer than the refinement limit, differing in two places
1160 // far apart.
1161 let mut old = vec![b'-'; MAX_REFINE_LEN * 2];
1162 old.push(b'\n');
1163 let mut new = old.clone();
1164 new[10] = b'X';
1165 new[MAX_REFINE_LEN * 2 - 10] = b'X';
1166 let sp = res!(diffed(&old, &new));
1167 assert_eq!(sp.len(), 1, "the run is too long to refine");
1168 assert!(sp[0].insert.len() > MAX_REFINE_LEN);
1169 Ok(())
1170 }
1171
1172 #[test]
1173 fn apply_refuses_a_list_that_does_not_fit() -> Outcome<()> {
1174 let old = b"alpha\nbeta\n";
1175 assert!(apply(old, &[Splice { at: 12, delete: 0, insert: Vec::new() }]).is_err());
1176 assert!(apply(old, &[Splice { at: 0, delete: 12, insert: Vec::new() }]).is_err());
1177 assert!(apply(old, &[
1178 Splice { at: 6, delete: 1, insert: Vec::new() }.into(),
1179 Splice { at: 2, delete: 1, insert: Vec::new() }.into(),
1180 ]).is_err());
1181 assert!(apply(old, &[
1182 Splice { at: 0, delete: 4, insert: Vec::new() }.into(),
1183 Splice { at: 2, delete: 1, insert: Vec::new() }.into(),
1184 ]).is_err(), "overlapping splices");
1185 assert!(apply(old, &[
1186 Splice { at: usize::MAX, delete: usize::MAX, insert: Vec::new() }.into(),
1187 ]).is_err(), "an offset that would overflow");
1188 Ok(())
1189 }
1190
1191 /// Whatever the diff returns is ascending, non-overlapping, and separated
1192 /// by at least the minimum gap.
1193 fn assert_well_formed(old: &[u8], splices: &[Splice]) {
1194 let mut at = 0usize;
1195 let mut first = true;
1196 for sp in splices {
1197 assert!(sp.at + sp.delete <= old.len(), "a splice reaches past the file");
1198 if first {
1199 first = false;
1200 } else {
1201 assert!(sp.at >= at + MIN_SPLICE_GAP,
1202 "splices at {} and {} are less than {} bytes apart",
1203 at, sp.at, MIN_SPLICE_GAP);
1204 }
1205 assert!(sp.delete > 0 || !sp.insert.is_empty(), "a splice does nothing");
1206 at = sp.at + sp.delete;
1207 }
1208 }
1209
1210 /// Two hundred random pairs of files, all of them applied back: the oracle
1211 /// is that the result is the new bytes exactly, every time.
1212 #[test]
1213 fn random_pairs_apply_back_to_the_new_bytes() -> Outcome<()> {
1214 // A small linear congruential generator, so a failure can be reproduced.
1215 let mut state = 0x2545_F491_4F6C_DD1Du64;
1216 let mut next = move || {
1217 state = state
1218 .wrapping_mul(6_364_136_223_846_793_005)
1219 .wrapping_add(1_442_695_040_888_963_407);
1220 (state >> 33) as usize
1221 };
1222 // A small alphabet of lines, so that lines repeat and the line level
1223 // pass has real matching to do.
1224 let words = ["alpha", "beta", "gamma", "delta", "epsilon", "zeta", "", "x"];
1225 for trial in 0..220 {
1226 let mut old: Vec<u8> = Vec::new();
1227 for _ in 0..(next() % 40) {
1228 old.extend_from_slice(words[next() % words.len()].as_bytes());
1229 // Most lines end; some do not, which is how a file ends up with
1230 // a fragment on the end and how two lines end up joined.
1231 if next() % 8 != 0 {
1232 old.push(b'\n');
1233 }
1234 }
1235 // The new file is the old one put through a handful of edits, so
1236 // that the two are usually related, and occasionally not at all.
1237 let mut new = old.clone();
1238 for _ in 0..(next() % 6) {
1239 match next() % 4 {
1240 0 if !new.is_empty() => {
1241 // Delete a run.
1242 let at = next() % new.len();
1243 let len = (next() % 12).min(new.len() - at);
1244 new.drain(at..at + len);
1245 },
1246 1 => {
1247 // Insert a line.
1248 let at = if new.is_empty() { 0 } else { next() % new.len() };
1249 let mut ins = words[next() % words.len()].as_bytes().to_vec();
1250 ins.push(b'\n');
1251 let tail = new.split_off(at);
1252 new.extend_from_slice(&ins);
1253 new.extend_from_slice(&tail);
1254 },
1255 2 if !new.is_empty() => {
1256 // Change one byte.
1257 let at = next() % new.len();
1258 new[at] = b'a' + (next() % 26) as u8;
1259 },
1260 _ => {
1261 // Move a run to the front.
1262 if new.len() > 4 {
1263 let at = next() % (new.len() - 2);
1264 let len = (next() % 10).min(new.len() - at);
1265 let run: Vec<u8> = new.drain(at..at + len).collect();
1266 let to = if new.is_empty() { 0 } else { next() % new.len() };
1267 let tail = new.split_off(to);
1268 new.extend_from_slice(&run);
1269 new.extend_from_slice(&tail);
1270 }
1271 },
1272 }
1273 }
1274 let splices = diff(&old, &new);
1275 let got = res!(apply(&old, &splices));
1276 assert_eq!(
1277 got, new,
1278 "trial {}: {:?} -> {:?} gave {:?}",
1279 trial, Bytes(&old), Bytes(&new), Bytes(&got),
1280 );
1281 assert_well_formed(&old, &splices);
1282 // The same pair under a budget that forces the fallback still
1283 // applies back.
1284 let bare = diff_with_budget(&old, &new, 0);
1285 assert_eq!(res!(apply(&old, &bare)), new, "trial {} under fallback", trial);
1286 }
1287 Ok(())
1288 }
1289
1290 /// Random byte soup, where the line level pass has almost nothing to work
1291 /// with, still applies back.
1292 #[test]
1293 fn random_binary_pairs_apply_back() -> Outcome<()> {
1294 let mut state = 0x9E37_79B9_7F4A_7C15u64;
1295 let mut next = move || {
1296 state = state
1297 .wrapping_mul(6_364_136_223_846_793_005)
1298 .wrapping_add(1_442_695_040_888_963_407);
1299 (state >> 33) as usize
1300 };
1301 for trial in 0..60 {
1302 let n = next() % 500;
1303 let old: Vec<u8> = (0..n).map(|_| (next() % 256) as u8).collect();
1304 let mut new = old.clone();
1305 for _ in 0..(next() % 8) {
1306 if new.is_empty() {
1307 new.push((next() % 256) as u8);
1308 continue;
1309 }
1310 let at = next() % new.len();
1311 match next() % 3 {
1312 0 => { new[at] = (next() % 256) as u8; },
1313 1 => { new.insert(at, (next() % 256) as u8); },
1314 _ => { new.remove(at); },
1315 }
1316 }
1317 let splices = diff(&old, &new);
1318 assert_eq!(res!(apply(&old, &splices)), new, "trial {}", trial);
1319 assert_well_formed(&old, &splices);
1320 }
1321 Ok(())
1322 }
1323
1324 /// Lines are cut where the newlines are, the last one carrying whatever is
1325 /// left, and an empty file has none.
1326 #[test]
1327 fn lines_are_cut_at_the_newlines() -> Outcome<()> {
1328 assert_eq!(line_starts(b""), vec![0]);
1329 assert_eq!(line_starts(b"\n"), vec![0, 1]);
1330 assert_eq!(line_starts(b"a"), vec![0, 1]);
1331 assert_eq!(line_starts(b"a\nb\n"), vec![0, 2, 4]);
1332 assert_eq!(line_starts(b"a\nb"), vec![0, 2, 3]);
1333 Ok(())
1334 }
1335
1336 /// A pseudorandom byte string of `n` bytes with no newline in it, which is
1337 /// what a compressed or otherwise structureless payload looks like to the
1338 /// line pass: one enormous line.
1339 fn binary(n: usize, seed: u64) -> Vec<u8> {
1340 let mut state = seed;
1341 let mut next = move || {
1342 state = state
1343 .wrapping_mul(6_364_136_223_846_793_005)
1344 .wrapping_add(1_442_695_040_888_963_407);
1345 (state >> 33) as usize
1346 };
1347 (0..n).map(|_| {
1348 let b = (next() % 255) as u8;
1349 if b >= b'\n' { b + 1 } else { b }
1350 }).collect()
1351 }
1352
1353 /// The route is chosen by what the content is, not by what it is called.
1354 #[test]
1355 fn the_route_follows_the_content() -> Outcome<()> {
1356 // Nothing to do.
1357 assert_eq!(diff_routed(b"same", b"same", MAX_PIECE_SCRIPT).1, Route::Same);
1358 // Text, of any size, cuts at the newlines.
1359 let mut lines = String::new();
1360 for i in 0..4000 {
1361 lines.push_str(&fmt!("line {} of a perfectly ordinary text file\n", i));
1362 }
1363 let old = lines.clone().into_bytes();
1364 let new = lines.replace("line 2000 ", "line 2000! ").into_bytes();
1365 assert!(old.len() > MIN_CHUNKED_LEN, "large enough to have a choice");
1366 assert_eq!(diff_routed(&old, &new, MAX_PIECE_SCRIPT).1, Route::Line);
1367 // Newline-poor content long enough to chunk takes the chunk route.
1368 let old = binary(MIN_CHUNKED_LEN * 4, 0x2545_F491_4F6C_DD1D);
1369 let mut new = old.clone();
1370 new[MIN_CHUNKED_LEN] ^= 0xff;
1371 assert_eq!(diff_routed(&old, &new, MAX_PIECE_SCRIPT).1, Route::Chunk);
1372 // The same content, too short to be worth chunking, falls to one splice.
1373 let short = &old[..MIN_CHUNKED_LEN - 1];
1374 let mut new = short.to_vec();
1375 new[10] ^= 0xff;
1376 new[MIN_CHUNKED_LEN - 100] ^= 0xff;
1377 let (sp, route) = diff_routed(short, &new, MAX_PIECE_SCRIPT);
1378 assert_eq!(route, Route::Line, "one line, refined by the byte pass");
1379 assert_eq!(res!(apply(short, &sp)), new);
1380 // And a budget no piece route can meet falls through both of them.
1381 assert_eq!(diff_routed(&old, &new, 0).1, Route::Whole);
1382 Ok(())
1383 }
1384
1385 #[test]
1386 fn an_exhausted_line_budget_hands_on_to_the_chunks() -> Outcome<()> {
1387 // Every other line changes, over enough lines that a small budget cannot
1388 // describe it, in a file long enough to chunk.
1389 let mut old = String::new();
1390 let mut new = String::new();
1391 for i in 0..900 {
1392 if i % 2 == 0 {
1393 old.push_str(&fmt!("old line {}, with a few words after it\n", i));
1394 new.push_str(&fmt!("new line {}, with a few words after it\n", i));
1395 } else {
1396 let same = fmt!("line {} is left exactly as it was, unchanged\n", i);
1397 old.push_str(&same);
1398 new.push_str(&same);
1399 }
1400 }
1401 let (old, new) = (old.into_bytes(), new.into_bytes());
1402 assert!(old.len() > MIN_CHUNKED_LEN);
1403 assert_eq!(diff_routed(&old, &new, MAX_PIECE_SCRIPT).1, Route::Line,
1404 "a full budget stays on the line route");
1405 let (sp, route) = diff_routed(&old, &new, 16);
1406 assert_eq!(route, Route::Chunk, "a starved line pass is not the end of it");
1407 assert_eq!(res!(apply(&old, &sp)), new);
1408 assert_well_formed(&old, &sp);
1409 Ok(())
1410 }
1411
1412 /// This is the property the whole route rests on, and it is the one thing a
1413 /// fixed-size chunker cannot do: there, one inserted byte shifts every
1414 /// boundary after it and nothing downstream matches anything.
1415 #[test]
1416 fn a_chunk_boundary_follows_the_content() -> Outcome<()> {
1417 let old = binary(1 << 20, 0x9E37_79B9_7F4A_7C15);
1418 let mut new = old.clone();
1419 new.insert(1000, 0x42);
1420 let before = chunk_starts(&old);
1421 let after = chunk_starts(&new);
1422 assert!(before.len() > 40, "a megabyte should cut into many chunks");
1423 // Every boundary beyond the disturbance is one byte along from where it
1424 // was, which is to say it is the same place in the content.
1425 let shifted: Vec<usize> = after.iter()
1426 .filter(|b| **b > MAX_CHUNK)
1427 .map(|b| b - 1)
1428 .collect();
1429 let kept = before.iter().filter(|b| shifted.contains(b)).count();
1430 assert!(
1431 kept as f64 > 0.9 * shifted.len() as f64,
1432 "only {} of {} boundaries re-synchronised", kept, shifted.len(),
1433 );
1434 Ok(())
1435 }
1436
1437 /// This is the claim the feature table makes, measured. The comparison is
1438 /// against what the same pair costs without the chunk route, which is the
1439 /// whole span between the outermost edits.
1440 #[test]
1441 fn scattered_edits_in_a_large_binary_cost_their_regions() -> Outcome<()> {
1442 let old = binary(4 << 20, 0x0f1e_2d3c_4b5a_6978);
1443 let mut new = old.clone();
1444 for at in [100_000usize, 2_000_000, 4_000_000] {
1445 new[at] ^= 0xff;
1446 }
1447 let (sp, route) = diff_routed(&old, &new, MAX_PIECE_SCRIPT);
1448 assert_eq!(route, Route::Chunk);
1449 assert_eq!(res!(apply(&old, &sp)), new);
1450 assert_well_formed(&old, &sp);
1451 assert_eq!(sp.len(), 3, "one region per edit");
1452 let cost = inserted(&sp) + deleted(&sp);
1453 // The bound the route guarantees is three refined regions, so at worst
1454 // three chunks; a few hundred kilobytes was the claim.
1455 assert!(
1456 cost < 3 * AVG_CHUNK,
1457 "three edits in four megabytes cost {} bytes", cost,
1458 );
1459 // What it actually costs is the three bytes, because trimming what the
1460 // two sides of a changed chunk share finds the edit exactly. Frozen,
1461 // because a change to the chunker that quietly stopped doing this is
1462 // precisely what this test is for.
1463 assert_eq!(cost, 6, "three bytes replaced by three bytes");
1464 // What the same pair costs with no piece route at all: everything between
1465 // the first change and the last.
1466 let bare = trimmed_splice(&old, &new);
1467 assert!(bare.insert.len() > 3_800_000, "the whole point of the exercise");
1468 Ok(())
1469 }
1470
1471 /// Randomised binary pairs with edits scattered through them, all applied
1472 /// back: the oracle is that the result is the new bytes exactly, on the route
1473 /// that produced them.
1474 #[test]
1475 fn random_binary_pairs_on_the_chunk_route_apply_back() -> Outcome<()> {
1476 let mut state = 0x1234_5678_9abc_def0u64;
1477 let mut next = move || {
1478 state = state
1479 .wrapping_mul(6_364_136_223_846_793_005)
1480 .wrapping_add(1_442_695_040_888_963_407);
1481 (state >> 33) as usize
1482 };
1483 let mut chunked = 0;
1484 for trial in 0..24 {
1485 let n = MIN_CHUNKED_LEN + next() % (200 * 1024);
1486 let old = binary(n, 0xdead_beef_0000_0000 + trial as u64);
1487 let mut new = old.clone();
1488 // A handful of edits at unrelated offsets, of every kind.
1489 for _ in 0..1 + next() % 8 {
1490 if new.is_empty() {
1491 break;
1492 }
1493 let at = next() % new.len();
1494 match next() % 4 {
1495 0 => { new[at] ^= 0xff; },
1496 1 => {
1497 let run: Vec<u8> = (0..1 + next() % 900)
1498 .map(|k| (at + k) as u8 | 1)
1499 .collect();
1500 let tail = new.split_off(at);
1501 new.extend_from_slice(&run);
1502 new.extend_from_slice(&tail);
1503 },
1504 2 => {
1505 let len = (next() % 3000).min(new.len() - at);
1506 new.drain(at..at + len);
1507 },
1508 _ => {
1509 // Move a run to another offset, which the diff has no verb
1510 // for and must describe as a deletion and an insertion.
1511 let len = (next() % 1500).min(new.len() - at);
1512 let run: Vec<u8> = new.drain(at..at + len).collect();
1513 let to = if new.is_empty() { 0 } else { next() % new.len() };
1514 let tail = new.split_off(to);
1515 new.extend_from_slice(&run);
1516 new.extend_from_slice(&tail);
1517 },
1518 }
1519 }
1520 let (sp, route) = diff_routed(&old, &new, MAX_PIECE_SCRIPT);
1521 assert_eq!(res!(apply(&old, &sp)), new, "trial {} on {}", trial, route.name());
1522 assert_well_formed(&old, &sp);
1523 if route == Route::Chunk {
1524 chunked += 1;
1525 }
1526 // And under a budget that forces the fallback, it still applies back.
1527 let (bare, route) = diff_routed(&old, &new, 0);
1528 assert_eq!(route, Route::Whole);
1529 assert_eq!(res!(apply(&old, &bare)), new, "trial {} under fallback", trial);
1530 }
1531 assert_eq!(chunked, 24, "every one of these is newline-free and large");
1532 Ok(())
1533 }
1534
1535 /// Chunks are cut where the content says, the last one carrying whatever is
1536 /// left, and an empty input has none.
1537 #[test]
1538 fn chunks_are_cut_within_their_bounds() -> Outcome<()> {
1539 assert_eq!(chunk_starts(b""), vec![0]);
1540 assert_eq!(chunk_starts(b"short"), vec![0, 5]);
1541 let bytes = binary(1 << 20, 0xabcd_ef01_2345_6789);
1542 let starts = chunk_starts(&bytes);
1543 assert_eq!(starts[0], 0);
1544 assert_eq!(starts[starts.len() - 1], bytes.len());
1545 for (i, w) in starts.windows(2).enumerate() {
1546 let len = w[1] - w[0];
1547 assert!(len > 0, "chunk {} is empty", i);
1548 assert!(len <= MAX_CHUNK, "chunk {} is {} bytes", i, len);
1549 // Every chunk but the last reaches the minimum.
1550 if i + 2 < starts.len() {
1551 assert!(len > MIN_CHUNK, "chunk {} is {} bytes", i, len);
1552 }
1553 }
1554 // A run of identical bytes offers the hash nothing, so the ceiling is
1555 // what cuts it.
1556 let flat = vec![0x5au8; 5 * MAX_CHUNK];
1557 let starts = chunk_starts(&flat);
1558 assert_eq!(starts.len(), 6);
1559 assert_eq!(starts[1], MAX_CHUNK);
1560 Ok(())
1561 }
1562
1563 // ── Stored patches ──
1564
1565 /// This module's own source: real text, with real indentation, real comment
1566 /// prose and real repeated tokens, rather than a fixture shaped to flatter
1567 /// the diff.
1568 const SOURCE: &str = include_str!("diff.rs");
1569
1570 /// One line changed in the middle of a real file must cost the line and not
1571 /// the file, and must come back exactly.
1572 #[test]
1573 fn a_patch_of_one_line_costs_the_line_and_not_the_file() -> Outcome<()> {
1574 let old = SOURCE.as_bytes();
1575 let mut lines: Vec<&str> = SOURCE.split('\n').collect();
1576 let at = lines.len() / 2;
1577 lines[at] = "\t// one line, changed.";
1578 let new_text = lines.join("\n");
1579 let new = new_text.as_bytes();
1580 let patch = res!(make_patch(old, new));
1581 assert!(
1582 patch.len() * 20 < old.len(),
1583 "a one line change cost {} bytes against a file of {}",
1584 patch.len(), old.len(),
1585 );
1586 assert_eq!(res!(apply_patch(old, &patch)), new);
1587 Ok(())
1588 }
1589
1590 /// The header says what it was made from and what it makes, without either
1591 /// file being present.
1592 #[test]
1593 fn a_patch_names_both_lengths_in_its_header() -> Outcome<()> {
1594 let old = b"one\ntwo\nthree\n";
1595 let new = b"one\ntwo\nthree\nfour\n";
1596 let patch = res!(make_patch(old, new));
1597 assert_eq!(res!(patch_lengths(&patch)), (old.len(), new.len()));
1598 Ok(())
1599 }
1600
1601 /// **The break this whole encoding exists for.** A splice list made against
1602 /// one parent, applied to a different parent OF THE SAME LENGTH, is
1603 /// structurally valid: `apply` cannot see anything wrong with it and returns
1604 /// bytes nobody ever wrote. The patch must refuse instead.
1605 ///
1606 /// The refusal is required to blame the INPUT and not the result. The
1607 /// checksum of the reconstruction would catch this too, and would say the
1608 /// patch made the wrong bytes -- true, useless, and pointing at the one
1609 /// thing that is not at fault. So the old side's checksum is what decides;
1610 /// the length beside it is the plainer message for the common case and is
1611 /// not load bearing on its own.
1612 #[test]
1613 fn a_patch_refuses_a_parent_it_was_not_made_against() -> Outcome<()> {
1614 let old = b"alpha\nbravo\ncharlie\ndelta\n";
1615 let new = b"alpha\nBRAVO\ncharlie\ndelta\n";
1616 // Same length, different bytes: a length check alone would pass this.
1617 let other = b"alpha\nbravo\ncharlie\nDELTA\n";
1618 assert_eq!(old.len(), other.len());
1619 let patch = res!(make_patch(old, new));
1620 // The bare splice list would have applied without complaint.
1621 let splices = diff(old, new);
1622 let wrong = res!(apply(other, &splices));
1623 assert_ne!(wrong, new.to_vec(), "the wrong parent gives bytes nobody wrote");
1624 // And one byte shorter, so the length is wrong as well as the content.
1625 let shorter = b"alpha\nbravo\ncharlie\ndelt\n";
1626 for parent in [&other[..], &shorter[..]] {
1627 match apply_patch(parent, &patch) {
1628 Ok(got) => panic!(
1629 "a patch applied to a parent it was not made against returned {} bytes",
1630 got.len()),
1631 Err(e) => {
1632 let tags = e.tags();
1633 assert!(
1634 tags.contains(&ErrTag::Input),
1635 "the refusal must blame the parent it was given; it said {:?}", tags);
1636 assert!(
1637 !tags.contains(&ErrTag::Data),
1638 "blaming the reconstruction points at the one thing not at fault; \
1639 it said {:?}", tags);
1640 },
1641 }
1642 }
1643 Ok(())
1644 }
1645
1646 /// A patch a byte short, and a patch a byte long, are both refused: nothing
1647 /// is decoded from a length the file does not carry, and nothing is left
1648 /// over unaccounted for.
1649 #[test]
1650 fn a_patch_that_is_not_its_own_length_is_refused() -> Outcome<()> {
1651 let old = SOURCE.as_bytes();
1652 let new_text = SOURCE.replace("Myers", "MYERS");
1653 let patch = res!(make_patch(old, new_text.as_bytes()));
1654 // Every truncation, not a few: the decode reads a length out of the
1655 // file and then indexes with it, so a bound it does not check is a
1656 // panic rather than a refusal -- and a panic in a browser takes the tab.
1657 for cut in 1..patch.len() {
1658 let short = &patch[..patch.len() - cut];
1659 assert!(apply_patch(old, short).is_err(), "a patch {} bytes short was read", cut);
1660 }
1661 let mut long = patch.clone();
1662 long.push(0);
1663 assert!(apply_patch(old, &long).is_err(), "a patch with a trailing byte was read");
1664 Ok(())
1665 }
1666
1667 /// A bit rotted anywhere in the payload is caught by the checksum of the
1668 /// result, which is the only check that can see it: the splices still fit
1669 /// the old bytes, so `apply` is happy.
1670 #[test]
1671 fn a_patch_whose_bytes_have_decayed_is_refused() -> Outcome<()> {
1672 let old = b"alpha\nbravo\ncharlie\ndelta\necho\n";
1673 let new = b"alpha\nbravo\nCHARLIE THE LONGER\ndelta\necho\n";
1674 let patch = res!(make_patch(old, new));
1675 let mut caught = 0;
1676 let mut passed = 0;
1677 for i in PATCH_HEADER..patch.len() {
1678 let mut bent = patch.clone();
1679 bent[i] ^= 0x20;
1680 match apply_patch(old, &bent) {
1681 Ok(got) => {
1682 passed += 1;
1683 assert_eq!(got, new.to_vec(), "byte {} bent and the answer was wrong", i);
1684 },
1685 Err(_) => caught += 1,
1686 }
1687 }
1688 assert_eq!(passed, 0, "{} bent patches were accepted", passed);
1689 assert!(caught > 0);
1690 // A bent patch blames neither the caller nor the parent: what is wrong
1691 // is the patch itself, and a reader has to be able to tell the two
1692 // apart to know which file to go and find.
1693 let mut bent = patch.clone();
1694 bent[patch.len() - 1] ^= 0x20;
1695 match apply_patch(old, &bent) {
1696 Ok(_) => panic!("a bent patch was applied"),
1697 Err(e) => assert!(
1698 e.tags().contains(&ErrTag::Data),
1699 "a bent patch must blame itself; it said {:?}", e.tags()),
1700 }
1701 Ok(())
1702 }
1703
1704 /// Handed a file that is not a patch, the reader says so rather than
1705 /// decoding whatever it was given.
1706 #[test]
1707 fn what_is_not_a_patch_is_not_read_as_one() -> Outcome<()> {
1708 let old = b"alpha\nbravo\n";
1709 for bytes in [&b""[..], b"{}", b"<html></html>", &[0u8; PATCH_HEADER][..]] {
1710 assert!(apply_patch(old, bytes).is_err(), "{:?} was read as a patch", Bytes(bytes));
1711 assert!(patch_lengths(bytes).is_err(), "{:?} was measured as a patch", Bytes(bytes));
1712 }
1713 // The right mark, an unknown layout.
1714 let mut future = res!(make_patch(old, b"alpha\nBRAVO\n"));
1715 future[4] = 0xff;
1716 assert!(apply_patch(old, &future).is_err(), "a patch from the future was read");
1717 // A whole valid patch with the mark alone spoilt. Nothing else in it is
1718 // wrong, so this is the one case where the mark is what has to catch it.
1719 let mut unmarked = res!(make_patch(old, b"alpha\nBRAVO\n"));
1720 unmarked[0] = b'X';
1721 assert!(apply_patch(old, &unmarked).is_err(), "a patch with the wrong mark was read");
1722 assert!(patch_lengths(&unmarked).is_err(), "a patch with the wrong mark was measured");
1723 Ok(())
1724 }
1725
1726 /// Both empty sides, and a patch that changes nothing, still round-trip.
1727 #[test]
1728 fn the_empty_cases_still_round_trip() -> Outcome<()> {
1729 let text = SOURCE.as_bytes();
1730 for (old, new) in [
1731 (&b""[..], &b""[..]),
1732 (&b""[..], text),
1733 (text, &b""[..]),
1734 (text, text),
1735 ] {
1736 let patch = res!(make_patch(old, new));
1737 assert_eq!(res!(apply_patch(old, &patch)), new.to_vec());
1738 }
1739 Ok(())
1740 }
1741
1742}