1// Copyright 2021 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5package fstest
6
7import (
8	"errors"
9	"internal/testenv"
10	"io/fs"
11	"os"
12	"path/filepath"
13	"slices"
14	"strings"
15	"testing"
16)
17
18func TestSymlink(t *testing.T) {
19	testenv.MustHaveSymlink(t)
20
21	tmp := t.TempDir()
22	tmpfs := os.DirFS(tmp)
23
24	if err := os.WriteFile(filepath.Join(tmp, "hello"), []byte("hello, world\n"), 0644); err != nil {
25		t.Fatal(err)
26	}
27
28	if err := os.Symlink(filepath.Join(tmp, "hello"), filepath.Join(tmp, "hello.link")); err != nil {
29		t.Fatal(err)
30	}
31
32	if err := TestFS(tmpfs, "hello", "hello.link"); err != nil {
33		t.Fatal(err)
34	}
35}
36
37func TestDash(t *testing.T) {
38	m := MapFS{
39		"a-b/a": {Data: []byte("a-b/a")},
40	}
41	if err := TestFS(m, "a-b/a"); err != nil {
42		t.Error(err)
43	}
44}
45
46type shuffledFS MapFS
47
48func (fsys shuffledFS) Open(name string) (fs.File, error) {
49	f, err := MapFS(fsys).Open(name)
50	if err != nil {
51		return nil, err
52	}
53	return &shuffledFile{File: f}, nil
54}
55
56type shuffledFile struct{ fs.File }
57
58func (f *shuffledFile) ReadDir(n int) ([]fs.DirEntry, error) {
59	dirents, err := f.File.(fs.ReadDirFile).ReadDir(n)
60	// Shuffle in a deterministic way, all we care about is making sure that the
61	// list of directory entries is not is the lexicographic order.
62	//
63	// We do this to make sure that the TestFS test suite is not affected by the
64	// order of directory entries.
65	slices.SortFunc(dirents, func(a, b fs.DirEntry) int {
66		return strings.Compare(b.Name(), a.Name())
67	})
68	return dirents, err
69}
70
71func TestShuffledFS(t *testing.T) {
72	fsys := shuffledFS{
73		"tmp/one":   {Data: []byte("1")},
74		"tmp/two":   {Data: []byte("2")},
75		"tmp/three": {Data: []byte("3")},
76	}
77	if err := TestFS(fsys, "tmp/one", "tmp/two", "tmp/three"); err != nil {
78		t.Error(err)
79	}
80}
81
82// failPermFS is a filesystem that always fails with fs.ErrPermission.
83type failPermFS struct{}
84
85func (f failPermFS) Open(name string) (fs.File, error) {
86	if !fs.ValidPath(name) {
87		return nil, &fs.PathError{Op: "open", Path: name, Err: fs.ErrInvalid}
88	}
89	return nil, &fs.PathError{Op: "open", Path: name, Err: fs.ErrPermission}
90}
91
92func TestTestFSWrappedErrors(t *testing.T) {
93	err := TestFS(failPermFS{})
94	if err == nil {
95		t.Fatal("error expected")
96	}
97	t.Logf("Error (expecting wrapped fs.ErrPermission):\n%v", err)
98
99	if !errors.Is(err, fs.ErrPermission) {
100		t.Errorf("error should be a wrapped ErrPermission: %#v", err)
101	}
102
103	// TestFS is expected to return a list of errors.
104	// Enforce that the list can be extracted for browsing.
105	var errs interface{ Unwrap() []error }
106	if !errors.As(err, &errs) {
107		t.Errorf("caller should be able to extract the errors as a list: %#v", err)
108	} else {
109		for _, err := range errs.Unwrap() {
110			// ErrPermission is expected
111			// but any other error must be reported.
112			if !errors.Is(err, fs.ErrPermission) {
113				t.Errorf("unexpected error: %v", err)
114			}
115		}
116	}
117}
118