1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154
|
// 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 (
"strconv"
"strings"
"testing"
"time"
"golang.org/x/exp/slices"
)
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.
var got []Attr
r.Attrs(func(a Attr) bool {
got = append(got, a)
return len(got) < 2
})
want := as[:2]
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, "golang.org/x/exp/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 panic.
r1.AddAttrs(Int("p", 0))
if !panics(func() { r2.AddAttrs(Int("p", 1)) }) {
t.Error("expected panic")
}
r1Attrs := attrsSlice(r1)
// 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
}
|