Artifact 4f1f3f32c95d23dd3a06c097cd689f26f7ea74c875122145e2fe00e04708da14:
- File
.github/workflows/test.yml
— part of check-in
[9ff6e07d74]
at
2026-07-26 13:15:42
on branch trunk
— ci: pin canonical fossil to 2.28 instead of the distro package (#173)
Both test jobs installed the canonical fossil binary with an unpinned `apt-get install -y fossil`. On the Ubuntu runner that resolves to 2.23, while all local real-fossil parity work is validated against 2.28 -- so CI was checking our output against a different reference implementation than development does. The distro package is not stable even at a fixed version string: it tracks different upstream check-ins over time, so two runs at nominally the same version can behave differently.
Install the official upstream release tarball instead, pinned by version and by SHA-256 so it is a fixed artifact rather than a moving target. The version lives in one workflow-level env value both jobs reference, and a verification step asserts the installed binary actually reports it, so an upstream change fails loudly instead of drifting silently.
This also explains the intermittent `table global_config already exists` failure recorded in #162. `global_config` is not part of the repository schema at all -- it is the only table in `~/.fossil`, fossil's per-user global config database, created lazily on first use. Under concurrent fossil invocations against a HOME that has no `~/.fossil` yet (a fresh CI runner, never a dev machine) 2.23 races on that CREATE TABLE; 2.28 does not. Reproduced at 24-way concurrency on a fresh HOME: 2.23 hit the error in 30/30 trials, 2.28 in 0/30, and 0/60 at 48-way.
Our schema creation is not implicated. 1500 iterations of TestCreateRepoSchema_FossilValidation against 2.28 (500 in-process plus 200 separate processes per driver variant) and 100 full `./db/...` runs passed with zero failures, and 40 independently created schema artifacts across both drivers produced byte-identical DDL.
Closes #162 (user: [email protected], size: 6536) [annotate] [blame] [check-ins using]
name: Tests on: push: branches: [main] pull_request: env: # The canonical fossil the real-fossil-gated tests compare against. Local # development validates against this exact version, so CI must too -- # otherwise the two are checking our output against different reference # implementations. Bump deliberately, here, in one place. # # The distro package is unusable for this: it is unpinned, currently lags # several releases behind, and tracks different upstream check-ins over time # under the same version string, so two runs at nominally the same version # can behave differently. The official upstream release tarball is a fixed # build, and FOSSIL_SHA256 pins it to that exact artifact -- a re-upload # upstream then fails the checksum loudly instead of silently changing what # we test against. FOSSIL_VERSION: "2.28" FOSSIL_SHA256: "c9d2bf9d150dff1076eae1b0b822ed3cde892e1273f78b040c4f8ace1b59dd40" jobs: test: name: Test (modernc) runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-go@v5 with: go-version-file: go.mod - name: Install pinned Fossil run: | curl -fsSL --retry 3 --retry-delay 2 --retry-all-errors \ -o /tmp/fossil.tar.gz \ "https://fossil-scm.org/home/uv/fossil-linux-x64-${FOSSIL_VERSION}.tar.gz" echo "${FOSSIL_SHA256} /tmp/fossil.tar.gz" | sha256sum -c - sudo tar -xzf /tmp/fossil.tar.gz -C /usr/local/bin fossil rm -f /tmp/fossil.tar.gz - name: Verify Fossil version # A silent upstream change has to surface as an explicit failure here, # not as mysterious behavioural drift in the real-fossil-gated tests. run: | reported=$(fossil version | sed -n 's/.*fossil version \([0-9][0-9.]*\).*/\1/p') if [ "$reported" != "${FOSSIL_VERSION}" ]; then echo "expected fossil ${FOSSIL_VERSION}, got: $(fossil version)" >&2 exit 1 fi fossil version - name: Build all packages # Build every package, not just the cmd/libfossil dependency closure. # The `go test $(go list ./...)` step below silently drops any package # that fails to compile (go list omits it from stdout instead of # failing), so a broken import can vanish from the test run undetected. # Building `./...` first makes that a loud, fail-fast build error. run: GOWORK=off go build ./... - name: Unit tests # REQUIRE_FOSSIL_BIN turns "fossil is missing" into a failure rather # than an invisible skip. Verifying a real fossil reads what we write # is the one thing our own tests cannot substitute for. env: REQUIRE_FOSSIL_BIN: "1" run: GOWORK=off go test $(GOWORK=off go list ./... | grep -v '/dst') -count=1 -timeout=120s - name: DST run: GOWORK=off go test ./dst/... -count=1 -timeout=300s - name: Driver tests (modernc) run: cd db/driver/modernc && GOWORK=off go test ./... -count=1 - name: Vet run: GOWORK=off go vet ./... test-ncruces: name: Test (ncruces) runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-go@v5 with: go-version-file: go.mod - name: Install pinned Fossil run: | curl -fsSL --retry 3 --retry-delay 2 --retry-all-errors \ -o /tmp/fossil.tar.gz \ "https://fossil-scm.org/home/uv/fossil-linux-x64-${FOSSIL_VERSION}.tar.gz" echo "${FOSSIL_SHA256} /tmp/fossil.tar.gz" | sha256sum -c - sudo tar -xzf /tmp/fossil.tar.gz -C /usr/local/bin fossil rm -f /tmp/fossil.tar.gz - name: Verify Fossil version # A silent upstream change has to surface as an explicit failure here, # not as mysterious behavioural drift in the real-fossil-gated tests. run: | reported=$(fossil version | sed -n 's/.*fossil version \([0-9][0-9.]*\).*/\1/p') if [ "$reported" != "${FOSSIL_VERSION}" ]; then echo "expected fossil ${FOSSIL_VERSION}, got: $(fossil version)" >&2 exit 1 fi fossil version - name: Build all packages (ncruces) # Same fail-fast guard as the modernc leg, under the test_ncruces tag so # the ncruces driver's compile surface is covered too. run: GOWORK=off go build -tags test_ncruces ./... - name: Unit tests (ncruces) env: REQUIRE_FOSSIL_BIN: "1" run: GOWORK=off go test -tags test_ncruces $(GOWORK=off go list ./... | grep -v '/dst' | grep -v 'cmd/libfossil') -count=1 -timeout=120s - name: DST (ncruces) run: GOWORK=off go test -tags test_ncruces ./dst/... -count=1 -timeout=300s - name: Driver tests (ncruces) run: cd db/driver/ncruces && GOWORK=off go test ./... -count=1 test-otel: name: Test (observer/otel) runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-go@v5 with: go-version-file: go.mod - name: OTel sub-module tests run: cd observer/otel && GOWORK=off go test ./... -count=1 test-docs: name: Test (docs/site) runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-go@v5 with: go-version-file: go.mod - name: Install Hugo (extended) # docs/site pulls the hextra theme via Hugo Modules, which need both the # Go toolchain (setup-go above) and the Hugo binary. Pin the version so a # future Hugo release can't silently change build behaviour under us. env: HUGO_VERSION: "0.162.1" run: | curl -fsSL "https://github.com/gohugoio/hugo/releases/download/v${HUGO_VERSION}/hugo_extended_${HUGO_VERSION}_linux-amd64.tar.gz" \ | sudo tar -xz -C /usr/local/bin hugo - name: Build docs site # docs/site is the one module with no Go source of its own: its go.mod # exists only to import the hextra theme as a Hugo Module, so `go build # ./...` there matches no packages and gates nothing. A real Hugo build # is the equivalent compile gate — it resolves the module import, # compiles templates, and renders content, failing loud on a broken # import or template the way the Go build steps do for the other modules. run: cd docs/site && GOWORK=off hugo --gc --minify