Test filesystem walks in memory¶
workspace never opens the real filesystem itself — it walks an
afero.Fs you hand it. In production that is
afero.NewOsFs(); in tests it is afero.NewMemMapFs(), so you can build an entire project
layout in memory and assert on the walk with no temp directories and no cleanup.
Build a layout and detect¶
func TestDetectsModuleRoot(t *testing.T) {
fs := afero.NewMemMapFs()
require.NoError(t, afero.WriteFile(fs, "/proj/go.mod", []byte("module x"), 0o644))
require.NoError(t, fs.MkdirAll("/proj/internal/pkg", 0o755))
ws, err := workspace.Detect(fs, "/proj/internal/pkg", workspace.DefaultMarkers)
require.NoError(t, err)
assert.Equal(t, "/proj", ws.Root)
assert.Equal(t, "go.mod", ws.Marker)
}
The marker is created two levels above the start directory; the walk climbs to it and reports the root and the marker that matched.
Assert the "not found" path¶
Because the memory filesystem contains only what you put there, the failure path is just as easy to exercise:
fs := afero.NewMemMapFs()
require.NoError(t, fs.MkdirAll("/nowhere/deep", 0o755))
_, err := workspace.Detect(fs, "/nowhere/deep", workspace.DefaultMarkers)
assert.ErrorIs(t, err, workspace.ErrNotFound)
Keep tests parallel-safe¶
An afero.NewMemMapFs() is independent per test, so detection tests can run with
t.Parallel() freely. DetectFromCWD reads the process working directory with
os.Getwd(), but the filesystem it queries is still the one you inject — a memory
filesystem seeded at that working directory keeps even the CWD variant hermetic:
cwd, _ := os.Getwd()
fs := afero.NewMemMapFs()
require.NoError(t, afero.WriteFile(fs, cwd+"/go.mod", []byte("module x"), 0o644))
ws, err := workspace.DetectFromCWD(fs, workspace.DefaultMarkers)
require.NoError(t, err)
assert.Equal(t, cwd, ws.Root)
Prefer Detect with an explicit start directory when you want a test that does not depend
on the working directory at all.
Always pass an absolute start directory in tests¶
A relative startDir is resolved with filepath.Abs, which joins it onto the process
working directory — the one the go test binary is running in, not anything inside your
memory filesystem. So Detect(fs, "sub", markers) against a MemMapFs looks for
/wherever/the/test/ran/sub, and a layout you seeded at /proj/sub is never visited.
Seed absolute paths and start from absolute paths, and the two filesystems stop interacting:
fs := afero.NewMemMapFs()
require.NoError(t, afero.WriteFile(fs, "/proj/go.mod", []byte("module x"), 0o644))
ws, err := workspace.Detect(fs, "/proj/internal", workspace.DefaultMarkers) // absolute
Do not assert that a bad start directory fails¶
It will not. Nothing stats startDir itself, so a path that does not exist in the memory
filesystem is simply a level with no markers, and the walk climbs past it to a parent that
may well match. A test written to prove "a nonexistent directory is rejected" will fail
because the package does not reject it — see
what happens when an argument is wrong.