Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement comparison state checks to replace TestCheckResourceAttrPtr, TestCheckResourceAttrPair and TestCheckTypeSetElemAttrPair #330

Merged
merged 66 commits into from
Aug 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
66 commits
Select commit Hold shift + click to select a range
55258e7
compare: Add compare package and implementations for ValuesSame, Valu…
bendbennett Apr 30, 2024
8e71907
statecheck: Add CompareValue, CompareValueContains, and CompareValueP…
bendbennett Apr 30, 2024
6f29fb4
statecheck: Fixing references in tests
bendbennett Apr 30, 2024
171c9a1
Adding go docs and constructor to compare package types
bendbennett May 1, 2024
38b9517
Adding go docs for CompareValue state checks
bendbennett May 1, 2024
973e808
Refactoring ValueComparer implementations to handle slice and map of …
bendbennett May 1, 2024
1ce7f29
Refactoring CompareValueCollection to extract nested values
bendbennett May 3, 2024
e652edc
Adding further test coverage for CompareValueCollection
bendbennett May 7, 2024
a5c6e02
Adding compare pkg doc
bendbennett May 7, 2024
c45bed0
Merge branch 'main' into bendbennett/issues-295
bendbennett May 7, 2024
235278c
Removing ValuesDifferAll, ValuesDifferAny, ValuesSameAny
bendbennett May 8, 2024
dcfee13
Adding website docs for value comparers and compare value state checks
bendbennett May 9, 2024
a772710
Amending docs for known value checks
bendbennett May 9, 2024
6d79718
build(deps): Bump hashicorp/setup-terraform from 3.1.0 to 3.1.1 (#335)
dependabot[bot] May 9, 2024
0dfcfff
build(deps): Bump hashicorp/setup-copywrite from 1.1.2 to 1.1.3 (#336)
dependabot[bot] May 10, 2024
773e336
Result of tsccr-helper -log-level=info gha update -latest . (#337)
hashicorp-tsccr[bot] May 13, 2024
1e21a90
build(deps): Bump github.com/hashicorp/terraform-json (#338)
dependabot[bot] May 15, 2024
1333bea
build(deps): Bump github.com/hashicorp/terraform-json (#339)
dependabot[bot] May 17, 2024
56d0810
build(deps): Bump github.com/hashicorp/terraform-exec (#341)
dependabot[bot] May 17, 2024
91740d7
all: Add deferred action testing support (plan checks, version check,…
austinvalle May 17, 2024
bf8eb7b
Update changelog
hc-github-team-tf-provider-devex May 17, 2024
e5cc909
[CI] Update issue comment triage workflow file
hc-github-team-tf-provider-devex May 17, 2024
252df65
[CI] Update issue comment triage workflow file
hc-github-team-tf-provider-devex May 17, 2024
ee740e8
[CI] Update issue comment triage workflow file
hc-github-team-tf-provider-devex May 17, 2024
46b3d5c
[CI] Update issue comment triage workflow file
hc-github-team-tf-provider-devex May 17, 2024
2c791e2
Result of tsccr-helper -log-level=info gha update -latest . (#342)
hashicorp-tsccr[bot] May 20, 2024
e309ffb
build(deps): Bump github.com/hashicorp/terraform-plugin-sdk/v2 (#343)
dependabot[bot] May 20, 2024
e8b691c
workflows: Delete old issue label remove workflow (#344)
austinvalle May 20, 2024
32ef3f1
build(deps): Bump github.com/hashicorp/hc-install from 0.6.4 to 0.7.0…
dependabot[bot] May 21, 2024
d7b7078
[CI] Update lock workflow file
hc-github-team-tf-provider-devex May 22, 2024
0d32cfa
build(deps): Bump github.com/hashicorp/go-version from 1.6.0 to 1.7.0…
dependabot[bot] May 24, 2024
cd40e58
Result of tsccr-helper -log-level=info gha update -latest . (#348)
hashicorp-tsccr[bot] May 28, 2024
7905bc7
[CI] terraform-devex-repos automation
hc-github-team-tf-provider-devex Jun 4, 2024
80a2d89
[CI] terraform-devex-repos automation
hc-github-team-tf-provider-devex Jun 4, 2024
26f93f0
[CI] terraform-devex-repos automation
hc-github-team-tf-provider-devex Jun 4, 2024
64094f2
[CI] terraform-devex-repos automation
hc-github-team-tf-provider-devex Jun 4, 2024
9b71c36
[CI] terraform-devex-repos automation
hc-github-team-tf-provider-devex Jun 4, 2024
5f42c08
[CI] terraform-devex-repos automation
hc-github-team-tf-provider-devex Jun 4, 2024
d67d494
build(deps): Bump golang.org/x/crypto from 0.23.0 to 0.24.0 (#350)
dependabot[bot] Jun 5, 2024
3871ae0
[CI] terraform-devex-repos automation
hc-github-team-tf-provider-devex Jun 7, 2024
3f5d56a
[CI] terraform-devex-repos automation
hc-github-team-tf-provider-devex Jun 7, 2024
b62ea3c
[CI] terraform-devex-repos automation
hc-github-team-tf-provider-devex Jun 7, 2024
835a837
[CI] Update lock workflow file
hc-github-team-tf-provider-devex Jun 10, 2024
751342d
build(deps): Bump github.com/hashicorp/hcl/v2 from 2.20.1 to 2.21.0 (…
dependabot[bot] Jun 21, 2024
1d6261e
SEC-090: Automated trusted workflow pinning (2024-06-17) (#351)
hashicorp-tsccr[bot] Jun 21, 2024
ad5287a
build(deps): Bump github.com/hashicorp/copywrite in /tools (#355)
dependabot[bot] Jun 25, 2024
acc1c0b
Result of tsccr-helper -log-level=info gha update -latest . (#354)
hashicorp-tsccr[bot] Jun 26, 2024
082330e
knownvalue: Add `Int32Exact` and `Float32Exact` checks (#356)
SBGoods Jul 1, 2024
ee0e396
build(deps): Bump golang.org/x/crypto from 0.24.0 to 0.25.0 (#358)
dependabot[bot] Jul 5, 2024
0cec1c6
Update changelog
hc-github-team-tf-provider-devex Jul 9, 2024
c13ca7b
all: Add deferred action testing support (plan checks, version check,…
austinvalle May 17, 2024
167bc62
Update changelog
hc-github-team-tf-provider-devex May 17, 2024
829c50a
[CI] terraform-devex-repos automation
hc-github-team-tf-provider-devex Jun 4, 2024
a1da161
[CI] terraform-devex-repos automation
hc-github-team-tf-provider-devex Jun 7, 2024
2c3375e
knownvalue: Add `Int32Exact` and `Float32Exact` checks (#356)
SBGoods Jul 1, 2024
07a62c3
Update changelog
hc-github-team-tf-provider-devex Jul 9, 2024
1d1b5bc
Merge branch 'main' into bendbennett/issues-295
austinvalle Jul 10, 2024
c4d0a40
Merge branch 'main' into bendbennett/issues-295
austinvalle Jul 16, 2024
546be85
quick doc fix
austinvalle Jul 16, 2024
eff8125
added invalid test case
austinvalle Jul 17, 2024
b57949f
adjust compare value collection to work with nested attributes
austinvalle Jul 17, 2024
d745fb0
add changelogs
austinvalle Jul 17, 2024
d61aac9
add tfversion skip for protov6
austinvalle Jul 17, 2024
4c19f9d
Merge branch 'main' into bendbennett/issues-295
austinvalle Aug 6, 2024
5d34e1c
Merge branch 'main' into bendbennett/issues-295
austinvalle Aug 7, 2024
bcdd810
update example for value comparers
austinvalle Aug 7, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changes/unreleased/FEATURES-20240717-155731.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
kind: FEATURES
body: 'compare: Introduced new `compare` package, which contains interfaces and implementations
for value comparisons in state checks.'
time: 2024-07-17T15:57:31.637692-04:00
custom:
Issue: "330"
6 changes: 6 additions & 0 deletions .changes/unreleased/FEATURES-20240717-160116.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
kind: FEATURES
body: 'statecheck: Added `CompareValue` state check, which compares sequential values of the
specified attribute at the given managed resource, or data source, using the supplied value comparer.'
time: 2024-07-17T16:01:16.194665-04:00
custom:
Issue: "330"
7 changes: 7 additions & 0 deletions .changes/unreleased/FEATURES-20240717-160331.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
kind: FEATURES
body: 'statecheck: Added `CompareValueCollection` state check, which compares each item in
the specified collection (e.g., list, set) attribute, with the second specified
attribute at the given managed resources, or data sources, using the supplied value comparer.'
time: 2024-07-17T16:03:31.77827-04:00
custom:
Issue: "330"
7 changes: 7 additions & 0 deletions .changes/unreleased/FEATURES-20240717-164418.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
kind: FEATURES
body: 'statecheck: Added `CompareValuePairs` state check, which compares the
specified attributes at the given managed resources, or data sources, using
the supplied value comparer.'
time: 2024-07-17T16:44:18.612874-04:00
custom:
Issue: "330"
6 changes: 6 additions & 0 deletions .changes/unreleased/NOTES-20240717-155810.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
kind: NOTES
body: 'compare: The `compare` package is considered experimental and may be altered
or removed in a subsequent release'
time: 2024-07-17T15:58:10.435384-04:00
custom:
Issue: "330"
7 changes: 7 additions & 0 deletions .changes/unreleased/NOTES-20240717-164911.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
kind: NOTES
body: 'statecheck: `CompareValue`, `CompareValueCollection`, and `CompareValuePairs`
state checks are considered experimental and may be altered or removed in a subsequent
release.'
time: 2024-07-17T16:49:11.296585-04:00
custom:
Issue: "330"
5 changes: 5 additions & 0 deletions compare/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

// Package compare contains the value comparer interface, and types implementing the value comparer interface.
package compare
13 changes: 13 additions & 0 deletions compare/value_comparer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package compare

// ValueComparer defines an interface that is implemented to run comparison logic on multiple values. Individual
// implementations determine how the comparison is performed (e.g., values differ, values equal).
type ValueComparer interface {
// CompareValues should assert the given known values against any expectations.
// Values are always ordered in the order they were added. Use the error
// return to signal unexpected values or implementation errors.
CompareValues(values ...any) error
}
31 changes: 31 additions & 0 deletions compare/values_differ.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package compare

import (
"fmt"
"reflect"
)

var _ ValueComparer = valuesDiffer{}

type valuesDiffer struct{}

// CompareValues determines whether each value in the sequence of the supplied values
// differs from the preceding value.
func (v valuesDiffer) CompareValues(values ...any) error {
for i := 1; i < len(values); i++ {
if reflect.DeepEqual(values[i-1], values[i]) {
return fmt.Errorf("expected values to differ, but they are the same: %v == %v", values[i-1], values[i])
}
}

return nil
}

// ValuesDiffer returns a ValueComparer for asserting that each value in the sequence of
// the values supplied to the CompareValues method differs from the preceding value.
func ValuesDiffer() valuesDiffer {
return valuesDiffer{}
}
62 changes: 62 additions & 0 deletions compare/values_differ_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package compare_test

import (
"fmt"
"testing"

"github.com/google/go-cmp/cmp"

"github.com/hashicorp/terraform-plugin-testing/compare"
)

func TestValuesDiffer_CompareValues(t *testing.T) {
t.Parallel()

testCases := map[string]struct {
in []any
expectedError error
}{
"nil": {},
"single-value": {
in: []any{"str"},
},
"non-matching-sequential-values": {
in: []any{"str", "other_str", "str"},
},
"matching-values-string": {
in: []any{"str", "other_str", "other_str"},
expectedError: fmt.Errorf("expected values to differ, but they are the same: other_str == other_str"),
},
"matching-values-slice": {
in: []any{
[]any{"other_str"},
[]any{"other_str"},
},
expectedError: fmt.Errorf("expected values to differ, but they are the same: [other_str] == [other_str]"),
},
"matching-values-map": {
in: []any{
map[string]any{"a": "other_str"},
map[string]any{"a": "other_str"},
},
expectedError: fmt.Errorf("expected values to differ, but they are the same: map[a:other_str] == map[a:other_str]"),
},
}

for name, testCase := range testCases {
name, testCase := name, testCase

t.Run(name, func(t *testing.T) {
t.Parallel()

err := compare.ValuesDiffer().CompareValues(testCase.in...)

if diff := cmp.Diff(err, testCase.expectedError, equateErrorMessage); diff != "" {
t.Errorf("unexpected difference: %s", diff)
}
})
}
}
31 changes: 31 additions & 0 deletions compare/values_same.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package compare

import (
"fmt"
"reflect"
)

var _ ValueComparer = valuesSame{}

type valuesSame struct{}

// CompareValues determines whether each value in the sequence of the supplied values
// is the same as the preceding value.
func (v valuesSame) CompareValues(values ...any) error {
for i := 1; i < len(values); i++ {
if !reflect.DeepEqual(values[i-1], values[i]) {
return fmt.Errorf("expected values to be the same, but they differ: %v != %v", values[i-1], values[i])
}
}

return nil
}

// ValuesSame returns a ValueComparer for asserting that each value in the sequence of
// the values supplied to the CompareValues method is the same as the preceding value.
func ValuesSame() valuesSame {
return valuesSame{}
}
73 changes: 73 additions & 0 deletions compare/values_same_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package compare_test

import (
"fmt"
"testing"

"github.com/google/go-cmp/cmp"

"github.com/hashicorp/terraform-plugin-testing/compare"
)

func TestValuesSame_CompareValues(t *testing.T) {
t.Parallel()

testCases := map[string]struct {
in []any
expectedError error
}{
"nil": {},
"single-value": {
in: []any{"str"},
},
"matching-values": {
in: []any{"str", "str", "str"},
},
"non-matching-values-string": {
in: []any{"str", "str", "other_str"},
expectedError: fmt.Errorf("expected values to be the same, but they differ: str != other_str"),
},
"non-matching-values-slice": {
in: []any{
[]any{"str"},
[]any{"str"},
[]any{"other_str"},
},
expectedError: fmt.Errorf("expected values to be the same, but they differ: [str] != [other_str]"),
},
"non-matching-values-map": {
in: []any{
map[string]any{"a": "str"},
map[string]any{"a": "str"},
map[string]any{"a": "other_str"},
},
expectedError: fmt.Errorf("expected values to be the same, but they differ: map[a:str] != map[a:other_str]"),
},
}

for name, testCase := range testCases {
name, testCase := name, testCase

t.Run(name, func(t *testing.T) {
t.Parallel()

err := compare.ValuesSame().CompareValues(testCase.in...)

if diff := cmp.Diff(err, testCase.expectedError, equateErrorMessage); diff != "" {
t.Errorf("unexpected difference: %s", diff)
}
})
}
}

// equateErrorMessage reports errors to be equal if both are nil
// or both have the same message.
var equateErrorMessage = cmp.Comparer(func(x, y error) bool {
if x == nil || y == nil {
return x == nil && y == nil
}
return x.Error() == y.Error()
})
114 changes: 114 additions & 0 deletions statecheck/compare_value.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package statecheck

import (
"context"
"fmt"

tfjson "github.com/hashicorp/terraform-json"

"github.com/hashicorp/terraform-plugin-testing/compare"
"github.com/hashicorp/terraform-plugin-testing/tfjsonpath"
)

// Resource State Check
var _ StateCheck = &compareValue{}

type compareValue struct {
resourceAddresses []string
attributePaths []tfjsonpath.Path
stateValues []any
comparer compare.ValueComparer
}

func (e *compareValue) AddStateValue(resourceAddress string, attributePath tfjsonpath.Path) StateCheck {
e.resourceAddresses = append(e.resourceAddresses, resourceAddress)
e.attributePaths = append(e.attributePaths, attributePath)

return e
}

// CheckState implements the state check logic.
func (e *compareValue) CheckState(ctx context.Context, req CheckStateRequest, resp *CheckStateResponse) {
var resource *tfjson.StateResource

if req.State == nil {
resp.Error = fmt.Errorf("state is nil")

return
}

if req.State.Values == nil {
resp.Error = fmt.Errorf("state does not contain any state values")

return
}

if req.State.Values.RootModule == nil {
resp.Error = fmt.Errorf("state does not contain a root module")

return
}

// All calls to AddStateValue occur before any TestStep is run, populating the resourceAddresses
// and attributePaths slices. The stateValues slice is populated during execution of each TestStep.
// Each call to CheckState happens sequentially during each TestStep.
// The currentIndex is reflective of the current state value being checked.
currentIndex := len(e.stateValues)

if len(e.resourceAddresses) <= currentIndex {
resp.Error = fmt.Errorf("resource addresses index out of bounds: %d", currentIndex)

return
}

resourceAddress := e.resourceAddresses[currentIndex]

for _, r := range req.State.Values.RootModule.Resources {
if resourceAddress == r.Address {
resource = r

break
}
}

if resource == nil {
resp.Error = fmt.Errorf("%s - Resource not found in state", resourceAddress)

return
}

if len(e.attributePaths) <= currentIndex {
resp.Error = fmt.Errorf("attribute paths index out of bounds: %d", currentIndex)

return
}

attributePath := e.attributePaths[currentIndex]

result, err := tfjsonpath.Traverse(resource.AttributeValues, attributePath)

if err != nil {
resp.Error = err

return
}

e.stateValues = append(e.stateValues, result)

err = e.comparer.CompareValues(e.stateValues...)

if err != nil {
resp.Error = err
}
}

// CompareValue returns a state check that compares values retrieved from state using the
// supplied value comparer.
func CompareValue(comparer compare.ValueComparer) *compareValue {
return &compareValue{
comparer: comparer,
}
}
Loading