github.com/go-asm/go@v1.21.1-0.20240213172139-40c5ead50c48/cmd/go/script/scripttest/scripttest.go (about)

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