1
0
mirror of https://github.com/golang/go synced 2024-11-23 20:30:04 -07:00

testenv: abstract run-with-timeout into testenv

This lifts the logic to run a subcommand with a timeout in a test from
the runtime's runTestProg into testenv. The implementation is
unchanged in this CL. We'll improve it in a future CL.

Currently, tests that run subcommands usually just timeout with no
useful output if the subcommand runs for too long. This is a step
toward improving this.

For #37405.

Change-Id: I2298770db516e216379c4c438e05d23cbbdda51d
Reviewed-on: https://go-review.googlesource.com/c/go/+/370701
Trust: Austin Clements <austin@google.com>
Run-TryBot: Austin Clements <austin@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
Reviewed-by: Michael Knyszek <mknyszek@google.com>
Reviewed-by: Bryan Mills <bcmills@google.com>
This commit is contained in:
Austin Clements 2021-12-09 12:25:04 -05:00
parent 9c6e8f63c0
commit 6b89773722
5 changed files with 71 additions and 48 deletions

View File

@ -11,6 +11,7 @@
package testenv
import (
"bytes"
"errors"
"flag"
"internal/cfg"
@ -22,6 +23,7 @@ import (
"strings"
"sync"
"testing"
"time"
)
// Builder reports the name of the builder running this test
@ -306,3 +308,52 @@ func SkipIfShortAndSlow(t testing.TB) {
t.Skipf("skipping test in -short mode on %s", runtime.GOARCH)
}
}
// RunWithTimeout runs cmd and returns its combined output. If the
// subprocess exits with a non-zero status, it will log that status
// and return a non-nil error, but this is not considered fatal.
func RunWithTimeout(t testing.TB, cmd *exec.Cmd) ([]byte, error) {
args := cmd.Args
if args == nil {
args = []string{cmd.Path}
}
var b bytes.Buffer
cmd.Stdout = &b
cmd.Stderr = &b
if err := cmd.Start(); err != nil {
t.Fatalf("starting %s: %v", args, err)
}
// If the process doesn't complete within 1 minute,
// assume it is hanging and kill it to get a stack trace.
p := cmd.Process
done := make(chan bool)
go func() {
scale := 1
// This GOARCH/GOOS test is copied from cmd/dist/test.go.
// TODO(iant): Have cmd/dist update the environment variable.
if runtime.GOARCH == "arm" || runtime.GOOS == "windows" {
scale = 2
}
if s := os.Getenv("GO_TEST_TIMEOUT_SCALE"); s != "" {
if sc, err := strconv.Atoi(s); err == nil {
scale = sc
}
}
select {
case <-done:
case <-time.After(time.Duration(scale) * time.Minute):
p.Signal(Sigquit)
}
}()
err := cmd.Wait()
if err != nil {
t.Logf("%s exit status: %v", args, err)
}
close(done)
return b.Bytes(), err
}

View File

@ -1,13 +1,13 @@
// Copyright 2016 The Go Authors. All rights reserved.
// Copyright 2021 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.
//go:build windows || plan9 || (js && wasm)
package runtime_test
package testenv
import "os"
// sigquit is the signal to send to kill a hanging testdata program.
// Sigquit is the signal to send to kill a hanging subprocess.
// On Unix we send SIGQUIT, but on non-Unix we only have os.Kill.
var sigquit = os.Kill
var Sigquit = os.Kill

View File

@ -0,0 +1,13 @@
// Copyright 2021 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.
//go:build aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris
package testenv
import "syscall"
// Sigquit is the signal to send to kill a hanging subprocess.
// Send SIGQUIT to get a stack trace.
var Sigquit = syscall.SIGQUIT

View File

@ -15,11 +15,9 @@ import (
"path/filepath"
"regexp"
"runtime"
"strconv"
"strings"
"sync"
"testing"
"time"
)
var toRemove []string
@ -71,43 +69,8 @@ func runBuiltTestProg(t *testing.T, exe, name string, env ...string) string {
if testing.Short() {
cmd.Env = append(cmd.Env, "RUNTIME_TEST_SHORT=1")
}
var b bytes.Buffer
cmd.Stdout = &b
cmd.Stderr = &b
if err := cmd.Start(); err != nil {
t.Fatalf("starting %s %s: %v", exe, name, err)
}
// If the process doesn't complete within 1 minute,
// assume it is hanging and kill it to get a stack trace.
p := cmd.Process
done := make(chan bool)
go func() {
scale := 1
// This GOARCH/GOOS test is copied from cmd/dist/test.go.
// TODO(iant): Have cmd/dist update the environment variable.
if runtime.GOARCH == "arm" || runtime.GOOS == "windows" {
scale = 2
}
if s := os.Getenv("GO_TEST_TIMEOUT_SCALE"); s != "" {
if sc, err := strconv.Atoi(s); err == nil {
scale = sc
}
}
select {
case <-done:
case <-time.After(time.Duration(scale) * time.Minute):
p.Signal(sigquit)
}
}()
if err := cmd.Wait(); err != nil {
t.Logf("%s %s exit status: %v", exe, name, err)
}
close(done)
return b.String()
out, _ := testenv.RunWithTimeout(t, cmd)
return string(out)
}
var serializeBuild = make(chan bool, 2)

View File

@ -21,16 +21,12 @@ import (
"unsafe"
)
// sigquit is the signal to send to kill a hanging testdata program.
// Send SIGQUIT to get a stack trace.
var sigquit = syscall.SIGQUIT
func init() {
if runtime.Sigisblocked(int(syscall.SIGQUIT)) {
// We can't use SIGQUIT to kill subprocesses because
// it's blocked. Use SIGKILL instead. See issue
// #19196 for an example of when this happens.
sigquit = syscall.SIGKILL
testenv.Sigquit = syscall.SIGKILL
}
}