Skip to content

Commit 87ba5be

Browse files
authored
Refactor Conformance Test Generation for better test logic reuse and shallower test tree. (#205)
* Refactor test/module escaping * Encapsulate Writer, add 'generated' banner, stop generating top level * Refactor to use provided `pass_*` and `fail_*` testing functions * Add ability to collapse generation at a given module depth
1 parent c76d6a1 commit 87ba5be

File tree

10 files changed

+230
-119
lines changed

10 files changed

+230
-119
lines changed

partiql-conformance-test-generator/src/generator.rs

Lines changed: 17 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use crate::schema::spec::{Assertion, Namespace, TestCase, TestDocument};
22
use crate::schema::structure::{TestDir, TestEntry, TestFile, TestRoot};
33

4-
use crate::StringExt;
4+
use crate::util::Escaper;
55
use codegen::{Function, Module, Scope};
66
use std::collections::HashMap;
77

@@ -58,12 +58,8 @@ impl Generator {
5858
}
5959

6060
pub fn generate(mut self, root: TestRoot) -> miette::Result<TestModule> {
61-
let TestRoot { fail, success } = root;
62-
for f in fail {
63-
self.test_entry(f)
64-
}
65-
for s in success {
66-
self.test_entry(s)
61+
for entry in root.0 {
62+
self.test_entry(entry)
6763
}
6864

6965
Ok(self.result)
@@ -72,7 +68,7 @@ impl Generator {
7268
fn test_entry(&mut self, entry: TestEntry) {
7369
match entry {
7470
TestEntry::Dir(TestDir { dir_name, contents }) => {
75-
self.curr_path.push(dir_name);
71+
self.curr_path.push(dir_name.escape_path());
7672
for c in contents {
7773
self.test_entry(c);
7874
}
@@ -82,7 +78,7 @@ impl Generator {
8278
file_name,
8379
contents,
8480
}) => {
85-
let mod_name = file_name.replace(".ion", "").escaped_snake_case();
81+
let mod_name = file_name.replace(".ion", "").escape_path();
8682
let out_file = format!("{}.rs", &mod_name);
8783
let path: Vec<_> = self
8884
.curr_path
@@ -107,27 +103,32 @@ fn gen_tests(scope: &mut Scope, test_document: &TestDocument) {
107103
}
108104

109105
fn gen_mod(scope: &mut Scope, namespace: &Namespace) {
110-
let module = scope.new_module(&namespace.name);
106+
let module = scope.new_module(&namespace.name.escape_module_name());
107+
111108
for ns in &namespace.namespaces {
112109
gen_mod(module.scope(), ns);
113110
}
114111
for test in &namespace.test_cases {
115112
gen_test(module.scope(), test);
116113
}
117114
}
115+
118116
fn gen_test(scope: &mut Scope, test_case: &TestCase) {
119-
let test_fn: &mut Function = scope.new_fn(&test_case.test_name);
117+
let test_fn: &mut Function = scope.new_fn(&test_case.test_name.escape_test_name());
120118
test_fn.attr("test");
121-
test_fn.line(format!("let statement = r#\"{}\"#;", &test_case.statement));
122119
for assertion in &test_case.assertions {
123120
match assertion {
124121
Assertion::SyntaxSuccess => {
125-
test_fn.line("let res = partiql_parser::Parser::default().parse(statement);");
126-
test_fn.line(r#"assert!(res.is_ok(), "For `{}`, expected `Ok(_)`, but was `{:#?}`", statement, res);"#);
122+
test_fn.line(format!(
123+
r####"crate::pass_syntax(r#"{}"#);"####,
124+
&test_case.statement
125+
));
127126
}
128127
Assertion::SyntaxFail => {
129-
test_fn.line("let res = partiql_parser::Parser::default().parse(statement);");
130-
test_fn.line(r#"assert!(res.is_err(), "For `{}`, expected `Err(_)`, but was `{:#?}`", statement, res);"#);
128+
test_fn.line(format!(
129+
r####"crate::fail_syntax(r#"{}"#);"####,
130+
&test_case.statement
131+
));
131132
}
132133
Assertion::NotYetImplemented => {
133134
// for `NotYetImplemented` assertions, add the 'ignore' annotation to the test case

partiql-conformance-test-generator/src/lib.rs

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,14 @@
1+
use crate::generator::Generator;
2+
use crate::reader::read_schema;
3+
use crate::writer::{TreeDepth, Writer, WriterConfig};
4+
use std::path::Path;
5+
16
mod generator;
27
mod reader;
38
mod schema;
49
mod util;
510
mod writer;
611

7-
use crate::generator::Generator;
8-
use crate::schema::spec::{
9-
Assertion, Assertions, Namespace, Namespaces, TestCase, TestCases, TestDocument,
10-
};
11-
use crate::util::StringExt;
12-
13-
use std::path::Path;
14-
15-
use crate::reader::read_schema;
16-
1712
// TODO docs
1813
#[derive(Debug, Copy, Clone)]
1914
pub enum OverwriteStrategy {
@@ -49,7 +44,8 @@ impl Config {
4944
let scopes = Generator::new().generate(schema)?;
5045

5146
// TODO implement OverwriteStrategy
52-
writer::write_scopes(out_path, scopes)?;
47+
let config = WriterConfig::new(TreeDepth::N(4));
48+
Writer::new(config).write(out_path, scopes)?;
5349
Ok(())
5450
}
5551
}

partiql-conformance-test-generator/src/reader.rs

Lines changed: 9 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,3 @@
1-
use crate::schema::structure::{TestDir, TestEntry, TestFile, TestRoot};
2-
use crate::{
3-
Assertion, Assertions, Namespace, Namespaces, StringExt, TestCase, TestCases, TestDocument,
4-
};
5-
61
use ion_rs::value::owned::Element;
72
use ion_rs::value::reader::{element_reader, ElementReader};
83
use ion_rs::value::{IonElement, IonSequence, IonStruct};
@@ -13,19 +8,17 @@ use std::ffi::OsStr;
138
use std::fs;
149
use std::fs::DirEntry;
1510

16-
use std::ops::Add;
17-
use std::path::{Path, PathBuf};
11+
use crate::schema::spec::{
12+
Assertion, Assertions, Namespace, Namespaces, TestCase, TestCases, TestDocument,
13+
};
14+
use crate::schema::structure::{TestDir, TestEntry, TestFile, TestRoot};
15+
use std::path::Path;
1816

1917
pub fn read_schema(root: impl AsRef<Path>) -> miette::Result<TestRoot> {
20-
let fail = read_root(&root, "fail")?;
21-
let success = read_root(&root, "success")?;
22-
Ok(TestRoot { fail, success })
18+
read_root(&root).map(|r| TestRoot(r))
2319
}
2420

25-
fn read_root(root: impl AsRef<Path>, root_type: &str) -> miette::Result<Vec<TestEntry>> {
26-
let mut dir: PathBuf = PathBuf::from(root.as_ref());
27-
dir.push(root_type);
28-
21+
fn read_root(root: impl AsRef<Path>) -> miette::Result<Vec<TestEntry>> {
2922
let root = fs::read_dir(root)
3023
.into_diagnostic()?
3124
.filter_map(|entry| read_entry(entry.expect("entry")))
@@ -124,8 +117,7 @@ fn test_namespace(element: &Element) -> Namespace {
124117
let name = annot
125118
.first()
126119
.expect("expected an annotation for the namespace")
127-
.escaped_snake_case()
128-
.add("_namespace");
120+
.to_string();
129121

130122
let mut namespaces: Namespaces = Vec::new();
131123
let mut test_cases: TestCases = Vec::new();
@@ -175,8 +167,7 @@ fn test_case(element: &Element) -> TestCase {
175167
.expect("name")
176168
.as_str()
177169
.expect("as_str()")
178-
.escaped_snake_case()
179-
.add("_test");
170+
.to_string();
180171
let statement = test_struct
181172
.get("statement")
182173
.expect("statement")

partiql-conformance-test-generator/src/schema.rs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,7 @@
11
pub mod structure {
22
use crate::schema::spec::TestDocument;
33

4-
pub struct TestRoot {
5-
pub fail: Vec<TestEntry>,
6-
pub success: Vec<TestEntry>,
7-
}
4+
pub struct TestRoot(pub Vec<TestEntry>);
85

96
pub enum TestEntry {
107
Dir(TestDir),
Lines changed: 46 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,70 @@
11
use inflector::Inflector;
22

3-
pub trait StringExt {
4-
/// Converts a string slice into an escaped `String`
5-
fn escaped_snake_case(&self) -> String;
3+
pub trait Escaper {
4+
/// Escapes a string intended to be used in a file path
5+
fn escape_path(&self) -> String;
6+
7+
/// Escapes a string intended to be used as the name of a test
8+
fn escape_test_name(&self) -> String;
9+
10+
/// Escapes a string intended to be used as the name of a module
11+
fn escape_module_name(&self) -> String;
612
}
713

8-
impl StringExt for &str {
9-
fn escaped_snake_case(&self) -> String {
10-
// TODO: currently non-text, non-numeric tokens are ignored and whitespace is converted to
11-
// underscores. More complicated escaping can be done to preserve more tokens
12-
// (e.g. punctuation)
14+
impl Escaper for &str {
15+
fn escape_path(&self) -> String {
1316
self.to_snake_case()
1417
}
18+
19+
fn escape_test_name(&self) -> String {
20+
format!("r#{}", self.to_snake_case())
21+
}
22+
fn escape_module_name(&self) -> String {
23+
format!("r#{}", self.to_snake_case())
24+
}
1525
}
1626

17-
impl StringExt for String {
18-
fn escaped_snake_case(&self) -> String {
19-
self.as_str().escaped_snake_case()
27+
impl Escaper for String {
28+
fn escape_path(&self) -> String {
29+
self.as_str().escape_path()
30+
}
31+
32+
fn escape_test_name(&self) -> String {
33+
self.as_str().escape_test_name()
34+
}
35+
36+
fn escape_module_name(&self) -> String {
37+
self.as_str().escape_module_name()
2038
}
2139
}
2240

2341
#[cfg(test)]
2442
mod test {
25-
use crate::util::StringExt;
43+
use crate::util::Escaper;
2644

2745
#[test]
2846
fn escaping_letters_and_whitespace() {
29-
assert_eq!("a B c \t D \n e_f_G".escaped_snake_case(), "a_b_c_d_e_f_g")
47+
assert_eq!("a B c \t D \n e_f_G".escape_path(), "a_b_c_d_e_f_g");
48+
assert_eq!("a B c \t D \n e_f_G".escape_test_name(), "r#a_b_c_d_e_f_g");
49+
assert_eq!(
50+
"a B c \t D \n e_f_G".escape_module_name(),
51+
"r#a_b_c_d_e_f_g"
52+
);
3053
}
3154

3255
#[test]
3356
fn escaping_letters_numbers_other_chars() {
3457
assert_eq!(
35-
"a B c 1 2 3 e f G !?#$%*!(".escaped_snake_case(),
58+
"a B c 1 2 3 e f G !?#$%*!(".escape_path(),
3659
"a_b_c_1_2_3_e_f_g"
37-
)
60+
);
61+
assert_eq!(
62+
"a B c 1 2 3 e f G !?#$%*!(".escape_test_name(),
63+
"r#a_b_c_1_2_3_e_f_g"
64+
);
65+
assert_eq!(
66+
"a B c 1 2 3 e f G !?#$%*!(".escape_module_name(),
67+
"r#a_b_c_1_2_3_e_f_g"
68+
);
3869
}
3970
}

0 commit comments

Comments
 (0)