unified_diff/
lib.rs

1use std::collections::VecDeque;
2use std::io::Write;
3
4#[derive(Debug, PartialEq)]
5pub enum DiffLine {
6    Context(Vec<u8>),
7    Expected(Vec<u8>),
8    Actual(Vec<u8>),
9    MissingNL,
10}
11
12#[derive(Debug, PartialEq)]
13struct Mismatch {
14    pub line_number_expected: u32,
15    pub line_number_actual: u32,
16    pub lines: Vec<DiffLine>,
17}
18
19impl Mismatch {
20    fn new(line_number_expected: u32, line_number_actual: u32) -> Mismatch {
21        Mismatch {
22            line_number_expected,
23            line_number_actual,
24            lines: Vec::new(),
25        }
26    }
27}
28
29// Produces a diff between the expected output and actual output.
30fn make_diff(expected: &[u8], actual: &[u8], context_size: usize) -> Vec<Mismatch> {
31    let mut line_number_expected = 1;
32    let mut line_number_actual = 1;
33    let mut context_queue: VecDeque<&[u8]> = VecDeque::with_capacity(context_size);
34    let mut lines_since_mismatch = context_size + 1;
35    let mut results = Vec::new();
36    let mut mismatch = Mismatch::new(0, 0);
37
38    let mut expected_lines: Vec<&[u8]> = expected.split(|&c| c == b'\n').collect();
39    let mut actual_lines: Vec<&[u8]> = actual.split(|&c| c == b'\n').collect();
40
41    debug_assert_eq!(b"".split(|&c| c == b'\n').count(), 1);
42    // ^ means that underflow here is impossible
43    let expected_lines_count = expected_lines.len() as u32 - 1;
44    let actual_lines_count = actual_lines.len() as u32 - 1;
45
46    if expected_lines.last() == Some(&&b""[..]) {
47        expected_lines.pop();
48    }
49
50    if actual_lines.last() == Some(&&b""[..]) {
51        actual_lines.pop();
52    }
53
54    for result in diff::slice(&expected_lines, &actual_lines) {
55        match result {
56            diff::Result::Left(str) => {
57                if lines_since_mismatch >= context_size && lines_since_mismatch > 0 {
58                    results.push(mismatch);
59                    mismatch = Mismatch::new(
60                        line_number_expected - context_queue.len() as u32,
61                        line_number_actual - context_queue.len() as u32,
62                    );
63                }
64
65                while let Some(line) = context_queue.pop_front() {
66                    mismatch.lines.push(DiffLine::Context(line.to_vec()));
67                }
68
69                if mismatch.lines.last() == Some(&DiffLine::MissingNL) {
70                    mismatch.lines.pop();
71                    match mismatch.lines.pop() {
72                        Some(DiffLine::Actual(res)) => {
73                            // We have to make sure that Actual (the + lines)
74                            // always come after Expected (the - lines)
75                            mismatch.lines.push(DiffLine::Expected(str.to_vec()));
76                            if line_number_expected > expected_lines_count {
77                                mismatch.lines.push(DiffLine::MissingNL)
78                            }
79                            mismatch.lines.push(DiffLine::Actual(res));
80                            mismatch.lines.push(DiffLine::MissingNL);
81                        }
82                        _ => unreachable!("unterminated Left and Common lines shouldn't be followed by more Left lines"),
83                    }
84                } else {
85                    mismatch.lines.push(DiffLine::Expected(str.to_vec()));
86                    if line_number_expected > expected_lines_count {
87                        mismatch.lines.push(DiffLine::MissingNL)
88                    }
89                }
90                line_number_expected += 1;
91                lines_since_mismatch = 0;
92            }
93            diff::Result::Right(str) => {
94                if lines_since_mismatch >= context_size && lines_since_mismatch > 0 {
95                    results.push(mismatch);
96                    mismatch = Mismatch::new(
97                        line_number_expected - context_queue.len() as u32,
98                        line_number_actual - context_queue.len() as u32,
99                    );
100                }
101
102                while let Some(line) = context_queue.pop_front() {
103                    debug_assert!(mismatch.lines.last() != Some(&DiffLine::MissingNL));
104                    mismatch.lines.push(DiffLine::Context(line.to_vec()));
105                }
106
107                mismatch.lines.push(DiffLine::Actual(str.to_vec()));
108                if line_number_actual > actual_lines_count {
109                    mismatch.lines.push(DiffLine::MissingNL)
110                }
111                line_number_actual += 1;
112                lines_since_mismatch = 0;
113            }
114            diff::Result::Both(str, _) => {
115                // if one of them is missing a newline and the other isn't, then they don't actually match
116                if (line_number_actual > actual_lines_count)
117                    && (line_number_expected > expected_lines_count)
118                {
119                    if context_queue.len() < context_size {
120                        while let Some(line) = context_queue.pop_front() {
121                            debug_assert!(mismatch.lines.last() != Some(&DiffLine::MissingNL));
122                            mismatch.lines.push(DiffLine::Context(line.to_vec()));
123                        }
124                        if lines_since_mismatch < context_size {
125                            mismatch.lines.push(DiffLine::Context(str.to_vec()));
126                            mismatch.lines.push(DiffLine::MissingNL);
127                        }
128                    }
129                    lines_since_mismatch = 0;
130                } else if line_number_actual > actual_lines_count {
131                    if lines_since_mismatch >= context_size && lines_since_mismatch > 0 {
132                        results.push(mismatch);
133                        mismatch = Mismatch::new(
134                            line_number_expected - context_queue.len() as u32,
135                            line_number_actual - context_queue.len() as u32,
136                        );
137                    }
138                    while let Some(line) = context_queue.pop_front() {
139                        debug_assert!(mismatch.lines.last() != Some(&DiffLine::MissingNL));
140                        mismatch.lines.push(DiffLine::Context(line.to_vec()));
141                    }
142                    mismatch.lines.push(DiffLine::Expected(str.to_vec()));
143                    mismatch.lines.push(DiffLine::Actual(str.to_vec()));
144                    mismatch.lines.push(DiffLine::MissingNL);
145                    lines_since_mismatch = 0;
146                } else if line_number_expected > expected_lines_count {
147                    if lines_since_mismatch >= context_size && lines_since_mismatch > 0 {
148                        results.push(mismatch);
149                        mismatch = Mismatch::new(
150                            line_number_expected - context_queue.len() as u32,
151                            line_number_actual - context_queue.len() as u32,
152                        );
153                    }
154                    while let Some(line) = context_queue.pop_front() {
155                        debug_assert!(mismatch.lines.last() != Some(&DiffLine::MissingNL));
156                        mismatch.lines.push(DiffLine::Context(line.to_vec()));
157                    }
158                    mismatch.lines.push(DiffLine::Expected(str.to_vec()));
159                    mismatch.lines.push(DiffLine::MissingNL);
160                    mismatch.lines.push(DiffLine::Actual(str.to_vec()));
161                    lines_since_mismatch = 0;
162                } else {
163                    debug_assert!(context_queue.len() <= context_size);
164                    if context_queue.len() >= context_size {
165                        let _ = context_queue.pop_front();
166                    }
167                    if lines_since_mismatch < context_size {
168                        mismatch.lines.push(DiffLine::Context(str.to_vec()));
169                    } else if context_size > 0 {
170                        context_queue.push_back(str);
171                    }
172                    lines_since_mismatch += 1;
173                }
174                line_number_expected += 1;
175                line_number_actual += 1;
176            }
177        }
178    }
179
180    results.push(mismatch);
181    results.remove(0);
182
183    if results.len() == 0 && expected_lines_count != actual_lines_count {
184        let mut mismatch = Mismatch::new(expected_lines.len() as u32, actual_lines.len() as u32);
185        // empty diff and only expected lines has a missing line at end
186        if expected_lines_count != expected_lines.len() as u32 {
187            mismatch.lines.push(DiffLine::Expected(
188                expected_lines
189                    .pop()
190                    .expect("can't be empty; produced by split()")
191                    .to_vec(),
192            ));
193            mismatch.lines.push(DiffLine::MissingNL);
194            mismatch.lines.push(DiffLine::Actual(
195                actual_lines
196                    .pop()
197                    .expect("can't be empty; produced by split()")
198                    .to_vec(),
199            ));
200            results.push(mismatch);
201        } else if actual_lines_count != actual_lines.len() as u32 {
202            mismatch.lines.push(DiffLine::Expected(
203                expected_lines
204                    .pop()
205                    .expect("can't be empty; produced by split()")
206                    .to_vec(),
207            ));
208            mismatch.lines.push(DiffLine::Actual(
209                actual_lines
210                    .pop()
211                    .expect("can't be empty; produced by split()")
212                    .to_vec(),
213            ));
214            mismatch.lines.push(DiffLine::MissingNL);
215            results.push(mismatch);
216        }
217    }
218
219    results
220}
221
222pub fn diff(
223    expected: &[u8],
224    expected_filename: &str,
225    actual: &[u8],
226    actual_filename: &str,
227    context_size: usize,
228) -> Vec<u8> {
229    let mut output =
230        format!("--- {}\t\n+++ {}\t\n", expected_filename, actual_filename).into_bytes();
231    let diff_results = make_diff(expected, actual, context_size);
232    if diff_results.len() == 0 {
233        return Vec::new();
234    };
235    for result in diff_results {
236        let mut line_number_expected = result.line_number_expected;
237        let mut line_number_actual = result.line_number_actual;
238        let mut expected_count = 0;
239        let mut actual_count = 0;
240        for line in &result.lines {
241            match line {
242                DiffLine::Expected(_) => {
243                    expected_count += 1;
244                }
245                DiffLine::Context(_) => {
246                    expected_count += 1;
247                    actual_count += 1;
248                }
249                DiffLine::Actual(_) => {
250                    actual_count += 1;
251                }
252                DiffLine::MissingNL => {}
253            }
254        }
255        // Let's imagine this diff file
256        //
257        // --- a/something
258        // +++ b/something
259        // @@ -2,0 +3,1 @@
260        // + x
261        //
262        // In the unified diff format as implemented by GNU diff and patch,
263        // this is an instruction to insert the x *after* the preexisting line 2,
264        // not before. You can demonstrate it this way:
265        //
266        // $ echo -ne '--- a/something\t\n+++ b/something\t\n@@ -2,0 +3,1 @@\n+ x\n' > diff
267        // $ echo -ne 'a\nb\nc\nd\n' > something
268        // $ patch -p1 < diff
269        // patching file something
270        // $ cat something
271        // a
272        // b
273        //  x
274        // c
275        // d
276        //
277        // Notice how the x winds up at line 3, not line 2. This requires contortions to
278        // work with our diffing algorithm, which keeps track of the "intended destination line",
279        // not a line that things are supposed to be placed after. It's changing the first number,
280        // not the second, that actually affects where the x goes.
281        //
282        // # change the first number from 2 to 3, and now the x is on line 4 (it's placed after line 3)
283        // $ echo -ne '--- a/something\t\n+++ b/something\t\n@@ -3,0 +3,1 @@\n+ x\n' > diff
284        // $ echo -ne 'a\nb\nc\nd\n' > something
285        // $ patch -p1 < diff
286        // patching file something
287        // $ cat something
288        // a
289        // b
290        // c
291        //  x
292        // d
293        // # change the third number from 3 to 1000, and it's obvious that it's the first number that's
294        // # actually being read
295        // $ echo -ne '--- a/something\t\n+++ b/something\t\n@@ -2,0 +1000,1 @@\n+ x\n' > diff
296        // $ echo -ne 'a\nb\nc\nd\n' > something
297        // $ patch -p1 < diff
298        // patching file something
299        // $ cat something
300        // a
301        // b
302        //  x
303        // c
304        // d
305        //
306        // Now watch what happens if I add a context line:
307        //
308        // $ echo -ne '--- a/something\t\n+++ b/something\t\n@@ -2,1 +3,2 @@\n+ x\n c\n' > diff
309        // $ echo -ne 'a\nb\nc\nd\n' > something
310        // $ patch -p1 < diff
311        // patching file something
312        // Hunk #1 succeeded at 3 (offset 1 line).
313        //
314        // It technically "succeeded", but this is a warning. We want to produce clean diffs.
315        // Now that I have a context line, I'm supposed to say what line it's actually on, which is the
316        // line that the x will wind up on, and not the line immediately before.
317        //
318        // $ echo -ne '--- a/something\t\n+++ b/something\t\n@@ -3,1 +3,2 @@\n+ x\n c\n' > diff
319        // $ echo -ne 'a\nb\nc\nd\n' > something
320        // $ patch -p1 < diff
321        // patching file something
322        // $ cat something
323        // a
324        // b
325        //  x
326        // c
327        // d
328        //
329        // I made this comment because this stuff is not obvious from GNU's
330        // documentation on the format at all.
331        if expected_count == 0 {
332            line_number_expected -= 1
333        }
334        if actual_count == 0 {
335            line_number_actual -= 1
336        }
337        let exp_ct = if expected_count == 1 {
338            String::new()
339        } else {
340            format!(",{}", expected_count)
341        };
342        let act_ct = if actual_count == 1 {
343            String::new()
344        } else {
345            format!(",{}", actual_count)
346        };
347        writeln!(
348            output,
349            "@@ -{}{} +{}{} @@",
350            line_number_expected, exp_ct, line_number_actual, act_ct
351        )
352        .expect("write to Vec is infallible");
353        for line in result.lines {
354            match line {
355                DiffLine::Expected(e) => {
356                    write!(output, "-").expect("write to Vec is infallible");
357                    output.write_all(&e).expect("write to Vec is infallible");
358                    writeln!(output).unwrap();
359                }
360                DiffLine::Context(c) => {
361                    write!(output, " ").expect("write to Vec is infallible");
362                    output.write_all(&c).expect("write to Vec is infallible");
363                    writeln!(output).unwrap();
364                }
365                DiffLine::Actual(r) => {
366                    write!(output, "+",).expect("write to Vec is infallible");
367                    output.write_all(&r).expect("write to Vec is infallible");
368                    writeln!(output).unwrap();
369                }
370                DiffLine::MissingNL => {
371                    writeln!(output, r"\ No newline at end of file")
372                        .expect("write to Vec is infallible");
373                }
374            }
375        }
376    }
377    output
378}
379
380#[test]
381fn test_permutations() {
382    // test all possible six-line files.
383    for &a in &[0, 1, 2] {
384        for &b in &[0, 1, 2] {
385            for &c in &[0, 1, 2] {
386                for &d in &[0, 1, 2] {
387                    for &e in &[0, 1, 2] {
388                        for &f in &[0, 1, 2] {
389                            use std::fs::{self, File};
390                            use std::io::Write;
391                            use std::process::Command;
392                            let mut alef = Vec::new();
393                            let mut bet = Vec::new();
394                            alef.write_all(if a == 0 { b"a\n" } else { b"b\n" })
395                                .unwrap();
396                            if a != 2 {
397                                bet.write_all(b"b\n").unwrap();
398                            }
399                            alef.write_all(if b == 0 { b"c\n" } else { b"d\n" })
400                                .unwrap();
401                            if b != 2 {
402                                bet.write_all(b"d\n").unwrap();
403                            }
404                            alef.write_all(if c == 0 { b"e\n" } else { b"f\n" })
405                                .unwrap();
406                            if c != 2 {
407                                bet.write_all(b"f\n").unwrap();
408                            }
409                            alef.write_all(if d == 0 { b"g\n" } else { b"h\n" })
410                                .unwrap();
411                            if d != 2 {
412                                bet.write_all(b"h\n").unwrap();
413                            }
414                            alef.write_all(if e == 0 { b"i\n" } else { b"j\n" })
415                                .unwrap();
416                            if e != 2 {
417                                bet.write_all(b"j\n").unwrap();
418                            }
419                            alef.write_all(if f == 0 { b"k\n" } else { b"l\n" })
420                                .unwrap();
421                            if f != 2 {
422                                bet.write_all(b"l\n").unwrap();
423                            }
424                            // This test diff is intentionally reversed.
425                            // We want it to turn the alef into bet.
426                            let diff = diff(&alef, "a/alef", &bet, "target/alef", 2);
427                            File::create("target/ab.diff")
428                                .unwrap()
429                                .write_all(&diff)
430                                .unwrap();
431                            let mut fa = File::create("target/alef").unwrap();
432                            fa.write_all(&alef[..]).unwrap();
433                            let mut fb = File::create("target/bet").unwrap();
434                            fb.write_all(&bet[..]).unwrap();
435                            let _ = fa;
436                            let _ = fb;
437                            let output = Command::new("patch")
438                                .arg("-p0")
439                                .stdin(File::open("target/ab.diff").unwrap())
440                                .output()
441                                .unwrap();
442                            if !output.status.success() {
443                                panic!("{:?}", output);
444                            }
445                            //println!("{}", String::from_utf8_lossy(&output.stdout));
446                            //println!("{}", String::from_utf8_lossy(&output.stderr));
447                            let alef = fs::read("target/alef").unwrap();
448                            assert_eq!(alef, bet);
449                        }
450                    }
451                }
452            }
453        }
454    }
455}
456
457#[test]
458fn test_permutations_missing_line_ending() {
459    // test all possible six-line files with missing newlines.
460    for &a in &[0, 1, 2] {
461        for &b in &[0, 1, 2] {
462            for &c in &[0, 1, 2] {
463                for &d in &[0, 1, 2] {
464                    for &e in &[0, 1, 2] {
465                        for &f in &[0, 1, 2] {
466                            for &g in &[0, 1, 2] {
467                                use std::fs::{self, File};
468                                use std::io::Write;
469                                use std::process::Command;
470                                let mut alef = Vec::new();
471                                let mut bet = Vec::new();
472                                alef.write_all(if a == 0 { b"a\n" } else { b"b\n" })
473                                    .unwrap();
474                                if a != 2 {
475                                    bet.write_all(b"b\n").unwrap();
476                                }
477                                alef.write_all(if b == 0 { b"c\n" } else { b"d\n" })
478                                    .unwrap();
479                                if b != 2 {
480                                    bet.write_all(b"d\n").unwrap();
481                                }
482                                alef.write_all(if c == 0 { b"e\n" } else { b"f\n" })
483                                    .unwrap();
484                                if c != 2 {
485                                    bet.write_all(b"f\n").unwrap();
486                                }
487                                alef.write_all(if d == 0 { b"g\n" } else { b"h\n" })
488                                    .unwrap();
489                                if d != 2 {
490                                    bet.write_all(b"h\n").unwrap();
491                                }
492                                alef.write_all(if e == 0 { b"i\n" } else { b"j\n" })
493                                    .unwrap();
494                                if e != 2 {
495                                    bet.write_all(b"j\n").unwrap();
496                                }
497                                alef.write_all(if f == 0 { b"k\n" } else { b"l\n" })
498                                    .unwrap();
499                                if f != 2 {
500                                    bet.write_all(b"l\n").unwrap();
501                                }
502                                match g {
503                                    0 => {
504                                        alef.pop();
505                                    }
506                                    1 => {
507                                        bet.pop();
508                                    }
509                                    2 => {
510                                        alef.pop();
511                                        bet.pop();
512                                    }
513                                    _ => unreachable!(),
514                                }
515                                // This test diff is intentionally reversed.
516                                // We want it to turn the alef into bet.
517                                let diff = diff(&alef, "a/alefn", &bet, "target/alefn", 2);
518                                File::create("target/abn.diff")
519                                    .unwrap()
520                                    .write_all(&diff)
521                                    .unwrap();
522                                let mut fa = File::create("target/alefn").unwrap();
523                                fa.write_all(&alef[..]).unwrap();
524                                let mut fb = File::create("target/betn").unwrap();
525                                fb.write_all(&bet[..]).unwrap();
526                                let _ = fa;
527                                let _ = fb;
528                                let output = Command::new("patch")
529                                    .arg("-p0")
530                                    .stdin(File::open("target/abn.diff").unwrap())
531                                    .output()
532                                    .unwrap();
533                                if !output.status.success() {
534                                    panic!("{:?}", output);
535                                }
536                                //println!("{}", String::from_utf8_lossy(&output.stdout));
537                                //println!("{}", String::from_utf8_lossy(&output.stderr));
538                                let alef = fs::read("target/alefn").unwrap();
539                                assert_eq!(alef, bet);
540                            }
541                        }
542                    }
543                }
544            }
545        }
546    }
547}
548
549#[test]
550fn test_permutations_empty_lines() {
551    // test all possible six-line files with missing newlines.
552    for &a in &[0, 1, 2] {
553        for &b in &[0, 1, 2] {
554            for &c in &[0, 1, 2] {
555                for &d in &[0, 1, 2] {
556                    for &e in &[0, 1, 2] {
557                        for &f in &[0, 1, 2] {
558                            for &g in &[0, 1, 2, 3] {
559                                use std::fs::{self, File};
560                                use std::io::Write;
561                                use std::process::Command;
562                                let mut alef = Vec::new();
563                                let mut bet = Vec::new();
564                                alef.write_all(if a == 0 { b"\n" } else { b"b\n" }).unwrap();
565                                if a != 2 {
566                                    bet.write_all(b"b\n").unwrap();
567                                }
568                                alef.write_all(if b == 0 { b"\n" } else { b"d\n" }).unwrap();
569                                if b != 2 {
570                                    bet.write_all(b"d\n").unwrap();
571                                }
572                                alef.write_all(if c == 0 { b"\n" } else { b"f\n" }).unwrap();
573                                if c != 2 {
574                                    bet.write_all(b"f\n").unwrap();
575                                }
576                                alef.write_all(if d == 0 { b"\n" } else { b"h\n" }).unwrap();
577                                if d != 2 {
578                                    bet.write_all(b"h\n").unwrap();
579                                }
580                                alef.write_all(if e == 0 { b"\n" } else { b"j\n" }).unwrap();
581                                if e != 2 {
582                                    bet.write_all(b"j\n").unwrap();
583                                }
584                                alef.write_all(if f == 0 { b"\n" } else { b"l\n" }).unwrap();
585                                if f != 2 {
586                                    bet.write_all(b"l\n").unwrap();
587                                }
588                                match g {
589                                    0 => {
590                                        alef.pop();
591                                    }
592                                    1 => {
593                                        bet.pop();
594                                    }
595                                    2 => {
596                                        alef.pop();
597                                        bet.pop();
598                                    }
599                                    3 => {}
600                                    _ => unreachable!(),
601                                }
602                                // This test diff is intentionally reversed.
603                                // We want it to turn the alef into bet.
604                                let diff = diff(&alef, "a/alef_", &bet, "target/alef_", 2);
605                                File::create("target/ab_.diff")
606                                    .unwrap()
607                                    .write_all(&diff)
608                                    .unwrap();
609                                let mut fa = File::create("target/alef_").unwrap();
610                                fa.write_all(&alef[..]).unwrap();
611                                let mut fb = File::create("target/bet_").unwrap();
612                                fb.write_all(&bet[..]).unwrap();
613                                let _ = fa;
614                                let _ = fb;
615                                let output = Command::new("patch")
616                                    .arg("-p0")
617                                    .stdin(File::open("target/ab_.diff").unwrap())
618                                    .output()
619                                    .unwrap();
620                                if !output.status.success() {
621                                    panic!("{:?}", output);
622                                }
623                                //println!("{}", String::from_utf8_lossy(&output.stdout));
624                                //println!("{}", String::from_utf8_lossy(&output.stderr));
625                                let alef = fs::read("target/alef_").unwrap();
626                                assert_eq!(alef, bet);
627                            }
628                        }
629                    }
630                }
631            }
632        }
633    }
634}
635
636#[test]
637fn test_permutations_missing_lines() {
638    // test all possible six-line files.
639    for &a in &[0, 1, 2] {
640        for &b in &[0, 1, 2] {
641            for &c in &[0, 1, 2] {
642                for &d in &[0, 1, 2] {
643                    for &e in &[0, 1, 2] {
644                        for &f in &[0, 1, 2] {
645                            use std::fs::{self, File};
646                            use std::io::Write;
647                            use std::process::Command;
648                            let mut alef = Vec::new();
649                            let mut bet = Vec::new();
650                            alef.write_all(if a == 0 { b"a\n" } else { b"" }).unwrap();
651                            if a != 2 {
652                                bet.write_all(b"b\n").unwrap();
653                            }
654                            alef.write_all(if b == 0 { b"c\n" } else { b"" }).unwrap();
655                            if b != 2 {
656                                bet.write_all(b"d\n").unwrap();
657                            }
658                            alef.write_all(if c == 0 { b"e\n" } else { b"" }).unwrap();
659                            if c != 2 {
660                                bet.write_all(b"f\n").unwrap();
661                            }
662                            alef.write_all(if d == 0 { b"g\n" } else { b"" }).unwrap();
663                            if d != 2 {
664                                bet.write_all(b"h\n").unwrap();
665                            }
666                            alef.write_all(if e == 0 { b"i\n" } else { b"" }).unwrap();
667                            if e != 2 {
668                                bet.write_all(b"j\n").unwrap();
669                            }
670                            alef.write_all(if f == 0 { b"k\n" } else { b"" }).unwrap();
671                            if f != 2 {
672                                bet.write_all(b"l\n").unwrap();
673                            }
674                            // This test diff is intentionally reversed.
675                            // We want it to turn the alef into bet.
676                            let diff = diff(&alef, "a/alefx", &bet, "target/alefx", 2);
677                            File::create("target/abx.diff")
678                                .unwrap()
679                                .write_all(&diff)
680                                .unwrap();
681                            let mut fa = File::create("target/alefx").unwrap();
682                            fa.write_all(&alef[..]).unwrap();
683                            let mut fb = File::create("target/betx").unwrap();
684                            fb.write_all(&bet[..]).unwrap();
685                            let _ = fa;
686                            let _ = fb;
687                            let output = Command::new("patch")
688                                .arg("-p0")
689                                .stdin(File::open("target/abx.diff").unwrap())
690                                .output()
691                                .unwrap();
692                            if !output.status.success() {
693                                panic!("{:?}", output);
694                            }
695                            //println!("{}", String::from_utf8_lossy(&output.stdout));
696                            //println!("{}", String::from_utf8_lossy(&output.stderr));
697                            let alef = fs::read("target/alefx").unwrap();
698                            assert_eq!(alef, bet);
699                        }
700                    }
701                }
702            }
703        }
704    }
705}
706
707#[test]
708fn test_permutations_reverse() {
709    // test all possible six-line files.
710    for &a in &[0, 1, 2] {
711        for &b in &[0, 1, 2] {
712            for &c in &[0, 1, 2] {
713                for &d in &[0, 1, 2] {
714                    for &e in &[0, 1, 2] {
715                        for &f in &[0, 1, 2] {
716                            use std::fs::{self, File};
717                            use std::io::Write;
718                            use std::process::Command;
719                            let mut alef = Vec::new();
720                            let mut bet = Vec::new();
721                            alef.write_all(if a == 0 { b"a\n" } else { b"f\n" })
722                                .unwrap();
723                            if a != 2 {
724                                bet.write_all(b"a\n").unwrap();
725                            }
726                            alef.write_all(if b == 0 { b"b\n" } else { b"e\n" })
727                                .unwrap();
728                            if b != 2 {
729                                bet.write_all(b"b\n").unwrap();
730                            }
731                            alef.write_all(if c == 0 { b"c\n" } else { b"d\n" })
732                                .unwrap();
733                            if c != 2 {
734                                bet.write_all(b"c\n").unwrap();
735                            }
736                            alef.write_all(if d == 0 { b"d\n" } else { b"c\n" })
737                                .unwrap();
738                            if d != 2 {
739                                bet.write_all(b"d\n").unwrap();
740                            }
741                            alef.write_all(if e == 0 { b"e\n" } else { b"b\n" })
742                                .unwrap();
743                            if e != 2 {
744                                bet.write_all(b"e\n").unwrap();
745                            }
746                            alef.write_all(if f == 0 { b"f\n" } else { b"a\n" })
747                                .unwrap();
748                            if f != 2 {
749                                bet.write_all(b"f\n").unwrap();
750                            }
751                            // This test diff is intentionally reversed.
752                            // We want it to turn the alef into bet.
753                            let diff = diff(&alef, "a/alefr", &bet, "target/alefr", 2);
754                            File::create("target/abr.diff")
755                                .unwrap()
756                                .write_all(&diff)
757                                .unwrap();
758                            let mut fa = File::create("target/alefr").unwrap();
759                            fa.write_all(&alef[..]).unwrap();
760                            let mut fb = File::create("target/betr").unwrap();
761                            fb.write_all(&bet[..]).unwrap();
762                            let _ = fa;
763                            let _ = fb;
764                            let output = Command::new("patch")
765                                .arg("-p0")
766                                .stdin(File::open("target/abr.diff").unwrap())
767                                .output()
768                                .unwrap();
769                            if !output.status.success() {
770                                panic!("{:?}", output);
771                            }
772                            //println!("{}", String::from_utf8_lossy(&output.stdout));
773                            //println!("{}", String::from_utf8_lossy(&output.stderr));
774                            let alef = fs::read("target/alefr").unwrap();
775                            assert_eq!(alef, bet);
776                        }
777                    }
778                }
779            }
780        }
781    }
782}