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