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 5package slog 6 7import ( 8 "fmt" 9 "reflect" 10 "strings" 11 "testing" 12 "time" 13 "unsafe" 14) 15 16func 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 22func 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 58func 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 68func 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 88func 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 122func 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 131func 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 164func 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 187func 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 235func 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 252func 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 264type replace struct { 265 v Value 266} 267 268func (r *replace) LogValue() Value { return r.v } 269 270type panickingLogValue struct{} 271 272func (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. 279func 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