Skip to content

Commit c6aa186

Browse files
authored
test(endtoend): Use Docker to start database servers (#4076)
* test(endtoend): Use Docker to start database servers * Only test with CGO disabled * Only run tests on Linux * Cross-compile for all supported architectures
1 parent a36654a commit c6aa186

File tree

8 files changed

+310
-56
lines changed

8 files changed

+310
-56
lines changed

.github/workflows/build.yml

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
name: build
2+
on:
3+
workflow_dispatch:
4+
jobs:
5+
build:
6+
strategy:
7+
matrix:
8+
os: [ubuntu-24.04, macos-14, windows-2022]
9+
name: build ${{ matrix.os }}
10+
runs-on: ${{ matrix.os }}
11+
steps:
12+
- uses: actions/checkout@v5
13+
- uses: actions/setup-go@v5
14+
with:
15+
go-version: '1.25.0'
16+
- name: install ./...
17+
run: go build ./...
18+
env:
19+
CGO_ENABLED: "0"

.github/workflows/ci.yml

Lines changed: 22 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -5,27 +5,25 @@ on:
55
- main
66
pull_request:
77
jobs:
8-
test:
8+
build:
99
strategy:
1010
matrix:
11-
# Disabling windows builds while we fix installing PostgreSQL 16
12-
# os: [ubuntu-22.04, macos-14, windows-2022]
13-
os: [ubuntu-22.04, macos-15]
14-
cgo: ['1', '0']
15-
# Workaround no native support for conditional matrix items
16-
# https://github.com/orgs/community/discussions/26253#discussioncomment-6745038
17-
isMain:
18-
- ${{ github.ref == 'refs/heads/main' }}
19-
exclude:
20-
- isMain: false
21-
include:
22-
- os: ubuntu-22.04
23-
cgo: '1'
24-
- os: ubuntu-22.04
25-
cgo: '0'
26-
name: test ${{ matrix.os }} cgo=${{ matrix.cgo }}
27-
runs-on: ${{ matrix.os }}
28-
11+
goos: [darwin, linux, windows]
12+
goarch: [amd64, arm64]
13+
name: build ${{ matrix.goos }}/${{ matrix.goarch }}
14+
runs-on: ubuntu-24.04
15+
steps:
16+
- uses: actions/checkout@v5
17+
- uses: actions/setup-go@v5
18+
with:
19+
go-version: '1.25.0'
20+
- run: go build ./...
21+
env:
22+
CGO_ENABLED: "0"
23+
GOOS: ${{ matrix.goos }}
24+
GOARCH: ${{ matrix.goarch }}
25+
test:
26+
runs-on: ubuntu-24.04
2927
steps:
3028
- uses: actions/checkout@v5
3129
- uses: actions/setup-go@v5
@@ -44,37 +42,25 @@ jobs:
4442
- name: install ./...
4543
run: go install ./...
4644
env:
47-
CGO_ENABLED: ${{ matrix.cgo }}
45+
CGO_ENABLED: "0"
4846

4947
- name: build internal/endtoend
5048
run: go build ./...
5149
working-directory: internal/endtoend/testdata
5250
env:
53-
CGO_ENABLED: ${{ matrix.cgo }}
54-
55-
# Start a PostgreSQL server
56-
- uses: sqlc-dev/action-setup-postgres@master
57-
with:
58-
postgres-version: "16"
59-
id: postgres
60-
61-
# Start a MySQL server
62-
- uses: shogo82148/actions-setup-mysql@v1
63-
with:
64-
mysql-version: "9.0"
51+
CGO_ENABLED: "0"
6552

6653
- name: test ./...
6754
run: gotestsum --junitfile junit.xml -- --tags=examples -timeout 20m ./...
55+
if: ${{ matrix.os }} != "windows-2022"
6856
env:
6957
CI_SQLC_PROJECT_ID: ${{ secrets.CI_SQLC_PROJECT_ID }}
7058
CI_SQLC_AUTH_TOKEN: ${{ secrets.CI_SQLC_AUTH_TOKEN }}
7159
SQLC_AUTH_TOKEN: ${{ secrets.CI_SQLC_AUTH_TOKEN }}
72-
MYSQL_SERVER_URI: root:@tcp(localhost:3306)/mysql?multiStatements=true&parseTime=true
73-
POSTGRESQL_SERVER_URI: ${{ steps.postgres.outputs.connection-uri }}?sslmode=disable
74-
CGO_ENABLED: ${{ matrix.cgo }}
60+
CGO_ENABLED: "0"
7561

7662
vuln_check:
77-
runs-on: ubuntu-22.04
63+
runs-on: ubuntu-24.04
7864
timeout-minutes: 5
7965

8066
steps:

internal/endtoend/endtoend_test.go

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import (
1717
"github.com/sqlc-dev/sqlc/internal/cmd"
1818
"github.com/sqlc-dev/sqlc/internal/config"
1919
"github.com/sqlc-dev/sqlc/internal/opts"
20-
"github.com/sqlc-dev/sqlc/internal/sqltest/local"
20+
"github.com/sqlc-dev/sqlc/internal/sqltest/docker"
2121
)
2222

2323
func lineEndings() cmp.Option {
@@ -112,6 +112,24 @@ func TestReplay(t *testing.T) {
112112
// t.Parallel()
113113
ctx := context.Background()
114114

115+
var mysqlURI, postgresURI string
116+
if err := docker.Installed(); err == nil {
117+
{
118+
host, err := docker.StartPostgreSQLServer(ctx)
119+
if err != nil {
120+
t.Fatalf("starting postgresql failed: %s", err)
121+
}
122+
postgresURI = host
123+
}
124+
{
125+
host, err := docker.StartMySQLServer(ctx)
126+
if err != nil {
127+
t.Fatalf("starting mysql failed: %s", err)
128+
}
129+
mysqlURI = host
130+
}
131+
}
132+
115133
contexts := map[string]textContext{
116134
"base": {
117135
Mutate: func(t *testing.T, path string) func(*config.Config) { return func(c *config.Config) {} },
@@ -124,13 +142,13 @@ func TestReplay(t *testing.T) {
124142
{
125143
Name: "postgres",
126144
Engine: config.EnginePostgreSQL,
127-
URI: local.PostgreSQLServer(),
145+
URI: postgresURI,
128146
},
129147

130148
{
131149
Name: "mysql",
132150
Engine: config.EngineMySQL,
133-
URI: local.MySQLServer(),
151+
URI: mysqlURI,
134152
},
135153
}
136154
for i := range c.SQL {
@@ -150,13 +168,8 @@ func TestReplay(t *testing.T) {
150168
}
151169
},
152170
Enabled: func() bool {
153-
if len(os.Getenv("POSTGRESQL_SERVER_URI")) == 0 {
154-
return false
155-
}
156-
if len(os.Getenv("MYSQL_SERVER_URI")) == 0 {
157-
return false
158-
}
159-
return true
171+
err := docker.Installed()
172+
return err == nil
160173
},
161174
},
162175
}

internal/sqltest/docker/enabled.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package docker
2+
3+
import (
4+
"fmt"
5+
"os/exec"
6+
7+
"golang.org/x/sync/singleflight"
8+
)
9+
10+
var flight singleflight.Group
11+
12+
func Installed() error {
13+
if _, err := exec.LookPath("docker"); err != nil {
14+
return fmt.Errorf("docker not found: %w", err)
15+
}
16+
return nil
17+
}

internal/sqltest/docker/mysql.go

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
package docker
2+
3+
import (
4+
"context"
5+
"database/sql"
6+
"fmt"
7+
"os/exec"
8+
"strings"
9+
"time"
10+
11+
_ "github.com/go-sql-driver/mysql"
12+
)
13+
14+
var mysqlHost string
15+
16+
func StartMySQLServer(c context.Context) (string, error) {
17+
if err := Installed(); err != nil {
18+
return "", err
19+
}
20+
if mysqlHost != "" {
21+
return mysqlHost, nil
22+
}
23+
value, err, _ := flight.Do("mysql", func() (interface{}, error) {
24+
host, err := startMySQLServer(c)
25+
if err != nil {
26+
return "", err
27+
}
28+
mysqlHost = host
29+
return host, nil
30+
})
31+
if err != nil {
32+
return "", err
33+
}
34+
data, ok := value.(string)
35+
if !ok {
36+
return "", fmt.Errorf("returned value was not a string")
37+
}
38+
return data, nil
39+
}
40+
41+
func startMySQLServer(c context.Context) (string, error) {
42+
{
43+
_, err := exec.Command("docker", "pull", "mysql:9").CombinedOutput()
44+
if err != nil {
45+
return "", fmt.Errorf("docker pull: mysql:9 %w", err)
46+
}
47+
}
48+
49+
var exists bool
50+
{
51+
cmd := exec.Command("docker", "container", "inspect", "sqlc_sqltest_docker_mysql")
52+
// This means we've already started the container
53+
exists = cmd.Run() == nil
54+
}
55+
56+
if !exists {
57+
cmd := exec.Command("docker", "run",
58+
"--name", "sqlc_sqltest_docker_mysql",
59+
"-e", "MYSQL_ROOT_PASSWORD=mysecretpassword",
60+
"-e", "MYSQL_DATABASE=dinotest",
61+
"-p", "3306:3306",
62+
"-d",
63+
"mysql:9",
64+
)
65+
66+
output, err := cmd.CombinedOutput()
67+
fmt.Println(string(output))
68+
69+
msg := `Conflict. The container name "/sqlc_sqltest_docker_mysql" is already in use by container`
70+
if !strings.Contains(string(output), msg) && err != nil {
71+
return "", err
72+
}
73+
}
74+
75+
ctx, cancel := context.WithTimeout(c, 10*time.Second)
76+
defer cancel()
77+
78+
// Create a ticker that fires every 10ms
79+
ticker := time.NewTicker(10 * time.Millisecond)
80+
defer ticker.Stop()
81+
82+
uri := "root:mysecretpassword@/dinotest?multiStatements=true&parseTime=true"
83+
84+
db, err := sql.Open("mysql", uri)
85+
if err != nil {
86+
return "", fmt.Errorf("sql.Open: %w", err)
87+
}
88+
89+
defer db.Close()
90+
91+
for {
92+
select {
93+
case <-ctx.Done():
94+
return "", fmt.Errorf("timeout reached: %w", ctx.Err())
95+
96+
case <-ticker.C:
97+
// Run your function here
98+
if err := db.PingContext(ctx); err != nil {
99+
continue
100+
}
101+
return uri, nil
102+
}
103+
}
104+
}

0 commit comments

Comments
 (0)