github.com/richardbowden/terraform@v0.6.12-0.20160901200758-30ea22c25211/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 ctx, err := NewContext(opts) 72 if err != nil { 73 t.Fatalf("err: %s", err) 74 } 75 76 return ctx 77 } 78 79 func testApplyFn( 80 info *InstanceInfo, 81 s *InstanceState, 82 d *InstanceDiff) (*InstanceState, error) { 83 if d.Destroy { 84 return nil, nil 85 } 86 87 id := "foo" 88 if idAttr, ok := d.Attributes["id"]; ok && !idAttr.NewComputed { 89 id = idAttr.New 90 } 91 92 result := &InstanceState{ 93 ID: id, 94 Attributes: make(map[string]string), 95 } 96 97 // Copy all the prior attributes 98 for k, v := range s.Attributes { 99 result.Attributes[k] = v 100 } 101 102 if d != nil { 103 result = result.MergeDiff(d) 104 } 105 return result, nil 106 } 107 108 func testDiffFn( 109 info *InstanceInfo, 110 s *InstanceState, 111 c *ResourceConfig) (*InstanceDiff, error) { 112 diff := new(InstanceDiff) 113 diff.Attributes = make(map[string]*ResourceAttrDiff) 114 115 if s != nil { 116 diff.DestroyTainted = s.Tainted 117 } 118 119 for k, v := range c.Raw { 120 if _, ok := v.(string); !ok { 121 continue 122 } 123 124 // Ignore __-prefixed keys since they're used for magic 125 if k[0] == '_' && k[1] == '_' { 126 continue 127 } 128 129 if k == "nil" { 130 return nil, nil 131 } 132 133 // This key is used for other purposes 134 if k == "compute_value" { 135 continue 136 } 137 138 if k == "compute" { 139 attrDiff := &ResourceAttrDiff{ 140 Old: "", 141 New: "", 142 NewComputed: true, 143 } 144 145 if cv, ok := c.Config["compute_value"]; ok { 146 if cv.(string) == "1" { 147 attrDiff.NewComputed = false 148 attrDiff.New = fmt.Sprintf("computed_%s", v.(string)) 149 } 150 } 151 152 diff.Attributes[v.(string)] = attrDiff 153 continue 154 } 155 156 // If this key is not computed, then look it up in the 157 // cleaned config. 158 found := false 159 for _, ck := range c.ComputedKeys { 160 if ck == k { 161 found = true 162 break 163 } 164 } 165 if !found { 166 v = c.Config[k] 167 } 168 169 for k, attrDiff := range testFlatAttrDiffs(k, v) { 170 if k == "require_new" { 171 attrDiff.RequiresNew = true 172 } 173 if _, ok := c.Raw["__"+k+"_requires_new"]; ok { 174 attrDiff.RequiresNew = true 175 } 176 diff.Attributes[k] = attrDiff 177 } 178 } 179 180 for _, k := range c.ComputedKeys { 181 diff.Attributes[k] = &ResourceAttrDiff{ 182 Old: "", 183 NewComputed: true, 184 } 185 } 186 187 // If we recreate this resource because it's tainted, we keep all attrs 188 if !diff.RequiresNew() { 189 for k, v := range diff.Attributes { 190 if v.NewComputed { 191 continue 192 } 193 194 old, ok := s.Attributes[k] 195 if !ok { 196 continue 197 } 198 199 if old == v.New { 200 delete(diff.Attributes, k) 201 } 202 } 203 } 204 205 if !diff.Empty() { 206 diff.Attributes["type"] = &ResourceAttrDiff{ 207 Old: "", 208 New: info.Type, 209 } 210 } 211 212 return diff, nil 213 } 214 215 // generate ResourceAttrDiffs for nested data structures in tests 216 func testFlatAttrDiffs(k string, i interface{}) map[string]*ResourceAttrDiff { 217 diffs := make(map[string]*ResourceAttrDiff) 218 // check for strings and empty containers first 219 switch t := i.(type) { 220 case string: 221 diffs[k] = &ResourceAttrDiff{New: t} 222 return diffs 223 case map[string]interface{}: 224 if len(t) == 0 { 225 diffs[k] = &ResourceAttrDiff{New: ""} 226 return diffs 227 } 228 case []interface{}: 229 if len(t) == 0 { 230 diffs[k] = &ResourceAttrDiff{New: ""} 231 return diffs 232 } 233 } 234 235 flat := flatmap.Flatten(map[string]interface{}{k: i}) 236 237 for k, v := range flat { 238 attrDiff := &ResourceAttrDiff{ 239 Old: "", 240 New: v, 241 } 242 diffs[k] = attrDiff 243 } 244 245 return diffs 246 } 247 248 func testProvider(prefix string) *MockResourceProvider { 249 p := new(MockResourceProvider) 250 p.RefreshFn = func(info *InstanceInfo, s *InstanceState) (*InstanceState, error) { 251 return s, nil 252 } 253 p.ResourcesReturn = []ResourceType{ 254 ResourceType{ 255 Name: fmt.Sprintf("%s_instance", prefix), 256 }, 257 } 258 259 return p 260 } 261 262 func testProvisioner() *MockResourceProvisioner { 263 p := new(MockResourceProvisioner) 264 return p 265 } 266 267 func checkStateString(t *testing.T, state *State, expected string) { 268 actual := strings.TrimSpace(state.String()) 269 expected = strings.TrimSpace(expected) 270 271 if actual != expected { 272 t.Fatalf("state does not match! actual:\n%s\n\nexpected:\n%s", actual, expected) 273 } 274 } 275 276 func resourceState(resourceType, resourceID string) *ResourceState { 277 return &ResourceState{ 278 Type: resourceType, 279 Primary: &InstanceState{ 280 ID: resourceID, 281 }, 282 } 283 } 284 285 // Test helper that gives a function 3 seconds to finish, assumes deadlock and 286 // fails test if it does not. 287 func testCheckDeadlock(t *testing.T, f func()) { 288 timeout := make(chan bool, 1) 289 done := make(chan bool, 1) 290 go func() { 291 time.Sleep(3 * time.Second) 292 timeout <- true 293 }() 294 go func(f func(), done chan bool) { 295 defer func() { done <- true }() 296 f() 297 }(f, done) 298 select { 299 case <-timeout: 300 t.Fatalf("timed out! probably deadlock") 301 case <-done: 302 // ok 303 } 304 } 305 306 const testContextGraph = ` 307 root: root 308 aws_instance.bar 309 aws_instance.bar -> provider.aws 310 aws_instance.foo 311 aws_instance.foo -> provider.aws 312 provider.aws 313 root 314 root -> aws_instance.bar 315 root -> aws_instance.foo 316 ` 317 318 const testContextRefreshModuleStr = ` 319 aws_instance.web: (tainted) 320 ID = bar 321 322 module.child: 323 aws_instance.web: 324 ID = new 325 ` 326 327 const testContextRefreshOutputStr = ` 328 aws_instance.web: 329 ID = foo 330 foo = bar 331 332 Outputs: 333 334 foo = bar 335 ` 336 337 const testContextRefreshOutputPartialStr = ` 338 <no state> 339 ` 340 341 const testContextRefreshTaintedStr = ` 342 aws_instance.web: (tainted) 343 ID = foo 344 `