From be3faf0552d60c996aeb51016f93ef64a3dd2d6f Mon Sep 17 00:00:00 2001 From: Jonas Maier Date: Mon, 4 May 2026 23:57:57 +0200 Subject: escape test --- src/parse/test.rs | 98 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 98 insertions(+) (limited to 'src/parse/test.rs') diff --git a/src/parse/test.rs b/src/parse/test.rs index dd6fbca..01dea5d 100644 --- a/src/parse/test.rs +++ b/src/parse/test.rs @@ -342,3 +342,101 @@ fn backslash_joins_lines() { pipes([cmd([estr(b"echo"), estr(b"foobar")]),]) ) } + +fn combinations(choices: &[&[u8]], n: usize) -> impl Iterator> { + struct CombinationGenerator<'a> { + choices: &'a [&'a [u8]], + indices: Vec, + done: bool, + } + + impl<'a> Iterator for CombinationGenerator<'a> { + type Item = Vec; + + fn next(&mut self) -> Option { + if self.done { + return None; + } + + // Build current combination + let mut out = Vec::new(); + for &i in &self.indices { + out.extend_from_slice(self.choices[i]); + } + + // Increment like a base-N counter + for pos in (0..self.indices.len()).rev() { + if self.indices[pos] + 1 < self.choices.len() { + self.indices[pos] += 1; + for j in pos + 1..self.indices.len() { + self.indices[j] = 0; + } + return Some(out); + } + } + + // Last combination reached + self.done = true; + Some(out) + } + } + + CombinationGenerator { + choices, + indices: vec![0; n], + done: n == 0 && choices.is_empty(), + } +} + +fn coerce(t: T) -> T { + t +} + +#[test] +fn test_escape_bruteforce() { + let words = [ + coerce::<&'static [u8]>(b"\""), + b"'", + b"x", + b"y", + b"z", + b"\\", + b"|", + b"$", + b"{", + b"}", + b"'''", + b"\"\"\"", + ]; + + let quotes = [ + StringDelimiter::None, + StringDelimiter::Interp, + StringDelimiter::Strict, + StringDelimiter::InterpCustom(b"x".into()), + StringDelimiter::StrictCustom(b"x".into()), + ]; + + for quote in quotes { + for phrase in combinations(&words[..], 5) { + let mut x = Vec::new(); + quote.write_opening_delimiter(&mut x); + quote.escape(&phrase, &mut x); + quote.write_closing_delimiter(&mut x); + let s = ExpString::parse(&mut Cursor::new(&x, ParseMode::Command)) + .map_err(|e| format!("{quote:?} escape {} failed: {e:?}", x.escape_ascii())) + .unwrap(); + assert_eq!(s.parts.len(), 1, "{}", x.escape_ascii()); + let s = s.parts[0].clone(); + let s = s.unwrap_boring(); + + let x = String::from_utf8(x).unwrap(); + let s = String::from_utf8(s).unwrap(); + let phrase = String::from_utf8(phrase).unwrap(); + assert_eq!( + phrase, s, + "escape/parse roundtrip in StringDelimiter::{quote:?} failed: {phrase} -> {x} -> {s}" + ); + } + } +} -- cgit v1.2.3