Skip to content
go-libfossil Artifact [2feb05f94f]
Login

Artifact [2feb05f94f]

Artifact 2feb05f94f4ec1ec8041408ce5d38f66bb2092268079389dba78965c76723019:


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: Warm Fossil global config
        # Fossil keeps a per-user global config database outside any repository
        # and creates it lazily on first use. `go test` runs packages
        # concurrently, several of them shell out to fossil, and nothing sets
        # HOME, so on a fresh runner two test processes can race to create it:
        # one wins and the others die with "table global_config already exists".
        # Creating it once here, single-threaded, means no test process is ever
        # the one to create it. `fossil version` does not open that database, so
        # the step above does not incidentally cover this; `fossil all list`
        # does, and prints nothing.
        #
        # The first call's exit status is ignored deliberately -- it exists to
        # create the database, not to assert anything, so an unrelated non-zero
        # must not red the job. The second call is what carries the weight: it
        # must succeed on its own, which proves the database is both present and
        # usable. Checking that a file exists would not -- a truncated or
        # partially-written database passes an existence check while concurrent
        # invocations still die on the missing schema, which is exactly the
        # "looks like protection while the race stays live" failure this guards
        # against. Re-running the command also keeps the check independent of
        # where fossil resolved the database to (~/.fossil, ~/.config/fossil.db,
        # $XDG_CONFIG_HOME, or $FOSSIL_HOME).
        run: |
          fossil all list >/dev/null 2>&1 || true
          fossil all list >/dev/null
      - 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=300s
      - 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: Warm Fossil global config
        # Same single-threaded warm-up as the modernc leg -- see the comment
        # there for why `fossil version` does not cover this, and why the first
        # call's exit status is ignored but the second call's is not.
        run: |
          fossil all list >/dev/null 2>&1 || true
          fossil all list >/dev/null
      - 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') -count=1 -timeout=300s
      - 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

  build-wasm:
    name: Build (js/wasm)
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-go@v5
        with:
          go-version-file: go.mod
      - name: Build main module for js/wasm
        # Browser support is the reason the ncruces driver is in this repo at
        # all, but until this job nothing was ever compiled for js/wasm.
        #
        # What a build gate catches is a dependency with no js/wasm build at
        # all -- most realistically the wrong SQLite driver reaching a shared
        # package, since modernc.org/libc is linux/darwin-only and would fail
        # here while both linux legs above stay green. It does not catch every
        # WASM problem: much of the stdlib (os/exec included) compiles for js
        # behind stubs and only fails when called, so runtime-behaviour gaps
        # need tests under a browser harness, which is a separate question.
        #
        # test_ncruces is the same tag the ncruces leg builds under. It switches
        # both internal/testdriver and cmd/libfossil from modernc to ncruces,
        # which is the driver that can actually target WASM. Without the tag,
        # both packages would pull modernc into this build.
        #
        # The package set comes from `go list`, not a hand-written list, so a
        # newly added package is inside the gate by default rather than
        # silently outside it. `-e` stops go list from aborting on the package
        # excluded below -- without it that unresolvable import would
        # discard the entire list. Packages that already declare `//go:build
        # !js` in source (internal/stash, internal/undo) are dropped by go list
        # itself under GOOS=js; that exclusion lives in the code, not here.
        #
        # The only exclusion is examples/embed-repo-api: its main package
        # imports the modernc driver directly, and modernc.org/libc ships no
        # js/wasm build. cmd/libfossil is deliberately built here with the
        # test_ncruces binding, so this gate compiles the tagged CLI for WASM.
        # examples/embed-repo-api stays fully covered by the linux legs above.
        env:
          GOWORK: "off"
          GOOS: js
          GOARCH: wasm
        run: |
          set -o pipefail
          pkgs=$(go list -e -tags test_ncruces ./... \
            | grep -v -e '/examples/embed-repo-api$')
          echo "building $(echo "$pkgs" | wc -l) packages for js/wasm:"
          echo "$pkgs"
          echo "$pkgs" | xargs go build -tags test_ncruces
      - name: Build ncruces driver for js/wasm
        # A separate module, so the step above never reaches it -- and it is
        # the driver the browser build actually runs on. Its js-only source
        # (ncruces_js.go) is compiled by no other job in this workflow.
        env:
          GOWORK: "off"
          GOOS: js
          GOARCH: wasm
        run: cd db/driver/ncruces && go build ./...

  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