memfs_test.gno
7.38 Kb · 279 lines
1package memfs
2
3import (
4 "testing"
5
6 ninep "gno.land/p/moul/x/plan9/ninep/v0"
7)
8
9func TestRootStat(t *testing.T) {
10 fs := New("glenda", 100)
11 s := fs.Root().Stat()
12 if s.Name != "/" {
13 t.Errorf("root name: got %q", s.Name)
14 }
15 if !s.IsDir() {
16 t.Error("root should be a directory")
17 }
18 if s.Length != 0 {
19 t.Errorf("a directory reports length 0 in 9P, got %d", s.Length)
20 }
21 if s.Uid != "glenda" || s.Gid != "glenda" {
22 t.Errorf("owner: got %q/%q", s.Uid, s.Gid)
23 }
24 if s.Mtime != 100 {
25 t.Errorf("mtime: got %d, want 100", s.Mtime)
26 }
27}
28
29func TestCreateWalkRead(t *testing.T) {
30 fs := New("glenda", 1)
31 root := fs.RootMutable()
32
33 f, err := root.Create("greeting", ninep.FilePerm, 2)
34 if err != nil {
35 t.Fatalf("create: %v", err)
36 }
37 m := f.(ninep.Mutable)
38 n, err := m.Write(0, "hello world", 3)
39 if err != nil {
40 t.Fatalf("write: %v", err)
41 }
42 if n != 11 {
43 t.Errorf("wrote %d bytes, want 11", n)
44 }
45
46 got, err := root.Walk("greeting")
47 if err != nil {
48 t.Fatalf("walk: %v", err)
49 }
50 data, err := ninep.ReadAll(got)
51 if err != nil {
52 t.Fatalf("read: %v", err)
53 }
54 if data != "hello world" {
55 t.Errorf("read back %q", data)
56 }
57 if l := got.Stat().Length; l != 11 {
58 t.Errorf("length %d, want 11", l)
59 }
60}
61
62func TestWalkErrors(t *testing.T) {
63 fs := New("glenda", 1)
64 root := fs.RootMutable()
65 root.Create("file", ninep.FilePerm, 1)
66
67 if _, err := root.Walk("absent"); err != ninep.ErrNotExist {
68 t.Errorf("missing name: got %v, want ErrNotExist", err)
69 }
70 if _, err := root.Walk("a/b"); err != ninep.ErrBadName {
71 t.Errorf("slash in name: got %v, want ErrBadName", err)
72 }
73 f, _ := root.Walk("file")
74 if _, err := f.Walk("x"); err != ninep.ErrNotDir {
75 t.Errorf("walk through a file: got %v, want ErrNotDir", err)
76 }
77 if _, err := f.ReadDir(); err != ninep.ErrNotDir {
78 t.Errorf("readdir of a file: got %v, want ErrNotDir", err)
79 }
80 if _, err := root.Read(0, -1); err != ninep.ErrIsDir {
81 t.Errorf("read of a directory: got %v, want ErrIsDir", err)
82 }
83}
84
85func TestReadDirIsOrdered(t *testing.T) {
86 fs := New("glenda", 1)
87 root := fs.RootMutable()
88 for _, name := range []string{"zulu", "alpha", "mike", "bravo"} {
89 if _, err := root.Create(name, ninep.FilePerm, 1); err != nil {
90 t.Fatalf("create %s: %v", name, err)
91 }
92 }
93 ents, err := root.ReadDir()
94 if err != nil {
95 t.Fatalf("readdir: %v", err)
96 }
97 want := []string{"alpha", "bravo", "mike", "zulu"}
98 if len(ents) != len(want) {
99 t.Fatalf("got %d entries, want %d", len(ents), len(want))
100 }
101 for i, w := range want {
102 if ents[i].Name != w {
103 t.Errorf("entry %d: got %q, want %q", i, ents[i].Name, w)
104 }
105 }
106}
107
108func TestCreateDuplicate(t *testing.T) {
109 fs := New("glenda", 1)
110 root := fs.RootMutable()
111 root.Create("x", ninep.FilePerm, 1)
112 if _, err := root.Create("x", ninep.FilePerm, 1); err != ninep.ErrExist {
113 t.Errorf("got %v, want ErrExist", err)
114 }
115 if _, err := root.Create("", ninep.FilePerm, 1); err != ninep.ErrBadName {
116 t.Errorf("empty name: got %v, want ErrBadName", err)
117 }
118 if _, err := root.Create("..", ninep.FilePerm, 1); err != ninep.ErrBadName {
119 t.Errorf("dotdot: got %v, want ErrBadName", err)
120 }
121}
122
123func TestRemove(t *testing.T) {
124 fs := New("glenda", 1)
125 root := fs.RootMutable()
126 root.Create("file", ninep.FilePerm, 1)
127 d, _ := root.Create("dir", ninep.DirPerm, 1)
128 d.(ninep.Mutable).Create("inner", ninep.FilePerm, 1)
129
130 if err := root.Remove("absent"); err != ninep.ErrNotExist {
131 t.Errorf("remove missing: got %v", err)
132 }
133 if err := root.Remove("dir"); err != ninep.ErrNotEmpty {
134 t.Errorf("remove non-empty dir: got %v, want ErrNotEmpty", err)
135 }
136 if err := d.(ninep.Mutable).Remove("inner"); err != nil {
137 t.Fatalf("remove inner: %v", err)
138 }
139 if err := root.Remove("dir"); err != nil {
140 t.Fatalf("remove emptied dir: %v", err)
141 }
142 if err := root.Remove("file"); err != nil {
143 t.Fatalf("remove file: %v", err)
144 }
145 ents, _ := root.ReadDir()
146 if len(ents) != 0 {
147 t.Errorf("root should be empty, has %d entries", len(ents))
148 }
149}
150
151func TestWriteOffsets(t *testing.T) {
152 tests := []struct {
153 name string
154 start string
155 off int64
156 data string
157 want string
158 }{
159 {"append at end", "abc", 3, "def", "abcdef"},
160 {"overwrite middle", "abcdef", 2, "XY", "abXYef"},
161 {"overwrite past end", "abc", 2, "XYZ", "abXYZ"},
162 {"from zero", "abcdef", 0, "ZZ", "ZZcdef"},
163 {"gap is nul filled", "ab", 4, "Z", "ab\x00\x00Z"},
164 {"negative offset clamps", "abc", -2, "Z", "Zbc"},
165 }
166 for _, tt := range tests {
167 fs := New("glenda", 1)
168 root := fs.RootMutable()
169 f, _ := root.Create("f", ninep.FilePerm, 1)
170 m := f.(ninep.Mutable)
171 m.Write(0, tt.start, 1)
172 m.Write(tt.off, tt.data, 2)
173 got, _ := ninep.ReadAll(f)
174 if got != tt.want {
175 t.Errorf("%s: got %q, want %q", tt.name, got, tt.want)
176 }
177 }
178}
179
180func TestAppendOnlyIgnoresOffset(t *testing.T) {
181 fs := New("glenda", 1)
182 root := fs.RootMutable()
183 f, _ := root.Create("log", ninep.DMAPPEND|0644, 1)
184 m := f.(ninep.Mutable)
185 m.Write(0, "one\n", 1)
186 m.Write(0, "two\n", 2) // offset 0, but append-only pins it to the end
187 got, _ := ninep.ReadAll(f)
188 if got != "one\ntwo\n" {
189 t.Errorf("got %q", got)
190 }
191}
192
193func TestTruncate(t *testing.T) {
194 fs := New("glenda", 1)
195 root := fs.RootMutable()
196 f, _ := root.Create("f", ninep.FilePerm, 1)
197 m := f.(ninep.Mutable)
198 m.Write(0, "abcdef", 1)
199
200 m.Truncate(3, 2)
201 if got, _ := ninep.ReadAll(f); got != "abc" {
202 t.Errorf("shrink: got %q", got)
203 }
204 m.Truncate(5, 3)
205 if got, _ := ninep.ReadAll(f); got != "abc\x00\x00" {
206 t.Errorf("grow: got %q", got)
207 }
208 m.Truncate(0, 4)
209 if got, _ := ninep.ReadAll(f); got != "" {
210 t.Errorf("zero: got %q", got)
211 }
212}
213
214func TestQidVersionAndPath(t *testing.T) {
215 fs := New("glenda", 1)
216 root := fs.RootMutable()
217 a, _ := root.Create("a", ninep.FilePerm, 1)
218 b, _ := root.Create("b", ninep.FilePerm, 1)
219
220 if a.Stat().Qid.Path == b.Stat().Qid.Path {
221 t.Error("two files share a qid path")
222 }
223 v0 := a.Stat().Qid.Version
224 a.(ninep.Mutable).Write(0, "x", 2)
225 if a.Stat().Qid.Version != v0+1 {
226 t.Errorf("version did not advance on write: %d -> %d", v0, a.Stat().Qid.Version)
227 }
228 if a.Stat().Mtime != 2 {
229 t.Errorf("mtime: got %d, want 2", a.Stat().Mtime)
230 }
231}
232
233func TestMkdirAllAndWriteFile(t *testing.T) {
234 fs := New("glenda", 1)
235 if _, err := fs.MkdirAll("/usr/glenda/lib", 1); err != nil {
236 t.Fatalf("mkdirall: %v", err)
237 }
238 // idempotent
239 if _, err := fs.MkdirAll("/usr/glenda", 1); err != nil {
240 t.Fatalf("mkdirall again: %v", err)
241 }
242 if err := fs.WriteFile("/usr/glenda/lib/profile", "font=pelm\n", 2); err != nil {
243 t.Fatalf("writefile: %v", err)
244 }
245 f, err := ninep.Walk(fs.Root(), ninep.Elems("/usr/glenda/lib/profile"))
246 if err != nil {
247 t.Fatalf("walk: %v", err)
248 }
249 if got, _ := ninep.ReadAll(f); got != "font=pelm\n" {
250 t.Errorf("got %q", got)
251 }
252 // rewriting replaces rather than appends
253 if err := fs.WriteFile("/usr/glenda/lib/profile", "font=lucida\n", 3); err != nil {
254 t.Fatalf("rewrite: %v", err)
255 }
256 f, _ = ninep.Walk(fs.Root(), ninep.Elems("/usr/glenda/lib/profile"))
257 if got, _ := ninep.ReadAll(f); got != "font=lucida\n" {
258 t.Errorf("rewrite got %q", got)
259 }
260}
261
262func TestMkdirAllThroughAFileFails(t *testing.T) {
263 fs := New("glenda", 1)
264 fs.WriteFile("/etc", "not a directory", 1)
265 if _, err := fs.MkdirAll("/etc/passwd", 2); err != ninep.ErrNotDir {
266 t.Errorf("got %v, want ErrNotDir", err)
267 }
268}
269
270func TestWalkDepthLimit(t *testing.T) {
271 fs := New("glenda", 1)
272 elems := []string{}
273 for i := 0; i <= ninep.MaxDepth; i++ {
274 elems = append(elems, "x")
275 }
276 if _, err := ninep.Walk(fs.Root(), elems); err != ninep.ErrTooDeep {
277 t.Errorf("got %v, want ErrTooDeep", err)
278 }
279}