diff --git a/staging/src/k8s.io/dynamic-resource-allocation/deviceattribute/attribute_test.go b/staging/src/k8s.io/dynamic-resource-allocation/deviceattribute/attribute_test.go index f3be4b84bf8..3a5e5cff089 100644 --- a/staging/src/k8s.io/dynamic-resource-allocation/deviceattribute/attribute_test.go +++ b/staging/src/k8s.io/dynamic-resource-allocation/deviceattribute/attribute_test.go @@ -24,19 +24,12 @@ func TestAddStandardDeviceAttributes(t *testing.T) { }) testSysFs := utils.NewSysfsWithRoot(testSysfsDir) - // "0123:45:67.8" - testPCIAddress := utils.PCIAddress{ - Domain: 0x0123, - Bus: 0x45, - Device: 0x67, - Function: 0x8, - } - // "0123:45" - testPCIRoot := utils.PCIRoot{ - Domain: 0x1234, - Bus: 0x56, - } - // /sys/bus/pci/devices/0123:45:67.8 --> /sys/devices/pci1234:56/0123:45:67.8 + // "0123:45:1e.7" + testPCIAddress := utils.MustNewPCIAddress(0x0123, 0x45, 0x1e, 0x7) + // "1234:56" + testPCIRoot := utils.MustNewPCIRoot(0x1234, 0x56) + + // /sys/bus/pci/devices/0123:45:1e.7 --> /sys/devices/pci1234:56/0123:45:1e.7 testPCIDevicePath := testSysFs.Devices("pci" + testPCIRoot.String() + "/" + testPCIAddress.String()) dratesting.TouchFile(t, testPCIDevicePath) testPCIBusPath := testSysFs.Bus("pci/devices/" + testPCIAddress.String()) @@ -49,7 +42,7 @@ func TestAddStandardDeviceAttributes(t *testing.T) { "pci attrs": { opts: []StandardDeviceAttributesOption{ WithStandardPCIDeviceAttributesOpts( - WithPCIDeviceAddress(&testPCIAddress), + WithPCIDeviceAddress(testPCIAddress), withSysfs(testSysFs), // Use the mock sysfs ), }, diff --git a/staging/src/k8s.io/dynamic-resource-allocation/deviceattribute/pci_test.go b/staging/src/k8s.io/dynamic-resource-allocation/deviceattribute/pci_test.go index a2989343cf4..2bd234fb0f2 100644 --- a/staging/src/k8s.io/dynamic-resource-allocation/deviceattribute/pci_test.go +++ b/staging/src/k8s.io/dynamic-resource-allocation/deviceattribute/pci_test.go @@ -17,8 +17,8 @@ import ( func TestStandardPCIDeviceAttributes(t *testing.T) { pcieRootAttrName := resourceapi.QualifiedName(resourceapi.StandardDeviceAttributePCIeRoot) - address := &utils.PCIAddress{Domain: 0, Bus: 1, Device: 2, Function: 3} - root := &utils.PCIRoot{Domain: 0, Bus: 1} + address := utils.MustNewPCIAddress(0x0, 0x1, 0x2, 0x3) + root := utils.MustNewPCIRoot(0x0, 0x1) standardAttributes := map[resourceapi.QualifiedName]resourceapi.DeviceAttribute{ pcieRootAttrName: { StringValue: ptr.To("pci" + root.String()), diff --git a/staging/src/k8s.io/dynamic-resource-allocation/utils/pci.go b/staging/src/k8s.io/dynamic-resource-allocation/utils/pci.go index 91b5eab6d0c..c5f7713e7cf 100644 --- a/staging/src/k8s.io/dynamic-resource-allocation/utils/pci.go +++ b/staging/src/k8s.io/dynamic-resource-allocation/utils/pci.go @@ -2,6 +2,7 @@ package utils import ( "fmt" + "math" "os" "path/filepath" "regexp" @@ -9,17 +10,45 @@ import ( "strings" ) +const ( + // PCI address consists of: + // - domain: 16 bits, it can be represented in 4-digit hexadecimal number + // - bus: 8 bits, it can be represented in 2-digit hexadecimal number + // - device: 5 bits, it can be represented in 2-digit hexadecimal number + // - function: 3 bits, it can be represented in 1-digit hexadecimal number + // + // ref: "Chapter 12. PCI Drivers - PCI Addressing" + // Linux Device Drivers, 3rd Edition by Jonathan Corbet, Alessandro Rubini, Greg Kroah-Hartman + // https://www.oreilly.com/library/view/linux-device-drivers/0596005903/ch12.html + PCIDomainBits = uint16(16) + PCIDomainMax = uint16(math.MaxUint16) + PCIBusBits = uint16(8) + PCIBusMax = uint16((1 << PCIBusBits) - 1) + PCIDeviceBits = uint16(5) + PCIDeviceMax = uint16((1 << PCIDeviceBits) - 1) + PCIFunctionBits = uint16(3) + PCIFunctionMax = uint16((1 << PCIFunctionBits) - 1) +) + var ( // bdfRegexp matches PCI address in BDF notation. // The format is ::. // where: - // - domain: 4-digit hexadecimal number representing the PCI domain - // - bus: 2-digit hexadecimal number representing the PCI bus - // - device: 2-digit hexadecimal number representing the PCI device - // - function: 1-digit hexadecimal number representing the PCI function + // - domain: 4-digit hexadecimal number representing the PCI domain (16 bits) + // - bus: 2-digit hexadecimal number representing the PCI bus (8 bits) + // - device: 2-digit hexadecimal number representing the PCI device (5 bits) + // - function: 1-digit hexadecimal number representing the PCI function (3 bits) + // // Example: "0000:0e:1f.0" represents domain 0, bus 14, device 31, function 0. + // // ref: https://wiki.xenproject.org/wiki/Bus:Device.Function_(BDF)_Notation bdfRegexp = regexp.MustCompile(`^([0-9a-f]{4}):([0-9a-f]{2}):([0-9a-f]{2})\.([0-9a-f]{1})$`) + + // pciRootRegexp matches PCI root address in the format "pci:". + // The format is: + // - domain: 4-digit hexadecimal number representing the PCI domain (32 bits) + // - bus: 2-digit hexadecimal number representing the PCI bus (8 bits) + pciRootRegexp = regexp.MustCompile(`^([0-9a-f]{4}):([0-9a-f]{2})$`) ) // PCIRoot represents a PCI root address in the combination of Domain and Bus. @@ -28,34 +57,84 @@ var ( // // ref: https://docs.kernel.org/PCI/sysfs-pci.html type PCIRoot struct { - Domain uint32 - Bus uint32 + domain uint16 + bus uint16 } +func NewPCIRoot(domain, bus uint16) (*PCIRoot, error) { + // no validation for domain, as it uses full 16 bits + + if bus > PCIBusMax { + return nil, fmt.Errorf("invalid PCI bus number: %02x, must be in range 0-%d (%dbits)", bus, PCIBusMax, PCIBusBits) + } + return &PCIRoot{ + domain: domain, + bus: bus, + }, nil +} + +func MustNewPCIRoot(domain, bus uint16) *PCIRoot { + root, err := NewPCIRoot(domain, bus) + if err != nil { + panic(fmt.Sprintf("failed to create PCIRoot: %v", err)) + } + return root +} + +// String returns the string representation of the PCIRoot in the format "domain:bus". func (p *PCIRoot) String() string { - return fmt.Sprintf("%04x:%02x", p.Domain, p.Bus) + return fmt.Sprintf("%04x:%02x", p.domain, p.bus) } // The PCIAddress holds PCI address components in BDF notation. // ::. // where: -// - domain: 4-digit hexadecimal number representing the PCI domain -// - bus: 2-digit hexadecimal number representing the PCI bus -// - device: 2-digit hexadecimal number representing the PCI device -// - function: 1-digit hexadecimal number representing the PCI function +// - domain: 4-digit hexadecimal number representing the PCI domain (16 bits) +// - bus: 2-digit hexadecimal number representing the PCI bus (8 bits) +// - device: 2-digit hexadecimal number representing the PCI device (5 bits) +// - function: 1-digit hexadecimal number representing the PCI function (3 bits) // // Example: "0000:0e:1f.0" represents domain 0, bus 14, device 31, function 0. // -// ref: https://wiki.xenproject.org/wiki/Bus:Device.Function_(BDF)_Notation +// ref: +// - BDF Notation: https://wiki.xenproject.org/wiki/Bus:Device.Function_(BDF)_Notation +// - PCI Addressing: https://docs.kernel.org/PCI/sysfs-pci.html type PCIAddress struct { - Domain uint32 - Bus uint32 - Device uint32 - Function uint32 + domain uint16 + bus uint16 + device uint16 + function uint16 +} + +func NewPCIAddress(domain, bus, device, function uint16) (*PCIAddress, error) { + // no validation for domain, as it uses full 16 bits + if bus > PCIBusMax { + return nil, fmt.Errorf("invalid PCI bus number: %02x, must be in range 0-%d (%dbits)", bus, PCIBusMax, PCIBusBits) + } + if device > PCIDeviceMax { + return nil, fmt.Errorf("invalid PCI device number: %02x, must be in range 0-%d (%dbits)", device, PCIDeviceMax, PCIDeviceBits) + } + if function > PCIFunctionMax { + return nil, fmt.Errorf("invalid PCI function number: %01x, must be in range 0-%d (%dbits)", function, PCIFunctionMax, PCIFunctionBits) + } + return &PCIAddress{ + domain: domain, + bus: bus, + device: device, + function: function, + }, nil +} + +func MustNewPCIAddress(domain, bus, device, function uint16) *PCIAddress { + addr, err := NewPCIAddress(domain, bus, device, function) + if err != nil { + panic(fmt.Sprintf("failed to create PCIAddress: %v", err)) + } + return addr } func (p *PCIAddress) String() string { - return fmt.Sprintf("%04x:%02x:%02x.%01x", p.Domain, p.Bus, p.Device, p.Function) + return fmt.Sprintf("%04x:%02x:%02x.%01x", p.domain, p.bus, p.device, p.function) } // ResolvePCIRoot resolves the PCI root for the PCIAddress. @@ -75,7 +154,7 @@ func (p *PCIAddress) ResolvePCIRoot(sysfs Sysfs) (*PCIRoot, error) { } pciRootPart := strings.Split(strings.TrimPrefix(sysDevicesPath, sysfs.Devices("")+"/"), "/")[0] - pciRoot, err := parsePCIRoot( + pciRoot, err := ParsePCIRoot( pciRootPart[3:], // skip "pci" prefix ) if err != nil { @@ -120,38 +199,36 @@ func (p *PCIAddress) resolveSysDevicesPath(sysfs Sysfs) (string, error) { return targetAbs, nil } -// parsePCIRoot parses a PCIRoot from a string in the format "domain:bus". +// ParsePCIRoot parses a PCIRoot from a string in the format "domain:bus". // The format is: // - domain: 4-digit hexadecimal number representing the PCI domain // - bus: 2-digit hexadecimal number representing the PCI bus // Example: "0000:01" represents domain 0, bus 1. -func parsePCIRoot(str string) (*PCIRoot, error) { - parts := strings.Split(str, ":") - if len(parts) != 2 { +func ParsePCIRoot(str string) (*PCIRoot, error) { + match := pciRootRegexp.FindStringSubmatch(str) + + if len(match) == 0 { return nil, fmt.Errorf("invalid PCIRoot format: %s", str) } - parsePart := func(name, part string) (uint32, error) { - value, err := parseHexToUint32(part) + parsePart := func(name, part string) (uint16, error) { + value, err := parseHexTouint16(part) if err != nil { return 0, fmt.Errorf("invalid value %s in PCIRoot %s: %w", name, str, err) } - return uint32(value), nil + return uint16(value), nil } - var domain, bus uint32 + var domain, bus uint16 var err error - if domain, err = parsePart("domain", parts[0]); err != nil { + if domain, err = parsePart("domain", match[1]); err != nil { return nil, err } - if bus, err = parsePart("bus", parts[1]); err != nil { + if bus, err = parsePart("bus", match[2]); err != nil { return nil, err } - return &PCIRoot{ - Domain: domain, - Bus: bus, - }, nil + return NewPCIRoot(domain, bus) } // ParsePCIAddress parses a PCI address in BDF notation. @@ -173,15 +250,15 @@ func ParsePCIAddress(bdfString string) (*PCIAddress, error) { return nil, fmt.Errorf("invalid PCI address format: %s", bdfString) } - parsePart := func(name, part string) (uint32, error) { - value, err := parseHexToUint32(part) + parsePart := func(name, part string) (uint16, error) { + value, err := parseHexTouint16(part) if err != nil { return 0, fmt.Errorf("invalid value %s in PCI address %s: %w", name, bdfString, err) } - return uint32(value), nil + return uint16(value), nil } - var domain, bus, device, function uint32 + var domain, bus, device, function uint16 var err error if domain, err = parsePart("domain", match[1]); err != nil { return nil, err @@ -196,18 +273,13 @@ func ParsePCIAddress(bdfString string) (*PCIAddress, error) { return nil, err } - return &PCIAddress{ - Domain: uint32(domain), - Bus: uint32(bus), - Device: uint32(device), - Function: uint32(function), - }, nil + return NewPCIAddress(domain, bus, device, function) } -func parseHexToUint32(s string) (uint32, error) { +func parseHexTouint16(s string) (uint16, error) { value, err := strconv.ParseUint(s, 16, 32) if err != nil { return 0, err } - return uint32(value), nil + return uint16(value), nil } diff --git a/staging/src/k8s.io/dynamic-resource-allocation/utils/pci_test.go b/staging/src/k8s.io/dynamic-resource-allocation/utils/pci_test.go index e0a79bd0a61..758ab2ac0ab 100644 --- a/staging/src/k8s.io/dynamic-resource-allocation/utils/pci_test.go +++ b/staging/src/k8s.io/dynamic-resource-allocation/utils/pci_test.go @@ -3,20 +3,63 @@ package utils import ( "os" "path/filepath" + "reflect" "strings" "testing" dratesting "k8s.io/dynamic-resource-allocation/testing" ) +func TestNewPCIAddress(t *testing.T) { + tests := map[string]struct { + domain, bus, device, function uint16 + expectsErr bool + expectedErrMsg string + }{ + "valid zero address": {0, 0, 0, 0, false, ""}, + "valid simple address": {1, 2, 3, 4, false, ""}, + "valid complex address": {0x1234, 0x56, 0x1e, 0x5, false, ""}, + // no invalid domain case because domain uses full 16 bits + "invalid bus": {0, PCIBusMax + 1, 0, 0, true, "invalid PCI bus number"}, + "invalid device": {0, 0, PCIDeviceMax + 1, 0, true, "invalid PCI device number"}, + "invalid function": {0, 0, 0, PCIFunctionMax + 1, true, "invalid PCI function number"}, + } + for name, test := range tests { + t.Run(name, func(t *testing.T) { + address, err := NewPCIAddress(test.domain, test.bus, test.device, test.function) + if test.expectsErr { + if err == nil { + t.Errorf("Expected error but got none") + return + } + if !strings.Contains(err.Error(), test.expectedErrMsg) { + t.Errorf("Expected error message %q contains %q", err.Error(), test.expectedErrMsg) + return + } + return + } + if err != nil { + t.Errorf("Unexpected error: %v", err) + return + } + if address.domain != test.domain || address.bus != test.bus || address.device != test.device || address.function != test.function { + t.Errorf("Expected PCIAddress(%d, %d, %d, %d), got (%d, %d, %d, %d)", + test.domain, test.bus, test.device, test.function, + address.domain, address.bus, address.device, address.function) + return + } + }) + } +} + func TestPCIAddressString(t *testing.T) { tests := map[string]struct { - address PCIAddress + address *PCIAddress expected string }{ - "zero": {PCIAddress{Domain: 0, Bus: 0, Device: 0, Function: 0}, "0000:00:00.0"}, - "simple": {PCIAddress{Domain: 1, Bus: 2, Device: 3, Function: 4}, "0001:02:03.4"}, - "complex": {PCIAddress{Domain: 0x1234, Bus: 0x56, Device: 0x78, Function: 0x9}, "1234:56:78.9"}, + "zero": {MustNewPCIAddress(0, 0, 0, 0), "0000:00:00.0"}, + "simple": {MustNewPCIAddress(0x0001, 0x02, 0x03, 0x4), "0001:02:03.4"}, + "complex": {MustNewPCIAddress(0x1234, 0x56, 0x1e, 0x5), "1234:56:1e.5"}, } for name, test := range tests { t.Run(name, func(t *testing.T) { @@ -29,27 +72,107 @@ func TestPCIAddressString(t *testing.T) { func TestParsePCIAddress(t *testing.T) { tests := map[string]struct { - input string - expected *PCIAddress - expectsErr bool + input string + expected *PCIAddress + expectsErr bool + expectedErrMsg string }{ - "zero": {"0000:00:00.0", &PCIAddress{Domain: 0, Bus: 0, Device: 0, Function: 0}, false}, - "simple": {"0001:02:03.4", &PCIAddress{Domain: 1, Bus: 2, Device: 3, Function: 4}, false}, - "complex": {"1234:56:78.9", &PCIAddress{Domain: 0x1234, Bus: 0x56, Device: 0x78, Function: 0x9}, false}, - "invalid format": {"0000:00:00", nil, true}, - "too many parts": {"0000:00:00.0.1", nil, true}, - "invalid hex value": {"0000:00:00.0a", nil, true}, - "completely invalid": {"invalid", nil, true}, + "valid zero": {"0000:00:00.0", MustNewPCIAddress(0x0, 0x0, 0x0, 0x0), false, ""}, + "valid simple": {"0001:02:03.4", MustNewPCIAddress(0x1, 0x2, 0x3, 0x4), false, ""}, + "valid complex": {"1234:56:1e.7", MustNewPCIAddress(0x1234, 0x56, 0x1e, 0x7), false, ""}, + "invalid format": {"0000-00-00-0", nil, true, "invalid PCI address forma"}, + "too less parts": {"0000:00:00", nil, true, "invalid PCI address forma"}, + "too many parts": {"0000:00:00.0.1", nil, true, "invalid PCI address forma"}, + "invalid hex value": {"0000:00:00.0g", nil, true, "invalid PCI address format"}, + "completely invalid": {"invalid", nil, true, "invalid PCI address forma"}, } for name, test := range tests { t.Run(name, func(t *testing.T) { result, err := ParsePCIAddress(test.input) - if (err != nil) != test.expectsErr { - t.Errorf("ParsePCIAddress(%s) error = %v, wantErr %v", test.input, err, test.expectsErr) + if test.expectsErr { + if err == nil { + t.Errorf("Expected error but got none for input %s", test.input) + return + } + if test.expectedErrMsg != "" && !strings.Contains(err.Error(), test.expectedErrMsg) { + t.Errorf("Expected error message %q contains %q for input %s", err.Error(), test.expectedErrMsg, test.input) + return + } return } - if !test.expectsErr && result.String() != test.expected.String() { - t.Errorf("ParsePCIAddress(%s) = %s, want %s", test.input, result.String(), test.expected.String()) + if err != nil { + t.Errorf("Unexpected error for input %s: %v", test.input, err) + return + } + if !reflect.DeepEqual(result, test.expected) { + t.Errorf("ParsePCIAddress(%s) = %+v, want %+v", test.input, result, test.expected) + return + } + }) + } +} + +func TestParsePCIAddressRoundTrip(t *testing.T) { + tests := map[string]struct { + addressStr string + expected *PCIAddress + }{ + "zero": {"0000:00:00.0", MustNewPCIAddress(0x0, 0x0, 0x0, 0x0)}, + "simple": {"0001:02:03.4", MustNewPCIAddress(0x1, 0x2, 0x3, 0x4)}, + "complex": {"1234:56:1e.5", MustNewPCIAddress(0x1234, 0x56, 0x1e, 0x5)}, + } + for name, test := range tests { + t.Run(name, func(t *testing.T) { + address, err := ParsePCIAddress(test.addressStr) + if err != nil { + t.Errorf("ParsePCIAddress(%s) returned error: %v", test.addressStr, err) + return + } + if !reflect.DeepEqual(address, test.expected) { + t.Errorf("ParsePCIAddress(%s) = %+v, want %+v", test.addressStr, address, test.expected) + return + } + if address.String() != test.expected.String() { + t.Errorf("Expected %s, got %s", test.expected.String(), address.String()) + } + }) + } +} + +func TestNewPCIRoot(t *testing.T) { + tests := map[string]struct { + domain uint16 + bus uint16 + address *PCIRoot + expectsErr bool + expectedErrMsg string + }{ + "valid zero root": {0, 0, MustNewPCIRoot(0x0, 0x0), false, ""}, + "valid simple root": {1, 2, MustNewPCIRoot(0x1, 0x2), false, ""}, + "valid complex root": {0x1234, 0x56, MustNewPCIRoot(0x1234, 0x56), false, ""}, + // no invalid domain case because domain uses full 16 bits + "invalid bus": {0, PCIBusMax + 1, nil, true, "invalid PCI bus number"}, + } + for name, test := range tests { + t.Run(name, func(t *testing.T) { + root, err := NewPCIRoot(test.domain, test.bus) + if test.expectsErr { + if err == nil { + t.Errorf("Expected error but got none") + return + } + if !strings.Contains(err.Error(), test.expectedErrMsg) { + t.Errorf("Expected error message %q contains %q", err.Error(), test.expectedErrMsg) + return + } + return + } + if err != nil { + t.Errorf("Unexpected error: %v", err) + return + } + if root.String() != test.address.String() { + t.Errorf("Expected PCIRoot %s, got %s", test.address.String(), root.String()) return } }) @@ -58,12 +181,12 @@ func TestParsePCIAddress(t *testing.T) { func TestPCIRootString(t *testing.T) { tests := map[string]struct { - root PCIRoot + root *PCIRoot expected string }{ - "zero": {PCIRoot{Domain: 0, Bus: 0}, "0000:00"}, - "simple": {PCIRoot{Domain: 1, Bus: 2}, "0001:02"}, - "complex": {PCIRoot{Domain: 0x1234, Bus: 0x56}, "1234:56"}, + "zero": {MustNewPCIRoot(0x0, 0x0), "0000:00"}, + "simple": {MustNewPCIRoot(0x1, 0x2), "0001:02"}, + "complex": {MustNewPCIRoot(0x1234, 0x56), "1234:56"}, } for name, test := range tests { t.Run(name, func(t *testing.T) { @@ -74,17 +197,78 @@ func TestPCIRootString(t *testing.T) { } } +func TestParsePCIRoot(t *testing.T) { + tests := map[string]struct { + input string + expected *PCIRoot + expectsErr bool + expectedErrMsg string + }{ + "valid zero": {"0000:00", MustNewPCIRoot(0x0, 0x0), false, ""}, + "valid simple": {"0001:02", MustNewPCIRoot(0x1, 0x2), false, ""}, + "valid complex": {"1234:56", MustNewPCIRoot(0x1234, 0x56), false, ""}, + "invalid format": {"0000-00", nil, true, "invalid PCIRoot format"}, + "invalid too less parts": {"0000", nil, true, "invalid PCIRoot format"}, + "invalid too many parts": {"0000:00:00", nil, true, "invalid PCIRoot format"}, + "invalid hex value": {"0000:0g", nil, true, "invalid PCIRoot format"}, + "invalid completely invalid": {"invalid", nil, true, "invalid PCIRoot format"}, + } + for name, test := range tests { + t.Run(name, func(t *testing.T) { + result, err := ParsePCIRoot(test.input) + if test.expectsErr { + if err == nil { + t.Errorf("Expected error but got none") + return + } + if test.expectedErrMsg != "" && !strings.Contains(err.Error(), test.expectedErrMsg) { + t.Errorf("Expected error message %q contains %q", err.Error(), test.expectedErrMsg) + return + } + return + } + if err != nil { + t.Errorf("Unexpected error: %v", err) + return + } + if !reflect.DeepEqual(result, test.expected) { + t.Errorf("Expected PCIRoot %s, got %s", test.expected.String(), result.String()) + return + } + }) + } +} + +func TestParsePCIRootRoundTrip(t *testing.T) { + tests := map[string]struct { + rootStr string + expected *PCIRoot + }{ + "zero": {"0000:00", MustNewPCIRoot(0x0, 0x0)}, + "simple": {"0001:02", MustNewPCIRoot(0x1, 0x2)}, + "complex": {"1234:56", MustNewPCIRoot(0x1234, 0x56)}, + } + for name, test := range tests { + t.Run(name, func(t *testing.T) { + root, err := ParsePCIRoot(test.rootStr) + if err != nil { + t.Errorf("ParsePCIRoot(%s) returned error: %v", test.rootStr, err) + return + } + if !reflect.DeepEqual(root, test.expected) { + t.Errorf("ParsePCIRoot(%s) = %+v, want %+v", test.rootStr, root, test.expected) + return + } + if root.String() != test.expected.String() { + t.Errorf("Expected %s, got %s", test.expected.String(), root.String()) + } + }) + } +} + func TestPCIAddressResolvePCIRoot(t *testing.T) { - address := PCIAddress{ - Domain: 0x1234, - Bus: 0x56, - Device: 0x78, - Function: 0x9, - } - root := PCIRoot{ - Domain: 0x2345, - Bus: 0x67, - } + address := MustNewPCIAddress(0x1234, 0x56, 0x1e, 0x7) + root := MustNewPCIRoot(0x2345, 0x67) tests := map[string]struct { sysfs func(*testing.T, string) Sysfs