use super::*;
trait ParsePeekExt<'a, T> {
fn parse_peek(self, input: &'a [u8]) -> Result<(&'a [u8], T), ()>;
fn parse(self, input: &'a [u8]) -> Result<T, ()>;
}
impl<'a, T, F> ParsePeekExt<'a, T> for F
where
F: FnOnce(&mut &'a [u8]) -> Result<T, ()>,
{
fn parse_peek(self, mut input: &'a [u8]) -> Result<(&'a [u8], T), ()> {
let value = self(&mut input)?;
Ok((input, value))
}
fn parse(self, input: &'a [u8]) -> Result<T, ()> {
let (remaining, value) = self.parse_peek(input)?;
if remaining.is_empty() {
Ok(value)
} else {
Err(())
}
}
}
mod config {
use super::from_bytes;
use crate::parse::tests::util::{name_event, newline_event, value_event, whitespace_event};
use crate::parse::Event;
#[test]
fn key_value_before_first_section_is_accepted() {
let mut events = Vec::new();
from_bytes(b"a = b\n", &mut |event| events.push(event)).unwrap();
assert_eq!(
events,
vec![
name_event("a"),
whitespace_event(" "),
Event::KeyValueSeparator,
whitespace_event(" "),
value_event("b"),
newline_event(),
],
"Git accepts this and reports `a=b`, as git_parse_source() parses alphabetic keys even before any section"
);
}
}
mod section_headers {
use std::borrow::Cow;
use super::{section_header, ParsePeekExt};
use crate::parse::{
section,
tests::util::{fully_consumed, section_header as parsed_section_header},
};
#[test]
fn no_subsection() {
assert_eq!(
section_header.parse_peek(b"[hello]").unwrap(),
fully_consumed(parsed_section_header("hello", None)),
);
}
#[test]
fn empty_section_name_without_quoted_subsection_is_rejected() {
assert!(section_header.parse_peek(b"[]").is_err());
}
#[test]
fn modern_subsection() {
assert_eq!(
section_header.parse_peek(br#"[hello "world"]"#).unwrap(),
fully_consumed(parsed_section_header("hello", (" ", "world"))),
);
}
#[test]
fn empty_section_name_with_quoted_subsection_is_accepted() {
assert_eq!(
section_header.parse_peek(br#"[ "core"]"#).unwrap(),
fully_consumed(section::Header {
name: section::Name(Cow::Borrowed("".into())),
separator: Some(Cow::Borrowed(" ".into())),
subsection_name: Some(Cow::Borrowed("core".into())),
}),
"Git accepts this as an empty section name with `core` as subsection, yielding keys like `.core.bare`; gix does this too for compatibility"
);
}
#[test]
fn quoted_section_name_without_leading_space_is_rejected() {
assert!(
section_header.parse_peek(br#"["core"]"#).is_err(),
"Git rejects this as a bad config line"
);
}
#[test]
fn escaped_subsection() {
assert_eq!(
section_header.parse_peek(br#"[hello "foo\\bar\""]"#).unwrap(),
fully_consumed(parsed_section_header("hello", (" ", r#"foo\bar""#))),
);
}
#[test]
fn deprecated_subsection() {
assert_eq!(
section_header.parse_peek(br#"[hello.world]"#).unwrap(),
fully_consumed(parsed_section_header("hello", (".", "world")))
);
assert_eq!(
section_header.parse_peek(br#"[Hello.World]"#).unwrap(),
fully_consumed(parsed_section_header("Hello", (".", "World")))
);
}
#[test]
fn empty_legacy_subsection_name() {
assert_eq!(
section_header.parse_peek(br#"[hello-world.]"#).unwrap(),
fully_consumed(parsed_section_header("hello-world", (".", "")))
);
}
#[test]
fn empty_modern_subsection_name() {
assert_eq!(
section_header.parse_peek(br#"[hello ""]"#).unwrap(),
fully_consumed(parsed_section_header("hello", (" ", "")))
);
}
#[test]
fn backslashes_in_subsections_do_not_escape_newlines_or_tabs() {
assert_eq!(
section_header.parse_peek(br#"[hello "single \ \\ \t \n \0"]"#).unwrap(),
fully_consumed(parsed_section_header("hello", (" ", r"single \ t n 0")))
);
}
#[test]
fn newline_in_header() {
assert!(section_header.parse_peek(b"[hello\n]").is_err());
}
#[test]
fn newline_in_sub_section() {
assert!(section_header.parse_peek(b"[hello \"hello\n\"]").is_err());
}
#[test]
fn null_byt_in_sub_section() {
assert_eq!(
section_header.parse_peek(b"[hello \"hello\0\"]").unwrap(),
fully_consumed(parsed_section_header("hello", (" ", "hello\0"))),
"Git accepts this because get_extended_base_var() only rejects newline in quoted subsections"
);
}
#[test]
fn escaped_newline_in_sub_section() {
assert!(section_header.parse_peek(b"[hello \"hello\\\n\"]").is_err());
}
#[test]
fn eof_after_escape_in_sub_section() {
assert!(section_header.parse_peek(br#"[hello "hello\"#).is_err());
}
#[test]
fn missing_closing_bracket_after_quoted_subsection() {
assert!(section_header.parse_peek(br#"[hello "world""#).is_err());
}
#[test]
fn whitespace_before_closing_bracket_after_quoted_subsection() {
assert!(
section_header.parse_peek(br#"[hello "world" ]"#).is_err(),
"yes, Git fails here, too!"
);
}
#[test]
fn null_byte_in_header() {
assert!(section_header.parse_peek(b"[hello\0]").is_err());
}
#[test]
fn invalid_characters_in_section() {
assert!(section_header.parse_peek(b"[$]").is_err());
}
#[test]
fn invalid_characters_in_legacy_sub_section() {
assert!(section_header.parse_peek(b"[hello.$]").is_err());
assert!(section_header.parse_peek(b"[hello. world]").is_err());
}
#[test]
fn right_brace_in_subsection_name() {
assert_eq!(
section_header.parse_peek(br#"[hello "]"]"#).unwrap(),
fully_consumed(parsed_section_header("hello", (" ", "]")))
);
}
}
mod sub_section {
use std::borrow::Cow;
use super::{quoted_sub_section, ParsePeekExt};
#[test]
fn zero_copy_simple() {
let actual = quoted_sub_section.parse_peek(br#"name""#).unwrap().1;
assert_eq!(actual.as_ref(), "name");
assert!(matches!(actual, Cow::Borrowed(_)));
}
#[test]
fn escapes_need_allocation() {
let actual = quoted_sub_section.parse_peek(br#"\x\t\n\0\\\"""#).unwrap().1;
assert_eq!(actual.as_ref(), r#"xtn0\""#);
assert!(matches!(actual, Cow::Owned(_)));
}
}
mod config_name {
use super::{config_name, ParsePeekExt};
use crate::parse::tests::util::fully_consumed;
#[test]
fn just_name() {
assert_eq!(config_name.parse_peek(b"name").unwrap(), fully_consumed("name".into()));
}
#[test]
fn must_start_with_alphabetic() {
assert!(config_name.parse_peek(b"4aaa").is_err());
assert!(config_name.parse_peek(b"-aaa").is_err());
}
#[test]
fn only_a_subset_of_characters_is_allowed() {
assert!(config_name.parse(b"Name$_").is_err());
assert!(config_name.parse(b"other#").is_err());
}
#[test]
fn cannot_be_empty() {
assert!(config_name.parse_peek(b"").is_err());
}
}
mod section {
use crate::parse::{
error::ParseNode,
tests::util::{
comment_event, fully_consumed, name_event, newline_custom_event, newline_event,
section_header as parsed_section_header, value_done_event, value_event, value_not_done_event,
whitespace_event,
},
Event, Section,
};
fn section<'a>(mut i: &'a [u8], node: &mut ParseNode) -> Result<(&'a [u8], Section<'a>), ()> {
let mut header = None;
let mut events = Vec::new();
super::section(&mut i, node, &mut |e| match &header {
None => {
header = Some(e);
}
Some(_) => events.push(e),
})
.map(|_| {
(
i,
Section {
header: match header.expect("header set") {
Event::SectionHeader(header) => header,
_ => unreachable!("unexpected"),
},
events,
},
)
})
}
#[test]
fn empty_value_with_windows_newlines() {
let mut node = ParseNode::SectionHeader;
assert_eq!(
section(b"[a] k = \r\n", &mut node).unwrap(),
fully_consumed(Section {
header: parsed_section_header("a", None),
events: vec![
whitespace_event(" "),
name_event("k"),
whitespace_event(" "),
Event::KeyValueSeparator,
whitespace_event(" "),
value_event(""),
newline_custom_event("\r\n")
]
}),
);
}
#[test]
fn simple_value_with_windows_newlines() {
let mut node = ParseNode::SectionHeader;
assert_eq!(
section(b"[a] k = v\r\n", &mut node).unwrap(),
fully_consumed(Section {
header: parsed_section_header("a", None),
events: vec![
whitespace_event(" "),
name_event("k"),
whitespace_event(" "),
Event::KeyValueSeparator,
whitespace_event(" "),
value_event("v"),
newline_custom_event("\r\n")
]
}),
);
assert_eq!(
section(b"[a] k = \r\n", &mut node).unwrap(),
fully_consumed(Section {
header: parsed_section_header("a", None),
events: vec![
whitespace_event(" "),
name_event("k"),
whitespace_event(" "),
Event::KeyValueSeparator,
whitespace_event(" "),
value_event(""),
newline_custom_event("\r\n")
]
}),
);
}
#[test]
fn empty_section() {
let mut node = ParseNode::SectionHeader;
assert_eq!(
section(b"[test]", &mut node).unwrap(),
fully_consumed(Section {
header: parsed_section_header("test", None),
events: Default::default()
}),
);
}
#[test]
fn simple_section() {
let mut node = ParseNode::SectionHeader;
let section_data = br#"[hello]
a = b
c
d = "lol""#;
assert_eq!(
section(section_data, &mut node).unwrap(),
fully_consumed(Section {
header: parsed_section_header("hello", None),
events: vec![
newline_event(),
whitespace_event(" "),
name_event("a"),
whitespace_event(" "),
Event::KeyValueSeparator,
whitespace_event(" "),
value_event("b"),
newline_event(),
whitespace_event(" "),
name_event("c"),
value_event(""),
newline_event(),
whitespace_event(" "),
name_event("d"),
whitespace_event(" "),
Event::KeyValueSeparator,
whitespace_event(" "),
value_event(r#""lol""#)
]
})
);
}
#[test]
fn section_with_empty_value_simplified() {
let mut node = ParseNode::SectionHeader;
let section_data = b"[a] k=";
assert_eq!(
section(section_data, &mut node).unwrap(),
fully_consumed(Section {
header: parsed_section_header("a", None),
events: vec![
whitespace_event(" "),
name_event("k"),
Event::KeyValueSeparator,
value_event(""),
]
})
);
let section_data = b"[a] k=\n";
assert_eq!(
section(section_data, &mut node).unwrap(),
fully_consumed(Section {
header: parsed_section_header("a", None),
events: vec![
whitespace_event(" "),
name_event("k"),
Event::KeyValueSeparator,
value_event(""),
newline_event(),
]
})
);
}
#[test]
fn section_with_empty_value() {
let mut node = ParseNode::SectionHeader;
let section_data = br#"[hello]
a = b
c=
d = "lol""#;
assert_eq!(
section(section_data, &mut node).unwrap(),
fully_consumed(Section {
header: parsed_section_header("hello", None),
events: vec![
newline_event(),
whitespace_event(" "),
name_event("a"),
whitespace_event(" "),
Event::KeyValueSeparator,
whitespace_event(" "),
value_event("b"),
newline_event(),
whitespace_event(" "),
name_event("c"),
Event::KeyValueSeparator,
value_event(""),
newline_event(),
whitespace_event(" "),
name_event("d"),
whitespace_event(" "),
Event::KeyValueSeparator,
whitespace_event(" "),
value_event(r#""lol""#)
]
})
);
}
#[test]
fn section_implicit_value() {
let mut node = ParseNode::SectionHeader;
assert_eq!(
section(b"[hello] c", &mut node).unwrap(),
fully_consumed(Section {
header: parsed_section_header("hello", None),
events: vec![whitespace_event(" "), name_event("c"), value_event("")]
})
);
assert_eq!(
section(b"[hello] c\nd", &mut node).unwrap(),
fully_consumed(Section {
header: parsed_section_header("hello", None),
events: vec![
whitespace_event(" "),
name_event("c"),
value_event(""),
newline_event(),
name_event("d"),
value_event("")
]
})
);
}
#[test]
fn section_very_commented() {
let mut node = ParseNode::SectionHeader;
let section_data = br#"[hello] ; commentA
a = b # commentB
; commentC
; commentD
c = d"#;
assert_eq!(
section(section_data, &mut node).unwrap(),
fully_consumed(Section {
header: parsed_section_header("hello", None),
events: vec![
whitespace_event(" "),
comment_event(';', " commentA"),
newline_event(),
whitespace_event(" "),
name_event("a"),
whitespace_event(" "),
Event::KeyValueSeparator,
whitespace_event(" "),
value_event("b"),
whitespace_event(" "),
comment_event('#', " commentB"),
newline_event(),
whitespace_event(" "),
comment_event(';', " commentC"),
newline_event(),
whitespace_event(" "),
comment_event(';', " commentD"),
newline_event(),
whitespace_event(" "),
name_event("c"),
whitespace_event(" "),
Event::KeyValueSeparator,
whitespace_event(" "),
value_event("d"),
]
})
);
}
#[test]
fn complex_continuation() {
let mut node = ParseNode::SectionHeader;
assert_eq!(
section(b"[section] a = 1 \"\\\"\\\na ; e \"\\\"\\\nd # \"b\t ; c", &mut node).unwrap(),
fully_consumed(Section {
header: parsed_section_header("section", None),
events: vec![
whitespace_event(" "),
name_event("a"),
whitespace_event(" "),
Event::KeyValueSeparator,
whitespace_event(" "),
value_not_done_event(r#"1 "\""#),
newline_event(),
value_not_done_event(r#"a ; e "\""#),
newline_event(),
value_done_event("d"),
whitespace_event(" "),
comment_event('#', " \"b\t ; c"),
]
})
);
}
#[test]
fn quote_split_over_two_lines() {
let mut node = ParseNode::SectionHeader;
assert_eq!(
section(b"[section \"a\"] b =\"\\\n;\";a", &mut node).unwrap(),
fully_consumed(Section {
header: parsed_section_header("section", (" ", "a")),
events: vec![
whitespace_event(" "),
name_event("b"),
whitespace_event(" "),
Event::KeyValueSeparator,
value_not_done_event("\""),
newline_event(),
value_done_event(";\""),
comment_event(';', "a"),
]
})
);
}
#[test]
fn section_handles_extraneous_whitespace_before_comment() {
let mut node = ParseNode::SectionHeader;
assert_eq!(
section(b"[s]hello #world", &mut node).unwrap(),
fully_consumed(Section {
header: parsed_section_header("s", None),
events: vec![
name_event("hello"),
whitespace_event(" "),
value_event(""),
comment_event('#', "world"),
]
})
);
}
}
mod value_continuation {
use bstr::ByteSlice;
use crate::parse::{
tests::util::{newline_custom_event, newline_event, value_done_event, value_not_done_event},
Event,
};
pub fn value<'a>(mut i: &'a [u8], events: &mut Vec<Event<'a>>) -> Result<(&'a [u8], ()), ()> {
super::value(&mut i, &mut |e| events.push(e)).map(|_| (i, ()))
}
#[test]
fn simple_continuation() {
let mut events = Vec::new();
assert_eq!(value(b"hello\\\nworld", &mut events).unwrap().0, b"");
assert_eq!(
events,
vec![
value_not_done_event("hello"),
newline_event(),
value_done_event("world")
]
);
}
#[test]
fn continuation_with_whitespace() {
let mut events = Vec::new();
assert_eq!(value(b"hello\\\n world", &mut events).unwrap().0, b"");
assert_eq!(
events,
vec![
value_not_done_event("hello"),
newline_event(),
value_done_event(" world")
]
);
let mut events = Vec::new();
assert_eq!(value(b"hello\\\r\n world", &mut events).unwrap().0, b"");
assert_eq!(
events,
vec![
value_not_done_event("hello"),
newline_custom_event("\r\n"),
value_done_event(" world")
]
);
let mut events = Vec::new();
assert!(
value(b"hello\\\r\r\n world", &mut events).is_err(),
r"\r must be followed by \n"
);
}
#[test]
fn complex_continuation_with_leftover_comment() {
let mut events = Vec::new();
assert_eq!(
value(b"1 \"\\\"\\\na ; e \"\\\"\\\nd # \"b\t ; c", &mut events)
.unwrap()
.0,
b" # \"b\t ; c"
);
assert_eq!(
events,
vec![
value_not_done_event(r#"1 "\""#),
newline_event(),
value_not_done_event(r#"a ; e "\""#),
newline_event(),
value_done_event("d")
]
);
}
#[test]
fn quote_split_over_two_lines_with_leftover_comment() {
let mut events = Vec::new();
assert_eq!(value(b"\"\\\n;\";a", &mut events).unwrap().0, b";a");
assert_eq!(
events,
vec![value_not_done_event("\""), newline_event(), value_done_event(";\"")]
);
let mut events = Vec::new();
assert_eq!(value(b"\"a\\\r\nb;\";c", &mut events).unwrap().0, b";c");
assert_eq!(
events,
vec![
value_not_done_event("\"a"),
newline_custom_event("\r\n"),
value_done_event("b;\"")
]
);
}
#[test]
fn quote_split_over_multiple_lines_without_surrounding_quotes_but_inner_quotes() {
let mut events = Vec::new();
assert_eq!(
value(
br#"1\
"2" a\
\"3 b\"\
4 ; comment "#,
&mut events
)
.unwrap()
.0
.as_bstr(),
b" ; comment ".as_bstr()
);
assert_eq!(
events,
vec![
value_not_done_event("1"),
newline_event(),
value_not_done_event(r#""2" a"#),
newline_event(),
value_not_done_event(r#"\"3 b\""#),
newline_event(),
value_done_event("4")
]
);
}
#[test]
fn quote_split_over_multiple_lines_with_surrounding_quotes() {
let mut events = Vec::new();
assert_eq!(
value(
br#""1\
"2" a\
\"3 b\"\
4 " ; comment "#,
&mut events
)
.unwrap()
.0
.as_bstr(),
b" ; comment ".as_bstr()
);
assert_eq!(
events,
vec![
value_not_done_event("\"1"),
newline_event(),
value_not_done_event(r#""2" a"#),
newline_event(),
value_not_done_event(r#"\"3 b\""#),
newline_event(),
value_done_event("4 \"")
]
);
}
}
mod value_no_continuation {
use super::value_continuation::value;
use crate::parse::tests::util::{value_done_event, value_event, value_not_done_event};
#[test]
fn no_comment() {
let mut events = Vec::new();
assert_eq!(value(b"hello", &mut events).unwrap().0, b"");
assert_eq!(events, vec![value_event("hello")]);
}
#[test]
fn windows_newline() {
let mut events = Vec::new();
assert_eq!(value(b"hi\r\nrest", &mut events).unwrap().0, b"\r\nrest");
assert_eq!(events, vec![value_event("hi")]);
events.clear();
assert_eq!(value(b"hi\r\r\r\nrest", &mut events).unwrap().0, b"\r\r\r\nrest");
assert_eq!(events, vec![value_event("hi")]);
}
#[test]
fn no_comment_newline() {
let mut events = Vec::new();
assert_eq!(value(b"hello\na", &mut events).unwrap().0, b"\na");
assert_eq!(events, vec![value_event("hello")]);
}
#[test]
fn semicolon_comment_not_consumed() {
let mut events = Vec::new();
assert_eq!(value(b"hello;world", &mut events).unwrap().0, b";world");
assert_eq!(events, vec![value_event("hello")]);
}
#[test]
fn octothorpe_comment_not_consumed() {
let mut events = Vec::new();
assert_eq!(value(b"hello#world", &mut events).unwrap().0, b"#world");
assert_eq!(events, vec![value_event("hello")]);
}
#[test]
fn values_with_extraneous_whitespace_without_comment() {
let mut events = Vec::new();
assert_eq!(
value(b"hello ", &mut events).unwrap().0,
b" "
);
assert_eq!(events, vec![value_event("hello")]);
}
#[test]
fn values_with_extraneous_whitespace_before_comment() {
let mut events = Vec::new();
assert_eq!(
value(b"hello #world", &mut events).unwrap().0,
b" #world"
);
assert_eq!(events, vec![value_event("hello")]);
let mut events = Vec::new();
assert_eq!(
value(b"hello ;world", &mut events).unwrap().0,
b" ;world"
);
assert_eq!(events, vec![value_event("hello")]);
}
#[test]
#[allow(clippy::needless_raw_string_hashes)]
fn trans_escaped_comment_marker_not_consumed() {
let mut events = Vec::new();
assert_eq!(value(br##"hello"#"world; a"##, &mut events).unwrap().0, b"; a");
assert_eq!(events, vec![value_event(r##"hello"#"world"##)]);
}
#[test]
fn complex_test() {
let mut events = Vec::new();
assert_eq!(value(br#"value";";ahhhh"#, &mut events).unwrap().0, b";ahhhh");
assert_eq!(events, vec![value_event(r#"value";""#)]);
}
#[test]
fn garbage_after_continuation_is_err() {
assert!(value(br"hello \afwjdls", &mut Default::default()).is_err());
}
#[test]
fn invalid_escape() {
assert!(value(br"\x", &mut Default::default()).is_err());
}
#[test]
fn incomplete_quote() {
assert!(value(br#"hello "world"#, &mut Default::default()).is_err());
}
#[test]
fn incomplete_escape() {
let mut events = Vec::new();
assert_eq!(value(br"hello world\", &mut events).unwrap().0, b"");
assert_eq!(
events,
vec![value_not_done_event("hello world"), value_done_event("")],
"Git accepts this because EOF is normalized to newline and the trailing backslash becomes a continuation"
);
}
}
mod key_value_pair {
use crate::parse::{
error::ParseNode,
tests::util::{name_event, value_event, whitespace_event},
Event,
};
fn key_value<'a>(mut i: &'a [u8], node: &mut ParseNode, events: &mut Vec<Event<'a>>) -> Result<(&'a [u8], ()), ()> {
super::key_value_pair(&mut i, node, &mut |e| events.push(e)).map(|_| (i, ()))
}
#[test]
fn nonascii_is_allowed_for_values_but_not_for_keys() {
let mut node = ParseNode::SectionHeader;
let mut vec = Default::default();
assert!(
key_value("ä½ å¥½".as_bytes(), &mut node, &mut vec).is_ok(),
"Verifying `is_ok` because bad keys get ignored, the caller parser handles this as error"
);
assert_eq!(vec, vec![]);
let mut node = ParseNode::SectionHeader;
let mut vec = Default::default();
assert!(key_value("a = ä½ å¥½ ".as_bytes(), &mut node, &mut vec).is_ok());
assert_eq!(
vec,
vec![
name_event("a"),
whitespace_event(" "),
Event::KeyValueSeparator,
whitespace_event(" "),
value_event("ä½ å¥½")
]
);
}
#[test]
fn whitespace_is_not_ambiguous() {
let mut node = ParseNode::SectionHeader;
let mut vec = Default::default();
assert!(key_value(b"a =b", &mut node, &mut vec).is_ok());
assert_eq!(
vec,
vec![
name_event("a"),
whitespace_event(" "),
Event::KeyValueSeparator,
value_event("b")
]
);
let mut vec = Default::default();
assert!(key_value(b"a= b", &mut node, &mut vec).is_ok());
assert_eq!(
vec,
vec![
name_event("a"),
Event::KeyValueSeparator,
whitespace_event(" "),
value_event("b")
]
);
}
}
mod value {
use super::value;
use crate::parse::{
tests::util::{newline_custom_event, newline_event, value_done_event, value_event, value_not_done_event},
Event,
};
fn parse(mut input: &[u8]) -> Result<(&[u8], Vec<Event<'_>>), ()> {
let mut events = Vec::new();
value(&mut input, &mut |event| events.push(event))?;
Ok((input, events))
}
#[test]
fn empty_value() {
let (remaining, events) = parse(b"").unwrap();
assert_eq!(remaining, b"");
assert_eq!(events, vec![value_event("")]);
}
#[test]
fn plain_value_runs_until_eof_and_trims_trailing_whitespace() {
let (remaining, events) = parse(b"hello \t").unwrap();
assert_eq!(remaining, b" \t");
assert_eq!(events, vec![value_event("hello")]);
}
#[test]
fn newline_without_backslash_is_not_a_continuation() {
let (remaining, events) = parse(b"config\n value").unwrap();
assert_eq!(remaining, b"\n value");
assert_eq!(events, vec![value_event("config")]);
}
#[test]
fn unquoted_comment_markers_end_the_value() {
let (remaining, events) = parse(b"hello ;comment").unwrap();
assert_eq!(remaining, b" ;comment");
assert_eq!(events, vec![value_event("hello")]);
let (remaining, events) = parse(b"hello #comment").unwrap();
assert_eq!(remaining, b" #comment");
assert_eq!(events, vec![value_event("hello")]);
}
#[test]
fn quoted_comment_markers_remain_part_of_the_value() {
let (remaining, events) = parse(br#""a;b#c";comment"#).unwrap();
assert_eq!(remaining, b";comment");
assert_eq!(events, vec![value_event(r#""a;b#c""#)]);
}
#[test]
fn allowed_escapes_remain_part_of_the_value() {
let (remaining, events) = parse(br#"\n\t\\\b\""#).unwrap();
assert_eq!(remaining, b"");
assert_eq!(events, vec![value_event(r#"\n\t\\\b\""#)]);
}
#[test]
fn backslash_lf_continuation_is_allowed() {
let (remaining, events) = parse(b"hello\\\n world").unwrap();
assert_eq!(remaining, b"");
assert_eq!(
events,
vec![
value_not_done_event("hello"),
newline_event(),
value_done_event(" world")
]
);
}
#[test]
fn backslash_crlf_continuation_is_allowed() {
let (remaining, events) = parse(b"hello\\\r\n world").unwrap();
assert_eq!(remaining, b"");
assert_eq!(
events,
vec![
value_not_done_event("hello"),
newline_custom_event("\r\n"),
value_done_event(" world")
]
);
}
#[test]
fn continuations_keep_quote_state() {
let (remaining, events) = parse(
br#""a\
;b";comment"#,
)
.unwrap();
assert_eq!(remaining, b";comment");
assert_eq!(
events,
vec![value_not_done_event("\"a"), newline_event(), value_done_event(";b\"")]
);
}
#[test]
fn backslash_cr_without_lf_is_rejected() {
assert!(parse(b"hello\\\r world").is_err());
}
#[test]
fn backslash_cr_crlf_is_rejected() {
assert!(parse(b"hello\\\r\r\n world").is_err());
}
#[test]
fn trailing_backslash_is_accepted_as_continuation_to_eof() {
let (remaining, events) = parse(b"hello\\").unwrap();
assert_eq!(
remaining, b"",
"it consumes everything, as the continuation backslash is no value"
);
assert_eq!(
events,
vec![value_not_done_event("hello"), value_done_event("")],
"Git accepts this because get_next_char() maps EOF to newline, which parse_value() treats as a continuation"
);
}
#[test]
fn unsupported_escapes_are_rejected() {
assert!(parse(br"\a").is_err());
assert!(parse(br"\x").is_err());
assert!(parse(b"\\\0").is_err());
}
#[test]
fn unterminated_quote_is_rejected() {
assert!(parse(br#""hello"#).is_err());
}
}
mod comment {
use super::{comment, ParsePeekExt};
use crate::parse::tests::util::{comment as parsed_comment, fully_consumed};
#[test]
fn semicolon() {
assert_eq!(
comment.parse_peek(b"; this is a semicolon comment").unwrap(),
fully_consumed(parsed_comment(';', " this is a semicolon comment")),
);
}
#[test]
fn octothorpe() {
assert_eq!(
comment.parse_peek(b"# this is an octothorpe comment").unwrap(),
fully_consumed(parsed_comment('#', " this is an octothorpe comment")),
);
}
#[test]
fn multiple_markers() {
assert_eq!(
comment.parse_peek(b"###### this is an octothorpe comment").unwrap(),
fully_consumed(parsed_comment('#', "##### this is an octothorpe comment")),
);
}
}