test(tui): seal HOME for the /context baseline tests

`commands::debug_diagnostics_baseline_tests::context_routing_and_report_
branches_match_baseline` failed on any machine whose real home carries global
instructions or installed skills: `/context report` counts both, so the frozen
token totals moved (here ~/.codewhale/instructions.md is 126,359 bytes, past the
102,400-byte cap, and the user's skills load too). The fixture owned its
workspace and skills directory but not the user's home.

Fix: `SealedHome` (debug_diagnostics_test_support)
takes lock_test_env and pins HOME, USERPROFILE and CODEWHALE_HOME to empty
temp dirs for the test's life, the same seal the restore-route tests use
(81d9ec8dc). Applied to the two baseline tests that read the report.

Sibling found by the real-home vs empty-home comparison:
`context_alias_and_bare_action_are_preserved` compares `/ctx report` with
`/context report` byte for byte and failed 6 of 40 real-home runs (the skills
block moved between the two calls: 8103 vs 8589 tokens); 0 failures in 60
real-home and 10 empty-home runs after sealing. Nothing else in the module
differed.

Comparison runs (family filter `commands::debug_diagnostics`, 87 tests; the
real-home loop is read-only for this family, checked by file mtimes):
- before, baseline release binary: real home 86 passed 1 failed (the target);
  empty temp HOME+CODEWHALE_HOME 87 passed 0 failed.
- after: real home 60/60 runs 87 passed 0 failed; empty home 10/10 87 passed.
- `commands::debug_diagnostics_baseline_tests` 14 passed 0 failed.
Not run: the full suite, hosted CI, Windows (the seal sets USERPROFILE too).

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Signed-off-by: CodeWhale Bot <[email protected]>
This commit is contained in:
CodeWhale BotandClaude Sonnet 5.5 committed 2026-10-01 19:38:38 -07:00
1 parent bc37c1cbad
commit 1969203080
2 files changed
+48 -1

No files matched your search

@@ -14,7 +14,7 @@
use std::time::Instant;
use crate::commands::debug_diagnostics_test_support::{
DiagnosticsHarness, assert_fixture, normalize_cache_ages,
DiagnosticsHarness, SealedHome, assert_fixture, normalize_cache_ages,
};
use crate::commands::{CommandResult, execute};
use crate::config::ApiProvider;
@@ -237,6 +237,8 @@ fn system_alias_and_truncation_boundary_are_preserved() {
/// shared renderer; an unknown subcommand is an exact error with no action.
#[test]
fn context_routing_and_report_branches_match_baseline() {
// The report counts the user's global instructions and installed skills.
let _home = SealedHome::new();
let mut harness = DiagnosticsHarness::new();
assert_fixture(
"context_bare.txt",
@@ -265,6 +267,10 @@ fn context_routing_and_report_branches_match_baseline() {
/// must return the inspector action with no message.
#[test]
fn context_alias_and_bare_action_are_preserved() {
// The alias and canonical reports are compared byte for byte, and both
// enumerate the user's installed skills: on a developer machine their
// discovery can differ between two calls in one test.
let _home = SealedHome::new();
let mut harness = DiagnosticsHarness::new();
let bare = execute("/context", &mut harness.app);
assert!(bare.message.is_none());
@@ -19,8 +19,49 @@ use regex::Regex;
use tempfile::TempDir;
use crate::config::ApiProvider;
use crate::test_support::{EnvVarGuard, TestEnvLock, lock_test_env};
use crate::tui::app::{App, TuiOptions};
/// Seal `HOME`, `USERPROFILE` and `CODEWHALE_HOME` onto empty temporary
/// directories for the life of one test.
///
/// The diagnostics surface reads the *user's* state: global instructions
/// (`~/.codewhale/instructions.md`), installed skills, persisted settings. A
/// fixture that only owns its workspace and skills directory still lands
/// those reads on the developer's real home, so token totals and context
/// reports change with whoever runs the suite. Take this first in any test
/// whose output depends on them, before building a [`DiagnosticsHarness`]:
/// it holds the process-wide environment lock, which the harness's settings
/// loader re-enters. Keep one per test; the lock is not reentrant across
/// two seals.
pub(crate) struct SealedHome {
// Drop order matters: restore the environment, then delete the
// directories, then release the lock.
_codewhale_home: EnvVarGuard,
_userprofile: EnvVarGuard,
_home: EnvVarGuard,
_dir: TempDir,
_lock: TestEnvLock,
}
impl SealedHome {
pub(crate) fn new() -> Self {
let lock = lock_test_env();
let dir = TempDir::new().expect("sealed home tempdir");
let home = dir.path().join("home");
let codewhale_home = dir.path().join("codewhale-home");
std::fs::create_dir_all(&home).expect("sealed home dir");
std::fs::create_dir_all(&codewhale_home).expect("sealed codewhale home dir");
Self {
_home: EnvVarGuard::set("HOME", &home),
_userprofile: EnvVarGuard::set("USERPROFILE", &home),
_codewhale_home: EnvVarGuard::set("CODEWHALE_HOME", &codewhale_home),
_dir: dir,
_lock: lock,
}
}
}
/// Host-isolated fixture for the diagnostics command surface.
///
/// The hermetic test-home wrapper isolates persisted settings. The owned