-
Notifications
You must be signed in to change notification settings - Fork 3.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #13054 from hashicorp/backport/acc_test_logic/wron…
…gly-witty-moccasin This pull request was automerged via backport-assistant
- Loading branch information
Showing
55 changed files
with
4,349 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
package packer_test | ||
|
||
import ( | ||
"fmt" | ||
"math/rand" | ||
"os" | ||
"os/exec" | ||
"path/filepath" | ||
"runtime" | ||
"testing" | ||
) | ||
|
||
// BuildTestPacker builds a new Packer binary based on the current state of the repository. | ||
// | ||
// If for some reason the binary cannot be built, we will immediately exit with an error. | ||
func BuildTestPacker(t *testing.T) (string, error) { | ||
testDir, err := currentDir() | ||
if err != nil { | ||
return "", fmt.Errorf("failed to compile packer binary: %s", err) | ||
} | ||
|
||
packerCoreDir := filepath.Dir(testDir) | ||
|
||
outBin := filepath.Join(os.TempDir(), fmt.Sprintf("packer_core-%d", rand.Int())) | ||
if runtime.GOOS == "windows" { | ||
outBin = fmt.Sprintf("%s.exe", outBin) | ||
} | ||
|
||
compileCommand := exec.Command("go", "build", "-C", packerCoreDir, "-o", outBin) | ||
logs, err := compileCommand.CombinedOutput() | ||
if err != nil { | ||
t.Fatalf("failed to compile Packer core: %s\ncompilation logs: %s", err, logs) | ||
} | ||
|
||
return outBin, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,118 @@ | ||
package packer_test | ||
|
||
import ( | ||
"fmt" | ||
"os" | ||
"os/exec" | ||
"strings" | ||
"sync" | ||
"testing" | ||
) | ||
|
||
type packerCommand struct { | ||
once sync.Once | ||
packerPath string | ||
args []string | ||
env map[string]string | ||
stderr *strings.Builder | ||
stdout *strings.Builder | ||
workdir string | ||
err error | ||
t *testing.T | ||
} | ||
|
||
// PackerCommand creates a skeleton of packer command with the ability to execute gadgets on the outputs of the command. | ||
func (ts *PackerTestSuite) PackerCommand() *packerCommand { | ||
stderr := &strings.Builder{} | ||
stdout := &strings.Builder{} | ||
|
||
return &packerCommand{ | ||
packerPath: ts.packerPath, | ||
env: map[string]string{ | ||
"PACKER_LOG": "1", | ||
// Required for Windows, otherwise since we overwrite all | ||
// the envvars for the test and Go relies on that envvar | ||
// being set in order to return another path than | ||
// C:\Windows by default | ||
// | ||
// If we don't have it, Packer immediately errors upon | ||
// invocation as the temporary logfile that we write in | ||
// case of Panic will fail to be created (unless tests | ||
// are running as Administrator, but please don't). | ||
"TMP": os.TempDir(), | ||
}, | ||
stderr: stderr, | ||
stdout: stdout, | ||
t: ts.T(), | ||
} | ||
} | ||
|
||
// NoVerbose removes the `PACKER_LOG=1` environment variable from the command | ||
func (pc *packerCommand) NoVerbose() *packerCommand { | ||
_, ok := pc.env["PACKER_LOG"] | ||
if ok { | ||
delete(pc.env, "PACKER_LOG") | ||
} | ||
return pc | ||
} | ||
|
||
// SetWD changes the directory Packer is invoked from | ||
func (pc *packerCommand) SetWD(dir string) *packerCommand { | ||
pc.workdir = dir | ||
return pc | ||
} | ||
|
||
// UsePluginDir sets the plugin directory in the environment to `dir` | ||
func (pc *packerCommand) UsePluginDir(dir string) *packerCommand { | ||
return pc.AddEnv("PACKER_PLUGIN_PATH", dir) | ||
} | ||
|
||
func (pc *packerCommand) SetArgs(args ...string) *packerCommand { | ||
pc.args = args | ||
return pc | ||
} | ||
|
||
func (pc *packerCommand) AddEnv(key, val string) *packerCommand { | ||
pc.env[key] = val | ||
return pc | ||
} | ||
|
||
// Run executes the packer command with the args/env requested and returns the | ||
// output streams (stdout, stderr) | ||
// | ||
// Note: "Run" will only execute the command once, and return the streams and | ||
// error from the only execution for every subsequent call | ||
func (pc *packerCommand) Run() (string, string, error) { | ||
pc.once.Do(pc.doRun) | ||
|
||
return pc.stdout.String(), pc.stderr.String(), pc.err | ||
} | ||
|
||
func (pc *packerCommand) doRun() { | ||
cmd := exec.Command(pc.packerPath, pc.args...) | ||
for key, val := range pc.env { | ||
cmd.Env = append(cmd.Env, fmt.Sprintf("%s=%s", key, val)) | ||
} | ||
cmd.Stdout = pc.stdout | ||
cmd.Stderr = pc.stderr | ||
|
||
if pc.workdir != "" { | ||
cmd.Dir = pc.workdir | ||
} | ||
|
||
pc.err = cmd.Run() | ||
} | ||
|
||
func (pc *packerCommand) Assert(checks ...Checker) { | ||
stdout, stderr, err := pc.Run() | ||
|
||
checks = append(checks, PanicCheck{}) | ||
|
||
for _, check := range checks { | ||
checkErr := check.Check(stdout, stderr, err) | ||
if checkErr != nil { | ||
checkerName := InferName(check) | ||
pc.t.Errorf("check %q failed: %s", checkerName, checkErr) | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,170 @@ | ||
package packer_test | ||
|
||
import ( | ||
"fmt" | ||
"reflect" | ||
"strings" | ||
"testing" | ||
) | ||
|
||
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 | ||
} | ||
|
||
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) | ||
} |
Oops, something went wrong.