mirror of
https://github.com/tailscale/tailscale.git
synced 2025-02-21 12:28:39 +00:00
types/opt: add generic Value[T any] for optional values of any types
Updates #12736 Signed-off-by: Nick Khyl <nickk@tailscale.com>
This commit is contained in:
parent
5576972261
commit
e21d8768f9
@ -10,6 +10,12 @@ tailscale.com/cmd/derper dependencies: (generated by github.com/tailscale/depawa
|
||||
L github.com/coreos/go-iptables/iptables from tailscale.com/util/linuxfw
|
||||
W 💣 github.com/dblohm7/wingoes from tailscale.com/util/winutil
|
||||
github.com/fxamacker/cbor/v2 from tailscale.com/tka
|
||||
github.com/go-json-experiment/json from tailscale.com/types/opt
|
||||
github.com/go-json-experiment/json/internal from github.com/go-json-experiment/json+
|
||||
github.com/go-json-experiment/json/internal/jsonflags from github.com/go-json-experiment/json+
|
||||
github.com/go-json-experiment/json/internal/jsonopts from github.com/go-json-experiment/json+
|
||||
github.com/go-json-experiment/json/internal/jsonwire from github.com/go-json-experiment/json+
|
||||
github.com/go-json-experiment/json/jsontext from github.com/go-json-experiment/json+
|
||||
github.com/golang/groupcache/lru from tailscale.com/net/dnscache
|
||||
L github.com/google/nftables from tailscale.com/util/linuxfw
|
||||
L 💣 github.com/google/nftables/alignedbuff from github.com/google/nftables/xt
|
||||
|
@ -98,11 +98,12 @@ tailscale.com/cmd/k8s-operator dependencies: (generated by github.com/tailscale/
|
||||
💣 github.com/fsnotify/fsnotify from sigs.k8s.io/controller-runtime/pkg/certwatcher
|
||||
github.com/fxamacker/cbor/v2 from tailscale.com/tka
|
||||
github.com/gaissmai/bart from tailscale.com/net/ipset+
|
||||
github.com/go-json-experiment/json from tailscale.com/types/opt
|
||||
github.com/go-json-experiment/json/internal from github.com/go-json-experiment/json/internal/jsonflags+
|
||||
github.com/go-json-experiment/json/internal/jsonflags from github.com/go-json-experiment/json/internal/jsonopts+
|
||||
github.com/go-json-experiment/json/internal/jsonopts from github.com/go-json-experiment/json/jsontext
|
||||
github.com/go-json-experiment/json/internal/jsonwire from github.com/go-json-experiment/json/jsontext
|
||||
github.com/go-json-experiment/json/jsontext from tailscale.com/logtail
|
||||
github.com/go-json-experiment/json/internal/jsonopts from github.com/go-json-experiment/json/jsontext+
|
||||
github.com/go-json-experiment/json/internal/jsonwire from github.com/go-json-experiment/json/jsontext+
|
||||
github.com/go-json-experiment/json/jsontext from tailscale.com/logtail+
|
||||
github.com/go-logr/logr from github.com/go-logr/logr/slogr+
|
||||
github.com/go-logr/logr/slogr from github.com/go-logr/zapr
|
||||
github.com/go-logr/zapr from sigs.k8s.io/controller-runtime/pkg/log/zap+
|
||||
@ -957,7 +958,7 @@ tailscale.com/cmd/k8s-operator dependencies: (generated by github.com/tailscale/
|
||||
math/big from crypto/dsa+
|
||||
math/bits from compress/flate+
|
||||
math/rand from github.com/google/go-cmp/cmp+
|
||||
math/rand/v2 from database/sql+
|
||||
math/rand/v2 from tailscale.com/derp+
|
||||
mime from github.com/prometheus/common/expfmt+
|
||||
mime/multipart from github.com/go-openapi/swag+
|
||||
mime/quotedprintable from mime/multipart
|
||||
|
@ -2,6 +2,12 @@ tailscale.com/cmd/stund dependencies: (generated by github.com/tailscale/depawar
|
||||
|
||||
github.com/beorn7/perks/quantile from github.com/prometheus/client_golang/prometheus
|
||||
💣 github.com/cespare/xxhash/v2 from github.com/prometheus/client_golang/prometheus
|
||||
github.com/go-json-experiment/json from tailscale.com/types/opt
|
||||
github.com/go-json-experiment/json/internal from github.com/go-json-experiment/json+
|
||||
github.com/go-json-experiment/json/internal/jsonflags from github.com/go-json-experiment/json+
|
||||
github.com/go-json-experiment/json/internal/jsonopts from github.com/go-json-experiment/json+
|
||||
github.com/go-json-experiment/json/internal/jsonwire from github.com/go-json-experiment/json+
|
||||
github.com/go-json-experiment/json/jsontext from github.com/go-json-experiment/json+
|
||||
github.com/google/uuid from tailscale.com/util/fastuuid
|
||||
💣 github.com/prometheus/client_golang/prometheus from tailscale.com/tsweb/promvarz
|
||||
github.com/prometheus/client_golang/prometheus/internal from github.com/prometheus/client_golang/prometheus
|
||||
@ -128,6 +134,7 @@ tailscale.com/cmd/stund dependencies: (generated by github.com/tailscale/depawar
|
||||
embed from crypto/internal/nistec+
|
||||
encoding from encoding/json+
|
||||
encoding/asn1 from crypto/x509+
|
||||
encoding/base32 from github.com/go-json-experiment/json
|
||||
encoding/base64 from encoding/json+
|
||||
encoding/binary from compress/gzip+
|
||||
encoding/hex from crypto/x509+
|
||||
|
@ -9,6 +9,12 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
|
||||
W 💣 github.com/dblohm7/wingoes from github.com/dblohm7/wingoes/pe+
|
||||
W 💣 github.com/dblohm7/wingoes/pe from tailscale.com/util/winutil/authenticode
|
||||
github.com/fxamacker/cbor/v2 from tailscale.com/tka
|
||||
github.com/go-json-experiment/json from tailscale.com/types/opt
|
||||
github.com/go-json-experiment/json/internal from github.com/go-json-experiment/json+
|
||||
github.com/go-json-experiment/json/internal/jsonflags from github.com/go-json-experiment/json+
|
||||
github.com/go-json-experiment/json/internal/jsonopts from github.com/go-json-experiment/json+
|
||||
github.com/go-json-experiment/json/internal/jsonwire from github.com/go-json-experiment/json+
|
||||
github.com/go-json-experiment/json/jsontext from github.com/go-json-experiment/json+
|
||||
github.com/golang/groupcache/lru from tailscale.com/net/dnscache
|
||||
L github.com/google/nftables from tailscale.com/util/linuxfw
|
||||
L 💣 github.com/google/nftables/alignedbuff from github.com/google/nftables/xt
|
||||
|
@ -90,11 +90,12 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
||||
💣 github.com/djherbis/times from tailscale.com/drive/driveimpl
|
||||
github.com/fxamacker/cbor/v2 from tailscale.com/tka
|
||||
github.com/gaissmai/bart from tailscale.com/net/tstun+
|
||||
github.com/go-json-experiment/json from tailscale.com/types/opt
|
||||
github.com/go-json-experiment/json/internal from github.com/go-json-experiment/json/internal/jsonflags+
|
||||
github.com/go-json-experiment/json/internal/jsonflags from github.com/go-json-experiment/json/internal/jsonopts+
|
||||
github.com/go-json-experiment/json/internal/jsonopts from github.com/go-json-experiment/json/jsontext
|
||||
github.com/go-json-experiment/json/internal/jsonwire from github.com/go-json-experiment/json/jsontext
|
||||
github.com/go-json-experiment/json/jsontext from tailscale.com/logtail
|
||||
github.com/go-json-experiment/json/internal/jsonopts from github.com/go-json-experiment/json/jsontext+
|
||||
github.com/go-json-experiment/json/internal/jsonwire from github.com/go-json-experiment/json/jsontext+
|
||||
github.com/go-json-experiment/json/jsontext from tailscale.com/logtail+
|
||||
W 💣 github.com/go-ole/go-ole from github.com/go-ole/go-ole/oleutil+
|
||||
W 💣 github.com/go-ole/go-ole/oleutil from tailscale.com/wgengine/winnet
|
||||
L 💣 github.com/godbus/dbus/v5 from tailscale.com/net/dns+
|
||||
|
122
types/opt/value.go
Normal file
122
types/opt/value.go
Normal file
@ -0,0 +1,122 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package opt
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
jsonv2 "github.com/go-json-experiment/json"
|
||||
"github.com/go-json-experiment/json/jsontext"
|
||||
)
|
||||
|
||||
// Value is an optional value to be JSON-encoded.
|
||||
// With [encoding/json], a zero Value is marshaled as a JSON null.
|
||||
// With [github.com/go-json-experiment/json], a zero Value is omitted from the
|
||||
// JSON object if the Go struct field specified with omitzero.
|
||||
// The omitempty tag option should never be used with Value fields.
|
||||
type Value[T any] struct {
|
||||
value T
|
||||
set bool
|
||||
}
|
||||
|
||||
// Equal reports whether the receiver and the other value are equal.
|
||||
// If the template type T in Value[T] implements an Equal method, it will be used
|
||||
// instead of the == operator for comparing values.
|
||||
type equatable[T any] interface {
|
||||
// Equal reports whether the receiver and the other values are equal.
|
||||
Equal(other T) bool
|
||||
}
|
||||
|
||||
// ValueOf returns an optional Value containing the specified value.
|
||||
// It treats nil slices and maps as empty slices and maps.
|
||||
func ValueOf[T any](v T) Value[T] {
|
||||
return Value[T]{value: v, set: true}
|
||||
}
|
||||
|
||||
// String implements [fmt.Stringer].
|
||||
func (o *Value[T]) String() string {
|
||||
if !o.set {
|
||||
return fmt.Sprintf("(empty[%T])", o.value)
|
||||
}
|
||||
return fmt.Sprint(o.value)
|
||||
}
|
||||
|
||||
// Set assigns the specified value to the optional value o.
|
||||
func (o *Value[T]) Set(v T) {
|
||||
*o = ValueOf(v)
|
||||
}
|
||||
|
||||
// Clear resets o to an empty state.
|
||||
func (o *Value[T]) Clear() {
|
||||
*o = Value[T]{}
|
||||
}
|
||||
|
||||
// IsSet reports whether o has a value set.
|
||||
func (o *Value[T]) IsSet() bool {
|
||||
return o.set
|
||||
}
|
||||
|
||||
// Get returns the value of o.
|
||||
// If a value hasn't been set, a zero value of T will be returned.
|
||||
func (o Value[T]) Get() T {
|
||||
return o.value
|
||||
}
|
||||
|
||||
// Get returns the value and a flag indicating whether the value is set.
|
||||
func (o Value[T]) GetOk() (v T, ok bool) {
|
||||
return o.value, o.set
|
||||
}
|
||||
|
||||
// Equal reports whether o is equal to v.
|
||||
// Two optional values are equal if both are empty,
|
||||
// or if both are set and the underlying values are equal.
|
||||
// If the template type T implements an Equal(T) bool method, it will be used
|
||||
// instead of the == operator for value comparison.
|
||||
// If T is not comparable, it returns false.
|
||||
func (o Value[T]) Equal(v Value[T]) bool {
|
||||
if o.set != v.set {
|
||||
return false
|
||||
}
|
||||
if !o.set {
|
||||
return true
|
||||
}
|
||||
ov := any(o.value)
|
||||
if eq, ok := ov.(equatable[T]); ok {
|
||||
return eq.Equal(v.value)
|
||||
}
|
||||
if reflect.TypeFor[T]().Comparable() {
|
||||
return ov == any(v.value)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// MarshalJSONV2 implements [jsonv2.MarshalerV2].
|
||||
func (o Value[T]) MarshalJSONV2(enc *jsontext.Encoder, opts jsonv2.Options) error {
|
||||
if !o.set {
|
||||
return enc.WriteToken(jsontext.Null)
|
||||
}
|
||||
return jsonv2.MarshalEncode(enc, &o.value, opts)
|
||||
}
|
||||
|
||||
// UnmarshalJSONV2 implements [jsonv2.UnmarshalerV2].
|
||||
func (o *Value[T]) UnmarshalJSONV2(dec *jsontext.Decoder, opts jsonv2.Options) error {
|
||||
if dec.PeekKind() == 'n' {
|
||||
*o = Value[T]{}
|
||||
_, err := dec.ReadToken() // read null
|
||||
return err
|
||||
}
|
||||
o.set = true
|
||||
return jsonv2.UnmarshalDecode(dec, &o.value, opts)
|
||||
}
|
||||
|
||||
// MarshalJSON implements [json.Marshaler].
|
||||
func (o Value[T]) MarshalJSON() ([]byte, error) {
|
||||
return jsonv2.Marshal(o) // uses MarshalJSONV2
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements [json.Unmarshaler].
|
||||
func (o *Value[T]) UnmarshalJSON(b []byte) error {
|
||||
return jsonv2.Unmarshal(b, o) // uses UnmarshalJSONV2
|
||||
}
|
296
types/opt/value_test.go
Normal file
296
types/opt/value_test.go
Normal file
@ -0,0 +1,296 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package opt
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
jsonv2 "github.com/go-json-experiment/json"
|
||||
)
|
||||
|
||||
type testStruct struct {
|
||||
Int int `json:",omitempty,omitzero"`
|
||||
Str string `json:",omitempty"`
|
||||
}
|
||||
|
||||
func TestValue(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
in any
|
||||
jsonv2 bool
|
||||
want string // JSON
|
||||
wantBack any
|
||||
}{
|
||||
{
|
||||
name: "null_for_unset",
|
||||
in: struct {
|
||||
True Value[bool]
|
||||
False Value[bool]
|
||||
Unset Value[bool]
|
||||
ExplicitUnset Value[bool]
|
||||
}{
|
||||
True: ValueOf(true),
|
||||
False: ValueOf(false),
|
||||
ExplicitUnset: Value[bool]{},
|
||||
},
|
||||
want: `{"True":true,"False":false,"Unset":null,"ExplicitUnset":null}`,
|
||||
wantBack: struct {
|
||||
True Value[bool]
|
||||
False Value[bool]
|
||||
Unset Value[bool]
|
||||
ExplicitUnset Value[bool]
|
||||
}{
|
||||
True: ValueOf(true),
|
||||
False: ValueOf(false),
|
||||
Unset: Value[bool]{},
|
||||
ExplicitUnset: Value[bool]{},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "null_for_unset_jsonv2",
|
||||
in: struct {
|
||||
True Value[bool]
|
||||
False Value[bool]
|
||||
Unset Value[bool]
|
||||
ExplicitUnset Value[bool]
|
||||
}{
|
||||
True: ValueOf(true),
|
||||
False: ValueOf(false),
|
||||
ExplicitUnset: Value[bool]{},
|
||||
},
|
||||
jsonv2: true,
|
||||
want: `{"True":true,"False":false,"Unset":null,"ExplicitUnset":null}`,
|
||||
wantBack: struct {
|
||||
True Value[bool]
|
||||
False Value[bool]
|
||||
Unset Value[bool]
|
||||
ExplicitUnset Value[bool]
|
||||
}{
|
||||
True: ValueOf(true),
|
||||
False: ValueOf(false),
|
||||
Unset: Value[bool]{},
|
||||
ExplicitUnset: Value[bool]{},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "null_for_unset_omitzero",
|
||||
in: struct {
|
||||
True Value[bool] `json:",omitzero"`
|
||||
False Value[bool] `json:",omitzero"`
|
||||
Unset Value[bool] `json:",omitzero"`
|
||||
ExplicitUnset Value[bool] `json:",omitzero"`
|
||||
}{
|
||||
True: ValueOf(true),
|
||||
False: ValueOf(false),
|
||||
ExplicitUnset: Value[bool]{},
|
||||
},
|
||||
want: `{"True":true,"False":false,"Unset":null,"ExplicitUnset":null}`,
|
||||
wantBack: struct {
|
||||
True Value[bool] `json:",omitzero"`
|
||||
False Value[bool] `json:",omitzero"`
|
||||
Unset Value[bool] `json:",omitzero"`
|
||||
ExplicitUnset Value[bool] `json:",omitzero"`
|
||||
}{
|
||||
True: ValueOf(true),
|
||||
False: ValueOf(false),
|
||||
Unset: Value[bool]{},
|
||||
ExplicitUnset: Value[bool]{},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "null_for_unset_omitzero_jsonv2",
|
||||
in: struct {
|
||||
True Value[bool] `json:",omitzero"`
|
||||
False Value[bool] `json:",omitzero"`
|
||||
Unset Value[bool] `json:",omitzero"`
|
||||
ExplicitUnset Value[bool] `json:",omitzero"`
|
||||
}{
|
||||
True: ValueOf(true),
|
||||
False: ValueOf(false),
|
||||
ExplicitUnset: Value[bool]{},
|
||||
},
|
||||
jsonv2: true,
|
||||
want: `{"True":true,"False":false}`,
|
||||
wantBack: struct {
|
||||
True Value[bool] `json:",omitzero"`
|
||||
False Value[bool] `json:",omitzero"`
|
||||
Unset Value[bool] `json:",omitzero"`
|
||||
ExplicitUnset Value[bool] `json:",omitzero"`
|
||||
}{
|
||||
True: ValueOf(true),
|
||||
False: ValueOf(false),
|
||||
Unset: Value[bool]{},
|
||||
ExplicitUnset: Value[bool]{},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "string",
|
||||
in: struct {
|
||||
EmptyString Value[string]
|
||||
NonEmpty Value[string]
|
||||
Unset Value[string]
|
||||
}{
|
||||
EmptyString: ValueOf(""),
|
||||
NonEmpty: ValueOf("value"),
|
||||
Unset: Value[string]{},
|
||||
},
|
||||
want: `{"EmptyString":"","NonEmpty":"value","Unset":null}`,
|
||||
wantBack: struct {
|
||||
EmptyString Value[string]
|
||||
NonEmpty Value[string]
|
||||
Unset Value[string]
|
||||
}{ValueOf(""), ValueOf("value"), Value[string]{}},
|
||||
},
|
||||
{
|
||||
name: "integer",
|
||||
in: struct {
|
||||
Zero Value[int]
|
||||
NonZero Value[int]
|
||||
Unset Value[int]
|
||||
}{
|
||||
Zero: ValueOf(0),
|
||||
NonZero: ValueOf(42),
|
||||
Unset: Value[int]{},
|
||||
},
|
||||
want: `{"Zero":0,"NonZero":42,"Unset":null}`,
|
||||
wantBack: struct {
|
||||
Zero Value[int]
|
||||
NonZero Value[int]
|
||||
Unset Value[int]
|
||||
}{ValueOf(0), ValueOf(42), Value[int]{}},
|
||||
},
|
||||
{
|
||||
name: "struct",
|
||||
in: struct {
|
||||
Zero Value[testStruct]
|
||||
NonZero Value[testStruct]
|
||||
Unset Value[testStruct]
|
||||
}{
|
||||
Zero: ValueOf(testStruct{}),
|
||||
NonZero: ValueOf(testStruct{Int: 42, Str: "String"}),
|
||||
Unset: Value[testStruct]{},
|
||||
},
|
||||
want: `{"Zero":{},"NonZero":{"Int":42,"Str":"String"},"Unset":null}`,
|
||||
wantBack: struct {
|
||||
Zero Value[testStruct]
|
||||
NonZero Value[testStruct]
|
||||
Unset Value[testStruct]
|
||||
}{ValueOf(testStruct{}), ValueOf(testStruct{Int: 42, Str: "String"}), Value[testStruct]{}},
|
||||
},
|
||||
{
|
||||
name: "struct_ptr",
|
||||
in: struct {
|
||||
Zero Value[*testStruct]
|
||||
NonZero Value[*testStruct]
|
||||
Unset Value[*testStruct]
|
||||
}{
|
||||
Zero: ValueOf(&testStruct{}),
|
||||
NonZero: ValueOf(&testStruct{Int: 42, Str: "String"}),
|
||||
Unset: Value[*testStruct]{},
|
||||
},
|
||||
want: `{"Zero":{},"NonZero":{"Int":42,"Str":"String"},"Unset":null}`,
|
||||
wantBack: struct {
|
||||
Zero Value[*testStruct]
|
||||
NonZero Value[*testStruct]
|
||||
Unset Value[*testStruct]
|
||||
}{ValueOf(&testStruct{}), ValueOf(&testStruct{Int: 42, Str: "String"}), Value[*testStruct]{}},
|
||||
},
|
||||
{
|
||||
name: "nil-slice-and-map",
|
||||
in: struct {
|
||||
Slice Value[[]int]
|
||||
Map Value[map[string]int]
|
||||
}{
|
||||
Slice: ValueOf[[]int](nil), // marshalled as []
|
||||
Map: ValueOf[map[string]int](nil), // marshalled as {}
|
||||
},
|
||||
want: `{"Slice":[],"Map":{}}`,
|
||||
wantBack: struct {
|
||||
Slice Value[[]int]
|
||||
Map Value[map[string]int]
|
||||
}{ValueOf([]int{}), ValueOf(map[string]int{})},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
var j []byte
|
||||
var err error
|
||||
if tt.jsonv2 {
|
||||
j, err = jsonv2.Marshal(tt.in)
|
||||
} else {
|
||||
j, err = json.Marshal(tt.in)
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if string(j) != tt.want {
|
||||
t.Errorf("wrong JSON:\n got: %s\nwant: %s\n", j, tt.want)
|
||||
}
|
||||
|
||||
wantBack := tt.in
|
||||
if tt.wantBack != nil {
|
||||
wantBack = tt.wantBack
|
||||
}
|
||||
// And back again:
|
||||
newVal := reflect.New(reflect.TypeOf(tt.in))
|
||||
out := newVal.Interface()
|
||||
if tt.jsonv2 {
|
||||
err = jsonv2.Unmarshal(j, out)
|
||||
} else {
|
||||
err = json.Unmarshal(j, out)
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("Unmarshal %#q: %v", j, err)
|
||||
}
|
||||
got := newVal.Elem().Interface()
|
||||
if !reflect.DeepEqual(got, wantBack) {
|
||||
t.Errorf("value mismatch\n got: %+v\nwant: %+v\n", got, wantBack)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestValueEqual(t *testing.T) {
|
||||
tests := []struct {
|
||||
o Value[bool]
|
||||
v Value[bool]
|
||||
want bool
|
||||
}{
|
||||
{ValueOf(true), ValueOf(true), true},
|
||||
{ValueOf(true), ValueOf(false), false},
|
||||
{ValueOf(true), Value[bool]{}, false},
|
||||
{ValueOf(false), ValueOf(false), true},
|
||||
{ValueOf(false), ValueOf(true), false},
|
||||
{ValueOf(false), Value[bool]{}, false},
|
||||
{Value[bool]{}, Value[bool]{}, true},
|
||||
{Value[bool]{}, ValueOf(true), false},
|
||||
{Value[bool]{}, ValueOf(false), false},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
if got := tt.o.Equal(tt.v); got != tt.want {
|
||||
t.Errorf("(%v).Equals(%v) = %v; want %v", tt.o, tt.v, got, tt.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestIncomparableValueEqual(t *testing.T) {
|
||||
tests := []struct {
|
||||
o Value[[]bool]
|
||||
v Value[[]bool]
|
||||
want bool
|
||||
}{
|
||||
{ValueOf([]bool{}), ValueOf([]bool{}), false},
|
||||
{ValueOf([]bool{true}), ValueOf([]bool{true}), false},
|
||||
{Value[[]bool]{}, ValueOf([]bool{}), false},
|
||||
{ValueOf([]bool{}), Value[[]bool]{}, false},
|
||||
{Value[[]bool]{}, Value[[]bool]{}, true},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
if got := tt.o.Equal(tt.v); got != tt.want {
|
||||
t.Errorf("(%v).Equals(%v) = %v; want %v", tt.o, tt.v, got, tt.want)
|
||||
}
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user