Skip to content

Commit b6f323e

Browse files
committed
Add endtoend replay coverage for parse and analyze
Extend the endtoend replay framework to run config-less, flag-driven commands. Exec gains an "args" field, FindTests discovers directories by exec.json when no sqlc config is present, and TestReplay dispatches parse/analyze through the CLI entry point, comparing stdout to a stdout.txt golden file. The config-based consumers (TestValidSchema, TestFormat) skip these config-less cases. Add two cases that pin the JSON output format of each command so future changes don't break it: parse of a named PostgreSQL query and analyze of a SELECT * query against a schema.
1 parent e4792a5 commit b6f323e

11 files changed

Lines changed: 196 additions & 2 deletions

File tree

internal/endtoend/case_test.go

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ type Testcase struct {
1515
Path string
1616
ConfigName string
1717
Stderr []byte
18+
Stdout []byte
1819
Exec *Exec
1920
}
2021

@@ -24,6 +25,7 @@ type ExecMeta struct {
2425

2526
type Exec struct {
2627
Command string `json:"command"`
28+
Args []string `json:"args"`
2729
Contexts []string `json:"contexts"`
2830
Process string `json:"process"`
2931
OS []string `json:"os"`
@@ -50,6 +52,29 @@ func parseStderr(t *testing.T, dir, testctx string) []byte {
5052
return nil
5153
}
5254

55+
func parseStdout(t *testing.T, dir string) []byte {
56+
t.Helper()
57+
path := filepath.Join(dir, "stdout.txt")
58+
if _, err := os.Stat(path); os.IsNotExist(err) {
59+
return nil
60+
}
61+
blob, err := os.ReadFile(path)
62+
if err != nil {
63+
t.Fatal(err)
64+
}
65+
return blob
66+
}
67+
68+
// hasSQLCConfig reports whether dir contains an sqlc configuration file.
69+
func hasSQLCConfig(dir string) bool {
70+
for _, name := range []string{"sqlc.json", "sqlc.yaml", "sqlc.yml"} {
71+
if _, err := os.Stat(filepath.Join(dir, name)); err == nil {
72+
return true
73+
}
74+
}
75+
return false
76+
}
77+
5378
func parseExec(t *testing.T, dir string) *Exec {
5479
t.Helper()
5580
path := filepath.Join(dir, "exec.json")
@@ -76,17 +101,34 @@ func FindTests(t *testing.T, root, testctx string) []*Testcase {
76101
if err != nil {
77102
return err
78103
}
79-
if info.Name() == "sqlc.json" || info.Name() == "sqlc.yaml" || info.Name() == "sqlc.yml" {
104+
name := info.Name()
105+
if name == "sqlc.json" || name == "sqlc.yaml" || name == "sqlc.yml" {
80106
dir := filepath.Dir(path)
81107
tcs = append(tcs, &Testcase{
82108
Path: dir,
83109
Name: strings.TrimPrefix(dir, root+string(filepath.Separator)),
84-
ConfigName: info.Name(),
110+
ConfigName: name,
85111
Stderr: parseStderr(t, dir, testctx),
112+
Stdout: parseStdout(t, dir),
86113
Exec: parseExec(t, dir),
87114
})
88115
return filepath.SkipDir
89116
}
117+
// Config-less command tests (e.g. parse, analyze) are discovered by
118+
// their exec.json when no sqlc config is present in the directory.
119+
if name == "exec.json" {
120+
dir := filepath.Dir(path)
121+
if !hasSQLCConfig(dir) {
122+
tcs = append(tcs, &Testcase{
123+
Path: dir,
124+
Name: strings.TrimPrefix(dir, root+string(filepath.Separator)),
125+
Stderr: parseStderr(t, dir, testctx),
126+
Stdout: parseStdout(t, dir),
127+
Exec: parseExec(t, dir),
128+
})
129+
return filepath.SkipDir
130+
}
131+
}
90132
return nil
91133
})
92134
if err != nil {

internal/endtoend/ddl_test.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,11 @@ func TestValidSchema(t *testing.T) {
2020
}
2121
}
2222

23+
// Config-less command tests (parse, analyze) have no schema to validate.
24+
if replay.ConfigName == "" {
25+
continue
26+
}
27+
2328
file := filepath.Join(replay.Path, replay.ConfigName)
2429
rd, err := os.Open(file)
2530
if err != nil {

internal/endtoend/endtoend_test.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package main
33
import (
44
"bytes"
55
"context"
6+
"fmt"
67
"os"
78
osexec "os/exec"
89
"path/filepath"
@@ -298,6 +299,28 @@ func TestReplay(t *testing.T) {
298299
}
299300
case "vet":
300301
err = cmd.Vet(ctx, path, "", &opts)
302+
case "parse", "analyze":
303+
// These commands are config-less and flag-driven. Run them
304+
// through the real CLI entry point from inside the test
305+
// directory so file arguments resolve and the output stays
306+
// independent of the absolute path.
307+
var stdout bytes.Buffer
308+
wd, werr := os.Getwd()
309+
if werr != nil {
310+
t.Fatal(werr)
311+
}
312+
if cerr := os.Chdir(path); cerr != nil {
313+
t.Fatal(cerr)
314+
}
315+
code := cmd.Do(append([]string{args.Command}, args.Args...), nil, &stdout, &stderr)
316+
if cerr := os.Chdir(wd); cerr != nil {
317+
t.Fatal(cerr)
318+
}
319+
if code != 0 {
320+
err = fmt.Errorf("%s exited with code %d", args.Command, code)
321+
} else if diff := cmp.Diff(strings.TrimSpace(string(tc.Stdout)), strings.TrimSpace(stdout.String()), lineEndings()); diff != "" {
322+
t.Errorf("stdout differed (-want +got):\n%s", diff)
323+
}
301324
default:
302325
t.Fatalf("unknown command")
303326
}

internal/endtoend/fmt_test.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,10 @@ func TestFormat(t *testing.T) {
3232
t.Parallel()
3333
for _, tc := range FindTests(t, "testdata", "base") {
3434
tc := tc
35+
// Config-less command tests (parse, analyze) have no config to format.
36+
if tc.ConfigName == "" {
37+
continue
38+
}
3539
t.Run(tc.Name, func(t *testing.T) {
3640
// Parse the config file to determine the engine
3741
configPath := filepath.Join(tc.Path, tc.ConfigName)
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"command": "analyze",
3+
"args": ["--dialect", "postgresql", "--schema", "schema.sql", "query.sql"],
4+
"contexts": ["base"]
5+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
-- name: GetAuthor :one
2+
SELECT * FROM authors WHERE id = $1;
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
CREATE TABLE authors (
2+
id BIGSERIAL PRIMARY KEY,
3+
name text NOT NULL,
4+
bio text
5+
);
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
[
2+
{
3+
"name": "GetAuthor",
4+
"cmd": ":one",
5+
"columns": [
6+
{
7+
"name": "id",
8+
"data_type": "bigserial",
9+
"not_null": true,
10+
"is_array": false,
11+
"table": "authors"
12+
},
13+
{
14+
"name": "name",
15+
"data_type": "text",
16+
"not_null": true,
17+
"is_array": false,
18+
"table": "authors"
19+
},
20+
{
21+
"name": "bio",
22+
"data_type": "text",
23+
"not_null": false,
24+
"is_array": false,
25+
"table": "authors"
26+
}
27+
],
28+
"params": [
29+
{
30+
"number": 1,
31+
"column": {
32+
"name": "id",
33+
"data_type": "bigserial",
34+
"not_null": true,
35+
"is_array": false,
36+
"table": "authors"
37+
}
38+
}
39+
]
40+
}
41+
]
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"command": "parse",
3+
"args": ["--dialect", "postgresql", "query.sql"],
4+
"contexts": ["base"]
5+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
-- name: GetValue :one
2+
SELECT 1;

0 commit comments

Comments
 (0)