mirror of
https://github.com/opentofu/opentofu.git
synced 2024-12-28 18:01:01 -06:00
a7507e140d
Module references, like resource references, need to always return the and object containing all instances in order to handle modules as single values, and to postpone index evaluation to when the expression as whole is evaluated.
716 lines
16 KiB
Go
716 lines
16 KiB
Go
package addrs
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"github.com/go-test/deep"
|
|
"github.com/hashicorp/hcl/v2"
|
|
"github.com/hashicorp/hcl/v2/hclsyntax"
|
|
"github.com/hashicorp/terraform/tfdiags"
|
|
"github.com/zclconf/go-cty/cty"
|
|
)
|
|
|
|
func TestParseRef(t *testing.T) {
|
|
tests := []struct {
|
|
Input string
|
|
Want *Reference
|
|
WantErr string
|
|
}{
|
|
|
|
// count
|
|
{
|
|
`count.index`,
|
|
&Reference{
|
|
Subject: CountAttr{
|
|
Name: "index",
|
|
},
|
|
SourceRange: tfdiags.SourceRange{
|
|
Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0},
|
|
End: tfdiags.SourcePos{Line: 1, Column: 12, Byte: 11},
|
|
},
|
|
},
|
|
``,
|
|
},
|
|
{
|
|
`count.index.blah`,
|
|
&Reference{
|
|
Subject: CountAttr{
|
|
Name: "index",
|
|
},
|
|
SourceRange: tfdiags.SourceRange{
|
|
Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0},
|
|
End: tfdiags.SourcePos{Line: 1, Column: 12, Byte: 11},
|
|
},
|
|
Remaining: hcl.Traversal{
|
|
hcl.TraverseAttr{
|
|
Name: "blah",
|
|
SrcRange: hcl.Range{
|
|
Start: hcl.Pos{Line: 1, Column: 12, Byte: 11},
|
|
End: hcl.Pos{Line: 1, Column: 17, Byte: 16},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
``, // valid at this layer, but will fail during eval because "index" is a number
|
|
},
|
|
{
|
|
`count`,
|
|
nil,
|
|
`The "count" object cannot be accessed directly. Instead, access one of its attributes.`,
|
|
},
|
|
{
|
|
`count["hello"]`,
|
|
nil,
|
|
`The "count" object does not support this operation.`,
|
|
},
|
|
|
|
// each
|
|
{
|
|
`each.key`,
|
|
&Reference{
|
|
Subject: ForEachAttr{
|
|
Name: "key",
|
|
},
|
|
SourceRange: tfdiags.SourceRange{
|
|
Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0},
|
|
End: tfdiags.SourcePos{Line: 1, Column: 9, Byte: 8},
|
|
},
|
|
},
|
|
``,
|
|
},
|
|
{
|
|
`each.value.blah`,
|
|
&Reference{
|
|
Subject: ForEachAttr{
|
|
Name: "value",
|
|
},
|
|
SourceRange: tfdiags.SourceRange{
|
|
Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0},
|
|
End: tfdiags.SourcePos{Line: 1, Column: 11, Byte: 10},
|
|
},
|
|
Remaining: hcl.Traversal{
|
|
hcl.TraverseAttr{
|
|
Name: "blah",
|
|
SrcRange: hcl.Range{
|
|
Start: hcl.Pos{Line: 1, Column: 11, Byte: 10},
|
|
End: hcl.Pos{Line: 1, Column: 16, Byte: 15},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
``,
|
|
},
|
|
{
|
|
`each`,
|
|
nil,
|
|
`The "each" object cannot be accessed directly. Instead, access one of its attributes.`,
|
|
},
|
|
{
|
|
`each["hello"]`,
|
|
nil,
|
|
`The "each" object does not support this operation.`,
|
|
},
|
|
// data
|
|
{
|
|
`data.external.foo`,
|
|
&Reference{
|
|
Subject: Resource{
|
|
Mode: DataResourceMode,
|
|
Type: "external",
|
|
Name: "foo",
|
|
},
|
|
SourceRange: tfdiags.SourceRange{
|
|
Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0},
|
|
End: tfdiags.SourcePos{Line: 1, Column: 18, Byte: 17},
|
|
},
|
|
},
|
|
``,
|
|
},
|
|
{
|
|
`data.external.foo.bar`,
|
|
&Reference{
|
|
Subject: ResourceInstance{
|
|
Resource: Resource{
|
|
Mode: DataResourceMode,
|
|
Type: "external",
|
|
Name: "foo",
|
|
},
|
|
},
|
|
SourceRange: tfdiags.SourceRange{
|
|
Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0},
|
|
End: tfdiags.SourcePos{Line: 1, Column: 18, Byte: 17},
|
|
},
|
|
Remaining: hcl.Traversal{
|
|
hcl.TraverseAttr{
|
|
Name: "bar",
|
|
SrcRange: hcl.Range{
|
|
Start: hcl.Pos{Line: 1, Column: 18, Byte: 17},
|
|
End: hcl.Pos{Line: 1, Column: 22, Byte: 21},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
``,
|
|
},
|
|
{
|
|
`data.external.foo["baz"].bar`,
|
|
&Reference{
|
|
Subject: ResourceInstance{
|
|
Resource: Resource{
|
|
Mode: DataResourceMode,
|
|
Type: "external",
|
|
Name: "foo",
|
|
},
|
|
Key: StringKey("baz"),
|
|
},
|
|
SourceRange: tfdiags.SourceRange{
|
|
Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0},
|
|
End: tfdiags.SourcePos{Line: 1, Column: 25, Byte: 24},
|
|
},
|
|
Remaining: hcl.Traversal{
|
|
hcl.TraverseAttr{
|
|
Name: "bar",
|
|
SrcRange: hcl.Range{
|
|
Start: hcl.Pos{Line: 1, Column: 25, Byte: 24},
|
|
End: hcl.Pos{Line: 1, Column: 29, Byte: 28},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
``,
|
|
},
|
|
{
|
|
`data.external.foo["baz"]`,
|
|
&Reference{
|
|
Subject: ResourceInstance{
|
|
Resource: Resource{
|
|
Mode: DataResourceMode,
|
|
Type: "external",
|
|
Name: "foo",
|
|
},
|
|
Key: StringKey("baz"),
|
|
},
|
|
SourceRange: tfdiags.SourceRange{
|
|
Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0},
|
|
End: tfdiags.SourcePos{Line: 1, Column: 25, Byte: 24},
|
|
},
|
|
},
|
|
``,
|
|
},
|
|
{
|
|
`data`,
|
|
nil,
|
|
`The "data" object must be followed by two attribute names: the data source type and the resource name.`,
|
|
},
|
|
{
|
|
`data.external`,
|
|
nil,
|
|
`The "data" object must be followed by two attribute names: the data source type and the resource name.`,
|
|
},
|
|
|
|
// local
|
|
{
|
|
`local.foo`,
|
|
&Reference{
|
|
Subject: LocalValue{
|
|
Name: "foo",
|
|
},
|
|
SourceRange: tfdiags.SourceRange{
|
|
Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0},
|
|
End: tfdiags.SourcePos{Line: 1, Column: 10, Byte: 9},
|
|
},
|
|
},
|
|
``,
|
|
},
|
|
{
|
|
`local.foo.blah`,
|
|
&Reference{
|
|
Subject: LocalValue{
|
|
Name: "foo",
|
|
},
|
|
SourceRange: tfdiags.SourceRange{
|
|
Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0},
|
|
End: tfdiags.SourcePos{Line: 1, Column: 10, Byte: 9},
|
|
},
|
|
Remaining: hcl.Traversal{
|
|
hcl.TraverseAttr{
|
|
Name: "blah",
|
|
SrcRange: hcl.Range{
|
|
Start: hcl.Pos{Line: 1, Column: 10, Byte: 9},
|
|
End: hcl.Pos{Line: 1, Column: 15, Byte: 14},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
``,
|
|
},
|
|
{
|
|
`local.foo["blah"]`,
|
|
&Reference{
|
|
Subject: LocalValue{
|
|
Name: "foo",
|
|
},
|
|
SourceRange: tfdiags.SourceRange{
|
|
Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0},
|
|
End: tfdiags.SourcePos{Line: 1, Column: 10, Byte: 9},
|
|
},
|
|
Remaining: hcl.Traversal{
|
|
hcl.TraverseIndex{
|
|
Key: cty.StringVal("blah"),
|
|
SrcRange: hcl.Range{
|
|
Start: hcl.Pos{Line: 1, Column: 10, Byte: 9},
|
|
End: hcl.Pos{Line: 1, Column: 18, Byte: 17},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
``,
|
|
},
|
|
{
|
|
`local`,
|
|
nil,
|
|
`The "local" object cannot be accessed directly. Instead, access one of its attributes.`,
|
|
},
|
|
{
|
|
`local["foo"]`,
|
|
nil,
|
|
`The "local" object does not support this operation.`,
|
|
},
|
|
|
|
// module
|
|
{
|
|
`module.foo`,
|
|
&Reference{
|
|
Subject: ModuleCall{
|
|
Name: "foo",
|
|
},
|
|
SourceRange: tfdiags.SourceRange{
|
|
Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0},
|
|
End: tfdiags.SourcePos{Line: 1, Column: 11, Byte: 10},
|
|
},
|
|
},
|
|
``,
|
|
},
|
|
{
|
|
`module.foo.bar`,
|
|
&Reference{
|
|
Subject: AbsModuleCallOutput{
|
|
Call: ModuleCallInstance{
|
|
Call: ModuleCall{
|
|
Name: "foo",
|
|
},
|
|
},
|
|
Name: "bar",
|
|
},
|
|
SourceRange: tfdiags.SourceRange{
|
|
Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0},
|
|
End: tfdiags.SourcePos{Line: 1, Column: 15, Byte: 14},
|
|
},
|
|
},
|
|
``,
|
|
},
|
|
{
|
|
`module.foo.bar.baz`,
|
|
&Reference{
|
|
Subject: AbsModuleCallOutput{
|
|
Call: ModuleCallInstance{
|
|
Call: ModuleCall{
|
|
Name: "foo",
|
|
},
|
|
},
|
|
Name: "bar",
|
|
},
|
|
SourceRange: tfdiags.SourceRange{
|
|
Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0},
|
|
End: tfdiags.SourcePos{Line: 1, Column: 15, Byte: 14},
|
|
},
|
|
Remaining: hcl.Traversal{
|
|
hcl.TraverseAttr{
|
|
Name: "baz",
|
|
SrcRange: hcl.Range{
|
|
Start: hcl.Pos{Line: 1, Column: 15, Byte: 14},
|
|
End: hcl.Pos{Line: 1, Column: 19, Byte: 18},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
``,
|
|
},
|
|
{
|
|
`module.foo["baz"]`,
|
|
&Reference{
|
|
Subject: ModuleCallInstance{
|
|
Call: ModuleCall{
|
|
Name: "foo",
|
|
},
|
|
Key: StringKey("baz"),
|
|
},
|
|
SourceRange: tfdiags.SourceRange{
|
|
Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0},
|
|
End: tfdiags.SourcePos{Line: 1, Column: 18, Byte: 17},
|
|
},
|
|
},
|
|
``,
|
|
},
|
|
{
|
|
`module.foo["baz"].bar`,
|
|
&Reference{
|
|
Subject: AbsModuleCallOutput{
|
|
Call: ModuleCallInstance{
|
|
Call: ModuleCall{
|
|
Name: "foo",
|
|
},
|
|
Key: StringKey("baz"),
|
|
},
|
|
Name: "bar",
|
|
},
|
|
SourceRange: tfdiags.SourceRange{
|
|
Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0},
|
|
End: tfdiags.SourcePos{Line: 1, Column: 22, Byte: 21},
|
|
},
|
|
},
|
|
``,
|
|
},
|
|
{
|
|
`module.foo["baz"].bar.boop`,
|
|
&Reference{
|
|
Subject: AbsModuleCallOutput{
|
|
Call: ModuleCallInstance{
|
|
Call: ModuleCall{
|
|
Name: "foo",
|
|
},
|
|
Key: StringKey("baz"),
|
|
},
|
|
Name: "bar",
|
|
},
|
|
SourceRange: tfdiags.SourceRange{
|
|
Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0},
|
|
End: tfdiags.SourcePos{Line: 1, Column: 22, Byte: 21},
|
|
},
|
|
Remaining: hcl.Traversal{
|
|
hcl.TraverseAttr{
|
|
Name: "boop",
|
|
SrcRange: hcl.Range{
|
|
Start: hcl.Pos{Line: 1, Column: 22, Byte: 21},
|
|
End: hcl.Pos{Line: 1, Column: 27, Byte: 26},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
``,
|
|
},
|
|
{
|
|
`module`,
|
|
nil,
|
|
`The "module" object cannot be accessed directly. Instead, access one of its attributes.`,
|
|
},
|
|
{
|
|
`module["foo"]`,
|
|
nil,
|
|
`The "module" object does not support this operation.`,
|
|
},
|
|
|
|
// path
|
|
{
|
|
`path.module`,
|
|
&Reference{
|
|
Subject: PathAttr{
|
|
Name: "module",
|
|
},
|
|
SourceRange: tfdiags.SourceRange{
|
|
Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0},
|
|
End: tfdiags.SourcePos{Line: 1, Column: 12, Byte: 11},
|
|
},
|
|
},
|
|
``,
|
|
},
|
|
{
|
|
`path.module.blah`,
|
|
&Reference{
|
|
Subject: PathAttr{
|
|
Name: "module",
|
|
},
|
|
SourceRange: tfdiags.SourceRange{
|
|
Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0},
|
|
End: tfdiags.SourcePos{Line: 1, Column: 12, Byte: 11},
|
|
},
|
|
Remaining: hcl.Traversal{
|
|
hcl.TraverseAttr{
|
|
Name: "blah",
|
|
SrcRange: hcl.Range{
|
|
Start: hcl.Pos{Line: 1, Column: 12, Byte: 11},
|
|
End: hcl.Pos{Line: 1, Column: 17, Byte: 16},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
``, // valid at this layer, but will fail during eval because "module" is a string
|
|
},
|
|
{
|
|
`path`,
|
|
nil,
|
|
`The "path" object cannot be accessed directly. Instead, access one of its attributes.`,
|
|
},
|
|
{
|
|
`path["module"]`,
|
|
nil,
|
|
`The "path" object does not support this operation.`,
|
|
},
|
|
|
|
// self
|
|
{
|
|
`self`,
|
|
&Reference{
|
|
Subject: Self,
|
|
SourceRange: tfdiags.SourceRange{
|
|
Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0},
|
|
End: tfdiags.SourcePos{Line: 1, Column: 5, Byte: 4},
|
|
},
|
|
},
|
|
``,
|
|
},
|
|
{
|
|
`self.blah`,
|
|
&Reference{
|
|
Subject: Self,
|
|
SourceRange: tfdiags.SourceRange{
|
|
Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0},
|
|
End: tfdiags.SourcePos{Line: 1, Column: 5, Byte: 4},
|
|
},
|
|
Remaining: hcl.Traversal{
|
|
hcl.TraverseAttr{
|
|
Name: "blah",
|
|
SrcRange: hcl.Range{
|
|
Start: hcl.Pos{Line: 1, Column: 5, Byte: 4},
|
|
End: hcl.Pos{Line: 1, Column: 10, Byte: 9},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
``,
|
|
},
|
|
|
|
// terraform
|
|
{
|
|
`terraform.workspace`,
|
|
&Reference{
|
|
Subject: TerraformAttr{
|
|
Name: "workspace",
|
|
},
|
|
SourceRange: tfdiags.SourceRange{
|
|
Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0},
|
|
End: tfdiags.SourcePos{Line: 1, Column: 20, Byte: 19},
|
|
},
|
|
},
|
|
``,
|
|
},
|
|
{
|
|
`terraform.workspace.blah`,
|
|
&Reference{
|
|
Subject: TerraformAttr{
|
|
Name: "workspace",
|
|
},
|
|
SourceRange: tfdiags.SourceRange{
|
|
Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0},
|
|
End: tfdiags.SourcePos{Line: 1, Column: 20, Byte: 19},
|
|
},
|
|
Remaining: hcl.Traversal{
|
|
hcl.TraverseAttr{
|
|
Name: "blah",
|
|
SrcRange: hcl.Range{
|
|
Start: hcl.Pos{Line: 1, Column: 20, Byte: 19},
|
|
End: hcl.Pos{Line: 1, Column: 25, Byte: 24},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
``, // valid at this layer, but will fail during eval because "workspace" is a string
|
|
},
|
|
{
|
|
`terraform`,
|
|
nil,
|
|
`The "terraform" object cannot be accessed directly. Instead, access one of its attributes.`,
|
|
},
|
|
{
|
|
`terraform["workspace"]`,
|
|
nil,
|
|
`The "terraform" object does not support this operation.`,
|
|
},
|
|
|
|
// var
|
|
{
|
|
`var.foo`,
|
|
&Reference{
|
|
Subject: InputVariable{
|
|
Name: "foo",
|
|
},
|
|
SourceRange: tfdiags.SourceRange{
|
|
Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0},
|
|
End: tfdiags.SourcePos{Line: 1, Column: 8, Byte: 7},
|
|
},
|
|
},
|
|
``,
|
|
},
|
|
{
|
|
`var.foo.blah`,
|
|
&Reference{
|
|
Subject: InputVariable{
|
|
Name: "foo",
|
|
},
|
|
SourceRange: tfdiags.SourceRange{
|
|
Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0},
|
|
End: tfdiags.SourcePos{Line: 1, Column: 8, Byte: 7},
|
|
},
|
|
Remaining: hcl.Traversal{
|
|
hcl.TraverseAttr{
|
|
Name: "blah",
|
|
SrcRange: hcl.Range{
|
|
Start: hcl.Pos{Line: 1, Column: 8, Byte: 7},
|
|
End: hcl.Pos{Line: 1, Column: 13, Byte: 12},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
``, // valid at this layer, but will fail during eval because "module" is a string
|
|
},
|
|
{
|
|
`var`,
|
|
nil,
|
|
`The "var" object cannot be accessed directly. Instead, access one of its attributes.`,
|
|
},
|
|
{
|
|
`var["foo"]`,
|
|
nil,
|
|
`The "var" object does not support this operation.`,
|
|
},
|
|
|
|
// anything else, interpreted as a managed resource reference
|
|
{
|
|
`boop_instance.foo`,
|
|
&Reference{
|
|
Subject: Resource{
|
|
Mode: ManagedResourceMode,
|
|
Type: "boop_instance",
|
|
Name: "foo",
|
|
},
|
|
SourceRange: tfdiags.SourceRange{
|
|
Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0},
|
|
End: tfdiags.SourcePos{Line: 1, Column: 18, Byte: 17},
|
|
},
|
|
},
|
|
``,
|
|
},
|
|
{
|
|
`boop_instance.foo.bar`,
|
|
&Reference{
|
|
Subject: ResourceInstance{
|
|
Resource: Resource{
|
|
Mode: ManagedResourceMode,
|
|
Type: "boop_instance",
|
|
Name: "foo",
|
|
},
|
|
},
|
|
SourceRange: tfdiags.SourceRange{
|
|
Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0},
|
|
End: tfdiags.SourcePos{Line: 1, Column: 18, Byte: 17},
|
|
},
|
|
Remaining: hcl.Traversal{
|
|
hcl.TraverseAttr{
|
|
Name: "bar",
|
|
SrcRange: hcl.Range{
|
|
Start: hcl.Pos{Line: 1, Column: 18, Byte: 17},
|
|
End: hcl.Pos{Line: 1, Column: 22, Byte: 21},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
``,
|
|
},
|
|
{
|
|
`boop_instance.foo["baz"].bar`,
|
|
&Reference{
|
|
Subject: ResourceInstance{
|
|
Resource: Resource{
|
|
Mode: ManagedResourceMode,
|
|
Type: "boop_instance",
|
|
Name: "foo",
|
|
},
|
|
Key: StringKey("baz"),
|
|
},
|
|
SourceRange: tfdiags.SourceRange{
|
|
Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0},
|
|
End: tfdiags.SourcePos{Line: 1, Column: 25, Byte: 24},
|
|
},
|
|
Remaining: hcl.Traversal{
|
|
hcl.TraverseAttr{
|
|
Name: "bar",
|
|
SrcRange: hcl.Range{
|
|
Start: hcl.Pos{Line: 1, Column: 25, Byte: 24},
|
|
End: hcl.Pos{Line: 1, Column: 29, Byte: 28},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
``,
|
|
},
|
|
{
|
|
`boop_instance.foo["baz"]`,
|
|
&Reference{
|
|
Subject: ResourceInstance{
|
|
Resource: Resource{
|
|
Mode: ManagedResourceMode,
|
|
Type: "boop_instance",
|
|
Name: "foo",
|
|
},
|
|
Key: StringKey("baz"),
|
|
},
|
|
SourceRange: tfdiags.SourceRange{
|
|
Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0},
|
|
End: tfdiags.SourcePos{Line: 1, Column: 25, Byte: 24},
|
|
},
|
|
},
|
|
``,
|
|
},
|
|
{
|
|
`boop_instance`,
|
|
nil,
|
|
`A reference to a resource type must be followed by at least one attribute access, specifying the resource name.`,
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
t.Run(test.Input, func(t *testing.T) {
|
|
traversal, travDiags := hclsyntax.ParseTraversalAbs([]byte(test.Input), "", hcl.Pos{Line: 1, Column: 1})
|
|
if travDiags.HasErrors() {
|
|
t.Fatal(travDiags.Error())
|
|
}
|
|
|
|
got, diags := ParseRef(traversal)
|
|
|
|
switch len(diags) {
|
|
case 0:
|
|
if test.WantErr != "" {
|
|
t.Fatalf("succeeded; want error: %s", test.WantErr)
|
|
}
|
|
case 1:
|
|
if test.WantErr == "" {
|
|
t.Fatalf("unexpected diagnostics: %s", diags.Err())
|
|
}
|
|
if got, want := diags[0].Description().Detail, test.WantErr; got != want {
|
|
t.Fatalf("wrong error\ngot: %s\nwant: %s", got, want)
|
|
}
|
|
default:
|
|
t.Fatalf("too many diagnostics: %s", diags.Err())
|
|
}
|
|
|
|
if diags.HasErrors() {
|
|
return
|
|
}
|
|
|
|
for _, problem := range deep.Equal(got, test.Want) {
|
|
t.Errorf(problem)
|
|
}
|
|
})
|
|
}
|
|
}
|