github.com/rstandt/terraform@v0.12.32-0.20230710220336-b1063613405c/backend/local/testing.go (about) 1 package local 2 3 import ( 4 "io/ioutil" 5 "os" 6 "path/filepath" 7 "testing" 8 9 "github.com/zclconf/go-cty/cty" 10 11 "github.com/hashicorp/terraform/addrs" 12 "github.com/hashicorp/terraform/backend" 13 "github.com/hashicorp/terraform/configs/configschema" 14 "github.com/hashicorp/terraform/providers" 15 "github.com/hashicorp/terraform/states" 16 "github.com/hashicorp/terraform/states/statemgr" 17 "github.com/hashicorp/terraform/terraform" 18 "github.com/hashicorp/terraform/tfdiags" 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, func()) { 27 t.Helper() 28 tempDir := testTempDir(t) 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 local.ShowDiagnostics = func(vals ...interface{}) { 38 var diags tfdiags.Diagnostics 39 diags = diags.Append(vals...) 40 for _, diag := range diags { 41 // NOTE: Since the caller here is not directly the TestLocal 42 // function, t.Helper doesn't apply and so the log source 43 // isn't correctly shown in the test log output. This seems 44 // unavoidable as long as this is happening so indirectly. 45 desc := diag.Description() 46 if desc.Detail != "" { 47 t.Logf("%s: %s", desc.Summary, desc.Detail) 48 } else { 49 t.Log(desc.Summary) 50 } 51 if local.CLI != nil { 52 local.CLI.Error(desc.Summary) 53 } 54 } 55 } 56 57 cleanup := func() { 58 if err := os.RemoveAll(tempDir); err != nil { 59 t.Fatal("error cleanup up test:", err) 60 } 61 } 62 63 return local, cleanup 64 } 65 66 // TestLocalProvider modifies the ContextOpts of the *Local parameter to 67 // have a provider with the given name. 68 func TestLocalProvider(t *testing.T, b *Local, name string, schema *terraform.ProviderSchema) *terraform.MockProvider { 69 // Build a mock resource provider for in-memory operations 70 p := new(terraform.MockProvider) 71 72 if schema == nil { 73 schema = &terraform.ProviderSchema{} // default schema is empty 74 } 75 p.GetSchemaReturn = schema 76 77 p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) providers.PlanResourceChangeResponse { 78 rSchema, _ := schema.SchemaForResourceType(addrs.ManagedResourceMode, req.TypeName) 79 if rSchema == nil { 80 rSchema = &configschema.Block{} // default schema is empty 81 } 82 plannedVals := map[string]cty.Value{} 83 for name, attrS := range rSchema.Attributes { 84 val := req.ProposedNewState.GetAttr(name) 85 if attrS.Computed && val.IsNull() { 86 val = cty.UnknownVal(attrS.Type) 87 } 88 plannedVals[name] = val 89 } 90 for name := range rSchema.BlockTypes { 91 // For simplicity's sake we just copy the block attributes over 92 // verbatim, since this package's mock providers are all relatively 93 // simple -- we're testing the backend, not esoteric provider features. 94 plannedVals[name] = req.ProposedNewState.GetAttr(name) 95 } 96 97 return providers.PlanResourceChangeResponse{ 98 PlannedState: cty.ObjectVal(plannedVals), 99 PlannedPrivate: req.PriorPrivate, 100 } 101 } 102 p.ReadResourceFn = func(req providers.ReadResourceRequest) providers.ReadResourceResponse { 103 return providers.ReadResourceResponse{NewState: req.PriorState} 104 } 105 p.ReadDataSourceFn = func(req providers.ReadDataSourceRequest) providers.ReadDataSourceResponse { 106 return providers.ReadDataSourceResponse{State: req.Config} 107 } 108 109 // Initialize the opts 110 if b.ContextOpts == nil { 111 b.ContextOpts = &terraform.ContextOpts{} 112 } 113 114 // Setup our provider 115 b.ContextOpts.ProviderResolver = providers.ResolverFixed( 116 map[addrs.Provider]providers.Factory{ 117 addrs.NewLegacyProvider(name): providers.FactoryFixed(p), 118 }, 119 ) 120 121 return p 122 123 } 124 125 // TestLocalSingleState is a backend implementation that wraps Local 126 // and modifies it to only support single states (returns 127 // ErrWorkspacesNotSupported for multi-state operations). 128 // 129 // This isn't an actual use case, this is exported just to provide a 130 // easy way to test that behavior. 131 type TestLocalSingleState struct { 132 *Local 133 } 134 135 // TestNewLocalSingle is a factory for creating a TestLocalSingleState. 136 // This function matches the signature required for backend/init. 137 func TestNewLocalSingle() backend.Backend { 138 return &TestLocalSingleState{Local: New()} 139 } 140 141 func (b *TestLocalSingleState) Workspaces() ([]string, error) { 142 return nil, backend.ErrWorkspacesNotSupported 143 } 144 145 func (b *TestLocalSingleState) DeleteWorkspace(string) error { 146 return backend.ErrWorkspacesNotSupported 147 } 148 149 func (b *TestLocalSingleState) StateMgr(name string) (statemgr.Full, error) { 150 if name != backend.DefaultStateName { 151 return nil, backend.ErrWorkspacesNotSupported 152 } 153 154 return b.Local.StateMgr(name) 155 } 156 157 // TestLocalNoDefaultState is a backend implementation that wraps 158 // Local and modifies it to support named states, but not the 159 // default state. It returns ErrDefaultWorkspaceNotSupported when 160 // the DefaultStateName is used. 161 type TestLocalNoDefaultState struct { 162 *Local 163 } 164 165 // TestNewLocalNoDefault is a factory for creating a TestLocalNoDefaultState. 166 // This function matches the signature required for backend/init. 167 func TestNewLocalNoDefault() backend.Backend { 168 return &TestLocalNoDefaultState{Local: New()} 169 } 170 171 func (b *TestLocalNoDefaultState) Workspaces() ([]string, error) { 172 workspaces, err := b.Local.Workspaces() 173 if err != nil { 174 return nil, err 175 } 176 177 filtered := workspaces[:0] 178 for _, name := range workspaces { 179 if name != backend.DefaultStateName { 180 filtered = append(filtered, name) 181 } 182 } 183 184 return filtered, nil 185 } 186 187 func (b *TestLocalNoDefaultState) DeleteWorkspace(name string) error { 188 if name == backend.DefaultStateName { 189 return backend.ErrDefaultWorkspaceNotSupported 190 } 191 return b.Local.DeleteWorkspace(name) 192 } 193 194 func (b *TestLocalNoDefaultState) StateMgr(name string) (statemgr.Full, error) { 195 if name == backend.DefaultStateName { 196 return nil, backend.ErrDefaultWorkspaceNotSupported 197 } 198 return b.Local.StateMgr(name) 199 } 200 201 func testTempDir(t *testing.T) string { 202 d, err := ioutil.TempDir("", "tf") 203 if err != nil { 204 t.Fatalf("err: %s", err) 205 } 206 207 return d 208 } 209 210 func testStateFile(t *testing.T, path string, s *states.State) { 211 stateFile := statemgr.NewFilesystem(path) 212 stateFile.WriteState(s) 213 }