Source file src/expvar/expvar_test.go

     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  
     5  package expvar
     6  
     7  import (
     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.
    24  func 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  
    33  func 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  
    41  func 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  
    67  func 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  
    77  func 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  
    87  func 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  
   113  func 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  
   123  func 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  
   133  func 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  
   155  func 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  
   165  func 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  
   187  func 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  	n = 0
   203  	colors.Do(func(KeyValue) { n++ })
   204  	if n != 1 {
   205  		t.Errorf("removed red, Do should invoke f 1 times; got %v", n)
   206  	}
   207  
   208  	colors.Delete("notfound")
   209  	n = 0
   210  	colors.Do(func(KeyValue) { n++ })
   211  	if n != 1 {
   212  		t.Errorf("attempted to remove notfound, Do should invoke f 1 times; got %v", n)
   213  	}
   214  
   215  	colors.Delete("blue")
   216  	colors.Delete("blue")
   217  	n = 0
   218  	colors.Do(func(KeyValue) { n++ })
   219  	if n != 0 {
   220  		t.Errorf("all keys removed, Do should invoke f 0 times; got %v", n)
   221  	}
   222  }
   223  
   224  func TestMapCounter(t *testing.T) {
   225  	RemoveAll()
   226  	colors := NewMap("bike-shed-colors")
   227  
   228  	colors.Add("red", 1)
   229  	colors.Add("red", 2)
   230  	colors.Add("blue", 4)
   231  	colors.AddFloat(`green "midori"`, 4.125)
   232  	if x := colors.Get("red").(*Int).Value(); x != 3 {
   233  		t.Errorf("colors.m[\"red\"] = %v, want 3", x)
   234  	}
   235  	if x := colors.Get("blue").(*Int).Value(); x != 4 {
   236  		t.Errorf("colors.m[\"blue\"] = %v, want 4", x)
   237  	}
   238  	if x := colors.Get(`green "midori"`).(*Float).Value(); x != 4.125 {
   239  		t.Errorf("colors.m[`green \"midori\"] = %v, want 4.125", x)
   240  	}
   241  
   242  	// colors.String() should be '{"red":3, "blue":4}',
   243  	// though the order of red and blue could vary.
   244  	s := colors.String()
   245  	var j any
   246  	err := json.Unmarshal([]byte(s), &j)
   247  	if err != nil {
   248  		t.Errorf("colors.String() isn't valid JSON: %v", err)
   249  	}
   250  	m, ok := j.(map[string]any)
   251  	if !ok {
   252  		t.Error("colors.String() didn't produce a map.")
   253  	}
   254  	red := m["red"]
   255  	x, ok := red.(float64)
   256  	if !ok {
   257  		t.Error("red.Kind() is not a number.")
   258  	}
   259  	if x != 3 {
   260  		t.Errorf("red = %v, want 3", x)
   261  	}
   262  }
   263  
   264  func TestMapNil(t *testing.T) {
   265  	RemoveAll()
   266  	const key = "key"
   267  	m := NewMap("issue527719")
   268  	m.Set(key, nil)
   269  	s := m.String()
   270  	var j any
   271  	if err := json.Unmarshal([]byte(s), &j); err != nil {
   272  		t.Fatalf("m.String() == %q isn't valid JSON: %v", s, err)
   273  	}
   274  	m2, ok := j.(map[string]any)
   275  	if !ok {
   276  		t.Fatalf("m.String() produced %T, wanted a map", j)
   277  	}
   278  	v, ok := m2[key]
   279  	if !ok {
   280  		t.Fatalf("missing %q in %v", key, m2)
   281  	}
   282  	if v != nil {
   283  		t.Fatalf("m[%q] = %v, want nil", key, v)
   284  	}
   285  }
   286  
   287  func BenchmarkMapSet(b *testing.B) {
   288  	m := new(Map).Init()
   289  
   290  	v := new(Int)
   291  
   292  	b.RunParallel(func(pb *testing.PB) {
   293  		for pb.Next() {
   294  			m.Set("red", v)
   295  		}
   296  	})
   297  }
   298  
   299  func BenchmarkMapSetDifferent(b *testing.B) {
   300  	procKeys := make([][]string, runtime.GOMAXPROCS(0))
   301  	for i := range procKeys {
   302  		keys := make([]string, 4)
   303  		for j := range keys {
   304  			keys[j] = fmt.Sprint(i, j)
   305  		}
   306  		procKeys[i] = keys
   307  	}
   308  
   309  	m := new(Map).Init()
   310  	v := new(Int)
   311  	b.ResetTimer()
   312  
   313  	var n int32
   314  	b.RunParallel(func(pb *testing.PB) {
   315  		i := int(atomic.AddInt32(&n, 1)-1) % len(procKeys)
   316  		keys := procKeys[i]
   317  
   318  		for pb.Next() {
   319  			for _, k := range keys {
   320  				m.Set(k, v)
   321  			}
   322  		}
   323  	})
   324  }
   325  
   326  // BenchmarkMapSetDifferentRandom simulates such a case where the concerned
   327  // keys of Map.Set are generated dynamically and as a result insertion is
   328  // out of order and the number of the keys may be large.
   329  func BenchmarkMapSetDifferentRandom(b *testing.B) {
   330  	keys := make([]string, 100)
   331  	for i := range keys {
   332  		keys[i] = fmt.Sprintf("%x", sha1.Sum([]byte(fmt.Sprint(i))))
   333  	}
   334  
   335  	v := new(Int)
   336  	b.ResetTimer()
   337  
   338  	for i := 0; i < b.N; i++ {
   339  		m := new(Map).Init()
   340  		for _, k := range keys {
   341  			m.Set(k, v)
   342  		}
   343  	}
   344  }
   345  
   346  func BenchmarkMapSetString(b *testing.B) {
   347  	m := new(Map).Init()
   348  
   349  	v := new(String)
   350  	v.Set("Hello, !")
   351  
   352  	b.RunParallel(func(pb *testing.PB) {
   353  		for pb.Next() {
   354  			m.Set("red", v)
   355  		}
   356  	})
   357  }
   358  
   359  func BenchmarkMapAddSame(b *testing.B) {
   360  	b.RunParallel(func(pb *testing.PB) {
   361  		for pb.Next() {
   362  			m := new(Map).Init()
   363  			m.Add("red", 1)
   364  			m.Add("red", 1)
   365  			m.Add("red", 1)
   366  			m.Add("red", 1)
   367  		}
   368  	})
   369  }
   370  
   371  func BenchmarkMapAddDifferent(b *testing.B) {
   372  	procKeys := make([][]string, runtime.GOMAXPROCS(0))
   373  	for i := range procKeys {
   374  		keys := make([]string, 4)
   375  		for j := range keys {
   376  			keys[j] = fmt.Sprint(i, j)
   377  		}
   378  		procKeys[i] = keys
   379  	}
   380  
   381  	b.ResetTimer()
   382  
   383  	var n int32
   384  	b.RunParallel(func(pb *testing.PB) {
   385  		i := int(atomic.AddInt32(&n, 1)-1) % len(procKeys)
   386  		keys := procKeys[i]
   387  
   388  		for pb.Next() {
   389  			m := new(Map).Init()
   390  			for _, k := range keys {
   391  				m.Add(k, 1)
   392  			}
   393  		}
   394  	})
   395  }
   396  
   397  // BenchmarkMapAddDifferentRandom simulates such a case where that the concerned
   398  // keys of Map.Add are generated dynamically and as a result insertion is out of
   399  // order and the number of the keys may be large.
   400  func BenchmarkMapAddDifferentRandom(b *testing.B) {
   401  	keys := make([]string, 100)
   402  	for i := range keys {
   403  		keys[i] = fmt.Sprintf("%x", sha1.Sum([]byte(fmt.Sprint(i))))
   404  	}
   405  
   406  	b.ResetTimer()
   407  
   408  	for i := 0; i < b.N; i++ {
   409  		m := new(Map).Init()
   410  		for _, k := range keys {
   411  			m.Add(k, 1)
   412  		}
   413  	}
   414  }
   415  
   416  func BenchmarkMapAddSameSteadyState(b *testing.B) {
   417  	m := new(Map).Init()
   418  	b.RunParallel(func(pb *testing.PB) {
   419  		for pb.Next() {
   420  			m.Add("red", 1)
   421  		}
   422  	})
   423  }
   424  
   425  func BenchmarkMapAddDifferentSteadyState(b *testing.B) {
   426  	procKeys := make([][]string, runtime.GOMAXPROCS(0))
   427  	for i := range procKeys {
   428  		keys := make([]string, 4)
   429  		for j := range keys {
   430  			keys[j] = fmt.Sprint(i, j)
   431  		}
   432  		procKeys[i] = keys
   433  	}
   434  
   435  	m := new(Map).Init()
   436  	b.ResetTimer()
   437  
   438  	var n int32
   439  	b.RunParallel(func(pb *testing.PB) {
   440  		i := int(atomic.AddInt32(&n, 1)-1) % len(procKeys)
   441  		keys := procKeys[i]
   442  
   443  		for pb.Next() {
   444  			for _, k := range keys {
   445  				m.Add(k, 1)
   446  			}
   447  		}
   448  	})
   449  }
   450  
   451  func TestFunc(t *testing.T) {
   452  	RemoveAll()
   453  	var x any = []string{"a", "b"}
   454  	f := Func(func() any { return x })
   455  	if s, exp := f.String(), `["a","b"]`; s != exp {
   456  		t.Errorf(`f.String() = %q, want %q`, s, exp)
   457  	}
   458  	if v := f.Value(); !reflect.DeepEqual(v, x) {
   459  		t.Errorf(`f.Value() = %q, want %q`, v, x)
   460  	}
   461  
   462  	x = 17
   463  	if s, exp := f.String(), `17`; s != exp {
   464  		t.Errorf(`f.String() = %q, want %q`, s, exp)
   465  	}
   466  }
   467  
   468  func TestHandler(t *testing.T) {
   469  	RemoveAll()
   470  	m := NewMap("map1")
   471  	m.Add("a", 1)
   472  	m.Add("z", 2)
   473  	m2 := NewMap("map2")
   474  	for i := 0; i < 9; i++ {
   475  		m2.Add(strconv.Itoa(i), int64(i))
   476  	}
   477  	rr := httptest.NewRecorder()
   478  	rr.Body = new(bytes.Buffer)
   479  	expvarHandler(rr, nil)
   480  	want := `{
   481  "map1": {"a": 1, "z": 2},
   482  "map2": {"0": 0, "1": 1, "2": 2, "3": 3, "4": 4, "5": 5, "6": 6, "7": 7, "8": 8}
   483  }
   484  `
   485  	if got := rr.Body.String(); got != want {
   486  		t.Errorf("HTTP handler wrote:\n%s\nWant:\n%s", got, want)
   487  	}
   488  }
   489  
   490  func BenchmarkMapString(b *testing.B) {
   491  	var m, m1, m2 Map
   492  	m.Set("map1", &m1)
   493  	m1.Add("a", 1)
   494  	m1.Add("z", 2)
   495  	m.Set("map2", &m2)
   496  	for i := 0; i < 9; i++ {
   497  		m2.Add(strconv.Itoa(i), int64(i))
   498  	}
   499  	var s1, s2 String
   500  	m.Set("str1", &s1)
   501  	s1.Set("hello, world!")
   502  	m.Set("str2", &s2)
   503  	s2.Set("fizz buzz")
   504  	b.ResetTimer()
   505  
   506  	b.ReportAllocs()
   507  	for i := 0; i < b.N; i++ {
   508  		_ = m.String()
   509  	}
   510  }
   511  
   512  func BenchmarkRealworldExpvarUsage(b *testing.B) {
   513  	var (
   514  		bytesSent Int
   515  		bytesRead Int
   516  	)
   517  
   518  	// The benchmark creates GOMAXPROCS client/server pairs.
   519  	// Each pair creates 4 goroutines: client reader/writer and server reader/writer.
   520  	// The benchmark stresses concurrent reading and writing to the same connection.
   521  	// Such pattern is used in net/http and net/rpc.
   522  
   523  	b.StopTimer()
   524  
   525  	P := runtime.GOMAXPROCS(0)
   526  	N := b.N / P
   527  	W := 1000
   528  
   529  	// Setup P client/server connections.
   530  	clients := make([]net.Conn, P)
   531  	servers := make([]net.Conn, P)
   532  	ln, err := net.Listen("tcp", "127.0.0.1:0")
   533  	if err != nil {
   534  		b.Fatalf("Listen failed: %v", err)
   535  	}
   536  	defer ln.Close()
   537  	done := make(chan bool, 1)
   538  	go func() {
   539  		for p := 0; p < P; p++ {
   540  			s, err := ln.Accept()
   541  			if err != nil {
   542  				b.Errorf("Accept failed: %v", err)
   543  				done <- false
   544  				return
   545  			}
   546  			servers[p] = s
   547  		}
   548  		done <- true
   549  	}()
   550  	for p := 0; p < P; p++ {
   551  		c, err := net.Dial("tcp", ln.Addr().String())
   552  		if err != nil {
   553  			<-done
   554  			b.Fatalf("Dial failed: %v", err)
   555  		}
   556  		clients[p] = c
   557  	}
   558  	if !<-done {
   559  		b.FailNow()
   560  	}
   561  
   562  	b.StartTimer()
   563  
   564  	var wg sync.WaitGroup
   565  	wg.Add(4 * P)
   566  	for p := 0; p < P; p++ {
   567  		// Client writer.
   568  		go func(c net.Conn) {
   569  			defer wg.Done()
   570  			var buf [1]byte
   571  			for i := 0; i < N; i++ {
   572  				v := byte(i)
   573  				for w := 0; w < W; w++ {
   574  					v *= v
   575  				}
   576  				buf[0] = v
   577  				n, err := c.Write(buf[:])
   578  				if err != nil {
   579  					b.Errorf("Write failed: %v", err)
   580  					return
   581  				}
   582  
   583  				bytesSent.Add(int64(n))
   584  			}
   585  		}(clients[p])
   586  
   587  		// Pipe between server reader and server writer.
   588  		pipe := make(chan byte, 128)
   589  
   590  		// Server reader.
   591  		go func(s net.Conn) {
   592  			defer wg.Done()
   593  			var buf [1]byte
   594  			for i := 0; i < N; i++ {
   595  				n, err := s.Read(buf[:])
   596  
   597  				if err != nil {
   598  					b.Errorf("Read failed: %v", err)
   599  					return
   600  				}
   601  
   602  				bytesRead.Add(int64(n))
   603  				pipe <- buf[0]
   604  			}
   605  		}(servers[p])
   606  
   607  		// Server writer.
   608  		go func(s net.Conn) {
   609  			defer wg.Done()
   610  			var buf [1]byte
   611  			for i := 0; i < N; i++ {
   612  				v := <-pipe
   613  				for w := 0; w < W; w++ {
   614  					v *= v
   615  				}
   616  				buf[0] = v
   617  				n, err := s.Write(buf[:])
   618  				if err != nil {
   619  					b.Errorf("Write failed: %v", err)
   620  					return
   621  				}
   622  
   623  				bytesSent.Add(int64(n))
   624  			}
   625  			s.Close()
   626  		}(servers[p])
   627  
   628  		// Client reader.
   629  		go func(c net.Conn) {
   630  			defer wg.Done()
   631  			var buf [1]byte
   632  			for i := 0; i < N; i++ {
   633  				n, err := c.Read(buf[:])
   634  
   635  				if err != nil {
   636  					b.Errorf("Read failed: %v", err)
   637  					return
   638  				}
   639  
   640  				bytesRead.Add(int64(n))
   641  			}
   642  			c.Close()
   643  		}(clients[p])
   644  	}
   645  	wg.Wait()
   646  }
   647  
   648  func TestAppendJSONQuote(t *testing.T) {
   649  	var b []byte
   650  	for i := 0; i < 128; i++ {
   651  		b = append(b, byte(i))
   652  	}
   653  	b = append(b, "\u2028\u2029"...)
   654  	got := string(appendJSONQuote(nil, string(b[:])))
   655  	want := `"` +
   656  		`\u0000\u0001\u0002\u0003\u0004\u0005\u0006\u0007\u0008\t\n\u000b\u000c\r\u000e\u000f` +
   657  		`\u0010\u0011\u0012\u0013\u0014\u0015\u0016\u0017\u0018\u0019\u001a\u001b\u001c\u001d\u001e\u001f` +
   658  		` !\"#$%\u0026'()*+,-./0123456789:;\u003c=\u003e?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_` +
   659  		"`" + `abcdefghijklmnopqrstuvwxyz{|}~` + "\x7f" + `\u2028\u2029"`
   660  	if got != want {
   661  		t.Errorf("appendJSONQuote mismatch:\ngot  %v\nwant %v", got, want)
   662  	}
   663  }
   664  

View as plain text