Go version

go version go1.24.1 windows/amd64

Output of go env in your module/workspace:

set AR=ar
set CC=gcc
set CGO_CFLAGS=-O2 -g
set CGO_CPPFLAGS=
set CGO_CXXFLAGS=-O2 -g
set CGO_ENABLED=1
set CGO_FFLAGS=-O2 -g
set CGO_LDFLAGS=-O2 -g
set CXX=g++
set GCCGO=gccgo
set GO111MODULE=
set GOAMD64=v1
set GOARCH=amd64
set GOAUTH=netrc
set GOBIN=
set GOCACHE=C:\Users\jabaile\AppData\Local\go-build
set GOCACHEPROG=
set GODEBUG=
set GOENV=C:\Users\jabaile\AppData\Roaming\go\env
set GOEXE=.exe
set GOEXPERIMENT=
set GOFIPS140=off
set GOFLAGS=
set GOGCCFLAGS=-m64 -mthreads -Wl,--no-gc-sections -fmessage-length=0 -ffile-prefix-map=C:\Users\jabaile\AppData\Local\Temp\go-build4138303354=/tmp/go-build -gno-record-gcc-switches
set GOHOSTARCH=amd64
set GOHOSTOS=windows
set GOINSECURE=
set GOMOD=D:\work\testfs-insensitive\go.mod
set GOMODCACHE=C:\Users\jabaile\go\pkg\mod
set GONOPROXY=
set GONOSUMDB=
set GOOS=windows
set GOPATH=C:\Users\jabaile\go
set GOPRIVATE=
set GOPROXY=https://proxy.golang.org,direct
set GOROOT=C:\Users\jabaile\scoop\apps\go\current
set GOSUMDB=sum.golang.org
set GOTELEMETRY=local
set GOTELEMETRYDIR=C:\Users\jabaile\AppData\Roaming\go\telemetry
set GOTMPDIR=
set GOTOOLCHAIN=auto
set GOTOOLDIR=C:\Users\jabaile\scoop\apps\go\current\pkg\tool\windows_amd64
set GOVCS=
set GOVERSION=go1.24.1
set GOWORK=
set PKG_CONFIG=pkg-config

What did you do?

func TestFS(t *testing.T) {
    dir, err := os.Getwd()
    if err != nil {
        t.Fatalf("failed to get current working directory: %v", err)
    }
    dir = filepath.Join(dir, "testdata")

    const testFile = "foo/bar/file.txt"
    const testFileUpper = "FOO/BAR/FILE.TXT"

    caseInsensitive := false
    if _, err := os.Stat(filepath.Join(dir, testFileUpper)); err == nil {
        caseInsensitive = true
    }

    fs := os.DirFS(dir)

    if err := fstest.TestFS(fs, testFile); err != nil {
        t.Fatalf("failed to test file system: %v", err)
    }

    if caseInsensitive {
        if err := fstest.TestFS(fs, testFileUpper); err != nil {
            t.Fatalf("failed to test file system case insensitively: %v", err)
        }
    } else {
        t.Logf("File system is case sensitive, skipping case insensitive test")
    }
}

https://github.com/jakebailey/go-testfs-insensitive

What did you see happen?

$ go test -v
=== RUN   TestFS
    fs_test.go:33: failed to test file system case insensitively: TestFS found errors:
        expected but not found: FOO/BAR/FILE.TXT
--- FAIL: TestFS (0.01s)
FAIL
exit status 1
FAIL    github.com/jakebailey/go-testfs-insensitive     1.623s

https://github.com/jakebailey/go-testfs-insensitive/actions/runs/14042320844 (this link will eventually expire, but it shows failure on Windows and macOS).

What did you expect to see?

A passing test.

For context, for the Go port of the TypeScript compiler, I implemented an testing/fstest.MapFS wrapper which implements case-insensitive path handling, which matches what platforms like Windows or macOS with case-insensitive file systems. TestFS works for exact matches, but when giving it the non-canonical path, it fails because it assumes that the paths roundtrip (which they don't on a real FS). See: https://github.com/microsoft/typescript-go/blob/19c84df406d80a94b638be52c360ff295d4748b2/internal/vfs/vfstest/vfstest_test.go#L77-L80

The provided test shows this just by using os.DirFS rather than my custom implementation, which shows the failure on Windows/macOS.

Comment From: seankhliao

I don't think there's anything to be done here. TestFS can't know about whether your filesystem should be case insensitive or not, and checking that the returned file matches the given path is a reasonable check.

Comment From: jakebailey

But this is actual behavior that comes out of os.DirFS, potentially even other FS implementations (HTTP is case-insensitive too). It doesn't seem great to be unable to test a class of paths with the standard io/fs.FS tester. It's very possible on Windows to end up with things like lowercase drive letters or other components of user-provided paths.

Comment From: seankhliao

Note that HTTP is case sensitive, otherwise you wouldn't be able to pass base64 data in URLs. While some software might ignore case in serving contents, HTTP URL paths are case sensitive (and also why there's things like rel=canonical).

As your code demonstrates, you can use TestFS to test canonical paths. If you want to test case insensitivity, you'll need another test, presumably to guarantee a case insensitive match even on case sensitive filesystems.