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