Source file src/log/slog/text_handler_test.go

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

View as plain text