testhelper

package
v1.6.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 3, 2023 License: Apache-2.0 Imports: 23 Imported by: 0

Documentation

Overview

Package testhelper contains functions that can be used to assist and standardize the execution of unit tests for IBM Cloud Terraform projects

Example (Advanced_settings)
package main

import (
	"testing"

	"github.com/terraform-ibm-modules/ibmcloud-terratest-wrapper/testhelper"
)

func main() {
	// (here for compile only)
	// this is the test object supplied in a unit test call
	t := &testing.T{}

	t.Run("advanced unit test", func(t *testing.T) {
		// create TestOptions using Default contructor. This will do several things for you:
		// * Prefix will have random string added to end
		// * Validate required OS Environment variables are set
		// * Dynamically choose best region for test, if region not supplied
		// * Set other option fields to their sensible defaults
		//
		// You call this constructor by passing it an existing TestOptions with minimal data and it will return
		// a new altered object.
		options := testhelper.TestOptionsDefault(&testhelper.TestOptions{
			Testing:            t,                      // the test object for unit test
			TerraformDir:       "examples/basic",       // location of example to test, relative to root of project
			Prefix:             "my-test",              // will have 6 char random string appended, can be used as variable to prefix test resources
			BestRegionYAMLPath: "location/of/yaml.yml", // YAML file to configure dynamic region selection (see cloudinfo/RegionData type)
			// Region: "us-south", // if you set Region, dynamic selection will be skipped

			// can specify vars in-line
			TerraformVars: map[string]interface{}{
				"variable_1": "foo",
				"variable_2": "bar",
			},

			// Advanced settings for ignoring resources for consistency checks.
			// If you have resources that you know will break idempotent (such as ones with lifecycle hooks) you can ignore them from test.
			IgnoreDestroys: testhelper.Exemptions{
				List: []string{
					"module.my_mod.null_resource.foo",
					"module.another_mod.some_resource.bar",
				},
			},
			IgnoreAdds: testhelper.Exemptions{
				List: []string{
					"module.my_mod.null_resource.foo",
					"module.another_mod.some_resource.bar",
				},
			},
			IgnoreUpdates: testhelper.Exemptions{
				List: []string{
					"module.my_mod.null_resource.foo",
					"module.another_mod.some_resource.bar",
				},
			},

			// You can also specify resources that can be removed from the terraform state file immediately before destroy of the test,
			// which can speed up the test run.
			// For example, helm releases inside a cluster might not need to be destroyed, as destroying the cluster will delete all anyway.
			// NOTE: each item in the list will be removed via `terraform state rm` command.
			ImplicitDestroy: []string{
				"some_resource.some_name",
				"module.first_module.a_resource_type.first_name",
				"module.whole_module",
			},
			ImplicitRequired: false, // you can set this true if you want test to fail because an implicit destroy was not found
		})

		// RunTestConsistency will init/apply, then plan again to verify idempotent
		terratestPlanStruct, err := options.RunTestConsistency()

		// these same advanced settings also work with upgrade test
		// terratestPlanStruct, err := options.RunTestUpgrade()

		if err != nil {
			t.Fail()
		}
		if terratestPlanStruct == nil {
			t.Fail()
		}
	})
}
Output:

Example (Default)
package main

import (
	"testing"

	"github.com/terraform-ibm-modules/ibmcloud-terratest-wrapper/testhelper"
)

func main() {
	// (here for compile only)
	// this is the test object supplied in a unit test call
	t := &testing.T{}

	t.Run("unit test", func(t *testing.T) {
		// create TestOptions using Default contructor. This will do several things for you:
		// * Prefix will have random string added to end
		// * Validate required OS Environment variables are set
		// * Dynamically choose best region for test, if region not supplied
		// * Set other option fields to their sensible defaults
		//
		// You call this constructor by passing it an existing TestOptions with minimal data and it will return
		// a new altered object.
		options := testhelper.TestOptionsDefault(&testhelper.TestOptions{
			Testing:            t,                      // the test object for unit test
			TerraformDir:       "examples/basic",       // location of example to test, relative to root of project
			Prefix:             "my-test",              // will have 6 char random string appended, can be used as variable to prefix test resources
			BestRegionYAMLPath: "location/of/yaml.yml", // YAML file to configure dynamic region selection (see cloudinfo/RegionData type)
			// Region: "us-south", // if you set Region, dynamic selection will be skipped
		})

		// You can set your Terraform variables using generated prefix and region from options defaults
		options.TerraformVars = map[string]interface{}{
			"variable_1":   "foo",
			"variable_2":   "bar",
			"is_something": true,
			"tags":         []string{"tag1", "tag2"},
			"region":       options.Region, // use dynamic region from Default constructor
			"prefix":       options.Prefix, // use unique prefix + random 6 character from Default constructor
		}

		// RunTestConsistency will init/apply, then plan again to verify idempotent
		terratestPlanStruct, err := options.RunTestConsistency()
		if err != nil {
			t.Fail()
		}
		if terratestPlanStruct == nil {
			t.Fail()
		}
	})
}
Output:

Example (Minimal)
package main

import (
	"testing"

	"github.com/terraform-ibm-modules/ibmcloud-terratest-wrapper/testhelper"
)

func main() {
	// (here for compile only)
	// this is the test object supplied in a unit test call
	t := &testing.T{}

	t.Run("unit test", func(t *testing.T) {
		// set up a TestOptions data struct from scratch
		options := &testhelper.TestOptions{
			Testing:      t,                // the test object for unit test
			TerraformDir: "examples/basic", // location of example to test, relative to root of project
			TerraformVars: map[string]interface{}{
				"variable_1":   "foo",
				"variable_2":   "bar",
				"is_something": true,
				"tags":         []string{"tag1", "tag2"},
			},
		}

		// RunTestConsistency will init/apply, then plan again to verify idempotent
		terratestPlanStruct, err := options.RunTestConsistency()
		if err != nil {
			t.Fail()
		}
		if terratestPlanStruct == nil {
			t.Fail()
		}
	})
}
Output:

Example (Standard_inputs)
package main

import (
	"testing"

	"github.com/terraform-ibm-modules/ibmcloud-terratest-wrapper/testhelper"
)

func main() {
	// (here for compile only)
	// this is the test object supplied in a unit test call
	t := &testing.T{}

	t.Run("unit test", func(t *testing.T) {
		// create TestOptions using Default contructor, and including some "standard" terraform input variables to your supplied list.
		// This will do several things for you:
		// * Prefix will have random string added to end
		// * Validate required OS Environment variables are set
		// * Dynamically choose best region for test, if region not supplied
		// * Set other option fields to their sensible defaults
		// * append the following standard terraform variables to the supplied array:
		//   - prefix: value is unique Prefix field
		//   - resource_group: value taken from field ResourceGroup
		//   - region: value from field Region (dynamically chosen if not supplied)
		//
		// You call this constructor by passing it an existing TestOptions with minimal data and Terraform input variables, and it will return
		// a new altered object.
		options := testhelper.TestOptionsDefaultWithVars(&testhelper.TestOptions{
			Testing:            t,                      // the test object for unit test
			TerraformDir:       "examples/basic",       // location of example to test, relative to root of project
			Prefix:             "my-test",              // will have 6 char random string appended, can be used as variable to prefix test resources
			BestRegionYAMLPath: "location/of/yaml.yml", // YAML file to configure dynamic region selection (see cloudinfo/RegionData type)
			// Region: "us-south", // if you set Region, dynamic selection will be skipped
			// TerraformVars is optional, supply here if you have extra variables beyond the standard ones,
			// and standard vars will be appended to list
			TerraformVars: map[string]interface{}{
				"extra_var_1": "foo",
				"extra_var_2": "bar",
			},
		})

		// RunTestConsistency will init/apply, then plan again to verify idempotent
		terratestPlanStruct, err := options.RunTestConsistency()
		if err != nil {
			t.Fail()
		}
		if terratestPlanStruct == nil {
			t.Fail()
		}
	})
}
Output:

Example (Upgrade_test)
package main

import (
	"testing"

	"github.com/terraform-ibm-modules/ibmcloud-terratest-wrapper/testhelper"
)

func main() {
	// (here for compile only)
	// this is the test object supplied in a unit test call
	t := &testing.T{}

	t.Run("upgrade unit test", func(t *testing.T) {
		// Perform Upgrade test by first using default constructor.
		// This will do several things for you:
		// * Prefix will have random string added to end
		// * Validate required OS Environment variables are set
		// * Dynamically choose best region for test, if region not supplied
		// * Set other option fields to their sensible defaults
		//
		// You call this constructor by passing it an existing TestOptions with minimal data and it will return
		// a new altered object.
		options := testhelper.TestOptionsDefault(&testhelper.TestOptions{
			Testing:            t,                      // the test object for unit test
			TerraformDir:       "examples/basic",       // location of example to test, relative to root of project
			Prefix:             "my-test",              // will have 6 char random string appended, can be used as variable to prefix test resources
			BestRegionYAMLPath: "location/of/yaml.yml", // YAML file to configure dynamic region selection (see cloudinfo/RegionData type)
			// Region: "us-south", // if you set Region, dynamic selection will be skipped
		})

		// You can set your Terraform variables using generated prefix and region from options defaults
		options.TerraformVars = map[string]interface{}{
			"variable_1":   "foo",
			"variable_2":   "bar",
			"is_something": true,
			"tags":         []string{"tag1", "tag2"},
			"region":       options.Region, // use dynamic region from Default constructor
			"prefix":       options.Prefix, // use unique prefix + random 6 character from Default constructor
		}

		// Run upgrade test, which will do the following:
		// 1. checkout main branch
		// 2. terraform apply
		// 3. checkout original test branch
		// 4. terraform plan
		// If the plan identifies resources that would be DESTROYED, it will fail the test
		terratestPlanStruct, err := options.RunTestUpgrade()

		// there are factors in a CI pipeline run that would cause an Upgrade test to be skipped.
		// you can access the UpgradeTestSkipped boolean to find out if test was run
		if !options.UpgradeTestSkipped {
			if err != nil {
				t.Fail()
			}
			if terratestPlanStruct == nil {
				t.Fail()
			}
		}
	})
}
Output:

Index

Examples

Constants

View Source
const ForceTestRegionEnvName = "FORCE_TEST_REGION"

Variables

This section is empty.

Functions

func GetBestPowerSystemsRegion added in v1.1.3

func GetBestPowerSystemsRegion(apiKey string, prefsFilePath string, defaultRegion string) (string, error)

GetBestPowerSystemsRegion is a method that will determine a region available to the caller account that currently contains the least amount of deployed PowerVS Cloud Connections. The determination can be influenced by specifying a prefsFilePath pointed to a valid YAML file. If an OS ENV is found called FORCE_TEST_REGION then it will be used without querying. This function assumes that all default Options will be used. Returns a string representing an IBM Cloud region name, and error.

func GetBestPowerSystemsRegionO added in v1.1.3

func GetBestPowerSystemsRegionO(apiKey string, prefsFilePath string, defaultRegion string, options TesthelperTerraformOptions) (string, error)

GetBestPowerSystemsRegionO is a method that will determine a region available to the caller account that currently contains the least amount of deployed PowerVS Cloud Connections. The determination can be influenced by specifying a prefsFilePath pointed to a valid YAML file. If an OS ENV is found called FORCE_TEST_REGION then it will be used without querying. Options data can also be called to supply the service to use that implements the correct interface. Returns a string representing an IBM Cloud region name, and error.

func GetBestVpcRegion

func GetBestVpcRegion(apiKey string, prefsFilePath string, defaultRegion string) (string, error)

GetBestVpcRegion is a method that will determine a region available to the caller account that currently contains the least amount of deployed VPCs. The determination can be influenced by specifying a prefsFilePath pointed to a valid YAML file. If an OS ENV is found called FORCE_TEST_REGION then it will be used without querying. This function assumes that all default Options will be used. Returns a string representing an IBM Cloud region name, and error.

func GetBestVpcRegionO

func GetBestVpcRegionO(apiKey string, prefsFilePath string, defaultRegion string, options TesthelperTerraformOptions) (string, error)

GetBestVpcRegionO is a method that will determine a region available to the caller account that currently contains the least amount of deployed VPCs. The determination can be influenced by specifying a prefsFilePath pointed to a valid YAML file. If an OS ENV is found called FORCE_TEST_REGION then it will be used without querying. Options data can also be called to supply the service to use that implements the correct interface. Returns a string representing an IBM Cloud region name, and error.

func RemoveFromStateFile

func RemoveFromStateFile(stateFile string, resourceAddress string) (string, error)

RemoveFromStateFile Attempts to remove resource from state file

Types

type EnvironmentVariables

type EnvironmentVariables struct {
	NewVariables map[string]string
	OldVariables map[string]string
}

EnvironmentVariables Holds a list of environment variables and their values When SetEnvironmentVariables is called it will save any existing environment variables in OldVariables and set NewVariables on the environment When ResetEnvironmentVariables is called it will set the environment variables back to the old values

func (EnvironmentVariables) ResetEnvironmentVariables

func (environment EnvironmentVariables) ResetEnvironmentVariables()

func (EnvironmentVariables) SetEnvironmentVariables

func (environment EnvironmentVariables) SetEnvironmentVariables()

type Exemptions

type Exemptions struct {
	List []string
}

Exemptions Struct to hold the list of exemptions

func (Exemptions) IsExemptedResource

func (exemptions Exemptions) IsExemptedResource(resource string) bool

IsExemptedResource Checks if resource string is in the list of exemptions

type TestOptions

type TestOptions struct {
	// REQUIRED: a pointer to an initialized testing object.
	// Typically you would assign the test object used in the unit test.
	Testing *testing.T `copier:"-"`

	// The default constructors will use this map to check that all required environment variables are set properly.
	// If any are missing, the test will fail.
	RequiredEnvironmentVars map[string]string

	// Path to YAML file contaning preferences for how dynamic regions should be chosen.
	// See examples in cloudinfo/testdata for proper format.
	BestRegionYAMLPath string

	// Used with dynamic region selection, if any errors occur this will be the region used (fail-open)
	DefaultRegion string

	// If set during creation, this region will be used for test and dynamic region selection will be skipped.
	// If left empty, this will be populated by dynamic region selection by default constructor and can be referenced later.
	Region string

	// Only required if using the WithVars constructor, as this value will then populate the `resource_group` input variable.
	ResourceGroup string

	// REQUIRED: the string prefix that will be prepended to all resource names, typically sent in as terraform input variable.
	// Set this value in the default constructors and a unique 6-digit random string will be appended.
	// Can then be referenced after construction and used as unique variable.
	//
	// Example:
	// Supplied to constructor = `my-test`
	// After constructor = `my-test-xu5oby`
	Prefix string

	// This map contains key-value pairs that will be used as variables for the test terraform run.
	// NOTE: when using the `...WithVars()` constructor, this map will be appended with the
	// standard test variables.
	TerraformVars map[string]interface{}

	// This is the subdirectory of the project that contains the terraform to run for the test.
	// This value is relative to the root directory of the project.
	// Defaults to root directory of project if not supplied.
	//
	// Example: if you are testing a module source, and the execution is located in the subdirectory `examples/basic`, then set this
	// to that value.
	TerraformDir string

	// Specify additional Terraform Options for Terratest using this variable.
	// see: https://pkg.go.dev/github.com/gruntwork-io/terratest/modules/terraform#Options
	TerraformOptions *terraform.Options `copier:"-"`

	// Use these options to have terratest execute using a terraform "workspace".
	UseTerraformWorkspace bool
	WorkspaceName         string
	WorkspacePath         string

	// Resource tags to use for tests.
	// NOTE: when using `...WithVars()` constructor, this value will be automatically added to the appropriate
	// TerraformVars entries for tags.
	Tags []string

	// For Consistency Checks: Specify terraform resource names to ignore for consistency checks.
	// You can ignore specific resources in both idempotent and upgrade consistency checks by adding their names to these
	// lists. There are separate lists for adds, updates, and destroys.
	//
	// This can be useful if you have resources like `null_resource` that are marked with a lifecycle that causes a refresh on every run.
	// Normally this would fail a consistency check but can be ignored by adding to one of these lists.
	//
	// Name format is terraform style, for example: `module.some_module.null_resource.foo`
	IgnoreAdds     Exemptions
	IgnoreDestroys Exemptions
	IgnoreUpdates  Exemptions

	// Implicit Destroy can be used to speed up the `terraform destroy` action of the test, by removing resources from the state file
	// before the destroy process is executed.
	//
	// Use this for resources that are destroyed as part of a parent resource and do not need to be destroyed on their own.
	// For example: most helm releases inside of an OCP instance do not need to be individually destroyed, they will be destroyed
	// when the OCP instance is destroyed.
	//
	// Name format is terraform style, for example: `module.some_module.null_resource.foo`
	// NOTE: can specify at any layer of name, all children will also be removed, for example: `module.some_module` will remove all resources for that module.
	ImplicitDestroy []string

	//  If true the test will fail if any resources in ImplicitDestroy list fails to be removed from the state file
	ImplicitRequired bool

	// Set to true if using the `TestOptionsDefault` constructors with dynamic region selection, and you wish to exclude any regions that already
	// contain an Activity Tracker.
	ExcludeActivityTrackerRegions bool

	// Optional instance of a CloudInfoService to be used for any dynamic region selections.
	// If you wish parallel unit tests to use the same instance of CloudInfoService so that they do not pick the same region, you can initialize
	// this service in a variable that is shared amongst all unit tests and supply the pointer here.
	CloudInfoService cloudinfo.CloudInfoServiceI

	// Set to true if you wish for an Upgrade test to do a final `terraform apply` after the consistency check on the new (not main) branch.
	CheckApplyResultForUpgrade bool

	// If you want to skip test setup and teardown use these
	SkipTestSetup    bool
	SkipTestTearDown bool

	// These properties are considered READ ONLY and are used internally in the service to keep track of certain data elements.
	// Some of these properties are public, and can be used after the test is run to determine specific outcomes.
	IsUpgradeTest      bool // Identifies if current test is an UPGRADE test, used for special processing
	UpgradeTestSkipped bool // Informs the calling test that conditions were met to skip the upgrade test
	// contains filtered or unexported fields
}

func TestOptionsDefault

func TestOptionsDefault(originalOptions *TestOptions) *TestOptions

Default constructor for TestOptions struct. This constructor takes in an existing TestOptions object with minimal values set, and returns a new object that has amended or new values set, based on standard defaults.

Summary of properties changed: * appends unique 6-char string to end of original prefix * checks that certain required environment variables are set * computes best dynamic region for test, if Region is not supplied * sets various other properties to sensible defaults

func TestOptionsDefaultWithVars

func TestOptionsDefaultWithVars(originalOptions *TestOptions) *TestOptions

Default constructor for TestOptions struct. This constructor takes in an existing TestOptions object with minimal values set, and returns a new object that has amended or new values set.

This version of the constructor will call `TestOptionsDefault` to set up most values, and then will add specific common variables to the `TerraformVars` map using supplied values from the existing TestOptions sent in.

Common TerraformVars added: * prefix * region * resource_group * resource_tags

DO NOT USE this constructor if your terraform test does not have these common variables as inputs, this is a convenience function for tests that support these common variables.

NOTE: the variables are merged into the existing TerraformVars map, so it is best practice to have additional vars already set in the TestOptions object that is supplied.

func (*TestOptions) Clone

func (options *TestOptions) Clone() (*TestOptions, error)

Clone makes a deep copy of most fields on the Options object and returns it.

NOTE: options.SshAgent and options.Logger CANNOT be deep copied (e.g., the SshAgent struct contains channels and listeners that can't be meaningfully copied), so the original values are retained.

func (*TestOptions) RunTest

func (options *TestOptions) RunTest() (string, error)

RunTest Runs Test and returns the output as a string for assertions

func (*TestOptions) RunTestConsistency

func (options *TestOptions) RunTestConsistency() (*terraform.PlanStruct, error)

RunTestConsistency Runs Test To check consistency between apply and re-apply, returns the output as string for further assertions

func (*TestOptions) RunTestPlan

func (options *TestOptions) RunTestPlan() (*terraform.PlanStruct, error)

RunTestPlan Runs Test plan and returns the plan as a struct for assertions

func (*TestOptions) RunTestUpgrade

func (options *TestOptions) RunTestUpgrade() (*terraform.PlanStruct, error)

RunTestUpgrade Runs upgrade Test and asserts no resources have been destroyed in the upgrade, returns plan struct for further assertions

func (*TestOptions) TestSetup added in v1.5.0

func (options *TestOptions) TestSetup()

Function to setup testing environment.

Summary of settings: * API_DATA_IS_SENSITIVE environment variable is set to true * If calling test had not provided its own TerraformOptions, then default settings are used * Temp directory is created

func (*TestOptions) TestTearDown added in v1.5.0

func (options *TestOptions) TestTearDown()

Function to destroy all resources. Resources are not destroyed if tests failed and "DO_NOT_DESTROY_ON_FAILURE" environment variable is true. If options.ImplicitDestroy is set then these resources from the State file are removed to allow implicit destroy.

type TesthelperTerraformOptions

type TesthelperTerraformOptions struct {
	CloudInfoService              cloudinfo.CloudInfoServiceI
	ExcludeActivityTrackerRegions bool
}

TesthelperTerraformOptions options object for optional variables to set primarily used for mocking external services in test cases

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL