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 52b245f4e7..0e09edc47d 100644 --- a/internal/fs/fs.go +++ b/internal/fs/fs.go @@ -40,6 +40,7 @@ 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" @@ -1309,13 +1310,6 @@ func (fs *fileSystem) StatFS( return } -// 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) -} - // LOCKS_EXCLUDED(fs.mu) func (fs *fileSystem) LookUpInode( ctx context.Context, @@ -1324,7 +1318,7 @@ func (fs *fileSystem) LookUpInode( // When ignore interrupts config is set, we are creating a new context not // cancellable by parent context. var cancel context.CancelFunc - ctx, cancel = isolateContextFromParentContext(ctx) + ctx, cancel = IsolateContextFromParentContext(ctx) defer cancel() } // Find the parent directory in question. @@ -1360,7 +1354,7 @@ func (fs *fileSystem) GetInodeAttributes( // When ignore interrupts config is set, we are creating a new context not // cancellable by parent context. var cancel context.CancelFunc - ctx, cancel = isolateContextFromParentContext(ctx) + ctx, cancel = IsolateContextFromParentContext(ctx) defer cancel() } // Find the inode. @@ -1388,7 +1382,7 @@ func (fs *fileSystem) SetInodeAttributes( // When ignore interrupts config is set, we are creating a new context not // cancellable by parent context. var cancel context.CancelFunc - ctx, cancel = isolateContextFromParentContext(ctx) + ctx, cancel = IsolateContextFromParentContext(ctx) defer cancel() } // Find the inode. @@ -1454,7 +1448,7 @@ func (fs *fileSystem) MkDir( // When ignore interrupts config is set, we are creating a new context not // cancellable by parent context. var cancel context.CancelFunc - ctx, cancel = isolateContextFromParentContext(ctx) + ctx, cancel = IsolateContextFromParentContext(ctx) defer cancel() } // Find the parent. @@ -1513,7 +1507,7 @@ func (fs *fileSystem) MkNode( // When ignore interrupts config is set, we are creating a new context not // cancellable by parent context. var cancel context.CancelFunc - ctx, cancel = isolateContextFromParentContext(ctx) + ctx, cancel = IsolateContextFromParentContext(ctx) defer cancel() } if (op.Mode & (iofs.ModeNamedPipe | iofs.ModeSocket)) != 0 { @@ -1643,7 +1637,7 @@ func (fs *fileSystem) CreateFile( // When ignore interrupts config is set, we are creating a new context not // cancellable by parent context. var cancel context.CancelFunc - ctx, cancel = isolateContextFromParentContext(ctx) + ctx, cancel = IsolateContextFromParentContext(ctx) defer cancel() } // Create the child. @@ -1692,7 +1686,7 @@ func (fs *fileSystem) CreateSymlink( // When ignore interrupts config is set, we are creating a new context not // cancellable by parent context. var cancel context.CancelFunc - ctx, cancel = isolateContextFromParentContext(ctx) + ctx, cancel = IsolateContextFromParentContext(ctx) defer cancel() } // Find the parent. @@ -1762,7 +1756,7 @@ func (fs *fileSystem) RmDir( // When ignore interrupts config is set, we are creating a new context not // cancellable by parent context. var cancel context.CancelFunc - ctx, cancel = isolateContextFromParentContext(ctx) + ctx, cancel = IsolateContextFromParentContext(ctx) defer cancel() } // Find the parent. @@ -1864,7 +1858,7 @@ func (fs *fileSystem) Rename( // When ignore interrupts config is set, we are creating a new context not // cancellable by parent context. var cancel context.CancelFunc - ctx, cancel = isolateContextFromParentContext(ctx) + ctx, cancel = IsolateContextFromParentContext(ctx) defer cancel() } // Find the old and new parents. @@ -2083,7 +2077,7 @@ func (fs *fileSystem) Unlink( // When ignore interrupts config is set, we are creating a new context not // cancellable by parent context. var cancel context.CancelFunc - ctx, cancel = isolateContextFromParentContext(ctx) + ctx, cancel = IsolateContextFromParentContext(ctx) defer cancel() } // Find the parent. @@ -2158,7 +2152,7 @@ func (fs *fileSystem) ReadDir( // When ignore interrupts config is set, we are creating a new context not // cancellable by parent context. var cancel context.CancelFunc - ctx, cancel = isolateContextFromParentContext(ctx) + ctx, cancel = IsolateContextFromParentContext(ctx) defer cancel() } // Find the handle. @@ -2230,7 +2224,7 @@ func (fs *fileSystem) ReadFile( // When ignore interrupts config is set, we are creating a new context not // cancellable by parent context. var cancel context.CancelFunc - ctx, cancel = isolateContextFromParentContext(ctx) + ctx, cancel = IsolateContextFromParentContext(ctx) defer cancel() } // Save readOp in context for access in logs. @@ -2281,7 +2275,7 @@ func (fs *fileSystem) WriteFile( // When ignore interrupts config is set, we are creating a new context not // cancellable by parent context. var cancel context.CancelFunc - ctx, cancel = isolateContextFromParentContext(ctx) + ctx, cancel = IsolateContextFromParentContext(ctx) defer cancel() } // Find the inode. @@ -2308,7 +2302,7 @@ func (fs *fileSystem) SyncFile( // When ignore interrupts config is set, we are creating a new context not // cancellable by parent context. var cancel context.CancelFunc - ctx, cancel = isolateContextFromParentContext(ctx) + ctx, cancel = IsolateContextFromParentContext(ctx) defer cancel() } // Find the inode. @@ -2341,7 +2335,7 @@ func (fs *fileSystem) FlushFile( // When ignore interrupts config is set, we are creating a new context not // cancellable by parent context. var cancel context.CancelFunc - ctx, cancel = isolateContextFromParentContext(ctx) + ctx, cancel = IsolateContextFromParentContext(ctx) defer cancel() } // Find the inode. diff --git a/internal/fs/interrupt_test.go b/internal/fs/interrupt_test.go deleted file mode 100644 index f6db4361bb..0000000000 --- a/internal/fs/interrupt_test.go +++ /dev/null @@ -1,40 +0,0 @@ -// 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. - -// A collection of tests for a file system where we do not attempt to write to -// the file system at all. Rather we set up contents in a GCS bucket out of -// band, wait for them to be available, and then read them via the file system. - -package fs - -import ( - "context" - "testing" - - "github.com/jacobsa/ogletest" -) - -func TestIsolateContextFromParentContext(t *testing.T) { - 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. - ogletest.AssertEq(nil, newCtx.Err()) - // cancel the new context and validate. - newCtxCancel() - ogletest.AssertEq(context.Canceled, newCtx.Err()) -} 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..eaa75cce98 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 (suite *UtilTest) TestResolveWhenParentProcDirEnvNotSetAndFilePathStartsWithTilda() { resolvedPath, err := GetResolvedPath("~/test.txt") - AssertEq(nil, err) + assert.Equal(suite.T(), nil, err) homeDir, err := os.UserHomeDir() - AssertEq(nil, err) - ExpectEq(filepath.Join(homeDir, "test.txt"), resolvedPath) + assert.Equal(suite.T(), nil, err) + assert.Equal(suite.T(), filepath.Join(homeDir, "test.txt"), resolvedPath) } -func (t *UtilTest) ResolveWhenParentProcDirEnvNotSetAndFilePathStartsWithDot() { +func (suite *UtilTest) TestResolveWhenParentProcDirEnvNotSetAndFilePathStartsWithDot() { resolvedPath, err := GetResolvedPath("./test.txt") - AssertEq(nil, err) + assert.Equal(suite.T(), nil, err) currentWorkingDir, err := os.Getwd() - AssertEq(nil, err) - ExpectEq(filepath.Join(currentWorkingDir, "./test.txt"), resolvedPath) + assert.Equal(suite.T(), nil, err) + assert.Equal(suite.T(), filepath.Join(currentWorkingDir, "./test.txt"), resolvedPath) } -func (t *UtilTest) ResolveWhenParentProcDirEnvNotSetAndFilePathStartsWithDoubleDot() { +func (suite *UtilTest) TestResolveWhenParentProcDirEnvNotSetAndFilePathStartsWithDoubleDot() { resolvedPath, err := GetResolvedPath("../test.txt") - AssertEq(nil, err) + assert.Equal(suite.T(), nil, err) currentWorkingDir, err := os.Getwd() - AssertEq(nil, err) - ExpectEq(filepath.Join(currentWorkingDir, "../test.txt"), resolvedPath) + assert.Equal(suite.T(), nil, err) + assert.Equal(suite.T(), filepath.Join(currentWorkingDir, "../test.txt"), resolvedPath) } -func (t *UtilTest) ResolveWhenParentProcDirEnvNotSetAndRelativePath() { +func (suite *UtilTest) TestResolveWhenParentProcDirEnvNotSetAndRelativePath() { resolvedPath, err := GetResolvedPath("test.txt") - AssertEq(nil, err) + assert.Equal(suite.T(), nil, err) currentWorkingDir, err := os.Getwd() - AssertEq(nil, err) - ExpectEq(filepath.Join(currentWorkingDir, "test.txt"), resolvedPath) + assert.Equal(suite.T(), nil, err) + assert.Equal(suite.T(), filepath.Join(currentWorkingDir, "test.txt"), resolvedPath) } -func (t *UtilTest) ResolveWhenParentProcDirEnvNotSetAndAbsoluteFilePath() { +func (suite *UtilTest) TestResolveWhenParentProcDirEnvNotSetAndAbsoluteFilePath() { resolvedPath, err := GetResolvedPath("/var/dir/test.txt") - AssertEq(nil, err) - ExpectEq("/var/dir/test.txt", resolvedPath) + assert.Equal(suite.T(), nil, err) + assert.Equal(suite.T(), "/var/dir/test.txt", resolvedPath) } -func (t *UtilTest) ResolveEmptyFilePath() { +func (suite *UtilTest) ResolveEmptyFilePath() { resolvedPath, err := GetResolvedPath("") - AssertEq(nil, err) - ExpectEq("", resolvedPath) + assert.Equal(suite.T(), nil, err) + assert.Equal(suite.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 (suite *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(suite.T(), nil, err) homeDir, err := os.UserHomeDir() - AssertEq(nil, err) - ExpectEq(filepath.Join(homeDir, "test.txt"), resolvedPath) + assert.Equal(suite.T(), nil, err) + assert.Equal(suite.T(), filepath.Join(homeDir, "test.txt"), resolvedPath) } -func (t *UtilTest) ResolveWhenParentProcDirEnvSetAndFilePathStartsWithDot() { +func (suite *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(suite.T(), nil, err) + assert.Equal(suite.T(), filepath.Join(gcsFuseParentProcessDir, "./test.txt"), resolvedPath) } -func (t *UtilTest) ResolveWhenParentProcDirEnvSetAndFilePathStartsWithDoubleDot() { +func (suite *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(suite.T(), nil, err) + assert.Equal(suite.T(), filepath.Join(gcsFuseParentProcessDir, "../test.txt"), resolvedPath) } -func (t *UtilTest) ResolveWhenParentProcDirEnvSetAndRelativePath() { +func (suite *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(suite.T(), nil, err) + assert.Equal(suite.T(), filepath.Join(gcsFuseParentProcessDir, "test.txt"), resolvedPath) } -func (t *UtilTest) ResolveWhenParentProcDirEnvSetAndAbsoluteFilePath() { +func (suite *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(suite.T(), nil, err) + assert.Equal(suite.T(), "/var/dir/test.txt", resolvedPath) } -func (t *UtilTest) TestStringifyShouldReturnAllFieldsPassedInCustomObjectAsMarshalledString() { +func (suite *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(suite.T(), expected, actual) } -func (t *UtilTest) TestStringifyShouldReturnEmptyStringWhenMarshalErrorsOut() { +func (suite *UtilTest) TestStringifyShouldReturnEmptyStringWhenMarshalErrorsOut() { customInstance := customTypeForError{ value: "example", } @@ -174,7 +177,7 @@ func (t *UtilTest) TestStringifyShouldReturnEmptyStringWhenMarshalErrorsOut() { actual, _ := Stringify(customInstance) expected := "" - AssertEq(expected, actual) + assert.Equal(suite.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 (suite *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(suite.T(), tc.bytes, MiBsToBytes(tc.mib)) } } -func (t *UtilTest) TestBytesToHigherMiBs() { +func (suite *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(suite.T(), tc.mib, BytesToHigherMiBs(tc.bytes)) } } + +func (suite *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(suite.T(), newCtx.Err()) + // Cancel the new context and validate. + newCtxCancel() + assert.ErrorIs(suite.T(), newCtx.Err(), context.Canceled) +} diff --git a/tools/integration_tests/interrupt/interrupt_test.go b/tools/integration_tests/interrupt/interrupt_test.go index 3c3e55a90b..5700f06c2b 100644 --- a/tools/integration_tests/interrupt/interrupt_test.go +++ b/tools/integration_tests/interrupt/interrupt_test.go @@ -28,10 +28,6 @@ const ( testDirName = "InterruptTest" ) -//////////////////////////////////////////////////////////////////////// -// Helpers -//////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////// // TestMain ////////////////////////////////////////////////////////////////////////