12483: Persist empty files.
[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         "crypto/md5"
9         "errors"
10         "fmt"
11         "io"
12         "io/ioutil"
13         "math/rand"
14         "net/http"
15         "os"
16         "regexp"
17         "sync"
18         "testing"
19
20         "git.curoverse.com/arvados.git/sdk/go/arvadostest"
21         check "gopkg.in/check.v1"
22 )
23
24 var _ = check.Suite(&CollectionFSSuite{})
25
26 type keepClientStub struct {
27         blocks map[string][]byte
28         sync.RWMutex
29 }
30
31 var errStub404 = errors.New("404 block not found")
32
33 func (kcs *keepClientStub) ReadAt(locator string, p []byte, off int) (int, error) {
34         kcs.RLock()
35         defer kcs.RUnlock()
36         buf := kcs.blocks[locator[:32]]
37         if buf == nil {
38                 return 0, errStub404
39         }
40         return copy(p, buf[off:]), nil
41 }
42
43 func (kcs *keepClientStub) PutB(p []byte) (string, int, error) {
44         locator := fmt.Sprintf("%x+%d+A12345@abcde", md5.Sum(p), len(p))
45         buf := make([]byte, len(p))
46         copy(buf, p)
47         kcs.Lock()
48         defer kcs.Unlock()
49         kcs.blocks[locator[:32]] = buf
50         return locator, 1, nil
51 }
52
53 type CollectionFSSuite struct {
54         client *Client
55         coll   Collection
56         fs     CollectionFileSystem
57         kc     keepClient
58 }
59
60 func (s *CollectionFSSuite) SetUpTest(c *check.C) {
61         s.client = NewClientFromEnv()
62         err := s.client.RequestAndDecode(&s.coll, "GET", "arvados/v1/collections/"+arvadostest.FooAndBarFilesInDirUUID, nil, nil)
63         c.Assert(err, check.IsNil)
64         s.kc = &keepClientStub{
65                 blocks: map[string][]byte{
66                         "3858f62230ac3c915f300c664312c63f": []byte("foobar"),
67                 }}
68         s.fs, err = s.coll.FileSystem(s.client, s.kc)
69         c.Assert(err, check.IsNil)
70 }
71
72 func (s *CollectionFSSuite) TestHttpFileSystemInterface(c *check.C) {
73         _, ok := s.fs.(http.FileSystem)
74         c.Check(ok, check.Equals, true)
75 }
76
77 func (s *CollectionFSSuite) TestReaddirFull(c *check.C) {
78         f, err := s.fs.Open("/dir1")
79         c.Assert(err, check.IsNil)
80
81         st, err := f.Stat()
82         c.Assert(err, check.IsNil)
83         c.Check(st.Size(), check.Equals, int64(2))
84         c.Check(st.IsDir(), check.Equals, true)
85
86         fis, err := f.Readdir(0)
87         c.Check(err, check.IsNil)
88         c.Check(len(fis), check.Equals, 2)
89         if len(fis) > 0 {
90                 c.Check(fis[0].Size(), check.Equals, int64(3))
91         }
92 }
93
94 func (s *CollectionFSSuite) TestReaddirLimited(c *check.C) {
95         f, err := s.fs.Open("./dir1")
96         c.Assert(err, check.IsNil)
97
98         fis, err := f.Readdir(1)
99         c.Check(err, check.IsNil)
100         c.Check(len(fis), check.Equals, 1)
101         if len(fis) > 0 {
102                 c.Check(fis[0].Size(), check.Equals, int64(3))
103         }
104
105         fis, err = f.Readdir(1)
106         c.Check(err, check.IsNil)
107         c.Check(len(fis), check.Equals, 1)
108         if len(fis) > 0 {
109                 c.Check(fis[0].Size(), check.Equals, int64(3))
110         }
111
112         fis, err = f.Readdir(1)
113         c.Check(len(fis), check.Equals, 0)
114         c.Check(err, check.NotNil)
115         c.Check(err, check.Equals, io.EOF)
116
117         f, err = s.fs.Open("dir1")
118         c.Assert(err, check.IsNil)
119         fis, err = f.Readdir(1)
120         c.Check(len(fis), check.Equals, 1)
121         c.Assert(err, check.IsNil)
122         fis, err = f.Readdir(2)
123         c.Check(len(fis), check.Equals, 1)
124         c.Assert(err, check.IsNil)
125         fis, err = f.Readdir(2)
126         c.Check(len(fis), check.Equals, 0)
127         c.Assert(err, check.Equals, io.EOF)
128 }
129
130 func (s *CollectionFSSuite) TestPathMunge(c *check.C) {
131         for _, path := range []string{".", "/", "./", "///", "/../", "/./.."} {
132                 f, err := s.fs.Open(path)
133                 c.Assert(err, check.IsNil)
134
135                 st, err := f.Stat()
136                 c.Assert(err, check.IsNil)
137                 c.Check(st.Size(), check.Equals, int64(1))
138                 c.Check(st.IsDir(), check.Equals, true)
139         }
140         for _, path := range []string{"/dir1", "dir1", "./dir1", "///dir1//.//", "../dir1/../dir1/"} {
141                 c.Logf("%q", path)
142                 f, err := s.fs.Open(path)
143                 c.Assert(err, check.IsNil)
144
145                 st, err := f.Stat()
146                 c.Assert(err, check.IsNil)
147                 c.Check(st.Size(), check.Equals, int64(2))
148                 c.Check(st.IsDir(), check.Equals, true)
149         }
150 }
151
152 func (s *CollectionFSSuite) TestNotExist(c *check.C) {
153         for _, path := range []string{"/no", "no", "./no", "n/o", "/n/o"} {
154                 f, err := s.fs.Open(path)
155                 c.Assert(f, check.IsNil)
156                 c.Assert(err, check.NotNil)
157                 c.Assert(os.IsNotExist(err), check.Equals, true)
158         }
159 }
160
161 func (s *CollectionFSSuite) TestReadOnlyFile(c *check.C) {
162         f, err := s.fs.OpenFile("/dir1/foo", os.O_RDONLY, 0)
163         c.Assert(err, check.IsNil)
164         st, err := f.Stat()
165         c.Assert(err, check.IsNil)
166         c.Check(st.Size(), check.Equals, int64(3))
167         n, err := f.Write([]byte("bar"))
168         c.Check(n, check.Equals, 0)
169         c.Check(err, check.Equals, ErrReadOnlyFile)
170 }
171
172 func (s *CollectionFSSuite) TestCreateFile(c *check.C) {
173         f, err := s.fs.OpenFile("/newfile", os.O_RDWR|os.O_CREATE, 0)
174         c.Assert(err, check.IsNil)
175         st, err := f.Stat()
176         c.Assert(err, check.IsNil)
177         c.Check(st.Size(), check.Equals, int64(0))
178
179         n, err := f.Write([]byte("bar"))
180         c.Check(n, check.Equals, 3)
181         c.Check(err, check.IsNil)
182
183         c.Check(f.Close(), check.IsNil)
184
185         f, err = s.fs.OpenFile("/newfile", os.O_RDWR|os.O_CREATE|os.O_EXCL, 0)
186         c.Check(f, check.IsNil)
187         c.Assert(err, check.NotNil)
188
189         f, err = s.fs.OpenFile("/newfile", os.O_RDWR, 0)
190         c.Assert(err, check.IsNil)
191         st, err = f.Stat()
192         c.Assert(err, check.IsNil)
193         c.Check(st.Size(), check.Equals, int64(3))
194
195         c.Check(f.Close(), check.IsNil)
196
197         // TODO: serialize to Collection, confirm manifest contents,
198         // make new FileSystem, confirm file contents.
199 }
200
201 func (s *CollectionFSSuite) TestReadWriteFile(c *check.C) {
202         maxBlockSize = 8
203         defer func() { maxBlockSize = 2 << 26 }()
204
205         f, err := s.fs.OpenFile("/dir1/foo", os.O_RDWR, 0)
206         c.Assert(err, check.IsNil)
207         defer f.Close()
208         st, err := f.Stat()
209         c.Assert(err, check.IsNil)
210         c.Check(st.Size(), check.Equals, int64(3))
211
212         f2, err := s.fs.OpenFile("/dir1/foo", os.O_RDWR, 0)
213         c.Assert(err, check.IsNil)
214         defer f2.Close()
215
216         buf := make([]byte, 64)
217         n, err := f.Read(buf)
218         c.Check(n, check.Equals, 3)
219         c.Check(err, check.Equals, io.EOF)
220         c.Check(string(buf[:3]), check.DeepEquals, "foo")
221
222         pos, err := f.Seek(-2, os.SEEK_CUR)
223         c.Check(pos, check.Equals, int64(1))
224         c.Check(err, check.IsNil)
225
226         // Split a storedExtent in two, and insert a memExtent
227         n, err = f.Write([]byte("*"))
228         c.Check(n, check.Equals, 1)
229         c.Check(err, check.IsNil)
230
231         pos, err = f.Seek(0, os.SEEK_CUR)
232         c.Check(pos, check.Equals, int64(2))
233         c.Check(err, check.IsNil)
234
235         pos, err = f.Seek(0, os.SEEK_SET)
236         c.Check(pos, check.Equals, int64(0))
237         c.Check(err, check.IsNil)
238
239         rbuf, err := ioutil.ReadAll(f)
240         c.Check(len(rbuf), check.Equals, 3)
241         c.Check(err, check.IsNil)
242         c.Check(string(rbuf), check.Equals, "f*o")
243
244         // Write multiple blocks in one call
245         f.Seek(1, os.SEEK_SET)
246         n, err = f.Write([]byte("0123456789abcdefg"))
247         c.Check(n, check.Equals, 17)
248         c.Check(err, check.IsNil)
249         pos, err = f.Seek(0, os.SEEK_CUR)
250         c.Check(pos, check.Equals, int64(18))
251         pos, err = f.Seek(-18, os.SEEK_CUR)
252         c.Check(err, check.IsNil)
253         n, err = io.ReadFull(f, buf)
254         c.Check(n, check.Equals, 18)
255         c.Check(err, check.Equals, io.ErrUnexpectedEOF)
256         c.Check(string(buf[:n]), check.Equals, "f0123456789abcdefg")
257
258         buf2, err := ioutil.ReadAll(f2)
259         c.Check(err, check.IsNil)
260         c.Check(string(buf2), check.Equals, "f0123456789abcdefg")
261
262         // truncate to current size
263         err = f.Truncate(18)
264         f2.Seek(0, os.SEEK_SET)
265         buf2, err = ioutil.ReadAll(f2)
266         c.Check(err, check.IsNil)
267         c.Check(string(buf2), check.Equals, "f0123456789abcdefg")
268
269         // shrink to zero some data
270         f.Truncate(15)
271         f2.Seek(0, os.SEEK_SET)
272         buf2, err = ioutil.ReadAll(f2)
273         c.Check(err, check.IsNil)
274         c.Check(string(buf2), check.Equals, "f0123456789abcd")
275
276         // grow to partial block/extent
277         f.Truncate(20)
278         f2.Seek(0, os.SEEK_SET)
279         buf2, err = ioutil.ReadAll(f2)
280         c.Check(err, check.IsNil)
281         c.Check(string(buf2), check.Equals, "f0123456789abcd\x00\x00\x00\x00\x00")
282
283         f.Truncate(0)
284         f2.Write([]byte("12345678abcdefghijkl"))
285
286         // grow to block/extent boundary
287         f.Truncate(64)
288         f2.Seek(0, os.SEEK_SET)
289         buf2, err = ioutil.ReadAll(f2)
290         c.Check(err, check.IsNil)
291         c.Check(len(buf2), check.Equals, 64)
292         c.Check(len(f.(*file).inode.(*filenode).extents), check.Equals, 8)
293
294         // shrink to block/extent boundary
295         err = f.Truncate(32)
296         f2.Seek(0, os.SEEK_SET)
297         buf2, err = ioutil.ReadAll(f2)
298         c.Check(err, check.IsNil)
299         c.Check(len(buf2), check.Equals, 32)
300         c.Check(len(f.(*file).inode.(*filenode).extents), check.Equals, 4)
301
302         // shrink to partial block/extent
303         err = f.Truncate(15)
304         f2.Seek(0, os.SEEK_SET)
305         buf2, err = ioutil.ReadAll(f2)
306         c.Check(err, check.IsNil)
307         c.Check(string(buf2), check.Equals, "12345678abcdefg")
308         c.Check(len(f.(*file).inode.(*filenode).extents), check.Equals, 2)
309
310         // Force flush to ensure the block "12345678" gets stored, so
311         // we know what to expect in the final manifest below.
312         _, err = s.fs.MarshalManifest(".")
313         c.Check(err, check.IsNil)
314
315         // Truncate to size=3 while f2's ptr is at 15
316         err = f.Truncate(3)
317         c.Check(err, check.IsNil)
318         buf2, err = ioutil.ReadAll(f2)
319         c.Check(err, check.IsNil)
320         c.Check(string(buf2), check.Equals, "")
321         f2.Seek(0, os.SEEK_SET)
322         buf2, err = ioutil.ReadAll(f2)
323         c.Check(err, check.IsNil)
324         c.Check(string(buf2), check.Equals, "123")
325         c.Check(len(f.(*file).inode.(*filenode).extents), check.Equals, 1)
326
327         m, err := s.fs.MarshalManifest(".")
328         c.Check(err, check.IsNil)
329         m = regexp.MustCompile(`\+A[^\+ ]+`).ReplaceAllLiteralString(m, "")
330         c.Check(m, check.Equals, "./dir1 3858f62230ac3c915f300c664312c63f+6 25d55ad283aa400af464c76d713c07ad+8 3:3:bar 6:3:foo\n")
331 }
332
333 func (s *CollectionFSSuite) TestMarshalSmallBlocks(c *check.C) {
334         maxBlockSize = 8
335         defer func() { maxBlockSize = 2 << 26 }()
336
337         var err error
338         s.fs, err = (&Collection{}).FileSystem(s.client, s.kc)
339         c.Assert(err, check.IsNil)
340         for _, name := range []string{"foo", "bar", "baz"} {
341                 f, err := s.fs.OpenFile(name, os.O_WRONLY|os.O_CREATE, 0)
342                 c.Assert(err, check.IsNil)
343                 f.Write([]byte(name))
344                 f.Close()
345         }
346
347         m, err := s.fs.MarshalManifest(".")
348         c.Check(err, check.IsNil)
349         m = regexp.MustCompile(`\+A[^\+ ]+`).ReplaceAllLiteralString(m, "")
350         c.Check(m, check.Equals, ". c3c23db5285662ef7172373df0003206+6 acbd18db4cc2f85cedef654fccc4a4d8+3 0:3:bar 3:3:baz 6:3:foo\n")
351 }
352
353 func (s *CollectionFSSuite) TestMkdir(c *check.C) {
354         err := s.fs.Mkdir("foo/bar", 0755)
355         c.Check(err, check.Equals, os.ErrNotExist)
356
357         f, err := s.fs.OpenFile("foo/bar", os.O_CREATE, 0)
358         c.Check(err, check.Equals, os.ErrNotExist)
359
360         err = s.fs.Mkdir("foo", 0755)
361         c.Check(err, check.IsNil)
362
363         f, err = s.fs.OpenFile("foo/bar", os.O_CREATE|os.O_WRONLY, 0)
364         c.Check(err, check.IsNil)
365         if err == nil {
366                 defer f.Close()
367                 f.Write([]byte("foo"))
368         }
369
370         // mkdir fails if a file already exists with that name
371         err = s.fs.Mkdir("foo/bar", 0755)
372         c.Check(err, check.NotNil)
373
374         err = s.fs.Remove("foo/bar")
375         c.Check(err, check.IsNil)
376
377         // mkdir succeds after the file is deleted
378         err = s.fs.Mkdir("foo/bar", 0755)
379         c.Check(err, check.IsNil)
380
381         // creating a file in a nonexistent subdir should still fail
382         f, err = s.fs.OpenFile("foo/bar/baz/foo.txt", os.O_CREATE|os.O_WRONLY, 0)
383         c.Check(err, check.Equals, os.ErrNotExist)
384
385         f, err = s.fs.OpenFile("foo/bar/foo.txt", os.O_CREATE|os.O_WRONLY, 0)
386         c.Check(err, check.IsNil)
387         if err == nil {
388                 defer f.Close()
389                 f.Write([]byte("foo"))
390         }
391
392         // creating foo/bar as a regular file should fail
393         f, err = s.fs.OpenFile("foo/bar", os.O_CREATE|os.O_EXCL, 0)
394         c.Check(err, check.NotNil)
395
396         // creating foo/bar as a directory should fail
397         f, err = s.fs.OpenFile("foo/bar", os.O_CREATE|os.O_EXCL, os.ModeDir)
398         c.Check(err, check.NotNil)
399         err = s.fs.Mkdir("foo/bar", 0755)
400         c.Check(err, check.NotNil)
401
402         m, err := s.fs.MarshalManifest(".")
403         c.Check(err, check.IsNil)
404         m = regexp.MustCompile(`\+A[^\+ ]+`).ReplaceAllLiteralString(m, "")
405         c.Check(m, check.Equals, "./dir1 3858f62230ac3c915f300c664312c63f+6 3:3:bar 0:3:foo\n./foo/bar acbd18db4cc2f85cedef654fccc4a4d8+3 0:3:foo.txt\n")
406 }
407
408 func (s *CollectionFSSuite) TestConcurrentWriters(c *check.C) {
409         maxBlockSize = 8
410         defer func() { maxBlockSize = 2 << 26 }()
411
412         var wg sync.WaitGroup
413         for n := 0; n < 128; n++ {
414                 wg.Add(1)
415                 go func() {
416                         defer wg.Done()
417                         f, err := s.fs.OpenFile("/dir1/foo", os.O_RDWR, 0)
418                         c.Assert(err, check.IsNil)
419                         defer f.Close()
420                         for i := 0; i < 6502; i++ {
421                                 switch rand.Int() & 3 {
422                                 case 0:
423                                         f.Truncate(int64(rand.Intn(64)))
424                                 case 1:
425                                         f.Seek(int64(rand.Intn(64)), os.SEEK_SET)
426                                 case 2:
427                                         _, err := f.Write([]byte("beep boop"))
428                                         c.Check(err, check.IsNil)
429                                 case 3:
430                                         _, err := ioutil.ReadAll(f)
431                                         c.Check(err, check.IsNil)
432                                 }
433                         }
434                 }()
435         }
436         wg.Wait()
437
438         f, err := s.fs.OpenFile("/dir1/foo", os.O_RDWR, 0)
439         c.Assert(err, check.IsNil)
440         defer f.Close()
441         buf, err := ioutil.ReadAll(f)
442         c.Check(err, check.IsNil)
443         c.Logf("after lots of random r/w/seek/trunc, buf is %q", buf)
444 }
445
446 func (s *CollectionFSSuite) TestRandomWrites(c *check.C) {
447         maxBlockSize = 40
448         defer func() { maxBlockSize = 2 << 26 }()
449
450         var err error
451         s.fs, err = (&Collection{}).FileSystem(s.client, s.kc)
452         c.Assert(err, check.IsNil)
453
454         const nfiles = 256
455         const ngoroutines = 256
456
457         var wg sync.WaitGroup
458         for n := 0; n < nfiles; n++ {
459                 wg.Add(1)
460                 go func(n int) {
461                         defer wg.Done()
462                         expect := make([]byte, 0, 64)
463                         wbytes := []byte("there's no simple explanation for anything important that any of us do")
464                         f, err := s.fs.OpenFile(fmt.Sprintf("random-%d", n), os.O_RDWR|os.O_CREATE|os.O_EXCL, 0)
465                         c.Assert(err, check.IsNil)
466                         defer f.Close()
467                         for i := 0; i < ngoroutines; i++ {
468                                 trunc := rand.Intn(65)
469                                 woff := rand.Intn(trunc + 1)
470                                 wbytes = wbytes[:rand.Intn(64-woff+1)]
471                                 for buf, i := expect[:cap(expect)], len(expect); i < trunc; i++ {
472                                         buf[i] = 0
473                                 }
474                                 expect = expect[:trunc]
475                                 if trunc < woff+len(wbytes) {
476                                         expect = expect[:woff+len(wbytes)]
477                                 }
478                                 copy(expect[woff:], wbytes)
479                                 f.Truncate(int64(trunc))
480                                 pos, err := f.Seek(int64(woff), os.SEEK_SET)
481                                 c.Check(pos, check.Equals, int64(woff))
482                                 c.Check(err, check.IsNil)
483                                 n, err := f.Write(wbytes)
484                                 c.Check(n, check.Equals, len(wbytes))
485                                 c.Check(err, check.IsNil)
486                                 pos, err = f.Seek(0, os.SEEK_SET)
487                                 c.Check(pos, check.Equals, int64(0))
488                                 c.Check(err, check.IsNil)
489                                 buf, err := ioutil.ReadAll(f)
490                                 c.Check(string(buf), check.Equals, string(expect))
491                                 c.Check(err, check.IsNil)
492                         }
493                         s.checkMemSize(c, f)
494                 }(n)
495         }
496         wg.Wait()
497
498         root, err := s.fs.Open("/")
499         c.Assert(err, check.IsNil)
500         defer root.Close()
501         fi, err := root.Readdir(-1)
502         c.Check(err, check.IsNil)
503         c.Check(len(fi), check.Equals, nfiles)
504
505         _, err = s.fs.MarshalManifest(".")
506         c.Check(err, check.IsNil)
507         // TODO: check manifest content
508 }
509
510 func (s *CollectionFSSuite) TestPersist(c *check.C) {
511         maxBlockSize = 1024
512         defer func() { maxBlockSize = 2 << 26 }()
513
514         var err error
515         s.fs, err = (&Collection{}).FileSystem(s.client, s.kc)
516         c.Assert(err, check.IsNil)
517         err = s.fs.Mkdir("d:r", 0755)
518         c.Assert(err, check.IsNil)
519
520         expect := map[string][]byte{}
521
522         var wg sync.WaitGroup
523         for _, name := range []string{"random 1", "random:2", "random\\3", "d:r/random4"} {
524                 buf := make([]byte, 500)
525                 rand.Read(buf)
526                 expect[name] = buf
527
528                 f, err := s.fs.OpenFile(name, os.O_WRONLY|os.O_CREATE, 0)
529                 c.Assert(err, check.IsNil)
530                 // Note: we don't close the file until after the test
531                 // is done. Writes to unclosed files should persist.
532                 defer f.Close()
533
534                 wg.Add(1)
535                 go func() {
536                         defer wg.Done()
537                         for i := 0; i < len(buf); i += 5 {
538                                 _, err := f.Write(buf[i : i+5])
539                                 c.Assert(err, check.IsNil)
540                         }
541                 }()
542         }
543         wg.Wait()
544
545         m, err := s.fs.MarshalManifest(".")
546         c.Check(err, check.IsNil)
547         c.Logf("%q", m)
548
549         root, err := s.fs.Open("/")
550         c.Assert(err, check.IsNil)
551         defer root.Close()
552         fi, err := root.Readdir(-1)
553         c.Check(err, check.IsNil)
554         c.Check(len(fi), check.Equals, 4)
555
556         persisted, err := (&Collection{ManifestText: m}).FileSystem(s.client, s.kc)
557         c.Assert(err, check.IsNil)
558
559         root, err = persisted.Open("/")
560         c.Assert(err, check.IsNil)
561         defer root.Close()
562         fi, err = root.Readdir(-1)
563         c.Check(err, check.IsNil)
564         c.Check(len(fi), check.Equals, 4)
565
566         for name, content := range expect {
567                 c.Logf("read %q", name)
568                 f, err := persisted.Open(name)
569                 c.Assert(err, check.IsNil)
570                 defer f.Close()
571                 buf, err := ioutil.ReadAll(f)
572                 c.Check(err, check.IsNil)
573                 c.Check(buf, check.DeepEquals, content)
574         }
575 }
576
577 func (s *CollectionFSSuite) TestPersistEmptyFiles(c *check.C) {
578         var err error
579         s.fs, err = (&Collection{}).FileSystem(s.client, s.kc)
580         c.Assert(err, check.IsNil)
581         for _, name := range []string{"dir", "zero", "zero/zero"} {
582                 err = s.fs.Mkdir(name, 0755)
583                 c.Assert(err, check.IsNil)
584         }
585
586         expect := map[string][]byte{
587                 "0":              nil,
588                 "00":             []byte{},
589                 "one":            []byte{1},
590                 "dir/0":          nil,
591                 "dir/two":        []byte{1, 2},
592                 "dir/zero":       nil,
593                 "zero/zero/zero": nil,
594         }
595         for name, data := range expect {
596                 f, err := s.fs.OpenFile(name, os.O_WRONLY|os.O_CREATE, 0)
597                 c.Assert(err, check.IsNil)
598                 if data != nil {
599                         _, err := f.Write(data)
600                         c.Assert(err, check.IsNil)
601                 }
602                 f.Close()
603         }
604
605         m, err := s.fs.MarshalManifest(".")
606         c.Check(err, check.IsNil)
607         c.Logf("%q", m)
608
609         persisted, err := (&Collection{ManifestText: m}).FileSystem(s.client, s.kc)
610         c.Assert(err, check.IsNil)
611
612         for name, data := range expect {
613                 f, err := persisted.Open("bogus-" + name)
614                 c.Check(err, check.NotNil)
615
616                 f, err = persisted.Open(name)
617                 c.Assert(err, check.IsNil)
618
619                 if data == nil {
620                         data = []byte{}
621                 }
622                 buf, err := ioutil.ReadAll(f)
623                 c.Check(err, check.IsNil)
624                 c.Check(buf, check.DeepEquals, data)
625         }
626 }
627
628 func (s *CollectionFSSuite) TestFlushFullBlocks(c *check.C) {
629         maxBlockSize = 1024
630         defer func() { maxBlockSize = 2 << 26 }()
631
632         fs, err := (&Collection{}).FileSystem(s.client, s.kc)
633         c.Assert(err, check.IsNil)
634         f, err := fs.OpenFile("50K", os.O_WRONLY|os.O_CREATE, 0)
635         c.Assert(err, check.IsNil)
636         defer f.Close()
637
638         data := make([]byte, 500)
639         rand.Read(data)
640
641         for i := 0; i < 100; i++ {
642                 n, err := f.Write(data)
643                 c.Assert(n, check.Equals, len(data))
644                 c.Assert(err, check.IsNil)
645         }
646
647         currentMemExtents := func() (memExtents []int) {
648                 for idx, e := range f.(*file).inode.(*filenode).extents {
649                         switch e.(type) {
650                         case *memExtent:
651                                 memExtents = append(memExtents, idx)
652                         }
653                 }
654                 return
655         }
656         c.Check(currentMemExtents(), check.HasLen, 1)
657
658         m, err := fs.MarshalManifest(".")
659         c.Check(m, check.Not(check.Equals), "")
660         c.Check(err, check.IsNil)
661         c.Check(currentMemExtents(), check.HasLen, 0)
662 }
663
664 func (s *CollectionFSSuite) TestBrokenManifests(c *check.C) {
665         for _, txt := range []string{
666                 "\n",
667                 ".\n",
668                 ". \n",
669                 ". d41d8cd98f00b204e9800998ecf8427e+0\n",
670                 ". d41d8cd98f00b204e9800998ecf8427e+0 \n",
671                 ". 0:0:foo\n",
672                 ".  0:0:foo\n",
673                 ". 0:0:foo 0:0:bar\n",
674                 ". d41d8cd98f00b204e9800998ecf8427e 0:0:foo\n",
675                 ". d41d8cd98f00b204e9800998ecf8427e+0 0:0:foo:bar\n",
676                 ". d41d8cd98f00b204e9800998ecf8427e+0 foo:0:foo\n",
677                 ". d41d8cd98f00b204e9800998ecf8427e+0 0:foo:foo\n",
678                 ". d41d8cd98f00b204e9800998ecf8427e+1 0:1:foo 1:1:bar\n",
679                 ". d41d8cd98f00b204e9800998ecf8427e+1 0:0:foo\n./foo d41d8cd98f00b204e9800998ecf8427e+1 0:0:bar\n",
680                 "./foo d41d8cd98f00b204e9800998ecf8427e+1 0:0:bar\n. d41d8cd98f00b204e9800998ecf8427e+1 0:0:foo\n",
681         } {
682                 c.Logf("<-%q", txt)
683                 fs, err := (&Collection{ManifestText: txt}).FileSystem(s.client, s.kc)
684                 c.Check(fs, check.IsNil)
685                 c.Logf("-> %s", err)
686                 c.Check(err, check.NotNil)
687         }
688 }
689
690 func (s *CollectionFSSuite) TestEdgeCaseManifests(c *check.C) {
691         for _, txt := range []string{
692                 "",
693                 ". d41d8cd98f00b204e9800998ecf8427e+0 0:0:foo\n",
694                 ". d41d8cd98f00b204e9800998ecf8427e+0 0:0:foo 0:0:foo 0:0:bar\n",
695                 ". d41d8cd98f00b204e9800998ecf8427e+0 0:0:foo 0:0:foo 0:0:bar\n",
696                 ". d41d8cd98f00b204e9800998ecf8427e+0 0:0:foo/bar\n./foo d41d8cd98f00b204e9800998ecf8427e+0 0:0:bar\n",
697         } {
698                 c.Logf("<-%q", txt)
699                 fs, err := (&Collection{ManifestText: txt}).FileSystem(s.client, s.kc)
700                 c.Check(err, check.IsNil)
701                 c.Check(fs, check.NotNil)
702         }
703 }
704
705 func (s *CollectionFSSuite) checkMemSize(c *check.C, f File) {
706         fn := f.(*file).inode.(*filenode)
707         var memsize int64
708         for _, ext := range fn.extents {
709                 if e, ok := ext.(*memExtent); ok {
710                         memsize += int64(len(e.buf))
711                 }
712         }
713         c.Check(fn.memsize, check.Equals, memsize)
714 }
715
716 // Gocheck boilerplate
717 func Test(t *testing.T) {
718         check.TestingT(t)
719 }