Modernizes several `internal/*` packages under the Go proxy, replaces
third-party forks with standard-library primitives, and brings the
test suite from near-zero to high coverage across the touched packages.
Package changes
- **`internal/errors`** — Rewrites the `pkg/errors` fork as a thin
wrapper
over stdlib `errors`. A single `withStack` struct captures stack
traces via `runtime.Callers`; `fmt.Errorf("%w", ...)` handles all
message wrapping. Restores `errors.Is`/`As`/`Unwrap` chain traversal
(silently broken in the fork) and deletes ~190 lines of stack/frame
formatting. `Is`, `As`, `Unwrap`, and `Join` are re-exported so
callers need a single import.
- **`internal/logger`** — Swaps stdlib `log.Logger` for `log/slog` JSON
handlers with UTC timestamps and custom level labels (`verb`, `debug`,
`warn`, `error`). Hides `withContextID` (no external callers).
- **`internal/sync`** — Converts `Map[K, V]` from a concrete struct to
an interface with a `NewMap` constructor for testability.
- **`internal/signal`** — Adds `signalNotify` / `osExit` indirections so
`InstallSignals` and `InstallForceQuit` can be exercised without real
OS signals or process termination.
- **`internal/utils`** — Drops deprecated `io/ioutil` and the stdlib
`errors` alias (the internal `errors` package re-exports what's
needed).
- **`internal/version`** — No code changes; fully covered by new tests.
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
175 lines
4.3 KiB
Go
175 lines
4.3 KiB
Go
// Copyright (c) 2026 Winlin
|
|
//
|
|
// SPDX-License-Identifier: MIT
|
|
package logger
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"io"
|
|
"log/slog"
|
|
"os"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func decodeLine(t *testing.T, line []byte) map[string]any {
|
|
t.Helper()
|
|
var m map[string]any
|
|
if err := json.Unmarshal(bytes.TrimSpace(line), &m); err != nil {
|
|
t.Fatalf("decode %q: %v", line, err)
|
|
}
|
|
return m
|
|
}
|
|
|
|
func bufLoggerPlus(w io.Writer, level slog.Level) *loggerPlus {
|
|
return newLoggerPlus(func(l *loggerPlus) {
|
|
l.logger = newJSONLogger(w)
|
|
l.level = level
|
|
})
|
|
}
|
|
|
|
func TestLevelLabel_Known(t *testing.T) {
|
|
cases := map[slog.Level]string{
|
|
levelVerb: "verb",
|
|
levelDebug: "debug",
|
|
levelWarn: "warn",
|
|
levelError: "error",
|
|
}
|
|
for lvl, want := range cases {
|
|
if got := levelLabel(lvl); got != want {
|
|
t.Errorf("levelLabel(%v) = %q, want %q", lvl, got, want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestLevelLabel_UnknownFallsBackToString(t *testing.T) {
|
|
got := levelLabel(slog.Level(99))
|
|
if got == "" {
|
|
t.Fatalf("levelLabel(99) returned empty")
|
|
}
|
|
if got == "verb" || got == "debug" || got == "warn" || got == "error" {
|
|
t.Fatalf("levelLabel(99) = %q, want slog.Level.String() form", got)
|
|
}
|
|
}
|
|
|
|
func TestPrintf_EmitsAllFields(t *testing.T) {
|
|
var buf bytes.Buffer
|
|
lp := bufLoggerPlus(&buf, levelDebug)
|
|
ctx := withContextID(context.Background(), "abc1234")
|
|
lp.Printf(ctx, "hello %s %d", "world", 42)
|
|
|
|
m := decodeLine(t, buf.Bytes())
|
|
if m["level"] != "debug" {
|
|
t.Errorf("level = %v, want debug", m["level"])
|
|
}
|
|
if m["msg"] != "hello world 42" {
|
|
t.Errorf("msg = %v, want %q", m["msg"], "hello world 42")
|
|
}
|
|
if m["cid"] != "abc1234" {
|
|
t.Errorf("cid = %v, want abc1234", m["cid"])
|
|
}
|
|
pid, ok := m["pid"].(float64)
|
|
if !ok || int(pid) != os.Getpid() {
|
|
t.Errorf("pid = %v, want %d", m["pid"], os.Getpid())
|
|
}
|
|
ts, ok := m["time"].(string)
|
|
if !ok || !strings.HasSuffix(ts, "Z") {
|
|
t.Errorf("time = %v, want UTC suffix Z", m["time"])
|
|
}
|
|
if _, err := time.Parse(time.RFC3339Nano, ts); err != nil {
|
|
t.Errorf("time %q not RFC3339Nano: %v", ts, err)
|
|
}
|
|
}
|
|
|
|
func TestPrintf_OmitsCIDWhenAbsent(t *testing.T) {
|
|
var buf bytes.Buffer
|
|
bufLoggerPlus(&buf, levelWarn).Printf(context.Background(), "no cid here")
|
|
|
|
m := decodeLine(t, buf.Bytes())
|
|
if v, present := m["cid"]; present {
|
|
t.Errorf("cid should be absent, got %v", v)
|
|
}
|
|
if m["level"] != "warn" {
|
|
t.Errorf("level = %v, want warn", m["level"])
|
|
}
|
|
}
|
|
|
|
func TestPrintf_AllLevelsMapToLabel(t *testing.T) {
|
|
cases := []struct {
|
|
level slog.Level
|
|
label string
|
|
}{
|
|
{levelVerb, "verb"},
|
|
{levelDebug, "debug"},
|
|
{levelWarn, "warn"},
|
|
{levelError, "error"},
|
|
}
|
|
for _, tc := range cases {
|
|
var buf bytes.Buffer
|
|
bufLoggerPlus(&buf, tc.level).Printf(context.Background(), "hi")
|
|
m := decodeLine(t, buf.Bytes())
|
|
if m["level"] != tc.label {
|
|
t.Errorf("level(%v) rendered as %v, want %q", tc.level, m["level"], tc.label)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestNewJSONLogger_GroupedAttrsPassThrough(t *testing.T) {
|
|
var buf bytes.Buffer
|
|
lg := newJSONLogger(&buf)
|
|
lg.LogAttrs(context.Background(), levelDebug, "grouped",
|
|
slog.Group("meta", slog.String("inner", "v")))
|
|
|
|
m := decodeLine(t, buf.Bytes())
|
|
meta, ok := m["meta"].(map[string]any)
|
|
if !ok {
|
|
t.Fatalf("meta not an object: %v", m["meta"])
|
|
}
|
|
if meta["inner"] != "v" {
|
|
t.Errorf("meta.inner = %v, want v", meta["inner"])
|
|
}
|
|
}
|
|
|
|
func TestPackageWrappers_RouteToRightLogger(t *testing.T) {
|
|
origV, origD, origW, origE := verboseLogger, debugLogger, warnLogger, errorLogger
|
|
t.Cleanup(func() {
|
|
verboseLogger, debugLogger, warnLogger, errorLogger = origV, origD, origW, origE
|
|
})
|
|
|
|
vBuf, dBuf, wBuf, eBuf := &bytes.Buffer{}, &bytes.Buffer{}, &bytes.Buffer{}, &bytes.Buffer{}
|
|
verboseLogger = bufLoggerPlus(vBuf, levelVerb)
|
|
debugLogger = bufLoggerPlus(dBuf, levelDebug)
|
|
warnLogger = bufLoggerPlus(wBuf, levelWarn)
|
|
errorLogger = bufLoggerPlus(eBuf, levelError)
|
|
|
|
ctx := context.Background()
|
|
Vf(ctx, "v=%d", 1)
|
|
Df(ctx, "d=%d", 2)
|
|
Wf(ctx, "w=%d", 3)
|
|
Ef(ctx, "e=%d", 4)
|
|
|
|
checks := []struct {
|
|
name string
|
|
buf *bytes.Buffer
|
|
label string
|
|
msg string
|
|
}{
|
|
{"Vf", vBuf, "verb", "v=1"},
|
|
{"Df", dBuf, "debug", "d=2"},
|
|
{"Wf", wBuf, "warn", "w=3"},
|
|
{"Ef", eBuf, "error", "e=4"},
|
|
}
|
|
for _, c := range checks {
|
|
m := decodeLine(t, c.buf.Bytes())
|
|
if m["level"] != c.label {
|
|
t.Errorf("%s level = %v, want %v", c.name, m["level"], c.label)
|
|
}
|
|
if m["msg"] != c.msg {
|
|
t.Errorf("%s msg = %v, want %v", c.name, m["msg"], c.msg)
|
|
}
|
|
}
|
|
}
|