Files
yetone 79f9b3752b sessions: a Codex archived copy rewritten at the same size in the clock tick of its last comparison is compared again, not taken from the cache, on Windows too (#1299, wizzy-yang)
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.
2026-10-08 19:04:18 +08:00

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