| // Copyright 2022 The Go Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style | |
| // license that can be found in the LICENSE file. | |
| package slog | |
| import ( | |
| "bytes" | |
| "context" | |
| "errors" | |
| "fmt" | |
| "io" | |
| "strings" | |
| "testing" | |
| "time" | |
| ) | |
| var testTime = time.Date(2000, 1, 2, 3, 4, 5, 0, time.UTC) | |
| func TestTextHandler(t *testing.T) { | |
| for _, test := range []struct { | |
| name string | |
| attr Attr | |
| wantKey, wantVal string | |
| }{ | |
| { | |
| "unquoted", | |
| Int("a", 1), | |
| "a", "1", | |
| }, | |
| { | |
| "quoted", | |
| String("x = y", `qu"o`), | |
| `"x = y"`, `"qu\"o"`, | |
| }, | |
| { | |
| "String method", | |
| Any("name", name{"Ren", "Hoek"}), | |
| `name`, `"Hoek, Ren"`, | |
| }, | |
| { | |
| "struct", | |
| Any("x", &struct{ A, b int }{A: 1, b: 2}), | |
| `x`, `"&{A:1 b:2}"`, | |
| }, | |
| { | |
| "TextMarshaler", | |
| Any("t", text{"abc"}), | |
| `t`, `"text{\"abc\"}"`, | |
| }, | |
| { | |
| "TextMarshaler error", | |
| Any("t", text{""}), | |
| `t`, `"!ERROR:text: empty string"`, | |
| }, | |
| { | |
| "nil value", | |
| Any("a", nil), | |
| `a`, `<nil>`, | |
| }, | |
| } { | |
| t.Run(test.name, func(t *testing.T) { | |
| for _, opts := range []struct { | |
| name string | |
| opts HandlerOptions | |
| wantPrefix string | |
| modKey func(string) string | |
| }{ | |
| { | |
| "none", | |
| HandlerOptions{}, | |
| `time=2000-01-02T03:04:05.000Z level=INFO msg="a message"`, | |
| func(s string) string { return s }, | |
| }, | |
| { | |
| "replace", | |
| HandlerOptions{ReplaceAttr: upperCaseKey}, | |
| `TIME=2000-01-02T03:04:05.000Z LEVEL=INFO MSG="a message"`, | |
| strings.ToUpper, | |
| }, | |
| } { | |
| t.Run(opts.name, func(t *testing.T) { | |
| var buf bytes.Buffer | |
| h := NewTextHandler(&buf, &opts.opts) | |
| r := NewRecord(testTime, LevelInfo, "a message", 0) | |
| r.AddAttrs(test.attr) | |
| if err := h.Handle(context.Background(), r); err != nil { | |
| t.Fatal(err) | |
| } | |
| got := buf.String() | |
| // Remove final newline. | |
| got = got[:len(got)-1] | |
| want := opts.wantPrefix + " " + opts.modKey(test.wantKey) + "=" + test.wantVal | |
| if got != want { | |
| t.Errorf("\ngot %s\nwant %s", got, want) | |
| } | |
| }) | |
| } | |
| }) | |
| } | |
| } | |
| // for testing fmt.Sprint | |
| type name struct { | |
| First, Last string | |
| } | |
| func (n name) String() string { return n.Last + ", " + n.First } | |
| // for testing TextMarshaler | |
| type text struct { | |
| s string | |
| } | |
| func (t text) String() string { return t.s } // should be ignored | |
| func (t text) MarshalText() ([]byte, error) { | |
| if t.s == "" { | |
| return nil, errors.New("text: empty string") | |
| } | |
| return []byte(fmt.Sprintf("text{%q}", t.s)), nil | |
| } | |
| func TestTextHandlerPreformatted(t *testing.T) { | |
| var buf bytes.Buffer | |
| var h Handler = NewTextHandler(&buf, nil) | |
| h = h.WithAttrs([]Attr{Duration("dur", time.Minute), Bool("b", true)}) | |
| // Also test omitting time. | |
| r := NewRecord(time.Time{}, 0 /* 0 Level is INFO */, "m", 0) | |
| r.AddAttrs(Int("a", 1)) | |
| if err := h.Handle(context.Background(), r); err != nil { | |
| t.Fatal(err) | |
| } | |
| got := strings.TrimSuffix(buf.String(), "\n") | |
| want := `level=INFO msg=m dur=1m0s b=true a=1` | |
| if got != want { | |
| t.Errorf("got %s, want %s", got, want) | |
| } | |
| } | |
| func TestTextHandlerAlloc(t *testing.T) { | |
| r := NewRecord(time.Now(), LevelInfo, "msg", 0) | |
| for i := 0; i < 10; i++ { | |
| r.AddAttrs(Int("x = y", i)) | |
| } | |
| var h Handler = NewTextHandler(io.Discard, nil) | |
| wantAllocs(t, 0, func() { h.Handle(context.Background(), r) }) | |
| h = h.WithGroup("s") | |
| r.AddAttrs(Group("g", Int("a", 1))) | |
| wantAllocs(t, 0, func() { h.Handle(context.Background(), r) }) | |
| } | |
| func TestNeedsQuoting(t *testing.T) { | |
| for _, test := range []struct { | |
| in string | |
| want bool | |
| }{ | |
| {"", true}, | |
| {"ab", false}, | |
| {"a=b", true}, | |
| {`"ab"`, true}, | |
| {"\a\b", true}, | |
| {"a\tb", true}, | |
| {"µåπ", false}, | |
| } { | |
| got := needsQuoting(test.in) | |
| if got != test.want { | |
| t.Errorf("%q: got %t, want %t", test.in, got, test.want) | |
| } | |
| } | |
| } |