github.com/opentofu/opentofu@v1.7.1/internal/backend/local/testing.go (about)

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