// Copyright (C) The Arvados Authors. All rights reserved. // // SPDX-License-Identifier: Apache-2.0 package arvados import ( "bytes" "crypto/md5" "errors" "fmt" "io" "io/ioutil" "math/rand" "net/http" "os" "regexp" "runtime" "sync" "testing" "time" "git.curoverse.com/arvados.git/sdk/go/arvadostest" check "gopkg.in/check.v1" ) var _ = check.Suite(&CollectionFSSuite{}) type keepClientStub struct { blocks map[string][]byte sync.RWMutex } var errStub404 = errors.New("404 block not found") func (kcs *keepClientStub) ReadAt(locator string, p []byte, off int) (int, error) { kcs.RLock() defer kcs.RUnlock() buf := kcs.blocks[locator[:32]] if buf == nil { return 0, errStub404 } return copy(p, buf[off:]), nil } func (kcs *keepClientStub) PutB(p []byte) (string, int, error) { locator := fmt.Sprintf("%x+%d+A12345@abcde", md5.Sum(p), len(p)) buf := make([]byte, len(p)) copy(buf, p) kcs.Lock() defer kcs.Unlock() kcs.blocks[locator[:32]] = buf return locator, 1, nil } type CollectionFSSuite struct { client *Client coll Collection fs CollectionFileSystem kc keepClient } func (s *CollectionFSSuite) SetUpTest(c *check.C) { s.client = NewClientFromEnv() err := s.client.RequestAndDecode(&s.coll, "GET", "arvados/v1/collections/"+arvadostest.FooAndBarFilesInDirUUID, nil, nil) c.Assert(err, check.IsNil) s.kc = &keepClientStub{ blocks: map[string][]byte{ "3858f62230ac3c915f300c664312c63f": []byte("foobar"), }} s.fs, err = s.coll.FileSystem(s.client, s.kc) c.Assert(err, check.IsNil) } func (s *CollectionFSSuite) TestHttpFileSystemInterface(c *check.C) { _, ok := s.fs.(http.FileSystem) c.Check(ok, check.Equals, true) } func (s *CollectionFSSuite) TestReaddirFull(c *check.C) { f, err := s.fs.Open("/dir1") c.Assert(err, check.IsNil) st, err := f.Stat() c.Assert(err, check.IsNil) c.Check(st.Size(), check.Equals, int64(2)) c.Check(st.IsDir(), check.Equals, true) fis, err := f.Readdir(0) c.Check(err, check.IsNil) c.Check(len(fis), check.Equals, 2) if len(fis) > 0 { c.Check(fis[0].Size(), check.Equals, int64(3)) } } func (s *CollectionFSSuite) TestReaddirLimited(c *check.C) { f, err := s.fs.Open("./dir1") c.Assert(err, check.IsNil) fis, err := f.Readdir(1) c.Check(err, check.IsNil) c.Check(len(fis), check.Equals, 1) if len(fis) > 0 { c.Check(fis[0].Size(), check.Equals, int64(3)) } fis, err = f.Readdir(1) c.Check(err, check.IsNil) c.Check(len(fis), check.Equals, 1) if len(fis) > 0 { c.Check(fis[0].Size(), check.Equals, int64(3)) } fis, err = f.Readdir(1) c.Check(len(fis), check.Equals, 0) c.Check(err, check.NotNil) c.Check(err, check.Equals, io.EOF) f, err = s.fs.Open("dir1") c.Assert(err, check.IsNil) fis, err = f.Readdir(1) c.Check(len(fis), check.Equals, 1) c.Assert(err, check.IsNil) fis, err = f.Readdir(2) c.Check(len(fis), check.Equals, 1) c.Assert(err, check.IsNil) fis, err = f.Readdir(2) c.Check(len(fis), check.Equals, 0) c.Assert(err, check.Equals, io.EOF) } func (s *CollectionFSSuite) TestPathMunge(c *check.C) { for _, path := range []string{".", "/", "./", "///", "/../", "/./.."} { f, err := s.fs.Open(path) c.Assert(err, check.IsNil) st, err := f.Stat() c.Assert(err, check.IsNil) c.Check(st.Size(), check.Equals, int64(1)) c.Check(st.IsDir(), check.Equals, true) } for _, path := range []string{"/dir1", "dir1", "./dir1", "///dir1//.//", "../dir1/../dir1/"} { c.Logf("%q", path) f, err := s.fs.Open(path) c.Assert(err, check.IsNil) st, err := f.Stat() c.Assert(err, check.IsNil) c.Check(st.Size(), check.Equals, int64(2)) c.Check(st.IsDir(), check.Equals, true) } } func (s *CollectionFSSuite) TestNotExist(c *check.C) { for _, path := range []string{"/no", "no", "./no", "n/o", "/n/o"} { f, err := s.fs.Open(path) c.Assert(f, check.IsNil) c.Assert(err, check.NotNil) c.Assert(os.IsNotExist(err), check.Equals, true) } } func (s *CollectionFSSuite) TestReadOnlyFile(c *check.C) { f, err := s.fs.OpenFile("/dir1/foo", os.O_RDONLY, 0) c.Assert(err, check.IsNil) st, err := f.Stat() c.Assert(err, check.IsNil) c.Check(st.Size(), check.Equals, int64(3)) n, err := f.Write([]byte("bar")) c.Check(n, check.Equals, 0) c.Check(err, check.Equals, ErrReadOnlyFile) } func (s *CollectionFSSuite) TestCreateFile(c *check.C) { f, err := s.fs.OpenFile("/new-file 1", os.O_RDWR|os.O_CREATE, 0) c.Assert(err, check.IsNil) st, err := f.Stat() c.Assert(err, check.IsNil) c.Check(st.Size(), check.Equals, int64(0)) n, err := f.Write([]byte("bar")) c.Check(n, check.Equals, 3) c.Check(err, check.IsNil) c.Check(f.Close(), check.IsNil) f, err = s.fs.OpenFile("/new-file 1", os.O_RDWR|os.O_CREATE|os.O_EXCL, 0) c.Check(f, check.IsNil) c.Assert(err, check.NotNil) f, err = s.fs.OpenFile("/new-file 1", os.O_RDWR, 0) c.Assert(err, check.IsNil) st, err = f.Stat() c.Assert(err, check.IsNil) c.Check(st.Size(), check.Equals, int64(3)) c.Check(f.Close(), check.IsNil) m, err := s.fs.MarshalManifest(".") c.Check(m, check.Matches, `. 37b51d194a7513e45b56f6524f2d51f2\+3\+\S+ 0:3:new-file\\0401\n./dir1 .* 3:3:bar 0:3:foo\n`) } func (s *CollectionFSSuite) TestReadWriteFile(c *check.C) { maxBlockSize = 8 defer func() { maxBlockSize = 2 << 26 }() f, err := s.fs.OpenFile("/dir1/foo", os.O_RDWR, 0) c.Assert(err, check.IsNil) defer f.Close() st, err := f.Stat() c.Assert(err, check.IsNil) c.Check(st.Size(), check.Equals, int64(3)) f2, err := s.fs.OpenFile("/dir1/foo", os.O_RDWR, 0) c.Assert(err, check.IsNil) defer f2.Close() buf := make([]byte, 64) n, err := f.Read(buf) c.Check(n, check.Equals, 3) c.Check(err, check.Equals, io.EOF) c.Check(string(buf[:3]), check.DeepEquals, "foo") pos, err := f.Seek(-2, io.SeekCurrent) c.Check(pos, check.Equals, int64(1)) c.Check(err, check.IsNil) // Split a storedExtent in two, and insert a memExtent n, err = f.Write([]byte("*")) c.Check(n, check.Equals, 1) c.Check(err, check.IsNil) pos, err = f.Seek(0, io.SeekCurrent) c.Check(pos, check.Equals, int64(2)) c.Check(err, check.IsNil) pos, err = f.Seek(0, io.SeekStart) c.Check(pos, check.Equals, int64(0)) c.Check(err, check.IsNil) rbuf, err := ioutil.ReadAll(f) c.Check(len(rbuf), check.Equals, 3) c.Check(err, check.IsNil) c.Check(string(rbuf), check.Equals, "f*o") // Write multiple blocks in one call f.Seek(1, io.SeekStart) n, err = f.Write([]byte("0123456789abcdefg")) c.Check(n, check.Equals, 17) c.Check(err, check.IsNil) pos, err = f.Seek(0, io.SeekCurrent) c.Check(pos, check.Equals, int64(18)) pos, err = f.Seek(-18, io.SeekCurrent) c.Check(err, check.IsNil) n, err = io.ReadFull(f, buf) c.Check(n, check.Equals, 18) c.Check(err, check.Equals, io.ErrUnexpectedEOF) c.Check(string(buf[:n]), check.Equals, "f0123456789abcdefg") buf2, err := ioutil.ReadAll(f2) c.Check(err, check.IsNil) c.Check(string(buf2), check.Equals, "f0123456789abcdefg") // truncate to current size err = f.Truncate(18) f2.Seek(0, io.SeekStart) buf2, err = ioutil.ReadAll(f2) c.Check(err, check.IsNil) c.Check(string(buf2), check.Equals, "f0123456789abcdefg") // shrink to zero some data f.Truncate(15) f2.Seek(0, io.SeekStart) buf2, err = ioutil.ReadAll(f2) c.Check(err, check.IsNil) c.Check(string(buf2), check.Equals, "f0123456789abcd") // grow to partial block/extent f.Truncate(20) f2.Seek(0, io.SeekStart) buf2, err = ioutil.ReadAll(f2) c.Check(err, check.IsNil) c.Check(string(buf2), check.Equals, "f0123456789abcd\x00\x00\x00\x00\x00") f.Truncate(0) f2.Seek(0, io.SeekStart) f2.Write([]byte("12345678abcdefghijkl")) // grow to block/extent boundary f.Truncate(64) f2.Seek(0, io.SeekStart) buf2, err = ioutil.ReadAll(f2) c.Check(err, check.IsNil) c.Check(len(buf2), check.Equals, 64) c.Check(len(f.(*filehandle).inode.(*filenode).segments), check.Equals, 8) // shrink to block/extent boundary err = f.Truncate(32) f2.Seek(0, io.SeekStart) buf2, err = ioutil.ReadAll(f2) c.Check(err, check.IsNil) c.Check(len(buf2), check.Equals, 32) c.Check(len(f.(*filehandle).inode.(*filenode).segments), check.Equals, 4) // shrink to partial block/extent err = f.Truncate(15) f2.Seek(0, io.SeekStart) buf2, err = ioutil.ReadAll(f2) c.Check(err, check.IsNil) c.Check(string(buf2), check.Equals, "12345678abcdefg") c.Check(len(f.(*filehandle).inode.(*filenode).segments), check.Equals, 2) // Force flush to ensure the block "12345678" gets stored, so // we know what to expect in the final manifest below. _, err = s.fs.MarshalManifest(".") c.Check(err, check.IsNil) // Truncate to size=3 while f2's ptr is at 15 err = f.Truncate(3) c.Check(err, check.IsNil) buf2, err = ioutil.ReadAll(f2) c.Check(err, check.IsNil) c.Check(string(buf2), check.Equals, "") f2.Seek(0, io.SeekStart) buf2, err = ioutil.ReadAll(f2) c.Check(err, check.IsNil) c.Check(string(buf2), check.Equals, "123") c.Check(len(f.(*filehandle).inode.(*filenode).segments), check.Equals, 1) m, err := s.fs.MarshalManifest(".") c.Check(err, check.IsNil) m = regexp.MustCompile(`\+A[^\+ ]+`).ReplaceAllLiteralString(m, "") c.Check(m, check.Equals, "./dir1 3858f62230ac3c915f300c664312c63f+6 25d55ad283aa400af464c76d713c07ad+8 3:3:bar 6:3:foo\n") } func (s *CollectionFSSuite) TestSeekSparse(c *check.C) { fs, err := (&Collection{}).FileSystem(s.client, s.kc) c.Assert(err, check.IsNil) f, err := fs.OpenFile("test", os.O_CREATE|os.O_RDWR, 0755) c.Assert(err, check.IsNil) defer f.Close() checkSize := func(size int64) { fi, err := f.Stat() c.Check(fi.Size(), check.Equals, size) f, err := fs.OpenFile("test", os.O_CREATE|os.O_RDWR, 0755) c.Assert(err, check.IsNil) defer f.Close() fi, err = f.Stat() c.Check(fi.Size(), check.Equals, size) pos, err := f.Seek(0, io.SeekEnd) c.Check(pos, check.Equals, size) } f.Seek(2, io.SeekEnd) checkSize(0) f.Write([]byte{1}) checkSize(3) f.Seek(2, io.SeekCurrent) checkSize(3) f.Write([]byte{}) checkSize(5) f.Seek(8, io.SeekStart) checkSize(5) n, err := f.Read(make([]byte, 1)) c.Check(n, check.Equals, 0) c.Check(err, check.Equals, io.EOF) checkSize(5) f.Write([]byte{1, 2, 3}) checkSize(11) } func (s *CollectionFSSuite) TestMarshalSmallBlocks(c *check.C) { maxBlockSize = 8 defer func() { maxBlockSize = 2 << 26 }() var err error s.fs, err = (&Collection{}).FileSystem(s.client, s.kc) c.Assert(err, check.IsNil) for _, name := range []string{"foo", "bar", "baz"} { f, err := s.fs.OpenFile(name, os.O_WRONLY|os.O_CREATE, 0) c.Assert(err, check.IsNil) f.Write([]byte(name)) f.Close() } m, err := s.fs.MarshalManifest(".") c.Check(err, check.IsNil) m = regexp.MustCompile(`\+A[^\+ ]+`).ReplaceAllLiteralString(m, "") c.Check(m, check.Equals, ". c3c23db5285662ef7172373df0003206+6 acbd18db4cc2f85cedef654fccc4a4d8+3 0:3:bar 3:3:baz 6:3:foo\n") } func (s *CollectionFSSuite) TestMkdir(c *check.C) { err := s.fs.Mkdir("foo/bar", 0755) c.Check(err, check.Equals, os.ErrNotExist) f, err := s.fs.OpenFile("foo/bar", os.O_CREATE, 0) c.Check(err, check.Equals, os.ErrNotExist) err = s.fs.Mkdir("foo", 0755) c.Check(err, check.IsNil) f, err = s.fs.OpenFile("foo/bar", os.O_CREATE|os.O_WRONLY, 0) c.Check(err, check.IsNil) if err == nil { defer f.Close() f.Write([]byte("foo")) } // mkdir fails if a file already exists with that name err = s.fs.Mkdir("foo/bar", 0755) c.Check(err, check.NotNil) err = s.fs.Remove("foo/bar") c.Check(err, check.IsNil) // mkdir succeds after the file is deleted err = s.fs.Mkdir("foo/bar", 0755) c.Check(err, check.IsNil) // creating a file in a nonexistent subdir should still fail f, err = s.fs.OpenFile("foo/bar/baz/foo.txt", os.O_CREATE|os.O_WRONLY, 0) c.Check(err, check.Equals, os.ErrNotExist) f, err = s.fs.OpenFile("foo/bar/foo.txt", os.O_CREATE|os.O_WRONLY, 0) c.Check(err, check.IsNil) if err == nil { defer f.Close() f.Write([]byte("foo")) } // creating foo/bar as a regular file should fail f, err = s.fs.OpenFile("foo/bar", os.O_CREATE|os.O_EXCL, 0) c.Check(err, check.NotNil) // creating foo/bar as a directory should fail f, err = s.fs.OpenFile("foo/bar", os.O_CREATE|os.O_EXCL, os.ModeDir) c.Check(err, check.NotNil) err = s.fs.Mkdir("foo/bar", 0755) c.Check(err, check.NotNil) m, err := s.fs.MarshalManifest(".") c.Check(err, check.IsNil) m = regexp.MustCompile(`\+A[^\+ ]+`).ReplaceAllLiteralString(m, "") c.Check(m, check.Equals, "./dir1 3858f62230ac3c915f300c664312c63f+6 3:3:bar 0:3:foo\n./foo/bar acbd18db4cc2f85cedef654fccc4a4d8+3 0:3:foo.txt\n") } func (s *CollectionFSSuite) TestConcurrentWriters(c *check.C) { maxBlockSize = 8 defer func() { maxBlockSize = 2 << 26 }() var wg sync.WaitGroup for n := 0; n < 128; n++ { wg.Add(1) go func() { defer wg.Done() f, err := s.fs.OpenFile("/dir1/foo", os.O_RDWR, 0) c.Assert(err, check.IsNil) defer f.Close() for i := 0; i < 6502; i++ { switch rand.Int() & 3 { case 0: f.Truncate(int64(rand.Intn(64))) case 1: f.Seek(int64(rand.Intn(64)), io.SeekStart) case 2: _, err := f.Write([]byte("beep boop")) c.Check(err, check.IsNil) case 3: _, err := ioutil.ReadAll(f) c.Check(err, check.IsNil) } } }() } wg.Wait() f, err := s.fs.OpenFile("/dir1/foo", os.O_RDWR, 0) c.Assert(err, check.IsNil) defer f.Close() buf, err := ioutil.ReadAll(f) c.Check(err, check.IsNil) c.Logf("after lots of random r/w/seek/trunc, buf is %q", buf) } func (s *CollectionFSSuite) TestRandomWrites(c *check.C) { maxBlockSize = 40 defer func() { maxBlockSize = 2 << 26 }() var err error s.fs, err = (&Collection{}).FileSystem(s.client, s.kc) c.Assert(err, check.IsNil) const nfiles = 256 const ngoroutines = 256 var wg sync.WaitGroup for n := 0; n < nfiles; n++ { wg.Add(1) go func(n int) { defer wg.Done() expect := make([]byte, 0, 64) wbytes := []byte("there's no simple explanation for anything important that any of us do") f, err := s.fs.OpenFile(fmt.Sprintf("random-%d", n), os.O_RDWR|os.O_CREATE|os.O_EXCL, 0) c.Assert(err, check.IsNil) defer f.Close() for i := 0; i < ngoroutines; i++ { trunc := rand.Intn(65) woff := rand.Intn(trunc + 1) wbytes = wbytes[:rand.Intn(64-woff+1)] for buf, i := expect[:cap(expect)], len(expect); i < trunc; i++ { buf[i] = 0 } expect = expect[:trunc] if trunc < woff+len(wbytes) { expect = expect[:woff+len(wbytes)] } copy(expect[woff:], wbytes) f.Truncate(int64(trunc)) pos, err := f.Seek(int64(woff), io.SeekStart) c.Check(pos, check.Equals, int64(woff)) c.Check(err, check.IsNil) n, err := f.Write(wbytes) c.Check(n, check.Equals, len(wbytes)) c.Check(err, check.IsNil) pos, err = f.Seek(0, io.SeekStart) c.Check(pos, check.Equals, int64(0)) c.Check(err, check.IsNil) buf, err := ioutil.ReadAll(f) c.Check(string(buf), check.Equals, string(expect)) c.Check(err, check.IsNil) } s.checkMemSize(c, f) }(n) } wg.Wait() root, err := s.fs.Open("/") c.Assert(err, check.IsNil) defer root.Close() fi, err := root.Readdir(-1) c.Check(err, check.IsNil) c.Check(len(fi), check.Equals, nfiles) _, err = s.fs.MarshalManifest(".") c.Check(err, check.IsNil) // TODO: check manifest content } func (s *CollectionFSSuite) TestRemove(c *check.C) { fs, err := (&Collection{}).FileSystem(s.client, s.kc) c.Assert(err, check.IsNil) err = fs.Mkdir("dir0", 0755) c.Assert(err, check.IsNil) err = fs.Mkdir("dir1", 0755) c.Assert(err, check.IsNil) err = fs.Mkdir("dir1/dir2", 0755) c.Assert(err, check.IsNil) err = fs.Mkdir("dir1/dir3", 0755) c.Assert(err, check.IsNil) err = fs.Remove("dir0") c.Check(err, check.IsNil) err = fs.Remove("dir0") c.Check(err, check.Equals, os.ErrNotExist) err = fs.Remove("dir1/dir2/.") c.Check(err, check.Equals, ErrInvalidArgument) err = fs.Remove("dir1/dir2/..") c.Check(err, check.Equals, ErrInvalidArgument) err = fs.Remove("dir1") c.Check(err, check.Equals, ErrDirectoryNotEmpty) err = fs.Remove("dir1/dir2/../../../dir1") c.Check(err, check.Equals, ErrDirectoryNotEmpty) err = fs.Remove("dir1/dir3/") c.Check(err, check.IsNil) err = fs.RemoveAll("dir1") c.Check(err, check.IsNil) err = fs.RemoveAll("dir1") c.Check(err, check.IsNil) } func (s *CollectionFSSuite) TestRename(c *check.C) { fs, err := (&Collection{}).FileSystem(s.client, s.kc) c.Assert(err, check.IsNil) const ( outer = 16 inner = 16 ) for i := 0; i < outer; i++ { err = fs.Mkdir(fmt.Sprintf("dir%d", i), 0755) c.Assert(err, check.IsNil) for j := 0; j < inner; j++ { err = fs.Mkdir(fmt.Sprintf("dir%d/dir%d", i, j), 0755) c.Assert(err, check.IsNil) for _, fnm := range []string{ fmt.Sprintf("dir%d/file%d", i, j), fmt.Sprintf("dir%d/dir%d/file%d", i, j, j), } { f, err := fs.OpenFile(fnm, os.O_CREATE|os.O_WRONLY, 0755) c.Assert(err, check.IsNil) _, err = f.Write([]byte("beep")) c.Assert(err, check.IsNil) f.Close() } } } var wg sync.WaitGroup for i := 0; i < outer; i++ { for j := 0; j < inner; j++ { wg.Add(1) go func(i, j int) { defer wg.Done() oldname := fmt.Sprintf("dir%d/dir%d/file%d", i, j, j) newname := fmt.Sprintf("dir%d/newfile%d", i, inner-j-1) _, err := fs.Open(newname) c.Check(err, check.Equals, os.ErrNotExist) err = fs.Rename(oldname, newname) c.Check(err, check.IsNil) f, err := fs.Open(newname) c.Check(err, check.IsNil) f.Close() }(i, j) wg.Add(1) go func(i, j int) { defer wg.Done() // oldname does not exist err := fs.Rename( fmt.Sprintf("dir%d/dir%d/missing", i, j), fmt.Sprintf("dir%d/dir%d/file%d", outer-i-1, j, j)) c.Check(err, check.ErrorMatches, `.*does not exist`) // newname parent dir does not exist err = fs.Rename( fmt.Sprintf("dir%d/dir%d", i, j), fmt.Sprintf("dir%d/missing/irrelevant", outer-i-1)) c.Check(err, check.ErrorMatches, `.*does not exist`) // oldname parent dir is a file err = fs.Rename( fmt.Sprintf("dir%d/file%d/patherror", i, j), fmt.Sprintf("dir%d/irrelevant", i)) c.Check(err, check.ErrorMatches, `.*does not exist`) // newname parent dir is a file err = fs.Rename( fmt.Sprintf("dir%d/dir%d/file%d", i, j, j), fmt.Sprintf("dir%d/file%d/patherror", i, inner-j-1)) c.Check(err, check.ErrorMatches, `.*does not exist`) }(i, j) } } wg.Wait() f, err := fs.OpenFile("dir1/newfile3", 0, 0) c.Assert(err, check.IsNil) c.Check(f.Size(), check.Equals, int64(4)) buf, err := ioutil.ReadAll(f) c.Check(buf, check.DeepEquals, []byte("beep")) c.Check(err, check.IsNil) _, err = fs.Open("dir1/dir1/file1") c.Check(err, check.Equals, os.ErrNotExist) } func (s *CollectionFSSuite) TestPersist(c *check.C) { maxBlockSize = 1024 defer func() { maxBlockSize = 2 << 26 }() var err error s.fs, err = (&Collection{}).FileSystem(s.client, s.kc) c.Assert(err, check.IsNil) err = s.fs.Mkdir("d:r", 0755) c.Assert(err, check.IsNil) expect := map[string][]byte{} var wg sync.WaitGroup for _, name := range []string{"random 1", "random:2", "random\\3", "d:r/random4"} { buf := make([]byte, 500) rand.Read(buf) expect[name] = buf f, err := s.fs.OpenFile(name, os.O_WRONLY|os.O_CREATE, 0) c.Assert(err, check.IsNil) // Note: we don't close the file until after the test // is done. Writes to unclosed files should persist. defer f.Close() wg.Add(1) go func() { defer wg.Done() for i := 0; i < len(buf); i += 5 { _, err := f.Write(buf[i : i+5]) c.Assert(err, check.IsNil) } }() } wg.Wait() m, err := s.fs.MarshalManifest(".") c.Check(err, check.IsNil) c.Logf("%q", m) root, err := s.fs.Open("/") c.Assert(err, check.IsNil) defer root.Close() fi, err := root.Readdir(-1) c.Check(err, check.IsNil) c.Check(len(fi), check.Equals, 4) persisted, err := (&Collection{ManifestText: m}).FileSystem(s.client, s.kc) c.Assert(err, check.IsNil) root, err = persisted.Open("/") c.Assert(err, check.IsNil) defer root.Close() fi, err = root.Readdir(-1) c.Check(err, check.IsNil) c.Check(len(fi), check.Equals, 4) for name, content := range expect { c.Logf("read %q", name) f, err := persisted.Open(name) c.Assert(err, check.IsNil) defer f.Close() buf, err := ioutil.ReadAll(f) c.Check(err, check.IsNil) c.Check(buf, check.DeepEquals, content) } } func (s *CollectionFSSuite) TestPersistEmptyFiles(c *check.C) { var err error s.fs, err = (&Collection{}).FileSystem(s.client, s.kc) c.Assert(err, check.IsNil) for _, name := range []string{"dir", "dir/zerodir", "zero", "zero/zero"} { err = s.fs.Mkdir(name, 0755) c.Assert(err, check.IsNil) } expect := map[string][]byte{ "0": nil, "00": []byte{}, "one": []byte{1}, "dir/0": nil, "dir/two": []byte{1, 2}, "dir/zero": nil, "dir/zerodir/zero": nil, "zero/zero/zero": nil, } for name, data := range expect { f, err := s.fs.OpenFile(name, os.O_WRONLY|os.O_CREATE, 0) c.Assert(err, check.IsNil) if data != nil { _, err := f.Write(data) c.Assert(err, check.IsNil) } f.Close() } m, err := s.fs.MarshalManifest(".") c.Check(err, check.IsNil) c.Logf("%q", m) persisted, err := (&Collection{ManifestText: m}).FileSystem(s.client, s.kc) c.Assert(err, check.IsNil) for name, data := range expect { f, err := persisted.Open("bogus-" + name) c.Check(err, check.NotNil) f, err = persisted.Open(name) c.Assert(err, check.IsNil) if data == nil { data = []byte{} } buf, err := ioutil.ReadAll(f) c.Check(err, check.IsNil) c.Check(buf, check.DeepEquals, data) } } func (s *CollectionFSSuite) TestOpenFileFlags(c *check.C) { fs, err := (&Collection{}).FileSystem(s.client, s.kc) c.Assert(err, check.IsNil) f, err := fs.OpenFile("missing", os.O_WRONLY, 0) c.Check(f, check.IsNil) c.Check(err, check.ErrorMatches, `file does not exist`) f, err = fs.OpenFile("new", os.O_CREATE|os.O_RDONLY, 0) c.Assert(err, check.IsNil) defer f.Close() n, err := f.Write([]byte{1, 2, 3}) c.Check(n, check.Equals, 0) c.Check(err, check.ErrorMatches, `read-only file`) n, err = f.Read(make([]byte, 1)) c.Check(n, check.Equals, 0) c.Check(err, check.Equals, io.EOF) f, err = fs.OpenFile("new", os.O_RDWR, 0) c.Assert(err, check.IsNil) defer f.Close() _, err = f.Write([]byte{4, 5, 6}) c.Check(err, check.IsNil) fi, err := f.Stat() c.Assert(err, check.IsNil) c.Check(fi.Size(), check.Equals, int64(3)) f, err = fs.OpenFile("new", os.O_TRUNC|os.O_RDWR, 0) c.Assert(err, check.IsNil) defer f.Close() pos, err := f.Seek(0, io.SeekEnd) c.Check(pos, check.Equals, int64(0)) c.Check(err, check.IsNil) fi, err = f.Stat() c.Assert(err, check.IsNil) c.Check(fi.Size(), check.Equals, int64(0)) fs.Remove("new") buf := make([]byte, 64) f, err = fs.OpenFile("append", os.O_EXCL|os.O_CREATE|os.O_RDWR|os.O_APPEND, 0) c.Assert(err, check.IsNil) f.Write([]byte{1, 2, 3}) f.Seek(0, io.SeekStart) n, _ = f.Read(buf[:1]) c.Check(n, check.Equals, 1) c.Check(buf[:1], check.DeepEquals, []byte{1}) pos, err = f.Seek(0, io.SeekCurrent) c.Check(pos, check.Equals, int64(1)) f.Write([]byte{4, 5, 6}) pos, err = f.Seek(0, io.SeekCurrent) c.Check(pos, check.Equals, int64(6)) f.Seek(0, io.SeekStart) n, err = f.Read(buf) c.Check(buf[:n], check.DeepEquals, []byte{1, 2, 3, 4, 5, 6}) c.Check(err, check.Equals, io.EOF) f.Close() f, err = fs.OpenFile("append", os.O_RDWR|os.O_APPEND, 0) c.Assert(err, check.IsNil) pos, err = f.Seek(0, io.SeekCurrent) c.Check(pos, check.Equals, int64(0)) c.Check(err, check.IsNil) f.Read(buf[:3]) pos, _ = f.Seek(0, io.SeekCurrent) c.Check(pos, check.Equals, int64(3)) f.Write([]byte{7, 8, 9}) pos, err = f.Seek(0, io.SeekCurrent) c.Check(pos, check.Equals, int64(9)) f.Close() f, err = fs.OpenFile("wronly", os.O_CREATE|os.O_WRONLY, 0) c.Assert(err, check.IsNil) n, err = f.Write([]byte{3, 2, 1}) c.Check(n, check.Equals, 3) c.Check(err, check.IsNil) pos, _ = f.Seek(0, io.SeekCurrent) c.Check(pos, check.Equals, int64(3)) pos, _ = f.Seek(0, io.SeekStart) c.Check(pos, check.Equals, int64(0)) n, err = f.Read(buf) c.Check(n, check.Equals, 0) c.Check(err, check.ErrorMatches, `.*O_WRONLY.*`) f, err = fs.OpenFile("wronly", os.O_RDONLY, 0) c.Assert(err, check.IsNil) n, _ = f.Read(buf) c.Check(buf[:n], check.DeepEquals, []byte{3, 2, 1}) f, err = fs.OpenFile("unsupported", os.O_CREATE|os.O_SYNC, 0) c.Check(f, check.IsNil) c.Check(err, check.NotNil) f, err = fs.OpenFile("append", os.O_RDWR|os.O_WRONLY, 0) c.Check(f, check.IsNil) c.Check(err, check.ErrorMatches, `invalid flag.*`) } func (s *CollectionFSSuite) TestFlushFullBlocks(c *check.C) { maxBlockSize = 1024 defer func() { maxBlockSize = 2 << 26 }() fs, err := (&Collection{}).FileSystem(s.client, s.kc) c.Assert(err, check.IsNil) f, err := fs.OpenFile("50K", os.O_WRONLY|os.O_CREATE, 0) c.Assert(err, check.IsNil) defer f.Close() data := make([]byte, 500) rand.Read(data) for i := 0; i < 100; i++ { n, err := f.Write(data) c.Assert(n, check.Equals, len(data)) c.Assert(err, check.IsNil) } currentMemExtents := func() (memExtents []int) { for idx, e := range f.(*filehandle).inode.(*filenode).segments { switch e.(type) { case *memSegment: memExtents = append(memExtents, idx) } } return } c.Check(currentMemExtents(), check.HasLen, 1) m, err := fs.MarshalManifest(".") c.Check(m, check.Matches, `[^:]* 0:50000:50K\n`) c.Check(err, check.IsNil) c.Check(currentMemExtents(), check.HasLen, 0) } func (s *CollectionFSSuite) TestBrokenManifests(c *check.C) { for _, txt := range []string{ "\n", ".\n", ". \n", ". d41d8cd98f00b204e9800998ecf8427e+0\n", ". d41d8cd98f00b204e9800998ecf8427e+0 \n", ". 0:0:foo\n", ". 0:0:foo\n", ". 0:0:foo 0:0:bar\n", ". d41d8cd98f00b204e9800998ecf8427e 0:0:foo\n", ". d41d8cd98f00b204e9800998ecf8427e+0 0:0:foo:bar\n", ". d41d8cd98f00b204e9800998ecf8427e+0 foo:0:foo\n", ". d41d8cd98f00b204e9800998ecf8427e+0 0:foo:foo\n", ". d41d8cd98f00b204e9800998ecf8427e+1 0:1:foo 1:1:bar\n", ". d41d8cd98f00b204e9800998ecf8427e+1 0:0:foo\n./foo d41d8cd98f00b204e9800998ecf8427e+1 0:0:bar\n", "./foo d41d8cd98f00b204e9800998ecf8427e+1 0:0:bar\n. d41d8cd98f00b204e9800998ecf8427e+1 0:0:foo\n", } { c.Logf("<-%q", txt) fs, err := (&Collection{ManifestText: txt}).FileSystem(s.client, s.kc) c.Check(fs, check.IsNil) c.Logf("-> %s", err) c.Check(err, check.NotNil) } } func (s *CollectionFSSuite) TestEdgeCaseManifests(c *check.C) { for _, txt := range []string{ "", ". d41d8cd98f00b204e9800998ecf8427e+0 0:0:foo\n", ". d41d8cd98f00b204e9800998ecf8427e+0 0:0:foo 0:0:foo 0:0:bar\n", ". d41d8cd98f00b204e9800998ecf8427e+0 0:0:foo 0:0:foo 0:0:bar\n", ". d41d8cd98f00b204e9800998ecf8427e+0 0:0:foo/bar\n./foo d41d8cd98f00b204e9800998ecf8427e+0 0:0:bar\n", } { c.Logf("<-%q", txt) fs, err := (&Collection{ManifestText: txt}).FileSystem(s.client, s.kc) c.Check(err, check.IsNil) c.Check(fs, check.NotNil) } } func (s *CollectionFSSuite) checkMemSize(c *check.C, f File) { fn := f.(*filehandle).inode.(*filenode) var memsize int64 for _, seg := range fn.segments { if e, ok := seg.(*memSegment); ok { memsize += int64(len(e.buf)) } } c.Check(fn.memsize, check.Equals, memsize) } type CollectionFSUnitSuite struct{} var _ = check.Suite(&CollectionFSUnitSuite{}) // expect ~2 seconds to load a manifest with 256K files func (s *CollectionFSUnitSuite) TestLargeManifest(c *check.C) { const ( dirCount = 512 fileCount = 512 ) mb := bytes.NewBuffer(make([]byte, 0, 40000000)) for i := 0; i < dirCount; i++ { fmt.Fprintf(mb, "./dir%d", i) for j := 0; j <= fileCount; j++ { fmt.Fprintf(mb, " %032x+42+A%040x@%08x", j, j, j) } for j := 0; j < fileCount; j++ { fmt.Fprintf(mb, " %d:%d:dir%d/file%d", j*42+21, 42, j, j) } mb.Write([]byte{'\n'}) } coll := Collection{ManifestText: mb.String()} c.Logf("%s built", time.Now()) var memstats runtime.MemStats runtime.ReadMemStats(&memstats) c.Logf("%s Alloc=%d Sys=%d", time.Now(), memstats.Alloc, memstats.Sys) f, err := coll.FileSystem(nil, nil) c.Check(err, check.IsNil) c.Logf("%s loaded", time.Now()) for i := 0; i < dirCount; i++ { for j := 0; j < fileCount; j++ { f.Stat(fmt.Sprintf("./dir%d/dir%d/file%d", i, j, j)) } } c.Logf("%s Stat() x %d", time.Now(), dirCount*fileCount) runtime.ReadMemStats(&memstats) c.Logf("%s Alloc=%d Sys=%d", time.Now(), memstats.Alloc, memstats.Sys) } // Gocheck boilerplate func Test(t *testing.T) { check.TestingT(t) }