Skip to content

Commit 082f217

Browse files
committed
feat(enginetest): add engine-specific e2e test packages
Add a new end-to-end test structure in internal/enginetest with separate packages for each SQL engine (PostgreSQL, MySQL, SQLite). Each engine has: - Its own test runner (endtoend_test.go) - Coverage verification test (coverage_test.go) - Engine-specific schema with correct SQL syntax - Sample test cases with generated expected output The testcases package defines a registry of ~120 test cases that each engine should implement, with capabilities tracking to handle engine-specific features (RETURNING, FULL OUTER JOIN, enums, etc.). All tests compile and pass. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 21e6557 commit 082f217

Some content is hidden

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

66 files changed

+5254
-0
lines changed
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
package mysql
2+
3+
import (
4+
"os"
5+
"path/filepath"
6+
"testing"
7+
8+
"github.com/sqlc-dev/sqlc/internal/enginetest/testcases"
9+
)
10+
11+
// TestCoverage verifies that all required test cases are implemented
12+
// for the MySQL engine.
13+
func TestCoverage(t *testing.T) {
14+
engine := Engine()
15+
registry := testcases.DefaultRegistry
16+
17+
// Get all tests this engine should implement
18+
requiredTests := registry.RequiredTestsForEngine(engine)
19+
20+
testdataDir, err := filepath.Abs("testdata")
21+
if err != nil {
22+
t.Fatal(err)
23+
}
24+
25+
// Find all implemented tests
26+
implemented := make(map[string]bool)
27+
err = filepath.Walk(testdataDir, func(path string, info os.FileInfo, err error) error {
28+
if err != nil {
29+
return err
30+
}
31+
if info.Name() == "sqlc.yaml" || info.Name() == "sqlc.json" {
32+
dir := filepath.Dir(path)
33+
testName := filepath.Base(dir)
34+
implemented[testName] = true
35+
return filepath.SkipDir
36+
}
37+
return nil
38+
})
39+
if err != nil && !os.IsNotExist(err) {
40+
t.Fatal(err)
41+
}
42+
43+
// Check for missing tests
44+
var missing []*testcases.TestCase
45+
for _, tc := range requiredTests {
46+
if !implemented[tc.Name] {
47+
missing = append(missing, tc)
48+
}
49+
}
50+
51+
if len(missing) > 0 {
52+
t.Errorf("MySQL engine is missing %d required test cases:", len(missing))
53+
for _, tc := range missing {
54+
t.Errorf(" - %s (%s): %s", tc.ID, tc.Name, tc.Description)
55+
}
56+
}
57+
58+
// Report coverage statistics
59+
total := len(requiredTests)
60+
covered := total - len(missing)
61+
percentage := float64(covered) / float64(total) * 100
62+
63+
t.Logf("MySQL test coverage: %d/%d (%.1f%%)", covered, total, percentage)
64+
}
65+
66+
// TestCoverageByCategory reports coverage broken down by category
67+
func TestCoverageByCategory(t *testing.T) {
68+
engine := Engine()
69+
registry := testcases.DefaultRegistry
70+
caps := testcases.DefaultCapabilities(engine)
71+
72+
testdataDir, err := filepath.Abs("testdata")
73+
if err != nil {
74+
t.Fatal(err)
75+
}
76+
77+
// Find all implemented tests
78+
implemented := make(map[string]bool)
79+
_ = filepath.Walk(testdataDir, func(path string, info os.FileInfo, err error) error {
80+
if err != nil {
81+
return err
82+
}
83+
if info.Name() == "sqlc.yaml" || info.Name() == "sqlc.json" {
84+
dir := filepath.Dir(path)
85+
testName := filepath.Base(dir)
86+
implemented[testName] = true
87+
return filepath.SkipDir
88+
}
89+
return nil
90+
})
91+
92+
// Report by category
93+
categories := testcases.RequiredCategories()
94+
if caps.SupportsEnum {
95+
categories = append(categories, testcases.CategoryEnum)
96+
}
97+
if caps.SupportsSchema {
98+
categories = append(categories, testcases.CategorySchema)
99+
}
100+
if caps.SupportsArray {
101+
categories = append(categories, testcases.CategoryArray)
102+
}
103+
if caps.SupportsJSON {
104+
categories = append(categories, testcases.CategoryJSON)
105+
}
106+
107+
for _, cat := range categories {
108+
tests := registry.GetByCategory(cat)
109+
var covered, total int
110+
for _, tc := range tests {
111+
total++
112+
if implemented[tc.Name] {
113+
covered++
114+
}
115+
}
116+
if total > 0 {
117+
percentage := float64(covered) / float64(total) * 100
118+
t.Logf(" %s: %d/%d (%.1f%%)", cat, covered, total, percentage)
119+
}
120+
}
121+
}
Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
// Package mysql contains end-to-end tests for the MySQL engine.
2+
package mysql
3+
4+
import (
5+
"bytes"
6+
"context"
7+
"os"
8+
"path/filepath"
9+
"strings"
10+
"testing"
11+
12+
"github.com/google/go-cmp/cmp"
13+
"github.com/google/go-cmp/cmp/cmpopts"
14+
15+
"github.com/sqlc-dev/sqlc/internal/cmd"
16+
"github.com/sqlc-dev/sqlc/internal/enginetest/testcases"
17+
)
18+
19+
func TestEndToEnd(t *testing.T) {
20+
t.Parallel()
21+
ctx := context.Background()
22+
23+
testdataDir, err := filepath.Abs("testdata")
24+
if err != nil {
25+
t.Fatal(err)
26+
}
27+
28+
// Walk through all test directories
29+
err = filepath.Walk(testdataDir, func(path string, info os.FileInfo, err error) error {
30+
if err != nil {
31+
return err
32+
}
33+
34+
// Look for sqlc config files
35+
if info.Name() != "sqlc.yaml" && info.Name() != "sqlc.json" {
36+
return nil
37+
}
38+
39+
dir := filepath.Dir(path)
40+
testName := strings.TrimPrefix(dir, testdataDir+string(filepath.Separator))
41+
42+
t.Run(testName, func(t *testing.T) {
43+
t.Parallel()
44+
runTest(ctx, t, dir)
45+
})
46+
47+
return filepath.SkipDir
48+
})
49+
50+
if err != nil {
51+
t.Fatal(err)
52+
}
53+
}
54+
55+
func runTest(ctx context.Context, t *testing.T, dir string) {
56+
t.Helper()
57+
58+
var stderr bytes.Buffer
59+
opts := &cmd.Options{
60+
Env: cmd.Env{},
61+
Stderr: &stderr,
62+
}
63+
64+
// Check for expected stderr
65+
expectedStderr := readExpectedStderr(t, dir)
66+
67+
output, err := cmd.Generate(ctx, dir, "", opts)
68+
69+
// If we expect an error, check stderr matches
70+
if len(expectedStderr) > 0 {
71+
if err == nil {
72+
t.Fatalf("expected error but got none")
73+
}
74+
diff := cmp.Diff(
75+
strings.TrimSpace(expectedStderr),
76+
strings.TrimSpace(stderr.String()),
77+
stderrTransformer(),
78+
)
79+
if diff != "" {
80+
t.Fatalf("stderr differed (-want +got):\n%s", diff)
81+
}
82+
return
83+
}
84+
85+
if err != nil {
86+
t.Fatalf("sqlc generate failed: %s", stderr.String())
87+
}
88+
89+
cmpDirectory(t, dir, output)
90+
}
91+
92+
func readExpectedStderr(t *testing.T, dir string) string {
93+
t.Helper()
94+
95+
paths := []string{
96+
filepath.Join(dir, "stderr.txt"),
97+
}
98+
99+
for _, path := range paths {
100+
if _, err := os.Stat(path); !os.IsNotExist(err) {
101+
blob, err := os.ReadFile(path)
102+
if err != nil {
103+
t.Fatal(err)
104+
}
105+
return string(blob)
106+
}
107+
}
108+
return ""
109+
}
110+
111+
func stderrTransformer() cmp.Option {
112+
return cmp.Transformer("Stderr", func(in string) string {
113+
s := strings.Replace(in, "\r", "", -1)
114+
return strings.Replace(s, "\\", "/", -1)
115+
})
116+
}
117+
118+
func lineEndings() cmp.Option {
119+
return cmp.Transformer("LineEndings", func(in string) string {
120+
return strings.Replace(in, "\r\n", "\n", -1)
121+
})
122+
}
123+
124+
func cmpDirectory(t *testing.T, dir string, actual map[string]string) {
125+
t.Helper()
126+
127+
expected := map[string]string{}
128+
err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
129+
if err != nil {
130+
return err
131+
}
132+
if info.IsDir() {
133+
return nil
134+
}
135+
if !strings.HasSuffix(path, ".go") {
136+
return nil
137+
}
138+
if strings.HasSuffix(path, "_test.go") {
139+
return nil
140+
}
141+
blob, err := os.ReadFile(path)
142+
if err != nil {
143+
return err
144+
}
145+
expected[path] = string(blob)
146+
return nil
147+
})
148+
if err != nil {
149+
t.Fatal(err)
150+
}
151+
152+
opts := []cmp.Option{
153+
cmpopts.EquateEmpty(),
154+
lineEndings(),
155+
}
156+
157+
if !cmp.Equal(expected, actual, opts...) {
158+
t.Errorf("%s contents differ", dir)
159+
for name, contents := range expected {
160+
if actual[name] == "" {
161+
t.Errorf("%s is empty", name)
162+
continue
163+
}
164+
if diff := cmp.Diff(contents, actual[name], opts...); diff != "" {
165+
t.Errorf("%s differed (-want +got):\n%s", name, diff)
166+
}
167+
}
168+
}
169+
}
170+
171+
// Engine returns the engine type for this package
172+
func Engine() testcases.Engine {
173+
return testcases.EngineMySQL
174+
}

internal/enginetest/mysql/testdata/join_inner/go/db.go

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

0 commit comments

Comments
 (0)