Skip to content

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.