github.com/rhenning/terraform@v0.8.0-beta2/terraform/context_test.go (about) 1 package terraform 2 3 import ( 4 "fmt" 5 "strings" 6 "testing" 7 "time" 8 9 "github.com/hashicorp/go-version" 10 "github.com/hashicorp/terraform/flatmap" 11 ) 12 13 func TestNewContextRequiredVersion(t *testing.T) { 14 cases := []struct { 15 Name string 16 Module string 17 Version string 18 Value string 19 Err bool 20 }{ 21 { 22 "no requirement", 23 "", 24 "0.1.0", 25 "", 26 false, 27 }, 28 29 { 30 "doesn't match", 31 "", 32 "0.1.0", 33 "> 0.6.0", 34 true, 35 }, 36 37 { 38 "matches", 39 "", 40 "0.7.0", 41 "> 0.6.0", 42 false, 43 }, 44 45 { 46 "module matches", 47 "context-required-version-module", 48 "0.5.0", 49 "", 50 false, 51 }, 52 53 { 54 "module doesn't match", 55 "context-required-version-module", 56 "0.4.0", 57 "", 58 true, 59 }, 60 } 61 62 for i, tc := range cases { 63 t.Run(fmt.Sprintf("%d-%s", i, tc.Name), func(t *testing.T) { 64 // Reset the version for the tests 65 old := SemVersion 66 SemVersion = version.Must(version.NewVersion(tc.Version)) 67 defer func() { SemVersion = old }() 68 69 name := "context-required-version" 70 if tc.Module != "" { 71 name = tc.Module 72 } 73 mod := testModule(t, name) 74 if tc.Value != "" { 75 mod.Config().Terraform.RequiredVersion = tc.Value 76 } 77 _, err := NewContext(&ContextOpts{ 78 Module: mod, 79 }) 80 if (err != nil) != tc.Err { 81 t.Fatalf("err: %s", err) 82 } 83 if err != nil { 84 return 85 } 86 }) 87 } 88 } 89 90 func TestNewContextState(t *testing.T) { 91 cases := map[string]struct { 92 Input *ContextOpts 93 Err bool 94 }{ 95 "empty TFVersion": { 96 &ContextOpts{ 97 State: &State{}, 98 }, 99 false, 100 }, 101 102 "past TFVersion": { 103 &ContextOpts{ 104 State: &State{TFVersion: "0.1.2"}, 105 }, 106 false, 107 }, 108 109 "equal TFVersion": { 110 &ContextOpts{ 111 State: &State{TFVersion: Version}, 112 }, 113 false, 114 }, 115 116 "future TFVersion": { 117 &ContextOpts{ 118 State: &State{TFVersion: "99.99.99"}, 119 }, 120 true, 121 }, 122 123 "future TFVersion, allowed": { 124 &ContextOpts{ 125 State: &State{TFVersion: "99.99.99"}, 126 StateFutureAllowed: true, 127 }, 128 false, 129 }, 130 } 131 132 for k, tc := range cases { 133 ctx, err := NewContext(tc.Input) 134 if (err != nil) != tc.Err { 135 t.Fatalf("%s: err: %s", k, err) 136 } 137 if err != nil { 138 continue 139 } 140 141 // Version should always be set to our current 142 if ctx.state.TFVersion != Version { 143 t.Fatalf("%s: state not set to current version", k) 144 } 145 } 146 } 147 148 func testContext2(t *testing.T, opts *ContextOpts) *Context { 149 // Enable the shadow graph 150 opts.Shadow = true 151 152 ctx, err := NewContext(opts) 153 if err != nil { 154 t.Fatalf("err: %s", err) 155 } 156 157 return ctx 158 } 159 160 func testDataApplyFn( 161 info *InstanceInfo, 162 d *InstanceDiff) (*InstanceState, error) { 163 return testApplyFn(info, new(InstanceState), d) 164 } 165 166 func testDataDiffFn( 167 info *InstanceInfo, 168 c *ResourceConfig) (*InstanceDiff, error) { 169 return testDiffFn(info, new(InstanceState), c) 170 } 171 172 func testApplyFn( 173 info *InstanceInfo, 174 s *InstanceState, 175 d *InstanceDiff) (*InstanceState, error) { 176 if d.Destroy { 177 return nil, nil 178 } 179 180 id := "foo" 181 if idAttr, ok := d.Attributes["id"]; ok && !idAttr.NewComputed { 182 id = idAttr.New 183 } 184 185 result := &InstanceState{ 186 ID: id, 187 Attributes: make(map[string]string), 188 } 189 190 // Copy all the prior attributes 191 for k, v := range s.Attributes { 192 result.Attributes[k] = v 193 } 194 195 if d != nil { 196 result = result.MergeDiff(d) 197 } 198 return result, nil 199 } 200 201 func testDiffFn( 202 info *InstanceInfo, 203 s *InstanceState, 204 c *ResourceConfig) (*InstanceDiff, error) { 205 diff := new(InstanceDiff) 206 diff.Attributes = make(map[string]*ResourceAttrDiff) 207 208 if s != nil { 209 diff.DestroyTainted = s.Tainted 210 } 211 212 for k, v := range c.Raw { 213 if _, ok := v.(string); !ok { 214 continue 215 } 216 217 // Ignore __-prefixed keys since they're used for magic 218 if k[0] == '_' && k[1] == '_' { 219 continue 220 } 221 222 if k == "nil" { 223 return nil, nil 224 } 225 226 // This key is used for other purposes 227 if k == "compute_value" { 228 continue 229 } 230 231 if k == "compute" { 232 attrDiff := &ResourceAttrDiff{ 233 Old: "", 234 New: "", 235 NewComputed: true, 236 } 237 238 if cv, ok := c.Config["compute_value"]; ok { 239 if cv.(string) == "1" { 240 attrDiff.NewComputed = false 241 attrDiff.New = fmt.Sprintf("computed_%s", v.(string)) 242 } 243 } 244 245 diff.Attributes[v.(string)] = attrDiff 246 continue 247 } 248 249 // If this key is not computed, then look it up in the 250 // cleaned config. 251 found := false 252 for _, ck := range c.ComputedKeys { 253 if ck == k { 254 found = true 255 break 256 } 257 } 258 if !found { 259 v = c.Config[k] 260 } 261 262 for k, attrDiff := range testFlatAttrDiffs(k, v) { 263 if k == "require_new" { 264 attrDiff.RequiresNew = true 265 } 266 if _, ok := c.Raw["__"+k+"_requires_new"]; ok { 267 attrDiff.RequiresNew = true 268 } 269 diff.Attributes[k] = attrDiff 270 } 271 } 272 273 for _, k := range c.ComputedKeys { 274 diff.Attributes[k] = &ResourceAttrDiff{ 275 Old: "", 276 NewComputed: true, 277 } 278 } 279 280 // If we recreate this resource because it's tainted, we keep all attrs 281 if !diff.RequiresNew() { 282 for k, v := range diff.Attributes { 283 if v.NewComputed { 284 continue 285 } 286 287 old, ok := s.Attributes[k] 288 if !ok { 289 continue 290 } 291 292 if old == v.New { 293 delete(diff.Attributes, k) 294 } 295 } 296 } 297 298 if !diff.Empty() { 299 diff.Attributes["type"] = &ResourceAttrDiff{ 300 Old: "", 301 New: info.Type, 302 } 303 } 304 305 return diff, nil 306 } 307 308 // generate ResourceAttrDiffs for nested data structures in tests 309 func testFlatAttrDiffs(k string, i interface{}) map[string]*ResourceAttrDiff { 310 diffs := make(map[string]*ResourceAttrDiff) 311 // check for strings and empty containers first 312 switch t := i.(type) { 313 case string: 314 diffs[k] = &ResourceAttrDiff{New: t} 315 return diffs 316 case map[string]interface{}: 317 if len(t) == 0 { 318 diffs[k] = &ResourceAttrDiff{New: ""} 319 return diffs 320 } 321 case []interface{}: 322 if len(t) == 0 { 323 diffs[k] = &ResourceAttrDiff{New: ""} 324 return diffs 325 } 326 } 327 328 flat := flatmap.Flatten(map[string]interface{}{k: i}) 329 330 for k, v := range flat { 331 attrDiff := &ResourceAttrDiff{ 332 Old: "", 333 New: v, 334 } 335 diffs[k] = attrDiff 336 } 337 338 return diffs 339 } 340 341 func testProvider(prefix string) *MockResourceProvider { 342 p := new(MockResourceProvider) 343 p.RefreshFn = func(info *InstanceInfo, s *InstanceState) (*InstanceState, error) { 344 return s, nil 345 } 346 p.ResourcesReturn = []ResourceType{ 347 ResourceType{ 348 Name: fmt.Sprintf("%s_instance", prefix), 349 }, 350 } 351 352 return p 353 } 354 355 func testProvisioner() *MockResourceProvisioner { 356 p := new(MockResourceProvisioner) 357 return p 358 } 359 360 func checkStateString(t *testing.T, state *State, expected string) { 361 actual := strings.TrimSpace(state.String()) 362 expected = strings.TrimSpace(expected) 363 364 if actual != expected { 365 t.Fatalf("state does not match! actual:\n%s\n\nexpected:\n%s", actual, expected) 366 } 367 } 368 369 func resourceState(resourceType, resourceID string) *ResourceState { 370 return &ResourceState{ 371 Type: resourceType, 372 Primary: &InstanceState{ 373 ID: resourceID, 374 }, 375 } 376 } 377 378 // Test helper that gives a function 3 seconds to finish, assumes deadlock and 379 // fails test if it does not. 380 func testCheckDeadlock(t *testing.T, f func()) { 381 timeout := make(chan bool, 1) 382 done := make(chan bool, 1) 383 go func() { 384 time.Sleep(3 * time.Second) 385 timeout <- true 386 }() 387 go func(f func(), done chan bool) { 388 defer func() { done <- true }() 389 f() 390 }(f, done) 391 select { 392 case <-timeout: 393 t.Fatalf("timed out! probably deadlock") 394 case <-done: 395 // ok 396 } 397 } 398 399 const testContextGraph = ` 400 root: root 401 aws_instance.bar 402 aws_instance.bar -> provider.aws 403 aws_instance.foo 404 aws_instance.foo -> provider.aws 405 provider.aws 406 root 407 root -> aws_instance.bar 408 root -> aws_instance.foo 409 ` 410 411 const testContextRefreshModuleStr = ` 412 aws_instance.web: (tainted) 413 ID = bar 414 415 module.child: 416 aws_instance.web: 417 ID = new 418 ` 419 420 const testContextRefreshOutputStr = ` 421 aws_instance.web: 422 ID = foo 423 foo = bar 424 425 Outputs: 426 427 foo = bar 428 ` 429 430 const testContextRefreshOutputPartialStr = ` 431 <no state> 432 ` 433 434 const testContextRefreshTaintedStr = ` 435 aws_instance.web: (tainted) 436 ID = foo 437 `