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