Skip to content

ci: gorm-upstream — drop ^ anchor, capture PIPESTATUS, #8

ci: gorm-upstream — drop ^ anchor, capture PIPESTATUS,

ci: gorm-upstream — drop ^ anchor, capture PIPESTATUS, #8

Workflow file for this run

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