Source file src/cmd/internal/script/scripttest/scripttest.go

     1  // Copyright 2022 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  // Package scripttest adapts the script engine for use in tests.
     6  package scripttest
     7  
     8  import (
     9  	"bufio"
    10  	"cmd/internal/pathcache"
    11  	"cmd/internal/script"
    12  	"errors"
    13  	"io"
    14  	"strings"
    15  	"testing"
    16  )
    17  
    18  // DefaultCmds returns a set of broadly useful script commands.
    19  //
    20  // This set includes all of the commands in script.DefaultCmds,
    21  // as well as a "skip" command that halts the script and causes the
    22  // testing.TB passed to Run to be skipped.
    23  func DefaultCmds() map[string]script.Cmd {
    24  	cmds := script.DefaultCmds()
    25  	cmds["skip"] = Skip()
    26  	return cmds
    27  }
    28  
    29  // DefaultConds returns a set of broadly useful script conditions.
    30  //
    31  // This set includes all of the conditions in script.DefaultConds,
    32  // as well as:
    33  //
    34  //   - Conditions of the form "exec:foo" are active when the executable "foo" is
    35  //     found in the test process's PATH, and inactive when the executable is
    36  //     not found.
    37  //
    38  //   - "short" is active when testing.Short() is true.
    39  //
    40  //   - "verbose" is active when testing.Verbose() is true.
    41  func DefaultConds() map[string]script.Cond {
    42  	conds := script.DefaultConds()
    43  	conds["exec"] = CachedExec()
    44  	conds["short"] = script.BoolCondition("testing.Short()", testing.Short())
    45  	conds["verbose"] = script.BoolCondition("testing.Verbose()", testing.Verbose())
    46  	return conds
    47  }
    48  
    49  // Run runs the script from the given filename starting at the given initial state.
    50  // When the script completes, Run closes the state.
    51  func Run(t testing.TB, e *script.Engine, s *script.State, filename string, testScript io.Reader) {
    52  	t.Helper()
    53  	err := func() (err error) {
    54  		log := new(strings.Builder)
    55  		log.WriteString("\n") // Start output on a new line for consistent indentation.
    56  
    57  		// Defer writing to the test log in case the script engine panics during execution,
    58  		// but write the log before we write the final "skip" or "FAIL" line.
    59  		t.Helper()
    60  		defer func() {
    61  			t.Helper()
    62  
    63  			if closeErr := s.CloseAndWait(log); err == nil {
    64  				err = closeErr
    65  			}
    66  
    67  			if log.Len() > 0 {
    68  				t.Log(strings.TrimSuffix(log.String(), "\n"))
    69  			}
    70  		}()
    71  
    72  		if testing.Verbose() {
    73  			// Add the environment to the start of the script log.
    74  			wait, err := script.Env().Run(s)
    75  			if err != nil {
    76  				t.Fatal(err)
    77  			}
    78  			if wait != nil {
    79  				stdout, stderr, err := wait(s)
    80  				if err != nil {
    81  					t.Fatalf("env: %v\n%s", err, stderr)
    82  				}
    83  				if len(stdout) > 0 {
    84  					s.Logf("%s\n", stdout)
    85  				}
    86  			}
    87  		}
    88  
    89  		return e.Execute(s, filename, bufio.NewReader(testScript), log)
    90  	}()
    91  
    92  	if skip := (skipError{}); errors.As(err, &skip) {
    93  		if skip.msg == "" {
    94  			t.Skip("SKIP")
    95  		} else {
    96  			t.Skipf("SKIP: %v", skip.msg)
    97  		}
    98  	}
    99  	if err != nil {
   100  		t.Errorf("FAIL: %v", err)
   101  	}
   102  }
   103  
   104  // Skip returns a sentinel error that causes Run to mark the test as skipped.
   105  func Skip() script.Cmd {
   106  	return script.Command(
   107  		script.CmdUsage{
   108  			Summary: "skip the current test",
   109  			Args:    "[msg]",
   110  		},
   111  		func(_ *script.State, args ...string) (script.WaitFunc, error) {
   112  			if len(args) > 1 {
   113  				return nil, script.ErrUsage
   114  			}
   115  			if len(args) == 0 {
   116  				return nil, skipError{""}
   117  			}
   118  			return nil, skipError{args[0]}
   119  		})
   120  }
   121  
   122  type skipError struct {
   123  	msg string
   124  }
   125  
   126  func (s skipError) Error() string {
   127  	if s.msg == "" {
   128  		return "skip"
   129  	}
   130  	return s.msg
   131  }
   132  
   133  // CachedExec returns a Condition that reports whether the PATH of the test
   134  // binary itself (not the script's current environment) contains the named
   135  // executable.
   136  func CachedExec() script.Cond {
   137  	return script.CachedCondition(
   138  		"<suffix> names an executable in the test binary's PATH",
   139  		func(name string) (bool, error) {
   140  			_, err := pathcache.LookPath(name)
   141  			return err == nil, nil
   142  		})
   143  }
   144  

View as plain text