github.com/rhenning/terraform@v0.8.0-beta2/terraform/interpolate_test.go (about)

     1  package terraform
     2  
     3  import (
     4  	"fmt"
     5  	"os"
     6  	"reflect"
     7  	"sync"
     8  	"testing"
     9  
    10  	"github.com/davecgh/go-spew/spew"
    11  	"github.com/hashicorp/hil"
    12  	"github.com/hashicorp/hil/ast"
    13  	"github.com/hashicorp/terraform/config"
    14  )
    15  
    16  func TestInterpolater_simpleVar(t *testing.T) {
    17  	i := &Interpolater{}
    18  	scope := &InterpolationScope{}
    19  	testInterpolateErr(t, i, scope, "simple")
    20  }
    21  
    22  func TestInterpolater_countIndex(t *testing.T) {
    23  	i := &Interpolater{}
    24  
    25  	scope := &InterpolationScope{
    26  		Path:     rootModulePath,
    27  		Resource: &Resource{CountIndex: 42},
    28  	}
    29  
    30  	testInterpolate(t, i, scope, "count.index", ast.Variable{
    31  		Value: 42,
    32  		Type:  ast.TypeInt,
    33  	})
    34  }
    35  
    36  func TestInterpolater_countIndexInWrongContext(t *testing.T) {
    37  	i := &Interpolater{}
    38  
    39  	scope := &InterpolationScope{
    40  		Path: rootModulePath,
    41  	}
    42  
    43  	n := "count.index"
    44  
    45  	v, err := config.NewInterpolatedVariable(n)
    46  	if err != nil {
    47  		t.Fatalf("err: %s", err)
    48  	}
    49  
    50  	expectedErr := fmt.Errorf("foo: count.index is only valid within resources")
    51  
    52  	_, err = i.Values(scope, map[string]config.InterpolatedVariable{
    53  		"foo": v,
    54  	})
    55  
    56  	if !reflect.DeepEqual(expectedErr, err) {
    57  		t.Fatalf("expected: %#v, got %#v", expectedErr, err)
    58  	}
    59  }
    60  
    61  func TestInterpolater_moduleVariable(t *testing.T) {
    62  	lock := new(sync.RWMutex)
    63  	state := &State{
    64  		Modules: []*ModuleState{
    65  			&ModuleState{
    66  				Path: rootModulePath,
    67  				Resources: map[string]*ResourceState{
    68  					"aws_instance.web": &ResourceState{
    69  						Type: "aws_instance",
    70  						Primary: &InstanceState{
    71  							ID: "bar",
    72  						},
    73  					},
    74  				},
    75  			},
    76  			&ModuleState{
    77  				Path: []string{RootModuleName, "child"},
    78  				Outputs: map[string]*OutputState{
    79  					"foo": &OutputState{
    80  						Type:  "string",
    81  						Value: "bar",
    82  					},
    83  				},
    84  			},
    85  		},
    86  	}
    87  
    88  	i := &Interpolater{
    89  		State:     state,
    90  		StateLock: lock,
    91  	}
    92  
    93  	scope := &InterpolationScope{
    94  		Path: rootModulePath,
    95  	}
    96  
    97  	testInterpolate(t, i, scope, "module.child.foo", ast.Variable{
    98  		Value: "bar",
    99  		Type:  ast.TypeString,
   100  	})
   101  }
   102  
   103  func TestInterpolater_pathCwd(t *testing.T) {
   104  	i := &Interpolater{}
   105  	scope := &InterpolationScope{}
   106  
   107  	expected, err := os.Getwd()
   108  	if err != nil {
   109  		t.Fatalf("err: %s", err)
   110  	}
   111  
   112  	testInterpolate(t, i, scope, "path.cwd", ast.Variable{
   113  		Value: expected,
   114  		Type:  ast.TypeString,
   115  	})
   116  }
   117  
   118  func TestInterpolater_pathModule(t *testing.T) {
   119  	mod := testModule(t, "interpolate-path-module")
   120  	i := &Interpolater{
   121  		Module: mod,
   122  	}
   123  	scope := &InterpolationScope{
   124  		Path: []string{RootModuleName, "child"},
   125  	}
   126  
   127  	path := mod.Child([]string{"child"}).Config().Dir
   128  	testInterpolate(t, i, scope, "path.module", ast.Variable{
   129  		Value: path,
   130  		Type:  ast.TypeString,
   131  	})
   132  }
   133  
   134  func TestInterpolater_pathRoot(t *testing.T) {
   135  	mod := testModule(t, "interpolate-path-module")
   136  	i := &Interpolater{
   137  		Module: mod,
   138  	}
   139  	scope := &InterpolationScope{
   140  		Path: []string{RootModuleName, "child"},
   141  	}
   142  
   143  	path := mod.Config().Dir
   144  	testInterpolate(t, i, scope, "path.root", ast.Variable{
   145  		Value: path,
   146  		Type:  ast.TypeString,
   147  	})
   148  }
   149  
   150  func TestInterpolater_resourceVariableMap(t *testing.T) {
   151  	lock := new(sync.RWMutex)
   152  	state := &State{
   153  		Modules: []*ModuleState{
   154  			&ModuleState{
   155  				Path: rootModulePath,
   156  				Resources: map[string]*ResourceState{
   157  					"aws_instance.web": &ResourceState{
   158  						Type: "aws_instance",
   159  						Primary: &InstanceState{
   160  							ID: "bar",
   161  							Attributes: map[string]string{
   162  								"amap.%":    "3",
   163  								"amap.key1": "value1",
   164  								"amap.key2": "value2",
   165  								"amap.key3": "value3",
   166  							},
   167  						},
   168  					},
   169  				},
   170  			},
   171  		},
   172  	}
   173  
   174  	i := &Interpolater{
   175  		Module:    testModule(t, "interpolate-resource-variable"),
   176  		State:     state,
   177  		StateLock: lock,
   178  	}
   179  
   180  	scope := &InterpolationScope{
   181  		Path: rootModulePath,
   182  	}
   183  
   184  	expected := map[string]interface{}{
   185  		"key1": "value1",
   186  		"key2": "value2",
   187  		"key3": "value3",
   188  	}
   189  
   190  	testInterpolate(t, i, scope, "aws_instance.web.amap",
   191  		interfaceToVariableSwallowError(expected))
   192  }
   193  
   194  func TestInterpolater_resourceVariableComplexMap(t *testing.T) {
   195  	lock := new(sync.RWMutex)
   196  	state := &State{
   197  		Modules: []*ModuleState{
   198  			&ModuleState{
   199  				Path: rootModulePath,
   200  				Resources: map[string]*ResourceState{
   201  					"aws_instance.web": &ResourceState{
   202  						Type: "aws_instance",
   203  						Primary: &InstanceState{
   204  							ID: "bar",
   205  							Attributes: map[string]string{
   206  								"amap.%":      "2",
   207  								"amap.key1.#": "2",
   208  								"amap.key1.0": "hello",
   209  								"amap.key1.1": "world",
   210  								"amap.key2.#": "1",
   211  								"amap.key2.0": "foo",
   212  							},
   213  						},
   214  					},
   215  				},
   216  			},
   217  		},
   218  	}
   219  
   220  	i := &Interpolater{
   221  		Module:    testModule(t, "interpolate-resource-variable"),
   222  		State:     state,
   223  		StateLock: lock,
   224  	}
   225  
   226  	scope := &InterpolationScope{
   227  		Path: rootModulePath,
   228  	}
   229  
   230  	expected := map[string]interface{}{
   231  		"key1": []interface{}{"hello", "world"},
   232  		"key2": []interface{}{"foo"},
   233  	}
   234  
   235  	testInterpolate(t, i, scope, "aws_instance.web.amap",
   236  		interfaceToVariableSwallowError(expected))
   237  }
   238  
   239  func TestInterpolater_resourceVariable(t *testing.T) {
   240  	lock := new(sync.RWMutex)
   241  	state := &State{
   242  		Modules: []*ModuleState{
   243  			&ModuleState{
   244  				Path: rootModulePath,
   245  				Resources: map[string]*ResourceState{
   246  					"aws_instance.web": &ResourceState{
   247  						Type: "aws_instance",
   248  						Primary: &InstanceState{
   249  							ID: "bar",
   250  							Attributes: map[string]string{
   251  								"foo": "bar",
   252  							},
   253  						},
   254  					},
   255  				},
   256  			},
   257  		},
   258  	}
   259  
   260  	i := &Interpolater{
   261  		Module:    testModule(t, "interpolate-resource-variable"),
   262  		State:     state,
   263  		StateLock: lock,
   264  	}
   265  
   266  	scope := &InterpolationScope{
   267  		Path: rootModulePath,
   268  	}
   269  
   270  	testInterpolate(t, i, scope, "aws_instance.web.foo", ast.Variable{
   271  		Value: "bar",
   272  		Type:  ast.TypeString,
   273  	})
   274  }
   275  
   276  func TestInterpolater_resourceVariableMissingDuringInput(t *testing.T) {
   277  	// During the input walk, computed resource attributes may be entirely
   278  	// absent since we've not yet produced diffs that tell us what computed
   279  	// attributes to expect. In that case, interpolator tolerates it and
   280  	// indicates the value is computed.
   281  
   282  	lock := new(sync.RWMutex)
   283  	state := &State{
   284  		Modules: []*ModuleState{
   285  			&ModuleState{
   286  				Path:      rootModulePath,
   287  				Resources: map[string]*ResourceState{
   288  				// No resources at all yet, because we're still dealing
   289  				// with input and so the resources haven't been created.
   290  				},
   291  			},
   292  		},
   293  	}
   294  
   295  	{
   296  		i := &Interpolater{
   297  			Operation: walkInput,
   298  			Module:    testModule(t, "interpolate-resource-variable"),
   299  			State:     state,
   300  			StateLock: lock,
   301  		}
   302  
   303  		scope := &InterpolationScope{
   304  			Path: rootModulePath,
   305  		}
   306  
   307  		testInterpolate(t, i, scope, "aws_instance.web.foo", ast.Variable{
   308  			Value: config.UnknownVariableValue,
   309  			Type:  ast.TypeUnknown,
   310  		})
   311  	}
   312  
   313  	// This doesn't apply during other walks, like plan
   314  	{
   315  		i := &Interpolater{
   316  			Operation: walkPlan,
   317  			Module:    testModule(t, "interpolate-resource-variable"),
   318  			State:     state,
   319  			StateLock: lock,
   320  		}
   321  
   322  		scope := &InterpolationScope{
   323  			Path: rootModulePath,
   324  		}
   325  
   326  		testInterpolateErr(t, i, scope, "aws_instance.web.foo")
   327  	}
   328  }
   329  
   330  func TestInterpolater_resourceVariableMulti(t *testing.T) {
   331  	lock := new(sync.RWMutex)
   332  	state := &State{
   333  		Modules: []*ModuleState{
   334  			&ModuleState{
   335  				Path: rootModulePath,
   336  				Resources: map[string]*ResourceState{
   337  					"aws_instance.web": &ResourceState{
   338  						Type: "aws_instance",
   339  						Primary: &InstanceState{
   340  							ID: "bar",
   341  							Attributes: map[string]string{
   342  								"foo": config.UnknownVariableValue,
   343  							},
   344  						},
   345  					},
   346  				},
   347  			},
   348  		},
   349  	}
   350  
   351  	i := &Interpolater{
   352  		Module:    testModule(t, "interpolate-resource-variable"),
   353  		State:     state,
   354  		StateLock: lock,
   355  	}
   356  
   357  	scope := &InterpolationScope{
   358  		Path: rootModulePath,
   359  	}
   360  
   361  	testInterpolate(t, i, scope, "aws_instance.web.*.foo", ast.Variable{
   362  		Value: config.UnknownVariableValue,
   363  		Type:  ast.TypeUnknown,
   364  	})
   365  }
   366  
   367  func TestInterpolater_resourceVariableMulti_interpolated(t *testing.T) {
   368  	lock := new(sync.RWMutex)
   369  	state := &State{
   370  		Modules: []*ModuleState{
   371  			&ModuleState{
   372  				Path: rootModulePath,
   373  				Resources: map[string]*ResourceState{
   374  					"aws_instance.web.0": &ResourceState{
   375  						Type: "aws_instance",
   376  						Primary: &InstanceState{
   377  							ID:         "a",
   378  							Attributes: map[string]string{"foo": "a"},
   379  						},
   380  					},
   381  
   382  					"aws_instance.web.1": &ResourceState{
   383  						Type: "aws_instance",
   384  						Primary: &InstanceState{
   385  							ID:         "b",
   386  							Attributes: map[string]string{"foo": "b"},
   387  						},
   388  					},
   389  				},
   390  			},
   391  		},
   392  	}
   393  
   394  	i := &Interpolater{
   395  		Operation: walkApply,
   396  		Module:    testModule(t, "interpolate-multi-interp"),
   397  		State:     state,
   398  		StateLock: lock,
   399  	}
   400  
   401  	scope := &InterpolationScope{
   402  		Path: rootModulePath,
   403  	}
   404  
   405  	expected := []interface{}{"a", "b"}
   406  	testInterpolate(t, i, scope, "aws_instance.web.*.foo",
   407  		interfaceToVariableSwallowError(expected))
   408  }
   409  
   410  func interfaceToVariableSwallowError(input interface{}) ast.Variable {
   411  	variable, _ := hil.InterfaceToVariable(input)
   412  	return variable
   413  }
   414  
   415  func TestInterpolator_resourceMultiAttributes(t *testing.T) {
   416  	lock := new(sync.RWMutex)
   417  	state := &State{
   418  		Modules: []*ModuleState{
   419  			{
   420  				Path: rootModulePath,
   421  				Resources: map[string]*ResourceState{
   422  					"aws_route53_zone.yada": {
   423  						Type:         "aws_route53_zone",
   424  						Dependencies: []string{},
   425  						Primary: &InstanceState{
   426  							ID: "AAABBBCCCDDDEEE",
   427  							Attributes: map[string]string{
   428  								"name_servers.#": "4",
   429  								"name_servers.0": "ns-1334.awsdns-38.org",
   430  								"name_servers.1": "ns-1680.awsdns-18.co.uk",
   431  								"name_servers.2": "ns-498.awsdns-62.com",
   432  								"name_servers.3": "ns-601.awsdns-11.net",
   433  								"listeners.#":    "1",
   434  								"listeners.0":    "red",
   435  								"tags.%":         "1",
   436  								"tags.Name":      "reindeer",
   437  								"nothing.#":      "0",
   438  							},
   439  						},
   440  					},
   441  				},
   442  			},
   443  		},
   444  	}
   445  
   446  	i := &Interpolater{
   447  		Module:    testModule(t, "interpolate-multi-vars"),
   448  		StateLock: lock,
   449  		State:     state,
   450  	}
   451  
   452  	scope := &InterpolationScope{
   453  		Path: rootModulePath,
   454  	}
   455  
   456  	name_servers := []interface{}{
   457  		"ns-1334.awsdns-38.org",
   458  		"ns-1680.awsdns-18.co.uk",
   459  		"ns-498.awsdns-62.com",
   460  		"ns-601.awsdns-11.net",
   461  	}
   462  
   463  	// More than 1 element
   464  	testInterpolate(t, i, scope, "aws_route53_zone.yada.name_servers",
   465  		interfaceToVariableSwallowError(name_servers))
   466  
   467  	// Exactly 1 element
   468  	testInterpolate(t, i, scope, "aws_route53_zone.yada.listeners",
   469  		interfaceToVariableSwallowError([]interface{}{"red"}))
   470  
   471  	// Zero elements
   472  	testInterpolate(t, i, scope, "aws_route53_zone.yada.nothing",
   473  		interfaceToVariableSwallowError([]interface{}{}))
   474  
   475  	// Maps still need to work
   476  	testInterpolate(t, i, scope, "aws_route53_zone.yada.tags.Name", ast.Variable{
   477  		Value: "reindeer",
   478  		Type:  ast.TypeString,
   479  	})
   480  }
   481  
   482  func TestInterpolator_resourceMultiAttributesWithResourceCount(t *testing.T) {
   483  	i := getInterpolaterFixture(t)
   484  	scope := &InterpolationScope{
   485  		Path: rootModulePath,
   486  	}
   487  
   488  	name_servers := []interface{}{
   489  		"ns-1334.awsdns-38.org",
   490  		"ns-1680.awsdns-18.co.uk",
   491  		"ns-498.awsdns-62.com",
   492  		"ns-601.awsdns-11.net",
   493  		"ns-000.awsdns-38.org",
   494  		"ns-444.awsdns-18.co.uk",
   495  		"ns-999.awsdns-62.com",
   496  		"ns-666.awsdns-11.net",
   497  	}
   498  
   499  	// More than 1 element
   500  	testInterpolate(t, i, scope, "aws_route53_zone.terra.0.name_servers",
   501  		interfaceToVariableSwallowError(name_servers[:4]))
   502  
   503  	// More than 1 element in both
   504  	testInterpolate(t, i, scope, "aws_route53_zone.terra.*.name_servers",
   505  		interfaceToVariableSwallowError([]interface{}{name_servers[:4], name_servers[4:]}))
   506  
   507  	// Exactly 1 element
   508  	testInterpolate(t, i, scope, "aws_route53_zone.terra.0.listeners",
   509  		interfaceToVariableSwallowError([]interface{}{"red"}))
   510  
   511  	// Exactly 1 element in both
   512  	testInterpolate(t, i, scope, "aws_route53_zone.terra.*.listeners",
   513  		interfaceToVariableSwallowError([]interface{}{[]interface{}{"red"}, []interface{}{"blue"}}))
   514  
   515  	// Zero elements
   516  	testInterpolate(t, i, scope, "aws_route53_zone.terra.0.nothing",
   517  		interfaceToVariableSwallowError([]interface{}{}))
   518  
   519  	// Zero + 1 element
   520  	testInterpolate(t, i, scope, "aws_route53_zone.terra.*.special",
   521  		interfaceToVariableSwallowError([]interface{}{[]interface{}{"extra"}}))
   522  
   523  	// Maps still need to work
   524  	testInterpolate(t, i, scope, "aws_route53_zone.terra.0.tags.Name", ast.Variable{
   525  		Value: "reindeer",
   526  		Type:  ast.TypeString,
   527  	})
   528  
   529  	// Maps still need to work in both
   530  	testInterpolate(t, i, scope, "aws_route53_zone.terra.*.tags.Name",
   531  		interfaceToVariableSwallowError([]interface{}{"reindeer", "white-hart"}))
   532  }
   533  
   534  func TestInterpolator_resourceMultiAttributesComputed(t *testing.T) {
   535  	lock := new(sync.RWMutex)
   536  	// The state would never be written with an UnknownVariableValue in it, but
   537  	// it can/does exist that way in memory during the plan phase.
   538  	state := &State{
   539  		Modules: []*ModuleState{
   540  			&ModuleState{
   541  				Path: rootModulePath,
   542  				Resources: map[string]*ResourceState{
   543  					"aws_route53_zone.yada": &ResourceState{
   544  						Type: "aws_route53_zone",
   545  						Primary: &InstanceState{
   546  							ID: "z-abc123",
   547  							Attributes: map[string]string{
   548  								"name_servers.#": config.UnknownVariableValue,
   549  							},
   550  						},
   551  					},
   552  				},
   553  			},
   554  		},
   555  	}
   556  	i := &Interpolater{
   557  		Module:    testModule(t, "interpolate-multi-vars"),
   558  		StateLock: lock,
   559  		State:     state,
   560  	}
   561  
   562  	scope := &InterpolationScope{
   563  		Path: rootModulePath,
   564  	}
   565  
   566  	testInterpolate(t, i, scope, "aws_route53_zone.yada.name_servers", ast.Variable{
   567  		Value: config.UnknownVariableValue,
   568  		Type:  ast.TypeUnknown,
   569  	})
   570  }
   571  
   572  func TestInterpolator_resourceAttributeComputed(t *testing.T) {
   573  	lock := new(sync.RWMutex)
   574  	// The state would never be written with an UnknownVariableValue in it, but
   575  	// it can/does exist that way in memory during the plan phase.
   576  	state := &State{
   577  		Modules: []*ModuleState{
   578  			&ModuleState{
   579  				Path: rootModulePath,
   580  				Resources: map[string]*ResourceState{
   581  					"aws_route53_zone.yada": &ResourceState{
   582  						Type: "aws_route53_zone",
   583  						Primary: &InstanceState{
   584  							ID: "z-abc123",
   585  							Attributes: map[string]string{
   586  								"id": config.UnknownVariableValue,
   587  							},
   588  						},
   589  					},
   590  				},
   591  			},
   592  		},
   593  	}
   594  	i := &Interpolater{
   595  		Module:    testModule(t, "interpolate-multi-vars"),
   596  		StateLock: lock,
   597  		State:     state,
   598  	}
   599  
   600  	scope := &InterpolationScope{
   601  		Path: rootModulePath,
   602  	}
   603  
   604  	testInterpolate(t, i, scope, "aws_route53_zone.yada.id", ast.Variable{
   605  		Value: config.UnknownVariableValue,
   606  		Type:  ast.TypeUnknown,
   607  	})
   608  }
   609  
   610  func TestInterpolater_selfVarWithoutResource(t *testing.T) {
   611  	i := &Interpolater{}
   612  
   613  	scope := &InterpolationScope{
   614  		Path: rootModulePath,
   615  	}
   616  
   617  	v, err := config.NewInterpolatedVariable("self.name")
   618  	if err != nil {
   619  		t.Fatalf("err: %s", err)
   620  	}
   621  
   622  	_, err = i.Values(scope, map[string]config.InterpolatedVariable{"foo": v})
   623  	if err == nil {
   624  		t.Fatalf("expected err, got none")
   625  	}
   626  }
   627  
   628  func getInterpolaterFixture(t *testing.T) *Interpolater {
   629  	lock := new(sync.RWMutex)
   630  	state := &State{
   631  		Modules: []*ModuleState{
   632  			&ModuleState{
   633  				Path: rootModulePath,
   634  				Resources: map[string]*ResourceState{
   635  					"aws_route53_zone.terra.0": &ResourceState{
   636  						Type:         "aws_route53_zone",
   637  						Dependencies: []string{},
   638  						Primary: &InstanceState{
   639  							ID: "AAABBBCCCDDDEEE",
   640  							Attributes: map[string]string{
   641  								"name_servers.#": "4",
   642  								"name_servers.0": "ns-1334.awsdns-38.org",
   643  								"name_servers.1": "ns-1680.awsdns-18.co.uk",
   644  								"name_servers.2": "ns-498.awsdns-62.com",
   645  								"name_servers.3": "ns-601.awsdns-11.net",
   646  								"listeners.#":    "1",
   647  								"listeners.0":    "red",
   648  								"tags.%":         "1",
   649  								"tags.Name":      "reindeer",
   650  								"nothing.#":      "0",
   651  							},
   652  						},
   653  					},
   654  					"aws_route53_zone.terra.1": &ResourceState{
   655  						Type:         "aws_route53_zone",
   656  						Dependencies: []string{},
   657  						Primary: &InstanceState{
   658  							ID: "EEEFFFGGGHHHIII",
   659  							Attributes: map[string]string{
   660  								"name_servers.#": "4",
   661  								"name_servers.0": "ns-000.awsdns-38.org",
   662  								"name_servers.1": "ns-444.awsdns-18.co.uk",
   663  								"name_servers.2": "ns-999.awsdns-62.com",
   664  								"name_servers.3": "ns-666.awsdns-11.net",
   665  								"listeners.#":    "1",
   666  								"listeners.0":    "blue",
   667  								"special.#":      "1",
   668  								"special.0":      "extra",
   669  								"tags.%":         "1",
   670  								"tags.Name":      "white-hart",
   671  								"nothing.#":      "0",
   672  							},
   673  						},
   674  					},
   675  				},
   676  			},
   677  		},
   678  	}
   679  
   680  	return &Interpolater{
   681  		Module:    testModule(t, "interpolate-multi-vars"),
   682  		StateLock: lock,
   683  		State:     state,
   684  	}
   685  }
   686  
   687  func testInterpolate(
   688  	t *testing.T, i *Interpolater,
   689  	scope *InterpolationScope,
   690  	n string, expectedVar ast.Variable) {
   691  	v, err := config.NewInterpolatedVariable(n)
   692  	if err != nil {
   693  		t.Fatalf("err: %s", err)
   694  	}
   695  
   696  	actual, err := i.Values(scope, map[string]config.InterpolatedVariable{
   697  		"foo": v,
   698  	})
   699  	if err != nil {
   700  		t.Fatalf("err: %s", err)
   701  	}
   702  
   703  	expected := map[string]ast.Variable{
   704  		"foo": expectedVar,
   705  	}
   706  	if !reflect.DeepEqual(actual, expected) {
   707  		spew.Config.DisableMethods = true
   708  		t.Fatalf("%q:\n\n  actual: %#v\nexpected: %#v\n\n%s\n\n%s\n\n", n, actual, expected,
   709  			spew.Sdump(actual), spew.Sdump(expected))
   710  	}
   711  }
   712  
   713  func testInterpolateErr(
   714  	t *testing.T, i *Interpolater,
   715  	scope *InterpolationScope,
   716  	n string) {
   717  	v, err := config.NewInterpolatedVariable(n)
   718  	if err != nil {
   719  		t.Fatalf("err: %s", err)
   720  	}
   721  
   722  	_, err = i.Values(scope, map[string]config.InterpolatedVariable{
   723  		"foo": v,
   724  	})
   725  	if err == nil {
   726  		t.Fatalf("%q: succeeded, but wanted error", n)
   727  	}
   728  }