github.com/hashicorp/terraform-plugin-sdk@v1.17.2/helper/resource/testing_new.go (about)

     1  package resource
     2  
     3  import (
     4  	"fmt"
     5  	"log"
     6  	"reflect"
     7  	"strings"
     8  	"testing"
     9  
    10  	"github.com/davecgh/go-spew/spew"
    11  	tfjson "github.com/hashicorp/terraform-json"
    12  	"github.com/hashicorp/terraform-plugin-sdk/acctest"
    13  	"github.com/hashicorp/terraform-plugin-sdk/terraform"
    14  	tftest "github.com/hashicorp/terraform-plugin-test/v2"
    15  )
    16  
    17  func runPostTestDestroy(t *testing.T, c TestCase, wd *tftest.WorkingDir, factories map[string]terraform.ResourceProviderFactory, statePreDestroy *terraform.State) error {
    18  	t.Helper()
    19  
    20  	err := runProviderCommand(t, func() error {
    21  		wd.RequireDestroy(t)
    22  		return nil
    23  	}, wd, factories)
    24  	if err != nil {
    25  		return err
    26  	}
    27  
    28  	if c.CheckDestroy != nil {
    29  		if err := c.CheckDestroy(statePreDestroy); err != nil {
    30  			return err
    31  		}
    32  	}
    33  
    34  	return nil
    35  }
    36  
    37  func RunNewTest(t *testing.T, c TestCase, providers map[string]terraform.ResourceProvider) {
    38  	t.Helper()
    39  
    40  	spewConf := spew.NewDefaultConfig()
    41  	spewConf.SortKeys = true
    42  	wd := acctest.TestHelper.RequireNewWorkingDir(t)
    43  
    44  	defer func() {
    45  		var statePreDestroy *terraform.State
    46  		err := runProviderCommand(t, func() error {
    47  			statePreDestroy = getState(t, wd)
    48  			return nil
    49  		}, wd, c.ProviderFactories)
    50  		if err != nil {
    51  			t.Fatalf("Error retrieving state, there may be dangling resources: %s", err.Error())
    52  			return
    53  		}
    54  
    55  		if !stateIsEmpty(statePreDestroy) {
    56  			err := runPostTestDestroy(t, c, wd, c.ProviderFactories, statePreDestroy)
    57  			if err != nil {
    58  				t.Fatalf("Error running post-test destroy, there may be dangling resources: %s", err.Error())
    59  			}
    60  		}
    61  
    62  		wd.Close()
    63  	}()
    64  
    65  	providerCfg, err := testProviderConfig(c)
    66  	if err != nil {
    67  		t.Fatal(err)
    68  	}
    69  
    70  	wd.RequireSetConfig(t, providerCfg)
    71  
    72  	err = runProviderCommand(t, func() error {
    73  		return wd.Init()
    74  	}, wd, c.ProviderFactories)
    75  	if err != nil {
    76  		t.Fatalf("Error running init: %s", err.Error())
    77  		return
    78  	}
    79  
    80  	// use this to track last step succesfully applied
    81  	// acts as default for import tests
    82  	var appliedCfg string
    83  
    84  	for i, step := range c.Steps {
    85  
    86  		if step.PreConfig != nil {
    87  			step.PreConfig()
    88  		}
    89  
    90  		if step.SkipFunc != nil {
    91  			skip, err := step.SkipFunc()
    92  			if err != nil {
    93  				t.Fatal(err)
    94  			}
    95  			if skip {
    96  				log.Printf("[WARN] Skipping step %d/%d", i+1, len(c.Steps))
    97  				continue
    98  			}
    99  		}
   100  
   101  		if step.ImportState {
   102  			step.providers = providers
   103  			err := testStepNewImportState(t, c, wd, step, appliedCfg)
   104  			if step.ExpectError != nil {
   105  				if err == nil {
   106  					t.Fatalf("Step %d/%d error running import: expected an error but got none", i+1, len(c.Steps))
   107  				}
   108  				if !step.ExpectError.MatchString(err.Error()) {
   109  					t.Fatalf("Step %d/%d error running import, expected an error with pattern (%s), no match on: %s", i+1, len(c.Steps), step.ExpectError.String(), err)
   110  				}
   111  			} else {
   112  				if err != nil {
   113  					t.Fatalf("Step %d/%d error running import: %s", i+1, len(c.Steps), err)
   114  				}
   115  			}
   116  			continue
   117  		}
   118  
   119  		if step.Config != "" {
   120  			err := testStepNewConfig(t, c, wd, step)
   121  			if step.ExpectError != nil {
   122  				if err == nil {
   123  					t.Fatalf("Step %d/%d, expected an error but got none", i+1, len(c.Steps))
   124  				}
   125  				if !step.ExpectError.MatchString(err.Error()) {
   126  					t.Fatalf("Step %d/%d, expected an error with pattern, no match on: %s", i+1, len(c.Steps), err)
   127  				}
   128  			} else {
   129  				if err != nil {
   130  					t.Fatalf("Step %d/%d error: %s", i+1, len(c.Steps), err)
   131  				}
   132  			}
   133  			appliedCfg = step.Config
   134  			continue
   135  		}
   136  
   137  		t.Fatal("Unsupported test mode")
   138  	}
   139  }
   140  
   141  func getState(t *testing.T, wd *tftest.WorkingDir) *terraform.State {
   142  	t.Helper()
   143  
   144  	jsonState := wd.RequireState(t)
   145  	state, err := shimStateFromJson(jsonState)
   146  	if err != nil {
   147  		t.Fatal(err)
   148  	}
   149  	return state
   150  }
   151  
   152  func stateIsEmpty(state *terraform.State) bool {
   153  	return state.Empty() || !state.HasResources()
   154  }
   155  
   156  func planIsEmpty(plan *tfjson.Plan) bool {
   157  	for _, rc := range plan.ResourceChanges {
   158  		if rc.Mode == tfjson.DataResourceMode {
   159  			// Skip data sources as the current implementation ignores
   160  			// existing state and they are all re-read every time
   161  			continue
   162  		}
   163  
   164  		for _, a := range rc.Change.Actions {
   165  			if a != tfjson.ActionNoop {
   166  				return false
   167  			}
   168  		}
   169  	}
   170  	return true
   171  }
   172  
   173  func testIDRefresh(c TestCase, t *testing.T, wd *tftest.WorkingDir, step TestStep, r *terraform.ResourceState) error {
   174  	t.Helper()
   175  
   176  	spewConf := spew.NewDefaultConfig()
   177  	spewConf.SortKeys = true
   178  
   179  	// Build the state. The state is just the resource with an ID. There
   180  	// are no attributes. We only set what is needed to perform a refresh.
   181  	state := terraform.NewState()
   182  	state.RootModule().Resources = make(map[string]*terraform.ResourceState)
   183  	state.RootModule().Resources[c.IDRefreshName] = &terraform.ResourceState{}
   184  
   185  	// Temporarily set the config to a minimal provider config for the refresh
   186  	// test. After the refresh we can reset it.
   187  	cfg, err := testProviderConfig(c)
   188  	if err != nil {
   189  		return err
   190  	}
   191  	wd.RequireSetConfig(t, cfg)
   192  	defer wd.RequireSetConfig(t, step.Config)
   193  
   194  	// Refresh!
   195  	err = runProviderCommand(t, func() error {
   196  		wd.RequireRefresh(t)
   197  		state = getState(t, wd)
   198  		return nil
   199  	}, wd, c.ProviderFactories)
   200  	if err != nil {
   201  		return err
   202  	}
   203  
   204  	// Verify attribute equivalence.
   205  	actualR := state.RootModule().Resources[c.IDRefreshName]
   206  	if actualR == nil {
   207  		return fmt.Errorf("Resource gone!")
   208  	}
   209  	if actualR.Primary == nil {
   210  		return fmt.Errorf("Resource has no primary instance")
   211  	}
   212  	actual := actualR.Primary.Attributes
   213  	expected := r.Primary.Attributes
   214  	// Remove fields we're ignoring
   215  	for _, v := range c.IDRefreshIgnore {
   216  		for k := range actual {
   217  			if strings.HasPrefix(k, v) {
   218  				delete(actual, k)
   219  			}
   220  		}
   221  		for k := range expected {
   222  			if strings.HasPrefix(k, v) {
   223  				delete(expected, k)
   224  			}
   225  		}
   226  	}
   227  
   228  	if !reflect.DeepEqual(actual, expected) {
   229  		// Determine only the different attributes
   230  		for k, v := range expected {
   231  			if av, ok := actual[k]; ok && v == av {
   232  				delete(expected, k)
   233  				delete(actual, k)
   234  			}
   235  		}
   236  
   237  		spewConf := spew.NewDefaultConfig()
   238  		spewConf.SortKeys = true
   239  		return fmt.Errorf(
   240  			"Attributes not equivalent. Difference is shown below. Top is actual, bottom is expected."+
   241  				"\n\n%s\n\n%s",
   242  			spewConf.Sdump(actual), spewConf.Sdump(expected))
   243  	}
   244  
   245  	return nil
   246  }