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