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