1package test
2
3import (
4	"os"
5	"runtime"
6
7	. "gopkg.in/check.v1"
8	. "github.com/go-git/go-billy/v5"
9	"github.com/go-git/go-billy/v5/util"
10)
11
12// FilesystemSuite is a convenient test suite to validate any implementation of
13// billy.Filesystem
14type FilesystemSuite struct {
15	FS Filesystem
16
17	BasicSuite
18	DirSuite
19	SymlinkSuite
20	TempFileSuite
21	ChrootSuite
22}
23
24// NewFilesystemSuite returns a new FilesystemSuite based on the given fs.
25func NewFilesystemSuite(fs Filesystem) FilesystemSuite {
26	s := FilesystemSuite{FS: fs}
27	s.BasicSuite.FS = s.FS
28	s.DirSuite.FS = s.FS
29	s.SymlinkSuite.FS = s.FS
30	s.TempFileSuite.FS = s.FS
31	s.ChrootSuite.FS = s.FS
32
33	return s
34}
35
36func (s *FilesystemSuite) TestSymlinkToDir(c *C) {
37	if runtime.GOOS == "plan9" {
38		c.Skip("skipping on Plan 9; symlinks are not supported")
39	}
40	err := s.FS.MkdirAll("dir", 0755)
41	c.Assert(err, IsNil)
42
43	err = s.FS.Symlink("dir", "link")
44	c.Assert(err, IsNil)
45
46	fi, err := s.FS.Stat("link")
47	c.Assert(err, IsNil)
48	c.Assert(fi.Name(), Equals, "link")
49	c.Assert(fi.IsDir(), Equals, true)
50}
51
52func (s *FilesystemSuite) TestSymlinkReadDir(c *C) {
53	if runtime.GOOS == "plan9" {
54		c.Skip("skipping on Plan 9; symlinks are not supported")
55	}
56	err := util.WriteFile(s.FS, "dir/file", []byte("foo"), 0644)
57	c.Assert(err, IsNil)
58
59	err = s.FS.Symlink("dir", "link")
60	c.Assert(err, IsNil)
61
62	info, err := s.FS.ReadDir("link")
63	c.Assert(err, IsNil)
64	c.Assert(info, HasLen, 1)
65
66	c.Assert(info[0].Size(), Equals, int64(3))
67	c.Assert(info[0].IsDir(), Equals, false)
68	c.Assert(info[0].Name(), Equals, "file")
69}
70
71func (s *FilesystemSuite) TestCreateWithExistantDir(c *C) {
72	err := s.FS.MkdirAll("foo", 0644)
73	c.Assert(err, IsNil)
74
75	f, err := s.FS.Create("foo")
76	c.Assert(err, NotNil)
77	c.Assert(f, IsNil)
78}
79
80func (s *ChrootSuite) TestReadDirWithChroot(c *C) {
81	files := []string{"foo", "bar", "qux/baz", "qux/qux"}
82	for _, name := range files {
83		err := util.WriteFile(s.FS, name, nil, 0644)
84		c.Assert(err, IsNil)
85	}
86
87	qux, _ := s.FS.Chroot("/qux")
88
89	info, err := qux.(Filesystem).ReadDir("/")
90	c.Assert(err, IsNil)
91	c.Assert(info, HasLen, 2)
92}
93
94func (s *FilesystemSuite) TestSymlinkWithChrootBasic(c *C) {
95	if runtime.GOOS == "plan9" {
96		c.Skip("skipping on Plan 9; symlinks are not supported")
97	}
98	qux, _ := s.FS.Chroot("/qux")
99
100	err := util.WriteFile(qux, "file", nil, 0644)
101	c.Assert(err, IsNil)
102
103	err = qux.(Filesystem).Symlink("file", "link")
104	c.Assert(err, IsNil)
105
106	fi, err := qux.Stat("link")
107	c.Assert(err, IsNil)
108	c.Assert(fi.Name(), Equals, "link")
109
110	fi, err = s.FS.Stat("qux/link")
111	c.Assert(err, IsNil)
112	c.Assert(fi.Name(), Equals, "link")
113}
114
115func (s *FilesystemSuite) TestSymlinkWithChrootCrossBounders(c *C) {
116	if runtime.GOOS == "plan9" {
117		c.Skip("skipping on Plan 9; symlinks are not supported")
118	}
119	qux, _ := s.FS.Chroot("/qux")
120	util.WriteFile(s.FS, "file", []byte("foo"), customMode)
121
122	err := qux.Symlink("../../file", "qux/link")
123	c.Assert(err, Equals, nil)
124
125	fi, err := qux.Stat("qux/link")
126	c.Assert(fi, NotNil)
127	c.Assert(err, Equals, nil)
128}
129
130func (s *FilesystemSuite) TestReadDirWithLink(c *C) {
131	if runtime.GOOS == "plan9" {
132		c.Skip("skipping on Plan 9; symlinks are not supported")
133	}
134	util.WriteFile(s.FS, "foo/bar", []byte("foo"), customMode)
135	s.FS.Symlink("bar", "foo/qux")
136
137	info, err := s.FS.ReadDir("/foo")
138	c.Assert(err, IsNil)
139	c.Assert(info, HasLen, 2)
140}
141
142func (s *FilesystemSuite) TestRemoveAllNonExistent(c *C) {
143	c.Assert(util.RemoveAll(s.FS, "non-existent"), IsNil)
144}
145
146func (s *FilesystemSuite) TestRemoveAllEmptyDir(c *C) {
147	c.Assert(s.FS.MkdirAll("empty", os.FileMode(0755)), IsNil)
148	c.Assert(util.RemoveAll(s.FS, "empty"), IsNil)
149	_, err := s.FS.Stat("empty")
150	c.Assert(err, NotNil)
151	c.Assert(os.IsNotExist(err), Equals, true)
152}
153
154func (s *FilesystemSuite) TestRemoveAll(c *C) {
155	fnames := []string{
156		"foo/1",
157		"foo/2",
158		"foo/bar/1",
159		"foo/bar/2",
160		"foo/bar/baz/1",
161		"foo/bar/baz/qux/1",
162		"foo/bar/baz/qux/2",
163		"foo/bar/baz/qux/3",
164	}
165
166	for _, fname := range fnames {
167		err := util.WriteFile(s.FS, fname, nil, 0644)
168		c.Assert(err, IsNil)
169	}
170
171	c.Assert(util.RemoveAll(s.FS, "foo"), IsNil)
172
173	for _, fname := range fnames {
174		_, err := s.FS.Stat(fname)
175		comment := Commentf("not removed: %s %s", fname, err)
176		c.Assert(os.IsNotExist(err), Equals, true, comment)
177	}
178}
179
180func (s *FilesystemSuite) TestRemoveAllRelative(c *C) {
181	fnames := []string{
182		"foo/1",
183		"foo/2",
184		"foo/bar/1",
185		"foo/bar/2",
186		"foo/bar/baz/1",
187		"foo/bar/baz/qux/1",
188		"foo/bar/baz/qux/2",
189		"foo/bar/baz/qux/3",
190	}
191
192	for _, fname := range fnames {
193		err := util.WriteFile(s.FS, fname, nil, 0644)
194		c.Assert(err, IsNil)
195	}
196
197	c.Assert(util.RemoveAll(s.FS, "foo/bar/.."), IsNil)
198
199	for _, fname := range fnames {
200		_, err := s.FS.Stat(fname)
201		comment := Commentf("not removed: %s %s", fname, err)
202		c.Assert(os.IsNotExist(err), Equals, true, comment)
203	}
204}
205
206func (s *FilesystemSuite) TestReadDir(c *C) {
207	err := s.FS.MkdirAll("qux", 0755)
208	c.Assert(err, IsNil)
209
210	files := []string{"foo", "bar", "qux/baz", "qux/qux"}
211	for _, name := range files {
212		err := util.WriteFile(s.FS, name, nil, 0644)
213		c.Assert(err, IsNil)
214	}
215
216	info, err := s.FS.ReadDir("/")
217	c.Assert(err, IsNil)
218	c.Assert(info, HasLen, 3)
219
220	info, err = s.FS.ReadDir("/qux")
221	c.Assert(err, IsNil)
222	c.Assert(info, HasLen, 2)
223}
224