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 }