Files
pad/.github/workflows/ci.yml
T
xarmian 756d91acad fix(ci): gofmt + bump race-detector timeout to 30m (#299)
CI on main has been failing since the PLAN-866 attachment work
landed. Two independent issues:

1. gofmt failures (golangci-lint) — seven files in the attachments
   path had trailing-comment alignment that gofmt wanted nudged a
   column. Pure whitespace; ran `gofmt -w` across the affected
   files. golangci-lint's gofmt linter caught it on every PR /
   push since TASK-870 but we hadn't been watching those signals.

   Files cleaned: internal/attachments/{fs_store_test,mime,
   mime_test,processor_test}.go, internal/server/{
   handlers_attachments_download_test,handlers_attachments_transform,
   render/attachments_test}.go.

   Local guard: `gofmt -l ./...` now exits clean.

2. Race-detector tests timed out at 20m on the GitHub-hosted runner.
   Two contributors:
     - PostgreSQL adds latency on every CREATE/DROP plus on the
       bcrypt hash inside auth/bootstrap (~3s per call under -race
       on the runner). Tests that bootstrap a fresh user (e.g.
       TestSessionIPChange_*) pay the full cost each time.
     - The PLAN-866 image-processing tests (thumbnail derivation,
       rotate / crop transform) added ~2-3 minutes of decode/encode
       work on top of the existing suite.

   The previous "20m gives margin without papering over a hang"
   comment was right at the time it was written; we now genuinely
   need more headroom. Bumped to 30m on both the SQLite and
   PostgreSQL race steps. Genuine deadlocks would still trip this
   and produce the goroutine-dump panic — we just stop confusing
   "slow but progressing" with "permanently hung".

   Reference points before / after:
     - TASK-875 main run #294: Go (PostgreSQL) finished in 17m48s ✓
     - TASK-880 main run #298: Go (PostgreSQL) hit 20m timeout ✗
     - Local: my new tests under -race add ~63s on a developer laptop
       (TestThumbnails + TestTransform + TestProcessor combined).

Verification:
  go test ./...             — pass
  go vet ./...              — clean
  gofmt -l (recursively)    — clean
2026-04-29 15:41:12 -04:00

216 lines
7.5 KiB
YAML

name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: true
permissions:
contents: read
# All third-party Actions are pinned to a 40-char commit SHA with a trailing
# '# vX.Y.Z' comment so a compromised maintainer or moved tag cannot silently
# execute attacker code in CI. Bump the SHA + comment together when updating.
jobs:
go:
name: Go
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
- uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5.6.0
with:
go-version: "1.26"
- name: Create web build placeholder for embed
run: mkdir -p web/build && echo "placeholder" > web/build/.gitkeep
- name: Run go vet
run: go vet ./...
- name: Run golangci-lint
# only-new-issues: false means CI fails on ANY linter finding,
# not just findings on PR-changed lines. The IDEA-732 cleanup
# (PRs #247/#249/#251/#252) cleared the existing findings under
# the configured linter set in .golangci.yml — staticcheck SA*,
# govet, ineffassign, gofmt, and the standalone `unused` linter
# (which reports U1000). Flipping the gate now prevents
# regression drift going forward.
# v2 of golangci-lint is required because v1 is capped at older
# Go releases that we no longer support.
uses: golangci/golangci-lint-action@1e7e51e771db61008b38414a730f564565cf7c20 # v9.2.0
with:
version: v2.11.4
args: --timeout=5m
only-new-issues: false
- name: Run govulncheck
# Fails the build on any known vulnerability in a package we
# actually reach via the call graph. Net-positive: catches CVEs
# in indirect deps early, without the noise of hitting every
# stale entry in our dependency tree.
#
# Pinned to a specific govulncheck release. Track upstream in
# Pad's workspace; bump intentionally so an upstream behavior
# change can't break unrelated PRs. Update via:
# go install golang.org/x/vuln/cmd/govulncheck@<new-tag>
run: |
go install golang.org/x/vuln/cmd/govulncheck@v1.2.0
"$(go env GOPATH)/bin/govulncheck" ./...
- name: Run tests
run: go test ./...
- name: Run tests with race detector
if: github.ref == 'refs/heads/main'
# Default 10m is tight: the full server-package suite under -race
# measures ~13m locally on a developer laptop after BUG-851 (the
# ipRateLimiter goroutine drain). The PLAN-866 attachment work
# (image decode/encode/resize across thumbnail + transform tests)
# pushes total race-step runtime past 20m on the GitHub-hosted
# runner — bumped to 30m to give headroom without papering over
# an actual hang. Genuine deadlocks would still hit this and
# produce the goroutine-dump panic.
run: go test -race -timeout=30m ./...
- name: Build binary
run: go build -o pad ./cmd/pad
- name: Verify binary runs
run: ./pad --help
go-postgres:
name: Go (PostgreSQL)
runs-on: ubuntu-latest
services:
postgres:
image: postgres:17-alpine
env:
POSTGRES_USER: pad
POSTGRES_PASSWORD: pad
POSTGRES_DB: pad
ports:
- 5432:5432
options: >-
--health-cmd "pg_isready -U pad"
--health-interval 5s
--health-timeout 3s
--health-retries 10
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
- uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5.6.0
with:
go-version: "1.26"
- name: Create web build placeholder for embed
run: mkdir -p web/build && echo "placeholder" > web/build/.gitkeep
- name: Run tests against PostgreSQL
env:
PAD_TEST_POSTGRES_URL: "postgres://pad:pad@localhost:5432/pad?sslmode=disable"
run: go test ./... -count=1
- name: Run tests with race detector against PostgreSQL
if: github.ref == 'refs/heads/main'
env:
PAD_TEST_POSTGRES_URL: "postgres://pad:pad@localhost:5432/pad?sslmode=disable"
# See SQLite race-detector step: 30m headroom over the default 10m.
# PostgreSQL adds latency on every CREATE/DROP and bcrypt under
# -race is ~3s per call — TestSessionIPChange-style tests that
# bootstrap a fresh user pay the full cost each time. The PLAN-866
# attachment work pushed the cumulative wall over 20m.
run: go test -race -timeout=30m ./... -count=1
web:
name: Web
runs-on: ubuntu-latest
defaults:
run:
working-directory: web
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: "22"
cache: "npm"
cache-dependency-path: web/package-lock.json
- name: Install dependencies
run: npm ci
- name: Audit npm dependencies (production, high+)
# Fail the build on any HIGH or CRITICAL advisory in production deps.
# Dev-only advisories are treated as informational — they don't ship
# and fixing them can require waiting on upstream maintainers.
run: npm audit --audit-level=high --omit=dev
- name: Build
run: npm run build
- name: Type check (svelte-check)
run: npm run check
e2e:
name: E2E (Playwright)
runs-on: ubuntu-latest
# Build the binary + UI once and reuse across Playwright projects.
# The suite is small (<10s at the time of writing — see TASK-733 for
# follow-up coverage); the `timeout-minutes` cap is a sanity check.
timeout-minutes: 10
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
- uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5.6.0
with:
go-version: "1.26"
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: "22"
cache: "npm"
cache-dependency-path: web/package-lock.json
- name: Install web dependencies
working-directory: web
run: npm ci
- name: Build web UI
working-directory: web
run: npm run build
- name: Build pad binary
# Web build output is embedded via //go:embed; it must exist before
# the Go build. The CI `go` job above builds against a placeholder,
# which is fine for tests — for e2e we need the real embedded UI.
run: go build -o pad ./cmd/pad
- name: Install Playwright browsers
working-directory: web
# --with-deps pulls in the Ubuntu libraries Playwright needs
# (libatk, libnss, libcups, …). Scoped to chromium to cut download
# time — the suite's mobile project uses Pixel 7, which defaults to
# Chromium, so we don't need WebKit.
run: npx playwright install --with-deps chromium
- name: Run Playwright
working-directory: web
env:
CI: "1"
run: npx playwright test
- name: Upload Playwright report on failure
if: failure()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: playwright-report
path: web/playwright-report/
retention-days: 14