Skip to content

Commit b33994f

Browse files
committed
test(logic): put composite vfs under test
1 parent d7fbc5f commit b33994f

File tree

4 files changed

+289
-7
lines changed

4 files changed

+289
-7
lines changed

x/logic/fs/composite/fs.go

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package composite
22

33
import (
4+
"errors"
45
"fmt"
56
"io"
67
"io/fs"
@@ -10,8 +11,8 @@ import (
1011
"golang.org/x/exp/maps"
1112
)
1213

13-
type CopositeFS interface {
14-
fs.FS
14+
type FS interface {
15+
fs.ReadFileFS
1516

1617
// Mount mounts a filesystem to the given mount point.
1718
// The mount point is the scheme of the URI.
@@ -29,7 +30,7 @@ var (
2930
_ fs.ReadFileFS = (*vfs)(nil)
3031
)
3132

32-
func NewFS() CopositeFS {
33+
func NewFS() FS {
3334
return &vfs{mounted: make(map[string]fs.FS)}
3435
}
3536

@@ -58,18 +59,23 @@ func (f *vfs) ReadFile(name string) ([]byte, error) {
5859
}
5960

6061
if vfs, ok := vfs.(fs.ReadFileFS); ok {
61-
return vfs.ReadFile(name)
62+
content, err := vfs.ReadFile(name)
63+
if err != nil {
64+
return nil, &fs.PathError{Op: "readfile", Path: name, Err: getUnderlyingError(err)}
65+
}
66+
67+
return content, nil
6268
}
6369

6470
file, err := vfs.Open(name)
6571
if err != nil {
66-
return nil, err
72+
return nil, &fs.PathError{Op: "readfile", Path: name, Err: getUnderlyingError(err)}
6773
}
6874
defer file.Close()
6975

7076
content, err := io.ReadAll(file)
7177
if err != nil {
72-
return nil, err
78+
return nil, &fs.PathError{Op: "readfile", Path: name, Err: getUnderlyingError(err)}
7379
}
7480

7581
return content, nil
@@ -106,3 +112,13 @@ func (f *vfs) resolve(uri *url.URL) (fs.FS, error) {
106112
}
107113
return vfs, nil
108114
}
115+
116+
// getUnderlyingError returns the underlying error of a path error.
117+
// If it's not a path error it returns the error itself.
118+
func getUnderlyingError(err error) error {
119+
var pathErr *fs.PathError
120+
if errors.As(err, &pathErr) {
121+
return pathErr.Err
122+
}
123+
return err
124+
}

x/logic/fs/composite/fs_test.go

Lines changed: 265 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,265 @@
1+
package composite
2+
3+
import (
4+
"errors"
5+
"fmt"
6+
"io/fs"
7+
"sort"
8+
"testing"
9+
"time"
10+
11+
"github.com/golang/mock/gomock"
12+
13+
. "github.com/smartystreets/goconvey/convey"
14+
15+
"github.com/axone-protocol/axoned/v8/x/logic/fs/wasm"
16+
"github.com/axone-protocol/axoned/v8/x/logic/testutil"
17+
)
18+
19+
type fileSpec struct {
20+
name string
21+
content []byte
22+
modTime time.Time
23+
isCorrupted bool
24+
}
25+
26+
var (
27+
vfs1File1 = fileSpec{
28+
name: "vfs1:///file1",
29+
content: []byte("vfs1-file1"),
30+
modTime: time.Unix(1681389446, 0),
31+
}
32+
33+
vfs1File2 = fileSpec{
34+
name: "vfs1:///file2",
35+
content: []byte("vfs1-file2"),
36+
modTime: time.Unix(1681389446, 0),
37+
}
38+
39+
vfs2File1 = fileSpec{
40+
name: "vfs2:///file1",
41+
content: []byte("vfs1-file1"),
42+
modTime: time.Unix(1681389446, 0),
43+
isCorrupted: true,
44+
}
45+
46+
vfs2File2 = fileSpec{
47+
name: "vfs2:///file2",
48+
content: []byte("vfs1-file2"),
49+
modTime: time.Unix(1681389446, 0),
50+
}
51+
)
52+
53+
type fsType int
54+
55+
const (
56+
fsTypeFile = iota
57+
fsTypeReadFile
58+
)
59+
60+
//nolint:gocognit
61+
func TestFilteredVFS(t *testing.T) {
62+
Convey("Given test cases", t, func() {
63+
ctrl := gomock.NewController(t)
64+
defer ctrl.Finish()
65+
66+
cases := []struct {
67+
files map[string][]fileSpec
68+
file string
69+
fsType fsType
70+
wantError string
71+
want *fileSpec
72+
}{
73+
{
74+
files: map[string][]fileSpec{"vfs1": {vfs1File1, vfs1File2}},
75+
file: "vfs1:///file1",
76+
wantError: "",
77+
want: &vfs1File1,
78+
},
79+
{
80+
files: map[string][]fileSpec{"vfs1": {vfs1File1, vfs1File2}},
81+
file: "vfs1:///file1",
82+
fsType: fsTypeReadFile,
83+
wantError: "",
84+
want: &vfs1File1,
85+
},
86+
{
87+
files: map[string][]fileSpec{"vfs1": {vfs1File1, vfs1File2}, "vfs2": {vfs2File2}},
88+
file: "vfs2:///file2",
89+
wantError: "",
90+
want: &vfs2File2,
91+
},
92+
{
93+
files: map[string][]fileSpec{"vfs1": {vfs1File1, vfs1File2}},
94+
file: "vfs3:///file1",
95+
wantError: "vfs3:///file1: file does not exist",
96+
want: nil,
97+
},
98+
{
99+
files: map[string][]fileSpec{"vfs1": {vfs1File1, vfs1File2, vfs2File2}},
100+
file: "vfs3:///file1",
101+
wantError: "vfs3:///file1: file does not exist",
102+
want: nil,
103+
},
104+
{
105+
files: map[string][]fileSpec{},
106+
file: "% %",
107+
wantError: "% %: invalid argument",
108+
want: nil,
109+
},
110+
{
111+
files: map[string][]fileSpec{},
112+
file: "foo",
113+
wantError: "foo: invalid argument",
114+
want: nil,
115+
},
116+
{
117+
files: map[string][]fileSpec{"vfs2": {vfs2File1}},
118+
file: "vfs2:///file1",
119+
fsType: fsTypeReadFile,
120+
wantError: "vfs2:///file1: file is corrupted",
121+
want: nil,
122+
},
123+
{
124+
files: map[string][]fileSpec{"vfs2": {vfs2File1}},
125+
file: "vfs2:///file1",
126+
fsType: fsTypeFile,
127+
wantError: "vfs2:///file1: file is corrupted",
128+
want: nil,
129+
},
130+
}
131+
132+
for nc, tc := range cases {
133+
Convey(fmt.Sprintf("Given the test case #%d - file %s", nc, tc.file), func() {
134+
Convey("and a bunch of mocked file systems", func() {
135+
vfss := make(map[string]fs.FS)
136+
for k, files := range tc.files {
137+
switch tc.fsType {
138+
case fsTypeFile:
139+
vfs := testutil.NewMockFS(ctrl)
140+
for _, f := range files {
141+
registerFileToFS(vfs, f.name, f.content, f.modTime, f.isCorrupted)
142+
}
143+
vfss[k] = vfs
144+
case fsTypeReadFile:
145+
vfs := testutil.NewMockReadFileFS(ctrl)
146+
for _, f := range files {
147+
registerFileToReadFileFS(vfs, f.name, f.content, f.modTime, f.isCorrupted)
148+
}
149+
vfss[k] = vfs
150+
default:
151+
t.Error("Unsupported fs type")
152+
}
153+
}
154+
155+
Convey("and a composite file system under test", func() {
156+
compositeFS := NewFS()
157+
for mountPoint, vfs := range vfss {
158+
compositeFS.Mount(mountPoint, vfs)
159+
}
160+
161+
Convey(fmt.Sprintf(`when the open("%s") is called`, tc.file), func() {
162+
result, err := compositeFS.Open(tc.file)
163+
164+
Convey("then the result should be as expected", func() {
165+
if tc.wantError == "" {
166+
So(err, ShouldBeNil)
167+
168+
stat, _ := result.Stat()
169+
So(stat.Name(), ShouldEqual, tc.file)
170+
So(stat.Size(), ShouldEqual, int64(len(tc.want.content)))
171+
So(stat.ModTime(), ShouldEqual, tc.want.modTime)
172+
So(stat.IsDir(), ShouldBeFalse)
173+
} else {
174+
So(err, ShouldNotBeNil)
175+
So(err.Error(), ShouldEqual, fmt.Sprintf("open %s", tc.wantError))
176+
}
177+
})
178+
})
179+
180+
Convey(fmt.Sprintf(`when the readFile("%s") is called`, tc.file), func() {
181+
result, err := compositeFS.ReadFile(tc.file)
182+
183+
Convey("Then the result should be as expected", func() {
184+
if tc.wantError == "" {
185+
So(err, ShouldBeNil)
186+
So(result, ShouldResemble, tc.want.content)
187+
} else {
188+
So(err, ShouldNotBeNil)
189+
So(err.Error(), ShouldEqual, fmt.Sprintf("readfile %s", tc.wantError))
190+
}
191+
})
192+
})
193+
194+
Convey(`when the ListMounts() is called`, func() {
195+
result := compositeFS.ListMounts()
196+
197+
Convey("Then the result should be as expected", func() {
198+
vfssNames := make([]string, 0, len(vfss))
199+
for k := range vfss {
200+
vfssNames = append(vfssNames, k)
201+
}
202+
sort.Strings(vfssNames)
203+
So(result, ShouldResemble, vfssNames)
204+
})
205+
})
206+
})
207+
})
208+
})
209+
}
210+
})
211+
}
212+
213+
func registerFileToFS(vfs *testutil.MockFS, name string, content []byte, modTime time.Time, corrupted bool) {
214+
vfs.EXPECT().Open(name).AnyTimes().
215+
DoAndReturn(func(file string) (fs.File, error) {
216+
if corrupted {
217+
return nil, &fs.PathError{Op: "open", Path: name, Err: fmt.Errorf("file is corrupted")}
218+
}
219+
return wasm.NewVirtualFile(
220+
file,
221+
content,
222+
modTime), nil
223+
})
224+
}
225+
226+
func registerFileToReadFileFS(vfs *testutil.MockReadFileFS, name string, content []byte, modTime time.Time, corrupted bool) {
227+
vfs.EXPECT().Open(name).AnyTimes().
228+
DoAndReturn(func(file string) (fs.File, error) {
229+
if corrupted {
230+
return nil, &fs.PathError{Op: "open", Path: name, Err: fmt.Errorf("file is corrupted")}
231+
}
232+
return wasm.NewVirtualFile(
233+
file,
234+
content,
235+
modTime), nil
236+
})
237+
vfs.EXPECT().ReadFile(name).AnyTimes().
238+
DoAndReturn(func(_ string) ([]byte, error) {
239+
if corrupted {
240+
return nil, &fs.PathError{Op: "open", Path: name, Err: fmt.Errorf("file is corrupted")}
241+
}
242+
return content, nil
243+
})
244+
}
245+
246+
func TestGetUnderlyingError(t *testing.T) {
247+
Convey("Given an error", t, func() {
248+
Convey("when the error is fs.PathError", func() {
249+
underlyingErr := errors.New("underlying error")
250+
pathErr := &fs.PathError{Err: underlyingErr}
251+
252+
Convey("then getUnderlyingError should return the underlying error", func() {
253+
So(getUnderlyingError(pathErr), ShouldEqual, underlyingErr)
254+
})
255+
})
256+
257+
Convey("when the error is not fs.PathError", func() {
258+
genericErr := errors.New("generic error")
259+
260+
Convey("then getUnderlyingError should return the error itself", func() {
261+
So(getUnderlyingError(genericErr), ShouldEqual, genericErr)
262+
})
263+
})
264+
})
265+
}

x/logic/fs/filtered/fs_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ func TestFilteredVFS(t *testing.T) {
8484
time.Unix(1681389446, 0)), nil
8585
})
8686
mockedFS.EXPECT().ReadFile(tc.file).AnyTimes().
87-
DoAndReturn(func(file string) ([]byte, error) {
87+
DoAndReturn(func(_ string) ([]byte, error) {
8888
return content, nil
8989
})
9090
Convey("and a filtered file system under test", func() {

x/logic/fs/wasm/fs_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import (
2424
"github.com/axone-protocol/axoned/v8/x/logic/testutil"
2525
)
2626

27+
//nolint:gocognit
2728
func TestWasmVFS(t *testing.T) {
2829
ctrl := gomock.NewController(t)
2930
defer ctrl.Finish()

0 commit comments

Comments
 (0)