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