Skip to content

Commit 490f64f

Browse files
test(analyser): generate tests with new-lintrule script and add testing suite (#203)
* test(analyser): add debug_test * format * test adjustments * nest deeper * with group folder * adjust that too * ok ok * checkout that… * dafuq it works * formatting * more format? * nope
1 parent 7cda2b3 commit 490f64f

File tree

12 files changed

+488
-14
lines changed

12 files changed

+488
-14
lines changed

Cargo.lock

Lines changed: 31 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -78,11 +78,8 @@ pglt_type_resolver = { path = "./crates/pglt_type_resolver", version =
7878
pglt_typecheck = { path = "./crates/pglt_typecheck", version = "0.0.0" }
7979
pglt_workspace = { path = "./crates/pglt_workspace", version = "0.0.0" }
8080

81-
pglt_test_utils = { path = "./crates/pglt_test_utils" }
81+
pglt_test_macros = { path = "./crates/pglt_test_macros" }
82+
pglt_test_utils = { path = "./crates/pglt_test_utils" }
8283
# parser = { path = "./crates/parser", version = "0.0.0" }
8384
# sql_parser = { path = "./crates/sql_parser", version = "0.0.0" }
8485
# sql_parser_codegen = { path = "./crates/sql_parser_codegen", version = "0.0.0" }
85-
86-
87-
[profile.dev.package]
88-
insta.opt-level = 3

crates/pglt_analyser/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,5 +18,7 @@ pglt_query_ext = { workspace = true }
1818
serde = { workspace = true }
1919

2020
[dev-dependencies]
21+
insta = { version = "1.42.1" }
2122
pglt_diagnostics = { workspace = true }
23+
pglt_test_macros = { workspace = true }
2224
termcolor = { workspace = true }
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
use core::slice;
2+
use std::{fmt::Write, fs::read_to_string, path::Path};
3+
4+
use pglt_analyse::{AnalyserOptions, AnalysisFilter, RuleDiagnostic, RuleFilter};
5+
use pglt_analyser::{Analyser, AnalyserConfig, AnalyserContext};
6+
use pglt_console::StdDisplay;
7+
use pglt_diagnostics::PrintDiagnostic;
8+
9+
pglt_test_macros::gen_tests! {
10+
"tests/specs/**/*.sql",
11+
crate::rule_test
12+
}
13+
14+
fn rule_test(full_path: &'static str, _: &str, _: &str) {
15+
let input_file = Path::new(full_path);
16+
17+
let (group, rule, fname) = parse_test_path(input_file);
18+
19+
let rule_filter = RuleFilter::Rule(group.as_str(), rule.as_str());
20+
let filter = AnalysisFilter {
21+
enabled_rules: Some(slice::from_ref(&rule_filter)),
22+
..Default::default()
23+
};
24+
25+
let query =
26+
read_to_string(full_path).expect(format!("Failed to read file: {} ", full_path).as_str());
27+
28+
let ast = pglt_query_ext::parse(&query).expect("failed to parse SQL");
29+
let options = AnalyserOptions::default();
30+
let analyser = Analyser::new(AnalyserConfig {
31+
options: &options,
32+
filter,
33+
});
34+
35+
let results = analyser.run(AnalyserContext { root: &ast });
36+
37+
let mut snapshot = String::new();
38+
write_snapshot(&mut snapshot, query.as_str(), results.as_slice());
39+
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+
});
46+
47+
let expectation = Expectation::from_file(&query);
48+
expectation.assert(results.as_slice());
49+
}
50+
51+
fn parse_test_path(path: &Path) -> (String, String, String) {
52+
let mut comps: Vec<&str> = path
53+
.components()
54+
.into_iter()
55+
.map(|c| c.as_os_str().to_str().unwrap())
56+
.collect();
57+
58+
let fname = comps.pop().unwrap();
59+
let rule = comps.pop().unwrap();
60+
let group = comps.pop().unwrap();
61+
62+
(group.into(), rule.into(), fname.into())
63+
}
64+
65+
fn write_snapshot(snapshot: &mut String, query: &str, diagnostics: &[RuleDiagnostic]) {
66+
writeln!(snapshot, "# Input").unwrap();
67+
writeln!(snapshot, "```").unwrap();
68+
writeln!(snapshot, "{query}").unwrap();
69+
writeln!(snapshot, "```").unwrap();
70+
writeln!(snapshot).unwrap();
71+
72+
if !diagnostics.is_empty() {
73+
writeln!(snapshot, "# Diagnostics").unwrap();
74+
for diagnostic in diagnostics {
75+
let printer = PrintDiagnostic::simple(diagnostic);
76+
77+
writeln!(snapshot, "{}", StdDisplay(printer)).unwrap();
78+
writeln!(snapshot).unwrap();
79+
}
80+
}
81+
}
82+
83+
enum Expectation {
84+
NoDiagnostics,
85+
AnyDiagnostics,
86+
}
87+
88+
impl Expectation {
89+
fn from_file(content: &str) -> Self {
90+
for line in content.lines() {
91+
if line.contains("expect-no-diagnostics") {
92+
return Self::NoDiagnostics;
93+
}
94+
}
95+
96+
Self::AnyDiagnostics
97+
}
98+
99+
fn assert(&self, diagnostics: &[RuleDiagnostic]) {
100+
match self {
101+
Self::NoDiagnostics => {
102+
if !diagnostics.is_empty() {
103+
panic!("This test should not have any diagnostics.");
104+
}
105+
}
106+
_ => {}
107+
}
108+
}
109+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
alter table test
2+
drop column id;
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
---
2+
source: crates/pglt_analyser/tests/rules_tests.rs
3+
expression: snapshot
4+
---
5+
# Input
6+
```
7+
alter table test
8+
drop column id;
9+
```
10+
11+
# Diagnostics
12+
lint/safety/banDropColumn ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
13+
14+
× Dropping a column may break existing clients.
15+
16+
i You can leave the column as nullable or delete the column once queries no longer select or modify the column.
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
alter table users
2+
alter column id
3+
drop not null;
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
---
2+
source: crates/pglt_analyser/tests/rules_tests.rs
3+
expression: snapshot
4+
---
5+
# Input
6+
```
7+
alter table users
8+
alter column id
9+
drop not null;
10+
```
11+
12+
# Diagnostics
13+
lint/safety/banDropNotNull ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
14+
15+
× Dropping a NOT NULL constraint may break existing clients.
16+
17+
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_console/src/utils.rs

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,30 @@
1-
use crate::fmt::{Display, Formatter};
1+
use termcolor::NoColor;
2+
3+
use crate::fmt::{Display, Formatter, Termcolor};
24
use crate::{markup, Markup};
35
use std::io;
46

7+
/// Adapter type providing a std::fmt::Display implementation for any type that
8+
/// implements pglt_console::fmt::Display.
9+
pub struct StdDisplay<T: Display>(pub T);
10+
11+
impl<T> std::fmt::Display for StdDisplay<T>
12+
where
13+
T: Display,
14+
{
15+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
16+
let mut buffer: Vec<u8> = Vec::new();
17+
let mut termcolor = Termcolor(NoColor::new(&mut buffer));
18+
let mut formatter = Formatter::new(&mut termcolor);
19+
20+
self.0.fmt(&mut formatter).map_err(|_| std::fmt::Error)?;
21+
22+
let content = String::from_utf8(buffer).map_err(|_| std::fmt::Error)?;
23+
24+
f.write_str(content.as_str())
25+
}
26+
}
27+
528
/// It displays a type that implements [std::fmt::Display]
629
pub struct DebugDisplay<T>(pub T);
730

crates/pglt_test_macros/Cargo.toml

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
2+
[package]
3+
authors.workspace = true
4+
categories.workspace = true
5+
description = "<DESCRIPTION>"
6+
edition.workspace = true
7+
homepage.workspace = true
8+
keywords.workspace = true
9+
license.workspace = true
10+
name = "pglt_test_macros"
11+
repository.workspace = true
12+
version = "0.0.0"
13+
14+
[lib]
15+
proc-macro = true
16+
17+
[dependencies]
18+
globwalk = { version = "0.9.1" }
19+
proc-macro-error = { version = "1.0.4" }
20+
proc-macro2 = { version = '1.0.93' }
21+
quote = { workspace = true }
22+
syn = { workspace = true }

0 commit comments

Comments
 (0)