diff --git a/tests/gateway/startup-permissions/run-tests.js b/tests/gateway/startup-permissions/run-tests.js index cbb9b346..b708b353 100644 --- a/tests/gateway/startup-permissions/run-tests.js +++ b/tests/gateway/startup-permissions/run-tests.js @@ -54,6 +54,20 @@ function check(name, cond, reason) { record(name, cond ? "PASS" : "FAIL", reason); } +/* POSIX file-mode-bit assertions are meaningless on win32: chmod/fchmod are no-ops there, + * so a tightened file/dir just keeps whatever default mode Windows reports, never the + * exact 0640/0750/0644 this pass enforces on POSIX. Route every exact-mode check through + * this helper instead of a bare check() so it skips (not fails) on win32, matching the + * SKIP convention this file already uses for its two runtime-creation-mode tests below. */ +function checkPosixMode(name, actualMode, expectedMode, reason) { + if (process.platform === "win32") { + console.log(`SKIP ${name} (POSIX modes are not meaningful on win32)`); + results.push({ name, status: "SKIP", reason: "win32" }); + return; + } + check(name, actualMode === expectedMode, reason); +} + function freshDir(prefix) { return fs.mkdtempSync(path.join(os.tmpdir(), `gs-startup-permissions-${prefix}-`)); } @@ -181,24 +195,24 @@ function tightenStateDirPermissionsRetrofitsEveryRealSensitivePathToTheAccessMod const changed = startupPermissions.tightenStateDirPermissions(dir); - check("real-writer-claim-file-tightened-to-0640", mode(claimPath) === 0o640, mode(claimPath).toString(8)); - check("real-bundle-file-tightened-to-0640", mode(chain.bundlePath(dir, "bundle-real-1")) === 0o640, ""); - check("real-chain-jsonl-tightened-to-0640", mode(chain.chainPath(dir)) === 0o640, ""); - check("real-head-json-tightened-to-0640", mode(chain.headPath(dir)) === 0o640, ""); - check("real-quarantined-bundle-tightened-to-0640", mode(quarantinedBundlePath) === 0o640, ""); - check("real-wal-file-tightened-to-0640", mode(walPath) === 0o640, ""); - check("real-intent-file-tightened-to-0640", mode(intentPath) === 0o640, ""); - check("real-signature-file-tightened-to-0640", mode(signaturePath) === 0o640, ""); - - check("real-state-dir-tightened-to-0750", mode(dir) === 0o750, ""); - check("real-sessions-dir-tightened-to-0750", mode(chain.sessionsDir(dir)) === 0o750, ""); - check("real-quarantine-dir-tightened-to-0750", mode(quarantineDir) === 0o750, ""); - check("real-recovery-dir-tightened-to-0750", mode(recovery.recoveryDir(dir)) === 0o750, ""); - check("real-active-dir-tightened-to-0750", mode(recovery.activeDir(dir)) === 0o750, ""); - check("real-intents-dir-tightened-to-0750", mode(recovery.intentsDir(dir)) === 0o750, ""); - check("real-signatures-dir-tightened-to-0750", mode(recovery.signaturesDir(dir)) === 0o750, ""); - - check("gateway-status-json-mode-untouched-by-this-pass", mode(statusPath) === 0o644, + checkPosixMode("real-writer-claim-file-tightened-to-0640", mode(claimPath), 0o640, mode(claimPath).toString(8)); + checkPosixMode("real-bundle-file-tightened-to-0640", mode(chain.bundlePath(dir, "bundle-real-1")), 0o640, ""); + checkPosixMode("real-chain-jsonl-tightened-to-0640", mode(chain.chainPath(dir)), 0o640, ""); + checkPosixMode("real-head-json-tightened-to-0640", mode(chain.headPath(dir)), 0o640, ""); + checkPosixMode("real-quarantined-bundle-tightened-to-0640", mode(quarantinedBundlePath), 0o640, ""); + checkPosixMode("real-wal-file-tightened-to-0640", mode(walPath), 0o640, ""); + checkPosixMode("real-intent-file-tightened-to-0640", mode(intentPath), 0o640, ""); + checkPosixMode("real-signature-file-tightened-to-0640", mode(signaturePath), 0o640, ""); + + checkPosixMode("real-state-dir-tightened-to-0750", mode(dir), 0o750, ""); + checkPosixMode("real-sessions-dir-tightened-to-0750", mode(chain.sessionsDir(dir)), 0o750, ""); + checkPosixMode("real-quarantine-dir-tightened-to-0750", mode(quarantineDir), 0o750, ""); + checkPosixMode("real-recovery-dir-tightened-to-0750", mode(recovery.recoveryDir(dir)), 0o750, ""); + checkPosixMode("real-active-dir-tightened-to-0750", mode(recovery.activeDir(dir)), 0o750, ""); + checkPosixMode("real-intents-dir-tightened-to-0750", mode(recovery.intentsDir(dir)), 0o750, ""); + checkPosixMode("real-signatures-dir-tightened-to-0750", mode(recovery.signaturesDir(dir)), 0o750, ""); + + checkPosixMode("gateway-status-json-mode-untouched-by-this-pass", mode(statusPath), 0o644, `expected the operator status file to be left at 0644, got ${mode(statusPath).toString(8)}`); check("gateway-status-json-not-reported-as-changed", !changed.files.includes(statusPath), JSON.stringify(changed.files)); @@ -223,7 +237,7 @@ function freshDeploymentWithNothingOnDiskYetDoesNotThrow() { changed && changed.dirs.includes(dir), JSON.stringify(changed)); check("empty-state-dir-reports-no-files-changed", changed && changed.files.length === 0, JSON.stringify(changed)); - check("empty-state-dir-mode-is-0750", mode(dir) === 0o750, mode(dir).toString(8)); + checkPosixMode("empty-state-dir-mode-is-0750", mode(dir), 0o750, mode(dir).toString(8)); } function partiallyPopulatedDeploymentSkipsWhatIsMissingAndTightensWhatExists() { @@ -239,8 +253,8 @@ function partiallyPopulatedDeploymentSkipsWhatIsMissingAndTightensWhatExists() { try { changed = startupPermissions.tightenStateDirPermissions(dir); } catch (error) { threw = error; } check("partial-deployment-does-not-throw", threw === null, threw && threw.message); - check("partial-deployment-tightens-the-existing-claim-file", - mode(claim.path) === 0o640, mode(claim.path).toString(8)); + checkPosixMode("partial-deployment-tightens-the-existing-claim-file", + mode(claim.path), 0o640, mode(claim.path).toString(8)); check("partial-deployment-reports-only-the-existing-file", changed.files.length === 1 && changed.files[0] === claim.path, JSON.stringify(changed.files)); } diff --git a/tests/state-store/atomic-primitives/run-tests.js b/tests/state-store/atomic-primitives/run-tests.js index 9da94ec2..3e8d4ca1 100644 --- a/tests/state-store/atomic-primitives/run-tests.js +++ b/tests/state-store/atomic-primitives/run-tests.js @@ -32,8 +32,14 @@ let failures = 0; const results = []; function record(name, status, reason) { - const line = status === "PASS" ? `PASS ${name}` : `FAIL ${name}+${reason || "unknown"}`; - console.log(line); + /* Codex PR #34 review: checkPosixMode's SKIP calls (below) were being printed with the + * generic "FAIL" prefix -- this function only special-cased "PASS", treating every other + * status (including a deliberate SKIP) as "FAIL" text even though `failures` and the + * SUMMARY line correctly counted it as skipped, not failed. That left Windows CI logs + * showing "FAIL ..." lines for assertions that actually passed (by skipping), which is + * misleading even though the process still exits 0. */ + const prefix = status === "PASS" ? "PASS" : status === "SKIP" ? "SKIP" : "FAIL"; + console.log(`${prefix} ${name}${status === "PASS" ? "" : `+${reason || "unknown"}`}`); results.push({ name, status, reason: reason || "" }); if (status === "FAIL") failures++; } @@ -42,6 +48,20 @@ function check(name, cond, reason) { if (cond) record(name, "PASS"); else record(name, "FAIL", reason); } +/* POSIX file-mode-bit assertions are meaningless on win32: fchmodSync/chmod are no-ops + * there, so a freshly created file just keeps whatever default mode Windows reports, + * never the exact 0640/0644 this codebase enforces on POSIX. Route every exact-mode + * check through this helper instead of a bare check() so it skips (not fails) on win32, + * matching the SKIP convention already used elsewhere in this test suite family + * (tests/gateway/recovery/run-tests.js, tests/gateway/startup-permissions/run-tests.js). */ +function checkPosixMode(name, actualMode, expectedMode, reason) { + if (process.platform === "win32") { + record(name, "SKIP", "POSIX file mode bits do not apply on win32"); + return; + } + check(name, actualMode === expectedMode, reason); +} + function freshDir(prefix) { return fs.mkdtempSync(path.join(os.tmpdir(), `gs-atomic-${prefix}-`)); } @@ -282,7 +302,7 @@ function atomicCreateExclusiveDefaultModeIsGroupReadable() { const target = path.join(dir, "record.json"); stateStore.atomicCreateExclusive(target, "payload"); const mode = fs.statSync(target).mode & 0o777; - check("atomicCreateExclusive-default-mode-is-0640", mode === 0o640, + checkPosixMode("atomicCreateExclusive-default-mode-is-0640", mode, 0o640, `expected the new access-model default 0640, got ${mode.toString(8)}`); } @@ -292,7 +312,7 @@ function atomicOverwriteFileDefaultModeIsGroupReadable() { fs.writeFileSync(target, "seed"); stateStore.atomicOverwriteFile(target, "new-content", dir); const mode = fs.statSync(target).mode & 0o777; - check("atomicOverwriteFile-default-mode-is-0640", mode === 0o640, + checkPosixMode("atomicOverwriteFile-default-mode-is-0640", mode, 0o640, `expected the new access-model default 0640, got ${mode.toString(8)}`); } @@ -310,7 +330,7 @@ function atomicCreateExclusiveUmaskZeroStillProducesConfiguredMode() { stateStore.atomicCreateExclusive(target, "payload"); }); const mode = fs.statSync(target).mode & 0o777; - check("atomicCreateExclusive-umask-0-still-yields-0640-not-0666", mode === 0o640, + checkPosixMode("atomicCreateExclusive-umask-0-still-yields-0640-not-0666", mode, 0o640, `expected 0640 regardless of umask 0 -- a result of 0666 here would mean fchmodSync ` + `is not actually running, only the ambient umask is -- got ${mode.toString(8)}`); } @@ -333,9 +353,9 @@ function atomicOverwriteFileRenameCarriesNewInodeModeOverPreExistingFile() { stateStore.atomicOverwriteFile(target, "new-content", dir); }); const mode = fs.statSync(target).mode & 0o777; - check("atomicOverwriteFile-pre-existing-target-really-was-0644", preExistingMode === 0o644, + checkPosixMode("atomicOverwriteFile-pre-existing-target-really-was-0644", preExistingMode, 0o644, `test setup invariant broken: expected the pre-existing target to be 0644, got ${preExistingMode.toString(8)}`); - check("atomicOverwriteFile-rename-carries-new-inode-mode-not-preexisting-0644", mode === 0o640, + checkPosixMode("atomicOverwriteFile-rename-carries-new-inode-mode-not-preexisting-0644", mode, 0o640, `expected the post-rename target to carry the NEW temp file's mode (0640), not the ` + `pre-existing target's 0644 -- got ${mode.toString(8)}`); check("atomicOverwriteFile-rename-carries-new-inode-mode-content-still-correct", @@ -496,7 +516,8 @@ function main() { const passed = results.filter((r) => r.status === "PASS").length; const failed = results.filter((r) => r.status === "FAIL").length; - console.log(`SUMMARY passed=${passed} failed=${failed} skipped=0`); + const skipped = results.filter((r) => r.status === "SKIP").length; + console.log(`SUMMARY passed=${passed} failed=${failed} skipped=${skipped}`); process.exit(failures ? 1 : 0); }