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

View as plain text