The copy cache trusted a change time it had seen as proof the file had not changed. NTFS stamps ChangeTime from Windows' clock tick, not the precise clock: on the Windows box, 300 ms of back-to-back same-size writes got a new ChangeTime only every ~0.5 ms (2147 of 2691 writes kept the previous stamp; diffs 0.5/1.0/1.5 ms). A rewrite in the tick of the comparison kept the stamp the cache saw, so the cached relation was reused. That, not write merging, is why TestCodexArchivedSameSizeRewriteInvalidatesDiscovery failed 19 of 300 runs on Windows, alone as well as in the full package. A comparison is now cached only when both files' change times are more than a second older than the moment it started (codexStampSettle); a file changed more recently is compared again next time. A later rewrite then lands in a later tick and changes the stamp. Change times are compared as times (Windows FILETIME converted). TestCodexSameTickRewriteIsNotCached gives every write one change time and a clock 10 ms into that tick. With the settle rule taken out (cacheable = both stamps present), it fails: "a rewrite in the tick of the comparison reused its stale relation". With it, it passes, and the second half checks settled copies are still compared once, not every time. TestCodexArchivedDiscoveryCacheAndAppend moves its clock a minute on so the just-written copies count as settled. The Windows CI step now runs the Codex copy tests (CodexArchived, CodexSameTick, CodexPlainTwin), which it had never run. Run: Windows 11 box (cross-built sessions.test.exe, sandbox USERPROFILE/ APPDATA/TEMP): old binary 19/300 failures of the reported test; new binary 0/1000 for the three Codex copy tests, and the Windows CI regex x5 with testdata passes. macOS: go test -race ./internal/sessions, -race -count=30 of the Codex copy tests, go vet (darwin, GOOS=linux, GOOS=windows), gofmt -l. Not run: Linux with a coarse ctime clock (kernels before 6.13); the rule covers it the same way.
170 lines
6.4 KiB
YAML
170 lines
6.4 KiB
YAML
name: Test
|
|
|
|
on:
|
|
push:
|
|
branches: [main]
|
|
pull_request:
|
|
workflow_dispatch:
|
|
|
|
# On main a run finishes: a release waits for a green run, and one cancelled
|
|
# by every next push left none to wait for (LESSONS.md). A newer push still
|
|
# replaces a run that hasn't started. A PR's run is cancelled by its next push.
|
|
concurrency:
|
|
group: test-${{ github.ref }}
|
|
cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}
|
|
|
|
permissions:
|
|
contents: read
|
|
|
|
jobs:
|
|
test:
|
|
name: test (${{ matrix.os }})
|
|
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-file: go.mod
|
|
|
|
# The GUI links GTK 3 and WebKitGTK 4.1 through cgo on Linux (see the
|
|
# Makefile), so vet and test need their headers and the gtk3 tag.
|
|
# A stuck apt mirror held main's run here for 2.5 hours (b0d456c3), and
|
|
# every later push queued behind it; it takes under a minute.
|
|
- name: Install GTK and WebKitGTK
|
|
if: runner.os == 'Linux'
|
|
timeout-minutes: 15
|
|
run: |
|
|
sudo apt-get update
|
|
sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.1-dev
|
|
|
|
- name: Tags
|
|
id: tags
|
|
shell: bash
|
|
run: |
|
|
if [ "$RUNNER_OS" = Linux ]; then echo "tags=gtk3" >> "$GITHUB_OUTPUT"; fi
|
|
|
|
# Unformatted files reached main 8 times from 10-01 to 10-06 and each
|
|
# took a gofmt commit of its own. Linux only: Windows checks out CRLF.
|
|
- name: Gofmt
|
|
if: runner.os == 'Linux'
|
|
shell: bash
|
|
run: |
|
|
bad=$(gofmt -l $(git ls-files '*.go'))
|
|
if [ -n "$bad" ]; then echo "not gofmt'd:"; echo "$bad"; exit 1; fi
|
|
|
|
- name: Vet
|
|
run: go vet -tags "${{ steps.tags.outputs.tags }}" ./...
|
|
|
|
- uses: oven-sh/setup-bun@v2
|
|
if: runner.os == 'Linux'
|
|
with:
|
|
bun-version: 1.4.2
|
|
|
|
- name: Test
|
|
if: runner.os != 'Windows'
|
|
run: go test -tags "${{ steps.tags.outputs.tags }}" ./...
|
|
|
|
- name: Production plugin flow
|
|
if: runner.os == 'Linux'
|
|
timeout-minutes: 10
|
|
env:
|
|
MAGPIE_FLOW_BIG: '1'
|
|
run: |
|
|
bun --version
|
|
go test -timeout 10m -tags "${{ steps.tags.outputs.tags }}" ./internal/plugin/... -run '^TestFlowBigReplyProd$' -count=1
|
|
|
|
# The redaction tests reuse this binary in fresh processes to check
|
|
# first-use races, so it must include the race detector. The gateway's
|
|
# tests can't call t.Parallel (t.Setenv), so its suite runs split over
|
|
# one process per core (#923); each process isolates its own home.
|
|
- name: Race
|
|
if: runner.os != 'Windows'
|
|
shell: bash
|
|
run: |
|
|
go test -race -c -o "$RUNNER_TEMP/gateway.race" -tags "${{ steps.tags.outputs.tags }}" ./internal/gateway/
|
|
build/race-shards.sh internal/gateway "$RUNNER_TEMP/gateway.race" "$(getconf _NPROCESSORS_ONLN)" -test.timeout=20m
|
|
go test -race -tags "${{ steps.tags.outputs.tags }}" ./internal/redact/
|
|
|
|
# Account usage mixes cached results with concurrent fetches.
|
|
- name: Race account usage
|
|
if: runner.os != 'Windows'
|
|
run: go test -race -tags "${{ steps.tags.outputs.tags }}" ./internal/provider/ -run '^Test(LoginUsage|SubscriptionUsageEachAccount)'
|
|
|
|
# Some tests fake a home through $HOME, which Windows doesn't read
|
|
# (USERPROFILE), or assume Unix file modes. Compile the full suite
|
|
# on Windows and run the isolated native checks below.
|
|
- name: Compile tests
|
|
if: runner.os == 'Windows'
|
|
run: go test -run '^$' ./...
|
|
|
|
# These checks use in-memory errors and loopback test servers. Run
|
|
# the native Winsock refusal case rather than only compiling it.
|
|
- name: Windows MCP health
|
|
if: runner.os == 'Windows'
|
|
run: go test -v -count=1 -timeout 2m ./internal/library -run '^(TestNetHealth|TestCheckHTTP)$'
|
|
|
|
# Usage isolates USERPROFILE and the agent homes; execute its native
|
|
# ChangeTime checks rather than only cross-compiling the Windows API.
|
|
- name: Windows usage cache
|
|
if: runner.os == 'Windows'
|
|
run: go test -v -count=1 ./internal/usage -run '^(TestWindows|TestLogChange|TestLogFileInfoPreservesIdentity$|TestUsageSameSizeAndMtimeRewrite$|TestLogGrowingRewriteThenAppend$|TestUsagePageCacheAfterUncachedQuery$)'
|
|
|
|
# Install tests keep their files in t.TempDir; exercise real Windows
|
|
# executable and held-handle rename/rollback behavior.
|
|
- name: Windows update install
|
|
if: runner.os == 'Windows'
|
|
run: go test -v -count=1 -timeout 2m ./internal/update -run '^(TestInstallBinary|TestMoveAside)'
|
|
|
|
# These tests use temporary paths, without the Unix HOME assumptions
|
|
# of the full suite. Run the native link-kind and sharing-error checks,
|
|
# and the Codex copy checks that read NTFS ChangeTime (#1299).
|
|
- name: Windows session moves
|
|
if: runner.os == 'Windows'
|
|
run: go test -v -count=1 ./internal/sessions -run '^Test(WindowsSession|Move|CopyAll|CodexArchived|CodexSameTick|CodexPlainTwin)'
|
|
|
|
# dsh's layered config is read and edited in temporary homes. Run
|
|
# these checks on Windows too, including Desktop's home patch layer.
|
|
- name: Windows dsh configuration
|
|
if: runner.os == 'Windows'
|
|
run: go test -v -count=1 ./internal/agent -run '^Test(Dsh|KeepDsh)'
|
|
|
|
- name: Windows VS Code detection
|
|
if: runner.os == 'Windows'
|
|
run: go test -v -count=1 ./internal/agent -run '^TestVSCodeDetected$'
|
|
|
|
cli:
|
|
name: cli (${{ matrix.goos }}/${{ matrix.goarch }}, nogui)
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
include:
|
|
- goos: linux
|
|
goarch: amd64
|
|
- goos: android
|
|
goarch: arm64
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
- uses: actions/setup-go@v5
|
|
with:
|
|
go-version-file: go.mod
|
|
- name: Build the terminal-only release
|
|
env:
|
|
CGO_ENABLED: '0'
|
|
run: make release-cli TARGETS="${{ matrix.goos }}/${{ matrix.goarch }}"
|
|
- uses: actions/setup-node@v4
|
|
if: matrix.goos == 'linux'
|
|
with:
|
|
node-version: 22
|
|
- name: Test the site and installer
|
|
if: matrix.goos == 'linux'
|
|
run: |
|
|
sh -n site/public/install.sh
|
|
node --test site/*.test.mjs
|