blob: 6d7bd7863b9f42b1bebf1906e794c5119d599e34 [file] [log] [blame]
// Copyright 2022 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.
// Package scripttest adapts the script engine for use in tests.
package scripttest
import (
"bufio"
"cmd/go/internal/cfg"
"cmd/go/internal/script"
"errors"
"io"
"strings"
"testing"
)
// DefaultCmds returns a set of broadly useful script commands.
//
// This set includes all of the commands in script.DefaultCmds,
// as well as a "skip" command that halts the script and causes the
// testing.TB passed to Run to be skipped.
func DefaultCmds() map[string]script.Cmd {
cmds := script.DefaultCmds()
cmds["skip"] = Skip()
return cmds
}
// DefaultConds returns a set of broadly useful script conditions.
//
// This set includes all of the conditions in script.DefaultConds,
// as well as:
//
// - Conditions of the form "exec:foo" are active when the executable "foo" is
// found in the test process's PATH, and inactive when the executable is
// not found.
//
// - "short" is active when testing.Short() is true.
//
// - "verbose" is active when testing.Verbose() is true.
func DefaultConds() map[string]script.Cond {
conds := script.DefaultConds()
conds["exec"] = CachedExec()
conds["short"] = script.BoolCondition("testing.Short()", testing.Short())
conds["verbose"] = script.BoolCondition("testing.Verbose()", testing.Verbose())
return conds
}
// Run runs the script from the given filename starting at the given initial state.
// When the script completes, Run closes the state.
func Run(t testing.TB, e *script.Engine, s *script.State, filename string, testScript io.Reader) {
t.Helper()
err := func() (err error) {
log := new(strings.Builder)
log.WriteString("\n") // Start output on a new line for consistent indentation.
// Defer writing to the test log in case the script engine panics during execution,
// but write the log before we write the final "skip" or "FAIL" line.
t.Helper()
defer func() {
t.Helper()
if closeErr := s.CloseAndWait(log); err == nil {
err = closeErr
}
if log.Len() > 0 {
t.Log(strings.TrimSuffix(log.String(), "\n"))
}
}()
if testing.Verbose() {
// Add the environment to the start of the script log.
wait, err := script.Env().Run(s)
if err != nil {
t.Fatal(err)
}
if wait != nil {
stdout, stderr, err := wait(s)
if err != nil {
t.Fatalf("env: %v\n%s", err, stderr)
}
if len(stdout) > 0 {
s.Logf("%s\n", stdout)
}
}
}
return e.Execute(s, filename, bufio.NewReader(testScript), log)
}()
if skip := (skipError{}); errors.As(err, &skip) {
if skip.msg == "" {
t.Skip("SKIP")
} else {
t.Skipf("SKIP: %v", skip.msg)
}
}
if err != nil {
t.Errorf("FAIL: %v", err)
}
}
// Skip returns a sentinel error that causes Run to mark the test as skipped.
func Skip() script.Cmd {
return script.Command(
script.CmdUsage{
Summary: "skip the current test",
Args: "[msg]",
},
func(_ *script.State, args ...string) (script.WaitFunc, error) {
if len(args) > 1 {
return nil, script.ErrUsage
}
if len(args) == 0 {
return nil, skipError{""}
}
return nil, skipError{args[0]}
})
}
type skipError struct {
msg string
}
func (s skipError) Error() string {
if s.msg == "" {
return "skip"
}
return s.msg
}
// CachedExec returns a Condition that reports whether the PATH of the test
// binary itself (not the script's current environment) contains the named
// executable.
func CachedExec() script.Cond {
return script.CachedCondition(
"<suffix> names an executable in the test binary's PATH",
func(name string) (bool, error) {
_, err := cfg.LookPath(name)
return err == nil, nil
})
}