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  }