github.com/vmware/govmomi@v0.43.0/object/option_value_list_test.go (about)

     1  /*
     2  Copyright (c) 2024-2024 VMware, Inc. All Rights Reserved.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8  http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package object_test
    18  
    19  import (
    20  	"fmt"
    21  	"strings"
    22  	"testing"
    23  
    24  	"github.com/stretchr/testify/assert"
    25  
    26  	"github.com/vmware/govmomi/object"
    27  	"github.com/vmware/govmomi/vim25/types"
    28  )
    29  
    30  type nillableOptionValue struct{}
    31  
    32  func (ov nillableOptionValue) GetOptionValue() *types.OptionValue {
    33  	return nil
    34  }
    35  
    36  func TestOptionValueList(t *testing.T) {
    37  	const (
    38  		sza   = "a"
    39  		szb   = "b"
    40  		szc   = "c"
    41  		szd   = "d"
    42  		sze   = "e"
    43  		sz1   = "1"
    44  		sz2   = "2"
    45  		sz3   = "3"
    46  		sz4   = "4"
    47  		sz5   = "5"
    48  		i32_1 = int32(1)
    49  		u64_2 = uint64(2)
    50  		f32_3 = float32(3)
    51  		f64_4 = float64(4)
    52  		b_5   = byte(5) //nolint:revive,stylecheck
    53  	)
    54  
    55  	var (
    56  		psz1   = &[]string{sz1}[0]
    57  		pu64_2 = &[]uint64{u64_2}[0]
    58  		pf32_3 = &[]float32{f32_3}[0]
    59  		pb_5   = &[]byte{b_5}[0] //nolint:revive,stylecheck
    60  	)
    61  
    62  	t.Run("OptionValueListFromMap", func(t *testing.T) {
    63  
    64  		t.Run("a nil map should return nil", func(t *testing.T) {
    65  			assert.Nil(t, object.OptionValueListFromMap[any](nil))
    66  		})
    67  
    68  		t.Run("a map with string values should return OptionValues", func(t *testing.T) {
    69  			assert.ElementsMatch(
    70  				t,
    71  				object.OptionValueListFromMap(map[string]string{
    72  					szc: sz3,
    73  					sza: sz1,
    74  					szb: sz2,
    75  				}),
    76  				[]types.BaseOptionValue{
    77  					&types.OptionValue{Key: szb, Value: sz2},
    78  					&types.OptionValue{Key: sza, Value: sz1},
    79  					&types.OptionValue{Key: szc, Value: sz3},
    80  				},
    81  			)
    82  		})
    83  
    84  		t.Run("a map with values of varying numeric types should return OptionValues", func(t *testing.T) {
    85  			assert.ElementsMatch(
    86  				t,
    87  				object.OptionValueListFromMap(map[string]any{
    88  					szc: f32_3,
    89  					sza: i32_1,
    90  					szb: u64_2,
    91  				}),
    92  				[]types.BaseOptionValue{
    93  					&types.OptionValue{Key: szb, Value: u64_2},
    94  					&types.OptionValue{Key: sza, Value: i32_1},
    95  					&types.OptionValue{Key: szc, Value: f32_3},
    96  				},
    97  			)
    98  		})
    99  
   100  		t.Run("a map with pointer values should return OptionValues", func(t *testing.T) {
   101  			assert.ElementsMatch(
   102  				t,
   103  				object.OptionValueListFromMap(map[string]any{
   104  					szc: pf32_3,
   105  					sza: psz1,
   106  					szb: pu64_2,
   107  				}),
   108  				[]types.BaseOptionValue{
   109  					&types.OptionValue{Key: szb, Value: pu64_2},
   110  					&types.OptionValue{Key: sza, Value: psz1},
   111  					&types.OptionValue{Key: szc, Value: pf32_3},
   112  				},
   113  			)
   114  		})
   115  	})
   116  
   117  	t.Run("IsTrueOrFalse", func(t *testing.T) {
   118  
   119  		type testCase struct {
   120  			name string
   121  			left object.OptionValueList
   122  			key  string
   123  			ok   bool
   124  		}
   125  
   126  		addBoolTestCases := func(b, ok bool) []testCase {
   127  			return []testCase{
   128  				{
   129  					name: fmt.Sprintf("a key with %v should return %v", b, ok),
   130  					left: object.OptionValueList{
   131  						&types.OptionValue{Key: sza, Value: b},
   132  					},
   133  					key: sza,
   134  					ok:  ok,
   135  				},
   136  				{
   137  					name: fmt.Sprintf("a key with %v should return %v", !b, !ok),
   138  					left: object.OptionValueList{
   139  						&types.OptionValue{Key: sza, Value: !b},
   140  					},
   141  					key: sza,
   142  					ok:  !ok,
   143  				},
   144  			}
   145  		}
   146  
   147  		addNumericalTestCases := func(i int, ok bool) []testCase {
   148  			return []testCase{
   149  				{
   150  					name: fmt.Sprintf("a key with byte(%v) should return %v", i, ok),
   151  					left: object.OptionValueList{
   152  						&types.OptionValue{Key: sza, Value: byte(i)},
   153  					},
   154  					key: sza,
   155  					ok:  ok,
   156  				},
   157  				{
   158  					name: fmt.Sprintf("a key with uint(%v) should return %v", i, ok),
   159  					left: object.OptionValueList{
   160  						&types.OptionValue{Key: sza, Value: uint(i)},
   161  					},
   162  					key: sza,
   163  					ok:  ok,
   164  				},
   165  				{
   166  					name: fmt.Sprintf("a key with uint8(%v) should return %v", i, ok),
   167  					left: object.OptionValueList{
   168  						&types.OptionValue{Key: sza, Value: uint8(i)},
   169  					},
   170  					key: sza,
   171  					ok:  ok,
   172  				},
   173  				{
   174  					name: fmt.Sprintf("a key with uint16(%v) should return %v", i, ok),
   175  					left: object.OptionValueList{
   176  						&types.OptionValue{Key: sza, Value: uint16(i)},
   177  					},
   178  					key: sza,
   179  					ok:  ok,
   180  				},
   181  				{
   182  					name: fmt.Sprintf("a key with uint32(%v) should return %v", i, ok),
   183  					left: object.OptionValueList{
   184  						&types.OptionValue{Key: sza, Value: uint32(i)},
   185  					},
   186  					key: sza,
   187  					ok:  ok,
   188  				},
   189  				{
   190  					name: fmt.Sprintf("a key with uint64(%v) should return %v", i, ok),
   191  					left: object.OptionValueList{
   192  						&types.OptionValue{Key: sza, Value: uint64(i)},
   193  					},
   194  					key: sza,
   195  					ok:  ok,
   196  				},
   197  
   198  				{
   199  					name: fmt.Sprintf("a key with int(%v) should return %v", i, ok),
   200  					left: object.OptionValueList{
   201  						&types.OptionValue{Key: sza, Value: int(i)},
   202  					},
   203  					key: sza,
   204  					ok:  ok,
   205  				},
   206  				{
   207  					name: fmt.Sprintf("a key with int8(%v) should return %v", i, ok),
   208  					left: object.OptionValueList{
   209  						&types.OptionValue{Key: sza, Value: int8(i)},
   210  					},
   211  					key: sza,
   212  					ok:  ok,
   213  				},
   214  				{
   215  					name: fmt.Sprintf("a key with int16(%v) should return %v", i, ok),
   216  					left: object.OptionValueList{
   217  						&types.OptionValue{Key: sza, Value: int16(i)},
   218  					},
   219  					key: sza,
   220  					ok:  ok,
   221  				},
   222  				{
   223  					name: fmt.Sprintf("a key with int32(%v) should return %v", i, ok),
   224  					left: object.OptionValueList{
   225  						&types.OptionValue{Key: sza, Value: int32(i)},
   226  					},
   227  					key: sza,
   228  					ok:  ok,
   229  				},
   230  				{
   231  					name: fmt.Sprintf("a key with int64(%v) should return %v", i, ok),
   232  					left: object.OptionValueList{
   233  						&types.OptionValue{Key: sza, Value: int64(i)},
   234  					},
   235  					key: sza,
   236  					ok:  ok,
   237  				},
   238  
   239  				{
   240  					name: fmt.Sprintf("a key with float32(%v) should return %v", i, ok),
   241  					left: object.OptionValueList{
   242  						&types.OptionValue{Key: sza, Value: float32(i)},
   243  					},
   244  					key: sza,
   245  					ok:  ok,
   246  				},
   247  				{
   248  					name: fmt.Sprintf("a key with float64(%v) should return %v", i, ok),
   249  					left: object.OptionValueList{
   250  						&types.OptionValue{Key: sza, Value: float64(i)},
   251  					},
   252  					key: sza,
   253  					ok:  ok,
   254  				},
   255  			}
   256  		}
   257  
   258  		addTestCasesForPermutedString := func(s string, ok bool) []testCase {
   259  			var testCases []testCase
   260  			for _, s := range permuteByCase(s) {
   261  				testCases = append(testCases, testCase{
   262  					name: fmt.Sprintf("a key with %q should return %v", s, ok),
   263  					left: object.OptionValueList{
   264  						&types.OptionValue{Key: sza, Value: s},
   265  					},
   266  					key: sza,
   267  					ok:  ok,
   268  				})
   269  			}
   270  			return testCases
   271  		}
   272  
   273  		addTestCasesForPermutedStrings := func(ok bool, args ...string) []testCase {
   274  			var testCases []testCase
   275  			for i := range args {
   276  				testCases = append(testCases, addTestCasesForPermutedString(args[i], ok)...)
   277  			}
   278  			return testCases
   279  		}
   280  
   281  		baseTestCases := []testCase{
   282  			{
   283  				name: "a nil receiver should not panic and return false",
   284  				left: nil,
   285  				key:  "",
   286  				ok:   false,
   287  			},
   288  			{
   289  				name: "a non-existent key should return false",
   290  				left: object.OptionValueList{},
   291  				key:  "",
   292  				ok:   false,
   293  			},
   294  		}
   295  
   296  		runTests := func(t *testing.T, expected bool) {
   297  			testCases := append([]testCase{}, baseTestCases...)
   298  
   299  			for i := range baseTestCases {
   300  				tc := testCases[i]
   301  				t.Run(tc.name, func(t *testing.T) {
   302  					var ok bool
   303  					if expected {
   304  						assert.NotPanics(t, func() { ok = tc.left.IsTrue(tc.key) })
   305  						assert.Equal(t, tc.ok, ok)
   306  					} else {
   307  						assert.NotPanics(t, func() { ok = tc.left.IsFalse(tc.key) })
   308  						assert.Equal(t, tc.ok, ok)
   309  					}
   310  				})
   311  			}
   312  
   313  			testCases = append([]testCase{}, addBoolTestCases(true, expected)...)
   314  			testCases = append(testCases, addNumericalTestCases(0, !expected)...)
   315  			testCases = append(testCases, addNumericalTestCases(1, expected)...)
   316  			testCases = append(testCases, addTestCasesForPermutedStrings(expected, "", "1", "on", "t", "true", "y", "yes")...)
   317  			testCases = append(testCases, addTestCasesForPermutedStrings(!expected, "0", "f", "false", "n", "no", "off")...)
   318  
   319  			for i := range testCases {
   320  				tc := testCases[i]
   321  				t.Run(tc.name, func(t *testing.T) {
   322  					var ok bool
   323  					if expected {
   324  						assert.NotPanics(t, func() { ok = tc.left.IsTrue(tc.key) })
   325  						assert.Equal(t, tc.ok, ok)
   326  						assert.NotPanics(t, func() { ok = tc.left.IsFalse(tc.key) })
   327  						assert.Equal(t, !tc.ok, ok)
   328  					} else {
   329  						assert.NotPanics(t, func() { ok = tc.left.IsTrue(tc.key) })
   330  						assert.Equal(t, !tc.ok, ok)
   331  						assert.NotPanics(t, func() { ok = tc.left.IsFalse(tc.key) })
   332  						assert.Equal(t, tc.ok, ok)
   333  					}
   334  
   335  				})
   336  			}
   337  		}
   338  
   339  		t.Run("IsTrue", func(t *testing.T) { runTests(t, true) })
   340  		t.Run("IsFalse", func(t *testing.T) { runTests(t, false) })
   341  
   342  	})
   343  
   344  	t.Run("Get", func(t *testing.T) {
   345  		testCases := []struct {
   346  			name string
   347  			left object.OptionValueList
   348  			key  string
   349  			out  any
   350  			ok   bool
   351  		}{
   352  			{
   353  				name: "a nil receiver should not panic and return nil, false",
   354  				left: nil,
   355  				key:  "",
   356  				out:  nil,
   357  				ok:   false,
   358  			},
   359  			{
   360  				name: "a non-existent key should return nil, false",
   361  				left: object.OptionValueList{},
   362  				key:  "",
   363  				out:  nil,
   364  				ok:   false,
   365  			},
   366  			{
   367  				name: "an existing key should return its value, true",
   368  				left: object.OptionValueList{
   369  					&types.OptionValue{Key: sza, Value: sz1},
   370  				},
   371  				key: sza,
   372  				out: sz1,
   373  				ok:  true,
   374  			},
   375  			{
   376  				name: "an existing key should return its value, true when data includes a nillable types.BaseOptionValue",
   377  				left: object.OptionValueList{
   378  					nillableOptionValue{},
   379  					&types.OptionValue{Key: sza, Value: sz1},
   380  				},
   381  				key: sza,
   382  				out: sz1,
   383  				ok:  true,
   384  			},
   385  		}
   386  
   387  		for i := range testCases {
   388  			tc := testCases[i]
   389  			t.Run(tc.name, func(t *testing.T) {
   390  				var (
   391  					out any
   392  					ok  bool
   393  				)
   394  				assert.NotPanics(t, func() { out, ok = tc.left.Get(tc.key) })
   395  				assert.Equal(t, tc.out, out)
   396  				assert.Equal(t, tc.ok, ok)
   397  			})
   398  		}
   399  	})
   400  
   401  	t.Run("GetString", func(t *testing.T) {
   402  		testCases := []struct {
   403  			name string
   404  			left object.OptionValueList
   405  			key  string
   406  			out  string
   407  			ok   bool
   408  		}{
   409  			{
   410  				name: "a nil receiver should not panic and return \"\", false",
   411  				left: nil,
   412  				key:  "",
   413  				out:  "",
   414  				ok:   false,
   415  			},
   416  			{
   417  				name: "a non-existent key should return \"\", false",
   418  				left: object.OptionValueList{},
   419  				key:  "",
   420  				out:  "",
   421  				ok:   false,
   422  			},
   423  			{
   424  				name: "an existing key for a string value should return its string value, true",
   425  				left: object.OptionValueList{
   426  					&types.OptionValue{Key: sza, Value: sz1},
   427  				},
   428  				key: sza,
   429  				out: sz1,
   430  				ok:  true,
   431  			},
   432  			{
   433  				name: "an existing key for a *string value that is not nil should return its string value, true",
   434  				left: object.OptionValueList{
   435  					&types.OptionValue{Key: sza, Value: psz1},
   436  				},
   437  				key: sza,
   438  				out: sz1,
   439  				ok:  true,
   440  			},
   441  			{
   442  				name: "an existing key for a *string value that is nil should return \"\", true",
   443  				left: object.OptionValueList{
   444  					&types.OptionValue{Key: sza, Value: (*string)(nil)},
   445  				},
   446  				key: sza,
   447  				out: "",
   448  				ok:  true,
   449  			},
   450  			{
   451  				name: "an existing key for an int32 value should return its string value, true",
   452  				left: object.OptionValueList{
   453  					&types.OptionValue{Key: sza, Value: i32_1},
   454  				},
   455  				key: sza,
   456  				out: sz1,
   457  				ok:  true,
   458  			},
   459  			{
   460  				name: "an existing key for a *uint64 value that is not nil should return its string value, true",
   461  				left: object.OptionValueList{
   462  					&types.OptionValue{Key: sza, Value: pu64_2},
   463  				},
   464  				key: sza,
   465  				out: sz2,
   466  				ok:  true,
   467  			},
   468  			{
   469  				name: "an existing key for a *uint64 value that is nil should return \"\", true",
   470  				left: object.OptionValueList{
   471  					&types.OptionValue{Key: sza, Value: (*uint64)(nil)},
   472  				},
   473  				key: sza,
   474  				out: "",
   475  				ok:  true,
   476  			},
   477  			{
   478  				name: "an existing key for a string value should return its string value, true when data includes a nillable types.BaseOptionValue",
   479  				left: object.OptionValueList{
   480  					nillableOptionValue{},
   481  					&types.OptionValue{Key: sza, Value: sz1},
   482  				},
   483  				key: sza,
   484  				out: sz1,
   485  				ok:  true,
   486  			},
   487  		}
   488  
   489  		for i := range testCases {
   490  			tc := testCases[i]
   491  			t.Run(tc.name, func(t *testing.T) {
   492  				var (
   493  					out string
   494  					ok  bool
   495  				)
   496  				assert.NotPanics(t, func() { out, ok = tc.left.GetString(tc.key) })
   497  				assert.Equal(t, tc.out, out)
   498  				assert.Equal(t, tc.ok, ok)
   499  			})
   500  		}
   501  	})
   502  
   503  	t.Run("Map", func(t *testing.T) {
   504  		testCases := []struct {
   505  			name string
   506  			left object.OptionValueList
   507  			out  map[string]any
   508  		}{
   509  			{
   510  				name: "a nil receiver should not panic and return nil",
   511  				left: nil,
   512  				out:  nil,
   513  			},
   514  			{
   515  				name: "data with homogeneous values should return a map",
   516  				left: object.OptionValueList{
   517  					&types.OptionValue{Key: sza, Value: sz1},
   518  				},
   519  				out: map[string]any{
   520  					sza: sz1,
   521  				},
   522  			},
   523  			{
   524  				name: "data with heterogeneous values should return a map",
   525  				left: object.OptionValueList{
   526  					&types.OptionValue{Key: sza, Value: sz1},
   527  					&types.OptionValue{Key: szb, Value: u64_2},
   528  					&types.OptionValue{Key: szc, Value: pf32_3},
   529  				},
   530  				out: map[string]any{
   531  					sza: sz1,
   532  					szb: u64_2,
   533  					szc: pf32_3,
   534  				},
   535  			},
   536  			{
   537  				name: "data with just a nillable types.BaseOptionValue should return nil",
   538  				left: object.OptionValueList{
   539  					nillableOptionValue{},
   540  				},
   541  				out: nil,
   542  			},
   543  		}
   544  
   545  		for i := range testCases {
   546  			tc := testCases[i]
   547  			t.Run(tc.name, func(t *testing.T) {
   548  				var out map[string]any
   549  				assert.NotPanics(t, func() { out = tc.left.Map() })
   550  				assert.Equal(t, tc.out, out)
   551  			})
   552  		}
   553  	})
   554  
   555  	t.Run("StringMap", func(t *testing.T) {
   556  		testCases := []struct {
   557  			name string
   558  			left object.OptionValueList
   559  			out  map[string]string
   560  		}{
   561  			{
   562  				name: "a nil receiver should not panic and return nil",
   563  				left: nil,
   564  				out:  nil,
   565  			},
   566  			{
   567  				name: "data with homogeneous values should return a map",
   568  				left: object.OptionValueList{
   569  					&types.OptionValue{Key: sza, Value: sz1},
   570  				},
   571  				out: map[string]string{
   572  					sza: sz1,
   573  				},
   574  			},
   575  			{
   576  				name: "data with heterogeneous values should return a map",
   577  				left: object.OptionValueList{
   578  					&types.OptionValue{Key: sza, Value: sz1},
   579  					&types.OptionValue{Key: szb, Value: u64_2},
   580  					&types.OptionValue{Key: szc, Value: pf32_3},
   581  				},
   582  				out: map[string]string{
   583  					sza: sz1,
   584  					szb: sz2,
   585  					szc: sz3,
   586  				},
   587  			},
   588  			{
   589  				name: "data with just a nillable types.BaseOptionValue should return nil",
   590  				left: object.OptionValueList{
   591  					nillableOptionValue{},
   592  				},
   593  				out: nil,
   594  			},
   595  		}
   596  
   597  		for i := range testCases {
   598  			tc := testCases[i]
   599  			t.Run(tc.name, func(t *testing.T) {
   600  				var out map[string]string
   601  				assert.NotPanics(t, func() { out = tc.left.StringMap() })
   602  				assert.Equal(t, tc.out, out)
   603  			})
   604  		}
   605  	})
   606  
   607  	t.Run("Additions", func(t *testing.T) {
   608  		testCases := []struct {
   609  			name  string
   610  			left  object.OptionValueList
   611  			right object.OptionValueList
   612  			out   object.OptionValueList
   613  		}{
   614  			{
   615  				name:  "a nil receiver and nil input should not panic and return nil",
   616  				left:  nil,
   617  				right: nil,
   618  				out:   nil,
   619  			},
   620  			{
   621  				name: "a nil receiver and non-nil input should not panic and return the diff",
   622  				left: nil,
   623  				right: object.OptionValueList{
   624  					&types.OptionValue{Key: szb, Value: ""},
   625  					&types.OptionValue{Key: szd, Value: f64_4},
   626  					&types.OptionValue{Key: sze, Value: pb_5},
   627  				},
   628  				out: object.OptionValueList{
   629  					&types.OptionValue{Key: szb, Value: ""},
   630  					&types.OptionValue{Key: szd, Value: f64_4},
   631  					&types.OptionValue{Key: sze, Value: pb_5},
   632  				},
   633  			},
   634  			{
   635  				name: "a non-nil receiver and nil input should return nil",
   636  				left: object.OptionValueList{
   637  					&types.OptionValue{Key: sza, Value: sz1},
   638  					&types.OptionValue{Key: szb, Value: sz2},
   639  					&types.OptionValue{Key: szc, Value: sz3},
   640  				},
   641  				right: nil,
   642  				out:   nil,
   643  			},
   644  			{
   645  				name: "a non-nil receiver and non-nil input should return the diff",
   646  				left: object.OptionValueList{
   647  					&types.OptionValue{Key: sza, Value: sz1},
   648  					&types.OptionValue{Key: szb, Value: sz2},
   649  					&types.OptionValue{Key: szc, Value: sz3},
   650  				},
   651  				right: object.OptionValueList{
   652  					&types.OptionValue{Key: szb, Value: ""},
   653  					&types.OptionValue{Key: szd, Value: f64_4},
   654  					&types.OptionValue{Key: sze, Value: pb_5},
   655  				},
   656  				out: object.OptionValueList{
   657  					&types.OptionValue{Key: szd, Value: f64_4},
   658  					&types.OptionValue{Key: sze, Value: pb_5},
   659  				},
   660  			},
   661  		}
   662  
   663  		for i := range testCases {
   664  			tc := testCases[i]
   665  			t.Run(tc.name, func(t *testing.T) {
   666  				var out object.OptionValueList
   667  				assert.NotPanics(t, func() { out = tc.left.Additions(tc.right...) })
   668  				assert.Equal(t, tc.out, out)
   669  			})
   670  		}
   671  	})
   672  
   673  	t.Run("Diff", func(t *testing.T) {
   674  		testCases := []struct {
   675  			name  string
   676  			left  object.OptionValueList
   677  			right object.OptionValueList
   678  			out   object.OptionValueList
   679  		}{
   680  			{
   681  				name:  "a nil receiver and nil input should not panic and return nil",
   682  				left:  nil,
   683  				right: nil,
   684  				out:   nil,
   685  			},
   686  			{
   687  				name: "a nil receiver and non-nil input should not panic and return the diff",
   688  				left: nil,
   689  				right: object.OptionValueList{
   690  					&types.OptionValue{Key: szb, Value: ""},
   691  					&types.OptionValue{Key: szd, Value: f64_4},
   692  					&types.OptionValue{Key: sze, Value: pb_5},
   693  				},
   694  				out: object.OptionValueList{
   695  					&types.OptionValue{Key: szb, Value: ""},
   696  					&types.OptionValue{Key: szd, Value: f64_4},
   697  					&types.OptionValue{Key: sze, Value: pb_5},
   698  				},
   699  			},
   700  			{
   701  				name: "a non-nil receiver and nil input should return nil",
   702  				left: object.OptionValueList{
   703  					&types.OptionValue{Key: sza, Value: sz1},
   704  					&types.OptionValue{Key: szb, Value: sz2},
   705  					&types.OptionValue{Key: szc, Value: sz3},
   706  				},
   707  				right: nil,
   708  				out:   nil,
   709  			},
   710  			{
   711  				name: "a non-nil receiver and non-nil input should return the diff",
   712  				left: object.OptionValueList{
   713  					&types.OptionValue{Key: sza, Value: sz1},
   714  					&types.OptionValue{Key: szb, Value: sz2},
   715  					&types.OptionValue{Key: szc, Value: sz3},
   716  				},
   717  				right: object.OptionValueList{
   718  					&types.OptionValue{Key: szb, Value: ""},
   719  					&types.OptionValue{Key: szd, Value: f64_4},
   720  					&types.OptionValue{Key: sze, Value: pb_5},
   721  				},
   722  				out: object.OptionValueList{
   723  					&types.OptionValue{Key: szb, Value: ""},
   724  					&types.OptionValue{Key: szd, Value: f64_4},
   725  					&types.OptionValue{Key: sze, Value: pb_5},
   726  				},
   727  			},
   728  		}
   729  
   730  		for i := range testCases {
   731  			tc := testCases[i]
   732  			t.Run(tc.name, func(t *testing.T) {
   733  				var out object.OptionValueList
   734  				assert.NotPanics(t, func() { out = tc.left.Diff(tc.right...) })
   735  				assert.Equal(t, tc.out, out)
   736  			})
   737  		}
   738  	})
   739  
   740  	t.Run("Join", func(t *testing.T) {
   741  		testCases := []struct {
   742  			name  string
   743  			left  object.OptionValueList
   744  			right object.OptionValueList
   745  			out   object.OptionValueList
   746  		}{
   747  			{
   748  				name:  "a nil receiver and nil input should not panic and return nil",
   749  				left:  nil,
   750  				right: nil,
   751  				out:   nil,
   752  			},
   753  			{
   754  				name: "a nil receiver and non-nil input should not panic and return the joined data",
   755  				left: nil,
   756  				right: object.OptionValueList{
   757  					&types.OptionValue{Key: szb, Value: ""},
   758  					&types.OptionValue{Key: szd, Value: f64_4},
   759  					&types.OptionValue{Key: sze, Value: pb_5},
   760  				},
   761  				out: object.OptionValueList{
   762  					&types.OptionValue{Key: szb, Value: ""},
   763  					&types.OptionValue{Key: szd, Value: f64_4},
   764  					&types.OptionValue{Key: sze, Value: pb_5},
   765  				},
   766  			},
   767  			{
   768  				name: "a non-nil receiver and nil input should return the joined data",
   769  				left: object.OptionValueList{
   770  					&types.OptionValue{Key: sza, Value: sz1},
   771  					&types.OptionValue{Key: szb, Value: sz2},
   772  					&types.OptionValue{Key: szc, Value: sz3},
   773  				},
   774  				right: nil,
   775  				out: object.OptionValueList{
   776  					&types.OptionValue{Key: sza, Value: sz1},
   777  					&types.OptionValue{Key: szb, Value: sz2},
   778  					&types.OptionValue{Key: szc, Value: sz3},
   779  				},
   780  			},
   781  			{
   782  				name: "a non-nil receiver and non-nil input should return the joined data",
   783  				left: object.OptionValueList{
   784  					&types.OptionValue{Key: sza, Value: sz1},
   785  					&types.OptionValue{Key: szb, Value: sz2},
   786  					&types.OptionValue{Key: szc, Value: sz3},
   787  				},
   788  				right: object.OptionValueList{
   789  					&types.OptionValue{Key: szb, Value: ""},
   790  					&types.OptionValue{Key: szd, Value: f64_4},
   791  					&types.OptionValue{Key: sze, Value: pb_5},
   792  				},
   793  				out: object.OptionValueList{
   794  					&types.OptionValue{Key: sza, Value: sz1},
   795  					&types.OptionValue{Key: szb, Value: sz2},
   796  					&types.OptionValue{Key: szc, Value: sz3},
   797  					&types.OptionValue{Key: szd, Value: f64_4},
   798  					&types.OptionValue{Key: sze, Value: pb_5},
   799  				},
   800  			},
   801  			{
   802  				name: "a non-nil receiver and non-nil input, flipping left and right, should return the joined data",
   803  				left: object.OptionValueList{
   804  					&types.OptionValue{Key: szb, Value: ""},
   805  					&types.OptionValue{Key: szd, Value: f64_4},
   806  					&types.OptionValue{Key: sze, Value: pb_5},
   807  				},
   808  				right: object.OptionValueList{
   809  					&types.OptionValue{Key: sza, Value: sz1},
   810  					&types.OptionValue{Key: szb, Value: sz2},
   811  					&types.OptionValue{Key: szc, Value: sz3},
   812  				},
   813  				out: object.OptionValueList{
   814  					&types.OptionValue{Key: szb, Value: ""},
   815  					&types.OptionValue{Key: szd, Value: f64_4},
   816  					&types.OptionValue{Key: sze, Value: pb_5},
   817  					&types.OptionValue{Key: sza, Value: sz1},
   818  					&types.OptionValue{Key: szc, Value: sz3},
   819  				},
   820  			},
   821  		}
   822  
   823  		for i := range testCases {
   824  			tc := testCases[i]
   825  			t.Run(tc.name, func(t *testing.T) {
   826  				var out object.OptionValueList
   827  				assert.NotPanics(t, func() { out = tc.left.Join(tc.right...) })
   828  				assert.Equal(t, tc.out, out)
   829  			})
   830  		}
   831  	})
   832  }
   833  
   834  func permuteByCase(s string) []string {
   835  	if len(s) == 0 {
   836  		return []string{s}
   837  	}
   838  
   839  	if len(s) == 1 {
   840  		lc := strings.ToLower(s)
   841  		uc := strings.ToUpper(s)
   842  		if lc == uc {
   843  			return []string{s}
   844  		}
   845  		return []string{lc, uc}
   846  	}
   847  
   848  	var p []string
   849  	for _, i := range permuteByCase(s[0:1]) {
   850  		for _, j := range permuteByCase(s[1:]) {
   851  			p = append(p, fmt.Sprintf("%s%s", i, j))
   852  		}
   853  	}
   854  
   855  	return p
   856  }