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