Repository: go-logr/logr
Pull request #53 (2021-06-02 23:12:25)

Add tests for funcr


Patch:
diff --git a/funcr/funcr.go b/funcr/funcr.go
index b6ee0dc..ef2f11f 100644
--- a/funcr/funcr.go
+++ b/funcr/funcr.go
@@ -33,7 +33,11 @@ import (
 
 // New returns a logr.Logger which is implemented by an arbitrary function.
 func New(fn func(prefix, args string), opts Options) logr.Logger {
-	fnl := &fnlogger{
+	return logr.New(newSink(fn, opts))
+}
+
+func newSink(fn func(prefix, args string), opts Options) logr.LogSink {
+	return &fnlogger{
 		prefix:    "",
 		values:    nil,
 		depth:     0,
@@ -41,7 +45,6 @@ func New(fn func(prefix, args string), opts Options) logr.Logger {
 		logCaller: opts.LogCaller,
 		verbosity: opts.Verbosity,
 	}
-	return logr.New(fnl)
 }
 
 // Options carries parameters which influence the way logs are generated.


Test patch:
diff --git a/funcr/funcr_test.go b/funcr/funcr_test.go
index c38e0fc..060b070 100644
--- a/funcr/funcr_test.go
+++ b/funcr/funcr_test.go
@@ -19,7 +19,11 @@ package funcr
 import (
 	"encoding/json"
 	"fmt"
+	"path/filepath"
+	"runtime"
 	"testing"
+
+	"github.com/go-logr/logr"
 )
 
 type substr string
@@ -104,3 +108,380 @@ func TestPretty(t *testing.T) {
 		}
 	}
 }
+
+func makeKV(args ...interface{}) []interface{} {
+	return args
+}
+
+func TestFlatten(t *testing.T) {
+	testCases := []struct {
+		name   string
+		kv     []interface{}
+		expect string
+	}{{
+		name:   "nil",
+		kv:     nil,
+		expect: "",
+	}, {
+		name:   "empty",
+		kv:     []interface{}{},
+		expect: "",
+	}, {
+		name:   "primitives",
+		kv:     makeKV("int", 1, "str", "ABC", "bool", true),
+		expect: `"int"=1 "str"="ABC" "bool"=true`,
+	}, {
+		name:   "missing value",
+		kv:     makeKV("key"),
+		expect: `"key"="<no-value>"`,
+	}, {
+		name:   "non-string key",
+		kv:     makeKV(123, "val"),
+		expect: `"<non-string-key-0>"="val"`,
+	}}
+
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			r := flatten(tc.kv...)
+			if r != tc.expect {
+				t.Errorf("expected %q, got %q", tc.expect, r)
+			}
+		})
+	}
+}
+
+func TestEnabled(t *testing.T) {
+	t.Run("default V", func(t *testing.T) {
+		log := newSink(func(prefix, args string) {}, Options{})
+		if !log.Enabled(0) {
+			t.Errorf("expected true")
+		}
+		if log.Enabled(1) {
+			t.Errorf("expected false")
+		}
+	})
+	t.Run("V=9", func(t *testing.T) {
+		log := newSink(func(prefix, args string) {}, Options{Verbosity: 9})
+		if !log.Enabled(8) {
+			t.Errorf("expected true")
+		}
+		if !log.Enabled(9) {
+			t.Errorf("expected true")
+		}
+		if log.Enabled(10) {
+			t.Errorf("expected false")
+		}
+	})
+}
+
+type capture struct {
+	log string
+}
+
+func (c *capture) Func(prefix, args string) {
+	c.log = prefix + " " + args
+}
+
+func TestInfo(t *testing.T) {
+	testCases := []struct {
+		name   string
+		args   []interface{}
+		expect string
+	}{{
+		name:   "just msg",
+		args:   makeKV(),
+		expect: ` "level"=0 "msg"="msg"`,
+	}, {
+		name:   "primitives",
+		args:   makeKV("int", 1, "str", "ABC", "bool", true),
+		expect: ` "level"=0 "msg"="msg" "int"=1 "str"="ABC" "bool"=true`,
+	}}
+
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			cap := &capture{}
+			sink := newSink(cap.Func, Options{})
+			sink.Info(0, "msg", tc.args...)
+			if cap.log != tc.expect {
+				t.Errorf("\nexpected %q\n     got %q", tc.expect, cap.log)
+			}
+		})
+	}
+}
+
+func TestInfoWithCaller(t *testing.T) {
+	t.Run("LogCaller=All", func(t *testing.T) {
+		cap := &capture{}
+		sink := newSink(cap.Func, Options{LogCaller: All})
+		sink.Info(0, "msg")
+		_, file, line, _ := runtime.Caller(0)
+		expect := fmt.Sprintf(` "caller"={"file":%q,"line":%d} "level"=0 "msg"="msg"`, filepath.Base(file), line-1)
+		if cap.log != expect {
+			t.Errorf("\nexpected %q\n     got %q", expect, cap.log)
+		}
+	})
+	t.Run("LogCaller=Info", func(t *testing.T) {
+		cap := &capture{}
+		sink := newSink(cap.Func, Options{LogCaller: Info})
+		sink.Info(0, "msg")
+		_, file, line, _ := runtime.Caller(0)
+		expect := fmt.Sprintf(` "caller"={"file":%q,"line":%d} "level"=0 "msg"="msg"`, filepath.Base(file), line-1)
+		if cap.log != expect {
+			t.Errorf("\nexpected %q\n     got %q", expect, cap.log)
+		}
+	})
+	t.Run("LogCaller=Error", func(t *testing.T) {
+		cap := &capture{}
+		sink := newSink(cap.Func, Options{LogCaller: Error})
+		sink.Info(0, "msg")
+		expect := ` "level"=0 "msg"="msg"`
+		if cap.log != expect {
+			t.Errorf("\nexpected %q\n     got %q", expect, cap.log)
+		}
+	})
+	t.Run("LogCaller=None", func(t *testing.T) {
+		cap := &capture{}
+		sink := newSink(cap.Func, Options{LogCaller: None})
+		sink.Info(0, "msg")
+		expect := ` "level"=0 "msg"="msg"`
+		if cap.log != expect {
+			t.Errorf("\nexpected %q\n     got %q", expect, cap.log)
+		}
+	})
+}
+
+func TestError(t *testing.T) {
+	testCases := []struct {
+		name   string
+		args   []interface{}
+		expect string
+	}{{
+		name:   "just msg",
+		args:   makeKV(),
+		expect: ` "msg"="msg" "error"="err"`,
+	}, {
+		name:   "primitives",
+		args:   makeKV("int", 1, "str", "ABC", "bool", true),
+		expect: ` "msg"="msg" "error"="err" "int"=1 "str"="ABC" "bool"=true`,
+	}}
+
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			cap := &capture{}
+			sink := newSink(cap.Func, Options{})
+			sink.Error(fmt.Errorf("err"), "msg", tc.args...)
+			if cap.log != tc.expect {
+				t.Errorf("\nexpected %q\n     got %q", tc.expect, cap.log)
+			}
+		})
+	}
+}
+
+func TestErrorWithCaller(t *testing.T) {
+	t.Run("LogCaller=All", func(t *testing.T) {
+		cap := &capture{}
+		sink := newSink(cap.Func, Options{LogCaller: All})
+		sink.Error(fmt.Errorf("err"), "msg")
+		_, file, line, _ := runtime.Caller(0)
+		expect := fmt.Sprintf(` "caller"={"file":%q,"line":%d} "msg"="msg" "error"="err"`, filepath.Base(file), line-1)
+		if cap.log != expect {
+			t.Errorf("\nexpected %q\n     got %q", expect, cap.log)
+		}
+	})
+	t.Run("LogCaller=Error", func(t *testing.T) {
+		cap := &capture{}
+		sink := newSink(cap.Func, Options{LogCaller: Error})
+		sink.Error(fmt.Errorf("err"), "msg")
+		_, file, line, _ := runtime.Caller(0)
+		expect := fmt.Sprintf(` "caller"={"file":%q,"line":%d} "msg"="msg" "error"="err"`, filepath.Base(file), line-1)
+		if cap.log != expect {
+			t.Errorf("\nexpected %q\n     got %q", expect, cap.log)
+		}
+	})
+	t.Run("LogCaller=Info", func(t *testing.T) {
+		cap := &capture{}
+		sink := newSink(cap.Func, Options{LogCaller: Info})
+		sink.Error(fmt.Errorf("err"), "msg")
+		expect := ` "msg"="msg" "error"="err"`
+		if cap.log != expect {
+			t.Errorf("\nexpected %q\n     got %q", expect, cap.log)
+		}
+	})
+	t.Run("LogCaller=None", func(t *testing.T) {
+		cap := &capture{}
+		sink := newSink(cap.Func, Options{LogCaller: None})
+		sink.Error(fmt.Errorf("err"), "msg")
+		expect := ` "msg"="msg" "error"="err"`
+		if cap.log != expect {
+			t.Errorf("\nexpected %q\n     got %q", expect, cap.log)
+		}
+	})
+}
+
+func TestInfoWithName(t *testing.T) {
+	testCases := []struct {
+		name   string
+		names  []string
+		args   []interface{}
+		expect string
+	}{{
+		name:   "one",
+		names:  []string{"pfx1"},
+		args:   makeKV("k", "v"),
+		expect: `pfx1 "level"=0 "msg"="msg" "k"="v"`,
+	}, {
+		name:   "two",
+		names:  []string{"pfx1", "pfx2"},
+		args:   makeKV("k", "v"),
+		expect: `pfx1/pfx2 "level"=0 "msg"="msg" "k"="v"`,
+	}}
+
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			cap := &capture{}
+			sink := newSink(cap.Func, Options{})
+			for _, n := range tc.names {
+				sink = sink.WithName(n)
+			}
+			sink.Info(0, "msg", tc.args...)
+			if cap.log != tc.expect {
+				t.Errorf("\nexpected %q\n     got %q", tc.expect, cap.log)
+			}
+		})
+	}
+}
+
+func TestErrorWithName(t *testing.T) {
+	testCases := []struct {
+		name   string
+		names  []string
+		args   []interface{}
+		expect string
+	}{{
+		name:   "one",
+		names:  []string{"pfx1"},
+		args:   makeKV("k", "v"),
+		expect: `pfx1 "msg"="msg" "error"="err" "k"="v"`,
+	}, {
+		name:   "two",
+		names:  []string{"pfx1", "pfx2"},
+		args:   makeKV("k", "v"),
+		expect: `pfx1/pfx2 "msg"="msg" "error"="err" "k"="v"`,
+	}}
+
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			cap := &capture{}
+			sink := newSink(cap.Func, Options{})
+			for _, n := range tc.names {
+				sink = sink.WithName(n)
+			}
+			sink.Error(fmt.Errorf("err"), "msg", tc.args...)
+			if cap.log != tc.expect {
+				t.Errorf("\nexpected %q\n     got %q", tc.expect, cap.log)
+			}
+		})
+	}
+}
+
+func TestInfoWithValues(t *testing.T) {
+	testCases := []struct {
+		name   string
+		values []interface{}
+		args   []interface{}
+		expect string
+	}{{
+		name:   "zero",
+		values: makeKV(),
+		args:   makeKV("k", "v"),
+		expect: ` "level"=0 "msg"="msg" "k"="v"`,
+	}, {
+		name:   "one",
+		values: makeKV("one", 1),
+		args:   makeKV("k", "v"),
+		expect: ` "level"=0 "msg"="msg" "one"=1 "k"="v"`,
+	}, {
+		name:   "two",
+		values: makeKV("one", 1, "two", 2),
+		args:   makeKV("k", "v"),
+		expect: ` "level"=0 "msg"="msg" "one"=1 "two"=2 "k"="v"`,
+	}}
+
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			cap := &capture{}
+			sink := newSink(cap.Func, Options{})
+			sink = sink.WithValues(tc.values...)
+			sink.Info(0, "msg", tc.args...)
+			if cap.log != tc.expect {
+				t.Errorf("\nexpected %q\n     got %q", tc.expect, cap.log)
+			}
+		})
+	}
+}
+
+func TestErrorWithValues(t *testing.T) {
+	testCases := []struct {
+		name   string
+		values []interface{}
+		args   []interface{}
+		expect string
+	}{{
+		name:   "zero",
+		values: makeKV(),
+		args:   makeKV("k", "v"),
+		expect: ` "msg"="msg" "error"="err" "k"="v"`,
+	}, {
+		name:   "one",
+		values: makeKV("one", 1),
+		args:   makeKV("k", "v"),
+		expect: ` "msg"="msg" "error"="err" "one"=1 "k"="v"`,
+	}, {
+		name:   "two",
+		values: makeKV("one", 1, "two", 2),
+		args:   makeKV("k", "v"),
+		expect: ` "msg"="msg" "error"="err" "one"=1 "two"=2 "k"="v"`,
+	}}
+
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			cap := &capture{}
+			sink := newSink(cap.Func, Options{})
+			sink = sink.WithValues(tc.values...)
+			sink.Error(fmt.Errorf("err"), "msg", tc.args...)
+			if cap.log != tc.expect {
+				t.Errorf("\nexpected %q\n     got %q", tc.expect, cap.log)
+			}
+		})
+	}
+}
+
+func TestInfoWithCallDepth(t *testing.T) {
+	t.Run("one", func(t *testing.T) {
+		cap := &capture{}
+		sink := newSink(cap.Func, Options{LogCaller: All})
+		dSink := sink.(logr.CallDepthLogSink)
+		sink = dSink.WithCallDepth(1)
+		sink.Info(0, "msg")
+		_, file, line, _ := runtime.Caller(1)
+		expect := fmt.Sprintf(` "caller"={"file":%q,"line":%d} "level"=0 "msg"="msg"`, filepath.Base(file), line)
+		if cap.log != expect {
+			t.Errorf("\nexpected %q\n     got %q", expect, cap.log)
+		}
+	})
+}
+
+func TestErrorWithCallDepth(t *testing.T) {
+	t.Run("one", func(t *testing.T) {
+		cap := &capture{}
+		sink := newSink(cap.Func, Options{LogCaller: All})
+		dSink := sink.(logr.CallDepthLogSink)
+		sink = dSink.WithCallDepth(1)
+		sink.Error(fmt.Errorf("err"), "msg")
+		_, file, line, _ := runtime.Caller(1)
+		expect := fmt.Sprintf(` "caller"={"file":%q,"line":%d} "msg"="msg" "error"="err"`, filepath.Base(file), line)
+		if cap.log != expect {
+			t.Errorf("\nexpected %q\n     got %q", expect, cap.log)
+		}
+	})
+}