github.com/sbinet/go@v0.0.0-20160827155028-54d7de7dd62b/src/testing/sub_test.go (about)

     1  // Copyright 2016 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 testing
     6  
     7  import (
     8  	"bytes"
     9  	"regexp"
    10  	"strings"
    11  	"sync/atomic"
    12  	"time"
    13  )
    14  
    15  func TestTestContext(t *T) {
    16  	const (
    17  		add1 = 0
    18  		done = 1
    19  	)
    20  	// After each of the calls are applied to the context, the
    21  	type call struct {
    22  		typ int // run or done
    23  		// result from applying the call
    24  		running int
    25  		waiting int
    26  		started bool
    27  	}
    28  	testCases := []struct {
    29  		max int
    30  		run []call
    31  	}{{
    32  		max: 1,
    33  		run: []call{
    34  			{typ: add1, running: 1, waiting: 0, started: true},
    35  			{typ: done, running: 0, waiting: 0, started: false},
    36  		},
    37  	}, {
    38  		max: 1,
    39  		run: []call{
    40  			{typ: add1, running: 1, waiting: 0, started: true},
    41  			{typ: add1, running: 1, waiting: 1, started: false},
    42  			{typ: done, running: 1, waiting: 0, started: true},
    43  			{typ: done, running: 0, waiting: 0, started: false},
    44  			{typ: add1, running: 1, waiting: 0, started: true},
    45  		},
    46  	}, {
    47  		max: 3,
    48  		run: []call{
    49  			{typ: add1, running: 1, waiting: 0, started: true},
    50  			{typ: add1, running: 2, waiting: 0, started: true},
    51  			{typ: add1, running: 3, waiting: 0, started: true},
    52  			{typ: add1, running: 3, waiting: 1, started: false},
    53  			{typ: add1, running: 3, waiting: 2, started: false},
    54  			{typ: add1, running: 3, waiting: 3, started: false},
    55  			{typ: done, running: 3, waiting: 2, started: true},
    56  			{typ: add1, running: 3, waiting: 3, started: false},
    57  			{typ: done, running: 3, waiting: 2, started: true},
    58  			{typ: done, running: 3, waiting: 1, started: true},
    59  			{typ: done, running: 3, waiting: 0, started: true},
    60  			{typ: done, running: 2, waiting: 0, started: false},
    61  			{typ: done, running: 1, waiting: 0, started: false},
    62  			{typ: done, running: 0, waiting: 0, started: false},
    63  		},
    64  	}}
    65  	for i, tc := range testCases {
    66  		ctx := &testContext{
    67  			startParallel: make(chan bool),
    68  			maxParallel:   tc.max,
    69  		}
    70  		for j, call := range tc.run {
    71  			doCall := func(f func()) chan bool {
    72  				done := make(chan bool)
    73  				go func() {
    74  					f()
    75  					done <- true
    76  				}()
    77  				return done
    78  			}
    79  			started := false
    80  			switch call.typ {
    81  			case add1:
    82  				signal := doCall(ctx.waitParallel)
    83  				select {
    84  				case <-signal:
    85  					started = true
    86  				case ctx.startParallel <- true:
    87  					<-signal
    88  				}
    89  			case done:
    90  				signal := doCall(ctx.release)
    91  				select {
    92  				case <-signal:
    93  				case <-ctx.startParallel:
    94  					started = true
    95  					<-signal
    96  				}
    97  			}
    98  			if started != call.started {
    99  				t.Errorf("%d:%d:started: got %v; want %v", i, j, started, call.started)
   100  			}
   101  			if ctx.running != call.running {
   102  				t.Errorf("%d:%d:running: got %v; want %v", i, j, ctx.running, call.running)
   103  			}
   104  			if ctx.numWaiting != call.waiting {
   105  				t.Errorf("%d:%d:waiting: got %v; want %v", i, j, ctx.numWaiting, call.waiting)
   106  			}
   107  		}
   108  	}
   109  }
   110  
   111  func TestTRun(t *T) {
   112  	realTest := t
   113  	testCases := []struct {
   114  		desc   string
   115  		ok     bool
   116  		maxPar int
   117  		chatty bool
   118  		output string
   119  		f      func(*T)
   120  	}{{
   121  		desc:   "failnow skips future sequential and parallel tests at same level",
   122  		ok:     false,
   123  		maxPar: 1,
   124  		output: `
   125  --- FAIL: failnow skips future sequential and parallel tests at same level (N.NNs)
   126      --- FAIL: failnow skips future sequential and parallel tests at same level/#00 (N.NNs)
   127      `,
   128  		f: func(t *T) {
   129  			ranSeq := false
   130  			ranPar := false
   131  			t.Run("", func(t *T) {
   132  				t.Run("par", func(t *T) {
   133  					t.Parallel()
   134  					ranPar = true
   135  				})
   136  				t.Run("seq", func(t *T) {
   137  					ranSeq = true
   138  				})
   139  				t.FailNow()
   140  				t.Run("seq", func(t *T) {
   141  					realTest.Error("test must be skipped")
   142  				})
   143  				t.Run("par", func(t *T) {
   144  					t.Parallel()
   145  					realTest.Error("test must be skipped.")
   146  				})
   147  			})
   148  			if !ranPar {
   149  				realTest.Error("parallel test was not run")
   150  			}
   151  			if !ranSeq {
   152  				realTest.Error("sequential test was not run")
   153  			}
   154  		},
   155  	}, {
   156  		desc:   "failure in parallel test propagates upwards",
   157  		ok:     false,
   158  		maxPar: 1,
   159  		output: `
   160  --- FAIL: failure in parallel test propagates upwards (N.NNs)
   161      --- FAIL: failure in parallel test propagates upwards/#00 (N.NNs)
   162          --- FAIL: failure in parallel test propagates upwards/#00/par (N.NNs)
   163  		`,
   164  		f: func(t *T) {
   165  			t.Run("", func(t *T) {
   166  				t.Parallel()
   167  				t.Run("par", func(t *T) {
   168  					t.Parallel()
   169  					t.Fail()
   170  				})
   171  			})
   172  		},
   173  	}, {
   174  		desc:   "skipping without message, chatty",
   175  		ok:     true,
   176  		chatty: true,
   177  		output: `
   178  === RUN   skipping without message, chatty
   179  --- SKIP: skipping without message, chatty (N.NNs)`,
   180  		f: func(t *T) { t.SkipNow() },
   181  	}, {
   182  		desc:   "chatty with recursion",
   183  		ok:     true,
   184  		chatty: true,
   185  		output: `
   186  === RUN   chatty with recursion
   187  === RUN   chatty with recursion/#00
   188  === RUN   chatty with recursion/#00/#00
   189  --- PASS: chatty with recursion (N.NNs)
   190      --- PASS: chatty with recursion/#00 (N.NNs)
   191          --- PASS: chatty with recursion/#00/#00 (N.NNs)`,
   192  		f: func(t *T) {
   193  			t.Run("", func(t *T) {
   194  				t.Run("", func(t *T) {})
   195  			})
   196  		},
   197  	}, {
   198  		desc: "skipping without message, not chatty",
   199  		ok:   true,
   200  		f:    func(t *T) { t.SkipNow() },
   201  	}, {
   202  		desc: "skipping after error",
   203  		output: `
   204  --- FAIL: skipping after error (N.NNs)
   205  	sub_test.go:NNN: an error
   206  	sub_test.go:NNN: skipped`,
   207  		f: func(t *T) {
   208  			t.Error("an error")
   209  			t.Skip("skipped")
   210  		},
   211  	}, {
   212  		desc:   "use Run to locally synchronize parallelism",
   213  		ok:     true,
   214  		maxPar: 1,
   215  		f: func(t *T) {
   216  			var count uint32
   217  			t.Run("waitGroup", func(t *T) {
   218  				for i := 0; i < 4; i++ {
   219  					t.Run("par", func(t *T) {
   220  						t.Parallel()
   221  						atomic.AddUint32(&count, 1)
   222  					})
   223  				}
   224  			})
   225  			if count != 4 {
   226  				t.Errorf("count was %d; want 4", count)
   227  			}
   228  		},
   229  	}, {
   230  		desc: "alternate sequential and parallel",
   231  		// Sequential tests should partake in the counting of running threads.
   232  		// Otherwise, if one runs parallel subtests in sequential tests that are
   233  		// itself subtests of parallel tests, the counts can get askew.
   234  		ok:     true,
   235  		maxPar: 1,
   236  		f: func(t *T) {
   237  			t.Run("a", func(t *T) {
   238  				t.Parallel()
   239  				t.Run("b", func(t *T) {
   240  					// Sequential: ensure running count is decremented.
   241  					t.Run("c", func(t *T) {
   242  						t.Parallel()
   243  					})
   244  
   245  				})
   246  			})
   247  		},
   248  	}, {
   249  		desc: "alternate sequential and parallel 2",
   250  		// Sequential tests should partake in the counting of running threads.
   251  		// Otherwise, if one runs parallel subtests in sequential tests that are
   252  		// itself subtests of parallel tests, the counts can get askew.
   253  		ok:     true,
   254  		maxPar: 2,
   255  		f: func(t *T) {
   256  			for i := 0; i < 2; i++ {
   257  				t.Run("a", func(t *T) {
   258  					t.Parallel()
   259  					time.Sleep(time.Nanosecond)
   260  					for i := 0; i < 2; i++ {
   261  						t.Run("b", func(t *T) {
   262  							time.Sleep(time.Nanosecond)
   263  							for i := 0; i < 2; i++ {
   264  								t.Run("c", func(t *T) {
   265  									t.Parallel()
   266  									time.Sleep(time.Nanosecond)
   267  								})
   268  							}
   269  
   270  						})
   271  					}
   272  				})
   273  			}
   274  		},
   275  	}, {
   276  		desc:   "stress test",
   277  		ok:     true,
   278  		maxPar: 4,
   279  		f: func(t *T) {
   280  			t.Parallel()
   281  			for i := 0; i < 12; i++ {
   282  				t.Run("a", func(t *T) {
   283  					t.Parallel()
   284  					time.Sleep(time.Nanosecond)
   285  					for i := 0; i < 12; i++ {
   286  						t.Run("b", func(t *T) {
   287  							time.Sleep(time.Nanosecond)
   288  							for i := 0; i < 12; i++ {
   289  								t.Run("c", func(t *T) {
   290  									t.Parallel()
   291  									time.Sleep(time.Nanosecond)
   292  									t.Run("d1", func(t *T) {})
   293  									t.Run("d2", func(t *T) {})
   294  									t.Run("d3", func(t *T) {})
   295  									t.Run("d4", func(t *T) {})
   296  								})
   297  							}
   298  						})
   299  					}
   300  				})
   301  			}
   302  		},
   303  	}, {
   304  		desc:   "skip output",
   305  		ok:     true,
   306  		maxPar: 4,
   307  		f: func(t *T) {
   308  			t.Skip()
   309  		},
   310  	}, {
   311  		desc:   "panic on goroutine fail after test exit",
   312  		ok:     false,
   313  		maxPar: 4,
   314  		f: func(t *T) {
   315  			ch := make(chan bool)
   316  			t.Run("", func(t *T) {
   317  				go func() {
   318  					<-ch
   319  					defer func() {
   320  						if r := recover(); r == nil {
   321  							realTest.Errorf("expected panic")
   322  						}
   323  						ch <- true
   324  					}()
   325  					t.Errorf("failed after success")
   326  				}()
   327  			})
   328  			ch <- true
   329  			<-ch
   330  		},
   331  	}}
   332  	for _, tc := range testCases {
   333  		ctx := newTestContext(tc.maxPar, newMatcher(regexp.MatchString, "", ""))
   334  		buf := &bytes.Buffer{}
   335  		root := &T{
   336  			common: common{
   337  				signal: make(chan bool),
   338  				name:   "Test",
   339  				w:      buf,
   340  				chatty: tc.chatty,
   341  			},
   342  			context: ctx,
   343  		}
   344  		ok := root.Run(tc.desc, tc.f)
   345  		ctx.release()
   346  
   347  		if ok != tc.ok {
   348  			t.Errorf("%s:ok: got %v; want %v", tc.desc, ok, tc.ok)
   349  		}
   350  		if ok != !root.Failed() {
   351  			t.Errorf("%s:root failed: got %v; want %v", tc.desc, !ok, root.Failed())
   352  		}
   353  		if ctx.running != 0 || ctx.numWaiting != 0 {
   354  			t.Errorf("%s:running and waiting non-zero: got %d and %d", tc.desc, ctx.running, ctx.numWaiting)
   355  		}
   356  		got := strings.TrimSpace(buf.String())
   357  		want := strings.TrimSpace(tc.output)
   358  		re := makeRegexp(want)
   359  		if ok, err := regexp.MatchString(re, got); !ok || err != nil {
   360  			t.Errorf("%s:ouput:\ngot:\n%s\nwant:\n%s", tc.desc, got, want)
   361  		}
   362  	}
   363  }
   364  
   365  func TestBRun(t *T) {
   366  	work := func(b *B) {
   367  		for i := 0; i < b.N; i++ {
   368  			time.Sleep(time.Nanosecond)
   369  		}
   370  	}
   371  	testCases := []struct {
   372  		desc   string
   373  		failed bool
   374  		chatty bool
   375  		output string
   376  		f      func(*B)
   377  	}{{
   378  		desc: "simulate sequential run of subbenchmarks.",
   379  		f: func(b *B) {
   380  			b.Run("", func(b *B) { work(b) })
   381  			time1 := b.result.NsPerOp()
   382  			b.Run("", func(b *B) { work(b) })
   383  			time2 := b.result.NsPerOp()
   384  			if time1 >= time2 {
   385  				t.Errorf("no time spent in benchmark t1 >= t2 (%d >= %d)", time1, time2)
   386  			}
   387  		},
   388  	}, {
   389  		desc: "bytes set by all benchmarks",
   390  		f: func(b *B) {
   391  			b.Run("", func(b *B) { b.SetBytes(10); work(b) })
   392  			b.Run("", func(b *B) { b.SetBytes(10); work(b) })
   393  			if b.result.Bytes != 20 {
   394  				t.Errorf("bytes: got: %d; want 20", b.result.Bytes)
   395  			}
   396  		},
   397  	}, {
   398  		desc: "bytes set by some benchmarks",
   399  		// In this case the bytes result is meaningless, so it must be 0.
   400  		f: func(b *B) {
   401  			b.Run("", func(b *B) { b.SetBytes(10); work(b) })
   402  			b.Run("", func(b *B) { work(b) })
   403  			b.Run("", func(b *B) { b.SetBytes(10); work(b) })
   404  			if b.result.Bytes != 0 {
   405  				t.Errorf("bytes: got: %d; want 0", b.result.Bytes)
   406  			}
   407  		},
   408  	}, {
   409  		desc:   "failure carried over to root",
   410  		failed: true,
   411  		output: "--- FAIL: root",
   412  		f:      func(b *B) { b.Fail() },
   413  	}, {
   414  		desc:   "skipping without message, chatty",
   415  		chatty: true,
   416  		output: "--- SKIP: root",
   417  		f:      func(b *B) { b.SkipNow() },
   418  	}, {
   419  		desc:   "skipping with message, chatty",
   420  		chatty: true,
   421  		output: `
   422  --- SKIP: root
   423  	sub_test.go:NNN: skipping`,
   424  		f: func(b *B) { b.Skip("skipping") },
   425  	}, {
   426  		desc:   "chatty with recursion",
   427  		chatty: true,
   428  		f: func(b *B) {
   429  			b.Run("", func(b *B) {
   430  				b.Run("", func(b *B) {})
   431  			})
   432  		},
   433  	}, {
   434  		desc: "skipping without message, not chatty",
   435  		f:    func(b *B) { b.SkipNow() },
   436  	}, {
   437  		desc:   "skipping after error",
   438  		failed: true,
   439  		output: `
   440  --- FAIL: root
   441  	sub_test.go:NNN: an error
   442  	sub_test.go:NNN: skipped`,
   443  		f: func(b *B) {
   444  			b.Error("an error")
   445  			b.Skip("skipped")
   446  		},
   447  	}, {
   448  		desc: "memory allocation",
   449  		f: func(b *B) {
   450  			const bufSize = 256
   451  			alloc := func(b *B) {
   452  				var buf [bufSize]byte
   453  				for i := 0; i < b.N; i++ {
   454  					_ = append([]byte(nil), buf[:]...)
   455  				}
   456  			}
   457  			b.Run("", func(b *B) { alloc(b) })
   458  			b.Run("", func(b *B) { alloc(b) })
   459  			// runtime.MemStats sometimes reports more allocations than the
   460  			// benchmark is responsible for. Luckily the point of this test is
   461  			// to ensure that the results are not underreported, so we can
   462  			// simply verify the lower bound.
   463  			if got := b.result.MemAllocs; got < 2 {
   464  				t.Errorf("MemAllocs was %v; want 2", got)
   465  			}
   466  			if got := b.result.MemBytes; got < 2*bufSize {
   467  				t.Errorf("MemBytes was %v; want %v", got, 2*bufSize)
   468  			}
   469  		},
   470  	}}
   471  	for _, tc := range testCases {
   472  		var ok bool
   473  		buf := &bytes.Buffer{}
   474  		// This is almost like the Benchmark function, except that we override
   475  		// the benchtime and catch the failure result of the subbenchmark.
   476  		root := &B{
   477  			common: common{
   478  				signal: make(chan bool),
   479  				name:   "root",
   480  				w:      buf,
   481  				chatty: tc.chatty,
   482  			},
   483  			benchFunc: func(b *B) { ok = b.Run("test", tc.f) }, // Use Run to catch failure.
   484  			benchTime: time.Microsecond,
   485  		}
   486  		root.runN(1)
   487  		if ok != !tc.failed {
   488  			t.Errorf("%s:ok: got %v; want %v", tc.desc, ok, !tc.failed)
   489  		}
   490  		if !ok != root.Failed() {
   491  			t.Errorf("%s:root failed: got %v; want %v", tc.desc, !ok, root.Failed())
   492  		}
   493  		// All tests are run as subtests
   494  		if root.result.N != 1 {
   495  			t.Errorf("%s: N for parent benchmark was %d; want 1", tc.desc, root.result.N)
   496  		}
   497  		got := strings.TrimSpace(buf.String())
   498  		want := strings.TrimSpace(tc.output)
   499  		re := makeRegexp(want)
   500  		if ok, err := regexp.MatchString(re, got); !ok || err != nil {
   501  			t.Errorf("%s:ouput:\ngot:\n%s\nwant:\n%s", tc.desc, got, want)
   502  		}
   503  	}
   504  }
   505  
   506  func makeRegexp(s string) string {
   507  	s = strings.Replace(s, ":NNN:", `:\d\d\d:`, -1)
   508  	s = strings.Replace(s, "(N.NNs)", `\(\d*\.\d*s\)`, -1)
   509  	return s
   510  }
   511  
   512  func TestBenchmarkOutput(t *T) {
   513  	// Ensure Benchmark initialized common.w by invoking it with an error and
   514  	// normal case.
   515  	Benchmark(func(b *B) { b.Error("do not print this output") })
   516  	Benchmark(func(b *B) {})
   517  }