runner: Clean up test logic.
This addresses some feedback in
https://boringssl-review.googlesource.com/c/boringssl/+/48131/1/ssl/test/runner/runner.go#1555,
pulled into a separate CL for clarity:
First, take the listener, waitChan, exec.Cmd trio and wrap them into a
shimProcess type. shimProcess is now responsible for the -port flag, so
it can manage the TCPListener internally.
Next, take the core test loop and moves it into a doExchanges()
function, so that it can use a more usual early return pattern for
errors, rather than thread err == nil through all the control flow. With
shimProcess pulled out, doExchanges() can just take a *shimProcess.
Finally, unacted-on err variable has gotten very far from where it's
actually used. Rename it to localErr, to align with our
expectedLocalError machinery.
Change-Id: I63697a5d79040ad77fa06c125253ec5031aeaf5c
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/48186
Commit-Queue: David Benjamin <davidben@google.com>
Reviewed-by: Adam Langley <agl@google.com>
diff --git a/ssl/test/runner/runner.go b/ssl/test/runner/runner.go
index b52a43b..f967850 100644
--- a/ssl/test/runner/runner.go
+++ b/ssl/test/runner/runner.go
@@ -1243,9 +1243,57 @@
errUnimplemented = errors.New("child process does not implement needed flags")
)
-// accept accepts a connection from listener, unless waitChan signals a process
-// exit first.
-func acceptOrWait(listener *net.TCPListener, waitChan chan error) (net.Conn, error) {
+type shimProcess struct {
+ cmd *exec.Cmd
+ waitChan chan error
+ listener *net.TCPListener
+ stdout, stderr bytes.Buffer
+}
+
+// newShimProcess starts a new shim with the specified executable, flags, and
+// environment. It internally creates a TCP listener and adds the the -port
+// flag.
+func newShimProcess(shimPath string, flags []string, env []string) (*shimProcess, error) {
+ shim := new(shimProcess)
+ var err error
+ shim.listener, err = net.ListenTCP("tcp", &net.TCPAddr{IP: net.IPv6loopback})
+ if err != nil {
+ shim.listener, err = net.ListenTCP("tcp4", &net.TCPAddr{IP: net.IP{127, 0, 0, 1}})
+ }
+ if err != nil {
+ return nil, err
+ }
+
+ flags = append([]string{"-port", strconv.Itoa(shim.listener.Addr().(*net.TCPAddr).Port)}, flags...)
+ if *useValgrind {
+ shim.cmd = valgrindOf(false, shimPath, flags...)
+ } else if *useGDB {
+ shim.cmd = gdbOf(shimPath, flags...)
+ } else if *useLLDB {
+ shim.cmd = lldbOf(shimPath, flags...)
+ } else if *useRR {
+ shim.cmd = rrOf(shimPath, flags...)
+ } else {
+ shim.cmd = exec.Command(shimPath, flags...)
+ }
+ shim.cmd.Stdin = os.Stdin
+ shim.cmd.Stdout = &shim.stdout
+ shim.cmd.Stderr = &shim.stderr
+ shim.cmd.Env = env
+
+ if err := shim.cmd.Start(); err != nil {
+ shim.listener.Close()
+ return nil, err
+ }
+
+ shim.waitChan = make(chan error, 1)
+ go func() { shim.waitChan <- shim.cmd.Wait() }()
+ return shim, nil
+}
+
+// accept returns a new TCP connection with the shim process, or returns an
+// error on timeout or shim exit.
+func (s *shimProcess) accept() (net.Conn, error) {
type connOrError struct {
conn net.Conn
err error
@@ -1253,21 +1301,93 @@
connChan := make(chan connOrError, 1)
go func() {
if !useDebugger() {
- listener.SetDeadline(time.Now().Add(*idleTimeout))
+ s.listener.SetDeadline(time.Now().Add(*idleTimeout))
}
- conn, err := listener.Accept()
+ conn, err := s.listener.Accept()
connChan <- connOrError{conn, err}
close(connChan)
}()
select {
case result := <-connChan:
return result.conn, result.err
- case childErr := <-waitChan:
- waitChan <- childErr
+ case childErr := <-s.waitChan:
+ s.waitChan <- childErr
+ if childErr == nil {
+ return nil, fmt.Errorf("child exited early with no error")
+ }
return nil, fmt.Errorf("child exited early: %s", childErr)
}
}
+// wait finishes the test and waits for the shim process to exit.
+func (s *shimProcess) wait() error {
+ // Close the listener now. This is to avoid hangs if the shim tries to open
+ // more connections than expected.
+ s.listener.Close()
+
+ if !useDebugger() {
+ waitTimeout := time.AfterFunc(*idleTimeout, func() {
+ s.cmd.Process.Kill()
+ })
+ defer waitTimeout.Stop()
+ }
+
+ err := <-s.waitChan
+ s.waitChan <- err
+ return err
+}
+
+// close releases resources associated with the shimProcess. This is safe to
+// call before or after |wait|.
+func (s *shimProcess) close() {
+ s.listener.Close()
+ s.cmd.Process.Kill()
+}
+
+func doExchanges(test *testCase, shim *shimProcess, resumeCount int, transcripts *[][]byte) error {
+ config := test.config
+ if *deterministic {
+ config.Rand = &deterministicRand{}
+ }
+
+ conn, err := shim.accept()
+ if err != nil {
+ return err
+ }
+ err = doExchange(test, &config, conn, false /* not a resumption */, transcripts, 0)
+ conn.Close()
+ if err != nil {
+ return err
+ }
+
+ for i := 0; i < resumeCount; i++ {
+ var resumeConfig Config
+ if test.resumeConfig != nil {
+ resumeConfig = *test.resumeConfig
+ if !test.newSessionsOnResume {
+ resumeConfig.SessionTicketKey = config.SessionTicketKey
+ resumeConfig.ClientSessionCache = config.ClientSessionCache
+ resumeConfig.ServerSessionCache = config.ServerSessionCache
+ }
+ resumeConfig.Rand = config.Rand
+ } else {
+ resumeConfig = config
+ }
+ var connResume net.Conn
+ connResume, err = shim.accept()
+ if err != nil {
+ return err
+ }
+ err = doExchange(test, &resumeConfig, connResume, true /* resumption */, transcripts, i+1)
+ connResume.Close()
+ if err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
+
func translateExpectedError(errorStr string) string {
if translated, ok := shimConfig.ErrorMap[errorStr]; ok {
return translated
@@ -1293,20 +1413,7 @@
}
}()
- listener, err := net.ListenTCP("tcp", &net.TCPAddr{IP: net.IPv6loopback})
- if err != nil {
- listener, err = net.ListenTCP("tcp4", &net.TCPAddr{IP: net.IP{127, 0, 0, 1}})
- }
- if err != nil {
- panic(err)
- }
- defer func() {
- if listener != nil {
- listener.Close()
- }
- }()
-
- flags := []string{"-port", strconv.Itoa(listener.Addr().(*net.TCPAddr).Port)}
+ var flags []string
if test.testType == serverTest {
flags = append(flags, "-server")
@@ -1495,88 +1602,26 @@
flags = append(flags, test.flags...)
- var shim *exec.Cmd
- if *useValgrind {
- shim = valgrindOf(false, shimPath, flags...)
- } else if *useGDB {
- shim = gdbOf(shimPath, flags...)
- } else if *useLLDB {
- shim = lldbOf(shimPath, flags...)
- } else if *useRR {
- shim = rrOf(shimPath, flags...)
- } else {
- shim = exec.Command(shimPath, flags...)
- }
- shim.Stdin = os.Stdin
- var stdoutBuf, stderrBuf bytes.Buffer
- shim.Stdout = &stdoutBuf
- shim.Stderr = &stderrBuf
+ var env []string
if mallocNumToFail >= 0 {
- shim.Env = os.Environ()
- shim.Env = append(shim.Env, "MALLOC_NUMBER_TO_FAIL="+strconv.FormatInt(mallocNumToFail, 10))
+ env = os.Environ()
+ env = append(env, "MALLOC_NUMBER_TO_FAIL="+strconv.FormatInt(mallocNumToFail, 10))
if *mallocTestDebug {
- shim.Env = append(shim.Env, "MALLOC_BREAK_ON_FAIL=1")
+ env = append(env, "MALLOC_BREAK_ON_FAIL=1")
}
- shim.Env = append(shim.Env, "_MALLOC_CHECK=1")
+ env = append(env, "_MALLOC_CHECK=1")
}
- if err := shim.Start(); err != nil {
- panic(err)
+ shim, err := newShimProcess(shimPath, flags, env)
+ if err != nil {
+ return err
}
- statusChan <- statusMsg{test: test, statusType: statusShimStarted, pid: shim.Process.Pid}
- waitChan := make(chan error, 1)
- go func() { waitChan <- shim.Wait() }()
+ defer shim.close()
- config := test.config
+ localErr := doExchanges(test, shim, resumeCount, &transcripts)
+ childErr := shim.wait()
- if *deterministic {
- config.Rand = &deterministicRand{}
- }
-
- conn, err := acceptOrWait(listener, waitChan)
- if err == nil {
- err = doExchange(test, &config, conn, false /* not a resumption */, &transcripts, 0)
- conn.Close()
- }
-
- for i := 0; err == nil && i < resumeCount; i++ {
- var resumeConfig Config
- if test.resumeConfig != nil {
- resumeConfig = *test.resumeConfig
- if !test.newSessionsOnResume {
- resumeConfig.SessionTicketKey = config.SessionTicketKey
- resumeConfig.ClientSessionCache = config.ClientSessionCache
- resumeConfig.ServerSessionCache = config.ServerSessionCache
- }
- resumeConfig.Rand = config.Rand
- } else {
- resumeConfig = config
- }
- var connResume net.Conn
- connResume, err = acceptOrWait(listener, waitChan)
- if err == nil {
- err = doExchange(test, &resumeConfig, connResume, true /* resumption */, &transcripts, i+1)
- connResume.Close()
- }
- }
-
- // Close the listener now. This is to avoid hangs should the shim try to
- // open more connections than expected.
- listener.Close()
- listener = nil
-
- var childErr error
- if useDebugger() {
- childErr = <-waitChan
- } else {
- waitTimeout := time.AfterFunc(*idleTimeout, func() {
- shim.Process.Kill()
- })
- childErr = <-waitChan
- waitTimeout.Stop()
- }
-
- // Now that the shim has exitted, all the settings files have been
+ // Now that the shim has exited, all the settings files have been
// written. Append the saved transcripts.
for i, transcript := range transcripts {
if err := appendTranscript(transcriptPrefix+strconv.Itoa(i), transcript); err != nil {
@@ -1599,8 +1644,8 @@
}
// Account for Windows line endings.
- stdout := strings.Replace(string(stdoutBuf.Bytes()), "\r\n", "\n", -1)
- stderr := strings.Replace(string(stderrBuf.Bytes()), "\r\n", "\n", -1)
+ stdout := strings.Replace(shim.stdout.String(), "\r\n", "\n", -1)
+ stderr := strings.Replace(shim.stderr.String(), "\r\n", "\n", -1)
// Work around an NDK / Android bug. The NDK r16 sometimes generates
// binaries with the DF_1_PIE, which the runtime linker on Android N
@@ -1622,22 +1667,22 @@
extraStderr = stderrParts[1]
}
- failed := err != nil || childErr != nil
+ failed := localErr != nil || childErr != nil
expectedError := translateExpectedError(test.expectedError)
correctFailure := len(expectedError) == 0 || strings.Contains(stderr, expectedError)
- localError := "none"
- if err != nil {
- localError = err.Error()
+ localErrString := "none"
+ if localErr != nil {
+ localErrString = localErr.Error()
}
if len(test.expectedLocalError) != 0 {
- correctFailure = correctFailure && strings.Contains(localError, test.expectedLocalError)
+ correctFailure = correctFailure && strings.Contains(localErrString, test.expectedLocalError)
}
if failed != test.shouldFail || failed && !correctFailure || mustFail {
- childError := "none"
+ childErrString := "none"
if childErr != nil {
- childError = childErr.Error()
+ childErrString = childErr.Error()
}
var msg string
@@ -1654,7 +1699,7 @@
panic("internal error")
}
- return fmt.Errorf("%s: local error '%s', child error '%s', stdout:\n%s\nstderr:\n%s\n%s", msg, localError, childError, stdout, stderr, extraStderr)
+ return fmt.Errorf("%s: local error '%s', child error '%s', stdout:\n%s\nstderr:\n%s\n%s", msg, localErrString, childErrString, stdout, stderr, extraStderr)
}
if len(extraStderr) > 0 || (!failed && len(stderr) > 0) {