mirror of
https://github.com/ivuorinen/gibidify.git
synced 2026-01-26 03:24:05 +00:00
243 lines
5.8 KiB
Go
243 lines
5.8 KiB
Go
package utils
|
|
|
|
import (
|
|
"bytes"
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/sirupsen/logrus"
|
|
)
|
|
|
|
// captureLogOutput captures logrus output for testing
|
|
func captureLogOutput(f func()) string {
|
|
var buf bytes.Buffer
|
|
logrus.SetOutput(&buf)
|
|
defer logrus.SetOutput(logrus.StandardLogger().Out)
|
|
f()
|
|
return buf.String()
|
|
}
|
|
|
|
func TestLogError(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
operation string
|
|
err error
|
|
args []any
|
|
wantLog string
|
|
wantEmpty bool
|
|
}{
|
|
{
|
|
name: "nil error should not log",
|
|
operation: "test operation",
|
|
err: nil,
|
|
args: nil,
|
|
wantEmpty: true,
|
|
},
|
|
{
|
|
name: "basic error logging",
|
|
operation: "failed to read file",
|
|
err: errors.New("permission denied"),
|
|
args: nil,
|
|
wantLog: "failed to read file: permission denied",
|
|
},
|
|
{
|
|
name: "error with formatting args",
|
|
operation: "failed to process file %s",
|
|
err: errors.New("file too large"),
|
|
args: []any{"test.txt"},
|
|
wantLog: "failed to process file test.txt: file too large",
|
|
},
|
|
{
|
|
name: "error with multiple formatting args",
|
|
operation: "failed to copy from %s to %s",
|
|
err: errors.New("disk full"),
|
|
args: []any{"source.txt", "dest.txt"},
|
|
wantLog: "failed to copy from source.txt to dest.txt: disk full",
|
|
},
|
|
{
|
|
name: "wrapped error",
|
|
operation: "database operation failed",
|
|
err: fmt.Errorf("connection error: %w", errors.New("timeout")),
|
|
args: nil,
|
|
wantLog: "database operation failed: connection error: timeout",
|
|
},
|
|
{
|
|
name: "empty operation string",
|
|
operation: "",
|
|
err: errors.New("some error"),
|
|
args: nil,
|
|
wantLog: ": some error",
|
|
},
|
|
{
|
|
name: "operation with percentage sign",
|
|
operation: "processing 50% complete",
|
|
err: errors.New("interrupted"),
|
|
args: nil,
|
|
wantLog: "processing 50% complete: interrupted",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
output := captureLogOutput(func() {
|
|
LogError(tt.operation, tt.err, tt.args...)
|
|
})
|
|
|
|
if tt.wantEmpty {
|
|
if output != "" {
|
|
t.Errorf("LogError() logged output when error was nil: %q", output)
|
|
}
|
|
return
|
|
}
|
|
|
|
if !strings.Contains(output, tt.wantLog) {
|
|
t.Errorf("LogError() output = %q, want to contain %q", output, tt.wantLog)
|
|
}
|
|
|
|
// Verify it's logged at ERROR level
|
|
if !strings.Contains(output, "level=error") {
|
|
t.Errorf("LogError() should log at ERROR level, got: %q", output)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestLogErrorf(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
err error
|
|
format string
|
|
args []any
|
|
wantLog string
|
|
wantEmpty bool
|
|
}{
|
|
{
|
|
name: "nil error should not log",
|
|
err: nil,
|
|
format: "operation %s failed",
|
|
args: []any{"test"},
|
|
wantEmpty: true,
|
|
},
|
|
{
|
|
name: "basic formatted error",
|
|
err: errors.New("not found"),
|
|
format: "file %s not found",
|
|
args: []any{"config.yaml"},
|
|
wantLog: "file config.yaml not found: not found",
|
|
},
|
|
{
|
|
name: "multiple format arguments",
|
|
err: errors.New("invalid range"),
|
|
format: "value %d is not between %d and %d",
|
|
args: []any{150, 0, 100},
|
|
wantLog: "value 150 is not between 0 and 100: invalid range",
|
|
},
|
|
{
|
|
name: "no format arguments",
|
|
err: errors.New("generic error"),
|
|
format: "operation failed",
|
|
args: nil,
|
|
wantLog: "operation failed: generic error",
|
|
},
|
|
{
|
|
name: "format with different types",
|
|
err: errors.New("type mismatch"),
|
|
format: "expected %s but got %d",
|
|
args: []any{"string", 42},
|
|
wantLog: "expected string but got 42: type mismatch",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
output := captureLogOutput(func() {
|
|
LogErrorf(tt.err, tt.format, tt.args...)
|
|
})
|
|
|
|
if tt.wantEmpty {
|
|
if output != "" {
|
|
t.Errorf("LogErrorf() logged output when error was nil: %q", output)
|
|
}
|
|
return
|
|
}
|
|
|
|
if !strings.Contains(output, tt.wantLog) {
|
|
t.Errorf("LogErrorf() output = %q, want to contain %q", output, tt.wantLog)
|
|
}
|
|
|
|
// Verify it's logged at ERROR level
|
|
if !strings.Contains(output, "level=error") {
|
|
t.Errorf("LogErrorf() should log at ERROR level, got: %q", output)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestLogErrorConcurrency(t *testing.T) {
|
|
// Test that LogError is safe for concurrent use
|
|
done := make(chan bool)
|
|
for i := 0; i < 10; i++ {
|
|
go func(n int) {
|
|
LogError("concurrent operation", fmt.Errorf("error %d", n))
|
|
done <- true
|
|
}(i)
|
|
}
|
|
|
|
// Wait for all goroutines to complete
|
|
for i := 0; i < 10; i++ {
|
|
<-done
|
|
}
|
|
}
|
|
|
|
func TestLogErrorfConcurrency(t *testing.T) {
|
|
// Test that LogErrorf is safe for concurrent use
|
|
done := make(chan bool)
|
|
for i := 0; i < 10; i++ {
|
|
go func(n int) {
|
|
LogErrorf(fmt.Errorf("error %d", n), "concurrent operation %d", n)
|
|
done <- true
|
|
}(i)
|
|
}
|
|
|
|
// Wait for all goroutines to complete
|
|
for i := 0; i < 10; i++ {
|
|
<-done
|
|
}
|
|
}
|
|
|
|
// BenchmarkLogError benchmarks the LogError function
|
|
func BenchmarkLogError(b *testing.B) {
|
|
err := errors.New("benchmark error")
|
|
// Disable output during benchmark
|
|
logrus.SetOutput(bytes.NewBuffer(nil))
|
|
defer logrus.SetOutput(logrus.StandardLogger().Out)
|
|
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
LogError("benchmark operation", err)
|
|
}
|
|
}
|
|
|
|
// BenchmarkLogErrorf benchmarks the LogErrorf function
|
|
func BenchmarkLogErrorf(b *testing.B) {
|
|
err := errors.New("benchmark error")
|
|
// Disable output during benchmark
|
|
logrus.SetOutput(bytes.NewBuffer(nil))
|
|
defer logrus.SetOutput(logrus.StandardLogger().Out)
|
|
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
LogErrorf(err, "benchmark operation %d", i)
|
|
}
|
|
}
|
|
|
|
// BenchmarkLogErrorNil benchmarks LogError with nil error (no-op case)
|
|
func BenchmarkLogErrorNil(b *testing.B) {
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
LogError("benchmark operation", nil)
|
|
}
|
|
}
|