| // Copyright 2012 The Go Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style | |
| // license that can be found in the LICENSE file. | |
| // +build race | |
| // This program is used to verify the race detector | |
| // by running the tests and parsing their output. | |
| // It does not check stack correctness, completeness or anything else: | |
| // it merely verifies that if a test is expected to be racy | |
| // then the race is detected. | |
| package race_test | |
| import ( | |
| "bufio" | |
| "bytes" | |
| "fmt" | |
| "internal/testenv" | |
| "io" | |
| "log" | |
| "math/rand" | |
| "os" | |
| "os/exec" | |
| "path/filepath" | |
| "strings" | |
| "sync" | |
| "sync/atomic" | |
| "testing" | |
| ) | |
| var ( | |
| passedTests = 0 | |
| totalTests = 0 | |
| falsePos = 0 | |
| falseNeg = 0 | |
| failingPos = 0 | |
| failingNeg = 0 | |
| failed = false | |
| ) | |
| const ( | |
| visibleLen = 40 | |
| testPrefix = "=== RUN Test" | |
| ) | |
| func TestRace(t *testing.T) { | |
| testOutput, err := runTests(t) | |
| if err != nil { | |
| t.Fatalf("Failed to run tests: %v\n%v", err, string(testOutput)) | |
| } | |
| reader := bufio.NewReader(bytes.NewReader(testOutput)) | |
| funcName := "" | |
| var tsanLog []string | |
| for { | |
| s, err := nextLine(reader) | |
| if err != nil { | |
| fmt.Printf("%s\n", processLog(funcName, tsanLog)) | |
| break | |
| } | |
| if strings.HasPrefix(s, testPrefix) { | |
| fmt.Printf("%s\n", processLog(funcName, tsanLog)) | |
| tsanLog = make([]string, 0, 100) | |
| funcName = s[len(testPrefix):] | |
| } else { | |
| tsanLog = append(tsanLog, s) | |
| } | |
| } | |
| if totalTests == 0 { | |
| t.Fatalf("failed to parse test output:\n%s", testOutput) | |
| } | |
| fmt.Printf("\nPassed %d of %d tests (%.02f%%, %d+, %d-)\n", | |
| passedTests, totalTests, 100*float64(passedTests)/float64(totalTests), falsePos, falseNeg) | |
| fmt.Printf("%d expected failures (%d has not fail)\n", failingPos+failingNeg, failingNeg) | |
| if failed { | |
| t.Fail() | |
| } | |
| } | |
| // nextLine is a wrapper around bufio.Reader.ReadString. | |
| // It reads a line up to the next '\n' character. Error | |
| // is non-nil if there are no lines left, and nil | |
| // otherwise. | |
| func nextLine(r *bufio.Reader) (string, error) { | |
| s, err := r.ReadString('\n') | |
| if err != nil { | |
| if err != io.EOF { | |
| log.Fatalf("nextLine: expected EOF, received %v", err) | |
| } | |
| return s, err | |
| } | |
| return s[:len(s)-1], nil | |
| } | |
| // processLog verifies whether the given ThreadSanitizer's log | |
| // contains a race report, checks this information against | |
| // the name of the testcase and returns the result of this | |
| // comparison. | |
| func processLog(testName string, tsanLog []string) string { | |
| if !strings.HasPrefix(testName, "Race") && !strings.HasPrefix(testName, "NoRace") { | |
| return "" | |
| } | |
| gotRace := false | |
| for _, s := range tsanLog { | |
| if strings.Contains(s, "DATA RACE") { | |
| gotRace = true | |
| break | |
| } | |
| } | |
| failing := strings.Contains(testName, "Failing") | |
| expRace := !strings.HasPrefix(testName, "No") | |
| for len(testName) < visibleLen { | |
| testName += " " | |
| } | |
| if expRace == gotRace { | |
| passedTests++ | |
| totalTests++ | |
| if failing { | |
| failed = true | |
| failingNeg++ | |
| } | |
| return fmt.Sprintf("%s .", testName) | |
| } | |
| pos := "" | |
| if expRace { | |
| falseNeg++ | |
| } else { | |
| falsePos++ | |
| pos = "+" | |
| } | |
| if failing { | |
| failingPos++ | |
| } else { | |
| failed = true | |
| } | |
| totalTests++ | |
| return fmt.Sprintf("%s %s%s", testName, "FAILED", pos) | |
| } | |
| // runTests assures that the package and its dependencies is | |
| // built with instrumentation enabled and returns the output of 'go test' | |
| // which includes possible data race reports from ThreadSanitizer. | |
| func runTests(t *testing.T) ([]byte, error) { | |
| tests, err := filepath.Glob("./testdata/*_test.go") | |
| if err != nil { | |
| return nil, err | |
| } | |
| args := []string{"test", "-race", "-v"} | |
| args = append(args, tests...) | |
| cmd := exec.Command(testenv.GoToolPath(t), args...) | |
| // The following flags turn off heuristics that suppress seemingly identical reports. | |
| // It is required because the tests contain a lot of data races on the same addresses | |
| // (the tests are simple and the memory is constantly reused). | |
| for _, env := range os.Environ() { | |
| if strings.HasPrefix(env, "GOMAXPROCS=") || | |
| strings.HasPrefix(env, "GODEBUG=") || | |
| strings.HasPrefix(env, "GORACE=") { | |
| continue | |
| } | |
| cmd.Env = append(cmd.Env, env) | |
| } | |
| // We set GOMAXPROCS=1 to prevent test flakiness. | |
| // There are two sources of flakiness: | |
| // 1. Some tests rely on particular execution order. | |
| // If the order is different, race does not happen at all. | |
| // 2. Ironically, ThreadSanitizer runtime contains a logical race condition | |
| // that can lead to false negatives if racy accesses happen literally at the same time. | |
| // Tests used to work reliably in the good old days of GOMAXPROCS=1. | |
| // So let's set it for now. A more reliable solution is to explicitly annotate tests | |
| // with required execution order by means of a special "invisible" synchronization primitive | |
| // (that's what is done for C++ ThreadSanitizer tests). This is issue #14119. | |
| cmd.Env = append(cmd.Env, | |
| "GOMAXPROCS=1", | |
| "GORACE=suppress_equal_stacks=0 suppress_equal_addresses=0", | |
| ) | |
| // There are races: we expect tests to fail and the exit code to be non-zero. | |
| out, _ := cmd.CombinedOutput() | |
| return out, nil | |
| } | |
| func TestIssue8102(t *testing.T) { | |
| // If this compiles with -race, the test passes. | |
| type S struct { | |
| x interface{} | |
| i int | |
| } | |
| c := make(chan int) | |
| a := [2]*int{} | |
| for ; ; c <- *a[S{}.i] { | |
| if t != nil { | |
| break | |
| } | |
| } | |
| } | |
| func TestIssue9137(t *testing.T) { | |
| a := []string{"a"} | |
| i := 0 | |
| a[i], a[len(a)-1], a = a[len(a)-1], "", a[:len(a)-1] | |
| if len(a) != 0 || a[:1][0] != "" { | |
| t.Errorf("mangled a: %q %q", a, a[:1]) | |
| } | |
| } | |
| func BenchmarkSyncLeak(b *testing.B) { | |
| const ( | |
| G = 1000 | |
| S = 1000 | |
| H = 10 | |
| ) | |
| var wg sync.WaitGroup | |
| wg.Add(G) | |
| for g := 0; g < G; g++ { | |
| go func() { | |
| defer wg.Done() | |
| hold := make([][]uint32, H) | |
| for i := 0; i < b.N; i++ { | |
| a := make([]uint32, S) | |
| atomic.AddUint32(&a[rand.Intn(len(a))], 1) | |
| hold[rand.Intn(len(hold))] = a | |
| } | |
| _ = hold | |
| }() | |
| } | |
| wg.Wait() | |
| } | |
| func BenchmarkStackLeak(b *testing.B) { | |
| done := make(chan bool, 1) | |
| for i := 0; i < b.N; i++ { | |
| go func() { | |
| growStack(rand.Intn(100)) | |
| done <- true | |
| }() | |
| <-done | |
| } | |
| } | |
| func growStack(i int) { | |
| if i == 0 { | |
| return | |
| } | |
| growStack(i - 1) | |
| } |