12483: Add MarshalManifest().
[arvados.git] / sdk / go / arvados / collection_fs_test.go
index e4eeeffa209f958be4cd0eecf79fafbb0e0ae52d..70d46e234c37b95477e530d704c9dde716a57ebc 100644 (file)
@@ -5,10 +5,14 @@
 package arvados
 
 import (
+       "fmt"
        "io"
        "io/ioutil"
+       "math/rand"
        "net/http"
        "os"
+       "regexp"
+       "sync"
        "testing"
 
        "git.curoverse.com/arvados.git/sdk/go/arvadostest"
@@ -236,6 +240,183 @@ func (s *CollectionFSSuite) TestReadWriteFile(c *check.C) {
        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, os.SEEK_SET)
+       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, os.SEEK_SET)
+       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, os.SEEK_SET)
+       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.Write([]byte("12345678abcdefghijkl"))
+
+       // grow to block/extent boundary
+       f.Truncate(64)
+       f2.Seek(0, os.SEEK_SET)
+       buf2, err = ioutil.ReadAll(f2)
+       c.Check(err, check.IsNil)
+       c.Check(len(buf2), check.Equals, 64)
+       c.Check(len(f.(*file).inode.(*filenode).extents), check.Equals, 8)
+
+       // shrink to block/extent boundary
+       err = f.Truncate(32)
+       f2.Seek(0, os.SEEK_SET)
+       buf2, err = ioutil.ReadAll(f2)
+       c.Check(err, check.IsNil)
+       c.Check(len(buf2), check.Equals, 32)
+       c.Check(len(f.(*file).inode.(*filenode).extents), check.Equals, 4)
+
+       // shrink to partial block/extent
+       err = f.Truncate(15)
+       f2.Seek(0, os.SEEK_SET)
+       buf2, err = ioutil.ReadAll(f2)
+       c.Check(err, check.IsNil)
+       c.Check(string(buf2), check.Equals, "12345678abcdefg")
+       c.Check(len(f.(*file).inode.(*filenode).extents), check.Equals, 2)
+
+       // 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, os.SEEK_SET)
+       buf2, err = ioutil.ReadAll(f2)
+       c.Check(err, check.IsNil)
+       c.Check(string(buf2), check.Equals, "123")
+       c.Check(len(f.(*file).inode.(*filenode).extents), 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 202cb962ac59075b964b07152d234b70+3 3:3:bar 6:3:foo\n")
+}
+
+func (s *CollectionFSSuite) TestMarshalSmallBlocks(c *check.C) {
+       maxBlockSize = 8
+       defer func() { maxBlockSize = 2 << 26 }()
+
+       s.fs = (&Collection{}).FileSystem(s.client, s.kc)
+       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) 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)), os.SEEK_SET)
+                               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 = 8
+       defer func() { maxBlockSize = 2 << 26 }()
+
+       var wg sync.WaitGroup
+       for n := 0; n < 128; 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 < 6502; 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), os.SEEK_SET)
+                               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, os.SEEK_SET)
+                               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)
+                       }
+               }(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.Logf("Readdir(): %#v", fi)
+
+       m, err := s.fs.MarshalManifest(".")
+       c.Check(err, check.IsNil)
+       c.Logf("%s", m)
 }
 
 // Gocheck boilerplate