-
Notifications
You must be signed in to change notification settings - Fork 52
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Improve the ability to parse/unparse files #127
Changes from 2 commits
c2ff663
9924a96
6f49f29
44ee05a
04c1724
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -132,6 +132,7 @@ pub enum Record { | |||||||||||||||||||||||||||
}, | ||||||||||||||||||||||||||||
Condition(Condition), | ||||||||||||||||||||||||||||
Comment(Vec<String>), | ||||||||||||||||||||||||||||
Whitespace(String), | ||||||||||||||||||||||||||||
/// Internally injected record which should not occur in the test file. | ||||||||||||||||||||||||||||
Injected(Injected), | ||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||
|
@@ -142,9 +143,15 @@ impl Record { | |||||||||||||||||||||||||||
/// # Panics | ||||||||||||||||||||||||||||
/// If the record is an internally injected record which should not occur in the test file. | ||||||||||||||||||||||||||||
pub fn unparse(&self, w: &mut impl std::io::Write) -> std::io::Result<()> { | ||||||||||||||||||||||||||||
write!(w, "{}", self) | ||||||||||||||||||||||||||||
xxchan marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
impl std::fmt::Display for Record { | ||||||||||||||||||||||||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | ||||||||||||||||||||||||||||
match self { | ||||||||||||||||||||||||||||
Record::Include { loc: _, filename } => { | ||||||||||||||||||||||||||||
write!(w, "include {}", filename) | ||||||||||||||||||||||||||||
write!(f, "include {}", filename) | ||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||
Record::Statement { | ||||||||||||||||||||||||||||
loc: _, | ||||||||||||||||||||||||||||
|
@@ -153,21 +160,21 @@ impl Record { | |||||||||||||||||||||||||||
sql, | ||||||||||||||||||||||||||||
expected_count, | ||||||||||||||||||||||||||||
} => { | ||||||||||||||||||||||||||||
write!(w, "statement ")?; | ||||||||||||||||||||||||||||
write!(f, "statement ")?; | ||||||||||||||||||||||||||||
match (expected_count, expected_error) { | ||||||||||||||||||||||||||||
(None, None) => write!(w, "ok")?, | ||||||||||||||||||||||||||||
(None, None) => write!(f, "ok")?, | ||||||||||||||||||||||||||||
(None, Some(err)) => { | ||||||||||||||||||||||||||||
if err.as_str().is_empty() { | ||||||||||||||||||||||||||||
write!(w, "error")?; | ||||||||||||||||||||||||||||
write!(f, "error")?; | ||||||||||||||||||||||||||||
} else { | ||||||||||||||||||||||||||||
write!(w, "error {}", err)?; | ||||||||||||||||||||||||||||
write!(f, "error {}", err)?; | ||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||
(Some(cnt), None) => write!(w, "count {}", cnt)?, | ||||||||||||||||||||||||||||
(Some(cnt), None) => write!(f, "count {}", cnt)?, | ||||||||||||||||||||||||||||
(Some(_), Some(_)) => unreachable!(), | ||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||
writeln!(w)?; | ||||||||||||||||||||||||||||
write!(w, "{}", sql) | ||||||||||||||||||||||||||||
writeln!(f)?; | ||||||||||||||||||||||||||||
write!(f, "{}", sql) | ||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||
Record::Query { | ||||||||||||||||||||||||||||
loc: _, | ||||||||||||||||||||||||||||
|
@@ -179,63 +186,66 @@ impl Record { | |||||||||||||||||||||||||||
sql, | ||||||||||||||||||||||||||||
expected_results, | ||||||||||||||||||||||||||||
} => { | ||||||||||||||||||||||||||||
write!(w, "query")?; | ||||||||||||||||||||||||||||
write!(f, "query")?; | ||||||||||||||||||||||||||||
if let Some(err) = expected_error { | ||||||||||||||||||||||||||||
writeln!(w, " error {}", err)?; | ||||||||||||||||||||||||||||
return write!(w, "{}", sql); | ||||||||||||||||||||||||||||
writeln!(f, " error {}", err)?; | ||||||||||||||||||||||||||||
return write!(f, "{}", sql); | ||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
write!( | ||||||||||||||||||||||||||||
w, | ||||||||||||||||||||||||||||
f, | ||||||||||||||||||||||||||||
" {}", | ||||||||||||||||||||||||||||
type_string.iter().map(|c| format!("{c}")).join("") | ||||||||||||||||||||||||||||
)?; | ||||||||||||||||||||||||||||
if let Some(sort_mode) = sort_mode { | ||||||||||||||||||||||||||||
write!(w, " {}", sort_mode.as_str())?; | ||||||||||||||||||||||||||||
write!(f, " {}", sort_mode.as_str())?; | ||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||
if let Some(label) = label { | ||||||||||||||||||||||||||||
write!(w, " {}", label)?; | ||||||||||||||||||||||||||||
write!(f, " {}", label)?; | ||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||
writeln!(w)?; | ||||||||||||||||||||||||||||
writeln!(w, "{}", sql)?; | ||||||||||||||||||||||||||||
writeln!(f)?; | ||||||||||||||||||||||||||||
writeln!(f, "{}", sql)?; | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
write!(w, "----")?; | ||||||||||||||||||||||||||||
write!(f, "----")?; | ||||||||||||||||||||||||||||
for result in expected_results { | ||||||||||||||||||||||||||||
write!(w, "\n{}", result)?; | ||||||||||||||||||||||||||||
write!(f, "\n{}", result)?; | ||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||
Ok(()) | ||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||
Record::Sleep { loc: _, duration } => { | ||||||||||||||||||||||||||||
write!(w, "sleep {}", humantime::format_duration(*duration)) | ||||||||||||||||||||||||||||
write!(f, "sleep {}", humantime::format_duration(*duration)) | ||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||
Record::Subtest { loc: _, name } => { | ||||||||||||||||||||||||||||
write!(w, "subtest {}", name) | ||||||||||||||||||||||||||||
write!(f, "subtest {}", name) | ||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||
Record::Halt { loc: _ } => { | ||||||||||||||||||||||||||||
write!(w, "halt") | ||||||||||||||||||||||||||||
write!(f, "halt") | ||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||
Record::Control(c) => match c { | ||||||||||||||||||||||||||||
Control::SortMode(m) => write!(w, "control sortmode {}", m.as_str()), | ||||||||||||||||||||||||||||
Control::SortMode(m) => write!(f, "control sortmode {}", m.as_str()), | ||||||||||||||||||||||||||||
}, | ||||||||||||||||||||||||||||
Record::Condition(cond) => match cond { | ||||||||||||||||||||||||||||
Condition::OnlyIf { engine_name } => { | ||||||||||||||||||||||||||||
write!(w, "onlyif {}", engine_name) | ||||||||||||||||||||||||||||
write!(f, "onlyif {}", engine_name) | ||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||
Condition::SkipIf { engine_name } => { | ||||||||||||||||||||||||||||
write!(w, "skipif {}", engine_name) | ||||||||||||||||||||||||||||
write!(f, "skipif {}", engine_name) | ||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||
}, | ||||||||||||||||||||||||||||
Record::HashThreshold { loc: _, threshold } => { | ||||||||||||||||||||||||||||
write!(w, "hash-threshold {}", threshold) | ||||||||||||||||||||||||||||
write!(f, "hash-threshold {}", threshold) | ||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||
Record::Comment(comment) => { | ||||||||||||||||||||||||||||
let mut iter = comment.iter(); | ||||||||||||||||||||||||||||
write!(w, "#{}", iter.next().unwrap().trim_end())?; | ||||||||||||||||||||||||||||
write!(f, "#{}", iter.next().unwrap().trim_end())?; | ||||||||||||||||||||||||||||
for line in iter { | ||||||||||||||||||||||||||||
write!(w, "\n#{}", line.trim_end())?; | ||||||||||||||||||||||||||||
write!(f, "\n#{}", line.trim_end())?; | ||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||
Ok(()) | ||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||
Record::Whitespace(w) => { | ||||||||||||||||||||||||||||
write!(f, "{}", w) | ||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||
Record::Injected(p) => panic!("unexpected injected record: {:?}", p), | ||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||
|
@@ -391,6 +401,7 @@ fn parse_inner(loc: &Location, script: &str) -> Result<Vec<Record>, ParseError> | |||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
if line.is_empty() { | ||||||||||||||||||||||||||||
records.push(Record::Whitespace(line.to_string())); | ||||||||||||||||||||||||||||
continue; | ||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
|
@@ -603,11 +614,85 @@ fn parse_file_inner(loc: Location) -> Result<Vec<Record>, ParseError> { | |||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
#[cfg(test)] | ||||||||||||||||||||||||||||
mod tests { | ||||||||||||||||||||||||||||
use crate::parse_file; | ||||||||||||||||||||||||||||
use difference::{Changeset, Difference}; | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
use super::*; | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
#[test] | ||||||||||||||||||||||||||||
fn test_include_glob() { | ||||||||||||||||||||||||||||
let records = parse_file("../examples/include/include_1.slt").unwrap(); | ||||||||||||||||||||||||||||
assert_eq!(14, records.len()); | ||||||||||||||||||||||||||||
assert_eq!(16, records.len()); | ||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
#[test] | ||||||||||||||||||||||||||||
fn test_basic() { | ||||||||||||||||||||||||||||
parse_roundtrip("../examples/basic/basic.slt") | ||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
/// Parses the specified file into Records, and ensures the | ||||||||||||||||||||||||||||
/// results of unparsing them are the same | ||||||||||||||||||||||||||||
Comment on lines
+635
to
+636
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I guess current implementation is still not enough to ensure e.g.,
Currently it will be unparsed to
But do we really need identical reconstruction (It can be hard)? If not, maybe we can just test There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. But preserving newlines makes some sense. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I don't really need exact reconstruction -- what I really want is to be able to "update" a file in place with expected output. If part of that update also ends up "normalizing" some of the commands (like collapsing However I think removing all blank newlines makes the files harder to read so I would prefer to keep them in There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Testing parse/unparse is a great idea -- I will add that |
||||||||||||||||||||||||||||
/// | ||||||||||||||||||||||||||||
/// Prints a hopefully useful message on failure | ||||||||||||||||||||||||||||
fn parse_roundtrip(filename: impl AsRef<Path>) { | ||||||||||||||||||||||||||||
let filename = filename.as_ref(); | ||||||||||||||||||||||||||||
let input_contents = std::fs::read_to_string(filename).expect("reading file"); | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
let records = parse_file(filename).expect("parsing to complete"); | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
let unparsed = records | ||||||||||||||||||||||||||||
.into_iter() | ||||||||||||||||||||||||||||
.map(record_to_string) | ||||||||||||||||||||||||||||
.collect::<Vec<_>>(); | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
let output_contents = unparsed.join("\n"); | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
let changeset = Changeset::new(&input_contents, &output_contents, "\n"); | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
assert!( | ||||||||||||||||||||||||||||
no_diffs(&changeset), | ||||||||||||||||||||||||||||
"Mismatch for {:?}\n\ | ||||||||||||||||||||||||||||
*********\n\ | ||||||||||||||||||||||||||||
diff:\n\ | ||||||||||||||||||||||||||||
*********\n\ | ||||||||||||||||||||||||||||
{}\n\n\ | ||||||||||||||||||||||||||||
*********\n\ | ||||||||||||||||||||||||||||
output:\n\ | ||||||||||||||||||||||||||||
*********\n\ | ||||||||||||||||||||||||||||
{}\n\n", | ||||||||||||||||||||||||||||
filename, | ||||||||||||||||||||||||||||
UsefulDiffDisplay(&changeset), | ||||||||||||||||||||||||||||
output_contents, | ||||||||||||||||||||||||||||
); | ||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
fn record_to_string(record: Record) -> String { | ||||||||||||||||||||||||||||
if matches!(&record, Record::Statement { .. } | Record::Query { .. }) { | ||||||||||||||||||||||||||||
// the statement parser includes a newline between the items but the display | ||||||||||||||||||||||||||||
// output does not, so add it here | ||||||||||||||||||||||||||||
// Not sure about this one | ||||||||||||||||||||||||||||
format!("{}\n", record) | ||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I found this to be necessary to recreate the original I think it would more naturally go in the Here is what the output looks like without this clause: Click me
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Update: I understand the problem wrongly here. I am aware of the trailing newline problem. AFAIK usually sqllogictest-rs/sqllogictest-bin/src/lib.rs Lines 628 to 632 in d753e4c
sqllogictest-rs/sqllogictest-bin/src/lib.rs Lines 587 to 594 in d753e4c
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. BTW why it doesn't work to add newline to every record here in There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh it is because newline is consumed when parsing There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Now I think it may be reasonable to add trailing newline in There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Yes, I think this is the reason. Logically a Or put another way, the following would not be parsed correctly.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
You if you do that, then there are extra newlines between every record (as I am already using |
||||||||||||||||||||||||||||
} else { | ||||||||||||||||||||||||||||
record.to_string() | ||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
/// returns true if there are no differences in the changeset | ||||||||||||||||||||||||||||
fn no_diffs(changeset: &Changeset) -> bool { | ||||||||||||||||||||||||||||
changeset | ||||||||||||||||||||||||||||
.diffs | ||||||||||||||||||||||||||||
.iter() | ||||||||||||||||||||||||||||
.all(|diff| matches!(diff, Difference::Same(_))) | ||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
struct UsefulDiffDisplay<'a>(&'a Changeset); | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
impl<'a> std::fmt::Display for UsefulDiffDisplay<'a> { | ||||||||||||||||||||||||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | ||||||||||||||||||||||||||||
self.0.diffs.iter().try_for_each(|diff| match diff { | ||||||||||||||||||||||||||||
Difference::Same(x) => writeln!(f, "{x}"), | ||||||||||||||||||||||||||||
Difference::Add(x) => writeln!(f, "+ {x}"), | ||||||||||||||||||||||||||||
Difference::Rem(x) => writeln!(f, "- {x}"), | ||||||||||||||||||||||||||||
}) | ||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
According to the comment below, what about renaming it to
Newline(usize)
?