Skip to content

Commit f338a62

Browse files
committed
fs: tests for defaultfs
1 parent 373ecce commit f338a62

File tree

1 file changed

+347
-0
lines changed

1 file changed

+347
-0
lines changed

fs/default_test.go

Lines changed: 347 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,347 @@
1+
package fs
2+
3+
import (
4+
"context"
5+
"io/fs"
6+
"testing"
7+
"time"
8+
)
9+
10+
// testFS is a filesystem that implements many interfaces.
11+
// It tracks which methods were called.
12+
type testFS struct {
13+
calls []string
14+
}
15+
16+
func (t *testFS) Open(name string) (fs.File, error) {
17+
t.calls = append(t.calls, "Open")
18+
return nil, ErrNotExist
19+
}
20+
21+
func (t *testFS) Mkdir(name string, perm FileMode) error {
22+
t.calls = append(t.calls, "Mkdir")
23+
return nil
24+
}
25+
26+
func (t *testFS) Remove(name string) error {
27+
t.calls = append(t.calls, "Remove")
28+
return nil
29+
}
30+
31+
func (t *testFS) Rename(oldname, newname string) error {
32+
t.calls = append(t.calls, "Rename")
33+
return nil
34+
}
35+
36+
func (t *testFS) Chmod(name string, mode FileMode) error {
37+
t.calls = append(t.calls, "Chmod")
38+
return nil
39+
}
40+
41+
func (t *testFS) Chown(name string, uid, gid int) error {
42+
t.calls = append(t.calls, "Chown")
43+
return nil
44+
}
45+
46+
func (t *testFS) Chtimes(name string, atime, mtime time.Time) error {
47+
t.calls = append(t.calls, "Chtimes")
48+
return nil
49+
}
50+
51+
func (t *testFS) Create(name string) (File, error) {
52+
t.calls = append(t.calls, "Create")
53+
return nil, nil
54+
}
55+
56+
func (t *testFS) Symlink(oldname, newname string) error {
57+
t.calls = append(t.calls, "Symlink")
58+
return nil
59+
}
60+
61+
func (t *testFS) Readlink(name string) (string, error) {
62+
t.calls = append(t.calls, "Readlink")
63+
return "", nil
64+
}
65+
66+
func (t *testFS) Truncate(name string, size int64) error {
67+
t.calls = append(t.calls, "Truncate")
68+
return nil
69+
}
70+
71+
func (t *testFS) OpenFile(name string, flag int, perm FileMode) (File, error) {
72+
t.calls = append(t.calls, "OpenFile")
73+
return nil, nil
74+
}
75+
76+
func (t *testFS) SetXattr(ctx context.Context, name, attr string, data []byte, flags int) error {
77+
t.calls = append(t.calls, "SetXattr")
78+
return nil
79+
}
80+
81+
func (t *testFS) GetXattr(ctx context.Context, name, attr string) ([]byte, error) {
82+
t.calls = append(t.calls, "GetXattr")
83+
return nil, nil
84+
}
85+
86+
func (t *testFS) ListXattrs(ctx context.Context, name string) ([]string, error) {
87+
t.calls = append(t.calls, "ListXattrs")
88+
return nil, nil
89+
}
90+
91+
func (t *testFS) RemoveXattr(ctx context.Context, name, attr string) error {
92+
t.calls = append(t.calls, "RemoveXattr")
93+
return nil
94+
}
95+
96+
// Verify testFS implements all these interfaces
97+
var (
98+
_ MkdirFS = (*testFS)(nil)
99+
_ RemoveFS = (*testFS)(nil)
100+
_ RenameFS = (*testFS)(nil)
101+
_ ChmodFS = (*testFS)(nil)
102+
_ ChownFS = (*testFS)(nil)
103+
_ ChtimesFS = (*testFS)(nil)
104+
_ CreateFS = (*testFS)(nil)
105+
_ SymlinkFS = (*testFS)(nil)
106+
_ ReadlinkFS = (*testFS)(nil)
107+
_ TruncateFS = (*testFS)(nil)
108+
_ OpenFileFS = (*testFS)(nil)
109+
_ XattrFS = (*testFS)(nil)
110+
)
111+
112+
// wrapperFS embeds DefaultFS and only overrides Open.
113+
// Without DefaultFS, type assertions for the embedded testFS interfaces would fail.
114+
type wrapperFS struct {
115+
*DefaultFS
116+
openCalled bool
117+
}
118+
119+
func (w *wrapperFS) Open(name string) (fs.File, error) {
120+
w.openCalled = true
121+
return w.DefaultFS.FS.Open(name)
122+
}
123+
124+
func TestDefaultFS_InterfacePassthrough(t *testing.T) {
125+
// Create a testFS that implements many interfaces
126+
original := &testFS{}
127+
128+
// Wrap with DefaultFS and embed in a new struct
129+
wrapped := &wrapperFS{
130+
DefaultFS: NewDefault(original),
131+
}
132+
133+
// Now use the fs package functions on the wrapper.
134+
// These should find the original testFS implementations via DefaultFS methods.
135+
136+
t.Run("Mkdir", func(t *testing.T) {
137+
original.calls = nil
138+
err := Mkdir(wrapped, "test", 0755)
139+
if err != nil {
140+
t.Errorf("unexpected error: %v", err)
141+
}
142+
if len(original.calls) != 1 || original.calls[0] != "Mkdir" {
143+
t.Errorf("expected Mkdir to be called on original, got: %v", original.calls)
144+
}
145+
})
146+
147+
t.Run("Remove", func(t *testing.T) {
148+
original.calls = nil
149+
err := Remove(wrapped, "test")
150+
if err != nil {
151+
t.Errorf("unexpected error: %v", err)
152+
}
153+
if len(original.calls) != 1 || original.calls[0] != "Remove" {
154+
t.Errorf("expected Remove to be called on original, got: %v", original.calls)
155+
}
156+
})
157+
158+
t.Run("Rename", func(t *testing.T) {
159+
original.calls = nil
160+
err := Rename(wrapped, "old", "new")
161+
if err != nil {
162+
t.Errorf("unexpected error: %v", err)
163+
}
164+
if len(original.calls) != 1 || original.calls[0] != "Rename" {
165+
t.Errorf("expected Rename to be called on original, got: %v", original.calls)
166+
}
167+
})
168+
169+
t.Run("Chmod", func(t *testing.T) {
170+
original.calls = nil
171+
err := Chmod(wrapped, "test", 0644)
172+
if err != nil {
173+
t.Errorf("unexpected error: %v", err)
174+
}
175+
if len(original.calls) != 1 || original.calls[0] != "Chmod" {
176+
t.Errorf("expected Chmod to be called on original, got: %v", original.calls)
177+
}
178+
})
179+
180+
t.Run("Chown", func(t *testing.T) {
181+
original.calls = nil
182+
err := Chown(wrapped, "test", 1000, 1000)
183+
if err != nil {
184+
t.Errorf("unexpected error: %v", err)
185+
}
186+
if len(original.calls) != 1 || original.calls[0] != "Chown" {
187+
t.Errorf("expected Chown to be called on original, got: %v", original.calls)
188+
}
189+
})
190+
191+
t.Run("Chtimes", func(t *testing.T) {
192+
original.calls = nil
193+
err := Chtimes(wrapped, "test", time.Now(), time.Now())
194+
if err != nil {
195+
t.Errorf("unexpected error: %v", err)
196+
}
197+
if len(original.calls) != 1 || original.calls[0] != "Chtimes" {
198+
t.Errorf("expected Chtimes to be called on original, got: %v", original.calls)
199+
}
200+
})
201+
202+
t.Run("Create", func(t *testing.T) {
203+
original.calls = nil
204+
_, err := Create(wrapped, "test")
205+
if err != nil {
206+
t.Errorf("unexpected error: %v", err)
207+
}
208+
if len(original.calls) != 1 || original.calls[0] != "Create" {
209+
t.Errorf("expected Create to be called on original, got: %v", original.calls)
210+
}
211+
})
212+
213+
t.Run("Symlink", func(t *testing.T) {
214+
original.calls = nil
215+
err := Symlink(wrapped, "target", "link")
216+
if err != nil {
217+
t.Errorf("unexpected error: %v", err)
218+
}
219+
if len(original.calls) != 1 || original.calls[0] != "Symlink" {
220+
t.Errorf("expected Symlink to be called on original, got: %v", original.calls)
221+
}
222+
})
223+
224+
t.Run("Readlink", func(t *testing.T) {
225+
original.calls = nil
226+
_, err := Readlink(wrapped, "link")
227+
if err != nil {
228+
t.Errorf("unexpected error: %v", err)
229+
}
230+
if len(original.calls) != 1 || original.calls[0] != "Readlink" {
231+
t.Errorf("expected Readlink to be called on original, got: %v", original.calls)
232+
}
233+
})
234+
235+
t.Run("Truncate", func(t *testing.T) {
236+
original.calls = nil
237+
err := Truncate(wrapped, "test", 100)
238+
if err != nil {
239+
t.Errorf("unexpected error: %v", err)
240+
}
241+
if len(original.calls) != 1 || original.calls[0] != "Truncate" {
242+
t.Errorf("expected Truncate to be called on original, got: %v", original.calls)
243+
}
244+
})
245+
246+
t.Run("OpenFile", func(t *testing.T) {
247+
original.calls = nil
248+
_, err := OpenFile(wrapped, "test", 0, 0)
249+
if err != nil {
250+
t.Errorf("unexpected error: %v", err)
251+
}
252+
if len(original.calls) != 1 || original.calls[0] != "OpenFile" {
253+
t.Errorf("expected OpenFile to be called on original, got: %v", original.calls)
254+
}
255+
})
256+
257+
t.Run("SetXattr", func(t *testing.T) {
258+
original.calls = nil
259+
err := SetXattr(context.Background(), wrapped, "test", "attr", []byte("data"), 0)
260+
if err != nil {
261+
t.Errorf("unexpected error: %v", err)
262+
}
263+
if len(original.calls) != 1 || original.calls[0] != "SetXattr" {
264+
t.Errorf("expected SetXattr to be called on original, got: %v", original.calls)
265+
}
266+
})
267+
268+
t.Run("GetXattr", func(t *testing.T) {
269+
original.calls = nil
270+
_, err := GetXattr(context.Background(), wrapped, "test", "attr")
271+
if err != nil {
272+
t.Errorf("unexpected error: %v", err)
273+
}
274+
if len(original.calls) != 1 || original.calls[0] != "GetXattr" {
275+
t.Errorf("expected GetXattr to be called on original, got: %v", original.calls)
276+
}
277+
})
278+
279+
t.Run("ListXattrs", func(t *testing.T) {
280+
original.calls = nil
281+
_, err := ListXattrs(context.Background(), wrapped, "test")
282+
if err != nil {
283+
t.Errorf("unexpected error: %v", err)
284+
}
285+
if len(original.calls) != 1 || original.calls[0] != "ListXattrs" {
286+
t.Errorf("expected ListXattrs to be called on original, got: %v", original.calls)
287+
}
288+
})
289+
290+
t.Run("RemoveXattr", func(t *testing.T) {
291+
original.calls = nil
292+
err := RemoveXattr(context.Background(), wrapped, "test", "attr")
293+
if err != nil {
294+
t.Errorf("unexpected error: %v", err)
295+
}
296+
if len(original.calls) != 1 || original.calls[0] != "RemoveXattr" {
297+
t.Errorf("expected RemoveXattr to be called on original, got: %v", original.calls)
298+
}
299+
})
300+
}
301+
302+
// shadowingWrapperFS embeds fs.FS (interface) instead of a concrete type.
303+
// This is a common pattern when you want to wrap any FS, not just a specific one.
304+
// Without DefaultFS, the wrapper won't implement any extended interfaces.
305+
type shadowingWrapperFS struct {
306+
fs.FS
307+
openCalled bool
308+
}
309+
310+
func (w *shadowingWrapperFS) Open(name string) (fs.File, error) {
311+
w.openCalled = true
312+
return w.FS.Open(name)
313+
}
314+
315+
// TestInterfaceEmbed_Problem demonstrates that when you embed fs.FS (the interface)
316+
// rather than a concrete type, you lose access to all the extended interfaces.
317+
// This is the problem DefaultFS solves.
318+
func TestInterfaceEmbed_Problem(t *testing.T) {
319+
original := &testFS{}
320+
321+
// Embed fs.FS interface directly - common pattern for generic wrappers
322+
direct := &shadowingWrapperFS{FS: original}
323+
324+
// Type assertions fail because direct only embeds fs.FS interface,
325+
// even though original implements MkdirFS
326+
if _, ok := (fs.FS)(direct).(MkdirFS); ok {
327+
t.Error("Interface embedding: MkdirFS assertion should fail")
328+
} else {
329+
t.Log("Interface embedding: MkdirFS assertion failed (this is the problem)")
330+
}
331+
332+
// Now wrap with DefaultFS - the wrapper gains all the interface implementations
333+
wrapped := &wrapperFS{DefaultFS: NewDefault(original)}
334+
335+
if _, ok := (fs.FS)(wrapped).(MkdirFS); !ok {
336+
t.Error("DefaultFS wrapper: MkdirFS assertion should succeed")
337+
} else {
338+
t.Log("DefaultFS wrapper: MkdirFS assertion succeeded (DefaultFS solved the problem)")
339+
}
340+
341+
// Verify that operations actually reach the original FS
342+
original.calls = nil
343+
Mkdir(wrapped, "test", 0755)
344+
if len(original.calls) != 1 || original.calls[0] != "Mkdir" {
345+
t.Errorf("expected Mkdir to reach original, got: %v", original.calls)
346+
}
347+
}

0 commit comments

Comments
 (0)