Skip to content

Commit 208998a

Browse files
authored
Improve the ability to parse/unparse files (#127)
1 parent 4f673ad commit 208998a

File tree

2 files changed

+121
-30
lines changed

2 files changed

+121
-30
lines changed

sqllogictest/src/parser.rs

+120-30
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@ pub enum Record {
132132
},
133133
Condition(Condition),
134134
Comment(Vec<String>),
135+
Newline,
135136
/// Internally injected record which should not occur in the test file.
136137
Injected(Injected),
137138
}
@@ -142,9 +143,18 @@ impl Record {
142143
/// # Panics
143144
/// If the record is an internally injected record which should not occur in the test file.
144145
pub fn unparse(&self, w: &mut impl std::io::Write) -> std::io::Result<()> {
146+
write!(w, "{}", self)
147+
}
148+
}
149+
150+
/// As is the standard for Display, does not print any trailing
151+
/// newline except for records that always end with a blank line such
152+
/// as Query and Statement.
153+
impl std::fmt::Display for Record {
154+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
145155
match self {
146156
Record::Include { loc: _, filename } => {
147-
write!(w, "include {}", filename)
157+
write!(f, "include {}", filename)
148158
}
149159
Record::Statement {
150160
loc: _,
@@ -153,21 +163,22 @@ impl Record {
153163
sql,
154164
expected_count,
155165
} => {
156-
write!(w, "statement ")?;
166+
write!(f, "statement ")?;
157167
match (expected_count, expected_error) {
158-
(None, None) => write!(w, "ok")?,
168+
(None, None) => write!(f, "ok")?,
159169
(None, Some(err)) => {
160170
if err.as_str().is_empty() {
161-
write!(w, "error")?;
171+
write!(f, "error")?;
162172
} else {
163-
write!(w, "error {}", err)?;
173+
write!(f, "error {}", err)?;
164174
}
165175
}
166-
(Some(cnt), None) => write!(w, "count {}", cnt)?,
176+
(Some(cnt), None) => write!(f, "count {}", cnt)?,
167177
(Some(_), Some(_)) => unreachable!(),
168178
}
169-
writeln!(w)?;
170-
write!(w, "{}", sql)
179+
writeln!(f)?;
180+
// statement always end with a blank line
181+
writeln!(f, "{}", sql)
171182
}
172183
Record::Query {
173184
loc: _,
@@ -179,63 +190,65 @@ impl Record {
179190
sql,
180191
expected_results,
181192
} => {
182-
write!(w, "query")?;
193+
write!(f, "query")?;
183194
if let Some(err) = expected_error {
184-
writeln!(w, " error {}", err)?;
185-
return write!(w, "{}", sql);
195+
writeln!(f, " error {}", err)?;
196+
return writeln!(f, "{}", sql);
186197
}
187198

188199
write!(
189-
w,
200+
f,
190201
" {}",
191202
type_string.iter().map(|c| format!("{c}")).join("")
192203
)?;
193204
if let Some(sort_mode) = sort_mode {
194-
write!(w, " {}", sort_mode.as_str())?;
205+
write!(f, " {}", sort_mode.as_str())?;
195206
}
196207
if let Some(label) = label {
197-
write!(w, " {}", label)?;
208+
write!(f, " {}", label)?;
198209
}
199-
writeln!(w)?;
200-
writeln!(w, "{}", sql)?;
210+
writeln!(f)?;
211+
writeln!(f, "{}", sql)?;
201212

202-
write!(w, "----")?;
213+
write!(f, "----")?;
203214
for result in expected_results {
204-
write!(w, "\n{}", result)?;
215+
write!(f, "\n{}", result)?;
205216
}
206-
Ok(())
217+
// query always ends with a blank line
218+
writeln!(f)
207219
}
208220
Record::Sleep { loc: _, duration } => {
209-
write!(w, "sleep {}", humantime::format_duration(*duration))
221+
write!(f, "sleep {}", humantime::format_duration(*duration))
210222
}
211223
Record::Subtest { loc: _, name } => {
212-
write!(w, "subtest {}", name)
224+
write!(f, "subtest {}", name)
213225
}
214226
Record::Halt { loc: _ } => {
215-
write!(w, "halt")
227+
write!(f, "halt")
216228
}
217229
Record::Control(c) => match c {
218-
Control::SortMode(m) => write!(w, "control sortmode {}", m.as_str()),
230+
Control::SortMode(m) => write!(f, "control sortmode {}", m.as_str()),
219231
},
220232
Record::Condition(cond) => match cond {
221233
Condition::OnlyIf { engine_name } => {
222-
write!(w, "onlyif {}", engine_name)
234+
write!(f, "onlyif {}", engine_name)
223235
}
224236
Condition::SkipIf { engine_name } => {
225-
write!(w, "skipif {}", engine_name)
237+
write!(f, "skipif {}", engine_name)
226238
}
227239
},
228240
Record::HashThreshold { loc: _, threshold } => {
229-
write!(w, "hash-threshold {}", threshold)
241+
write!(f, "hash-threshold {}", threshold)
230242
}
231243
Record::Comment(comment) => {
232244
let mut iter = comment.iter();
233-
write!(w, "#{}", iter.next().unwrap().trim_end())?;
245+
write!(f, "#{}", iter.next().unwrap().trim_end())?;
234246
for line in iter {
235-
write!(w, "\n#{}", line.trim_end())?;
247+
write!(f, "\n#{}", line.trim_end())?;
236248
}
237249
Ok(())
238250
}
251+
Record::Newline => Ok(()), // Display doesn't end with newline
239252
Record::Injected(p) => panic!("unexpected injected record: {:?}", p),
240253
}
241254
}
@@ -391,6 +404,7 @@ fn parse_inner(loc: &Location, script: &str) -> Result<Vec<Record>, ParseError>
391404
}
392405

393406
if line.is_empty() {
407+
records.push(Record::Newline);
394408
continue;
395409
}
396410

@@ -603,11 +617,87 @@ fn parse_file_inner(loc: Location) -> Result<Vec<Record>, ParseError> {
603617

604618
#[cfg(test)]
605619
mod tests {
606-
use crate::parse_file;
620+
use difference::{Changeset, Difference};
621+
622+
use super::*;
607623

608624
#[test]
609625
fn test_include_glob() {
610626
let records = parse_file("../examples/include/include_1.slt").unwrap();
611-
assert_eq!(14, records.len());
627+
assert_eq!(16, records.len());
628+
}
629+
630+
#[test]
631+
fn test_basic() {
632+
parse_roundtrip("../examples/basic/basic.slt")
633+
}
634+
635+
/// Parses the specified file into Records, and ensures the
636+
/// results of unparsing them are the same
637+
///
638+
/// Prints a hopefully useful message on failure
639+
fn parse_roundtrip(filename: impl AsRef<Path>) {
640+
let filename = filename.as_ref();
641+
let input_contents = std::fs::read_to_string(filename).expect("reading file");
642+
643+
let records = parse_file(filename).expect("parsing to complete");
644+
645+
let unparsed = records
646+
.iter()
647+
.map(|record| record.to_string())
648+
.collect::<Vec<_>>();
649+
650+
// Technically this will not always be the same due to some whitespace normalization
651+
//
652+
// query III
653+
// select * from foo;
654+
// ----
655+
// 1 2
656+
//
657+
// Will print out collaposting the spaces between `query`
658+
//
659+
// query III
660+
// select * from foo;
661+
// ----
662+
// 1 2
663+
let output_contents = unparsed.join("\n");
664+
665+
let changeset = Changeset::new(&input_contents, &output_contents, "\n");
666+
667+
assert!(
668+
no_diffs(&changeset),
669+
"Mismatch for {:?}\n\
670+
*********\n\
671+
diff:\n\
672+
*********\n\
673+
{}\n\n\
674+
*********\n\
675+
output:\n\
676+
*********\n\
677+
{}\n\n",
678+
filename,
679+
UsefulDiffDisplay(&changeset),
680+
output_contents,
681+
);
682+
}
683+
684+
/// returns true if there are no differences in the changeset
685+
fn no_diffs(changeset: &Changeset) -> bool {
686+
changeset
687+
.diffs
688+
.iter()
689+
.all(|diff| matches!(diff, Difference::Same(_)))
690+
}
691+
692+
struct UsefulDiffDisplay<'a>(&'a Changeset);
693+
694+
impl<'a> std::fmt::Display for UsefulDiffDisplay<'a> {
695+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
696+
self.0.diffs.iter().try_for_each(|diff| match diff {
697+
Difference::Same(x) => writeln!(f, "{x}"),
698+
Difference::Add(x) => writeln!(f, "+ {x}"),
699+
Difference::Rem(x) => writeln!(f, "- {x}"),
700+
})
701+
}
612702
}
613703
}

sqllogictest/src/runner.rs

+1
Original file line numberDiff line numberDiff line change
@@ -546,6 +546,7 @@ impl<D: AsyncDB> Runner<D> {
546546
}
547547
Record::Include { .. }
548548
| Record::Comment(_)
549+
| Record::Newline
549550
| Record::Subtest { .. }
550551
| Record::Halt { .. }
551552
| Record::Injected(_)

0 commit comments

Comments
 (0)