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