Better output for test assertions (#3009)

Signed-off-by: Diogenes Fernandes <diofeher@gmail.com>
This commit is contained in:
Diógenes Fernandes
2025-07-17 12:51:06 -03:00
committed by GitHub
parent b6b1573482
commit f95ca42871
8 changed files with 534 additions and 77 deletions

View File

@@ -14,6 +14,10 @@ import (
"github.com/hashicorp/hcl/v2"
"github.com/opentofu/opentofu/internal/command/jsonentities"
"github.com/opentofu/opentofu/internal/command/jsonformat/computed"
"github.com/opentofu/opentofu/internal/command/jsonformat/differ"
"github.com/opentofu/opentofu/internal/command/jsonformat/structured"
"github.com/opentofu/opentofu/internal/command/jsonformat/structured/attribute_path"
"github.com/opentofu/opentofu/internal/tfdiags"
"github.com/mitchellh/colorstring"
@@ -123,6 +127,31 @@ func DiagnosticFromJSON(diag *jsonentities.Diagnostic, color *colorstring.Colori
return ruleBuf.String()
}
// appendDifferenceOutput is used to create colored and aligned lines to be used
// on the test suite assertions
func appendDifferenceOutput(buf *bytes.Buffer, diag *jsonentities.Diagnostic, color *colorstring.Colorize) {
if diag.Difference == nil {
return
}
change := structured.FromJsonChange(*diag.Difference, attribute_path.AlwaysMatcher())
differed := differ.ComputeDiffForOutput(change)
out := differed.RenderHuman(0, computed.RenderHumanOpts{Colorize: color})
// The next line is needed in order to make the output aligned, since the first line
// rendered by RenderHuman is on column 0, but all the subsequent lines are having 4 spaces before
// the actual content.
out = fmt.Sprintf(" %s", out)
fmt.Fprint(buf, color.Color(" [dark_gray]├────────────────[reset]\n"))
fmt.Fprint(buf, color.Color(" [dark_gray]│[reset] [bold]Diff: [reset]"))
lines := strings.Split(out, "\n")
for line := range lines {
buf.WriteString(color.Color("\n [dark_gray]│[reset] "))
buf.WriteString(lines[line])
}
buf.WriteByte('\n')
}
// DiagnosticPlain is an alternative to Diagnostic which minimises the use of
// virtual terminal formatting sequences.
//
@@ -319,7 +348,7 @@ func appendSourceSnippets(buf *bytes.Buffer, diag *jsonentities.Diagnostic, colo
fmt.Fprintf(buf, color.Color(" [dark_gray]│[reset] [bold]%s[reset] %s\n"), value.Traversal, value.Statement)
}
}
appendDifferenceOutput(buf, diag, color)
}
buf.WriteByte('\n')
}

View File

@@ -265,12 +265,107 @@ func TestDiagnostic(t *testing.T) {
[red]│[reset]
[red]│[reset] Whatever shall we do?
[red]╵[reset]
`,
},
"test number assertion difference": {
&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Bad testing",
Detail: "Number testing went wrong.",
Expression: &hclsyntax.BinaryOpExpr{
LHS: &hclsyntax.LiteralValueExpr{
Val: cty.StringVal("3"),
},
RHS: &hclsyntax.LiteralValueExpr{
Val: cty.StringVal("5"),
},
Op: hclsyntax.OpEqual,
},
Subject: &hcl.Range{
Filename: "test.tf",
Start: hcl.Pos{Line: 1, Column: 6, Byte: 5},
End: hcl.Pos{Line: 1, Column: 12, Byte: 11},
},
EvalContext: &hcl.EvalContext{},
},
`[red]╷[reset]
[red]│[reset] [bold][red]Error: [reset][bold]Bad testing[reset]
[red]│[reset]
[red]│[reset] on test.tf line 1:
[red]│[reset] 1: test [underline]source[reset] code
[red]│[reset] [dark_gray]├────────────────[reset]
[red]│[reset] [dark_gray]│[reset] [bold]Diff: [reset]
[red]│[reset] [dark_gray]│[reset] "3" [yellow]->[reset] "5"
[red]│[reset]
[red]│[reset] Number testing went wrong.
[red]╵[reset]
`,
},
"test object assertion difference": {
&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Bad testing",
Detail: "Test object assertion!",
Expression: &hclsyntax.BinaryOpExpr{
LHS: &hclsyntax.ScopeTraversalExpr{
Traversal: hcl.Traversal{
hcl.TraverseRoot{Name: "var"},
hcl.TraverseAttr{Name: "json_headers"},
},
},
RHS: &hclsyntax.LiteralValueExpr{
Val: cty.ObjectVal(map[string]cty.Value{
"Test-Header-1": cty.StringVal("foo"),
"Test-Header-2": cty.StringVal("bar"),
}),
},
Op: hclsyntax.OpEqual,
},
Subject: &hcl.Range{
Filename: "json_encode.tf",
Start: hcl.Pos{Line: 1, Column: 12, Byte: 12},
End: hcl.Pos{Line: 4, Column: 20, Byte: 150},
},
EvalContext: &hcl.EvalContext{
Variables: map[string]cty.Value{
"var": cty.ObjectVal(map[string]cty.Value{
"json_headers": cty.ObjectVal(map[string]cty.Value{
"Test-Header-1": cty.StringVal("foo"),
"Test-Header-2": cty.StringVal("foo"),
}),
}),
},
},
},
`[red]╷[reset]
[red]│[reset] [bold][red]Error: [reset][bold]Bad testing[reset]
[red]│[reset]
[red]│[reset] on json_encode.tf line 1:
[red]│[reset] 1: condition = [underline]jsonencode(var.json_headers) == jsonencode([
[red]│[reset] 2: "Test-Header-1: foo",
[red]│[reset] 3: "Test-Header-2: bar"
[red]│[reset] 4: ])[reset]
[red]│[reset] [dark_gray]├────────────────[reset]
[red]│[reset] [dark_gray]│[reset] [bold]var.json_headers[reset] is object with 2 attributes
[red]│[reset] [dark_gray]├────────────────[reset]
[red]│[reset] [dark_gray]│[reset] [bold]Diff: [reset]
[red]│[reset] [dark_gray]│[reset] {
[red]│[reset] [dark_gray]│[reset] [yellow]~[reset] Test-Header-2 = "foo" [yellow]->[reset] "bar"
[red]│[reset] [dark_gray]│[reset] [dark_gray]# (1 unchanged attribute hidden)[reset]
[red]│[reset] [dark_gray]│[reset] }
[red]│[reset]
[red]│[reset] Test object assertion!
[red]╵[reset]
`,
},
}
sources := map[string]*hcl.File{
"test.tf": {Bytes: []byte(`test source code`)},
"json_encode.tf": {Bytes: []byte(`condition = jsonencode(var.json_headers) == jsonencode([
"Test-Header-1: foo",
"Test-Header-2: bar"
])`)},
}
// This empty Colorize just passes through all of the formatting codes
@@ -284,8 +379,8 @@ func TestDiagnostic(t *testing.T) {
diag := diags[0]
got := strings.TrimSpace(Diagnostic(diag, sources, colorize, 40))
want := strings.TrimSpace(test.Want)
if got != want {
t.Errorf("wrong result\ngot:\n%s\n\nwant:\n%s\n\n", got, want)
if diff := cmp.Diff(want, got); diff != "" {
t.Errorf("diff:\n%s", diff)
}
})
}
@@ -588,8 +683,8 @@ Whatever shall we do?
diag := diags[0]
got := strings.TrimSpace(DiagnosticPlain(diag, sources, 40))
want := strings.TrimSpace(test.Want)
if got != want {
t.Errorf("wrong result\ngot:\n%s\n\nwant:\n%s\n\n", got, want)
if diff := cmp.Diff(want, got); diff != "" {
t.Errorf("diff:\n%s", diff)
}
})
}
@@ -702,8 +797,8 @@ func TestDiagnostic_nonOverlappingHighlightContext(t *testing.T) {
`
output := Diagnostic(diags[0], sources, color, 80)
if output != expected {
t.Fatalf("unexpected output: got:\n%s\nwant\n%s\n", output, expected)
if diff := cmp.Diff(output, expected); diff != "" {
t.Errorf("diff:\n%s", diff)
}
}
@@ -750,8 +845,8 @@ func TestDiagnostic_emptyOverlapHighlightContext(t *testing.T) {
`
output := Diagnostic(diags[0], sources, color, 80)
if output != expected {
t.Fatalf("unexpected output: got:\n%s\nwant\n%s\n", output, expected)
if diff := cmp.Diff(output, expected); diff != "" {
t.Errorf("diff:\n%s", diff)
}
}
@@ -793,8 +888,8 @@ Error: Some error
`
output := DiagnosticPlain(diags[0], sources, 80)
if output != expected {
t.Fatalf("unexpected output: got:\n%s\nwant\n%s\n", output, expected)
if diff := cmp.Diff(output, expected); diff != "" {
t.Errorf("diff:\n%s", diff)
}
}
@@ -826,8 +921,8 @@ func TestDiagnostic_wrapDetailIncludingCommand(t *testing.T) {
`
output := Diagnostic(diags[0], nil, color, 76)
if output != expected {
t.Fatalf("unexpected output: got:\n%s\nwant\n%s\n", output, expected)
if diff := cmp.Diff(output, expected); diff != "" {
t.Errorf("diff:\n%s", diff)
}
}
@@ -854,8 +949,8 @@ eventually make it onto multiple lines. THE END
`
output := DiagnosticPlain(diags[0], nil, 76)
if output != expected {
t.Fatalf("unexpected output: got:\n%s\nwant\n%s\n", output, expected)
if diff := cmp.Diff(output, expected); diff != "" {
t.Errorf("diff:\n%s", diff)
}
}
@@ -905,8 +1000,8 @@ func TestDiagnosticFromJSON_invalid(t *testing.T) {
t.Run(name, func(t *testing.T) {
got := strings.TrimSpace(DiagnosticFromJSON(test.Diag, colorize, 40))
want := strings.TrimSpace(test.Want)
if got != want {
t.Errorf("wrong result\ngot:\n%s\n\nwant:\n%s\n\n", got, want)
if diff := cmp.Diff(got, want); diff != "" {
t.Errorf("wrong result\n%s", diff)
}
})
}

View File

@@ -17,6 +17,7 @@ import (
"github.com/hashicorp/hcl/v2/hclsyntax"
"github.com/zclconf/go-cty/cty"
"github.com/opentofu/opentofu/internal/command/jsonplan"
"github.com/opentofu/opentofu/internal/lang/marks"
"github.com/opentofu/opentofu/internal/tfdiags"
@@ -36,12 +37,13 @@ const (
// range field.
type Diagnostic struct {
Severity string `json:"severity"`
Summary string `json:"summary"`
Detail string `json:"detail"`
Address string `json:"address,omitempty"`
Range *DiagnosticRange `json:"range,omitempty"`
Snippet *DiagnosticSnippet `json:"snippet,omitempty"`
Severity string `json:"severity"`
Summary string `json:"summary"`
Detail string `json:"detail"`
Address string `json:"address,omitempty"`
Range *DiagnosticRange `json:"range,omitempty"`
Snippet *DiagnosticSnippet `json:"snippet,omitempty"`
Difference *jsonplan.Change `json:"difference,omitempty"`
}
// Pos represents a position in the source code.
@@ -162,14 +164,17 @@ func NewDiagnostic(diag tfdiags.Diagnostic, sources map[string]*hcl.File) *Diagn
snippet.FunctionCall = newDiagnosticSnippetFunctionCall(diag)
}
difference := newDiagnosticDifference(diag)
desc := diag.Description()
return &Diagnostic{
Severity: sev,
Summary: desc.Summary,
Detail: desc.Detail,
Address: desc.Address,
Range: newDiagnosticRange(highlightRange),
Snippet: snippet,
Severity: sev,
Summary: desc.Summary,
Detail: desc.Detail,
Address: desc.Address,
Range: newDiagnosticRange(highlightRange),
Snippet: snippet,
Difference: difference,
}
}
@@ -340,6 +345,35 @@ func newDiagnosticSnippet(snippetRange, highlightRange *tfdiags.SourceRange, sou
return ret
}
// newDiagnosticDifference covers expressions in the binary
// expression operation format of x == y.
// It's used to create a pretty and descriptive output for the test suite assertions.
// For context: https://github.com/opentofu/opentofu/issues/2545
func newDiagnosticDifference(diag tfdiags.Diagnostic) *jsonplan.Change {
fromExpr := diag.FromExpr()
if fromExpr == nil {
return nil
}
expr := fromExpr.Expression
ctx := fromExpr.EvalContext
binExpr, ok := expr.(*hclsyntax.BinaryOpExpr)
if !ok || binExpr.Op != hclsyntax.OpEqual {
// We're not dealing with a binary op expr, return it early
return nil
}
lhs, _ := binExpr.LHS.Value(ctx)
rhs, _ := binExpr.RHS.Value(ctx)
change, err := jsonplan.GenerateChange(lhs, rhs)
if err != nil {
return nil
}
return change
}
func newDiagnosticExpressionValues(diag tfdiags.Diagnostic) []DiagnosticExpressionValue {
fromExpr := diag.FromExpr()
if fromExpr == nil {

View File

@@ -16,7 +16,9 @@ import (
"github.com/google/go-cmp/cmp"
"github.com/hashicorp/hcl/v2"
"github.com/hashicorp/hcl/v2/hclsyntax"
"github.com/hashicorp/hcl/v2/hcltest"
"github.com/opentofu/opentofu/internal/command/jsonplan"
"github.com/opentofu/opentofu/internal/lang/marks"
"github.com/opentofu/opentofu/internal/tfdiags"
"github.com/zclconf/go-cty/cty"
@@ -50,6 +52,25 @@ func TestNewDiagnostic(t *testing.T) {
var.k,
]
`)},
"test.tftest.hcl": {Bytes: []byte(`run "fails_without_useful_diff" {
command = plan
plan_options {
refresh = false
}
assert {
condition = 3 == 5
error_message = "Error."
}
assert {
condition = jsonencode(var.json_headers) == jsonencode([
"Test-Header-1: foo",
"Test-Header-2: bar",
])
error_message = "Error."
}
}
`)},
}
testCases := map[string]struct {
diag interface{} // allow various kinds of diags
@@ -806,6 +827,136 @@ func TestNewDiagnostic(t *testing.T) {
},
},
},
"error with integer binary comparisons": {
&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Test assertion failed",
Subject: &hcl.Range{
Filename: "test.tftest.hcl",
Start: hcl.Pos{Line: 7, Column: 17, Byte: 118},
End: hcl.Pos{Line: 7, Column: 23, Byte: 125},
},
Expression: &hclsyntax.BinaryOpExpr{
LHS: &hclsyntax.LiteralValueExpr{
Val: cty.StringVal("3"),
},
RHS: &hclsyntax.LiteralValueExpr{
Val: cty.StringVal("5"),
},
Op: hclsyntax.OpEqual,
},
EvalContext: &hcl.EvalContext{},
},
&Diagnostic{
Severity: "error",
Summary: "Test assertion failed",
Range: &DiagnosticRange{
Filename: "test.tftest.hcl",
Start: Pos{
Line: 7,
Column: 17,
Byte: 118,
},
End: Pos{
Line: 7,
Column: 23,
Byte: 125,
},
},
Snippet: &DiagnosticSnippet{
Context: strPtr(`run "fails_without_useful_diff"`),
Code: (" condition = 3 == 5"),
StartLine: (7),
HighlightStartOffset: (15),
HighlightEndOffset: (22),
Values: []DiagnosticExpressionValue{},
},
Difference: &jsonplan.Change{
Before: json.RawMessage(`"3"`),
After: json.RawMessage(`"5"`),
AfterUnknown: json.RawMessage(`false`),
AfterSensitive: json.RawMessage(`false`),
BeforeSensitive: json.RawMessage(`false`),
ReplacePaths: nil,
},
},
},
"error with object binary comparisons": {
&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "object assertion failed",
Subject: &hcl.Range{
Filename: "test.tftest.hcl",
Start: hcl.Pos{Line: 12, Column: 17, Byte: 171},
End: hcl.Pos{Line: 20, Column: 6, Byte: 288},
},
Expression: &hclsyntax.BinaryOpExpr{
LHS: &hclsyntax.ScopeTraversalExpr{
Traversal: hcl.Traversal{
hcl.TraverseRoot{Name: "var"},
hcl.TraverseAttr{Name: "json_headers"},
},
},
RHS: &hclsyntax.LiteralValueExpr{
Val: cty.ObjectVal(map[string]cty.Value{
"Test-Header-1": cty.StringVal("foo"),
"Test-Header-2": cty.StringVal("bar"),
}),
},
Op: hclsyntax.OpEqual,
},
EvalContext: &hcl.EvalContext{
Variables: map[string]cty.Value{
"var": cty.ObjectVal(map[string]cty.Value{
"json_headers": cty.ObjectVal(map[string]cty.Value{
"Test-Header-1": cty.StringVal("foo"),
"Test-Header-2": cty.StringVal("foo"),
}),
}),
},
},
},
&Diagnostic{
Severity: "error",
Summary: "object assertion failed",
Range: &DiagnosticRange{
Filename: "test.tftest.hcl",
Start: Pos{
Line: 12,
Column: 17,
Byte: 171,
},
End: Pos{
Line: 20,
Column: 6,
Byte: 288,
},
},
Snippet: &DiagnosticSnippet{
Context: strPtr(`run "fails_without_useful_diff"`),
Code: (` condition = jsonencode(var.json_headers) == jsonencode([
"Test-Header-1: foo",
"Test-Header-2: bar",
])`),
StartLine: (12),
HighlightStartOffset: (0),
HighlightEndOffset: (117),
Values: []DiagnosticExpressionValue{
{
Traversal: "var.json_headers", Statement: "is object with 2 attributes",
},
},
},
Difference: &jsonplan.Change{
Before: json.RawMessage(`{"Test-Header-1":"foo","Test-Header-2":"foo"}`),
After: json.RawMessage(`{"Test-Header-1":"foo","Test-Header-2":"bar"}`),
AfterUnknown: json.RawMessage(`false`),
AfterSensitive: json.RawMessage(`{}`),
BeforeSensitive: json.RawMessage(`{}`),
ReplacePaths: nil,
},
},
},
"error with source code subject with multiple expression values": {
&hcl.Diagnostic{
Severity: hcl.DiagError,

View File

@@ -0,0 +1,33 @@
{
"severity": "error",
"summary": "Test assertion failed",
"detail": "",
"range": {
"filename": "test.tftest.hcl",
"start": {
"line": 7,
"column": 17,
"byte": 118
},
"end": {
"line": 7,
"column": 23,
"byte": 125
}
},
"snippet": {
"context": "run \"fails_without_useful_diff\"",
"code": " condition = 3 == 5",
"start_line": 7,
"highlight_start_offset": 15,
"highlight_end_offset": 22,
"values": []
},
"difference": {
"before": "3",
"after": "5",
"after_unknown": false,
"before_sensitive": false,
"after_sensitive": false
}
}

View File

@@ -0,0 +1,44 @@
{
"severity": "error",
"summary": "object assertion failed",
"detail": "",
"range": {
"filename": "test.tftest.hcl",
"start": {
"line": 12,
"column": 17,
"byte": 171
},
"end": {
"line": 20,
"column": 6,
"byte": 288
}
},
"snippet": {
"context": "run \"fails_without_useful_diff\"",
"code": " condition = jsonencode(var.json_headers) == jsonencode([\n \"Test-Header-1: foo\",\n \"Test-Header-2: bar\",\n ])",
"start_line": 12,
"highlight_start_offset": 0,
"highlight_end_offset": 117,
"values": [
{
"traversal": "var.json_headers",
"statement": "is object with 2 attributes"
}
]
},
"difference": {
"before": {
"Test-Header-1": "foo",
"Test-Header-2": "foo"
},
"after": {
"Test-Header-1": "foo",
"Test-Header-2": "bar"
},
"after_unknown": false,
"before_sensitive": {},
"after_sensitive": {}
}
}

View File

@@ -562,6 +562,69 @@ func MarshalResourceChanges(resources []*plans.ResourceInstanceChangeSrc, schema
return ret, nil
}
// GenerateChange is used to receive two values and calculate the difference
// between them in order to return a Change struct
func GenerateChange(beforeVal, afterVal cty.Value) (*Change, error) {
var err error
beforeVal, marks := beforeVal.UnmarkDeepWithPaths()
bs := jsonstate.SensitiveAsBoolWithPathValueMarks(beforeVal, marks)
beforeSensitive, err := ctyjson.Marshal(bs, bs.Type())
if err != nil {
return nil, err
}
afterVal, marks = afterVal.UnmarkDeepWithPaths()
as := jsonstate.SensitiveAsBoolWithPathValueMarks(afterVal, marks)
afterSensitive, err := ctyjson.Marshal(as, as.Type())
if err != nil {
return nil, err
}
var before, after []byte
var afterUnknown cty.Value
if beforeVal != cty.NilVal {
before, err = ctyjson.Marshal(beforeVal, beforeVal.Type())
if err != nil {
return nil, err
}
}
if afterVal != cty.NilVal {
if afterVal.IsWhollyKnown() {
after, err = ctyjson.Marshal(afterVal, afterVal.Type())
if err != nil {
return nil, err
}
afterUnknown = cty.False
} else {
filteredAfter := omitUnknowns(afterVal)
if filteredAfter.IsNull() {
after = nil
} else {
after, err = ctyjson.Marshal(filteredAfter, filteredAfter.Type())
if err != nil {
return nil, err
}
}
afterUnknown = unknownAsBool(afterVal)
}
}
a, _ := ctyjson.Marshal(afterUnknown, afterUnknown.Type())
return &Change{
Before: json.RawMessage(before),
After: json.RawMessage(after),
AfterUnknown: a,
BeforeSensitive: json.RawMessage(beforeSensitive),
AfterSensitive: json.RawMessage(afterSensitive),
// Just to be explicit, outputs cannot be imported so this is always
// nil.
Importing: nil,
}, nil
}
// MarshalOutputChanges converts the provided internal representation of
// Changes objects into the structured JSON representation.
//
@@ -589,40 +652,6 @@ func MarshalOutputChanges(changes *plans.Changes) (map[string]Change, error) {
if err != nil {
return nil, err
}
// We drop the marks from the change, as decoding is only an
// intermediate step to re-encode the values as json
changeV.Before, _ = changeV.Before.UnmarkDeep()
changeV.After, _ = changeV.After.UnmarkDeep()
var before, after []byte
var afterUnknown cty.Value
if changeV.Before != cty.NilVal {
before, err = ctyjson.Marshal(changeV.Before, changeV.Before.Type())
if err != nil {
return nil, err
}
}
if changeV.After != cty.NilVal {
if changeV.After.IsWhollyKnown() {
after, err = ctyjson.Marshal(changeV.After, changeV.After.Type())
if err != nil {
return nil, err
}
afterUnknown = cty.False
} else {
filteredAfter := omitUnknowns(changeV.After)
if filteredAfter.IsNull() {
after = nil
} else {
after, err = ctyjson.Marshal(filteredAfter, filteredAfter.Type())
if err != nil {
return nil, err
}
}
afterUnknown = unknownAsBool(changeV.After)
}
}
// The only information we have in the plan about output sensitivity is
// a boolean which is true if the output was or is marked sensitive. As
@@ -637,22 +666,16 @@ func MarshalOutputChanges(changes *plans.Changes) (map[string]Change, error) {
return nil, err
}
a, _ := ctyjson.Marshal(afterUnknown, afterUnknown.Type())
c := Change{
Actions: actionString(oc.Action.String()),
Before: json.RawMessage(before),
After: json.RawMessage(after),
AfterUnknown: a,
BeforeSensitive: json.RawMessage(sensitive),
AfterSensitive: json.RawMessage(sensitive),
// Just to be explicit, outputs cannot be imported so this is always
// nil.
Importing: nil,
change, err := GenerateChange(changeV.Before, changeV.After)
if err != nil {
return nil, err
}
outputChanges[oc.Addr.OutputValue.Name] = c
change.Actions = actionString(oc.Action.String())
change.BeforeSensitive = json.RawMessage(sensitive)
change.AfterSensitive = json.RawMessage(sensitive)
outputChanges[oc.Addr.OutputValue.Name] = *change
}
return outputChanges, nil

View File

@@ -16,6 +16,7 @@ import (
"github.com/zclconf/go-cty/cty"
"github.com/opentofu/opentofu/internal/addrs"
"github.com/opentofu/opentofu/internal/lang/marks"
"github.com/opentofu/opentofu/internal/plans"
)
@@ -514,3 +515,50 @@ func BenchmarkUnknownAsBool_9(b *testing.B) {
unknownAsBool(value)
}
}
// TestGenerateChange covers sensitivity tests for GenerateChange.
// TestOutputs test cases covered by outputs, but since is invalid to
// have outputs with sensitivity on the root module, we're creating this test
// to cover the remaining edge cases.
func TestGenerateChange(t *testing.T) {
tests := map[string]struct {
val1 cty.Value
val2 cty.Value
expected *Change
}{
"basic change": {
val1: cty.StringVal("test0"),
val2: cty.StringVal("test1"),
expected: &Change{
Before: json.RawMessage("\"test0\""),
After: json.RawMessage("\"test1\""),
AfterUnknown: json.RawMessage("false"),
BeforeSensitive: json.RawMessage("false"),
AfterSensitive: json.RawMessage("false"),
},
},
"handles sensitivity": {
val1: cty.NumberIntVal(3).Mark(marks.Sensitive),
val2: cty.NumberIntVal(5).Mark(marks.Sensitive),
expected: &Change{
Before: json.RawMessage("3"),
After: json.RawMessage("5"),
AfterUnknown: json.RawMessage("false"),
BeforeSensitive: json.RawMessage("true"),
AfterSensitive: json.RawMessage("true"),
},
},
}
for name, test := range tests {
t.Run(name, func(t *testing.T) {
change, err := GenerateChange(test.val1, test.val2)
if err != nil {
t.Fatalf("unexpected err: %s", err)
}
if !cmp.Equal(change, test.expected) {
t.Errorf("wrong result:\n %v\n", cmp.Diff(change, test.expected))
}
})
}
}