github.com/kevinklinger/open_terraform@v1.3.6/noninternal/backend/local/testing.go (about)

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