13111: Merge branch 'master' into 13111-webdav-projects
[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.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         if testing.Short() {
478                 c.Skip("slow")
479         }
480
481         maxBlockSize = 8
482         defer func() { maxBlockSize = 2 << 26 }()
483
484         var wg sync.WaitGroup
485         for n := 0; n < 128; n++ {
486                 wg.Add(1)
487                 go func() {
488                         defer wg.Done()
489                         f, err := s.fs.OpenFile("/dir1/foo", os.O_RDWR, 0)
490                         c.Assert(err, check.IsNil)
491                         defer f.Close()
492                         for i := 0; i < 6502; i++ {
493                                 switch rand.Int() & 3 {
494                                 case 0:
495                                         f.Truncate(int64(rand.Intn(64)))
496                                 case 1:
497                                         f.Seek(int64(rand.Intn(64)), io.SeekStart)
498                                 case 2:
499                                         _, err := f.Write([]byte("beep boop"))
500                                         c.Check(err, check.IsNil)
501                                 case 3:
502                                         _, err := ioutil.ReadAll(f)
503                                         c.Check(err, check.IsNil)
504                                 }
505                         }
506                 }()
507         }
508         wg.Wait()
509
510         f, err := s.fs.OpenFile("/dir1/foo", os.O_RDWR, 0)
511         c.Assert(err, check.IsNil)
512         defer f.Close()
513         buf, err := ioutil.ReadAll(f)
514         c.Check(err, check.IsNil)
515         c.Logf("after lots of random r/w/seek/trunc, buf is %q", buf)
516 }
517
518 func (s *CollectionFSSuite) TestRandomWrites(c *check.C) {
519         maxBlockSize = 40
520         defer func() { maxBlockSize = 2 << 26 }()
521
522         var err error
523         s.fs, err = (&Collection{}).FileSystem(s.client, s.kc)
524         c.Assert(err, check.IsNil)
525
526         const nfiles = 256
527         const ngoroutines = 256
528
529         var wg sync.WaitGroup
530         for n := 0; n < nfiles; n++ {
531                 wg.Add(1)
532                 go func(n int) {
533                         defer wg.Done()
534                         expect := make([]byte, 0, 64)
535                         wbytes := []byte("there's no simple explanation for anything important that any of us do")
536                         f, err := s.fs.OpenFile(fmt.Sprintf("random-%d", n), os.O_RDWR|os.O_CREATE|os.O_EXCL, 0)
537                         c.Assert(err, check.IsNil)
538                         defer f.Close()
539                         for i := 0; i < ngoroutines; i++ {
540                                 trunc := rand.Intn(65)
541                                 woff := rand.Intn(trunc + 1)
542                                 wbytes = wbytes[:rand.Intn(64-woff+1)]
543                                 for buf, i := expect[:cap(expect)], len(expect); i < trunc; i++ {
544                                         buf[i] = 0
545                                 }
546                                 expect = expect[:trunc]
547                                 if trunc < woff+len(wbytes) {
548                                         expect = expect[:woff+len(wbytes)]
549                                 }
550                                 copy(expect[woff:], wbytes)
551                                 f.Truncate(int64(trunc))
552                                 pos, err := f.Seek(int64(woff), io.SeekStart)
553                                 c.Check(pos, check.Equals, int64(woff))
554                                 c.Check(err, check.IsNil)
555                                 n, err := f.Write(wbytes)
556                                 c.Check(n, check.Equals, len(wbytes))
557                                 c.Check(err, check.IsNil)
558                                 pos, err = f.Seek(0, io.SeekStart)
559                                 c.Check(pos, check.Equals, int64(0))
560                                 c.Check(err, check.IsNil)
561                                 buf, err := ioutil.ReadAll(f)
562                                 c.Check(string(buf), check.Equals, string(expect))
563                                 c.Check(err, check.IsNil)
564                         }
565                         s.checkMemSize(c, f)
566                 }(n)
567         }
568         wg.Wait()
569
570         root, err := s.fs.Open("/")
571         c.Assert(err, check.IsNil)
572         defer root.Close()
573         fi, err := root.Readdir(-1)
574         c.Check(err, check.IsNil)
575         c.Check(len(fi), check.Equals, nfiles)
576
577         _, err = s.fs.MarshalManifest(".")
578         c.Check(err, check.IsNil)
579         // TODO: check manifest content
580 }
581
582 func (s *CollectionFSSuite) TestRemove(c *check.C) {
583         fs, err := (&Collection{}).FileSystem(s.client, s.kc)
584         c.Assert(err, check.IsNil)
585         err = fs.Mkdir("dir0", 0755)
586         c.Assert(err, check.IsNil)
587         err = fs.Mkdir("dir1", 0755)
588         c.Assert(err, check.IsNil)
589         err = fs.Mkdir("dir1/dir2", 0755)
590         c.Assert(err, check.IsNil)
591         err = fs.Mkdir("dir1/dir3", 0755)
592         c.Assert(err, check.IsNil)
593
594         err = fs.Remove("dir0")
595         c.Check(err, check.IsNil)
596         err = fs.Remove("dir0")
597         c.Check(err, check.Equals, os.ErrNotExist)
598
599         err = fs.Remove("dir1/dir2/.")
600         c.Check(err, check.Equals, ErrInvalidArgument)
601         err = fs.Remove("dir1/dir2/..")
602         c.Check(err, check.Equals, ErrInvalidArgument)
603         err = fs.Remove("dir1")
604         c.Check(err, check.Equals, ErrDirectoryNotEmpty)
605         err = fs.Remove("dir1/dir2/../../../dir1")
606         c.Check(err, check.Equals, ErrDirectoryNotEmpty)
607         err = fs.Remove("dir1/dir3/")
608         c.Check(err, check.IsNil)
609         err = fs.RemoveAll("dir1")
610         c.Check(err, check.IsNil)
611         err = fs.RemoveAll("dir1")
612         c.Check(err, check.IsNil)
613 }
614
615 func (s *CollectionFSSuite) TestRenameError(c *check.C) {
616         fs, err := (&Collection{}).FileSystem(s.client, s.kc)
617         c.Assert(err, check.IsNil)
618         err = fs.Mkdir("first", 0755)
619         c.Assert(err, check.IsNil)
620         err = fs.Mkdir("first/second", 0755)
621         c.Assert(err, check.IsNil)
622         f, err := fs.OpenFile("first/second/file", os.O_CREATE|os.O_WRONLY, 0755)
623         c.Assert(err, check.IsNil)
624         f.Write([]byte{1, 2, 3, 4, 5})
625         f.Close()
626         err = fs.Rename("first", "first/second/third")
627         c.Check(err, check.Equals, ErrInvalidArgument)
628         err = fs.Rename("first", "first/third")
629         c.Check(err, check.Equals, ErrInvalidArgument)
630         err = fs.Rename("first/second", "second")
631         c.Check(err, check.IsNil)
632         f, err = fs.OpenFile("second/file", 0, 0)
633         c.Assert(err, check.IsNil)
634         data, err := ioutil.ReadAll(f)
635         c.Check(err, check.IsNil)
636         c.Check(data, check.DeepEquals, []byte{1, 2, 3, 4, 5})
637 }
638
639 func (s *CollectionFSSuite) TestRename(c *check.C) {
640         fs, err := (&Collection{}).FileSystem(s.client, s.kc)
641         c.Assert(err, check.IsNil)
642         const (
643                 outer = 16
644                 inner = 16
645         )
646         for i := 0; i < outer; i++ {
647                 err = fs.Mkdir(fmt.Sprintf("dir%d", i), 0755)
648                 c.Assert(err, check.IsNil)
649                 for j := 0; j < inner; j++ {
650                         err = fs.Mkdir(fmt.Sprintf("dir%d/dir%d", i, j), 0755)
651                         c.Assert(err, check.IsNil)
652                         for _, fnm := range []string{
653                                 fmt.Sprintf("dir%d/file%d", i, j),
654                                 fmt.Sprintf("dir%d/dir%d/file%d", i, j, j),
655                         } {
656                                 f, err := fs.OpenFile(fnm, os.O_CREATE|os.O_WRONLY, 0755)
657                                 c.Assert(err, check.IsNil)
658                                 _, err = f.Write([]byte("beep"))
659                                 c.Assert(err, check.IsNil)
660                                 f.Close()
661                         }
662                 }
663         }
664         var wg sync.WaitGroup
665         for i := 0; i < outer; i++ {
666                 for j := 0; j < inner; j++ {
667                         wg.Add(1)
668                         go func(i, j int) {
669                                 defer wg.Done()
670                                 oldname := fmt.Sprintf("dir%d/dir%d/file%d", i, j, j)
671                                 newname := fmt.Sprintf("dir%d/newfile%d", i, inner-j-1)
672                                 _, err := fs.Open(newname)
673                                 c.Check(err, check.Equals, os.ErrNotExist)
674                                 err = fs.Rename(oldname, newname)
675                                 c.Check(err, check.IsNil)
676                                 f, err := fs.Open(newname)
677                                 c.Check(err, check.IsNil)
678                                 f.Close()
679                         }(i, j)
680
681                         wg.Add(1)
682                         go func(i, j int) {
683                                 defer wg.Done()
684                                 // oldname does not exist
685                                 err := fs.Rename(
686                                         fmt.Sprintf("dir%d/dir%d/missing", i, j),
687                                         fmt.Sprintf("dir%d/dir%d/file%d", outer-i-1, j, j))
688                                 c.Check(err, check.ErrorMatches, `.*does not exist`)
689
690                                 // newname parent dir does not exist
691                                 err = fs.Rename(
692                                         fmt.Sprintf("dir%d/dir%d", i, j),
693                                         fmt.Sprintf("dir%d/missing/irrelevant", outer-i-1))
694                                 c.Check(err, check.ErrorMatches, `.*does not exist`)
695
696                                 // oldname parent dir is a file
697                                 err = fs.Rename(
698                                         fmt.Sprintf("dir%d/file%d/patherror", i, j),
699                                         fmt.Sprintf("dir%d/irrelevant", i))
700                                 c.Check(err, check.ErrorMatches, `.*not a directory`)
701
702                                 // newname parent dir is a file
703                                 err = fs.Rename(
704                                         fmt.Sprintf("dir%d/dir%d/file%d", i, j, j),
705                                         fmt.Sprintf("dir%d/file%d/patherror", i, inner-j-1))
706                                 c.Check(err, check.ErrorMatches, `.*not a directory`)
707                         }(i, j)
708                 }
709         }
710         wg.Wait()
711
712         f, err := fs.OpenFile("dir1/newfile3", 0, 0)
713         c.Assert(err, check.IsNil)
714         c.Check(f.Size(), check.Equals, int64(4))
715         buf, err := ioutil.ReadAll(f)
716         c.Check(buf, check.DeepEquals, []byte("beep"))
717         c.Check(err, check.IsNil)
718         _, err = fs.Open("dir1/dir1/file1")
719         c.Check(err, check.Equals, os.ErrNotExist)
720 }
721
722 func (s *CollectionFSSuite) TestPersist(c *check.C) {
723         maxBlockSize = 1024
724         defer func() { maxBlockSize = 2 << 26 }()
725
726         var err error
727         s.fs, err = (&Collection{}).FileSystem(s.client, s.kc)
728         c.Assert(err, check.IsNil)
729         err = s.fs.Mkdir("d:r", 0755)
730         c.Assert(err, check.IsNil)
731
732         expect := map[string][]byte{}
733
734         var wg sync.WaitGroup
735         for _, name := range []string{"random 1", "random:2", "random\\3", "d:r/random4"} {
736                 buf := make([]byte, 500)
737                 rand.Read(buf)
738                 expect[name] = buf
739
740                 f, err := s.fs.OpenFile(name, os.O_WRONLY|os.O_CREATE, 0)
741                 c.Assert(err, check.IsNil)
742                 // Note: we don't close the file until after the test
743                 // is done. Writes to unclosed files should persist.
744                 defer f.Close()
745
746                 wg.Add(1)
747                 go func() {
748                         defer wg.Done()
749                         for i := 0; i < len(buf); i += 5 {
750                                 _, err := f.Write(buf[i : i+5])
751                                 c.Assert(err, check.IsNil)
752                         }
753                 }()
754         }
755         wg.Wait()
756
757         m, err := s.fs.MarshalManifest(".")
758         c.Check(err, check.IsNil)
759         c.Logf("%q", m)
760
761         root, err := s.fs.Open("/")
762         c.Assert(err, check.IsNil)
763         defer root.Close()
764         fi, err := root.Readdir(-1)
765         c.Check(err, check.IsNil)
766         c.Check(len(fi), check.Equals, 4)
767
768         persisted, err := (&Collection{ManifestText: m}).FileSystem(s.client, s.kc)
769         c.Assert(err, check.IsNil)
770
771         root, err = persisted.Open("/")
772         c.Assert(err, check.IsNil)
773         defer root.Close()
774         fi, err = root.Readdir(-1)
775         c.Check(err, check.IsNil)
776         c.Check(len(fi), check.Equals, 4)
777
778         for name, content := range expect {
779                 c.Logf("read %q", name)
780                 f, err := persisted.Open(name)
781                 c.Assert(err, check.IsNil)
782                 defer f.Close()
783                 buf, err := ioutil.ReadAll(f)
784                 c.Check(err, check.IsNil)
785                 c.Check(buf, check.DeepEquals, content)
786         }
787 }
788
789 func (s *CollectionFSSuite) TestPersistEmptyFiles(c *check.C) {
790         var err error
791         s.fs, err = (&Collection{}).FileSystem(s.client, s.kc)
792         c.Assert(err, check.IsNil)
793         for _, name := range []string{"dir", "dir/zerodir", "zero", "zero/zero"} {
794                 err = s.fs.Mkdir(name, 0755)
795                 c.Assert(err, check.IsNil)
796         }
797
798         expect := map[string][]byte{
799                 "0":                nil,
800                 "00":               {},
801                 "one":              {1},
802                 "dir/0":            nil,
803                 "dir/two":          {1, 2},
804                 "dir/zero":         nil,
805                 "dir/zerodir/zero": nil,
806                 "zero/zero/zero":   nil,
807         }
808         for name, data := range expect {
809                 f, err := s.fs.OpenFile(name, os.O_WRONLY|os.O_CREATE, 0)
810                 c.Assert(err, check.IsNil)
811                 if data != nil {
812                         _, err := f.Write(data)
813                         c.Assert(err, check.IsNil)
814                 }
815                 f.Close()
816         }
817
818         m, err := s.fs.MarshalManifest(".")
819         c.Check(err, check.IsNil)
820         c.Logf("%q", m)
821
822         persisted, err := (&Collection{ManifestText: m}).FileSystem(s.client, s.kc)
823         c.Assert(err, check.IsNil)
824
825         for name, data := range expect {
826                 _, err = persisted.Open("bogus-" + name)
827                 c.Check(err, check.NotNil)
828
829                 f, err := persisted.Open(name)
830                 c.Assert(err, check.IsNil)
831
832                 if data == nil {
833                         data = []byte{}
834                 }
835                 buf, err := ioutil.ReadAll(f)
836                 c.Check(err, check.IsNil)
837                 c.Check(buf, check.DeepEquals, data)
838         }
839 }
840
841 func (s *CollectionFSSuite) TestOpenFileFlags(c *check.C) {
842         fs, err := (&Collection{}).FileSystem(s.client, s.kc)
843         c.Assert(err, check.IsNil)
844
845         f, err := fs.OpenFile("missing", os.O_WRONLY, 0)
846         c.Check(f, check.IsNil)
847         c.Check(err, check.ErrorMatches, `file does not exist`)
848
849         f, err = fs.OpenFile("new", os.O_CREATE|os.O_RDONLY, 0)
850         c.Assert(err, check.IsNil)
851         defer f.Close()
852         n, err := f.Write([]byte{1, 2, 3})
853         c.Check(n, check.Equals, 0)
854         c.Check(err, check.ErrorMatches, `read-only file`)
855         n, err = f.Read(make([]byte, 1))
856         c.Check(n, check.Equals, 0)
857         c.Check(err, check.Equals, io.EOF)
858         f, err = fs.OpenFile("new", os.O_RDWR, 0)
859         c.Assert(err, check.IsNil)
860         defer f.Close()
861         _, err = f.Write([]byte{4, 5, 6})
862         c.Check(err, check.IsNil)
863         fi, err := f.Stat()
864         c.Assert(err, check.IsNil)
865         c.Check(fi.Size(), check.Equals, int64(3))
866
867         f, err = fs.OpenFile("new", os.O_TRUNC|os.O_RDWR, 0)
868         c.Assert(err, check.IsNil)
869         defer f.Close()
870         pos, err := f.Seek(0, io.SeekEnd)
871         c.Check(pos, check.Equals, int64(0))
872         c.Check(err, check.IsNil)
873         fi, err = f.Stat()
874         c.Assert(err, check.IsNil)
875         c.Check(fi.Size(), check.Equals, int64(0))
876         fs.Remove("new")
877
878         buf := make([]byte, 64)
879         f, err = fs.OpenFile("append", os.O_EXCL|os.O_CREATE|os.O_RDWR|os.O_APPEND, 0)
880         c.Assert(err, check.IsNil)
881         f.Write([]byte{1, 2, 3})
882         f.Seek(0, io.SeekStart)
883         n, _ = f.Read(buf[:1])
884         c.Check(n, check.Equals, 1)
885         c.Check(buf[:1], check.DeepEquals, []byte{1})
886         pos, err = f.Seek(0, io.SeekCurrent)
887         c.Assert(err, check.IsNil)
888         c.Check(pos, check.Equals, int64(1))
889         f.Write([]byte{4, 5, 6})
890         pos, err = f.Seek(0, io.SeekCurrent)
891         c.Assert(err, check.IsNil)
892         c.Check(pos, check.Equals, int64(6))
893         f.Seek(0, io.SeekStart)
894         n, err = f.Read(buf)
895         c.Check(buf[:n], check.DeepEquals, []byte{1, 2, 3, 4, 5, 6})
896         c.Check(err, check.Equals, io.EOF)
897         f.Close()
898
899         f, err = fs.OpenFile("append", os.O_RDWR|os.O_APPEND, 0)
900         c.Assert(err, check.IsNil)
901         pos, err = f.Seek(0, io.SeekCurrent)
902         c.Check(pos, check.Equals, int64(0))
903         c.Check(err, check.IsNil)
904         f.Read(buf[:3])
905         pos, _ = f.Seek(0, io.SeekCurrent)
906         c.Check(pos, check.Equals, int64(3))
907         f.Write([]byte{7, 8, 9})
908         pos, err = f.Seek(0, io.SeekCurrent)
909         c.Check(err, check.IsNil)
910         c.Check(pos, check.Equals, int64(9))
911         f.Close()
912
913         f, err = fs.OpenFile("wronly", os.O_CREATE|os.O_WRONLY, 0)
914         c.Assert(err, check.IsNil)
915         n, err = f.Write([]byte{3, 2, 1})
916         c.Check(n, check.Equals, 3)
917         c.Check(err, check.IsNil)
918         pos, _ = f.Seek(0, io.SeekCurrent)
919         c.Check(pos, check.Equals, int64(3))
920         pos, _ = f.Seek(0, io.SeekStart)
921         c.Check(pos, check.Equals, int64(0))
922         n, err = f.Read(buf)
923         c.Check(n, check.Equals, 0)
924         c.Check(err, check.ErrorMatches, `.*O_WRONLY.*`)
925         f, err = fs.OpenFile("wronly", os.O_RDONLY, 0)
926         c.Assert(err, check.IsNil)
927         n, _ = f.Read(buf)
928         c.Check(buf[:n], check.DeepEquals, []byte{3, 2, 1})
929
930         f, err = fs.OpenFile("unsupported", os.O_CREATE|os.O_SYNC, 0)
931         c.Check(f, check.IsNil)
932         c.Check(err, check.NotNil)
933
934         f, err = fs.OpenFile("append", os.O_RDWR|os.O_WRONLY, 0)
935         c.Check(f, check.IsNil)
936         c.Check(err, check.ErrorMatches, `invalid flag.*`)
937 }
938
939 func (s *CollectionFSSuite) TestFlushFullBlocks(c *check.C) {
940         maxBlockSize = 1024
941         defer func() { maxBlockSize = 2 << 26 }()
942
943         fs, err := (&Collection{}).FileSystem(s.client, s.kc)
944         c.Assert(err, check.IsNil)
945         f, err := fs.OpenFile("50K", os.O_WRONLY|os.O_CREATE, 0)
946         c.Assert(err, check.IsNil)
947         defer f.Close()
948
949         data := make([]byte, 500)
950         rand.Read(data)
951
952         for i := 0; i < 100; i++ {
953                 n, err := f.Write(data)
954                 c.Assert(n, check.Equals, len(data))
955                 c.Assert(err, check.IsNil)
956         }
957
958         currentMemExtents := func() (memExtents []int) {
959                 for idx, e := range f.(*filehandle).inode.(*filenode).segments {
960                         switch e.(type) {
961                         case *memSegment:
962                                 memExtents = append(memExtents, idx)
963                         }
964                 }
965                 return
966         }
967         c.Check(currentMemExtents(), check.HasLen, 1)
968
969         m, err := fs.MarshalManifest(".")
970         c.Check(m, check.Matches, `[^:]* 0:50000:50K\n`)
971         c.Check(err, check.IsNil)
972         c.Check(currentMemExtents(), check.HasLen, 0)
973 }
974
975 func (s *CollectionFSSuite) TestBrokenManifests(c *check.C) {
976         for _, txt := range []string{
977                 "\n",
978                 ".\n",
979                 ". \n",
980                 ". d41d8cd98f00b204e9800998ecf8427e+0\n",
981                 ". d41d8cd98f00b204e9800998ecf8427e+0 \n",
982                 ". 0:0:foo\n",
983                 ".  0:0:foo\n",
984                 ". 0:0:foo 0:0:bar\n",
985                 ". d41d8cd98f00b204e9800998ecf8427e 0:0:foo\n",
986                 ". d41d8cd98f00b204e9800998ecf8427e+0 :0:0:foo\n",
987                 ". d41d8cd98f00b204e9800998ecf8427e+0 foo:0:foo\n",
988                 ". d41d8cd98f00b204e9800998ecf8427e+0 0:foo:foo\n",
989                 ". d41d8cd98f00b204e9800998ecf8427e+1 0:1:foo 1:1:bar\n",
990                 ". d41d8cd98f00b204e9800998ecf8427e+1 0:0:foo\n./foo d41d8cd98f00b204e9800998ecf8427e+1 0:0:bar\n",
991                 "./foo d41d8cd98f00b204e9800998ecf8427e+1 0:0:bar\n. d41d8cd98f00b204e9800998ecf8427e+1 0:0:foo\n",
992         } {
993                 c.Logf("<-%q", txt)
994                 fs, err := (&Collection{ManifestText: txt}).FileSystem(s.client, s.kc)
995                 c.Check(fs, check.IsNil)
996                 c.Logf("-> %s", err)
997                 c.Check(err, check.NotNil)
998         }
999 }
1000
1001 func (s *CollectionFSSuite) TestEdgeCaseManifests(c *check.C) {
1002         for _, txt := range []string{
1003                 "",
1004                 ". d41d8cd98f00b204e9800998ecf8427e+0 0:0:foo\n",
1005                 ". d41d8cd98f00b204e9800998ecf8427e+0 0:0:foo 0:0:foo 0:0:bar\n",
1006                 ". d41d8cd98f00b204e9800998ecf8427e+0 0:0:foo 0:0:foo 0:0:bar\n",
1007                 ". d41d8cd98f00b204e9800998ecf8427e+0 0:0:foo/bar\n./foo d41d8cd98f00b204e9800998ecf8427e+0 0:0:bar\n",
1008         } {
1009                 c.Logf("<-%q", txt)
1010                 fs, err := (&Collection{ManifestText: txt}).FileSystem(s.client, s.kc)
1011                 c.Check(err, check.IsNil)
1012                 c.Check(fs, check.NotNil)
1013         }
1014 }
1015
1016 func (s *CollectionFSSuite) checkMemSize(c *check.C, f File) {
1017         fn := f.(*filehandle).inode.(*filenode)
1018         var memsize int64
1019         for _, seg := range fn.segments {
1020                 if e, ok := seg.(*memSegment); ok {
1021                         memsize += int64(len(e.buf))
1022                 }
1023         }
1024         c.Check(fn.memsize, check.Equals, memsize)
1025 }
1026
1027 type CollectionFSUnitSuite struct{}
1028
1029 var _ = check.Suite(&CollectionFSUnitSuite{})
1030
1031 // expect ~2 seconds to load a manifest with 256K files
1032 func (s *CollectionFSUnitSuite) TestLargeManifest(c *check.C) {
1033         if testing.Short() {
1034                 c.Skip("slow")
1035         }
1036
1037         const (
1038                 dirCount  = 512
1039                 fileCount = 512
1040         )
1041
1042         mb := bytes.NewBuffer(make([]byte, 0, 40000000))
1043         for i := 0; i < dirCount; i++ {
1044                 fmt.Fprintf(mb, "./dir%d", i)
1045                 for j := 0; j <= fileCount; j++ {
1046                         fmt.Fprintf(mb, " %032x+42+A%040x@%08x", j, j, j)
1047                 }
1048                 for j := 0; j < fileCount; j++ {
1049                         fmt.Fprintf(mb, " %d:%d:dir%d/file%d", j*42+21, 42, j, j)
1050                 }
1051                 mb.Write([]byte{'\n'})
1052         }
1053         coll := Collection{ManifestText: mb.String()}
1054         c.Logf("%s built", time.Now())
1055
1056         var memstats runtime.MemStats
1057         runtime.ReadMemStats(&memstats)
1058         c.Logf("%s Alloc=%d Sys=%d", time.Now(), memstats.Alloc, memstats.Sys)
1059
1060         f, err := coll.FileSystem(nil, nil)
1061         c.Check(err, check.IsNil)
1062         c.Logf("%s loaded", time.Now())
1063
1064         for i := 0; i < dirCount; i++ {
1065                 for j := 0; j < fileCount; j++ {
1066                         f.Stat(fmt.Sprintf("./dir%d/dir%d/file%d", i, j, j))
1067                 }
1068         }
1069         c.Logf("%s Stat() x %d", time.Now(), dirCount*fileCount)
1070
1071         runtime.ReadMemStats(&memstats)
1072         c.Logf("%s Alloc=%d Sys=%d", time.Now(), memstats.Alloc, memstats.Sys)
1073 }
1074
1075 // Gocheck boilerplate
1076 func Test(t *testing.T) {
1077         check.TestingT(t)
1078 }