Skip to content

Commit 9658f9d

Browse files
author
Liam Cervante
authored
mocking overrides: default to concrete empty object when values are missing (#34563)
1 parent d7f97ec commit 9658f9d

File tree

7 files changed

+159
-6
lines changed

7 files changed

+159
-6
lines changed

internal/command/test_test.go

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ func TestTest_Runs(t *testing.T) {
2929
expectedErr string
3030
expectedResourceCount int
3131
code int
32+
initCode int
3233
skip bool
3334
}{
3435
"simple_pass": {
@@ -212,9 +213,13 @@ func TestTest_Runs(t *testing.T) {
212213
code: 0,
213214
},
214215
"mocking": {
215-
expectedOut: "5 passed, 0 failed.",
216+
expectedOut: "6 passed, 0 failed.",
216217
code: 0,
217218
},
219+
"mocking-invalid": {
220+
expectedErr: "Invalid outputs attribute",
221+
initCode: 1,
222+
},
218223
"dangling_data_block": {
219224
expectedOut: "2 passed, 0 failed.",
220225
code: 0,
@@ -270,8 +275,30 @@ func TestTest_Runs(t *testing.T) {
270275
Meta: meta,
271276
}
272277

273-
if code := init.Run(nil); code != 0 {
274-
t.Fatalf("expected status code 0 but got %d: %s", code, ui.ErrorWriter)
278+
if code := init.Run(nil); code != tc.initCode {
279+
t.Fatalf("expected status code %d but got %d: %s", tc.initCode, code, ui.ErrorWriter)
280+
}
281+
282+
if tc.initCode > 0 {
283+
// Then we don't expect the init step to succeed. So we'll check
284+
// the init output for our expected error messages and outputs.
285+
286+
stdout, stderr := ui.ErrorWriter.String(), ui.ErrorWriter.String()
287+
288+
if !strings.Contains(stdout, tc.expectedOut) {
289+
t.Errorf("output didn't contain expected string:\n\n%s", stdout)
290+
}
291+
292+
if !strings.Contains(stderr, tc.expectedErr) {
293+
t.Errorf("error didn't contain expected string:\n\n%s", stderr)
294+
} else if tc.expectedErr == "" && stderr != "" {
295+
t.Errorf("unexpected stderr output\n%s", stderr)
296+
}
297+
298+
// If `terraform init` failed, then we don't expect that
299+
// `terraform test` will have run at all, so we can just return
300+
// here.
301+
return
275302
}
276303

277304
c := &TestCommand{
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
terraform {
2+
required_providers {
3+
test = {
4+
source = "hashicorp/test"
5+
configuration_aliases = [test.primary, test.secondary]
6+
}
7+
}
8+
}
9+
10+
variable "instances" {
11+
type = number
12+
}
13+
14+
resource "test_resource" "primary" {
15+
provider = test.primary
16+
count = var.instances
17+
}
18+
19+
resource "test_resource" "secondary" {
20+
provider = test.secondary
21+
count = var.instances
22+
}
23+
24+
output "primary" {
25+
value = test_resource.primary
26+
}
27+
28+
output "secondary" {
29+
value = test_resource.secondary
30+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
terraform {
2+
required_providers {
3+
test = {
4+
source = "hashicorp/test"
5+
}
6+
}
7+
}
8+
9+
provider "test" {
10+
alias = "primary"
11+
}
12+
13+
provider "test" {
14+
alias = "secondary"
15+
}
16+
17+
variable "instances" {
18+
type = number
19+
}
20+
21+
variable "child_instances" {
22+
type = number
23+
}
24+
25+
resource "test_resource" "primary" {
26+
provider = test.primary
27+
count = var.instances
28+
}
29+
30+
resource "test_resource" "secondary" {
31+
provider = test.secondary
32+
count = var.instances
33+
}
34+
35+
module "child" {
36+
count = var.instances
37+
38+
source = "./child"
39+
40+
providers = {
41+
test.primary = test.primary
42+
test.secondary = test.secondary
43+
}
44+
45+
instances = var.child_instances
46+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
override_module {
2+
target = module.child[1]
3+
outputs = "should be an object"
4+
}
5+
6+
variables {
7+
instances = 3
8+
child_instances = 1
9+
}
10+
11+
run "test" {
12+
# We won't even execute this, as the configuration isn't valid.
13+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
override_module {
2+
target = module.child[1]
3+
}
4+
5+
variables {
6+
instances = 3
7+
child_instances = 1
8+
}
9+
10+
run "test" {
11+
# Just want to make sure things don't crash with missing `outputs` attribute.
12+
}

internal/configs/mock_provider.go

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -435,8 +435,27 @@ func decodeOverrideBlock(block *hcl.Block, attributeName string, blockName strin
435435
diags = append(diags, valueDiags...)
436436
} else {
437437
// It's fine if we don't have any values, just means we'll generate
438-
// values for everything ourselves.
439-
override.Values = cty.NilVal
438+
// values for everything ourselves. We set this to an empty object so
439+
// it's equivalent to `values = {}` which makes later processing easier.
440+
override.Values = cty.EmptyObjectVal
441+
}
442+
443+
if !override.Values.Type().IsObjectType() {
444+
445+
var attributePreposition string
446+
switch attributeName {
447+
case "outputs":
448+
attributePreposition = "an"
449+
default:
450+
attributePreposition = "a"
451+
}
452+
453+
diags = diags.Append(&hcl.Diagnostic{
454+
Severity: hcl.DiagError,
455+
Summary: fmt.Sprintf("Invalid %s attribute", attributeName),
456+
Detail: fmt.Sprintf("%s blocks must specify %s %s attribute that is an object.", blockName, attributePreposition, attributeName),
457+
Subject: override.ValuesRange.Ptr(),
458+
})
440459
}
441460

442461
return override, diags

internal/terraform/graph.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,13 @@ func (g *Graph) checkAndApplyOverrides(overrides *mocking.Overrides, target dag.
196196

197197
setOverride := func(values cty.Value) {
198198
key := v.Addr.OutputValue.Name
199-
if values.Type().HasAttribute(key) {
199+
200+
// The values.Type() should be an object type, but it might have
201+
// been set to nil by a test or something. We can handle it in the
202+
// same way as the attribute just not being specified. It's
203+
// functionally the same for us and not something we need to raise
204+
// alarms about.
205+
if values.Type().IsObjectType() && values.Type().HasAttribute(key) {
200206
v.override = values.GetAttr(key)
201207
} else {
202208
// If we don't have a value provided for an output, then we'll

0 commit comments

Comments
 (0)