summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--pkg/sentry/fs/gofer/cache_policy.go46
-rw-r--r--pkg/sentry/fs/gofer/file.go13
-rw-r--r--pkg/sentry/fs/gofer/gofer_test.go597
3 files changed, 408 insertions, 248 deletions
diff --git a/pkg/sentry/fs/gofer/cache_policy.go b/pkg/sentry/fs/gofer/cache_policy.go
index eec8c07cb..52d97b54f 100644
--- a/pkg/sentry/fs/gofer/cache_policy.go
+++ b/pkg/sentry/fs/gofer/cache_policy.go
@@ -34,8 +34,34 @@ const (
// Use virtual file system cache for everything, but send writes to the
// fs agent immediately.
cacheAllWritethrough
+
+ // Use virtual file system cache for everything, but reload dirents
+ // from the remote filesystem on each lookup. Thus, if the remote
+ // filesystem has changed, the returned dirent will have the updated
+ // state.
+ //
+ // This policy should *only* be used with remote filesystems that
+ // donate their host FDs to the sandbox and thus use the host page
+ // cache, otherwise the dirent state will be inconsistent.
+ cacheRemoteRevalidating
)
+// String returns the string name of the cache policy.
+func (cp cachePolicy) String() string {
+ switch cp {
+ case cacheNone:
+ return "cacheNone"
+ case cacheAll:
+ return "cacheAll"
+ case cacheAllWritethrough:
+ return "cacheAllWritethrough"
+ case cacheRemoteRevalidating:
+ return "cacheRemoteRevalidating"
+ default:
+ return "unknown"
+ }
+}
+
func parseCachePolicy(policy string) (cachePolicy, error) {
switch policy {
case "fscache":
@@ -44,6 +70,8 @@ func parseCachePolicy(policy string) (cachePolicy, error) {
return cacheNone, nil
case "fscache_writethrough":
return cacheAllWritethrough, nil
+ case "remote_revalidating":
+ return cacheRemoteRevalidating, nil
}
return cacheNone, fmt.Errorf("unsupported cache mode: %s", policy)
}
@@ -63,14 +91,16 @@ func (cp cachePolicy) cacheReaddir() bool {
}
// usePageCache determines whether the page cache should be used for the given
-// inode.
+// inode. If the remote filesystem donates host FDs to the sentry, then the
+// host kernel's page cache will be used, otherwise we will use a
+// sentry-internal page cache.
func (cp cachePolicy) usePageCache(inode *fs.Inode) bool {
// Do cached IO for regular files only. Some "character devices" expect
// no caching.
if !fs.IsFile(inode.StableAttr) {
return false
}
- return cp == cacheAll || cp == cacheAllWritethrough
+ return cp == cacheAll || cp == cacheAllWritethrough || cp == cacheRemoteRevalidating
}
// writeThough indicates whether writes to the file should be synced to the
@@ -79,10 +109,16 @@ func (cp cachePolicy) writeThrough(inode *fs.Inode) bool {
return cp == cacheNone || cp == cacheAllWritethrough
}
-// revalidateDirent indicates that dirents should be revalidated after they are
-// looked up.
+// revalidateDirent indicates that a dirent should be revalidated after a
+// lookup, because the looked up version may be stale.
func (cp cachePolicy) revalidateDirent() bool {
- return cp == cacheNone
+ if cp == cacheAll || cp == cacheAllWritethrough {
+ return false
+ }
+
+ // TODO: The cacheRemoteRevalidating policy should only
+ // return true if the remote file's attributes have changed.
+ return true
}
// keepDirent indicates that dirents should be kept pinned in the dirent tree
diff --git a/pkg/sentry/fs/gofer/file.go b/pkg/sentry/fs/gofer/file.go
index 46a6bbd5d..c4a210656 100644
--- a/pkg/sentry/fs/gofer/file.go
+++ b/pkg/sentry/fs/gofer/file.go
@@ -15,6 +15,7 @@
package gofer
import (
+ "fmt"
"syscall"
"gvisor.googlesource.com/gvisor/pkg/log"
@@ -72,6 +73,17 @@ func NewFile(ctx context.Context, dirent *fs.Dirent, name string, flags fs.FileF
flags.Pread = true
flags.Pwrite = true
+ if fs.IsFile(dirent.Inode.StableAttr) {
+ // If cache policy is "remote revalidating", then we must
+ // ensure that we have a host FD. Otherwise, the
+ // sentry-internal page cache will be used, and we can end up
+ // in an inconsistent state if the remote file changes.
+ cp := dirent.Inode.InodeOperations.(*inodeOperations).session().cachePolicy
+ if cp == cacheRemoteRevalidating && handles.Host == nil {
+ panic(fmt.Sprintf("remote-revalidating cache policy requires gofer to donate host FD, but file %q did not have host FD", name))
+ }
+ }
+
f := &fileOperations{
inodeOperations: i,
handles: handles,
@@ -202,7 +214,6 @@ func (f *fileOperations) Write(ctx context.Context, file *fs.File, src usermem.I
err = f.inodeOperations.cachingInodeOps.WriteOut(ctx, file.Dirent.Inode)
}
return n, err
-
}
return src.CopyInTo(ctx, f.handles.readWriterAt(ctx, offset))
}
diff --git a/pkg/sentry/fs/gofer/gofer_test.go b/pkg/sentry/fs/gofer/gofer_test.go
index 3df72dd37..764b530cb 100644
--- a/pkg/sentry/fs/gofer/gofer_test.go
+++ b/pkg/sentry/fs/gofer/gofer_test.go
@@ -15,11 +15,11 @@
package gofer
import (
- "errors"
"fmt"
"io"
"syscall"
"testing"
+ "time"
"gvisor.googlesource.com/gvisor/pkg/log"
"gvisor.googlesource.com/gvisor/pkg/p9"
@@ -32,15 +32,12 @@ import (
"gvisor.googlesource.com/gvisor/pkg/unet"
)
-// A errMock is an error that comes from bad usage of the mock.
-var errMock = errors.New("mock error")
-
// goodMockFile returns a file that can be Walk'ed to and created.
func goodMockFile(mode p9.FileMode, size uint64) *p9test.FileMock {
return &p9test.FileMock{
GetAttrMock: p9test.GetAttrMock{
- Valid: p9.AttrMask{Mode: true, Size: true, RDev: true},
Attr: p9.Attr{Mode: mode, Size: size, RDev: 0},
+ Valid: p9.AttrMaskAll(),
},
}
}
@@ -62,7 +59,7 @@ func newClosedSocket() (*unet.Socket, error) {
// root returns a p9 file mock and an fs.InodeOperations created from that file. Any
// functions performed on fs.InodeOperations will use the p9 file mock.
-func root(ctx context.Context, mode p9.FileMode, size uint64) (*p9test.FileMock, *fs.Inode, error) {
+func root(ctx context.Context, cp cachePolicy, mode p9.FileMode, size uint64) (*p9test.FileMock, *fs.Inode, error) {
sock, err := newClosedSocket()
if err != nil {
return nil, nil, err
@@ -72,7 +69,8 @@ func root(ctx context.Context, mode p9.FileMode, size uint64) (*p9test.FileMock,
s := &session{
conn: sock,
mounter: fs.RootOwner,
- cachePolicy: cacheNone,
+ cachePolicy: cp,
+ client: &p9.Client{},
}
rootFile := goodMockFile(mode, size)
@@ -109,43 +107,149 @@ func TestLookup(t *testing.T) {
ctx := contexttest.Context(t)
for _, test := range tests {
- // Set up mock.
- rootFile, rootInode, err := root(ctx, p9.PermissionsMask, 0)
- if err != nil {
- t.Errorf("TestWalk %s failed: root error got %v, want nil", test.name, err)
- }
-
- rootFile.WalkGetAttrMock.QIDs = []p9.QID{{}}
- rootFile.WalkGetAttrMock.Err = test.want
- rootFile.WalkGetAttrMock.File = goodMockFile(p9.PermissionsMask, 0)
-
- // Call function.
- dirent, err := rootInode.Lookup(ctx, test.fileName)
-
- // Unwrap the InodeOperations.
- var newInodeOperations fs.InodeOperations
- if dirent != nil {
- if dirent.IsNegative() {
- err = syscall.ENOENT
- } else {
- newInodeOperations = dirent.Inode.InodeOperations
+ t.Run(test.name, func(t *testing.T) {
+ // Set up mock.
+ rootFile, rootInode, err := root(ctx, cacheNone, p9.PermissionsMask, 0)
+ if err != nil {
+ t.Fatalf("error creating root: %v", err)
+ }
+
+ rootFile.WalkGetAttrMock.QIDs = []p9.QID{{}}
+ rootFile.WalkGetAttrMock.Err = test.want
+ rootFile.WalkGetAttrMock.File = goodMockFile(p9.PermissionsMask, 0)
+
+ // Call function.
+ dirent, err := rootInode.Lookup(ctx, test.fileName)
+
+ // Unwrap the InodeOperations.
+ var newInodeOperations fs.InodeOperations
+ if dirent != nil {
+ if dirent.IsNegative() {
+ err = syscall.ENOENT
+ } else {
+ newInodeOperations = dirent.Inode.InodeOperations
+ }
+ }
+
+ // Check return values.
+ if err != test.want {
+ t.Errorf("Lookup got err %v, want %v", err, test.want)
+ }
+ if err == nil && newInodeOperations == nil {
+ t.Errorf("Lookup got non-nil err and non-nil node, wanted at least one non-nil")
+ }
+
+ // Check mock parameters.
+ if !rootFile.WalkGetAttrMock.Called {
+ t.Errorf("GetAttr not called; error: %v", err)
+ } else if rootFile.WalkGetAttrMock.Names[0] != test.fileName {
+ t.Errorf("file name not set")
+ }
+ })
+ }
+}
+
+func TestRevalidation(t *testing.T) {
+ tests := []struct {
+ cachePolicy cachePolicy
+ preModificationWantReval bool
+ postModificationWantReval bool
+ }{
+ {
+ // Policy cacheNone causes Revalidate to always return
+ // true.
+ cachePolicy: cacheNone,
+ preModificationWantReval: true,
+ postModificationWantReval: true,
+ },
+ {
+ // Policy cacheAll causes Revalidate to always return
+ // false.
+ cachePolicy: cacheAll,
+ preModificationWantReval: false,
+ postModificationWantReval: false,
+ },
+ {
+ // Policy cacheAllWritethrough causes Revalidate to
+ // always return false.
+ cachePolicy: cacheAllWritethrough,
+ preModificationWantReval: false,
+ postModificationWantReval: false,
+ },
+ {
+ // Policy cacheRemoteRevalidating causes Revalidate to
+ // always return true.
+ //
+ // TODO: The cacheRemoteRevalidating
+ // policy should only return true if the remote file's
+ // attributes have changed.
+ cachePolicy: cacheRemoteRevalidating,
+ preModificationWantReval: true,
+ postModificationWantReval: true,
+ },
+ }
+
+ ctx := contexttest.Context(t)
+ for _, test := range tests {
+ name := fmt.Sprintf("cachepolicy=%s", test.cachePolicy)
+ t.Run(name, func(t *testing.T) {
+ // Set up mock.
+ rootFile, rootInode, err := root(ctx, test.cachePolicy, p9.ModeDirectory|p9.PermissionsMask, 0)
+ if err != nil {
+ t.Fatalf("error creating root: %v", err)
+ }
+
+ rootDir := fs.NewDirent(rootInode, "root")
+
+ // Create a mock file that we will walk to from the root.
+ const (
+ name = "foo"
+ mode = p9.PermissionsMask
+ )
+ file := goodMockFile(mode, 0)
+ file.GetAttrMock.Valid = p9.AttrMaskAll()
+
+ // Tell the root mock how to walk to this file.
+ rootFile.WalkGetAttrMock.QIDs = []p9.QID{{}}
+ rootFile.WalkGetAttrMock.File = file
+ rootFile.WalkGetAttrMock.Attr = file.GetAttrMock.Attr
+ rootFile.WalkGetAttrMock.Valid = file.GetAttrMock.Valid
+
+ // Do the walk.
+ dirent, err := rootDir.Walk(ctx, rootDir, name)
+ if err != nil {
+ t.Fatalf("Lookup(%q) failed: %v", name, err)
+ }
+
+ // Walk again. Depending on the cache policy, we may get a new
+ // dirent.
+ newDirent, err := rootDir.Walk(ctx, rootDir, name)
+ if err != nil {
+ t.Fatalf("Lookup(%q) failed: %v", name, err)
+ }
+ if test.preModificationWantReval && dirent == newDirent {
+ t.Errorf("Lookup(%q) with cachePolicy=%s got old dirent %v, wanted a new dirent", name, test.cachePolicy, dirent)
+ }
+ if !test.preModificationWantReval && dirent != newDirent {
+ t.Errorf("Lookup(%q) with cachePolicy=%s got new dirent %v, wanted old dirent %v", name, test.cachePolicy, newDirent, dirent)
}
- }
- // Check return values.
- if err != test.want {
- t.Errorf("TestWalk %s failed: got %v, want %v", test.name, err, test.want)
- }
- if err == nil && newInodeOperations == nil {
- t.Errorf("TestWalk %s failed: expected either non-nil err or non-nil node, but both are nil", test.name)
- }
+ // Modify the underlying mocked file's modification time.
+ file.GetAttrMock.Attr.MTimeSeconds = uint64(time.Now().Unix())
- // Check mock parameters.
- if !rootFile.WalkGetAttrMock.Called {
- t.Errorf("TestWalk %s failed: GetAttr not called; error: %v", test.name, err)
- } else if rootFile.WalkGetAttrMock.Names[0] != test.fileName {
- t.Errorf("TestWalk %s failed: file name not set", test.name)
- }
+ // Walk again. Depending on the cache policy, we may get a new
+ // dirent.
+ newDirent, err = rootDir.Walk(ctx, rootDir, name)
+ if err != nil {
+ t.Fatalf("Lookup(%q) failed: %v", name, err)
+ }
+ if test.postModificationWantReval && dirent == newDirent {
+ t.Errorf("Lookup(%q) with cachePolicy=%s got old dirent %v, wanted a new dirent", name, test.cachePolicy, dirent)
+ }
+ if !test.postModificationWantReval && dirent != newDirent {
+ t.Errorf("Lookup(%q) with cachePolicy=%s got new dirent %v, wanted old dirent %v", name, test.cachePolicy, newDirent, dirent)
+ }
+ })
}
}
@@ -197,56 +301,57 @@ func TestSetTimestamps(t *testing.T) {
}
for _, test := range tests {
- // Set up mock.
- rootFile, rootInode, err := root(ctx, p9.PermissionsMask, 0)
- if err != nil {
- t.Errorf("TestSetTimestamps %s failed: root error got %v, want nil", test.name, err)
- }
-
- // Call function.
- err = rootInode.SetTimestamps(ctx, nil /* Dirent */, test.ts)
-
- // Check return values.
- if err != nil {
- t.Errorf("TestSetTimestamps %s failed: got %v, want nil", test.name, err)
- }
+ t.Run(test.name, func(t *testing.T) {
+ // Set up mock.
+ rootFile, rootInode, err := root(ctx, cacheNone, p9.PermissionsMask, 0)
+ if err != nil {
+ t.Fatalf("error creating root: %v", err)
+ }
- // Check mock parameters.
- if !(test.ts.ATimeOmit && test.ts.MTimeOmit) && !rootFile.SetAttrMock.Called {
- t.Errorf("TestSetTimestamps %s failed: SetAttr not called", test.name)
- continue
- }
+ // Call function.
+ err = rootInode.SetTimestamps(ctx, nil /* Dirent */, test.ts)
- // Check what was passed to the mock function.
- attr := rootFile.SetAttrMock.Attr
- atimeGiven := ktime.FromUnix(int64(attr.ATimeSeconds), int64(attr.ATimeNanoSeconds))
- if test.ts.ATimeOmit {
- if rootFile.SetAttrMock.Valid.ATime {
- t.Errorf("TestSetTimestamps %s failed: ATime got set true in mask, wanted false", test.name)
- }
- } else {
- if got, want := rootFile.SetAttrMock.Valid.ATimeNotSystemTime, !test.ts.ATimeSetSystemTime; got != want {
- t.Errorf("TestSetTimestamps %s failed: got ATimeNotSystemTime %v, want %v", test.name, got, want)
+ // Check return values.
+ if err != nil {
+ t.Errorf("SetTimestamps failed: got error %v, want nil", err)
}
- if !test.ts.ATimeSetSystemTime && !test.ts.ATime.Equal(atimeGiven) {
- t.Errorf("TestSetTimestamps %s failed: ATime got %v, want %v", test.name, atimeGiven, test.ts.ATime)
- }
- }
- mtimeGiven := ktime.FromUnix(int64(attr.MTimeSeconds), int64(attr.MTimeNanoSeconds))
- if test.ts.MTimeOmit {
- if rootFile.SetAttrMock.Valid.MTime {
- t.Errorf("TestSetTimestamps %s failed: MTime got set true in mask, wanted false", test.name)
- }
- } else {
- if got, want := rootFile.SetAttrMock.Valid.MTimeNotSystemTime, !test.ts.MTimeSetSystemTime; got != want {
- t.Errorf("TestSetTimestamps %s failed: got MTimeNotSystemTime %v, want %v", test.name, got, want)
+ // Check mock parameters.
+ if !(test.ts.ATimeOmit && test.ts.MTimeOmit) && !rootFile.SetAttrMock.Called {
+ t.Errorf("TestSetTimestamps failed: SetAttr not called")
+ return
}
- if !test.ts.MTimeSetSystemTime && !test.ts.MTime.Equal(mtimeGiven) {
- t.Errorf("TestSetTimestamps %s failed: MTime got %v, want %v", test.name, mtimeGiven, test.ts.MTime)
+
+ // Check what was passed to the mock function.
+ attr := rootFile.SetAttrMock.Attr
+ atimeGiven := ktime.FromUnix(int64(attr.ATimeSeconds), int64(attr.ATimeNanoSeconds))
+ if test.ts.ATimeOmit {
+ if rootFile.SetAttrMock.Valid.ATime {
+ t.Errorf("ATime got set true in mask, wanted false")
+ }
+ } else {
+ if got, want := rootFile.SetAttrMock.Valid.ATimeNotSystemTime, !test.ts.ATimeSetSystemTime; got != want {
+ t.Errorf("got ATimeNotSystemTime %v, want %v", got, want)
+ }
+ if !test.ts.ATimeSetSystemTime && !test.ts.ATime.Equal(atimeGiven) {
+ t.Errorf("ATime got %v, want %v", atimeGiven, test.ts.ATime)
+ }
}
- }
+ mtimeGiven := ktime.FromUnix(int64(attr.MTimeSeconds), int64(attr.MTimeNanoSeconds))
+ if test.ts.MTimeOmit {
+ if rootFile.SetAttrMock.Valid.MTime {
+ t.Errorf("MTime got set true in mask, wanted false")
+ }
+ } else {
+ if got, want := rootFile.SetAttrMock.Valid.MTimeNotSystemTime, !test.ts.MTimeSetSystemTime; got != want {
+ t.Errorf("got MTimeNotSystemTime %v, want %v", got, want)
+ }
+ if !test.ts.MTimeSetSystemTime && !test.ts.MTime.Equal(mtimeGiven) {
+ t.Errorf("MTime got %v, want %v", mtimeGiven, test.ts.MTime)
+ }
+ }
+ })
}
}
@@ -283,43 +388,43 @@ func TestSetPermissions(t *testing.T) {
ctx := contexttest.Context(t)
for _, test := range tests {
- // Set up mock.
- rootFile, rootInode, err := root(ctx, 0, 0)
- if err != nil {
- t.Errorf("TestSetPermissions %s failed: root error got %v, want nil", test.name, err)
- }
- rootFile.SetAttrMock.Err = test.setAttrErr
-
- ok := rootInode.SetPermissions(ctx, nil /* Dirent */, test.perms)
-
- // Check return value.
- if ok != test.want {
- t.Errorf("TestSetPermissions %s failed: got %v, want %v", test.name, ok, test.want)
- }
-
- // Check mock parameters.
- pattr := rootFile.SetAttrMock.Attr
- if !rootFile.SetAttrMock.Called {
- t.Errorf("TestSetPermissions %s failed: SetAttr not called", test.name)
- continue
- }
- if !rootFile.SetAttrMock.Valid.Permissions {
- t.Errorf("TestSetPermissions %s failed: SetAttr did not get right request (got false, expected SetAttrMask.Permissions true)",
- test.name)
- }
- if got := fs.FilePermsFromP9(pattr.Permissions); got != test.perms {
- t.Errorf("TestSetPermissions %s failed: SetAttr did not get right permissions -- got %v, want %v",
- test.name, got, test.perms)
- }
+ t.Run(test.name, func(t *testing.T) {
+ // Set up mock.
+ rootFile, rootInode, err := root(ctx, cacheNone, 0, 0)
+ if err != nil {
+ t.Fatalf("error creating root: %v", err)
+ }
+ rootFile.SetAttrMock.Err = test.setAttrErr
+
+ ok := rootInode.SetPermissions(ctx, nil /* Dirent */, test.perms)
+
+ // Check return value.
+ if ok != test.want {
+ t.Errorf("SetPermissions got %v, want %v", ok, test.want)
+ }
+
+ // Check mock parameters.
+ pattr := rootFile.SetAttrMock.Attr
+ if !rootFile.SetAttrMock.Called {
+ t.Errorf("SetAttr not called")
+ return
+ }
+ if !rootFile.SetAttrMock.Valid.Permissions {
+ t.Errorf("SetAttr did not get right request (got false, expected SetAttrMask.Permissions true)")
+ }
+ if got := fs.FilePermsFromP9(pattr.Permissions); got != test.perms {
+ t.Errorf("SetAttr did not get right permissions -- got %v, want %v", got, test.perms)
+ }
+ })
}
}
func TestClose(t *testing.T) {
ctx := contexttest.Context(t)
// Set up mock.
- rootFile, rootInode, err := root(ctx, p9.PermissionsMask, 0)
+ rootFile, rootInode, err := root(ctx, cacheNone, p9.PermissionsMask, 0)
if err != nil {
- t.Errorf("TestClose failed: root error got %v, want nil", err)
+ t.Fatalf("error creating root: %v", err)
}
// Call function.
@@ -350,9 +455,9 @@ func TestRename(t *testing.T) {
want error
}
ctx := contexttest.Context(t)
- rootFile, rootInode, err := root(ctx, p9.PermissionsMask, 0)
+ rootFile, rootInode, err := root(ctx, cacheNone, p9.PermissionsMask, 0)
if err != nil {
- t.Errorf("TestRename failed: root error got %v, want nil", err)
+ t.Fatalf("error creating root: %v", err)
}
tests := []renameTest{
@@ -383,35 +488,37 @@ func TestRename(t *testing.T) {
}
for _, test := range tests {
- mockFile := goodMockFile(p9.PermissionsMask, 0)
- rootFile.WalkGetAttrMock.QIDs = []p9.QID{{}}
- rootFile.WalkGetAttrMock.File = mockFile
-
- dirent, err := rootInode.Lookup(ctx, "foo")
- if err != nil {
- t.Fatalf("root.Walk failed: %v", err)
- }
- mockFile.RenameMock.Err = test.renameErr
- mockFile.RenameMock.Called = false
-
- // Use a dummy oldParent to acquire write access to that directory.
- oldParent := &inodeOperations{
- readdirCache: fs.NewSortedDentryMap(nil),
- }
- oldInode := fs.NewInode(oldParent, fs.NewMockMountSource(nil), fs.StableAttr{Type: fs.Directory})
-
- // Call function.
- err = dirent.Inode.InodeOperations.Rename(ctx, oldInode, "", test.newParent, test.newName)
-
- // Check return value.
- if err != test.want {
- t.Errorf("TestRename %s failed: got %v, want %v", test.name, err, test.want)
- }
-
- // Check mock parameters.
- if got, want := mockFile.RenameMock.Called, test.renameCalled; got != want {
- t.Errorf("TestRename %s failed: renameCalled got %v want %v", test.name, got, want)
- }
+ t.Run(test.name, func(t *testing.T) {
+ mockFile := goodMockFile(p9.PermissionsMask, 0)
+ rootFile.WalkGetAttrMock.QIDs = []p9.QID{{}}
+ rootFile.WalkGetAttrMock.File = mockFile
+
+ dirent, err := rootInode.Lookup(ctx, "foo")
+ if err != nil {
+ t.Fatalf("root.Walk failed: %v", err)
+ }
+ mockFile.RenameMock.Err = test.renameErr
+ mockFile.RenameMock.Called = false
+
+ // Use a dummy oldParent to acquire write access to that directory.
+ oldParent := &inodeOperations{
+ readdirCache: fs.NewSortedDentryMap(nil),
+ }
+ oldInode := fs.NewInode(oldParent, fs.NewMockMountSource(nil), fs.StableAttr{Type: fs.Directory})
+
+ // Call function.
+ err = dirent.Inode.InodeOperations.Rename(ctx, oldInode, "", test.newParent, test.newName)
+
+ // Check return value.
+ if err != test.want {
+ t.Errorf("Rename got %v, want %v", err, test.want)
+ }
+
+ // Check mock parameters.
+ if got, want := mockFile.RenameMock.Called, test.renameCalled; got != want {
+ t.Errorf("renameCalled got %v want %v", got, want)
+ }
+ })
}
}
@@ -523,46 +630,48 @@ func TestPreadv(t *testing.T) {
ctx := contexttest.Context(t)
for _, test := range tests {
- // Set up mock.
- rootFile, rootInode, err := root(ctx, test.mode, 1024)
- if err != nil {
- t.Errorf("TestPreadv %s failed: root error got %v, want nil", test.name, err)
- }
-
- // Set up the read buffer.
- dst := usermem.BytesIOSequence(test.buffer[:test.sliceSize])
-
- // This file will be read from.
- openFile := &readAtFileFake{
- Err: test.readAtErr,
- FileLength: test.sliceSize,
- ChunkSize: test.chunkSize,
- }
- rootFile.WalkGetAttrMock.File = openFile
- rootFile.WalkGetAttrMock.Attr.Mode = test.mode
- rootFile.WalkGetAttrMock.Valid.Mode = true
-
- f := NewFile(
- ctx,
- fs.NewDirent(rootInode, ""),
- "",
- fs.FileFlags{Read: true},
- rootInode.InodeOperations.(*inodeOperations),
- &handles{File: contextFile{file: openFile}},
- )
-
- // Call function.
- _, err = f.Preadv(ctx, dst, 0)
-
- // Check return value.
- if err != test.want {
- t.Errorf("TestPreadv %s failed: got %v, want %v", test.name, err, test.want)
- }
-
- // Check mock parameters.
- if test.readAtCalled != openFile.Called {
- t.Errorf("TestPreadv %s failed: ReadAt called: %v, but expected opposite", test.name, openFile.Called)
- }
+ t.Run(test.name, func(t *testing.T) {
+ // Set up mock.
+ rootFile, rootInode, err := root(ctx, cacheNone, test.mode, 1024)
+ if err != nil {
+ t.Fatalf("error creating root: %v", err)
+ }
+
+ // Set up the read buffer.
+ dst := usermem.BytesIOSequence(test.buffer[:test.sliceSize])
+
+ // This file will be read from.
+ openFile := &readAtFileFake{
+ Err: test.readAtErr,
+ FileLength: test.sliceSize,
+ ChunkSize: test.chunkSize,
+ }
+ rootFile.WalkGetAttrMock.File = openFile
+ rootFile.WalkGetAttrMock.Attr.Mode = test.mode
+ rootFile.WalkGetAttrMock.Valid.Mode = true
+
+ f := NewFile(
+ ctx,
+ fs.NewDirent(rootInode, ""),
+ "",
+ fs.FileFlags{Read: true},
+ rootInode.InodeOperations.(*inodeOperations),
+ &handles{File: contextFile{file: openFile}},
+ )
+
+ // Call function.
+ _, err = f.Preadv(ctx, dst, 0)
+
+ // Check return value.
+ if err != test.want {
+ t.Errorf("Preadv got %v, want %v", err, test.want)
+ }
+
+ // Check mock parameters.
+ if test.readAtCalled != openFile.Called {
+ t.Errorf("ReadAt called: %v, but expected opposite", openFile.Called)
+ }
+ })
}
}
@@ -610,28 +719,30 @@ func TestReadlink(t *testing.T) {
ctx := contexttest.Context(t)
for _, test := range tests {
- // Set up mock.
- rootFile, rootInode, err := root(ctx, test.mode, 0)
- if err != nil {
- t.Errorf("TestReadlink %s failed: root error got %v, want nil", test.name, err)
- }
+ t.Run(test.name, func(t *testing.T) {
+ // Set up mock.
+ rootFile, rootInode, err := root(ctx, cacheNone, test.mode, 0)
+ if err != nil {
+ t.Fatalf("error creating root: %v", err)
+ }
- openFile := goodMockFile(test.mode, 0)
- rootFile.WalkMock.File = openFile
- rootFile.ReadlinkMock.Err = test.readlinkErr
+ openFile := goodMockFile(test.mode, 0)
+ rootFile.WalkMock.File = openFile
+ rootFile.ReadlinkMock.Err = test.readlinkErr
- // Call function.
- _, err = rootInode.Readlink(ctx)
+ // Call function.
+ _, err = rootInode.Readlink(ctx)
- // Check return value.
- if err != test.want {
- t.Errorf("TestReadlink %s failed: got %v, want %v", test.name, err, test.want)
- }
+ // Check return value.
+ if err != test.want {
+ t.Errorf("Readlink got %v, want %v", err, test.want)
+ }
- // Check mock parameters.
- if test.readlinkCalled && !rootFile.ReadlinkMock.Called {
- t.Errorf("TestReadlink %s failed: Readlink not called", test.name)
- }
+ // Check mock parameters.
+ if test.readlinkCalled && !rootFile.ReadlinkMock.Called {
+ t.Errorf("Readlink not called")
+ }
+ })
}
}
@@ -735,44 +846,46 @@ func TestPwritev(t *testing.T) {
ctx := contexttest.Context(t)
for _, test := range tests {
- // Set up mock.
- _, rootInode, err := root(ctx, test.mode, 0)
- if err != nil {
- t.Errorf("TestPwritev %s failed: root error got %v, want nil", test.name, err)
- }
-
- src := usermem.BytesIOSequence(test.buffer[:test.sliceSize])
-
- // This is the file that will be used for writing.
- openFile := &writeAtFileFake{
- Err: test.writeAtErr,
- ChunkSize: test.chunkSize,
- }
-
- f := NewFile(
- ctx,
- fs.NewDirent(rootInode, ""),
- "",
- fs.FileFlags{Write: true},
- rootInode.InodeOperations.(*inodeOperations),
- &handles{File: contextFile{file: openFile}},
- )
-
- // Call function.
- _, err = f.Pwritev(ctx, src, 0)
-
- // Check return value.
- if err != test.want {
- t.Errorf("TestPwritev %s failed: got %v, want %v", test.name, err, test.want)
- }
-
- // Check mock parameters.
- if test.writeAtCalled != openFile.Called {
- t.Errorf("TestPwritev %s failed: WriteAt called: %v, but expected opposite", test.name, openFile.Called)
- continue
- }
- if openFile.Called && test.writeAtErr != nil && openFile.LengthWritten != test.sliceSize {
- t.Errorf("TestPwritev %s failed: wrote %d bytes, expected %d bytes written", test.name, openFile.LengthWritten, test.sliceSize)
- }
+ t.Run(test.name, func(t *testing.T) {
+ // Set up mock.
+ _, rootInode, err := root(ctx, cacheNone, test.mode, 0)
+ if err != nil {
+ t.Fatalf("error creating root: %v", err)
+ }
+
+ src := usermem.BytesIOSequence(test.buffer[:test.sliceSize])
+
+ // This is the file that will be used for writing.
+ openFile := &writeAtFileFake{
+ Err: test.writeAtErr,
+ ChunkSize: test.chunkSize,
+ }
+
+ f := NewFile(
+ ctx,
+ fs.NewDirent(rootInode, ""),
+ "",
+ fs.FileFlags{Write: true},
+ rootInode.InodeOperations.(*inodeOperations),
+ &handles{File: contextFile{file: openFile}},
+ )
+
+ // Call function.
+ _, err = f.Pwritev(ctx, src, 0)
+
+ // Check return value.
+ if err != test.want {
+ t.Errorf("Pwritev got %v, want %v", err, test.want)
+ }
+
+ // Check mock parameters.
+ if test.writeAtCalled != openFile.Called {
+ t.Errorf("WriteAt called: %v, but expected opposite", openFile.Called)
+ return
+ }
+ if openFile.Called && test.writeAtErr != nil && openFile.LengthWritten != test.sliceSize {
+ t.Errorf("wrote %d bytes, expected %d bytes written", openFile.LengthWritten, test.sliceSize)
+ }
+ })
}
}