From 5db731a55e46af0c920b3be80ce7d2d8ac12e3d2 Mon Sep 17 00:00:00 2001 From: ruinivist Date: Sun, 22 Mar 2026 15:59:44 +0000 Subject: [PATCH] fix: relative mount paths to be joined to repo root --- internal/config/config.go | 29 ++++++++++++++++++- internal/config/config_test.go | 52 ++++++++++++++++++++++++++++------ internal/config/mire.toml | 1 + 3 files changed, 73 insertions(+), 9 deletions(-) diff --git a/internal/config/config.go b/internal/config/config.go index 6b66a0f..a85aab5 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -7,6 +7,7 @@ import ( "os" "path/filepath" "regexp" + "strings" "github.com/BurntSushi/toml" ) @@ -151,7 +152,33 @@ func validateSandbox(path string, sandbox map[string]string, mounts []string) (m return nil, nil, fmt.Errorf("failed to read %s: sandbox.home must be an absolute path", path) } - return validated, cloneMounts(mounts), nil + validatedMounts, err := normalizeMounts(path, mounts) + if err != nil { + return nil, nil, err + } + + return validated, validatedMounts, nil +} + +func normalizeMounts(configPath string, mounts []string) ([]string, error) { + baseDir := filepath.Dir(configPath) + normalized := make([]string, 0, len(mounts)) + for _, mount := range mounts { + hostPath, sandboxPath, ok := strings.Cut(mount, ":") + if !ok { + normalized = append(normalized, mount) + continue + } + if !filepath.IsAbs(hostPath) { + hostPath = filepath.Clean(filepath.Join(baseDir, hostPath)) + } + if _, err := os.Stat(hostPath); err != nil { + return nil, fmt.Errorf("failed to read %s: sandbox mount host path %q does not exist", configPath, hostPath) + } + normalized = append(normalized, hostPath+":"+sandboxPath) + } + + return normalized, nil } func cloneSandbox(sandbox map[string]string) map[string]string { diff --git a/internal/config/config_test.go b/internal/config/config_test.go index a915a1f..edbdc95 100644 --- a/internal/config/config_test.go +++ b/internal/config/config_test.go @@ -17,16 +17,26 @@ func TestReadConfig(t *testing.T) { wantMounts []string wantErr string wantMissing bool + setup func(t *testing.T, dir string) (string, []string) }{ { - name: "with test dir and sandbox", - content: "[mire]\ntest_dir = \"custom/suite\"\n\n[sandbox]\nhome = \"/home/test\"\nmounts = [\"/host/data:/sandbox/data\", \"/host/cache:/sandbox/cache\"]\nkey_word = \"value\"\n", + name: "with test dir and sandbox", + setup: func(t *testing.T, dir string) (string, []string) { + hostData := filepath.Join(dir, "host-data") + hostCache := filepath.Join(dir, "host-cache") + for _, path := range []string{hostData, hostCache} { + if err := os.MkdirAll(path, 0o755); err != nil { + t.Fatalf("MkdirAll(%q) error = %v", path, err) + } + } + return "[mire]\ntest_dir = \"custom/suite\"\n\n[sandbox]\nhome = \"/home/test\"\nmounts = [\"" + hostData + ":/sandbox/data\", \"" + hostCache + ":/sandbox/cache\"]\nkey_word = \"value\"\n", + []string{hostData + ":/sandbox/data", hostCache + ":/sandbox/cache"} + }, wantDir: "custom/suite", wantSandbox: map[string]string{ "home": "/home/test", "key_word": "value", }, - wantMounts: []string{"/host/data:/sandbox/data", "/host/cache:/sandbox/cache"}, }, { name: "legacy top level key", @@ -97,13 +107,39 @@ func TestReadConfig(t *testing.T) { name: "missing file", wantMissing: true, }, + { + name: "normalizes relative mount host path", + setup: func(t *testing.T, dir string) (string, []string) { + hostBuild := filepath.Join(dir, "build") + if err := os.MkdirAll(hostBuild, 0o755); err != nil { + t.Fatalf("MkdirAll(%q) error = %v", hostBuild, err) + } + return "[mire]\ntest_dir = \"custom/suite\"\n\n[sandbox]\nhome = \"/home/test\"\nmounts = [\"./build:/sandbox/build\"]\n", + []string{hostBuild + ":/sandbox/build"} + }, + wantDir: "custom/suite", + wantSandbox: map[string]string{ + "home": "/home/test", + }, + }, + { + name: "missing mount host path", + content: "[mire]\ntest_dir = \"custom/suite\"\n\n[sandbox]\nhome = \"/home/test\"\nmounts = [\"./missing:/sandbox/build\"]\n", + wantErr: "sandbox mount host path", + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - path := filepath.Join(t.TempDir(), "mire.toml") + dir := t.TempDir() + path := filepath.Join(dir, "mire.toml") + wantMounts := tt.wantMounts + content := tt.content + if tt.setup != nil { + content, wantMounts = tt.setup(t, dir) + } if !tt.wantMissing { - if err := os.WriteFile(path, []byte(tt.content), 0o644); err != nil { + if err := os.WriteFile(path, []byte(content), 0o644); err != nil { t.Fatalf("WriteFile() error = %v", err) } } @@ -138,10 +174,10 @@ func TestReadConfig(t *testing.T) { t.Fatalf("ReadConfig() Sandbox[%q] = %q, want %q", key, got.Sandbox[key], want) } } - if len(got.Mounts) != len(tt.wantMounts) { - t.Fatalf("ReadConfig() Mounts = %#v, want %#v", got.Mounts, tt.wantMounts) + if len(got.Mounts) != len(wantMounts) { + t.Fatalf("ReadConfig() Mounts = %#v, want %#v", got.Mounts, wantMounts) } - for i, want := range tt.wantMounts { + for i, want := range wantMounts { if got.Mounts[i] != want { t.Fatalf("ReadConfig() Mounts[%d] = %q, want %q", i, got.Mounts[i], want) } diff --git a/internal/config/mire.toml b/internal/config/mire.toml index 3f4e49f..aea22cc 100644 --- a/internal/config/mire.toml +++ b/internal/config/mire.toml @@ -6,4 +6,5 @@ # home is where mire would drop you by default on record home = "/home/test" # read only paths from host, entry looks like "path on host:path on sanbox" + # paths on host are absolute or relative to repo root mounts = []