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 }