github.com/terramate-io/tf@v0.0.0-20230830114523-fce866b4dfcd/backend/local/testing.go (about)

     1  // Copyright (c) HashiCorp, Inc.
     2  // SPDX-License-Identifier: MPL-2.0
     3  
     4  package local
     5  
     6  import (
     7  	"path/filepath"
     8  	"testing"
     9  
    10  	"github.com/zclconf/go-cty/cty"
    11  
    12  	"github.com/terramate-io/tf/addrs"
    13  	"github.com/terramate-io/tf/backend"
    14  	"github.com/terramate-io/tf/configs/configschema"
    15  	"github.com/terramate-io/tf/providers"
    16  	"github.com/terramate-io/tf/states"
    17  	"github.com/terramate-io/tf/states/statemgr"
    18  	"github.com/terramate-io/tf/terraform"
    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 {
    27  	t.Helper()
    28  	tempDir, err := filepath.EvalSymlinks(t.TempDir())
    29  	if err != nil {
    30  		t.Fatal(err)
    31  	}
    32  
    33  	local := New()
    34  	local.StatePath = filepath.Join(tempDir, "state.tfstate")
    35  	local.StateOutPath = filepath.Join(tempDir, "state.tfstate")
    36  	local.StateBackupPath = filepath.Join(tempDir, "state.tfstate.bak")
    37  	local.StateWorkspaceDir = filepath.Join(tempDir, "state.tfstate.d")
    38  	local.ContextOpts = &terraform.ContextOpts{}
    39  
    40  	return local
    41  }
    42  
    43  // TestLocalProvider modifies the ContextOpts of the *Local parameter to
    44  // have a provider with the given name.
    45  func TestLocalProvider(t *testing.T, b *Local, name string, schema providers.ProviderSchema) *terraform.MockProvider {
    46  	// Build a mock resource provider for in-memory operations
    47  	p := new(terraform.MockProvider)
    48  
    49  	p.GetProviderSchemaResponse = &schema
    50  
    51  	p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) (resp providers.PlanResourceChangeResponse) {
    52  		// this is a destroy plan,
    53  		if req.ProposedNewState.IsNull() {
    54  			resp.PlannedState = req.ProposedNewState
    55  			resp.PlannedPrivate = req.PriorPrivate
    56  			return resp
    57  		}
    58  
    59  		rSchema, _ := schema.SchemaForResourceType(addrs.ManagedResourceMode, req.TypeName)
    60  		if rSchema == nil {
    61  			rSchema = &configschema.Block{} // default schema is empty
    62  		}
    63  		plannedVals := map[string]cty.Value{}
    64  		for name, attrS := range rSchema.Attributes {
    65  			val := req.ProposedNewState.GetAttr(name)
    66  			if attrS.Computed && val.IsNull() {
    67  				val = cty.UnknownVal(attrS.Type)
    68  			}
    69  			plannedVals[name] = val
    70  		}
    71  		for name := range rSchema.BlockTypes {
    72  			// For simplicity's sake we just copy the block attributes over
    73  			// verbatim, since this package's mock providers are all relatively
    74  			// simple -- we're testing the backend, not esoteric provider features.
    75  			plannedVals[name] = req.ProposedNewState.GetAttr(name)
    76  		}
    77  
    78  		return providers.PlanResourceChangeResponse{
    79  			PlannedState:   cty.ObjectVal(plannedVals),
    80  			PlannedPrivate: req.PriorPrivate,
    81  		}
    82  	}
    83  	p.ReadResourceFn = func(req providers.ReadResourceRequest) providers.ReadResourceResponse {
    84  		return providers.ReadResourceResponse{NewState: req.PriorState}
    85  	}
    86  	p.ReadDataSourceFn = func(req providers.ReadDataSourceRequest) providers.ReadDataSourceResponse {
    87  		return providers.ReadDataSourceResponse{State: req.Config}
    88  	}
    89  
    90  	// Initialize the opts
    91  	if b.ContextOpts == nil {
    92  		b.ContextOpts = &terraform.ContextOpts{}
    93  	}
    94  
    95  	// Set up our provider
    96  	b.ContextOpts.Providers = map[addrs.Provider]providers.Factory{
    97  		addrs.NewDefaultProvider(name): providers.FactoryFixed(p),
    98  	}
    99  
   100  	return p
   101  
   102  }
   103  
   104  // TestLocalSingleState is a backend implementation that wraps Local
   105  // and modifies it to only support single states (returns
   106  // ErrWorkspacesNotSupported for multi-state operations).
   107  //
   108  // This isn't an actual use case, this is exported just to provide a
   109  // easy way to test that behavior.
   110  type TestLocalSingleState struct {
   111  	*Local
   112  }
   113  
   114  // TestNewLocalSingle is a factory for creating a TestLocalSingleState.
   115  // This function matches the signature required for backend/init.
   116  func TestNewLocalSingle() backend.Backend {
   117  	return &TestLocalSingleState{Local: New()}
   118  }
   119  
   120  func (b *TestLocalSingleState) Workspaces() ([]string, error) {
   121  	return nil, backend.ErrWorkspacesNotSupported
   122  }
   123  
   124  func (b *TestLocalSingleState) DeleteWorkspace(string, bool) error {
   125  	return backend.ErrWorkspacesNotSupported
   126  }
   127  
   128  func (b *TestLocalSingleState) StateMgr(name string) (statemgr.Full, error) {
   129  	if name != backend.DefaultStateName {
   130  		return nil, backend.ErrWorkspacesNotSupported
   131  	}
   132  
   133  	return b.Local.StateMgr(name)
   134  }
   135  
   136  // TestLocalNoDefaultState is a backend implementation that wraps
   137  // Local and modifies it to support named states, but not the
   138  // default state. It returns ErrDefaultWorkspaceNotSupported when
   139  // the DefaultStateName is used.
   140  type TestLocalNoDefaultState struct {
   141  	*Local
   142  }
   143  
   144  // TestNewLocalNoDefault is a factory for creating a TestLocalNoDefaultState.
   145  // This function matches the signature required for backend/init.
   146  func TestNewLocalNoDefault() backend.Backend {
   147  	return &TestLocalNoDefaultState{Local: New()}
   148  }
   149  
   150  func (b *TestLocalNoDefaultState) Workspaces() ([]string, error) {
   151  	workspaces, err := b.Local.Workspaces()
   152  	if err != nil {
   153  		return nil, err
   154  	}
   155  
   156  	filtered := workspaces[:0]
   157  	for _, name := range workspaces {
   158  		if name != backend.DefaultStateName {
   159  			filtered = append(filtered, name)
   160  		}
   161  	}
   162  
   163  	return filtered, nil
   164  }
   165  
   166  func (b *TestLocalNoDefaultState) DeleteWorkspace(name string, force bool) error {
   167  	if name == backend.DefaultStateName {
   168  		return backend.ErrDefaultWorkspaceNotSupported
   169  	}
   170  	return b.Local.DeleteWorkspace(name, force)
   171  }
   172  
   173  func (b *TestLocalNoDefaultState) StateMgr(name string) (statemgr.Full, error) {
   174  	if name == backend.DefaultStateName {
   175  		return nil, backend.ErrDefaultWorkspaceNotSupported
   176  	}
   177  	return b.Local.StateMgr(name)
   178  }
   179  
   180  func testStateFile(t *testing.T, path string, s *states.State) {
   181  	stateFile := statemgr.NewFilesystem(path)
   182  	stateFile.WriteState(s)
   183  }
   184  
   185  func mustProviderConfig(s string) addrs.AbsProviderConfig {
   186  	p, diags := addrs.ParseAbsProviderConfigStr(s)
   187  	if diags.HasErrors() {
   188  		panic(diags.Err())
   189  	}
   190  	return p
   191  }
   192  
   193  func mustResourceInstanceAddr(s string) addrs.AbsResourceInstance {
   194  	addr, diags := addrs.ParseAbsResourceInstanceStr(s)
   195  	if diags.HasErrors() {
   196  		panic(diags.Err())
   197  	}
   198  	return addr
   199  }
   200  
   201  // assertBackendStateUnlocked attempts to lock the backend state. Failure
   202  // indicates that the state was indeed locked and therefore this function will
   203  // return true.
   204  func assertBackendStateUnlocked(t *testing.T, b *Local) bool {
   205  	t.Helper()
   206  	stateMgr, _ := b.StateMgr(backend.DefaultStateName)
   207  	if _, err := stateMgr.Lock(statemgr.NewLockInfo()); err != nil {
   208  		t.Errorf("state is already locked: %s", err.Error())
   209  		return false
   210  	}
   211  	return true
   212  }
   213  
   214  // assertBackendStateLocked attempts to lock the backend state. Failure
   215  // indicates that the state was already locked and therefore this function will
   216  // return false.
   217  func assertBackendStateLocked(t *testing.T, b *Local) bool {
   218  	t.Helper()
   219  	stateMgr, _ := b.StateMgr(backend.DefaultStateName)
   220  	if _, err := stateMgr.Lock(statemgr.NewLockInfo()); err != nil {
   221  		return true
   222  	}
   223  	t.Error("unexpected success locking state")
   224  	return true
   225  }