Test / test (push) Canceled after 0s
Test / fuzz (./compat, FuzzParseFormat) (push) Canceled after 0s
Test / fuzz (./formatter, FuzzFormatJSON) (push) Canceled after 0s
Test / fuzz (./formatter, FuzzFormatTextPolicy) (push) Canceled after 0s
Test / fuzz (./sanitizer, FuzzSanitize) (push) Canceled after 0s
Test / fuzz (./sanitizer, FuzzSerializerJSON) (push) Canceled after 0s
119 lines
4.3 KiB
Go
119 lines
4.3 KiB
Go
package formatter
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"math"
|
|
"strings"
|
|
"testing"
|
|
"unicode"
|
|
"unicode/utf8"
|
|
|
|
"github.com/lixenwraith/log/sanitizer"
|
|
)
|
|
|
|
func FuzzFormatJSON(f *testing.F) {
|
|
for _, s := range []string{"", "safe", "\xff\n\"\\", "\u0085\x1b[0m", "世界"} {
|
|
f.Add(s, s, s, uint64(0), uint8(0))
|
|
}
|
|
f.Fuzz(func(t *testing.T, value, key, layout string, bits uint64, mode uint8) {
|
|
policies := []sanitizer.PolicyPreset{sanitizer.PolicyRaw, sanitizer.PolicyTxt, sanitizer.PolicyJSON, sanitizer.PolicyShell}
|
|
fm := New(sanitizer.New().Policy(policies[int(mode)%4])).Type("json").TimestampFormat(layout).ContextKeys(key)
|
|
flags := FlagDefault
|
|
args := []any{value, []byte(value), math.Float64frombits(bits), uint32(bits)}
|
|
switch mode % 3 {
|
|
case 1:
|
|
flags |= FlagKV
|
|
args = []any{key, value, "number", math.Float64frombits(bits)}
|
|
case 2:
|
|
flags |= FlagStructuredJSON
|
|
args = []any{value, map[string]any{key: []any{value, math.Float64frombits(bits)}}}
|
|
}
|
|
prefix := []byte("prefix:")
|
|
out := fm.AppendFormatCtx(bytes.Clone(prefix), Context{Tag: value}, flags, testStamp, int64(bits), value, args)
|
|
if !bytes.HasPrefix(out, prefix) {
|
|
t.Fatal("prefix changed")
|
|
}
|
|
data := out[len(prefix):]
|
|
if !utf8.Valid(data) || !json.Valid(data) || bytes.Count(data, []byte{'\n'}) != 1 || data[len(data)-1] != '\n' {
|
|
t.Fatalf("invalid JSON record %q", data)
|
|
}
|
|
if second := fm.AppendFormatCtx(nil, Context{Tag: value}, flags, testStamp, int64(bits), value, args); !bytes.Equal(data, second) {
|
|
t.Fatal("non-deterministic formatter")
|
|
}
|
|
})
|
|
}
|
|
|
|
func FuzzFormatTextPolicy(f *testing.F) {
|
|
for _, s := range []string{"", "\n\r\x1b[31m", "\xff<00>\u0085", "a=b \"c\""} {
|
|
f.Add(s, s, s)
|
|
}
|
|
f.Fuzz(func(t *testing.T, value, key, layout string) {
|
|
fm := New(sanitizer.New().Policy(sanitizer.PolicyTxt)).Type("txt").TimestampFormat(layout).ContextKeys(key, key)
|
|
out := fm.AppendFormatCtx(nil, Context{Tag: value}, FlagDefault|FlagKV, testStamp, 0, value, []any{key, value})
|
|
if !bytes.HasSuffix(out, []byte{'\n'}) {
|
|
t.Fatal("missing terminator")
|
|
}
|
|
for _, r := range string(out[:len(out)-1]) {
|
|
if unicode.IsControl(r) || !unicode.IsPrint(r) {
|
|
t.Fatalf("unsafe rune %U in %q", r, out)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
type panickingStringer struct{}
|
|
|
|
func (panickingStringer) String() string { panic("broken Stringer") }
|
|
|
|
func TestFormatHardening(t *testing.T) {
|
|
t.Run("marshal error", func(t *testing.T) {
|
|
cycle := map[string]any{}
|
|
cycle["self"] = cycle
|
|
for _, v := range []any{math.NaN(), make(chan int), cycle, json.RawMessage(`invalid`)} {
|
|
f := New().Type("json")
|
|
record := unmarshalRecord(t, f.Format(FlagStructuredJSON, testStamp, 0, "", []any{"msg", map[string]any{"bad": v}}))
|
|
if _, ok := record["fields"].(map[string]any)["_marshal_error"].(string); !ok {
|
|
t.Fatalf("missing marshal error: %v", record)
|
|
}
|
|
}
|
|
})
|
|
t.Run("nested sanitization", func(t *testing.T) {
|
|
f := New(sanitizer.New().Policy(sanitizer.PolicyTxt)).Type("json")
|
|
record := unmarshalRecord(t, f.Format(FlagStructuredJSON, testStamp, 0, "", []any{"msg", map[string]any{"k\n": []any{map[string]any{"v": "\x1b[31m"}, 42, true}}}))
|
|
nested := record["fields"].(map[string]any)["k<0a>"].([]any)
|
|
eq(t, nested[0].(map[string]any)["v"], any("<1b>[31m"), "nested content policy")
|
|
eq(t, nested[1], any(float64(42)), "number preserved")
|
|
})
|
|
t.Run("explicit suppression wins", func(t *testing.T) {
|
|
f := New().Type("json")
|
|
record := unmarshalRecord(t, f.FormatWithOptions("json", FlagDefault|FlagNoLevel|FlagNoTimestamp, testStamp, 0, "", nil))
|
|
eq(t, len(record), 0, "suppressed metadata")
|
|
})
|
|
t.Run("bad stringer is printable", func(t *testing.T) {
|
|
for _, flags := range []int64{0, FlagRaw} {
|
|
out := New().Format(flags, testStamp, 0, "", []any{panickingStringer{}})
|
|
if !strings.Contains(string(out), "PANIC") {
|
|
t.Fatalf("missing fmt panic marker: %q", out)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestCyclicComplexValues(t *testing.T) {
|
|
cycle := map[string]any{}
|
|
cycle["self"] = cycle
|
|
slice := make([]any, 1)
|
|
slice[0] = slice
|
|
for _, value := range []any{cycle, slice} {
|
|
for _, format := range []string{"txt", "json", "raw"} {
|
|
for _, flags := range []int64{0, FlagRaw} {
|
|
output := New().Type(format).Format(flags, testStamp, 0, "", []any{value})
|
|
if !strings.Contains(string(output), "cyclic or oversized value") {
|
|
t.Fatalf("%s: missing cycle marker: %q", format, output)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|