Reticulum-Go/pkg/debug/debug_test.go

320 lines
7.7 KiB
Go

// SPDX-License-Identifier: Apache-2.0
// Copyright (c) 2024-2026 Quad4.io
package debug
import (
"bytes"
"context"
"flag"
"log/slog"
"strings"
"testing"
)
func TestInit(t *testing.T) {
originalFlag := flag.CommandLine
defer func() {
flag.CommandLine = originalFlag
initialized = false
}()
flag.CommandLine = flag.NewFlagSet("test", flag.ContinueOnError)
debugLevel = flag.Int("debug", 3, "debug level")
levelAtomic.Store(int64(*debugLevel))
levelAtomic.Store(int64(*debugLevel))
Init()
if !initialized {
t.Error("Init() should set initialized to true")
}
if GetLogger() == nil {
t.Error("GetLogger() should return non-nil logger after Init()")
}
}
func TestGetLogger(t *testing.T) {
originalFlag := flag.CommandLine
defer func() {
flag.CommandLine = originalFlag
initialized = false
}()
flag.CommandLine = flag.NewFlagSet("test", flag.ContinueOnError)
debugLevel = flag.Int("debug", 3, "debug level")
levelAtomic.Store(int64(*debugLevel))
levelAtomic.Store(int64(*debugLevel))
initialized = false
logger := GetLogger()
if logger == nil {
t.Error("GetLogger() should return non-nil logger")
}
if !initialized {
t.Error("GetLogger() should initialize if not already initialized")
}
}
func TestLog(t *testing.T) {
originalFlag := flag.CommandLine
defer func() {
flag.CommandLine = originalFlag
initialized = false
}()
flag.CommandLine = flag.NewFlagSet("test", flag.ContinueOnError)
debugLevel = flag.Int("debug", 7, "debug level")
levelAtomic.Store(int64(*debugLevel))
levelAtomic.Store(int64(*debugLevel))
initialized = false
Log(DebugInfo, "test message", "key", "value")
}
func TestSetDebugLevel(t *testing.T) {
originalFlag := flag.CommandLine
defer func() {
flag.CommandLine = originalFlag
initialized = false
}()
flag.CommandLine = flag.NewFlagSet("test", flag.ContinueOnError)
debugLevel = flag.Int("debug", 3, "debug level")
levelAtomic.Store(int64(*debugLevel))
levelAtomic.Store(int64(*debugLevel))
initialized = false
SetDebugLevel(5)
if GetDebugLevel() != 5 {
t.Errorf("SetDebugLevel(5) did not set level correctly, got %d", GetDebugLevel())
}
}
func TestGetDebugLevel(t *testing.T) {
originalFlag := flag.CommandLine
defer func() {
flag.CommandLine = originalFlag
initialized = false
}()
flag.CommandLine = flag.NewFlagSet("test", flag.ContinueOnError)
debugLevel = flag.Int("debug", 4, "debug level")
levelAtomic.Store(int64(*debugLevel))
levelAtomic.Store(int64(*debugLevel))
level := GetDebugLevel()
if level != 4 {
t.Errorf("GetDebugLevel() = %d, want 4", level)
}
}
func TestLog_LevelFiltering(t *testing.T) {
originalFlag := flag.CommandLine
defer func() {
flag.CommandLine = originalFlag
initialized = false
}()
flag.CommandLine = flag.NewFlagSet("test", flag.ContinueOnError)
debugLevel = flag.Int("debug", 3, "debug level")
levelAtomic.Store(int64(*debugLevel))
levelAtomic.Store(int64(*debugLevel))
initialized = false
Log(DebugTrace, "trace message")
Log(DebugInfo, "info message")
Log(DebugError, "error message")
}
func TestConstants(t *testing.T) {
if DebugCritical != 1 {
t.Errorf("DebugCritical = %d, want 1", DebugCritical)
}
if DebugError != 2 {
t.Errorf("DebugError = %d, want 2", DebugError)
}
if DebugInfo != 3 {
t.Errorf("DebugInfo = %d, want 3", DebugInfo)
}
if DebugVerbose != 4 {
t.Errorf("DebugVerbose = %d, want 4", DebugVerbose)
}
if DebugTrace != 5 {
t.Errorf("DebugTrace = %d, want 5", DebugTrace)
}
if DebugPackets != 6 {
t.Errorf("DebugPackets = %d, want 6", DebugPackets)
}
if DebugAll != 7 {
t.Errorf("DebugAll = %d, want 7", DebugAll)
}
}
func TestLog_WithArgs(t *testing.T) {
originalFlag := flag.CommandLine
defer func() {
flag.CommandLine = originalFlag
initialized = false
}()
flag.CommandLine = flag.NewFlagSet("test", flag.ContinueOnError)
debugLevel = flag.Int("debug", 7, "debug level")
levelAtomic.Store(int64(*debugLevel))
initialized = false
Log(DebugInfo, "test message", "key1", "value1", "key2", "value2")
}
func TestInit_MultipleCalls(t *testing.T) {
originalFlag := flag.CommandLine
defer func() {
flag.CommandLine = originalFlag
initialized = false
}()
flag.CommandLine = flag.NewFlagSet("test", flag.ContinueOnError)
debugLevel = flag.Int("debug", 3, "debug level")
levelAtomic.Store(int64(*debugLevel))
initialized = false
Init()
firstLogger := GetLogger()
Init()
secondLogger := GetLogger()
if firstLogger != secondLogger {
t.Error("Multiple Init() calls should not create new loggers")
}
}
func TestLog_DisabledLevel(t *testing.T) {
originalFlag := flag.CommandLine
defer func() {
flag.CommandLine = originalFlag
initialized = false
}()
flag.CommandLine = flag.NewFlagSet("test", flag.ContinueOnError)
debugLevel = flag.Int("debug", 1, "debug level")
levelAtomic.Store(int64(*debugLevel))
initialized = false
Log(DebugTrace, "this should be filtered")
}
// captureLog swaps in a buffer-backed slog handler at the given level
// and returns whatever was written during fn.
func captureLog(t *testing.T, level slog.Level, fn func()) string {
t.Helper()
mu.Lock()
prev := logger
var buf bytes.Buffer
logger = slog.New(slog.NewTextHandler(&buf, &slog.HandlerOptions{Level: level}))
initialized = true
mu.Unlock()
defer func() {
mu.Lock()
logger = prev
mu.Unlock()
}()
fn()
return buf.String()
}
// TestSetDebugLevel_SilencesEverythingButCritical verifies that lowering
// the debug level at runtime truly suppresses higher-level output.
func TestSetDebugLevel_SilencesEverythingButCritical(t *testing.T) {
originalFlag := flag.CommandLine
defer func() {
flag.CommandLine = originalFlag
mu.Lock()
initialized = false
mu.Unlock()
}()
flag.CommandLine = flag.NewFlagSet("test", flag.ContinueOnError)
debugLevel = flag.Int("debug", DebugInfo, "debug level")
levelAtomic.Store(int64(*debugLevel))
mu.Lock()
initialized = false
mu.Unlock()
Init()
SetDebugLevel(DebugCritical)
out := captureLog(t, slogLevelFor(DebugCritical), func() {
Log(DebugCritical, "boom")
Log(DebugError, "err")
Log(DebugInfo, "info")
Log(DebugVerbose, "verbose")
Log(DebugTrace, "trace")
})
if !strings.Contains(out, "boom") {
t.Fatalf("critical message should pass: %q", out)
}
for _, banned := range []string{"err", "info", "verbose", "trace"} {
if strings.Contains(out, banned) {
t.Fatalf("debug level CRITICAL should suppress %q, got: %q", banned, out)
}
}
}
// TestSetDebugLevel_RaisesAfterInit verifies that raising the debug
// level at runtime makes previously-suppressed messages appear.
func TestSetDebugLevel_RaisesAfterInit(t *testing.T) {
originalFlag := flag.CommandLine
defer func() {
flag.CommandLine = originalFlag
mu.Lock()
initialized = false
mu.Unlock()
}()
flag.CommandLine = flag.NewFlagSet("test", flag.ContinueOnError)
debugLevel = flag.Int("debug", DebugCritical, "debug level")
levelAtomic.Store(int64(*debugLevel))
mu.Lock()
initialized = false
mu.Unlock()
Init()
SetDebugLevel(DebugTrace)
out := captureLog(t, slogLevelFor(DebugTrace), func() {
Log(DebugTrace, "trace-now-on")
})
if !strings.Contains(out, "trace-now-on") {
t.Fatalf("trace should be enabled after raising level: %q", out)
}
}
// TestSlogLevelFor sanity-checks the RNS->slog level mapping so the
// handler filter and the explicit Log filter stay consistent.
func TestSlogLevelFor(t *testing.T) {
cases := []struct {
in int
want slog.Level
}{
{DebugCritical, slog.LevelError},
{DebugError, slog.LevelWarn},
{DebugInfo, slog.LevelInfo},
{DebugVerbose, slog.LevelDebug},
{DebugTrace, slog.LevelDebug},
{DebugPackets, slog.LevelDebug},
{DebugAll, slog.LevelDebug},
}
for _, c := range cases {
if got := slogLevelFor(c.in); got != c.want {
t.Errorf("slogLevelFor(%d)=%v, want %v", c.in, got, c.want)
}
}
_ = context.Background()
}