diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index f9e6b40..32e5e58 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -186,4 +186,4 @@ jobs: - name: "verify used" run: | - go test -v ./testing/ci '-verify-used' + go test -v ./testing/ci '-verify-used' '-relative-open' diff --git a/testing/ci/ci_test.go b/testing/ci/ci_test.go index d86adfd..cf1b321 100644 --- a/testing/ci/ci_test.go +++ b/testing/ci/ci_test.go @@ -4,15 +4,48 @@ package ci import ( + "bytes" "encoding/json" "flag" "net/http" + "os" + "path/filepath" "testing" "github.com/tailscale/gomodfs" ) -var runVerifyUsed = flag.Bool("verify-used", false, "if set, runs TestVerifyUsed") +var ( + runVerifyUsed = flag.Bool("verify-used", false, "if set, runs TestVerifyUsed") + runRelativeOpen = flag.Bool("relative-open", false, "if set, runs TestRelativeOpen against the mounted $GOMODCACHE") +) + +// TestRelativeOpen opens files in the mounted module cache by paths relative +// to a working directory on the mount, as cmd/asm does with #include files. +// On Windows, WinFsp used to pass such opens to gomodfs with the working +// directory's path upcased, because gomodfs's volume claimed to be +// case-insensitive and not case-preserving. +func TestRelativeOpen(t *testing.T) { + if !*runRelativeOpen { + t.Skip("only runs in CI with --relative-open set") + } + modCache := os.Getenv("GOMODCACHE") + if modCache == "" { + t.Fatal("GOMODCACHE not set") + } + t.Chdir(filepath.Join(modCache, "golang.org", "x", "sys@v0.16.0")) + + b, err := os.ReadFile("go.mod") + if err != nil { + t.Fatalf("reading go.mod relative to the module dir: %v", err) + } + if !bytes.Contains(b, []byte("module golang.org/x/sys")) { + t.Errorf("go.mod contents = %q; want the golang.org/x/sys module", b) + } + if _, err := os.Stat("no-such-file"); !os.IsNotExist(err) { + t.Errorf("Stat of a missing relative path: err = %v; want not exist", err) + } +} func TestVerifyUsed(t *testing.T) { if !*runVerifyUsed { diff --git a/winfsp.go b/winfsp.go index fc35918..e2efe75 100644 --- a/winfsp.go +++ b/winfsp.go @@ -36,7 +36,13 @@ func (mfs *FS) MountWinFSP(mntPoint string) (MountRunner, error) { }, mntPoint, // e.g. "M:" winfsp.FileSystemName("gomodfs"), - winfsp.Attributes(winfsp.FspFSAttributeReadOnlyVolume), + // gomodfs paths are case-sensitive, like the GOMODCACHE layout it + // emulates (which escapes capitals as "!x"). Say so: on a volume + // that claims to be case-insensitive and not case-preserving, + // Windows passes relative opens to us with the working directory's + // path upcased, which we don't recognize. + winfsp.CaseSensitive(true), + winfsp.Attributes(winfsp.FspFSAttributeReadOnlyVolume|winfsp.FspFSAttributeCasePreservedNames), ) if err != nil { return nil, fmt.Errorf("winfsp.Mount: %w", err)