use super::*; #[test]#[cfg(target_pointer_width = "64")]fn input_size_is_limited_by_span_representation() { assert!(ensure_supported_input_size(u32::MAX as usize).is_ok()); let actual = u32::MAX as usize + 1; let err = ensure_supported_input_size(actual).expect_err("inputs above the span limit must be rejected"); assert_eq!(err.line_number(), 1); assert!(err.remaining_data().is_empty()); assert_eq!( err.to_string(), format!( "Configuration input is {actual} bytes large, but at most {} bytes are supported", u32::MAX ) );} /// Extend basic types to get their owned versions, without leaking this into production code.trait ToOwnedIn { type Owned; fn to_owned_in(self, backing: &[u8]) -> Self::Owned;} impl ToOwnedIn for Span { type Owned = bstr::BString; fn to_owned_in(self, backing: &[u8]) -> Self::Owned { self.to_bstring_in(backing) }} impl ToOwnedIn for crate::parse::MaybeDecoded { type Owned = bstr::BString; fn to_owned_in(self, backing: &[u8]) -> Self::Owned { self.value_in(backing).to_owned() }} impl ToOwnedIn for Event { type Owned = crate::parse::tests::util::OwnedEvent; fn to_owned_in(self, backing: &[u8]) -> Self::Owned { crate::parse::tests::util::own_event(&self, backing) }} impl ToOwnedIn for Comment { type Owned = crate::parse::tests::util::OwnedComment; fn to_owned_in(self, backing: &[u8]) -> Self::Owned { crate::parse::tests::util::OwnedComment { tag: self.tag, text: self.text.to_bstring_in(backing), } }} impl ToOwnedIn for crate::parse::section::HeaderData { type Owned = crate::parse::tests::util::OwnedHeader; fn to_owned_in(self, backing: &[u8]) -> Self::Owned { crate::parse::tests::util::own_header(&self, backing) }} impl ToOwnedIn for crate::parse::SectionData { type Owned = crate::parse::tests::util::OwnedSection; fn to_owned_in(self, backing: &[u8]) -> Self::Owned { crate::parse::tests::util::OwnedSection { header: self.header.to_owned_in(backing), events: self .events .into_iter() .map(|event| event.to_owned_in(backing)) .collect(), } }} 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 Fwhere 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(()) } }} /// Adapt a parser that produces spans into a separate backing allocation for convenient assertions.////// [`Self::parse_peek()`] copies `input` into a temporary backing vector, invokes the parser with/// that backing and a cursor into it, and resolves the result into its test-only owned equivalent/// before dropping the backing. The returned remainder still refers to the original `input`, with/// its start determined from the number of bytes consumed in the backing.trait ParsePeekWithBackingExt<'a, T> { fn parse_peek(self, input: &'a [u8]) -> Result<(&'a [u8], <T as ToOwnedIn>::Owned), ()> where T: ToOwnedIn;} impl<'a, T, F> ParsePeekWithBackingExt<'a, T> for Fwhere F: for<'backing, 'input> FnOnce(&'backing [u8], &'input mut &'backing [u8]) -> Result<T, ()>, T: ToOwnedIn,{ fn parse_peek(self, input: &'a [u8]) -> Result<(&'a [u8], T::Owned), ()> { let backing = input.to_vec(); let mut current = &backing[..]; let value = self(&backing, &mut current)?; let consumed = input.len() - current.len(); Ok((&input[consumed..], value.to_owned_in(&backing))) }} mod config { use super::from_bytes; use crate::parse::tests::util::{ OwnedEvent as Event, comment_event, name_event, newline_custom_event, newline_event, own_event, value_event, whitespace_event, }; #[test] fn key_value_before_first_section_is_accepted() { let mut events = Vec::new(); let backing = b"a = b\n"; from_bytes(backing, &mut |event| events.push(event)).unwrap(); let events: Vec<_> = events.iter().map(|event| own_event(event, backing)).collect(); 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" ); } #[test] fn comments_do_not_consume_the_carriage_return_of_crlf() { let mut events = Vec::new(); let backing = b"; comment\r\n"; from_bytes(backing, &mut |event| events.push(event)).expect("valid comment"); let events: Vec<_> = events.iter().map(|event| own_event(event, backing)).collect(); assert_eq!( events, vec![comment_event(';', " comment"), newline_custom_event("\r\n")], "the complete CRLF sequence must belong to the newline event" ); let mut events = Vec::new(); let backing = b"; comment\r"; from_bytes(backing, &mut |event| events.push(event)).expect("a bare carriage return is comment text"); let events: Vec<_> = events.iter().map(|event| own_event(event, backing)).collect(); assert_eq!( events, vec![comment_event(';', " comment\r")], "a carriage return without a following line feed must remain part of the comment" ); }} mod section_headers { use super::{ParsePeekWithBackingExt, section_header}; use crate::parse::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(crate::parse::tests::util::OwnedHeader { name: "".into(), separator: Some(" ".into()), subsection_name: Some("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 bstr::ByteSlice; use super::{ParsePeekWithBackingExt, quoted_sub_section}; #[test] fn simple() { let actual = quoted_sub_section.parse_peek(br#"name""#).unwrap().1; assert_eq!(actual.as_bstr(), "name"); } #[test] fn escapes_are_unescaped() { let actual = quoted_sub_section.parse_peek(br#"\x\t\n\0\\\"""#).unwrap().1; assert_eq!(actual.as_bstr(), r#"xtn0\""#); }} mod config_name { use super::{ParsePeekExt, config_name}; 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::{ Event as RawEvent, error::ParseNode, tests::util::{ OwnedEvent as Event, OwnedSection as SectionData, comment_event, fully_consumed, name_event, newline_custom_event, newline_event, own_event, own_header, section_header as parsed_section_header, value_done_event, value_event, value_not_done_event, whitespace_event, }, }; fn section<'a>(input: &'a [u8], node: &mut ParseNode) -> Result<(&'a [u8], SectionData), ()> { let backing = input.to_vec(); let mut current = &backing[..]; let mut header = None; let mut events = Vec::new(); super::section(&backing, &mut current, node, &mut |e| match &header { None => { header = Some(e); } Some(_) => events.push(e), }) .map(|_| { let consumed = input.len() - current.len(); ( &input[consumed..], SectionData { header: match header.expect("header set") { RawEvent::SectionHeader(header) => own_header(&header, &backing), _ => unreachable!("unexpected"), }, events: events.iter().map(|event| own_event(event, &backing)).collect(), }, ) }) } #[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(SectionData { 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(SectionData { 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(SectionData { 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(SectionData { 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(SectionData { header: parsed_section_header("hello", None), events: vec![ newline_event(), whitespace_event(" "), name_event("a"),Lines 1–500 of 1240. Load more
use super::*; #[test] #[cfg(target_pointer_width = "64")] fn input_size_is_limited_by_span_representation() { assert!(ensure_supported_input_size(u32::MAX as usize).is_ok()); let actual = u32::MAX as usize + 1; let err = ensure_supported_input_size(actual).expect_err("inputs above the span limit must be rejected"); assert_eq!(err.line_number(), 1); assert!(err.remaining_data().is_empty()); assert_eq!( err.to_string(), format!( "Configuration input is {actual} bytes large, but at most {} bytes are supported", u32::MAX ) ); } /// Extend basic types to get their owned versions, without leaking this into production code. trait ToOwnedIn { type Owned; fn to_owned_in(self, backing: &[u8]) -> Self::Owned; } impl ToOwnedIn for Span { type Owned = bstr::BString; fn to_owned_in(self, backing: &[u8]) -> Self::Owned { self.to_bstring_in(backing) } } impl ToOwnedIn for crate::parse::MaybeDecoded { type Owned = bstr::BString; fn to_owned_in(self, backing: &[u8]) -> Self::Owned { self.value_in(backing).to_owned() } } impl ToOwnedIn for Event { type Owned = crate::parse::tests::util::OwnedEvent; fn to_owned_in(self, backing: &[u8]) -> Self::Owned { crate::parse::tests::util::own_event(&self, backing) } } impl ToOwnedIn for Comment { type Owned = crate::parse::tests::util::OwnedComment; fn to_owned_in(self, backing: &[u8]) -> Self::Owned { crate::parse::tests::util::OwnedComment { tag: self.tag, text: self.text.to_bstring_in(backing), } } } impl ToOwnedIn for crate::parse::section::HeaderData { type Owned = crate::parse::tests::util::OwnedHeader; fn to_owned_in(self, backing: &[u8]) -> Self::Owned { crate::parse::tests::util::own_header(&self, backing) } } impl ToOwnedIn for crate::parse::SectionData { type Owned = crate::parse::tests::util::OwnedSection; fn to_owned_in(self, backing: &[u8]) -> Self::Owned { crate::parse::tests::util::OwnedSection { header: self.header.to_owned_in(backing), events: self .events .into_iter() .map(|event| event.to_owned_in(backing)) .collect(), } } } 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(()) } } } /// Adapt a parser that produces spans into a separate backing allocation for convenient assertions. /// /// [`Self::parse_peek()`] copies `input` into a temporary backing vector, invokes the parser with /// that backing and a cursor into it, and resolves the result into its test-only owned equivalent /// before dropping the backing. The returned remainder still refers to the original `input`, with /// its start determined from the number of bytes consumed in the backing. trait ParsePeekWithBackingExt<'a, T> { fn parse_peek(self, input: &'a [u8]) -> Result<(&'a [u8], <T as ToOwnedIn>::Owned), ()> where T: ToOwnedIn; } impl<'a, T, F> ParsePeekWithBackingExt<'a, T> for F where F: for<'backing, 'input> FnOnce(&'backing [u8], &'input mut &'backing [u8]) -> Result<T, ()>, T: ToOwnedIn, { fn parse_peek(self, input: &'a [u8]) -> Result<(&'a [u8], T::Owned), ()> { let backing = input.to_vec(); let mut current = &backing[..]; let value = self(&backing, &mut current)?; let consumed = input.len() - current.len(); Ok((&input[consumed..], value.to_owned_in(&backing))) } } mod config { use super::from_bytes; use crate::parse::tests::util::{ OwnedEvent as Event, comment_event, name_event, newline_custom_event, newline_event, own_event, value_event, whitespace_event, }; #[test] fn key_value_before_first_section_is_accepted() { let mut events = Vec::new(); let backing = b"a = b\n"; from_bytes(backing, &mut |event| events.push(event)).unwrap(); let events: Vec<_> = events.iter().map(|event| own_event(event, backing)).collect(); 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" ); } #[test] fn comments_do_not_consume_the_carriage_return_of_crlf() { let mut events = Vec::new(); let backing = b"; comment\r\n"; from_bytes(backing, &mut |event| events.push(event)).expect("valid comment"); let events: Vec<_> = events.iter().map(|event| own_event(event, backing)).collect(); assert_eq!( events, vec![comment_event(';', " comment"), newline_custom_event("\r\n")], "the complete CRLF sequence must belong to the newline event" ); let mut events = Vec::new(); let backing = b"; comment\r"; from_bytes(backing, &mut |event| events.push(event)).expect("a bare carriage return is comment text"); let events: Vec<_> = events.iter().map(|event| own_event(event, backing)).collect(); assert_eq!( events, vec![comment_event(';', " comment\r")], "a carriage return without a following line feed must remain part of the comment" ); } } mod section_headers { use super::{ParsePeekWithBackingExt, section_header}; use crate::parse::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(crate::parse::tests::util::OwnedHeader { name: "".into(), separator: Some(" ".into()), subsection_name: Some("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 bstr::ByteSlice; use super::{ParsePeekWithBackingExt, quoted_sub_section}; #[test] fn simple() { let actual = quoted_sub_section.parse_peek(br#"name""#).unwrap().1; assert_eq!(actual.as_bstr(), "name"); } #[test] fn escapes_are_unescaped() { let actual = quoted_sub_section.parse_peek(br#"\x\t\n\0\\\"""#).unwrap().1; assert_eq!(actual.as_bstr(), r#"xtn0\""#); } } mod config_name { use super::{ParsePeekExt, config_name}; 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::{ Event as RawEvent, error::ParseNode, tests::util::{ OwnedEvent as Event, OwnedSection as SectionData, comment_event, fully_consumed, name_event, newline_custom_event, newline_event, own_event, own_header, section_header as parsed_section_header, value_done_event, value_event, value_not_done_event, whitespace_event, }, }; fn section<'a>(input: &'a [u8], node: &mut ParseNode) -> Result<(&'a [u8], SectionData), ()> { let backing = input.to_vec(); let mut current = &backing[..]; let mut header = None; let mut events = Vec::new(); super::section(&backing, &mut current, node, &mut |e| match &header { None => { header = Some(e); } Some(_) => events.push(e), }) .map(|_| { let consumed = input.len() - current.len(); ( &input[consumed..], SectionData { header: match header.expect("header set") { RawEvent::SectionHeader(header) => own_header(&header, &backing), _ => unreachable!("unexpected"), }, events: events.iter().map(|event| own_event(event, &backing)).collect(), }, ) }) } #[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(SectionData { 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(SectionData { 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(SectionData { 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(SectionData { 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(SectionData { header: parsed_section_header("hello", None), events: vec![ newline_event(), whitespace_event(" "), name_event("a"),