ci: gorm-upstream — drop ^ anchor, capture PIPESTATUS,
#8
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: CI | |
| on: | |
| push: | |
| branches: [main] | |
| pull_request: | |
| branches: [main] | |
| jobs: | |
| # Run the full test suite on the three platforms most consumers actually | |
| # use. Cross-build coverage for the long tail of modernc-supported targets | |
| # is in build_all_targets below. | |
| test: | |
| name: test | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| os: [ubuntu-latest, macos-latest, windows-latest] | |
| go: ['1.25.x'] | |
| runs-on: ${{ matrix.os }} | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: actions/setup-go@v5 | |
| with: | |
| go-version: ${{ matrix.go }} | |
| - name: go test ./... | |
| run: go test -count=1 -timeout 5m ./... | |
| # Race-mode test pass — linux/amd64 only because race-detection support is | |
| # spotty on other targets and the bulk of the locking surface lives in | |
| # platform-independent Go code anyway. | |
| race: | |
| name: race | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: actions/setup-go@v5 | |
| with: | |
| go-version: '1.25.x' | |
| - name: go test -race | |
| run: go test -race -count=1 -timeout 10m ./... | |
| # Static analysis: go vet (always available) plus staticcheck (matches the | |
| # modernc/sqlite CI lint pass). | |
| lint: | |
| name: lint | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: actions/setup-go@v5 | |
| with: | |
| go-version: '1.25.x' | |
| - name: go vet | |
| # unsafeptr=false silences the uintptr↔unsafe.Pointer false positives | |
| # in the modernc-derived wrapper (see .golangci.yml + justfile vet | |
| # recipe for the same suppression). | |
| run: go vet -unsafeptr=false ./... | |
| - name: gofmt -d | |
| run: | | |
| out=$(gofmt -d $(find . -name '*.go' -not -path './.sqlite/*' -not -path './.go-sqlite3/*' -not -path './.gorm-sqlite*/*' -not -path './.fts/*')) | |
| if [ -n "$out" ]; then echo "$out"; exit 1; fi | |
| - name: staticcheck | |
| uses: dominikh/staticcheck-action@v1 | |
| with: | |
| version: latest | |
| install-go: false | |
| - name: golangci-lint | |
| # v8 of the action defaults to golangci-lint v2.x, which is the | |
| # major version `.golangci.yml` is written against (`version: "2"`). | |
| # We also pin the binary to a v2 release explicitly so a future | |
| # action revision that flips the default cannot silently downgrade | |
| # us back into the v1 series. v2.x is built with Go 1.25, which | |
| # matches go.mod and avoids the "Go language version used to build | |
| # golangci-lint is lower than the targeted Go version" error that | |
| # v1.x throws on Go 1.25 modules. | |
| uses: golangci/golangci-lint-action@v8 | |
| with: | |
| version: v2.4.0 | |
| args: --timeout 5m | |
| # Cross-build every modernc-supported GOOS/GOARCH so we catch | |
| # platform-specific compile errors without running the (often unavailable) | |
| # test runtime there. The matrix mirrors modernc/sqlite's builder.json. | |
| build_all_targets: | |
| name: build (${{ matrix.goos }}/${{ matrix.goarch }}) | |
| runs-on: ubuntu-latest | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - { goos: darwin, goarch: amd64 } | |
| - { goos: darwin, goarch: arm64 } | |
| - { goos: freebsd, goarch: amd64 } | |
| - { goos: freebsd, goarch: arm64 } | |
| - { goos: linux, goarch: 386 } | |
| - { goos: linux, goarch: amd64 } | |
| - { goos: linux, goarch: arm } | |
| - { goos: linux, goarch: arm64 } | |
| - { goos: linux, goarch: loong64 } | |
| - { goos: linux, goarch: ppc64le } | |
| - { goos: linux, goarch: riscv64 } | |
| - { goos: linux, goarch: s390x } | |
| - { goos: windows, goarch: 386 } | |
| - { goos: windows, goarch: amd64 } | |
| - { goos: windows, goarch: arm64 } | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: actions/setup-go@v5 | |
| with: | |
| go-version: '1.25.x' | |
| - name: cross build | |
| env: | |
| GOOS: ${{ matrix.goos }} | |
| GOARCH: ${{ matrix.goarch }} | |
| # modernc.org/sqlite/vec is only transpiled for the same OS set we | |
| # build the package on (linux/darwin/freebsd/openbsd/windows). | |
| # `go build ./vec/...` on excluded targets fails because the | |
| # underlying lib hasn't been generated there; the `|| true` is | |
| # deliberate so the cross-build matrix doesn't fail on platforms | |
| # we never claimed to support for vec. | |
| CGO_ENABLED: '0' | |
| run: | | |
| go build ./ | |
| go build ./gorm/... | |
| go build ./fts/... | |
| go build ./vfs/... | |
| go build ./vec/... || echo "vec not supported on ${GOOS}/${GOARCH} — skipping" | |
| # Run the SQL conformance suite under tests/sql/. This is our methodical | |
| # feature-by-feature proof that every documented SQLite SQL surface the | |
| # driver exposes actually works. See docs/coverage-sql.md for the matrix. | |
| sql_conformance: | |
| name: sql-conformance | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| os: [ubuntu-latest, macos-latest, windows-latest] | |
| runs-on: ${{ matrix.os }} | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: actions/setup-go@v5 | |
| with: | |
| go-version: '1.25.x' | |
| - name: go test ./tests/sql/... | |
| run: go test -count=1 -timeout 2m -v ./tests/sql/... | |
| # Run the vendored subset of modernc.org/sqlite's own test suite against | |
| # this fork. See docs/modernc-upstream.md for what's in/out and why. | |
| # The `modernc_upstream` build tag gates this so the default `go test` | |
| # path stays fast. | |
| modernc_upstream: | |
| name: modernc-upstream | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: actions/setup-go@v5 | |
| with: | |
| go-version: '1.25.x' | |
| - name: go test -tags=modernc_upstream | |
| run: go test -tags=modernc_upstream -count=1 -timeout 5m -v . | |
| # Run the vendored subset of mattn/go-sqlite3's test suite against this | |
| # fork. See docs/mattn-upstream.md for what's in/out and why — the | |
| # subset is small because mattn's tests probe mattn-internal types | |
| # heavily, not the database/sql contract. This lane is a canary for | |
| # the mattn-compat surface (math UDFs, unlock-notify semantics, DSN | |
| # flag handling), not a parity proof. | |
| mattn_upstream: | |
| name: mattn-upstream | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: actions/setup-go@v5 | |
| with: | |
| go-version: '1.25.x' | |
| - name: go test -tags=mattn_upstream | |
| run: go test -tags=mattn_upstream -count=1 -timeout 5m -v -run 'TestMath|TestUnlock' . | |
| # Run gorm.io/gorm's own integration test suite against our dialector. | |
| # The setup, reasoning, and current pass/fail counts live in | |
| # docs/gorm-upstream.md; this job is the CI-enforced version of the | |
| # recipe documented there. We pin gorm to the same version go.mod | |
| # depends on, so the suite stays in lockstep with our `gorm/` package. | |
| gorm_upstream: | |
| name: gorm-upstream (v1.31.1) | |
| runs-on: ubuntu-latest | |
| env: | |
| GORM_VERSION: v1.31.1 | |
| WORK_DIR: ${{ github.workspace }}/.gorm-upstream | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| path: sqlite | |
| - uses: actions/setup-go@v5 | |
| with: | |
| go-version: '1.25.x' | |
| - name: Clone gorm at pinned version | |
| run: | | |
| mkdir -p "$WORK_DIR" | |
| git clone --depth 1 --branch "$GORM_VERSION" \ | |
| https://github.com/go-gorm/gorm.git "$WORK_DIR/gorm" | |
| - name: Write shim module re-exporting our dialector | |
| # The upstream tests import gorm.io/driver/sqlite. We expose | |
| # github.com/go-again/sqlite/gorm under that path via a tiny shim, | |
| # then append WAL + busy_timeout DSN flags so modernc's pure-Go | |
| # file locks don't return SQLITE_BUSY on concurrent goroutines — | |
| # mattn/go-sqlite3 (which gorm's tests were written against) | |
| # serializes via libc-level locks instead. | |
| run: | | |
| mkdir -p "$WORK_DIR/shim" | |
| cat > "$WORK_DIR/shim/go.mod" <<EOF | |
| module gorm.io/driver/sqlite | |
| go 1.25.0 | |
| require ( | |
| github.com/go-again/sqlite v0.0.0 | |
| gorm.io/gorm $GORM_VERSION | |
| ) | |
| replace github.com/go-again/sqlite => ${{ github.workspace }}/sqlite | |
| EOF | |
| cat > "$WORK_DIR/shim/sqlite.go" <<'EOF' | |
| package sqlite | |
| import ( | |
| "strings" | |
| gagorm "github.com/go-again/sqlite/gorm" | |
| "gorm.io/gorm" | |
| ) | |
| const DriverName = gagorm.DriverName | |
| type ( | |
| Dialector = gagorm.Dialector | |
| Config = gagorm.Config | |
| ) | |
| const extraFlags = "_busy_timeout=5000&_journal=WAL&_sync=NORMAL" | |
| func withFlags(dsn string) string { | |
| if dsn == "" || strings.HasPrefix(dsn, ":memory:") || strings.Contains(dsn, "busy_timeout") { | |
| return dsn | |
| } | |
| sep := "?" | |
| if strings.Contains(dsn, "?") { | |
| sep = "&" | |
| } | |
| return dsn + sep + extraFlags | |
| } | |
| func Open(dsn string) gorm.Dialector { return gagorm.Open(withFlags(dsn)) } | |
| func New(cfg Config) gorm.Dialector { | |
| cfg.DSN = withFlags(cfg.DSN) | |
| return gagorm.New(cfg) | |
| } | |
| EOF | |
| (cd "$WORK_DIR/shim" && go mod tidy) | |
| - name: Wire shim into gorm/tests module | |
| run: | | |
| cat >> "$WORK_DIR/gorm/tests/go.mod" <<EOF | |
| replace gorm.io/driver/sqlite => $WORK_DIR/shim | |
| replace github.com/go-again/sqlite => ${{ github.workspace }}/sqlite | |
| EOF | |
| (cd "$WORK_DIR/gorm/tests" && go mod tidy) | |
| - name: Run gorm upstream test suite | |
| run: | | |
| cd "$WORK_DIR/gorm/tests" | |
| # tee duplicates output to a file we parse for PASS/FAIL counts | |
| # below. Two pitfalls we work around here: | |
| # 1. GitHub Actions prefixes every line with a UTC timestamp, | |
| # even in the file written by tee — so we cannot anchor our | |
| # grep with `^--- PASS:`. We use an unanchored match. | |
| # 2. `go test`'s exit code is masked by tee's (always 0). We | |
| # capture it via PIPESTATUS before the next command resets | |
| # $?, and disable errexit around the pipeline so a | |
| # legitimate test-fail doesn't abort our reporter. | |
| set +e | |
| go test -count=1 -timeout 5m -v ./... 2>&1 | tee /tmp/gorm-upstream.log | |
| rc=${PIPESTATUS[0]} | |
| set -e | |
| # `grep -c` exits 1 on zero matches; `|| true` keeps the | |
| # outer script alive so we can report the actual counts. | |
| PASS=$(grep -c -- '--- PASS:' /tmp/gorm-upstream.log || true) | |
| FAIL=$(grep -c -- '--- FAIL:' /tmp/gorm-upstream.log || true) | |
| SKIP=$(grep -c -- '--- SKIP:' /tmp/gorm-upstream.log || true) | |
| echo "--- summary ---" | |
| echo "PASS=$PASS FAIL=$FAIL SKIP=$SKIP go-test-exit=$rc" | |
| if [ "$rc" -ne 0 ]; then | |
| echo "::error::go test exited with $rc" | |
| exit "$rc" | |
| fi | |
| if [ "$FAIL" -ne 0 ]; then | |
| echo "::error::gorm upstream suite reports $FAIL failures" | |
| exit 1 | |
| fi | |
| # PASS count includes sub-test lines (which are indented in the | |
| # file too) — for gorm v1.31.1 the total is ~15000. Floor at | |
| # 10000 to catch a "no tests ran at all" regression without | |
| # being brittle on minor gorm bumps. | |
| if [ "$PASS" -lt 10000 ]; then | |
| echo "::error::gorm upstream PASS count regressed: got $PASS, expected >= 10000" | |
| exit 1 | |
| fi |