mirror of https://github.com/hashicorp/packer
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
233 lines
5.5 KiB
233 lines
5.5 KiB
package check
|
|
|
|
import (
|
|
"fmt"
|
|
"reflect"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/hashicorp/go-multierror"
|
|
"github.com/hashicorp/go-version"
|
|
)
|
|
|
|
type Stream int
|
|
|
|
const (
|
|
// BothStreams will use both stdout and stderr for performing a check
|
|
BothStreams Stream = iota
|
|
// OnlyStdout will only use stdout for performing a check
|
|
OnlyStdout
|
|
// OnlySterr will only use stderr for performing a check
|
|
OnlyStderr
|
|
)
|
|
|
|
func (s Stream) String() string {
|
|
switch s {
|
|
case BothStreams:
|
|
return "Both streams"
|
|
case OnlyStdout:
|
|
return "Stdout"
|
|
case OnlyStderr:
|
|
return "Stderr"
|
|
}
|
|
|
|
panic(fmt.Sprintf("Unknown stream value: %d", s))
|
|
}
|
|
|
|
// Checker represents anything that can be used in conjunction with Assert.
|
|
//
|
|
// The role of a checker is performing a test on a command's outputs/error, and
|
|
// return an error if the test fails.
|
|
//
|
|
// Note: the Check method is the only required, however during tests the name
|
|
// of the checker is printed out in case it fails, so it may be useful to have
|
|
// a custom string for this: the `Name() string` method is exactly what to
|
|
// implement for this kind of customization.
|
|
type Checker interface {
|
|
Check(stdout, stderr string, err error) error
|
|
}
|
|
|
|
func InferName(c Checker) string {
|
|
if c == nil {
|
|
panic("nil checker - malformed test?")
|
|
}
|
|
|
|
checkerType := reflect.TypeOf(c)
|
|
_, ok := checkerType.MethodByName("Name")
|
|
if !ok {
|
|
return checkerType.String()
|
|
}
|
|
|
|
retVals := reflect.ValueOf(c).MethodByName("Name").Call([]reflect.Value{})
|
|
if len(retVals) != 1 {
|
|
panic(fmt.Sprintf("Name function called - returned %d values. Must be one string only.", len(retVals)))
|
|
}
|
|
|
|
return retVals[0].String()
|
|
}
|
|
|
|
func MustSucceed() Checker {
|
|
return mustSucceed{}
|
|
}
|
|
|
|
type mustSucceed struct{}
|
|
|
|
func (_ mustSucceed) Check(stdout, stderr string, err error) error {
|
|
return err
|
|
}
|
|
|
|
func MustFail() Checker {
|
|
return mustFail{}
|
|
}
|
|
|
|
type mustFail struct{}
|
|
|
|
func (_ mustFail) Check(stdout, stderr string, err error) error {
|
|
if err == nil {
|
|
return fmt.Errorf("unexpected command success")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
type GrepOpts int
|
|
|
|
const (
|
|
// Invert the check, i.e. by default an empty grep fails, if this is set, a non-empty grep fails
|
|
GrepInvert GrepOpts = iota
|
|
// Only grep stderr
|
|
GrepStderr
|
|
// Only grep stdout
|
|
GrepStdout
|
|
)
|
|
|
|
// Grep returns a checker that performs a regexp match on the command's output and returns an error if it failed
|
|
//
|
|
// Note: by default both streams will be checked by the grep
|
|
func Grep(expression string, opts ...GrepOpts) Checker {
|
|
pc := PipeChecker{
|
|
name: fmt.Sprintf("command | grep -E %q", expression),
|
|
stream: BothStreams,
|
|
pipers: []Pipe{
|
|
PipeGrep(expression),
|
|
},
|
|
check: ExpectNonEmptyInput(),
|
|
}
|
|
for _, opt := range opts {
|
|
switch opt {
|
|
case GrepInvert:
|
|
pc.check = ExpectEmptyInput()
|
|
case GrepStderr:
|
|
pc.stream = OnlyStderr
|
|
case GrepStdout:
|
|
pc.stream = OnlyStdout
|
|
}
|
|
}
|
|
return pc
|
|
}
|
|
|
|
func GrepInverted(expression string, opts ...GrepOpts) Checker {
|
|
return Grep(expression, append(opts, GrepInvert)...)
|
|
}
|
|
|
|
type PluginVersionTuple struct {
|
|
Source string
|
|
Version *version.Version
|
|
}
|
|
|
|
func NewPluginVersionTuple(src, pluginVersion string) PluginVersionTuple {
|
|
ver := version.Must(version.NewVersion(pluginVersion))
|
|
return PluginVersionTuple{
|
|
Source: src,
|
|
Version: ver,
|
|
}
|
|
}
|
|
|
|
type pluginsUsed struct {
|
|
invert bool
|
|
plugins []PluginVersionTuple
|
|
}
|
|
|
|
func (pu pluginsUsed) Check(stdout, stderr string, _ error) error {
|
|
var opts []GrepOpts
|
|
if !pu.invert {
|
|
opts = append(opts, GrepInvert)
|
|
}
|
|
|
|
var retErr error
|
|
|
|
for _, pvt := range pu.plugins {
|
|
// `error` is ignored for Grep, so we can pass in nil
|
|
err := Grep(
|
|
fmt.Sprintf("%s_v%s[^:]+\\\\s*plugin process exited", pvt.Source, pvt.Version.Core()),
|
|
opts...,
|
|
).Check(stdout, stderr, nil)
|
|
if err != nil {
|
|
retErr = multierror.Append(retErr, err)
|
|
}
|
|
}
|
|
|
|
return retErr
|
|
}
|
|
|
|
// PluginsUsed is a glorified `Grep` checker that looks for a bunch of plugins
|
|
// used from the logs of a packer build or packer validate.
|
|
//
|
|
// Each tuple passed as parameter is looked for in the logs using Grep
|
|
func PluginsUsed(invert bool, plugins ...PluginVersionTuple) Checker {
|
|
return pluginsUsed{
|
|
invert: invert,
|
|
plugins: plugins,
|
|
}
|
|
}
|
|
|
|
func Dump(t *testing.T) Checker {
|
|
return &dump{t}
|
|
}
|
|
|
|
type dump struct {
|
|
t *testing.T
|
|
}
|
|
|
|
func (d dump) Check(stdout, stderr string, err error) error {
|
|
d.t.Logf("Dumping command result.")
|
|
d.t.Logf("Stdout: %s", stdout)
|
|
d.t.Logf("stderr: %s", stderr)
|
|
return nil
|
|
}
|
|
|
|
type PanicCheck struct{}
|
|
|
|
func (_ PanicCheck) Check(stdout, stderr string, _ error) error {
|
|
if strings.Contains(stdout, "= PACKER CRASH =") || strings.Contains(stderr, "= PACKER CRASH =") {
|
|
return fmt.Errorf("packer has crashed: this is never normal and should be investigated")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// CustomCheck is meant to be a one-off checker with a user-provided function.
|
|
//
|
|
// Use this if none of the existing checkers match your use case, and it is not
|
|
// reusable/generic enough for use in other tests.
|
|
type CustomCheck struct {
|
|
name string
|
|
checkFunc func(stdout, stderr string, err error) error
|
|
}
|
|
|
|
func (c CustomCheck) Check(stdout, stderr string, err error) error {
|
|
return c.checkFunc(stdout, stderr, err)
|
|
}
|
|
|
|
func (c CustomCheck) Name() string {
|
|
return fmt.Sprintf("custom check - %s", c.name)
|
|
}
|
|
|
|
// LineCountCheck builds a pipe checker to count the number of lines on stdout by default
|
|
//
|
|
// To change the stream(s) on which to perform the check, you can call SetStream on the
|
|
// returned pipe checker.
|
|
func LineCountCheck(lines int) *PipeChecker {
|
|
return MkPipeCheck(fmt.Sprintf("line count (%d)", lines), LineCount()).
|
|
SetTester(IntCompare(Eq, lines)).
|
|
SetStream(OnlyStdout)
|
|
}
|