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