# Develop and test

> Build commands, go test ./... and integration testcontainers, pre-commit checks, stress runner, and release-process checklist.

- Repository: timescale/tigerfs
- GitHub: https://github.com/timescale/tigerfs
- Human docs: https://grok-wiki.com/public/docs/timescale-tigerfs-60719456a5c3
- Complete Markdown: https://grok-wiki.com/public/docs/timescale-tigerfs-60719456a5c3/llms-full.txt

## Source Files

- `CLAUDE.md`
- `docs/release-process.md`
- `test/integration/main_test.go`
- `test/stress/README.md`
- `scripts/pre-commit`
- `.github/workflows/test.yml`

---

---
title: "Develop and test"
description: "Build commands, go test ./... and integration testcontainers, pre-commit checks, stress runner, and release-process checklist."
---

TigerFS is a Go module (`github.com/timescale/tigerfs`, Go 1.25.1) built from `cmd/tigerfs` into `bin/tigerfs`. Local verification spans unit tests under `internal/tigerfs/`, mount-based integration tests in `test/integration/` (PostgreSQL via local env or testcontainers), optional Docker FUSE runs via `scripts/test-docker.sh`, the `tigerfs-stress` binary in `test/stress/`, and a release checklist in `docs/release-process.md` that pairs Postgres 18 + TimescaleDB with GoReleaser tagging.

## Prerequisites

| Requirement | Used by |
|-------------|---------|
| Go 1.25+ (see `go.mod`) | Build, all `go test` |
| PostgreSQL / Docker | DB unit tests (`internal/tigerfs/db/`), integration, release |
| Linux: FUSE (`/dev/fuse`, `libfuse-dev`) | Native integration mounts, `scripts/test-docker.sh`, CI |
| macOS: NFS (`/sbin/mount_nfs`) | Default integration mount on Darwin |
| Docker daemon | testcontainers fallback, stress runner, docker-FUSE stress |

<Note>
Many `internal/tigerfs/db/` tests call `t.Skip` when `PGHOST` / `TEST_DATABASE_URL` are unset. CI and release flows always set those variables so DB tests actually run.
</Note>

## Build

Primary binary:

```bash
go build -o bin/tigerfs ./cmd/tigerfs
```

Build all packages (matches CI):

```bash
go build -v ./...
```

Stress runner (separate main under `test/stress`):

```bash
go build -o bin/tigerfs-stress ./test/stress
```

Release snapshot (no tag push):

```bash
goreleaser release --snapshot --clean
./dist/tigerfs_darwin_arm64*/tigerfs version
```

GoReleaser builds `tigerfs` for `linux` and `darwin` × `amd64`/`arm64` with `CGO_ENABLED=0`, embedding version metadata via ldflags.

## Unit tests

### Full tree

```bash
go test ./...                    # everything, including integration
go test -race ./...              # race detector
go test -run TestName ./path     # single test
```

### Package-scoped (no integration)

Exclude mount tests when iterating quickly:

```bash
PACKAGES=$(go list ./... | grep -v /test/integration)
go test -race $PACKAGES
```

### DB-connected unit tests

Point at a running Postgres (CI uses port 5432; release doc uses 15432):

```bash
PGHOST=127.0.0.1 PGPORT=5432 PGUSER=postgres PGPASSWORD=test PGDATABASE=postgres \
TEST_DATABASE_URL='postgres://postgres:test@127.0.0.1:5432/postgres?sslmode=disable' \
  go test -count=1 -timeout 300s ./internal/tigerfs/...
```

`-count=1` disables the Go test cache so skipped tests do not silently stay skipped from a prior run.

### Synth tests

Filter all synthesized-app tests across packages:

```bash
go test ./internal/tigerfs/fs/synth/...
go test -run "^TestSynth_" ./internal/tigerfs/fs/... ./test/integration/...
```

FUSE-layer tests that need a live mount are skipped in-package with a pointer to `test/integration/`; filesystem behavior belongs in integration tests.

## Integration tests

Package: `test/integration/`. Tests mount TigerFS against real PostgreSQL and exercise `os.ReadFile`, `os.Rename`, pipelines, synth workspaces, DDL, history/undo, and more.

### Database provisioning

`TestMain` in `main_test.go`:

1. Probes local PostgreSQL (`TEST_DATABASE_URL` or `PG*` vars, with Unix-socket detection when no host/password).
2. If local PG is unavailable and Docker is reachable, starts one shared **TimescaleDB HA pg18** container via testcontainers-go.
3. Each test isolates data with unique schemas (`setupLocalTestDB` / `setupLocalTestDBEmpty`).

```bash
go test -count=1 -timeout 600s ./test/integration/...
go test -count=1 -timeout 600s ./test/integration/... -run TestMount_ListTables
```

If neither local PG nor Docker is available, tests skip with `Neither local PostgreSQL nor Docker available`.

### Mount method matrix

| Host OS | Default mount | Override |
|---------|---------------|----------|
| Linux | FUSE (`/dev/fuse`) | — |
| macOS | NFS (in-process `go-nfs`) | `TEST_MOUNT_METHOD=docker` runs tests in a Linux FUSE container |

Platform-specific test prefixes (use only when behavior is mount-specific):

| Prefix | Meaning |
|--------|---------|
| `TestMount_`, `TestWrite_`, `TestDDL_`, … | Generic; runs on FUSE or NFS |
| `TestNFS_` | NFS-only; skipped on Linux/FUSE |
| `TestFUSE_` | FUSE-only; skipped on macOS/NFS |

### Docker FUSE integration (Linux container)

For Linux-native FUSE from any host (including macOS):

```bash
./scripts/test-docker.sh                      # all integration tests
./scripts/test-docker.sh -run TestPipeline    # filter
./scripts/test-docker.sh -v -timeout 10m      # extra go test flags
./scripts/test-docker.sh -out results.log     # custom log path
```

Uses `test/docker/docker-compose.test.yml`; tears down compose on exit.

## Pre-commit checks

Install the git hook:

```bash
./scripts/install-hooks.sh
```

Hook script `scripts/pre-commit` runs on every commit (skip with `git commit --no-verify`):

<Steps>
<Step title="Format">
`gofmt -l .` must be empty (otherwise run `go fmt ./...`).
</Step>
<Step title="Vet">
`go vet ./...`
</Step>
<Step title="Unit tests with race">
`go test -race` on all packages **except** `test/integration`.
</Step>
</Steps>

Full maintainer gate before commit (from project guidance — includes integration and mod tidy):

```bash
go fmt ./... && go vet ./... && go test ./... && go mod tidy
```

<Warning>
The pre-commit hook does **not** run integration tests or `go mod tidy`. Release and CI cover those surfaces.
</Warning>

## CI workflows

### `test.yml` (default on PR and `main` push)

| Step | Command / behavior |
|------|------------------|
| Service | `timescale/timescaledb:latest-pg18` on port 5432 |
| FUSE | `apt-get install fuse libfuse-dev` |
| Tidy check | `go mod tidy` + `git diff --exit-code go.mod go.sum` |
| Build | `go build -v ./...` |
| Vet | `go vet ./...` |
| Unit tests | `-race -timeout 300s -coverprofile=coverage.txt`, packages excluding `test/integration`, with `PGHOST` / `TEST_DATABASE_URL` set |
| Integration | Only when `workflow_dispatch` input `run_integration: true` |
| Coverage | Codecov upload on PR / `main` |

Trigger integration tests manually:

```bash
gh workflow run test.yml --ref "$(git branch --show-current)" -f run_integration=true
```

### `stress.yml`

Path-filtered on PRs touching `fs/`, `db/`, `fuse/`, migrate, or stress scripts. Runs `./scripts/stress-docker.sh` on Ubuntu with FUSE inside Docker:

| Trigger | Iterations |
|---------|------------|
| PR | 200 |
| Weekly schedule (Mon 06:17 UTC) | 1000 |
| `workflow_dispatch` | Configurable input |

Flags: `--validate-every 1 --large-files --many-files`. Failure dumps upload from `test/stress/docker/host-out/`.

### `release.yml`

Pushing a `v*.*.*` tag runs GoReleaser `release --clean` and publishes binaries; non-prerelease tags also sync `install.sh` and `latest.txt` to S3.

## Stress runner (`tigerfs-stress`)

Standalone binary — **no TigerFS internal imports**. It drives a mounted workspace through `os.*` calls, maintains an in-memory MD5 model, and validates after every op (and around undo).

```text
tigerfs-stress ── os.WriteFile ──> kernel mount ──> TigerFS ──> PostgreSQL
       │                                    │
       └── ValidateWorkspace() ◄── os.ReadFile + hash compare
```

| Mode | Where tigerfs runs | Mount |
|------|-------------------|-------|
| `bin/tigerfs-stress start` on macOS | Host | NFS |
| Same on Linux | Host | FUSE |
| `./scripts/stress-docker.sh` | Linux container | FUSE |

Common commands:

```bash
bin/tigerfs-stress start
bin/tigerfs-stress start --seed 42 --iterations 50
bin/tigerfs-stress start --large-files --many-files --iterations 100 --validate-every 5
bin/tigerfs-stress stop
./scripts/stress-docker.sh --seed 42 --iterations 200
```

Stress package unit tests (no Docker/TigerFS):

```bash
go test ./test/stress/...
```

<Info>
Long macOS NFS runs may print `[warn iter ...] readLatestLogID regressed` lines. The runner retries and continues; these reflect NFS snapshot timing, not TigerFS data loss. See the troubleshooting page for interpretation.
</Info>

Exit codes: `0` pass, `1` verification failure, `2` infrastructure failure. Validation failures write diagnostic dumps under `/tmp/tigerfs-stress-*` (or `test/stress/docker/host-out/` in docker mode) and leave infrastructure up for inspection.

## Release checklist

Follow `docs/release-process.md` end-to-end before tagging.

<Steps>
<Step title="Start Postgres 18 + TimescaleDB">
```bash
docker run --rm -d --name tigerfs-release-pg -e POSTGRES_PASSWORD=test \
  -p 15432:5432 timescale/timescaledb-ha:pg18
sleep 5
```
</Step>
<Step title="Run full test suite">
```bash
go fmt ./... && go vet ./... && go mod tidy

PGHOST=127.0.0.1 PGPORT=15432 PGUSER=postgres PGPASSWORD=test PGDATABASE=postgres \
TEST_DATABASE_URL='postgres://postgres:test@127.0.0.1:15432/postgres?sslmode=disable' \
  go test -count=1 -timeout 300s ./internal/tigerfs/...

go test -count=1 -timeout 600s ./test/integration/...
./scripts/test-docker.sh -v -timeout 300s
```
</Step>
<Step title="Migration verification (when migrations ship)">
Seed a pre-release workspace, then `tigerfs migrate --describe`, `--dry-run`, apply, and re-run `go test ... -run TestMigrate` against the migrated DB.
</Step>
<Step title="CHANGELOG and checklist">
Add a user-facing section to `CHANGELOG.md` (replace `YYYY-MM-DD` with the release date). Mark tasks in `docs/implementation/implementation-tasks-checklist.md`.
</Step>
<Step title="Snapshot build">
`goreleaser release --snapshot --clean` and smoke-test `./dist/.../tigerfs version`.
</Step>
<Step title="Commit, tag, push">
```bash
git add CHANGELOG.md README.md docs/
git commit -m "docs: prepare vX.Y.Z release"
git tag vX.Y.Z
git push origin main
git push origin vX.Y.Z
```
Edit the GitHub release body to match `CHANGELOG.md` (GoReleaser auto-changelog is a starting point only).
</Step>
<Step title="Stop release container">
`docker stop tigerfs-release-pg`
</Step>
</Steps>

## Quick reference

| Goal | Command |
|------|---------|
| Build CLI | `go build -o bin/tigerfs ./cmd/tigerfs` |
| Fast unit loop | `go test -race $(go list ./... \| grep -v /test/integration)` |
| Integration (local) | `go test -count=1 -timeout 600s ./test/integration/...` |
| Integration (FUSE in Docker) | `./scripts/test-docker.sh` |
| Install pre-commit hook | `./scripts/install-hooks.sh` |
| Stress (native) | `go build -o bin/tigerfs-stress ./test/stress && bin/tigerfs-stress start` |
| Stress (FUSE in Docker) | `./scripts/stress-docker.sh` |
| Release snapshot | `goreleaser release --snapshot --clean` |

```mermaid
flowchart TB
  subgraph local["Local developer"]
    fmt["go fmt / go vet"]
    unit["go test internal/..."]
    integ["go test test/integration/..."]
    docker["scripts/test-docker.sh"]
    stress["bin/tigerfs-stress"]
  end
  subgraph pg["PostgreSQL"]
    localPG["Local PG / PG* env"]
    tc["testcontainers shared container"]
  end
  subgraph ci["GitHub Actions"]
    testyml["test.yml"]
    stressyml["stress.yml"]
    rel["release.yml on v* tag"]
  end
  fmt --> unit
  unit --> localPG
  integ --> localPG
  integ --> tc
  docker --> integ
  stress --> pg
  testyml --> unit
  stressyml --> stress
  rel --> rel
```

## Related pages

<CardGroup>
<Card title="Installation" href="/installation">
Platform prerequisites (FUSE, NFS, Docker) before running tests locally.
</Card>
<Card title="Troubleshooting" href="/troubleshooting">
Stress monotonicity warnings, `--debug` logging, and errno recovery.
</Card>
<Card title="Migrate workspaces" href="/migrate-workspaces">
Release-time `tigerfs migrate` verification and `TestMigrate` integration coverage.
</Card>
<Card title="Demo environment" href="/demo-environment">
`demo.sh` workflows for manual exploration separate from automated tests.
</Card>
</CardGroup>
