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