| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310 |
- // 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"
- "tailscale.com/types/bools"
- "tailscale.com/util/must"
- )
- var (
- _ jsonv2.MarshalerTo = (*Value[bool])(nil)
- _ jsonv2.UnmarshalerFrom = (*Value[bool])(nil)
- )
- 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: bools.IfElse(
- // Detect whether v1 "encoding/json" supports `omitzero` or not.
- // TODO(Go1.24): Remove this after `omitzero` is supported.
- string(must.Get(json.Marshal(struct {
- X int `json:",omitzero"`
- }{}))) == `{}`,
- `{"True":true,"False":false}`, // omitzero supported
- `{"True":true,"False":false,"Unset":null,"ExplicitUnset":null}`), // omitzero not supported
- 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)
- }
- }
- }
|