diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-16 19:25:22 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-16 19:25:22 +0000 |
commit | f6ad4dcef54c5ce997a4bad5a6d86de229015700 (patch) | |
tree | 7cfa4e31ace5c2bd95c72b154d15af494b2bcbef /src/expvar/expvar_test.go | |
parent | Initial commit. (diff) | |
download | golang-1.22-f6ad4dcef54c5ce997a4bad5a6d86de229015700.tar.xz golang-1.22-f6ad4dcef54c5ce997a4bad5a6d86de229015700.zip |
Adding upstream version 1.22.1.upstream/1.22.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/expvar/expvar_test.go')
-rw-r--r-- | src/expvar/expvar_test.go | 663 |
1 files changed, 663 insertions, 0 deletions
diff --git a/src/expvar/expvar_test.go b/src/expvar/expvar_test.go new file mode 100644 index 0000000..b827c4d --- /dev/null +++ b/src/expvar/expvar_test.go @@ -0,0 +1,663 @@ +// 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() { + vars.keysMu.Lock() + defer vars.keysMu.Unlock() + for _, k := range vars.keys { + vars.m.Delete(k) + } + vars.keys = 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 BenchmarkMapString(b *testing.B) { + var m, m1, m2 Map + m.Set("map1", &m1) + m1.Add("a", 1) + m1.Add("z", 2) + m.Set("map2", &m2) + for i := 0; i < 9; i++ { + m2.Add(strconv.Itoa(i), int64(i)) + } + var s1, s2 String + m.Set("str1", &s1) + s1.Set("hello, world!") + m.Set("str2", &s2) + s2.Set("fizz buzz") + b.ResetTimer() + + b.ReportAllocs() + for i := 0; i < b.N; i++ { + _ = m.String() + } +} + +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() +} + +func TestAppendJSONQuote(t *testing.T) { + var b []byte + for i := 0; i < 128; i++ { + b = append(b, byte(i)) + } + b = append(b, "\u2028\u2029"...) + got := string(appendJSONQuote(nil, string(b[:]))) + want := `"` + + `\u0000\u0001\u0002\u0003\u0004\u0005\u0006\u0007\u0008\t\n\u000b\u000c\r\u000e\u000f` + + `\u0010\u0011\u0012\u0013\u0014\u0015\u0016\u0017\u0018\u0019\u001a\u001b\u001c\u001d\u001e\u001f` + + ` !\"#$%\u0026'()*+,-./0123456789:;\u003c=\u003e?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_` + + "`" + `abcdefghijklmnopqrstuvwxyz{|}~` + "\x7f" + `\u2028\u2029"` + if got != want { + t.Errorf("appendJSONQuote mismatch:\ngot %v\nwant %v", got, want) + } +} |