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 }