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 5package slog 6 7import ( 8 "bytes" 9 "context" 10 "errors" 11 "fmt" 12 "internal/testenv" 13 "io" 14 "strings" 15 "testing" 16 "time" 17) 18 19var testTime = time.Date(2000, 1, 2, 3, 4, 5, 0, time.UTC) 20 21func 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 105type name struct { 106 First, Last string 107} 108 109func (n name) String() string { return n.Last + ", " + n.First } 110 111// for testing TextMarshaler 112type text struct { 113 s string 114} 115 116func (t text) String() string { return t.s } // should be ignored 117 118func (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 125func 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 142func 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 156func 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