Skip to content

Commit 150f1a2

Browse files
test: add Readme.md for macros, fix some bugs, add expectations (#205)
1 parent 490f64f commit 150f1a2

File tree

9 files changed

+137
-22
lines changed

9 files changed

+137
-22
lines changed

crates/pglt_analyse/src/rule.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,11 @@ impl RuleDiagnostic {
263263
pub fn advices(&self) -> &RuleAdvice {
264264
&self.rule_advice
265265
}
266+
267+
/// Will return the rule's category name as defined via `define_categories! { .. }`.
268+
pub fn get_category_name(&self) -> &'static str {
269+
self.category.name()
270+
}
266271
}
267272

268273
#[derive(Debug, Clone, Eq)]

crates/pglt_analyser/tests/rules_tests.rs

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,14 +83,25 @@ fn write_snapshot(snapshot: &mut String, query: &str, diagnostics: &[RuleDiagnos
8383
enum Expectation {
8484
NoDiagnostics,
8585
AnyDiagnostics,
86+
OnlyOne(String),
8687
}
8788

8889
impl Expectation {
8990
fn from_file(content: &str) -> Self {
9091
for line in content.lines() {
91-
if line.contains("expect-no-diagnostics") {
92+
if line.contains("expect_no_diagnostics") {
9293
return Self::NoDiagnostics;
9394
}
95+
96+
if line.contains("expect_only_") {
97+
let kind = line
98+
.splitn(3, "_")
99+
.last()
100+
.expect("Use pattern: `-- expect_only_<category>`")
101+
.trim();
102+
103+
return Self::OnlyOne(kind.into());
104+
}
94105
}
95106

96107
Self::AnyDiagnostics
@@ -103,7 +114,20 @@ impl Expectation {
103114
panic!("This test should not have any diagnostics.");
104115
}
105116
}
106-
_ => {}
117+
Self::OnlyOne(category) => {
118+
let found_kinds = diagnostics
119+
.iter()
120+
.map(|d| d.get_category_name())
121+
.collect::<Vec<&str>>()
122+
.join(", ");
123+
124+
if diagnostics.len() != 1 || diagnostics[0].get_category_name() != category {
125+
panic!(
126+
"This test should only have one diagnostic of kind: {category}\nReceived: {found_kinds}"
127+
);
128+
}
129+
}
130+
Self::AnyDiagnostics => {}
107131
}
108132
}
109133
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
1+
-- expect_only_lint/safety/banDropColumn
12
alter table test
23
drop column id;

crates/pglt_analyser/tests/specs/safety/banDropColumn/basic.sql.snap

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ expression: snapshot
44
---
55
# Input
66
```
7+
-- expect_only_lint/safety/banDropColumn
78
alter table test
89
drop column id;
910
```
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
-- expect_only_lint/safety/banDropNotNull
12
alter table users
23
alter column id
34
drop not null;

crates/pglt_analyser/tests/specs/safety/banDropNotNull/basic.sql.snap

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ expression: snapshot
44
---
55
# Input
66
```
7+
-- expect_only_lint/safety/banDropNotNull
78
alter table users
89
alter column id
910
drop not null;

crates/pglt_test_macros/README.md

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
# Tests macros
2+
3+
Macros to help auto-generate tests based on files.
4+
5+
## Usage
6+
7+
Pass a glob pattern that'll identify your files and a test-function that'll run for each file. The glob pattern has to start at the root of your crate.
8+
9+
You can add a `.expected.` file next to your test file. Its path will be passed to your test function so you can make outcome-based assertions. (Alternatively, write snapshot tests.)
10+
11+
Given the following file structure:
12+
13+
```txt
14+
crate/
15+
|-- src/
16+
|-- tests/
17+
|-- queries/
18+
|-- test.sql
19+
|-- test.expected.sql
20+
|-- querytest.rs
21+
```
22+
23+
You can generate tests like so:
24+
25+
```rust
26+
// crate/tests/querytest.rs
27+
28+
tests_macros::gen_tests!{
29+
"tests/queries/*.sql",
30+
crate::run_test // use `crate::` if the linter complains.
31+
}
32+
33+
fn run_test(
34+
test_path: &str, // absolute path on the machine
35+
expected_path: &str, // absolute path of .expected file
36+
test_dir: &str // absolute path of the test file's parent
37+
) {
38+
// your logic
39+
}
40+
```
41+
42+
Given a `crate/tests/queries/some_test_abc.sql` file, this will generate the following:
43+
44+
```rust
45+
#[test]
46+
pub fn some_test_abc()
47+
{
48+
let test_file = "<crate>/tests/queries/some_test_abc.sql";
49+
let test_expected_file = "<crate>/tests/queries/some_test_abc.expected.sql";
50+
let parent = "<crate>/tests/queries";
51+
run_test(test_file, test_expected_file, parent);
52+
}
53+
```
54+
55+
This will be replicated for each file matched by the glob pattern.
56+
57+
## Pitfalls
58+
59+
- If you use a Rust-keyword as a file name, this'll result in invalid syntax for the generated tests.
60+
- You might get linting errors if your test files aren't snake case.
61+
- All files of the glob-pattern must (currently) be `.sql` files.
62+
- The `.expected.sql` file-name will always be passed, even if the file doesn't exist.
63+
- The macro will wrap your tests in a `mod tests { .. }` module. If you need multiple generations, wrap them in modules like so: `mod some_test { tests_macros::gen_tests! { .. } }`.
64+
65+
## How to run
66+
67+
Simply run your `cargo test` commands as usual.

crates/pglt_test_macros/src/lib.rs

Lines changed: 26 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -197,38 +197,50 @@ struct Variables {
197197
impl TryFrom<PathBuf> for Variables {
198198
type Error = &'static str;
199199

200-
fn try_from(path: PathBuf) -> Result<Self, Self::Error> {
201-
let test_name = path
200+
fn try_from(mut path: PathBuf) -> Result<Self, Self::Error> {
201+
let test_name: String = path
202202
.file_stem()
203203
.ok_or("Cannot get file stem.")?
204204
.to_str()
205-
.ok_or("Cannot convert file stem to string.")?;
205+
.ok_or("Cannot convert file stem to string.")?
206+
.into();
206207

207-
let ext = path
208+
let ext: String = path
208209
.extension()
209210
.ok_or("Cannot get extension.")?
210211
.to_str()
211-
.ok_or("Cannot convert extension to string.")?;
212+
.ok_or("Cannot convert extension to string.")?
213+
.into();
212214
assert_eq!(ext, "sql", "Expected .sql extension but received: {}", ext);
213215

214-
let test_dir = path
216+
let test_dir: String = path
215217
.parent()
216218
.ok_or("Cannot get parent directory.")?
217219
.to_str()
218-
.ok_or("Cannot convert parent directory to string.")?;
220+
.ok_or("Cannot convert parent directory to string.")?
221+
.into();
219222

220-
let test_fullpath = path.to_str().ok_or("Cannot convert path to string.")?;
223+
let test_fullpath: String = path
224+
.as_os_str()
225+
.to_str()
226+
.ok_or("Cannot convert file stem to string.")?
227+
.into();
228+
229+
path.set_extension(OsStr::new(""));
221230

222-
let mut without_ext = test_fullpath.to_string();
223-
without_ext.pop();
231+
let without_ext: String = path
232+
.as_os_str()
233+
.to_str()
234+
.ok_or("Cannot convert file stem to string.")?
235+
.into();
224236

225237
let test_expected_fullpath = format!("{}.expected.{}", without_ext, ext);
226238

227239
Ok(Variables {
228-
test_name: test_name.into(),
229-
test_fullpath: test_fullpath.into(),
230-
test_expected_fullpath: test_expected_fullpath.into(),
231-
test_dir: test_dir.into(),
240+
test_name,
241+
test_fullpath,
242+
test_expected_fullpath,
243+
test_dir,
232244
})
233245
}
234246
}

xtask/codegen/src/generate_new_analyser_rule.rs

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -73,9 +73,9 @@ impl Rule for {rule_name_upper_camel} {{
7373
)
7474
}
7575

76-
static EXAMPLE_SQL: &'static str = r#"
77-
-- select 1;
78-
"#;
76+
fn gen_sql(category_name: &str) -> String {
77+
format!("-- expect_only_{category_name}\n-- select 1;").into()
78+
}
7979

8080
pub fn generate_new_analyser_rule(category: Category, rule_name: &str, group: &str) {
8181
let rule_name_camel = Case::Camel.convert(rule_name);
@@ -143,7 +143,10 @@ pub fn generate_new_analyser_rule(category: Category, rule_name: &str, group: &s
143143
std::fs::create_dir(test_folder.clone()).expect("To create the test rule folder");
144144
}
145145

146-
let test_file_name = format!("{}/query.sql", test_folder.display());
147-
std::fs::write(test_file_name.clone(), EXAMPLE_SQL)
148-
.unwrap_or_else(|_| panic!("To write {}", &test_file_name));
146+
let test_file_name = format!("{}/basic.sql", test_folder.display());
147+
std::fs::write(
148+
test_file_name.clone(),
149+
gen_sql(format!("lint/{group}/{rule_name_camel}").as_str()),
150+
)
151+
.unwrap_or_else(|_| panic!("To write {}", &test_file_name));
149152
}

0 commit comments

Comments
 (0)