mod section { use crate::parse::{Comment, Event, Events, SectionRef, section}; #[test] #[cfg(target_pointer_width = "64")] fn size_of_events() { assert_eq!( std::mem::size_of::<SectionRef>(), 40, "this value should only ever decrease" ); assert_eq!(std::mem::size_of::<Events>(), 512); assert_eq!(std::mem::size_of::<crate::parse::Span>(), 8); assert_eq!(std::mem::size_of::<Event>(), 56); assert_eq!(std::mem::size_of::<Comment>(), 12); assert_eq!(std::mem::size_of::<section::Name>(), 24); assert_eq!(std::mem::size_of::<section::ValueName>(), 24); } mod header { mod unvalidated { use crate::parse::section::unvalidated::KeyRef; #[test] fn section_name_only() { assert_eq!( KeyRef::parse("core").unwrap(), KeyRef { section_name: "core", subsection_name: None } ); } #[test] fn section_name_and_subsection() { assert_eq!( KeyRef::parse("core.bare").unwrap(), KeyRef { section_name: "core", subsection_name: Some("bare".into()) } ); } #[test] fn section_name_and_subsection_with_separators() { assert_eq!( KeyRef::parse("remote.https:///home/user.git").unwrap(), KeyRef { section_name: "remote", subsection_name: Some("https:///home/user.git".into()) } ); } } }} mod span { use crate::parse::Span; #[test] #[cfg(target_pointer_width = "64")] fn reports_range_and_rebase_overflow() { assert!(Span::range(u32::MAX as usize + 1, 0).is_err()); assert!(Span::range(0, u32::MAX as usize + 1).is_err()); let mut span = Span::range(u32::MAX as usize, 0).expect("the maximum offset is representable"); assert_eq!(span.rebase(1), Err(crate::parse::span::Error)); }} mod event { mod write_to { use crate::parse::{EventRef, Events}; fn header( name: &'static str, subsection: impl Into<Option<(&'static str, &'static str)>>, ) -> EventRef<'static> { let (separator, subsection_name) = subsection.into().map_or((None, None), |(separator, subsection_name)| { (Some(separator.into()), Some(subsection_name.into())) }); EventRef::SectionHeader { name: name.into(), separator, subsection_name, } } fn write_event(event: EventRef<'_>) -> Vec<u8> { let mut out = Vec::new(); event.write_to(&mut out).expect("writing to memory succeeds"); out } fn write_events(input: &str) -> Vec<u8> { let events = Events::from_str(input).unwrap(); let mut out = Vec::new(); for event in events.iter() { event.write_to(&mut out).unwrap(); } out } #[test] fn legacy_subsection_format_does_not_use_escapes() { assert_eq!( write_event(header("invalid", Some((".", r#"\ ""#)))), br#"[invalid.\ "]"#, "no escaping happens for legacy subsections" ); } #[test] fn subsections_escape_two_characters_only() { assert_eq!( write_event(header("invalid", Some((" ", "\\ \"\npost newline")))), b"[invalid \"\\\\ \\\"\npost newline\"]", "newlines are invalid in validated subsections, but EventRef can represent them" ); } #[test] fn empty_section_name_with_quoted_subsection() { assert_eq!( write_event(header("", Some((" ", "core")))), br#"[ "core"]"#, "Git accepts an empty section name with `core` as subsection" ); } #[test] fn nul_byte_in_quoted_subsection() { assert_eq!( write_event(header("hello", Some((" ", "hello\0")))), b"[hello \"hello\0\"]", "Git accepts NUL bytes in quoted subsection names" ); } #[test] fn key_value_before_first_section() { let input = "a = b\n"; assert_eq!( write_events(input), input.as_bytes(), "Git accepts key/value pairs before the first section, and we preserve them" ); } #[test] fn value_with_trailing_backslash_at_eof() { let input = "[core]\na=hello\\"; assert_eq!( write_events(input), input.as_bytes(), "Git accepts EOF as a line continuation terminator, and we preserve the original trailing backslash" ); } }} pub(crate) mod util { //! This module is only included for tests, and contains common unit test helper //! functions. //! //! Parsed events store spans into a separate backing buffer, so their fields cannot be inspected //! or compared meaningfully without the backing buffer. The `Owned*` types resolve those spans into //! owned byte strings, allowing tests to compare event contents independently of span coordinates //! and to retain expected or actual values after a temporary backing buffer is dropped. use bstr::BString; use crate::parse::{Event, section}; #[derive(Clone, Debug, Eq, PartialEq)] pub(crate) struct OwnedHeader { pub(crate) name: BString, pub(crate) separator: Option<BString>, pub(crate) subsection_name: Option<BString>, } #[derive(Clone, Debug, Eq, PartialEq)] pub(crate) enum OwnedEvent { Comment(OwnedComment), SectionHeader(OwnedHeader), SectionValueName(BString), Value(BString), Newline(BString), ValueNotDone(BString), ValueDone(BString), Whitespace(BString), KeyValueSeparator, } #[derive(Clone, Debug, Eq, PartialEq)] pub(crate) struct OwnedComment { pub(crate) tag: u8, pub(crate) text: BString, } #[derive(Clone, Debug, Eq, PartialEq)] pub(crate) struct OwnedSection { pub(crate) header: OwnedHeader, pub(crate) events: Vec<OwnedEvent>, } pub(crate) fn own_event(event: &Event, backing: &[u8]) -> OwnedEvent { match event { Event::Comment(comment) => OwnedEvent::Comment(OwnedComment { tag: comment.tag, text: comment.text.to_bstring_in(backing), }), Event::SectionHeader(header) => OwnedEvent::SectionHeader(own_header(header, backing)), Event::SectionValueName(name) => OwnedEvent::SectionValueName(name.to_bstring_in(backing)), Event::Value(value) => OwnedEvent::Value(value.to_bstring_in(backing)), Event::Newline(value) => OwnedEvent::Newline(value.to_bstring_in(backing)), Event::ValueNotDone(value) => OwnedEvent::ValueNotDone(value.to_bstring_in(backing)), Event::ValueDone(value) => OwnedEvent::ValueDone(value.to_bstring_in(backing)), Event::Whitespace(value) => OwnedEvent::Whitespace(value.to_bstring_in(backing)), Event::KeyValueSeparator => OwnedEvent::KeyValueSeparator, } } pub(crate) fn own_header(header: §ion::HeaderData, backing: &[u8]) -> OwnedHeader { OwnedHeader { name: header.name.to_bstring_in(backing), separator: header.separator.map(|separator| separator.to_bstring_in(backing)), subsection_name: header .subsection_name .as_ref() .map(|subsection_name| subsection_name.value_in(backing).to_owned()), } } pub fn section_header(name: &str, subsection: impl Into<Option<(&'static str, &'static str)>>) -> OwnedHeader { if let Some((separator, subsection_name)) = subsection.into() { OwnedHeader { name: name.into(), separator: Some(separator.into()), subsection_name: Some(subsection_name.into()), } } else { OwnedHeader { name: name.into(), separator: None, subsection_name: None, } } } pub(crate) fn name_event(name: &'static str) -> OwnedEvent { OwnedEvent::SectionValueName(name.into()) } pub(crate) fn value_event(value: &'static str) -> OwnedEvent { OwnedEvent::Value(value.into()) } pub(crate) fn value_not_done_event(value: &'static str) -> OwnedEvent { OwnedEvent::ValueNotDone(value.into()) } pub(crate) fn value_done_event(value: &'static str) -> OwnedEvent { OwnedEvent::ValueDone(value.into()) } pub(crate) fn newline_event() -> OwnedEvent { newline_custom_event("\n") } pub(crate) fn newline_custom_event(value: &'static str) -> OwnedEvent { OwnedEvent::Newline(value.into()) } pub(crate) fn whitespace_event(value: &'static str) -> OwnedEvent { OwnedEvent::Whitespace(value.into()) } pub(crate) fn comment_event(tag: char, msg: &'static str) -> OwnedEvent { OwnedEvent::Comment(comment(tag, msg)) } pub(crate) fn comment(comment_tag: char, comment: &'static str) -> OwnedComment { OwnedComment { tag: comment_tag as u8, text: comment.into(), } } pub(crate) const fn fully_consumed<T>(t: T) -> (&'static [u8], T) { (&[], t) }}
mod section {
use crate::parse::{Comment, Event, Events, SectionRef, section};
#[test]
#[cfg(target_pointer_width = "64")]
fn size_of_events() {
assert_eq!(
std::mem::size_of::<SectionRef>(),
40,
"this value should only ever decrease"
);
assert_eq!(std::mem::size_of::<Events>(), 512);
assert_eq!(std::mem::size_of::<crate::parse::Span>(), 8);
assert_eq!(std::mem::size_of::<Event>(), 56);
assert_eq!(std::mem::size_of::<Comment>(), 12);
assert_eq!(std::mem::size_of::<section::Name>(), 24);
assert_eq!(std::mem::size_of::<section::ValueName>(), 24);
}
mod header {
mod unvalidated {
use crate::parse::section::unvalidated::KeyRef;
#[test]
fn section_name_only() {
assert_eq!(
KeyRef::parse("core").unwrap(),
KeyRef {
section_name: "core",
subsection_name: None
}
);
}
#[test]
fn section_name_and_subsection() {
assert_eq!(
KeyRef::parse("core.bare").unwrap(),
KeyRef {
section_name: "core",
subsection_name: Some("bare".into())
}
);
}
#[test]
fn section_name_and_subsection_with_separators() {
assert_eq!(
KeyRef::parse("remote.https:///home/user.git").unwrap(),
KeyRef {
section_name: "remote",
subsection_name: Some("https:///home/user.git".into())
}
);
}
}
}
}
mod span {
use crate::parse::Span;
#[test]
#[cfg(target_pointer_width = "64")]
fn reports_range_and_rebase_overflow() {
assert!(Span::range(u32::MAX as usize + 1, 0).is_err());
assert!(Span::range(0, u32::MAX as usize + 1).is_err());
let mut span = Span::range(u32::MAX as usize, 0).expect("the maximum offset is representable");
assert_eq!(span.rebase(1), Err(crate::parse::span::Error));
}
}
mod event {
mod write_to {
use crate::parse::{EventRef, Events};
fn header(
name: &'static str,
subsection: impl Into<Option<(&'static str, &'static str)>>,
) -> EventRef<'static> {
let (separator, subsection_name) =
subsection.into().map_or((None, None), |(separator, subsection_name)| {
(Some(separator.into()), Some(subsection_name.into()))
});
EventRef::SectionHeader {
name: name.into(),
separator,
subsection_name,
}
}
fn write_event(event: EventRef<'_>) -> Vec<u8> {
let mut out = Vec::new();
event.write_to(&mut out).expect("writing to memory succeeds");
out
}
fn write_events(input: &str) -> Vec<u8> {
let events = Events::from_str(input).unwrap();
let mut out = Vec::new();
for event in events.iter() {
event.write_to(&mut out).unwrap();
}
out
}
#[test]
fn legacy_subsection_format_does_not_use_escapes() {
assert_eq!(
write_event(header("invalid", Some((".", r#"\ ""#)))),
br#"[invalid.\ "]"#,
"no escaping happens for legacy subsections"
);
}
#[test]
fn subsections_escape_two_characters_only() {
assert_eq!(
write_event(header("invalid", Some((" ", "\\ \"\npost newline")))),
b"[invalid \"\\\\ \\\"\npost newline\"]",
"newlines are invalid in validated subsections, but EventRef can represent them"
);
}
#[test]
fn empty_section_name_with_quoted_subsection() {
assert_eq!(
write_event(header("", Some((" ", "core")))),
br#"[ "core"]"#,
"Git accepts an empty section name with `core` as subsection"
);
}
#[test]
fn nul_byte_in_quoted_subsection() {
assert_eq!(
write_event(header("hello", Some((" ", "hello\0")))),
b"[hello \"hello\0\"]",
"Git accepts NUL bytes in quoted subsection names"
);
}
#[test]
fn key_value_before_first_section() {
let input = "a = b\n";
assert_eq!(
write_events(input),
input.as_bytes(),
"Git accepts key/value pairs before the first section, and we preserve them"
);
}
#[test]
fn value_with_trailing_backslash_at_eof() {
let input = "[core]\na=hello\\";
assert_eq!(
write_events(input),
input.as_bytes(),
"Git accepts EOF as a line continuation terminator, and we preserve the original trailing backslash"
);
}
}
}
pub(crate) mod util {
//! This module is only included for tests, and contains common unit test helper
//! functions.
//!
//! Parsed events store spans into a separate backing buffer, so their fields cannot be inspected
//! or compared meaningfully without the backing buffer. The `Owned*` types resolve those spans into
//! owned byte strings, allowing tests to compare event contents independently of span coordinates
//! and to retain expected or actual values after a temporary backing buffer is dropped.
use bstr::BString;
use crate::parse::{Event, section};
#[derive(Clone, Debug, Eq, PartialEq)]
pub(crate) struct OwnedHeader {
pub(crate) name: BString,
pub(crate) separator: Option<BString>,
pub(crate) subsection_name: Option<BString>,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub(crate) enum OwnedEvent {
Comment(OwnedComment),
SectionHeader(OwnedHeader),
SectionValueName(BString),
Value(BString),
Newline(BString),
ValueNotDone(BString),
ValueDone(BString),
Whitespace(BString),
KeyValueSeparator,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub(crate) struct OwnedComment {
pub(crate) tag: u8,
pub(crate) text: BString,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub(crate) struct OwnedSection {
pub(crate) header: OwnedHeader,
pub(crate) events: Vec<OwnedEvent>,
}
pub(crate) fn own_event(event: &Event, backing: &[u8]) -> OwnedEvent {
match event {
Event::Comment(comment) => OwnedEvent::Comment(OwnedComment {
tag: comment.tag,
text: comment.text.to_bstring_in(backing),
}),
Event::SectionHeader(header) => OwnedEvent::SectionHeader(own_header(header, backing)),
Event::SectionValueName(name) => OwnedEvent::SectionValueName(name.to_bstring_in(backing)),
Event::Value(value) => OwnedEvent::Value(value.to_bstring_in(backing)),
Event::Newline(value) => OwnedEvent::Newline(value.to_bstring_in(backing)),
Event::ValueNotDone(value) => OwnedEvent::ValueNotDone(value.to_bstring_in(backing)),
Event::ValueDone(value) => OwnedEvent::ValueDone(value.to_bstring_in(backing)),
Event::Whitespace(value) => OwnedEvent::Whitespace(value.to_bstring_in(backing)),
Event::KeyValueSeparator => OwnedEvent::KeyValueSeparator,
}
}
pub(crate) fn own_header(header: §ion::HeaderData, backing: &[u8]) -> OwnedHeader {
OwnedHeader {
name: header.name.to_bstring_in(backing),
separator: header.separator.map(|separator| separator.to_bstring_in(backing)),
subsection_name: header
.subsection_name
.as_ref()
.map(|subsection_name| subsection_name.value_in(backing).to_owned()),
}
}
pub fn section_header(name: &str, subsection: impl Into<Option<(&'static str, &'static str)>>) -> OwnedHeader {
if let Some((separator, subsection_name)) = subsection.into() {
OwnedHeader {
name: name.into(),
separator: Some(separator.into()),
subsection_name: Some(subsection_name.into()),
}
} else {
OwnedHeader {
name: name.into(),
separator: None,
subsection_name: None,
}
}
}
pub(crate) fn name_event(name: &'static str) -> OwnedEvent {
OwnedEvent::SectionValueName(name.into())
}
pub(crate) fn value_event(value: &'static str) -> OwnedEvent {
OwnedEvent::Value(value.into())
}
pub(crate) fn value_not_done_event(value: &'static str) -> OwnedEvent {
OwnedEvent::ValueNotDone(value.into())
}
pub(crate) fn value_done_event(value: &'static str) -> OwnedEvent {
OwnedEvent::ValueDone(value.into())
}
pub(crate) fn newline_event() -> OwnedEvent {
newline_custom_event("\n")
}
pub(crate) fn newline_custom_event(value: &'static str) -> OwnedEvent {
OwnedEvent::Newline(value.into())
}
pub(crate) fn whitespace_event(value: &'static str) -> OwnedEvent {
OwnedEvent::Whitespace(value.into())
}
pub(crate) fn comment_event(tag: char, msg: &'static str) -> OwnedEvent {
OwnedEvent::Comment(comment(tag, msg))
}
pub(crate) fn comment(comment_tag: char, comment: &'static str) -> OwnedComment {
OwnedComment {
tag: comment_tag as u8,
text: comment.into(),
}
}
pub(crate) const fn fully_consumed<T>(t: T) -> (&'static [u8], T) {
(&[], t)
}
}