add testing helper that checks diagnostic extra infos

This commit is contained in:
Daniel Schmidt 2026-02-05 13:03:47 +01:00
parent c7da02b4a8
commit ad3837f37a
6 changed files with 718 additions and 15 deletions

View file

@ -7236,7 +7236,7 @@ module "mod2" {
_, diags := ctx.Plan(m, nil, SimplePlanOpts(plans.NormalMode, InputValues{}))
tfdiags.AssertDiagnosticsMatch(t, diags, tfdiags.Diagnostics{}.Append(
tfdiags.AssertDiagnosticsAndExtrasMatch(t, diags, tfdiags.Diagnostics{}.Append(
&hcl.Diagnostic{
Severity: hcl.DiagWarning,
Summary: "Deprecated value used",
@ -7246,6 +7246,9 @@ module "mod2" {
Start: hcl.Pos{Line: 6, Column: 12, Byte: 84},
End: hcl.Pos{Line: 6, Column: 26, Byte: 98},
},
Extra: &tfdiags.DeprecationOriginDiagnosticExtra{
OriginDescription: "module.mod.old",
},
},
).Append(
&hcl.Diagnostic{
@ -7257,6 +7260,9 @@ module "mod2" {
Start: hcl.Pos{Line: 12, Column: 12, Byte: 225},
End: hcl.Pos{Line: 12, Column: 26, Byte: 239},
},
Extra: &tfdiags.DeprecationOriginDiagnosticExtra{
OriginDescription: "module.mod.old",
},
},
).Append(
&hcl.Diagnostic{
@ -7268,6 +7274,9 @@ module "mod2" {
Start: hcl.Pos{Line: 15, Column: 10, Byte: 284},
End: hcl.Pos{Line: 15, Column: 24, Byte: 298},
},
Extra: &tfdiags.DeprecationOriginDiagnosticExtra{
OriginDescription: "module.mod.old",
},
},
).Append(
&hcl.Diagnostic{
@ -7279,6 +7288,9 @@ module "mod2" {
Start: hcl.Pos{Line: 18, Column: 10, Byte: 355},
End: hcl.Pos{Line: 18, Column: 49, Byte: 394},
},
Extra: &tfdiags.DeprecationOriginDiagnosticExtra{
OriginDescription: "module.mod.old",
},
},
).Append(
&hcl.Diagnostic{
@ -7290,6 +7302,9 @@ module "mod2" {
Start: hcl.Pos{Line: 22, Column: 10, Byte: 451},
End: hcl.Pos{Line: 22, Column: 24, Byte: 465},
},
Extra: &tfdiags.DeprecationOriginDiagnosticExtra{
OriginDescription: "module.mod.old",
},
},
))
}
@ -7399,6 +7414,9 @@ output "test_output_no_warning" {
Start: hcl.Pos{Line: 17, Column: 10, Byte: 180},
End: hcl.Pos{Line: 17, Column: 24, Byte: 194},
},
Extra: &tfdiags.DeprecationOriginDiagnosticExtra{
OriginDescription: "module.mod.old",
},
},
&hcl.Diagnostic{
Severity: hcl.DiagWarning,
@ -7409,6 +7427,9 @@ output "test_output_no_warning" {
Start: hcl.Pos{Line: 21, Column: 10, Byte: 250},
End: hcl.Pos{Line: 21, Column: 28, Byte: 268},
},
Extra: &tfdiags.DeprecationOriginDiagnosticExtra{
OriginDescription: "module.mod2[0].old",
},
},
&hcl.Diagnostic{
Severity: hcl.DiagWarning,
@ -7419,10 +7440,13 @@ output "test_output_no_warning" {
Start: hcl.Pos{Line: 25, Column: 10, Byte: 324},
End: hcl.Pos{Line: 25, Column: 24, Byte: 338},
},
Extra: &tfdiags.DeprecationOriginDiagnosticExtra{
OriginDescription: "module.mod.new",
},
},
)
tfdiags.AssertDiagnosticsMatch(t, diags, expectedDiags)
tfdiags.AssertDiagnosticsAndExtrasMatch(t, diags, expectedDiags)
}
func TestContext2Plan_deprecated_output_expansion_with_splat(t *testing.T) {
@ -7466,7 +7490,7 @@ output "test_output2" {
_, diags := ctx.Plan(m, nil, SimplePlanOpts(plans.NormalMode, InputValues{}))
tfdiags.AssertDiagnosticsMatch(t, diags, tfdiags.Diagnostics{}.Append(
tfdiags.AssertDiagnosticsAndExtrasMatch(t, diags, tfdiags.Diagnostics{}.Append(
&hcl.Diagnostic{
Severity: hcl.DiagWarning,
Summary: "Deprecated value used",
@ -7476,6 +7500,9 @@ output "test_output2" {
Start: hcl.Pos{Line: 7, Column: 10, Byte: 80},
End: hcl.Pos{Line: 7, Column: 27, Byte: 97},
},
Extra: &tfdiags.DeprecationOriginDiagnosticExtra{
OriginDescription: "module.mod[0].old",
},
},
).Append(
&hcl.Diagnostic{
@ -7487,6 +7514,9 @@ output "test_output2" {
Start: hcl.Pos{Line: 7, Column: 10, Byte: 80},
End: hcl.Pos{Line: 7, Column: 27, Byte: 97},
},
Extra: &tfdiags.DeprecationOriginDiagnosticExtra{
OriginDescription: "module.mod[1].old",
},
},
))
}
@ -7540,7 +7570,7 @@ check "deprecated_check" {
})
_, diags := ctx.Plan(m, nil, SimplePlanOpts(plans.NormalMode, InputValues{}))
tfdiags.AssertDiagnosticsMatch(t, diags, tfdiags.Diagnostics{}.Append(
tfdiags.AssertDiagnosticsAndExtrasMatch(t, diags, tfdiags.Diagnostics{}.Append(
&hcl.Diagnostic{
Severity: hcl.DiagWarning,
Summary: "Deprecated value used",
@ -7550,6 +7580,9 @@ check "deprecated_check" {
Start: hcl.Pos{Line: 7, Column: 21, Byte: 97},
End: hcl.Pos{Line: 7, Column: 64, Byte: 140},
},
Extra: &tfdiags.DeprecationOriginDiagnosticExtra{
OriginDescription: "module.mod.old",
},
},
).Append(
&hcl.Diagnostic{
@ -7561,6 +7594,9 @@ check "deprecated_check" {
Start: hcl.Pos{Line: 8, Column: 21, Byte: 161},
End: hcl.Pos{Line: 8, Column: 108, Byte: 248},
},
Extra: &tfdiags.DeprecationOriginDiagnosticExtra{
OriginDescription: "module.mod.old",
},
},
))
}
@ -7623,7 +7659,7 @@ resource "test_resource" "test" {
_, diags := ctx.Plan(m, nil, SimplePlanOpts(plans.NormalMode, InputValues{}))
tfdiags.AssertDiagnosticsMatch(t, diags, tfdiags.Diagnostics{}.Append(
tfdiags.AssertDiagnosticsAndExtrasMatch(t, diags, tfdiags.Diagnostics{}.Append(
&hcl.Diagnostic{
Severity: hcl.DiagWarning,
Summary: "Deprecated value used",
@ -7633,6 +7669,9 @@ resource "test_resource" "test" {
Start: hcl.Pos{Line: 16, Column: 29, Byte: 207},
End: hcl.Pos{Line: 16, Column: 52, Byte: 230},
},
Extra: &tfdiags.DeprecationOriginDiagnosticExtra{
OriginDescription: "module.mod.old",
},
},
).Append(
&hcl.Diagnostic{
@ -7644,6 +7683,9 @@ resource "test_resource" "test" {
Start: hcl.Pos{Line: 22, Column: 29, Byte: 389},
End: hcl.Pos{Line: 22, Column: 89, Byte: 449},
},
Extra: &tfdiags.DeprecationOriginDiagnosticExtra{
OriginDescription: "module.mod.old",
},
},
))
}
@ -7880,7 +7922,7 @@ locals {
_, diags := ctx.Plan(m, nil, SimplePlanOpts(plans.NormalMode, InputValues{}))
tfdiags.AssertDiagnosticsMatch(t, diags, tfdiags.Diagnostics{}.Append(&hcl.Diagnostic{
tfdiags.AssertDiagnosticsAndExtrasMatch(t, diags, tfdiags.Diagnostics{}.Append(&hcl.Diagnostic{
Severity: hcl.DiagWarning,
Summary: "Deprecated value used",
Detail: "Please stop using this",
@ -7889,5 +7931,8 @@ locals {
Start: hcl.Pos{Line: 12, Column: 24, Byte: 259},
End: hcl.Pos{Line: 12, Column: 46, Byte: 281},
},
Extra: &tfdiags.DeprecationOriginDiagnosticExtra{
OriginDescription: "module.silenced[0].old",
},
}))
}

View file

@ -4403,7 +4403,7 @@ resource "test_resource" "test" {
diags := ctx.Validate(m, &ValidateOpts{})
tfdiags.AssertDiagnosticsMatch(t, diags, tfdiags.Diagnostics{}.Append(&hcl.Diagnostic{
tfdiags.AssertDiagnosticsAndExtrasMatch(t, diags, tfdiags.Diagnostics{}.Append(&hcl.Diagnostic{
Severity: hcl.DiagWarning,
Summary: "Deprecated value used",
Detail: "Please stop using this",
@ -4412,6 +4412,9 @@ resource "test_resource" "test" {
Start: hcl.Pos{Line: 6, Column: 16, Byte: 84},
End: hcl.Pos{Line: 6, Column: 30, Byte: 98},
},
Extra: &tfdiags.DeprecationOriginDiagnosticExtra{
OriginDescription: "module.mod.old",
},
}).Append(&hcl.Diagnostic{
Severity: hcl.DiagWarning,
Summary: "Deprecated value used",
@ -4421,6 +4424,9 @@ resource "test_resource" "test" {
Start: hcl.Pos{Line: 9, Column: 16, Byte: 160},
End: hcl.Pos{Line: 9, Column: 30, Byte: 174},
},
Extra: &tfdiags.DeprecationOriginDiagnosticExtra{
OriginDescription: "module.mod.old",
},
}))
}
@ -4473,7 +4479,7 @@ resource "test_resource" "test" {
diags := ctx.Validate(m, &ValidateOpts{})
tfdiags.AssertDiagnosticsMatch(t, diags, tfdiags.Diagnostics{}.Append(&hcl.Diagnostic{
tfdiags.AssertDiagnosticsAndExtrasMatch(t, diags, tfdiags.Diagnostics{}.Append(&hcl.Diagnostic{
Severity: hcl.DiagWarning,
Summary: "Deprecated value used",
Detail: "Please stop using this",
@ -4482,6 +4488,9 @@ resource "test_resource" "test" {
Start: hcl.Pos{Line: 6, Column: 11, Byte: 79},
End: hcl.Pos{Line: 6, Column: 25, Byte: 93},
},
Extra: &tfdiags.DeprecationOriginDiagnosticExtra{
OriginDescription: "module.mod.old",
},
}).Append(&hcl.Diagnostic{
Severity: hcl.DiagWarning,
Summary: "Deprecated value used",
@ -4491,6 +4500,9 @@ resource "test_resource" "test" {
Start: hcl.Pos{Line: 9, Column: 16, Byte: 155},
End: hcl.Pos{Line: 9, Column: 30, Byte: 169},
},
Extra: &tfdiags.DeprecationOriginDiagnosticExtra{
OriginDescription: "module.mod.old",
},
}))
}
@ -4562,7 +4574,7 @@ module "mod2" {
diags := ctx.Validate(m, &ValidateOpts{})
tfdiags.AssertDiagnosticsMatch(t, diags, tfdiags.Diagnostics{}.Append(&hcl.Diagnostic{
tfdiags.AssertDiagnosticsAndExtrasMatch(t, diags, tfdiags.Diagnostics{}.Append(&hcl.Diagnostic{
Severity: hcl.DiagWarning,
Summary: "Deprecated value used",
Detail: "Please stop using this",
@ -4571,6 +4583,9 @@ module "mod2" {
Start: hcl.Pos{Line: 6, Column: 12, Byte: 84},
End: hcl.Pos{Line: 6, Column: 26, Byte: 98},
},
Extra: &tfdiags.DeprecationOriginDiagnosticExtra{
OriginDescription: "module.mod.old",
},
}).Append(&hcl.Diagnostic{
Severity: hcl.DiagWarning,
Summary: "Deprecated value used",
@ -4580,6 +4595,9 @@ module "mod2" {
Start: hcl.Pos{Line: 12, Column: 12, Byte: 225},
End: hcl.Pos{Line: 12, Column: 26, Byte: 239},
},
Extra: &tfdiags.DeprecationOriginDiagnosticExtra{
OriginDescription: "module.mod.old",
},
}).Append(&hcl.Diagnostic{
Severity: hcl.DiagWarning,
Summary: "Deprecated value used",
@ -4589,6 +4607,9 @@ module "mod2" {
Start: hcl.Pos{Line: 15, Column: 10, Byte: 284},
End: hcl.Pos{Line: 15, Column: 24, Byte: 298},
},
Extra: &tfdiags.DeprecationOriginDiagnosticExtra{
OriginDescription: "module.mod.old",
},
}).Append(&hcl.Diagnostic{
Severity: hcl.DiagWarning,
Summary: "Deprecated value used",
@ -4598,6 +4619,9 @@ module "mod2" {
Start: hcl.Pos{Line: 18, Column: 10, Byte: 355},
End: hcl.Pos{Line: 18, Column: 49, Byte: 394},
},
Extra: &tfdiags.DeprecationOriginDiagnosticExtra{
OriginDescription: "module.mod.old",
},
}).Append(&hcl.Diagnostic{
Severity: hcl.DiagWarning,
Summary: "Deprecated value used",
@ -4607,6 +4631,9 @@ module "mod2" {
Start: hcl.Pos{Line: 22, Column: 10, Byte: 451},
End: hcl.Pos{Line: 22, Column: 24, Byte: 465},
},
Extra: &tfdiags.DeprecationOriginDiagnosticExtra{
OriginDescription: "module.mod.old",
},
}))
}
@ -4683,7 +4710,7 @@ output "test_output_deprecated_use" {
diags := ctx.Validate(m, &ValidateOpts{})
tfdiags.AssertDiagnosticsMatch(
tfdiags.AssertDiagnosticsAndExtrasMatch(
t,
diags,
tfdiags.Diagnostics{}.Append(
@ -4696,6 +4723,9 @@ output "test_output_deprecated_use" {
Start: hcl.Pos{Line: 9, Column: 12, Byte: 124},
End: hcl.Pos{Line: 9, Column: 35, Byte: 147},
},
Extra: &tfdiags.DeprecationOriginDiagnosticExtra{
OriginDescription: "module.mod.redeprecated",
},
},
).Append(
&hcl.Diagnostic{
@ -4707,6 +4737,9 @@ output "test_output_deprecated_use" {
Start: hcl.Pos{Line: 15, Column: 10, Byte: 336},
End: hcl.Pos{Line: 15, Column: 33, Byte: 359},
},
Extra: &tfdiags.DeprecationOriginDiagnosticExtra{
OriginDescription: "module.mod.redeprecated",
},
},
).Append(
&hcl.Diagnostic{
@ -4718,6 +4751,9 @@ output "test_output_deprecated_use" {
Start: hcl.Pos{Line: 10, Column: 11, Byte: 295},
End: hcl.Pos{Line: 10, Column: 28, Byte: 312},
},
Extra: &tfdiags.DeprecationOriginDiagnosticExtra{
OriginDescription: "module.nested.old",
},
},
),
)
@ -4769,7 +4805,7 @@ resource "test_resource" "test" {
diags := ctx.Validate(m, &ValidateOpts{})
tfdiags.AssertDiagnosticsMatch(t, diags, tfdiags.Diagnostics{}.Append(&hcl.Diagnostic{
tfdiags.AssertDiagnosticsAndExtrasMatch(t, diags, tfdiags.Diagnostics{}.Append(&hcl.Diagnostic{
Severity: hcl.DiagWarning,
Summary: "Deprecated value used",
Detail: "Please stop using this",
@ -4778,6 +4814,9 @@ resource "test_resource" "test" {
Start: hcl.Pos{Line: 7, Column: 11, Byte: 59},
End: hcl.Pos{Line: 7, Column: 25, Byte: 73},
},
Extra: &tfdiags.DeprecationOriginDiagnosticExtra{
OriginDescription: "module.mod.old",
},
}))
}
@ -4836,7 +4875,7 @@ action "test_action" "test" {
diags := ctx.Validate(m, &ValidateOpts{})
tfdiags.AssertDiagnosticsMatch(t, diags, tfdiags.Diagnostics{}.Append(&hcl.Diagnostic{
tfdiags.AssertDiagnosticsAndExtrasMatch(t, diags, tfdiags.Diagnostics{}.Append(&hcl.Diagnostic{
Severity: hcl.DiagWarning,
Summary: "Deprecated value used",
Detail: "Please stop using this",
@ -4845,6 +4884,9 @@ action "test_action" "test" {
Start: hcl.Pos{Line: 8, Column: 12, Byte: 92},
End: hcl.Pos{Line: 8, Column: 26, Byte: 106},
},
Extra: &tfdiags.DeprecationOriginDiagnosticExtra{
OriginDescription: "module.mod.old",
},
}))
}
@ -4907,7 +4949,7 @@ module "sink" {
diags := ctx.Validate(m, &ValidateOpts{})
tfdiags.AssertDiagnosticsMatch(t, diags, tfdiags.Diagnostics{}.Append(&hcl.Diagnostic{
tfdiags.AssertDiagnosticsAndExtrasMatch(t, diags, tfdiags.Diagnostics{}.Append(&hcl.Diagnostic{
Severity: hcl.DiagWarning,
Summary: "Deprecated value used",
Detail: "Please stop using this",
@ -4916,6 +4958,9 @@ module "sink" {
Start: hcl.Pos{Line: 8, Column: 13, Byte: 100},
End: hcl.Pos{Line: 8, Column: 30, Byte: 117},
},
Extra: &tfdiags.DeprecationOriginDiagnosticExtra{
OriginDescription: "module.source.old",
},
}))
}
@ -5063,7 +5108,7 @@ locals {
diags := ctx.Validate(m, &ValidateOpts{})
tfdiags.AssertDiagnosticsMatch(t, diags, tfdiags.Diagnostics{}.Append(&hcl.Diagnostic{
tfdiags.AssertDiagnosticsAndExtrasMatch(t, diags, tfdiags.Diagnostics{}.Append(&hcl.Diagnostic{
Severity: hcl.DiagWarning,
Summary: "Deprecated value used",
Detail: "Please stop using this",
@ -5072,6 +5117,9 @@ locals {
Start: hcl.Pos{Line: 15, Column: 22, Byte: 285},
End: hcl.Pos{Line: 15, Column: 39, Byte: 302},
},
Extra: &tfdiags.DeprecationOriginDiagnosticExtra{
OriginDescription: "module.normal.old",
},
}).Append(&hcl.Diagnostic{
Severity: hcl.DiagWarning,
Summary: "Deprecated value used",
@ -5081,6 +5129,9 @@ locals {
Start: hcl.Pos{Line: 16, Column: 24, Byte: 336},
End: hcl.Pos{Line: 16, Column: 43, Byte: 355},
},
Extra: &tfdiags.DeprecationOriginDiagnosticExtra{
OriginDescription: "module.silenced.old",
},
}).Append(&hcl.Diagnostic{
Severity: hcl.DiagWarning,
Summary: "Deprecated value used",
@ -5090,6 +5141,9 @@ locals {
Start: hcl.Pos{Line: 12, Column: 9, Byte: 141},
End: hcl.Pos{Line: 12, Column: 26, Byte: 158},
},
Extra: &tfdiags.DeprecationOriginDiagnosticExtra{
OriginDescription: "module.nested.old",
},
}).Append(&hcl.Diagnostic{
Severity: hcl.DiagWarning,
Summary: "Deprecated value used",
@ -5099,6 +5153,9 @@ locals {
Start: hcl.Pos{Line: 12, Column: 9, Byte: 155},
End: hcl.Pos{Line: 12, Column: 26, Byte: 172},
},
Extra: &tfdiags.DeprecationOriginDiagnosticExtra{
OriginDescription: "module.deeper.old",
},
}))
}
@ -5160,7 +5217,7 @@ output "test_output" {
diags := ctx.Validate(m, &ValidateOpts{})
tfdiags.AssertDiagnosticsMatch(t, diags, tfdiags.Diagnostics{}.Append(&hcl.Diagnostic{
tfdiags.AssertDiagnosticsAndExtrasMatch(t, diags, tfdiags.Diagnostics{}.Append(&hcl.Diagnostic{
Severity: hcl.DiagWarning,
Summary: "Deprecated value used",
Detail: "Please stop using this",
@ -5169,5 +5226,8 @@ output "test_output" {
Start: hcl.Pos{Line: 11, Column: 13, Byte: 143},
End: hcl.Pos{Line: 11, Column: 27, Byte: 157},
},
Extra: &tfdiags.DeprecationOriginDiagnosticExtra{
OriginDescription: "module.mod.old",
},
}))
}

View file

@ -307,3 +307,80 @@ func (c *DeprecationOriginDiagnosticExtra) WrapDiagnosticExtra(inner interface{}
func (c *DeprecationOriginDiagnosticExtra) DeprecatedOriginDescription() string {
return c.OriginDescription
}
// DiagnosticExtrasEqual compares the extra information of two diagnostics.
// This is intended to be used for testing purposes where we want to verify
// that diagnostics have the expected extra information.
//
// The comparison checks all known DiagnosticExtra* interfaces defined in this file.
// NOTE: This function should be kept in sync with the extra interfaces defined
// in this file. When adding a new DiagnosticExtra* interface, also add a
// corresponding check here to ensure test coverage remains comprehensive.
func DiagnosticExtrasEqual(diag1, diag2 Diagnostic) bool {
// Check DiagnosticExtraBecauseUnknown
unknown1 := ExtraInfo[DiagnosticExtraBecauseUnknown](diag1)
unknown2 := ExtraInfo[DiagnosticExtraBecauseUnknown](diag2)
if (unknown1 == nil) != (unknown2 == nil) {
return false
}
if unknown1 != nil && unknown1.DiagnosticCausedByUnknown() != unknown2.DiagnosticCausedByUnknown() {
return false
}
// Check DiagnosticExtraBecauseEphemeral
ephemeral1 := ExtraInfo[DiagnosticExtraBecauseEphemeral](diag1)
ephemeral2 := ExtraInfo[DiagnosticExtraBecauseEphemeral](diag2)
if (ephemeral1 == nil) != (ephemeral2 == nil) {
return false
}
if ephemeral1 != nil && ephemeral1.DiagnosticCausedByEphemeral() != ephemeral2.DiagnosticCausedByEphemeral() {
return false
}
// Check DiagnosticExtraBecauseSensitive
sensitive1 := ExtraInfo[DiagnosticExtraBecauseSensitive](diag1)
sensitive2 := ExtraInfo[DiagnosticExtraBecauseSensitive](diag2)
if (sensitive1 == nil) != (sensitive2 == nil) {
return false
}
if sensitive1 != nil && sensitive1.DiagnosticCausedBySensitive() != sensitive2.DiagnosticCausedBySensitive() {
return false
}
// Check DiagnosticExtraDoNotConsolidate
doNotConsolidate1 := ExtraInfo[DiagnosticExtraDoNotConsolidate](diag1)
doNotConsolidate2 := ExtraInfo[DiagnosticExtraDoNotConsolidate](diag2)
if (doNotConsolidate1 == nil) != (doNotConsolidate2 == nil) {
return false
}
if doNotConsolidate1 != nil && doNotConsolidate1.DoNotConsolidateDiagnostic() != doNotConsolidate2.DoNotConsolidateDiagnostic() {
return false
}
// Check DiagnosticExtraCausedByTestFailure
testFailure1 := ExtraInfo[DiagnosticExtraCausedByTestFailure](diag1)
testFailure2 := ExtraInfo[DiagnosticExtraCausedByTestFailure](diag2)
if (testFailure1 == nil) != (testFailure2 == nil) {
return false
}
if testFailure1 != nil {
if testFailure1.DiagnosticCausedByTestFailure() != testFailure2.DiagnosticCausedByTestFailure() {
return false
}
if testFailure1.IsTestVerboseMode() != testFailure2.IsTestVerboseMode() {
return false
}
}
// Check DiagnosticExtraDeprecationOrigin
deprecation1 := ExtraInfo[DiagnosticExtraDeprecationOrigin](diag1)
deprecation2 := ExtraInfo[DiagnosticExtraDeprecationOrigin](diag2)
if (deprecation1 == nil) != (deprecation2 == nil) {
return false
}
if deprecation1 != nil && deprecation1.DeprecatedOriginDescription() != deprecation2.DeprecatedOriginDescription() {
return false
}
return true
}

View file

@ -0,0 +1,275 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package tfdiags
import (
"testing"
)
// testDiagnosticWithExtra is a test helper that creates a diagnostic with extra info
type testDiagnosticWithExtra struct {
severity Severity
summary string
detail string
extra interface{}
}
var _ Diagnostic = testDiagnosticWithExtra{}
func (d testDiagnosticWithExtra) Severity() Severity {
return d.severity
}
func (d testDiagnosticWithExtra) Description() Description {
return Description{
Summary: d.summary,
Detail: d.detail,
}
}
func (d testDiagnosticWithExtra) Source() Source {
return Source{}
}
func (d testDiagnosticWithExtra) FromExpr() *FromExpr {
return nil
}
func (d testDiagnosticWithExtra) ExtraInfo() interface{} {
return d.extra
}
// Mock implementations for testing
type mockBecauseUnknown struct {
caused bool
}
func (m mockBecauseUnknown) DiagnosticCausedByUnknown() bool {
return m.caused
}
type mockBecauseEphemeral struct {
caused bool
}
func (m mockBecauseEphemeral) DiagnosticCausedByEphemeral() bool {
return m.caused
}
type mockBecauseSensitive struct {
caused bool
}
func (m mockBecauseSensitive) DiagnosticCausedBySensitive() bool {
return m.caused
}
type mockDoNotConsolidate struct {
doNotConsolidate bool
}
func (m mockDoNotConsolidate) DoNotConsolidateDiagnostic() bool {
return m.doNotConsolidate
}
type mockCausedByTestFailure struct {
causedByTestFailure bool
verboseMode bool
}
func (m mockCausedByTestFailure) DiagnosticCausedByTestFailure() bool {
return m.causedByTestFailure
}
func (m mockCausedByTestFailure) IsTestVerboseMode() bool {
return m.verboseMode
}
func TestDiagnosticExtrasEqual(t *testing.T) {
tests := map[string]struct {
diag1 Diagnostic
diag2 Diagnostic
wantEqual bool
}{
"both nil extras": {
diag1: testDiagnosticWithExtra{extra: nil},
diag2: testDiagnosticWithExtra{extra: nil},
wantEqual: true,
},
// DiagnosticExtraBecauseUnknown tests
"unknown: both have same value true": {
diag1: testDiagnosticWithExtra{extra: mockBecauseUnknown{caused: true}},
diag2: testDiagnosticWithExtra{extra: mockBecauseUnknown{caused: true}},
wantEqual: true,
},
"unknown: both have same value false": {
diag1: testDiagnosticWithExtra{extra: mockBecauseUnknown{caused: false}},
diag2: testDiagnosticWithExtra{extra: mockBecauseUnknown{caused: false}},
wantEqual: true,
},
"unknown: different values": {
diag1: testDiagnosticWithExtra{extra: mockBecauseUnknown{caused: true}},
diag2: testDiagnosticWithExtra{extra: mockBecauseUnknown{caused: false}},
wantEqual: false,
},
"unknown: one nil one not": {
diag1: testDiagnosticWithExtra{extra: mockBecauseUnknown{caused: true}},
diag2: testDiagnosticWithExtra{extra: nil},
wantEqual: false,
},
// DiagnosticExtraBecauseEphemeral tests
"ephemeral: both have same value true": {
diag1: testDiagnosticWithExtra{extra: mockBecauseEphemeral{caused: true}},
diag2: testDiagnosticWithExtra{extra: mockBecauseEphemeral{caused: true}},
wantEqual: true,
},
"ephemeral: different values": {
diag1: testDiagnosticWithExtra{extra: mockBecauseEphemeral{caused: true}},
diag2: testDiagnosticWithExtra{extra: mockBecauseEphemeral{caused: false}},
wantEqual: false,
},
"ephemeral: one nil one not": {
diag1: testDiagnosticWithExtra{extra: mockBecauseEphemeral{caused: true}},
diag2: testDiagnosticWithExtra{extra: nil},
wantEqual: false,
},
// DiagnosticExtraBecauseSensitive tests
"sensitive: both have same value true": {
diag1: testDiagnosticWithExtra{extra: mockBecauseSensitive{caused: true}},
diag2: testDiagnosticWithExtra{extra: mockBecauseSensitive{caused: true}},
wantEqual: true,
},
"sensitive: different values": {
diag1: testDiagnosticWithExtra{extra: mockBecauseSensitive{caused: true}},
diag2: testDiagnosticWithExtra{extra: mockBecauseSensitive{caused: false}},
wantEqual: false,
},
"sensitive: one nil one not": {
diag1: testDiagnosticWithExtra{extra: mockBecauseSensitive{caused: true}},
diag2: testDiagnosticWithExtra{extra: nil},
wantEqual: false,
},
// DiagnosticExtraDoNotConsolidate tests
"doNotConsolidate: both have same value true": {
diag1: testDiagnosticWithExtra{extra: mockDoNotConsolidate{doNotConsolidate: true}},
diag2: testDiagnosticWithExtra{extra: mockDoNotConsolidate{doNotConsolidate: true}},
wantEqual: true,
},
"doNotConsolidate: different values": {
diag1: testDiagnosticWithExtra{extra: mockDoNotConsolidate{doNotConsolidate: true}},
diag2: testDiagnosticWithExtra{extra: mockDoNotConsolidate{doNotConsolidate: false}},
wantEqual: false,
},
"doNotConsolidate: one nil one not": {
diag1: testDiagnosticWithExtra{extra: mockDoNotConsolidate{doNotConsolidate: true}},
diag2: testDiagnosticWithExtra{extra: nil},
wantEqual: false,
},
// DiagnosticExtraCausedByTestFailure tests
"testFailure: both have same values": {
diag1: testDiagnosticWithExtra{extra: mockCausedByTestFailure{causedByTestFailure: true, verboseMode: true}},
diag2: testDiagnosticWithExtra{extra: mockCausedByTestFailure{causedByTestFailure: true, verboseMode: true}},
wantEqual: true,
},
"testFailure: different causedByTestFailure": {
diag1: testDiagnosticWithExtra{extra: mockCausedByTestFailure{causedByTestFailure: true, verboseMode: true}},
diag2: testDiagnosticWithExtra{extra: mockCausedByTestFailure{causedByTestFailure: false, verboseMode: true}},
wantEqual: false,
},
"testFailure: different verboseMode": {
diag1: testDiagnosticWithExtra{extra: mockCausedByTestFailure{causedByTestFailure: true, verboseMode: true}},
diag2: testDiagnosticWithExtra{extra: mockCausedByTestFailure{causedByTestFailure: true, verboseMode: false}},
wantEqual: false,
},
"testFailure: one nil one not": {
diag1: testDiagnosticWithExtra{extra: mockCausedByTestFailure{causedByTestFailure: true, verboseMode: true}},
diag2: testDiagnosticWithExtra{extra: nil},
wantEqual: false,
},
// DiagnosticExtraDeprecationOrigin tests
"deprecation: both have same description": {
diag1: testDiagnosticWithExtra{extra: &DeprecationOriginDiagnosticExtra{OriginDescription: "test origin"}},
diag2: testDiagnosticWithExtra{extra: &DeprecationOriginDiagnosticExtra{OriginDescription: "test origin"}},
wantEqual: true,
},
"deprecation: different descriptions": {
diag1: testDiagnosticWithExtra{extra: &DeprecationOriginDiagnosticExtra{OriginDescription: "origin 1"}},
diag2: testDiagnosticWithExtra{extra: &DeprecationOriginDiagnosticExtra{OriginDescription: "origin 2"}},
wantEqual: false,
},
"deprecation: one nil one not": {
diag1: testDiagnosticWithExtra{extra: &DeprecationOriginDiagnosticExtra{OriginDescription: "test origin"}},
diag2: testDiagnosticWithExtra{extra: nil},
wantEqual: false,
},
"deprecation: empty vs non-empty": {
diag1: testDiagnosticWithExtra{extra: &DeprecationOriginDiagnosticExtra{OriginDescription: ""}},
diag2: testDiagnosticWithExtra{extra: &DeprecationOriginDiagnosticExtra{OriginDescription: "test"}},
wantEqual: false,
},
"deprecation: both empty": {
diag1: testDiagnosticWithExtra{extra: &DeprecationOriginDiagnosticExtra{OriginDescription: ""}},
diag2: testDiagnosticWithExtra{extra: &DeprecationOriginDiagnosticExtra{OriginDescription: ""}},
wantEqual: true,
},
// Different extra types (only matching interfaces matter)
"different extra types with same interface values": {
diag1: testDiagnosticWithExtra{extra: mockBecauseUnknown{caused: true}},
diag2: testDiagnosticWithExtra{extra: mockBecauseSensitive{caused: true}},
wantEqual: false, // different because one has unknown, other has sensitive
},
}
for name, tc := range tests {
t.Run(name, func(t *testing.T) {
got := DiagnosticExtrasEqual(tc.diag1, tc.diag2)
if got != tc.wantEqual {
t.Errorf("DiagnosticExtrasEqual() = %v, want %v", got, tc.wantEqual)
}
})
}
}
// Test that the comparison is symmetric
func TestDiagnosticExtrasEqual_Symmetric(t *testing.T) {
tests := []struct {
name string
diag1 Diagnostic
diag2 Diagnostic
}{
{
name: "unknown extras",
diag1: testDiagnosticWithExtra{extra: mockBecauseUnknown{caused: true}},
diag2: testDiagnosticWithExtra{extra: mockBecauseUnknown{caused: false}},
},
{
name: "deprecation extras",
diag1: testDiagnosticWithExtra{extra: &DeprecationOriginDiagnosticExtra{OriginDescription: "a"}},
diag2: testDiagnosticWithExtra{extra: &DeprecationOriginDiagnosticExtra{OriginDescription: "b"}},
},
{
name: "nil vs non-nil",
diag1: testDiagnosticWithExtra{extra: nil},
diag2: testDiagnosticWithExtra{extra: mockBecauseUnknown{caused: true}},
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
result1 := DiagnosticExtrasEqual(tc.diag1, tc.diag2)
result2 := DiagnosticExtrasEqual(tc.diag2, tc.diag1)
if result1 != result2 {
t.Errorf("DiagnosticExtrasEqual is not symmetric: (a,b)=%v, (b,a)=%v", result1, result2)
}
})
}
}

View file

@ -3,6 +3,7 @@
package tfdiags
import (
"fmt"
"testing"
"github.com/google/go-cmp/cmp"
@ -54,6 +55,52 @@ func assertDiagnosticMatch(got, want Diagnostic) string {
return cmp.Diff(want, got, DiagnosticComparer)
}
// AssertDiagnosticsAndExtrasMatch fails the test in progress (using t.Fatal) if the
// two sets of diagnostics don't match after being normalized using the
// "ForRPC" processing step, AND also checks that the extra information on each
// diagnostic matches.
//
// This is similar to AssertDiagnosticsMatch but additionally verifies that the
// extra information (ExtraInfo) on diagnostics is equal, using DiagnosticExtrasEqual.
//
// AssertDiagnosticsAndExtrasMatch sorts the two sets of diagnostics in the usual way
// before comparing them, though diagnostics only have a partial order so that
// will not totally normalize the ordering of all diagnostics sets.
func AssertDiagnosticsAndExtrasMatch(t *testing.T, got, want Diagnostics) {
t.Helper()
if diff := assertDiagnosticsAndExtrasMatch(got, want); diff != "" {
t.Fatalf("unexpected diagnostics difference:\n%s", diff)
}
}
func assertDiagnosticsAndExtrasMatch(got, want Diagnostics) string {
// First check that the basic diagnostic info matches
if diff := assertDiagnosticsMatch(got, want); diff != "" {
return diff
}
// Now check extras - we need to compare the original diagnostics
// since ForRPC discards extra info
gotSorted := make(Diagnostics, len(got))
copy(gotSorted, got)
gotSorted.Sort()
wantSorted := make(Diagnostics, len(want))
copy(wantSorted, want)
wantSorted.Sort()
// Length should already match if assertDiagnosticsMatch passed
for i := range gotSorted {
if !DiagnosticExtrasEqual(gotSorted[i], wantSorted[i]) {
return fmt.Sprintf("diagnostic[%d] extras do not match:\n got extra: %#v\n want extra: %#v",
i, gotSorted[i].ExtraInfo(), wantSorted[i].ExtraInfo())
}
}
return ""
}
// AssertNoDiagnostics will fail a test if any diagnostics are present.
// If diagnostics are present, they will each be logged.
func AssertNoDiagnostics(t *testing.T, diags Diagnostics) {

View file

@ -8,6 +8,51 @@ import (
"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.
//
@ -57,6 +102,160 @@ func Test_assertDiagnosticMatch_differentConcreteTypes(t *testing.T) {
}
}
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,