Source file src/log/slog/record_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  	"slices"
     9  	"strconv"
    10  	"strings"
    11  	"testing"
    12  	"time"
    13  )
    14  
    15  func TestRecordAttrs(t *testing.T) {
    16  	as := []Attr{Int("k1", 1), String("k2", "foo"), Int("k3", 3),
    17  		Int64("k4", -1), Float64("f", 3.1), Uint64("u", 999)}
    18  	r := newRecordWithAttrs(as)
    19  	if g, w := r.NumAttrs(), len(as); g != w {
    20  		t.Errorf("NumAttrs: got %d, want %d", g, w)
    21  	}
    22  	if got := attrsSlice(r); !attrsEqual(got, as) {
    23  		t.Errorf("got %v, want %v", got, as)
    24  	}
    25  
    26  	// Early return.
    27  	// Hit both loops in Record.Attrs: front and back.
    28  	for _, stop := range []int{2, 6} {
    29  		var got []Attr
    30  		r.Attrs(func(a Attr) bool {
    31  			got = append(got, a)
    32  			return len(got) < stop
    33  		})
    34  		want := as[:stop]
    35  		if !attrsEqual(got, want) {
    36  			t.Errorf("got %v, want %v", got, want)
    37  		}
    38  	}
    39  }
    40  
    41  func TestRecordSource(t *testing.T) {
    42  	// Zero call depth => nil *Source.
    43  	for _, test := range []struct {
    44  		depth            int
    45  		wantFunction     string
    46  		wantFile         string
    47  		wantLinePositive bool
    48  		wantNil          bool
    49  	}{
    50  		{0, "", "", false, true},
    51  		{-16, "", "", false, true},
    52  		{1, "log/slog.TestRecordSource", "record_test.go", true, false}, // 1: caller of NewRecord
    53  		{2, "testing.tRunner", "testing.go", true, false},
    54  	} {
    55  		var pc uintptr
    56  		if test.depth > 0 {
    57  			pc = callerPC(test.depth + 1)
    58  		}
    59  		r := NewRecord(time.Time{}, 0, "", pc)
    60  		got := r.Source()
    61  		if test.wantNil {
    62  			if got != nil {
    63  				t.Errorf("depth %d: got non-nil Source, want nil", test.depth)
    64  			}
    65  			continue
    66  		}
    67  		if got == nil {
    68  			t.Errorf("depth %d: got nil Source, want non-nil", test.depth)
    69  			continue
    70  		}
    71  		if i := strings.LastIndexByte(got.File, '/'); i >= 0 {
    72  			got.File = got.File[i+1:]
    73  		}
    74  		if got.Function != test.wantFunction || got.File != test.wantFile || (got.Line > 0) != test.wantLinePositive {
    75  			t.Errorf("depth %d: got (%q, %q, %d), want (%q, %q, %t)",
    76  				test.depth,
    77  				got.Function, got.File, got.Line,
    78  				test.wantFunction, test.wantFile, test.wantLinePositive)
    79  		}
    80  	}
    81  }
    82  
    83  func TestAliasingAndClone(t *testing.T) {
    84  	intAttrs := func(from, to int) []Attr {
    85  		var as []Attr
    86  		for i := from; i < to; i++ {
    87  			as = append(as, Int("k", i))
    88  		}
    89  		return as
    90  	}
    91  
    92  	check := func(r Record, want []Attr) {
    93  		t.Helper()
    94  		got := attrsSlice(r)
    95  		if !attrsEqual(got, want) {
    96  			t.Errorf("got %v, want %v", got, want)
    97  		}
    98  	}
    99  
   100  	// Create a record whose Attrs overflow the inline array,
   101  	// creating a slice in r.back.
   102  	r1 := NewRecord(time.Time{}, 0, "", 0)
   103  	r1.AddAttrs(intAttrs(0, nAttrsInline+1)...)
   104  	// Ensure that r1.back's capacity exceeds its length.
   105  	b := make([]Attr, len(r1.back), len(r1.back)+1)
   106  	copy(b, r1.back)
   107  	r1.back = b
   108  	// Make a copy that shares state.
   109  	r2 := r1
   110  	// Adding to both should insert a special Attr in the second.
   111  	r1AttrsBefore := attrsSlice(r1)
   112  	r1.AddAttrs(Int("p", 0))
   113  	r2.AddAttrs(Int("p", 1))
   114  	check(r1, append(slices.Clip(r1AttrsBefore), Int("p", 0)))
   115  	r1Attrs := attrsSlice(r1)
   116  	check(r2, append(slices.Clip(r1AttrsBefore),
   117  		String("!BUG", "AddAttrs unsafely called on copy of Record made without using Record.Clone"), Int("p", 1)))
   118  
   119  	// Adding to a clone is fine.
   120  	r2 = r1.Clone()
   121  	check(r2, r1Attrs)
   122  	r2.AddAttrs(Int("p", 2))
   123  	check(r1, r1Attrs) // r1 is unchanged
   124  	check(r2, append(slices.Clip(r1Attrs), Int("p", 2)))
   125  }
   126  
   127  func newRecordWithAttrs(as []Attr) Record {
   128  	r := NewRecord(time.Now(), LevelInfo, "", 0)
   129  	r.AddAttrs(as...)
   130  	return r
   131  }
   132  
   133  func attrsSlice(r Record) []Attr {
   134  	s := make([]Attr, 0, r.NumAttrs())
   135  	r.Attrs(func(a Attr) bool { s = append(s, a); return true })
   136  	return s
   137  }
   138  
   139  func attrsEqual(as1, as2 []Attr) bool {
   140  	return slices.EqualFunc(as1, as2, Attr.Equal)
   141  }
   142  
   143  // Currently, pc(2) takes over 400ns, which is too expensive
   144  // to call it for every log message.
   145  func BenchmarkPC(b *testing.B) {
   146  	for depth := 0; depth < 5; depth++ {
   147  		b.Run(strconv.Itoa(depth), func(b *testing.B) {
   148  			b.ReportAllocs()
   149  			var x uintptr
   150  			for i := 0; i < b.N; i++ {
   151  				x = callerPC(depth)
   152  			}
   153  			_ = x
   154  		})
   155  	}
   156  }
   157  
   158  func BenchmarkRecord(b *testing.B) {
   159  	const nAttrs = nAttrsInline * 10
   160  	var a Attr
   161  
   162  	for i := 0; i < b.N; i++ {
   163  		r := NewRecord(time.Time{}, LevelInfo, "", 0)
   164  		for j := 0; j < nAttrs; j++ {
   165  			r.AddAttrs(Int("k", j))
   166  		}
   167  		r.Attrs(func(b Attr) bool { a = b; return true })
   168  	}
   169  	_ = a
   170  }
   171  

View as plain text