mirror of https://go.googlesource.com/go
160 lines
4.0 KiB
Go
160 lines
4.0 KiB
Go
// Copyright 2022 The Go Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package slog
|
|
|
|
import (
|
|
"slices"
|
|
"strconv"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func TestRecordAttrs(t *testing.T) {
|
|
as := []Attr{Int("k1", 1), String("k2", "foo"), Int("k3", 3),
|
|
Int64("k4", -1), Float64("f", 3.1), Uint64("u", 999)}
|
|
r := newRecordWithAttrs(as)
|
|
if g, w := r.NumAttrs(), len(as); g != w {
|
|
t.Errorf("NumAttrs: got %d, want %d", g, w)
|
|
}
|
|
if got := attrsSlice(r); !attrsEqual(got, as) {
|
|
t.Errorf("got %v, want %v", got, as)
|
|
}
|
|
|
|
// Early return.
|
|
// Hit both loops in Record.Attrs: front and back.
|
|
for _, stop := range []int{2, 6} {
|
|
var got []Attr
|
|
r.Attrs(func(a Attr) bool {
|
|
got = append(got, a)
|
|
return len(got) < stop
|
|
})
|
|
want := as[:stop]
|
|
if !attrsEqual(got, want) {
|
|
t.Errorf("got %v, want %v", got, want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestRecordSource(t *testing.T) {
|
|
// Zero call depth => empty *Source.
|
|
for _, test := range []struct {
|
|
depth int
|
|
wantFunction string
|
|
wantFile string
|
|
wantLinePositive bool
|
|
}{
|
|
{0, "", "", false},
|
|
{-16, "", "", false},
|
|
{1, "log/slog.TestRecordSource", "record_test.go", true}, // 1: caller of NewRecord
|
|
{2, "testing.tRunner", "testing.go", true},
|
|
} {
|
|
var pc uintptr
|
|
if test.depth > 0 {
|
|
pc = callerPC(test.depth + 1)
|
|
}
|
|
r := NewRecord(time.Time{}, 0, "", pc)
|
|
got := r.source()
|
|
if i := strings.LastIndexByte(got.File, '/'); i >= 0 {
|
|
got.File = got.File[i+1:]
|
|
}
|
|
if got.Function != test.wantFunction || got.File != test.wantFile || (got.Line > 0) != test.wantLinePositive {
|
|
t.Errorf("depth %d: got (%q, %q, %d), want (%q, %q, %t)",
|
|
test.depth,
|
|
got.Function, got.File, got.Line,
|
|
test.wantFunction, test.wantFile, test.wantLinePositive)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestAliasingAndClone(t *testing.T) {
|
|
intAttrs := func(from, to int) []Attr {
|
|
var as []Attr
|
|
for i := from; i < to; i++ {
|
|
as = append(as, Int("k", i))
|
|
}
|
|
return as
|
|
}
|
|
|
|
check := func(r Record, want []Attr) {
|
|
t.Helper()
|
|
got := attrsSlice(r)
|
|
if !attrsEqual(got, want) {
|
|
t.Errorf("got %v, want %v", got, want)
|
|
}
|
|
}
|
|
|
|
// Create a record whose Attrs overflow the inline array,
|
|
// creating a slice in r.back.
|
|
r1 := NewRecord(time.Time{}, 0, "", 0)
|
|
r1.AddAttrs(intAttrs(0, nAttrsInline+1)...)
|
|
// Ensure that r1.back's capacity exceeds its length.
|
|
b := make([]Attr, len(r1.back), len(r1.back)+1)
|
|
copy(b, r1.back)
|
|
r1.back = b
|
|
// Make a copy that shares state.
|
|
r2 := r1
|
|
// Adding to both should insert a special Attr in the second.
|
|
r1AttrsBefore := attrsSlice(r1)
|
|
r1.AddAttrs(Int("p", 0))
|
|
r2.AddAttrs(Int("p", 1))
|
|
check(r1, append(slices.Clip(r1AttrsBefore), Int("p", 0)))
|
|
r1Attrs := attrsSlice(r1)
|
|
check(r2, append(slices.Clip(r1AttrsBefore),
|
|
String("!BUG", "AddAttrs unsafely called on copy of Record made without using Record.Clone"), Int("p", 1)))
|
|
|
|
// Adding to a clone is fine.
|
|
r2 = r1.Clone()
|
|
check(r2, r1Attrs)
|
|
r2.AddAttrs(Int("p", 2))
|
|
check(r1, r1Attrs) // r1 is unchanged
|
|
check(r2, append(slices.Clip(r1Attrs), Int("p", 2)))
|
|
}
|
|
|
|
func newRecordWithAttrs(as []Attr) Record {
|
|
r := NewRecord(time.Now(), LevelInfo, "", 0)
|
|
r.AddAttrs(as...)
|
|
return r
|
|
}
|
|
|
|
func attrsSlice(r Record) []Attr {
|
|
s := make([]Attr, 0, r.NumAttrs())
|
|
r.Attrs(func(a Attr) bool { s = append(s, a); return true })
|
|
return s
|
|
}
|
|
|
|
func attrsEqual(as1, as2 []Attr) bool {
|
|
return slices.EqualFunc(as1, as2, Attr.Equal)
|
|
}
|
|
|
|
// Currently, pc(2) takes over 400ns, which is too expensive
|
|
// to call it for every log message.
|
|
func BenchmarkPC(b *testing.B) {
|
|
for depth := 0; depth < 5; depth++ {
|
|
b.Run(strconv.Itoa(depth), func(b *testing.B) {
|
|
b.ReportAllocs()
|
|
var x uintptr
|
|
for i := 0; i < b.N; i++ {
|
|
x = callerPC(depth)
|
|
}
|
|
_ = x
|
|
})
|
|
}
|
|
}
|
|
|
|
func BenchmarkRecord(b *testing.B) {
|
|
const nAttrs = nAttrsInline * 10
|
|
var a Attr
|
|
|
|
for i := 0; i < b.N; i++ {
|
|
r := NewRecord(time.Time{}, LevelInfo, "", 0)
|
|
for j := 0; j < nAttrs; j++ {
|
|
r.AddAttrs(Int("k", j))
|
|
}
|
|
r.Attrs(func(b Attr) bool { a = b; return true })
|
|
}
|
|
_ = a
|
|
}
|