use super::error::ParsePatchErrorKind;
use super::parse::parse;
use super::parse::parse_bytes;
use alloc::format;
use alloc::string::ToString;
#[test]
fn trailing_garbage_after_complete_hunk() {
let s = "\
--- a/file.txt
+++ b/file.txt
@@ -1 +1 @@
-old line
+new line
this is trailing garbage
that should be ignored
";
let patch = parse(s).unwrap();
assert_eq!(patch.hunks().len(), 1);
assert_eq!(patch.hunks()[0].old_range().len(), 1);
assert_eq!(patch.hunks()[0].new_range().len(), 1);
}
#[test]
fn garbage_before_hunk_complete_fails() {
let s = "\
--- a/file.txt
+++ b/file.txt
@@ -1,3 +1,3 @@
-line 1
+LINE 1
garbage before hunk complete
line 3
";
assert_eq!(
parse(s).unwrap_err().kind,
ParsePatchErrorKind::UnexpectedHunkLine,
);
}
#[test]
fn git_headers_after_hunk_ignored() {
let s = "\
--- a/file.txt
+++ b/file.txt
@@ -1 +1 @@
-old
+new
diff --git a/other.txt b/other.txt
index 1234567..89abcdef 100644
";
let patch = parse(s).unwrap();
assert_eq!(patch.hunks().len(), 1);
}
#[test]
fn no_newline_at_eof_followed_by_trailing_garbage() {
let s = "\
--- a/file.html
+++ b/file.html
@@ -1,3 +1,3 @@
<div>
-<p>old</p>
+<p>new</p>
</div>
\\ No newline at end of file
diff --git a/other.html b/other.html
index 1234567..89abcdef 100644
";
let patch = parse(s).unwrap();
assert_eq!(patch.hunks().len(), 1);
assert_eq!(patch.hunks()[0].old_range().len(), 3);
assert_eq!(patch.hunks()[0].new_range().len(), 3);
}
#[test]
fn multi_hunk_with_trailing_garbage() {
let s = "\
--- a/file.txt
+++ b/file.txt
@@ -1 +1 @@
-a
+A
@@ -5 +5 @@
-b
+B
some trailing garbage
";
let patch = parse(s).unwrap();
assert_eq!(patch.hunks().len(), 2);
}
#[test]
fn garbage_between_hunks_rejects_orphaned_header() {
let s = "\
--- a/file.txt
+++ b/file.txt
@@ -1 +1 @@
-a
+A
not a hunk line
@@ -5 +5 @@
-b
+B
";
assert_eq!(
parse(s).unwrap_err().kind,
ParsePatchErrorKind::OrphanedHunkHeader,
);
}
#[test]
fn context_lines_counted_correctly() {
let s = "\
--- a/file.txt
+++ b/file.txt
@@ -1,4 +1,4 @@
context 1
-deleted
+inserted
context 2
context 3
trailing garbage
";
let patch = parse(s).unwrap();
assert_eq!(patch.hunks().len(), 1);
assert_eq!(patch.hunks()[0].old_range().len(), 4);
assert_eq!(patch.hunks()[0].new_range().len(), 4);
}
mod trailing_content {
use super::*;
#[test]
fn trailing_junk_allowed() {
let s = "\
--- a/file.txt
+++ b/file.txt
@@ -1 +1 @@
-old
+new
this is trailing garbage
";
let patch = parse(s).unwrap();
assert_eq!(patch.hunks().len(), 1);
}
#[test]
fn trailing_junk_allowed_bytes() {
let s = b"\
--- a/file.txt
+++ b/file.txt
@@ -1 +1 @@
-old
+new
this is trailing garbage
";
let patch = parse_bytes(&s[..]).unwrap();
assert_eq!(patch.hunks().len(), 1);
}
#[test]
fn orphaned_hunk_header_after_junk() {
let s = "\
--- a/file.txt
+++ b/file.txt
@@ -1 +1 @@
-a
+A
not a hunk line
@@ -5 +5 @@
-b
+B
";
assert_eq!(
parse(s).unwrap_err().kind,
ParsePatchErrorKind::OrphanedHunkHeader,
);
}
#[test]
fn no_junk_parses_normally() {
let s = "\
--- a/file.txt
+++ b/file.txt
@@ -1 +1 @@
-old
+new
";
let patch = parse(s).unwrap();
assert_eq!(patch.hunks().len(), 1);
}
#[test]
fn multi_hunk_no_junk() {
let s = "\
--- a/file.txt
+++ b/file.txt
@@ -1 +1 @@
-a
+A
@@ -5 +5 @@
-b
+B
";
let patch = parse(s).unwrap();
assert_eq!(patch.hunks().len(), 2);
}
#[test]
fn garbage_before_hunk_complete_fails() {
let s = "\
--- a/file.txt
+++ b/file.txt
@@ -1,3 +1,3 @@
-line 1
+LINE 1
garbage before hunk complete
line 3
";
assert_eq!(
parse(s).unwrap_err().kind,
ParsePatchErrorKind::UnexpectedHunkLine,
);
}
}
#[test]
fn test_escaped_filenames() {
let s = "\
--- original
+++ modified
@@ -1,0 +1,1 @@
+Oathbringer
";
parse(s).unwrap();
parse_bytes(s.as_ref()).unwrap();
let s = "\
--- ori\"ginal
+++ modified
@@ -1,0 +1,1 @@
+Oathbringer
";
assert_eq!(
parse(s).unwrap_err().kind,
ParsePatchErrorKind::InvalidCharInUnquotedFilename,
);
parse_bytes(s.as_ref()).unwrap_err();
let s = "\
--- \"ori\\\"g\rinal\"
+++ modified
@@ -1,0 +1,1 @@
+Oathbringer
";
assert_eq!(
parse(s).unwrap_err().kind,
ParsePatchErrorKind::InvalidUnescapedChar,
);
parse_bytes(s.as_ref()).unwrap_err();
let s = r#"\
--- "ori\"g\tinal"
+++ "mo\000\t\r\n\\dified"
@@ -1,0 +1,1 @@
+Oathbringer
"#;
let p = parse(s).unwrap();
assert_eq!(p.original(), Some("ori\"g\tinal"));
assert_eq!(p.modified(), Some("mo\0\t\r\n\\dified"));
let b = parse_bytes(s.as_ref()).unwrap();
assert_eq!(b.original(), Some(&b"ori\"g\tinal"[..]));
assert_eq!(b.modified(), Some(&b"mo\0\t\r\n\\dified"[..]));
}
#[test]
fn escaped_filename_named_escapes() {
let cases: &[(&str, u8)] = &[
("\\a", b'\x07'),
("\\b", b'\x08'),
("\\f", b'\x0c'),
("\\v", b'\x0b'),
];
for (esc, expected_byte) in cases {
let s = format!(
"\
--- \"orig{esc}\"
+++ \"mod{esc}\"
@@ -1,0 +1,1 @@
+content
"
);
let p = parse(&s).unwrap();
let expected_orig = format!("orig{}", *expected_byte as char);
let expected_mod = format!("mod{}", *expected_byte as char);
assert_eq!(p.original(), Some(expected_orig.as_str()));
assert_eq!(p.modified(), Some(expected_mod.as_str()));
}
}
#[test]
fn escaped_filename_octal() {
let s = r#"\
--- "orig\033"
+++ "mod\033"
@@ -1,0 +1,1 @@
+content
"#;
let p = parse(s).unwrap();
assert_eq!(p.original(), Some("orig\x1b"));
assert_eq!(p.modified(), Some("mod\x1b"));
let s = r#"\
--- "orig\000"
+++ "mod\000"
@@ -1,0 +1,1 @@
+content
"#;
let p = parse(s).unwrap();
assert_eq!(p.original(), Some("orig\x00"));
assert_eq!(p.modified(), Some("mod\x00"));
let s = r#"\
--- "orig\177"
+++ "mod\177"
@@ -1,0 +1,1 @@
+content
"#;
let p = parse(s).unwrap();
assert_eq!(p.original(), Some("orig\x7f"));
assert_eq!(p.modified(), Some("mod\x7f"));
let s = r#"\
--- "orig\377"
+++ "mod\377"
@@ -1,0 +1,1 @@
+content
"#;
let b = parse_bytes(s.as_ref()).unwrap();
assert_eq!(b.original(), Some(&b"orig\xff"[..]));
assert_eq!(b.modified(), Some(&b"mod\xff"[..]));
let s = r#"\
--- "orig\477"
+++ "mod\477"
@@ -1,0 +1,1 @@
+content
"#;
assert_eq!(
parse(s).unwrap_err(),
ParsePatchErrorKind::InvalidEscapedChar.into(),
);
let s = r#"\
--- "orig\101"
+++ "mod\101"
@@ -1,0 +1,1 @@
+content
"#;
let p = parse(s).unwrap();
assert_eq!(p.original(), Some("origA"));
assert_eq!(p.modified(), Some("modA"));
let s = r#"\
--- "orig\277"
+++ "mod\277"
@@ -1,0 +1,1 @@
+content
"#;
let b = parse_bytes(s.as_ref()).unwrap();
assert_eq!(b.original(), Some(&b"orig\xbf"[..]));
assert_eq!(b.modified(), Some(&b"mod\xbf"[..]));
let s = r#"\
--- "orig\03"
+++ "mod\03"
@@ -1,0 +1,1 @@
+content
"#;
assert_eq!(
parse(s).unwrap_err().kind,
ParsePatchErrorKind::InvalidEscapedChar,
);
let s = r#"\
--- "orig\08x"
+++ "mod\08x"
@@ -1,0 +1,1 @@
+content
"#;
assert_eq!(
parse(s).unwrap_err().kind,
ParsePatchErrorKind::InvalidEscapedChar,
);
}
#[test]
fn test_missing_filename_header() {
let patch = r#"
@@ -1,11 +1,12 @@
diesel::table! {
users1 (id) {
- id -> Nullable<Integer>,
+ id -> Integer,
}
}
diesel::table! {
- users2 (id) {
- id -> Nullable<Integer>,
+ users2 (myid) {
+ #[sql_name = "id"]
+ myid -> Integer,
}
}
"#;
parse(patch).unwrap();
let s = "\
+++ modified
@@ -1,0 +1,1 @@
+Oathbringer
";
parse(s).unwrap();
let s = "\
--- original
@@ -1,0 +1,1 @@
+Oathbringer
";
parse(s).unwrap();
let s = "\
+++ modified
--- original
@@ -1,0 +1,1 @@
+Oathbringer
";
parse(s).unwrap();
let s = "\
--- original
--- modified
@@ -1,0 +1,1 @@
+Oathbringer
";
assert_eq!(
parse(s).unwrap_err().kind,
ParsePatchErrorKind::MultipleOriginalHeaders,
);
}
#[test]
fn adjacent_hunks_correctly_parse() {
let s = "\
--- original
+++ modified
@@ -110,7 +110,7 @@
--
I am afraid, however, that all I have known - that my story - will be forgotten.
I am afraid for the world that is to come.
-Afraid that my plans will fail. Afraid of a doom worse than the Deepness.
+Afraid that Alendi will fail. Afraid of a doom brought by the Deepness.
Alendi was never the Hero of Ages.
@@ -117,7 +117,7 @@
At best, I have amplified his virtues, creating a Hero where there was none.
-At worst, I fear that all we believe may have been corrupted.
+At worst, I fear that I have corrupted all we believe.
--
Alendi must not reach the Well of Ascension. He must not take the power for himself.
";
parse(s).unwrap();
}
#[test]
fn escaped_filename_roundtrip_named() {
let s = r#"\
--- "a\a\b\t\n\v\f\r\\\""
+++ "b\a\b\t\n\v\f\r\\\""
@@ -1,1 +1,1 @@
-old
+new
"#;
let p = parse(s).unwrap();
let formatted = p.to_string();
let p2 = parse(&formatted).unwrap();
assert_eq!(p.original(), p2.original());
assert_eq!(p.modified(), p2.modified());
let b = parse_bytes(s.as_ref()).unwrap();
let bytes = b.to_bytes();
let b2 = parse_bytes(&bytes).unwrap();
assert_eq!(b.original(), b2.original());
assert_eq!(b.modified(), b2.modified());
}
#[test]
fn escaped_filename_roundtrip_octal() {
let s = r#"\
--- "a\001\002\037\177"
+++ "b\001\002\037\177"
@@ -1,1 +1,1 @@
-old
+new
"#;
let p = parse(s).unwrap();
let formatted = p.to_string();
let p2 = parse(&formatted).unwrap();
assert_eq!(p.original(), p2.original());
assert_eq!(p.modified(), p2.modified());
let s = r#"\
--- "a\377"
+++ "b\377"
@@ -1,1 +1,1 @@
-old
+new
"#;
let b = parse_bytes(s.as_ref()).unwrap();
let bytes = b.to_bytes();
let b2 = parse_bytes(&bytes).unwrap();
assert_eq!(b.original(), b2.original());
assert_eq!(b.modified(), b2.modified());
}
#[test]
fn plain_filename_roundtrip() {
let s = "\
--- a/normal.txt
+++ b/normal.txt
@@ -1,1 +1,1 @@
-old
+new
";
let p = parse(s).unwrap();
let formatted = p.to_string();
assert!(!formatted.contains('"'));
let p2 = parse(&formatted).unwrap();
assert_eq!(p.original(), p2.original());
assert_eq!(p.modified(), p2.modified());
}
#[test]
fn non_utf8_escaped_filename_returns_error_on_str_parse() {
let s = r#"\
--- "a/foo\377"
+++ "b/foo\377"
@@ -1 +1 @@
-x
+y
"#;
assert_eq!(
parse(s).unwrap_err().kind,
ParsePatchErrorKind::InvalidUtf8Path,
);
}
#[test]
fn hunk_range_overflow() {
let s = format!(
"\
--- a/file.txt
+++ b/file.txt
@@ -{},1 +1 @@
-old
+new
",
usize::MAX,
);
assert_eq!(
crate::Patch::from_str(&s).unwrap_err().kind,
ParsePatchErrorKind::InvalidRange,
);
}
mod error_display {
use alloc::string::ToString;
use crate::Patch;
use crate::patch::error::ParsePatchErrorKind;
use snapbox::assert_data_eq;
use snapbox::str;
#[test]
fn invalid_hunk_header() {
let content = "\
--- a/file.rs
+++ b/file.rs
@@ invalid @@
-old
+new
";
let err = Patch::from_str(content).unwrap_err();
assert_data_eq!(
err.to_string(),
str!["error parsing patch at byte 28: unable to parse hunk header"]
);
}
#[test]
fn hunk_mismatch() {
let content = "\
--- a/file.rs
+++ b/file.rs
@@ -1,2 +1,2 @@
-only one line
+only one line
";
let err = Patch::from_str(content).unwrap_err();
assert_data_eq!(
err.to_string(),
str!["error parsing patch at byte 28: hunk header does not match hunk"]
);
}
#[test]
fn kind_preserved() {
let content = "\
--- a/file.rs
+++ b/file.rs
@@ invalid @@
-old
+new
";
let err = Patch::from_str(content).unwrap_err();
assert_eq!(err.kind, ParsePatchErrorKind::InvalidHunkHeader);
}
}