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 }