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.
213 lines
5.9 KiB
213 lines
5.9 KiB
package common
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"os/exec"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/hashicorp/packer/packer_test/common/check"
|
|
)
|
|
|
|
type packerCommand struct {
|
|
runs int
|
|
packerPath string
|
|
args []string
|
|
env map[string]string
|
|
stdin string
|
|
stderr *strings.Builder
|
|
stdout *strings.Builder
|
|
workdir string
|
|
err error
|
|
t *testing.T
|
|
fatalfAssert bool
|
|
}
|
|
|
|
// PackerCommand creates a skeleton of packer command with the ability to execute gadgets on the outputs of the command.
|
|
func (ts *PackerTestSuite) PackerCommand() *packerCommand {
|
|
return &packerCommand{
|
|
packerPath: ts.packerPath,
|
|
runs: 1,
|
|
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(),
|
|
// Since those commands are used to run tests, we want to
|
|
// make them as self-contained and quick as possible.
|
|
// Removing telemetry here is probably for the best.
|
|
"CHECKPOINT_DISABLE": "1",
|
|
},
|
|
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 *PluginDirSpec) *packerCommand {
|
|
return pc.UseRawPluginDir(dir.dirPath)
|
|
}
|
|
|
|
// UseRawPluginDir is meant to be used for setting the plugin directory with a
|
|
// raw directory path instead of a PluginDirSpec.
|
|
func (pc *packerCommand) UseRawPluginDir(dirPath string) *packerCommand {
|
|
return pc.AddEnv("PACKER_PLUGIN_PATH", dirPath)
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
// Runs changes the number of times the command is run.
|
|
//
|
|
// This is useful for testing non-deterministic bugs, which we can reasonably
|
|
// execute multiple times and expose a dysfunctional run.
|
|
//
|
|
// This is not necessarily a guarantee that the code is sound, but so long as
|
|
// we run the test enough times, we can be decently confident the problem has
|
|
// been solved.
|
|
func (pc *packerCommand) Runs(runs int) *packerCommand {
|
|
if runs <= 0 {
|
|
panic(fmt.Sprintf("cannot set command runs to %d", runs))
|
|
}
|
|
|
|
pc.runs = runs
|
|
return pc
|
|
}
|
|
|
|
// Stdin changes the contents of the stdin for the command.
|
|
//
|
|
// Each run will be populated with a copy of this string, and wait for the
|
|
// command to terminate.
|
|
//
|
|
// Note: this could lead to a deadlock if the command doesn't support stdin
|
|
// closing after it's finished feeding the inputs.
|
|
func (pc *packerCommand) Stdin(in string) *packerCommand {
|
|
pc.stdin = in
|
|
return pc
|
|
}
|
|
|
|
// SetAssertFatal allows changing how Assert behaves when reporting an error.
|
|
//
|
|
// By default Assert will invoke t.Errorf with the error details, but this can be
|
|
// changed to a t.Fatalf so that if the assertion fails, the test invoking it will
|
|
// also immediately fail and stop execution.
|
|
func (pc *packerCommand) SetAssertFatal() *packerCommand {
|
|
pc.fatalfAssert = true
|
|
return pc
|
|
}
|
|
|
|
// Run executes the packer command with the args/env requested and returns the
|
|
// output streams (stdout, stderr)
|
|
//
|
|
// Note: while originally "Run" was designed to be idempotent, with the
|
|
// introduction of multiple runs for a command, this is not the case anymore
|
|
// and the function should not be considered thread-safe anymore.
|
|
func (pc *packerCommand) run() (string, string, error) {
|
|
if pc.runs <= 0 {
|
|
return pc.stdout.String(), pc.stderr.String(), pc.err
|
|
}
|
|
pc.runs--
|
|
|
|
pc.stdout = &strings.Builder{}
|
|
pc.stderr = &strings.Builder{}
|
|
|
|
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.stdin != "" {
|
|
cmd.Stdin = strings.NewReader(pc.stdin)
|
|
}
|
|
|
|
if pc.workdir != "" {
|
|
cmd.Dir = pc.workdir
|
|
}
|
|
|
|
pc.err = cmd.Run()
|
|
|
|
// Check that the command didn't panic, and if it did, we can immediately error
|
|
panicErr := check.PanicCheck{}.Check(pc.stdout.String(), pc.stderr.String(), pc.err)
|
|
if panicErr != nil {
|
|
pc.t.Fatalf("Packer panicked during execution: %s", panicErr)
|
|
}
|
|
|
|
return pc.stdout.String(), pc.stderr.String(), pc.err
|
|
}
|
|
|
|
// Output returns the results of the latest Run that was executed.
|
|
//
|
|
// In general there is only one run of the command, but as it can be changed
|
|
// through the Runs function, only the latest run will be returned.
|
|
//
|
|
// If the command was not run (through Assert), this will make the test fail
|
|
// immediately.
|
|
func (pc *packerCommand) Output() (string, string, error) {
|
|
if pc.runs > 0 {
|
|
pc.t.Fatalf("command was not run, invoke Assert first, then Output.")
|
|
}
|
|
|
|
return pc.stdout.String(), pc.stderr.String(), pc.err
|
|
}
|
|
|
|
func (pc *packerCommand) Assert(checks ...check.Checker) {
|
|
attempt := 0
|
|
for pc.runs > 0 {
|
|
attempt++
|
|
stdout, stderr, err := pc.run()
|
|
|
|
for _, checker := range checks {
|
|
checkErr := checker.Check(stdout, stderr, err)
|
|
if checkErr != nil {
|
|
checkerName := check.InferName(checker)
|
|
|
|
pc.t.Errorf("check %q failed: %s", checkerName, checkErr)
|
|
}
|
|
}
|
|
|
|
if pc.t.Failed() {
|
|
pc.t.Errorf("attempt %d failed validation", attempt)
|
|
|
|
pc.t.Logf("dumping stdout: %s", stdout)
|
|
pc.t.Logf("dumping stdout: %s", stderr)
|
|
|
|
if pc.fatalfAssert {
|
|
pc.t.Fatalf("stopping test now because of failures reported")
|
|
}
|
|
|
|
break
|
|
}
|
|
}
|
|
}
|