github.com/vmware/govmomi@v0.51.0/fault/fault_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 fault_test
     6  
     7  import (
     8  	"errors"
     9  	"fmt"
    10  	"reflect"
    11  	"testing"
    12  
    13  	"github.com/stretchr/testify/assert"
    14  
    15  	"github.com/vmware/govmomi/fault"
    16  	"github.com/vmware/govmomi/task"
    17  	"github.com/vmware/govmomi/vim25/soap"
    18  	"github.com/vmware/govmomi/vim25/types"
    19  )
    20  
    21  func asPtr[T any](args ...T) *T {
    22  	if len(args) == 0 {
    23  		var t T
    24  		return &t
    25  	}
    26  	return &args[0]
    27  }
    28  
    29  var (
    30  	nilSystemErr       = asPtr[*types.SystemError]()
    31  	nilInvalidVmConfig = asPtr[*types.InvalidVmConfig]()
    32  )
    33  
    34  type nilErrWrapper struct{}
    35  
    36  func (e nilErrWrapper) Unwrap() error {
    37  	return nil
    38  }
    39  
    40  type unwrappableErrSlice []error
    41  
    42  func (e unwrappableErrSlice) Unwrap() []error {
    43  	return e
    44  }
    45  
    46  type nilBaseMethodFault struct{}
    47  
    48  func (f nilBaseMethodFault) GetMethodFault() *types.MethodFault {
    49  	return nil
    50  }
    51  
    52  type asFaulter struct {
    53  	val  any
    54  	okay bool
    55  	msg  string
    56  }
    57  
    58  func (f asFaulter) AsFault(target any) (string, bool) {
    59  	if !f.okay {
    60  		return "", false
    61  	}
    62  	targetVal := reflect.ValueOf(target)
    63  	targetVal.Elem().Set(reflect.ValueOf(f.val))
    64  	return f.msg, true
    65  }
    66  
    67  type isFaulter bool
    68  
    69  func (f isFaulter) GetMethodFault() *types.MethodFault {
    70  	return nil
    71  }
    72  
    73  func (f isFaulter) IsFault(target types.BaseMethodFault) bool {
    74  	return bool(f)
    75  }
    76  
    77  func TestAs(t *testing.T) {
    78  
    79  	testCases := []struct {
    80  		name                     string
    81  		err                      any
    82  		target                   any
    83  		expectedLocalizedMessage string
    84  		expectedOkay             bool
    85  		expectedPanic            any
    86  		expectedTarget           any
    87  	}{
    88  
    89  		{
    90  			name:           "err is nil",
    91  			err:            nil,
    92  			target:         asPtr[*types.SystemError](),
    93  			expectedTarget: asPtr[*types.SystemError](),
    94  		},
    95  		{
    96  			name:           "err is not supported",
    97  			err:            struct{}{},
    98  			target:         asPtr[*types.SystemError](),
    99  			expectedTarget: asPtr[*types.SystemError](),
   100  		},
   101  		{
   102  			name:           "target is nil",
   103  			err:            errors.New("error"),
   104  			target:         nil,
   105  			expectedTarget: nil,
   106  			expectedPanic:  "fault: target cannot be nil",
   107  		},
   108  		{
   109  			name:           "target is not pointer",
   110  			err:            errors.New("error"),
   111  			target:         types.SystemError{},
   112  			expectedTarget: types.SystemError{},
   113  			expectedPanic:  "fault: target must be a non-nil pointer",
   114  		},
   115  		{
   116  			name:           "target is not pointer to expected type",
   117  			err:            errors.New("error"),
   118  			target:         &types.SystemError{},
   119  			expectedTarget: &types.SystemError{},
   120  			expectedPanic:  "fault: *target must be interface or implement BaseMethodFault",
   121  		},
   122  		{
   123  			name: "err is task.Error with fault",
   124  			err: task.Error{
   125  				LocalizedMethodFault: &types.LocalizedMethodFault{
   126  					Fault: &types.SystemError{},
   127  				},
   128  			},
   129  			target:                   asPtr[*types.SystemError](),
   130  			expectedTarget:           asPtr(&types.SystemError{}),
   131  			expectedOkay:             true,
   132  			expectedLocalizedMessage: "",
   133  		},
   134  		{
   135  			name:           "err unwraps to nil error",
   136  			err:            nilErrWrapper{},
   137  			target:         asPtr[*types.SystemError](),
   138  			expectedTarget: asPtr[*types.SystemError](),
   139  		},
   140  		{
   141  			name:           "err unwraps to nil error slice",
   142  			err:            unwrappableErrSlice{},
   143  			target:         asPtr[*types.SystemError](),
   144  			expectedTarget: asPtr[*types.SystemError](),
   145  		},
   146  		{
   147  			name: "err is wrapped task.Error with fault",
   148  			err: fmt.Errorf("my error: %w", task.Error{
   149  				LocalizedMethodFault: &types.LocalizedMethodFault{
   150  					Fault: &types.SystemError{},
   151  				},
   152  			}),
   153  			target:                   asPtr[*types.SystemError](),
   154  			expectedTarget:           asPtr(&types.SystemError{}),
   155  			expectedOkay:             true,
   156  			expectedLocalizedMessage: "",
   157  		},
   158  		{
   159  			name: "err is wrapped nil error and task.Error with fault",
   160  			err: unwrappableErrSlice{
   161  				nil,
   162  				task.Error{
   163  					LocalizedMethodFault: &types.LocalizedMethodFault{
   164  						Fault: &types.SystemError{},
   165  					},
   166  				},
   167  			},
   168  			target:                   asPtr[*types.SystemError](),
   169  			expectedTarget:           asPtr(&types.SystemError{}),
   170  			expectedOkay:             true,
   171  			expectedLocalizedMessage: "",
   172  		},
   173  		{
   174  			name:                     "err is types.BaseMethodFault",
   175  			err:                      &types.SystemError{},
   176  			target:                   asPtr[*types.SystemError](),
   177  			expectedTarget:           asPtr(&types.SystemError{}),
   178  			expectedOkay:             true,
   179  			expectedLocalizedMessage: "",
   180  		},
   181  		{
   182  			name:                     "err is types.BaseMethodFault that returns nil",
   183  			err:                      nilBaseMethodFault{},
   184  			target:                   asPtr[*types.SystemError](),
   185  			expectedTarget:           asPtr[*types.SystemError](),
   186  			expectedLocalizedMessage: "",
   187  		},
   188  		{
   189  			name:                     "err is asFaulter that returns true and fault",
   190  			err:                      asFaulter{okay: true, msg: "Hello, world.", val: &types.SystemError{}},
   191  			target:                   asPtr[*types.SystemError](),
   192  			expectedTarget:           asPtr(&types.SystemError{}),
   193  			expectedOkay:             true,
   194  			expectedLocalizedMessage: "Hello, world.",
   195  		},
   196  		{
   197  			name:                     "err is asFaulter that returns false",
   198  			err:                      asFaulter{okay: false, msg: "Hello, world."},
   199  			target:                   asPtr[*types.SystemError](),
   200  			expectedTarget:           asPtr[*types.SystemError](),
   201  			expectedLocalizedMessage: "",
   202  		},
   203  		{
   204  			name: "err is *types.LocalizedMethodFault",
   205  			err: &types.LocalizedMethodFault{
   206  				LocalizedMessage: "fake",
   207  				Fault:            &types.SystemError{},
   208  			},
   209  			target:                   asPtr[*types.SystemError](),
   210  			expectedTarget:           asPtr(&types.SystemError{}),
   211  			expectedOkay:             true,
   212  			expectedLocalizedMessage: "fake",
   213  		},
   214  		{
   215  			name: "err is *types.LocalizedMethodFault w nil fault",
   216  			err: &types.LocalizedMethodFault{
   217  				LocalizedMessage: "fake",
   218  				Fault:            nil,
   219  			},
   220  			target:         asPtr[*types.SystemError](),
   221  			expectedTarget: asPtr[*types.SystemError](),
   222  			expectedOkay:   false,
   223  		},
   224  		{
   225  			name: "err is task.Error with nested fault",
   226  			err: &types.LocalizedMethodFault{
   227  				LocalizedMessage: "fake1",
   228  				Fault: &types.SystemError{
   229  					RuntimeFault: types.RuntimeFault{
   230  						MethodFault: types.MethodFault{
   231  							FaultCause: &types.LocalizedMethodFault{
   232  								LocalizedMessage: "fake2",
   233  								Fault:            &types.InvalidVmConfig{},
   234  							},
   235  						},
   236  					},
   237  				},
   238  			},
   239  			target:                   asPtr[*types.InvalidVmConfig](),
   240  			expectedTarget:           asPtr(&types.InvalidVmConfig{}),
   241  			expectedOkay:             true,
   242  			expectedLocalizedMessage: "fake2",
   243  		},
   244  		{
   245  			name: "err is soap fault",
   246  			err: soap.WrapSoapFault(&soap.Fault{
   247  				Detail: struct {
   248  					Fault types.AnyType "xml:\",any,typeattr\""
   249  				}{
   250  					Fault: &types.SystemError{
   251  						RuntimeFault: types.RuntimeFault{
   252  							MethodFault: types.MethodFault{
   253  								FaultCause: &types.LocalizedMethodFault{
   254  									Fault: &types.InvalidVmConfig{},
   255  								},
   256  							},
   257  						},
   258  					},
   259  				},
   260  			}),
   261  			target:         asPtr[*types.InvalidVmConfig](),
   262  			expectedTarget: asPtr(&types.InvalidVmConfig{}),
   263  			expectedOkay:   true,
   264  		},
   265  		{
   266  			name: "err is soap fault with nil vim fault",
   267  			err: soap.WrapSoapFault(&soap.Fault{
   268  				Detail: struct {
   269  					Fault types.AnyType "xml:\",any,typeattr\""
   270  				}{
   271  					Fault: nil,
   272  				},
   273  			}),
   274  			target:         asPtr[*types.InvalidVmConfig](),
   275  			expectedTarget: asPtr[*types.InvalidVmConfig](),
   276  			expectedOkay:   false,
   277  		},
   278  		{
   279  			name: "err is vim fault",
   280  			err: soap.WrapVimFault(&types.SystemError{
   281  				RuntimeFault: types.RuntimeFault{
   282  					MethodFault: types.MethodFault{
   283  						FaultCause: &types.LocalizedMethodFault{
   284  							Fault: &types.InvalidVmConfig{},
   285  						},
   286  					},
   287  				},
   288  			}),
   289  			target:         asPtr[*types.InvalidVmConfig](),
   290  			expectedTarget: asPtr(&types.InvalidVmConfig{}),
   291  			expectedOkay:   true,
   292  		},
   293  		{
   294  			name: "err is vim fault with nil vim fault",
   295  			err: soap.WrapVimFault(&types.SystemError{
   296  				RuntimeFault: types.RuntimeFault{
   297  					MethodFault: types.MethodFault{
   298  						FaultCause: &types.LocalizedMethodFault{
   299  							Fault: nil,
   300  						},
   301  					},
   302  				},
   303  			}),
   304  			target:         asPtr[*types.InvalidVmConfig](),
   305  			expectedTarget: asPtr[*types.InvalidVmConfig](),
   306  			expectedOkay:   false,
   307  		},
   308  	}
   309  
   310  	for i := range testCases {
   311  		tc := testCases[i]
   312  		t.Run(tc.name, func(t *testing.T) {
   313  			t.Parallel()
   314  
   315  			var (
   316  				okay             bool
   317  				localizedMessage string
   318  			)
   319  
   320  			if tc.expectedPanic != nil {
   321  				assert.PanicsWithValue(
   322  					t,
   323  					tc.expectedPanic,
   324  					func() {
   325  						localizedMessage, okay = fault.As(tc.err, tc.target)
   326  					})
   327  			} else {
   328  				localizedMessage, okay = fault.As(tc.err, tc.target)
   329  			}
   330  
   331  			assert.Equal(t, tc.expectedOkay, okay)
   332  			assert.Equal(t, tc.expectedLocalizedMessage, localizedMessage)
   333  			assert.Equal(t, tc.expectedTarget, tc.target)
   334  		})
   335  	}
   336  }
   337  
   338  type faultResult struct {
   339  	fault               types.BaseMethodFault
   340  	localizedMessage    string
   341  	localizableMessages []types.LocalizableMessage
   342  }
   343  
   344  type testHarness struct {
   345  	isNil                bool
   346  	numCalls             int
   347  	numCallsToReturnTrue int
   348  	results              []faultResult
   349  }
   350  
   351  func (h *testHarness) Callback(
   352  	fault types.BaseMethodFault,
   353  	localizedMessage string,
   354  	localizableMessages []types.LocalizableMessage) bool {
   355  
   356  	h.numCalls++
   357  
   358  	rvFault := reflect.ValueOf(fault)
   359  	if rvFault.Kind() == reflect.Pointer {
   360  		rvFault = rvFault.Elem()
   361  	}
   362  	emptyFault := reflect.New(rvFault.Type())
   363  
   364  	h.results = append(h.results, faultResult{
   365  		fault:               emptyFault.Interface().(types.BaseMethodFault),
   366  		localizedMessage:    localizedMessage,
   367  		localizableMessages: localizableMessages,
   368  	})
   369  	return h.numCallsToReturnTrue == h.numCalls
   370  }
   371  
   372  func TestIn(t *testing.T) {
   373  	const unsupported = "fault: err must implement error, types.BaseMethodFault, or types.HasLocalizedMethodFault"
   374  
   375  	testCases := []struct {
   376  		name             string
   377  		err              any
   378  		callback         *testHarness
   379  		expectedNumCalls int
   380  		expectedResults  []faultResult
   381  		expectedPanic    any
   382  	}{
   383  		{
   384  			name:             "err is nil",
   385  			err:              nil,
   386  			callback:         &testHarness{},
   387  			expectedNumCalls: 0,
   388  			expectedPanic:    unsupported,
   389  		},
   390  		{
   391  			name:             "callback is nil",
   392  			err:              errors.New("error"),
   393  			callback:         &testHarness{isNil: true},
   394  			expectedNumCalls: 0,
   395  			expectedPanic:    "fault: onFaultFn must not be nil",
   396  		},
   397  		{
   398  			name:             "err is unsupported",
   399  			err:              struct{}{},
   400  			callback:         &testHarness{},
   401  			expectedNumCalls: 0,
   402  			expectedPanic:    unsupported,
   403  		},
   404  		{
   405  			name:             "err is unsupported but still an err",
   406  			err:              errors.New("error"),
   407  			callback:         &testHarness{},
   408  			expectedNumCalls: 0,
   409  		},
   410  		{
   411  			name:             "error is task.Error",
   412  			err:              task.Error{},
   413  			callback:         &testHarness{},
   414  			expectedNumCalls: 0,
   415  		},
   416  		{
   417  			name: "error is task.Error with localized message and fault",
   418  			err: task.Error{
   419  				LocalizedMethodFault: &types.LocalizedMethodFault{
   420  					LocalizedMessage: "fake",
   421  					Fault:            &types.SystemError{},
   422  				},
   423  			},
   424  			callback:         &testHarness{},
   425  			expectedNumCalls: 1,
   426  			expectedResults: []faultResult{
   427  				{
   428  					localizedMessage: "fake",
   429  					fault:            &types.SystemError{},
   430  				},
   431  			},
   432  		},
   433  		{
   434  			name:             "error is types.SystemError",
   435  			err:              &types.SystemError{},
   436  			callback:         &testHarness{},
   437  			expectedNumCalls: 1,
   438  			expectedResults: []faultResult{
   439  				{
   440  					fault: &types.SystemError{},
   441  				},
   442  			},
   443  		},
   444  		{
   445  			name: "error is task.Error with a localized message but no fault",
   446  			err: task.Error{
   447  				LocalizedMethodFault: &types.LocalizedMethodFault{
   448  					LocalizedMessage: "fake",
   449  				},
   450  			},
   451  			callback:         &testHarness{},
   452  			expectedNumCalls: 0,
   453  		},
   454  		{
   455  			name: "error is multiple, wrapped errors",
   456  			err: unwrappableErrSlice{
   457  				nil,
   458  				task.Error{
   459  					LocalizedMethodFault: &types.LocalizedMethodFault{
   460  						LocalizedMessage: "fake",
   461  						Fault:            &types.SystemError{},
   462  					},
   463  				},
   464  			},
   465  			callback:         &testHarness{},
   466  			expectedNumCalls: 1,
   467  			expectedResults: []faultResult{
   468  				{
   469  					localizedMessage: "fake",
   470  					fault:            &types.SystemError{},
   471  				},
   472  			},
   473  		},
   474  		{
   475  			name: "error has nested task.Error with localized message and fault",
   476  			err: fmt.Errorf(
   477  				"error 1: %w",
   478  				fmt.Errorf(
   479  					"error 2: %w",
   480  					task.Error{
   481  						LocalizedMethodFault: &types.LocalizedMethodFault{
   482  							LocalizedMessage: "fake",
   483  							Fault:            &types.SystemError{},
   484  						},
   485  					},
   486  				),
   487  			),
   488  			callback:         &testHarness{},
   489  			expectedNumCalls: 1,
   490  			expectedResults: []faultResult{
   491  				{
   492  					localizedMessage: "fake",
   493  					fault:            &types.SystemError{},
   494  				},
   495  			},
   496  		},
   497  		{
   498  			name: "error is task.Error with localized message and fault with localizable messages",
   499  			err: task.Error{
   500  				LocalizedMethodFault: &types.LocalizedMethodFault{
   501  					LocalizedMessage: "fake",
   502  					Fault: &types.SystemError{
   503  						RuntimeFault: types.RuntimeFault{
   504  							MethodFault: types.MethodFault{
   505  								FaultMessage: []types.LocalizableMessage{
   506  									{
   507  										Key:     "fake.id",
   508  										Message: "fake",
   509  									},
   510  								},
   511  							},
   512  						},
   513  					},
   514  				},
   515  			},
   516  			callback:         &testHarness{},
   517  			expectedNumCalls: 1,
   518  			expectedResults: []faultResult{
   519  				{
   520  					fault:            &types.SystemError{},
   521  					localizedMessage: "fake",
   522  					localizableMessages: []types.LocalizableMessage{
   523  						{
   524  							Key:     "fake.id",
   525  							Message: "fake",
   526  						},
   527  					},
   528  				},
   529  			},
   530  		},
   531  		{
   532  			name: "error is task.Error with nested faults",
   533  			err: task.Error{
   534  				LocalizedMethodFault: &types.LocalizedMethodFault{
   535  					LocalizedMessage: "fake1",
   536  					Fault: &types.SystemError{
   537  						RuntimeFault: types.RuntimeFault{
   538  							MethodFault: types.MethodFault{
   539  								FaultMessage: []types.LocalizableMessage{
   540  									{
   541  										Key:     "fake1.id",
   542  										Message: "fake1",
   543  									},
   544  								},
   545  								FaultCause: &types.LocalizedMethodFault{
   546  									LocalizedMessage: "fake2",
   547  									Fault: &types.NotSupported{
   548  										RuntimeFault: types.RuntimeFault{
   549  											MethodFault: types.MethodFault{
   550  												FaultMessage: []types.LocalizableMessage{
   551  													{
   552  														Key:     "fake2.id",
   553  														Message: "fake2",
   554  													},
   555  												},
   556  											},
   557  										},
   558  									},
   559  								},
   560  							},
   561  						},
   562  					},
   563  				},
   564  			},
   565  			callback:         &testHarness{},
   566  			expectedNumCalls: 2,
   567  			expectedResults: []faultResult{
   568  				{
   569  					fault:            &types.SystemError{},
   570  					localizedMessage: "fake1",
   571  					localizableMessages: []types.LocalizableMessage{
   572  						{
   573  							Key:     "fake1.id",
   574  							Message: "fake1",
   575  						},
   576  					},
   577  				},
   578  				{
   579  					fault:            &types.NotSupported{},
   580  					localizedMessage: "fake2",
   581  					localizableMessages: []types.LocalizableMessage{
   582  						{
   583  							Key:     "fake2.id",
   584  							Message: "fake2",
   585  						},
   586  					},
   587  				},
   588  			},
   589  		},
   590  		{
   591  			name: "error is task.Error with nested faults but returns after single fault",
   592  			err: task.Error{
   593  				LocalizedMethodFault: &types.LocalizedMethodFault{
   594  					LocalizedMessage: "fake1",
   595  					Fault: &types.SystemError{
   596  						RuntimeFault: types.RuntimeFault{
   597  							MethodFault: types.MethodFault{
   598  								FaultMessage: []types.LocalizableMessage{
   599  									{
   600  										Key:     "fake1.id",
   601  										Message: "fake1",
   602  									},
   603  								},
   604  								FaultCause: &types.LocalizedMethodFault{
   605  									LocalizedMessage: "fake2",
   606  									Fault: &types.NotSupported{
   607  										RuntimeFault: types.RuntimeFault{
   608  											MethodFault: types.MethodFault{
   609  												FaultMessage: []types.LocalizableMessage{
   610  													{
   611  														Key:     "fake2.id",
   612  														Message: "fake2",
   613  													},
   614  												},
   615  											},
   616  										},
   617  									},
   618  								},
   619  							},
   620  						},
   621  					},
   622  				},
   623  			},
   624  			callback:         &testHarness{numCallsToReturnTrue: 1},
   625  			expectedNumCalls: 1,
   626  			expectedResults: []faultResult{
   627  				{
   628  					fault:            &types.SystemError{},
   629  					localizedMessage: "fake1",
   630  					localizableMessages: []types.LocalizableMessage{
   631  						{
   632  							Key:     "fake1.id",
   633  							Message: "fake1",
   634  						},
   635  					},
   636  				},
   637  			},
   638  		},
   639  		{
   640  			name: "err is soap fault",
   641  			err: soap.WrapSoapFault(&soap.Fault{
   642  				Detail: struct {
   643  					Fault types.AnyType "xml:\",any,typeattr\""
   644  				}{
   645  					Fault: &types.SystemError{},
   646  				},
   647  			}),
   648  			callback:         &testHarness{numCallsToReturnTrue: 1},
   649  			expectedNumCalls: 1,
   650  			expectedResults: []faultResult{
   651  				{
   652  					fault: &types.SystemError{},
   653  				},
   654  			},
   655  		},
   656  		{
   657  			name:             "err is vim fault",
   658  			err:              soap.WrapVimFault(&types.SystemError{}),
   659  			callback:         &testHarness{numCallsToReturnTrue: 1},
   660  			expectedNumCalls: 1,
   661  			expectedResults: []faultResult{
   662  				{
   663  					fault: &types.SystemError{},
   664  				},
   665  			},
   666  		},
   667  	}
   668  
   669  	for i := range testCases {
   670  		tc := testCases[i]
   671  		t.Run(tc.name, func(t *testing.T) {
   672  			t.Parallel()
   673  
   674  			var callback fault.OnFaultFn
   675  
   676  			if !tc.callback.isNil {
   677  				callback = tc.callback.Callback
   678  			}
   679  
   680  			if tc.expectedPanic != nil {
   681  				assert.PanicsWithValue(
   682  					t,
   683  					tc.expectedPanic,
   684  					func() {
   685  						fault.In(tc.err, callback)
   686  					})
   687  			} else {
   688  				fault.In(tc.err, callback)
   689  			}
   690  
   691  			assert.Equal(t, tc.expectedNumCalls, tc.callback.numCalls)
   692  			assert.Equal(t, tc.expectedResults, tc.callback.results)
   693  		})
   694  	}
   695  }
   696  
   697  func TestIs(t *testing.T) {
   698  
   699  	testCases := []struct {
   700  		name          string
   701  		err           any
   702  		target        types.BaseMethodFault
   703  		expectedOkay  bool
   704  		expectedPanic any
   705  	}{
   706  		{
   707  			name:         "err is nil",
   708  			err:          nil,
   709  			target:       &types.SystemError{},
   710  			expectedOkay: false,
   711  		},
   712  		{
   713  			name:         "target is nil",
   714  			err:          &types.SystemError{},
   715  			target:       nil,
   716  			expectedOkay: false,
   717  		},
   718  		{
   719  			name:         "target and error are nil",
   720  			err:          nil,
   721  			target:       nil,
   722  			expectedOkay: true,
   723  		},
   724  		{
   725  			name:         "err is not supported",
   726  			err:          struct{}{},
   727  			expectedOkay: false,
   728  		},
   729  		{
   730  			name:         "err and target are same value type",
   731  			err:          nilBaseMethodFault{},
   732  			target:       nilBaseMethodFault{},
   733  			expectedOkay: true,
   734  		},
   735  		{
   736  			name:         "target implements IsFault",
   737  			err:          isFaulter(true),
   738  			target:       &types.SystemError{},
   739  			expectedOkay: true,
   740  		},
   741  		{
   742  			name:         "err is *LocalizedMethodFault with nil fault",
   743  			err:          &types.LocalizedMethodFault{},
   744  			target:       &types.SystemError{},
   745  			expectedOkay: false,
   746  		},
   747  		{
   748  			name: "err is *LocalizedMethodFault with SystemError fault",
   749  			err: &types.LocalizedMethodFault{
   750  				Fault: &types.SystemError{},
   751  			},
   752  			target:       &types.SystemError{},
   753  			expectedOkay: true,
   754  		},
   755  		{
   756  			name: "err is *LocalizedMethodFault with InvalidVmConfig fault",
   757  			err: &types.LocalizedMethodFault{
   758  				Fault: &types.SystemError{},
   759  			},
   760  			target:       &types.InvalidVmConfig{},
   761  			expectedOkay: false,
   762  		},
   763  		{
   764  			name: "err is task.Error with nested InvalidVmConfig fault",
   765  			err: task.Error{
   766  				LocalizedMethodFault: &types.LocalizedMethodFault{
   767  					Fault: &types.SystemError{
   768  						RuntimeFault: types.RuntimeFault{
   769  							MethodFault: types.MethodFault{
   770  								FaultCause: &types.LocalizedMethodFault{
   771  									Fault: &types.InvalidVmConfig{},
   772  								},
   773  							},
   774  						},
   775  					},
   776  				},
   777  			},
   778  			target:       &types.InvalidVmConfig{},
   779  			expectedOkay: true,
   780  		},
   781  		{
   782  			name: "err is *LocalizedMethodFault with nested InvalidVmConfig fault",
   783  			err: &types.LocalizedMethodFault{
   784  				Fault: &types.SystemError{
   785  					RuntimeFault: types.RuntimeFault{
   786  						MethodFault: types.MethodFault{
   787  							FaultCause: &types.LocalizedMethodFault{
   788  								Fault: &types.InvalidVmConfig{},
   789  							},
   790  						},
   791  					},
   792  				},
   793  			},
   794  			target:       &types.InvalidVmConfig{},
   795  			expectedOkay: true,
   796  		},
   797  		{
   798  			name: "err is *LocalizedMethodFault with nested nil fault",
   799  			err: &types.LocalizedMethodFault{
   800  				Fault: &types.SystemError{
   801  					RuntimeFault: types.RuntimeFault{
   802  						MethodFault: types.MethodFault{
   803  							FaultCause: &types.LocalizedMethodFault{
   804  								Fault: nilBaseMethodFault{},
   805  							},
   806  						},
   807  					},
   808  				},
   809  			},
   810  			target:       &types.InvalidVmConfig{},
   811  			expectedOkay: false,
   812  		},
   813  		{
   814  			name: "err is wrapped task.Error with nested InvalidVmConfig fault",
   815  			err: fmt.Errorf("my error: %w", task.Error{
   816  				LocalizedMethodFault: &types.LocalizedMethodFault{
   817  					Fault: &types.SystemError{
   818  						RuntimeFault: types.RuntimeFault{
   819  							MethodFault: types.MethodFault{
   820  								FaultCause: &types.LocalizedMethodFault{
   821  									Fault: &types.InvalidVmConfig{},
   822  								},
   823  							},
   824  						},
   825  					},
   826  				},
   827  			}),
   828  			target:       &types.InvalidVmConfig{},
   829  			expectedOkay: true,
   830  		},
   831  		{
   832  			name:         "err is wrapped nil error",
   833  			err:          nilErrWrapper{},
   834  			target:       &types.InvalidVmConfig{},
   835  			expectedOkay: false,
   836  		},
   837  		{
   838  			name: "err is wrapped error slice with expected value",
   839  			err: unwrappableErrSlice{
   840  				nil,
   841  				task.Error{
   842  					LocalizedMethodFault: &types.LocalizedMethodFault{
   843  						Fault: &types.SystemError{
   844  							RuntimeFault: types.RuntimeFault{
   845  								MethodFault: types.MethodFault{
   846  									FaultCause: &types.LocalizedMethodFault{
   847  										Fault: &types.InvalidVmConfig{},
   848  									},
   849  								},
   850  							},
   851  						},
   852  					},
   853  				},
   854  			},
   855  			target:       &types.InvalidVmConfig{},
   856  			expectedOkay: true,
   857  		},
   858  		{
   859  			name: "err is wrapped error slice sans expected value",
   860  			err: unwrappableErrSlice{
   861  				nil,
   862  				task.Error{
   863  					LocalizedMethodFault: &types.LocalizedMethodFault{
   864  						Fault: &types.SystemError{
   865  							RuntimeFault: types.RuntimeFault{
   866  								MethodFault: types.MethodFault{
   867  									FaultCause: &types.LocalizedMethodFault{
   868  										Fault: &types.SystemError{},
   869  									},
   870  								},
   871  							},
   872  						},
   873  					},
   874  				},
   875  			},
   876  			target:       &types.InvalidVmConfig{},
   877  			expectedOkay: false,
   878  		},
   879  		{
   880  			name: "err is soap fault",
   881  			err: soap.WrapSoapFault(&soap.Fault{
   882  				Detail: struct {
   883  					Fault types.AnyType "xml:\",any,typeattr\""
   884  				}{
   885  					Fault: &types.SystemError{
   886  						RuntimeFault: types.RuntimeFault{
   887  							MethodFault: types.MethodFault{
   888  								FaultCause: &types.LocalizedMethodFault{
   889  									Fault: &types.InvalidVmConfig{},
   890  								},
   891  							},
   892  						},
   893  					},
   894  				},
   895  			}),
   896  			target:       &types.InvalidVmConfig{},
   897  			expectedOkay: true,
   898  		},
   899  		{
   900  			name: "err is soap fault with nil vim fault",
   901  			err: soap.WrapSoapFault(&soap.Fault{
   902  				Detail: struct {
   903  					Fault types.AnyType "xml:\",any,typeattr\""
   904  				}{
   905  					Fault: nil,
   906  				},
   907  			}),
   908  			target:       &types.InvalidVmConfig{},
   909  			expectedOkay: false,
   910  		},
   911  		{
   912  			name: "err is vim fault",
   913  			err: soap.WrapVimFault(&types.SystemError{
   914  				RuntimeFault: types.RuntimeFault{
   915  					MethodFault: types.MethodFault{
   916  						FaultCause: &types.LocalizedMethodFault{
   917  							Fault: &types.InvalidVmConfig{},
   918  						},
   919  					},
   920  				},
   921  			}),
   922  			target:       &types.InvalidVmConfig{},
   923  			expectedOkay: true,
   924  		},
   925  		{
   926  			name: "err is vim fault with nil vim fault",
   927  			err: soap.WrapVimFault(&types.SystemError{
   928  				RuntimeFault: types.RuntimeFault{
   929  					MethodFault: types.MethodFault{
   930  						FaultCause: &types.LocalizedMethodFault{
   931  							Fault: nil,
   932  						},
   933  					},
   934  				},
   935  			}),
   936  			target:       &types.InvalidVmConfig{},
   937  			expectedOkay: false,
   938  		},
   939  	}
   940  
   941  	for i := range testCases {
   942  		tc := testCases[i]
   943  		t.Run(tc.name, func(t *testing.T) {
   944  			t.Parallel()
   945  
   946  			var okay bool
   947  
   948  			if tc.expectedPanic != nil {
   949  				assert.PanicsWithValue(
   950  					t,
   951  					tc.expectedPanic,
   952  					func() {
   953  						okay = fault.Is(tc.err, tc.target)
   954  					})
   955  			} else {
   956  				okay = fault.Is(tc.err, tc.target)
   957  			}
   958  
   959  			assert.Equal(t, tc.expectedOkay, okay)
   960  		})
   961  	}
   962  }