1// Copyright 2009 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 expvar 6 7import ( 8 "bytes" 9 "crypto/sha1" 10 "encoding/json" 11 "fmt" 12 "net" 13 "net/http/httptest" 14 "reflect" 15 "runtime" 16 "strconv" 17 "sync" 18 "sync/atomic" 19 "testing" 20) 21 22// RemoveAll removes all exported variables. 23// This is for tests only. 24func RemoveAll() { 25 vars.keysMu.Lock() 26 defer vars.keysMu.Unlock() 27 for _, k := range vars.keys { 28 vars.m.Delete(k) 29 } 30 vars.keys = nil 31} 32 33func TestNil(t *testing.T) { 34 RemoveAll() 35 val := Get("missing") 36 if val != nil { 37 t.Errorf("got %v, want nil", val) 38 } 39} 40 41func TestInt(t *testing.T) { 42 RemoveAll() 43 reqs := NewInt("requests") 44 if i := reqs.Value(); i != 0 { 45 t.Errorf("reqs.Value() = %v, want 0", i) 46 } 47 if reqs != Get("requests").(*Int) { 48 t.Errorf("Get() failed.") 49 } 50 51 reqs.Add(1) 52 reqs.Add(3) 53 if i := reqs.Value(); i != 4 { 54 t.Errorf("reqs.Value() = %v, want 4", i) 55 } 56 57 if s := reqs.String(); s != "4" { 58 t.Errorf("reqs.String() = %q, want \"4\"", s) 59 } 60 61 reqs.Set(-2) 62 if i := reqs.Value(); i != -2 { 63 t.Errorf("reqs.Value() = %v, want -2", i) 64 } 65} 66 67func BenchmarkIntAdd(b *testing.B) { 68 var v Int 69 70 b.RunParallel(func(pb *testing.PB) { 71 for pb.Next() { 72 v.Add(1) 73 } 74 }) 75} 76 77func BenchmarkIntSet(b *testing.B) { 78 var v Int 79 80 b.RunParallel(func(pb *testing.PB) { 81 for pb.Next() { 82 v.Set(1) 83 } 84 }) 85} 86 87func TestFloat(t *testing.T) { 88 RemoveAll() 89 reqs := NewFloat("requests-float") 90 if reqs.f.Load() != 0.0 { 91 t.Errorf("reqs.f = %v, want 0", reqs.f.Load()) 92 } 93 if reqs != Get("requests-float").(*Float) { 94 t.Errorf("Get() failed.") 95 } 96 97 reqs.Add(1.5) 98 reqs.Add(1.25) 99 if v := reqs.Value(); v != 2.75 { 100 t.Errorf("reqs.Value() = %v, want 2.75", v) 101 } 102 103 if s := reqs.String(); s != "2.75" { 104 t.Errorf("reqs.String() = %q, want \"4.64\"", s) 105 } 106 107 reqs.Add(-2) 108 if v := reqs.Value(); v != 0.75 { 109 t.Errorf("reqs.Value() = %v, want 0.75", v) 110 } 111} 112 113func BenchmarkFloatAdd(b *testing.B) { 114 var f Float 115 116 b.RunParallel(func(pb *testing.PB) { 117 for pb.Next() { 118 f.Add(1.0) 119 } 120 }) 121} 122 123func BenchmarkFloatSet(b *testing.B) { 124 var f Float 125 126 b.RunParallel(func(pb *testing.PB) { 127 for pb.Next() { 128 f.Set(1.0) 129 } 130 }) 131} 132 133func TestString(t *testing.T) { 134 RemoveAll() 135 name := NewString("my-name") 136 if s := name.Value(); s != "" { 137 t.Errorf(`NewString("my-name").Value() = %q, want ""`, s) 138 } 139 140 name.Set("Mike") 141 if s, want := name.String(), `"Mike"`; s != want { 142 t.Errorf(`after name.Set("Mike"), name.String() = %q, want %q`, s, want) 143 } 144 if s, want := name.Value(), "Mike"; s != want { 145 t.Errorf(`after name.Set("Mike"), name.Value() = %q, want %q`, s, want) 146 } 147 148 // Make sure we produce safe JSON output. 149 name.Set("<") 150 if s, want := name.String(), "\"\\u003c\""; s != want { 151 t.Errorf(`after name.Set("<"), name.String() = %q, want %q`, s, want) 152 } 153} 154 155func BenchmarkStringSet(b *testing.B) { 156 var s String 157 158 b.RunParallel(func(pb *testing.PB) { 159 for pb.Next() { 160 s.Set("red") 161 } 162 }) 163} 164 165func TestMapInit(t *testing.T) { 166 RemoveAll() 167 colors := NewMap("bike-shed-colors") 168 colors.Add("red", 1) 169 colors.Add("blue", 1) 170 colors.Add("chartreuse", 1) 171 172 n := 0 173 colors.Do(func(KeyValue) { n++ }) 174 if n != 3 { 175 t.Errorf("after three Add calls with distinct keys, Do should invoke f 3 times; got %v", n) 176 } 177 178 colors.Init() 179 180 n = 0 181 colors.Do(func(KeyValue) { n++ }) 182 if n != 0 { 183 t.Errorf("after Init, Do should invoke f 0 times; got %v", n) 184 } 185} 186 187func TestMapDelete(t *testing.T) { 188 RemoveAll() 189 colors := NewMap("bike-shed-colors") 190 191 colors.Add("red", 1) 192 colors.Add("red", 2) 193 colors.Add("blue", 4) 194 195 n := 0 196 colors.Do(func(KeyValue) { n++ }) 197 if n != 2 { 198 t.Errorf("after two Add calls with distinct keys, Do should invoke f 2 times; got %v", n) 199 } 200 201 colors.Delete("red") 202 if v := colors.Get("red"); v != nil { 203 t.Errorf("removed red, Get should return nil; got %v", v) 204 } 205 n = 0 206 colors.Do(func(KeyValue) { n++ }) 207 if n != 1 { 208 t.Errorf("removed red, Do should invoke f 1 times; got %v", n) 209 } 210 211 colors.Delete("notfound") 212 n = 0 213 colors.Do(func(KeyValue) { n++ }) 214 if n != 1 { 215 t.Errorf("attempted to remove notfound, Do should invoke f 1 times; got %v", n) 216 } 217 218 colors.Delete("blue") 219 colors.Delete("blue") 220 if v := colors.Get("blue"); v != nil { 221 t.Errorf("removed blue, Get should return nil; got %v", v) 222 } 223 n = 0 224 colors.Do(func(KeyValue) { n++ }) 225 if n != 0 { 226 t.Errorf("all keys removed, Do should invoke f 0 times; got %v", n) 227 } 228} 229 230func TestMapCounter(t *testing.T) { 231 RemoveAll() 232 colors := NewMap("bike-shed-colors") 233 234 colors.Add("red", 1) 235 colors.Add("red", 2) 236 colors.Add("blue", 4) 237 colors.AddFloat(`green "midori"`, 4.125) 238 if x := colors.Get("red").(*Int).Value(); x != 3 { 239 t.Errorf("colors.m[\"red\"] = %v, want 3", x) 240 } 241 if x := colors.Get("blue").(*Int).Value(); x != 4 { 242 t.Errorf("colors.m[\"blue\"] = %v, want 4", x) 243 } 244 if x := colors.Get(`green "midori"`).(*Float).Value(); x != 4.125 { 245 t.Errorf("colors.m[`green \"midori\"] = %v, want 4.125", x) 246 } 247 248 // colors.String() should be '{"red":3, "blue":4}', 249 // though the order of red and blue could vary. 250 s := colors.String() 251 var j any 252 err := json.Unmarshal([]byte(s), &j) 253 if err != nil { 254 t.Errorf("colors.String() isn't valid JSON: %v", err) 255 } 256 m, ok := j.(map[string]any) 257 if !ok { 258 t.Error("colors.String() didn't produce a map.") 259 } 260 red := m["red"] 261 x, ok := red.(float64) 262 if !ok { 263 t.Error("red.Kind() is not a number.") 264 } 265 if x != 3 { 266 t.Errorf("red = %v, want 3", x) 267 } 268} 269 270func TestMapNil(t *testing.T) { 271 RemoveAll() 272 const key = "key" 273 m := NewMap("issue527719") 274 m.Set(key, nil) 275 s := m.String() 276 var j any 277 if err := json.Unmarshal([]byte(s), &j); err != nil { 278 t.Fatalf("m.String() == %q isn't valid JSON: %v", s, err) 279 } 280 m2, ok := j.(map[string]any) 281 if !ok { 282 t.Fatalf("m.String() produced %T, wanted a map", j) 283 } 284 v, ok := m2[key] 285 if !ok { 286 t.Fatalf("missing %q in %v", key, m2) 287 } 288 if v != nil { 289 t.Fatalf("m[%q] = %v, want nil", key, v) 290 } 291} 292 293func BenchmarkMapSet(b *testing.B) { 294 m := new(Map).Init() 295 296 v := new(Int) 297 298 b.RunParallel(func(pb *testing.PB) { 299 for pb.Next() { 300 m.Set("red", v) 301 } 302 }) 303} 304 305func BenchmarkMapSetDifferent(b *testing.B) { 306 procKeys := make([][]string, runtime.GOMAXPROCS(0)) 307 for i := range procKeys { 308 keys := make([]string, 4) 309 for j := range keys { 310 keys[j] = fmt.Sprint(i, j) 311 } 312 procKeys[i] = keys 313 } 314 315 m := new(Map).Init() 316 v := new(Int) 317 b.ResetTimer() 318 319 var n int32 320 b.RunParallel(func(pb *testing.PB) { 321 i := int(atomic.AddInt32(&n, 1)-1) % len(procKeys) 322 keys := procKeys[i] 323 324 for pb.Next() { 325 for _, k := range keys { 326 m.Set(k, v) 327 } 328 } 329 }) 330} 331 332// BenchmarkMapSetDifferentRandom simulates such a case where the concerned 333// keys of Map.Set are generated dynamically and as a result insertion is 334// out of order and the number of the keys may be large. 335func BenchmarkMapSetDifferentRandom(b *testing.B) { 336 keys := make([]string, 100) 337 for i := range keys { 338 keys[i] = fmt.Sprintf("%x", sha1.Sum([]byte(fmt.Sprint(i)))) 339 } 340 341 v := new(Int) 342 b.ResetTimer() 343 344 for i := 0; i < b.N; i++ { 345 m := new(Map).Init() 346 for _, k := range keys { 347 m.Set(k, v) 348 } 349 } 350} 351 352func BenchmarkMapSetString(b *testing.B) { 353 m := new(Map).Init() 354 355 v := new(String) 356 v.Set("Hello, !") 357 358 b.RunParallel(func(pb *testing.PB) { 359 for pb.Next() { 360 m.Set("red", v) 361 } 362 }) 363} 364 365func BenchmarkMapAddSame(b *testing.B) { 366 b.RunParallel(func(pb *testing.PB) { 367 for pb.Next() { 368 m := new(Map).Init() 369 m.Add("red", 1) 370 m.Add("red", 1) 371 m.Add("red", 1) 372 m.Add("red", 1) 373 } 374 }) 375} 376 377func BenchmarkMapAddDifferent(b *testing.B) { 378 procKeys := make([][]string, runtime.GOMAXPROCS(0)) 379 for i := range procKeys { 380 keys := make([]string, 4) 381 for j := range keys { 382 keys[j] = fmt.Sprint(i, j) 383 } 384 procKeys[i] = keys 385 } 386 387 b.ResetTimer() 388 389 var n int32 390 b.RunParallel(func(pb *testing.PB) { 391 i := int(atomic.AddInt32(&n, 1)-1) % len(procKeys) 392 keys := procKeys[i] 393 394 for pb.Next() { 395 m := new(Map).Init() 396 for _, k := range keys { 397 m.Add(k, 1) 398 } 399 } 400 }) 401} 402 403// BenchmarkMapAddDifferentRandom simulates such a case where that the concerned 404// keys of Map.Add are generated dynamically and as a result insertion is out of 405// order and the number of the keys may be large. 406func BenchmarkMapAddDifferentRandom(b *testing.B) { 407 keys := make([]string, 100) 408 for i := range keys { 409 keys[i] = fmt.Sprintf("%x", sha1.Sum([]byte(fmt.Sprint(i)))) 410 } 411 412 b.ResetTimer() 413 414 for i := 0; i < b.N; i++ { 415 m := new(Map).Init() 416 for _, k := range keys { 417 m.Add(k, 1) 418 } 419 } 420} 421 422func BenchmarkMapAddSameSteadyState(b *testing.B) { 423 m := new(Map).Init() 424 b.RunParallel(func(pb *testing.PB) { 425 for pb.Next() { 426 m.Add("red", 1) 427 } 428 }) 429} 430 431func BenchmarkMapAddDifferentSteadyState(b *testing.B) { 432 procKeys := make([][]string, runtime.GOMAXPROCS(0)) 433 for i := range procKeys { 434 keys := make([]string, 4) 435 for j := range keys { 436 keys[j] = fmt.Sprint(i, j) 437 } 438 procKeys[i] = keys 439 } 440 441 m := new(Map).Init() 442 b.ResetTimer() 443 444 var n int32 445 b.RunParallel(func(pb *testing.PB) { 446 i := int(atomic.AddInt32(&n, 1)-1) % len(procKeys) 447 keys := procKeys[i] 448 449 for pb.Next() { 450 for _, k := range keys { 451 m.Add(k, 1) 452 } 453 } 454 }) 455} 456 457func TestFunc(t *testing.T) { 458 RemoveAll() 459 var x any = []string{"a", "b"} 460 f := Func(func() any { return x }) 461 if s, exp := f.String(), `["a","b"]`; s != exp { 462 t.Errorf(`f.String() = %q, want %q`, s, exp) 463 } 464 if v := f.Value(); !reflect.DeepEqual(v, x) { 465 t.Errorf(`f.Value() = %q, want %q`, v, x) 466 } 467 468 x = 17 469 if s, exp := f.String(), `17`; s != exp { 470 t.Errorf(`f.String() = %q, want %q`, s, exp) 471 } 472} 473 474func TestHandler(t *testing.T) { 475 RemoveAll() 476 m := NewMap("map1") 477 m.Add("a", 1) 478 m.Add("z", 2) 479 m2 := NewMap("map2") 480 for i := 0; i < 9; i++ { 481 m2.Add(strconv.Itoa(i), int64(i)) 482 } 483 rr := httptest.NewRecorder() 484 rr.Body = new(bytes.Buffer) 485 expvarHandler(rr, nil) 486 want := `{ 487"map1": {"a": 1, "z": 2}, 488"map2": {"0": 0, "1": 1, "2": 2, "3": 3, "4": 4, "5": 5, "6": 6, "7": 7, "8": 8} 489} 490` 491 if got := rr.Body.String(); got != want { 492 t.Errorf("HTTP handler wrote:\n%s\nWant:\n%s", got, want) 493 } 494} 495 496func BenchmarkMapString(b *testing.B) { 497 var m, m1, m2 Map 498 m.Set("map1", &m1) 499 m1.Add("a", 1) 500 m1.Add("z", 2) 501 m.Set("map2", &m2) 502 for i := 0; i < 9; i++ { 503 m2.Add(strconv.Itoa(i), int64(i)) 504 } 505 var s1, s2 String 506 m.Set("str1", &s1) 507 s1.Set("hello, world!") 508 m.Set("str2", &s2) 509 s2.Set("fizz buzz") 510 b.ResetTimer() 511 512 b.ReportAllocs() 513 for i := 0; i < b.N; i++ { 514 _ = m.String() 515 } 516} 517 518func BenchmarkRealworldExpvarUsage(b *testing.B) { 519 var ( 520 bytesSent Int 521 bytesRead Int 522 ) 523 524 // The benchmark creates GOMAXPROCS client/server pairs. 525 // Each pair creates 4 goroutines: client reader/writer and server reader/writer. 526 // The benchmark stresses concurrent reading and writing to the same connection. 527 // Such pattern is used in net/http and net/rpc. 528 529 b.StopTimer() 530 531 P := runtime.GOMAXPROCS(0) 532 N := b.N / P 533 W := 1000 534 535 // Setup P client/server connections. 536 clients := make([]net.Conn, P) 537 servers := make([]net.Conn, P) 538 ln, err := net.Listen("tcp", "127.0.0.1:0") 539 if err != nil { 540 b.Fatalf("Listen failed: %v", err) 541 } 542 defer ln.Close() 543 done := make(chan bool, 1) 544 go func() { 545 for p := 0; p < P; p++ { 546 s, err := ln.Accept() 547 if err != nil { 548 b.Errorf("Accept failed: %v", err) 549 done <- false 550 return 551 } 552 servers[p] = s 553 } 554 done <- true 555 }() 556 for p := 0; p < P; p++ { 557 c, err := net.Dial("tcp", ln.Addr().String()) 558 if err != nil { 559 <-done 560 b.Fatalf("Dial failed: %v", err) 561 } 562 clients[p] = c 563 } 564 if !<-done { 565 b.FailNow() 566 } 567 568 b.StartTimer() 569 570 var wg sync.WaitGroup 571 wg.Add(4 * P) 572 for p := 0; p < P; p++ { 573 // Client writer. 574 go func(c net.Conn) { 575 defer wg.Done() 576 var buf [1]byte 577 for i := 0; i < N; i++ { 578 v := byte(i) 579 for w := 0; w < W; w++ { 580 v *= v 581 } 582 buf[0] = v 583 n, err := c.Write(buf[:]) 584 if err != nil { 585 b.Errorf("Write failed: %v", err) 586 return 587 } 588 589 bytesSent.Add(int64(n)) 590 } 591 }(clients[p]) 592 593 // Pipe between server reader and server writer. 594 pipe := make(chan byte, 128) 595 596 // Server reader. 597 go func(s net.Conn) { 598 defer wg.Done() 599 var buf [1]byte 600 for i := 0; i < N; i++ { 601 n, err := s.Read(buf[:]) 602 603 if err != nil { 604 b.Errorf("Read failed: %v", err) 605 return 606 } 607 608 bytesRead.Add(int64(n)) 609 pipe <- buf[0] 610 } 611 }(servers[p]) 612 613 // Server writer. 614 go func(s net.Conn) { 615 defer wg.Done() 616 var buf [1]byte 617 for i := 0; i < N; i++ { 618 v := <-pipe 619 for w := 0; w < W; w++ { 620 v *= v 621 } 622 buf[0] = v 623 n, err := s.Write(buf[:]) 624 if err != nil { 625 b.Errorf("Write failed: %v", err) 626 return 627 } 628 629 bytesSent.Add(int64(n)) 630 } 631 s.Close() 632 }(servers[p]) 633 634 // Client reader. 635 go func(c net.Conn) { 636 defer wg.Done() 637 var buf [1]byte 638 for i := 0; i < N; i++ { 639 n, err := c.Read(buf[:]) 640 641 if err != nil { 642 b.Errorf("Read failed: %v", err) 643 return 644 } 645 646 bytesRead.Add(int64(n)) 647 } 648 c.Close() 649 }(clients[p]) 650 } 651 wg.Wait() 652} 653 654func TestAppendJSONQuote(t *testing.T) { 655 var b []byte 656 for i := 0; i < 128; i++ { 657 b = append(b, byte(i)) 658 } 659 b = append(b, "\u2028\u2029"...) 660 got := string(appendJSONQuote(nil, string(b[:]))) 661 want := `"` + 662 `\u0000\u0001\u0002\u0003\u0004\u0005\u0006\u0007\u0008\t\n\u000b\u000c\r\u000e\u000f` + 663 `\u0010\u0011\u0012\u0013\u0014\u0015\u0016\u0017\u0018\u0019\u001a\u001b\u001c\u001d\u001e\u001f` + 664 ` !\"#$%\u0026'()*+,-./0123456789:;\u003c=\u003e?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_` + 665 "`" + `abcdefghijklmnopqrstuvwxyz{|}~` + "\x7f" + `\u2028\u2029"` 666 if got != want { 667 t.Errorf("appendJSONQuote mismatch:\ngot %v\nwant %v", got, want) 668 } 669} 670