mirror of
https://github.com/golang/go.git
synced 2025-12-08 06:10:04 +00:00
runtime: move test programs out of source code, coalesce
Now there are just three programs to compile instead of many, and repeated tests can reuse the compilation result instead of rebuilding it. Combined, these changes reduce the time spent testing runtime during all.bash on my laptop from about 60 to about 30 seconds. (All.bash itself runs in 5½ minutes.) For #10571. Change-Id: Ie2c1798b847f1a635a860d11dcdab14375319ae9 Reviewed-on: https://go-review.googlesource.com/18085 Reviewed-by: Austin Clements <austin@google.com> Run-TryBot: Austin Clements <austin@google.com>
This commit is contained in:
parent
a699320512
commit
8d5ff2e182
24 changed files with 994 additions and 820 deletions
|
|
@ -16,9 +16,18 @@ import (
|
|||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
"text/template"
|
||||
)
|
||||
|
||||
var toRemove []string
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
status := m.Run()
|
||||
for _, file := range toRemove {
|
||||
os.RemoveAll(file)
|
||||
}
|
||||
os.Exit(status)
|
||||
}
|
||||
|
||||
func testEnv(cmd *exec.Cmd) *exec.Cmd {
|
||||
if cmd.Env != nil {
|
||||
panic("environment already set")
|
||||
|
|
@ -38,55 +47,63 @@ func testEnv(cmd *exec.Cmd) *exec.Cmd {
|
|||
return cmd
|
||||
}
|
||||
|
||||
func executeTest(t *testing.T, templ string, data interface{}, extra ...string) string {
|
||||
var testprog struct {
|
||||
sync.Mutex
|
||||
dir string
|
||||
target map[string]buildexe
|
||||
}
|
||||
|
||||
type buildexe struct {
|
||||
exe string
|
||||
err error
|
||||
}
|
||||
|
||||
func runTestProg(t *testing.T, binary, name string) string {
|
||||
testenv.MustHaveGoBuild(t)
|
||||
|
||||
exe, err := buildTestProg(t, binary)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
got, _ := testEnv(exec.Command(exe, name)).CombinedOutput()
|
||||
return string(got)
|
||||
}
|
||||
|
||||
func buildTestProg(t *testing.T, binary string) (string, error) {
|
||||
checkStaleRuntime(t)
|
||||
|
||||
st := template.Must(template.New("crashSource").Parse(templ))
|
||||
|
||||
dir, err := ioutil.TempDir("", "go-build")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create temp directory: %v", err)
|
||||
}
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
src := filepath.Join(dir, "main.go")
|
||||
f, err := os.Create(src)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create file: %v", err)
|
||||
}
|
||||
err = st.Execute(f, data)
|
||||
if err != nil {
|
||||
f.Close()
|
||||
t.Fatalf("failed to execute template: %v", err)
|
||||
}
|
||||
if err := f.Close(); err != nil {
|
||||
t.Fatalf("failed to close file: %v", err)
|
||||
}
|
||||
|
||||
for i := 0; i < len(extra); i += 2 {
|
||||
fname := extra[i]
|
||||
contents := extra[i+1]
|
||||
if d, _ := filepath.Split(fname); d != "" {
|
||||
if err := os.Mkdir(filepath.Join(dir, d), 0755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
if err := ioutil.WriteFile(filepath.Join(dir, fname), []byte(contents), 0666); err != nil {
|
||||
t.Fatal(err)
|
||||
testprog.Lock()
|
||||
defer testprog.Unlock()
|
||||
if testprog.dir == "" {
|
||||
dir, err := ioutil.TempDir("", "go-build")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create temp directory: %v", err)
|
||||
}
|
||||
testprog.dir = dir
|
||||
toRemove = append(toRemove, dir)
|
||||
}
|
||||
|
||||
cmd := exec.Command("go", "build", "-o", "a.exe")
|
||||
cmd.Dir = dir
|
||||
if testprog.target == nil {
|
||||
testprog.target = make(map[string]buildexe)
|
||||
}
|
||||
target, ok := testprog.target[binary]
|
||||
if ok {
|
||||
return target.exe, target.err
|
||||
}
|
||||
|
||||
exe := filepath.Join(testprog.dir, binary+".exe")
|
||||
cmd := exec.Command("go", "build", "-o", exe)
|
||||
cmd.Dir = "testdata/" + binary
|
||||
out, err := testEnv(cmd).CombinedOutput()
|
||||
if err != nil {
|
||||
t.Fatalf("building source: %v\n%s", err, out)
|
||||
exe = ""
|
||||
target.err = fmt.Errorf("building %s: %v\n%s", binary, err, out)
|
||||
testprog.target[binary] = target
|
||||
return "", err
|
||||
}
|
||||
|
||||
got, _ := testEnv(exec.Command(filepath.Join(dir, "a.exe"))).CombinedOutput()
|
||||
return string(got)
|
||||
target.exe = exe
|
||||
testprog.target[binary] = target
|
||||
return exe, nil
|
||||
}
|
||||
|
||||
var (
|
||||
|
|
@ -115,7 +132,12 @@ func testCrashHandler(t *testing.T, cgo bool) {
|
|||
type crashTest struct {
|
||||
Cgo bool
|
||||
}
|
||||
output := executeTest(t, crashSource, &crashTest{Cgo: cgo})
|
||||
var output string
|
||||
if cgo {
|
||||
output = runTestProg(t, "testprogcgo", "Crash")
|
||||
} else {
|
||||
output = runTestProg(t, "testprog", "Crash")
|
||||
}
|
||||
want := "main: recovered done\nnew-thread: recovered done\nsecond-new-thread: recovered done\nmain-again: recovered done\n"
|
||||
if output != want {
|
||||
t.Fatalf("output:\n%s\n\nwanted:\n%s", output, want)
|
||||
|
|
@ -126,8 +148,8 @@ func TestCrashHandler(t *testing.T) {
|
|||
testCrashHandler(t, false)
|
||||
}
|
||||
|
||||
func testDeadlock(t *testing.T, source string) {
|
||||
output := executeTest(t, source, nil)
|
||||
func testDeadlock(t *testing.T, name string) {
|
||||
output := runTestProg(t, "testprog", name)
|
||||
want := "fatal error: all goroutines are asleep - deadlock!\n"
|
||||
if !strings.HasPrefix(output, want) {
|
||||
t.Fatalf("output does not start with %q:\n%s", want, output)
|
||||
|
|
@ -135,23 +157,23 @@ func testDeadlock(t *testing.T, source string) {
|
|||
}
|
||||
|
||||
func TestSimpleDeadlock(t *testing.T) {
|
||||
testDeadlock(t, simpleDeadlockSource)
|
||||
testDeadlock(t, "SimpleDeadlock")
|
||||
}
|
||||
|
||||
func TestInitDeadlock(t *testing.T) {
|
||||
testDeadlock(t, initDeadlockSource)
|
||||
testDeadlock(t, "InitDeadlock")
|
||||
}
|
||||
|
||||
func TestLockedDeadlock(t *testing.T) {
|
||||
testDeadlock(t, lockedDeadlockSource)
|
||||
testDeadlock(t, "LockedDeadlock")
|
||||
}
|
||||
|
||||
func TestLockedDeadlock2(t *testing.T) {
|
||||
testDeadlock(t, lockedDeadlockSource2)
|
||||
testDeadlock(t, "LockedDeadlock2")
|
||||
}
|
||||
|
||||
func TestGoexitDeadlock(t *testing.T) {
|
||||
output := executeTest(t, goexitDeadlockSource, nil)
|
||||
output := runTestProg(t, "testprog", "GoexitDeadlock")
|
||||
want := "no goroutines (main called runtime.Goexit) - deadlock!"
|
||||
if !strings.Contains(output, want) {
|
||||
t.Fatalf("output:\n%s\n\nwant output containing: %s", output, want)
|
||||
|
|
@ -159,15 +181,15 @@ func TestGoexitDeadlock(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestStackOverflow(t *testing.T) {
|
||||
output := executeTest(t, stackOverflowSource, nil)
|
||||
want := "runtime: goroutine stack exceeds 4194304-byte limit\nfatal error: stack overflow"
|
||||
output := runTestProg(t, "testprog", "StackOverflow")
|
||||
want := "runtime: goroutine stack exceeds 1474560-byte limit\nfatal error: stack overflow"
|
||||
if !strings.HasPrefix(output, want) {
|
||||
t.Fatalf("output does not start with %q:\n%s", want, output)
|
||||
}
|
||||
}
|
||||
|
||||
func TestThreadExhaustion(t *testing.T) {
|
||||
output := executeTest(t, threadExhaustionSource, nil)
|
||||
output := runTestProg(t, "testprog", "ThreadExhaustion")
|
||||
want := "runtime: program exceeds 10-thread limit\nfatal error: thread exhaustion"
|
||||
if !strings.HasPrefix(output, want) {
|
||||
t.Fatalf("output does not start with %q:\n%s", want, output)
|
||||
|
|
@ -175,7 +197,7 @@ func TestThreadExhaustion(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestRecursivePanic(t *testing.T) {
|
||||
output := executeTest(t, recursivePanicSource, nil)
|
||||
output := runTestProg(t, "testprog", "RecursivePanic")
|
||||
want := `wrap: bad
|
||||
panic: again
|
||||
|
||||
|
|
@ -187,7 +209,7 @@ panic: again
|
|||
}
|
||||
|
||||
func TestGoexitCrash(t *testing.T) {
|
||||
output := executeTest(t, goexitExitSource, nil)
|
||||
output := runTestProg(t, "testprog", "GoexitExit")
|
||||
want := "no goroutines (main called runtime.Goexit) - deadlock!"
|
||||
if !strings.Contains(output, want) {
|
||||
t.Fatalf("output:\n%s\n\nwant output containing: %s", output, want)
|
||||
|
|
@ -211,15 +233,15 @@ func TestGoexitDefer(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestGoNil(t *testing.T) {
|
||||
output := executeTest(t, goNilSource, nil)
|
||||
output := runTestProg(t, "testprog", "GoNil")
|
||||
want := "go of nil func value"
|
||||
if !strings.Contains(output, want) {
|
||||
t.Fatalf("output:\n%s\n\nwant output containing: %s", output, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMainGoroutineId(t *testing.T) {
|
||||
output := executeTest(t, mainGoroutineIdSource, nil)
|
||||
func TestMainGoroutineID(t *testing.T) {
|
||||
output := runTestProg(t, "testprog", "MainGoroutineID")
|
||||
want := "panic: test\n\ngoroutine 1 [running]:\n"
|
||||
if !strings.HasPrefix(output, want) {
|
||||
t.Fatalf("output does not start with %q:\n%s", want, output)
|
||||
|
|
@ -227,7 +249,7 @@ func TestMainGoroutineId(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestNoHelperGoroutines(t *testing.T) {
|
||||
output := executeTest(t, noHelperGoroutinesSource, nil)
|
||||
output := runTestProg(t, "testprog", "NoHelperGoroutines")
|
||||
matches := regexp.MustCompile(`goroutine [0-9]+ \[`).FindAllStringSubmatch(output, -1)
|
||||
if len(matches) != 1 || matches[0][0] != "goroutine 1 [" {
|
||||
t.Fatalf("want to see only goroutine 1, see:\n%s", output)
|
||||
|
|
@ -235,7 +257,7 @@ func TestNoHelperGoroutines(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestBreakpoint(t *testing.T) {
|
||||
output := executeTest(t, breakpointSource, nil)
|
||||
output := runTestProg(t, "testprog", "Breakpoint")
|
||||
want := "runtime.Breakpoint()"
|
||||
if !strings.Contains(output, want) {
|
||||
t.Fatalf("output:\n%s\n\nwant output containing: %s", output, want)
|
||||
|
|
@ -253,293 +275,34 @@ import (
|
|||
{{if .Cgo}}
|
||||
import "C"
|
||||
{{end}}
|
||||
|
||||
func test(name string) {
|
||||
defer func() {
|
||||
if x := recover(); x != nil {
|
||||
fmt.Printf(" recovered")
|
||||
}
|
||||
fmt.Printf(" done\n")
|
||||
}()
|
||||
fmt.Printf("%s:", name)
|
||||
var s *string
|
||||
_ = *s
|
||||
fmt.Print("SHOULD NOT BE HERE")
|
||||
}
|
||||
|
||||
func testInNewThread(name string) {
|
||||
c := make(chan bool)
|
||||
go func() {
|
||||
runtime.LockOSThread()
|
||||
test(name)
|
||||
c <- true
|
||||
}()
|
||||
<-c
|
||||
}
|
||||
|
||||
func main() {
|
||||
runtime.LockOSThread()
|
||||
test("main")
|
||||
testInNewThread("new-thread")
|
||||
testInNewThread("second-new-thread")
|
||||
test("main-again")
|
||||
}
|
||||
`
|
||||
|
||||
const simpleDeadlockSource = `
|
||||
package main
|
||||
func main() {
|
||||
select {}
|
||||
}
|
||||
`
|
||||
|
||||
const initDeadlockSource = `
|
||||
package main
|
||||
func init() {
|
||||
select {}
|
||||
}
|
||||
func main() {
|
||||
}
|
||||
`
|
||||
|
||||
const lockedDeadlockSource = `
|
||||
package main
|
||||
import "runtime"
|
||||
func main() {
|
||||
runtime.LockOSThread()
|
||||
select {}
|
||||
}
|
||||
`
|
||||
|
||||
const lockedDeadlockSource2 = `
|
||||
package main
|
||||
import (
|
||||
"runtime"
|
||||
"time"
|
||||
)
|
||||
func main() {
|
||||
go func() {
|
||||
runtime.LockOSThread()
|
||||
select {}
|
||||
}()
|
||||
time.Sleep(time.Millisecond)
|
||||
select {}
|
||||
}
|
||||
`
|
||||
|
||||
const goexitDeadlockSource = `
|
||||
package main
|
||||
import (
|
||||
"runtime"
|
||||
)
|
||||
|
||||
func F() {
|
||||
for i := 0; i < 10; i++ {
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
go F()
|
||||
go F()
|
||||
runtime.Goexit()
|
||||
}
|
||||
`
|
||||
|
||||
const stackOverflowSource = `
|
||||
package main
|
||||
|
||||
import "runtime/debug"
|
||||
|
||||
func main() {
|
||||
debug.SetMaxStack(4<<20)
|
||||
f(make([]byte, 10))
|
||||
}
|
||||
|
||||
func f(x []byte) byte {
|
||||
var buf [64<<10]byte
|
||||
return x[0] + f(buf[:])
|
||||
}
|
||||
`
|
||||
|
||||
const threadExhaustionSource = `
|
||||
package main
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
"runtime/debug"
|
||||
)
|
||||
|
||||
func main() {
|
||||
debug.SetMaxThreads(10)
|
||||
c := make(chan int)
|
||||
for i := 0; i < 100; i++ {
|
||||
go func() {
|
||||
runtime.LockOSThread()
|
||||
c <- 0
|
||||
select{}
|
||||
}()
|
||||
<-c
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
const recursivePanicSource = `
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func main() {
|
||||
func() {
|
||||
defer func() {
|
||||
fmt.Println(recover())
|
||||
}()
|
||||
var x [8192]byte
|
||||
func(x [8192]byte) {
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
panic("wrap: " + err.(string))
|
||||
}
|
||||
}()
|
||||
panic("bad")
|
||||
}(x)
|
||||
}()
|
||||
panic("again")
|
||||
}
|
||||
`
|
||||
|
||||
const goexitExitSource = `
|
||||
package main
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
"time"
|
||||
)
|
||||
|
||||
func main() {
|
||||
go func() {
|
||||
time.Sleep(time.Millisecond)
|
||||
}()
|
||||
i := 0
|
||||
runtime.SetFinalizer(&i, func(p *int) {})
|
||||
runtime.GC()
|
||||
runtime.Goexit()
|
||||
}
|
||||
`
|
||||
|
||||
const goNilSource = `
|
||||
package main
|
||||
|
||||
func main() {
|
||||
defer func() {
|
||||
recover()
|
||||
}()
|
||||
var f func()
|
||||
go f()
|
||||
select{}
|
||||
}
|
||||
`
|
||||
|
||||
const mainGoroutineIdSource = `
|
||||
package main
|
||||
func main() {
|
||||
panic("test")
|
||||
}
|
||||
`
|
||||
|
||||
const noHelperGoroutinesSource = `
|
||||
package main
|
||||
import (
|
||||
"runtime"
|
||||
"time"
|
||||
)
|
||||
func init() {
|
||||
i := 0
|
||||
runtime.SetFinalizer(&i, func(p *int) {})
|
||||
time.AfterFunc(time.Hour, func() {})
|
||||
panic("oops")
|
||||
}
|
||||
func main() {
|
||||
}
|
||||
`
|
||||
|
||||
const breakpointSource = `
|
||||
package main
|
||||
import "runtime"
|
||||
func main() {
|
||||
runtime.Breakpoint()
|
||||
}
|
||||
`
|
||||
|
||||
func TestGoexitInPanic(t *testing.T) {
|
||||
// see issue 8774: this code used to trigger an infinite recursion
|
||||
output := executeTest(t, goexitInPanicSource, nil)
|
||||
output := runTestProg(t, "testprog", "GoexitInPanic")
|
||||
want := "fatal error: no goroutines (main called runtime.Goexit) - deadlock!"
|
||||
if !strings.HasPrefix(output, want) {
|
||||
t.Fatalf("output does not start with %q:\n%s", want, output)
|
||||
}
|
||||
}
|
||||
|
||||
const goexitInPanicSource = `
|
||||
package main
|
||||
import "runtime"
|
||||
func main() {
|
||||
go func() {
|
||||
defer func() {
|
||||
runtime.Goexit()
|
||||
}()
|
||||
panic("hello")
|
||||
}()
|
||||
runtime.Goexit()
|
||||
}
|
||||
`
|
||||
|
||||
func TestPanicAfterGoexit(t *testing.T) {
|
||||
// an uncaught panic should still work after goexit
|
||||
output := executeTest(t, panicAfterGoexitSource, nil)
|
||||
output := runTestProg(t, "testprog", "PanicAfterGoexit")
|
||||
want := "panic: hello"
|
||||
if !strings.HasPrefix(output, want) {
|
||||
t.Fatalf("output does not start with %q:\n%s", want, output)
|
||||
}
|
||||
}
|
||||
|
||||
const panicAfterGoexitSource = `
|
||||
package main
|
||||
import "runtime"
|
||||
func main() {
|
||||
defer func() {
|
||||
panic("hello")
|
||||
}()
|
||||
runtime.Goexit()
|
||||
}
|
||||
`
|
||||
|
||||
func TestRecoveredPanicAfterGoexit(t *testing.T) {
|
||||
output := executeTest(t, recoveredPanicAfterGoexitSource, nil)
|
||||
output := runTestProg(t, "testprog", "RecoveredPanicAfterGoexit")
|
||||
want := "fatal error: no goroutines (main called runtime.Goexit) - deadlock!"
|
||||
if !strings.HasPrefix(output, want) {
|
||||
t.Fatalf("output does not start with %q:\n%s", want, output)
|
||||
}
|
||||
}
|
||||
|
||||
const recoveredPanicAfterGoexitSource = `
|
||||
package main
|
||||
import "runtime"
|
||||
func main() {
|
||||
defer func() {
|
||||
defer func() {
|
||||
r := recover()
|
||||
if r == nil {
|
||||
panic("bad recover")
|
||||
}
|
||||
}()
|
||||
panic("hello")
|
||||
}()
|
||||
runtime.Goexit()
|
||||
}
|
||||
`
|
||||
|
||||
func TestRecoverBeforePanicAfterGoexit(t *testing.T) {
|
||||
// 1. defer a function that recovers
|
||||
// 2. defer a function that panics
|
||||
|
|
@ -561,7 +324,7 @@ func TestRecoverBeforePanicAfterGoexit(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestNetpollDeadlock(t *testing.T) {
|
||||
output := executeTest(t, netpollDeadlockSource, nil)
|
||||
output := runTestProg(t, "testprognet", "NetpollDeadlock")
|
||||
want := "done\n"
|
||||
if !strings.HasSuffix(output, want) {
|
||||
t.Fatalf("output does not start with %q:\n%s", want, output)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue