12483: Add (File)Truncate().
[arvados.git] / sdk / go / arvados / collection_fs_test.go
index 7e8588b85e6769b9ea3814b1f2afdd7615b38c88..e635bb7f3070e6c16cb44e9655d36153861c8be9 100644 (file)
@@ -1,7 +1,12 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: Apache-2.0
+
 package arvados
 
 import (
        "io"
+       "io/ioutil"
        "net/http"
        "os"
        "testing"
@@ -12,17 +17,39 @@ import (
 
 var _ = check.Suite(&CollectionFSSuite{})
 
+type keepClientStub struct {
+       blocks map[string][]byte
+}
+
+func (kcs *keepClientStub) ReadAt(locator string, p []byte, off int) (int, error) {
+       buf := kcs.blocks[locator[:32]]
+       if buf == nil {
+               return 0, os.ErrNotExist
+       }
+       return copy(p, buf[off:]), nil
+}
+
 type CollectionFSSuite struct {
        client *Client
        coll   Collection
-       fs     http.FileSystem
+       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.fs = s.coll.FileSystem(s.client, nil)
+       s.kc = &keepClientStub{
+               blocks: map[string][]byte{
+                       "3858f62230ac3c915f300c664312c63f": []byte("foobar"),
+               }}
+       s.fs = s.coll.FileSystem(s.client, s.kc)
+}
+
+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) {
@@ -54,7 +81,7 @@ func (s *CollectionFSSuite) TestReaddirLimited(c *check.C) {
        }
 
        fis, err = f.Readdir(1)
-       c.Check(err, check.Equals, io.EOF)
+       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))
@@ -72,7 +99,7 @@ func (s *CollectionFSSuite) TestReaddirLimited(c *check.C) {
        c.Assert(err, check.IsNil)
        fis, err = f.Readdir(2)
        c.Check(len(fis), check.Equals, 1)
-       c.Assert(err, check.Equals, io.EOF)
+       c.Assert(err, check.IsNil)
        fis, err = f.Readdir(2)
        c.Check(len(fis), check.Equals, 0)
        c.Assert(err, check.Equals, io.EOF)
@@ -109,14 +136,166 @@ func (s *CollectionFSSuite) TestNotExist(c *check.C) {
        }
 }
 
-func (s *CollectionFSSuite) TestOpenFile(c *check.C) {
-       c.Skip("cannot test files with nil keepclient")
+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)
+}
 
-       f, err := s.fs.Open("/foo.txt")
+func (s *CollectionFSSuite) TestCreateFile(c *check.C) {
+       f, err := s.fs.OpenFile("/newfile", 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("/newfile", 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("/newfile", 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)
+
+       // TODO: serialize to Collection, confirm manifest contents,
+       // make new FileSystem, confirm file contents.
+}
+
+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, os.SEEK_CUR)
+       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, os.SEEK_CUR)
+       c.Check(pos, check.Equals, int64(2))
+       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)
+
+       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, os.SEEK_SET)
+       n, err = f.Write([]byte("0123456789abcdefg"))
+       c.Check(n, check.Equals, 17)
+       c.Check(err, check.IsNil)
+       pos, err = f.Seek(0, os.SEEK_CUR)
+       c.Check(pos, check.Equals, int64(18))
+       pos, err = f.Seek(-18, os.SEEK_CUR)
+       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, 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)
 }
 
 // Gocheck boilerplate