]> Cypherpunks.ru repositories - gostls13.git/blob - src/log/slog/text_handler_test.go
log/slog: initial commit
[gostls13.git] / src / log / slog / text_handler_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         "bytes"
9         "context"
10         "errors"
11         "fmt"
12         "internal/testenv"
13         "io"
14         "regexp"
15         "strings"
16         "testing"
17         "time"
18 )
19
20 var testTime = time.Date(2000, 1, 2, 3, 4, 5, 0, time.UTC)
21
22 func TestTextHandler(t *testing.T) {
23         for _, test := range []struct {
24                 name             string
25                 attr             Attr
26                 wantKey, wantVal string
27         }{
28                 {
29                         "unquoted",
30                         Int("a", 1),
31                         "a", "1",
32                 },
33                 {
34                         "quoted",
35                         String("x = y", `qu"o`),
36                         `"x = y"`, `"qu\"o"`,
37                 },
38                 {
39                         "String method",
40                         Any("name", name{"Ren", "Hoek"}),
41                         `name`, `"Hoek, Ren"`,
42                 },
43                 {
44                         "struct",
45                         Any("x", &struct{ A, b int }{A: 1, b: 2}),
46                         `x`, `"&{A:1 b:2}"`,
47                 },
48                 {
49                         "TextMarshaler",
50                         Any("t", text{"abc"}),
51                         `t`, `"text{\"abc\"}"`,
52                 },
53                 {
54                         "TextMarshaler error",
55                         Any("t", text{""}),
56                         `t`, `"!ERROR:text: empty string"`,
57                 },
58                 {
59                         "nil value",
60                         Any("a", nil),
61                         `a`, `<nil>`,
62                 },
63         } {
64                 t.Run(test.name, func(t *testing.T) {
65                         for _, opts := range []struct {
66                                 name       string
67                                 opts       HandlerOptions
68                                 wantPrefix string
69                                 modKey     func(string) string
70                         }{
71                                 {
72                                         "none",
73                                         HandlerOptions{},
74                                         `time=2000-01-02T03:04:05.000Z level=INFO msg="a message"`,
75                                         func(s string) string { return s },
76                                 },
77                                 {
78                                         "replace",
79                                         HandlerOptions{ReplaceAttr: upperCaseKey},
80                                         `TIME=2000-01-02T03:04:05.000Z LEVEL=INFO MSG="a message"`,
81                                         strings.ToUpper,
82                                 },
83                         } {
84                                 t.Run(opts.name, func(t *testing.T) {
85                                         var buf bytes.Buffer
86                                         h := opts.opts.NewTextHandler(&buf)
87                                         r := NewRecord(testTime, LevelInfo, "a message", 0)
88                                         r.AddAttrs(test.attr)
89                                         if err := h.Handle(context.Background(), r); err != nil {
90                                                 t.Fatal(err)
91                                         }
92                                         got := buf.String()
93                                         // Remove final newline.
94                                         got = got[:len(got)-1]
95                                         want := opts.wantPrefix + " " + opts.modKey(test.wantKey) + "=" + test.wantVal
96                                         if got != want {
97                                                 t.Errorf("\ngot  %s\nwant %s", got, want)
98                                         }
99                                 })
100                         }
101                 })
102         }
103 }
104
105 // for testing fmt.Sprint
106 type name struct {
107         First, Last string
108 }
109
110 func (n name) String() string { return n.Last + ", " + n.First }
111
112 // for testing TextMarshaler
113 type text struct {
114         s string
115 }
116
117 func (t text) String() string { return t.s } // should be ignored
118
119 func (t text) MarshalText() ([]byte, error) {
120         if t.s == "" {
121                 return nil, errors.New("text: empty string")
122         }
123         return []byte(fmt.Sprintf("text{%q}", t.s)), nil
124 }
125
126 func TestTextHandlerSource(t *testing.T) {
127         var buf bytes.Buffer
128         h := HandlerOptions{AddSource: true}.NewTextHandler(&buf)
129         r := NewRecord(testTime, LevelInfo, "m", callerPC(2))
130         if err := h.Handle(context.Background(), r); err != nil {
131                 t.Fatal(err)
132         }
133         if got := buf.String(); !sourceRegexp.MatchString(got) {
134                 t.Errorf("got\n%q\nwanted to match %s", got, sourceRegexp)
135         }
136 }
137
138 var sourceRegexp = regexp.MustCompile(`source="?([A-Z]:)?[^:]+text_handler_test\.go:\d+"? msg`)
139
140 func TestSourceRegexp(t *testing.T) {
141         for _, s := range []string{
142                 `source=/tmp/path/to/text_handler_test.go:23 msg=m`,
143                 `source=C:\windows\path\text_handler_test.go:23 msg=m"`,
144                 `source="/tmp/tmp.XcGZ9cG9Xb/with spaces/exp/slog/text_handler_test.go:95" msg=m`,
145         } {
146                 if !sourceRegexp.MatchString(s) {
147                         t.Errorf("failed to match %s", s)
148                 }
149         }
150 }
151
152 func TestTextHandlerPreformatted(t *testing.T) {
153         var buf bytes.Buffer
154         var h Handler = NewTextHandler(&buf)
155         h = h.WithAttrs([]Attr{Duration("dur", time.Minute), Bool("b", true)})
156         // Also test omitting time.
157         r := NewRecord(time.Time{}, 0 /* 0 Level is INFO */, "m", 0)
158         r.AddAttrs(Int("a", 1))
159         if err := h.Handle(context.Background(), r); err != nil {
160                 t.Fatal(err)
161         }
162         got := strings.TrimSuffix(buf.String(), "\n")
163         want := `level=INFO msg=m dur=1m0s b=true a=1`
164         if got != want {
165                 t.Errorf("got %s, want %s", got, want)
166         }
167 }
168
169 func TestTextHandlerAlloc(t *testing.T) {
170         testenv.SkipIfOptimizationOff(t)
171         r := NewRecord(time.Now(), LevelInfo, "msg", 0)
172         for i := 0; i < 10; i++ {
173                 r.AddAttrs(Int("x = y", i))
174         }
175         var h Handler = NewTextHandler(io.Discard)
176         wantAllocs(t, 0, func() { h.Handle(context.Background(), r) })
177
178         h = h.WithGroup("s")
179         r.AddAttrs(Group("g", Int("a", 1)))
180         wantAllocs(t, 0, func() { h.Handle(context.Background(), r) })
181 }
182
183 func TestNeedsQuoting(t *testing.T) {
184         for _, test := range []struct {
185                 in   string
186                 want bool
187         }{
188                 {"", false},
189                 {"ab", false},
190                 {"a=b", true},
191                 {`"ab"`, true},
192                 {"\a\b", true},
193                 {"a\tb", true},
194                 {"µåπ", false},
195         } {
196                 got := needsQuoting(test.in)
197                 if got != test.want {
198                         t.Errorf("%q: got %t, want %t", test.in, got, test.want)
199                 }
200         }
201 }