core: Update TestContext2Apply_multiVarComprehensive for new assumptions

This comprehensive test was covering a few different behaviors that are
intentionally different for v0.12:

- Applying the splat operator to a list of resource instances that haven't
  been created yet produces a list of unknown values rather than a single
  unknown list as before. This is important because it allows that list
  to be passed into length().

- Wrapping a splat expression in another round of brackets now produces
  a list of lists, whereas before we had a special case (for compatibility
  with prior to v0.10) that would flatten this away in the schema layer.
This commit is contained in:
Martin Atkins 2018-05-30 16:07:52 -07:00
parent cdce0d7e27
commit 3e64311dc2
3 changed files with 92 additions and 65 deletions

View File

@ -14,6 +14,7 @@ import (
"time" "time"
"github.com/davecgh/go-spew/spew" "github.com/davecgh/go-spew/spew"
"github.com/go-test/deep"
"github.com/zclconf/go-cty/cty" "github.com/zclconf/go-cty/cty"
"github.com/hashicorp/terraform/addrs" "github.com/hashicorp/terraform/addrs"
@ -3514,8 +3515,8 @@ func TestContext2Apply_multiVarComprehensive(t *testing.T) {
"source_names": {Type: cty.List(cty.String), Optional: true}, "source_names": {Type: cty.List(cty.String), Optional: true},
"source_ids_from_func": {Type: cty.List(cty.String), Optional: true}, "source_ids_from_func": {Type: cty.List(cty.String), Optional: true},
"source_names_from_func": {Type: cty.List(cty.String), Optional: true}, "source_names_from_func": {Type: cty.List(cty.String), Optional: true},
"source_ids_wrapped": {Type: cty.List(cty.String), Optional: true}, "source_ids_wrapped": {Type: cty.List(cty.List(cty.String)), Optional: true},
"source_names_wrapped": {Type: cty.List(cty.String), Optional: true}, "source_names_wrapped": {Type: cty.List(cty.List(cty.String)), Optional: true},
"id": {Type: cty.String, Computed: true}, "id": {Type: cty.String, Computed: true},
"name": {Type: cty.String, Computed: true}, "name": {Type: cty.String, Computed: true},
@ -3546,32 +3547,45 @@ func TestContext2Apply_multiVarComprehensive(t *testing.T) {
checkConfig := func(name string, want map[string]interface{}) { checkConfig := func(name string, want map[string]interface{}) {
got := configs[name].Config got := configs[name].Config
if !reflect.DeepEqual(got, want) { t.Run("config for "+name, func(t *testing.T) {
t.Errorf( for _, problem := range deep.Equal(got, want) {
"wrong config for %s\ngot: %s\nwant: %s", t.Errorf(problem)
name, spew.Sdump(got), spew.Sdump(want), }
) })
}
} }
checkConfig("test_thing.multi_count_var.0", map[string]interface{}{ checkConfig("test_thing.multi_count_var.0", map[string]interface{}{
"id": unknownValue(),
"name": unknownValue(),
"source_id": unknownValue(), "source_id": unknownValue(),
"source_name": "test_thing.source.0", "source_name": "test_thing.source.0",
}) })
checkConfig("test_thing.multi_count_var.2", map[string]interface{}{ checkConfig("test_thing.multi_count_var.2", map[string]interface{}{
"id": unknownValue(),
"name": unknownValue(),
"source_id": unknownValue(), "source_id": unknownValue(),
"source_name": "test_thing.source.2", "source_name": "test_thing.source.2",
}) })
checkConfig("test_thing.multi_count_derived.0", map[string]interface{}{ checkConfig("test_thing.multi_count_derived.0", map[string]interface{}{
"id": unknownValue(),
"name": unknownValue(),
"source_id": unknownValue(), "source_id": unknownValue(),
"source_name": "test_thing.source.0", "source_name": "test_thing.source.0",
}) })
checkConfig("test_thing.multi_count_derived.2", map[string]interface{}{ checkConfig("test_thing.multi_count_derived.2", map[string]interface{}{
"id": unknownValue(),
"name": unknownValue(),
"source_id": unknownValue(), "source_id": unknownValue(),
"source_name": "test_thing.source.2", "source_name": "test_thing.source.2",
}) })
checkConfig("test_thing.whole_splat", map[string]interface{}{ checkConfig("test_thing.whole_splat", map[string]interface{}{
"source_ids": unknownValue(), "id": unknownValue(),
"name": unknownValue(),
"source_ids": []interface{}{
unknownValue(),
unknownValue(),
unknownValue(),
},
"source_names": []interface{}{ "source_names": []interface{}{
"test_thing.source.0", "test_thing.source.0",
"test_thing.source.1", "test_thing.source.1",
@ -3584,47 +3598,60 @@ func TestContext2Apply_multiVarComprehensive(t *testing.T) {
"test_thing.source.2", "test_thing.source.2",
}, },
// This one ends up being a list with a single unknown value at this "source_ids_wrapped": []interface{}{
// layer, but is fixed up inside helper/schema. There is a test for []interface{}{
// this inside the "test" provider, since core tests can't exercise unknownValue(),
// helper/schema functionality. unknownValue(),
"source_ids_wrapped": []interface{}{unknownValue()}, unknownValue(),
},
"source_names_wrapped": []interface{}{
"test_thing.source.0",
"test_thing.source.1",
"test_thing.source.2",
}, },
"source_names_wrapped": []interface{}{
[]interface{}{
"test_thing.source.0",
"test_thing.source.1",
"test_thing.source.2",
},
},
"first_source_id": unknownValue(), "first_source_id": unknownValue(),
"first_source_name": "test_thing.source.0", "first_source_name": "test_thing.source.0",
}) })
checkConfig("module.child.test_thing.whole_splat", map[string]interface{}{ checkConfig("module.child.test_thing.whole_splat", map[string]interface{}{
"source_ids": unknownValue(), "id": unknownValue(),
"name": unknownValue(),
"source_ids": []interface{}{
unknownValue(),
unknownValue(),
unknownValue(),
},
"source_names": []interface{}{ "source_names": []interface{}{
"test_thing.source.0", "test_thing.source.0",
"test_thing.source.1", "test_thing.source.1",
"test_thing.source.2", "test_thing.source.2",
}, },
// This one ends up being a list with a single unknown value at this "source_ids_wrapped": []interface{}{
// layer, but is fixed up inside helper/schema. There is a test for []interface{}{
// this inside the "test" provider, since core tests can't exercise unknownValue(),
// helper/schema functionality. unknownValue(),
"source_ids_wrapped": []interface{}{unknownValue()}, unknownValue(),
},
},
"source_names_wrapped": []interface{}{ "source_names_wrapped": []interface{}{
"test_thing.source.0", []interface{}{
"test_thing.source.1", "test_thing.source.0",
"test_thing.source.2", "test_thing.source.1",
"test_thing.source.2",
},
}, },
}) })
state, diags := ctx.Apply() t.Run("apply", func(t *testing.T) {
if diags.HasErrors() { state, diags := ctx.Apply()
t.Fatalf("error during apply: %s", diags.Err()) if diags.HasErrors() {
} t.Fatalf("error during apply: %s", diags.Err())
}
{
want := map[string]interface{}{ want := map[string]interface{}{
"source_ids": []interface{}{"foo", "foo", "foo"}, "source_ids": []interface{}{"foo", "foo", "foo"},
"source_names": []interface{}{ "source_names": []interface{}{
@ -3643,7 +3670,7 @@ func TestContext2Apply_multiVarComprehensive(t *testing.T) {
spew.Sdump(got), spew.Sdump(want), spew.Sdump(got), spew.Sdump(want),
) )
} }
} })
} }
// Test that multi-var (splat) access is ordered by count, not by // Test that multi-var (splat) access is ordered by count, not by

View File

@ -10,16 +10,16 @@ variable "source_names" {
} }
resource "test_thing" "multi_count_var" { resource "test_thing" "multi_count_var" {
count = "${var.num}" count = var.num
# Can pluck a single item out of a multi-var # Can pluck a single item out of a multi-var
source_id = "${var.source_ids[count.index]}" source_id = var.source_ids[count.index]
} }
resource "test_thing" "whole_splat" { resource "test_thing" "whole_splat" {
# Can "splat" the ids directly into an attribute of type list. # Can "splat" the ids directly into an attribute of type list.
source_ids = "${var.source_ids}" source_ids = var.source_ids
source_names = "${var.source_names}" source_names = var.source_names
source_ids_wrapped = ["${var.source_ids}"] source_ids_wrapped = ["${var.source_ids}"]
source_names_wrapped = ["${var.source_names}"] source_names_wrapped = ["${var.source_names}"]
} }

View File

@ -2,48 +2,48 @@ variable "num" {
} }
resource "test_thing" "source" { resource "test_thing" "source" {
count = "${var.num}" count = var.num
# The diffFunc in the test exports "name" here too, which we can use # The diffFunc in the test exports "name" here too, which we can use
# to test values that are known during plan. # to test values that are known during plan.
} }
resource "test_thing" "multi_count_var" { resource "test_thing" "multi_count_var" {
count = "${var.num}" count = var.num
# Can pluck a single item out of a multi-var # Can pluck a single item out of a multi-var
source_id = "${test_thing.source.*.id[count.index]}" source_id = test_thing.source.*.id[count.index]
source_name = "${test_thing.source.*.name[count.index]}" source_name = test_thing.source.*.name[count.index]
} }
resource "test_thing" "multi_count_derived" { resource "test_thing" "multi_count_derived" {
# Can use the source to get the count # Can use the source to get the count
count = "${length(test_thing.source)}" count = length(test_thing.source)
source_id = "${test_thing.source.*.id[count.index]}" source_id = test_thing.source.*.id[count.index]
source_name = "${test_thing.source.*.name[count.index]}" source_name = test_thing.source.*.name[count.index]
} }
resource "test_thing" "whole_splat" { resource "test_thing" "whole_splat" {
# Can "splat" the ids directly into an attribute of type list. # Can "splat" the ids directly into an attribute of type list.
source_ids = "${test_thing.source.*.id}" source_ids = test_thing.source.*.id
source_names = "${test_thing.source.*.name}" source_names = test_thing.source.*.name
# Accessing through a function should work. # Accessing through a function should work.
source_ids_from_func = "${split(" ", join(" ", test_thing.source.*.id))}" source_ids_from_func = split(" ", join(" ", test_thing.source.*.id))
source_names_from_func = "${split(" ", join(" ", test_thing.source.*.name))}" source_names_from_func = split(" ", join(" ", test_thing.source.*.name))
# A common pattern of selecting with a default. # A common pattern of selecting with a default.
first_source_id = "${element(concat(test_thing.source.*.id, list("default")), 0)}" first_source_id = element(concat(test_thing.source.*.id, ["default"]), 0)
first_source_name = "${element(concat(test_thing.source.*.name, list("default")), 0)}" first_source_name = element(concat(test_thing.source.*.name, ["default"]), 0)
# Legacy form: Prior to Terraform having comprehensive list support, # Prior to v0.12 we were handling lists containing list interpolations as
# splats were treated as a special case and required to be presented # a special case, flattening the result, for compatibility with behavior
# in a wrapping list. This is no longer the suggested form, but we # prior to v0.10. This deprecated handling is now removed, and so these
# need it to keep working for compatibility. # each produce a list of lists. We're still using the interpolation syntax
# # here, rather than the splat expression directly, to properly mimic how
# This should result in exactly the same result as the above, even # this would've looked prior to v0.12 to be explicit about what the new
# though it looks like it would result in a list of lists. # behavior is for this old syntax.
source_ids_wrapped = ["${test_thing.source.*.id}"] source_ids_wrapped = ["${test_thing.source.*.id}"]
source_names_wrapped = ["${test_thing.source.*.name}"] source_names_wrapped = ["${test_thing.source.*.name}"]
@ -52,15 +52,15 @@ resource "test_thing" "whole_splat" {
module "child" { module "child" {
source = "./child" source = "./child"
num = "${var.num}" num = var.num
source_ids = "${test_thing.source.*.id}" source_ids = test_thing.source.*.id
source_names = "${test_thing.source.*.name}" source_names = test_thing.source.*.name
} }
output "source_ids" { output "source_ids" {
value = "${test_thing.source.*.id}" value = test_thing.source.*.id
} }
output "source_names" { output "source_names" {
value = "${test_thing.source.*.name}" value = test_thing.source.*.name
} }