• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright 2022 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5package slog
6
7import (
8	"slices"
9	"strconv"
10	"strings"
11	"testing"
12	"time"
13)
14
15func TestRecordAttrs(t *testing.T) {
16	as := []Attr{Int("k1", 1), String("k2", "foo"), Int("k3", 3),
17		Int64("k4", -1), Float64("f", 3.1), Uint64("u", 999)}
18	r := newRecordWithAttrs(as)
19	if g, w := r.NumAttrs(), len(as); g != w {
20		t.Errorf("NumAttrs: got %d, want %d", g, w)
21	}
22	if got := attrsSlice(r); !attrsEqual(got, as) {
23		t.Errorf("got %v, want %v", got, as)
24	}
25
26	// Early return.
27	// Hit both loops in Record.Attrs: front and back.
28	for _, stop := range []int{2, 6} {
29		var got []Attr
30		r.Attrs(func(a Attr) bool {
31			got = append(got, a)
32			return len(got) < stop
33		})
34		want := as[:stop]
35		if !attrsEqual(got, want) {
36			t.Errorf("got %v, want %v", got, want)
37		}
38	}
39}
40
41func TestRecordSource(t *testing.T) {
42	// Zero call depth => empty *Source.
43	for _, test := range []struct {
44		depth            int
45		wantFunction     string
46		wantFile         string
47		wantLinePositive bool
48	}{
49		{0, "", "", false},
50		{-16, "", "", false},
51		{1, "log/slog.TestRecordSource", "record_test.go", true}, // 1: caller of NewRecord
52		{2, "testing.tRunner", "testing.go", true},
53	} {
54		var pc uintptr
55		if test.depth > 0 {
56			pc = callerPC(test.depth + 1)
57		}
58		r := NewRecord(time.Time{}, 0, "", pc)
59		got := r.source()
60		if i := strings.LastIndexByte(got.File, '/'); i >= 0 {
61			got.File = got.File[i+1:]
62		}
63		if got.Function != test.wantFunction || got.File != test.wantFile || (got.Line > 0) != test.wantLinePositive {
64			t.Errorf("depth %d: got (%q, %q, %d), want (%q, %q, %t)",
65				test.depth,
66				got.Function, got.File, got.Line,
67				test.wantFunction, test.wantFile, test.wantLinePositive)
68		}
69	}
70}
71
72func TestAliasingAndClone(t *testing.T) {
73	intAttrs := func(from, to int) []Attr {
74		var as []Attr
75		for i := from; i < to; i++ {
76			as = append(as, Int("k", i))
77		}
78		return as
79	}
80
81	check := func(r Record, want []Attr) {
82		t.Helper()
83		got := attrsSlice(r)
84		if !attrsEqual(got, want) {
85			t.Errorf("got %v, want %v", got, want)
86		}
87	}
88
89	// Create a record whose Attrs overflow the inline array,
90	// creating a slice in r.back.
91	r1 := NewRecord(time.Time{}, 0, "", 0)
92	r1.AddAttrs(intAttrs(0, nAttrsInline+1)...)
93	// Ensure that r1.back's capacity exceeds its length.
94	b := make([]Attr, len(r1.back), len(r1.back)+1)
95	copy(b, r1.back)
96	r1.back = b
97	// Make a copy that shares state.
98	r2 := r1
99	// Adding to both should insert a special Attr in the second.
100	r1AttrsBefore := attrsSlice(r1)
101	r1.AddAttrs(Int("p", 0))
102	r2.AddAttrs(Int("p", 1))
103	check(r1, append(slices.Clip(r1AttrsBefore), Int("p", 0)))
104	r1Attrs := attrsSlice(r1)
105	check(r2, append(slices.Clip(r1AttrsBefore),
106		String("!BUG", "AddAttrs unsafely called on copy of Record made without using Record.Clone"), Int("p", 1)))
107
108	// Adding to a clone is fine.
109	r2 = r1.Clone()
110	check(r2, r1Attrs)
111	r2.AddAttrs(Int("p", 2))
112	check(r1, r1Attrs) // r1 is unchanged
113	check(r2, append(slices.Clip(r1Attrs), Int("p", 2)))
114}
115
116func newRecordWithAttrs(as []Attr) Record {
117	r := NewRecord(time.Now(), LevelInfo, "", 0)
118	r.AddAttrs(as...)
119	return r
120}
121
122func attrsSlice(r Record) []Attr {
123	s := make([]Attr, 0, r.NumAttrs())
124	r.Attrs(func(a Attr) bool { s = append(s, a); return true })
125	return s
126}
127
128func attrsEqual(as1, as2 []Attr) bool {
129	return slices.EqualFunc(as1, as2, Attr.Equal)
130}
131
132// Currently, pc(2) takes over 400ns, which is too expensive
133// to call it for every log message.
134func BenchmarkPC(b *testing.B) {
135	for depth := 0; depth < 5; depth++ {
136		b.Run(strconv.Itoa(depth), func(b *testing.B) {
137			b.ReportAllocs()
138			var x uintptr
139			for i := 0; i < b.N; i++ {
140				x = callerPC(depth)
141			}
142			_ = x
143		})
144	}
145}
146
147func BenchmarkRecord(b *testing.B) {
148	const nAttrs = nAttrsInline * 10
149	var a Attr
150
151	for i := 0; i < b.N; i++ {
152		r := NewRecord(time.Time{}, LevelInfo, "", 0)
153		for j := 0; j < nAttrs; j++ {
154			r.AddAttrs(Int("k", j))
155		}
156		r.Attrs(func(b Attr) bool { a = b; return true })
157	}
158	_ = a
159}
160