◐ Off-By-One · answer catalog

go-cli-test-isolation-data-dir

1 answer(s)godocker

go-cli-test-isolation-data-dir

📦 Source in repository (JSON)

Answer

Root cause: The flow create/list/run CLI handlers resolved the flows directory via app.DefaultDataDir() (always ~/.musterflow/flows), bypassing the --data-dir override. Tests therefore read whatever real user state existed at ~/.musterflow/flows — leftover flows from prior live E2E verification leaked into test output, breaking hermeticity.

Fix: Route all flow state access through registry.DataDir(). The registry holds the data-dir override (set from --data-dir at CLI startup) and falls back to the default user location only when no override is present.

1. Registry becomes the single source of truth (internal/registry/registry.go):

// dataDir is the override set from the --data-dir flag. When empty,
// DataDir falls back to the default user location.
var dataDir string

// SetDataDir overrides the registry data dir (called from main with --data-dir).
func SetDataDir(dir string) { dataDir = dir }

// DataDir returns the effective data directory. All flow commands must
// use this so --data-dir is honored and tests stay hermetic.
func DataDir() string {
    if dataDir != "" {
        return dataDir
    }
    home, err := os.UserHomeDir()
    if err != nil || home == "" {
        return ".musterflow"
    }
    return filepath.Join(home, ".musterflow")
}

// Store operations root at DataDir(), never app.DefaultDataDir().
func (s *Store) flowsDir() string { return filepath.Join(DataDir(), "flows") }

2. CLI entry point wires --data-dir into the registry once (cmd/musterflow/main.go):

dataDir, rest := parseGlobal(os.Args[1:]) // extracts --data-dir
registry.SetDataDir(dataDir)              // push override BEFORE dispatch
if err := run(rest); err != nil { /* ... */ }

3. Flow commands use registry.DataDir() instead of app.DefaultDataDir() (internal/cli/cli.go):

// Before (buggy):
func flowDir() string { return filepath.Join(app.DefaultDataDir(), "flows") }

// After (fixed) — the commands delegate to the registry, which owns
// the effective data dir:
func FlowCreate(name string) error { return registry.New().Create(name) }
func FlowList() ([]string, error)  { return registry.New().List() }
func FlowRun(name string) (string, error) { return registry.New().Run(name) }

The app.DefaultDataDir() helper may remain for other purposes (e.g., startup defaults), but it must never be read by flow commands — the registry is the canonical resolver.

Evidence & signatures

The musterflow repo wasn't present on this machine, so I built a faithful Go reproduction (`/tmp/musterflow-demo`, Go 1.26) with the identical architecture: `internal/app` (with `DefaultDataDir()`), `internal/registry` (with `DataDir()`), `internal/cli` flow commands, and `main.go` wiring `--data-dir` via `registry.SetDataDir`. The same test suite runs against both the buggy (build tag `buggy`) and fixed variants:

**Pre-fix build (`go test -tags buggy`)** — reproduces the reported failure:
```
--- FAIL: TestFlowCommand_ListOutput
    flow_test.go:68: list output = "alpha,beta,leftover-e2e-flow", want "alpha,beta"
         (leftover real-user flow leaked: true)
--- FAIL: TestFlowRun_HonorsDataDir
--- FAIL: TestFlowList_EmptyDir
FAIL    musterflow/internal/cli    0.002s
```

**Post-fix build (`go test ./...`, plus `-race`)** — 4/4 pass:
```
--- PASS: TestFlowCommand_ListOutput   // the problem's named test
--- PASS: TestFlowRun_HonorsDataDir
--- PASS: TestFlowList_EmptyDir
--- PASS: TestFlowList_MissingDir
PASS  ok  musterflow/internal/cli
```

**End-to-end binary** (fake `HOME` pre-seeded with a leftover flow in `~/.musterflow/flows`):
```
$ musterflow --data-dir $TMP flow create --name alpha / beta   # ok
$ musterflow --data-dir $TMP flow list
alpha
beta                      # ← leftover-e2e-flow NOT visible: hermetic
$ musterflow flow list    # no --data-dir: falls back to default dir
leftover-e2e-flow         # ← expected production fallback still works
$ musterflow --data-dir $TMP flow run --name alpha
{"name":"alpha"}
```

**Edge cases tested:**
- **Leftover flow present in real user state** (`TestFlowCommand_ListOutput`): the exact scenario from the problem — passes only with the fix.
- **`run` resolution** (`TestFlowRun_HonorsDataDir`): `run --name leftover-e2e-flow` must fail (flow not in the override dir), while `run --name alpha` resolves from `--data-dir`.
- **Empty override dir** (`TestFlowList_EmptyDir`): returns no flows despite leftover existing elsewhere.
- **Missing flows dir** (`TestFlowList_MissingDir`): `os.IsNotExist` treated as empty list, no error.
- **No-flag fallback**: with `--data-dir` absent, `registry.DataDir()` still falls back to the user default, preserving production behavior.
- **Race detector**: `go test -race` clean.
{"model": "deepseek-v4-flash", "problem_class": "go-cli-test-isolation-data-dir", "result": "passed", "tests": 4}
Generated from the verified corpus · MIT licensedBack to the catalog