2018-10-19 23:34:09 +00:00
// Copyright 2018 Google LLC
2018-04-27 17:37:02 +00:00
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package gofer
import (
"fmt"
"io"
"syscall"
"testing"
2018-08-07 18:42:29 +00:00
"time"
2018-04-27 17:37:02 +00:00
"gvisor.googlesource.com/gvisor/pkg/log"
"gvisor.googlesource.com/gvisor/pkg/p9"
"gvisor.googlesource.com/gvisor/pkg/p9/p9test"
"gvisor.googlesource.com/gvisor/pkg/sentry/context"
"gvisor.googlesource.com/gvisor/pkg/sentry/context/contexttest"
"gvisor.googlesource.com/gvisor/pkg/sentry/fs"
ktime "gvisor.googlesource.com/gvisor/pkg/sentry/kernel/time"
"gvisor.googlesource.com/gvisor/pkg/sentry/usermem"
"gvisor.googlesource.com/gvisor/pkg/unet"
)
// 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 {
Attr : p9 . Attr { Mode : mode , Size : size , RDev : 0 } ,
2018-08-07 18:42:29 +00:00
Valid : p9 . AttrMaskAll ( ) ,
2018-04-27 17:37:02 +00:00
} ,
}
}
func newClosedSocket ( ) ( * unet . Socket , error ) {
fd , err := syscall . Socket ( syscall . AF_UNIX , syscall . SOCK_STREAM , 0 )
if err != nil {
return nil , err
}
s , err := unet . NewSocket ( fd )
if err != nil {
syscall . Close ( fd )
return nil , err
}
return s , s . Close ( )
}
// 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.
2018-08-07 18:42:29 +00:00
func root ( ctx context . Context , cp cachePolicy , mode p9 . FileMode , size uint64 ) ( * p9test . FileMock , * fs . Inode , error ) {
2018-04-27 17:37:02 +00:00
sock , err := newClosedSocket ( )
if err != nil {
return nil , nil , err
}
// Construct a dummy session that we can destruct.
s := & session {
conn : sock ,
mounter : fs . RootOwner ,
2018-08-07 18:42:29 +00:00
cachePolicy : cp ,
client : & p9 . Client { } ,
2018-04-27 17:37:02 +00:00
}
rootFile := goodMockFile ( mode , size )
2018-08-10 21:31:56 +00:00
sattr , rootInodeOperations := newInodeOperations ( ctx , s , contextFile { file : rootFile } , p9 . QID { } , rootFile . GetAttrMock . Valid , rootFile . GetAttrMock . Attr , false /* socket */ )
2018-04-27 17:37:02 +00:00
m := fs . NewMountSource ( s , & filesystem { } , fs . MountSourceFlags { } )
return rootFile , fs . NewInode ( rootInodeOperations , m , sattr ) , nil
}
func TestLookup ( t * testing . T ) {
// Test parameters.
type lookupTest struct {
// Name of the test.
name string
// Function input parameters.
fileName string
// Expected return value.
want error
}
tests := [ ] lookupTest {
{
name : "mock Walk passes (function succeeds)" ,
fileName : "ppp" ,
want : nil ,
} ,
{
name : "mock Walk fails (function fails)" ,
fileName : "ppp" ,
want : syscall . ENOENT ,
} ,
}
ctx := contexttest . Context ( t )
for _ , test := range tests {
2018-08-07 18:42:29 +00:00
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 {
2018-08-27 21:25:21 +00:00
cachePolicy cachePolicy
// Whether dirent should be reloaded before any modifications.
preModificationWantReload bool
// Whether dirent should be reloaded after updating an unstable
// attribute on the remote fs.
postModificationWantReload bool
// Whether dirent unstable attributes should be updated after
// updating an attribute on the remote fs.
postModificationWantUpdatedAttrs bool
// Whether dirent should be reloaded after the remote has
// removed the file.
postRemovalWantReload bool
2018-08-07 18:42:29 +00:00
} {
{
// Policy cacheNone causes Revalidate to always return
// true.
2018-08-27 21:25:21 +00:00
cachePolicy : cacheNone ,
preModificationWantReload : true ,
postModificationWantReload : true ,
postModificationWantUpdatedAttrs : true ,
postRemovalWantReload : true ,
2018-08-07 18:42:29 +00:00
} ,
{
// Policy cacheAll causes Revalidate to always return
// false.
2018-08-27 21:25:21 +00:00
cachePolicy : cacheAll ,
preModificationWantReload : false ,
postModificationWantReload : false ,
postModificationWantUpdatedAttrs : false ,
postRemovalWantReload : false ,
2018-08-07 18:42:29 +00:00
} ,
{
// Policy cacheAllWritethrough causes Revalidate to
// always return false.
2018-08-27 21:25:21 +00:00
cachePolicy : cacheAllWritethrough ,
preModificationWantReload : false ,
postModificationWantReload : false ,
postModificationWantUpdatedAttrs : false ,
postRemovalWantReload : false ,
2018-08-07 18:42:29 +00:00
} ,
{
// Policy cacheRemoteRevalidating causes Revalidate to
2018-08-27 21:25:21 +00:00
// return update cached unstable attrs, and returns
// true only when the remote inode itself has been
// removed or replaced.
cachePolicy : cacheRemoteRevalidating ,
preModificationWantReload : false ,
postModificationWantReload : false ,
postModificationWantUpdatedAttrs : true ,
postRemovalWantReload : true ,
2018-08-07 18:42:29 +00:00
} ,
}
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 )
}
2018-08-27 21:25:21 +00:00
if test . preModificationWantReload && dirent == newDirent {
2018-08-07 18:42:29 +00:00
t . Errorf ( "Lookup(%q) with cachePolicy=%s got old dirent %v, wanted a new dirent" , name , test . cachePolicy , dirent )
}
2018-08-27 21:25:21 +00:00
if ! test . preModificationWantReload && dirent != newDirent {
2018-08-07 18:42:29 +00:00
t . Errorf ( "Lookup(%q) with cachePolicy=%s got new dirent %v, wanted old dirent %v" , name , test . cachePolicy , newDirent , dirent )
2018-04-27 17:37:02 +00:00
}
2018-08-07 18:42:29 +00:00
// Modify the underlying mocked file's modification time.
2018-08-27 21:25:21 +00:00
nowSeconds := time . Now ( ) . Unix ( )
rootFile . WalkGetAttrMock . Attr . MTimeSeconds = uint64 ( nowSeconds )
file . GetAttrMock . Attr . MTimeSeconds = uint64 ( nowSeconds )
2018-04-27 17:37:02 +00:00
2018-08-07 18:42:29 +00:00
// 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 )
}
2018-08-27 21:25:21 +00:00
if test . postModificationWantReload && dirent == newDirent {
2018-08-07 18:42:29 +00:00
t . Errorf ( "Lookup(%q) with cachePolicy=%s got old dirent %v, wanted a new dirent" , name , test . cachePolicy , dirent )
}
2018-08-27 21:25:21 +00:00
if ! test . postModificationWantReload && dirent != newDirent {
2018-08-07 18:42:29 +00:00
t . Errorf ( "Lookup(%q) with cachePolicy=%s got new dirent %v, wanted old dirent %v" , name , test . cachePolicy , newDirent , dirent )
}
2018-08-27 21:25:21 +00:00
uattrs , err := newDirent . Inode . UnstableAttr ( ctx )
if err != nil {
t . Fatalf ( "Error getting unstable attrs: %v" , err )
}
gotModTimeSeconds := uattrs . ModificationTime . Seconds ( )
if test . postModificationWantUpdatedAttrs && gotModTimeSeconds != nowSeconds {
t . Fatalf ( "Lookup(%q) with cachePolicy=%s got new modification time %v, wanted %v" , name , test . cachePolicy , gotModTimeSeconds , nowSeconds )
}
// Make WalkGetAttr return ENOENT. This simulates
// removing the file from the remote fs.
rootFile . WalkGetAttrMock = p9test . WalkGetAttrMock {
Err : syscall . ENOENT ,
}
// Walk again. Depending on the cache policy, we may
// get ENOENT.
newDirent , err = rootDir . Walk ( ctx , rootDir , name )
if test . postRemovalWantReload && err == nil {
t . Errorf ( "Lookup(%q) with cachePolicy=%s got nil error, wanted ENOENT" , name , test . cachePolicy )
}
if ! test . postRemovalWantReload && ( err != nil || dirent != newDirent ) {
t . Errorf ( "Lookup(%q) with cachePolicy=%s got new dirent %v and error %v, wanted old dirent %v and nil error" , name , test . cachePolicy , newDirent , err , dirent )
}
2018-08-07 18:42:29 +00:00
} )
2018-04-27 17:37:02 +00:00
}
}
func TestSetTimestamps ( t * testing . T ) {
// Test parameters.
type setTimestampsTest struct {
// Name of the test.
name string
// Function input parameters.
ts fs . TimeSpec
}
ctx := contexttest . Context ( t )
now := ktime . NowFromContext ( ctx )
tests := [ ] setTimestampsTest {
{
name : "mock SetAttr passes (function succeeds)" ,
ts : fs . TimeSpec {
ATime : now ,
MTime : now ,
} ,
} ,
{
name : "mock SetAttr passes, times are 0 (function succeeds)" ,
ts : fs . TimeSpec { } ,
} ,
{
name : "mock SetAttr passes, times are 0 and not system time (function succeeds)" ,
ts : fs . TimeSpec {
ATimeSetSystemTime : false ,
MTimeSetSystemTime : false ,
} ,
} ,
{
name : "mock SetAttr passes, times are set to system time (function succeeds)" ,
ts : fs . TimeSpec {
ATimeSetSystemTime : true ,
MTimeSetSystemTime : true ,
} ,
} ,
{
name : "mock SetAttr passes, times are omitted (function succeeds)" ,
ts : fs . TimeSpec {
ATimeOmit : true ,
MTimeOmit : true ,
} ,
} ,
}
for _ , test := range tests {
2018-08-07 18:42:29 +00:00
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 )
}
2018-04-27 17:37:02 +00:00
2018-08-07 18:42:29 +00:00
// Call function.
err = rootInode . SetTimestamps ( ctx , nil /* Dirent */ , test . ts )
2018-04-27 17:37:02 +00:00
2018-08-07 18:42:29 +00:00
// Check return values.
if err != nil {
t . Errorf ( "SetTimestamps failed: got error %v, want nil" , err )
2018-04-27 17:37:02 +00:00
}
2018-08-07 18:42:29 +00:00
// Check mock parameters.
if ! ( test . ts . ATimeOmit && test . ts . MTimeOmit ) && ! rootFile . SetAttrMock . Called {
t . Errorf ( "TestSetTimestamps failed: SetAttr not called" )
return
2018-04-27 17:37:02 +00:00
}
2018-08-07 18:42:29 +00:00
// 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 )
}
2018-04-27 17:37:02 +00:00
}
2018-08-07 18:42:29 +00:00
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 )
}
}
} )
2018-04-27 17:37:02 +00:00
}
}
func TestSetPermissions ( t * testing . T ) {
// Test parameters.
type setPermissionsTest struct {
// Name of the test.
name string
// SetPermissions input parameters.
perms fs . FilePermissions
// Error that SetAttr mock should return.
setAttrErr error
// Expected return value.
want bool
}
tests := [ ] setPermissionsTest {
{
name : "SetAttr mock succeeds (function succeeds)" ,
perms : fs . FilePermissions { User : fs . PermMask { Read : true , Write : true , Execute : true } } ,
want : true ,
setAttrErr : nil ,
} ,
{
name : "SetAttr mock fails (function fails)" ,
perms : fs . FilePermissions { User : fs . PermMask { Read : true , Write : true } } ,
want : false ,
setAttrErr : syscall . ENOENT ,
} ,
}
ctx := contexttest . Context ( t )
for _ , test := range tests {
2018-08-07 18:42:29 +00:00
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 )
}
} )
2018-04-27 17:37:02 +00:00
}
}
func TestClose ( t * testing . T ) {
ctx := contexttest . Context ( t )
// Set up mock.
2018-08-07 18:42:29 +00:00
rootFile , rootInode , err := root ( ctx , cacheNone , p9 . PermissionsMask , 0 )
2018-04-27 17:37:02 +00:00
if err != nil {
2018-08-07 18:42:29 +00:00
t . Fatalf ( "error creating root: %v" , err )
2018-04-27 17:37:02 +00:00
}
// Call function.
rootInode . InodeOperations . Release ( ctx )
// Check mock parameters.
if ! rootFile . CloseMock . Called {
t . Errorf ( "TestClose failed: Close not called" )
}
}
func TestRename ( t * testing . T ) {
// Test parameters.
type renameTest struct {
// Name of the test.
name string
// Input parameters.
newParent * fs . Inode
newName string
// Rename mock parameters.
renameErr error
renameCalled bool
// Error want to return given the parameters. (Same as what
// we expect and tell rename to return.)
want error
}
ctx := contexttest . Context ( t )
2018-08-07 18:42:29 +00:00
rootFile , rootInode , err := root ( ctx , cacheNone , p9 . PermissionsMask , 0 )
2018-04-27 17:37:02 +00:00
if err != nil {
2018-08-07 18:42:29 +00:00
t . Fatalf ( "error creating root: %v" , err )
2018-04-27 17:37:02 +00:00
}
tests := [ ] renameTest {
{
name : "mock Rename succeeds (function succeeds)" ,
newParent : rootInode ,
newName : "foo2" ,
want : nil ,
renameErr : nil ,
renameCalled : true ,
} ,
{
name : "mock Rename fails (function fails)" ,
newParent : rootInode ,
newName : "foo2" ,
want : syscall . ENOENT ,
renameErr : syscall . ENOENT ,
renameCalled : true ,
} ,
{
name : "newParent is not inodeOperations but should be (function fails)" ,
newParent : fs . NewMockInode ( ctx , fs . NewMockMountSource ( nil ) , fs . StableAttr { Type : fs . Directory } ) ,
newName : "foo2" ,
want : syscall . EXDEV ,
renameErr : nil ,
renameCalled : false ,
} ,
}
for _ , test := range tests {
2018-08-07 18:42:29 +00:00
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 )
}
} )
2018-04-27 17:37:02 +00:00
}
}
// This file is read from in TestPreadv.
type readAtFileFake struct {
p9test . FileMock
// Parameters for faking ReadAt.
FileLength int
Err error
ChunkSize int
Called bool
LengthRead int
}
func ( r * readAtFileFake ) ReadAt ( p [ ] byte , offset uint64 ) ( int , error ) {
r . Called = true
log . Warningf ( "ReadAt fake: length read so far = %d, len(p) = %d, offset = %d" , r . LengthRead , len ( p ) , offset )
if int ( offset ) != r . LengthRead {
return 0 , fmt . Errorf ( "offset got %d; expected %d" , offset , r . LengthRead )
}
if r . Err != nil {
return 0 , r . Err
}
if r . LengthRead >= r . FileLength {
return 0 , io . EOF
}
// Read at most ChunkSize and read at most what's left in the file.
toBeRead := len ( p )
if r . LengthRead + toBeRead >= r . FileLength {
toBeRead = r . FileLength - int ( offset )
}
if toBeRead > r . ChunkSize {
toBeRead = r . ChunkSize
}
r . LengthRead += toBeRead
if r . LengthRead == r . FileLength {
return toBeRead , io . EOF
}
return toBeRead , nil
}
func TestPreadv ( t * testing . T ) {
// Test parameters.
type preadvTest struct {
// Name of the test.
name string
// Mock parameters
mode p9 . FileMode
// Buffer to read into.
buffer [ 512 ] byte
sliceSize int
// How much readAt returns at a time.
chunkSize int
// Whether or not we expect ReadAt to be called.
readAtCalled bool
readAtErr error
// Expected return values.
want error
}
tests := [ ] preadvTest {
{
name : "fake ReadAt succeeds, 512 bytes requested, 512 byte chunks (function succeeds)" ,
want : nil ,
readAtErr : nil ,
mode : p9 . PermissionsMask ,
readAtCalled : true ,
sliceSize : 512 ,
chunkSize : 512 ,
} ,
{
name : "fake ReadAt succeeds, 512 bytes requested, 200 byte chunks (function succeeds)" ,
want : nil ,
readAtErr : nil ,
mode : p9 . PermissionsMask ,
readAtCalled : true ,
sliceSize : 512 ,
chunkSize : 200 ,
} ,
{
name : "fake ReadAt succeeds, 0 bytes requested (function succeeds)" ,
want : nil ,
readAtErr : nil ,
mode : p9 . PermissionsMask ,
readAtCalled : false ,
sliceSize : 0 ,
chunkSize : 100 ,
} ,
{
name : "fake ReadAt returns 0 bytes and EOF (function fails)" ,
want : io . EOF ,
readAtErr : io . EOF ,
mode : p9 . PermissionsMask ,
readAtCalled : true ,
sliceSize : 512 ,
chunkSize : 512 ,
} ,
}
ctx := contexttest . Context ( t )
for _ , test := range tests {
2018-08-07 18:42:29 +00:00
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 )
}
} )
2018-04-27 17:37:02 +00:00
}
}
func TestReadlink ( t * testing . T ) {
// Test parameters.
type readlinkTest struct {
// Name of the test.
name string
// Mock parameters
mode p9 . FileMode
// Whether or not we expect ReadAt to be called and what error
// it shall return.
readlinkCalled bool
readlinkErr error
// Expected return values.
want error
}
tests := [ ] readlinkTest {
{
name : "file is not symlink (function fails)" ,
want : syscall . ENOLINK ,
mode : p9 . PermissionsMask ,
readlinkCalled : false ,
readlinkErr : nil ,
} ,
{
name : "mock Readlink succeeds (function succeeds)" ,
want : nil ,
mode : p9 . PermissionsMask | p9 . ModeSymlink ,
readlinkCalled : true ,
readlinkErr : nil ,
} ,
{
name : "mock Readlink fails (function fails)" ,
want : syscall . ENOENT ,
mode : p9 . PermissionsMask | p9 . ModeSymlink ,
readlinkCalled : true ,
readlinkErr : syscall . ENOENT ,
} ,
}
ctx := contexttest . Context ( t )
for _ , test := range tests {
2018-08-07 18:42:29 +00:00
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 )
}
2018-04-27 17:37:02 +00:00
2018-08-07 18:42:29 +00:00
openFile := goodMockFile ( test . mode , 0 )
rootFile . WalkMock . File = openFile
rootFile . ReadlinkMock . Err = test . readlinkErr
2018-04-27 17:37:02 +00:00
2018-08-07 18:42:29 +00:00
// Call function.
_ , err = rootInode . Readlink ( ctx )
2018-04-27 17:37:02 +00:00
2018-08-07 18:42:29 +00:00
// Check return value.
if err != test . want {
t . Errorf ( "Readlink got %v, want %v" , err , test . want )
}
2018-04-27 17:37:02 +00:00
2018-08-07 18:42:29 +00:00
// Check mock parameters.
if test . readlinkCalled && ! rootFile . ReadlinkMock . Called {
t . Errorf ( "Readlink not called" )
}
} )
2018-04-27 17:37:02 +00:00
}
}
// This file is write from in TestPwritev.
type writeAtFileFake struct {
p9test . FileMock
// Parameters for faking WriteAt.
Err error
ChunkSize int
Called bool
LengthWritten int
}
func ( r * writeAtFileFake ) WriteAt ( p [ ] byte , offset uint64 ) ( int , error ) {
r . Called = true
log . Warningf ( "WriteAt fake: length written so far = %d, len(p) = %d, offset = %d" , r . LengthWritten , len ( p ) , offset )
if int ( offset ) != r . LengthWritten {
return 0 , fmt . Errorf ( "offset got %d; want %d" , offset , r . LengthWritten )
}
if r . Err != nil {
return 0 , r . Err
}
// Write at most ChunkSize.
toBeWritten := len ( p )
if toBeWritten > r . ChunkSize {
toBeWritten = r . ChunkSize
}
r . LengthWritten += toBeWritten
return toBeWritten , nil
}
func TestPwritev ( t * testing . T ) {
// Test parameters.
type pwritevTest struct {
// Name of the test.
name string
// Mock parameters
mode p9 . FileMode
allowWrite bool
// Buffer to write into.
buffer [ 512 ] byte
sliceSize int
chunkSize int
// Whether or not we expect writeAt to be called.
writeAtCalled bool
writeAtErr error
// Expected return values.
want error
}
tests := [ ] pwritevTest {
{
name : "fake writeAt succeeds, one chunk (function succeeds)" ,
want : nil ,
writeAtErr : nil ,
mode : p9 . PermissionsMask ,
allowWrite : true ,
writeAtCalled : true ,
sliceSize : 512 ,
chunkSize : 512 ,
} ,
{
name : "fake writeAt fails, short write (function fails)" ,
want : io . ErrShortWrite ,
writeAtErr : nil ,
mode : p9 . PermissionsMask ,
allowWrite : true ,
writeAtCalled : true ,
sliceSize : 512 ,
chunkSize : 200 ,
} ,
{
name : "fake writeAt succeeds, len 0 (function succeeds)" ,
want : nil ,
writeAtErr : nil ,
mode : p9 . PermissionsMask ,
allowWrite : true ,
writeAtCalled : false ,
sliceSize : 0 ,
chunkSize : 0 ,
} ,
{
name : "writeAt can still write despite file permissions read only (function succeeds)" ,
want : nil ,
writeAtErr : nil ,
mode : p9 . PermissionsMask ,
allowWrite : false ,
writeAtCalled : true ,
sliceSize : 512 ,
chunkSize : 512 ,
} ,
}
ctx := contexttest . Context ( t )
for _ , test := range tests {
2018-08-07 18:42:29 +00:00
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 )
}
} )
2018-04-27 17:37:02 +00:00
}
}