github.com/minamijoyo/terraform@v0.7.8-0.20161029001309-18b3736ba44b/helper/schema/schema_test.go (about)

     1  package schema
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"os"
     7  	"reflect"
     8  	"strconv"
     9  	"testing"
    10  
    11  	"github.com/hashicorp/hil"
    12  	"github.com/hashicorp/hil/ast"
    13  	"github.com/hashicorp/terraform/config"
    14  	"github.com/hashicorp/terraform/helper/hashcode"
    15  	"github.com/hashicorp/terraform/terraform"
    16  )
    17  
    18  func TestEnvDefaultFunc(t *testing.T) {
    19  	key := "TF_TEST_ENV_DEFAULT_FUNC"
    20  	defer os.Unsetenv(key)
    21  
    22  	f := EnvDefaultFunc(key, "42")
    23  	if err := os.Setenv(key, "foo"); err != nil {
    24  		t.Fatalf("err: %s", err)
    25  	}
    26  
    27  	actual, err := f()
    28  	if err != nil {
    29  		t.Fatalf("err: %s", err)
    30  	}
    31  	if actual != "foo" {
    32  		t.Fatalf("bad: %#v", actual)
    33  	}
    34  
    35  	if err := os.Unsetenv(key); err != nil {
    36  		t.Fatalf("err: %s", err)
    37  	}
    38  
    39  	actual, err = f()
    40  	if err != nil {
    41  		t.Fatalf("err: %s", err)
    42  	}
    43  	if actual != "42" {
    44  		t.Fatalf("bad: %#v", actual)
    45  	}
    46  }
    47  
    48  func TestMultiEnvDefaultFunc(t *testing.T) {
    49  	keys := []string{
    50  		"TF_TEST_MULTI_ENV_DEFAULT_FUNC1",
    51  		"TF_TEST_MULTI_ENV_DEFAULT_FUNC2",
    52  	}
    53  	defer func() {
    54  		for _, k := range keys {
    55  			os.Unsetenv(k)
    56  		}
    57  	}()
    58  
    59  	// Test that the first key is returned first
    60  	f := MultiEnvDefaultFunc(keys, "42")
    61  	if err := os.Setenv(keys[0], "foo"); err != nil {
    62  		t.Fatalf("err: %s", err)
    63  	}
    64  
    65  	actual, err := f()
    66  	if err != nil {
    67  		t.Fatalf("err: %s", err)
    68  	}
    69  	if actual != "foo" {
    70  		t.Fatalf("bad: %#v", actual)
    71  	}
    72  
    73  	if err := os.Unsetenv(keys[0]); err != nil {
    74  		t.Fatalf("err: %s", err)
    75  	}
    76  
    77  	// Test that the second key is returned if the first one is empty
    78  	f = MultiEnvDefaultFunc(keys, "42")
    79  	if err := os.Setenv(keys[1], "foo"); err != nil {
    80  		t.Fatalf("err: %s", err)
    81  	}
    82  
    83  	actual, err = f()
    84  	if err != nil {
    85  		t.Fatalf("err: %s", err)
    86  	}
    87  	if actual != "foo" {
    88  		t.Fatalf("bad: %#v", actual)
    89  	}
    90  
    91  	if err := os.Unsetenv(keys[1]); err != nil {
    92  		t.Fatalf("err: %s", err)
    93  	}
    94  
    95  	// Test that the default value is returned when no keys are set
    96  	actual, err = f()
    97  	if err != nil {
    98  		t.Fatalf("err: %s", err)
    99  	}
   100  	if actual != "42" {
   101  		t.Fatalf("bad: %#v", actual)
   102  	}
   103  }
   104  
   105  func TestValueType_Zero(t *testing.T) {
   106  	cases := []struct {
   107  		Type  ValueType
   108  		Value interface{}
   109  	}{
   110  		{TypeBool, false},
   111  		{TypeInt, 0},
   112  		{TypeFloat, 0.0},
   113  		{TypeString, ""},
   114  		{TypeList, []interface{}{}},
   115  		{TypeMap, map[string]interface{}{}},
   116  		{TypeSet, new(Set)},
   117  	}
   118  
   119  	for i, tc := range cases {
   120  		actual := tc.Type.Zero()
   121  		if !reflect.DeepEqual(actual, tc.Value) {
   122  			t.Fatalf("%d: %#v != %#v", i, actual, tc.Value)
   123  		}
   124  	}
   125  }
   126  
   127  func interfaceToVariableSwallowError(input interface{}) ast.Variable {
   128  	variable, _ := hil.InterfaceToVariable(input)
   129  	return variable
   130  }
   131  
   132  func TestSchemaMap_Diff(t *testing.T) {
   133  	cases := map[string]struct {
   134  		Schema          map[string]*Schema
   135  		State           *terraform.InstanceState
   136  		Config          map[string]interface{}
   137  		ConfigVariables map[string]ast.Variable
   138  		Diff            *terraform.InstanceDiff
   139  		Err             bool
   140  	}{
   141  		"#0": {
   142  			Schema: map[string]*Schema{
   143  				"availability_zone": &Schema{
   144  					Type:     TypeString,
   145  					Optional: true,
   146  					Computed: true,
   147  					ForceNew: true,
   148  				},
   149  			},
   150  
   151  			State: nil,
   152  
   153  			Config: map[string]interface{}{
   154  				"availability_zone": "foo",
   155  			},
   156  
   157  			Diff: &terraform.InstanceDiff{
   158  				Attributes: map[string]*terraform.ResourceAttrDiff{
   159  					"availability_zone": &terraform.ResourceAttrDiff{
   160  						Old:         "",
   161  						New:         "foo",
   162  						RequiresNew: true,
   163  					},
   164  				},
   165  			},
   166  
   167  			Err: false,
   168  		},
   169  
   170  		"#1": {
   171  			Schema: map[string]*Schema{
   172  				"availability_zone": &Schema{
   173  					Type:     TypeString,
   174  					Optional: true,
   175  					Computed: true,
   176  					ForceNew: true,
   177  				},
   178  			},
   179  
   180  			State: nil,
   181  
   182  			Config: map[string]interface{}{},
   183  
   184  			Diff: &terraform.InstanceDiff{
   185  				Attributes: map[string]*terraform.ResourceAttrDiff{
   186  					"availability_zone": &terraform.ResourceAttrDiff{
   187  						Old:         "",
   188  						NewComputed: true,
   189  						RequiresNew: true,
   190  					},
   191  				},
   192  			},
   193  
   194  			Err: false,
   195  		},
   196  
   197  		"#2": {
   198  			Schema: map[string]*Schema{
   199  				"availability_zone": &Schema{
   200  					Type:     TypeString,
   201  					Optional: true,
   202  					Computed: true,
   203  					ForceNew: true,
   204  				},
   205  			},
   206  
   207  			State: &terraform.InstanceState{
   208  				ID: "foo",
   209  			},
   210  
   211  			Config: map[string]interface{}{},
   212  
   213  			Diff: nil,
   214  
   215  			Err: false,
   216  		},
   217  
   218  		"#3 Computed, but set in config": {
   219  			Schema: map[string]*Schema{
   220  				"availability_zone": &Schema{
   221  					Type:     TypeString,
   222  					Optional: true,
   223  					Computed: true,
   224  				},
   225  			},
   226  
   227  			State: &terraform.InstanceState{
   228  				Attributes: map[string]string{
   229  					"availability_zone": "foo",
   230  				},
   231  			},
   232  
   233  			Config: map[string]interface{}{
   234  				"availability_zone": "bar",
   235  			},
   236  
   237  			Diff: &terraform.InstanceDiff{
   238  				Attributes: map[string]*terraform.ResourceAttrDiff{
   239  					"availability_zone": &terraform.ResourceAttrDiff{
   240  						Old: "foo",
   241  						New: "bar",
   242  					},
   243  				},
   244  			},
   245  
   246  			Err: false,
   247  		},
   248  
   249  		"#4 Default": {
   250  			Schema: map[string]*Schema{
   251  				"availability_zone": &Schema{
   252  					Type:     TypeString,
   253  					Optional: true,
   254  					Default:  "foo",
   255  				},
   256  			},
   257  
   258  			State: nil,
   259  
   260  			Config: nil,
   261  
   262  			Diff: &terraform.InstanceDiff{
   263  				Attributes: map[string]*terraform.ResourceAttrDiff{
   264  					"availability_zone": &terraform.ResourceAttrDiff{
   265  						Old: "",
   266  						New: "foo",
   267  					},
   268  				},
   269  			},
   270  
   271  			Err: false,
   272  		},
   273  
   274  		"#5 DefaultFunc, value": {
   275  			Schema: map[string]*Schema{
   276  				"availability_zone": &Schema{
   277  					Type:     TypeString,
   278  					Optional: true,
   279  					DefaultFunc: func() (interface{}, error) {
   280  						return "foo", nil
   281  					},
   282  				},
   283  			},
   284  
   285  			State: nil,
   286  
   287  			Config: nil,
   288  
   289  			Diff: &terraform.InstanceDiff{
   290  				Attributes: map[string]*terraform.ResourceAttrDiff{
   291  					"availability_zone": &terraform.ResourceAttrDiff{
   292  						Old: "",
   293  						New: "foo",
   294  					},
   295  				},
   296  			},
   297  
   298  			Err: false,
   299  		},
   300  
   301  		"#6 DefaultFunc, configuration set": {
   302  			Schema: map[string]*Schema{
   303  				"availability_zone": &Schema{
   304  					Type:     TypeString,
   305  					Optional: true,
   306  					DefaultFunc: func() (interface{}, error) {
   307  						return "foo", nil
   308  					},
   309  				},
   310  			},
   311  
   312  			State: nil,
   313  
   314  			Config: map[string]interface{}{
   315  				"availability_zone": "bar",
   316  			},
   317  
   318  			Diff: &terraform.InstanceDiff{
   319  				Attributes: map[string]*terraform.ResourceAttrDiff{
   320  					"availability_zone": &terraform.ResourceAttrDiff{
   321  						Old: "",
   322  						New: "bar",
   323  					},
   324  				},
   325  			},
   326  
   327  			Err: false,
   328  		},
   329  
   330  		"String with StateFunc": {
   331  			Schema: map[string]*Schema{
   332  				"availability_zone": &Schema{
   333  					Type:     TypeString,
   334  					Optional: true,
   335  					Computed: true,
   336  					StateFunc: func(a interface{}) string {
   337  						return a.(string) + "!"
   338  					},
   339  				},
   340  			},
   341  
   342  			State: nil,
   343  
   344  			Config: map[string]interface{}{
   345  				"availability_zone": "foo",
   346  			},
   347  
   348  			Diff: &terraform.InstanceDiff{
   349  				Attributes: map[string]*terraform.ResourceAttrDiff{
   350  					"availability_zone": &terraform.ResourceAttrDiff{
   351  						Old:      "",
   352  						New:      "foo!",
   353  						NewExtra: "foo",
   354  					},
   355  				},
   356  			},
   357  
   358  			Err: false,
   359  		},
   360  
   361  		"StateFunc not called with nil value": {
   362  			Schema: map[string]*Schema{
   363  				"availability_zone": &Schema{
   364  					Type:     TypeString,
   365  					Optional: true,
   366  					Computed: true,
   367  					StateFunc: func(a interface{}) string {
   368  						t.Fatalf("should not get here!")
   369  						return ""
   370  					},
   371  				},
   372  			},
   373  
   374  			State: nil,
   375  
   376  			Config: map[string]interface{}{},
   377  
   378  			Diff: &terraform.InstanceDiff{
   379  				Attributes: map[string]*terraform.ResourceAttrDiff{
   380  					"availability_zone": &terraform.ResourceAttrDiff{
   381  						Old:         "",
   382  						New:         "",
   383  						NewComputed: true,
   384  					},
   385  				},
   386  			},
   387  
   388  			Err: false,
   389  		},
   390  
   391  		"#8 Variable (just checking)": {
   392  			Schema: map[string]*Schema{
   393  				"availability_zone": &Schema{
   394  					Type:     TypeString,
   395  					Optional: true,
   396  				},
   397  			},
   398  
   399  			State: nil,
   400  
   401  			Config: map[string]interface{}{
   402  				"availability_zone": "${var.foo}",
   403  			},
   404  
   405  			ConfigVariables: map[string]ast.Variable{
   406  				"var.foo": interfaceToVariableSwallowError("bar"),
   407  			},
   408  
   409  			Diff: &terraform.InstanceDiff{
   410  				Attributes: map[string]*terraform.ResourceAttrDiff{
   411  					"availability_zone": &terraform.ResourceAttrDiff{
   412  						Old: "",
   413  						New: "bar",
   414  					},
   415  				},
   416  			},
   417  
   418  			Err: false,
   419  		},
   420  
   421  		"#9 Variable computed": {
   422  			Schema: map[string]*Schema{
   423  				"availability_zone": &Schema{
   424  					Type:     TypeString,
   425  					Optional: true,
   426  				},
   427  			},
   428  
   429  			State: nil,
   430  
   431  			Config: map[string]interface{}{
   432  				"availability_zone": "${var.foo}",
   433  			},
   434  
   435  			ConfigVariables: map[string]ast.Variable{
   436  				"var.foo": interfaceToVariableSwallowError(config.UnknownVariableValue),
   437  			},
   438  
   439  			Diff: &terraform.InstanceDiff{
   440  				Attributes: map[string]*terraform.ResourceAttrDiff{
   441  					"availability_zone": &terraform.ResourceAttrDiff{
   442  						Old:         "",
   443  						New:         "${var.foo}",
   444  						NewComputed: true,
   445  					},
   446  				},
   447  			},
   448  
   449  			Err: false,
   450  		},
   451  
   452  		"#10 Int decode": {
   453  			Schema: map[string]*Schema{
   454  				"port": &Schema{
   455  					Type:     TypeInt,
   456  					Optional: true,
   457  					Computed: true,
   458  					ForceNew: true,
   459  				},
   460  			},
   461  
   462  			State: nil,
   463  
   464  			Config: map[string]interface{}{
   465  				"port": 27,
   466  			},
   467  
   468  			Diff: &terraform.InstanceDiff{
   469  				Attributes: map[string]*terraform.ResourceAttrDiff{
   470  					"port": &terraform.ResourceAttrDiff{
   471  						Old:         "",
   472  						New:         "27",
   473  						RequiresNew: true,
   474  					},
   475  				},
   476  			},
   477  
   478  			Err: false,
   479  		},
   480  
   481  		"#11 bool decode": {
   482  			Schema: map[string]*Schema{
   483  				"port": &Schema{
   484  					Type:     TypeBool,
   485  					Optional: true,
   486  					Computed: true,
   487  					ForceNew: true,
   488  				},
   489  			},
   490  
   491  			State: nil,
   492  
   493  			Config: map[string]interface{}{
   494  				"port": false,
   495  			},
   496  
   497  			Diff: &terraform.InstanceDiff{
   498  				Attributes: map[string]*terraform.ResourceAttrDiff{
   499  					"port": &terraform.ResourceAttrDiff{
   500  						Old:         "",
   501  						New:         "false",
   502  						RequiresNew: true,
   503  					},
   504  				},
   505  			},
   506  
   507  			Err: false,
   508  		},
   509  
   510  		"#12 Bool": {
   511  			Schema: map[string]*Schema{
   512  				"delete": &Schema{
   513  					Type:     TypeBool,
   514  					Optional: true,
   515  					Default:  false,
   516  				},
   517  			},
   518  
   519  			State: &terraform.InstanceState{
   520  				Attributes: map[string]string{
   521  					"delete": "false",
   522  				},
   523  			},
   524  
   525  			Config: nil,
   526  
   527  			Diff: nil,
   528  
   529  			Err: false,
   530  		},
   531  
   532  		"#13 List decode": {
   533  			Schema: map[string]*Schema{
   534  				"ports": &Schema{
   535  					Type:     TypeList,
   536  					Required: true,
   537  					Elem:     &Schema{Type: TypeInt},
   538  				},
   539  			},
   540  
   541  			State: nil,
   542  
   543  			Config: map[string]interface{}{
   544  				"ports": []interface{}{1, 2, 5},
   545  			},
   546  
   547  			Diff: &terraform.InstanceDiff{
   548  				Attributes: map[string]*terraform.ResourceAttrDiff{
   549  					"ports.#": &terraform.ResourceAttrDiff{
   550  						Old: "0",
   551  						New: "3",
   552  					},
   553  					"ports.0": &terraform.ResourceAttrDiff{
   554  						Old: "",
   555  						New: "1",
   556  					},
   557  					"ports.1": &terraform.ResourceAttrDiff{
   558  						Old: "",
   559  						New: "2",
   560  					},
   561  					"ports.2": &terraform.ResourceAttrDiff{
   562  						Old: "",
   563  						New: "5",
   564  					},
   565  				},
   566  			},
   567  
   568  			Err: false,
   569  		},
   570  
   571  		"#14": {
   572  			Schema: map[string]*Schema{
   573  				"ports": &Schema{
   574  					Type:     TypeList,
   575  					Required: true,
   576  					Elem:     &Schema{Type: TypeInt},
   577  				},
   578  			},
   579  
   580  			State: nil,
   581  
   582  			Config: map[string]interface{}{
   583  				"ports": []interface{}{1, "${var.foo}"},
   584  			},
   585  
   586  			ConfigVariables: map[string]ast.Variable{
   587  				"var.foo": interfaceToVariableSwallowError([]interface{}{"2", "5"}),
   588  			},
   589  
   590  			Diff: &terraform.InstanceDiff{
   591  				Attributes: map[string]*terraform.ResourceAttrDiff{
   592  					"ports.#": &terraform.ResourceAttrDiff{
   593  						Old: "0",
   594  						New: "3",
   595  					},
   596  					"ports.0": &terraform.ResourceAttrDiff{
   597  						Old: "",
   598  						New: "1",
   599  					},
   600  					"ports.1": &terraform.ResourceAttrDiff{
   601  						Old: "",
   602  						New: "2",
   603  					},
   604  					"ports.2": &terraform.ResourceAttrDiff{
   605  						Old: "",
   606  						New: "5",
   607  					},
   608  				},
   609  			},
   610  
   611  			Err: false,
   612  		},
   613  
   614  		"#15": {
   615  			Schema: map[string]*Schema{
   616  				"ports": &Schema{
   617  					Type:     TypeList,
   618  					Required: true,
   619  					Elem:     &Schema{Type: TypeInt},
   620  				},
   621  			},
   622  
   623  			State: nil,
   624  
   625  			Config: map[string]interface{}{
   626  				"ports": []interface{}{1, "${var.foo}"},
   627  			},
   628  
   629  			ConfigVariables: map[string]ast.Variable{
   630  				"var.foo": interfaceToVariableSwallowError([]interface{}{
   631  					config.UnknownVariableValue, "5"}),
   632  			},
   633  
   634  			Diff: &terraform.InstanceDiff{
   635  				Attributes: map[string]*terraform.ResourceAttrDiff{
   636  					"ports.#": &terraform.ResourceAttrDiff{
   637  						Old:         "0",
   638  						New:         "",
   639  						NewComputed: true,
   640  					},
   641  				},
   642  			},
   643  
   644  			Err: false,
   645  		},
   646  
   647  		"#16": {
   648  			Schema: map[string]*Schema{
   649  				"ports": &Schema{
   650  					Type:     TypeList,
   651  					Required: true,
   652  					Elem:     &Schema{Type: TypeInt},
   653  				},
   654  			},
   655  
   656  			State: &terraform.InstanceState{
   657  				Attributes: map[string]string{
   658  					"ports.#": "3",
   659  					"ports.0": "1",
   660  					"ports.1": "2",
   661  					"ports.2": "5",
   662  				},
   663  			},
   664  
   665  			Config: map[string]interface{}{
   666  				"ports": []interface{}{1, 2, 5},
   667  			},
   668  
   669  			Diff: nil,
   670  
   671  			Err: false,
   672  		},
   673  
   674  		"#17": {
   675  			Schema: map[string]*Schema{
   676  				"ports": &Schema{
   677  					Type:     TypeList,
   678  					Required: true,
   679  					Elem:     &Schema{Type: TypeInt},
   680  				},
   681  			},
   682  
   683  			State: &terraform.InstanceState{
   684  				Attributes: map[string]string{
   685  					"ports.#": "2",
   686  					"ports.0": "1",
   687  					"ports.1": "2",
   688  				},
   689  			},
   690  
   691  			Config: map[string]interface{}{
   692  				"ports": []interface{}{1, 2, 5},
   693  			},
   694  
   695  			Diff: &terraform.InstanceDiff{
   696  				Attributes: map[string]*terraform.ResourceAttrDiff{
   697  					"ports.#": &terraform.ResourceAttrDiff{
   698  						Old: "2",
   699  						New: "3",
   700  					},
   701  					"ports.2": &terraform.ResourceAttrDiff{
   702  						Old: "",
   703  						New: "5",
   704  					},
   705  				},
   706  			},
   707  
   708  			Err: false,
   709  		},
   710  
   711  		"#18": {
   712  			Schema: map[string]*Schema{
   713  				"ports": &Schema{
   714  					Type:     TypeList,
   715  					Required: true,
   716  					Elem:     &Schema{Type: TypeInt},
   717  					ForceNew: true,
   718  				},
   719  			},
   720  
   721  			State: nil,
   722  
   723  			Config: map[string]interface{}{
   724  				"ports": []interface{}{1, 2, 5},
   725  			},
   726  
   727  			Diff: &terraform.InstanceDiff{
   728  				Attributes: map[string]*terraform.ResourceAttrDiff{
   729  					"ports.#": &terraform.ResourceAttrDiff{
   730  						Old:         "0",
   731  						New:         "3",
   732  						RequiresNew: true,
   733  					},
   734  					"ports.0": &terraform.ResourceAttrDiff{
   735  						Old:         "",
   736  						New:         "1",
   737  						RequiresNew: true,
   738  					},
   739  					"ports.1": &terraform.ResourceAttrDiff{
   740  						Old:         "",
   741  						New:         "2",
   742  						RequiresNew: true,
   743  					},
   744  					"ports.2": &terraform.ResourceAttrDiff{
   745  						Old:         "",
   746  						New:         "5",
   747  						RequiresNew: true,
   748  					},
   749  				},
   750  			},
   751  
   752  			Err: false,
   753  		},
   754  
   755  		"#19": {
   756  			Schema: map[string]*Schema{
   757  				"ports": &Schema{
   758  					Type:     TypeList,
   759  					Optional: true,
   760  					Computed: true,
   761  					Elem:     &Schema{Type: TypeInt},
   762  				},
   763  			},
   764  
   765  			State: nil,
   766  
   767  			Config: map[string]interface{}{},
   768  
   769  			Diff: &terraform.InstanceDiff{
   770  				Attributes: map[string]*terraform.ResourceAttrDiff{
   771  					"ports.#": &terraform.ResourceAttrDiff{
   772  						Old:         "",
   773  						NewComputed: true,
   774  					},
   775  				},
   776  			},
   777  
   778  			Err: false,
   779  		},
   780  
   781  		"#20 Set": {
   782  			Schema: map[string]*Schema{
   783  				"ports": &Schema{
   784  					Type:     TypeSet,
   785  					Required: true,
   786  					Elem:     &Schema{Type: TypeInt},
   787  					Set: func(a interface{}) int {
   788  						return a.(int)
   789  					},
   790  				},
   791  			},
   792  
   793  			State: nil,
   794  
   795  			Config: map[string]interface{}{
   796  				"ports": []interface{}{5, 2, 1},
   797  			},
   798  
   799  			Diff: &terraform.InstanceDiff{
   800  				Attributes: map[string]*terraform.ResourceAttrDiff{
   801  					"ports.#": &terraform.ResourceAttrDiff{
   802  						Old: "0",
   803  						New: "3",
   804  					},
   805  					"ports.1": &terraform.ResourceAttrDiff{
   806  						Old: "",
   807  						New: "1",
   808  					},
   809  					"ports.2": &terraform.ResourceAttrDiff{
   810  						Old: "",
   811  						New: "2",
   812  					},
   813  					"ports.5": &terraform.ResourceAttrDiff{
   814  						Old: "",
   815  						New: "5",
   816  					},
   817  				},
   818  			},
   819  
   820  			Err: false,
   821  		},
   822  
   823  		"#21 Set": {
   824  			Schema: map[string]*Schema{
   825  				"ports": &Schema{
   826  					Type:     TypeSet,
   827  					Computed: true,
   828  					Required: true,
   829  					Elem:     &Schema{Type: TypeInt},
   830  					Set: func(a interface{}) int {
   831  						return a.(int)
   832  					},
   833  				},
   834  			},
   835  
   836  			State: &terraform.InstanceState{
   837  				Attributes: map[string]string{
   838  					"ports.#": "0",
   839  				},
   840  			},
   841  
   842  			Config: nil,
   843  
   844  			Diff: nil,
   845  
   846  			Err: false,
   847  		},
   848  
   849  		"#22 Set": {
   850  			Schema: map[string]*Schema{
   851  				"ports": &Schema{
   852  					Type:     TypeSet,
   853  					Optional: true,
   854  					Computed: true,
   855  					Elem:     &Schema{Type: TypeInt},
   856  					Set: func(a interface{}) int {
   857  						return a.(int)
   858  					},
   859  				},
   860  			},
   861  
   862  			State: nil,
   863  
   864  			Config: nil,
   865  
   866  			Diff: &terraform.InstanceDiff{
   867  				Attributes: map[string]*terraform.ResourceAttrDiff{
   868  					"ports.#": &terraform.ResourceAttrDiff{
   869  						Old:         "",
   870  						NewComputed: true,
   871  					},
   872  				},
   873  			},
   874  
   875  			Err: false,
   876  		},
   877  
   878  		"#23 Set": {
   879  			Schema: map[string]*Schema{
   880  				"ports": &Schema{
   881  					Type:     TypeSet,
   882  					Required: true,
   883  					Elem:     &Schema{Type: TypeInt},
   884  					Set: func(a interface{}) int {
   885  						return a.(int)
   886  					},
   887  				},
   888  			},
   889  
   890  			State: nil,
   891  
   892  			Config: map[string]interface{}{
   893  				"ports": []interface{}{"${var.foo}", 1},
   894  			},
   895  
   896  			ConfigVariables: map[string]ast.Variable{
   897  				"var.foo": interfaceToVariableSwallowError([]interface{}{"2", "5"}),
   898  			},
   899  
   900  			Diff: &terraform.InstanceDiff{
   901  				Attributes: map[string]*terraform.ResourceAttrDiff{
   902  					"ports.#": &terraform.ResourceAttrDiff{
   903  						Old: "0",
   904  						New: "3",
   905  					},
   906  					"ports.1": &terraform.ResourceAttrDiff{
   907  						Old: "",
   908  						New: "1",
   909  					},
   910  					"ports.2": &terraform.ResourceAttrDiff{
   911  						Old: "",
   912  						New: "2",
   913  					},
   914  					"ports.5": &terraform.ResourceAttrDiff{
   915  						Old: "",
   916  						New: "5",
   917  					},
   918  				},
   919  			},
   920  
   921  			Err: false,
   922  		},
   923  
   924  		"#24 Set": {
   925  			Schema: map[string]*Schema{
   926  				"ports": &Schema{
   927  					Type:     TypeSet,
   928  					Required: true,
   929  					Elem:     &Schema{Type: TypeInt},
   930  					Set: func(a interface{}) int {
   931  						return a.(int)
   932  					},
   933  				},
   934  			},
   935  
   936  			State: nil,
   937  
   938  			Config: map[string]interface{}{
   939  				"ports": []interface{}{1, "${var.foo}"},
   940  			},
   941  
   942  			ConfigVariables: map[string]ast.Variable{
   943  				"var.foo": interfaceToVariableSwallowError([]interface{}{
   944  					config.UnknownVariableValue, "5"}),
   945  			},
   946  
   947  			Diff: &terraform.InstanceDiff{
   948  				Attributes: map[string]*terraform.ResourceAttrDiff{
   949  					"ports.#": &terraform.ResourceAttrDiff{
   950  						Old:         "",
   951  						New:         "",
   952  						NewComputed: true,
   953  					},
   954  				},
   955  			},
   956  
   957  			Err: false,
   958  		},
   959  
   960  		"#25 Set": {
   961  			Schema: map[string]*Schema{
   962  				"ports": &Schema{
   963  					Type:     TypeSet,
   964  					Required: true,
   965  					Elem:     &Schema{Type: TypeInt},
   966  					Set: func(a interface{}) int {
   967  						return a.(int)
   968  					},
   969  				},
   970  			},
   971  
   972  			State: &terraform.InstanceState{
   973  				Attributes: map[string]string{
   974  					"ports.#": "2",
   975  					"ports.1": "1",
   976  					"ports.2": "2",
   977  				},
   978  			},
   979  
   980  			Config: map[string]interface{}{
   981  				"ports": []interface{}{5, 2, 1},
   982  			},
   983  
   984  			Diff: &terraform.InstanceDiff{
   985  				Attributes: map[string]*terraform.ResourceAttrDiff{
   986  					"ports.#": &terraform.ResourceAttrDiff{
   987  						Old: "2",
   988  						New: "3",
   989  					},
   990  					"ports.1": &terraform.ResourceAttrDiff{
   991  						Old: "1",
   992  						New: "1",
   993  					},
   994  					"ports.2": &terraform.ResourceAttrDiff{
   995  						Old: "2",
   996  						New: "2",
   997  					},
   998  					"ports.5": &terraform.ResourceAttrDiff{
   999  						Old: "",
  1000  						New: "5",
  1001  					},
  1002  				},
  1003  			},
  1004  
  1005  			Err: false,
  1006  		},
  1007  
  1008  		"#26 Set": {
  1009  			Schema: map[string]*Schema{
  1010  				"ports": &Schema{
  1011  					Type:     TypeSet,
  1012  					Required: true,
  1013  					Elem:     &Schema{Type: TypeInt},
  1014  					Set: func(a interface{}) int {
  1015  						return a.(int)
  1016  					},
  1017  				},
  1018  			},
  1019  
  1020  			State: &terraform.InstanceState{
  1021  				Attributes: map[string]string{
  1022  					"ports.#": "2",
  1023  					"ports.1": "1",
  1024  					"ports.2": "2",
  1025  				},
  1026  			},
  1027  
  1028  			Config: map[string]interface{}{},
  1029  
  1030  			Diff: &terraform.InstanceDiff{
  1031  				Attributes: map[string]*terraform.ResourceAttrDiff{
  1032  					"ports.#": &terraform.ResourceAttrDiff{
  1033  						Old: "2",
  1034  						New: "0",
  1035  					},
  1036  					"ports.1": &terraform.ResourceAttrDiff{
  1037  						Old:        "1",
  1038  						New:        "0",
  1039  						NewRemoved: true,
  1040  					},
  1041  					"ports.2": &terraform.ResourceAttrDiff{
  1042  						Old:        "2",
  1043  						New:        "0",
  1044  						NewRemoved: true,
  1045  					},
  1046  				},
  1047  			},
  1048  
  1049  			Err: false,
  1050  		},
  1051  
  1052  		"#27 Set": {
  1053  			Schema: map[string]*Schema{
  1054  				"ports": &Schema{
  1055  					Type:     TypeSet,
  1056  					Optional: true,
  1057  					Computed: true,
  1058  					Elem:     &Schema{Type: TypeInt},
  1059  					Set: func(a interface{}) int {
  1060  						return a.(int)
  1061  					},
  1062  				},
  1063  			},
  1064  
  1065  			State: &terraform.InstanceState{
  1066  				Attributes: map[string]string{
  1067  					"availability_zone": "bar",
  1068  					"ports.#":           "1",
  1069  					"ports.80":          "80",
  1070  				},
  1071  			},
  1072  
  1073  			Config: map[string]interface{}{},
  1074  
  1075  			Diff: nil,
  1076  
  1077  			Err: false,
  1078  		},
  1079  
  1080  		"#28 Set": {
  1081  			Schema: map[string]*Schema{
  1082  				"ingress": &Schema{
  1083  					Type:     TypeSet,
  1084  					Required: true,
  1085  					Elem: &Resource{
  1086  						Schema: map[string]*Schema{
  1087  							"ports": &Schema{
  1088  								Type:     TypeList,
  1089  								Optional: true,
  1090  								Elem:     &Schema{Type: TypeInt},
  1091  							},
  1092  						},
  1093  					},
  1094  					Set: func(v interface{}) int {
  1095  						m := v.(map[string]interface{})
  1096  						ps := m["ports"].([]interface{})
  1097  						result := 0
  1098  						for _, p := range ps {
  1099  							result += p.(int)
  1100  						}
  1101  						return result
  1102  					},
  1103  				},
  1104  			},
  1105  
  1106  			State: &terraform.InstanceState{
  1107  				Attributes: map[string]string{
  1108  					"ingress.#":           "2",
  1109  					"ingress.80.ports.#":  "1",
  1110  					"ingress.80.ports.0":  "80",
  1111  					"ingress.443.ports.#": "1",
  1112  					"ingress.443.ports.0": "443",
  1113  				},
  1114  			},
  1115  
  1116  			Config: map[string]interface{}{
  1117  				"ingress": []map[string]interface{}{
  1118  					map[string]interface{}{
  1119  						"ports": []interface{}{443},
  1120  					},
  1121  					map[string]interface{}{
  1122  						"ports": []interface{}{80},
  1123  					},
  1124  				},
  1125  			},
  1126  
  1127  			Diff: nil,
  1128  
  1129  			Err: false,
  1130  		},
  1131  
  1132  		"#29 List of structure decode": {
  1133  			Schema: map[string]*Schema{
  1134  				"ingress": &Schema{
  1135  					Type:     TypeList,
  1136  					Required: true,
  1137  					Elem: &Resource{
  1138  						Schema: map[string]*Schema{
  1139  							"from": &Schema{
  1140  								Type:     TypeInt,
  1141  								Required: true,
  1142  							},
  1143  						},
  1144  					},
  1145  				},
  1146  			},
  1147  
  1148  			State: nil,
  1149  
  1150  			Config: map[string]interface{}{
  1151  				"ingress": []interface{}{
  1152  					map[string]interface{}{
  1153  						"from": 8080,
  1154  					},
  1155  				},
  1156  			},
  1157  
  1158  			Diff: &terraform.InstanceDiff{
  1159  				Attributes: map[string]*terraform.ResourceAttrDiff{
  1160  					"ingress.#": &terraform.ResourceAttrDiff{
  1161  						Old: "0",
  1162  						New: "1",
  1163  					},
  1164  					"ingress.0.from": &terraform.ResourceAttrDiff{
  1165  						Old: "",
  1166  						New: "8080",
  1167  					},
  1168  				},
  1169  			},
  1170  
  1171  			Err: false,
  1172  		},
  1173  
  1174  		"#30 ComputedWhen": {
  1175  			Schema: map[string]*Schema{
  1176  				"availability_zone": &Schema{
  1177  					Type:         TypeString,
  1178  					Computed:     true,
  1179  					ComputedWhen: []string{"port"},
  1180  				},
  1181  
  1182  				"port": &Schema{
  1183  					Type:     TypeInt,
  1184  					Optional: true,
  1185  				},
  1186  			},
  1187  
  1188  			State: &terraform.InstanceState{
  1189  				Attributes: map[string]string{
  1190  					"availability_zone": "foo",
  1191  					"port":              "80",
  1192  				},
  1193  			},
  1194  
  1195  			Config: map[string]interface{}{
  1196  				"port": 80,
  1197  			},
  1198  
  1199  			Diff: nil,
  1200  
  1201  			Err: false,
  1202  		},
  1203  
  1204  		"#31": {
  1205  			Schema: map[string]*Schema{
  1206  				"availability_zone": &Schema{
  1207  					Type:         TypeString,
  1208  					Computed:     true,
  1209  					ComputedWhen: []string{"port"},
  1210  				},
  1211  
  1212  				"port": &Schema{
  1213  					Type:     TypeInt,
  1214  					Optional: true,
  1215  				},
  1216  			},
  1217  
  1218  			State: &terraform.InstanceState{
  1219  				Attributes: map[string]string{
  1220  					"port": "80",
  1221  				},
  1222  			},
  1223  
  1224  			Config: map[string]interface{}{
  1225  				"port": 80,
  1226  			},
  1227  
  1228  			Diff: &terraform.InstanceDiff{
  1229  				Attributes: map[string]*terraform.ResourceAttrDiff{
  1230  					"availability_zone": &terraform.ResourceAttrDiff{
  1231  						NewComputed: true,
  1232  					},
  1233  				},
  1234  			},
  1235  
  1236  			Err: false,
  1237  		},
  1238  
  1239  		/* TODO
  1240  		{
  1241  			Schema: map[string]*Schema{
  1242  				"availability_zone": &Schema{
  1243  					Type:         TypeString,
  1244  					Computed:     true,
  1245  					ComputedWhen: []string{"port"},
  1246  				},
  1247  
  1248  				"port": &Schema{
  1249  					Type:     TypeInt,
  1250  					Optional: true,
  1251  				},
  1252  			},
  1253  
  1254  			State: &terraform.InstanceState{
  1255  				Attributes: map[string]string{
  1256  					"availability_zone": "foo",
  1257  					"port":              "80",
  1258  				},
  1259  			},
  1260  
  1261  			Config: map[string]interface{}{
  1262  				"port": 8080,
  1263  			},
  1264  
  1265  			Diff: &terraform.ResourceDiff{
  1266  				Attributes: map[string]*terraform.ResourceAttrDiff{
  1267  					"availability_zone": &terraform.ResourceAttrDiff{
  1268  						Old:         "foo",
  1269  						NewComputed: true,
  1270  					},
  1271  					"port": &terraform.ResourceAttrDiff{
  1272  						Old: "80",
  1273  						New: "8080",
  1274  					},
  1275  				},
  1276  			},
  1277  
  1278  			Err: false,
  1279  		},
  1280  		*/
  1281  
  1282  		"#32 Maps": {
  1283  			Schema: map[string]*Schema{
  1284  				"config_vars": &Schema{
  1285  					Type: TypeMap,
  1286  				},
  1287  			},
  1288  
  1289  			State: nil,
  1290  
  1291  			Config: map[string]interface{}{
  1292  				"config_vars": []interface{}{
  1293  					map[string]interface{}{
  1294  						"bar": "baz",
  1295  					},
  1296  				},
  1297  			},
  1298  
  1299  			Diff: &terraform.InstanceDiff{
  1300  				Attributes: map[string]*terraform.ResourceAttrDiff{
  1301  					"config_vars.%": &terraform.ResourceAttrDiff{
  1302  						Old: "0",
  1303  						New: "1",
  1304  					},
  1305  
  1306  					"config_vars.bar": &terraform.ResourceAttrDiff{
  1307  						Old: "",
  1308  						New: "baz",
  1309  					},
  1310  				},
  1311  			},
  1312  
  1313  			Err: false,
  1314  		},
  1315  
  1316  		"#33 Maps": {
  1317  			Schema: map[string]*Schema{
  1318  				"config_vars": &Schema{
  1319  					Type: TypeMap,
  1320  				},
  1321  			},
  1322  
  1323  			State: &terraform.InstanceState{
  1324  				Attributes: map[string]string{
  1325  					"config_vars.foo": "bar",
  1326  				},
  1327  			},
  1328  
  1329  			Config: map[string]interface{}{
  1330  				"config_vars": []interface{}{
  1331  					map[string]interface{}{
  1332  						"bar": "baz",
  1333  					},
  1334  				},
  1335  			},
  1336  
  1337  			Diff: &terraform.InstanceDiff{
  1338  				Attributes: map[string]*terraform.ResourceAttrDiff{
  1339  					"config_vars.foo": &terraform.ResourceAttrDiff{
  1340  						Old:        "bar",
  1341  						NewRemoved: true,
  1342  					},
  1343  					"config_vars.bar": &terraform.ResourceAttrDiff{
  1344  						Old: "",
  1345  						New: "baz",
  1346  					},
  1347  				},
  1348  			},
  1349  
  1350  			Err: false,
  1351  		},
  1352  
  1353  		"#34 Maps": {
  1354  			Schema: map[string]*Schema{
  1355  				"vars": &Schema{
  1356  					Type:     TypeMap,
  1357  					Optional: true,
  1358  					Computed: true,
  1359  				},
  1360  			},
  1361  
  1362  			State: &terraform.InstanceState{
  1363  				Attributes: map[string]string{
  1364  					"vars.foo": "bar",
  1365  				},
  1366  			},
  1367  
  1368  			Config: map[string]interface{}{
  1369  				"vars": []interface{}{
  1370  					map[string]interface{}{
  1371  						"bar": "baz",
  1372  					},
  1373  				},
  1374  			},
  1375  
  1376  			Diff: &terraform.InstanceDiff{
  1377  				Attributes: map[string]*terraform.ResourceAttrDiff{
  1378  					"vars.foo": &terraform.ResourceAttrDiff{
  1379  						Old:        "bar",
  1380  						New:        "",
  1381  						NewRemoved: true,
  1382  					},
  1383  					"vars.bar": &terraform.ResourceAttrDiff{
  1384  						Old: "",
  1385  						New: "baz",
  1386  					},
  1387  				},
  1388  			},
  1389  
  1390  			Err: false,
  1391  		},
  1392  
  1393  		"#35 Maps": {
  1394  			Schema: map[string]*Schema{
  1395  				"vars": &Schema{
  1396  					Type:     TypeMap,
  1397  					Computed: true,
  1398  				},
  1399  			},
  1400  
  1401  			State: &terraform.InstanceState{
  1402  				Attributes: map[string]string{
  1403  					"vars.foo": "bar",
  1404  				},
  1405  			},
  1406  
  1407  			Config: nil,
  1408  
  1409  			Diff: nil,
  1410  
  1411  			Err: false,
  1412  		},
  1413  
  1414  		"#36 Maps": {
  1415  			Schema: map[string]*Schema{
  1416  				"config_vars": &Schema{
  1417  					Type: TypeList,
  1418  					Elem: &Schema{Type: TypeMap},
  1419  				},
  1420  			},
  1421  
  1422  			State: &terraform.InstanceState{
  1423  				Attributes: map[string]string{
  1424  					"config_vars.#":     "1",
  1425  					"config_vars.0.foo": "bar",
  1426  				},
  1427  			},
  1428  
  1429  			Config: map[string]interface{}{
  1430  				"config_vars": []interface{}{
  1431  					map[string]interface{}{
  1432  						"bar": "baz",
  1433  					},
  1434  				},
  1435  			},
  1436  
  1437  			Diff: &terraform.InstanceDiff{
  1438  				Attributes: map[string]*terraform.ResourceAttrDiff{
  1439  					"config_vars.0.foo": &terraform.ResourceAttrDiff{
  1440  						Old:        "bar",
  1441  						NewRemoved: true,
  1442  					},
  1443  					"config_vars.0.bar": &terraform.ResourceAttrDiff{
  1444  						Old: "",
  1445  						New: "baz",
  1446  					},
  1447  				},
  1448  			},
  1449  
  1450  			Err: false,
  1451  		},
  1452  
  1453  		"#37 Maps": {
  1454  			Schema: map[string]*Schema{
  1455  				"config_vars": &Schema{
  1456  					Type: TypeList,
  1457  					Elem: &Schema{Type: TypeMap},
  1458  				},
  1459  			},
  1460  
  1461  			State: &terraform.InstanceState{
  1462  				Attributes: map[string]string{
  1463  					"config_vars.#":     "1",
  1464  					"config_vars.0.foo": "bar",
  1465  					"config_vars.0.bar": "baz",
  1466  				},
  1467  			},
  1468  
  1469  			Config: map[string]interface{}{},
  1470  
  1471  			Diff: &terraform.InstanceDiff{
  1472  				Attributes: map[string]*terraform.ResourceAttrDiff{
  1473  					"config_vars.#": &terraform.ResourceAttrDiff{
  1474  						Old: "1",
  1475  						New: "0",
  1476  					},
  1477  					"config_vars.0.%": &terraform.ResourceAttrDiff{
  1478  						Old: "2",
  1479  						New: "0",
  1480  					},
  1481  					"config_vars.0.foo": &terraform.ResourceAttrDiff{
  1482  						Old:        "bar",
  1483  						NewRemoved: true,
  1484  					},
  1485  					"config_vars.0.bar": &terraform.ResourceAttrDiff{
  1486  						Old:        "baz",
  1487  						NewRemoved: true,
  1488  					},
  1489  				},
  1490  			},
  1491  
  1492  			Err: false,
  1493  		},
  1494  
  1495  		"#38 ForceNews": {
  1496  			Schema: map[string]*Schema{
  1497  				"availability_zone": &Schema{
  1498  					Type:     TypeString,
  1499  					Optional: true,
  1500  					ForceNew: true,
  1501  				},
  1502  
  1503  				"address": &Schema{
  1504  					Type:     TypeString,
  1505  					Optional: true,
  1506  					Computed: true,
  1507  				},
  1508  			},
  1509  
  1510  			State: &terraform.InstanceState{
  1511  				Attributes: map[string]string{
  1512  					"availability_zone": "bar",
  1513  					"address":           "foo",
  1514  				},
  1515  			},
  1516  
  1517  			Config: map[string]interface{}{
  1518  				"availability_zone": "foo",
  1519  			},
  1520  
  1521  			Diff: &terraform.InstanceDiff{
  1522  				Attributes: map[string]*terraform.ResourceAttrDiff{
  1523  					"availability_zone": &terraform.ResourceAttrDiff{
  1524  						Old:         "bar",
  1525  						New:         "foo",
  1526  						RequiresNew: true,
  1527  					},
  1528  
  1529  					"address": &terraform.ResourceAttrDiff{
  1530  						Old:         "foo",
  1531  						New:         "",
  1532  						NewComputed: true,
  1533  					},
  1534  				},
  1535  			},
  1536  
  1537  			Err: false,
  1538  		},
  1539  
  1540  		"#39 Set": {
  1541  			Schema: map[string]*Schema{
  1542  				"availability_zone": &Schema{
  1543  					Type:     TypeString,
  1544  					Optional: true,
  1545  					ForceNew: true,
  1546  				},
  1547  
  1548  				"ports": &Schema{
  1549  					Type:     TypeSet,
  1550  					Optional: true,
  1551  					Computed: true,
  1552  					Elem:     &Schema{Type: TypeInt},
  1553  					Set: func(a interface{}) int {
  1554  						return a.(int)
  1555  					},
  1556  				},
  1557  			},
  1558  
  1559  			State: &terraform.InstanceState{
  1560  				Attributes: map[string]string{
  1561  					"availability_zone": "bar",
  1562  					"ports.#":           "1",
  1563  					"ports.80":          "80",
  1564  				},
  1565  			},
  1566  
  1567  			Config: map[string]interface{}{
  1568  				"availability_zone": "foo",
  1569  			},
  1570  
  1571  			Diff: &terraform.InstanceDiff{
  1572  				Attributes: map[string]*terraform.ResourceAttrDiff{
  1573  					"availability_zone": &terraform.ResourceAttrDiff{
  1574  						Old:         "bar",
  1575  						New:         "foo",
  1576  						RequiresNew: true,
  1577  					},
  1578  
  1579  					"ports.#": &terraform.ResourceAttrDiff{
  1580  						Old:         "1",
  1581  						New:         "",
  1582  						NewComputed: true,
  1583  					},
  1584  				},
  1585  			},
  1586  
  1587  			Err: false,
  1588  		},
  1589  
  1590  		"#40 Set": {
  1591  			Schema: map[string]*Schema{
  1592  				"instances": &Schema{
  1593  					Type:     TypeSet,
  1594  					Elem:     &Schema{Type: TypeString},
  1595  					Optional: true,
  1596  					Computed: true,
  1597  					Set: func(v interface{}) int {
  1598  						return len(v.(string))
  1599  					},
  1600  				},
  1601  			},
  1602  
  1603  			State: &terraform.InstanceState{
  1604  				Attributes: map[string]string{
  1605  					"instances.#": "0",
  1606  				},
  1607  			},
  1608  
  1609  			Config: map[string]interface{}{
  1610  				"instances": []interface{}{"${var.foo}"},
  1611  			},
  1612  
  1613  			ConfigVariables: map[string]ast.Variable{
  1614  				"var.foo": interfaceToVariableSwallowError(config.UnknownVariableValue),
  1615  			},
  1616  
  1617  			Diff: &terraform.InstanceDiff{
  1618  				Attributes: map[string]*terraform.ResourceAttrDiff{
  1619  					"instances.#": &terraform.ResourceAttrDiff{
  1620  						NewComputed: true,
  1621  					},
  1622  				},
  1623  			},
  1624  
  1625  			Err: false,
  1626  		},
  1627  
  1628  		"#41 Set": {
  1629  			Schema: map[string]*Schema{
  1630  				"route": &Schema{
  1631  					Type:     TypeSet,
  1632  					Optional: true,
  1633  					Elem: &Resource{
  1634  						Schema: map[string]*Schema{
  1635  							"index": &Schema{
  1636  								Type:     TypeInt,
  1637  								Required: true,
  1638  							},
  1639  
  1640  							"gateway": &Schema{
  1641  								Type:     TypeString,
  1642  								Optional: true,
  1643  							},
  1644  						},
  1645  					},
  1646  					Set: func(v interface{}) int {
  1647  						m := v.(map[string]interface{})
  1648  						return m["index"].(int)
  1649  					},
  1650  				},
  1651  			},
  1652  
  1653  			State: nil,
  1654  
  1655  			Config: map[string]interface{}{
  1656  				"route": []map[string]interface{}{
  1657  					map[string]interface{}{
  1658  						"index":   "1",
  1659  						"gateway": "${var.foo}",
  1660  					},
  1661  				},
  1662  			},
  1663  
  1664  			ConfigVariables: map[string]ast.Variable{
  1665  				"var.foo": interfaceToVariableSwallowError(config.UnknownVariableValue),
  1666  			},
  1667  
  1668  			Diff: &terraform.InstanceDiff{
  1669  				Attributes: map[string]*terraform.ResourceAttrDiff{
  1670  					"route.#": &terraform.ResourceAttrDiff{
  1671  						Old: "0",
  1672  						New: "1",
  1673  					},
  1674  					"route.~1.index": &terraform.ResourceAttrDiff{
  1675  						Old: "",
  1676  						New: "1",
  1677  					},
  1678  					"route.~1.gateway": &terraform.ResourceAttrDiff{
  1679  						Old:         "",
  1680  						New:         "${var.foo}",
  1681  						NewComputed: true,
  1682  					},
  1683  				},
  1684  			},
  1685  
  1686  			Err: false,
  1687  		},
  1688  
  1689  		"#42 Set": {
  1690  			Schema: map[string]*Schema{
  1691  				"route": &Schema{
  1692  					Type:     TypeSet,
  1693  					Optional: true,
  1694  					Elem: &Resource{
  1695  						Schema: map[string]*Schema{
  1696  							"index": &Schema{
  1697  								Type:     TypeInt,
  1698  								Required: true,
  1699  							},
  1700  
  1701  							"gateway": &Schema{
  1702  								Type:     TypeSet,
  1703  								Optional: true,
  1704  								Elem:     &Schema{Type: TypeInt},
  1705  								Set: func(a interface{}) int {
  1706  									return a.(int)
  1707  								},
  1708  							},
  1709  						},
  1710  					},
  1711  					Set: func(v interface{}) int {
  1712  						m := v.(map[string]interface{})
  1713  						return m["index"].(int)
  1714  					},
  1715  				},
  1716  			},
  1717  
  1718  			State: nil,
  1719  
  1720  			Config: map[string]interface{}{
  1721  				"route": []map[string]interface{}{
  1722  					map[string]interface{}{
  1723  						"index": "1",
  1724  						"gateway": []interface{}{
  1725  							"${var.foo}",
  1726  						},
  1727  					},
  1728  				},
  1729  			},
  1730  
  1731  			ConfigVariables: map[string]ast.Variable{
  1732  				"var.foo": interfaceToVariableSwallowError(config.UnknownVariableValue),
  1733  			},
  1734  
  1735  			Diff: &terraform.InstanceDiff{
  1736  				Attributes: map[string]*terraform.ResourceAttrDiff{
  1737  					"route.#": &terraform.ResourceAttrDiff{
  1738  						Old: "0",
  1739  						New: "1",
  1740  					},
  1741  					"route.~1.index": &terraform.ResourceAttrDiff{
  1742  						Old: "",
  1743  						New: "1",
  1744  					},
  1745  					"route.~1.gateway.#": &terraform.ResourceAttrDiff{
  1746  						NewComputed: true,
  1747  					},
  1748  				},
  1749  			},
  1750  
  1751  			Err: false,
  1752  		},
  1753  
  1754  		"#43 - Computed maps": {
  1755  			Schema: map[string]*Schema{
  1756  				"vars": &Schema{
  1757  					Type:     TypeMap,
  1758  					Computed: true,
  1759  				},
  1760  			},
  1761  
  1762  			State: nil,
  1763  
  1764  			Config: nil,
  1765  
  1766  			Diff: &terraform.InstanceDiff{
  1767  				Attributes: map[string]*terraform.ResourceAttrDiff{
  1768  					"vars.%": &terraform.ResourceAttrDiff{
  1769  						Old:         "",
  1770  						NewComputed: true,
  1771  					},
  1772  				},
  1773  			},
  1774  
  1775  			Err: false,
  1776  		},
  1777  
  1778  		"#44 - Computed maps": {
  1779  			Schema: map[string]*Schema{
  1780  				"vars": &Schema{
  1781  					Type:     TypeMap,
  1782  					Computed: true,
  1783  				},
  1784  			},
  1785  
  1786  			State: &terraform.InstanceState{
  1787  				Attributes: map[string]string{
  1788  					"vars.%": "0",
  1789  				},
  1790  			},
  1791  
  1792  			Config: map[string]interface{}{
  1793  				"vars": map[string]interface{}{
  1794  					"bar": "${var.foo}",
  1795  				},
  1796  			},
  1797  
  1798  			ConfigVariables: map[string]ast.Variable{
  1799  				"var.foo": interfaceToVariableSwallowError(config.UnknownVariableValue),
  1800  			},
  1801  
  1802  			Diff: &terraform.InstanceDiff{
  1803  				Attributes: map[string]*terraform.ResourceAttrDiff{
  1804  					"vars.%": &terraform.ResourceAttrDiff{
  1805  						Old:         "",
  1806  						NewComputed: true,
  1807  					},
  1808  				},
  1809  			},
  1810  
  1811  			Err: false,
  1812  		},
  1813  
  1814  		"#45 - Empty": {
  1815  			Schema: map[string]*Schema{},
  1816  
  1817  			State: &terraform.InstanceState{},
  1818  
  1819  			Config: map[string]interface{}{},
  1820  
  1821  			Diff: nil,
  1822  
  1823  			Err: false,
  1824  		},
  1825  
  1826  		"#46 - Float": {
  1827  			Schema: map[string]*Schema{
  1828  				"some_threshold": &Schema{
  1829  					Type: TypeFloat,
  1830  				},
  1831  			},
  1832  
  1833  			State: &terraform.InstanceState{
  1834  				Attributes: map[string]string{
  1835  					"some_threshold": "567.8",
  1836  				},
  1837  			},
  1838  
  1839  			Config: map[string]interface{}{
  1840  				"some_threshold": 12.34,
  1841  			},
  1842  
  1843  			Diff: &terraform.InstanceDiff{
  1844  				Attributes: map[string]*terraform.ResourceAttrDiff{
  1845  					"some_threshold": &terraform.ResourceAttrDiff{
  1846  						Old: "567.8",
  1847  						New: "12.34",
  1848  					},
  1849  				},
  1850  			},
  1851  
  1852  			Err: false,
  1853  		},
  1854  
  1855  		"#47 - https://github.com/hashicorp/terraform/issues/824": {
  1856  			Schema: map[string]*Schema{
  1857  				"block_device": &Schema{
  1858  					Type:     TypeSet,
  1859  					Optional: true,
  1860  					Computed: true,
  1861  					Elem: &Resource{
  1862  						Schema: map[string]*Schema{
  1863  							"device_name": &Schema{
  1864  								Type:     TypeString,
  1865  								Required: true,
  1866  							},
  1867  							"delete_on_termination": &Schema{
  1868  								Type:     TypeBool,
  1869  								Optional: true,
  1870  								Default:  true,
  1871  							},
  1872  						},
  1873  					},
  1874  					Set: func(v interface{}) int {
  1875  						var buf bytes.Buffer
  1876  						m := v.(map[string]interface{})
  1877  						buf.WriteString(fmt.Sprintf("%s-", m["device_name"].(string)))
  1878  						buf.WriteString(fmt.Sprintf("%t-", m["delete_on_termination"].(bool)))
  1879  						return hashcode.String(buf.String())
  1880  					},
  1881  				},
  1882  			},
  1883  
  1884  			State: &terraform.InstanceState{
  1885  				Attributes: map[string]string{
  1886  					"block_device.#":                                "2",
  1887  					"block_device.616397234.delete_on_termination":  "true",
  1888  					"block_device.616397234.device_name":            "/dev/sda1",
  1889  					"block_device.2801811477.delete_on_termination": "true",
  1890  					"block_device.2801811477.device_name":           "/dev/sdx",
  1891  				},
  1892  			},
  1893  
  1894  			Config: map[string]interface{}{
  1895  				"block_device": []map[string]interface{}{
  1896  					map[string]interface{}{
  1897  						"device_name": "/dev/sda1",
  1898  					},
  1899  					map[string]interface{}{
  1900  						"device_name": "/dev/sdx",
  1901  					},
  1902  				},
  1903  			},
  1904  			Diff: nil,
  1905  			Err:  false,
  1906  		},
  1907  
  1908  		"#48 - Zero value in state shouldn't result in diff": {
  1909  			Schema: map[string]*Schema{
  1910  				"port": &Schema{
  1911  					Type:     TypeBool,
  1912  					Optional: true,
  1913  					ForceNew: true,
  1914  				},
  1915  			},
  1916  
  1917  			State: &terraform.InstanceState{
  1918  				Attributes: map[string]string{
  1919  					"port": "false",
  1920  				},
  1921  			},
  1922  
  1923  			Config: map[string]interface{}{},
  1924  
  1925  			Diff: nil,
  1926  
  1927  			Err: false,
  1928  		},
  1929  
  1930  		"#49 Set - Same as #48 but for sets": {
  1931  			Schema: map[string]*Schema{
  1932  				"route": &Schema{
  1933  					Type:     TypeSet,
  1934  					Optional: true,
  1935  					Elem: &Resource{
  1936  						Schema: map[string]*Schema{
  1937  							"index": &Schema{
  1938  								Type:     TypeInt,
  1939  								Required: true,
  1940  							},
  1941  
  1942  							"gateway": &Schema{
  1943  								Type:     TypeSet,
  1944  								Optional: true,
  1945  								Elem:     &Schema{Type: TypeInt},
  1946  								Set: func(a interface{}) int {
  1947  									return a.(int)
  1948  								},
  1949  							},
  1950  						},
  1951  					},
  1952  					Set: func(v interface{}) int {
  1953  						m := v.(map[string]interface{})
  1954  						return m["index"].(int)
  1955  					},
  1956  				},
  1957  			},
  1958  
  1959  			State: &terraform.InstanceState{
  1960  				Attributes: map[string]string{
  1961  					"route.#": "0",
  1962  				},
  1963  			},
  1964  
  1965  			Config: map[string]interface{}{},
  1966  
  1967  			Diff: nil,
  1968  
  1969  			Err: false,
  1970  		},
  1971  
  1972  		"#50 - A set computed element shouldn't cause a diff": {
  1973  			Schema: map[string]*Schema{
  1974  				"active": &Schema{
  1975  					Type:     TypeBool,
  1976  					Computed: true,
  1977  					ForceNew: true,
  1978  				},
  1979  			},
  1980  
  1981  			State: &terraform.InstanceState{
  1982  				Attributes: map[string]string{
  1983  					"active": "true",
  1984  				},
  1985  			},
  1986  
  1987  			Config: map[string]interface{}{},
  1988  
  1989  			Diff: nil,
  1990  
  1991  			Err: false,
  1992  		},
  1993  
  1994  		"#51 - An empty set should show up in the diff": {
  1995  			Schema: map[string]*Schema{
  1996  				"instances": &Schema{
  1997  					Type:     TypeSet,
  1998  					Elem:     &Schema{Type: TypeString},
  1999  					Optional: true,
  2000  					ForceNew: true,
  2001  					Set: func(v interface{}) int {
  2002  						return len(v.(string))
  2003  					},
  2004  				},
  2005  			},
  2006  
  2007  			State: &terraform.InstanceState{
  2008  				Attributes: map[string]string{
  2009  					"instances.#": "1",
  2010  					"instances.3": "foo",
  2011  				},
  2012  			},
  2013  
  2014  			Config: map[string]interface{}{},
  2015  
  2016  			Diff: &terraform.InstanceDiff{
  2017  				Attributes: map[string]*terraform.ResourceAttrDiff{
  2018  					"instances.#": &terraform.ResourceAttrDiff{
  2019  						Old:         "1",
  2020  						New:         "0",
  2021  						RequiresNew: true,
  2022  					},
  2023  					"instances.3": &terraform.ResourceAttrDiff{
  2024  						Old:        "foo",
  2025  						New:        "",
  2026  						NewRemoved: true,
  2027  					},
  2028  				},
  2029  			},
  2030  
  2031  			Err: false,
  2032  		},
  2033  
  2034  		"#52 - Map with empty value": {
  2035  			Schema: map[string]*Schema{
  2036  				"vars": &Schema{
  2037  					Type: TypeMap,
  2038  				},
  2039  			},
  2040  
  2041  			State: nil,
  2042  
  2043  			Config: map[string]interface{}{
  2044  				"vars": map[string]interface{}{
  2045  					"foo": "",
  2046  				},
  2047  			},
  2048  
  2049  			Diff: &terraform.InstanceDiff{
  2050  				Attributes: map[string]*terraform.ResourceAttrDiff{
  2051  					"vars.%": &terraform.ResourceAttrDiff{
  2052  						Old: "0",
  2053  						New: "1",
  2054  					},
  2055  					"vars.foo": &terraform.ResourceAttrDiff{
  2056  						Old: "",
  2057  						New: "",
  2058  					},
  2059  				},
  2060  			},
  2061  
  2062  			Err: false,
  2063  		},
  2064  
  2065  		"#53 - Unset bool, not in state": {
  2066  			Schema: map[string]*Schema{
  2067  				"force": &Schema{
  2068  					Type:     TypeBool,
  2069  					Optional: true,
  2070  					ForceNew: true,
  2071  				},
  2072  			},
  2073  
  2074  			State: nil,
  2075  
  2076  			Config: map[string]interface{}{},
  2077  
  2078  			Diff: nil,
  2079  
  2080  			Err: false,
  2081  		},
  2082  
  2083  		"#54 - Unset set, not in state": {
  2084  			Schema: map[string]*Schema{
  2085  				"metadata_keys": &Schema{
  2086  					Type:     TypeSet,
  2087  					Optional: true,
  2088  					ForceNew: true,
  2089  					Elem:     &Schema{Type: TypeInt},
  2090  					Set:      func(interface{}) int { return 0 },
  2091  				},
  2092  			},
  2093  
  2094  			State: nil,
  2095  
  2096  			Config: map[string]interface{}{},
  2097  
  2098  			Diff: nil,
  2099  
  2100  			Err: false,
  2101  		},
  2102  
  2103  		"#55 - Unset list in state, should not show up computed": {
  2104  			Schema: map[string]*Schema{
  2105  				"metadata_keys": &Schema{
  2106  					Type:     TypeList,
  2107  					Optional: true,
  2108  					Computed: true,
  2109  					ForceNew: true,
  2110  					Elem:     &Schema{Type: TypeInt},
  2111  				},
  2112  			},
  2113  
  2114  			State: &terraform.InstanceState{
  2115  				Attributes: map[string]string{
  2116  					"metadata_keys.#": "0",
  2117  				},
  2118  			},
  2119  
  2120  			Config: map[string]interface{}{},
  2121  
  2122  			Diff: nil,
  2123  
  2124  			Err: false,
  2125  		},
  2126  
  2127  		"#56 - Set element computed substring": {
  2128  			Schema: map[string]*Schema{
  2129  				"ports": &Schema{
  2130  					Type:     TypeSet,
  2131  					Required: true,
  2132  					Elem:     &Schema{Type: TypeInt},
  2133  					Set: func(a interface{}) int {
  2134  						return a.(int)
  2135  					},
  2136  				},
  2137  			},
  2138  
  2139  			State: nil,
  2140  
  2141  			Config: map[string]interface{}{
  2142  				"ports": []interface{}{1, "${var.foo}32"},
  2143  			},
  2144  
  2145  			ConfigVariables: map[string]ast.Variable{
  2146  				"var.foo": interfaceToVariableSwallowError(config.UnknownVariableValue),
  2147  			},
  2148  
  2149  			Diff: &terraform.InstanceDiff{
  2150  				Attributes: map[string]*terraform.ResourceAttrDiff{
  2151  					"ports.#": &terraform.ResourceAttrDiff{
  2152  						Old:         "",
  2153  						New:         "",
  2154  						NewComputed: true,
  2155  					},
  2156  				},
  2157  			},
  2158  
  2159  			Err: false,
  2160  		},
  2161  
  2162  		"#57 Computed map without config that's known to be empty does not generate diff": {
  2163  			Schema: map[string]*Schema{
  2164  				"tags": &Schema{
  2165  					Type:     TypeMap,
  2166  					Computed: true,
  2167  				},
  2168  			},
  2169  
  2170  			Config: nil,
  2171  
  2172  			State: &terraform.InstanceState{
  2173  				Attributes: map[string]string{
  2174  					"tags.%": "0",
  2175  				},
  2176  			},
  2177  
  2178  			Diff: nil,
  2179  
  2180  			Err: false,
  2181  		},
  2182  
  2183  		"#58 Set with hyphen keys": {
  2184  			Schema: map[string]*Schema{
  2185  				"route": &Schema{
  2186  					Type:     TypeSet,
  2187  					Optional: true,
  2188  					Elem: &Resource{
  2189  						Schema: map[string]*Schema{
  2190  							"index": &Schema{
  2191  								Type:     TypeInt,
  2192  								Required: true,
  2193  							},
  2194  
  2195  							"gateway-name": &Schema{
  2196  								Type:     TypeString,
  2197  								Optional: true,
  2198  							},
  2199  						},
  2200  					},
  2201  					Set: func(v interface{}) int {
  2202  						m := v.(map[string]interface{})
  2203  						return m["index"].(int)
  2204  					},
  2205  				},
  2206  			},
  2207  
  2208  			State: nil,
  2209  
  2210  			Config: map[string]interface{}{
  2211  				"route": []map[string]interface{}{
  2212  					map[string]interface{}{
  2213  						"index":        "1",
  2214  						"gateway-name": "hello",
  2215  					},
  2216  				},
  2217  			},
  2218  
  2219  			Diff: &terraform.InstanceDiff{
  2220  				Attributes: map[string]*terraform.ResourceAttrDiff{
  2221  					"route.#": &terraform.ResourceAttrDiff{
  2222  						Old: "0",
  2223  						New: "1",
  2224  					},
  2225  					"route.1.index": &terraform.ResourceAttrDiff{
  2226  						Old: "",
  2227  						New: "1",
  2228  					},
  2229  					"route.1.gateway-name": &terraform.ResourceAttrDiff{
  2230  						Old: "",
  2231  						New: "hello",
  2232  					},
  2233  				},
  2234  			},
  2235  
  2236  			Err: false,
  2237  		},
  2238  
  2239  		"#59: StateFunc in nested set (#1759)": {
  2240  			Schema: map[string]*Schema{
  2241  				"service_account": &Schema{
  2242  					Type:     TypeList,
  2243  					Optional: true,
  2244  					ForceNew: true,
  2245  					Elem: &Resource{
  2246  						Schema: map[string]*Schema{
  2247  							"scopes": &Schema{
  2248  								Type:     TypeSet,
  2249  								Required: true,
  2250  								ForceNew: true,
  2251  								Elem: &Schema{
  2252  									Type: TypeString,
  2253  									StateFunc: func(v interface{}) string {
  2254  										return v.(string) + "!"
  2255  									},
  2256  								},
  2257  								Set: func(v interface{}) int {
  2258  									i, err := strconv.Atoi(v.(string))
  2259  									if err != nil {
  2260  										t.Fatalf("err: %s", err)
  2261  									}
  2262  									return i
  2263  								},
  2264  							},
  2265  						},
  2266  					},
  2267  				},
  2268  			},
  2269  
  2270  			State: nil,
  2271  
  2272  			Config: map[string]interface{}{
  2273  				"service_account": []map[string]interface{}{
  2274  					{
  2275  						"scopes": []interface{}{"123"},
  2276  					},
  2277  				},
  2278  			},
  2279  
  2280  			Diff: &terraform.InstanceDiff{
  2281  				Attributes: map[string]*terraform.ResourceAttrDiff{
  2282  					"service_account.#": &terraform.ResourceAttrDiff{
  2283  						Old:         "0",
  2284  						New:         "1",
  2285  						RequiresNew: true,
  2286  					},
  2287  					"service_account.0.scopes.#": &terraform.ResourceAttrDiff{
  2288  						Old:         "0",
  2289  						New:         "1",
  2290  						RequiresNew: true,
  2291  					},
  2292  					"service_account.0.scopes.123": &terraform.ResourceAttrDiff{
  2293  						Old:         "",
  2294  						New:         "123!",
  2295  						NewExtra:    "123",
  2296  						RequiresNew: true,
  2297  					},
  2298  				},
  2299  			},
  2300  
  2301  			Err: false,
  2302  		},
  2303  
  2304  		"#60 - Removing set elements": {
  2305  			Schema: map[string]*Schema{
  2306  				"instances": &Schema{
  2307  					Type:     TypeSet,
  2308  					Elem:     &Schema{Type: TypeString},
  2309  					Optional: true,
  2310  					ForceNew: true,
  2311  					Set: func(v interface{}) int {
  2312  						return len(v.(string))
  2313  					},
  2314  				},
  2315  			},
  2316  
  2317  			State: &terraform.InstanceState{
  2318  				Attributes: map[string]string{
  2319  					"instances.#": "2",
  2320  					"instances.3": "333",
  2321  					"instances.2": "22",
  2322  				},
  2323  			},
  2324  
  2325  			Config: map[string]interface{}{
  2326  				"instances": []interface{}{"333", "4444"},
  2327  			},
  2328  
  2329  			Diff: &terraform.InstanceDiff{
  2330  				Attributes: map[string]*terraform.ResourceAttrDiff{
  2331  					"instances.#": &terraform.ResourceAttrDiff{
  2332  						Old: "2",
  2333  						New: "2",
  2334  					},
  2335  					"instances.2": &terraform.ResourceAttrDiff{
  2336  						Old:        "22",
  2337  						New:        "",
  2338  						NewRemoved: true,
  2339  					},
  2340  					"instances.3": &terraform.ResourceAttrDiff{
  2341  						Old:         "333",
  2342  						New:         "333",
  2343  						RequiresNew: true,
  2344  					},
  2345  					"instances.4": &terraform.ResourceAttrDiff{
  2346  						Old:         "",
  2347  						New:         "4444",
  2348  						RequiresNew: true,
  2349  					},
  2350  				},
  2351  			},
  2352  
  2353  			Err: false,
  2354  		},
  2355  
  2356  		"Bools can be set with 0/1 in config, still get true/false": {
  2357  			Schema: map[string]*Schema{
  2358  				"one": &Schema{
  2359  					Type:     TypeBool,
  2360  					Optional: true,
  2361  				},
  2362  				"two": &Schema{
  2363  					Type:     TypeBool,
  2364  					Optional: true,
  2365  				},
  2366  				"three": &Schema{
  2367  					Type:     TypeBool,
  2368  					Optional: true,
  2369  				},
  2370  			},
  2371  
  2372  			State: &terraform.InstanceState{
  2373  				Attributes: map[string]string{
  2374  					"one":   "false",
  2375  					"two":   "true",
  2376  					"three": "true",
  2377  				},
  2378  			},
  2379  
  2380  			Config: map[string]interface{}{
  2381  				"one": "1",
  2382  				"two": "0",
  2383  			},
  2384  
  2385  			Diff: &terraform.InstanceDiff{
  2386  				Attributes: map[string]*terraform.ResourceAttrDiff{
  2387  					"one": &terraform.ResourceAttrDiff{
  2388  						Old: "false",
  2389  						New: "true",
  2390  					},
  2391  					"two": &terraform.ResourceAttrDiff{
  2392  						Old: "true",
  2393  						New: "false",
  2394  					},
  2395  					"three": &terraform.ResourceAttrDiff{
  2396  						Old:        "true",
  2397  						New:        "false",
  2398  						NewRemoved: true,
  2399  					},
  2400  				},
  2401  			},
  2402  
  2403  			Err: false,
  2404  		},
  2405  
  2406  		"tainted in state w/ no attr changes is still a replacement": {
  2407  			Schema: map[string]*Schema{},
  2408  
  2409  			State: &terraform.InstanceState{
  2410  				Attributes: map[string]string{
  2411  					"id": "someid",
  2412  				},
  2413  				Tainted: true,
  2414  			},
  2415  
  2416  			Config: map[string]interface{}{},
  2417  
  2418  			Diff: &terraform.InstanceDiff{
  2419  				Attributes:     map[string]*terraform.ResourceAttrDiff{},
  2420  				DestroyTainted: true,
  2421  			},
  2422  
  2423  			Err: false,
  2424  		},
  2425  	}
  2426  
  2427  	for tn, tc := range cases {
  2428  		c, err := config.NewRawConfig(tc.Config)
  2429  		if err != nil {
  2430  			t.Fatalf("#%q err: %s", tn, err)
  2431  		}
  2432  
  2433  		if len(tc.ConfigVariables) > 0 {
  2434  			if err := c.Interpolate(tc.ConfigVariables); err != nil {
  2435  				t.Fatalf("#%q err: %s", tn, err)
  2436  			}
  2437  		}
  2438  
  2439  		d, err := schemaMap(tc.Schema).Diff(
  2440  			tc.State, terraform.NewResourceConfig(c))
  2441  		if err != nil != tc.Err {
  2442  			t.Fatalf("#%q err: %s", tn, err)
  2443  		}
  2444  
  2445  		if !reflect.DeepEqual(tc.Diff, d) {
  2446  			t.Fatalf("#%q:\n\nexpected:\n%#v\n\ngot:\n%#v", tn, tc.Diff, d)
  2447  		}
  2448  	}
  2449  }
  2450  
  2451  func TestSchemaMap_Input(t *testing.T) {
  2452  	cases := map[string]struct {
  2453  		Schema map[string]*Schema
  2454  		Config map[string]interface{}
  2455  		Input  map[string]string
  2456  		Result map[string]interface{}
  2457  		Err    bool
  2458  	}{
  2459  		/*
  2460  		 * String decode
  2461  		 */
  2462  
  2463  		"uses input on optional field with no config": {
  2464  			Schema: map[string]*Schema{
  2465  				"availability_zone": &Schema{
  2466  					Type:     TypeString,
  2467  					Optional: true,
  2468  				},
  2469  			},
  2470  
  2471  			Input: map[string]string{
  2472  				"availability_zone": "foo",
  2473  			},
  2474  
  2475  			Result: map[string]interface{}{
  2476  				"availability_zone": "foo",
  2477  			},
  2478  
  2479  			Err: false,
  2480  		},
  2481  
  2482  		"input ignored when config has a value": {
  2483  			Schema: map[string]*Schema{
  2484  				"availability_zone": &Schema{
  2485  					Type:     TypeString,
  2486  					Optional: true,
  2487  				},
  2488  			},
  2489  
  2490  			Config: map[string]interface{}{
  2491  				"availability_zone": "bar",
  2492  			},
  2493  
  2494  			Input: map[string]string{
  2495  				"availability_zone": "foo",
  2496  			},
  2497  
  2498  			Result: map[string]interface{}{},
  2499  
  2500  			Err: false,
  2501  		},
  2502  
  2503  		"input ignored when schema has a default": {
  2504  			Schema: map[string]*Schema{
  2505  				"availability_zone": &Schema{
  2506  					Type:     TypeString,
  2507  					Default:  "foo",
  2508  					Optional: true,
  2509  				},
  2510  			},
  2511  
  2512  			Input: map[string]string{
  2513  				"availability_zone": "bar",
  2514  			},
  2515  
  2516  			Result: map[string]interface{}{},
  2517  
  2518  			Err: false,
  2519  		},
  2520  
  2521  		"input ignored when default function returns a value": {
  2522  			Schema: map[string]*Schema{
  2523  				"availability_zone": &Schema{
  2524  					Type: TypeString,
  2525  					DefaultFunc: func() (interface{}, error) {
  2526  						return "foo", nil
  2527  					},
  2528  					Optional: true,
  2529  				},
  2530  			},
  2531  
  2532  			Input: map[string]string{
  2533  				"availability_zone": "bar",
  2534  			},
  2535  
  2536  			Result: map[string]interface{}{},
  2537  
  2538  			Err: false,
  2539  		},
  2540  
  2541  		"input ignored when default function returns an empty string": {
  2542  			Schema: map[string]*Schema{
  2543  				"availability_zone": &Schema{
  2544  					Type:     TypeString,
  2545  					Default:  "",
  2546  					Optional: true,
  2547  				},
  2548  			},
  2549  
  2550  			Input: map[string]string{
  2551  				"availability_zone": "bar",
  2552  			},
  2553  
  2554  			Result: map[string]interface{}{},
  2555  
  2556  			Err: false,
  2557  		},
  2558  
  2559  		"input used when default function returns nil": {
  2560  			Schema: map[string]*Schema{
  2561  				"availability_zone": &Schema{
  2562  					Type: TypeString,
  2563  					DefaultFunc: func() (interface{}, error) {
  2564  						return nil, nil
  2565  					},
  2566  					Optional: true,
  2567  				},
  2568  			},
  2569  
  2570  			Input: map[string]string{
  2571  				"availability_zone": "bar",
  2572  			},
  2573  
  2574  			Result: map[string]interface{}{
  2575  				"availability_zone": "bar",
  2576  			},
  2577  
  2578  			Err: false,
  2579  		},
  2580  	}
  2581  
  2582  	for i, tc := range cases {
  2583  		if tc.Config == nil {
  2584  			tc.Config = make(map[string]interface{})
  2585  		}
  2586  
  2587  		c, err := config.NewRawConfig(tc.Config)
  2588  		if err != nil {
  2589  			t.Fatalf("err: %s", err)
  2590  		}
  2591  
  2592  		input := new(terraform.MockUIInput)
  2593  		input.InputReturnMap = tc.Input
  2594  
  2595  		rc := terraform.NewResourceConfig(c)
  2596  		rc.Config = make(map[string]interface{})
  2597  
  2598  		actual, err := schemaMap(tc.Schema).Input(input, rc)
  2599  		if err != nil != tc.Err {
  2600  			t.Fatalf("#%v err: %s", i, err)
  2601  		}
  2602  
  2603  		if !reflect.DeepEqual(tc.Result, actual.Config) {
  2604  			t.Fatalf("#%v: bad:\n\ngot: %#v\nexpected: %#v", i, actual.Config, tc.Result)
  2605  		}
  2606  	}
  2607  }
  2608  
  2609  func TestSchemaMap_InputDefault(t *testing.T) {
  2610  	emptyConfig := make(map[string]interface{})
  2611  	c, err := config.NewRawConfig(emptyConfig)
  2612  	if err != nil {
  2613  		t.Fatalf("err: %s", err)
  2614  	}
  2615  	rc := terraform.NewResourceConfig(c)
  2616  	rc.Config = make(map[string]interface{})
  2617  
  2618  	input := new(terraform.MockUIInput)
  2619  	input.InputFn = func(opts *terraform.InputOpts) (string, error) {
  2620  		t.Fatalf("InputFn should not be called on: %#v", opts)
  2621  		return "", nil
  2622  	}
  2623  
  2624  	schema := map[string]*Schema{
  2625  		"availability_zone": &Schema{
  2626  			Type:     TypeString,
  2627  			Default:  "foo",
  2628  			Optional: true,
  2629  		},
  2630  	}
  2631  	actual, err := schemaMap(schema).Input(input, rc)
  2632  	if err != nil {
  2633  		t.Fatalf("err: %s", err)
  2634  	}
  2635  
  2636  	expected := map[string]interface{}{}
  2637  
  2638  	if !reflect.DeepEqual(expected, actual.Config) {
  2639  		t.Fatalf("got: %#v\nexpected: %#v", actual.Config, expected)
  2640  	}
  2641  }
  2642  
  2643  func TestSchemaMap_InputDeprecated(t *testing.T) {
  2644  	emptyConfig := make(map[string]interface{})
  2645  	c, err := config.NewRawConfig(emptyConfig)
  2646  	if err != nil {
  2647  		t.Fatalf("err: %s", err)
  2648  	}
  2649  	rc := terraform.NewResourceConfig(c)
  2650  	rc.Config = make(map[string]interface{})
  2651  
  2652  	input := new(terraform.MockUIInput)
  2653  	input.InputFn = func(opts *terraform.InputOpts) (string, error) {
  2654  		t.Fatalf("InputFn should not be called on: %#v", opts)
  2655  		return "", nil
  2656  	}
  2657  
  2658  	schema := map[string]*Schema{
  2659  		"availability_zone": &Schema{
  2660  			Type:       TypeString,
  2661  			Deprecated: "long gone",
  2662  			Optional:   true,
  2663  		},
  2664  	}
  2665  	actual, err := schemaMap(schema).Input(input, rc)
  2666  	if err != nil {
  2667  		t.Fatalf("err: %s", err)
  2668  	}
  2669  
  2670  	expected := map[string]interface{}{}
  2671  
  2672  	if !reflect.DeepEqual(expected, actual.Config) {
  2673  		t.Fatalf("got: %#v\nexpected: %#v", actual.Config, expected)
  2674  	}
  2675  }
  2676  
  2677  func TestSchemaMap_InternalValidate(t *testing.T) {
  2678  	cases := map[string]struct {
  2679  		In  map[string]*Schema
  2680  		Err bool
  2681  	}{
  2682  		"nothing": {
  2683  			nil,
  2684  			false,
  2685  		},
  2686  
  2687  		"Both optional and required": {
  2688  			map[string]*Schema{
  2689  				"foo": &Schema{
  2690  					Type:     TypeInt,
  2691  					Optional: true,
  2692  					Required: true,
  2693  				},
  2694  			},
  2695  			true,
  2696  		},
  2697  
  2698  		"No optional and no required": {
  2699  			map[string]*Schema{
  2700  				"foo": &Schema{
  2701  					Type: TypeInt,
  2702  				},
  2703  			},
  2704  			true,
  2705  		},
  2706  
  2707  		"Missing Type": {
  2708  			map[string]*Schema{
  2709  				"foo": &Schema{
  2710  					Required: true,
  2711  				},
  2712  			},
  2713  			true,
  2714  		},
  2715  
  2716  		"Required but computed": {
  2717  			map[string]*Schema{
  2718  				"foo": &Schema{
  2719  					Type:     TypeInt,
  2720  					Required: true,
  2721  					Computed: true,
  2722  				},
  2723  			},
  2724  			true,
  2725  		},
  2726  
  2727  		"Looks good": {
  2728  			map[string]*Schema{
  2729  				"foo": &Schema{
  2730  					Type:     TypeString,
  2731  					Required: true,
  2732  				},
  2733  			},
  2734  			false,
  2735  		},
  2736  
  2737  		"Computed but has default": {
  2738  			map[string]*Schema{
  2739  				"foo": &Schema{
  2740  					Type:     TypeInt,
  2741  					Optional: true,
  2742  					Computed: true,
  2743  					Default:  "foo",
  2744  				},
  2745  			},
  2746  			true,
  2747  		},
  2748  
  2749  		"Required but has default": {
  2750  			map[string]*Schema{
  2751  				"foo": &Schema{
  2752  					Type:     TypeInt,
  2753  					Optional: true,
  2754  					Required: true,
  2755  					Default:  "foo",
  2756  				},
  2757  			},
  2758  			true,
  2759  		},
  2760  
  2761  		"List element not set": {
  2762  			map[string]*Schema{
  2763  				"foo": &Schema{
  2764  					Type: TypeList,
  2765  				},
  2766  			},
  2767  			true,
  2768  		},
  2769  
  2770  		"List default": {
  2771  			map[string]*Schema{
  2772  				"foo": &Schema{
  2773  					Type:    TypeList,
  2774  					Elem:    &Schema{Type: TypeInt},
  2775  					Default: "foo",
  2776  				},
  2777  			},
  2778  			true,
  2779  		},
  2780  
  2781  		"List element computed": {
  2782  			map[string]*Schema{
  2783  				"foo": &Schema{
  2784  					Type:     TypeList,
  2785  					Optional: true,
  2786  					Elem: &Schema{
  2787  						Type:     TypeInt,
  2788  						Computed: true,
  2789  					},
  2790  				},
  2791  			},
  2792  			true,
  2793  		},
  2794  
  2795  		"List element with Set set": {
  2796  			map[string]*Schema{
  2797  				"foo": &Schema{
  2798  					Type:     TypeList,
  2799  					Elem:     &Schema{Type: TypeInt},
  2800  					Set:      func(interface{}) int { return 0 },
  2801  					Optional: true,
  2802  				},
  2803  			},
  2804  			true,
  2805  		},
  2806  
  2807  		"Set element with no Set set": {
  2808  			map[string]*Schema{
  2809  				"foo": &Schema{
  2810  					Type:     TypeSet,
  2811  					Elem:     &Schema{Type: TypeInt},
  2812  					Optional: true,
  2813  				},
  2814  			},
  2815  			false,
  2816  		},
  2817  
  2818  		"Required but computedWhen": {
  2819  			map[string]*Schema{
  2820  				"foo": &Schema{
  2821  					Type:         TypeInt,
  2822  					Required:     true,
  2823  					ComputedWhen: []string{"foo"},
  2824  				},
  2825  			},
  2826  			true,
  2827  		},
  2828  
  2829  		"Conflicting attributes cannot be required": {
  2830  			map[string]*Schema{
  2831  				"blacklist": &Schema{
  2832  					Type:     TypeBool,
  2833  					Required: true,
  2834  				},
  2835  				"whitelist": &Schema{
  2836  					Type:          TypeBool,
  2837  					Optional:      true,
  2838  					ConflictsWith: []string{"blacklist"},
  2839  				},
  2840  			},
  2841  			true,
  2842  		},
  2843  
  2844  		"Attribute with conflicts cannot be required": {
  2845  			map[string]*Schema{
  2846  				"whitelist": &Schema{
  2847  					Type:          TypeBool,
  2848  					Required:      true,
  2849  					ConflictsWith: []string{"blacklist"},
  2850  				},
  2851  			},
  2852  			true,
  2853  		},
  2854  
  2855  		"ConflictsWith cannot be used w/ Computed": {
  2856  			map[string]*Schema{
  2857  				"blacklist": &Schema{
  2858  					Type:     TypeBool,
  2859  					Computed: true,
  2860  				},
  2861  				"whitelist": &Schema{
  2862  					Type:          TypeBool,
  2863  					Optional:      true,
  2864  					ConflictsWith: []string{"blacklist"},
  2865  				},
  2866  			},
  2867  			true,
  2868  		},
  2869  
  2870  		"ConflictsWith cannot be used w/ ComputedWhen": {
  2871  			map[string]*Schema{
  2872  				"blacklist": &Schema{
  2873  					Type:         TypeBool,
  2874  					ComputedWhen: []string{"foor"},
  2875  				},
  2876  				"whitelist": &Schema{
  2877  					Type:          TypeBool,
  2878  					Required:      true,
  2879  					ConflictsWith: []string{"blacklist"},
  2880  				},
  2881  			},
  2882  			true,
  2883  		},
  2884  
  2885  		"Sub-resource invalid": {
  2886  			map[string]*Schema{
  2887  				"foo": &Schema{
  2888  					Type:     TypeList,
  2889  					Optional: true,
  2890  					Elem: &Resource{
  2891  						Schema: map[string]*Schema{
  2892  							"foo": new(Schema),
  2893  						},
  2894  					},
  2895  				},
  2896  			},
  2897  			true,
  2898  		},
  2899  
  2900  		"Sub-resource valid": {
  2901  			map[string]*Schema{
  2902  				"foo": &Schema{
  2903  					Type:     TypeList,
  2904  					Optional: true,
  2905  					Elem: &Resource{
  2906  						Schema: map[string]*Schema{
  2907  							"foo": &Schema{
  2908  								Type:     TypeInt,
  2909  								Optional: true,
  2910  							},
  2911  						},
  2912  					},
  2913  				},
  2914  			},
  2915  			false,
  2916  		},
  2917  
  2918  		"ValidateFunc on non-primitive": {
  2919  			map[string]*Schema{
  2920  				"foo": &Schema{
  2921  					Type:     TypeSet,
  2922  					Required: true,
  2923  					ValidateFunc: func(v interface{}, k string) (ws []string, es []error) {
  2924  						return
  2925  					},
  2926  				},
  2927  			},
  2928  			true,
  2929  		},
  2930  	}
  2931  
  2932  	for tn, tc := range cases {
  2933  		err := schemaMap(tc.In).InternalValidate(schemaMap{})
  2934  		if err != nil != tc.Err {
  2935  			if tc.Err {
  2936  				t.Fatalf("%q: Expected error did not occur:\n\n%#v", tn, tc.In)
  2937  			}
  2938  			t.Fatalf("%q: Unexpected error occurred:\n\n%#v", tn, tc.In)
  2939  		}
  2940  	}
  2941  
  2942  }
  2943  
  2944  func TestSchemaMap_DiffSuppress(t *testing.T) {
  2945  	cases := map[string]struct {
  2946  		Schema          map[string]*Schema
  2947  		State           *terraform.InstanceState
  2948  		Config          map[string]interface{}
  2949  		ConfigVariables map[string]ast.Variable
  2950  		ExpectedDiff    *terraform.InstanceDiff
  2951  		Err             bool
  2952  	}{
  2953  		"#0 - Suppress otherwise valid diff by returning true": {
  2954  			Schema: map[string]*Schema{
  2955  				"availability_zone": {
  2956  					Type:     TypeString,
  2957  					Optional: true,
  2958  					DiffSuppressFunc: func(k, old, new string, d *ResourceData) bool {
  2959  						// Always suppress any diff
  2960  						return true
  2961  					},
  2962  				},
  2963  			},
  2964  
  2965  			State: nil,
  2966  
  2967  			Config: map[string]interface{}{
  2968  				"availability_zone": "foo",
  2969  			},
  2970  
  2971  			ExpectedDiff: nil,
  2972  
  2973  			Err: false,
  2974  		},
  2975  
  2976  		"#1 - Don't suppress diff by returning false": {
  2977  			Schema: map[string]*Schema{
  2978  				"availability_zone": {
  2979  					Type:     TypeString,
  2980  					Optional: true,
  2981  					DiffSuppressFunc: func(k, old, new string, d *ResourceData) bool {
  2982  						// Always suppress any diff
  2983  						return false
  2984  					},
  2985  				},
  2986  			},
  2987  
  2988  			State: nil,
  2989  
  2990  			Config: map[string]interface{}{
  2991  				"availability_zone": "foo",
  2992  			},
  2993  
  2994  			ExpectedDiff: &terraform.InstanceDiff{
  2995  				Attributes: map[string]*terraform.ResourceAttrDiff{
  2996  					"availability_zone": {
  2997  						Old: "",
  2998  						New: "foo",
  2999  					},
  3000  				},
  3001  			},
  3002  
  3003  			Err: false,
  3004  		},
  3005  
  3006  		"Default with suppress makes no diff": {
  3007  			Schema: map[string]*Schema{
  3008  				"availability_zone": {
  3009  					Type:     TypeString,
  3010  					Optional: true,
  3011  					Default:  "foo",
  3012  					DiffSuppressFunc: func(k, old, new string, d *ResourceData) bool {
  3013  						return true
  3014  					},
  3015  				},
  3016  			},
  3017  
  3018  			State: nil,
  3019  
  3020  			Config: map[string]interface{}{},
  3021  
  3022  			ExpectedDiff: nil,
  3023  
  3024  			Err: false,
  3025  		},
  3026  
  3027  		"Default with false suppress makes diff": {
  3028  			Schema: map[string]*Schema{
  3029  				"availability_zone": {
  3030  					Type:     TypeString,
  3031  					Optional: true,
  3032  					Default:  "foo",
  3033  					DiffSuppressFunc: func(k, old, new string, d *ResourceData) bool {
  3034  						return false
  3035  					},
  3036  				},
  3037  			},
  3038  
  3039  			State: nil,
  3040  
  3041  			Config: map[string]interface{}{},
  3042  
  3043  			ExpectedDiff: &terraform.InstanceDiff{
  3044  				Attributes: map[string]*terraform.ResourceAttrDiff{
  3045  					"availability_zone": {
  3046  						Old: "",
  3047  						New: "foo",
  3048  					},
  3049  				},
  3050  			},
  3051  
  3052  			Err: false,
  3053  		},
  3054  	}
  3055  
  3056  	for tn, tc := range cases {
  3057  		t.Run(tn, func(t *testing.T) {
  3058  			c, err := config.NewRawConfig(tc.Config)
  3059  			if err != nil {
  3060  				t.Fatalf("#%q err: %s", tn, err)
  3061  			}
  3062  
  3063  			if len(tc.ConfigVariables) > 0 {
  3064  				if err := c.Interpolate(tc.ConfigVariables); err != nil {
  3065  					t.Fatalf("#%q err: %s", tn, err)
  3066  				}
  3067  			}
  3068  
  3069  			d, err := schemaMap(tc.Schema).Diff(
  3070  				tc.State, terraform.NewResourceConfig(c))
  3071  			if err != nil != tc.Err {
  3072  				t.Fatalf("#%q err: %s", tn, err)
  3073  			}
  3074  
  3075  			if !reflect.DeepEqual(tc.ExpectedDiff, d) {
  3076  				t.Fatalf("#%q:\n\nexpected:\n%#v\n\ngot:\n%#v", tn, tc.ExpectedDiff, d)
  3077  			}
  3078  		})
  3079  	}
  3080  }
  3081  
  3082  func TestSchemaMap_Validate(t *testing.T) {
  3083  	cases := map[string]struct {
  3084  		Schema   map[string]*Schema
  3085  		Config   map[string]interface{}
  3086  		Vars     map[string]string
  3087  		Err      bool
  3088  		Errors   []error
  3089  		Warnings []string
  3090  	}{
  3091  		"Good": {
  3092  			Schema: map[string]*Schema{
  3093  				"availability_zone": &Schema{
  3094  					Type:     TypeString,
  3095  					Optional: true,
  3096  					Computed: true,
  3097  					ForceNew: true,
  3098  				},
  3099  			},
  3100  
  3101  			Config: map[string]interface{}{
  3102  				"availability_zone": "foo",
  3103  			},
  3104  		},
  3105  
  3106  		"Good, because the var is not set and that error will come elsewhere": {
  3107  			Schema: map[string]*Schema{
  3108  				"size": &Schema{
  3109  					Type:     TypeInt,
  3110  					Required: true,
  3111  				},
  3112  			},
  3113  
  3114  			Config: map[string]interface{}{
  3115  				"size": "${var.foo}",
  3116  			},
  3117  
  3118  			Vars: map[string]string{
  3119  				"var.foo": config.UnknownVariableValue,
  3120  			},
  3121  		},
  3122  
  3123  		"Required field not set": {
  3124  			Schema: map[string]*Schema{
  3125  				"availability_zone": &Schema{
  3126  					Type:     TypeString,
  3127  					Required: true,
  3128  				},
  3129  			},
  3130  
  3131  			Config: map[string]interface{}{},
  3132  
  3133  			Err: true,
  3134  		},
  3135  
  3136  		"Invalid basic type": {
  3137  			Schema: map[string]*Schema{
  3138  				"port": &Schema{
  3139  					Type:     TypeInt,
  3140  					Required: true,
  3141  				},
  3142  			},
  3143  
  3144  			Config: map[string]interface{}{
  3145  				"port": "I am invalid",
  3146  			},
  3147  
  3148  			Err: true,
  3149  		},
  3150  
  3151  		"Invalid complex type": {
  3152  			Schema: map[string]*Schema{
  3153  				"user_data": &Schema{
  3154  					Type:     TypeString,
  3155  					Optional: true,
  3156  				},
  3157  			},
  3158  
  3159  			Config: map[string]interface{}{
  3160  				"user_data": []interface{}{
  3161  					map[string]interface{}{
  3162  						"foo": "bar",
  3163  					},
  3164  				},
  3165  			},
  3166  
  3167  			Err: true,
  3168  		},
  3169  
  3170  		"Bad type, interpolated": {
  3171  			Schema: map[string]*Schema{
  3172  				"size": &Schema{
  3173  					Type:     TypeInt,
  3174  					Required: true,
  3175  				},
  3176  			},
  3177  
  3178  			Config: map[string]interface{}{
  3179  				"size": "${var.foo}",
  3180  			},
  3181  
  3182  			Vars: map[string]string{
  3183  				"var.foo": "nope",
  3184  			},
  3185  
  3186  			Err: true,
  3187  		},
  3188  
  3189  		"Required but has DefaultFunc": {
  3190  			Schema: map[string]*Schema{
  3191  				"availability_zone": &Schema{
  3192  					Type:     TypeString,
  3193  					Required: true,
  3194  					DefaultFunc: func() (interface{}, error) {
  3195  						return "foo", nil
  3196  					},
  3197  				},
  3198  			},
  3199  
  3200  			Config: nil,
  3201  		},
  3202  
  3203  		"Required but has DefaultFunc return nil": {
  3204  			Schema: map[string]*Schema{
  3205  				"availability_zone": &Schema{
  3206  					Type:     TypeString,
  3207  					Required: true,
  3208  					DefaultFunc: func() (interface{}, error) {
  3209  						return nil, nil
  3210  					},
  3211  				},
  3212  			},
  3213  
  3214  			Config: nil,
  3215  
  3216  			Err: true,
  3217  		},
  3218  
  3219  		"Optional sub-resource": {
  3220  			Schema: map[string]*Schema{
  3221  				"ingress": &Schema{
  3222  					Type: TypeList,
  3223  					Elem: &Resource{
  3224  						Schema: map[string]*Schema{
  3225  							"from": &Schema{
  3226  								Type:     TypeInt,
  3227  								Required: true,
  3228  							},
  3229  						},
  3230  					},
  3231  				},
  3232  			},
  3233  
  3234  			Config: map[string]interface{}{},
  3235  
  3236  			Err: false,
  3237  		},
  3238  
  3239  		"Sub-resource is the wrong type": {
  3240  			Schema: map[string]*Schema{
  3241  				"ingress": &Schema{
  3242  					Type:     TypeList,
  3243  					Required: true,
  3244  					Elem: &Resource{
  3245  						Schema: map[string]*Schema{
  3246  							"from": &Schema{
  3247  								Type:     TypeInt,
  3248  								Required: true,
  3249  							},
  3250  						},
  3251  					},
  3252  				},
  3253  			},
  3254  
  3255  			Config: map[string]interface{}{
  3256  				"ingress": []interface{}{"foo"},
  3257  			},
  3258  
  3259  			Err: true,
  3260  		},
  3261  
  3262  		"Not a list": {
  3263  			Schema: map[string]*Schema{
  3264  				"ingress": &Schema{
  3265  					Type: TypeList,
  3266  					Elem: &Resource{
  3267  						Schema: map[string]*Schema{
  3268  							"from": &Schema{
  3269  								Type:     TypeInt,
  3270  								Required: true,
  3271  							},
  3272  						},
  3273  					},
  3274  				},
  3275  			},
  3276  
  3277  			Config: map[string]interface{}{
  3278  				"ingress": "foo",
  3279  			},
  3280  
  3281  			Err: true,
  3282  		},
  3283  
  3284  		"Required sub-resource field": {
  3285  			Schema: map[string]*Schema{
  3286  				"ingress": &Schema{
  3287  					Type: TypeList,
  3288  					Elem: &Resource{
  3289  						Schema: map[string]*Schema{
  3290  							"from": &Schema{
  3291  								Type:     TypeInt,
  3292  								Required: true,
  3293  							},
  3294  						},
  3295  					},
  3296  				},
  3297  			},
  3298  
  3299  			Config: map[string]interface{}{
  3300  				"ingress": []interface{}{
  3301  					map[string]interface{}{},
  3302  				},
  3303  			},
  3304  
  3305  			Err: true,
  3306  		},
  3307  
  3308  		"Good sub-resource": {
  3309  			Schema: map[string]*Schema{
  3310  				"ingress": &Schema{
  3311  					Type:     TypeList,
  3312  					Optional: true,
  3313  					Elem: &Resource{
  3314  						Schema: map[string]*Schema{
  3315  							"from": &Schema{
  3316  								Type:     TypeInt,
  3317  								Required: true,
  3318  							},
  3319  						},
  3320  					},
  3321  				},
  3322  			},
  3323  
  3324  			Config: map[string]interface{}{
  3325  				"ingress": []interface{}{
  3326  					map[string]interface{}{
  3327  						"from": 80,
  3328  					},
  3329  				},
  3330  			},
  3331  
  3332  			Err: false,
  3333  		},
  3334  
  3335  		"Invalid/unknown field": {
  3336  			Schema: map[string]*Schema{
  3337  				"availability_zone": &Schema{
  3338  					Type:     TypeString,
  3339  					Optional: true,
  3340  					Computed: true,
  3341  					ForceNew: true,
  3342  				},
  3343  			},
  3344  
  3345  			Config: map[string]interface{}{
  3346  				"foo": "bar",
  3347  			},
  3348  
  3349  			Err: true,
  3350  		},
  3351  
  3352  		"Invalid/unknown field with computed value": {
  3353  			Schema: map[string]*Schema{
  3354  				"availability_zone": &Schema{
  3355  					Type:     TypeString,
  3356  					Optional: true,
  3357  					Computed: true,
  3358  					ForceNew: true,
  3359  				},
  3360  			},
  3361  
  3362  			Config: map[string]interface{}{
  3363  				"foo": "${var.foo}",
  3364  			},
  3365  
  3366  			Vars: map[string]string{
  3367  				"var.foo": config.UnknownVariableValue,
  3368  			},
  3369  
  3370  			Err: true,
  3371  		},
  3372  
  3373  		"Computed field set": {
  3374  			Schema: map[string]*Schema{
  3375  				"availability_zone": &Schema{
  3376  					Type:     TypeString,
  3377  					Computed: true,
  3378  				},
  3379  			},
  3380  
  3381  			Config: map[string]interface{}{
  3382  				"availability_zone": "bar",
  3383  			},
  3384  
  3385  			Err: true,
  3386  		},
  3387  
  3388  		"Not a set": {
  3389  			Schema: map[string]*Schema{
  3390  				"ports": &Schema{
  3391  					Type:     TypeSet,
  3392  					Required: true,
  3393  					Elem:     &Schema{Type: TypeInt},
  3394  					Set: func(a interface{}) int {
  3395  						return a.(int)
  3396  					},
  3397  				},
  3398  			},
  3399  
  3400  			Config: map[string]interface{}{
  3401  				"ports": "foo",
  3402  			},
  3403  
  3404  			Err: true,
  3405  		},
  3406  
  3407  		"Maps": {
  3408  			Schema: map[string]*Schema{
  3409  				"user_data": &Schema{
  3410  					Type:     TypeMap,
  3411  					Optional: true,
  3412  				},
  3413  			},
  3414  
  3415  			Config: map[string]interface{}{
  3416  				"user_data": "foo",
  3417  			},
  3418  
  3419  			Err: true,
  3420  		},
  3421  
  3422  		"Good map: data surrounded by extra slice": {
  3423  			Schema: map[string]*Schema{
  3424  				"user_data": &Schema{
  3425  					Type:     TypeMap,
  3426  					Optional: true,
  3427  				},
  3428  			},
  3429  
  3430  			Config: map[string]interface{}{
  3431  				"user_data": []interface{}{
  3432  					map[string]interface{}{
  3433  						"foo": "bar",
  3434  					},
  3435  				},
  3436  			},
  3437  		},
  3438  
  3439  		"Good map": {
  3440  			Schema: map[string]*Schema{
  3441  				"user_data": &Schema{
  3442  					Type:     TypeMap,
  3443  					Optional: true,
  3444  				},
  3445  			},
  3446  
  3447  			Config: map[string]interface{}{
  3448  				"user_data": map[string]interface{}{
  3449  					"foo": "bar",
  3450  				},
  3451  			},
  3452  		},
  3453  
  3454  		"Bad map: just a slice": {
  3455  			Schema: map[string]*Schema{
  3456  				"user_data": &Schema{
  3457  					Type:     TypeMap,
  3458  					Optional: true,
  3459  				},
  3460  			},
  3461  
  3462  			Config: map[string]interface{}{
  3463  				"user_data": []interface{}{
  3464  					"foo",
  3465  				},
  3466  			},
  3467  
  3468  			Err: true,
  3469  		},
  3470  
  3471  		"Good set: config has slice with single interpolated value": {
  3472  			Schema: map[string]*Schema{
  3473  				"security_groups": &Schema{
  3474  					Type:     TypeSet,
  3475  					Optional: true,
  3476  					Computed: true,
  3477  					ForceNew: true,
  3478  					Elem:     &Schema{Type: TypeString},
  3479  					Set: func(v interface{}) int {
  3480  						return len(v.(string))
  3481  					},
  3482  				},
  3483  			},
  3484  
  3485  			Config: map[string]interface{}{
  3486  				"security_groups": []interface{}{"${var.foo}"},
  3487  			},
  3488  
  3489  			Err: false,
  3490  		},
  3491  
  3492  		"Bad set: config has single interpolated value": {
  3493  			Schema: map[string]*Schema{
  3494  				"security_groups": &Schema{
  3495  					Type:     TypeSet,
  3496  					Optional: true,
  3497  					Computed: true,
  3498  					ForceNew: true,
  3499  					Elem:     &Schema{Type: TypeString},
  3500  				},
  3501  			},
  3502  
  3503  			Config: map[string]interface{}{
  3504  				"security_groups": "${var.foo}",
  3505  			},
  3506  
  3507  			Err: true,
  3508  		},
  3509  
  3510  		"Bad, subresource should not allow unknown elements": {
  3511  			Schema: map[string]*Schema{
  3512  				"ingress": &Schema{
  3513  					Type:     TypeList,
  3514  					Optional: true,
  3515  					Elem: &Resource{
  3516  						Schema: map[string]*Schema{
  3517  							"port": &Schema{
  3518  								Type:     TypeInt,
  3519  								Required: true,
  3520  							},
  3521  						},
  3522  					},
  3523  				},
  3524  			},
  3525  
  3526  			Config: map[string]interface{}{
  3527  				"ingress": []interface{}{
  3528  					map[string]interface{}{
  3529  						"port":  80,
  3530  						"other": "yes",
  3531  					},
  3532  				},
  3533  			},
  3534  
  3535  			Err: true,
  3536  		},
  3537  
  3538  		"Bad, subresource should not allow invalid types": {
  3539  			Schema: map[string]*Schema{
  3540  				"ingress": &Schema{
  3541  					Type:     TypeList,
  3542  					Optional: true,
  3543  					Elem: &Resource{
  3544  						Schema: map[string]*Schema{
  3545  							"port": &Schema{
  3546  								Type:     TypeInt,
  3547  								Required: true,
  3548  							},
  3549  						},
  3550  					},
  3551  				},
  3552  			},
  3553  
  3554  			Config: map[string]interface{}{
  3555  				"ingress": []interface{}{
  3556  					map[string]interface{}{
  3557  						"port": "bad",
  3558  					},
  3559  				},
  3560  			},
  3561  
  3562  			Err: true,
  3563  		},
  3564  
  3565  		"Bad, should not allow lists to be assigned to string attributes": {
  3566  			Schema: map[string]*Schema{
  3567  				"availability_zone": &Schema{
  3568  					Type:     TypeString,
  3569  					Required: true,
  3570  				},
  3571  			},
  3572  
  3573  			Config: map[string]interface{}{
  3574  				"availability_zone": []interface{}{"foo", "bar", "baz"},
  3575  			},
  3576  
  3577  			Err: true,
  3578  		},
  3579  
  3580  		"Bad, should not allow maps to be assigned to string attributes": {
  3581  			Schema: map[string]*Schema{
  3582  				"availability_zone": &Schema{
  3583  					Type:     TypeString,
  3584  					Required: true,
  3585  				},
  3586  			},
  3587  
  3588  			Config: map[string]interface{}{
  3589  				"availability_zone": map[string]interface{}{"foo": "bar", "baz": "thing"},
  3590  			},
  3591  
  3592  			Err: true,
  3593  		},
  3594  
  3595  		"Deprecated attribute usage generates warning, but not error": {
  3596  			Schema: map[string]*Schema{
  3597  				"old_news": &Schema{
  3598  					Type:       TypeString,
  3599  					Optional:   true,
  3600  					Deprecated: "please use 'new_news' instead",
  3601  				},
  3602  			},
  3603  
  3604  			Config: map[string]interface{}{
  3605  				"old_news": "extra extra!",
  3606  			},
  3607  
  3608  			Err: false,
  3609  
  3610  			Warnings: []string{
  3611  				"\"old_news\": [DEPRECATED] please use 'new_news' instead",
  3612  			},
  3613  		},
  3614  
  3615  		"Deprecated generates no warnings if attr not used": {
  3616  			Schema: map[string]*Schema{
  3617  				"old_news": &Schema{
  3618  					Type:       TypeString,
  3619  					Optional:   true,
  3620  					Deprecated: "please use 'new_news' instead",
  3621  				},
  3622  			},
  3623  
  3624  			Err: false,
  3625  
  3626  			Warnings: nil,
  3627  		},
  3628  
  3629  		"Removed attribute usage generates error": {
  3630  			Schema: map[string]*Schema{
  3631  				"long_gone": &Schema{
  3632  					Type:     TypeString,
  3633  					Optional: true,
  3634  					Removed:  "no longer supported by Cloud API",
  3635  				},
  3636  			},
  3637  
  3638  			Config: map[string]interface{}{
  3639  				"long_gone": "still here!",
  3640  			},
  3641  
  3642  			Err: true,
  3643  			Errors: []error{
  3644  				fmt.Errorf("\"long_gone\": [REMOVED] no longer supported by Cloud API"),
  3645  			},
  3646  		},
  3647  
  3648  		"Removed generates no errors if attr not used": {
  3649  			Schema: map[string]*Schema{
  3650  				"long_gone": &Schema{
  3651  					Type:     TypeString,
  3652  					Optional: true,
  3653  					Removed:  "no longer supported by Cloud API",
  3654  				},
  3655  			},
  3656  
  3657  			Err: false,
  3658  		},
  3659  
  3660  		"Conflicting attributes generate error": {
  3661  			Schema: map[string]*Schema{
  3662  				"whitelist": &Schema{
  3663  					Type:     TypeString,
  3664  					Optional: true,
  3665  				},
  3666  				"blacklist": &Schema{
  3667  					Type:          TypeString,
  3668  					Optional:      true,
  3669  					ConflictsWith: []string{"whitelist"},
  3670  				},
  3671  			},
  3672  
  3673  			Config: map[string]interface{}{
  3674  				"whitelist": "white-val",
  3675  				"blacklist": "black-val",
  3676  			},
  3677  
  3678  			Err: true,
  3679  			Errors: []error{
  3680  				fmt.Errorf("\"blacklist\": conflicts with whitelist (\"white-val\")"),
  3681  			},
  3682  		},
  3683  
  3684  		"Required attribute & undefined conflicting optional are good": {
  3685  			Schema: map[string]*Schema{
  3686  				"required_att": &Schema{
  3687  					Type:     TypeString,
  3688  					Required: true,
  3689  				},
  3690  				"optional_att": &Schema{
  3691  					Type:          TypeString,
  3692  					Optional:      true,
  3693  					ConflictsWith: []string{"required_att"},
  3694  				},
  3695  			},
  3696  
  3697  			Config: map[string]interface{}{
  3698  				"required_att": "required-val",
  3699  			},
  3700  
  3701  			Err: false,
  3702  		},
  3703  
  3704  		"Required conflicting attribute & defined optional generate error": {
  3705  			Schema: map[string]*Schema{
  3706  				"required_att": &Schema{
  3707  					Type:     TypeString,
  3708  					Required: true,
  3709  				},
  3710  				"optional_att": &Schema{
  3711  					Type:          TypeString,
  3712  					Optional:      true,
  3713  					ConflictsWith: []string{"required_att"},
  3714  				},
  3715  			},
  3716  
  3717  			Config: map[string]interface{}{
  3718  				"required_att": "required-val",
  3719  				"optional_att": "optional-val",
  3720  			},
  3721  
  3722  			Err: true,
  3723  			Errors: []error{
  3724  				fmt.Errorf(`"optional_att": conflicts with required_att ("required-val")`),
  3725  			},
  3726  		},
  3727  
  3728  		"Good with ValidateFunc": {
  3729  			Schema: map[string]*Schema{
  3730  				"validate_me": &Schema{
  3731  					Type:     TypeString,
  3732  					Required: true,
  3733  					ValidateFunc: func(v interface{}, k string) (ws []string, es []error) {
  3734  						return
  3735  					},
  3736  				},
  3737  			},
  3738  			Config: map[string]interface{}{
  3739  				"validate_me": "valid",
  3740  			},
  3741  			Err: false,
  3742  		},
  3743  
  3744  		"Bad with ValidateFunc": {
  3745  			Schema: map[string]*Schema{
  3746  				"validate_me": &Schema{
  3747  					Type:     TypeString,
  3748  					Required: true,
  3749  					ValidateFunc: func(v interface{}, k string) (ws []string, es []error) {
  3750  						es = append(es, fmt.Errorf("something is not right here"))
  3751  						return
  3752  					},
  3753  				},
  3754  			},
  3755  			Config: map[string]interface{}{
  3756  				"validate_me": "invalid",
  3757  			},
  3758  			Err: true,
  3759  			Errors: []error{
  3760  				fmt.Errorf(`something is not right here`),
  3761  			},
  3762  		},
  3763  
  3764  		"ValidateFunc not called when type does not match": {
  3765  			Schema: map[string]*Schema{
  3766  				"number": &Schema{
  3767  					Type:     TypeInt,
  3768  					Required: true,
  3769  					ValidateFunc: func(v interface{}, k string) (ws []string, es []error) {
  3770  						t.Fatalf("Should not have gotten validate call")
  3771  						return
  3772  					},
  3773  				},
  3774  			},
  3775  			Config: map[string]interface{}{
  3776  				"number": "NaN",
  3777  			},
  3778  			Err: true,
  3779  		},
  3780  
  3781  		"ValidateFunc gets decoded type": {
  3782  			Schema: map[string]*Schema{
  3783  				"maybe": &Schema{
  3784  					Type:     TypeBool,
  3785  					Required: true,
  3786  					ValidateFunc: func(v interface{}, k string) (ws []string, es []error) {
  3787  						if _, ok := v.(bool); !ok {
  3788  							t.Fatalf("Expected bool, got: %#v", v)
  3789  						}
  3790  						return
  3791  					},
  3792  				},
  3793  			},
  3794  			Config: map[string]interface{}{
  3795  				"maybe": "true",
  3796  			},
  3797  		},
  3798  
  3799  		"ValidateFunc is not called with a computed value": {
  3800  			Schema: map[string]*Schema{
  3801  				"validate_me": &Schema{
  3802  					Type:     TypeString,
  3803  					Required: true,
  3804  					ValidateFunc: func(v interface{}, k string) (ws []string, es []error) {
  3805  						es = append(es, fmt.Errorf("something is not right here"))
  3806  						return
  3807  					},
  3808  				},
  3809  			},
  3810  			Config: map[string]interface{}{
  3811  				"validate_me": "${var.foo}",
  3812  			},
  3813  			Vars: map[string]string{
  3814  				"var.foo": config.UnknownVariableValue,
  3815  			},
  3816  
  3817  			Err: false,
  3818  		},
  3819  	}
  3820  
  3821  	for tn, tc := range cases {
  3822  		c, err := config.NewRawConfig(tc.Config)
  3823  		if err != nil {
  3824  			t.Fatalf("err: %s", err)
  3825  		}
  3826  		if tc.Vars != nil {
  3827  			vars := make(map[string]ast.Variable)
  3828  			for k, v := range tc.Vars {
  3829  				vars[k] = ast.Variable{Value: v, Type: ast.TypeString}
  3830  			}
  3831  
  3832  			if err := c.Interpolate(vars); err != nil {
  3833  				t.Fatalf("err: %s", err)
  3834  			}
  3835  		}
  3836  
  3837  		ws, es := schemaMap(tc.Schema).Validate(terraform.NewResourceConfig(c))
  3838  		if len(es) > 0 != tc.Err {
  3839  			if len(es) == 0 {
  3840  				t.Errorf("%q: no errors", tn)
  3841  			}
  3842  
  3843  			for _, e := range es {
  3844  				t.Errorf("%q: err: %s", tn, e)
  3845  			}
  3846  
  3847  			t.FailNow()
  3848  		}
  3849  
  3850  		if !reflect.DeepEqual(ws, tc.Warnings) {
  3851  			t.Fatalf("%q: warnings:\n\nexpected: %#v\ngot:%#v", tn, tc.Warnings, ws)
  3852  		}
  3853  
  3854  		if tc.Errors != nil {
  3855  			if !reflect.DeepEqual(es, tc.Errors) {
  3856  				t.Fatalf("%q: errors:\n\nexpected: %q\ngot: %q", tn, tc.Errors, es)
  3857  			}
  3858  		}
  3859  	}
  3860  }
  3861  
  3862  func TestSchemaSet_ValidateMaxItems(t *testing.T) {
  3863  	cases := map[string]struct {
  3864  		Schema          map[string]*Schema
  3865  		State           *terraform.InstanceState
  3866  		Config          map[string]interface{}
  3867  		ConfigVariables map[string]string
  3868  		Diff            *terraform.InstanceDiff
  3869  		Err             bool
  3870  		Errors          []error
  3871  	}{
  3872  		"#0": {
  3873  			Schema: map[string]*Schema{
  3874  				"aliases": &Schema{
  3875  					Type:     TypeSet,
  3876  					Optional: true,
  3877  					MaxItems: 1,
  3878  					Elem:     &Schema{Type: TypeString},
  3879  				},
  3880  			},
  3881  			State: nil,
  3882  			Config: map[string]interface{}{
  3883  				"aliases": []interface{}{"foo", "bar"},
  3884  			},
  3885  			Diff: nil,
  3886  			Err:  true,
  3887  			Errors: []error{
  3888  				fmt.Errorf("aliases: attribute supports 1 item maximum, config has 2 declared"),
  3889  			},
  3890  		},
  3891  		"#1": {
  3892  			Schema: map[string]*Schema{
  3893  				"aliases": &Schema{
  3894  					Type:     TypeSet,
  3895  					Optional: true,
  3896  					Elem:     &Schema{Type: TypeString},
  3897  				},
  3898  			},
  3899  			State: nil,
  3900  			Config: map[string]interface{}{
  3901  				"aliases": []interface{}{"foo", "bar"},
  3902  			},
  3903  			Diff:   nil,
  3904  			Err:    false,
  3905  			Errors: nil,
  3906  		},
  3907  		"#2": {
  3908  			Schema: map[string]*Schema{
  3909  				"aliases": &Schema{
  3910  					Type:     TypeSet,
  3911  					Optional: true,
  3912  					MaxItems: 1,
  3913  					Elem:     &Schema{Type: TypeString},
  3914  				},
  3915  			},
  3916  			State: nil,
  3917  			Config: map[string]interface{}{
  3918  				"aliases": []interface{}{"foo"},
  3919  			},
  3920  			Diff:   nil,
  3921  			Err:    false,
  3922  			Errors: nil,
  3923  		},
  3924  	}
  3925  
  3926  	for tn, tc := range cases {
  3927  		c, err := config.NewRawConfig(tc.Config)
  3928  		if err != nil {
  3929  			t.Fatalf("%q: err: %s", tn, err)
  3930  		}
  3931  		_, es := schemaMap(tc.Schema).Validate(terraform.NewResourceConfig(c))
  3932  
  3933  		if len(es) > 0 != tc.Err {
  3934  			if len(es) == 0 {
  3935  				t.Errorf("%q: no errors", tn)
  3936  			}
  3937  
  3938  			for _, e := range es {
  3939  				t.Errorf("%q: err: %s", tn, e)
  3940  			}
  3941  
  3942  			t.FailNow()
  3943  		}
  3944  
  3945  		if tc.Errors != nil {
  3946  			if !reflect.DeepEqual(es, tc.Errors) {
  3947  				t.Fatalf("%q: expected: %q\ngot: %q", tn, tc.Errors, es)
  3948  			}
  3949  		}
  3950  	}
  3951  }
  3952  
  3953  func TestSchemaSet_ValidateMinItems(t *testing.T) {
  3954  	cases := map[string]struct {
  3955  		Schema          map[string]*Schema
  3956  		State           *terraform.InstanceState
  3957  		Config          map[string]interface{}
  3958  		ConfigVariables map[string]string
  3959  		Diff            *terraform.InstanceDiff
  3960  		Err             bool
  3961  		Errors          []error
  3962  	}{
  3963  		"#0": {
  3964  			Schema: map[string]*Schema{
  3965  				"aliases": &Schema{
  3966  					Type:     TypeSet,
  3967  					Optional: true,
  3968  					MinItems: 2,
  3969  					Elem:     &Schema{Type: TypeString},
  3970  				},
  3971  			},
  3972  			State: nil,
  3973  			Config: map[string]interface{}{
  3974  				"aliases": []interface{}{"foo", "bar"},
  3975  			},
  3976  			Diff:   nil,
  3977  			Err:    false,
  3978  			Errors: nil,
  3979  		},
  3980  		"#1": {
  3981  			Schema: map[string]*Schema{
  3982  				"aliases": &Schema{
  3983  					Type:     TypeSet,
  3984  					Optional: true,
  3985  					Elem:     &Schema{Type: TypeString},
  3986  				},
  3987  			},
  3988  			State: nil,
  3989  			Config: map[string]interface{}{
  3990  				"aliases": []interface{}{"foo", "bar"},
  3991  			},
  3992  			Diff:   nil,
  3993  			Err:    false,
  3994  			Errors: nil,
  3995  		},
  3996  		"#2": {
  3997  			Schema: map[string]*Schema{
  3998  				"aliases": &Schema{
  3999  					Type:     TypeSet,
  4000  					Optional: true,
  4001  					MinItems: 2,
  4002  					Elem:     &Schema{Type: TypeString},
  4003  				},
  4004  			},
  4005  			State: nil,
  4006  			Config: map[string]interface{}{
  4007  				"aliases": []interface{}{"foo"},
  4008  			},
  4009  			Diff: nil,
  4010  			Err:  true,
  4011  			Errors: []error{
  4012  				fmt.Errorf("aliases: attribute supports 2 item as a minimum, config has 1 declared"),
  4013  			},
  4014  		},
  4015  	}
  4016  
  4017  	for tn, tc := range cases {
  4018  		c, err := config.NewRawConfig(tc.Config)
  4019  		if err != nil {
  4020  			t.Fatalf("%q: err: %s", tn, err)
  4021  		}
  4022  		_, es := schemaMap(tc.Schema).Validate(terraform.NewResourceConfig(c))
  4023  
  4024  		if len(es) > 0 != tc.Err {
  4025  			if len(es) == 0 {
  4026  				t.Errorf("%q: no errors", tn)
  4027  			}
  4028  
  4029  			for _, e := range es {
  4030  				t.Errorf("%q: err: %s", tn, e)
  4031  			}
  4032  
  4033  			t.FailNow()
  4034  		}
  4035  
  4036  		if tc.Errors != nil {
  4037  			if !reflect.DeepEqual(es, tc.Errors) {
  4038  				t.Fatalf("%q: expected: %q\ngot: %q", tn, tc.Errors, es)
  4039  			}
  4040  		}
  4041  	}
  4042  }