github.com/nevins-b/terraform@v0.3.8-0.20170215184714-bbae22007d5a/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 diff.Attributes[k] = attrDiff 266 } 267 } 268 269 for _, k := range c.ComputedKeys { 270 diff.Attributes[k] = &ResourceAttrDiff{ 271 Old: "", 272 NewComputed: true, 273 } 274 } 275 276 // If we recreate this resource because it's tainted, we keep all attrs 277 if !diff.RequiresNew() { 278 for k, v := range diff.Attributes { 279 if v.NewComputed { 280 continue 281 } 282 283 old, ok := s.Attributes[k] 284 if !ok { 285 continue 286 } 287 288 if old == v.New { 289 delete(diff.Attributes, k) 290 } 291 } 292 } 293 294 if !diff.Empty() { 295 diff.Attributes["type"] = &ResourceAttrDiff{ 296 Old: "", 297 New: info.Type, 298 } 299 } 300 301 return diff, nil 302 } 303 304 // generate ResourceAttrDiffs for nested data structures in tests 305 func testFlatAttrDiffs(k string, i interface{}) map[string]*ResourceAttrDiff { 306 diffs := make(map[string]*ResourceAttrDiff) 307 // check for strings and empty containers first 308 switch t := i.(type) { 309 case string: 310 diffs[k] = &ResourceAttrDiff{New: t} 311 return diffs 312 case map[string]interface{}: 313 if len(t) == 0 { 314 diffs[k] = &ResourceAttrDiff{New: ""} 315 return diffs 316 } 317 case []interface{}: 318 if len(t) == 0 { 319 diffs[k] = &ResourceAttrDiff{New: ""} 320 return diffs 321 } 322 } 323 324 flat := flatmap.Flatten(map[string]interface{}{k: i}) 325 326 for k, v := range flat { 327 attrDiff := &ResourceAttrDiff{ 328 Old: "", 329 New: v, 330 } 331 diffs[k] = attrDiff 332 } 333 334 return diffs 335 } 336 337 func testProvider(prefix string) *MockResourceProvider { 338 p := new(MockResourceProvider) 339 p.RefreshFn = func(info *InstanceInfo, s *InstanceState) (*InstanceState, error) { 340 return s, nil 341 } 342 p.ResourcesReturn = []ResourceType{ 343 ResourceType{ 344 Name: fmt.Sprintf("%s_instance", prefix), 345 }, 346 } 347 348 return p 349 } 350 351 func testProvisioner() *MockResourceProvisioner { 352 p := new(MockResourceProvisioner) 353 return p 354 } 355 356 func checkStateString(t *testing.T, state *State, expected string) { 357 actual := strings.TrimSpace(state.String()) 358 expected = strings.TrimSpace(expected) 359 360 if actual != expected { 361 t.Fatalf("state does not match! actual:\n%s\n\nexpected:\n%s", actual, expected) 362 } 363 } 364 365 func resourceState(resourceType, resourceID string) *ResourceState { 366 return &ResourceState{ 367 Type: resourceType, 368 Primary: &InstanceState{ 369 ID: resourceID, 370 }, 371 } 372 } 373 374 // Test helper that gives a function 3 seconds to finish, assumes deadlock and 375 // fails test if it does not. 376 func testCheckDeadlock(t *testing.T, f func()) { 377 timeout := make(chan bool, 1) 378 done := make(chan bool, 1) 379 go func() { 380 time.Sleep(3 * time.Second) 381 timeout <- true 382 }() 383 go func(f func(), done chan bool) { 384 defer func() { done <- true }() 385 f() 386 }(f, done) 387 select { 388 case <-timeout: 389 t.Fatalf("timed out! probably deadlock") 390 case <-done: 391 // ok 392 } 393 } 394 395 const testContextGraph = ` 396 root: root 397 aws_instance.bar 398 aws_instance.bar -> provider.aws 399 aws_instance.foo 400 aws_instance.foo -> provider.aws 401 provider.aws 402 root 403 root -> aws_instance.bar 404 root -> aws_instance.foo 405 ` 406 407 const testContextRefreshModuleStr = ` 408 aws_instance.web: (tainted) 409 ID = bar 410 411 module.child: 412 aws_instance.web: 413 ID = new 414 ` 415 416 const testContextRefreshOutputStr = ` 417 aws_instance.web: 418 ID = foo 419 foo = bar 420 421 Outputs: 422 423 foo = bar 424 ` 425 426 const testContextRefreshOutputPartialStr = ` 427 <no state> 428 ` 429 430 const testContextRefreshTaintedStr = ` 431 aws_instance.web: (tainted) 432 ID = foo 433 `