github.com/hashicorp/packer@v1.14.3/acctest/testing.go (about)

     1  // Copyright (c) HashiCorp, Inc.
     2  // SPDX-License-Identifier: BUSL-1.1
     3  
     4  package acctest
     5  
     6  import (
     7  	"context"
     8  	"fmt"
     9  	"io"
    10  	"log"
    11  	"os"
    12  	"strings"
    13  	"testing"
    14  
    15  	packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
    16  	"github.com/hashicorp/packer-plugin-sdk/template"
    17  	"github.com/hashicorp/packer/packer"
    18  	"github.com/hashicorp/packer/provisioner/file"
    19  	shellprovisioner "github.com/hashicorp/packer/provisioner/shell"
    20  )
    21  
    22  // TestEnvVar must be set to a non-empty value for acceptance tests to run.
    23  const TestEnvVar = "PACKER_ACC"
    24  
    25  // TestCase is a single set of tests to run for a backend. A TestCase
    26  // should generally map 1:1 to each test method for your acceptance
    27  // tests.
    28  type TestCase struct {
    29  	// Precheck, if non-nil, will be called once before the test case
    30  	// runs at all. This can be used for some validation prior to the
    31  	// test running.
    32  	PreCheck func()
    33  
    34  	// Builder is the Builder that will be tested. It will be available
    35  	// as the "test" builder in the template.
    36  	Builder packersdk.Builder
    37  
    38  	// Template is the template contents to use.
    39  	Template string
    40  
    41  	// Check is called after this step is executed in order to test that
    42  	// the step executed successfully. If this is not set, then the next
    43  	// step will be called
    44  	Check TestCheckFunc
    45  
    46  	// Teardown will be called before the test case is over regardless
    47  	// of if the test succeeded or failed. This should return an error
    48  	// in the case that the test can't guarantee all resources were
    49  	// properly cleaned up.
    50  	Teardown TestTeardownFunc
    51  
    52  	// If SkipArtifactTeardown is true, we will not attempt to destroy the
    53  	// artifact created in this test run.
    54  	SkipArtifactTeardown bool
    55  	// If set, overrides the default provisioner store with custom provisioners.
    56  	// This can be useful for running acceptance tests for a particular
    57  	// provisioner using a specific builder.
    58  	// Default provisioner store:
    59  	// ProvisionerStore: packersdk.MapOfProvisioner{
    60  	// 	"shell": func() (packersdk.Provisioner, error) { return &shellprovisioner.Provisioner{}, nil },
    61  	// 	"file":  func() (packersdk.Provisioner, error) { return &file.Provisioner{}, nil },
    62  	// },
    63  	ProvisionerStore packersdk.MapOfProvisioner
    64  }
    65  
    66  // TestCheckFunc is the callback used for Check in TestStep.
    67  type TestCheckFunc func([]packersdk.Artifact) error
    68  
    69  // TestTeardownFunc is the callback used for Teardown in TestCase.
    70  type TestTeardownFunc func() error
    71  
    72  // TestT is the interface used to handle the test lifecycle of a test.
    73  //
    74  // Users should just use a *testing.T object, which implements this.
    75  type TestT interface {
    76  	Error(args ...interface{})
    77  	Fatal(args ...interface{})
    78  	Skip(args ...interface{})
    79  }
    80  
    81  type TestBuilderSet struct {
    82  	packer.BuilderSet
    83  	StartFn func(name string) (packersdk.Builder, error)
    84  }
    85  
    86  func (tbs TestBuilderSet) Start(name string) (packersdk.Builder, error) { return tbs.StartFn(name) }
    87  
    88  // Test performs an acceptance test on a backend with the given test case.
    89  //
    90  // Tests are not run unless an environmental variable "PACKER_ACC" is
    91  // set to some non-empty value. This is to avoid test cases surprising
    92  // a user by creating real resources.
    93  //
    94  // Tests will fail unless the verbose flag (`go test -v`, or explicitly
    95  // the "-test.v" flag) is set. Because some acceptance tests take quite
    96  // long, we require the verbose flag so users are able to see progress
    97  // output.
    98  func Test(t TestT, c TestCase) {
    99  	// We only run acceptance tests if an env var is set because they're
   100  	// slow and generally require some outside configuration.
   101  	if os.Getenv(TestEnvVar) == "" {
   102  		t.Skip(fmt.Sprintf(
   103  			"Acceptance tests skipped unless env '%s' set",
   104  			TestEnvVar))
   105  		return
   106  	}
   107  
   108  	// We require verbose mode so that the user knows what is going on.
   109  	if !testTesting && !testing.Verbose() {
   110  		t.Fatal("Acceptance tests must be run with the -v flag on tests")
   111  		return
   112  	}
   113  
   114  	// Run the PreCheck if we have it
   115  	if c.PreCheck != nil {
   116  		c.PreCheck()
   117  	}
   118  
   119  	// Parse the template
   120  	log.Printf("[DEBUG] Parsing template...")
   121  	tpl, err := template.Parse(strings.NewReader(c.Template))
   122  	if err != nil {
   123  		t.Fatal(fmt.Sprintf("Failed to parse template: %s", err))
   124  		return
   125  	}
   126  
   127  	if c.ProvisionerStore == nil {
   128  		c.ProvisionerStore = packersdk.MapOfProvisioner{
   129  			"shell": func() (packersdk.Provisioner, error) { return &shellprovisioner.Provisioner{}, nil },
   130  			"file":  func() (packersdk.Provisioner, error) { return &file.Provisioner{}, nil },
   131  		}
   132  	}
   133  	// Build the core
   134  	log.Printf("[DEBUG] Initializing core...")
   135  	core := packer.NewCore(&packer.CoreConfig{
   136  		Components: packer.ComponentFinder{
   137  			PluginConfig: &packer.PluginConfig{
   138  				Builders: TestBuilderSet{
   139  					BuilderSet: packersdk.MapOfBuilder{
   140  						"test": func() (packersdk.Builder, error) { return c.Builder, nil },
   141  					},
   142  					StartFn: func(n string) (packersdk.Builder, error) {
   143  						if n == "test" {
   144  							return c.Builder, nil
   145  						}
   146  
   147  						return nil, nil
   148  					},
   149  				},
   150  				Provisioners: c.ProvisionerStore,
   151  			},
   152  		},
   153  		Template: tpl,
   154  	})
   155  	diags := core.Initialize(packer.InitializeOptions{})
   156  	if diags.HasErrors() {
   157  		t.Fatal(fmt.Sprintf("Failed to init core: %s", err))
   158  		return
   159  	}
   160  
   161  	// Get the build
   162  	log.Printf("[DEBUG] Retrieving 'test' build")
   163  	build, err := core.Build("test")
   164  	if err != nil {
   165  		t.Fatal(fmt.Sprintf("Failed to get 'test' build: %s", err))
   166  		return
   167  	}
   168  
   169  	// Prepare it
   170  	log.Printf("[DEBUG] Preparing 'test' build")
   171  	warnings, err := build.Prepare()
   172  	if err != nil {
   173  		t.Fatal(fmt.Sprintf("Prepare error: %s", err))
   174  		return
   175  	}
   176  	if len(warnings) > 0 {
   177  		t.Fatal(fmt.Sprintf(
   178  			"Prepare warnings:\n\n%s",
   179  			strings.Join(warnings, "\n")))
   180  		return
   181  	}
   182  
   183  	// Run it! We use a temporary directory for caching and discard
   184  	// any UI output. We discard since it shows up in logs anyways.
   185  	log.Printf("[DEBUG] Running 'test' build")
   186  	ui := &packersdk.BasicUi{
   187  		Reader:      os.Stdin,
   188  		Writer:      io.Discard,
   189  		ErrorWriter: io.Discard,
   190  		PB:          &packersdk.NoopProgressTracker{},
   191  	}
   192  	artifacts, err := build.Run(context.Background(), ui)
   193  	if err != nil {
   194  		t.Fatal(fmt.Sprintf("Run error:\n\n%s", err))
   195  		goto TEARDOWN
   196  	}
   197  
   198  	// Check function
   199  	if c.Check != nil {
   200  		log.Printf("[DEBUG] Running check function")
   201  		if err := c.Check(artifacts); err != nil {
   202  			t.Fatal(fmt.Sprintf("Check error:\n\n%s", err))
   203  			goto TEARDOWN
   204  		}
   205  	}
   206  
   207  TEARDOWN:
   208  	if !c.SkipArtifactTeardown {
   209  		// Delete all artifacts
   210  		for _, a := range artifacts {
   211  			if err := a.Destroy(); err != nil {
   212  				t.Error(fmt.Sprintf(
   213  					"!!! ERROR REMOVING ARTIFACT '%s': %s !!!",
   214  					a.String(), err))
   215  			}
   216  		}
   217  	}
   218  
   219  	// Teardown
   220  	if c.Teardown != nil {
   221  		log.Printf("[DEBUG] Running teardown function")
   222  		if err := c.Teardown(); err != nil {
   223  			t.Fatal(fmt.Sprintf("Teardown failure:\n\n%s", err))
   224  			return
   225  		}
   226  	}
   227  }
   228  
   229  // This is for unit tests of this package.
   230  var testTesting = false