Skip to content

Commit

Permalink
knownvalue: Add TupleExact, TuplePartial and TupleSizeExact che…
Browse files Browse the repository at this point in the history
…cks for dynamic value testing (#313)

* knownvalue: Add `Tuple*` equivalents for exact, partial, and size checks

* add changelog

* add documentation for tuples

* test name dup
  • Loading branch information
austinvalle authored Mar 25, 2024
1 parent e984fdb commit c294752
Show file tree
Hide file tree
Showing 12 changed files with 709 additions and 1 deletion.
6 changes: 6 additions & 0 deletions .changes/unreleased/ENHANCEMENTS-20240325-120539.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
kind: ENHANCEMENTS
body: 'knownvalue: Add `TupleExact`, `TuplePartial` and `TupleSizeExact` checks for
dynamic value testing.'
time: 2024-03-25T12:05:39.777695-04:00
custom:
Issue: "312"
2 changes: 1 addition & 1 deletion knownvalue/list_partial_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ func TestListValuePartial_CheckValue(t *testing.T) {
}
}

func TestListValuePartialPartial_String(t *testing.T) {
func TestListValuePartial_String(t *testing.T) {
t.Parallel()

got := knownvalue.ListPartial(map[int]knownvalue.Check{
Expand Down
67 changes: 67 additions & 0 deletions knownvalue/tuple.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package knownvalue

import (
"fmt"
)

var _ Check = tupleExact{}

type tupleExact struct {
value []Check
}

// CheckValue determines whether the passed value is of type []any, and
// contains matching slice entries in the same sequence.
func (v tupleExact) CheckValue(other any) error {
otherVal, ok := other.([]any)

if !ok {
return fmt.Errorf("expected []any value for TupleExact check, got: %T", other)
}

if len(otherVal) != len(v.value) {
expectedElements := "elements"
actualElements := "elements"

if len(v.value) == 1 {
expectedElements = "element"
}

if len(otherVal) == 1 {
actualElements = "element"
}

return fmt.Errorf("expected %d %s for TupleExact check, got %d %s", len(v.value), expectedElements, len(otherVal), actualElements)
}

for i := 0; i < len(v.value); i++ {
if err := v.value[i].CheckValue(otherVal[i]); err != nil {
return fmt.Errorf("tuple element index %d: %s", i, err)
}
}

return nil
}

// String returns the string representation of the value.
func (v tupleExact) String() string {
var tupleVals []string

for _, val := range v.value {
tupleVals = append(tupleVals, val.String())
}

return fmt.Sprintf("%s", tupleVals)
}

// TupleExact returns a Check for asserting equality between the
// supplied []Check and the value passed to the CheckValue method.
// This is an order-dependent check.
func TupleExact(value []Check) tupleExact {
return tupleExact{
value: value,
}
}
89 changes: 89 additions & 0 deletions knownvalue/tuple_partial.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package knownvalue

import (
"bytes"
"fmt"
"sort"
"strings"
)

var _ Check = tuplePartial{}

type tuplePartial struct {
value map[int]Check
}

// CheckValue determines whether the passed value is of type []any, and
// contains matching slice entries in the same sequence.
func (v tuplePartial) CheckValue(other any) error {
otherVal, ok := other.([]any)

if !ok {
return fmt.Errorf("expected []any value for TuplePartial check, got: %T", other)
}

var keys []int

for k := range v.value {
keys = append(keys, k)
}

sort.SliceStable(keys, func(i, j int) bool {
return keys[i] < keys[j]
})

for _, k := range keys {
if len(otherVal) <= k {
return fmt.Errorf("missing element index %d for TuplePartial check", k)
}

if err := v.value[k].CheckValue(otherVal[k]); err != nil {
return fmt.Errorf("tuple element %d: %s", k, err)
}
}

return nil
}

// String returns the string representation of the value.
func (v tuplePartial) String() string {
var b bytes.Buffer

b.WriteString("[")

var keys []int

var tupleVals []string

for k := range v.value {
keys = append(keys, k)
}

sort.SliceStable(keys, func(i, j int) bool {
return keys[i] < keys[j]
})

for _, k := range keys {
tupleVals = append(tupleVals, fmt.Sprintf("%d:%s", k, v.value[k]))
}

b.WriteString(strings.Join(tupleVals, " "))

b.WriteString("]")

return b.String()
}

// TuplePartial returns a Check for asserting partial equality between the
// supplied map[int]Check and the value passed to the CheckValue method. The
// map keys represent the zero-ordered element indices within the tuple that is
// being checked. Only the elements at the indices defined within the
// supplied map[int]Check are checked.
func TuplePartial(value map[int]Check) tuplePartial {
return tuplePartial{
value: value,
}
}
138 changes: 138 additions & 0 deletions knownvalue/tuple_partial_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package knownvalue_test

import (
"encoding/json"
"fmt"
"testing"

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

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

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

testCases := map[string]struct {
self knownvalue.Check
other any
expectedError error
}{
"zero-nil": {
self: knownvalue.TuplePartial(map[int]knownvalue.Check{}),
expectedError: fmt.Errorf("expected []any value for TuplePartial check, got: <nil>"),
},
"zero-other": {
self: knownvalue.TuplePartial(map[int]knownvalue.Check{}),
other: []any{}, // checking against the underlying value field zero-value
},
"nil": {
self: knownvalue.TuplePartial(map[int]knownvalue.Check{
0: knownvalue.Float64Exact(1.23),
2: knownvalue.StringExact("world"),
3: knownvalue.Bool(true),
}),
expectedError: fmt.Errorf("expected []any value for TuplePartial check, got: <nil>"),
},
"wrong-type": {
self: knownvalue.TuplePartial(map[int]knownvalue.Check{
0: knownvalue.Float64Exact(1.23),
2: knownvalue.StringExact("world"),
3: knownvalue.Bool(true),
}),
other: 1.234,
expectedError: fmt.Errorf("expected []any value for TuplePartial check, got: float64"),
},
"empty": {
self: knownvalue.TuplePartial(map[int]knownvalue.Check{
0: knownvalue.Float64Exact(1.23),
2: knownvalue.StringExact("world"),
3: knownvalue.Bool(true),
}),
other: []any{},
expectedError: fmt.Errorf("missing element index 0 for TuplePartial check"),
},
"wrong-length": {
self: knownvalue.TuplePartial(map[int]knownvalue.Check{
0: knownvalue.Float64Exact(1.23),
2: knownvalue.StringExact("world"),
3: knownvalue.Bool(true),
}),
other: []any{
json.Number("1.23"),
"hello",
},
expectedError: fmt.Errorf("missing element index 2 for TuplePartial check"),
},
"not-equal": {
self: knownvalue.TuplePartial(map[int]knownvalue.Check{
0: knownvalue.Float64Exact(1.23),
2: knownvalue.StringExact("world"),
3: knownvalue.Bool(true),
}),
other: []any{
json.Number("1.23"),
"world",
"hello",
},
expectedError: fmt.Errorf("tuple element 2: expected value world for StringExact check, got: hello"),
},
"wrong-order": {
self: knownvalue.TuplePartial(map[int]knownvalue.Check{
0: knownvalue.Float64Exact(1.23),
2: knownvalue.StringExact("world"),
3: knownvalue.Bool(true),
}),
other: []any{
json.Number("1.23"),
"world",
true,
},
expectedError: fmt.Errorf("tuple element 2: expected string value for StringExact check, got: bool"),
},
"equal": {
self: knownvalue.TuplePartial(map[int]knownvalue.Check{
0: knownvalue.Float64Exact(1.23),
2: knownvalue.StringExact("world"),
3: knownvalue.Bool(true),
}),
other: []any{
json.Number("1.23"),
"hello",
"world",
true,
},
},
}

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

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

got := testCase.self.CheckValue(testCase.other)

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

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

got := knownvalue.TuplePartial(map[int]knownvalue.Check{
0: knownvalue.Float64Exact(1.23),
2: knownvalue.StringExact("world"),
3: knownvalue.Bool(true),
}).String()

if diff := cmp.Diff(got, "[0:1.23 2:world 3:true]"); diff != "" {
t.Errorf("unexpected difference: %s", diff)
}
}
55 changes: 55 additions & 0 deletions knownvalue/tuple_size.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package knownvalue

import (
"fmt"
"strconv"
)

var _ Check = tupleSizeExact{}

type tupleSizeExact struct {
size int
}

// CheckValue verifies that the passed value is a tuple, map, object,
// or set, and contains a matching number of elements.
func (v tupleSizeExact) CheckValue(other any) error {
otherVal, ok := other.([]any)

if !ok {
return fmt.Errorf("expected []any value for TupleSizeExact check, got: %T", other)
}

if len(otherVal) != v.size {
expectedElements := "elements"
actualElements := "elements"

if v.size == 1 {
expectedElements = "element"
}

if len(otherVal) == 1 {
actualElements = "element"
}

return fmt.Errorf("expected %d %s for TupleSizeExact check, got %d %s", v.size, expectedElements, len(otherVal), actualElements)
}

return nil
}

// String returns the string representation of the value.
func (v tupleSizeExact) String() string {
return strconv.FormatInt(int64(v.size), 10)
}

// TupleSizeExact returns a Check for asserting that
// a tuple has size elements.
func TupleSizeExact(size int) tupleSizeExact {
return tupleSizeExact{
size: size,
}
}
Loading

0 comments on commit c294752

Please sign in to comment.