mirror of
https://github.com/k3s-io/kubernetes.git
synced 2026-07-23 19:16:25 +00:00
Validate PCI Address (domain is 16 bits, bus is 8 bits, device is 5 bits, function is 3 bits)
it also changes the data type from uint32 to uint16 for PCI Root and PCI Address Signed-off-by: Shingo Omura <everpeace@gmail.com>
This commit is contained in:
@@ -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
|
||||
),
|
||||
},
|
||||
|
||||
@@ -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()),
|
||||
|
||||
@@ -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 <domain>:<bus>:<device>.<function>
|
||||
// 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<domain>:<bus>".
|
||||
// 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.
|
||||
// <domain>:<bus>:<device>.<function>
|
||||
// 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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user