diff --git a/internal/config/config.go b/internal/config/config.go index 8a4a889998..543e4b1a27 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -48,6 +48,7 @@ const ( envSymbol = "_" ) +// Bind binds the actual data from the receiver field. func (c *GlobalConfig) Bind() *GlobalConfig { c.Version = GetActualValue(c.Version) c.TZ = GetActualValue(c.TZ) @@ -58,6 +59,7 @@ func (c *GlobalConfig) Bind() *GlobalConfig { return c } +// UnmarshalJSON parses the JSON-encoded data and stores the result in the field of receiver. func (c *GlobalConfig) UnmarshalJSON(data []byte) (err error) { ic := new(struct { Ver string `json:"version"` @@ -74,7 +76,7 @@ func (c *GlobalConfig) UnmarshalJSON(data []byte) (err error) { return nil } -// New returns config struct or error when decode the configuration file to actually *Config struct. +// Read returns config struct or error when decoding the configuration file to actually *Config struct. func Read(path string, cfg interface{}) (err error) { f, err := os.OpenFile(path, os.O_RDONLY, 0o600) if err != nil { @@ -117,6 +119,9 @@ func GetActualValue(val string) (res string) { return } +// GetActualValues returns the environment variable values if the vals has string slice that has prefix and suffix "_", +// if actual value start with file://{path} the return value will read from file +// otherwise the val will directly return. func GetActualValues(vals []string) []string { for i, val := range vals { vals[i] = GetActualValue(val) @@ -129,6 +134,7 @@ func checkPrefixAndSuffix(str, pref, suf string) bool { return strings.HasPrefix(str, pref) && strings.HasSuffix(str, suf) } +// ToRawYaml writes the YAML encoding of v to the stream and returns the string written to stream. func ToRawYaml(data interface{}) string { buf := bytes.NewBuffer(nil) err := yaml.NewEncoder(buf).Encode(data) diff --git a/internal/config/config_test.go b/internal/config/config_test.go index 90e6777340..33a395d03a 100644 --- a/internal/config/config_test.go +++ b/internal/config/config_test.go @@ -18,13 +18,27 @@ package config import ( + "io/fs" + "os" "reflect" + "syscall" "testing" "github.com/vdaas/vald/internal/errors" + "github.com/vdaas/vald/internal/log" "go.uber.org/goleak" ) +// Goroutine leak is detected by `fastime`, but it should be ignored in the test because it is an external package. +var goleakIgnoreOptions = []goleak.Option{ + goleak.IgnoreTopFunction("github.com/kpango/fastime.(*Fastime).StartTimerD.func1"), +} + +func TestMain(m *testing.M) { + log.Init() + os.Exit(m.Run()) +} + func TestGlobalConfig_Bind(t *testing.T) { type fields struct { Version string @@ -49,40 +63,189 @@ func TestGlobalConfig_Bind(t *testing.T) { return nil } tests := []test{ - // TODO test cases - /* - { - name: "test_case_1", - fields: fields { - Version: "", - TZ: "", - Logging: Logging{}, - }, - want: want{}, - checkFunc: defaultCheckFunc, - }, - */ - - // TODO test cases - /* - func() test { - return test { - name: "test_case_2", - fields: fields { - Version: "", - TZ: "", - Logging: Logging{}, - }, - want: want{}, - checkFunc: defaultCheckFunc, - } - }(), - */ + func() test { + return test{ + name: "return GlobalConfig when all fields are embedded", + fields: fields{ + Version: "v1.0.0", + TZ: "UTC", + Logging: &Logging{ + Logger: "glg", + Level: "warn", + Format: "json", + }, + }, + want: want{ + want: &GlobalConfig{ + Version: "v1.0.0", + TZ: "UTC", + Logging: &Logging{ + Logger: "glg", + Level: "warn", + Format: "json", + }, + }, + }, + } + }(), + func() test { + return test{ + name: "return GlobalConfig when version and time_zone are embedded but logging is nil", + fields: fields{ + Version: "v1.0.0", + TZ: "UTC", + }, + want: want{ + want: &GlobalConfig{ + Version: "v1.0.0", + TZ: "UTC", + }, + }, + } + }(), + func() test { + return test{ + name: "return GlobalConfig when version is empty and time_zone is embedded but logging is nil", + fields: fields{ + TZ: "UTC", + }, + want: want{ + want: &GlobalConfig{ + TZ: "UTC", + }, + }, + } + }(), + func() test { + return test{ + name: "return GlobalConfig when version is embedded and time_zone is empty but logging is nil", + fields: fields{ + Version: "v1.0.0", + }, + want: want{ + want: &GlobalConfig{ + Version: "v1.0.0", + }, + }, + } + }(), + func() test { + return test{ + name: "return GlobalConfig when Logging.Logger is an empty", + fields: fields{ + Version: "v1.0.0", + TZ: "UTC", + Logging: &Logging{ + Level: "warn", + Format: "json", + }, + }, + want: want{ + want: &GlobalConfig{ + Version: "v1.0.0", + TZ: "UTC", + Logging: &Logging{ + Level: "warn", + Format: "json", + }, + }, + }, + } + }(), + func() test { + return test{ + name: "return GlobalConfig when Logging.Level is an empty", + fields: fields{ + Version: "v1.0.0", + TZ: "UTC", + Logging: &Logging{ + Logger: "glg", + Format: "json", + }, + }, + want: want{ + want: &GlobalConfig{ + Version: "v1.0.0", + TZ: "UTC", + Logging: &Logging{ + Logger: "glg", + Format: "json", + }, + }, + }, + } + }(), + func() test { + return test{ + name: "return GlobalConfig when Logging.Format is an empty", + fields: fields{ + Version: "v1.0.0", + TZ: "UTC", + Logging: &Logging{ + Logger: "glg", + Level: "warn", + }, + }, + want: want{ + want: &GlobalConfig{ + Version: "v1.0.0", + TZ: "UTC", + Logging: &Logging{ + Logger: "glg", + Level: "warn", + }, + }, + }, + } + }(), + func() test { + env := map[string]string{ + "VERSION": "v1.0.0", + "TZ": "UTC", + "LOGGER": "glg", + "LEVEL": "warn", + "FORMAT": "json", + } + + return test{ + name: "return GlobalConfig when all fields are read from environment variable", + fields: fields{ + Version: "_VERSION_", + TZ: "_TZ_", + Logging: &Logging{ + Logger: "_LOGGER_", + Level: "_LEVEL_", + Format: "_FORMAT_", + }, + }, + want: want{ + want: &GlobalConfig{ + Version: "v1.0.0", + TZ: "UTC", + Logging: &Logging{ + Logger: "glg", + Level: "warn", + Format: "json", + }, + }, + }, + beforeFunc: func() { + for key, val := range env { + os.Setenv(key, val) + } + }, + afterFunc: func() { + for key := range env { + os.Unsetenv(key) + } + }, + } + }(), } for _, test := range tests { t.Run(test.name, func(tt *testing.T) { - defer goleak.VerifyNone(t) + defer goleak.VerifyNone(tt, goleakIgnoreOptions...) if test.beforeFunc != nil { test.beforeFunc() } @@ -116,64 +279,267 @@ func TestGlobalConfig_UnmarshalJSON(t *testing.T) { Logging *Logging } type want struct { - err error + want *GlobalConfig + err error } type test struct { name string args args fields fields want want - checkFunc func(want, error) error + checkFunc func(want, *GlobalConfig, error) error beforeFunc func(args) afterFunc func(args) } - defaultCheckFunc := func(w want, err error) error { + defaultCheckFunc := func(w want, got *GlobalConfig, err error) error { if !errors.Is(err, w.err) { return errors.Errorf("got_error: \"%#v\",\n\t\t\t\twant: \"%#v\"", err, w.err) } return nil } tests := []test{ - // TODO test cases - /* - { - name: "test_case_1", - args: args { - data: nil, - }, - fields: fields { - Version: "", - TZ: "", - Logging: Logging{}, - }, - want: want{}, - checkFunc: defaultCheckFunc, - }, - */ - - // TODO test cases - /* - func() test { - return test { - name: "test_case_2", - args: args { - data: nil, - }, - fields: fields { - Version: "", - TZ: "", - Logging: Logging{}, - }, - want: want{}, - checkFunc: defaultCheckFunc, - } - }(), - */ + func() test { + data := []byte(`{ + "version": "v1.0.0", + "time_zone": "UTC", + "logging": { + "logger": "glg", + "level": "warn", + "format": "json" + }}`) + + return test{ + name: "return nil when json unmarshal successes", + args: args{ + data: data, + }, + want: want{ + want: &GlobalConfig{ + Version: "v1.0.0", + TZ: "UTC", + Logging: &Logging{ + Logger: "glg", + Level: "warn", + Format: "json", + }, + }, + err: nil, + }, + } + }(), + func() test { + data := []byte(`{ + "time_zone": "UTC", + "logging": { + "logger": "glg", + "level": "warn", + "format": "json" + } + }`) + + return test{ + name: "return nil when version key is empty and json unmarshal successes", + args: args{ + data: data, + }, + want: want{ + want: &GlobalConfig{ + TZ: "UTC", + Logging: &Logging{ + Logger: "glg", + Level: "warn", + Format: "json", + }, + }, + err: nil, + }, + } + }(), + func() test { + data := []byte(`{ + "version": "v1.0.0", + "logging": { + "logger": "glg", + "level": "warn", + "format": "json" + } + }`) + + return test{ + name: "return nil when time_zone key is empty and json unmarshal successes", + args: args{ + data: data, + }, + fields: fields{}, + want: want{ + want: &GlobalConfig{ + Version: "v1.0.0", + Logging: &Logging{ + Logger: "glg", + Level: "warn", + Format: "json", + }, + }, + err: nil, + }, + } + }(), + func() test { + data := []byte(`{ + "version": "v1.0.0", + "time_zone": "UTC" + }`) + + return test{ + name: "return nil when logging key is empty and json unmarshal successes", + args: args{ + data: data, + }, + fields: fields{}, + want: want{ + want: &GlobalConfig{ + Version: "v1.0.0", + TZ: "UTC", + }, + err: nil, + }, + } + }(), + func() test { + data := []byte(`{ + "version": "v1.0.0", + "time_zone": "UTC", + "logging": { + "level": "warn", + "format": "json" + } + }`) + + return test{ + name: "return nil when logging.logger key is empty and json unmarshal successes", + args: args{ + data: data, + }, + fields: fields{}, + want: want{ + want: &GlobalConfig{ + Version: "v1.0.0", + TZ: "UTC", + Logging: &Logging{ + Level: "warn", + Format: "json", + }, + }, + err: nil, + }, + } + }(), + func() test { + data := []byte(`{ + "version": "v1.0.0", + "time_zone": "UTC", + "logging": { + "logger": "glg", + "format": "json" + } + }`) + + return test{ + name: "return nil when logging.level key is empty and json unmarshal successes", + args: args{ + data: data, + }, + fields: fields{}, + want: want{ + want: &GlobalConfig{ + Version: "v1.0.0", + TZ: "UTC", + Logging: &Logging{ + Logger: "glg", + Format: "json", + }, + }, + err: nil, + }, + } + }(), + func() test { + data := []byte(`{ + "version": "v1.0.0", + "time_zone": "UTC", + "logging": { + "logger": "glg", + "level": "warn" + } + }`) + + return test{ + name: "return nil when logging.format key is empty and json unmarshal successes", + args: args{ + data: data, + }, + fields: fields{}, + want: want{ + want: &GlobalConfig{ + Version: "v1.0.0", + TZ: "UTC", + Logging: &Logging{ + Logger: "glg", + Level: "warn", + }, + }, + err: nil, + }, + } + }(), + func() test { + data := []byte(`{time_zone}`) + + return test{ + name: "return unmarshal error when json data is invalid", + args: args{ + data: data, + }, + fields: fields{}, + want: want{ + want: &GlobalConfig{}, + err: errors.New("readFieldHash: expect \", but found t, error found in #2 byte of ...|{time_zone}|..., bigger context ...|{time_zone}|..."), + }, + } + }(), + func() test { + data := []byte(``) + + return test{ + name: "return unmarshal error when json data is empty", + args: args{ + data: data, + }, + fields: fields{}, + want: want{ + want: &GlobalConfig{}, + err: errors.New("readObjectStart: expect { or n, but found \x00, error found in #0 byte of ...||..., bigger context ...||..."), + }, + } + }(), + func() test { + return test{ + name: "return unmarshal error when data is nil", + args: args{ + data: nil, + }, + fields: fields{}, + want: want{ + want: &GlobalConfig{}, + err: errors.New("readObjectStart: expect { or n, but found \x00, error found in #0 byte of ...||..., bigger context ...||..."), + }, + } + }(), } for _, test := range tests { t.Run(test.name, func(tt *testing.T) { - defer goleak.VerifyNone(t) + defer goleak.VerifyNone(tt, goleakIgnoreOptions...) if test.beforeFunc != nil { test.beforeFunc(test.args) } @@ -190,7 +556,7 @@ func TestGlobalConfig_UnmarshalJSON(t *testing.T) { } err := c.UnmarshalJSON(test.args.data) - if err := test.checkFunc(test.want, err); err != nil { + if err := test.checkFunc(test.want, c, err); err != nil { tt.Errorf("error = %v", err) } }) @@ -203,67 +569,616 @@ func TestRead(t *testing.T) { cfg interface{} } type want struct { - err error + want interface{} + err error } type test struct { name string args args want want - checkFunc func(want, error) error - beforeFunc func(args) - afterFunc func(args) + checkFunc func(want, interface{}, error) error + beforeFunc func(*testing.T, args) + afterFunc func(*testing.T, args) } - defaultCheckFunc := func(w want, err error) error { + defaultCheckFunc := func(w want, got interface{}, err error) error { if !errors.Is(err, w.err) { return errors.Errorf("got_error: \"%#v\",\n\t\t\t\twant: \"%#v\"", err, w.err) } + if !reflect.DeepEqual(got, w.want) { + return errors.Errorf("got: \"%#v\",\n\t\t\t\twant: \"%#v\"", got, w.want) + } return nil } tests := []test{ - // TODO test cases - /* - { - name: "test_case_1", - args: args { - path: "", - cfg: nil, - }, - want: want{}, - checkFunc: defaultCheckFunc, - }, - */ - - // TODO test cases - /* - func() test { - return test { - name: "test_case_2", - args: args { - path: "", - cfg: nil, - }, - want: want{}, - checkFunc: defaultCheckFunc, - } - }(), - */ + func() test { + path := "read_config_test.json" + data := `{ + "version": "v1.0.0", + "time_zone": "UTC", + "logging": { + "logger": "glg", + "level": "warn", + "format": "json" + }}` + cfg := new(GlobalConfig) + + return test{ + name: "return nil when read json file and input data type is struct", + args: args{ + path: path, + cfg: cfg, + }, + beforeFunc: func(t *testing.T, _ args) { + t.Helper() + f, err := os.Create(path) + if err != nil { + t.Fatal(err) + } + defer func() { + if err := f.Close(); err != nil { + t.Error(err) + } + }() + + if _, err := f.WriteString(data); err != nil { + t.Error(err) + } + }, + afterFunc: func(t *testing.T, _ args) { + t.Helper() + if err := os.Remove(path); err != nil { + t.Fatal(err) + } + }, + want: want{ + want: &GlobalConfig{ + Version: "v1.0.0", + TZ: "UTC", + Logging: &Logging{ + Logger: "glg", + Level: "warn", + Format: "json", + }, + }, + err: nil, + }, + } + }(), + func() test { + path := "read_config_test.json" + data := `{ + "version": "v1.0.0", + "time_zone": "UTC" + }` + cfg := make(map[string]string) + + return test{ + name: "return nil when read json file successes and input data type is map", + args: args{ + path: path, + cfg: &cfg, + }, + beforeFunc: func(t *testing.T, _ args) { + t.Helper() + f, err := os.Create(path) + if err != nil { + t.Fatal(err) + } + defer func() { + if err := f.Close(); err != nil { + t.Error(err) + } + }() + + if _, err := f.WriteString(data); err != nil { + t.Error(err) + } + }, + afterFunc: func(t *testing.T, _ args) { + t.Helper() + if err := os.Remove(path); err != nil { + t.Fatal(err) + } + }, + want: want{ + want: &map[string]string{ + "version": "v1.0.0", + "time_zone": "UTC", + }, + err: nil, + }, + } + }(), + func() test { + path := "read_config_test.json" + data := `{ + "version": "v1.0.0", + "time_zone": "UTC", + "logging": { + "logger": "glg" + } + }` + cfg := make(map[string]interface{}) + + return test{ + name: "return nil when read json file successes and input data type is nested map", + args: args{ + path: path, + cfg: &cfg, + }, + beforeFunc: func(t *testing.T, _ args) { + t.Helper() + f, err := os.Create(path) + if err != nil { + t.Fatal(err) + } + defer func() { + if err := f.Close(); err != nil { + t.Error(err) + } + }() + + if _, err := f.WriteString(data); err != nil { + t.Error(err) + } + }, + afterFunc: func(t *testing.T, _ args) { + t.Helper() + if err := os.Remove(path); err != nil { + t.Fatal(err) + } + }, + want: want{ + want: &map[string]interface{}{ + "version": "v1.0.0", + "time_zone": "UTC", + "logging": map[string]interface{}{ + "logger": "glg", + }, + }, + err: nil, + }, + } + }(), + func() test { + path := "read_config_test.json" + data := `[ + { + "addr": "0.0.0.0", + "port": "8080" + }, + { + "addr": "0.0.0.0", + "port": "3001" + } + ]` + cfg := make([]map[string]interface{}, 0) + + return test{ + name: "return nil when read json file successes and input data type is map slice", + args: args{ + path: path, + cfg: &cfg, + }, + beforeFunc: func(t *testing.T, _ args) { + t.Helper() + f, err := os.Create(path) + if err != nil { + t.Fatal(err) + } + defer func() { + if err := f.Close(); err != nil { + t.Error(err) + } + }() + + if _, err := f.WriteString(data); err != nil { + t.Error(err) + } + }, + afterFunc: func(t *testing.T, _ args) { + t.Helper() + if err := os.Remove(path); err != nil { + t.Fatal(err) + } + }, + want: want{ + want: &[]map[string]interface{}{ + { + "addr": "0.0.0.0", + "port": "8080", + }, + { + "addr": "0.0.0.0", + "port": "3001", + }, + }, + err: nil, + }, + } + }(), + func() test { + path := "read_config_test.json" + data := `"vdaas"` + var cfg string + + return test{ + name: "return nil when read json file successes and input data type is string", + args: args{ + path: path, + cfg: &cfg, + }, + beforeFunc: func(t *testing.T, _ args) { + t.Helper() + f, err := os.Create(path) + if err != nil { + t.Fatal(err) + } + defer func() { + if err := f.Close(); err != nil { + t.Error(err) + } + }() + + if _, err := f.WriteString(data); err != nil { + t.Error(err) + } + }, + afterFunc: func(t *testing.T, _ args) { + t.Helper() + if err := os.Remove(path); err != nil { + t.Fatal(err) + } + }, + want: want{ + want: func() (str *string) { + s := "vdaas" + return &s + }(), + err: nil, + }, + } + }(), + func() test { + path := "read_test_config.yaml" + data := "time_zone: UTC\nversion: v1.0.0\nlogging:\n format: json\n level: warn\n logger: glg" + cfg := new(GlobalConfig) + + return test{ + name: "return nil when read yaml file and input data type is struct", + args: args{ + path: path, + cfg: cfg, + }, + beforeFunc: func(t *testing.T, _ args) { + t.Helper() + f, err := os.Create(path) + if err != nil { + t.Fatal(err) + } + defer func() { + if err := f.Close(); err != nil { + t.Error(err) + } + }() + + if _, err := f.WriteString(data); err != nil { + t.Error(err) + } + }, + afterFunc: func(t *testing.T, _ args) { + t.Helper() + if err := os.Remove(path); err != nil { + t.Fatal(err) + } + }, + want: want{ + want: &GlobalConfig{ + Version: "v1.0.0", + TZ: "UTC", + Logging: &Logging{ + Logger: "glg", + Level: "warn", + Format: "json", + }, + }, + err: nil, + }, + } + }(), + + func() test { + path := "read_config_test.yaml" + data := "version: v1.0.0\ntime_zone: UTC" + cfg := make(map[string]string) + + return test{ + name: "return nil when read yaml file successes and input data type is map", + args: args{ + path: path, + cfg: &cfg, + }, + beforeFunc: func(t *testing.T, _ args) { + t.Helper() + f, err := os.Create(path) + if err != nil { + t.Fatal(err) + } + defer func() { + if err := f.Close(); err != nil { + t.Error(err) + } + }() + + if _, err := f.WriteString(data); err != nil { + t.Error(err) + } + }, + afterFunc: func(t *testing.T, _ args) { + t.Helper() + if err := os.Remove(path); err != nil { + t.Fatal(err) + } + }, + want: want{ + want: &map[string]string{ + "version": "v1.0.0", + "time_zone": "UTC", + }, + err: nil, + }, + } + }(), + func() test { + path := "read_config_test.yaml" + data := "version: v1.0.0\ntime_zone: UTC\nlogging:\n logger: glg" + cfg := make(map[string]interface{}) + + return test{ + name: "return nil when read yaml file successes and input data type is nested map", + args: args{ + path: path, + cfg: &cfg, + }, + beforeFunc: func(t *testing.T, _ args) { + t.Helper() + f, err := os.Create(path) + if err != nil { + t.Fatal(err) + } + defer func() { + if err := f.Close(); err != nil { + t.Error(err) + } + }() + + if _, err := f.WriteString(data); err != nil { + t.Error(err) + } + }, + afterFunc: func(t *testing.T, _ args) { + t.Helper() + if err := os.Remove(path); err != nil { + t.Fatal(err) + } + }, + want: want{ + want: &map[string]interface{}{ + "version": "v1.0.0", + "time_zone": "UTC", + "logging": map[interface{}]interface{}{ + "logger": "glg", + }, + }, + err: nil, + }, + } + }(), + func() test { + path := "read_config_test.yaml" + data := "- \n addr: 0.0.0.0\n port: \"8080\"\n- \n addr: 0.0.0.0\n port: \"3001\"" + cfg := make([]map[string]interface{}, 0) + + return test{ + name: "return nil when read yaml file successes and input data type is map slice", + args: args{ + path: path, + cfg: &cfg, + }, + beforeFunc: func(t *testing.T, _ args) { + t.Helper() + f, err := os.Create(path) + if err != nil { + t.Fatal(err) + } + defer func() { + if err := f.Close(); err != nil { + t.Error(err) + } + }() + + if _, err := f.WriteString(data); err != nil { + t.Error(err) + } + }, + afterFunc: func(t *testing.T, _ args) { + t.Helper() + if err := os.Remove(path); err != nil { + t.Fatal(err) + } + }, + want: want{ + want: &[]map[string]interface{}{ + { + "addr": "0.0.0.0", + "port": "8080", + }, + { + "addr": "0.0.0.0", + "port": "3001", + }, + }, + err: nil, + }, + } + }(), + func() test { + path := "read_config_test.yaml" + data := `"vdaas"` + var cfg string + + return test{ + name: "return nil when read yaml file successes and input data type is string", + args: args{ + path: path, + cfg: &cfg, + }, + beforeFunc: func(t *testing.T, _ args) { + t.Helper() + f, err := os.Create(path) + if err != nil { + t.Fatal(err) + } + defer func() { + if err := f.Close(); err != nil { + t.Error(err) + } + }() + + if _, err := f.WriteString(data); err != nil { + t.Error(err) + } + }, + afterFunc: func(t *testing.T, _ args) { + t.Helper() + if err := os.Remove(path); err != nil { + t.Fatal(err) + } + }, + want: want{ + want: func() (str *string) { + s := "vdaas" + return &s + }(), + err: nil, + }, + } + }(), + func() test { + path := "read_test_config.yaml" + cfg := new(GlobalConfig) + + return test{ + name: "return no entry error when the file open fails", + args: args{ + path: path, + cfg: cfg, + }, + want: want{ + want: cfg, + err: &fs.PathError{ + Op: "open", + Path: "read_test_config.yaml", + Err: syscall.ENOENT, + }, + }, + } + }(), + func() test { + path := "read_test_config.yaml" + data := "timezone\n:" + cfg := new(GlobalConfig) + + return test{ + name: "return yaml decode error when the contents of yaml is invalid", + args: args{ + path: path, + cfg: cfg, + }, + beforeFunc: func(t *testing.T, _ args) { + t.Helper() + f, err := os.Create(path) + if err != nil { + t.Fatal(err) + } + defer func() { + if err := f.Close(); err != nil { + t.Error(err) + } + }() + + if _, err := f.WriteString(data); err != nil { + t.Error(err) + } + }, + afterFunc: func(t *testing.T, _ args) { + t.Helper() + if err := os.Remove(path); err != nil { + t.Fatal(err) + } + }, + want: want{ + want: cfg, + err: errors.New("yaml: unmarshal errors:\n line 1: cannot unmarshal !!str `timezone` into config.GlobalConfig"), + }, + } + }(), + func() test { + path := "read_test_config.json" + data := "timezone\n:" + cfg := new(GlobalConfig) + + return test{ + name: "return json decode error when the contents of json file is invalid", + args: args{ + path: path, + cfg: cfg, + }, + beforeFunc: func(t *testing.T, _ args) { + t.Helper() + f, err := os.Create(path) + if err != nil { + t.Fatal(err) + } + defer func() { + if err := f.Close(); err != nil { + t.Error(err) + } + }() + + if _, err := f.WriteString(data); err != nil { + t.Error(err) + } + }, + afterFunc: func(t *testing.T, _ args) { + t.Helper() + if err := os.Remove(path); err != nil { + t.Fatal(err) + } + }, + want: want{ + want: cfg, + err: errors.New("skipThreeBytes: expect rue, error found in #2 byte of ...|timezone\n:|..., bigger context ...|timezone\n:|..."), + }, + } + }(), } for _, test := range tests { t.Run(test.name, func(tt *testing.T) { - defer goleak.VerifyNone(t) + defer goleak.VerifyNone(tt, goleakIgnoreOptions...) if test.beforeFunc != nil { - test.beforeFunc(test.args) + test.beforeFunc(tt, test.args) } if test.afterFunc != nil { - defer test.afterFunc(test.args) + defer test.afterFunc(tt, test.args) } if test.checkFunc == nil { test.checkFunc = defaultCheckFunc } err := Read(test.args.path, test.args.cfg) - if err := test.checkFunc(test.want, err); err != nil { + if err := test.checkFunc(test.want, test.args.cfg, err); err != nil { tt.Errorf("error = %v", err) } }) @@ -282,8 +1197,8 @@ func TestGetActualValue(t *testing.T) { args args want want checkFunc func(want, string) error - beforeFunc func(args) - afterFunc func(args) + beforeFunc func(*testing.T, args) + afterFunc func(*testing.T, args) } defaultCheckFunc := func(w want, gotRes string) error { if !reflect.DeepEqual(gotRes, w.wantRes) { @@ -292,41 +1207,121 @@ func TestGetActualValue(t *testing.T) { return nil } tests := []test{ - // TODO test cases - /* - { - name: "test_case_1", - args: args { - val: "", - }, - want: want{}, - checkFunc: defaultCheckFunc, - }, - */ - - // TODO test cases - /* - func() test { - return test { - name: "test_case_2", - args: args { - val: "", - }, - want: want{}, - checkFunc: defaultCheckFunc, - } - }(), - */ + func() test { + return test{ + name: "return v1.0.0. when val is _VERSION_", + args: args{ + val: "_VERSION_", + }, + beforeFunc: func(t *testing.T, _ args) { + t.Helper() + if err := os.Setenv("VERSION", "v1.0.0"); err != nil { + t.Error(err) + } + }, + afterFunc: func(t *testing.T, _ args) { + t.Helper() + if err := os.Unsetenv("VERSION"); err != nil { + t.Error(err) + } + }, + want: want{ + wantRes: "v1.0.0", + }, + } + }(), + func() test { + return test{ + name: "return v1.0.0 when val is $VERSION", + args: args{ + val: "$VERSION", + }, + beforeFunc: func(t *testing.T, _ args) { + t.Helper() + if err := os.Setenv("VERSION", "v1.0.0"); err != nil { + t.Error(err) + } + }, + afterFunc: func(t *testing.T, _ args) { + t.Helper() + if err := os.Unsetenv("VERSION"); err != nil { + t.Error(err) + } + }, + want: want{ + wantRes: "v1.0.0", + }, + } + }(), + func() test { + return test{ + name: "return VERSION version when val is VERSION", + args: args{ + val: "VERSION", + }, + want: want{ + wantRes: "VERSION", + }, + } + }(), + func() test { + fname := "version" + + return test{ + name: "return file contents when val is file://env", + args: args{ + val: "file://" + fname, + }, + beforeFunc: func(t *testing.T, _ args) { + t.Helper() + f, err := os.Create(fname) + if err != nil { + t.Error(err) + return + } + defer func() { + if err := f.Close(); err != nil { + t.Error(err) + } + }() + + if _, err := f.WriteString("v1.0.0"); err != nil { + t.Error(err) + } + }, + afterFunc: func(t *testing.T, _ args) { + t.Helper() + if err := os.Remove(fname); err != nil { + t.Error(err) + } + }, + want: want{ + wantRes: "v1.0.0", + }, + } + }(), + func() test { + fname := "version" + return test{ + name: "return file contents when val is file://env", + args: args{ + val: "file://" + fname, + }, + want: want{ + wantRes: "file://" + fname, + }, + } + }(), } for _, test := range tests { t.Run(test.name, func(tt *testing.T) { - defer goleak.VerifyNone(t) + defer goleak.VerifyNone(tt, goleakIgnoreOptions...) if test.beforeFunc != nil { - test.beforeFunc(test.args) + test.beforeFunc(tt, test.args) } if test.afterFunc != nil { - defer test.afterFunc(test.args) + defer test.afterFunc(tt, test.args) } if test.checkFunc == nil { test.checkFunc = defaultCheckFunc @@ -352,8 +1347,8 @@ func TestGetActualValues(t *testing.T) { args args want want checkFunc func(want, []string) error - beforeFunc func(args) - afterFunc func(args) + beforeFunc func(*testing.T, args) + afterFunc func(*testing.T, args) } defaultCheckFunc := func(w want, got []string) error { if !reflect.DeepEqual(got, w.want) { @@ -362,41 +1357,94 @@ func TestGetActualValues(t *testing.T) { return nil } tests := []test{ - // TODO test cases - /* - { - name: "test_case_1", - args: args { - vals: nil, - }, - want: want{}, - checkFunc: defaultCheckFunc, - }, - */ - - // TODO test cases - /* - func() test { - return test { - name: "test_case_2", - args: args { - vals: nil, - }, - want: want{}, - checkFunc: defaultCheckFunc, - } - }(), - */ + func() test { + env := map[string]string{ + "VERSION": "v1.0.0", + "LOGGER": "glg", + } + + return test{ + name: "return v1.0.0 and glg when vals are _LOGGER_ and _VERSION_", + args: args{ + vals: []string{ + "_VERSION_", + "_LOGGER_", + }, + }, + beforeFunc: func(t *testing.T, _ args) { + t.Helper() + for key, val := range env { + if err := os.Setenv(key, val); err != nil { + t.Error(err) + } + } + }, + afterFunc: func(t *testing.T, _ args) { + t.Helper() + for key := range env { + if err := os.Unsetenv(key); err != nil { + t.Error(err) + } + } + }, + want: want{ + want: []string{ + "v1.0.0", + "glg", + }, + }, + } + }(), + func() test { + return test{ + name: "return v1.0.0 and LOGGER when vals are _VERSION_ and LOGGER", + args: args{ + vals: []string{ + "_VERSION_", + "LOGGER", + }, + }, + beforeFunc: func(t *testing.T, _ args) { + t.Helper() + if err := os.Setenv("VERSION", "v1.0.0"); err != nil { + t.Error(err) + } + }, + afterFunc: func(t *testing.T, _ args) { + t.Helper() + if err := os.Unsetenv("VERSION"); err != nil { + t.Error(err) + } + }, + want: want{ + want: []string{ + "v1.0.0", + "LOGGER", + }, + }, + } + }(), + func() test { + return test{ + name: "return empty when vals is empty", + args: args{ + vals: []string{}, + }, + want: want{ + want: []string{}, + }, + } + }(), } for _, test := range tests { t.Run(test.name, func(tt *testing.T) { - defer goleak.VerifyNone(t) + defer goleak.VerifyNone(tt, goleakIgnoreOptions...) if test.beforeFunc != nil { - test.beforeFunc(test.args) + test.beforeFunc(tt, test.args) } if test.afterFunc != nil { - defer test.afterFunc(test.args) + defer test.afterFunc(tt, test.args) } if test.checkFunc == nil { test.checkFunc = defaultCheckFunc @@ -434,40 +1482,132 @@ func Test_checkPrefixAndSuffix(t *testing.T) { return nil } tests := []test{ - // TODO test cases - /* - { - name: "test_case_1", - args: args { - str: "", - pref: "", - suf: "", - }, - want: want{}, - checkFunc: defaultCheckFunc, - }, - */ - - // TODO test cases - /* - func() test { - return test { - name: "test_case_2", - args: args { - str: "", - pref: "", - suf: "", - }, - want: want{}, - checkFunc: defaultCheckFunc, - } - }(), - */ + { + name: "return true when prefix and suffix are _ and str is _POD_NAME_", + args: args{ + str: "_POD_NAME_", + pref: "_", + suf: "_", + }, + want: want{ + want: true, + }, + }, + { + name: "return true when prefix and suffix are _ and str is __POD_NAME__", + args: args{ + str: "__POD_NAME__", + pref: "_", + suf: "_", + }, + want: want{ + want: true, + }, + }, + { + name: "return true when prefix and suffix are __ and str is __POD_NAME__", + args: args{ + str: "__POD_NAME__", + pref: "__", + suf: "__", + }, + want: want{ + want: true, + }, + }, + { + name: "return true when prefix is $ and suffix is # and str is $POD_NAME#", + args: args{ + str: "$POD_NAME#", + pref: "$", + suf: "#", + }, + want: want{ + want: true, + }, + }, + { + name: "return true when prefix is $# and suffix is #$ and str is $#POD_NAME#$", + args: args{ + str: "$#POD_NAME#$", + pref: "$#", + suf: "#$", + }, + want: want{ + want: true, + }, + }, + { + name: "return true when prefix is _ and suffix is empty and str is _POD_NAME_", + args: args{ + str: "_POD_NAME_", + pref: "_", + suf: "", + }, + want: want{ + want: true, + }, + }, + { + name: "return true when prefix is empty and suffix is _ and str is _POD_NAME_", + args: args{ + str: "_POD_NAME_", + pref: "", + suf: "_", + }, + want: want{ + want: true, + }, + }, + { + name: "return false when prefix and suffix are _ and str is empty", + args: args{ + str: "", + pref: "_", + suf: "_", + }, + want: want{ + want: false, + }, + }, + { + name: "return false when prefix and suffix are _ and str is _POD_NAME", + args: args{ + str: "_POD_NAME", + pref: "_", + suf: "_", + }, + want: want{ + want: false, + }, + }, + { + name: "return false when prefix and suffix are _ and str is POD_NAME_", + args: args{ + str: "POD_NAME_", + pref: "_", + suf: "_", + }, + want: want{ + want: false, + }, + }, + { + name: "return false when prefix and suffix are _ and str is POD_NAME&", + args: args{ + str: "POD_NAME&", + pref: "_", + suf: "_", + }, + want: want{ + want: false, + }, + }, } for _, test := range tests { t.Run(test.name, func(tt *testing.T) { - defer goleak.VerifyNone(t) + defer goleak.VerifyNone(tt, goleakIgnoreOptions...) if test.beforeFunc != nil { test.beforeFunc(test.args) } @@ -508,36 +1648,88 @@ func TestToRawYaml(t *testing.T) { return nil } tests := []test{ - // TODO test cases - /* - { - name: "test_case_1", - args: args { - data: nil, - }, - want: want{}, - checkFunc: defaultCheckFunc, - }, - */ - - // TODO test cases - /* - func() test { - return test { - name: "test_case_2", - args: args { - data: nil, - }, - want: want{}, - checkFunc: defaultCheckFunc, - } - }(), - */ + { + name: "return row string when data is an int type", + args: args{ + data: 1, + }, + want: want{ + want: "1\n", + }, + }, + { + name: "return row string when data is a string type", + args: args{ + data: "vdaas.vald", + }, + want: want{ + want: "vdaas.vald\n", + }, + }, + { + name: "return row string when data is a map string type", + args: args{ + data: map[string]string{ + "time_zone": "UTC", + }, + }, + want: want{ + want: "time_zone: UTC\n", + }, + }, + { + name: "return row string when data is a nested map type", + args: args{ + data: map[string]interface{}{ + "logging": map[string]interface{}{ + "logger": "glg", + }, + }, + }, + want: want{ + want: "logging:\n logger: glg\n", + }, + }, + { + name: "return row string when data is a empty string", + args: args{ + data: "", + }, + want: want{ + want: "\"\"\n", + }, + }, + { + name: "return row string when data is a GlobalConfig type", + args: args{ + data: GlobalConfig{ + Version: "v1.0.0", + TZ: "UTC", + Logging: &Logging{ + Logger: "glg", + Level: "warn", + Format: "json", + }, + }, + }, + want: want{ + want: "version: v1.0.0\ntime_zone: UTC\nlogging:\n logger: glg\n level: warn\n format: json\n", + }, + }, + { + name: "return row string when data is a nil", + args: args{ + data: nil, + }, + want: want{ + want: "null\n", + }, + }, } for _, test := range tests { t.Run(test.name, func(tt *testing.T) { - defer goleak.VerifyNone(t) + defer goleak.VerifyNone(tt, goleakIgnoreOptions...) if test.beforeFunc != nil { test.beforeFunc(test.args) }