github.com/ns1/terraform@v0.7.10-0.20161109153551-8949419bef40/helper/schema/schema_test.go (about)

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