diff --git a/go.mod b/go.mod index a0a9378654..e869e91843 100644 --- a/go.mod +++ b/go.mod @@ -19,6 +19,7 @@ require ( github.com/jacobsa/syncutil v0.0.0-20180201203307-228ac8e5a6c3 github.com/jacobsa/timeutil v0.0.0-20170205232429-577e5acbbcf6 github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 + github.com/stretchr/testify v1.9.0 github.com/urfave/cli v1.22.14 go.opencensus.io v0.24.0 golang.org/x/net v0.22.0 @@ -46,6 +47,7 @@ require ( github.com/cncf/udpa/go v0.0.0-20220112060539-c52dc94e7fbe // indirect github.com/cncf/xds/go v0.0.0-20231128003011-0fa0005c9caa // indirect github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect github.com/envoyproxy/go-control-plane v0.12.0 // indirect github.com/envoyproxy/protoc-gen-validate v1.0.4 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect @@ -61,6 +63,7 @@ require ( github.com/grpc-ecosystem/grpc-gateway/v2 v2.15.2 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/pkg/xattr v0.4.9 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect github.com/prometheus/prometheus v0.35.0 // indirect github.com/rogpeppe/go-internal v1.12.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect diff --git a/go.sum b/go.sum index 332e9ed9a4..f72878f69e 100644 --- a/go.sum +++ b/go.sum @@ -1076,8 +1076,9 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/syndtr/gocapability v0.0.0-20170704070218-db04d3cc01c8/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= github.com/syndtr/gocapability v0.0.0-20180916011248-d98352740cb2/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= diff --git a/internal/fs/fs.go b/internal/fs/fs.go index 2be5d32df5..0724748392 100644 --- a/internal/fs/fs.go +++ b/internal/fs/fs.go @@ -15,6 +15,7 @@ package fs import ( + "context" "errors" "fmt" "io" @@ -30,7 +31,7 @@ import ( "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/file" "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/file/downloader" "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/lru" - "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/util" + cacheutil "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/util" "github.com/googlecloudplatform/gcsfuse/v2/internal/config" "github.com/googlecloudplatform/gcsfuse/v2/internal/contentcache" "github.com/googlecloudplatform/gcsfuse/v2/internal/fs/handle" @@ -39,11 +40,11 @@ import ( "github.com/googlecloudplatform/gcsfuse/v2/internal/locker" "github.com/googlecloudplatform/gcsfuse/v2/internal/logger" "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" + "github.com/googlecloudplatform/gcsfuse/v2/internal/util" "github.com/jacobsa/fuse" "github.com/jacobsa/fuse/fuseops" "github.com/jacobsa/fuse/fuseutil" "github.com/jacobsa/timeutil" - "golang.org/x/net/context" ) type ServerConfig struct { @@ -223,7 +224,7 @@ func createFileCacheHandler(cfg *ServerConfig) (fileCacheHandler *file.CacheHand if cfg.MountConfig.FileCacheConfig.MaxSizeMB == -1 { sizeInBytes = math.MaxUint64 } else { - sizeInBytes = uint64(cfg.MountConfig.FileCacheConfig.MaxSizeMB) * util.MiB + sizeInBytes = uint64(cfg.MountConfig.FileCacheConfig.MaxSizeMB) * cacheutil.MiB } fileInfoCache := lru.NewCache(sizeInBytes) @@ -231,12 +232,12 @@ func createFileCacheHandler(cfg *ServerConfig) (fileCacheHandler *file.CacheHand // Adding a new directory inside cacheDir to keep file-cache separate from // metadata cache if and when we support storing metadata cache on disk in // the future. - cacheDir = path.Join(cacheDir, util.FileCache) + cacheDir = path.Join(cacheDir, cacheutil.FileCache) - filePerm := util.DefaultFilePerm - dirPerm := util.DefaultDirPerm + filePerm := cacheutil.DefaultFilePerm + dirPerm := cacheutil.DefaultDirPerm - cacheDirErr := util.CreateCacheDirectoryIfNotPresentAt(cacheDir, dirPerm) + cacheDirErr := cacheutil.CreateCacheDirectoryIfNotPresentAt(cacheDir, dirPerm) if cacheDirErr != nil { return nil, fmt.Errorf("createFileCacheHandler: while creating file cache directory: %w", cacheDirErr) } @@ -1313,6 +1314,13 @@ func (fs *fileSystem) StatFS( func (fs *fileSystem) LookUpInode( ctx context.Context, op *fuseops.LookUpInodeOp) (err error) { + if fs.mountConfig.FileSystemConfig.IgnoreInterrupts { + // When ignore interrupts config is set, we are creating a new context not + // cancellable by parent context. + var cancel context.CancelFunc + ctx, cancel = util.IsolateContextFromParentContext(ctx) + defer cancel() + } // Find the parent directory in question. fs.mu.Lock() parent := fs.dirInodeOrDie(op.Parent) @@ -1342,6 +1350,13 @@ func (fs *fileSystem) LookUpInode( func (fs *fileSystem) GetInodeAttributes( ctx context.Context, op *fuseops.GetInodeAttributesOp) (err error) { + if fs.mountConfig.FileSystemConfig.IgnoreInterrupts { + // When ignore interrupts config is set, we are creating a new context not + // cancellable by parent context. + var cancel context.CancelFunc + ctx, cancel = util.IsolateContextFromParentContext(ctx) + defer cancel() + } // Find the inode. fs.mu.Lock() in := fs.inodeOrDie(op.Inode) @@ -1363,6 +1378,13 @@ func (fs *fileSystem) GetInodeAttributes( func (fs *fileSystem) SetInodeAttributes( ctx context.Context, op *fuseops.SetInodeAttributesOp) (err error) { + if fs.mountConfig.FileSystemConfig.IgnoreInterrupts { + // When ignore interrupts config is set, we are creating a new context not + // cancellable by parent context. + var cancel context.CancelFunc + ctx, cancel = util.IsolateContextFromParentContext(ctx) + defer cancel() + } // Find the inode. fs.mu.Lock() in := fs.inodeOrDie(op.Inode) @@ -1422,6 +1444,13 @@ func (fs *fileSystem) ForgetInode( func (fs *fileSystem) MkDir( ctx context.Context, op *fuseops.MkDirOp) (err error) { + if fs.mountConfig.FileSystemConfig.IgnoreInterrupts { + // When ignore interrupts config is set, we are creating a new context not + // cancellable by parent context. + var cancel context.CancelFunc + ctx, cancel = util.IsolateContextFromParentContext(ctx) + defer cancel() + } // Find the parent. fs.mu.Lock() parent := fs.dirInodeOrDie(op.Parent) @@ -1474,6 +1503,13 @@ func (fs *fileSystem) MkDir( func (fs *fileSystem) MkNode( ctx context.Context, op *fuseops.MkNodeOp) (err error) { + if fs.mountConfig.FileSystemConfig.IgnoreInterrupts { + // When ignore interrupts config is set, we are creating a new context not + // cancellable by parent context. + var cancel context.CancelFunc + ctx, cancel = util.IsolateContextFromParentContext(ctx) + defer cancel() + } if (op.Mode & (iofs.ModeNamedPipe | iofs.ModeSocket)) != 0 { return syscall.ENOTSUP } @@ -1597,6 +1633,13 @@ func (fs *fileSystem) createLocalFile( func (fs *fileSystem) CreateFile( ctx context.Context, op *fuseops.CreateFileOp) (err error) { + if fs.mountConfig.FileSystemConfig.IgnoreInterrupts { + // When ignore interrupts config is set, we are creating a new context not + // cancellable by parent context. + var cancel context.CancelFunc + ctx, cancel = util.IsolateContextFromParentContext(ctx) + defer cancel() + } // Create the child. var child inode.Inode if fs.mountConfig.CreateEmptyFile { @@ -1639,6 +1682,13 @@ func (fs *fileSystem) CreateFile( func (fs *fileSystem) CreateSymlink( ctx context.Context, op *fuseops.CreateSymlinkOp) (err error) { + if fs.mountConfig.FileSystemConfig.IgnoreInterrupts { + // When ignore interrupts config is set, we are creating a new context not + // cancellable by parent context. + var cancel context.CancelFunc + ctx, cancel = util.IsolateContextFromParentContext(ctx) + defer cancel() + } // Find the parent. fs.mu.Lock() parent := fs.dirInodeOrDie(op.Parent) @@ -1702,6 +1752,13 @@ func (fs *fileSystem) RmDir( ctx context.Context, op *fuseops.RmDirOp) (err error) { + if fs.mountConfig.FileSystemConfig.IgnoreInterrupts { + // When ignore interrupts config is set, we are creating a new context not + // cancellable by parent context. + var cancel context.CancelFunc + ctx, cancel = util.IsolateContextFromParentContext(ctx) + defer cancel() + } // Find the parent. fs.mu.Lock() parent := fs.dirInodeOrDie(op.Parent) @@ -1797,6 +1854,13 @@ func (fs *fileSystem) RmDir( func (fs *fileSystem) Rename( ctx context.Context, op *fuseops.RenameOp) (err error) { + if fs.mountConfig.FileSystemConfig.IgnoreInterrupts { + // When ignore interrupts config is set, we are creating a new context not + // cancellable by parent context. + var cancel context.CancelFunc + ctx, cancel = util.IsolateContextFromParentContext(ctx) + defer cancel() + } // Find the old and new parents. fs.mu.Lock() oldParent := fs.dirInodeOrDie(op.OldParent) @@ -2009,6 +2073,13 @@ func (fs *fileSystem) renameDir( func (fs *fileSystem) Unlink( ctx context.Context, op *fuseops.UnlinkOp) (err error) { + if fs.mountConfig.FileSystemConfig.IgnoreInterrupts { + // When ignore interrupts config is set, we are creating a new context not + // cancellable by parent context. + var cancel context.CancelFunc + ctx, cancel = util.IsolateContextFromParentContext(ctx) + defer cancel() + } // Find the parent. fs.mu.Lock() parent := fs.dirInodeOrDie(op.Parent) @@ -2077,6 +2148,13 @@ func (fs *fileSystem) OpenDir( func (fs *fileSystem) ReadDir( ctx context.Context, op *fuseops.ReadDirOp) (err error) { + if fs.mountConfig.FileSystemConfig.IgnoreInterrupts { + // When ignore interrupts config is set, we are creating a new context not + // cancellable by parent context. + var cancel context.CancelFunc + ctx, cancel = util.IsolateContextFromParentContext(ctx) + defer cancel() + } // Find the handle. fs.mu.Lock() dh := fs.handles[op.Handle].(*handle.DirHandle) @@ -2142,7 +2220,13 @@ func (fs *fileSystem) OpenFile( func (fs *fileSystem) ReadFile( ctx context.Context, op *fuseops.ReadFileOp) (err error) { - + if fs.mountConfig.FileSystemConfig.IgnoreInterrupts { + // When ignore interrupts config is set, we are creating a new context not + // cancellable by parent context. + var cancel context.CancelFunc + ctx, cancel = util.IsolateContextFromParentContext(ctx) + defer cancel() + } // Save readOp in context for access in logs. ctx = context.WithValue(ctx, gcsx.ReadOp, op) @@ -2187,6 +2271,13 @@ func (fs *fileSystem) ReadSymlink( func (fs *fileSystem) WriteFile( ctx context.Context, op *fuseops.WriteFileOp) (err error) { + if fs.mountConfig.FileSystemConfig.IgnoreInterrupts { + // When ignore interrupts config is set, we are creating a new context not + // cancellable by parent context. + var cancel context.CancelFunc + ctx, cancel = util.IsolateContextFromParentContext(ctx) + defer cancel() + } // Find the inode. fs.mu.Lock() in := fs.fileInodeOrDie(op.Inode) @@ -2207,6 +2298,13 @@ func (fs *fileSystem) WriteFile( func (fs *fileSystem) SyncFile( ctx context.Context, op *fuseops.SyncFileOp) (err error) { + if fs.mountConfig.FileSystemConfig.IgnoreInterrupts { + // When ignore interrupts config is set, we are creating a new context not + // cancellable by parent context. + var cancel context.CancelFunc + ctx, cancel = util.IsolateContextFromParentContext(ctx) + defer cancel() + } // Find the inode. fs.mu.Lock() in := fs.inodeOrDie(op.Inode) @@ -2233,6 +2331,13 @@ func (fs *fileSystem) SyncFile( func (fs *fileSystem) FlushFile( ctx context.Context, op *fuseops.FlushFileOp) (err error) { + if fs.mountConfig.FileSystemConfig.IgnoreInterrupts { + // When ignore interrupts config is set, we are creating a new context not + // cancellable by parent context. + var cancel context.CancelFunc + ctx, cancel = util.IsolateContextFromParentContext(ctx) + defer cancel() + } // Find the inode. fs.mu.Lock() in := fs.fileInodeOrDie(op.Inode) diff --git a/internal/util/util.go b/internal/util/util.go index 88ec6d97fd..1343591bd9 100644 --- a/internal/util/util.go +++ b/internal/util/util.go @@ -15,6 +15,7 @@ package util import ( + "context" "encoding/json" "fmt" "math" @@ -102,3 +103,10 @@ func BytesToHigherMiBs(bytes uint64) uint64 { const bytesInOneMiB uint64 = 1 << 20 return uint64(math.Ceil(float64(bytes) / float64(bytesInOneMiB))) } + +// IsolateContextFromParentContext creates a copy of the parent context which is +// not cancelled when parent context is cancelled. +func IsolateContextFromParentContext(ctx context.Context) (context.Context, context.CancelFunc) { + ctx = context.WithoutCancel(ctx) + return context.WithCancel(ctx) +} diff --git a/internal/util/util_test.go b/internal/util/util_test.go index b9dbd9f9a0..38bbd91eaf 100644 --- a/internal/util/util_test.go +++ b/internal/util/util_test.go @@ -15,137 +15,140 @@ package util import ( + "context" "errors" "math" "os" "path/filepath" "testing" - . "github.com/jacobsa/ogletest" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/suite" ) const gcsFuseParentProcessDir = "/var/generic/google" -func TestUtil(t *testing.T) { RunTests(t) } - //////////////////////////////////////////////////////////////////////// // Boilerplate //////////////////////////////////////////////////////////////////////// type UtilTest struct { + suite.Suite } -func init() { RegisterTestSuite(&UtilTest{}) } +func TestUtilSuite(t *testing.T) { + suite.Run(t, new(UtilTest)) +} //////////////////////////////////////////////////////////////////////// // Tests //////////////////////////////////////////////////////////////////////// -func (t *UtilTest) ResolveWhenParentProcDirEnvNotSetAndFilePathStartsWithTilda() { +func (ts *UtilTest) TestResolveWhenParentProcDirEnvNotSetAndFilePathStartsWithTilda() { resolvedPath, err := GetResolvedPath("~/test.txt") - AssertEq(nil, err) + assert.Equal(ts.T(), nil, err) homeDir, err := os.UserHomeDir() - AssertEq(nil, err) - ExpectEq(filepath.Join(homeDir, "test.txt"), resolvedPath) + assert.Equal(ts.T(), nil, err) + assert.Equal(ts.T(), filepath.Join(homeDir, "test.txt"), resolvedPath) } -func (t *UtilTest) ResolveWhenParentProcDirEnvNotSetAndFilePathStartsWithDot() { +func (ts *UtilTest) TestResolveWhenParentProcDirEnvNotSetAndFilePathStartsWithDot() { resolvedPath, err := GetResolvedPath("./test.txt") - AssertEq(nil, err) + assert.Equal(ts.T(), nil, err) currentWorkingDir, err := os.Getwd() - AssertEq(nil, err) - ExpectEq(filepath.Join(currentWorkingDir, "./test.txt"), resolvedPath) + assert.Equal(ts.T(), nil, err) + assert.Equal(ts.T(), filepath.Join(currentWorkingDir, "./test.txt"), resolvedPath) } -func (t *UtilTest) ResolveWhenParentProcDirEnvNotSetAndFilePathStartsWithDoubleDot() { +func (ts *UtilTest) TestResolveWhenParentProcDirEnvNotSetAndFilePathStartsWithDoubleDot() { resolvedPath, err := GetResolvedPath("../test.txt") - AssertEq(nil, err) + assert.Equal(ts.T(), nil, err) currentWorkingDir, err := os.Getwd() - AssertEq(nil, err) - ExpectEq(filepath.Join(currentWorkingDir, "../test.txt"), resolvedPath) + assert.Equal(ts.T(), nil, err) + assert.Equal(ts.T(), filepath.Join(currentWorkingDir, "../test.txt"), resolvedPath) } -func (t *UtilTest) ResolveWhenParentProcDirEnvNotSetAndRelativePath() { +func (ts *UtilTest) TestResolveWhenParentProcDirEnvNotSetAndRelativePath() { resolvedPath, err := GetResolvedPath("test.txt") - AssertEq(nil, err) + assert.Equal(ts.T(), nil, err) currentWorkingDir, err := os.Getwd() - AssertEq(nil, err) - ExpectEq(filepath.Join(currentWorkingDir, "test.txt"), resolvedPath) + assert.Equal(ts.T(), nil, err) + assert.Equal(ts.T(), filepath.Join(currentWorkingDir, "test.txt"), resolvedPath) } -func (t *UtilTest) ResolveWhenParentProcDirEnvNotSetAndAbsoluteFilePath() { +func (ts *UtilTest) TestResolveWhenParentProcDirEnvNotSetAndAbsoluteFilePath() { resolvedPath, err := GetResolvedPath("/var/dir/test.txt") - AssertEq(nil, err) - ExpectEq("/var/dir/test.txt", resolvedPath) + assert.Equal(ts.T(), nil, err) + assert.Equal(ts.T(), "/var/dir/test.txt", resolvedPath) } -func (t *UtilTest) ResolveEmptyFilePath() { +func (ts *UtilTest) ResolveEmptyFilePath() { resolvedPath, err := GetResolvedPath("") - AssertEq(nil, err) - ExpectEq("", resolvedPath) + assert.Equal(ts.T(), nil, err) + assert.Equal(ts.T(), "", resolvedPath) } // Below all tests when GCSFUSE_PARENT_PROCESS_DIR env variable is set. // By setting this environment variable, resolve will work for child process. -func (t *UtilTest) ResolveWhenParentProcDirEnvSetAndFilePathStartsWithTilda() { +func (ts *UtilTest) ResolveWhenParentProcDirEnvSetAndFilePathStartsWithTilda() { os.Setenv(GCSFUSE_PARENT_PROCESS_DIR, gcsFuseParentProcessDir) defer os.Unsetenv(GCSFUSE_PARENT_PROCESS_DIR) resolvedPath, err := GetResolvedPath("~/test.txt") - AssertEq(nil, err) + assert.Equal(ts.T(), nil, err) homeDir, err := os.UserHomeDir() - AssertEq(nil, err) - ExpectEq(filepath.Join(homeDir, "test.txt"), resolvedPath) + assert.Equal(ts.T(), nil, err) + assert.Equal(ts.T(), filepath.Join(homeDir, "test.txt"), resolvedPath) } -func (t *UtilTest) ResolveWhenParentProcDirEnvSetAndFilePathStartsWithDot() { +func (ts *UtilTest) ResolveWhenParentProcDirEnvSetAndFilePathStartsWithDot() { os.Setenv(GCSFUSE_PARENT_PROCESS_DIR, gcsFuseParentProcessDir) defer os.Unsetenv(GCSFUSE_PARENT_PROCESS_DIR) resolvedPath, err := GetResolvedPath("./test.txt") - AssertEq(nil, err) - ExpectEq(filepath.Join(gcsFuseParentProcessDir, "./test.txt"), resolvedPath) + assert.Equal(ts.T(), nil, err) + assert.Equal(ts.T(), filepath.Join(gcsFuseParentProcessDir, "./test.txt"), resolvedPath) } -func (t *UtilTest) ResolveWhenParentProcDirEnvSetAndFilePathStartsWithDoubleDot() { +func (ts *UtilTest) ResolveWhenParentProcDirEnvSetAndFilePathStartsWithDoubleDot() { os.Setenv(GCSFUSE_PARENT_PROCESS_DIR, gcsFuseParentProcessDir) defer os.Unsetenv(GCSFUSE_PARENT_PROCESS_DIR) resolvedPath, err := GetResolvedPath("../test.txt") - AssertEq(nil, err) - ExpectEq(filepath.Join(gcsFuseParentProcessDir, "../test.txt"), resolvedPath) + assert.Equal(ts.T(), nil, err) + assert.Equal(ts.T(), filepath.Join(gcsFuseParentProcessDir, "../test.txt"), resolvedPath) } -func (t *UtilTest) ResolveWhenParentProcDirEnvSetAndRelativePath() { +func (ts *UtilTest) ResolveWhenParentProcDirEnvSetAndRelativePath() { os.Setenv(GCSFUSE_PARENT_PROCESS_DIR, gcsFuseParentProcessDir) defer os.Unsetenv(GCSFUSE_PARENT_PROCESS_DIR) resolvedPath, err := GetResolvedPath("test.txt") - AssertEq(nil, err) - ExpectEq(filepath.Join(gcsFuseParentProcessDir, "test.txt"), resolvedPath) + assert.Equal(ts.T(), nil, err) + assert.Equal(ts.T(), filepath.Join(gcsFuseParentProcessDir, "test.txt"), resolvedPath) } -func (t *UtilTest) ResolveWhenParentProcDirEnvSetAndAbsoluteFilePath() { +func (ts *UtilTest) ResolveWhenParentProcDirEnvSetAndAbsoluteFilePath() { os.Setenv(GCSFUSE_PARENT_PROCESS_DIR, gcsFuseParentProcessDir) defer os.Unsetenv(GCSFUSE_PARENT_PROCESS_DIR) resolvedPath, err := GetResolvedPath("/var/dir/test.txt") - AssertEq(nil, err) - ExpectEq("/var/dir/test.txt", resolvedPath) + assert.Equal(ts.T(), nil, err) + assert.Equal(ts.T(), "/var/dir/test.txt", resolvedPath) } -func (t *UtilTest) TestStringifyShouldReturnAllFieldsPassedInCustomObjectAsMarshalledString() { +func (ts *UtilTest) TestStringifyShouldReturnAllFieldsPassedInCustomObjectAsMarshalledString() { sampleMap := map[string]int{ "1": 1, "2": 2, @@ -163,10 +166,10 @@ func (t *UtilTest) TestStringifyShouldReturnAllFieldsPassedInCustomObjectAsMarsh actual, _ := Stringify(customObject) expected := "{\"Value\":\"test_value\",\"NestedValue\":{\"SomeField\":10,\"SomeOther\":{\"1\":1,\"2\":2,\"3\":3}}}" - AssertEq(expected, actual) + assert.Equal(ts.T(), expected, actual) } -func (t *UtilTest) TestStringifyShouldReturnEmptyStringWhenMarshalErrorsOut() { +func (ts *UtilTest) TestStringifyShouldReturnEmptyStringWhenMarshalErrorsOut() { customInstance := customTypeForError{ value: "example", } @@ -174,7 +177,7 @@ func (t *UtilTest) TestStringifyShouldReturnEmptyStringWhenMarshalErrorsOut() { actual, _ := Stringify(customInstance) expected := "" - AssertEq(expected, actual) + assert.Equal(ts.T(), expected, actual) } type customTypeForSuccess struct { @@ -194,7 +197,7 @@ func (c customTypeForError) MarshalJSON() ([]byte, error) { return nil, errors.New("intentional error during JSON marshaling") } -func (t *UtilTest) TestMiBsToBytes() { +func (ts *UtilTest) TestMiBsToBytes() { cases := []struct { mib uint64 bytes uint64 @@ -222,11 +225,11 @@ func (t *UtilTest) TestMiBsToBytes() { } for _, tc := range cases { - AssertEq(tc.bytes, MiBsToBytes(tc.mib)) + assert.Equal(ts.T(), tc.bytes, MiBsToBytes(tc.mib)) } } -func (t *UtilTest) TestBytesToHigherMiBs() { +func (ts *UtilTest) TestBytesToHigherMiBs() { cases := []struct { bytes uint64 mib uint64 @@ -258,6 +261,20 @@ func (t *UtilTest) TestBytesToHigherMiBs() { } for _, tc := range cases { - AssertEq(tc.mib, BytesToHigherMiBs(tc.bytes)) + assert.Equal(ts.T(), tc.mib, BytesToHigherMiBs(tc.bytes)) } } + +func (ts *UtilTest) TestIsolateContextFromParentContext() { + parentCtx, parentCtxCancel := context.WithCancel(context.Background()) + + // Call the method and cancel the parent context. + newCtx, newCtxCancel := IsolateContextFromParentContext(parentCtx) + parentCtxCancel() + + // Validate new context is not cancelled after parent's cancellation. + assert.NoError(ts.T(), newCtx.Err()) + // Cancel the new context and validate. + newCtxCancel() + assert.ErrorIs(ts.T(), newCtx.Err(), context.Canceled) +} diff --git a/tools/integration_tests/interrupt/git_clone_test.go b/tools/integration_tests/interrupt/git_clone_test.go new file mode 100644 index 0000000000..9bd451ebfe --- /dev/null +++ b/tools/integration_tests/interrupt/git_clone_test.go @@ -0,0 +1,146 @@ +// Copyright 2023 Google Inc. All Rights Reserved. +// +// 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. +// +// Provides integration tests for running git operations with ignore interrupts +// flag/config set. + +package interrupt + +import ( + "path" + "testing" + + "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/util" + "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" + "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" + "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/test_setup" +) + +const ( + repoURL = "https://github.com/gcsfuse-github-machine-user-bot/test-repository.git" + repoName = "test-repository" + branchName = "test-branch" + testFileName = "testFile" + tool = "git" +) + +var ( + testDirPath string +) + +//////////////////////////////////////////////////////////////////////// +// Boilerplate +//////////////////////////////////////////////////////////////////////// + +type ignoreInterruptsTest struct{} + +func (s *ignoreInterruptsTest) Teardown(t *testing.T) {} + +func (s *ignoreInterruptsTest) Setup(t *testing.T) { + testDirPath = setup.SetupTestDirectory(testDirName) +} + +//////////////////////////////////////////////////////////////////////// +// Helpers +//////////////////////////////////////////////////////////////////////// + +func cloneRepository() ([]byte, error) { + return operations.ExecuteToolCommandfInDirectory(testDirPath, tool, "clone %s", repoURL) +} + +func checkoutBranch(branchName string) ([]byte, error) { + repositoryPath := path.Join(testDirPath, repoName) + return operations.ExecuteToolCommandfInDirectory(repositoryPath, tool, "checkout %s", branchName) +} + +func emptyCommit() ([]byte, error) { + repositoryPath := path.Join(testDirPath, repoName) + return operations.ExecuteToolCommandfInDirectory(repositoryPath, tool, "commit --allow-empty -m \" empty commit\"") +} + +func gitAdd(filePath string) ([]byte, error) { + repositoryPath := path.Join(testDirPath, repoName) + return operations.ExecuteToolCommandfInDirectory(repositoryPath, tool, "add %s", filePath) +} + +func nonEmptyCommit() ([]byte, error) { + repositoryPath := path.Join(testDirPath, repoName) + return operations.ExecuteToolCommandfInDirectory(repositoryPath, tool, "commit -m \"test\"") +} + +//////////////////////////////////////////////////////////////////////// +// Test scenarios +//////////////////////////////////////////////////////////////////////// + +func (s *ignoreInterruptsTest) TestGitClone(t *testing.T) { + output, err := cloneRepository() + + if err != nil { + t.Errorf("Git clone failed: %s: %v", string(output), err) + } +} + +func (s *ignoreInterruptsTest) TestGitCheckout(t *testing.T) { + _, err := cloneRepository() + if err != nil { + t.Errorf("cloneRepository() failed: %v", err) + } + + output, err := checkoutBranch(branchName) + + if err != nil { + t.Errorf("Git checkout failed: %s: %v", string(output), err) + } +} + +func (s *ignoreInterruptsTest) TestGitEmptyCommit(t *testing.T) { + _, err := cloneRepository() + if err != nil { + t.Errorf("cloneRepository() failed: %v", err) + } + + output, err := emptyCommit() + + if err != nil { + t.Errorf("Git empty commit failed: %s: %v", string(output), err) + } +} + +func (s *ignoreInterruptsTest) TestGitCommitWithChanges(t *testing.T) { + _, err := cloneRepository() + if err != nil { + t.Errorf("cloneRepository() failed: %v", err) + } + + filePath := path.Join(testDirPath, repoName, testFileName) + operations.CreateFileOfSize(util.MiB, filePath, t) + output, err := gitAdd(filePath) + if err != nil { + t.Errorf("Git add failed: %s: %v", string(output), err) + } + output, err = nonEmptyCommit() + + if err != nil { + t.Errorf("Git commit failed: %s: %v", string(output), err) + } +} + +//////////////////////////////////////////////////////////////////////// +// Test Function (Runs once before all tests) +//////////////////////////////////////////////////////////////////////// + +func TestIgnoreInterrupts(t *testing.T) { + ts := &ignoreInterruptsTest{} + test_setup.RunTests(t, ts) +} diff --git a/tools/integration_tests/interrupt/interrupt_test.go b/tools/integration_tests/interrupt/interrupt_test.go new file mode 100644 index 0000000000..5700f06c2b --- /dev/null +++ b/tools/integration_tests/interrupt/interrupt_test.go @@ -0,0 +1,61 @@ +// Copyright 2024 Google Inc. All Rights Reserved. +// +// 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 interrupt + +import ( + "os" + "path" + "testing" + + "github.com/googlecloudplatform/gcsfuse/v2/internal/config" + "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/mounting/static_mounting" + "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" +) + +const ( + testDirName = "InterruptTest" +) + +//////////////////////////////////////////////////////////////////////// +// TestMain +//////////////////////////////////////////////////////////////////////// + +func TestMain(m *testing.M) { + setup.ParseSetUpFlags() + setup.ExitWithFailureIfBothTestBucketAndMountedDirectoryFlagsAreNotSet() + + setup.RunTestsForMountedDirectoryFlag(m) + + // Else run tests for testBucket. + // Set up test directory. + setup.SetUpTestDirForTestBucketFlag() + + // Set up flags to run tests on. + mountConfig := config.MountConfig{ + FileSystemConfig: config.FileSystemConfig{IgnoreInterrupts: true}, + LogConfig: config.LogConfig{ + Severity: config.TRACE, + LogRotateConfig: config.DefaultLogRotateConfig(), + }, + } + flags := [][]string{{"--implicit-dirs=true", "--ignore-interrupts"}, + {"--config-file=" + setup.YAMLConfigFile(mountConfig, "config.yaml")}} + + successCode := static_mounting.RunTests(flags, m) + + // Clean up test directory created. + setup.CleanupDirectoryOnGCS(path.Join(setup.TestBucket(), testDirName)) + os.Exit(successCode) +} diff --git a/tools/integration_tests/run_e2e_tests.sh b/tools/integration_tests/run_e2e_tests.sh index 0215b6615f..2d7b9396fc 100755 --- a/tools/integration_tests/run_e2e_tests.sh +++ b/tools/integration_tests/run_e2e_tests.sh @@ -46,6 +46,7 @@ TEST_DIR_PARALLEL=( "read_large_files" "explicit_dir" "implicit_dir" + "interrupt" "operations" ) # These tests never become parallel as it is changing bucket permissions. diff --git a/tools/integration_tests/util/operations/operations.go b/tools/integration_tests/util/operations/operations.go index a1e28a47c7..c1fbb6da2e 100644 --- a/tools/integration_tests/util/operations/operations.go +++ b/tools/integration_tests/util/operations/operations.go @@ -39,6 +39,19 @@ func executeToolCommandf(tool string, format string, args ...any) ([]byte, error cmdArgs := tool + " " + fmt.Sprintf(format, args...) cmd := exec.Command("/bin/bash", "-c", cmdArgs) + return runCommand(cmd) +} + +// Executes any given tool (e.g. gsutil/gcloud) with given args in specified directory. +func ExecuteToolCommandfInDirectory(dirPath, tool, format string, args ...any) ([]byte, error) { + cmdArgs := tool + " " + fmt.Sprintf(format, args...) + cmd := exec.Command("/bin/bash", "-c", cmdArgs) + cmd.Dir = dirPath + + return runCommand(cmd) +} + +func runCommand(cmd *exec.Cmd) ([]byte, error) { var stdout bytes.Buffer var stderr bytes.Buffer @@ -47,7 +60,7 @@ func executeToolCommandf(tool string, format string, args ...any) ([]byte, error err := cmd.Run() if err != nil { - return stdout.Bytes(), fmt.Errorf("failed command '%s': %v, %s", cmdArgs, err, stderr.String()) + return stdout.Bytes(), fmt.Errorf("failed command '%s': %v, %s", cmd.String(), err, stderr.String()) } return stdout.Bytes(), nil