Skip to content

Commit 8fffa8c

Browse files
authored
Add regenerate-explain tool for multi-statement test cases (#65)
1 parent 8567118 commit 8fffa8c

File tree

101,964 files changed

+1580484
-2973
lines changed

Some content is hidden

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

101,964 files changed

+1580484
-2973
lines changed

CLAUDE.md

Lines changed: 14 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,13 @@
22

33
## Next Steps
44

5-
To find the next test to work on, run:
5+
To find the next explain test to work on (fewest pending statements first), run:
66

77
```bash
8-
go run ./cmd/next-test
8+
go run ./cmd/next-test -explain
99
```
1010

11-
This tool finds all tests with `todo: true` in their metadata and returns the one with the shortest `query.sql` file.
11+
This finds tests with `explain_todo` entries in their metadata.
1212

1313
To find the next format roundtrip test to work on, run:
1414

@@ -18,18 +18,6 @@ go run ./cmd/next-test -format
1818

1919
This finds tests with `todo_format: true` in their metadata.
2020

21-
## Workflow
22-
23-
1. Run `go run ./cmd/next-test` to find the next test to implement
24-
2. Check the test's `query.sql` to understand what ClickHouse SQL needs parsing
25-
3. Check the test's `explain.txt` to understand the expected EXPLAIN output
26-
4. Implement the necessary AST types in `ast/`
27-
5. Add parser logic in `parser/parser.go`
28-
6. Update the `Explain()` function if needed to match ClickHouse's output format
29-
7. Enable the test by removing `todo: true` from its `metadata.json` (set it to `{}`)
30-
8. Run `go test ./parser/... -timeout 5s` to verify
31-
9. Check if other todo tests now pass (see below)
32-
3321
## Running Tests
3422

3523
Always run parser tests with a 5 second timeout:
@@ -40,38 +28,39 @@ go test ./parser/... -timeout 5s
4028

4129
The tests are very fast. If a test is timing out, it indicates a bug (likely an infinite loop in the parser).
4230

43-
## Checking for Newly Passing Todo Tests
31+
## Checking for Newly Passing Format Tests
4432

45-
After implementing parser changes, run:
33+
After implementing format changes, run:
4634

4735
```bash
48-
go test ./parser/... -check-skipped -v 2>&1 | grep "PASSES NOW"
36+
go test ./parser/... -check-format -v 2>&1 | grep "FORMAT PASSES NOW"
4937
```
5038

51-
Tests that output `PASSES NOW` can have their `todo` flag removed from `metadata.json`. This helps identify when parser improvements fix multiple tests at once.
39+
Tests that output `FORMAT PASSES NOW` can have their `todo_format` flag removed from `metadata.json`.
5240

53-
## Checking for Newly Passing Format Tests
41+
## Checking for Newly Passing Explain Tests
5442

55-
After implementing format changes, run:
43+
After implementing parser/explain changes, run:
5644

5745
```bash
58-
go test ./parser/... -check-format -v 2>&1 | grep "FORMAT PASSES NOW"
46+
go test ./parser/... -check-explain -v 2>&1 | grep "EXPLAIN PASSES NOW"
5947
```
6048

61-
Tests that output `FORMAT PASSES NOW` can have their `todo_format` flag removed from `metadata.json`.
49+
Tests that output `EXPLAIN PASSES NOW` can have their statement removed from `explain_todo` in `metadata.json`.
6250

6351
## Test Structure
6452

6553
Each test in `parser/testdata/` contains:
6654

67-
- `metadata.json` - `{}` for enabled tests, `{"todo": true}` for pending tests
55+
- `metadata.json` - `{}` for enabled tests
6856
- `query.sql` - ClickHouse SQL to parse
6957
- `explain.txt` - Expected EXPLAIN AST output (matches ClickHouse's format)
58+
- `explain_N.txt` - Expected EXPLAIN AST output for Nth statement (N >= 2)
7059

7160
### Metadata Options
7261

73-
- `todo: true` - Test is pending parser/explain implementation
7462
- `todo_format: true` - Format roundtrip test is pending implementation
63+
- `explain_todo: {"stmt2": true}` - Skip specific statement subtests
7564
- `skip: true` - Skip test entirely (e.g., causes infinite loop)
7665
- `explain: false` - Skip test (e.g., ClickHouse couldn't parse it)
7766
- `parse_error: true` - Query is intentionally invalid SQL

cmd/next-test/main.go

Lines changed: 66 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -9,24 +9,33 @@ import (
99
"sort"
1010
)
1111

12-
var formatFlag = flag.Bool("format", false, "Find tests with todo_format instead of todo")
12+
var formatFlag = flag.Bool("format", false, "Find tests with todo_format: true")
13+
var explainFlag = flag.Bool("explain", false, "Find tests with explain_todo entries (fewest first)")
1314

1415
type testMetadata struct {
15-
Todo bool `json:"todo,omitempty"`
16-
TodoFormat bool `json:"todo_format,omitempty"`
17-
Explain *bool `json:"explain,omitempty"`
18-
Skip bool `json:"skip,omitempty"`
19-
ParseError bool `json:"parse_error,omitempty"`
16+
TodoFormat bool `json:"todo_format,omitempty"`
17+
ExplainTodo map[string]bool `json:"explain_todo,omitempty"`
18+
Explain *bool `json:"explain,omitempty"`
19+
Skip bool `json:"skip,omitempty"`
20+
ParseError bool `json:"parse_error,omitempty"`
2021
}
2122

2223
type todoTest struct {
23-
name string
24-
querySize int
24+
name string
25+
querySize int
26+
explainTodoLen int
2527
}
2628

2729
func main() {
2830
flag.Parse()
2931

32+
if !*formatFlag && !*explainFlag {
33+
fmt.Fprintf(os.Stderr, "Usage: go run ./cmd/next-test [-format | -explain]\n")
34+
fmt.Fprintf(os.Stderr, " -format Find tests with todo_format: true\n")
35+
fmt.Fprintf(os.Stderr, " -explain Find tests with explain_todo entries (fewest first)\n")
36+
os.Exit(1)
37+
}
38+
3039
testdataDir := "parser/testdata"
3140
entries, err := os.ReadDir(testdataDir)
3241
if err != nil {
@@ -55,22 +64,22 @@ func main() {
5564
continue
5665
}
5766

58-
// Check for todo or todo_format based on flag
67+
// Skip tests with skip or explain=false or parse_error
68+
if metadata.Skip || (metadata.Explain != nil && !*metadata.Explain) || metadata.ParseError {
69+
continue
70+
}
71+
72+
// Check based on flag
5973
if *formatFlag {
6074
if !metadata.TodoFormat {
6175
continue
6276
}
63-
} else {
64-
if !metadata.Todo {
77+
} else if *explainFlag {
78+
if len(metadata.ExplainTodo) == 0 {
6579
continue
6680
}
6781
}
6882

69-
// Skip tests with skip or explain=false or parse_error
70-
if metadata.Skip || (metadata.Explain != nil && !*metadata.Explain) || metadata.ParseError {
71-
continue
72-
}
73-
7483
// Read query to get its size
7584
queryPath := filepath.Join(testDir, "query.sql")
7685
queryBytes, err := os.ReadFile(queryPath)
@@ -79,31 +88,47 @@ func main() {
7988
}
8089

8190
todoTests = append(todoTests, todoTest{
82-
name: entry.Name(),
83-
querySize: len(queryBytes),
91+
name: entry.Name(),
92+
querySize: len(queryBytes),
93+
explainTodoLen: len(metadata.ExplainTodo),
8494
})
8595
}
8696

87-
todoType := "todo"
88-
if *formatFlag {
89-
todoType = "todo_format"
97+
todoType := "todo_format"
98+
if *explainFlag {
99+
todoType = "explain_todo"
90100
}
91101

92102
if len(todoTests) == 0 {
93103
fmt.Printf("No %s tests found!\n", todoType)
94104
return
95105
}
96106

97-
// Sort by query size (shortest first)
98-
sort.Slice(todoTests, func(i, j int) bool {
99-
return todoTests[i].querySize < todoTests[j].querySize
100-
})
107+
// Sort based on mode
108+
if *explainFlag {
109+
// Sort by explain_todo count (fewest first), then by query size
110+
sort.Slice(todoTests, func(i, j int) bool {
111+
if todoTests[i].explainTodoLen != todoTests[j].explainTodoLen {
112+
return todoTests[i].explainTodoLen < todoTests[j].explainTodoLen
113+
}
114+
return todoTests[i].querySize < todoTests[j].querySize
115+
})
116+
} else {
117+
// Sort by query size (shortest first)
118+
sort.Slice(todoTests, func(i, j int) bool {
119+
return todoTests[i].querySize < todoTests[j].querySize
120+
})
121+
}
101122

102-
// Print the shortest one
123+
// Print the best candidate
103124
next := todoTests[0]
104125
testDir := filepath.Join(testdataDir, next.name)
105126

106-
fmt.Printf("Next %s test: %s\n\n", todoType, next.name)
127+
if *explainFlag {
128+
fmt.Printf("Next %s test: %s (%d pending statements)\n\n", todoType, next.name, next.explainTodoLen)
129+
} else {
130+
fmt.Printf("Next %s test: %s\n\n", todoType, next.name)
131+
}
107132

108133
// Print query.sql contents
109134
queryPath := filepath.Join(testDir, "query.sql")
@@ -116,5 +141,19 @@ func main() {
116141
fmt.Printf("\nExpected EXPLAIN output:\n%s\n", string(explainBytes))
117142
}
118143

144+
// Print explain_todo entries if in explain mode
145+
if *explainFlag {
146+
metadataPath := filepath.Join(testDir, "metadata.json")
147+
if metadataBytes, err := os.ReadFile(metadataPath); err == nil {
148+
var metadata testMetadata
149+
if json.Unmarshal(metadataBytes, &metadata) == nil {
150+
fmt.Printf("\nPending statements (explain_todo):\n")
151+
for stmt := range metadata.ExplainTodo {
152+
fmt.Printf(" - %s\n", stmt)
153+
}
154+
}
155+
}
156+
}
157+
119158
fmt.Printf("\nRemaining %s tests: %d\n", todoType, len(todoTests))
120159
}

0 commit comments

Comments
 (0)