github.com/vmware/govmomi@v0.51.0/vim25/types/helpers_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 types 6 7 import ( 8 "fmt" 9 "reflect" 10 "slices" 11 "testing" 12 13 "github.com/stretchr/testify/assert" 14 15 "github.com/vmware/govmomi/vim25/xml" 16 ) 17 18 func TestVirtualMachineConfigInfoToConfigSpec(t *testing.T) { 19 testCases := []struct { 20 name string 21 conf VirtualMachineConfigInfo 22 spec VirtualMachineConfigSpec 23 fail bool 24 }{ 25 { 26 name: "default value", 27 conf: VirtualMachineConfigInfo{}, 28 spec: VirtualMachineConfigSpec{}, 29 }, 30 { 31 name: "matching names", 32 conf: VirtualMachineConfigInfo{ 33 Name: "Hello, world.", 34 }, 35 spec: VirtualMachineConfigSpec{ 36 Name: "Hello, world.", 37 }, 38 }, 39 { 40 name: "matching nics", 41 conf: VirtualMachineConfigInfo{ 42 Name: "Hello, world.", 43 Hardware: VirtualHardware{ 44 Device: []BaseVirtualDevice{ 45 &VirtualVmxnet3{ 46 VirtualVmxnet: VirtualVmxnet{ 47 VirtualEthernetCard: VirtualEthernetCard{ 48 VirtualDevice: VirtualDevice{ 49 Key: 3, 50 }, 51 MacAddress: "00:11:22:33:44:55:66:77", 52 }, 53 }, 54 }, 55 }, 56 }, 57 }, 58 spec: VirtualMachineConfigSpec{ 59 Name: "Hello, world.", 60 DeviceChange: []BaseVirtualDeviceConfigSpec{ 61 &VirtualDeviceConfigSpec{ 62 Operation: VirtualDeviceConfigSpecOperationAdd, 63 FileOperation: VirtualDeviceConfigSpecFileOperationCreate, 64 Device: &VirtualVmxnet3{ 65 VirtualVmxnet: VirtualVmxnet{ 66 VirtualEthernetCard: VirtualEthernetCard{ 67 VirtualDevice: VirtualDevice{ 68 Key: 3, 69 }, 70 MacAddress: "00:11:22:33:44:55:66:77", 71 }, 72 }, 73 }, 74 }, 75 }, 76 }, 77 }, 78 { 79 name: "nics with different mac addresses", 80 fail: true, 81 conf: VirtualMachineConfigInfo{ 82 Name: "Hello, world.", 83 Hardware: VirtualHardware{ 84 Device: []BaseVirtualDevice{ 85 &VirtualVmxnet3{ 86 VirtualVmxnet: VirtualVmxnet{ 87 VirtualEthernetCard: VirtualEthernetCard{ 88 VirtualDevice: VirtualDevice{ 89 Key: 3, 90 }, 91 MacAddress: "00:11:22:33:44:55:66:77", 92 }, 93 }, 94 }, 95 }, 96 }, 97 }, 98 spec: VirtualMachineConfigSpec{ 99 Name: "Hello, world.", 100 DeviceChange: []BaseVirtualDeviceConfigSpec{ 101 &VirtualDeviceConfigSpec{ 102 Operation: VirtualDeviceConfigSpecOperationAdd, 103 FileOperation: VirtualDeviceConfigSpecFileOperationCreate, 104 Device: &VirtualVmxnet3{ 105 VirtualVmxnet: VirtualVmxnet{ 106 VirtualEthernetCard: VirtualEthernetCard{ 107 VirtualDevice: VirtualDevice{ 108 Key: 3, 109 }, 110 MacAddress: "00:11:22:33:44:55:66:88", 111 }, 112 }, 113 }, 114 }, 115 }, 116 }, 117 }, 118 { 119 name: "vAppConfig", 120 conf: VirtualMachineConfigInfo{ 121 Name: "Hello, world.", 122 VAppConfig: &VmConfigInfo{ 123 InstallBootRequired: false, 124 IpAssignment: VAppIPAssignmentInfo{}, 125 Product: []VAppProductInfo{ 126 { 127 Key: 1, 128 Name: "P1", 129 }, 130 }, 131 }, 132 }, 133 spec: VirtualMachineConfigSpec{ 134 Name: "Hello, world.", 135 VAppConfig: &VmConfigSpec{ 136 InstallBootRequired: NewBool(false), 137 IpAssignment: &VAppIPAssignmentInfo{}, 138 Product: []VAppProductSpec{ 139 { 140 ArrayUpdateSpec: ArrayUpdateSpec{ 141 Operation: ArrayUpdateOperationAdd, 142 }, 143 Info: &VAppProductInfo{ 144 Key: 1, 145 Name: "P1", 146 }, 147 }, 148 }, 149 }, 150 }, 151 }, 152 { 153 name: "really big config", 154 conf: VirtualMachineConfigInfo{ 155 Name: "vm-001", 156 GuestId: "otherGuest", 157 Files: VirtualMachineFileInfo{VmPathName: "[datastore1]"}, 158 Hardware: VirtualHardware{ 159 NumCPU: 1, 160 MemoryMB: 128, 161 Device: []BaseVirtualDevice{ 162 &VirtualLsiLogicController{ 163 VirtualSCSIController: VirtualSCSIController{ 164 SharedBus: VirtualSCSISharingNoSharing, 165 VirtualController: VirtualController{ 166 BusNumber: 0, 167 VirtualDevice: VirtualDevice{ 168 Key: 1000, 169 }, 170 }, 171 }, 172 }, 173 &VirtualDisk{ 174 VirtualDevice: VirtualDevice{ 175 Key: 0, 176 ControllerKey: 1000, 177 UnitNumber: new(int32), // zero default value 178 Backing: &VirtualDiskFlatVer2BackingInfo{ 179 DiskMode: string(VirtualDiskModePersistent), 180 ThinProvisioned: NewBool(true), 181 VirtualDeviceFileBackingInfo: VirtualDeviceFileBackingInfo{ 182 FileName: "[datastore1]", 183 }, 184 }, 185 }, 186 CapacityInKB: 4000000, 187 }, 188 &VirtualE1000{ 189 VirtualEthernetCard: VirtualEthernetCard{ 190 VirtualDevice: VirtualDevice{ 191 Key: 0, 192 DeviceInfo: &Description{ 193 Label: "Network Adapter 1", 194 Summary: "VM Network", 195 }, 196 Backing: &VirtualEthernetCardNetworkBackingInfo{ 197 VirtualDeviceDeviceBackingInfo: VirtualDeviceDeviceBackingInfo{ 198 DeviceName: "VM Network", 199 }, 200 }, 201 }, 202 AddressType: string(VirtualEthernetCardMacTypeGenerated), 203 }, 204 }, 205 }, 206 }, 207 ExtraConfig: []BaseOptionValue{ 208 &OptionValue{Key: "bios.bootOrder", Value: "ethernet0"}, 209 }, 210 }, 211 spec: VirtualMachineConfigSpec{ 212 Name: "vm-001", 213 GuestId: "otherGuest", 214 Files: &VirtualMachineFileInfo{VmPathName: "[datastore1]"}, 215 NumCPUs: 1, 216 MemoryMB: 128, 217 DeviceChange: []BaseVirtualDeviceConfigSpec{ 218 &VirtualDeviceConfigSpec{ 219 Operation: VirtualDeviceConfigSpecOperationAdd, 220 FileOperation: VirtualDeviceConfigSpecFileOperationCreate, 221 Device: &VirtualLsiLogicController{VirtualSCSIController{ 222 SharedBus: VirtualSCSISharingNoSharing, 223 VirtualController: VirtualController{ 224 BusNumber: 0, 225 VirtualDevice: VirtualDevice{ 226 Key: 1000, 227 }, 228 }, 229 }}, 230 }, 231 &VirtualDeviceConfigSpec{ 232 Operation: VirtualDeviceConfigSpecOperationAdd, 233 FileOperation: VirtualDeviceConfigSpecFileOperationCreate, 234 Device: &VirtualDisk{ 235 VirtualDevice: VirtualDevice{ 236 Key: 0, 237 ControllerKey: 1000, 238 UnitNumber: new(int32), // zero default value 239 Backing: &VirtualDiskFlatVer2BackingInfo{ 240 DiskMode: string(VirtualDiskModePersistent), 241 ThinProvisioned: NewBool(true), 242 VirtualDeviceFileBackingInfo: VirtualDeviceFileBackingInfo{ 243 FileName: "[datastore1]", 244 }, 245 }, 246 }, 247 CapacityInKB: 4000000, 248 }, 249 }, 250 &VirtualDeviceConfigSpec{ 251 Operation: VirtualDeviceConfigSpecOperationAdd, 252 FileOperation: VirtualDeviceConfigSpecFileOperationCreate, 253 Device: &VirtualE1000{VirtualEthernetCard{ 254 VirtualDevice: VirtualDevice{ 255 Key: 0, 256 DeviceInfo: &Description{ 257 Label: "Network Adapter 1", 258 Summary: "VM Network", 259 }, 260 Backing: &VirtualEthernetCardNetworkBackingInfo{ 261 VirtualDeviceDeviceBackingInfo: VirtualDeviceDeviceBackingInfo{ 262 DeviceName: "VM Network", 263 }, 264 }, 265 }, 266 AddressType: string(VirtualEthernetCardMacTypeGenerated), 267 }}, 268 }, 269 }, 270 ExtraConfig: []BaseOptionValue{ 271 &OptionValue{Key: "bios.bootOrder", Value: "ethernet0"}, 272 }, 273 }, 274 }, 275 } 276 277 for i := range testCases { 278 tc := testCases[i] 279 t.Run(tc.name, func(t *testing.T) { 280 e, a := tc.spec, tc.conf.ToConfigSpec() 281 ed, err := xml.MarshalIndent(e, "", " ") 282 if err != nil { 283 t.Fatalf("failed to marshal expected ConfigSpec: %v", err) 284 } 285 ad, err := xml.MarshalIndent(a, "", " ") 286 if err != nil { 287 t.Fatalf("failed to marshal actual ConfigSpec: %v", err) 288 } 289 eds, ads := string(ed), string(ad) 290 if eds != ads && !tc.fail { 291 t.Errorf("unexpected error: \n\n"+ 292 "exp=%+v\n\nact=%+v\n\n"+ 293 "exp.s=%s\n\nact.s=%s\n\n", e, a, eds, ads) 294 } else if eds == ads && tc.fail { 295 t.Errorf("expected error did not occur: \n\n"+ 296 "exp=%+v\n\nact=%+v\n\n"+ 297 "exp.s=%s\n\nact.s=%s\n\n", e, a, eds, ads) 298 } 299 }) 300 } 301 } 302 303 type toStringTestCase struct { 304 name string 305 in any 306 expected string 307 } 308 309 func newToStringTestCases[T any](in T, expected string) []toStringTestCase { 310 return newToStringTestCasesWithTestCaseName( 311 in, expected, reflect.TypeOf(in).Name()) 312 } 313 314 func newToStringTestCasesWithTestCaseName[T any]( 315 in T, expected, testCaseName string) []toStringTestCase { 316 317 return []toStringTestCase{ 318 { 319 name: testCaseName, 320 in: in, 321 expected: expected, 322 }, 323 { 324 name: "*" + testCaseName, 325 in: &[]T{in}[0], 326 expected: expected, 327 }, 328 { 329 name: "(any)(" + testCaseName + ")", 330 in: (any)(in), 331 expected: expected, 332 }, 333 { 334 name: "(any)(*" + testCaseName + ")", 335 in: (any)(&[]T{in}[0]), 336 expected: expected, 337 }, 338 { 339 name: "(any)((*" + testCaseName + ")(nil))", 340 in: (any)((*T)(nil)), 341 expected: "null", 342 }, 343 } 344 } 345 346 type toStringTypeWithErr struct { 347 errOnCall []int 348 callCount *int 349 doPanic bool 350 } 351 352 func (t toStringTypeWithErr) String() string { 353 return "{}" 354 } 355 356 func (t toStringTypeWithErr) MarshalJSON() ([]byte, error) { 357 defer func() { 358 *t.callCount++ 359 }() 360 if !slices.Contains(t.errOnCall, *t.callCount) { 361 return []byte{'{', '}'}, nil 362 } 363 if t.doPanic { 364 panic(fmt.Errorf("marshal json panic'd")) 365 } 366 return nil, fmt.Errorf("marshal json failed") 367 } 368 369 func TestToString(t *testing.T) { 370 const ( 371 helloWorld = "Hello, world." 372 ) 373 374 testCases := []toStringTestCase{ 375 { 376 name: "nil", 377 in: nil, 378 expected: "null", 379 }, 380 } 381 382 testCases = append(testCases, newToStringTestCases( 383 "Hello, world.", "Hello, world.")...) 384 385 testCases = append(testCases, newToStringTestCasesWithTestCaseName( 386 byte(1), "1", "byte")...) 387 testCases = append(testCases, newToStringTestCasesWithTestCaseName( 388 'a', "97", "rune")...) 389 390 testCases = append(testCases, newToStringTestCases( 391 true, "true")...) 392 393 testCases = append(testCases, newToStringTestCases( 394 complex(float32(1), float32(4)), "(1+4i)")...) 395 testCases = append(testCases, newToStringTestCases( 396 complex(float64(1), float64(4)), "(1+4i)")...) 397 398 testCases = append(testCases, newToStringTestCases( 399 float32(1.1), "1.1")...) 400 testCases = append(testCases, newToStringTestCases( 401 float64(1.1), "1.1")...) 402 403 testCases = append(testCases, newToStringTestCases( 404 int(1), "1")...) 405 testCases = append(testCases, newToStringTestCases( 406 int8(1), "1")...) 407 testCases = append(testCases, newToStringTestCases( 408 int16(1), "1")...) 409 testCases = append(testCases, newToStringTestCases( 410 int32(1), "1")...) 411 testCases = append(testCases, newToStringTestCases( 412 int64(1), "1")...) 413 414 testCases = append(testCases, newToStringTestCases( 415 uint(1), "1")...) 416 testCases = append(testCases, newToStringTestCases( 417 uint8(1), "1")...) 418 testCases = append(testCases, newToStringTestCases( 419 uint16(1), "1")...) 420 testCases = append(testCases, newToStringTestCases( 421 uint32(1), "1")...) 422 testCases = append(testCases, newToStringTestCases( 423 uint64(1), "1")...) 424 425 testCases = append(testCases, newToStringTestCases( 426 VirtualMachineConfigSpec{}, 427 `{"_typeName":"VirtualMachineConfigSpec"}`)...) 428 testCases = append(testCases, newToStringTestCasesWithTestCaseName( 429 VirtualMachineConfigSpec{ 430 VAppConfig: (*VmConfigSpec)(nil), 431 }, 432 `{"_typeName":"VirtualMachineConfigSpec","vAppConfig":null}`, 433 "VirtualMachineConfigSpec w nil iface")...) 434 435 testCases = append(testCases, toStringTestCase{ 436 name: "MarshalJSON returns error on special encode", 437 in: toStringTypeWithErr{callCount: new(int), errOnCall: []int{0}}, 438 expected: "{}", 439 }) 440 testCases = append(testCases, toStringTestCase{ 441 name: "MarshalJSON returns error on special and stdlib encode", 442 in: toStringTypeWithErr{callCount: new(int), errOnCall: []int{0, 1}}, 443 expected: "{}", 444 }) 445 testCases = append(testCases, toStringTestCase{ 446 name: "MarshalJSON panics on special encode", 447 in: toStringTypeWithErr{callCount: new(int), doPanic: true, errOnCall: []int{0}}, 448 expected: "{}", 449 }) 450 testCases = append(testCases, toStringTestCase{ 451 name: "MarshalJSON panics on special and stdlib encode", 452 in: toStringTypeWithErr{callCount: new(int), doPanic: true, errOnCall: []int{0, 1}}, 453 expected: "{}", 454 }) 455 456 for i := range testCases { 457 tc := testCases[i] 458 t.Run(tc.name, func(t *testing.T) { 459 t.Parallel() 460 assert.Equal(t, tc.expected, ToString(tc.in)) 461 }) 462 } 463 }