2025-11-06 13:53:02 +01:00
|
|
|
// Copyright (c) 2023 - 2025 IBM Corp.
|
|
|
|
|
// All rights reserved.
|
|
|
|
|
//
|
|
|
|
|
// 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 logging
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"bytes"
|
|
|
|
|
"log"
|
|
|
|
|
"strings"
|
|
|
|
|
"testing"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// TestLoggingCallbacks_NoLoggers tests the case when no loggers are provided.
|
|
|
|
|
// It should return two callbacks using the default logger.
|
|
|
|
|
func TestLoggingCallbacks_NoLoggers(t *testing.T) {
|
|
|
|
|
infoLog, errLog := LoggingCallbacks()
|
|
|
|
|
|
|
|
|
|
if infoLog == nil {
|
|
|
|
|
t.Error("Expected infoLog to be non-nil")
|
|
|
|
|
}
|
|
|
|
|
if errLog == nil {
|
|
|
|
|
t.Error("Expected errLog to be non-nil")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Verify both callbacks work
|
|
|
|
|
var buf bytes.Buffer
|
|
|
|
|
log.SetOutput(&buf)
|
|
|
|
|
defer log.SetOutput(nil)
|
|
|
|
|
|
|
|
|
|
infoLog("test info: %s", "message")
|
|
|
|
|
if !strings.Contains(buf.String(), "test info: message") {
|
|
|
|
|
t.Errorf("Expected log to contain 'test info: message', got: %s", buf.String())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
buf.Reset()
|
|
|
|
|
errLog("test error: %s", "message")
|
|
|
|
|
if !strings.Contains(buf.String(), "test error: message") {
|
|
|
|
|
t.Errorf("Expected log to contain 'test error: message', got: %s", buf.String())
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TestLoggingCallbacks_OneLogger tests the case when one logger is provided.
|
|
|
|
|
// Both callbacks should use the same logger.
|
|
|
|
|
func TestLoggingCallbacks_OneLogger(t *testing.T) {
|
|
|
|
|
var buf bytes.Buffer
|
|
|
|
|
logger := log.New(&buf, "TEST: ", 0)
|
|
|
|
|
|
|
|
|
|
infoLog, errLog := LoggingCallbacks(logger)
|
|
|
|
|
|
|
|
|
|
if infoLog == nil {
|
|
|
|
|
t.Error("Expected infoLog to be non-nil")
|
|
|
|
|
}
|
|
|
|
|
if errLog == nil {
|
|
|
|
|
t.Error("Expected errLog to be non-nil")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Test info callback
|
|
|
|
|
infoLog("info message: %d", 42)
|
|
|
|
|
output := buf.String()
|
|
|
|
|
if !strings.Contains(output, "TEST: info message: 42") {
|
|
|
|
|
t.Errorf("Expected log to contain 'TEST: info message: 42', got: %s", output)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Test error callback uses same logger
|
|
|
|
|
buf.Reset()
|
|
|
|
|
errLog("error message: %s", "failed")
|
|
|
|
|
output = buf.String()
|
|
|
|
|
if !strings.Contains(output, "TEST: error message: failed") {
|
|
|
|
|
t.Errorf("Expected log to contain 'TEST: error message: failed', got: %s", output)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TestLoggingCallbacks_TwoLoggers tests the case when two loggers are provided.
|
|
|
|
|
// First callback should use first logger, second callback should use second logger.
|
|
|
|
|
func TestLoggingCallbacks_TwoLoggers(t *testing.T) {
|
|
|
|
|
var infoBuf, errBuf bytes.Buffer
|
|
|
|
|
infoLogger := log.New(&infoBuf, "INFO: ", 0)
|
|
|
|
|
errorLogger := log.New(&errBuf, "ERROR: ", 0)
|
|
|
|
|
|
|
|
|
|
infoLog, errLog := LoggingCallbacks(infoLogger, errorLogger)
|
|
|
|
|
|
|
|
|
|
if infoLog == nil {
|
|
|
|
|
t.Error("Expected infoLog to be non-nil")
|
|
|
|
|
}
|
|
|
|
|
if errLog == nil {
|
|
|
|
|
t.Error("Expected errLog to be non-nil")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Test info callback uses first logger
|
|
|
|
|
infoLog("success: %s", "operation completed")
|
|
|
|
|
infoOutput := infoBuf.String()
|
|
|
|
|
if !strings.Contains(infoOutput, "INFO: success: operation completed") {
|
|
|
|
|
t.Errorf("Expected info log to contain 'INFO: success: operation completed', got: %s", infoOutput)
|
|
|
|
|
}
|
|
|
|
|
if errBuf.Len() != 0 {
|
|
|
|
|
t.Errorf("Expected error buffer to be empty, got: %s", errBuf.String())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Test error callback uses second logger
|
|
|
|
|
errLog("failure: %s", "operation failed")
|
|
|
|
|
errOutput := errBuf.String()
|
|
|
|
|
if !strings.Contains(errOutput, "ERROR: failure: operation failed") {
|
|
|
|
|
t.Errorf("Expected error log to contain 'ERROR: failure: operation failed', got: %s", errOutput)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TestLoggingCallbacks_MultipleLoggers tests the case when more than two loggers are provided.
|
|
|
|
|
// Should use first two loggers and ignore the rest.
|
|
|
|
|
func TestLoggingCallbacks_MultipleLoggers(t *testing.T) {
|
|
|
|
|
var buf1, buf2, buf3 bytes.Buffer
|
|
|
|
|
logger1 := log.New(&buf1, "LOG1: ", 0)
|
|
|
|
|
logger2 := log.New(&buf2, "LOG2: ", 0)
|
|
|
|
|
logger3 := log.New(&buf3, "LOG3: ", 0)
|
|
|
|
|
|
|
|
|
|
infoLog, errLog := LoggingCallbacks(logger1, logger2, logger3)
|
|
|
|
|
|
|
|
|
|
if infoLog == nil {
|
|
|
|
|
t.Error("Expected infoLog to be non-nil")
|
|
|
|
|
}
|
|
|
|
|
if errLog == nil {
|
|
|
|
|
t.Error("Expected errLog to be non-nil")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Test that first logger is used for info
|
|
|
|
|
infoLog("message 1")
|
|
|
|
|
if !strings.Contains(buf1.String(), "LOG1: message 1") {
|
|
|
|
|
t.Errorf("Expected first logger to be used, got: %s", buf1.String())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Test that second logger is used for error
|
|
|
|
|
errLog("message 2")
|
|
|
|
|
if !strings.Contains(buf2.String(), "LOG2: message 2") {
|
|
|
|
|
t.Errorf("Expected second logger to be used, got: %s", buf2.String())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Test that third logger is not used
|
|
|
|
|
if buf3.Len() != 0 {
|
|
|
|
|
t.Errorf("Expected third logger to not be used, got: %s", buf3.String())
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TestLoggingCallbacks_FormattingWithMultipleArgs tests that formatting works correctly
|
|
|
|
|
// with multiple arguments.
|
|
|
|
|
func TestLoggingCallbacks_FormattingWithMultipleArgs(t *testing.T) {
|
|
|
|
|
var buf bytes.Buffer
|
|
|
|
|
logger := log.New(&buf, "", 0)
|
|
|
|
|
|
|
|
|
|
infoLog, _ := LoggingCallbacks(logger)
|
|
|
|
|
|
|
|
|
|
infoLog("test %s %d %v", "string", 123, true)
|
|
|
|
|
output := buf.String()
|
|
|
|
|
if !strings.Contains(output, "test string 123 true") {
|
|
|
|
|
t.Errorf("Expected formatted output 'test string 123 true', got: %s", output)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TestLoggingCallbacks_NoFormatting tests logging without format specifiers.
|
|
|
|
|
func TestLoggingCallbacks_NoFormatting(t *testing.T) {
|
|
|
|
|
var buf bytes.Buffer
|
|
|
|
|
logger := log.New(&buf, "PREFIX: ", 0)
|
|
|
|
|
|
|
|
|
|
infoLog, _ := LoggingCallbacks(logger)
|
|
|
|
|
|
|
|
|
|
infoLog("simple message")
|
|
|
|
|
output := buf.String()
|
|
|
|
|
if !strings.Contains(output, "PREFIX: simple message") {
|
|
|
|
|
t.Errorf("Expected 'PREFIX: simple message', got: %s", output)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TestLoggingCallbacks_EmptyMessage tests logging with empty message.
|
|
|
|
|
func TestLoggingCallbacks_EmptyMessage(t *testing.T) {
|
|
|
|
|
var buf bytes.Buffer
|
|
|
|
|
logger := log.New(&buf, "", 0)
|
|
|
|
|
|
|
|
|
|
infoLog, _ := LoggingCallbacks(logger)
|
|
|
|
|
|
|
|
|
|
infoLog("")
|
|
|
|
|
output := buf.String()
|
|
|
|
|
// Should still produce output (newline at minimum)
|
|
|
|
|
if len(output) == 0 {
|
|
|
|
|
t.Error("Expected some output even with empty message")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TestLoggingCallbacks_NilLogger tests behavior when nil logger is passed.
|
|
|
|
|
// This tests edge case handling.
|
|
|
|
|
func TestLoggingCallbacks_NilLogger(t *testing.T) {
|
|
|
|
|
// This should not panic
|
|
|
|
|
defer func() {
|
|
|
|
|
if r := recover(); r != nil {
|
|
|
|
|
t.Errorf("LoggingCallbacks panicked with nil logger: %v", r)
|
|
|
|
|
}
|
|
|
|
|
}()
|
|
|
|
|
|
|
|
|
|
infoLog, errLog := LoggingCallbacks(nil)
|
|
|
|
|
|
|
|
|
|
// The callbacks should still be created
|
|
|
|
|
if infoLog == nil {
|
|
|
|
|
t.Error("Expected infoLog to be non-nil even with nil logger")
|
|
|
|
|
}
|
|
|
|
|
if errLog == nil {
|
|
|
|
|
t.Error("Expected errLog to be non-nil even with nil logger")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TestLoggingCallbacks_ConsecutiveCalls tests that callbacks can be called multiple times.
|
|
|
|
|
func TestLoggingCallbacks_ConsecutiveCalls(t *testing.T) {
|
|
|
|
|
var buf bytes.Buffer
|
|
|
|
|
logger := log.New(&buf, "", 0)
|
|
|
|
|
|
|
|
|
|
infoLog, errLog := LoggingCallbacks(logger)
|
|
|
|
|
|
|
|
|
|
// Multiple calls to info
|
|
|
|
|
infoLog("call 1")
|
|
|
|
|
infoLog("call 2")
|
|
|
|
|
infoLog("call 3")
|
|
|
|
|
|
|
|
|
|
output := buf.String()
|
|
|
|
|
if !strings.Contains(output, "call 1") || !strings.Contains(output, "call 2") || !strings.Contains(output, "call 3") {
|
|
|
|
|
t.Errorf("Expected all three calls to be logged, got: %s", output)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
buf.Reset()
|
|
|
|
|
|
|
|
|
|
// Multiple calls to error
|
|
|
|
|
errLog("error 1")
|
|
|
|
|
errLog("error 2")
|
|
|
|
|
|
|
|
|
|
output = buf.String()
|
|
|
|
|
if !strings.Contains(output, "error 1") || !strings.Contains(output, "error 2") {
|
|
|
|
|
t.Errorf("Expected both error calls to be logged, got: %s", output)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// BenchmarkLoggingCallbacks_NoLoggers benchmarks the no-logger case.
|
|
|
|
|
func BenchmarkLoggingCallbacks_NoLoggers(b *testing.B) {
|
2025-11-15 17:00:22 +01:00
|
|
|
for b.Loop() {
|
2025-11-06 13:53:02 +01:00
|
|
|
LoggingCallbacks()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// BenchmarkLoggingCallbacks_OneLogger benchmarks the single-logger case.
|
|
|
|
|
func BenchmarkLoggingCallbacks_OneLogger(b *testing.B) {
|
|
|
|
|
var buf bytes.Buffer
|
|
|
|
|
logger := log.New(&buf, "", 0)
|
|
|
|
|
|
|
|
|
|
b.ResetTimer()
|
2025-11-15 17:00:22 +01:00
|
|
|
for b.Loop() {
|
2025-11-06 13:53:02 +01:00
|
|
|
LoggingCallbacks(logger)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// BenchmarkLoggingCallbacks_TwoLoggers benchmarks the two-logger case.
|
|
|
|
|
func BenchmarkLoggingCallbacks_TwoLoggers(b *testing.B) {
|
|
|
|
|
var buf1, buf2 bytes.Buffer
|
|
|
|
|
logger1 := log.New(&buf1, "", 0)
|
|
|
|
|
logger2 := log.New(&buf2, "", 0)
|
|
|
|
|
|
|
|
|
|
b.ResetTimer()
|
2025-11-15 17:00:22 +01:00
|
|
|
for b.Loop() {
|
2025-11-06 13:53:02 +01:00
|
|
|
LoggingCallbacks(logger1, logger2)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// BenchmarkLoggingCallbacks_Logging benchmarks actual logging operations.
|
|
|
|
|
func BenchmarkLoggingCallbacks_Logging(b *testing.B) {
|
|
|
|
|
var buf bytes.Buffer
|
|
|
|
|
logger := log.New(&buf, "", 0)
|
|
|
|
|
infoLog, _ := LoggingCallbacks(logger)
|
|
|
|
|
|
|
|
|
|
b.ResetTimer()
|
|
|
|
|
for i := 0; i < b.N; i++ {
|
|
|
|
|
infoLog("benchmark message %d", i)
|
|
|
|
|
}
|
|
|
|
|
}
|