Skip to content

Commit 424b92f

Browse files
committed
Add AST JSON golden files for regression testing
Create ast.json files for all 6033 passing tests to ensure AST structure doesn't change unintentionally. Changes include: - Add cmd/generateast tool to generate ast.json files for passing tests - Update parser_test.go to verify AST JSON matches golden files - Generate ast.json for each test that passes the explain.txt comparison
1 parent 43cd5f7 commit 424b92f

6,035 files changed

Lines changed: 147399 additions & 0 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

cmd/generateast/main.go

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
package main
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
"fmt"
7+
"os"
8+
"path/filepath"
9+
"strings"
10+
"time"
11+
12+
"github.com/kyleconroy/doubleclick/parser"
13+
)
14+
15+
type testMetadata struct {
16+
Todo bool `json:"todo,omitempty"`
17+
Source string `json:"source,omitempty"`
18+
Explain *bool `json:"explain,omitempty"`
19+
Skip bool `json:"skip,omitempty"`
20+
ParseError bool `json:"parse_error,omitempty"`
21+
}
22+
23+
func main() {
24+
testdataDir := "parser/testdata"
25+
26+
entries, err := os.ReadDir(testdataDir)
27+
if err != nil {
28+
fmt.Fprintf(os.Stderr, "Failed to read testdata directory: %v\n", err)
29+
os.Exit(1)
30+
}
31+
32+
var generated, skipped, failed int
33+
34+
for _, entry := range entries {
35+
if !entry.IsDir() {
36+
continue
37+
}
38+
39+
testDir := filepath.Join(testdataDir, entry.Name())
40+
testName := entry.Name()
41+
42+
// Read optional metadata
43+
var metadata testMetadata
44+
metadataPath := filepath.Join(testDir, "metadata.json")
45+
if metadataBytes, err := os.ReadFile(metadataPath); err == nil {
46+
if err := json.Unmarshal(metadataBytes, &metadata); err != nil {
47+
fmt.Printf("SKIP %s: failed to parse metadata.json: %v\n", testName, err)
48+
skipped++
49+
continue
50+
}
51+
}
52+
53+
// Skip tests marked with skip: true
54+
if metadata.Skip {
55+
skipped++
56+
continue
57+
}
58+
59+
// Skip tests where explain is explicitly false
60+
if metadata.Explain != nil && !*metadata.Explain {
61+
skipped++
62+
continue
63+
}
64+
65+
// Skip tests marked as todo (they don't pass yet)
66+
if metadata.Todo {
67+
skipped++
68+
continue
69+
}
70+
71+
// Skip tests marked as parse_error (intentionally invalid SQL)
72+
if metadata.ParseError {
73+
skipped++
74+
continue
75+
}
76+
77+
// Check if explain.txt exists (we only generate ast.json for tests with explain.txt)
78+
explainPath := filepath.Join(testDir, "explain.txt")
79+
expectedBytes, err := os.ReadFile(explainPath)
80+
if err != nil {
81+
skipped++
82+
continue
83+
}
84+
85+
// Read the query
86+
queryPath := filepath.Join(testDir, "query.sql")
87+
queryBytes, err := os.ReadFile(queryPath)
88+
if err != nil {
89+
fmt.Printf("SKIP %s: failed to read query.sql: %v\n", testName, err)
90+
skipped++
91+
continue
92+
}
93+
94+
// Build query from non-comment lines until we hit a line ending with semicolon
95+
var queryParts []string
96+
for _, line := range strings.Split(string(queryBytes), "\n") {
97+
trimmed := strings.TrimSpace(line)
98+
if trimmed == "" || strings.HasPrefix(trimmed, "--") {
99+
continue
100+
}
101+
lineContent := trimmed
102+
if idx := strings.Index(trimmed, " -- "); idx >= 0 {
103+
lineContent = strings.TrimSpace(trimmed[:idx])
104+
}
105+
if strings.HasSuffix(lineContent, ";") {
106+
queryParts = append(queryParts, lineContent)
107+
break
108+
}
109+
queryParts = append(queryParts, trimmed)
110+
}
111+
query := strings.Join(queryParts, " ")
112+
113+
// Parse the query with timeout
114+
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
115+
stmts, err := parser.Parse(ctx, strings.NewReader(query))
116+
cancel()
117+
118+
if err != nil {
119+
fmt.Printf("FAIL %s: parse error: %v\n", testName, err)
120+
failed++
121+
continue
122+
}
123+
124+
if len(stmts) == 0 {
125+
fmt.Printf("FAIL %s: no statements returned\n", testName)
126+
failed++
127+
continue
128+
}
129+
130+
// Compare explain output
131+
expected := strings.TrimSpace(string(expectedBytes))
132+
// Strip server error messages from expected output
133+
if idx := strings.Index(expected, "\nThe query succeeded but the server error"); idx != -1 {
134+
expected = strings.TrimSpace(expected[:idx])
135+
}
136+
actual := strings.TrimSpace(parser.Explain(stmts[0]))
137+
138+
if actual != expected {
139+
fmt.Printf("FAIL %s: explain mismatch\n", testName)
140+
failed++
141+
continue
142+
}
143+
144+
// Generate ast.json
145+
astBytes, err := json.MarshalIndent(stmts[0], "", " ")
146+
if err != nil {
147+
fmt.Printf("FAIL %s: JSON marshal error: %v\n", testName, err)
148+
failed++
149+
continue
150+
}
151+
152+
astPath := filepath.Join(testDir, "ast.json")
153+
if err := os.WriteFile(astPath, append(astBytes, '\n'), 0644); err != nil {
154+
fmt.Printf("FAIL %s: failed to write ast.json: %v\n", testName, err)
155+
failed++
156+
continue
157+
}
158+
159+
generated++
160+
}
161+
162+
fmt.Printf("\nGenerated: %d, Skipped: %d, Failed: %d\n", generated, skipped, failed)
163+
}

parser/parser_test.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,21 @@ func TestParser(t *testing.T) {
156156
t.Errorf("Explain output mismatch\nQuery: %s\nExpected:\n%s\n\nGot:\n%s", query, expected, actual)
157157
}
158158
}
159+
160+
// Check AST JSON output if ast.json exists (golden file for AST regression testing)
161+
astPath := filepath.Join(testDir, "ast.json")
162+
if expectedASTBytes, err := os.ReadFile(astPath); err == nil {
163+
actualASTBytes, _ := json.MarshalIndent(stmts[0], "", " ")
164+
expectedAST := strings.TrimSpace(string(expectedASTBytes))
165+
actualAST := strings.TrimSpace(string(actualASTBytes))
166+
if actualAST != expectedAST {
167+
if metadata.Todo {
168+
t.Skipf("TODO: AST JSON mismatch\nQuery: %s\nExpected:\n%s\n\nGot:\n%s", query, expectedAST, actualAST)
169+
return
170+
}
171+
t.Errorf("AST JSON mismatch\nQuery: %s\nExpected:\n%s\n\nGot:\n%s", query, expectedAST, actualAST)
172+
}
173+
}
159174
})
160175
}
161176
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
{
2+
"selects": [
3+
{
4+
"columns": [
5+
{
6+
"name": "count"
7+
}
8+
],
9+
"from": {
10+
"tables": [
11+
{
12+
"table": {
13+
"table": {
14+
"database": "test",
15+
"table": "hits"
16+
}
17+
}
18+
}
19+
]
20+
}
21+
}
22+
]
23+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"selects": [
3+
{
4+
"columns": [
5+
{
6+
"type": "Integer",
7+
"value": 1
8+
}
9+
]
10+
}
11+
]
12+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
{
2+
"selects": [
3+
{
4+
"columns": [
5+
{
6+
"name": "sum",
7+
"arguments": [
8+
{
9+
"parts": [
10+
"Sign"
11+
]
12+
}
13+
]
14+
}
15+
],
16+
"from": {
17+
"tables": [
18+
{
19+
"table": {
20+
"table": {
21+
"database": "test",
22+
"table": "visits"
23+
}
24+
}
25+
}
26+
]
27+
}
28+
}
29+
]
30+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"settings": [
3+
{
4+
"name": "send_logs_level",
5+
"value": {
6+
"type": "String",
7+
"value": "fatal"
8+
}
9+
}
10+
]
11+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
{
2+
"selects": [
3+
{
4+
"columns": [
5+
{
6+
"parts": [
7+
"number"
8+
]
9+
}
10+
],
11+
"from": {
12+
"tables": [
13+
{
14+
"table": {
15+
"table": {
16+
"database": "system",
17+
"table": "numbers"
18+
}
19+
}
20+
}
21+
]
22+
},
23+
"where": {
24+
"left": {
25+
"name": "reinterpretAsString",
26+
"arguments": [
27+
{
28+
"parts": [
29+
"number"
30+
]
31+
}
32+
]
33+
},
34+
"op": "=",
35+
"right": {
36+
"type": "String",
37+
"value": "Ё"
38+
}
39+
},
40+
"limit": {
41+
"type": "Integer",
42+
"value": 1
43+
}
44+
}
45+
]
46+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
{
2+
"selects": [
3+
{
4+
"columns": [
5+
{
6+
"left": {
7+
"parts": [
8+
"dummy"
9+
],
10+
"alias": "x"
11+
},
12+
"op": "-",
13+
"right": {
14+
"type": "Integer",
15+
"value": 1
16+
}
17+
}
18+
],
19+
"from": {
20+
"tables": [
21+
{
22+
"table": {
23+
"table": {
24+
"name": "remote",
25+
"arguments": [
26+
{
27+
"type": "String",
28+
"value": "127.0.0.{2,3}"
29+
},
30+
{
31+
"parts": [
32+
"system"
33+
]
34+
},
35+
{
36+
"parts": [
37+
"one"
38+
]
39+
}
40+
]
41+
}
42+
}
43+
}
44+
]
45+
}
46+
}
47+
]
48+
}

0 commit comments

Comments
 (0)