github.com/rstandt/terraform@v0.12.32-0.20230710220336-b1063613405c/backend/local/testing.go (about)

     1  package local
     2  
     3  import (
     4  	"io/ioutil"
     5  	"os"
     6  	"path/filepath"
     7  	"testing"
     8  
     9  	"github.com/zclconf/go-cty/cty"
    10  
    11  	"github.com/hashicorp/terraform/addrs"
    12  	"github.com/hashicorp/terraform/backend"
    13  	"github.com/hashicorp/terraform/configs/configschema"
    14  	"github.com/hashicorp/terraform/providers"
    15  	"github.com/hashicorp/terraform/states"
    16  	"github.com/hashicorp/terraform/states/statemgr"
    17  	"github.com/hashicorp/terraform/terraform"
    18  	"github.com/hashicorp/terraform/tfdiags"
    19  )
    20  
    21  // TestLocal returns a configured Local struct with temporary paths and
    22  // in-memory ContextOpts.
    23  //
    24  // No operations will be called on the returned value, so you can still set
    25  // public fields without any locks.
    26  func TestLocal(t *testing.T) (*Local, func()) {
    27  	t.Helper()
    28  	tempDir := testTempDir(t)
    29  
    30  	local := New()
    31  	local.StatePath = filepath.Join(tempDir, "state.tfstate")
    32  	local.StateOutPath = filepath.Join(tempDir, "state.tfstate")
    33  	local.StateBackupPath = filepath.Join(tempDir, "state.tfstate.bak")
    34  	local.StateWorkspaceDir = filepath.Join(tempDir, "state.tfstate.d")
    35  	local.ContextOpts = &terraform.ContextOpts{}
    36  
    37  	local.ShowDiagnostics = func(vals ...interface{}) {
    38  		var diags tfdiags.Diagnostics
    39  		diags = diags.Append(vals...)
    40  		for _, diag := range diags {
    41  			// NOTE: Since the caller here is not directly the TestLocal
    42  			// function, t.Helper doesn't apply and so the log source
    43  			// isn't correctly shown in the test log output. This seems
    44  			// unavoidable as long as this is happening so indirectly.
    45  			desc := diag.Description()
    46  			if desc.Detail != "" {
    47  				t.Logf("%s: %s", desc.Summary, desc.Detail)
    48  			} else {
    49  				t.Log(desc.Summary)
    50  			}
    51  			if local.CLI != nil {
    52  				local.CLI.Error(desc.Summary)
    53  			}
    54  		}
    55  	}
    56  
    57  	cleanup := func() {
    58  		if err := os.RemoveAll(tempDir); err != nil {
    59  			t.Fatal("error cleanup up test:", err)
    60  		}
    61  	}
    62  
    63  	return local, cleanup
    64  }
    65  
    66  // TestLocalProvider modifies the ContextOpts of the *Local parameter to
    67  // have a provider with the given name.
    68  func TestLocalProvider(t *testing.T, b *Local, name string, schema *terraform.ProviderSchema) *terraform.MockProvider {
    69  	// Build a mock resource provider for in-memory operations
    70  	p := new(terraform.MockProvider)
    71  
    72  	if schema == nil {
    73  		schema = &terraform.ProviderSchema{} // default schema is empty
    74  	}
    75  	p.GetSchemaReturn = schema
    76  
    77  	p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) providers.PlanResourceChangeResponse {
    78  		rSchema, _ := schema.SchemaForResourceType(addrs.ManagedResourceMode, req.TypeName)
    79  		if rSchema == nil {
    80  			rSchema = &configschema.Block{} // default schema is empty
    81  		}
    82  		plannedVals := map[string]cty.Value{}
    83  		for name, attrS := range rSchema.Attributes {
    84  			val := req.ProposedNewState.GetAttr(name)
    85  			if attrS.Computed && val.IsNull() {
    86  				val = cty.UnknownVal(attrS.Type)
    87  			}
    88  			plannedVals[name] = val
    89  		}
    90  		for name := range rSchema.BlockTypes {
    91  			// For simplicity's sake we just copy the block attributes over
    92  			// verbatim, since this package's mock providers are all relatively
    93  			// simple -- we're testing the backend, not esoteric provider features.
    94  			plannedVals[name] = req.ProposedNewState.GetAttr(name)
    95  		}
    96  
    97  		return providers.PlanResourceChangeResponse{
    98  			PlannedState:   cty.ObjectVal(plannedVals),
    99  			PlannedPrivate: req.PriorPrivate,
   100  		}
   101  	}
   102  	p.ReadResourceFn = func(req providers.ReadResourceRequest) providers.ReadResourceResponse {
   103  		return providers.ReadResourceResponse{NewState: req.PriorState}
   104  	}
   105  	p.ReadDataSourceFn = func(req providers.ReadDataSourceRequest) providers.ReadDataSourceResponse {
   106  		return providers.ReadDataSourceResponse{State: req.Config}
   107  	}
   108  
   109  	// Initialize the opts
   110  	if b.ContextOpts == nil {
   111  		b.ContextOpts = &terraform.ContextOpts{}
   112  	}
   113  
   114  	// Setup our provider
   115  	b.ContextOpts.ProviderResolver = providers.ResolverFixed(
   116  		map[addrs.Provider]providers.Factory{
   117  			addrs.NewLegacyProvider(name): providers.FactoryFixed(p),
   118  		},
   119  	)
   120  
   121  	return p
   122  
   123  }
   124  
   125  // TestLocalSingleState is a backend implementation that wraps Local
   126  // and modifies it to only support single states (returns
   127  // ErrWorkspacesNotSupported for multi-state operations).
   128  //
   129  // This isn't an actual use case, this is exported just to provide a
   130  // easy way to test that behavior.
   131  type TestLocalSingleState struct {
   132  	*Local
   133  }
   134  
   135  // TestNewLocalSingle is a factory for creating a TestLocalSingleState.
   136  // This function matches the signature required for backend/init.
   137  func TestNewLocalSingle() backend.Backend {
   138  	return &TestLocalSingleState{Local: New()}
   139  }
   140  
   141  func (b *TestLocalSingleState) Workspaces() ([]string, error) {
   142  	return nil, backend.ErrWorkspacesNotSupported
   143  }
   144  
   145  func (b *TestLocalSingleState) DeleteWorkspace(string) error {
   146  	return backend.ErrWorkspacesNotSupported
   147  }
   148  
   149  func (b *TestLocalSingleState) StateMgr(name string) (statemgr.Full, error) {
   150  	if name != backend.DefaultStateName {
   151  		return nil, backend.ErrWorkspacesNotSupported
   152  	}
   153  
   154  	return b.Local.StateMgr(name)
   155  }
   156  
   157  // TestLocalNoDefaultState is a backend implementation that wraps
   158  // Local and modifies it to support named states, but not the
   159  // default state. It returns ErrDefaultWorkspaceNotSupported when
   160  // the DefaultStateName is used.
   161  type TestLocalNoDefaultState struct {
   162  	*Local
   163  }
   164  
   165  // TestNewLocalNoDefault is a factory for creating a TestLocalNoDefaultState.
   166  // This function matches the signature required for backend/init.
   167  func TestNewLocalNoDefault() backend.Backend {
   168  	return &TestLocalNoDefaultState{Local: New()}
   169  }
   170  
   171  func (b *TestLocalNoDefaultState) Workspaces() ([]string, error) {
   172  	workspaces, err := b.Local.Workspaces()
   173  	if err != nil {
   174  		return nil, err
   175  	}
   176  
   177  	filtered := workspaces[:0]
   178  	for _, name := range workspaces {
   179  		if name != backend.DefaultStateName {
   180  			filtered = append(filtered, name)
   181  		}
   182  	}
   183  
   184  	return filtered, nil
   185  }
   186  
   187  func (b *TestLocalNoDefaultState) DeleteWorkspace(name string) error {
   188  	if name == backend.DefaultStateName {
   189  		return backend.ErrDefaultWorkspaceNotSupported
   190  	}
   191  	return b.Local.DeleteWorkspace(name)
   192  }
   193  
   194  func (b *TestLocalNoDefaultState) StateMgr(name string) (statemgr.Full, error) {
   195  	if name == backend.DefaultStateName {
   196  		return nil, backend.ErrDefaultWorkspaceNotSupported
   197  	}
   198  	return b.Local.StateMgr(name)
   199  }
   200  
   201  func testTempDir(t *testing.T) string {
   202  	d, err := ioutil.TempDir("", "tf")
   203  	if err != nil {
   204  		t.Fatalf("err: %s", err)
   205  	}
   206  
   207  	return d
   208  }
   209  
   210  func testStateFile(t *testing.T, path string, s *states.State) {
   211  	stateFile := statemgr.NewFilesystem(path)
   212  	stateFile.WriteState(s)
   213  }