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