Source file src/runtime/race/race_test.go

     1  // Copyright 2012 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  //go:build race
     6  
     7  // This program is used to verify the race detector
     8  // by running the tests and parsing their output.
     9  // It does not check stack correctness, completeness or anything else:
    10  // it merely verifies that if a test is expected to be racy
    11  // then the race is detected.
    12  package race_test
    13  
    14  import (
    15  	"bufio"
    16  	"bytes"
    17  	"fmt"
    18  	"internal/testenv"
    19  	"io"
    20  	"log"
    21  	"math/rand"
    22  	"os"
    23  	"os/exec"
    24  	"path/filepath"
    25  	"strings"
    26  	"sync"
    27  	"sync/atomic"
    28  	"testing"
    29  )
    30  
    31  var (
    32  	passedTests = 0
    33  	totalTests  = 0
    34  	falsePos    = 0
    35  	falseNeg    = 0
    36  	failingPos  = 0
    37  	failingNeg  = 0
    38  	failed      = false
    39  )
    40  
    41  const (
    42  	visibleLen = 40
    43  	testPrefix = "=== RUN   Test"
    44  )
    45  
    46  func TestRace(t *testing.T) {
    47  	testOutput, err := runTests(t)
    48  	if err != nil {
    49  		t.Fatalf("Failed to run tests: %v\n%v", err, string(testOutput))
    50  	}
    51  	reader := bufio.NewReader(bytes.NewReader(testOutput))
    52  
    53  	funcName := ""
    54  	var tsanLog []string
    55  	for {
    56  		s, err := nextLine(reader)
    57  		if err != nil {
    58  			fmt.Printf("%s\n", processLog(funcName, tsanLog))
    59  			break
    60  		}
    61  		if strings.HasPrefix(s, testPrefix) {
    62  			fmt.Printf("%s\n", processLog(funcName, tsanLog))
    63  			tsanLog = make([]string, 0, 100)
    64  			funcName = s[len(testPrefix):]
    65  		} else {
    66  			tsanLog = append(tsanLog, s)
    67  		}
    68  	}
    69  
    70  	if totalTests == 0 {
    71  		t.Fatalf("failed to parse test output:\n%s", testOutput)
    72  	}
    73  	fmt.Printf("\nPassed %d of %d tests (%.02f%%, %d+, %d-)\n",
    74  		passedTests, totalTests, 100*float64(passedTests)/float64(totalTests), falsePos, falseNeg)
    75  	fmt.Printf("%d expected failures (%d has not fail)\n", failingPos+failingNeg, failingNeg)
    76  	if failed {
    77  		t.Fail()
    78  	}
    79  }
    80  
    81  // nextLine is a wrapper around bufio.Reader.ReadString.
    82  // It reads a line up to the next '\n' character. Error
    83  // is non-nil if there are no lines left, and nil
    84  // otherwise.
    85  func nextLine(r *bufio.Reader) (string, error) {
    86  	s, err := r.ReadString('\n')
    87  	if err != nil {
    88  		if err != io.EOF {
    89  			log.Fatalf("nextLine: expected EOF, received %v", err)
    90  		}
    91  		return s, err
    92  	}
    93  	return s[:len(s)-1], nil
    94  }
    95  
    96  // processLog verifies whether the given ThreadSanitizer's log
    97  // contains a race report, checks this information against
    98  // the name of the testcase and returns the result of this
    99  // comparison.
   100  func processLog(testName string, tsanLog []string) string {
   101  	if !strings.HasPrefix(testName, "Race") && !strings.HasPrefix(testName, "NoRace") {
   102  		return ""
   103  	}
   104  	gotRace := false
   105  	for _, s := range tsanLog {
   106  		if strings.Contains(s, "DATA RACE") {
   107  			gotRace = true
   108  			break
   109  		}
   110  		if strings.Contains(s, "fatal error: concurrent map") {
   111  			// Detected by the runtime, not the race detector.
   112  			gotRace = true
   113  			break
   114  		}
   115  	}
   116  
   117  	failing := strings.Contains(testName, "Failing")
   118  	expRace := !strings.HasPrefix(testName, "No")
   119  	for len(testName) < visibleLen {
   120  		testName += " "
   121  	}
   122  	if expRace == gotRace {
   123  		passedTests++
   124  		totalTests++
   125  		if failing {
   126  			failed = true
   127  			failingNeg++
   128  		}
   129  		return fmt.Sprintf("%s .", testName)
   130  	}
   131  	pos := ""
   132  	if expRace {
   133  		falseNeg++
   134  	} else {
   135  		falsePos++
   136  		pos = "+"
   137  	}
   138  	if failing {
   139  		failingPos++
   140  	} else {
   141  		failed = true
   142  	}
   143  	totalTests++
   144  	return fmt.Sprintf("%s %s%s", testName, "FAILED", pos)
   145  }
   146  
   147  // runTests assures that the package and its dependencies is
   148  // built with instrumentation enabled and returns the output of 'go test'
   149  // which includes possible data race reports from ThreadSanitizer.
   150  func runTests(t *testing.T) ([]byte, error) {
   151  	tests, err := filepath.Glob("./testdata/*_test.go")
   152  	if err != nil {
   153  		return nil, err
   154  	}
   155  	args := []string{"test", "-race", "-v"}
   156  	args = append(args, tests...)
   157  	cmd := exec.Command(testenv.GoToolPath(t), args...)
   158  	// The following flags turn off heuristics that suppress seemingly identical reports.
   159  	// It is required because the tests contain a lot of data races on the same addresses
   160  	// (the tests are simple and the memory is constantly reused).
   161  	for _, env := range os.Environ() {
   162  		if strings.HasPrefix(env, "GOMAXPROCS=") ||
   163  			strings.HasPrefix(env, "GODEBUG=") ||
   164  			strings.HasPrefix(env, "GORACE=") {
   165  			continue
   166  		}
   167  		cmd.Env = append(cmd.Env, env)
   168  	}
   169  	// We set GOMAXPROCS=1 to prevent test flakiness.
   170  	// There are two sources of flakiness:
   171  	// 1. Some tests rely on particular execution order.
   172  	//    If the order is different, race does not happen at all.
   173  	// 2. Ironically, ThreadSanitizer runtime contains a logical race condition
   174  	//    that can lead to false negatives if racy accesses happen literally at the same time.
   175  	// Tests used to work reliably in the good old days of GOMAXPROCS=1.
   176  	// So let's set it for now. A more reliable solution is to explicitly annotate tests
   177  	// with required execution order by means of a special "invisible" synchronization primitive
   178  	// (that's what is done for C++ ThreadSanitizer tests). This is issue #14119.
   179  	cmd.Env = append(cmd.Env,
   180  		"GOMAXPROCS=1",
   181  		"GORACE=suppress_equal_stacks=0 suppress_equal_addresses=0",
   182  	)
   183  	// There are races: we expect tests to fail and the exit code to be non-zero.
   184  	out, _ := cmd.CombinedOutput()
   185  	fatals := bytes.Count(out, []byte("fatal error:"))
   186  	mapFatals := bytes.Count(out, []byte("fatal error: concurrent map"))
   187  	if fatals > mapFatals {
   188  		// But don't expect runtime to crash (other than
   189  		// in the map concurrent access detector).
   190  		return out, fmt.Errorf("runtime fatal error")
   191  	}
   192  	return out, nil
   193  }
   194  
   195  func TestIssue8102(t *testing.T) {
   196  	// If this compiles with -race, the test passes.
   197  	type S struct {
   198  		x any
   199  		i int
   200  	}
   201  	c := make(chan int)
   202  	a := [2]*int{}
   203  	for ; ; c <- *a[S{}.i] {
   204  		if t != nil {
   205  			break
   206  		}
   207  	}
   208  }
   209  
   210  func TestIssue9137(t *testing.T) {
   211  	a := []string{"a"}
   212  	i := 0
   213  	a[i], a[len(a)-1], a = a[len(a)-1], "", a[:len(a)-1]
   214  	if len(a) != 0 || a[:1][0] != "" {
   215  		t.Errorf("mangled a: %q %q", a, a[:1])
   216  	}
   217  }
   218  
   219  func BenchmarkSyncLeak(b *testing.B) {
   220  	const (
   221  		G = 1000
   222  		S = 1000
   223  		H = 10
   224  	)
   225  	var wg sync.WaitGroup
   226  	wg.Add(G)
   227  	for g := 0; g < G; g++ {
   228  		go func() {
   229  			defer wg.Done()
   230  			hold := make([][]uint32, H)
   231  			for i := 0; i < b.N; i++ {
   232  				a := make([]uint32, S)
   233  				atomic.AddUint32(&a[rand.Intn(len(a))], 1)
   234  				hold[rand.Intn(len(hold))] = a
   235  			}
   236  			_ = hold
   237  		}()
   238  	}
   239  	wg.Wait()
   240  }
   241  
   242  func BenchmarkStackLeak(b *testing.B) {
   243  	done := make(chan bool, 1)
   244  	for i := 0; i < b.N; i++ {
   245  		go func() {
   246  			growStack(rand.Intn(100))
   247  			done <- true
   248  		}()
   249  		<-done
   250  	}
   251  }
   252  
   253  func growStack(i int) {
   254  	if i == 0 {
   255  		return
   256  	}
   257  	growStack(i - 1)
   258  }
   259  

View as plain text