package fog import ( "bytes" "encoding/json" "strings" "testing" "time" "github.com/go-logr/logr" "github.com/stretchr/testify/assert" ) // Test improperUsage WithLabels: expect failure via odd number of inputs // Test Proper usage of WithLabels: single pair & multiple pairs func TestWithLabels(t *testing.T) { l := New(setDevelopment()) assert.Panics(t, func() { WithLabels(l, "testBadPair") }) type testCase struct { message string labelPairs []string keys []string expectedValues []string } tests := []testCase{ { message: "test proper pair", labelPairs: []string{"testKey", "testValue"}, keys: []string{"testKey"}, expectedValues: []string{"testValue"}, }, { message: "test multiple pairs", labelPairs: []string{"key", "value", "testKey2", "testValue2"}, keys: []string{"key", "testKey2"}, expectedValues: []string{"value", "testValue2"}, }, } for _, tt := range tests { buf := new(bytes.Buffer) l := New(To(buf)) l = WithLabels(l, tt.labelPairs...) l.Info(tt.message) var out map[string]interface{} if err := json.Unmarshal(buf.Bytes(), &out); err != nil { t.Fatal(err) } labels, _ := out[labelsKey].(map[string]interface{}) for i, k := range tt.keys { assert.Equal(t, tt.expectedValues[i], labels[k]) } } } func TestInfo(t *testing.T) { t.Parallel() type testCase struct { msg string names []string kv []any // inline logging context key/value pairs withKV []any // context key/value pairs wrapper func(logr.Logger, string, ...any) // logging wrapper } testCases := map[string]testCase{ "simple": { msg: "simple log", }, "inline simple kv": { msg: "look at my values", kv: []any{"foo", "bar", "pods", 2}, }, "with simple kv": { msg: "look at my values that i didnt set on the message 8-)", kv: []any{"foo", "bar", "pods", 2}, }, "single name": { msg: "ive got a name", kv: []any{"foo", "bar"}, names: []string{"single"}, }, "single name with kv": { msg: "ive got a name", withKV: []any{"foo", "bar"}, names: []string{"single-with-style"}, }, "multiple names": { msg: "ive got a name", kv: []any{"foo", "bar"}, names: []string{"single", "joiner", "tony"}, }, "multiple names with kv": { msg: "ive got a name", withKV: []any{"foo", "bar"}, names: []string{"single", "joiner", "tony"}, }, // TODO: more complex KV pairs // TODO: panic // TODO: wrapper (call depth is correct?) } test := func(t *testing.T, data testCase) { t.Helper() buf := new(bytes.Buffer) l := New(To(buf)) for _, n := range data.names { l = l.WithName(n) } if len(data.withKV) > 0 { l = l.WithValues(data.withKV...) } if data.wrapper != nil { data.wrapper(l, data.msg, data.kv...) } else { l.Info(data.msg, data.kv...) } // Parse log message object out := make(map[string]any) assert.NoError(t, json.Unmarshal(buf.Bytes(), &out)) // Verify logger name field switch len(data.names) { case 0: assert.NotContains(t, out, "logger", "'logger' field should not be present for unnamed loggers") case 1: assert.Contains(t, out, "logger", "'logger' field should be present for named loggers") assert.Equal(t, data.names[0], out["logger"]) default: assert.Contains(t, out, "logger", "'logger' field should be present for named loggers") assert.Equal(t, strings.Join(data.names, "."), out["logger"], "'logger' field should be joined by periods for multiple names") } // Verify logger source location // TODO: should verify that src info is well formed (line is number, etc) assert.Contains(t, out, SourceKey) assert.Contains(t, out[SourceKey], "function") assert.Contains(t, out[SourceKey], "file") assert.Contains(t, out[SourceKey], "line") // Verify logger timestamp _, err := time.Parse(time.RFC3339, out["time"].(string)) assert.NoError(t, err, "failed to parse 'time' from log into RFC3339 format") // Verify logger values // TODO: verify more complex values (fmt.Stringer, logr.Marshaler, structs, maps, arrays, yada yada) // TODO: verify non-string key behavior args := append(data.kv, data.withKV...) for i := 0; i < len(args); { key, val := args[i], args[i+1] t.Logf("key %s type: %T", key, val) assert.Contains(t, out, key) assert.EqualValues(t, val, out[key.(string)]) i += 2 } } for name, tc := range testCases { tc := tc t.Run(name, func(t *testing.T) { t.Parallel() test(t, tc) }) } }