github.com/hashicorp/packer@v1.14.3/packer_test/common/check/gadgets.go (about)

     1  package check
     2  
     3  import (
     4  	"fmt"
     5  	"reflect"
     6  	"strings"
     7  	"testing"
     8  
     9  	"github.com/hashicorp/go-multierror"
    10  	"github.com/hashicorp/go-version"
    11  )
    12  
    13  type Stream int
    14  
    15  const (
    16  	// BothStreams will use both stdout and stderr for performing a check
    17  	BothStreams Stream = iota
    18  	// OnlyStdout will only use stdout for performing a check
    19  	OnlyStdout
    20  	// OnlySterr will only use stderr for performing a check
    21  	OnlyStderr
    22  )
    23  
    24  func (s Stream) String() string {
    25  	switch s {
    26  	case BothStreams:
    27  		return "Both streams"
    28  	case OnlyStdout:
    29  		return "Stdout"
    30  	case OnlyStderr:
    31  		return "Stderr"
    32  	}
    33  
    34  	panic(fmt.Sprintf("Unknown stream value: %d", s))
    35  }
    36  
    37  // Checker represents anything that can be used in conjunction with Assert.
    38  //
    39  // The role of a checker is performing a test on a command's outputs/error, and
    40  // return an error if the test fails.
    41  //
    42  // Note: the Check method is the only required, however during tests the name
    43  // of the checker is printed out in case it fails, so it may be useful to have
    44  // a custom string for this: the `Name() string` method is exactly what to
    45  // implement for this kind of customization.
    46  type Checker interface {
    47  	Check(stdout, stderr string, err error) error
    48  }
    49  
    50  func InferName(c Checker) string {
    51  	if c == nil {
    52  		panic("nil checker - malformed test?")
    53  	}
    54  
    55  	checkerType := reflect.TypeOf(c)
    56  	_, ok := checkerType.MethodByName("Name")
    57  	if !ok {
    58  		return checkerType.String()
    59  	}
    60  
    61  	retVals := reflect.ValueOf(c).MethodByName("Name").Call([]reflect.Value{})
    62  	if len(retVals) != 1 {
    63  		panic(fmt.Sprintf("Name function called - returned %d values. Must be one string only.", len(retVals)))
    64  	}
    65  
    66  	return retVals[0].String()
    67  }
    68  
    69  func MustSucceed() Checker {
    70  	return mustSucceed{}
    71  }
    72  
    73  type mustSucceed struct{}
    74  
    75  func (_ mustSucceed) Check(stdout, stderr string, err error) error {
    76  	return err
    77  }
    78  
    79  func MustFail() Checker {
    80  	return mustFail{}
    81  }
    82  
    83  type mustFail struct{}
    84  
    85  func (_ mustFail) Check(stdout, stderr string, err error) error {
    86  	if err == nil {
    87  		return fmt.Errorf("unexpected command success")
    88  	}
    89  	return nil
    90  }
    91  
    92  type GrepOpts int
    93  
    94  const (
    95  	// Invert the check, i.e. by default an empty grep fails, if this is set, a non-empty grep fails
    96  	GrepInvert GrepOpts = iota
    97  	// Only grep stderr
    98  	GrepStderr
    99  	// Only grep stdout
   100  	GrepStdout
   101  )
   102  
   103  // Grep returns a checker that performs a regexp match on the command's output and returns an error if it failed
   104  //
   105  // Note: by default both streams will be checked by the grep
   106  func Grep(expression string, opts ...GrepOpts) Checker {
   107  	pc := PipeChecker{
   108  		name:   fmt.Sprintf("command | grep -E %q", expression),
   109  		stream: BothStreams,
   110  		pipers: []Pipe{
   111  			PipeGrep(expression),
   112  		},
   113  		check: ExpectNonEmptyInput(),
   114  	}
   115  	for _, opt := range opts {
   116  		switch opt {
   117  		case GrepInvert:
   118  			pc.check = ExpectEmptyInput()
   119  		case GrepStderr:
   120  			pc.stream = OnlyStderr
   121  		case GrepStdout:
   122  			pc.stream = OnlyStdout
   123  		}
   124  	}
   125  	return pc
   126  }
   127  
   128  func GrepInverted(expression string, opts ...GrepOpts) Checker {
   129  	return Grep(expression, append(opts, GrepInvert)...)
   130  }
   131  
   132  type PluginVersionTuple struct {
   133  	Source  string
   134  	Version *version.Version
   135  }
   136  
   137  func NewPluginVersionTuple(src, pluginVersion string) PluginVersionTuple {
   138  	ver := version.Must(version.NewVersion(pluginVersion))
   139  	return PluginVersionTuple{
   140  		Source:  src,
   141  		Version: ver,
   142  	}
   143  }
   144  
   145  type pluginsUsed struct {
   146  	invert  bool
   147  	plugins []PluginVersionTuple
   148  }
   149  
   150  func (pu pluginsUsed) Check(stdout, stderr string, _ error) error {
   151  	var opts []GrepOpts
   152  	if !pu.invert {
   153  		opts = append(opts, GrepInvert)
   154  	}
   155  
   156  	var retErr error
   157  
   158  	for _, pvt := range pu.plugins {
   159  		// `error` is ignored for Grep, so we can pass in nil
   160  		err := Grep(
   161  			fmt.Sprintf("%s_v%s[^:]+\\\\s*plugin process exited", pvt.Source, pvt.Version.Core()),
   162  			opts...,
   163  		).Check(stdout, stderr, nil)
   164  		if err != nil {
   165  			retErr = multierror.Append(retErr, err)
   166  		}
   167  	}
   168  
   169  	return retErr
   170  }
   171  
   172  // PluginsUsed is a glorified `Grep` checker that looks for a bunch of plugins
   173  // used from the logs of a packer build or packer validate.
   174  //
   175  // Each tuple passed as parameter is looked for in the logs using Grep
   176  func PluginsUsed(invert bool, plugins ...PluginVersionTuple) Checker {
   177  	return pluginsUsed{
   178  		invert:  invert,
   179  		plugins: plugins,
   180  	}
   181  }
   182  
   183  func Dump(t *testing.T) Checker {
   184  	return &dump{t}
   185  }
   186  
   187  type dump struct {
   188  	t *testing.T
   189  }
   190  
   191  func (d dump) Check(stdout, stderr string, err error) error {
   192  	d.t.Logf("Dumping command result.")
   193  	d.t.Logf("Stdout: %s", stdout)
   194  	d.t.Logf("stderr: %s", stderr)
   195  	return nil
   196  }
   197  
   198  type PanicCheck struct{}
   199  
   200  func (_ PanicCheck) Check(stdout, stderr string, _ error) error {
   201  	if strings.Contains(stdout, "= PACKER CRASH =") || strings.Contains(stderr, "= PACKER CRASH =") {
   202  		return fmt.Errorf("packer has crashed: this is never normal and should be investigated")
   203  	}
   204  	return nil
   205  }
   206  
   207  // CustomCheck is meant to be a one-off checker with a user-provided function.
   208  //
   209  // Use this if none of the existing checkers match your use case, and it is not
   210  // reusable/generic enough for use in other tests.
   211  type CustomCheck struct {
   212  	name      string
   213  	checkFunc func(stdout, stderr string, err error) error
   214  }
   215  
   216  func (c CustomCheck) Check(stdout, stderr string, err error) error {
   217  	return c.checkFunc(stdout, stderr, err)
   218  }
   219  
   220  func (c CustomCheck) Name() string {
   221  	return fmt.Sprintf("custom check - %s", c.name)
   222  }
   223  
   224  // LineCountCheck builds a pipe checker to count the number of lines on stdout by default
   225  //
   226  // To change the stream(s) on which to perform the check, you can call SetStream on the
   227  // returned pipe checker.
   228  func LineCountCheck(lines int) *PipeChecker {
   229  	return MkPipeCheck(fmt.Sprintf("line count (%d)", lines), LineCount()).
   230  		SetTester(IntCompare(Eq, lines)).
   231  		SetStream(OnlyStdout)
   232  }