github.com/hugorut/terraform@v1.1.3/src/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/hugorut/terraform/src/addrs"
    10  	"github.com/hugorut/terraform/src/backend"
    11  	"github.com/hugorut/terraform/src/configs/configschema"
    12  	"github.com/hugorut/terraform/src/providers"
    13  	"github.com/hugorut/terraform/src/states"
    14  	"github.com/hugorut/terraform/src/states/statemgr"
    15  	"github.com/hugorut/terraform/src/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) providers.PlanResourceChangeResponse {
    66  		rSchema, _ := schema.SchemaForResourceType(addrs.ManagedResourceMode, req.TypeName)
    67  		if rSchema == nil {
    68  			rSchema = &configschema.Block{} // default schema is empty
    69  		}
    70  		plannedVals := map[string]cty.Value{}
    71  		for name, attrS := range rSchema.Attributes {
    72  			val := req.ProposedNewState.GetAttr(name)
    73  			if attrS.Computed && val.IsNull() {
    74  				val = cty.UnknownVal(attrS.Type)
    75  			}
    76  			plannedVals[name] = val
    77  		}
    78  		for name := range rSchema.BlockTypes {
    79  			// For simplicity's sake we just copy the block attributes over
    80  			// verbatim, since this package's mock providers are all relatively
    81  			// simple -- we're testing the backend, not esoteric provider features.
    82  			plannedVals[name] = req.ProposedNewState.GetAttr(name)
    83  		}
    84  
    85  		return providers.PlanResourceChangeResponse{
    86  			PlannedState:   cty.ObjectVal(plannedVals),
    87  			PlannedPrivate: req.PriorPrivate,
    88  		}
    89  	}
    90  	p.ReadResourceFn = func(req providers.ReadResourceRequest) providers.ReadResourceResponse {
    91  		return providers.ReadResourceResponse{NewState: req.PriorState}
    92  	}
    93  	p.ReadDataSourceFn = func(req providers.ReadDataSourceRequest) providers.ReadDataSourceResponse {
    94  		return providers.ReadDataSourceResponse{State: req.Config}
    95  	}
    96  
    97  	// Initialize the opts
    98  	if b.ContextOpts == nil {
    99  		b.ContextOpts = &terraform.ContextOpts{}
   100  	}
   101  
   102  	// Set up our provider
   103  	b.ContextOpts.Providers = map[addrs.Provider]providers.Factory{
   104  		addrs.NewDefaultProvider(name): providers.FactoryFixed(p),
   105  	}
   106  
   107  	return p
   108  
   109  }
   110  
   111  // TestLocalSingleState is a backend implementation that wraps Local
   112  // and modifies it to only support single states (returns
   113  // ErrWorkspacesNotSupported for multi-state operations).
   114  //
   115  // This isn't an actual use case, this is exported just to provide a
   116  // easy way to test that behavior.
   117  type TestLocalSingleState struct {
   118  	*Local
   119  }
   120  
   121  // TestNewLocalSingle is a factory for creating a TestLocalSingleState.
   122  // This function matches the signature required for backend/init.
   123  func TestNewLocalSingle() backend.Backend {
   124  	return &TestLocalSingleState{Local: New()}
   125  }
   126  
   127  func (b *TestLocalSingleState) Workspaces() ([]string, error) {
   128  	return nil, backend.ErrWorkspacesNotSupported
   129  }
   130  
   131  func (b *TestLocalSingleState) DeleteWorkspace(string) error {
   132  	return backend.ErrWorkspacesNotSupported
   133  }
   134  
   135  func (b *TestLocalSingleState) StateMgr(name string) (statemgr.Full, error) {
   136  	if name != backend.DefaultStateName {
   137  		return nil, backend.ErrWorkspacesNotSupported
   138  	}
   139  
   140  	return b.Local.StateMgr(name)
   141  }
   142  
   143  // TestLocalNoDefaultState is a backend implementation that wraps
   144  // Local and modifies it to support named states, but not the
   145  // default state. It returns ErrDefaultWorkspaceNotSupported when
   146  // the DefaultStateName is used.
   147  type TestLocalNoDefaultState struct {
   148  	*Local
   149  }
   150  
   151  // TestNewLocalNoDefault is a factory for creating a TestLocalNoDefaultState.
   152  // This function matches the signature required for backend/init.
   153  func TestNewLocalNoDefault() backend.Backend {
   154  	return &TestLocalNoDefaultState{Local: New()}
   155  }
   156  
   157  func (b *TestLocalNoDefaultState) Workspaces() ([]string, error) {
   158  	workspaces, err := b.Local.Workspaces()
   159  	if err != nil {
   160  		return nil, err
   161  	}
   162  
   163  	filtered := workspaces[:0]
   164  	for _, name := range workspaces {
   165  		if name != backend.DefaultStateName {
   166  			filtered = append(filtered, name)
   167  		}
   168  	}
   169  
   170  	return filtered, nil
   171  }
   172  
   173  func (b *TestLocalNoDefaultState) DeleteWorkspace(name string) error {
   174  	if name == backend.DefaultStateName {
   175  		return backend.ErrDefaultWorkspaceNotSupported
   176  	}
   177  	return b.Local.DeleteWorkspace(name)
   178  }
   179  
   180  func (b *TestLocalNoDefaultState) StateMgr(name string) (statemgr.Full, error) {
   181  	if name == backend.DefaultStateName {
   182  		return nil, backend.ErrDefaultWorkspaceNotSupported
   183  	}
   184  	return b.Local.StateMgr(name)
   185  }
   186  
   187  func testStateFile(t *testing.T, path string, s *states.State) {
   188  	stateFile := statemgr.NewFilesystem(path)
   189  	stateFile.WriteState(s)
   190  }
   191  
   192  func mustProviderConfig(s string) addrs.AbsProviderConfig {
   193  	p, diags := addrs.ParseAbsProviderConfigStr(s)
   194  	if diags.HasErrors() {
   195  		panic(diags.Err())
   196  	}
   197  	return p
   198  }
   199  
   200  func mustResourceInstanceAddr(s string) addrs.AbsResourceInstance {
   201  	addr, diags := addrs.ParseAbsResourceInstanceStr(s)
   202  	if diags.HasErrors() {
   203  		panic(diags.Err())
   204  	}
   205  	return addr
   206  }
   207  
   208  // assertBackendStateUnlocked attempts to lock the backend state. Failure
   209  // indicates that the state was indeed locked and therefore this function will
   210  // return true.
   211  func assertBackendStateUnlocked(t *testing.T, b *Local) bool {
   212  	t.Helper()
   213  	stateMgr, _ := b.StateMgr(backend.DefaultStateName)
   214  	if _, err := stateMgr.Lock(statemgr.NewLockInfo()); err != nil {
   215  		t.Errorf("state is already locked: %s", err.Error())
   216  		return false
   217  	}
   218  	return true
   219  }
   220  
   221  // assertBackendStateLocked attempts to lock the backend state. Failure
   222  // indicates that the state was already locked and therefore this function will
   223  // return false.
   224  func assertBackendStateLocked(t *testing.T, b *Local) bool {
   225  	t.Helper()
   226  	stateMgr, _ := b.StateMgr(backend.DefaultStateName)
   227  	if _, err := stateMgr.Lock(statemgr.NewLockInfo()); err != nil {
   228  		return true
   229  	}
   230  	t.Error("unexpected success locking state")
   231  	return true
   232  }