Skip to content

ci: run full test suite on Windows - #652

Open
zzet wants to merge 1 commit into
mainfrom
ci/run-full-tests-on-windows
Open

ci: run full test suite on Windows#652
zzet wants to merge 1 commit into
mainfrom
ci/run-full-tests-on-windows

Conversation

@zzet

@zzet zzet commented Aug 21, 2026

Copy link
Copy Markdown
Owner

Summary

  • add windows-latest to the existing full test matrix used by Linux and macOS
  • set fail-fast: false so every OS shard completes
  • remove the redundant selector-based build-windows job

Why

PR #647 showed that focused Windows test-name selectors can miss the exact regression they are meant to protect. Running go test -race -timeout=30m -coverprofile=coverage.out ./... on every supported OS makes the platform contract explicit and lets CI reveal portability failures directly.

macOS was already running the same full command as Linux; this change brings Windows into that matrix.

Validation

  • actionlint .github/workflows/ci.yml
  • go build -o <temp>/gortex ./cmd/gortex/
  • Gortex impact and contract checks: low risk, no violations
  • Gortex exact-diff review: APPROVE

The repository-wide local test was attempted in the shared checkout. Its only observed failure was TestNewIsFencedToIndexerStaging, caused by unrelated .claude/worktrees/* directories being scanned. GitHub clean runners are the authoritative result.

Draft status

The previous workflow deliberately avoided the full Windows suite, and recent Windows runs documented existing platform-assumption failures. This PR stays draft while the new shard enumerates those failures so they can be classified from concrete CI output rather than another guessed selector list.

@zzet
zzet marked this pull request as ready for review August 21, 2026 20:26
@tiendungdev

Copy link
Copy Markdown
Contributor

I have the enumeration this draft is waiting for — I ran the full suite on Windows locally while working on #641/#644/#645/#646/#647, so here it is up front rather than after a red shard.

windows/amd64, go1.26.6, -count=1, no -race.

package failures measured at
internal/mcp 27 c982cf52 (after #645)
internal/indexer 32 b8b13ca7
internal/analysis 4 b8b13ca7
internal/persistence 1 b8b13ca7
internal/graph/store_sqlite 5 pre-#641

store_sqlite and persistence are closed by #641/#644. Two of the four in analysis are closed by #644; the other two are not yours — see the environment section.

internal/indexer — 32

cluster n
TestLoadCompileCommands_* (clangd compile DB) 5
TestGDScript* 5
TestTSConfigPathAlias_* 3
symlink confinement (*SymlinkOutOfRepo*) 4
startup-barrier / marker permission fail-closed 2
unclustered 13

internal/mcp — 27

cluster n note
symlink / file-mode 5 SeCreateSymbolicLinkPrivilege, and Windows has no POSIX mode bits
review rulepack join 4 3 addressed by #646, 1 is a different cause
localization ranking (Swift ×3, Rust, C#, PHP) 6 not diagnosed
fidelity glob 1 #647
POSIX-spelled assertions ~4 e.g. TestPathToFileURI_Absolute wants file:///work/main.go, Windows resolves file:///D:/work/main.go
unclustered ~7

Two things that will bite the shard, and one is the same class as the failure you already hit

1. Some local Windows failures are the developer's environment, not the platform. Your note about .claude/worktrees/* and TestNewIsFencedToIndexerStaging is exactly this class. I hit my own: TestMapGitDiffRepoPrefixJoin and TestMapGitDiffFileChangeKinds fail here on TempDir RemoveAll cleanup: …\.git: The directory is not empty — because the git on my PATH is a non-stock fork that writes .git/ai/logs into every git init. Reproduced with a bare git init in an empty directory outside any repo. A clean runner with stock git will not show those two, so my analysis count of 4 should read as 2 for CI purposes.

Worth stating in the workflow or the contributing notes that the Windows shard is authoritative and a local red is suspect until reproduced there — the inverse of the usual advice, and non-obvious.

2. fail-fast: false plus a full suite will surface order-dependent flakes, and there is at least one. TestNotesManager_SaveQueryDelete passed in one full-package run and failed 3 of 3 when run in isolation on the same commit. I nearly credited a fix for it in #647 on the strength of a before/after count. Anyone reading the first red shard should diff failing test names against a baseline rather than counts, or a flake will read as a regression — and vice versa.

On removing the build-windows job

No objection from me, and the reasoning in #647 is fair — a selector list is a guess and mine did drift. One consequence worth deciding deliberately rather than inheriting: those steps also carried three non-test guarantees that the full-suite matrix does not reproduce — the CGO/tree-sitter build smoke test (go build ./... on native Windows), and the gortex.exe CLI build. If the full shard only runs go test, a Windows build break lands without a red until someone runs the suite. Cheap to keep as two steps in the new shard if you want that signal.

Happy to take any of the clusters above once the shard has enumerated them for real — the .NET/GDScript/TSConfig ones in particular look like one root cause each rather than per-test work.

tiendungdev added a commit to tiendungdev/gortex that referenced this pull request Aug 22, 2026
Addresses the review on zzet#646.

reviewChangedGraphPaths inferred "this path is already graph-keyed" from
strings.HasPrefix(f, repoPrefix+"/"). The two domains overlap, so that is
not recoverable by inspection: in a repo whose tree carries a top-level
directory named like the repo prefix, `repo-a/pkg/widget.go` is a valid
git-relative path AND a valid graph key for a different file. The
inference skips the real key `repo-a/repo-a/pkg/widget.go`, so the
changed file is never scanned - and when a same-named `pkg/widget.go`
exists, that unchanged shadow is scanned in its place.

Make the domain explicit and travel with the data:

  changedPathsRepoRelative  git's spelling, relative to the working tree
  changedPathsGraphKeyed    the graph's "<prefix>/<rel>" node key

All four production callers pass DiffResult.ChangedFiles, which MapGitDiff
documents as keeping "the diff-relative paths (callers re-join them with
git pathspecs)", so they pass changedPathsRepoRelative and the prefix is
now applied unconditionally. Only the test covering a caller that already
holds node keys passes changedPathsGraphKeyed.

The Norm-based join and output normalization from the first revision are
unchanged; this only replaces the inference.

Regression test: a fixture carrying both pkg/widget.go and
repo-a/pkg/widget.go, only the nested path marked changed, asserting every
match reports the changed target. Both files carry the detector fixture,
so a wrong-target scan still returns matches and only the reported path
separates the outcomes - restoring the HasPrefix inference fails it with
"pkg/widget.go" is the unchanged shadow.

The ci.yml selector hunk is dropped: zzet#652 removes that job outright.
@tiendungdev

Copy link
Copy Markdown
Contributor

Full classification, as promised. Measured on origin/main 699f351e, windows/amd64, go1.26.6, -count=1, no -race, clean worktree.

173 failing tests across 28 packages — but 2 of those are my machine, not the repo, so read the number as 171.

internal/analysis (TestMapGitDiff*) and internal/releases fail on TempDir RemoveAll: .git: The directory is not empty. I inspected the residue: the only thing left in every leftover directory is .git/ai/working_logs/initial, created by a git fork on my PATH. Not yours. I mention it because the same shape appears in bucket 4, where it is real.

Buckets

# Cluster n What it is
1 Test harness assumes a POSIX shell 24 fake-claude.sh / fake-codex.sh%1 is not a valid Win32 application (17); /bin/sh and /bin/cat spawns in internal/semantic/lsp (4); cannot signal this process on this platform (1); launchd-plist and XDG-env tests running on Windows (2)
2 os.SymlinkA required privilege is not held 12 every symlink-confinement test: internal/indexer (4), internal/mcp (4), internal/artifacts (2), internal/search/trigram (2)
3 POSIX file modes 5 file perm = 666, want 0600; should be executable (mode=-rw-rw-rw-)
4 Leaked handles → TempDir cleanup 5 sidecar.sqlite: The process cannot access the file because it is being used by another processcmd/gortex (3), internal/serverstack (2). Same family as #641
5 CRLF checkout 2 fixed in #655
6 Not yet attributable 7 + 1 pkg spinner glyphs, a GC-percent assertion, a nanosecond-equality assertion, internal/parser/crashpool rename race — plus internal/embedding, where TestNewLocalProvider_ReturnsWorkingProvider hung for the full 40m timeout inside ensureHugotModel. That is a model download; it may well be my network rather than Windows, and CI's -timeout=30m would be exceeded either way. Needs a clean-runner data point
7 Path domain — / vs \ ≈115 the remainder, and the actual work

Buckets 1–6 are individually enumerated (I have the test names). Bucket 7 is the residual, classified by first error line, so a handful may move as each is opened.

Bucket 7 is the same family as #646 and it reaches production code, not just fixtures. Confirmed samples:

  • internal/forge: hunk FilePath = "internal\forge\forge.go", want "internal/forge/forge.go" — a forge API path, native-spelled
  • internal/savings: EventsPathFor("/tmp/savings.json") = "\tmp\savings.jsonl"filepath where path belongs
  • internal/pathkey: foldPath returns \Users\me\project
  • internal/excludes: a pattern containing \ no longer matches

Three things I need from you

1. File modes (bucket 3) — a real decision, and one is security-shaped.

internal/daemon asserts servers.json is written 0600. On Windows that property genuinely does not exist: os.Chmod there only toggles the read-only bit, so the file lands 0666 no matter who runs it. This is not a test problem to skip past — it is a narrower confidentiality guarantee on Windows than on POSIX. Options: accept and document it, skip the assertion on Windows, or implement an ACL. Same question, lower stakes, for the emitted *.sh hook handlers' exec bit.

2. The launchd / XDG tests. TestServiceStatusReportsAnUnreadablePlist and TestXDGServiceEnv_OnlyAbsoluteSet exercise unix-only service integration and currently run on Windows. //go:build !windows rather than a skip, unless you want them asserting something Windows-specific?

3. Sequencing. The Windows leg cannot go green in one step — 171 failures across 26 packages is several PRs of work, and one PR carrying all of it would not be reviewable. Two ways to land it:

  • (a) merge this PR with the Windows leg marked continue-on-error: true, land fixes in tranches, flip it to blocking when it is green;
  • (b) hold this PR, land fixes guarded by the existing narrow selector step, merge this last and delete build-windows.

I would take (a). It reports real numbers from day one, it does not ask anyone to maintain a selector list you have already said can miss the regression it is meant to protect, and the flip to blocking becomes a one-line PR at the end.

I have not measured bucket 2 on your runner. GitHub's windows image may well hold SeCreateSymbolicLinkPrivilege, in which case those 12 need nothing at all — the first red run under (a) settles it, which is another reason to prefer it.

#655 is the first tranche and is independent of whichever you pick.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants