From 4994552882f700a63702d4598193f07ebe62dc2d Mon Sep 17 00:00:00 2001 From: Chris Koch Date: Wed, 7 Feb 2024 23:12:09 +0000 Subject: [PATCH] llog: tests and examples Signed-off-by: Chris Koch --- llog/default2_test.go | 20 +++++ llog/default_test.go | 36 +++++++++ llog/example_test.go | 26 +++++++ llog/levellog.go | 10 +-- llog/llog_test.go | 174 ++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 261 insertions(+), 5 deletions(-) create mode 100644 llog/default2_test.go create mode 100644 llog/default_test.go create mode 100644 llog/example_test.go create mode 100644 llog/llog_test.go diff --git a/llog/default2_test.go b/llog/default2_test.go new file mode 100644 index 0000000..5621ea2 --- /dev/null +++ b/llog/default2_test.go @@ -0,0 +1,20 @@ +// Copyright 2024 the u-root Authors. All rights reserved +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package llog_test + +import ( + "log/slog" + + "github.com/u-root/uio/llog" +) + +func ExampleDefault_withtime() { + l := llog.Default() + l.Infof("An INFO level string") + l.Debugf("A DEBUG level that does not appear") + + l.Level = llog.Level(slog.LevelDebug) + l.Debugf("A DEBUG level that appears") +} diff --git a/llog/default_test.go b/llog/default_test.go new file mode 100644 index 0000000..f485759 --- /dev/null +++ b/llog/default_test.go @@ -0,0 +1,36 @@ +// Copyright 2024 the u-root Authors. All rights reserved +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package llog_test + +import ( + "log" + "log/slog" + "os" + + "github.com/u-root/uio/llog" +) + +func ExampleDefault() { + // Example is reproducible without date/time displayed. + log.SetFlags(0) + // Examples only go out to stdout. + log.SetOutput(os.Stdout) + + l := llog.Default() + l.Infof("An INFO level string") + l.Debugf("A DEBUG level that does not appear") + + l.Level = llog.Level(slog.LevelDebug) + l.Debugf("A DEBUG level that appears") + + l.Warnf("I'm warning you") + l.Errorf("This is going to error") + + // Output: + // INFO An INFO level string + // DEBUG A DEBUG level that appears + // WARN I'm warning you + // ERROR This is going to error +} diff --git a/llog/example_test.go b/llog/example_test.go new file mode 100644 index 0000000..f996ab8 --- /dev/null +++ b/llog/example_test.go @@ -0,0 +1,26 @@ +// Copyright 2024 the u-root Authors. All rights reserved +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package llog_test + +import ( + "flag" + "log/slog" + + "github.com/u-root/uio/llog" +) + +func someFunc(v llog.Printf) { + v("logs at the given level %s", "foo") +} + +func Example() { + l := llog.Default() + // If -v is set, l.Level becomes slog.LevelDebug. + l.Level.RegisterDebugFlag(flag.CommandLine, "v") + flag.Parse() + + someFunc(l.Debugf) + someFunc(l.Printf(slog.LevelWarn)) +} diff --git a/llog/levellog.go b/llog/levellog.go index e5022bd..b91ff4e 100644 --- a/llog/levellog.go +++ b/llog/levellog.go @@ -69,7 +69,7 @@ func SinkFor(p Printf) Sink { // Prepend log level. format = "%s " + format args = append([]any{level}, args...) - p(format, args) + p(format, args...) } } @@ -87,7 +87,7 @@ type Logger struct { // uses l as the default log level. // // Logs with level >= l will be printed using p. -func New(p Printf, l Level) *Logger { +func New(l Level, p Printf) *Logger { return &Logger{ Sink: SinkFor(p), Level: l, @@ -96,10 +96,10 @@ func New(p Printf, l Level) *Logger { // Printf returns a Printf that can be passed around to log at the given level. func (l *Logger) Printf(level slog.Level) Printf { + if l == nil || l.Sink == nil { + return func(fmt string, args ...any) {} + } return func(fmt string, args ...any) { - if l == nil { - return - } l.Logf(level, fmt, args...) } } diff --git a/llog/llog_test.go b/llog/llog_test.go new file mode 100644 index 0000000..3b8bb77 --- /dev/null +++ b/llog/llog_test.go @@ -0,0 +1,174 @@ +// Copyright 2024 the u-root Authors. All rights reserved +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package llog + +import ( + "flag" + "fmt" + "log" + "log/slog" + "strconv" + "strings" + "testing" +) + +func TestLevelFlag(t *testing.T) { + for _, tt := range []struct { + args []string + want Level + }{ + { + args: []string{"-level=4"}, + want: Level(slog.LevelWarn), + }, + { + args: []string{}, + want: Level(slog.LevelInfo), + }, + } { + f := flag.NewFlagSet("", flag.ContinueOnError) + + var v Level + v.RegisterLevelFlag(f, "level") + f.Parse(tt.args) + + if v != tt.want { + t.Errorf("Parse(%#v) = %v, want %v", tt.args, v, tt.want) + } + } + + for _, tt := range []struct { + args []string + want Level + err error + }{ + { + args: []string{"-v"}, + want: Level(slog.LevelWarn), + }, + { + args: []string{}, + want: Level(slog.LevelInfo), + }, + { + args: []string{"-v=true"}, + want: Level(slog.LevelWarn), + }, + { + args: []string{"-v=true", "-v=false"}, + want: Level(slog.LevelWarn), + }, + { + args: []string{"-v=foobar"}, + want: Level(slog.LevelInfo), + err: strconv.ErrSyntax, + }, + } { + f := flag.NewFlagSet("", flag.ContinueOnError) + + var v Level + v.RegisterVerboseFlag(f, "v", slog.LevelWarn) + // Parse doesn't use %w. + if err := f.Parse(tt.args); err != tt.err && err != nil && !strings.Contains(err.Error(), tt.err.Error()) { + t.Errorf("Parse(%#v) = %v, want %v", tt.args, err, tt.err) + } + if v != tt.want { + t.Errorf("Parse(%#v) = %v, want %v", tt.args, v, tt.want) + } + } + + for _, tt := range []struct { + args []string + want Level + err error + }{ + { + args: []string{"-v"}, + want: Level(slog.LevelDebug), + }, + { + args: []string{}, + want: Level(slog.LevelInfo), + }, + { + args: []string{"-v=true"}, + want: Level(slog.LevelDebug), + }, + { + args: []string{"-v=true", "-v=false"}, + want: Level(slog.LevelDebug), + }, + { + args: []string{"-v=foobar"}, + want: Level(slog.LevelInfo), + err: strconv.ErrSyntax, + }, + } { + f := flag.NewFlagSet("", flag.ContinueOnError) + + var v Level + v.RegisterDebugFlag(f, "v") + // Parse doesn't use %w. + if err := f.Parse(tt.args); err != tt.err && err != nil && !strings.Contains(err.Error(), tt.err.Error()) { + t.Errorf("Parse(%#v) = %v, want %v", tt.args, err, tt.err) + } + if v != tt.want { + t.Errorf("Parse(%#v) = %v, want %v", tt.args, v, tt.want) + } + } +} + +func TestNilLogger(t *testing.T) { + for _, l := range []*Logger{nil, &Logger{}} { + // Test that none of this panics. + l.Printf(slog.LevelDebug)("nothing") + l.Debugf("nothing") + l.Infof("nothing") + l.Warnf("nothing") + l.Errorf("nothing") + l.Logf(slog.LevelDebug, "nothing") + } +} + +func TestLog(t *testing.T) { + var s strings.Builder + l := New(Level(slog.LevelDebug), func(format string, args ...any) { + fmt.Fprintf(&s, format+"\n", args...) + }) + + l.Printf(slog.LevelDebug)("nothing") + l.Debugf("nothing") + l.Infof("nothing") + l.Warnf("nothing") + l.Errorf("nothing") + l.Logf(slog.LevelDebug, "nothing") + + want := `DEBUG nothing +DEBUG nothing +INFO nothing +WARN nothing +ERROR nothing +DEBUG nothing +` + if got := s.String(); got != want { + t.Errorf("got %v, want %v", got, want) + } +} + +func TestDefaults(t *testing.T) { + var s strings.Builder + log.SetOutput(&s) + log.SetFlags(0) + + l := Debug() + l.Debugf("foobar") + want := "DEBUG foobar\n" + if got := s.String(); got != want { + t.Errorf("got %v, want %v", got, want) + } + + l = Test(t) + l.Debugf("more foobar") +}