diff --git a/cfg/types_test.go b/cfg/types_test.go index 511f3bd4ab..4e15d2f2e5 100644 --- a/cfg/types_test.go +++ b/cfg/types_test.go @@ -15,31 +15,257 @@ package cfg import ( + "fmt" + "os" + "path" "testing" "github.com/googlecloudplatform/gcsfuse/v2/internal/util" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) -func TestOctalTypeInConfigStringify(t *testing.T) { +func TestOctalTypeInConfigMarshalling(t *testing.T) { c := Config{ FileSystem: FileSystemConfig{ DirMode: 0755, }, } + expected := + `file-system: + dir-mode: "755" +` - str, err := util.Stringify(&c) + str, err := util.YAMLStringify(&c) if assert.NoError(t, err) { - assert.Equal(t, "file-system:\n dir-mode: \"755\"\n", str) + assert.Equal(t, expected, str) } } -func TestOctalStringify(t *testing.T) { +func TestOctalMarshalling(t *testing.T) { o := Octal(0765) - str, err := util.Stringify(&o) + str, err := util.YAMLStringify(&o) if assert.NoError(t, err) { assert.Equal(t, "\"765\"\n", str) } } + +func TestOctalUnmarshalling(t *testing.T) { + t.Parallel() + tests := []struct { + str string + expected Octal + wantErr bool + }{ + { + str: "753", + expected: 0753, + wantErr: false, + }, + { + str: "644", + expected: 0644, + wantErr: false, + }, + { + str: "945", + wantErr: true, + }, + { + str: "abc", + wantErr: true, + }, + } + + for idx, tc := range tests { + tc := tc + t.Run(fmt.Sprintf("octal-unmarshalling: %d", idx), func(t *testing.T) { + t.Parallel() + var o Octal + + err := (&o).UnmarshalText([]byte(tc.str)) + + if tc.wantErr { + assert.Error(t, err) + } else if assert.NoError(t, err) { + assert.Equal(t, tc.expected, o) + } + }) + } +} + +func TestLogSeverityUnmarshalling(t *testing.T) { + t.Parallel() + tests := []struct { + str string + expected LogSeverity + wantErr bool + }{ + { + str: "TRACE", + expected: "TRACE", + wantErr: false, + }, + { + str: "info", + expected: "INFO", + wantErr: false, + }, + { + str: "debUG", + expected: "DEBUG", + wantErr: false, + }, + { + str: "waRniNg", + expected: "WARNING", + wantErr: false, + }, + { + str: "OFF", + expected: "OFF", + wantErr: false, + }, + { + str: "ERROR", + expected: "ERROR", + wantErr: false, + }, + { + str: "EMPEROR", + wantErr: true, + }, + } + + for idx, tc := range tests { + tc := tc + t.Run(fmt.Sprintf("log-severity-unmarshalling: %d", idx), func(t *testing.T) { + t.Parallel() + var l LogSeverity + + err := (&l).UnmarshalText([]byte(tc.str)) + + if tc.wantErr { + assert.Error(t, err) + } else if assert.NoError(t, err) { + assert.Equal(t, tc.expected, l) + } + }) + } +} + +func TestProtocolUnmarshalling(t *testing.T) { + t.Parallel() + tests := []struct { + str string + expected Protocol + wantErr bool + }{ + { + str: "http1", + expected: "http1", + wantErr: false, + }, + { + str: "HTtp1", + expected: "http1", + wantErr: false, + }, + { + str: "gRPC", + expected: "grpc", + wantErr: false, + }, + { + str: "HTTP2", + expected: "http2", + wantErr: false, + }, + { + str: "http100", + wantErr: true, + }, + } + + for idx, tc := range tests { + tc := tc + t.Run(fmt.Sprintf("protocol-unmarshalling: %d", idx), func(t *testing.T) { + t.Parallel() + var p Protocol + + err := (&p).UnmarshalText([]byte(tc.str)) + + if tc.wantErr { + assert.Error(t, err) + } else if assert.NoError(t, err) { + assert.Equal(t, tc.expected, p) + } + }) + } +} + +func TestResolvedPathUnmarshalling(t *testing.T) { + t.Parallel() + h, err := os.UserHomeDir() + require.NoError(t, err) + tests := []struct { + str string + expected ResolvedPath + }{ + { + str: "~/test.txt", + expected: ResolvedPath(path.Join(h, "test.txt")), + }, + { + str: "/a/test.txt", + expected: "/a/test.txt", + }, + } + + for idx, tc := range tests { + tc := tc + t.Run(fmt.Sprintf("resolved-path-unmarshalling: %d", idx), func(t *testing.T) { + t.Parallel() + var p ResolvedPath + + err := (&p).UnmarshalText([]byte(tc.str)) + + if assert.NoError(t, err) { + assert.Equal(t, tc.expected, p) + } + }) + } +} + +func TestConfigMarshalling(t *testing.T) { + t.Parallel() + c := Config{ + FileSystem: FileSystemConfig{ + FileMode: 0732, + }, + GcsConnection: GcsConnectionConfig{ + ClientProtocol: "grpc", + BillingProject: "abc", + }, + GcsRetries: GcsRetriesConfig{ + MaxRetryAttempts: 45, + }, + } + + actual, err := util.YAMLStringify(c) + + expected := + `file-system: + file-mode: "732" +gcs-connection: + billing-project: abc + client-protocol: grpc +gcs-retries: + max-retry-attempts: 45 +` + if assert.NoError(t, err) { + assert.Equal(t, expected, actual) + } +} diff --git a/cmd/legacy_main.go b/cmd/legacy_main.go index ddefc7f222..3c5281ca8c 100644 --- a/cmd/legacy_main.go +++ b/cmd/legacy_main.go @@ -90,7 +90,7 @@ func getUserAgent(appName string, config string) string { } func logNewConfiguration(newConfig *cfg.Config) { - cfgStr, err := util.Stringify(newConfig) + cfgStr, err := util.YAMLStringify(newConfig) if err != nil { logger.Warnf("failed to stringify configuration: %v", err) return @@ -98,14 +98,14 @@ func logNewConfiguration(newConfig *cfg.Config) { logger.Infof("GCSFuse mount config: %s", cfgStr) } func logLegacyConfiguration(flags *flagStorage, mountConfig *config.MountConfig) { - flagsStringified, err := util.Stringify(*flags) + flagsStringified, err := util.JSONStringify(*flags) if err != nil { logger.Warnf("failed to stringify cli flags: %v", err) } else { logger.Infof("GCSFuse mount command flags: %s", flagsStringified) } - mountConfigStringified, err := util.Stringify(*mountConfig) + mountConfigStringified, err := util.JSONStringify(*mountConfig) if err != nil { logger.Warnf("failed to stringify config-file: %v", err) } else { diff --git a/internal/util/util.go b/internal/util/util.go index 2099b4a0dd..e31140f312 100644 --- a/internal/util/util.go +++ b/internal/util/util.go @@ -24,6 +24,8 @@ import ( "path/filepath" "strings" "time" + + "gopkg.in/yaml.v3" ) const ( @@ -75,12 +77,22 @@ func GetResolvedPath(filePath string) (resolvedPath string, err error) { } } -// Stringify marshals an object (only exported attribute) to a JSON string. If marshalling fails, it returns an empty string. -func Stringify(input any) (string, error) { +// JSONStringify marshals an object (only exported attribute) to a JSON string. If marshalling fails, it returns an empty string. +func JSONStringify(input any) (string, error) { inputBytes, err := json.Marshal(input) if err != nil { - return "", fmt.Errorf("error in Stringify %w", err) + return "", fmt.Errorf("error in JSONStringify %w", err) + } + return string(inputBytes), nil +} + +// YAMLStringify marshals an object to a YAML string. If marshalling fails, it returns an error. +func YAMLStringify(input any) (string, error) { + inputBytes, err := yaml.Marshal(input) + + if err != nil { + return "", fmt.Errorf("error in YAMLStringify %w", err) } return string(inputBytes), nil } diff --git a/internal/util/util_test.go b/internal/util/util_test.go index 38bbd91eaf..ab97d187a4 100644 --- a/internal/util/util_test.go +++ b/internal/util/util_test.go @@ -148,6 +148,43 @@ func (ts *UtilTest) ResolveWhenParentProcDirEnvSetAndAbsoluteFilePath() { assert.Equal(ts.T(), "/var/dir/test.txt", resolvedPath) } +func TestYAMLStringify(t *testing.T) { + tests := []struct { + name string + obj any + expected string + }{ + { + name: "Test Map", + obj: map[string]int{ + "1": 1, + "2": 2, + "3": 3, + }, + expected: "\"1\": 1\n\"2\": 2\n\"3\": 3\n", + }, + { + name: "Test Nested Map", + obj: map[string]any{ + "1": 1, + "2": map[string]int{ + "3": 1, + }, + }, + expected: "\"1\": 1\n\"2\":\n \"3\": 1\n", + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + s, err := YAMLStringify(tc.obj) + + if assert.NoError(t, err) { + assert.Equal(t, tc.expected, s) + } + }) + } +} func (ts *UtilTest) TestStringifyShouldReturnAllFieldsPassedInCustomObjectAsMarshalledString() { sampleMap := map[string]int{ "1": 1, @@ -163,7 +200,7 @@ func (ts *UtilTest) TestStringifyShouldReturnAllFieldsPassedInCustomObjectAsMars NestedValue: sampleNestedValue, } - actual, _ := Stringify(customObject) + actual, _ := JSONStringify(customObject) expected := "{\"Value\":\"test_value\",\"NestedValue\":{\"SomeField\":10,\"SomeOther\":{\"1\":1,\"2\":2,\"3\":3}}}" assert.Equal(ts.T(), expected, actual) @@ -174,7 +211,7 @@ func (ts *UtilTest) TestStringifyShouldReturnEmptyStringWhenMarshalErrorsOut() { value: "example", } - actual, _ := Stringify(customInstance) + actual, _ := JSONStringify(customInstance) expected := "" assert.Equal(ts.T(), expected, actual)