testdirs: assert against real config/cache paths, not $HOME prefix · Entire
testdirs: assert against real config/cache paths, not $HOME prefix
d1a2dd0→main·
toothbrush·1mo ago·6 files·+32 added/-13 removed
Review feedback on #1411: the fallback uses os.MkdirTemp, which respects TMPDIR — and TMPDIR can legitimately live under $HOME. The invariant is "never the real ~/.config/entire or ~/.cache/entire", not "outside the home directory". Tighten the two regression tests to assert exactly that, and reword the doc comments to stop implying the fallback is guaranteed to be outside $HOME.
Co-Authored-By: Claude Fable 5 noreply@anthropic.com
Sessions
85db2d359086View transcript
[?
Fix Test Fixture Leaking Into User ConfigClaude Code·Fable 5.[1m]·6 steps](/content/gh/entireio/cli/session/688d98e0-f0a6-4240-acff-5f1100197cba#timeline-85db2d359086/index.html)
Changes
6
cmd/entire/cli/versioncheck
Mversioncheck.go+1/-1
internal
entireclient
contexts
Mcontexts.go+3/-3
Mcontexts_test.go+10/-4
discovery
Mcache.go+2/-1
testdirs
Mtestdirs.go+4/-1
Mtestdirs_test.go+12/-3
101 unmodified lines
102
103
104
105
105
106
107
108
101 unmodified lines
// globalConfigDirPath returns the CLI's global config directory:
// $ENTIRE_CONFIG_DIR if set, else ~/.config/entire. Under `go test` an
// unset ENTIRE_CONFIG_DIR resolves to a throwaway per-process directory
// instead of the real home (see internal/testdirs).
// instead of the real ~/.config/entire (see internal/testdirs).
func globalConfigDirPath() (string, error) {
if dir := os.Getenv("ENTIRE_CONFIG_DIR"); dir != "" {
return dir, nil
Mcmd/entire/cli/versioncheck/versioncheck.go+1/-1
66 unmodified lines
67
68
69
70
71
72
70
71
72
73
74
75
66 unmodified lines
// DefaultConfigDir is $ENTIRE_CONFIG_DIR if set, else ~/.config/entire.
// Under `go test` an unset ENTIRE_CONFIG_DIR resolves to a throwaway
// per-process directory instead of the real home, so a test that forgets to
// isolate can never read or pollute the developer's real config (see
// internal/testdirs).
// per-process directory instead of the real ~/.config/entire, so a test that
// forgets to isolate can never read or pollute the developer's real config
// (see internal/testdirs).
func DefaultConfigDir() string {
if dir := os.Getenv("ENTIRE_CONFIG_DIR"); dir != "" {
return dir
Minternal/entireclient/contexts/contexts.go+3/-3
292 unmodified lines
293
294
295
296
296
297
298
299
299
300
301
302
303
304
305
306
307
306
307
308
309
310
311
312
313
314
315
292 unmodified lines
}
}
func TestDefaultConfigDir_TestRunsNeverResolveRealHome(t *testing.T) {
func TestDefaultConfigDir_TestRunsNeverResolveRealConfigDir(t *testing.T) {
// With no explicit override, a `go test` process must fall back to a
// throwaway directory — never ~/.config/entire, where it could read or
// pollute the developer's real contexts.json.
// pollute the developer's real contexts.json. (The fallback lives under
// os.TempDir, which may itself be under $HOME via TMPDIR — that's fine;
// only the real config location is off-limits.)
t.Setenv("ENTIRE_CONFIG_DIR", "")
got := contexts.DefaultConfigDir()
if got == "" {
t.Fatal("DefaultConfigDir returned empty string")
}
home, err := os.UserHomeDir()
if err == nil && (got == home || strings.HasPrefix(got, home+string(os.PathSeparator))) {
t.Fatalf("DefaultConfigDir = %q resolves under the real home %q during tests", got, home)
}
if err != nil {
t.Skipf("no home dir: %v", err)
}
realDir := filepath.Join(home, ".config", "entire")
if got == realDir || strings.HasPrefix(got, realDir+string(os.PathSeparator)) {
t.Fatalf("DefaultConfigDir = %q resolves to the real config dir %q during tests", got, realDir)
}
}
Minternal/entireclient/contexts/contexts_test.go+10/-4
40 unmodified lines
41
42
43
44
44
45
46
47
48
40 unmodified lines
// DefaultCacheDir returns ~/.cache/entire, respecting XDG_CACHE_HOME.
// Under `go test` an unset XDG_CACHE_HOME resolves to a throwaway
// per-process directory instead of the real home (see internal/testdirs).
// per-process directory instead of the real ~/.cache/entire (see
// internal/testdirs).
func DefaultCacheDir() string {
if xdg := os.Getenv("XDG_CACHE_HOME"); xdg != "" {
return filepath.Join(xdg, "entire")
Minternal/entireclient/discovery/cache.go+2/-1
7 unmodified lines
8
9
10
11
11
12
13
14
15
16
17
7 unmodified lines
// ENTIRE_TOKEN_STORE, ...) would silently read and write the developer's
// real configuration — real auth contexts have been polluted with test
// fixtures this way. Dir gives the resolution functions a safe default under
// test: a throwaway directory under os.TempDir instead of the real home.
// test: a throwaway directory under os.TempDir, never the real config/cache
// locations. (os.TempDir respects TMPDIR and may itself live under $HOME;
// the guarantee is "not ~/.config/entire or ~/.cache/entire", not "outside
// the home directory".
//
// The fallback directory is per-process, not per-test: all tests in one test
// binary share it. Tests that need isolation from each other must still set
Minternal/testdirs/testdirs.go+4/-1
1 unmodified line
2
3
4
5
6
7
8
18 unmodified lines
27
28
29
30
31
32
33
30
31
32
34
35
36
37
38
39
40
41
42
43
44
1 unmodified line
import (
"os"
"path/filepath"
"strings"
"testing"
)
18 unmodified lines
t.Fatalf("surfaces must not share a directory: %q", cache)
// The invariant is "never the real config/cache locations" — not "never
// under $HOME": os.MkdirTemp respects TMPDIR, which may itself live under
// the home directory on some setups.
if home, err := os.UserHomeDir(); err == nil {
for _, d := range []string{cfg1, cache} {
if strings.HasPrefix(d, home+string(os.PathSeparator)) || d == home {
t.Fatalf("fallback dir %q resolves under the real home %q", d, home)
}
}
for _, realDir := range []string{
filepath.Join(home, ".config"),
filepath.Join(home, ".cache"),
} {
for _, d := range []string{cfg1, cache} {
if d == realDir || strings.HasPrefix(d, realDir+string(os.PathSeparator)) {
t.Fatalf("fallback dir %q resolves under the real %q", d, realDir)
}
}
}
}
Minternal/testdirs/testdirs_test.go+12/-3