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 "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
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
105 type name struct {
106 First, Last string
107 }
108
109 func (n name) String() string { return n.Last + ", " + n.First }
110
111
112 type text struct {
113 s string
114 }
115
116 func (t text) String() string { return t.s }
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
130 r := NewRecord(time.Time{}, 0 , "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 {"a b", true},
169 {"badutf8\xF6", true},
170 } {
171 got := needsQuoting(test.in)
172 if got != test.want {
173 t.Errorf("%q: got %t, want %t", test.in, got, test.want)
174 }
175 }
176 }
177
View as plain text