Skip to content

Commit a301aba

Browse files
ok
1 parent 490f64f commit a301aba

File tree

8 files changed

+178
-24
lines changed

8 files changed

+178
-24
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: 45 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,17 @@ pglt_test_macros::gen_tests! {
1111
crate::rule_test
1212
}
1313

14+
mod testibear {
15+
pglt_test_macros::gen_tests! {
16+
"tests/specs/**/*.sql",
17+
crate::printer
18+
}
19+
}
20+
21+
fn printer(fp: &'static str, expected: &str, dir: &str) {
22+
println!("{fp}\n{expected}\n{dir}");
23+
}
24+
1425
fn rule_test(full_path: &'static str, _: &str, _: &str) {
1526
let input_file = Path::new(full_path);
1627

@@ -34,15 +45,15 @@ fn rule_test(full_path: &'static str, _: &str, _: &str) {
3445

3546
let results = analyser.run(AnalyserContext { root: &ast });
3647

37-
let mut snapshot = String::new();
38-
write_snapshot(&mut snapshot, query.as_str(), results.as_slice());
48+
// let mut snapshot = String::new();
49+
// write_snapshot(&mut snapshot, query.as_str(), results.as_slice());
3950

40-
insta::with_settings!({
41-
prepend_module_to_snapshot => false,
42-
snapshot_path => input_file.parent().unwrap(),
43-
}, {
44-
insta::assert_snapshot!(fname, snapshot);
45-
});
51+
// insta::with_settings!({
52+
// prepend_module_to_snapshot => false,
53+
// snapshot_path => input_file.parent().unwrap(),
54+
// }, {
55+
// insta::assert_snapshot!(fname, snapshot);
56+
// });
4657

4758
let expectation = Expectation::from_file(&query);
4859
expectation.assert(results.as_slice());
@@ -83,14 +94,25 @@ fn write_snapshot(snapshot: &mut String, query: &str, diagnostics: &[RuleDiagnos
8394
enum Expectation {
8495
NoDiagnostics,
8596
AnyDiagnostics,
97+
OnlyOne(String),
8698
}
8799

88100
impl Expectation {
89101
fn from_file(content: &str) -> Self {
90102
for line in content.lines() {
91-
if line.contains("expect-no-diagnostics") {
103+
if line.contains("expect_no_diagnostics") {
92104
return Self::NoDiagnostics;
93105
}
106+
107+
if line.contains("expect_only_") {
108+
let kind = line
109+
.splitn(3, "_")
110+
.last()
111+
.expect("Use pattern: `-- expect_only_<category>`")
112+
.trim();
113+
114+
return Self::OnlyOne(kind.into());
115+
}
94116
}
95117

96118
Self::AnyDiagnostics
@@ -103,7 +125,20 @@ impl Expectation {
103125
panic!("This test should not have any diagnostics.");
104126
}
105127
}
106-
_ => {}
128+
Self::OnlyOne(category) => {
129+
let found_kinds = diagnostics
130+
.iter()
131+
.map(|d| d.get_category_name())
132+
.collect::<Vec<&str>>()
133+
.join(", ");
134+
135+
if diagnostics.len() != 1 || diagnostics[0].get_category_name() != category {
136+
panic!(
137+
"This test should only have one diagnostic of kind: {category}\nReceived: {found_kinds}"
138+
);
139+
}
140+
}
141+
Self::AnyDiagnostics => {}
107142
}
108143
}
109144
}
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;
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
---
2+
source: crates/pglt_analyser/tests/rules_tests.rs
3+
assertion_line: 44
4+
expression: snapshot
5+
---
6+
# Input
7+
```
8+
-- expect-only-safety/banDropColumn
9+
alter table test
10+
drop column id;
11+
```
12+
13+
# Diagnostics
14+
lint/safety/banDropColumn ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
15+
16+
× Dropping a column may break existing clients.
17+
18+
i You can leave the column as nullable or delete the column once queries no longer select or modify the column.
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;
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
---
2+
source: crates/pglt_analyser/tests/rules_tests.rs
3+
assertion_line: 44
4+
expression: snapshot
5+
---
6+
# Input
7+
```
8+
-- expect-only-bullshit
9+
alter table users
10+
alter column id
11+
drop not null;
12+
```
13+
14+
# Diagnostics
15+
lint/safety/banDropNotNull ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
16+
17+
× Dropping a NOT NULL constraint may break existing clients.
18+
19+
i Consider using a marker value that represents NULL. Alternatively, create a new table allowing NULL values, copy the data from the old table, and create a view that filters NULL values.

crates/pglt_test_macros/README.md

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
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 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+
Test name is the "snake case" version of the file name.
43+
this will generate the following for each file:
44+
45+
```rust
46+
#[test]
47+
pub fn somefilename()
48+
{
49+
let test_file = "<crate's cargo.toml full path>/tests/sometest.txt";
50+
let test_expected_file = "<crate's cargo.toml full path>/tests/sometest.expected.txt";
51+
run_test(test_file, test_expected_file);
52+
}
53+
```
54+
55+
## Pitfalls
56+
57+
- If you use a Rust-keyword as a file name, this'll result in invalid syntax for the generated tests.
58+
- All files of the glob-pattern must (currently) be `.sql` files.
59+
- 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! { .. } }`.
60+
61+
## How to run
62+
63+
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
}

0 commit comments

Comments
 (0)