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}
783