terraform/internal/tfdiags/testing_test.go
2026-02-17 13:56:34 +00:00

300 lines
8 KiB
Go

// Copyright IBM Corp. 2014, 2026
// SPDX-License-Identifier: BUSL-1.1
package tfdiags
import (
"testing"
"github.com/hashicorp/hcl/v2"
)
// testDiagWithExtra is a test helper that creates a diagnostic with extra info
type testDiagWithExtra struct {
severity Severity
summary string
detail string
subject *SourceRange
extra interface{}
}
var _ Diagnostic = testDiagWithExtra{}
var _ ComparableDiagnostic = testDiagWithExtra{}
func (d testDiagWithExtra) Severity() Severity {
return d.severity
}
func (d testDiagWithExtra) Description() Description {
return Description{
Summary: d.summary,
Detail: d.detail,
}
}
func (d testDiagWithExtra) Source() Source {
return Source{
Subject: d.subject,
}
}
func (d testDiagWithExtra) FromExpr() *FromExpr {
return nil
}
func (d testDiagWithExtra) ExtraInfo() interface{} {
return d.extra
}
func (d testDiagWithExtra) Equals(other ComparableDiagnostic) bool {
od, ok := other.(testDiagWithExtra)
if !ok {
return false
}
return d.severity == od.severity && d.summary == od.summary && d.detail == od.detail
}
// These tests are to ensure that the normalisation of the diagnostics' concrete
// types doesn't impact how the diagnostics are compared.
//
// Full tests of the comparison logic in DiagnosticComparer and
// DiagnosticComparerWithSource are in compare_test.go
func Test_assertDiagnosticMatch_differentConcreteTypes(t *testing.T) {
baseError := hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "error",
Detail: "this is an error",
}
cases := map[string]struct {
diag1 Diagnostic
diag2 Diagnostic
expectDiff bool
}{
"diagnostics match but are different concrete types": {
expectDiff: false,
diag1: hclDiagnostic{&baseError},
diag2: makeRPCFriendlyDiag(hclDiagnostic{&baseError}),
},
"diagnostics don't match and are different concrete types": {
expectDiff: true,
diag1: hclDiagnostic{&baseError},
diag2: func() Diagnostic {
d := baseError
d.Severity = hcl.DiagWarning // Altered severity level
return makeRPCFriendlyDiag(hclDiagnostic{&d})
}(),
},
}
for tn, tc := range cases {
t.Run(tn, func(t *testing.T) {
// This should show no diff as, internally, the two diags are transformed into the same
// concrete type
diff := assertDiagnosticMatch(tc.diag1, tc.diag2)
if !tc.expectDiff && len(diff) > 0 {
t.Fatalf("unexpected diff:\n%s", diff)
}
if tc.expectDiff && len(diff) == 0 {
t.Fatalf("expected a diff but got none")
}
})
}
}
func Test_assertDiagnosticsAndExtrasMatch(t *testing.T) {
baseError := hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "error",
Detail: "this is an error",
}
cases := map[string]struct {
diags1 Diagnostics
diags2 Diagnostics
expectDiff bool
}{
"both empty": {
expectDiff: false,
diags1: Diagnostics{},
diags2: Diagnostics{},
},
"diagnostics match with no extras": {
expectDiff: false,
diags1: Diagnostics{hclDiagnostic{&baseError}},
diags2: Diagnostics{makeRPCFriendlyDiag(hclDiagnostic{&baseError})},
},
"diagnostics match with same deprecation extra": {
expectDiff: false,
diags1: Diagnostics{testDiagWithExtra{
severity: Error,
summary: "error",
detail: "this is an error",
extra: &DeprecationOriginDiagnosticExtra{OriginDescription: "test origin"},
}},
diags2: Diagnostics{testDiagWithExtra{
severity: Error,
summary: "error",
detail: "this is an error",
extra: &DeprecationOriginDiagnosticExtra{OriginDescription: "test origin"},
}},
},
"diagnostics match but different deprecation extras": {
expectDiff: true,
diags1: Diagnostics{testDiagWithExtra{
severity: Error,
summary: "error",
detail: "this is an error",
extra: &DeprecationOriginDiagnosticExtra{OriginDescription: "origin 1"},
}},
diags2: Diagnostics{testDiagWithExtra{
severity: Error,
summary: "error",
detail: "this is an error",
extra: &DeprecationOriginDiagnosticExtra{OriginDescription: "origin 2"},
}},
},
"diagnostics match but one has extra and one doesn't": {
expectDiff: true,
diags1: Diagnostics{testDiagWithExtra{
severity: Error,
summary: "error",
detail: "this is an error",
extra: &DeprecationOriginDiagnosticExtra{OriginDescription: "test origin"},
}},
diags2: Diagnostics{testDiagWithExtra{
severity: Error,
summary: "error",
detail: "this is an error",
extra: nil,
}},
},
"diagnostics don't match - fails on base comparison": {
expectDiff: true,
diags1: Diagnostics{hclDiagnostic{&baseError}},
diags2: func() Diagnostics {
d := baseError
d.Severity = hcl.DiagWarning
return Diagnostics{hclDiagnostic{&d}}
}(),
},
"multiple diagnostics with matching extras": {
expectDiff: false,
diags1: Diagnostics{
testDiagWithExtra{
severity: Warning,
summary: "warning 1",
detail: "detail 1",
extra: &DeprecationOriginDiagnosticExtra{OriginDescription: "origin 1"},
},
testDiagWithExtra{
severity: Error,
summary: "error 1",
detail: "detail 2",
extra: &DeprecationOriginDiagnosticExtra{OriginDescription: "origin 2"},
},
},
diags2: Diagnostics{
testDiagWithExtra{
severity: Warning,
summary: "warning 1",
detail: "detail 1",
extra: &DeprecationOriginDiagnosticExtra{OriginDescription: "origin 1"},
},
testDiagWithExtra{
severity: Error,
summary: "error 1",
detail: "detail 2",
extra: &DeprecationOriginDiagnosticExtra{OriginDescription: "origin 2"},
},
},
},
"multiple diagnostics with one mismatched extra": {
expectDiff: true,
diags1: Diagnostics{
testDiagWithExtra{
severity: Warning,
summary: "warning 1",
detail: "detail 1",
extra: &DeprecationOriginDiagnosticExtra{OriginDescription: "origin 1"},
},
testDiagWithExtra{
severity: Error,
summary: "error 1",
detail: "detail 2",
extra: &DeprecationOriginDiagnosticExtra{OriginDescription: "origin 2"},
},
},
diags2: Diagnostics{
testDiagWithExtra{
severity: Warning,
summary: "warning 1",
detail: "detail 1",
extra: &DeprecationOriginDiagnosticExtra{OriginDescription: "origin 1"},
},
testDiagWithExtra{
severity: Error,
summary: "error 1",
detail: "detail 2",
extra: &DeprecationOriginDiagnosticExtra{OriginDescription: "different origin"},
},
},
},
}
for tn, tc := range cases {
t.Run(tn, func(t *testing.T) {
diff := assertDiagnosticsAndExtrasMatch(tc.diags1, tc.diags2)
if !tc.expectDiff && len(diff) > 0 {
t.Fatalf("unexpected diff:\n%s", diff)
}
if tc.expectDiff && len(diff) == 0 {
t.Fatalf("expected a diff but got none")
}
})
}
}
func Test_assertDiagnosticsMatch_differentConcreteTypes(t *testing.T) {
baseError := hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "error",
Detail: "this is an error",
}
cases := map[string]struct {
diags1 Diagnostics
diags2 Diagnostics
expectDiff bool
}{
"diagnostics match but are different concrete types": {
expectDiff: false,
diags1: Diagnostics{hclDiagnostic{&baseError}},
diags2: Diagnostics{makeRPCFriendlyDiag(hclDiagnostic{&baseError})},
},
"diagnostics don't match and are different concrete types": {
expectDiff: true,
diags1: Diagnostics{hclDiagnostic{&baseError}},
diags2: func() Diagnostics {
d := baseError
d.Severity = hcl.DiagWarning // Altered severity level
return Diagnostics{makeRPCFriendlyDiag(hclDiagnostic{&d})}
}(),
},
}
for tn, tc := range cases {
t.Run(tn, func(t *testing.T) {
// This should show no diff as, internally, the two diags are transformed into the same
// concrete type
diff := assertDiagnosticsMatch(tc.diags1, tc.diags2)
if !tc.expectDiff && len(diff) > 0 {
t.Fatalf("unexpected diff:\n%s", diff)
}
if tc.expectDiff && len(diff) == 0 {
t.Fatalf("expected a diff but got none")
}
})
}
}