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

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