]> Cypherpunks.ru repositories - gostls13.git/blob - src/log/slog/record_test.go
c40c6183fad8d804cf3fb5c908a0259468e2e3b9
[gostls13.git] / src / log / slog / record_test.go
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
5 package slog
6
7 import (
8         "log/slog/internal/buffer"
9         "slices"
10         "strconv"
11         "strings"
12         "testing"
13         "time"
14 )
15
16 func TestRecordAttrs(t *testing.T) {
17         as := []Attr{Int("k1", 1), String("k2", "foo"), Int("k3", 3),
18                 Int64("k4", -1), Float64("f", 3.1), Uint64("u", 999)}
19         r := newRecordWithAttrs(as)
20         if g, w := r.NumAttrs(), len(as); g != w {
21                 t.Errorf("NumAttrs: got %d, want %d", g, w)
22         }
23         if got := attrsSlice(r); !attrsEqual(got, as) {
24                 t.Errorf("got %v, want %v", got, as)
25         }
26
27         // Early return.
28         var got []Attr
29         r.Attrs(func(a Attr) bool {
30                 got = append(got, a)
31                 return len(got) < 2
32         })
33         want := as[:2]
34         if !attrsEqual(got, want) {
35                 t.Errorf("got %v, want %v", got, want)
36         }
37 }
38
39 func TestRecordSourceLine(t *testing.T) {
40         // Zero call depth => empty file/line
41         for _, test := range []struct {
42                 depth            int
43                 wantFile         string
44                 wantLinePositive bool
45         }{
46                 {0, "", false},
47                 {-16, "", false},
48                 {1, "record_test.go", true}, // 1: caller of NewRecord
49                 {2, "testing.go", true},
50         } {
51                 var pc uintptr
52                 if test.depth > 0 {
53                         pc = callerPC(test.depth + 1)
54                 }
55                 r := NewRecord(time.Time{}, 0, "", pc)
56                 gotFile, gotLine := sourceLine(r)
57                 if i := strings.LastIndexByte(gotFile, '/'); i >= 0 {
58                         gotFile = gotFile[i+1:]
59                 }
60                 if gotFile != test.wantFile || (gotLine > 0) != test.wantLinePositive {
61                         t.Errorf("depth %d: got (%q, %d), want (%q, %t)",
62                                 test.depth, gotFile, gotLine, test.wantFile, test.wantLinePositive)
63                 }
64         }
65 }
66
67 func TestAliasingAndClone(t *testing.T) {
68         intAttrs := func(from, to int) []Attr {
69                 var as []Attr
70                 for i := from; i < to; i++ {
71                         as = append(as, Int("k", i))
72                 }
73                 return as
74         }
75
76         check := func(r Record, want []Attr) {
77                 t.Helper()
78                 got := attrsSlice(r)
79                 if !attrsEqual(got, want) {
80                         t.Errorf("got %v, want %v", got, want)
81                 }
82         }
83
84         // Create a record whose Attrs overflow the inline array,
85         // creating a slice in r.back.
86         r1 := NewRecord(time.Time{}, 0, "", 0)
87         r1.AddAttrs(intAttrs(0, nAttrsInline+1)...)
88         // Ensure that r1.back's capacity exceeds its length.
89         b := make([]Attr, len(r1.back), len(r1.back)+1)
90         copy(b, r1.back)
91         r1.back = b
92         // Make a copy that shares state.
93         r2 := r1
94         // Adding to both should panic.
95         r1.AddAttrs(Int("p", 0))
96         if !panics(func() { r2.AddAttrs(Int("p", 1)) }) {
97                 t.Error("expected panic")
98         }
99         r1Attrs := attrsSlice(r1)
100         // Adding to a clone is fine.
101         r2 = r1.Clone()
102         check(r2, r1Attrs)
103         r2.AddAttrs(Int("p", 2))
104         check(r1, r1Attrs) // r1 is unchanged
105         check(r2, append(slices.Clip(r1Attrs), Int("p", 2)))
106 }
107
108 func newRecordWithAttrs(as []Attr) Record {
109         r := NewRecord(time.Now(), LevelInfo, "", 0)
110         r.AddAttrs(as...)
111         return r
112 }
113
114 func attrsSlice(r Record) []Attr {
115         s := make([]Attr, 0, r.NumAttrs())
116         r.Attrs(func(a Attr) bool { s = append(s, a); return true })
117         return s
118 }
119
120 func attrsEqual(as1, as2 []Attr) bool {
121         return slices.EqualFunc(as1, as2, Attr.Equal)
122 }
123
124 // Currently, pc(2) takes over 400ns, which is too expensive
125 // to call it for every log message.
126 func BenchmarkPC(b *testing.B) {
127         for depth := 0; depth < 5; depth++ {
128                 b.Run(strconv.Itoa(depth), func(b *testing.B) {
129                         b.ReportAllocs()
130                         var x uintptr
131                         for i := 0; i < b.N; i++ {
132                                 x = callerPC(depth)
133                         }
134                         _ = x
135                 })
136         }
137 }
138
139 func BenchmarkSourceLine(b *testing.B) {
140         r := NewRecord(time.Now(), LevelInfo, "", 5)
141         b.Run("alone", func(b *testing.B) {
142                 for i := 0; i < b.N; i++ {
143                         file, line := sourceLine(r)
144                         _ = file
145                         _ = line
146                 }
147         })
148         b.Run("stringifying", func(b *testing.B) {
149                 for i := 0; i < b.N; i++ {
150                         file, line := sourceLine(r)
151                         buf := buffer.New()
152                         buf.WriteString(file)
153                         buf.WriteByte(':')
154                         buf.WritePosInt(line)
155                         s := buf.String()
156                         buf.Free()
157                         _ = s
158                 }
159         })
160 }
161
162 func BenchmarkRecord(b *testing.B) {
163         const nAttrs = nAttrsInline * 10
164         var a Attr
165
166         for i := 0; i < b.N; i++ {
167                 r := NewRecord(time.Time{}, LevelInfo, "", 0)
168                 for j := 0; j < nAttrs; j++ {
169                         r.AddAttrs(Int("k", j))
170                 }
171                 r.Attrs(func(b Attr) bool { a = b; return true })
172         }
173         _ = a
174 }