Fix missingnl behavior on normal-diff

This commit is contained in:
Michael Howell
2021-04-03 09:42:35 -07:00
parent a4f7642d7a
commit da05a5254b
4 changed files with 225 additions and 12 deletions
+4
View File
@@ -0,0 +1,4 @@
target
corpus
artifacts
+27
View File
@@ -0,0 +1,27 @@
[package]
name = "normal-diff-fuzz"
version = "0.0.0"
authors = ["Automatically generated"]
publish = false
edition = "2018"
[package.metadata]
cargo-fuzz = true
[dependencies]
libfuzzer-sys = "0.3"
[dependencies.normal-diff]
path = ".."
# Prevent this from interfering with workspaces
[workspace]
members = ["."]
[[bin]]
name = "fuzz_patch"
path = "fuzz_targets/fuzz_patch.rs"
test = false
doc = false
@@ -0,0 +1,57 @@
#![no_main]
#[macro_use] extern crate libfuzzer_sys;
extern crate normal_diff;
use std::fs::{self, File};
use std::io::Write;
use std::process::Command;
fuzz_target!(|x: (Vec<u8>, Vec<u8>)| {
let (from, to) = x;
/*if let Ok(s) = String::from_utf8(from.clone()) {
if !s.is_ascii() { return }
if s.find(|x| x < ' ' && x != '\n').is_some() { return }
} else {
return
}
if let Ok(s) = String::from_utf8(to.clone()) {
if !s.is_ascii() { return }
if s.find(|x| x < ' ' && x != '\n').is_some() { return }
} else {
return
}*/
let diff = normal_diff::diff(&from, &to);
File::create("target/fuzz.file.original")
.unwrap()
.write_all(&from)
.unwrap();
File::create("target/fuzz.file.expected")
.unwrap()
.write_all(&to)
.unwrap();
File::create("target/fuzz.file")
.unwrap()
.write_all(&from)
.unwrap();
File::create("target/fuzz.diff")
.unwrap()
.write_all(&diff)
.unwrap();
let output = Command::new("patch")
.arg("-p0")
.arg("--binary")
.arg("--fuzz=0")
.arg("--normal")
.arg("target/fuzz.file")
.stdin(File::open("target/fuzz.diff").unwrap())
.output()
.unwrap();
if !output.status.success() {
panic!("STDOUT:\n{}\nSTDERR:\n{}", String::from_utf8_lossy(&output.stdout), String::from_utf8_lossy(&output.stderr));
}
let result = fs::read("target/fuzz.file").unwrap();
if result != to {
panic!("STDOUT:\n{}\nSTDERR:\n{}", String::from_utf8_lossy(&output.stdout), String::from_utf8_lossy(&output.stderr));
}
});
+137 -12
View File
@@ -6,6 +6,8 @@ struct Mismatch {
pub line_number_actual: usize,
pub expected: Vec<Vec<u8>>,
pub actual: Vec<Vec<u8>>,
pub expected_missing_nl: bool,
pub actual_missing_nl: bool,
}
impl Mismatch {
@@ -15,6 +17,8 @@ impl Mismatch {
line_number_actual,
expected: Vec::new(),
actual: Vec::new(),
expected_missing_nl: false,
actual_missing_nl: false,
}
}
}
@@ -31,8 +35,8 @@ fn make_diff(expected: &[u8], actual: &[u8]) -> Vec<Mismatch> {
debug_assert_eq!(b"".split(|&c| c == b'\n').count(), 1);
// ^ means that underflow here is impossible
let _expected_lines_count = expected_lines.len() - 1;
let _actual_lines_count = actual_lines.len() - 1;
let expected_lines_count = expected_lines.len() - 1;
let actual_lines_count = actual_lines.len() - 1;
if expected_lines.last() == Some(&&b""[..]) {
expected_lines.pop();
@@ -45,26 +49,46 @@ fn make_diff(expected: &[u8], actual: &[u8]) -> Vec<Mismatch> {
for result in diff::slice(&expected_lines, &actual_lines) {
match result {
diff::Result::Left(str) => {
if mismatch.actual.len() != 0 {
if mismatch.actual.len() != 0 && !mismatch.actual_missing_nl {
results.push(mismatch);
mismatch = Mismatch::new(line_number_expected, line_number_actual);
}
mismatch.expected.push(str.to_vec());
mismatch.expected_missing_nl = line_number_expected > expected_lines_count;
line_number_expected += 1;
}
diff::Result::Right(str) => {
mismatch.actual.push(str.to_vec());
mismatch.actual_missing_nl = line_number_actual > actual_lines_count;
line_number_actual += 1;
}
diff::Result::Both(_str, _) => {
line_number_expected += 1;
line_number_actual += 1;
if mismatch.actual.len() != 0 || mismatch.expected.len() != 0 {
results.push(mismatch);
mismatch = Mismatch::new(line_number_expected, line_number_actual);
} else {
mismatch.line_number_expected = line_number_expected;
mismatch.line_number_actual = line_number_actual;
diff::Result::Both(str, _) => {
match (line_number_expected > expected_lines_count, line_number_actual > actual_lines_count) {
(true, false) => {
line_number_expected += 1;
line_number_actual += 1;
mismatch.expected.push(str.to_vec());
mismatch.expected_missing_nl = true;
mismatch.actual.push(str.to_vec());
}
(false, true) => {
line_number_expected += 1;
line_number_actual += 1;
mismatch.actual.push(str.to_vec());
mismatch.actual_missing_nl = true;
mismatch.expected.push(str.to_vec());
}
(true, true) | (false, false) => {
line_number_expected += 1;
line_number_actual += 1;
if mismatch.actual.len() != 0 || mismatch.expected.len() != 0 {
results.push(mismatch);
mismatch = Mismatch::new(line_number_expected, line_number_actual);
} else {
mismatch.line_number_expected = line_number_expected;
mismatch.line_number_actual = line_number_actual;
}
},
}
}
}
@@ -118,6 +142,9 @@ pub fn diff(expected: &[u8], actual: &[u8]) -> Vec<u8> {
output.write_all(expected).unwrap();
writeln!(&mut output, "").unwrap();
}
if result.expected_missing_nl {
writeln!(&mut output, r"\ No newline at end of file").unwrap();
}
if expected_count != 0 && actual_count != 0 {
writeln!(&mut output, "---").unwrap();
}
@@ -126,6 +153,9 @@ pub fn diff(expected: &[u8], actual: &[u8]) -> Vec<u8> {
output.write_all(actual).unwrap();
writeln!(&mut output, "").unwrap();
}
if result.actual_missing_nl {
writeln!(&mut output, r"\ No newline at end of file").unwrap();
}
}
output
}
@@ -209,6 +239,101 @@ fn test_permutations() {
}
}
#[test]
fn test_permutations_missing_line_ending() {
// test all possible six-line files with missing newlines.
let _ = std::fs::create_dir("target");
for &a in &[0, 1, 2] {
for &b in &[0, 1, 2] {
for &c in &[0, 1, 2] {
for &d in &[0, 1, 2] {
for &e in &[0, 1, 2] {
for &f in &[0, 1, 2] {
for &g in &[0, 1, 2] {
use std::fs::{self, File};
use std::io::Write;
use std::process::Command;
let mut alef = Vec::new();
let mut bet = Vec::new();
alef.write_all(if a == 0 { b"a\n" } else { b"b\n" })
.unwrap();
if a != 2 {
bet.write_all(b"b\n").unwrap();
}
alef.write_all(if b == 0 { b"c\n" } else { b"d\n" })
.unwrap();
if b != 2 {
bet.write_all(b"d\n").unwrap();
}
alef.write_all(if c == 0 { b"e\n" } else { b"f\n" })
.unwrap();
if c != 2 {
bet.write_all(b"f\n").unwrap();
}
alef.write_all(if d == 0 { b"g\n" } else { b"h\n" })
.unwrap();
if d != 2 {
bet.write_all(b"h\n").unwrap();
}
alef.write_all(if e == 0 { b"i\n" } else { b"j\n" })
.unwrap();
if e != 2 {
bet.write_all(b"j\n").unwrap();
}
alef.write_all(if f == 0 { b"k\n" } else { b"l\n" })
.unwrap();
if f != 2 {
bet.write_all(b"l\n").unwrap();
}
match g {
0 => {
alef.pop();
}
1 => {
bet.pop();
}
2 => {
alef.pop();
bet.pop();
}
_ => unreachable!(),
}
// This test diff is intentionally reversed.
// We want it to turn the alef into bet.
let diff = diff(&alef, &bet);
File::create("target/abn.diff")
.unwrap()
.write_all(&diff)
.unwrap();
let mut fa = File::create("target/alefn").unwrap();
fa.write_all(&alef[..]).unwrap();
let mut fb = File::create("target/betn").unwrap();
fb.write_all(&bet[..]).unwrap();
let _ = fa;
let _ = fb;
let output = Command::new("patch")
.arg("-p0")
.arg("--normal")
.arg("target/alefn")
.stdin(File::open("target/abn.diff").unwrap())
.output()
.unwrap();
if !output.status.success() {
panic!("{:?}", output);
}
//println!("{}", String::from_utf8_lossy(&output.stdout));
//println!("{}", String::from_utf8_lossy(&output.stderr));
let alef = fs::read("target/alefn").unwrap();
assert_eq!(alef, bet);
}
}
}
}
}
}
}
}
#[test]
fn test_permutations_empty_lines() {
// test all possible six-line files with missing newlines.