Skip to content

Commit 95b33ab

Browse files
committed
Add tests for parser error recovery and diagnostics
Adds tests specifically designed to validate the parser's resilience and error-handling capabilities. Using a schema with known syntax errors, this test ensures that the parser can recover from failures and produce meaningful diagnostic messages for the user.
1 parent c6e4c01 commit 95b33ab

File tree

2 files changed

+352
-0
lines changed

2 files changed

+352
-0
lines changed
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// Schema with various syntax errors for error recovery testing
2+
3+
// Missing closing brace
4+
model User {
5+
id String @id
6+
7+
// Invalid field syntax
8+
model Post {
9+
id String @id
10+
title // missing type
11+
content String @invalid_attribute
12+
13+
// Unclosed enum
14+
enum Status {
15+
ACTIVE
16+
INACTIVE
17+
18+
// Invalid datasource
19+
datasource {
20+
provider = "postgresql"
21+
// missing name
22+
}
23+
24+
// Invalid generator
25+
generator client
26+
provider = "prisma-client-js"
27+
// missing braces
Lines changed: 325 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,325 @@
1+
//! Diagnostic Quality and Error Recovery Tests
2+
//!
3+
//! Tests that validate error handling, diagnostic quality, and recovery
4+
//! capabilities using the diagnostic utility functions.
5+
6+
use crate::scanner_parser::{
7+
assert_diagnostic_quality, parse_schema_end_to_end,
8+
};
9+
use std::fs;
10+
11+
fn load_fixture(path: &str) -> String {
12+
fs::read_to_string(format!("tests/fixtures/schemas/{path}"))
13+
.unwrap_or_else(|_| panic!("Failed to load fixture: {path}"))
14+
}
15+
16+
#[test]
17+
fn syntax_error_diagnostic_quality() {
18+
let invalid_schema = load_fixture("invalid/syntax_errors.prisma");
19+
let result = parse_schema_end_to_end(&invalid_schema);
20+
21+
// Should produce diagnostics for syntax errors
22+
assert!(
23+
!result.diagnostics.is_empty(),
24+
"Invalid schema should produce diagnostics"
25+
);
26+
27+
// All diagnostics should meet quality standards
28+
assert_diagnostic_quality(&result.diagnostics);
29+
30+
println!(
31+
"Found {} diagnostics for syntax errors",
32+
result.diagnostics.len()
33+
);
34+
for (i, diagnostic) in result.diagnostics.iter().enumerate() {
35+
println!("Diagnostic {}: {:?}", i + 1, diagnostic);
36+
}
37+
}
38+
39+
#[test]
40+
fn missing_brace_error() {
41+
let input = r"
42+
model User {
43+
id String @id
44+
name String
45+
// Missing closing brace
46+
";
47+
let result = parse_schema_end_to_end(input);
48+
49+
// Should produce error diagnostics
50+
assert!(
51+
!result.diagnostics.is_empty(),
52+
"Missing brace should produce diagnostics"
53+
);
54+
55+
// Validate diagnostic quality
56+
assert_diagnostic_quality(&result.diagnostics);
57+
58+
// All diagnostics have been validated by assert_diagnostic_quality above
59+
60+
println!("Missing brace diagnostics: {:#?}", result.diagnostics);
61+
}
62+
63+
#[test]
64+
fn invalid_field_syntax() {
65+
let input = r"
66+
model User {
67+
id String @id
68+
title // missing type
69+
content String
70+
}
71+
";
72+
let result = parse_schema_end_to_end(input);
73+
74+
// Should produce diagnostics for invalid field
75+
assert!(
76+
!result.diagnostics.is_empty(),
77+
"Invalid field should produce diagnostics"
78+
);
79+
assert_diagnostic_quality(&result.diagnostics);
80+
81+
// Should still attempt to parse the model
82+
if let Some(schema) = &result.schema {
83+
assert!(
84+
!schema.declarations.is_empty(),
85+
"Should still create some declarations"
86+
);
87+
}
88+
89+
println!("Invalid field diagnostics: {:#?}", result.diagnostics);
90+
}
91+
92+
#[test]
93+
fn unknown_attribute_error() {
94+
let input = r#"
95+
model User {
96+
id String @invalid_attribute
97+
name String @unknown_attr("param")
98+
}
99+
"#;
100+
let result = parse_schema_end_to_end(input);
101+
102+
// May or may not produce diagnostics depending on parser implementation
103+
// But if it does, they should be high quality
104+
if !result.diagnostics.is_empty() {
105+
assert_diagnostic_quality(&result.diagnostics);
106+
107+
// Diagnostics should point to the problematic attributes
108+
for diagnostic in &result.diagnostics {
109+
println!(
110+
"Attribute diagnostic: {} at {}:{}",
111+
diagnostic.message,
112+
diagnostic.span.start.line,
113+
diagnostic.span.start.column
114+
);
115+
}
116+
}
117+
}
118+
119+
#[test]
120+
fn unclosed_enum_error() {
121+
let input = r"
122+
enum Status {
123+
ACTIVE
124+
INACTIVE
125+
// Missing closing brace
126+
127+
model User {
128+
id String
129+
}
130+
";
131+
let result = parse_schema_end_to_end(input);
132+
133+
// Should produce error diagnostics
134+
assert!(
135+
!result.diagnostics.is_empty(),
136+
"Unclosed enum should produce diagnostics"
137+
);
138+
assert_diagnostic_quality(&result.diagnostics);
139+
140+
println!("Unclosed enum diagnostics: {:#?}", result.diagnostics);
141+
}
142+
143+
#[test]
144+
fn strict_mode_diagnostics() {
145+
let input = r"
146+
model User {
147+
id String @id
148+
optional_field String?
149+
}
150+
";
151+
152+
// Parse with strict options
153+
let strict_result = parse_schema_end_to_end(input);
154+
155+
// Compare with lenient parsing if there are differences
156+
if !strict_result.diagnostics.is_empty() {
157+
assert_diagnostic_quality(&strict_result.diagnostics);
158+
println!("Strict mode diagnostics: {:#?}", strict_result.diagnostics);
159+
}
160+
}
161+
162+
#[test]
163+
fn error_recovery_continues_parsing() {
164+
let input = r"
165+
model User {
166+
id String @id
167+
// Syntax error here
168+
invalid syntax error
169+
}
170+
171+
// Parser should recover and continue
172+
model Post {
173+
id String @id
174+
title String
175+
}
176+
177+
enum Status {
178+
ACTIVE
179+
INACTIVE
180+
}
181+
";
182+
let result = parse_schema_end_to_end(input);
183+
184+
// Should have diagnostics for the error
185+
assert!(
186+
!result.diagnostics.is_empty(),
187+
"Should produce diagnostics for syntax error"
188+
);
189+
assert_diagnostic_quality(&result.diagnostics);
190+
191+
// But should still parse some declarations
192+
if let Some(schema) = &result.schema {
193+
assert!(
194+
!schema.declarations.is_empty(),
195+
"Should recover and parse some declarations"
196+
);
197+
println!(
198+
"Recovered {} declarations despite errors",
199+
schema.declarations.len()
200+
);
201+
}
202+
203+
println!("Error recovery diagnostics: {:#?}", result.diagnostics);
204+
}
205+
206+
#[test]
207+
fn diagnostic_message_helpfulness() {
208+
let test_cases = [
209+
(r"model { }", "missing model name"),
210+
(r"model User", "missing braces"),
211+
(r"model User { id }", "missing field type"),
212+
];
213+
214+
for (input, expected_error_type) in test_cases {
215+
let result = parse_schema_end_to_end(input);
216+
217+
if !result.diagnostics.is_empty() {
218+
assert_diagnostic_quality(&result.diagnostics);
219+
220+
// Check that error messages are descriptive
221+
for diagnostic in &result.diagnostics {
222+
assert!(
223+
diagnostic.message.len() >= 10,
224+
"Diagnostic message should be reasonably descriptive: '{}'",
225+
diagnostic.message
226+
);
227+
228+
// Messages shouldn't just be generic
229+
assert!(
230+
!diagnostic.message.contains("error"),
231+
"Diagnostic messages should be specific, not generic: '{}'",
232+
diagnostic.message
233+
);
234+
}
235+
236+
println!("{}: {:#?}", expected_error_type, result.diagnostics);
237+
}
238+
}
239+
}
240+
241+
#[test]
242+
fn diagnostic_span_accuracy() {
243+
let input = r"model User {
244+
id String @id
245+
invalid_field_without_type
246+
name String
247+
}";
248+
249+
let result = parse_schema_end_to_end(input);
250+
251+
if !result.diagnostics.is_empty() {
252+
assert_diagnostic_quality(&result.diagnostics);
253+
254+
for diagnostic in &result.diagnostics {
255+
// Spans should point to reasonable locations
256+
assert!(
257+
diagnostic.span.start.line <= 5,
258+
"Diagnostic line should be within schema bounds"
259+
);
260+
261+
// End should be after or equal to start
262+
assert!(
263+
diagnostic.span.end.line >= diagnostic.span.start.line,
264+
"Diagnostic end line should be >= start line"
265+
);
266+
if diagnostic.span.end.line == diagnostic.span.start.line {
267+
assert!(
268+
diagnostic.span.end.column >= diagnostic.span.start.column,
269+
"Diagnostic end column should be >= start column on same line"
270+
);
271+
}
272+
273+
println!(
274+
"Diagnostic span: {}:{}-{}:{} - {}",
275+
diagnostic.span.start.line,
276+
diagnostic.span.start.column,
277+
diagnostic.span.end.line,
278+
diagnostic.span.end.column,
279+
diagnostic.message
280+
);
281+
}
282+
}
283+
}
284+
285+
#[test]
286+
fn multiple_error_handling() {
287+
let input = r"
288+
model { // Error 1: missing name
289+
id String @id
290+
invalid_field // Error 2: missing type
291+
}
292+
293+
enum { // Error 3: missing name
294+
VALUE1
295+
VALUE2
296+
}
297+
298+
model Post
299+
id String // Error 4: missing braces
300+
title String
301+
}
302+
";
303+
304+
let result = parse_schema_end_to_end(input);
305+
306+
// Should handle multiple errors gracefully
307+
if !result.diagnostics.is_empty() {
308+
assert_diagnostic_quality(&result.diagnostics);
309+
310+
// Should have multiple diagnostics
311+
assert!(
312+
result.diagnostics.len() >= 2,
313+
"Should detect multiple errors, found: {}",
314+
result.diagnostics.len()
315+
);
316+
317+
println!("Multiple error diagnostics: {:#?}", result.diagnostics);
318+
}
319+
320+
// Parser should not crash despite multiple errors
321+
assert!(
322+
result.parse_time.as_millis() < 1000,
323+
"Should complete parsing despite multiple errors"
324+
);
325+
}

0 commit comments

Comments
 (0)