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