1use std::collections::VecDeque;
7use std::io::Write;
8
9use crate::params::Params;
10use crate::utils::do_write_line;
11use crate::utils::get_modification_time;
12
13#[derive(Debug, PartialEq)]
14pub enum DiffLine {
15 Context(Vec<u8>),
16 Change(Vec<u8>),
17 Add(Vec<u8>),
18}
19
20#[derive(Debug, PartialEq)]
21struct Mismatch {
22 pub line_number_expected: usize,
23 pub line_number_actual: usize,
24 pub expected: Vec<DiffLine>,
25 pub actual: Vec<DiffLine>,
26 pub expected_missing_nl: bool,
27 pub actual_missing_nl: bool,
28 pub expected_all_context: bool,
29 pub actual_all_context: bool,
30}
31
32impl Mismatch {
33 fn new(line_number_expected: usize, line_number_actual: usize) -> Mismatch {
34 Mismatch {
35 line_number_expected,
36 line_number_actual,
37 expected: Vec::new(),
38 actual: Vec::new(),
39 expected_missing_nl: false,
40 actual_missing_nl: false,
41 expected_all_context: false,
42 actual_all_context: false,
43 }
44 }
45}
46
47fn make_diff(
49 expected: &[u8],
50 actual: &[u8],
51 context_size: usize,
52 stop_early: bool,
53) -> Vec<Mismatch> {
54 let mut line_number_expected = 1;
55 let mut line_number_actual = 1;
56 let mut context_queue: VecDeque<&[u8]> = VecDeque::with_capacity(context_size);
57 let mut lines_since_mismatch = context_size + 1;
58 let mut results = Vec::new();
59 let mut mismatch = Mismatch::new(0, 0);
60
61 let mut expected_lines: Vec<&[u8]> = expected.split(|&c| c == b'\n').collect();
62 let mut actual_lines: Vec<&[u8]> = actual.split(|&c| c == b'\n').collect();
63
64 debug_assert_eq!(b"".split(|&c| c == b'\n').count(), 1);
65 let expected_lines_count = expected_lines.len() - 1;
67 let actual_lines_count = actual_lines.len() - 1;
68
69 if expected_lines.last() == Some(&&b""[..]) {
70 expected_lines.pop();
71 }
72
73 if actual_lines.last() == Some(&&b""[..]) {
74 actual_lines.pop();
75 }
76
77 let mut expected_lines_change_idx: usize = !0;
79
80 for result in diff::slice(&expected_lines, &actual_lines) {
81 match result {
82 diff::Result::Left(str) => {
83 if lines_since_mismatch > context_size && lines_since_mismatch > 0 {
84 results.push(mismatch);
85 mismatch = Mismatch::new(
86 line_number_expected - context_queue.len(),
87 line_number_actual - context_queue.len(),
88 );
89 }
90
91 while let Some(line) = context_queue.pop_front() {
92 mismatch.expected.push(DiffLine::Context(line.to_vec()));
93 mismatch.actual.push(DiffLine::Context(line.to_vec()));
94 }
95
96 expected_lines_change_idx = mismatch.expected.len();
97 mismatch.expected.push(DiffLine::Add(str.to_vec()));
98 if line_number_expected > expected_lines_count {
99 mismatch.expected_missing_nl = true;
100 }
101 line_number_expected += 1;
102 lines_since_mismatch = 0;
103 }
104 diff::Result::Right(str) => {
105 if lines_since_mismatch > context_size && lines_since_mismatch > 0 {
106 results.push(mismatch);
107 mismatch = Mismatch::new(
108 line_number_expected - context_queue.len(),
109 line_number_actual - context_queue.len(),
110 );
111 expected_lines_change_idx = !0;
112 }
113
114 while let Some(line) = context_queue.pop_front() {
115 mismatch.expected.push(DiffLine::Context(line.to_vec()));
116 mismatch.actual.push(DiffLine::Context(line.to_vec()));
117 }
118
119 if let Some(DiffLine::Add(content)) =
120 mismatch.expected.get_mut(expected_lines_change_idx)
121 {
122 let content = std::mem::take(content);
123 mismatch.expected[expected_lines_change_idx] = DiffLine::Change(content);
124 expected_lines_change_idx = expected_lines_change_idx.wrapping_sub(1); mismatch.actual.push(DiffLine::Change(str.to_vec()));
126 } else {
127 mismatch.actual.push(DiffLine::Add(str.to_vec()));
128 }
129 if line_number_actual > actual_lines_count {
130 mismatch.actual_missing_nl = true;
131 }
132 line_number_actual += 1;
133 lines_since_mismatch = 0;
134 }
135 diff::Result::Both(str, _) => {
136 expected_lines_change_idx = !0;
137 if (line_number_actual > actual_lines_count)
139 && (line_number_expected > expected_lines_count)
140 {
141 if context_queue.len() < context_size {
142 while let Some(line) = context_queue.pop_front() {
143 mismatch.expected.push(DiffLine::Context(line.to_vec()));
144 mismatch.actual.push(DiffLine::Context(line.to_vec()));
145 }
146 if lines_since_mismatch < context_size {
147 mismatch.expected.push(DiffLine::Context(str.to_vec()));
148 mismatch.actual.push(DiffLine::Context(str.to_vec()));
149 mismatch.expected_missing_nl = true;
150 mismatch.actual_missing_nl = true;
151 }
152 }
153 lines_since_mismatch = 0;
154 } else if line_number_actual > actual_lines_count {
155 if lines_since_mismatch >= context_size && lines_since_mismatch > 0 {
156 results.push(mismatch);
157 mismatch = Mismatch::new(
158 line_number_expected - context_queue.len(),
159 line_number_actual - context_queue.len(),
160 );
161 }
162 while let Some(line) = context_queue.pop_front() {
163 mismatch.expected.push(DiffLine::Context(line.to_vec()));
164 mismatch.actual.push(DiffLine::Context(line.to_vec()));
165 }
166 mismatch.expected.push(DiffLine::Change(str.to_vec()));
167 mismatch.actual.push(DiffLine::Change(str.to_vec()));
168 mismatch.actual_missing_nl = true;
169 lines_since_mismatch = 0;
170 } else if line_number_expected > expected_lines_count {
171 if lines_since_mismatch >= context_size && lines_since_mismatch > 0 {
172 results.push(mismatch);
173 mismatch = Mismatch::new(
174 line_number_expected - context_queue.len(),
175 line_number_actual - context_queue.len(),
176 );
177 }
178 while let Some(line) = context_queue.pop_front() {
179 mismatch.expected.push(DiffLine::Context(line.to_vec()));
180 mismatch.actual.push(DiffLine::Context(line.to_vec()));
181 }
182 mismatch.expected.push(DiffLine::Change(str.to_vec()));
183 mismatch.expected_missing_nl = true;
184 mismatch.actual.push(DiffLine::Change(str.to_vec()));
185 lines_since_mismatch = 0;
186 } else {
187 debug_assert!(context_queue.len() <= context_size);
188 if context_queue.len() >= context_size {
189 let _ = context_queue.pop_front();
190 }
191 if lines_since_mismatch < context_size {
192 mismatch.expected.push(DiffLine::Context(str.to_vec()));
193 mismatch.actual.push(DiffLine::Context(str.to_vec()));
194 } else if context_size > 0 {
195 context_queue.push_back(str);
196 }
197 lines_since_mismatch += 1;
198 }
199 line_number_expected += 1;
200 line_number_actual += 1;
201 }
202 }
203 if stop_early && !results.is_empty() {
204 return results;
206 }
207 }
208
209 results.push(mismatch);
210 results.remove(0);
211
212 if results.is_empty() && expected_lines_count != actual_lines_count {
213 let mut mismatch = Mismatch::new(expected_lines.len(), actual_lines.len());
214 if expected_lines_count != expected_lines.len() {
216 mismatch.expected.push(DiffLine::Change(
217 expected_lines
218 .pop()
219 .expect("can't be empty; produced by split()")
220 .to_vec(),
221 ));
222 mismatch.expected_missing_nl = true;
223 mismatch.actual.push(DiffLine::Change(
224 actual_lines
225 .pop()
226 .expect("can't be empty; produced by split()")
227 .to_vec(),
228 ));
229 results.push(mismatch);
230 } else if actual_lines_count != actual_lines.len() {
231 mismatch.expected.push(DiffLine::Change(
232 expected_lines
233 .pop()
234 .expect("can't be empty; produced by split()")
235 .to_vec(),
236 ));
237 mismatch.actual.push(DiffLine::Change(
238 actual_lines
239 .pop()
240 .expect("can't be empty; produced by split()")
241 .to_vec(),
242 ));
243 mismatch.actual_missing_nl = true;
244 results.push(mismatch);
245 }
246 }
247
248 for mismatch in &mut results {
250 if !mismatch
251 .expected
252 .iter()
253 .any(|x| !matches!(&x, DiffLine::Context(_)))
254 {
255 mismatch.expected_all_context = true;
256 }
257 if !mismatch
258 .actual
259 .iter()
260 .any(|x| !matches!(&x, DiffLine::Context(_)))
261 {
262 mismatch.actual_all_context = true;
263 }
264 }
265
266 results
267}
268
269#[must_use]
270pub fn diff(expected: &[u8], actual: &[u8], params: &Params) -> Vec<u8> {
271 let from_modified_time = get_modification_time(¶ms.from.to_string_lossy());
272 let to_modified_time = get_modification_time(¶ms.to.to_string_lossy());
273 let mut output = format!(
274 "*** {0}\t{1}\n--- {2}\t{3}\n",
275 params.from.to_string_lossy(),
276 from_modified_time,
277 params.to.to_string_lossy(),
278 to_modified_time
279 )
280 .into_bytes();
281 let diff_results = make_diff(expected, actual, params.context_count, params.brief);
282 if diff_results.is_empty() {
283 return Vec::new();
284 }
285 if params.brief {
286 return output;
287 }
288 for result in diff_results {
289 let mut line_number_expected = result.line_number_expected;
290 let mut line_number_actual = result.line_number_actual;
291 let mut expected_count = result.expected.len();
292 let mut actual_count = result.actual.len();
293 if expected_count == 0 {
294 line_number_expected -= 1;
295 expected_count = 1;
296 }
297 if actual_count == 0 {
298 line_number_actual -= 1;
299 actual_count = 1;
300 }
301 let end_line_number_expected = expected_count + line_number_expected - 1;
302 let end_line_number_actual = actual_count + line_number_actual - 1;
303 let exp_start = if end_line_number_expected == line_number_expected {
304 String::new()
305 } else {
306 format!("{line_number_expected},")
307 };
308 let act_start = if end_line_number_actual == line_number_actual {
309 String::new()
310 } else {
311 format!("{line_number_actual},")
312 };
313 writeln!(
314 output,
315 "***************\n*** {exp_start}{end_line_number_expected} ****"
316 )
317 .expect("write to Vec is infallible");
318 if !result.expected_all_context {
319 for line in result.expected {
320 match line {
321 DiffLine::Context(e) => {
322 write!(output, " ").expect("write to Vec is infallible");
323 do_write_line(&mut output, &e, params.expand_tabs, params.tabsize)
324 .expect("write to Vec is infallible");
325 writeln!(output).unwrap();
326 }
327 DiffLine::Change(e) => {
328 write!(output, "! ").expect("write to Vec is infallible");
329 do_write_line(&mut output, &e, params.expand_tabs, params.tabsize)
330 .expect("write to Vec is infallible");
331 writeln!(output).unwrap();
332 }
333 DiffLine::Add(e) => {
334 write!(output, "- ").expect("write to Vec is infallible");
335 do_write_line(&mut output, &e, params.expand_tabs, params.tabsize)
336 .expect("write to Vec is infallible");
337 writeln!(output).unwrap();
338 }
339 }
340 }
341 if result.expected_missing_nl {
342 writeln!(output, r"\ No newline at end of file")
343 .expect("write to Vec is infallible");
344 }
345 }
346 writeln!(output, "--- {act_start}{end_line_number_actual} ----")
347 .expect("write to Vec is infallible");
348 if !result.actual_all_context {
349 for line in result.actual {
350 match line {
351 DiffLine::Context(e) => {
352 write!(output, " ").expect("write to Vec is infallible");
353 do_write_line(&mut output, &e, params.expand_tabs, params.tabsize)
354 .expect("write to Vec is infallible");
355 writeln!(output).unwrap();
356 }
357 DiffLine::Change(e) => {
358 write!(output, "! ").expect("write to Vec is infallible");
359 do_write_line(&mut output, &e, params.expand_tabs, params.tabsize)
360 .expect("write to Vec is infallible");
361 writeln!(output).unwrap();
362 }
363 DiffLine::Add(e) => {
364 write!(output, "+ ").expect("write to Vec is infallible");
365 do_write_line(&mut output, &e, params.expand_tabs, params.tabsize)
366 .expect("write to Vec is infallible");
367 writeln!(output).unwrap();
368 }
369 }
370 }
371 if result.actual_missing_nl {
372 writeln!(output, r"\ No newline at end of file")
373 .expect("write to Vec is infallible");
374 }
375 }
376 }
377 output
378}
379
380#[cfg(test)]
381mod tests {
382 use super::*;
383 use pretty_assertions::assert_eq;
384 #[test]
385 fn test_permutations() {
386 let target = "target/context-diff/";
388 let _ = std::fs::create_dir(target);
389 for &a in &[0, 1, 2] {
390 for &b in &[0, 1, 2] {
391 for &c in &[0, 1, 2] {
392 for &d in &[0, 1, 2] {
393 for &e in &[0, 1, 2] {
394 for &f in &[0, 1, 2] {
395 use std::fs::{self, File};
396 use std::io::Write;
397 use std::process::Command;
398 let mut alef = Vec::new();
399 let mut bet = Vec::new();
400 alef.write_all(if a == 0 { b"a\n" } else { b"b\n" })
401 .unwrap();
402 if a != 2 {
403 bet.write_all(b"b\n").unwrap();
404 }
405 alef.write_all(if b == 0 { b"c\n" } else { b"d\n" })
406 .unwrap();
407 if b != 2 {
408 bet.write_all(b"d\n").unwrap();
409 }
410 alef.write_all(if c == 0 { b"e\n" } else { b"f\n" })
411 .unwrap();
412 if c != 2 {
413 bet.write_all(b"f\n").unwrap();
414 }
415 alef.write_all(if d == 0 { b"g\n" } else { b"h\n" })
416 .unwrap();
417 if d != 2 {
418 bet.write_all(b"h\n").unwrap();
419 }
420 alef.write_all(if e == 0 { b"i\n" } else { b"j\n" })
421 .unwrap();
422 if e != 2 {
423 bet.write_all(b"j\n").unwrap();
424 }
425 alef.write_all(if f == 0 { b"k\n" } else { b"l\n" })
426 .unwrap();
427 if f != 2 {
428 bet.write_all(b"l\n").unwrap();
429 }
430 let diff = diff(
433 &alef,
434 &bet,
435 &Params {
436 from: "a/alef".into(),
437 to: (&format!("{target}/alef")).into(),
438 context_count: 2,
439 ..Default::default()
440 },
441 );
442 File::create(format!("{target}/ab.diff"))
443 .unwrap()
444 .write_all(&diff)
445 .unwrap();
446 let mut fa = File::create(format!("{target}/alef")).unwrap();
447 fa.write_all(&alef[..]).unwrap();
448 let mut fb = File::create(format!("{target}/bet")).unwrap();
449 fb.write_all(&bet[..]).unwrap();
450 let _ = fa;
451 let _ = fb;
452 let output = Command::new("patch")
453 .arg("-p0")
454 .arg("--context")
455 .stdin(File::open(format!("{target}/ab.diff")).unwrap())
456 .output()
457 .unwrap();
458 assert!(output.status.success(), "{output:?}");
459 let alef = fs::read(format!("{target}/alef")).unwrap();
462 assert_eq!(alef, bet);
463 }
464 }
465 }
466 }
467 }
468 }
469 }
470
471 #[test]
472 fn test_permutations_empty_lines() {
473 let target = "target/context-diff/";
474 let _ = std::fs::create_dir(target);
476 for &a in &[0, 1, 2] {
477 for &b in &[0, 1, 2] {
478 for &c in &[0, 1, 2] {
479 for &d in &[0, 1, 2] {
480 for &e in &[0, 1, 2] {
481 for &f in &[0, 1, 2] {
482 use std::fs::{self, File};
483 use std::io::Write;
484 use std::process::Command;
485 let mut alef = Vec::new();
486 let mut bet = Vec::new();
487 alef.write_all(if a == 0 { b"\n" } else { b"b\n" }).unwrap();
488 if a != 2 {
489 bet.write_all(b"b\n").unwrap();
490 }
491 alef.write_all(if b == 0 { b"\n" } else { b"d\n" }).unwrap();
492 if b != 2 {
493 bet.write_all(b"d\n").unwrap();
494 }
495 alef.write_all(if c == 0 { b"\n" } else { b"f\n" }).unwrap();
496 if c != 2 {
497 bet.write_all(b"f\n").unwrap();
498 }
499 alef.write_all(if d == 0 { b"\n" } else { b"h\n" }).unwrap();
500 if d != 2 {
501 bet.write_all(b"h\n").unwrap();
502 }
503 alef.write_all(if e == 0 { b"\n" } else { b"j\n" }).unwrap();
504 if e != 2 {
505 bet.write_all(b"j\n").unwrap();
506 }
507 alef.write_all(if f == 0 { b"\n" } else { b"l\n" }).unwrap();
508 if f != 2 {
509 bet.write_all(b"l\n").unwrap();
510 }
511 let diff = diff(
514 &alef,
515 &bet,
516 &Params {
517 from: "a/alef_".into(),
518 to: (&format!("{target}/alef_")).into(),
519 context_count: 2,
520 ..Default::default()
521 },
522 );
523 File::create(format!("{target}/ab_.diff"))
524 .unwrap()
525 .write_all(&diff)
526 .unwrap();
527 let mut fa = File::create(format!("{target}/alef_")).unwrap();
528 fa.write_all(&alef[..]).unwrap();
529 let mut fb = File::create(format!("{target}/bet_")).unwrap();
530 fb.write_all(&bet[..]).unwrap();
531 let _ = fa;
532 let _ = fb;
533 let output = Command::new("patch")
534 .arg("-p0")
535 .arg("--context")
536 .stdin(File::open(format!("{target}/ab_.diff")).unwrap())
537 .output()
538 .unwrap();
539 assert!(output.status.success(), "{output:?}");
540 let alef = fs::read(format!("{target}/alef_")).unwrap();
543 assert_eq!(alef, bet);
544 }
545 }
546 }
547 }
548 }
549 }
550 }
551
552 #[test]
553 fn test_permutations_missing_lines() {
554 let target = "target/context-diff/";
555 let _ = std::fs::create_dir(target);
557 for &a in &[0, 1, 2] {
558 for &b in &[0, 1, 2] {
559 for &c in &[0, 1, 2] {
560 for &d in &[0, 1, 2] {
561 for &e in &[0, 1, 2] {
562 for &f in &[0, 1, 2] {
563 use std::fs::{self, File};
564 use std::io::Write;
565 use std::process::Command;
566 let mut alef = Vec::new();
567 let mut bet = Vec::new();
568 alef.write_all(if a == 0 { b"a\n" } else { b"" }).unwrap();
569 if a != 2 {
570 bet.write_all(b"b\n").unwrap();
571 }
572 alef.write_all(if b == 0 { b"c\n" } else { b"" }).unwrap();
573 if b != 2 {
574 bet.write_all(b"d\n").unwrap();
575 }
576 alef.write_all(if c == 0 { b"e\n" } else { b"" }).unwrap();
577 if c != 2 {
578 bet.write_all(b"f\n").unwrap();
579 }
580 alef.write_all(if d == 0 { b"g\n" } else { b"" }).unwrap();
581 if d != 2 {
582 bet.write_all(b"h\n").unwrap();
583 }
584 alef.write_all(if e == 0 { b"i\n" } else { b"" }).unwrap();
585 if e != 2 {
586 bet.write_all(b"j\n").unwrap();
587 }
588 alef.write_all(if f == 0 { b"k\n" } else { b"" }).unwrap();
589 if f != 2 {
590 bet.write_all(b"l\n").unwrap();
591 }
592 if alef.is_empty() && bet.is_empty() {
593 continue;
594 };
595 let diff = diff(
598 &alef,
599 &bet,
600 &Params {
601 from: "a/alefx".into(),
602 to: (&format!("{target}/alefx")).into(),
603 context_count: 2,
604 ..Default::default()
605 },
606 );
607 File::create(format!("{target}/abx.diff"))
608 .unwrap()
609 .write_all(&diff)
610 .unwrap();
611 let mut fa = File::create(format!("{target}/alefx")).unwrap();
612 fa.write_all(&alef[..]).unwrap();
613 let mut fb = File::create(format!("{target}/betx")).unwrap();
614 fb.write_all(&bet[..]).unwrap();
615 let _ = fa;
616 let _ = fb;
617 let output = Command::new("patch")
618 .arg("-p0")
619 .arg("--context")
620 .stdin(File::open(format!("{target}/abx.diff")).unwrap())
621 .output()
622 .unwrap();
623 assert!(output.status.success(), "{output:?}");
624 let alef = fs::read(format!("{target}/alefx")).unwrap();
627 assert_eq!(alef, bet);
628 }
629 }
630 }
631 }
632 }
633 }
634 }
635
636 #[test]
637 fn test_permutations_reverse() {
638 let target = "target/context-diff/";
639 let _ = std::fs::create_dir(target);
641 for &a in &[0, 1, 2] {
642 for &b in &[0, 1, 2] {
643 for &c in &[0, 1, 2] {
644 for &d in &[0, 1, 2] {
645 for &e in &[0, 1, 2] {
646 for &f in &[0, 1, 2] {
647 use std::fs::{self, File};
648 use std::io::Write;
649 use std::process::Command;
650 let mut alef = Vec::new();
651 let mut bet = Vec::new();
652 alef.write_all(if a == 0 { b"a\n" } else { b"f\n" })
653 .unwrap();
654 if a != 2 {
655 bet.write_all(b"a\n").unwrap();
656 }
657 alef.write_all(if b == 0 { b"b\n" } else { b"e\n" })
658 .unwrap();
659 if b != 2 {
660 bet.write_all(b"b\n").unwrap();
661 }
662 alef.write_all(if c == 0 { b"c\n" } else { b"d\n" })
663 .unwrap();
664 if c != 2 {
665 bet.write_all(b"c\n").unwrap();
666 }
667 alef.write_all(if d == 0 { b"d\n" } else { b"c\n" })
668 .unwrap();
669 if d != 2 {
670 bet.write_all(b"d\n").unwrap();
671 }
672 alef.write_all(if e == 0 { b"e\n" } else { b"b\n" })
673 .unwrap();
674 if e != 2 {
675 bet.write_all(b"e\n").unwrap();
676 }
677 alef.write_all(if f == 0 { b"f\n" } else { b"a\n" })
678 .unwrap();
679 if f != 2 {
680 bet.write_all(b"f\n").unwrap();
681 }
682 let diff = diff(
685 &alef,
686 &bet,
687 &Params {
688 from: "a/alefr".into(),
689 to: (&format!("{target}/alefr")).into(),
690 context_count: 2,
691 ..Default::default()
692 },
693 );
694 File::create(format!("{target}/abr.diff"))
695 .unwrap()
696 .write_all(&diff)
697 .unwrap();
698 let mut fa = File::create(format!("{target}/alefr")).unwrap();
699 fa.write_all(&alef[..]).unwrap();
700 let mut fb = File::create(format!("{target}/betr")).unwrap();
701 fb.write_all(&bet[..]).unwrap();
702 let _ = fa;
703 let _ = fb;
704 let output = Command::new("patch")
705 .arg("-p0")
706 .arg("--context")
707 .stdin(File::open(format!("{target}/abr.diff")).unwrap())
708 .output()
709 .unwrap();
710 assert!(output.status.success(), "{output:?}");
711 let alef = fs::read(format!("{target}/alefr")).unwrap();
714 assert_eq!(alef, bet);
715 }
716 }
717 }
718 }
719 }
720 }
721 }
722
723 #[test]
724 fn test_stop_early() {
725 use crate::assert_diff_eq;
726
727 let from_filename = "foo";
728 let from = ["a", "b", "c", ""].join("\n");
729 let to_filename = "bar";
730 let to = ["a", "d", "c", ""].join("\n");
731
732 let diff_full = diff(
733 from.as_bytes(),
734 to.as_bytes(),
735 &Params {
736 from: from_filename.into(),
737 to: to_filename.into(),
738 ..Default::default()
739 },
740 );
741
742 let expected_full = [
743 "*** foo\tTIMESTAMP",
744 "--- bar\tTIMESTAMP",
745 "***************",
746 "*** 1,3 ****",
747 " a",
748 "! b",
749 " c",
750 "--- 1,3 ----",
751 " a",
752 "! d",
753 " c",
754 "",
755 ]
756 .join("\n");
757 assert_diff_eq!(diff_full, expected_full);
758
759 let diff_brief = diff(
760 from.as_bytes(),
761 to.as_bytes(),
762 &Params {
763 from: from_filename.into(),
764 to: to_filename.into(),
765 brief: true,
766 ..Default::default()
767 },
768 );
769
770 let expected_brief = ["*** foo\tTIMESTAMP", "--- bar\tTIMESTAMP", ""].join("\n");
771 assert_diff_eq!(diff_brief, expected_brief);
772
773 let nodiff_full = diff(
774 from.as_bytes(),
775 from.as_bytes(),
776 &Params {
777 from: from_filename.into(),
778 to: to_filename.into(),
779 ..Default::default()
780 },
781 );
782 assert!(nodiff_full.is_empty());
783
784 let nodiff_brief = diff(
785 from.as_bytes(),
786 from.as_bytes(),
787 &Params {
788 from: from_filename.into(),
789 to: to_filename.into(),
790 brief: true,
791 ..Default::default()
792 },
793 );
794 assert!(nodiff_brief.is_empty());
795 }
796}