From a5b60ca594a3b94f425f23e00a0a36e9c89e9974 Mon Sep 17 00:00:00 2001 From: n30nex Date: Sat, 10 Oct 2026 15:20:40 -0400 Subject: [PATCH] test: make local validation independent of runner environment (#2160) Fixes #2159. Related to #1995. Standalone release tests failed when `GITHUB_REF_NAME` was absent and could use an unrelated runner tag when it was present. Supply the simulated tag explicitly; exercise the real release-notes step with both missing and unrelated host values, including present and absent notes files. Two filesystem fixtures now report unsupported Windows capabilities explicitly: POSIX permission bits, and symlink creation denied with error 1314. Unix mode checks and all symlink safety assertions remain intact; other symlink setup errors still fail. Production code and workflows are unchanged. This is 29 added lines across three test files. Validation: reproduced all three failures before fixing them; focused regressions, the full standalone frontend suite, and native Go server/ingestor suites pass. The POSIX permission and unprivileged Windows symlink fixtures are explicitly skipped on this host and remain enabled in Linux CI. The browser smoke suite passed 148 checks, with three fixture-dependent skips and two pre-existing disabled cases. No configuration or customizer changes. --- cmd/ingestor/stats_file_test.go | 6 ++++++ cmd/server/config_mode_test.go | 4 ++++ tests/unit/test-issue-1956-release-routing.js | 19 +++++++++++++++++++ 3 files changed, 29 insertions(+) diff --git a/cmd/ingestor/stats_file_test.go b/cmd/ingestor/stats_file_test.go index e96d56bd..59d59f0b 100644 --- a/cmd/ingestor/stats_file_test.go +++ b/cmd/ingestor/stats_file_test.go @@ -2,8 +2,11 @@ package main import ( "encoding/json" + "errors" "os" "path/filepath" + "runtime" + "syscall" "testing" "time" ) @@ -129,6 +132,9 @@ func TestWriteStatsAtomic_SymlinkAtDestIsReplaced(t *testing.T) { // Pre-plant a symlink at the destination path. path := filepath.Join(dir, "stats.json") if err := os.Symlink(target, path); err != nil { + if runtime.GOOS == "windows" && errors.Is(err, syscall.Errno(1314)) { + t.Skip("Windows account cannot create symlinks; this security fixture must run on Linux CI") + } t.Fatalf("symlink: %v", err) } diff --git a/cmd/server/config_mode_test.go b/cmd/server/config_mode_test.go index 601ef765..0abb6bfc 100644 --- a/cmd/server/config_mode_test.go +++ b/cmd/server/config_mode_test.go @@ -3,6 +3,7 @@ package main import ( "os" "path/filepath" + "runtime" "testing" ) @@ -10,6 +11,9 @@ import ( // API key and broker passwords, so the rewrite must keep whatever mode the // operator set rather than resetting it to 0644. func TestSaveGeoFilterPreservesFileMode(t *testing.T) { + if runtime.GOOS == "windows" { + t.Skip("Windows does not support POSIX file permission bits") + } dir := t.TempDir() path := filepath.Join(dir, "config.json") if err := os.WriteFile(path, []byte(`{"apiKey":"secret-key-that-is-long-enough"}`+"\n"), 0600); err != nil { diff --git a/tests/unit/test-issue-1956-release-routing.js b/tests/unit/test-issue-1956-release-routing.js index 89ee1738..bb520ee6 100644 --- a/tests/unit/test-issue-1956-release-routing.js +++ b/tests/unit/test-issue-1956-release-routing.js @@ -57,6 +57,8 @@ function runSteps(source, context, edge, mutateFails = false) { const log = path.join(dir, 'commands'); fs.writeFileSync(log, ''); fs.mkdirSync(path.join(dir, 'cmd/decrypt'), { recursive: true }); + fs.mkdirSync(path.join(dir, 'docs/release-notes'), { recursive: true }); + fs.writeFileSync(path.join(dir, 'docs/release-notes/v9.8.7.md'), 'Fixture release notes\n'); // go() only logs, so nothing real is produced; the release job's own // static/runnable verification step still needs these to exist. for (const arch of ['amd64', 'arm64']) { @@ -131,6 +133,7 @@ function runSteps(source, context, edge, mutateFails = false) { input: stubs + '\n' + expand(script, context), cwd: dir, encoding: 'utf8', timeout: 15000, env: { ...process.env, GITHUB_REF: context.github.ref, GITHUB_SHA: context.github.sha, + GITHUB_REF_NAME: context.github.ref_name, GITHUB_OUTPUT: bashPath(output), COMMAND_LOG: bashPath(log), TMPDIR: bashPath(dir), EDGE_CONFIG: edge === null ? 'missing' : JSON.stringify({ config: { Labels: { 'org.opencontainers.image.revision': edge } } }), // :edge is a two-platform index plus the two buildx attestation @@ -285,6 +288,22 @@ assert.equal(value(dispatchInput, 'type', 8), 'boolean', 'dispatch flag must ret assert.equal(value(dispatchInput, 'default', 8), 'false', 'manual and fallback dispatches must build images by default'); const release = block(deploy, 'release-artifacts', 2); +const notesStep = steps(release).find(step => value(step, 'id', 8) === 'notes'); +const inheritedRefName = process.env.GITHUB_REF_NAME; +try { + for (const hostRef of [undefined, 'unrelated-runner-branch']) { + if (hostRef === undefined) delete process.env.GITHUB_REF_NAME; + else process.env.GITHUB_REF_NAME = hostRef; + for (const [tag, expectedPath] of [['v9.8.7', 'docs/release-notes/v9.8.7.md'], ['v9.8.8', '']]) { + const ctx = context(`refs/tags/${tag}`); + runSteps(`steps:\n${notesStep}`, ctx, null); + assert.equal(ctx.steps.notes.outputs.path, expectedPath, 'release notes must use the simulated tag, independent of the host environment'); + } + } +} finally { + if (inheritedRefName === undefined) delete process.env.GITHUB_REF_NAME; + else process.env.GITHUB_REF_NAME = inheritedRefName; +} const builds = runSteps(release, context(), null).commands.filter(command => command[0] === 'go'); // CGO_ENABLED=1 since the SQLite driver became github.com/mattn/go-sqlite3, and // CC must be zig targeting musl — that is what makes the artifact static and