Summary
The project already ships Windows binaries (release matrix + GOOS=windows cross-compile in CI) and includes Windows-specific implementations (internal/filelock, internal/daemonctl, internal/daemon, internal/childproc). Automated verification still happens only on Linux. This issue tracks making Windows a first-class CI target and cleaning up test code so go test ./... is reliable on windows/amd64 (and optionally windows/arm64).
Current state
| Area |
Today |
| CI unit tests |
ubuntu-latest only (.github/workflows/ci.yml → go-checks) |
| Windows in CI |
Cross-compile GOOS=windows GOARCH=amd64 go build ./... — compiles, does not execute tests on Windows |
| Integration tests |
//go:build integration; import golang.org/x/sys/unix (e.g. unix.Flock in integration_test.go) — will not build on Windows as-is |
| Unix-only unit tests |
daemonctl_unix_test.go, childproc_unix_test.go — correctly gated with //go:build !windows |
| Portable tests with Unix path literals |
Several tests assert paths like /tmp/hostmux.sock and /tmp/hostmux.toml (run_test.go, root_test.go, parts of internal/config/config_test.go, internal/tlsconfig/tlsconfig_test.go) — fragile or wrong on Windows |
Goals
- CI: Run the same quality gate (fmt check, vet, staticcheck, tests) on Windows runners so regressions in Windows-specific files are caught early.
- Tests: Prefer
t.TempDir(), filepath.Join, and explicit path expectations so default unit tests pass on Windows without duplicating entire files.
- Scope clarity: Keep integration tests explicitly Unix-oriented until someone invests in a Windows path (or split
unix-only helpers behind build tags).
Proposed plan
Phase 1 — Windows job in CI (minimal behavior change)
- Add a Windows job (recommend
windows-latest) that mirrors go-checks:
- Checkout,
actions/setup-go with go-version-file: go.mod.
- Formatting: same
gofmt -l . gate (use shell: bash on the Windows job so the existing bash snippet works unchanged, or provide an equivalent PowerShell check).
- Static analysis:
go vet ./..., go tool staticcheck ./....
- Tests:
go test ./... -count=1.
- Decide on architecture:
- Option A (simplest):
windows-amd64 only — matches most users and keeps minutes down.
- Option B: matrix
windows-latest × [amd64, arm64] if you want parity with release artifacts early (ARM64 Windows runners may differ; verify availability/cost).
- Wire
needs / concurrency so PRs stay fast (e.g. Windows job in parallel with Linux go-checks, both required for merge once stable).
Note: Until Phase 2 fixes land, the new job may fail — use a draft PR or allow-failure-style branch to land test fixes first, then make the Windows job required.
Phase 2 — Fix or adjust unit tests for Windows
- CLI / config tests that hardcode
/tmp/...:
- Replace with
t.TempDir()-backed paths and filepath.Join, or assert on normalized behavior (parsed path equals the input string you passed in) using a temp path that is valid on all platforms.
- Files called out by a quick audit:
run_test.go, root_test.go, internal/config/config_test.go (TOML with socket = "/tmp/x.sock"), internal/tlsconfig/tlsconfig_test.go (cert paths under /tmp).
- Subprocess / binary name: Any test that builds or runs
hostmux should use hostmux.exe under GOOS=windows when constructing paths (pattern already partially used with filepath.Join in integration_test.go for the output name — align all call sites).
- Line endings / shell: If any test assumes
\n vs \r\n for config or discovery files, normalize assertions.
Phase 3 — Windows-specific coverage (optional but valuable)
- Add small targeted tests that compile only on Windows (e.g.
*_windows_test.go) for behaviors that differ from Unix: detached process spawn, daemonctl, file locking edge cases — without duplicating large suites from *_unix_test.go.
- Consider running
go test -race on Windows for packages where race detection is supported and useful (optional; can be nightly if too slow).
Phase 4 — Integration tests on Windows (larger effort)
- Today,
integration_test.go is unsuitable for Windows because of golang.org/x/sys/unix usage.
- Options:
- Tag integration tests as Unix-only: e.g.
//go:build integration && unix (or !windows) and document that go test -tags=integration is supported on Linux/macOS only until Windows support exists.
- Refactor: Move
unix.Flock usage behind the existing internal/filelock abstraction (or a tiny test helper with //go:build files) so the main integration test file does not import unix on Windows.
- Windows E2E: After (2), validate AF_UNIX / named pipe / TCP assumptions against real Windows behavior (Go supports Unix domain sockets on modern Windows; confirm hostmux’s socket story matches what you want to test).
Recommendation: do (1) quickly for honesty in docs/CI; treat (2)+(3) as a follow-up epic if Windows E2E is a product requirement.
Phase 5 — Docs and contributor expectations
AGENTS.md: Add a short note that Windows CI runs go test ./... and that integration tests may remain Unix-only.
- Optionally document one-liners for local Windows verification (PowerShell vs Git Bash).
Acceptance criteria
Risks / notes
- CI time & cost: Adding Windows doubles cold CI time unless jobs are parallelized; tune with path filters or merge queue rules if needed.
staticcheck / Go toolchain: Keep go-version-file in sync so Windows and Linux use the same toolchain.
- Release job can stay cross-compiling from Ubuntu; native Windows CI is about test execution, not replacing release builds.
References (codebase)
- CI:
.github/workflows/ci.yml (go-checks, cross-compile step, release matrix).
- Windows implementations:
internal/filelock/filelock_windows.go, internal/daemonctl/daemonctl_windows.go, internal/daemon/spawn_windows.go, internal/childproc/childproc_windows.go.
- Unix-only tests:
internal/daemonctl/daemonctl_unix_test.go, internal/childproc/childproc_unix_test.go.
- Integration:
integration_test.go (//go:build integration, unix import).
If this plan looks right, suggested execution order is Phase 2 (test fixes) → Phase 1 (enable Windows CI) → Phase 5 (docs), with Phases 3–4 as follow-ups.
Summary
The project already ships Windows binaries (release matrix +
GOOS=windowscross-compile in CI) and includes Windows-specific implementations (internal/filelock,internal/daemonctl,internal/daemon,internal/childproc). Automated verification still happens only on Linux. This issue tracks making Windows a first-class CI target and cleaning up test code sogo test ./...is reliable onwindows/amd64(and optionallywindows/arm64).Current state
ubuntu-latestonly (.github/workflows/ci.yml→go-checks)GOOS=windows GOARCH=amd64 go build ./...— compiles, does not execute tests on Windows//go:build integration; importgolang.org/x/sys/unix(e.g.unix.Flockinintegration_test.go) — will not build on Windows as-isdaemonctl_unix_test.go,childproc_unix_test.go— correctly gated with//go:build !windows/tmp/hostmux.sockand/tmp/hostmux.toml(run_test.go,root_test.go, parts ofinternal/config/config_test.go,internal/tlsconfig/tlsconfig_test.go) — fragile or wrong on WindowsGoals
t.TempDir(),filepath.Join, and explicit path expectations so default unit tests pass on Windows without duplicating entire files.unix-only helpers behind build tags).Proposed plan
Phase 1 — Windows job in CI (minimal behavior change)
windows-latest) that mirrorsgo-checks:actions/setup-gowithgo-version-file: go.mod.gofmt -l .gate (useshell: bashon the Windows job so the existing bash snippet works unchanged, or provide an equivalent PowerShell check).go vet ./...,go tool staticcheck ./....go test ./... -count=1.windows-amd64only — matches most users and keeps minutes down.windows-latest×[amd64, arm64]if you want parity with release artifacts early (ARM64 Windows runners may differ; verify availability/cost).needs/ concurrency so PRs stay fast (e.g. Windows job in parallel with Linuxgo-checks, both required for merge once stable).Note: Until Phase 2 fixes land, the new job may fail — use a draft PR or allow-failure-style branch to land test fixes first, then make the Windows job required.
Phase 2 — Fix or adjust unit tests for Windows
/tmp/...:t.TempDir()-backed paths andfilepath.Join, or assert on normalized behavior (parsed path equals the input string you passed in) using a temp path that is valid on all platforms.run_test.go,root_test.go,internal/config/config_test.go(TOML withsocket = "/tmp/x.sock"),internal/tlsconfig/tlsconfig_test.go(cert paths under/tmp).hostmuxshould usehostmux.exeunderGOOS=windowswhen constructing paths (pattern already partially used withfilepath.Joininintegration_test.gofor the output name — align all call sites).\nvs\r\nfor config or discovery files, normalize assertions.Phase 3 — Windows-specific coverage (optional but valuable)
*_windows_test.go) for behaviors that differ from Unix: detached process spawn,daemonctl, file locking edge cases — without duplicating large suites from*_unix_test.go.go test -raceon Windows for packages where race detection is supported and useful (optional; can be nightly if too slow).Phase 4 — Integration tests on Windows (larger effort)
integration_test.gois unsuitable for Windows because ofgolang.org/x/sys/unixusage.//go:build integration && unix(or!windows) and document thatgo test -tags=integrationis supported on Linux/macOS only until Windows support exists.unix.Flockusage behind the existinginternal/filelockabstraction (or a tiny test helper with//go:buildfiles) so the main integration test file does not importunixon Windows.Recommendation: do (1) quickly for honesty in docs/CI; treat (2)+(3) as a follow-up epic if Windows E2E is a product requirement.
Phase 5 — Docs and contributor expectations
AGENTS.md: Add a short note that Windows CI runsgo test ./...and that integration tests may remain Unix-only.Acceptance criteria
gofmtcheck,go vet,staticcheck, andgo test ./...successfully on a supported Windows runner./tmp-style paths for assertions unless guarded for Windows.Risks / notes
staticcheck/ Go toolchain: Keepgo-version-filein sync so Windows and Linux use the same toolchain.References (codebase)
.github/workflows/ci.yml(go-checks, cross-compile step, release matrix).internal/filelock/filelock_windows.go,internal/daemonctl/daemonctl_windows.go,internal/daemon/spawn_windows.go,internal/childproc/childproc_windows.go.internal/daemonctl/daemonctl_unix_test.go,internal/childproc/childproc_unix_test.go.integration_test.go(//go:build integration,uniximport).If this plan looks right, suggested execution order is Phase 2 (test fixes) → Phase 1 (enable Windows CI) → Phase 5 (docs), with Phases 3–4 as follow-ups.