github.com/coreos/mantle@v0.13.0/harness/harness_test.go (about)

     1  // Copyright 2017 CoreOS, Inc.
     2  // Copyright 2016 The Go Authors.
     3  //
     4  // Licensed under the Apache License, Version 2.0 (the "License");
     5  // you may not use this file except in compliance with the License.
     6  // You may obtain a copy of the License at
     7  //
     8  //     http://www.apache.org/licenses/LICENSE-2.0
     9  //
    10  // Unless required by applicable law or agreed to in writing, software
    11  // distributed under the License is distributed on an "AS IS" BASIS,
    12  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  // See the License for the specific language governing permissions and
    14  // limitations under the License.
    15  
    16  package harness
    17  
    18  import (
    19  	"bytes"
    20  	"fmt"
    21  	"io/ioutil"
    22  	"os"
    23  	"path/filepath"
    24  	"reflect"
    25  	"regexp"
    26  	"runtime"
    27  	"strings"
    28  	"sync/atomic"
    29  	"testing"
    30  	"time"
    31  )
    32  
    33  func TestMain(m *testing.M) {
    34  	g0 := runtime.NumGoroutine()
    35  
    36  	code := m.Run()
    37  	if code != 0 {
    38  		os.Exit(code)
    39  	}
    40  
    41  	// Check that there are no goroutines left behind.
    42  	t0 := time.Now()
    43  	stacks := make([]byte, 1<<20)
    44  	for {
    45  		g1 := runtime.NumGoroutine()
    46  		if g1 == g0 {
    47  			return
    48  		}
    49  		stacks = stacks[:runtime.Stack(stacks, true)]
    50  		time.Sleep(50 * time.Millisecond)
    51  		if time.Since(t0) > 2*time.Second {
    52  			fmt.Fprintf(os.Stderr, "Unexpected leftover goroutines detected: %v -> %v\n%s\n", g0, g1, stacks)
    53  			os.Exit(1)
    54  		}
    55  	}
    56  }
    57  
    58  func TestContextCancel(t *testing.T) {
    59  	suite := NewSuite(Options{}, Tests{
    60  		"ContextCancel": func(h *H) {
    61  			ctx := h.Context()
    62  			// Tests we don't leak this goroutine:
    63  			go func() {
    64  				<-ctx.Done()
    65  			}()
    66  		}})
    67  	buf := &bytes.Buffer{}
    68  	if err := suite.runTests(buf, nil); err != nil {
    69  		t.Log("\n" + buf.String())
    70  		t.Error(err)
    71  	}
    72  }
    73  
    74  func TestSubTests(t *testing.T) {
    75  	realTest := t
    76  	testCases := []struct {
    77  		desc   string
    78  		err    error
    79  		maxPar int
    80  		chatty bool
    81  		output string
    82  		f      func(*H)
    83  	}{{
    84  		desc:   "failnow skips future sequential and parallel tests at same level",
    85  		err:    SuiteFailed,
    86  		maxPar: 1,
    87  		output: `
    88  --- FAIL: failnow skips future sequential and parallel tests at same level (N.NNs)
    89      --- FAIL: failnow skips future sequential and parallel tests at same level/#00 (N.NNs)
    90      `,
    91  		f: func(t *H) {
    92  			ranSeq := false
    93  			ranPar := false
    94  			t.Run("", func(t *H) {
    95  				t.Run("par", func(t *H) {
    96  					t.Parallel()
    97  					ranPar = true
    98  				})
    99  				t.Run("seq", func(t *H) {
   100  					ranSeq = true
   101  				})
   102  				t.FailNow()
   103  				t.Run("seq", func(t *H) {
   104  					realTest.Error("test must be skipped")
   105  				})
   106  				t.Run("par", func(t *H) {
   107  					t.Parallel()
   108  					realTest.Error("test must be skipped.")
   109  				})
   110  			})
   111  			if !ranPar {
   112  				realTest.Error("parallel test was not run")
   113  			}
   114  			if !ranSeq {
   115  				realTest.Error("sequential test was not run")
   116  			}
   117  		},
   118  	}, {
   119  		desc:   "failure in parallel test propagates upwards",
   120  		err:    SuiteFailed,
   121  		maxPar: 1,
   122  		output: `
   123  --- FAIL: failure in parallel test propagates upwards (N.NNs)
   124      --- FAIL: failure in parallel test propagates upwards/#00 (N.NNs)
   125          --- FAIL: failure in parallel test propagates upwards/#00/par (N.NNs)
   126  		`,
   127  		f: func(t *H) {
   128  			t.Run("", func(t *H) {
   129  				t.Parallel()
   130  				t.Run("par", func(t *H) {
   131  					t.Parallel()
   132  					t.Fail()
   133  				})
   134  			})
   135  		},
   136  	}, {
   137  		desc:   "skipping without message, chatty",
   138  		chatty: true,
   139  		output: `
   140  === RUN   skipping without message, chatty
   141  --- SKIP: skipping without message, chatty (N.NNs)`,
   142  		f: func(t *H) { t.SkipNow() },
   143  	}, {
   144  		desc:   "chatty with recursion",
   145  		chatty: true,
   146  		output: `
   147  === RUN   chatty with recursion
   148  === RUN   chatty with recursion/#00
   149  === RUN   chatty with recursion/#00/#00
   150  --- PASS: chatty with recursion (N.NNs)
   151      --- PASS: chatty with recursion/#00 (N.NNs)
   152          --- PASS: chatty with recursion/#00/#00 (N.NNs)`,
   153  		f: func(t *H) {
   154  			t.Run("", func(t *H) {
   155  				t.Run("", func(t *H) {})
   156  			})
   157  		},
   158  	}, {
   159  		desc: "skipping without message, not chatty",
   160  		f:    func(t *H) { t.SkipNow() },
   161  	}, {
   162  		desc: "skipping after error",
   163  		err:  SuiteFailed,
   164  		output: `
   165  --- FAIL: skipping after error (N.NNs)
   166          harness_test.go:NNN: an error
   167          harness_test.go:NNN: skipped`,
   168  		f: func(t *H) {
   169  			t.Error("an error")
   170  			t.Skip("skipped")
   171  		},
   172  	}, {
   173  		desc:   "use Run to locally synchronize parallelism",
   174  		maxPar: 1,
   175  		f: func(t *H) {
   176  			var count uint32
   177  			t.Run("waitGroup", func(t *H) {
   178  				for i := 0; i < 4; i++ {
   179  					t.Run("par", func(t *H) {
   180  						t.Parallel()
   181  						atomic.AddUint32(&count, 1)
   182  					})
   183  				}
   184  			})
   185  			if count != 4 {
   186  				t.Errorf("count was %d; want 4", count)
   187  			}
   188  		},
   189  	}, {
   190  		desc: "alternate sequential and parallel",
   191  		// Sequential tests should partake in the counting of running threads.
   192  		// Otherwise, if one runs parallel subtests in sequential tests that are
   193  		// itself subtests of parallel tests, the counts can get askew.
   194  		maxPar: 1,
   195  		f: func(t *H) {
   196  			t.Run("a", func(t *H) {
   197  				t.Parallel()
   198  				t.Run("b", func(t *H) {
   199  					// Sequential: ensure running count is decremented.
   200  					t.Run("c", func(t *H) {
   201  						t.Parallel()
   202  					})
   203  
   204  				})
   205  			})
   206  		},
   207  	}, {
   208  		desc: "alternate sequential and parallel 2",
   209  		// Sequential tests should partake in the counting of running threads.
   210  		// Otherwise, if one runs parallel subtests in sequential tests that are
   211  		// itself subtests of parallel tests, the counts can get askew.
   212  		maxPar: 2,
   213  		f: func(t *H) {
   214  			for i := 0; i < 2; i++ {
   215  				t.Run("a", func(t *H) {
   216  					t.Parallel()
   217  					time.Sleep(time.Nanosecond)
   218  					for i := 0; i < 2; i++ {
   219  						t.Run("b", func(t *H) {
   220  							time.Sleep(time.Nanosecond)
   221  							for i := 0; i < 2; i++ {
   222  								t.Run("c", func(t *H) {
   223  									t.Parallel()
   224  									time.Sleep(time.Nanosecond)
   225  								})
   226  							}
   227  
   228  						})
   229  					}
   230  				})
   231  			}
   232  		},
   233  	}, {
   234  		desc:   "stress test",
   235  		maxPar: 4,
   236  		f: func(t *H) {
   237  			// t.Parallel doesn't work in the pseudo-H we start with:
   238  			// it leaks a goroutine.
   239  			// Call t.Run to get a real one.
   240  			t.Run("X", func(t *H) {
   241  				t.Parallel()
   242  				for i := 0; i < 12; i++ {
   243  					t.Run("a", func(t *H) {
   244  						t.Parallel()
   245  						time.Sleep(time.Nanosecond)
   246  						for i := 0; i < 12; i++ {
   247  							t.Run("b", func(t *H) {
   248  								time.Sleep(time.Nanosecond)
   249  								for i := 0; i < 12; i++ {
   250  									t.Run("c", func(t *H) {
   251  										t.Parallel()
   252  										time.Sleep(time.Nanosecond)
   253  										t.Run("d1", func(t *H) {})
   254  										t.Run("d2", func(t *H) {})
   255  										t.Run("d3", func(t *H) {})
   256  										t.Run("d4", func(t *H) {})
   257  									})
   258  								}
   259  							})
   260  						}
   261  					})
   262  				}
   263  			})
   264  		},
   265  	}, {
   266  		desc:   "skip output",
   267  		maxPar: 4,
   268  		f: func(t *H) {
   269  			t.Skip()
   270  		},
   271  	}, {
   272  		desc:   "panic on goroutine fail after test exit",
   273  		err:    SuiteFailed,
   274  		maxPar: 4,
   275  		f: func(t *H) {
   276  			ch := make(chan bool)
   277  			t.Run("", func(t *H) {
   278  				go func() {
   279  					<-ch
   280  					defer func() {
   281  						if r := recover(); r == nil {
   282  							realTest.Errorf("expected panic")
   283  						}
   284  						ch <- true
   285  					}()
   286  					t.Errorf("failed after success")
   287  				}()
   288  			})
   289  			ch <- true
   290  			<-ch
   291  		},
   292  	}}
   293  	for _, tc := range testCases {
   294  		suite := NewSuite(Options{
   295  			Verbose:  tc.chatty,
   296  			Parallel: tc.maxPar,
   297  		}, Tests{tc.desc: tc.f})
   298  		buf := &bytes.Buffer{}
   299  		err := suite.runTests(buf, nil)
   300  		suite.release()
   301  
   302  		if err != tc.err {
   303  			t.Errorf("%s:err: got %v; want %v", tc.desc, err, tc.err)
   304  		}
   305  		if suite.running != 0 || suite.waiting != 0 {
   306  			t.Errorf("%s:running and waiting non-zero: got %d and %d", tc.desc, suite.running, suite.waiting)
   307  		}
   308  		got := strings.TrimSpace(buf.String())
   309  		want := strings.TrimSpace(tc.output)
   310  		re := makeRegexp(want)
   311  		if ok, err := regexp.MatchString(re, got); !ok || err != nil {
   312  			t.Errorf("%s:ouput:\ngot:\n%s\nwant:\n%s", tc.desc, got, want)
   313  		}
   314  	}
   315  }
   316  
   317  func makeRegexp(s string) string {
   318  	s = strings.Replace(s, ":NNN:", `:\d\d\d:`, -1)
   319  	s = strings.Replace(s, "(N.NNs)", `\(\d*\.\d*s\)`, -1)
   320  	return s
   321  }
   322  
   323  func TestOutputDir(t *testing.T) {
   324  	var suitedir string
   325  	if dir, err := ioutil.TempDir("", ""); err != nil {
   326  		t.Fatal(err)
   327  	} else {
   328  		defer os.RemoveAll(dir)
   329  		suitedir = filepath.Join(dir, "_test_temp")
   330  	}
   331  
   332  	var testdirs []string
   333  	adddir := func(h *H) {
   334  		testdirs = append(testdirs, h.OutputDir())
   335  	}
   336  
   337  	opts := Options{
   338  		OutputDir: suitedir,
   339  		Verbose:   true,
   340  	}
   341  	suite := NewSuite(opts, Tests{
   342  		"OutputDir": adddir,
   343  	})
   344  
   345  	buf := &bytes.Buffer{}
   346  	if err := suite.runTests(buf, nil); err != nil {
   347  		t.Log("\n" + buf.String())
   348  		t.Error(err)
   349  	}
   350  
   351  	expect := []string{
   352  		filepath.Join(suitedir, "OutputDir"),
   353  	}
   354  	if !reflect.DeepEqual(testdirs, expect) {
   355  		t.Errorf("%v != %v", testdirs, expect)
   356  	}
   357  }
   358  
   359  func TestSubDirs(t *testing.T) {
   360  	var suitedir string
   361  	if dir, err := ioutil.TempDir("", ""); err != nil {
   362  		t.Fatal(err)
   363  	} else {
   364  		defer os.RemoveAll(dir)
   365  		suitedir = filepath.Join(dir, "_test_temp")
   366  	}
   367  
   368  	var testdirs []string
   369  	adddir := func(h *H) {
   370  		testdirs = append(testdirs, h.OutputDir())
   371  	}
   372  
   373  	opts := Options{
   374  		OutputDir: suitedir,
   375  		Verbose:   true,
   376  	}
   377  	suite := NewSuite(opts, Tests{
   378  		"OutputDir": adddir,
   379  	})
   380  
   381  	buf := &bytes.Buffer{}
   382  	if err := suite.runTests(buf, nil); err != nil {
   383  		t.Log("\n" + buf.String())
   384  		t.Error(err)
   385  	}
   386  
   387  	expect := []string{
   388  		filepath.Join(suitedir, "OutputDir"),
   389  	}
   390  	if !reflect.DeepEqual(testdirs, expect) {
   391  		t.Errorf("%v != %v", testdirs, expect)
   392  	}
   393  }
   394  
   395  func TestTempDir(t *testing.T) {
   396  	var suitedir string
   397  	if dir, err := ioutil.TempDir("", ""); err != nil {
   398  		t.Fatal(err)
   399  	} else {
   400  		defer os.RemoveAll(dir)
   401  		suitedir = filepath.Join(dir, "_test_temp")
   402  	}
   403  
   404  	var testdirs []string
   405  	opts := Options{
   406  		OutputDir: suitedir,
   407  		Verbose:   true,
   408  	}
   409  	suite := NewSuite(opts, Tests{
   410  		"TempDir": func(h *H) {
   411  			testdirs = append(testdirs, h.TempDir("first"))
   412  			testdirs = append(testdirs, h.TempDir("second"))
   413  		},
   414  	})
   415  
   416  	buf := &bytes.Buffer{}
   417  	if err := suite.runTests(buf, nil); err != nil {
   418  		t.Log("\n" + buf.String())
   419  		t.Error(err)
   420  	}
   421  
   422  	if len(testdirs) != 2 {
   423  		t.Fatalf("expected 2 paths: %v", testdirs)
   424  	}
   425  
   426  	expect := filepath.Join(suitedir, "TempDir")
   427  	dir := filepath.Dir(testdirs[0])
   428  	if dir != expect {
   429  		t.Errorf("%q != %q", dir, expect)
   430  	}
   431  	first := filepath.Base(testdirs[0])
   432  	if !strings.HasPrefix(first, "first") {
   433  		t.Errorf("%q missing %q prefix", first, "first")
   434  	}
   435  	second := filepath.Base(testdirs[1])
   436  	if !strings.HasPrefix(second, "second") {
   437  		t.Errorf("%q missing %q prefix", second, "second")
   438  	}
   439  }
   440  
   441  func TestTempFile(t *testing.T) {
   442  	var suitedir string
   443  	if dir, err := ioutil.TempDir("", ""); err != nil {
   444  		t.Fatal(err)
   445  	} else {
   446  		defer os.RemoveAll(dir)
   447  		suitedir = filepath.Join(dir, "_test_temp")
   448  	}
   449  
   450  	var testfiles []string
   451  	opts := Options{
   452  		OutputDir: suitedir,
   453  		Verbose:   true,
   454  	}
   455  	suite := NewSuite(opts, Tests{
   456  		"TempFile": func(h *H) {
   457  			f := h.TempFile("first")
   458  			testfiles = append(testfiles, f.Name())
   459  			f.Close()
   460  			f = h.TempFile("second")
   461  			testfiles = append(testfiles, f.Name())
   462  			f.Close()
   463  		},
   464  	})
   465  
   466  	buf := &bytes.Buffer{}
   467  	if err := suite.runTests(buf, nil); err != nil {
   468  		t.Log("\n" + buf.String())
   469  		t.Error(err)
   470  	}
   471  
   472  	if len(testfiles) != 2 {
   473  		t.Fatalf("expected 2 paths: %v", testfiles)
   474  	}
   475  
   476  	expect := filepath.Join(suitedir, "TempFile")
   477  	dir := filepath.Dir(testfiles[0])
   478  	if dir != expect {
   479  		t.Errorf("%q != %q", dir, expect)
   480  	}
   481  	first := filepath.Base(testfiles[0])
   482  	if !strings.HasPrefix(first, "first") {
   483  		t.Errorf("%q missing %q prefix", first, "first")
   484  	}
   485  	second := filepath.Base(testfiles[1])
   486  	if !strings.HasPrefix(second, "second") {
   487  		t.Errorf("%q missing %q prefix", second, "second")
   488  	}
   489  }