Files
Packer-Cn/test/plugin_test.go
T
Lucas Bajolet 486c1d3dcb test: add base suite for core acceptance testing
Acceptance testing, i.e. running Packer core commands in a controlled
environment and ensuring the behaviour is consistent to what we
expect/document, is not something we have a robust and usable framework
for at the moment.

This commit is a proposal for a base testing framework of the sort, that
is meant to be shipped with packer core, and which will eventually host
most of the tests we currently do in command where we mock an
environment.
2024-06-10 09:59:32 -04:00

193 lines
5.6 KiB
Go

package test
import (
"fmt"
"os"
"os/exec"
"path/filepath"
"runtime"
"sync"
"testing"
"github.com/hashicorp/go-version"
)
var compiledPlugins = struct {
pluginVersions map[string]string
RWMutex sync.RWMutex
}{
pluginVersions: map[string]string{},
}
func StorePluginVersion(pluginVersion, path string) {
compiledPlugins.RWMutex.Lock()
defer compiledPlugins.RWMutex.Unlock()
compiledPlugins.pluginVersions[pluginVersion] = path
}
func LoadPluginVersion(pluginVersion string) (string, bool) {
compiledPlugins.RWMutex.RLock()
defer compiledPlugins.RWMutex.RUnlock()
path, ok := compiledPlugins.pluginVersions[pluginVersion]
return path, ok
}
var tempPluginBinaryPath = struct {
path string
once sync.Once
}{}
// PluginBinaryDir returns the path to the directory where temporary binaries will be compiled
func PluginBinaryDir() string {
tempPluginBinaryPath.once.Do(func() {
tempDir, err := os.MkdirTemp("", "packer-core-acc-test-")
if err != nil {
panic(fmt.Sprintf("failed to create temporary directory for compiled plugins: %s", err))
}
tempPluginBinaryPath.path = tempDir
})
return tempPluginBinaryPath.path
}
type PluginBuildConfig struct {
version *version.Version
}
func NewPluginBuildConfig(versionStr string) *PluginBuildConfig {
return &PluginBuildConfig{
version.Must(version.NewVersion(versionStr)),
}
}
// Version is the core version string of the test plugin.
//
// If the version isn't set, it'll default to 1.0.0
func (pc PluginBuildConfig) Version() string {
return pc.version.Core().String()
}
func (pc PluginBuildConfig) PreRelease() string {
return pc.version.Prerelease()
}
func (pc PluginBuildConfig) Metadata() string {
return pc.version.Metadata()
}
// LDFlags compiles the ldflags for the plugin to compile based on the information provided.
func (pc PluginBuildConfig) LDFlags() string {
pluginPackage := "github.com/hashicorp/packer-plugin-tester"
ldflagsArg := fmt.Sprintf("-X %s/version.Version=%s", pluginPackage, pc.Version())
if pc.PreRelease() != "" {
ldflagsArg = fmt.Sprintf("%s -X %s/version.VersionPrerelease=%s", ldflagsArg, pluginPackage, pc.PreRelease())
}
if pc.Metadata() != "" {
ldflagsArg = fmt.Sprintf("%s -X %s/version.VersionMetadata=%s", ldflagsArg, pluginPackage, pc.Metadata())
}
return ldflagsArg
}
// BinaryName is the raw name of the plugin binary to produce
//
// It's expected to be in the "mini-plugin_<version>[-<prerelease>][+<metadata>]" format
func (pc PluginBuildConfig) BinaryName() string {
retStr := fmt.Sprintf("mini-plugin_%s", pc.Version())
if pc.PreRelease() != "" {
retStr = fmt.Sprintf("%s-%s", retStr, pc.PreRelease())
}
if pc.Metadata() != "" {
retStr = fmt.Sprintf("%s+%s", retStr, pc.Metadata())
}
return retStr
}
// BuildSimplePlugin creates a plugin that essentially does nothing.
//
// The plugin's code is contained in a subdirectory of this, and lets us
// change the attributes of the plugin binary itself, like the SDK version,
// the plugin's version, etc.
//
// The plugin is functional, and can be used to run builds with.
// There won't be anything substantial created though, its goal is only
// to validate the core functionality of Packer.
//
// The path to the plugin is returned, it won't be removed automatically
// though, deletion is the caller's responsibility.
func BuildSimplePlugin(config *PluginBuildConfig, t *testing.T) {
t.Logf("Building plugin in version %v", config.version)
testDir, err := currentDir()
if err != nil {
t.Fatalf("failed to compile plugin binary: %s", err)
}
miniPluginDir := filepath.Join(testDir, "mini_plugin")
outBin := filepath.Join(PluginBinaryDir(), config.BinaryName())
compileCommand := exec.Command("go", "build", "-C", miniPluginDir, "-o", outBin, "-ldflags", config.LDFlags(), ".")
logs, err := compileCommand.CombinedOutput()
if err != nil {
t.Fatalf("failed to compile plugin binary: %s\ncompiler logs: %s", err, logs)
}
StorePluginVersion(config.version.String(), outBin)
}
// currentDir returns the directory in which the current file is located.
//
// Since we're in tests it's reliable as they're supposed to run on the same
// machine the binary's compiled from, but goes to say it's not meant for use
// in distributed binaries.
func currentDir() (string, error) {
// pc uintptr, file string, line int, ok bool
_, testDir, _, ok := runtime.Caller(0)
if !ok {
return "", fmt.Errorf("couldn't get the location of the test suite file")
}
return filepath.Dir(testDir), nil
}
// MakePluginDir installs a list of plugins into a temporary directory and returns its path
//
// This can be set in the environment for a test through a function like t.SetEnv(), so
// packer will be able to use that directory for running its functions.
//
// Deletion of the directory is the caller's responsibility.
func (ts *PackerTestSuite) MakePluginDir(t *testing.T, pluginVersions ...string) (pluginTempDir string) {
var err error
defer func() {
if err != nil {
if pluginTempDir != "" {
os.RemoveAll(pluginTempDir)
}
t.Fatalf("failed to prepare temporary plugin directory %q: %s", pluginTempDir, err)
}
}()
pluginTempDir, err = os.MkdirTemp("", "packer-plugin-dir-temp-")
if err != nil {
return
}
for _, pluginVersion := range pluginVersions {
path, _ := LoadPluginVersion(pluginVersion)
cmd := ts.PackerCommand().SetArgs("plugins", "install", "--path", path, "github.com/hashicorp/tester").AddEnv("PACKER_PLUGIN_PATH", pluginTempDir)
cmd.Assert(t, MustSucceed{})
out, stderr, cmdErr := cmd.Run(t)
if cmdErr != nil {
err = fmt.Errorf("failed to install tester plugin version %q: %s\nCommand stdout: %s\nCommand stderr: %s", pluginVersion, err, out, stderr)
return
}
}
return pluginTempDir
}