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