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  	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  
   230  func 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  
   270  func 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  
   293  func 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  
   305  func 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.
   335  func 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  
   352  func 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  
   365  func 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  
   377  func 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.
   406  func 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  
   422  func 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  
   431  func 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  
   457  func 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  
   474  func 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  
   496  func 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  
   518  func 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  
   654  func 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  

View as plain text