From d6f9703fbb263180bf4da21dc2fdc0779a23f9dc Mon Sep 17 00:00:00 2001 From: Tom Pantelis Date: Wed, 12 Feb 2025 11:30:05 -0500 Subject: [PATCH] Increase unit test coverage in the log package Signed-off-by: Tom Pantelis --- pkg/log/log_suite_test.go | 79 ++++++++++++++++++++++++++++ pkg/log/logger.go | 7 ++- pkg/log/logger_test.go | 108 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 192 insertions(+), 2 deletions(-) create mode 100644 pkg/log/log_suite_test.go create mode 100644 pkg/log/logger_test.go diff --git a/pkg/log/log_suite_test.go b/pkg/log/log_suite_test.go new file mode 100644 index 00000000..bd3133e6 --- /dev/null +++ b/pkg/log/log_suite_test.go @@ -0,0 +1,79 @@ +/* +SPDX-License-Identifier: Apache-2.0 + +Copyright Contributors to the Submariner project. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package log_test + +import ( + "testing" + + "github.com/go-logr/logr" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + "github.com/submariner-io/admiral/pkg/log" + logf "sigs.k8s.io/controller-runtime/pkg/log" +) + +func TestLog(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Log Suite") +} + +var ( + rootLogSink = newLogSinkImpl() + exited bool +) + +var _ = BeforeSuite(func() { + logf.SetLogger(logr.New(rootLogSink)) + + log.Exit = func(_ int) { + exited = true + } +}) + +type logSinkImpl struct { + infoCh chan []any + errorCh chan []any +} + +func newLogSinkImpl() *logSinkImpl { + return &logSinkImpl{infoCh: make(chan []any, 10), errorCh: make(chan []any, 10)} +} + +func (l *logSinkImpl) Init(_ logr.RuntimeInfo) { +} + +func (l *logSinkImpl) Enabled(_ int) bool { + return true +} + +func (l *logSinkImpl) Info(level int, msg string, keysAndValues ...any) { + l.infoCh <- append([]any{level, msg}, keysAndValues...) +} + +func (l *logSinkImpl) Error(err error, msg string, keysAndValues ...any) { + l.errorCh <- append([]any{err, msg}, keysAndValues...) +} + +func (l *logSinkImpl) WithValues(_ ...any) logr.LogSink { + return newLogSinkImpl() +} + +func (l *logSinkImpl) WithName(_ string) logr.LogSink { + return newLogSinkImpl() +} diff --git a/pkg/log/logger.go b/pkg/log/logger.go index ffe2a8c8..cc2dabe7 100644 --- a/pkg/log/logger.go +++ b/pkg/log/logger.go @@ -30,6 +30,9 @@ const ( FatalKey = "FATAL" ) +// Exit hook for unit tests. +var Exit = os.Exit + type Logger struct { logr.Logger } @@ -60,7 +63,7 @@ func (l Logger) Warningf(format string, args ...interface{}) { func (l Logger) Fatal(msg string, keysAndValues ...interface{}) { l.Logger.Error(nil, msg, append(keysAndValues, FatalKey, "true")...) - os.Exit(255) + Exit(255) } func (l Logger) Fatalf(format string, args ...interface{}) { @@ -73,7 +76,7 @@ func (l Logger) FatalOnError(err error, msg string, keysAndValues ...interface{} } l.Logger.Error(err, msg, append(keysAndValues, FatalKey, "true")...) - os.Exit(255) + Exit(255) } func (l Logger) FatalfOnError(err error, format string, args ...interface{}) { diff --git a/pkg/log/logger_test.go b/pkg/log/logger_test.go new file mode 100644 index 00000000..a2438635 --- /dev/null +++ b/pkg/log/logger_test.go @@ -0,0 +1,108 @@ +/* +SPDX-License-Identifier: Apache-2.0 + +Copyright Contributors to the Submariner project. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package log_test + +import ( + "errors" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + "github.com/submariner-io/admiral/pkg/log" + logf "sigs.k8s.io/controller-runtime/pkg/log" +) + +var _ = Describe("Logger", func() { + var logger log.Logger + + BeforeEach(func() { + exited = false + logger = log.Logger{Logger: logf.Log} + }) + + Specify("Infof should log a formatted info message", func() { + logger.Infof("value is %d", 3) + Expect(rootLogSink.infoCh).To(Receive(HaveExactElements(0, "value is 3"))) + }) + + Specify("Info should log an info message with keys and values", func() { + logger.Info("hello", "key", "value") + Expect(rootLogSink.infoCh).To(Receive(HaveExactElements(0, "hello", "key", "value"))) + }) + + Specify("Errorf should log the error with a formatted message", func() { + err := errors.New("some error") + logger.Errorf(err, "value is %d", 3) + Expect(rootLogSink.errorCh).To(Receive(HaveExactElements(err, "value is 3"))) + }) + + Specify("Error should log the error with a message and keys and values", func() { + err := errors.New("some error") + logger.Error(err, "failed", "key", "value") + Expect(rootLogSink.errorCh).To(Receive(HaveExactElements(err, "failed", "key", "value"))) + }) + + Specify("Warningf should log a formatted info message", func() { + logger.Warningf("value is %d", 3) + Expect(rootLogSink.infoCh).To(Receive(HaveExactElements(0, "value is 3", log.WarningKey, "true"))) + }) + + Specify("Warning should log an info message with keys and values", func() { + logger.Warning("hello", "key", "value") + Expect(rootLogSink.infoCh).To(Receive(HaveExactElements(0, "hello", "key", "value", log.WarningKey, "true"))) + }) + + Specify("Fatalf should log a formatted error message and then exit", func() { + logger.Fatalf("value is %d", 3) + Expect(rootLogSink.errorCh).To(Receive(HaveExactElements(nil, "value is 3", log.FatalKey, "true"))) + Expect(exited).To(BeTrue()) + }) + + Specify("Fatal should log an error message with keys and values and then exit", func() { + logger.Fatal("failed", "key", "value") + Expect(rootLogSink.errorCh).To(Receive(HaveExactElements(nil, "failed", "key", "value", log.FatalKey, "true"))) + Expect(exited).To(BeTrue()) + }) + + Context("FatalfOnError", func() { + Specify("with an error specified should log the error and then exit", func() { + err := errors.New("some error") + logger.FatalfOnError(err, "value is %d", 3) + Expect(rootLogSink.errorCh).To(Receive(HaveExactElements(err, "value is 3", log.FatalKey, "true"))) + Expect(exited).To(BeTrue()) + }) + + Specify("with no error specified should not exit", func() { + logger.FatalfOnError(nil, "value is %d", 3) + Expect(rootLogSink.errorCh).NotTo(Receive()) + Expect(exited).To(BeFalse()) + }) + }) + + Specify("FatalOnError should log the error and then exit", func() { + err := errors.New("some error") + logger.FatalOnError(err, "failed", "key", "value") + Expect(rootLogSink.errorCh).To(Receive(HaveExactElements(err, "failed", "key", "value", log.FatalKey, "true"))) + Expect(exited).To(BeTrue()) + }) + + Specify("V should set the log level", func() { + logger.V(2).Info("message") + Expect(rootLogSink.infoCh).To(Receive(HaveExactElements(2, "message"))) + }) +})