Source file src/log/slog/value_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  	"fmt"
     9  	"reflect"
    10  	"strings"
    11  	"testing"
    12  	"time"
    13  	"unsafe"
    14  )
    15  
    16  func TestKindString(t *testing.T) {
    17  	if got, want := KindGroup.String(), "Group"; got != want {
    18  		t.Errorf("got %q, want %q", got, want)
    19  	}
    20  }
    21  
    22  func TestValueEqual(t *testing.T) {
    23  	var x, y int
    24  	vals := []Value{
    25  		{},
    26  		Int64Value(1),
    27  		Int64Value(2),
    28  		Float64Value(3.5),
    29  		Float64Value(3.7),
    30  		BoolValue(true),
    31  		BoolValue(false),
    32  		TimeValue(testTime),
    33  		AnyValue(&x),
    34  		AnyValue(&y),
    35  		GroupValue(Bool("b", true), Int("i", 3)),
    36  	}
    37  	for i, v1 := range vals {
    38  		for j, v2 := range vals {
    39  			got := v1.Equal(v2)
    40  			want := i == j
    41  			if got != want {
    42  				t.Errorf("%v.Equal(%v): got %t, want %t", v1, v2, got, want)
    43  			}
    44  		}
    45  	}
    46  }
    47  
    48  func panics(f func()) (b bool) {
    49  	defer func() {
    50  		if x := recover(); x != nil {
    51  			b = true
    52  		}
    53  	}()
    54  	f()
    55  	return false
    56  }
    57  
    58  func TestValueString(t *testing.T) {
    59  	for _, test := range []struct {
    60  		v    Value
    61  		want string
    62  	}{
    63  		{Int64Value(-3), "-3"},
    64  		{Uint64Value(1), "1"},
    65  		{Float64Value(.15), "0.15"},
    66  		{BoolValue(true), "true"},
    67  		{StringValue("foo"), "foo"},
    68  		{TimeValue(testTime), "2000-01-02 03:04:05 +0000 UTC"},
    69  		{AnyValue(time.Duration(3 * time.Second)), "3s"},
    70  		{GroupValue(Int("a", 1), Bool("b", true)), "[a=1 b=true]"},
    71  	} {
    72  		if got := test.v.String(); got != test.want {
    73  			t.Errorf("%#v:\ngot  %q\nwant %q", test.v, got, test.want)
    74  		}
    75  	}
    76  }
    77  
    78  func TestValueNoAlloc(t *testing.T) {
    79  	// Assign values just to make sure the compiler doesn't optimize away the statements.
    80  	var (
    81  		i  int64
    82  		u  uint64
    83  		f  float64
    84  		b  bool
    85  		s  string
    86  		x  any
    87  		p  = &i
    88  		d  time.Duration
    89  		tm time.Time
    90  	)
    91  	a := int(testing.AllocsPerRun(5, func() {
    92  		i = Int64Value(1).Int64()
    93  		u = Uint64Value(1).Uint64()
    94  		f = Float64Value(1).Float64()
    95  		b = BoolValue(true).Bool()
    96  		s = StringValue("foo").String()
    97  		d = DurationValue(d).Duration()
    98  		tm = TimeValue(testTime).Time()
    99  		x = AnyValue(p).Any()
   100  	}))
   101  	if a != 0 {
   102  		t.Errorf("got %d allocs, want zero", a)
   103  	}
   104  	_ = u
   105  	_ = f
   106  	_ = b
   107  	_ = s
   108  	_ = x
   109  	_ = tm
   110  }
   111  
   112  func TestAnyLevelAlloc(t *testing.T) {
   113  	// Because typical Levels are small integers,
   114  	// they are zero-alloc.
   115  	var a Value
   116  	x := LevelDebug + 100
   117  	wantAllocs(t, 0, func() { a = AnyValue(x) })
   118  	_ = a
   119  }
   120  
   121  func TestAnyValue(t *testing.T) {
   122  	for _, test := range []struct {
   123  		in   any
   124  		want Value
   125  	}{
   126  		{1, IntValue(1)},
   127  		{1.5, Float64Value(1.5)},
   128  		{float32(2.5), Float64Value(2.5)},
   129  		{"s", StringValue("s")},
   130  		{true, BoolValue(true)},
   131  		{testTime, TimeValue(testTime)},
   132  		{time.Hour, DurationValue(time.Hour)},
   133  		{[]Attr{Int("i", 3)}, GroupValue(Int("i", 3))},
   134  		{IntValue(4), IntValue(4)},
   135  		{uint(2), Uint64Value(2)},
   136  		{uint8(3), Uint64Value(3)},
   137  		{uint16(4), Uint64Value(4)},
   138  		{uint32(5), Uint64Value(5)},
   139  		{uint64(6), Uint64Value(6)},
   140  		{uintptr(7), Uint64Value(7)},
   141  		{int8(8), Int64Value(8)},
   142  		{int16(9), Int64Value(9)},
   143  		{int32(10), Int64Value(10)},
   144  		{int64(11), Int64Value(11)},
   145  	} {
   146  		got := AnyValue(test.in)
   147  		if !got.Equal(test.want) {
   148  			t.Errorf("%v (%[1]T): got %v (kind %s), want %v (kind %s)",
   149  				test.in, got, got.Kind(), test.want, test.want.Kind())
   150  		}
   151  	}
   152  }
   153  
   154  func TestValueAny(t *testing.T) {
   155  	for _, want := range []any{
   156  		nil,
   157  		LevelDebug + 100,
   158  		time.UTC, // time.Locations treated specially...
   159  		KindBool, // ...as are Kinds
   160  		[]Attr{Int("a", 1)},
   161  		int64(2),
   162  		uint64(3),
   163  		true,
   164  		time.Minute,
   165  		time.Time{},
   166  		3.14,
   167  	} {
   168  		v := AnyValue(want)
   169  		got := v.Any()
   170  		if !reflect.DeepEqual(got, want) {
   171  			t.Errorf("got %v, want %v", got, want)
   172  		}
   173  	}
   174  }
   175  
   176  func TestLogValue(t *testing.T) {
   177  	want := "replaced"
   178  	r := &replace{StringValue(want)}
   179  	v := AnyValue(r)
   180  	if g, w := v.Kind(), KindLogValuer; g != w {
   181  		t.Errorf("got %s, want %s", g, w)
   182  	}
   183  	got := v.LogValuer().LogValue().Any()
   184  	if got != want {
   185  		t.Errorf("got %#v, want %#v", got, want)
   186  	}
   187  
   188  	// Test Resolve.
   189  	got = v.Resolve().Any()
   190  	if got != want {
   191  		t.Errorf("got %#v, want %#v", got, want)
   192  	}
   193  
   194  	// Test Resolve max iteration.
   195  	r.v = AnyValue(r) // create a cycle
   196  	got = AnyValue(r).Resolve().Any()
   197  	if _, ok := got.(error); !ok {
   198  		t.Errorf("expected error, got %T", got)
   199  	}
   200  
   201  	// Groups are not recursively resolved.
   202  	c := Any("c", &replace{StringValue("d")})
   203  	v = AnyValue(&replace{GroupValue(Int("a", 1), Group("b", c))})
   204  	got2 := v.Resolve().Any().([]Attr)
   205  	want2 := []Attr{Int("a", 1), Group("b", c)}
   206  	if !attrsEqual(got2, want2) {
   207  		t.Errorf("got %v, want %v", got2, want2)
   208  	}
   209  
   210  	// Verify that panics in Resolve are caught and turn into errors.
   211  	v = AnyValue(panickingLogValue{})
   212  	got = v.Resolve().Any()
   213  	gotErr, ok := got.(error)
   214  	if !ok {
   215  		t.Errorf("expected error, got %T", got)
   216  	}
   217  	// The error should provide some context information.
   218  	// We'll just check that this function name appears in it.
   219  	if got, want := gotErr.Error(), "TestLogValue"; !strings.Contains(got, want) {
   220  		t.Errorf("got %q, want substring %q", got, want)
   221  	}
   222  }
   223  
   224  func TestZeroTime(t *testing.T) {
   225  	z := time.Time{}
   226  	got := TimeValue(z).Time()
   227  	if !got.IsZero() {
   228  		t.Errorf("got %s (%#[1]v), not zero time (%#v)", got, z)
   229  	}
   230  }
   231  
   232  func TestEmptyGroup(t *testing.T) {
   233  	g := GroupValue(
   234  		Int("a", 1),
   235  		Group("g1", Group("g2")),
   236  		Group("g3", Group("g4", Int("b", 2))))
   237  	got := g.Group()
   238  	want := []Attr{Int("a", 1), Group("g3", Group("g4", Int("b", 2)))}
   239  	if !attrsEqual(got, want) {
   240  		t.Errorf("\ngot  %v\nwant %v", got, want)
   241  	}
   242  }
   243  
   244  type replace struct {
   245  	v Value
   246  }
   247  
   248  func (r *replace) LogValue() Value { return r.v }
   249  
   250  type panickingLogValue struct{}
   251  
   252  func (panickingLogValue) LogValue() Value { panic("bad") }
   253  
   254  // A Value with "unsafe" strings is significantly faster:
   255  // safe:  1785 ns/op, 0 allocs
   256  // unsafe: 690 ns/op, 0 allocs
   257  
   258  // Run this with and without -tags unsafe_kvs to compare.
   259  func BenchmarkUnsafeStrings(b *testing.B) {
   260  	b.ReportAllocs()
   261  	dst := make([]Value, 100)
   262  	src := make([]Value, len(dst))
   263  	b.Logf("Value size = %d", unsafe.Sizeof(Value{}))
   264  	for i := range src {
   265  		src[i] = StringValue(fmt.Sprintf("string#%d", i))
   266  	}
   267  	b.ResetTimer()
   268  	var d string
   269  	for i := 0; i < b.N; i++ {
   270  		copy(dst, src)
   271  		for _, a := range dst {
   272  			d = a.String()
   273  		}
   274  	}
   275  	_ = d
   276  }
   277  

View as plain text