12483: More loading speed.
[arvados.git] / sdk / go / arvados / collection_fs_test.go
1 // Copyright (C) The Arvados Authors. All rights reserved.
2 //
3 // SPDX-License-Identifier: Apache-2.0
4
5 package arvados
6
7 import (
8         "bytes"
9         "crypto/md5"
10         "errors"
11         "fmt"
12         "io"
13         "io/ioutil"
14         "math/rand"
15         "net/http"
16         "os"
17         "regexp"
18         "runtime"
19         "sync"
20         "testing"
21         "time"
22
23         "git.curoverse.com/arvados.git/sdk/go/arvadostest"
24         check "gopkg.in/check.v1"
25 )
26
27 var _ = check.Suite(&CollectionFSSuite{})
28
29 type keepClientStub struct {
30         blocks map[string][]byte
31         sync.RWMutex
32 }
33
34 var errStub404 = errors.New("404 block not found")
35
36 func (kcs *keepClientStub) ReadAt(locator string, p []byte, off int) (int, error) {
37         kcs.RLock()
38         defer kcs.RUnlock()
39         buf := kcs.blocks[locator[:32]]
40         if buf == nil {
41                 return 0, errStub404
42         }
43         return copy(p, buf[off:]), nil
44 }
45
46 func (kcs *keepClientStub) PutB(p []byte) (string, int, error) {
47         locator := fmt.Sprintf("%x+%d+A12345@abcde", md5.Sum(p), len(p))
48         buf := make([]byte, len(p))
49         copy(buf, p)
50         kcs.Lock()
51         defer kcs.Unlock()
52         kcs.blocks[locator[:32]] = buf
53         return locator, 1, nil
54 }
55
56 type CollectionFSSuite struct {
57         client *Client
58         coll   Collection
59         fs     CollectionFileSystem
60         kc     keepClient
61 }
62
63 func (s *CollectionFSSuite) SetUpTest(c *check.C) {
64         s.client = NewClientFromEnv()
65         err := s.client.RequestAndDecode(&s.coll, "GET", "arvados/v1/collections/"+arvadostest.FooAndBarFilesInDirUUID, nil, nil)
66         c.Assert(err, check.IsNil)
67         s.kc = &keepClientStub{
68                 blocks: map[string][]byte{
69                         "3858f62230ac3c915f300c664312c63f": []byte("foobar"),
70                 }}
71         s.fs, err = s.coll.FileSystem(s.client, s.kc)
72         c.Assert(err, check.IsNil)
73 }
74
75 func (s *CollectionFSSuite) TestHttpFileSystemInterface(c *check.C) {
76         _, ok := s.fs.(http.FileSystem)
77         c.Check(ok, check.Equals, true)
78 }
79
80 func (s *CollectionFSSuite) TestReaddirFull(c *check.C) {
81         f, err := s.fs.Open("/dir1")
82         c.Assert(err, check.IsNil)
83
84         st, err := f.Stat()
85         c.Assert(err, check.IsNil)
86         c.Check(st.Size(), check.Equals, int64(2))
87         c.Check(st.IsDir(), check.Equals, true)
88
89         fis, err := f.Readdir(0)
90         c.Check(err, check.IsNil)
91         c.Check(len(fis), check.Equals, 2)
92         if len(fis) > 0 {
93                 c.Check(fis[0].Size(), check.Equals, int64(3))
94         }
95 }
96
97 func (s *CollectionFSSuite) TestReaddirLimited(c *check.C) {
98         f, err := s.fs.Open("./dir1")
99         c.Assert(err, check.IsNil)
100
101         fis, err := f.Readdir(1)
102         c.Check(err, check.IsNil)
103         c.Check(len(fis), check.Equals, 1)
104         if len(fis) > 0 {
105                 c.Check(fis[0].Size(), check.Equals, int64(3))
106         }
107
108         fis, err = f.Readdir(1)
109         c.Check(err, check.IsNil)
110         c.Check(len(fis), check.Equals, 1)
111         if len(fis) > 0 {
112                 c.Check(fis[0].Size(), check.Equals, int64(3))
113         }
114
115         fis, err = f.Readdir(1)
116         c.Check(len(fis), check.Equals, 0)
117         c.Check(err, check.NotNil)
118         c.Check(err, check.Equals, io.EOF)
119
120         f, err = s.fs.Open("dir1")
121         c.Assert(err, check.IsNil)
122         fis, err = f.Readdir(1)
123         c.Check(len(fis), check.Equals, 1)
124         c.Assert(err, check.IsNil)
125         fis, err = f.Readdir(2)
126         c.Check(len(fis), check.Equals, 1)
127         c.Assert(err, check.IsNil)
128         fis, err = f.Readdir(2)
129         c.Check(len(fis), check.Equals, 0)
130         c.Assert(err, check.Equals, io.EOF)
131 }
132
133 func (s *CollectionFSSuite) TestPathMunge(c *check.C) {
134         for _, path := range []string{".", "/", "./", "///", "/../", "/./.."} {
135                 f, err := s.fs.Open(path)
136                 c.Assert(err, check.IsNil)
137
138                 st, err := f.Stat()
139                 c.Assert(err, check.IsNil)
140                 c.Check(st.Size(), check.Equals, int64(1))
141                 c.Check(st.IsDir(), check.Equals, true)
142         }
143         for _, path := range []string{"/dir1", "dir1", "./dir1", "///dir1//.//", "../dir1/../dir1/"} {
144                 c.Logf("%q", path)
145                 f, err := s.fs.Open(path)
146                 c.Assert(err, check.IsNil)
147
148                 st, err := f.Stat()
149                 c.Assert(err, check.IsNil)
150                 c.Check(st.Size(), check.Equals, int64(2))
151                 c.Check(st.IsDir(), check.Equals, true)
152         }
153 }
154
155 func (s *CollectionFSSuite) TestNotExist(c *check.C) {
156         for _, path := range []string{"/no", "no", "./no", "n/o", "/n/o"} {
157                 f, err := s.fs.Open(path)
158                 c.Assert(f, check.IsNil)
159                 c.Assert(err, check.NotNil)
160                 c.Assert(os.IsNotExist(err), check.Equals, true)
161         }
162 }
163
164 func (s *CollectionFSSuite) TestReadOnlyFile(c *check.C) {
165         f, err := s.fs.OpenFile("/dir1/foo", os.O_RDONLY, 0)
166         c.Assert(err, check.IsNil)
167         st, err := f.Stat()
168         c.Assert(err, check.IsNil)
169         c.Check(st.Size(), check.Equals, int64(3))
170         n, err := f.Write([]byte("bar"))
171         c.Check(n, check.Equals, 0)
172         c.Check(err, check.Equals, ErrReadOnlyFile)
173 }
174
175 func (s *CollectionFSSuite) TestCreateFile(c *check.C) {
176         f, err := s.fs.OpenFile("/newfile", os.O_RDWR|os.O_CREATE, 0)
177         c.Assert(err, check.IsNil)
178         st, err := f.Stat()
179         c.Assert(err, check.IsNil)
180         c.Check(st.Size(), check.Equals, int64(0))
181
182         n, err := f.Write([]byte("bar"))
183         c.Check(n, check.Equals, 3)
184         c.Check(err, check.IsNil)
185
186         c.Check(f.Close(), check.IsNil)
187
188         f, err = s.fs.OpenFile("/newfile", os.O_RDWR|os.O_CREATE|os.O_EXCL, 0)
189         c.Check(f, check.IsNil)
190         c.Assert(err, check.NotNil)
191
192         f, err = s.fs.OpenFile("/newfile", os.O_RDWR, 0)
193         c.Assert(err, check.IsNil)
194         st, err = f.Stat()
195         c.Assert(err, check.IsNil)
196         c.Check(st.Size(), check.Equals, int64(3))
197
198         c.Check(f.Close(), check.IsNil)
199
200         // TODO: serialize to Collection, confirm manifest contents,
201         // make new FileSystem, confirm file contents.
202 }
203
204 func (s *CollectionFSSuite) TestReadWriteFile(c *check.C) {
205         maxBlockSize = 8
206         defer func() { maxBlockSize = 2 << 26 }()
207
208         f, err := s.fs.OpenFile("/dir1/foo", os.O_RDWR, 0)
209         c.Assert(err, check.IsNil)
210         defer f.Close()
211         st, err := f.Stat()
212         c.Assert(err, check.IsNil)
213         c.Check(st.Size(), check.Equals, int64(3))
214
215         f2, err := s.fs.OpenFile("/dir1/foo", os.O_RDWR, 0)
216         c.Assert(err, check.IsNil)
217         defer f2.Close()
218
219         buf := make([]byte, 64)
220         n, err := f.Read(buf)
221         c.Check(n, check.Equals, 3)
222         c.Check(err, check.Equals, io.EOF)
223         c.Check(string(buf[:3]), check.DeepEquals, "foo")
224
225         pos, err := f.Seek(-2, os.SEEK_CUR)
226         c.Check(pos, check.Equals, int64(1))
227         c.Check(err, check.IsNil)
228
229         // Split a storedExtent in two, and insert a memExtent
230         n, err = f.Write([]byte("*"))
231         c.Check(n, check.Equals, 1)
232         c.Check(err, check.IsNil)
233
234         pos, err = f.Seek(0, os.SEEK_CUR)
235         c.Check(pos, check.Equals, int64(2))
236         c.Check(err, check.IsNil)
237
238         pos, err = f.Seek(0, os.SEEK_SET)
239         c.Check(pos, check.Equals, int64(0))
240         c.Check(err, check.IsNil)
241
242         rbuf, err := ioutil.ReadAll(f)
243         c.Check(len(rbuf), check.Equals, 3)
244         c.Check(err, check.IsNil)
245         c.Check(string(rbuf), check.Equals, "f*o")
246
247         // Write multiple blocks in one call
248         f.Seek(1, os.SEEK_SET)
249         n, err = f.Write([]byte("0123456789abcdefg"))
250         c.Check(n, check.Equals, 17)
251         c.Check(err, check.IsNil)
252         pos, err = f.Seek(0, os.SEEK_CUR)
253         c.Check(pos, check.Equals, int64(18))
254         pos, err = f.Seek(-18, os.SEEK_CUR)
255         c.Check(err, check.IsNil)
256         n, err = io.ReadFull(f, buf)
257         c.Check(n, check.Equals, 18)
258         c.Check(err, check.Equals, io.ErrUnexpectedEOF)
259         c.Check(string(buf[:n]), check.Equals, "f0123456789abcdefg")
260
261         buf2, err := ioutil.ReadAll(f2)
262         c.Check(err, check.IsNil)
263         c.Check(string(buf2), check.Equals, "f0123456789abcdefg")
264
265         // truncate to current size
266         err = f.Truncate(18)
267         f2.Seek(0, os.SEEK_SET)
268         buf2, err = ioutil.ReadAll(f2)
269         c.Check(err, check.IsNil)
270         c.Check(string(buf2), check.Equals, "f0123456789abcdefg")
271
272         // shrink to zero some data
273         f.Truncate(15)
274         f2.Seek(0, os.SEEK_SET)
275         buf2, err = ioutil.ReadAll(f2)
276         c.Check(err, check.IsNil)
277         c.Check(string(buf2), check.Equals, "f0123456789abcd")
278
279         // grow to partial block/extent
280         f.Truncate(20)
281         f2.Seek(0, os.SEEK_SET)
282         buf2, err = ioutil.ReadAll(f2)
283         c.Check(err, check.IsNil)
284         c.Check(string(buf2), check.Equals, "f0123456789abcd\x00\x00\x00\x00\x00")
285
286         f.Truncate(0)
287         f2.Seek(0, os.SEEK_SET)
288         f2.Write([]byte("12345678abcdefghijkl"))
289
290         // grow to block/extent boundary
291         f.Truncate(64)
292         f2.Seek(0, os.SEEK_SET)
293         buf2, err = ioutil.ReadAll(f2)
294         c.Check(err, check.IsNil)
295         c.Check(len(buf2), check.Equals, 64)
296         c.Check(len(f.(*file).inode.(*filenode).extents), check.Equals, 8)
297
298         // shrink to block/extent boundary
299         err = f.Truncate(32)
300         f2.Seek(0, os.SEEK_SET)
301         buf2, err = ioutil.ReadAll(f2)
302         c.Check(err, check.IsNil)
303         c.Check(len(buf2), check.Equals, 32)
304         c.Check(len(f.(*file).inode.(*filenode).extents), check.Equals, 4)
305
306         // shrink to partial block/extent
307         err = f.Truncate(15)
308         f2.Seek(0, os.SEEK_SET)
309         buf2, err = ioutil.ReadAll(f2)
310         c.Check(err, check.IsNil)
311         c.Check(string(buf2), check.Equals, "12345678abcdefg")
312         c.Check(len(f.(*file).inode.(*filenode).extents), check.Equals, 2)
313
314         // Force flush to ensure the block "12345678" gets stored, so
315         // we know what to expect in the final manifest below.
316         _, err = s.fs.MarshalManifest(".")
317         c.Check(err, check.IsNil)
318
319         // Truncate to size=3 while f2's ptr is at 15
320         err = f.Truncate(3)
321         c.Check(err, check.IsNil)
322         buf2, err = ioutil.ReadAll(f2)
323         c.Check(err, check.IsNil)
324         c.Check(string(buf2), check.Equals, "")
325         f2.Seek(0, os.SEEK_SET)
326         buf2, err = ioutil.ReadAll(f2)
327         c.Check(err, check.IsNil)
328         c.Check(string(buf2), check.Equals, "123")
329         c.Check(len(f.(*file).inode.(*filenode).extents), check.Equals, 1)
330
331         m, err := s.fs.MarshalManifest(".")
332         c.Check(err, check.IsNil)
333         m = regexp.MustCompile(`\+A[^\+ ]+`).ReplaceAllLiteralString(m, "")
334         c.Check(m, check.Equals, "./dir1 3858f62230ac3c915f300c664312c63f+6 25d55ad283aa400af464c76d713c07ad+8 3:3:bar 6:3:foo\n")
335 }
336
337 func (s *CollectionFSSuite) TestSeekSparse(c *check.C) {
338         fs, err := (&Collection{}).FileSystem(s.client, s.kc)
339         c.Assert(err, check.IsNil)
340         f, err := fs.OpenFile("test", os.O_CREATE|os.O_RDWR, 0755)
341         c.Assert(err, check.IsNil)
342         defer f.Close()
343
344         checkSize := func(size int64) {
345                 fi, err := f.Stat()
346                 c.Check(fi.Size(), check.Equals, size)
347
348                 f, err := fs.OpenFile("test", os.O_CREATE|os.O_RDWR, 0755)
349                 c.Assert(err, check.IsNil)
350                 defer f.Close()
351                 fi, err = f.Stat()
352                 c.Check(fi.Size(), check.Equals, size)
353                 pos, err := f.Seek(0, os.SEEK_END)
354                 c.Check(pos, check.Equals, size)
355         }
356
357         f.Seek(2, os.SEEK_END)
358         checkSize(0)
359         f.Write([]byte{1})
360         checkSize(3)
361
362         f.Seek(2, os.SEEK_CUR)
363         checkSize(3)
364         f.Write([]byte{})
365         checkSize(5)
366
367         f.Seek(8, os.SEEK_SET)
368         checkSize(5)
369         n, err := f.Read(make([]byte, 1))
370         c.Check(n, check.Equals, 0)
371         c.Check(err, check.Equals, io.EOF)
372         checkSize(5)
373         f.Write([]byte{1, 2, 3})
374         checkSize(11)
375 }
376
377 func (s *CollectionFSSuite) TestMarshalSmallBlocks(c *check.C) {
378         maxBlockSize = 8
379         defer func() { maxBlockSize = 2 << 26 }()
380
381         var err error
382         s.fs, err = (&Collection{}).FileSystem(s.client, s.kc)
383         c.Assert(err, check.IsNil)
384         for _, name := range []string{"foo", "bar", "baz"} {
385                 f, err := s.fs.OpenFile(name, os.O_WRONLY|os.O_CREATE, 0)
386                 c.Assert(err, check.IsNil)
387                 f.Write([]byte(name))
388                 f.Close()
389         }
390
391         m, err := s.fs.MarshalManifest(".")
392         c.Check(err, check.IsNil)
393         m = regexp.MustCompile(`\+A[^\+ ]+`).ReplaceAllLiteralString(m, "")
394         c.Check(m, check.Equals, ". c3c23db5285662ef7172373df0003206+6 acbd18db4cc2f85cedef654fccc4a4d8+3 0:3:bar 3:3:baz 6:3:foo\n")
395 }
396
397 func (s *CollectionFSSuite) TestMkdir(c *check.C) {
398         err := s.fs.Mkdir("foo/bar", 0755)
399         c.Check(err, check.Equals, os.ErrNotExist)
400
401         f, err := s.fs.OpenFile("foo/bar", os.O_CREATE, 0)
402         c.Check(err, check.Equals, os.ErrNotExist)
403
404         err = s.fs.Mkdir("foo", 0755)
405         c.Check(err, check.IsNil)
406
407         f, err = s.fs.OpenFile("foo/bar", os.O_CREATE|os.O_WRONLY, 0)
408         c.Check(err, check.IsNil)
409         if err == nil {
410                 defer f.Close()
411                 f.Write([]byte("foo"))
412         }
413
414         // mkdir fails if a file already exists with that name
415         err = s.fs.Mkdir("foo/bar", 0755)
416         c.Check(err, check.NotNil)
417
418         err = s.fs.Remove("foo/bar")
419         c.Check(err, check.IsNil)
420
421         // mkdir succeds after the file is deleted
422         err = s.fs.Mkdir("foo/bar", 0755)
423         c.Check(err, check.IsNil)
424
425         // creating a file in a nonexistent subdir should still fail
426         f, err = s.fs.OpenFile("foo/bar/baz/foo.txt", os.O_CREATE|os.O_WRONLY, 0)
427         c.Check(err, check.Equals, os.ErrNotExist)
428
429         f, err = s.fs.OpenFile("foo/bar/foo.txt", os.O_CREATE|os.O_WRONLY, 0)
430         c.Check(err, check.IsNil)
431         if err == nil {
432                 defer f.Close()
433                 f.Write([]byte("foo"))
434         }
435
436         // creating foo/bar as a regular file should fail
437         f, err = s.fs.OpenFile("foo/bar", os.O_CREATE|os.O_EXCL, 0)
438         c.Check(err, check.NotNil)
439
440         // creating foo/bar as a directory should fail
441         f, err = s.fs.OpenFile("foo/bar", os.O_CREATE|os.O_EXCL, os.ModeDir)
442         c.Check(err, check.NotNil)
443         err = s.fs.Mkdir("foo/bar", 0755)
444         c.Check(err, check.NotNil)
445
446         m, err := s.fs.MarshalManifest(".")
447         c.Check(err, check.IsNil)
448         m = regexp.MustCompile(`\+A[^\+ ]+`).ReplaceAllLiteralString(m, "")
449         c.Check(m, check.Equals, "./dir1 3858f62230ac3c915f300c664312c63f+6 3:3:bar 0:3:foo\n./foo/bar acbd18db4cc2f85cedef654fccc4a4d8+3 0:3:foo.txt\n")
450 }
451
452 func (s *CollectionFSSuite) TestConcurrentWriters(c *check.C) {
453         maxBlockSize = 8
454         defer func() { maxBlockSize = 2 << 26 }()
455
456         var wg sync.WaitGroup
457         for n := 0; n < 128; n++ {
458                 wg.Add(1)
459                 go func() {
460                         defer wg.Done()
461                         f, err := s.fs.OpenFile("/dir1/foo", os.O_RDWR, 0)
462                         c.Assert(err, check.IsNil)
463                         defer f.Close()
464                         for i := 0; i < 6502; i++ {
465                                 switch rand.Int() & 3 {
466                                 case 0:
467                                         f.Truncate(int64(rand.Intn(64)))
468                                 case 1:
469                                         f.Seek(int64(rand.Intn(64)), os.SEEK_SET)
470                                 case 2:
471                                         _, err := f.Write([]byte("beep boop"))
472                                         c.Check(err, check.IsNil)
473                                 case 3:
474                                         _, err := ioutil.ReadAll(f)
475                                         c.Check(err, check.IsNil)
476                                 }
477                         }
478                 }()
479         }
480         wg.Wait()
481
482         f, err := s.fs.OpenFile("/dir1/foo", os.O_RDWR, 0)
483         c.Assert(err, check.IsNil)
484         defer f.Close()
485         buf, err := ioutil.ReadAll(f)
486         c.Check(err, check.IsNil)
487         c.Logf("after lots of random r/w/seek/trunc, buf is %q", buf)
488 }
489
490 func (s *CollectionFSSuite) TestRandomWrites(c *check.C) {
491         maxBlockSize = 40
492         defer func() { maxBlockSize = 2 << 26 }()
493
494         var err error
495         s.fs, err = (&Collection{}).FileSystem(s.client, s.kc)
496         c.Assert(err, check.IsNil)
497
498         const nfiles = 256
499         const ngoroutines = 256
500
501         var wg sync.WaitGroup
502         for n := 0; n < nfiles; n++ {
503                 wg.Add(1)
504                 go func(n int) {
505                         defer wg.Done()
506                         expect := make([]byte, 0, 64)
507                         wbytes := []byte("there's no simple explanation for anything important that any of us do")
508                         f, err := s.fs.OpenFile(fmt.Sprintf("random-%d", n), os.O_RDWR|os.O_CREATE|os.O_EXCL, 0)
509                         c.Assert(err, check.IsNil)
510                         defer f.Close()
511                         for i := 0; i < ngoroutines; i++ {
512                                 trunc := rand.Intn(65)
513                                 woff := rand.Intn(trunc + 1)
514                                 wbytes = wbytes[:rand.Intn(64-woff+1)]
515                                 for buf, i := expect[:cap(expect)], len(expect); i < trunc; i++ {
516                                         buf[i] = 0
517                                 }
518                                 expect = expect[:trunc]
519                                 if trunc < woff+len(wbytes) {
520                                         expect = expect[:woff+len(wbytes)]
521                                 }
522                                 copy(expect[woff:], wbytes)
523                                 f.Truncate(int64(trunc))
524                                 pos, err := f.Seek(int64(woff), os.SEEK_SET)
525                                 c.Check(pos, check.Equals, int64(woff))
526                                 c.Check(err, check.IsNil)
527                                 n, err := f.Write(wbytes)
528                                 c.Check(n, check.Equals, len(wbytes))
529                                 c.Check(err, check.IsNil)
530                                 pos, err = f.Seek(0, os.SEEK_SET)
531                                 c.Check(pos, check.Equals, int64(0))
532                                 c.Check(err, check.IsNil)
533                                 buf, err := ioutil.ReadAll(f)
534                                 c.Check(string(buf), check.Equals, string(expect))
535                                 c.Check(err, check.IsNil)
536                         }
537                         s.checkMemSize(c, f)
538                 }(n)
539         }
540         wg.Wait()
541
542         root, err := s.fs.Open("/")
543         c.Assert(err, check.IsNil)
544         defer root.Close()
545         fi, err := root.Readdir(-1)
546         c.Check(err, check.IsNil)
547         c.Check(len(fi), check.Equals, nfiles)
548
549         _, err = s.fs.MarshalManifest(".")
550         c.Check(err, check.IsNil)
551         // TODO: check manifest content
552 }
553
554 func (s *CollectionFSSuite) TestRemove(c *check.C) {
555         fs, err := (&Collection{}).FileSystem(s.client, s.kc)
556         c.Assert(err, check.IsNil)
557         err = fs.Mkdir("dir0", 0755)
558         c.Assert(err, check.IsNil)
559         err = fs.Mkdir("dir1", 0755)
560         c.Assert(err, check.IsNil)
561         err = fs.Mkdir("dir1/dir2", 0755)
562         c.Assert(err, check.IsNil)
563
564         err = fs.Remove("dir0")
565         c.Check(err, check.IsNil)
566         err = fs.Remove("dir0")
567         c.Check(err, check.Equals, os.ErrNotExist)
568
569         err = fs.Remove("dir1/dir2/")
570         c.Check(err, check.Equals, ErrInvalidArgument)
571         err = fs.Remove("dir1/dir2/.")
572         c.Check(err, check.Equals, ErrInvalidArgument)
573         err = fs.Remove("dir1/dir2/..")
574         c.Check(err, check.Equals, ErrInvalidArgument)
575         err = fs.Remove("dir1")
576         c.Check(err, check.Equals, ErrDirectoryNotEmpty)
577         err = fs.Remove("dir1/dir2/../../../dir1")
578         c.Check(err, check.Equals, ErrDirectoryNotEmpty)
579         err = fs.RemoveAll("dir1")
580         c.Check(err, check.IsNil)
581         err = fs.RemoveAll("dir1")
582         c.Check(err, check.Equals, os.ErrNotExist)
583 }
584
585 func (s *CollectionFSSuite) TestRename(c *check.C) {
586         fs, err := (&Collection{}).FileSystem(s.client, s.kc)
587         c.Assert(err, check.IsNil)
588         const (
589                 outer = 16
590                 inner = 16
591         )
592         for i := 0; i < outer; i++ {
593                 err = fs.Mkdir(fmt.Sprintf("dir%d", i), 0755)
594                 c.Assert(err, check.IsNil)
595                 for j := 0; j < inner; j++ {
596                         err = fs.Mkdir(fmt.Sprintf("dir%d/dir%d", i, j), 0755)
597                         c.Assert(err, check.IsNil)
598                         for _, fnm := range []string{
599                                 fmt.Sprintf("dir%d/file%d", i, j),
600                                 fmt.Sprintf("dir%d/dir%d/file%d", i, j, j),
601                         } {
602                                 f, err := fs.OpenFile(fnm, os.O_CREATE|os.O_WRONLY, 0755)
603                                 c.Assert(err, check.IsNil)
604                                 _, err = f.Write([]byte("beep"))
605                                 c.Assert(err, check.IsNil)
606                                 f.Close()
607                         }
608                 }
609         }
610         var wg sync.WaitGroup
611         for i := 0; i < outer; i++ {
612                 for j := 0; j < inner; j++ {
613                         wg.Add(1)
614                         go func(i, j int) {
615                                 defer wg.Done()
616                                 oldname := fmt.Sprintf("dir%d/dir%d/file%d", i, j, j)
617                                 newname := fmt.Sprintf("dir%d/newfile%d", i, inner-j-1)
618                                 _, err := fs.Open(newname)
619                                 c.Check(err, check.Equals, os.ErrNotExist)
620                                 err = fs.Rename(oldname, newname)
621                                 c.Check(err, check.IsNil)
622                                 f, err := fs.Open(newname)
623                                 c.Check(err, check.IsNil)
624                                 f.Close()
625                         }(i, j)
626
627                         wg.Add(1)
628                         go func(i, j int) {
629                                 defer wg.Done()
630                                 // oldname does not exist
631                                 err := fs.Rename(
632                                         fmt.Sprintf("dir%d/dir%d/missing", i, j),
633                                         fmt.Sprintf("dir%d/irelevant", outer-i-1))
634                                 c.Check(err, check.ErrorMatches, `.*does not exist`)
635
636                                 // newname parent dir does not exist
637                                 err = fs.Rename(
638                                         fmt.Sprintf("dir%d/dir%d", i, j),
639                                         fmt.Sprintf("dir%d/missing/irrelevant", outer-i-1))
640                                 c.Check(err, check.ErrorMatches, `.*does not exist`)
641
642                                 // oldname parent dir is a file
643                                 err = fs.Rename(
644                                         fmt.Sprintf("dir%d/file%d/patherror", i, j),
645                                         fmt.Sprintf("dir%d/irrelevant", i))
646                                 c.Check(err, check.ErrorMatches, `.*does not exist`)
647
648                                 // newname parent dir is a file
649                                 err = fs.Rename(
650                                         fmt.Sprintf("dir%d/dir%d/file%d", i, j, j),
651                                         fmt.Sprintf("dir%d/file%d/patherror", i, inner-j-1))
652                                 c.Check(err, check.ErrorMatches, `.*does not exist`)
653                         }(i, j)
654                 }
655         }
656         wg.Wait()
657
658         f, err := fs.OpenFile("dir1/newfile3", 0, 0)
659         c.Assert(err, check.IsNil)
660         c.Check(f.Size(), check.Equals, int64(4))
661         buf, err := ioutil.ReadAll(f)
662         c.Check(buf, check.DeepEquals, []byte("beep"))
663         c.Check(err, check.IsNil)
664         _, err = fs.Open("dir1/dir1/file1")
665         c.Check(err, check.Equals, os.ErrNotExist)
666 }
667
668 func (s *CollectionFSSuite) TestPersist(c *check.C) {
669         maxBlockSize = 1024
670         defer func() { maxBlockSize = 2 << 26 }()
671
672         var err error
673         s.fs, err = (&Collection{}).FileSystem(s.client, s.kc)
674         c.Assert(err, check.IsNil)
675         err = s.fs.Mkdir("d:r", 0755)
676         c.Assert(err, check.IsNil)
677
678         expect := map[string][]byte{}
679
680         var wg sync.WaitGroup
681         for _, name := range []string{"random 1", "random:2", "random\\3", "d:r/random4"} {
682                 buf := make([]byte, 500)
683                 rand.Read(buf)
684                 expect[name] = buf
685
686                 f, err := s.fs.OpenFile(name, os.O_WRONLY|os.O_CREATE, 0)
687                 c.Assert(err, check.IsNil)
688                 // Note: we don't close the file until after the test
689                 // is done. Writes to unclosed files should persist.
690                 defer f.Close()
691
692                 wg.Add(1)
693                 go func() {
694                         defer wg.Done()
695                         for i := 0; i < len(buf); i += 5 {
696                                 _, err := f.Write(buf[i : i+5])
697                                 c.Assert(err, check.IsNil)
698                         }
699                 }()
700         }
701         wg.Wait()
702
703         m, err := s.fs.MarshalManifest(".")
704         c.Check(err, check.IsNil)
705         c.Logf("%q", m)
706
707         root, err := s.fs.Open("/")
708         c.Assert(err, check.IsNil)
709         defer root.Close()
710         fi, err := root.Readdir(-1)
711         c.Check(err, check.IsNil)
712         c.Check(len(fi), check.Equals, 4)
713
714         persisted, err := (&Collection{ManifestText: m}).FileSystem(s.client, s.kc)
715         c.Assert(err, check.IsNil)
716
717         root, err = persisted.Open("/")
718         c.Assert(err, check.IsNil)
719         defer root.Close()
720         fi, err = root.Readdir(-1)
721         c.Check(err, check.IsNil)
722         c.Check(len(fi), check.Equals, 4)
723
724         for name, content := range expect {
725                 c.Logf("read %q", name)
726                 f, err := persisted.Open(name)
727                 c.Assert(err, check.IsNil)
728                 defer f.Close()
729                 buf, err := ioutil.ReadAll(f)
730                 c.Check(err, check.IsNil)
731                 c.Check(buf, check.DeepEquals, content)
732         }
733 }
734
735 func (s *CollectionFSSuite) TestPersistEmptyFiles(c *check.C) {
736         var err error
737         s.fs, err = (&Collection{}).FileSystem(s.client, s.kc)
738         c.Assert(err, check.IsNil)
739         for _, name := range []string{"dir", "dir/zerodir", "zero", "zero/zero"} {
740                 err = s.fs.Mkdir(name, 0755)
741                 c.Assert(err, check.IsNil)
742         }
743
744         expect := map[string][]byte{
745                 "0":                nil,
746                 "00":               []byte{},
747                 "one":              []byte{1},
748                 "dir/0":            nil,
749                 "dir/two":          []byte{1, 2},
750                 "dir/zero":         nil,
751                 "dir/zerodir/zero": nil,
752                 "zero/zero/zero":   nil,
753         }
754         for name, data := range expect {
755                 f, err := s.fs.OpenFile(name, os.O_WRONLY|os.O_CREATE, 0)
756                 c.Assert(err, check.IsNil)
757                 if data != nil {
758                         _, err := f.Write(data)
759                         c.Assert(err, check.IsNil)
760                 }
761                 f.Close()
762         }
763
764         m, err := s.fs.MarshalManifest(".")
765         c.Check(err, check.IsNil)
766         c.Logf("%q", m)
767
768         persisted, err := (&Collection{ManifestText: m}).FileSystem(s.client, s.kc)
769         c.Assert(err, check.IsNil)
770
771         for name, data := range expect {
772                 f, err := persisted.Open("bogus-" + name)
773                 c.Check(err, check.NotNil)
774
775                 f, err = persisted.Open(name)
776                 c.Assert(err, check.IsNil)
777
778                 if data == nil {
779                         data = []byte{}
780                 }
781                 buf, err := ioutil.ReadAll(f)
782                 c.Check(err, check.IsNil)
783                 c.Check(buf, check.DeepEquals, data)
784         }
785 }
786
787 func (s *CollectionFSSuite) TestOpenFileFlags(c *check.C) {
788         fs, err := (&Collection{}).FileSystem(s.client, s.kc)
789         c.Assert(err, check.IsNil)
790
791         f, err := fs.OpenFile("missing", os.O_WRONLY, 0)
792         c.Check(f, check.IsNil)
793         c.Check(err, check.ErrorMatches, `file does not exist`)
794
795         f, err = fs.OpenFile("new", os.O_CREATE|os.O_RDONLY, 0)
796         c.Assert(err, check.IsNil)
797         defer f.Close()
798         n, err := f.Write([]byte{1, 2, 3})
799         c.Check(n, check.Equals, 0)
800         c.Check(err, check.ErrorMatches, `read-only file`)
801         n, err = f.Read(make([]byte, 1))
802         c.Check(n, check.Equals, 0)
803         c.Check(err, check.Equals, io.EOF)
804         f, err = fs.OpenFile("new", os.O_RDWR, 0)
805         c.Assert(err, check.IsNil)
806         defer f.Close()
807         _, err = f.Write([]byte{4, 5, 6})
808         c.Check(err, check.IsNil)
809         fi, err := f.Stat()
810         c.Assert(err, check.IsNil)
811         c.Check(fi.Size(), check.Equals, int64(3))
812
813         f, err = fs.OpenFile("new", os.O_TRUNC|os.O_RDWR, 0)
814         c.Assert(err, check.IsNil)
815         defer f.Close()
816         pos, err := f.Seek(0, os.SEEK_END)
817         c.Check(pos, check.Equals, int64(0))
818         c.Check(err, check.IsNil)
819         fi, err = f.Stat()
820         c.Assert(err, check.IsNil)
821         c.Check(fi.Size(), check.Equals, int64(0))
822         fs.Remove("new")
823
824         buf := make([]byte, 64)
825         f, err = fs.OpenFile("append", os.O_EXCL|os.O_CREATE|os.O_RDWR|os.O_APPEND, 0)
826         c.Assert(err, check.IsNil)
827         f.Write([]byte{1, 2, 3})
828         f.Seek(0, os.SEEK_SET)
829         n, _ = f.Read(buf[:1])
830         c.Check(n, check.Equals, 1)
831         c.Check(buf[:1], check.DeepEquals, []byte{1})
832         pos, err = f.Seek(0, os.SEEK_CUR)
833         c.Check(pos, check.Equals, int64(1))
834         f.Write([]byte{4, 5, 6})
835         pos, err = f.Seek(0, os.SEEK_CUR)
836         c.Check(pos, check.Equals, int64(6))
837         f.Seek(0, os.SEEK_SET)
838         n, err = f.Read(buf)
839         c.Check(buf[:n], check.DeepEquals, []byte{1, 2, 3, 4, 5, 6})
840         c.Check(err, check.Equals, io.EOF)
841         f.Close()
842
843         f, err = fs.OpenFile("append", os.O_RDWR|os.O_APPEND, 0)
844         c.Assert(err, check.IsNil)
845         pos, err = f.Seek(0, os.SEEK_CUR)
846         c.Check(pos, check.Equals, int64(0))
847         c.Check(err, check.IsNil)
848         f.Read(buf[:3])
849         pos, _ = f.Seek(0, os.SEEK_CUR)
850         c.Check(pos, check.Equals, int64(3))
851         f.Write([]byte{7, 8, 9})
852         pos, err = f.Seek(0, os.SEEK_CUR)
853         c.Check(pos, check.Equals, int64(9))
854         f.Close()
855
856         f, err = fs.OpenFile("wronly", os.O_CREATE|os.O_WRONLY, 0)
857         c.Assert(err, check.IsNil)
858         n, err = f.Write([]byte{3, 2, 1})
859         c.Check(n, check.Equals, 3)
860         c.Check(err, check.IsNil)
861         pos, _ = f.Seek(0, os.SEEK_CUR)
862         c.Check(pos, check.Equals, int64(3))
863         pos, _ = f.Seek(0, os.SEEK_SET)
864         c.Check(pos, check.Equals, int64(0))
865         n, err = f.Read(buf)
866         c.Check(n, check.Equals, 0)
867         c.Check(err, check.ErrorMatches, `.*O_WRONLY.*`)
868         f, err = fs.OpenFile("wronly", os.O_RDONLY, 0)
869         c.Assert(err, check.IsNil)
870         n, _ = f.Read(buf)
871         c.Check(buf[:n], check.DeepEquals, []byte{3, 2, 1})
872
873         f, err = fs.OpenFile("unsupported", os.O_CREATE|os.O_SYNC, 0)
874         c.Check(f, check.IsNil)
875         c.Check(err, check.NotNil)
876
877         f, err = fs.OpenFile("append", os.O_RDWR|os.O_WRONLY, 0)
878         c.Check(f, check.IsNil)
879         c.Check(err, check.ErrorMatches, `invalid flag.*`)
880 }
881
882 func (s *CollectionFSSuite) TestFlushFullBlocks(c *check.C) {
883         maxBlockSize = 1024
884         defer func() { maxBlockSize = 2 << 26 }()
885
886         fs, err := (&Collection{}).FileSystem(s.client, s.kc)
887         c.Assert(err, check.IsNil)
888         f, err := fs.OpenFile("50K", os.O_WRONLY|os.O_CREATE, 0)
889         c.Assert(err, check.IsNil)
890         defer f.Close()
891
892         data := make([]byte, 500)
893         rand.Read(data)
894
895         for i := 0; i < 100; i++ {
896                 n, err := f.Write(data)
897                 c.Assert(n, check.Equals, len(data))
898                 c.Assert(err, check.IsNil)
899         }
900
901         currentMemExtents := func() (memExtents []int) {
902                 for idx, e := range f.(*file).inode.(*filenode).extents {
903                         switch e.(type) {
904                         case *memExtent:
905                                 memExtents = append(memExtents, idx)
906                         }
907                 }
908                 return
909         }
910         c.Check(currentMemExtents(), check.HasLen, 1)
911
912         m, err := fs.MarshalManifest(".")
913         c.Check(m, check.Matches, `[^:]* 0:50000:50K\n`)
914         c.Check(err, check.IsNil)
915         c.Check(currentMemExtents(), check.HasLen, 0)
916 }
917
918 func (s *CollectionFSSuite) TestBrokenManifests(c *check.C) {
919         for _, txt := range []string{
920                 "\n",
921                 ".\n",
922                 ". \n",
923                 ". d41d8cd98f00b204e9800998ecf8427e+0\n",
924                 ". d41d8cd98f00b204e9800998ecf8427e+0 \n",
925                 ". 0:0:foo\n",
926                 ".  0:0:foo\n",
927                 ". 0:0:foo 0:0:bar\n",
928                 ". d41d8cd98f00b204e9800998ecf8427e 0:0:foo\n",
929                 ". d41d8cd98f00b204e9800998ecf8427e+0 0:0:foo:bar\n",
930                 ". d41d8cd98f00b204e9800998ecf8427e+0 foo:0:foo\n",
931                 ". d41d8cd98f00b204e9800998ecf8427e+0 0:foo:foo\n",
932                 ". d41d8cd98f00b204e9800998ecf8427e+1 0:1:foo 1:1:bar\n",
933                 ". d41d8cd98f00b204e9800998ecf8427e+1 0:0:foo\n./foo d41d8cd98f00b204e9800998ecf8427e+1 0:0:bar\n",
934                 "./foo d41d8cd98f00b204e9800998ecf8427e+1 0:0:bar\n. d41d8cd98f00b204e9800998ecf8427e+1 0:0:foo\n",
935         } {
936                 c.Logf("<-%q", txt)
937                 fs, err := (&Collection{ManifestText: txt}).FileSystem(s.client, s.kc)
938                 c.Check(fs, check.IsNil)
939                 c.Logf("-> %s", err)
940                 c.Check(err, check.NotNil)
941         }
942 }
943
944 func (s *CollectionFSSuite) TestEdgeCaseManifests(c *check.C) {
945         for _, txt := range []string{
946                 "",
947                 ". d41d8cd98f00b204e9800998ecf8427e+0 0:0:foo\n",
948                 ". d41d8cd98f00b204e9800998ecf8427e+0 0:0:foo 0:0:foo 0:0:bar\n",
949                 ". d41d8cd98f00b204e9800998ecf8427e+0 0:0:foo 0:0:foo 0:0:bar\n",
950                 ". d41d8cd98f00b204e9800998ecf8427e+0 0:0:foo/bar\n./foo d41d8cd98f00b204e9800998ecf8427e+0 0:0:bar\n",
951         } {
952                 c.Logf("<-%q", txt)
953                 fs, err := (&Collection{ManifestText: txt}).FileSystem(s.client, s.kc)
954                 c.Check(err, check.IsNil)
955                 c.Check(fs, check.NotNil)
956         }
957 }
958
959 func (s *CollectionFSSuite) checkMemSize(c *check.C, f File) {
960         fn := f.(*file).inode.(*filenode)
961         var memsize int64
962         for _, ext := range fn.extents {
963                 if e, ok := ext.(*memExtent); ok {
964                         memsize += int64(len(e.buf))
965                 }
966         }
967         c.Check(fn.memsize, check.Equals, memsize)
968 }
969
970 type CollectionFSUnitSuite struct{}
971
972 var _ = check.Suite(&CollectionFSUnitSuite{})
973
974 // expect ~2 seconds to load a manifest with 256K files
975 func (s *CollectionFSUnitSuite) TestLargeManifest(c *check.C) {
976         const (
977                 dirCount  = 512
978                 fileCount = 512
979         )
980
981         mb := bytes.NewBuffer(make([]byte, 0, 40000000))
982         for i := 0; i < dirCount; i++ {
983                 fmt.Fprintf(mb, "./dir%d", i)
984                 for j := 0; j <= fileCount; j++ {
985                         fmt.Fprintf(mb, " %032x+42+A%040x@%08x", j, j, j)
986                 }
987                 for j := 0; j < fileCount; j++ {
988                         fmt.Fprintf(mb, " %d:%d:dir%d/file%d", j*42+21, 42, j, j)
989                 }
990                 mb.Write([]byte{'\n'})
991         }
992         coll := Collection{ManifestText: mb.String()}
993         c.Logf("%s built", time.Now())
994
995         var memstats runtime.MemStats
996         runtime.ReadMemStats(&memstats)
997         c.Logf("%s Alloc=%d Sys=%d", time.Now(), memstats.Alloc, memstats.Sys)
998
999         f, err := coll.FileSystem(nil, nil)
1000         c.Check(err, check.IsNil)
1001         c.Logf("%s loaded", time.Now())
1002
1003         for i := 0; i < dirCount; i++ {
1004                 for j := 0; j < fileCount; j++ {
1005                         f.Stat(fmt.Sprintf("./dir%d/dir%d/file%d", i, j, j))
1006                 }
1007         }
1008         c.Logf("%s Stat() x %d", time.Now(), dirCount*fileCount)
1009
1010         runtime.ReadMemStats(&memstats)
1011         c.Logf("%s Alloc=%d Sys=%d", time.Now(), memstats.Alloc, memstats.Sys)
1012 }
1013
1014 // Gocheck boilerplate
1015 func Test(t *testing.T) {
1016         check.TestingT(t)
1017 }