golang.org/x/exp@v0.0.0-20240506185415-9bf2ced13842/slog/text_handler_test.go (about) 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 "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 // Remove final newline. 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 // for testing fmt.Sprint 104 type name struct { 105 First, Last string 106 } 107 108 func (n name) String() string { return n.Last + ", " + n.First } 109 110 // for testing TextMarshaler 111 type text struct { 112 s string 113 } 114 115 func (t text) String() string { return t.s } // should be ignored 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 // Also test omitting time. 129 r := NewRecord(time.Time{}, 0 /* 0 Level is INFO */, "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 }