github.com/brandonstevens/terraform@v0.9.6-0.20170512224929-5367f2607e16/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 // Ignore __-prefixed keys since they're used for magic 214 if k[0] == '_' && k[1] == '_' { 215 continue 216 } 217 218 if k == "nil" { 219 return nil, nil 220 } 221 222 // This key is used for other purposes 223 if k == "compute_value" { 224 continue 225 } 226 227 if k == "compute" { 228 attrDiff := &ResourceAttrDiff{ 229 Old: "", 230 New: "", 231 NewComputed: true, 232 } 233 234 if cv, ok := c.Config["compute_value"]; ok { 235 if cv.(string) == "1" { 236 attrDiff.NewComputed = false 237 attrDiff.New = fmt.Sprintf("computed_%s", v.(string)) 238 } 239 } 240 241 diff.Attributes[v.(string)] = attrDiff 242 continue 243 } 244 245 // If this key is not computed, then look it up in the 246 // cleaned config. 247 found := false 248 for _, ck := range c.ComputedKeys { 249 if ck == k { 250 found = true 251 break 252 } 253 } 254 if !found { 255 v = c.Config[k] 256 } 257 258 for k, attrDiff := range testFlatAttrDiffs(k, v) { 259 if k == "require_new" { 260 attrDiff.RequiresNew = true 261 } 262 if _, ok := c.Raw["__"+k+"_requires_new"]; ok { 263 attrDiff.RequiresNew = true 264 } 265 266 if attr, ok := s.Attributes[k]; ok { 267 attrDiff.Old = attr 268 } 269 270 diff.Attributes[k] = attrDiff 271 } 272 } 273 274 for _, k := range c.ComputedKeys { 275 diff.Attributes[k] = &ResourceAttrDiff{ 276 Old: "", 277 NewComputed: true, 278 } 279 } 280 281 // If we recreate this resource because it's tainted, we keep all attrs 282 if !diff.RequiresNew() { 283 for k, v := range diff.Attributes { 284 if v.NewComputed { 285 continue 286 } 287 288 old, ok := s.Attributes[k] 289 if !ok { 290 continue 291 } 292 293 if old == v.New { 294 delete(diff.Attributes, k) 295 } 296 } 297 } 298 299 if !diff.Empty() { 300 diff.Attributes["type"] = &ResourceAttrDiff{ 301 Old: "", 302 New: info.Type, 303 } 304 } 305 306 return diff, nil 307 } 308 309 // generate ResourceAttrDiffs for nested data structures in tests 310 func testFlatAttrDiffs(k string, i interface{}) map[string]*ResourceAttrDiff { 311 diffs := make(map[string]*ResourceAttrDiff) 312 // check for strings and empty containers first 313 switch t := i.(type) { 314 case string: 315 diffs[k] = &ResourceAttrDiff{New: t} 316 return diffs 317 case map[string]interface{}: 318 if len(t) == 0 { 319 diffs[k] = &ResourceAttrDiff{New: ""} 320 return diffs 321 } 322 case []interface{}: 323 if len(t) == 0 { 324 diffs[k] = &ResourceAttrDiff{New: ""} 325 return diffs 326 } 327 } 328 329 flat := flatmap.Flatten(map[string]interface{}{k: i}) 330 331 for k, v := range flat { 332 attrDiff := &ResourceAttrDiff{ 333 Old: "", 334 New: v, 335 } 336 diffs[k] = attrDiff 337 } 338 339 return diffs 340 } 341 342 func testProvider(prefix string) *MockResourceProvider { 343 p := new(MockResourceProvider) 344 p.RefreshFn = func(info *InstanceInfo, s *InstanceState) (*InstanceState, error) { 345 return s, nil 346 } 347 p.ResourcesReturn = []ResourceType{ 348 ResourceType{ 349 Name: fmt.Sprintf("%s_instance", prefix), 350 }, 351 } 352 353 return p 354 } 355 356 func testProvisioner() *MockResourceProvisioner { 357 p := new(MockResourceProvisioner) 358 return p 359 } 360 361 func checkStateString(t *testing.T, state *State, expected string) { 362 actual := strings.TrimSpace(state.String()) 363 expected = strings.TrimSpace(expected) 364 365 if actual != expected { 366 t.Fatalf("state does not match! actual:\n%s\n\nexpected:\n%s", actual, expected) 367 } 368 } 369 370 func resourceState(resourceType, resourceID string) *ResourceState { 371 return &ResourceState{ 372 Type: resourceType, 373 Primary: &InstanceState{ 374 ID: resourceID, 375 }, 376 } 377 } 378 379 // Test helper that gives a function 3 seconds to finish, assumes deadlock and 380 // fails test if it does not. 381 func testCheckDeadlock(t *testing.T, f func()) { 382 timeout := make(chan bool, 1) 383 done := make(chan bool, 1) 384 go func() { 385 time.Sleep(3 * time.Second) 386 timeout <- true 387 }() 388 go func(f func(), done chan bool) { 389 defer func() { done <- true }() 390 f() 391 }(f, done) 392 select { 393 case <-timeout: 394 t.Fatalf("timed out! probably deadlock") 395 case <-done: 396 // ok 397 } 398 } 399 400 const testContextGraph = ` 401 root: root 402 aws_instance.bar 403 aws_instance.bar -> provider.aws 404 aws_instance.foo 405 aws_instance.foo -> provider.aws 406 provider.aws 407 root 408 root -> aws_instance.bar 409 root -> aws_instance.foo 410 ` 411 412 const testContextRefreshModuleStr = ` 413 aws_instance.web: (tainted) 414 ID = bar 415 416 module.child: 417 aws_instance.web: 418 ID = new 419 ` 420 421 const testContextRefreshOutputStr = ` 422 aws_instance.web: 423 ID = foo 424 foo = bar 425 426 Outputs: 427 428 foo = bar 429 ` 430 431 const testContextRefreshOutputPartialStr = ` 432 <no state> 433 ` 434 435 const testContextRefreshTaintedStr = ` 436 aws_instance.web: (tainted) 437 ID = foo 438 `