|  | // Copyright 2009 The Go Authors. All rights reserved. | 
|  | // Use of this source code is governed by a BSD-style | 
|  | // license that can be found in the LICENSE file. | 
|  |  | 
|  | package expvar | 
|  |  | 
|  | import ( | 
|  | "bytes" | 
|  | "crypto/sha1" | 
|  | "encoding/json" | 
|  | "fmt" | 
|  | "net" | 
|  | "net/http/httptest" | 
|  | "reflect" | 
|  | "runtime" | 
|  | "strconv" | 
|  | "sync" | 
|  | "sync/atomic" | 
|  | "testing" | 
|  | ) | 
|  |  | 
|  | // RemoveAll removes all exported variables. | 
|  | // This is for tests only. | 
|  | func RemoveAll() { | 
|  | varKeysMu.Lock() | 
|  | defer varKeysMu.Unlock() | 
|  | for _, k := range varKeys { | 
|  | vars.Delete(k) | 
|  | } | 
|  | varKeys = nil | 
|  | } | 
|  |  | 
|  | func TestNil(t *testing.T) { | 
|  | RemoveAll() | 
|  | val := Get("missing") | 
|  | if val != nil { | 
|  | t.Errorf("got %v, want nil", val) | 
|  | } | 
|  | } | 
|  |  | 
|  | func TestInt(t *testing.T) { | 
|  | RemoveAll() | 
|  | reqs := NewInt("requests") | 
|  | if i := reqs.Value(); i != 0 { | 
|  | t.Errorf("reqs.Value() = %v, want 0", i) | 
|  | } | 
|  | if reqs != Get("requests").(*Int) { | 
|  | t.Errorf("Get() failed.") | 
|  | } | 
|  |  | 
|  | reqs.Add(1) | 
|  | reqs.Add(3) | 
|  | if i := reqs.Value(); i != 4 { | 
|  | t.Errorf("reqs.Value() = %v, want 4", i) | 
|  | } | 
|  |  | 
|  | if s := reqs.String(); s != "4" { | 
|  | t.Errorf("reqs.String() = %q, want \"4\"", s) | 
|  | } | 
|  |  | 
|  | reqs.Set(-2) | 
|  | if i := reqs.Value(); i != -2 { | 
|  | t.Errorf("reqs.Value() = %v, want -2", i) | 
|  | } | 
|  | } | 
|  |  | 
|  | func BenchmarkIntAdd(b *testing.B) { | 
|  | var v Int | 
|  |  | 
|  | b.RunParallel(func(pb *testing.PB) { | 
|  | for pb.Next() { | 
|  | v.Add(1) | 
|  | } | 
|  | }) | 
|  | } | 
|  |  | 
|  | func BenchmarkIntSet(b *testing.B) { | 
|  | var v Int | 
|  |  | 
|  | b.RunParallel(func(pb *testing.PB) { | 
|  | for pb.Next() { | 
|  | v.Set(1) | 
|  | } | 
|  | }) | 
|  | } | 
|  |  | 
|  | func TestFloat(t *testing.T) { | 
|  | RemoveAll() | 
|  | reqs := NewFloat("requests-float") | 
|  | if reqs.f.Load() != 0.0 { | 
|  | t.Errorf("reqs.f = %v, want 0", reqs.f.Load()) | 
|  | } | 
|  | if reqs != Get("requests-float").(*Float) { | 
|  | t.Errorf("Get() failed.") | 
|  | } | 
|  |  | 
|  | reqs.Add(1.5) | 
|  | reqs.Add(1.25) | 
|  | if v := reqs.Value(); v != 2.75 { | 
|  | t.Errorf("reqs.Value() = %v, want 2.75", v) | 
|  | } | 
|  |  | 
|  | if s := reqs.String(); s != "2.75" { | 
|  | t.Errorf("reqs.String() = %q, want \"4.64\"", s) | 
|  | } | 
|  |  | 
|  | reqs.Add(-2) | 
|  | if v := reqs.Value(); v != 0.75 { | 
|  | t.Errorf("reqs.Value() = %v, want 0.75", v) | 
|  | } | 
|  | } | 
|  |  | 
|  | func BenchmarkFloatAdd(b *testing.B) { | 
|  | var f Float | 
|  |  | 
|  | b.RunParallel(func(pb *testing.PB) { | 
|  | for pb.Next() { | 
|  | f.Add(1.0) | 
|  | } | 
|  | }) | 
|  | } | 
|  |  | 
|  | func BenchmarkFloatSet(b *testing.B) { | 
|  | var f Float | 
|  |  | 
|  | b.RunParallel(func(pb *testing.PB) { | 
|  | for pb.Next() { | 
|  | f.Set(1.0) | 
|  | } | 
|  | }) | 
|  | } | 
|  |  | 
|  | func TestString(t *testing.T) { | 
|  | RemoveAll() | 
|  | name := NewString("my-name") | 
|  | if s := name.Value(); s != "" { | 
|  | t.Errorf(`NewString("my-name").Value() = %q, want ""`, s) | 
|  | } | 
|  |  | 
|  | name.Set("Mike") | 
|  | if s, want := name.String(), `"Mike"`; s != want { | 
|  | t.Errorf(`after name.Set("Mike"), name.String() = %q, want %q`, s, want) | 
|  | } | 
|  | if s, want := name.Value(), "Mike"; s != want { | 
|  | t.Errorf(`after name.Set("Mike"), name.Value() = %q, want %q`, s, want) | 
|  | } | 
|  |  | 
|  | // Make sure we produce safe JSON output. | 
|  | name.Set("<") | 
|  | if s, want := name.String(), "\"\\u003c\""; s != want { | 
|  | t.Errorf(`after name.Set("<"), name.String() = %q, want %q`, s, want) | 
|  | } | 
|  | } | 
|  |  | 
|  | func BenchmarkStringSet(b *testing.B) { | 
|  | var s String | 
|  |  | 
|  | b.RunParallel(func(pb *testing.PB) { | 
|  | for pb.Next() { | 
|  | s.Set("red") | 
|  | } | 
|  | }) | 
|  | } | 
|  |  | 
|  | func TestMapInit(t *testing.T) { | 
|  | RemoveAll() | 
|  | colors := NewMap("bike-shed-colors") | 
|  | colors.Add("red", 1) | 
|  | colors.Add("blue", 1) | 
|  | colors.Add("chartreuse", 1) | 
|  |  | 
|  | n := 0 | 
|  | colors.Do(func(KeyValue) { n++ }) | 
|  | if n != 3 { | 
|  | t.Errorf("after three Add calls with distinct keys, Do should invoke f 3 times; got %v", n) | 
|  | } | 
|  |  | 
|  | colors.Init() | 
|  |  | 
|  | n = 0 | 
|  | colors.Do(func(KeyValue) { n++ }) | 
|  | if n != 0 { | 
|  | t.Errorf("after Init, Do should invoke f 0 times; got %v", n) | 
|  | } | 
|  | } | 
|  |  | 
|  | func TestMapDelete(t *testing.T) { | 
|  | RemoveAll() | 
|  | colors := NewMap("bike-shed-colors") | 
|  |  | 
|  | colors.Add("red", 1) | 
|  | colors.Add("red", 2) | 
|  | colors.Add("blue", 4) | 
|  |  | 
|  | n := 0 | 
|  | colors.Do(func(KeyValue) { n++ }) | 
|  | if n != 2 { | 
|  | t.Errorf("after two Add calls with distinct keys, Do should invoke f 2 times; got %v", n) | 
|  | } | 
|  |  | 
|  | colors.Delete("red") | 
|  | n = 0 | 
|  | colors.Do(func(KeyValue) { n++ }) | 
|  | if n != 1 { | 
|  | t.Errorf("removed red, Do should invoke f 1 times; got %v", n) | 
|  | } | 
|  |  | 
|  | colors.Delete("notfound") | 
|  | n = 0 | 
|  | colors.Do(func(KeyValue) { n++ }) | 
|  | if n != 1 { | 
|  | t.Errorf("attempted to remove notfound, Do should invoke f 1 times; got %v", n) | 
|  | } | 
|  |  | 
|  | colors.Delete("blue") | 
|  | colors.Delete("blue") | 
|  | n = 0 | 
|  | colors.Do(func(KeyValue) { n++ }) | 
|  | if n != 0 { | 
|  | t.Errorf("all keys removed, Do should invoke f 0 times; got %v", n) | 
|  | } | 
|  | } | 
|  |  | 
|  | func TestMapCounter(t *testing.T) { | 
|  | RemoveAll() | 
|  | colors := NewMap("bike-shed-colors") | 
|  |  | 
|  | colors.Add("red", 1) | 
|  | colors.Add("red", 2) | 
|  | colors.Add("blue", 4) | 
|  | colors.AddFloat(`green "midori"`, 4.125) | 
|  | if x := colors.Get("red").(*Int).Value(); x != 3 { | 
|  | t.Errorf("colors.m[\"red\"] = %v, want 3", x) | 
|  | } | 
|  | if x := colors.Get("blue").(*Int).Value(); x != 4 { | 
|  | t.Errorf("colors.m[\"blue\"] = %v, want 4", x) | 
|  | } | 
|  | if x := colors.Get(`green "midori"`).(*Float).Value(); x != 4.125 { | 
|  | t.Errorf("colors.m[`green \"midori\"] = %v, want 4.125", x) | 
|  | } | 
|  |  | 
|  | // colors.String() should be '{"red":3, "blue":4}', | 
|  | // though the order of red and blue could vary. | 
|  | s := colors.String() | 
|  | var j any | 
|  | err := json.Unmarshal([]byte(s), &j) | 
|  | if err != nil { | 
|  | t.Errorf("colors.String() isn't valid JSON: %v", err) | 
|  | } | 
|  | m, ok := j.(map[string]any) | 
|  | if !ok { | 
|  | t.Error("colors.String() didn't produce a map.") | 
|  | } | 
|  | red := m["red"] | 
|  | x, ok := red.(float64) | 
|  | if !ok { | 
|  | t.Error("red.Kind() is not a number.") | 
|  | } | 
|  | if x != 3 { | 
|  | t.Errorf("red = %v, want 3", x) | 
|  | } | 
|  | } | 
|  |  | 
|  | func TestMapNil(t *testing.T) { | 
|  | RemoveAll() | 
|  | const key = "key" | 
|  | m := NewMap("issue527719") | 
|  | m.Set(key, nil) | 
|  | s := m.String() | 
|  | var j any | 
|  | if err := json.Unmarshal([]byte(s), &j); err != nil { | 
|  | t.Fatalf("m.String() == %q isn't valid JSON: %v", s, err) | 
|  | } | 
|  | m2, ok := j.(map[string]any) | 
|  | if !ok { | 
|  | t.Fatalf("m.String() produced %T, wanted a map", j) | 
|  | } | 
|  | v, ok := m2[key] | 
|  | if !ok { | 
|  | t.Fatalf("missing %q in %v", key, m2) | 
|  | } | 
|  | if v != nil { | 
|  | t.Fatalf("m[%q] = %v, want nil", key, v) | 
|  | } | 
|  | } | 
|  |  | 
|  | func BenchmarkMapSet(b *testing.B) { | 
|  | m := new(Map).Init() | 
|  |  | 
|  | v := new(Int) | 
|  |  | 
|  | b.RunParallel(func(pb *testing.PB) { | 
|  | for pb.Next() { | 
|  | m.Set("red", v) | 
|  | } | 
|  | }) | 
|  | } | 
|  |  | 
|  | func BenchmarkMapSetDifferent(b *testing.B) { | 
|  | procKeys := make([][]string, runtime.GOMAXPROCS(0)) | 
|  | for i := range procKeys { | 
|  | keys := make([]string, 4) | 
|  | for j := range keys { | 
|  | keys[j] = fmt.Sprint(i, j) | 
|  | } | 
|  | procKeys[i] = keys | 
|  | } | 
|  |  | 
|  | m := new(Map).Init() | 
|  | v := new(Int) | 
|  | b.ResetTimer() | 
|  |  | 
|  | var n int32 | 
|  | b.RunParallel(func(pb *testing.PB) { | 
|  | i := int(atomic.AddInt32(&n, 1)-1) % len(procKeys) | 
|  | keys := procKeys[i] | 
|  |  | 
|  | for pb.Next() { | 
|  | for _, k := range keys { | 
|  | m.Set(k, v) | 
|  | } | 
|  | } | 
|  | }) | 
|  | } | 
|  |  | 
|  | // BenchmarkMapSetDifferentRandom simulates such a case where the concerned | 
|  | // keys of Map.Set are generated dynamically and as a result insertion is | 
|  | // out of order and the number of the keys may be large. | 
|  | func BenchmarkMapSetDifferentRandom(b *testing.B) { | 
|  | keys := make([]string, 100) | 
|  | for i := range keys { | 
|  | keys[i] = fmt.Sprintf("%x", sha1.Sum([]byte(fmt.Sprint(i)))) | 
|  | } | 
|  |  | 
|  | v := new(Int) | 
|  | b.ResetTimer() | 
|  |  | 
|  | for i := 0; i < b.N; i++ { | 
|  | m := new(Map).Init() | 
|  | for _, k := range keys { | 
|  | m.Set(k, v) | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | func BenchmarkMapSetString(b *testing.B) { | 
|  | m := new(Map).Init() | 
|  |  | 
|  | v := new(String) | 
|  | v.Set("Hello, !") | 
|  |  | 
|  | b.RunParallel(func(pb *testing.PB) { | 
|  | for pb.Next() { | 
|  | m.Set("red", v) | 
|  | } | 
|  | }) | 
|  | } | 
|  |  | 
|  | func BenchmarkMapAddSame(b *testing.B) { | 
|  | b.RunParallel(func(pb *testing.PB) { | 
|  | for pb.Next() { | 
|  | m := new(Map).Init() | 
|  | m.Add("red", 1) | 
|  | m.Add("red", 1) | 
|  | m.Add("red", 1) | 
|  | m.Add("red", 1) | 
|  | } | 
|  | }) | 
|  | } | 
|  |  | 
|  | func BenchmarkMapAddDifferent(b *testing.B) { | 
|  | procKeys := make([][]string, runtime.GOMAXPROCS(0)) | 
|  | for i := range procKeys { | 
|  | keys := make([]string, 4) | 
|  | for j := range keys { | 
|  | keys[j] = fmt.Sprint(i, j) | 
|  | } | 
|  | procKeys[i] = keys | 
|  | } | 
|  |  | 
|  | b.ResetTimer() | 
|  |  | 
|  | var n int32 | 
|  | b.RunParallel(func(pb *testing.PB) { | 
|  | i := int(atomic.AddInt32(&n, 1)-1) % len(procKeys) | 
|  | keys := procKeys[i] | 
|  |  | 
|  | for pb.Next() { | 
|  | m := new(Map).Init() | 
|  | for _, k := range keys { | 
|  | m.Add(k, 1) | 
|  | } | 
|  | } | 
|  | }) | 
|  | } | 
|  |  | 
|  | // BenchmarkMapAddDifferentRandom simulates such a case where that the concerned | 
|  | // keys of Map.Add are generated dynamically and as a result insertion is out of | 
|  | // order and the number of the keys may be large. | 
|  | func BenchmarkMapAddDifferentRandom(b *testing.B) { | 
|  | keys := make([]string, 100) | 
|  | for i := range keys { | 
|  | keys[i] = fmt.Sprintf("%x", sha1.Sum([]byte(fmt.Sprint(i)))) | 
|  | } | 
|  |  | 
|  | b.ResetTimer() | 
|  |  | 
|  | for i := 0; i < b.N; i++ { | 
|  | m := new(Map).Init() | 
|  | for _, k := range keys { | 
|  | m.Add(k, 1) | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | func BenchmarkMapAddSameSteadyState(b *testing.B) { | 
|  | m := new(Map).Init() | 
|  | b.RunParallel(func(pb *testing.PB) { | 
|  | for pb.Next() { | 
|  | m.Add("red", 1) | 
|  | } | 
|  | }) | 
|  | } | 
|  |  | 
|  | func BenchmarkMapAddDifferentSteadyState(b *testing.B) { | 
|  | procKeys := make([][]string, runtime.GOMAXPROCS(0)) | 
|  | for i := range procKeys { | 
|  | keys := make([]string, 4) | 
|  | for j := range keys { | 
|  | keys[j] = fmt.Sprint(i, j) | 
|  | } | 
|  | procKeys[i] = keys | 
|  | } | 
|  |  | 
|  | m := new(Map).Init() | 
|  | b.ResetTimer() | 
|  |  | 
|  | var n int32 | 
|  | b.RunParallel(func(pb *testing.PB) { | 
|  | i := int(atomic.AddInt32(&n, 1)-1) % len(procKeys) | 
|  | keys := procKeys[i] | 
|  |  | 
|  | for pb.Next() { | 
|  | for _, k := range keys { | 
|  | m.Add(k, 1) | 
|  | } | 
|  | } | 
|  | }) | 
|  | } | 
|  |  | 
|  | func TestFunc(t *testing.T) { | 
|  | RemoveAll() | 
|  | var x any = []string{"a", "b"} | 
|  | f := Func(func() any { return x }) | 
|  | if s, exp := f.String(), `["a","b"]`; s != exp { | 
|  | t.Errorf(`f.String() = %q, want %q`, s, exp) | 
|  | } | 
|  | if v := f.Value(); !reflect.DeepEqual(v, x) { | 
|  | t.Errorf(`f.Value() = %q, want %q`, v, x) | 
|  | } | 
|  |  | 
|  | x = 17 | 
|  | if s, exp := f.String(), `17`; s != exp { | 
|  | t.Errorf(`f.String() = %q, want %q`, s, exp) | 
|  | } | 
|  | } | 
|  |  | 
|  | func TestHandler(t *testing.T) { | 
|  | RemoveAll() | 
|  | m := NewMap("map1") | 
|  | m.Add("a", 1) | 
|  | m.Add("z", 2) | 
|  | m2 := NewMap("map2") | 
|  | for i := 0; i < 9; i++ { | 
|  | m2.Add(strconv.Itoa(i), int64(i)) | 
|  | } | 
|  | rr := httptest.NewRecorder() | 
|  | rr.Body = new(bytes.Buffer) | 
|  | expvarHandler(rr, nil) | 
|  | want := `{ | 
|  | "map1": {"a": 1, "z": 2}, | 
|  | "map2": {"0": 0, "1": 1, "2": 2, "3": 3, "4": 4, "5": 5, "6": 6, "7": 7, "8": 8} | 
|  | } | 
|  | ` | 
|  | if got := rr.Body.String(); got != want { | 
|  | t.Errorf("HTTP handler wrote:\n%s\nWant:\n%s", got, want) | 
|  | } | 
|  | } | 
|  |  | 
|  | func BenchmarkRealworldExpvarUsage(b *testing.B) { | 
|  | var ( | 
|  | bytesSent Int | 
|  | bytesRead Int | 
|  | ) | 
|  |  | 
|  | // The benchmark creates GOMAXPROCS client/server pairs. | 
|  | // Each pair creates 4 goroutines: client reader/writer and server reader/writer. | 
|  | // The benchmark stresses concurrent reading and writing to the same connection. | 
|  | // Such pattern is used in net/http and net/rpc. | 
|  |  | 
|  | b.StopTimer() | 
|  |  | 
|  | P := runtime.GOMAXPROCS(0) | 
|  | N := b.N / P | 
|  | W := 1000 | 
|  |  | 
|  | // Setup P client/server connections. | 
|  | clients := make([]net.Conn, P) | 
|  | servers := make([]net.Conn, P) | 
|  | ln, err := net.Listen("tcp", "127.0.0.1:0") | 
|  | if err != nil { | 
|  | b.Fatalf("Listen failed: %v", err) | 
|  | } | 
|  | defer ln.Close() | 
|  | done := make(chan bool, 1) | 
|  | go func() { | 
|  | for p := 0; p < P; p++ { | 
|  | s, err := ln.Accept() | 
|  | if err != nil { | 
|  | b.Errorf("Accept failed: %v", err) | 
|  | done <- false | 
|  | return | 
|  | } | 
|  | servers[p] = s | 
|  | } | 
|  | done <- true | 
|  | }() | 
|  | for p := 0; p < P; p++ { | 
|  | c, err := net.Dial("tcp", ln.Addr().String()) | 
|  | if err != nil { | 
|  | <-done | 
|  | b.Fatalf("Dial failed: %v", err) | 
|  | } | 
|  | clients[p] = c | 
|  | } | 
|  | if !<-done { | 
|  | b.FailNow() | 
|  | } | 
|  |  | 
|  | b.StartTimer() | 
|  |  | 
|  | var wg sync.WaitGroup | 
|  | wg.Add(4 * P) | 
|  | for p := 0; p < P; p++ { | 
|  | // Client writer. | 
|  | go func(c net.Conn) { | 
|  | defer wg.Done() | 
|  | var buf [1]byte | 
|  | for i := 0; i < N; i++ { | 
|  | v := byte(i) | 
|  | for w := 0; w < W; w++ { | 
|  | v *= v | 
|  | } | 
|  | buf[0] = v | 
|  | n, err := c.Write(buf[:]) | 
|  | if err != nil { | 
|  | b.Errorf("Write failed: %v", err) | 
|  | return | 
|  | } | 
|  |  | 
|  | bytesSent.Add(int64(n)) | 
|  | } | 
|  | }(clients[p]) | 
|  |  | 
|  | // Pipe between server reader and server writer. | 
|  | pipe := make(chan byte, 128) | 
|  |  | 
|  | // Server reader. | 
|  | go func(s net.Conn) { | 
|  | defer wg.Done() | 
|  | var buf [1]byte | 
|  | for i := 0; i < N; i++ { | 
|  | n, err := s.Read(buf[:]) | 
|  |  | 
|  | if err != nil { | 
|  | b.Errorf("Read failed: %v", err) | 
|  | return | 
|  | } | 
|  |  | 
|  | bytesRead.Add(int64(n)) | 
|  | pipe <- buf[0] | 
|  | } | 
|  | }(servers[p]) | 
|  |  | 
|  | // Server writer. | 
|  | go func(s net.Conn) { | 
|  | defer wg.Done() | 
|  | var buf [1]byte | 
|  | for i := 0; i < N; i++ { | 
|  | v := <-pipe | 
|  | for w := 0; w < W; w++ { | 
|  | v *= v | 
|  | } | 
|  | buf[0] = v | 
|  | n, err := s.Write(buf[:]) | 
|  | if err != nil { | 
|  | b.Errorf("Write failed: %v", err) | 
|  | return | 
|  | } | 
|  |  | 
|  | bytesSent.Add(int64(n)) | 
|  | } | 
|  | s.Close() | 
|  | }(servers[p]) | 
|  |  | 
|  | // Client reader. | 
|  | go func(c net.Conn) { | 
|  | defer wg.Done() | 
|  | var buf [1]byte | 
|  | for i := 0; i < N; i++ { | 
|  | n, err := c.Read(buf[:]) | 
|  |  | 
|  | if err != nil { | 
|  | b.Errorf("Read failed: %v", err) | 
|  | return | 
|  | } | 
|  |  | 
|  | bytesRead.Add(int64(n)) | 
|  | } | 
|  | c.Close() | 
|  | }(clients[p]) | 
|  | } | 
|  | wg.Wait() | 
|  | } |