Typing with mypy
This commit is contained in:
parent
657a881112
commit
ba2557bca1
|
@ -32,6 +32,7 @@ coverage.xml
|
|||
*.cover
|
||||
.hypothesis/
|
||||
.pytest_cache/
|
||||
.mypy_cache/
|
||||
|
||||
profile_default/
|
||||
ipython_config.py
|
||||
|
|
|
@ -30,6 +30,11 @@ flake8:
|
|||
script:
|
||||
- flake8
|
||||
|
||||
mypy:
|
||||
stage: test
|
||||
script:
|
||||
- mypy .
|
||||
|
||||
doc8:
|
||||
stage: test
|
||||
script:
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
#!/usr/bin/env python3
|
||||
from pathlib import Path
|
||||
from typing import Optional, List
|
||||
from typing import Optional, Dict, Any
|
||||
from pylspci.command import CommandBuilder, IDResolveOption
|
||||
from pylspci.filters import SlotFilter, DeviceFilter
|
||||
import argparse
|
||||
|
@ -156,15 +156,15 @@ def get_parser() -> argparse.ArgumentParser:
|
|||
return parser
|
||||
|
||||
|
||||
def main():
|
||||
def main() -> None:
|
||||
parser: argparse.ArgumentParser = get_parser()
|
||||
args: dict = vars(parser.parse_args())
|
||||
args: Dict[str, Any] = vars(parser.parse_args())
|
||||
|
||||
# Specific parsing required
|
||||
use_parser: bool = args.pop('json', True)
|
||||
kernel_modules: bool = args.pop('kernel_modules', False)
|
||||
access_method: Optional[str] = args.pop('access_method', None)
|
||||
pcilib_params: List[str] = args.pop('pcilib_params', []) or []
|
||||
pcilib_params = args.pop('pcilib_params', []) or []
|
||||
|
||||
builder: CommandBuilder = CommandBuilder(**args)
|
||||
if kernel_modules:
|
||||
|
@ -195,7 +195,7 @@ def main():
|
|||
print(item)
|
||||
return
|
||||
|
||||
def _item_handler(item):
|
||||
def _item_handler(item: Any) -> Any:
|
||||
if hasattr(item, '_asdict'):
|
||||
return item._asdict()
|
||||
return item
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
from enum import Enum
|
||||
from typing import Optional, Union, Tuple, List, Mapping, Any, Iterator
|
||||
from typing import \
|
||||
Optional, Union, List, Mapping, MutableMapping, Any, Iterator
|
||||
from pathlib import Path
|
||||
from pylspci.device import Device
|
||||
from pylspci.fields import PCIAccessParameter
|
||||
from pylspci.filters import SlotFilter, DeviceFilter
|
||||
from pylspci.parsers.base import Parser
|
||||
import subprocess
|
||||
|
||||
OptionalPath = Optional[Union[str, Path]]
|
||||
|
@ -140,7 +142,7 @@ def lspci(
|
|||
)
|
||||
|
||||
|
||||
def list_access_methods():
|
||||
def list_access_methods() -> List[str]:
|
||||
"""
|
||||
Calls ``lspci(access_method='help')`` to list the PCI access methods
|
||||
the underlying ``pcilib`` provides and parses the human-readable list into
|
||||
|
@ -157,7 +159,7 @@ def list_access_methods():
|
|||
))
|
||||
|
||||
|
||||
def list_pcilib_params_raw():
|
||||
def list_pcilib_params_raw() -> List[str]:
|
||||
"""
|
||||
Calls ``lspci -Ohelp`` to list the PCI access parameters the underlying
|
||||
``pcilib`` provides.
|
||||
|
@ -176,7 +178,7 @@ def list_pcilib_params_raw():
|
|||
))
|
||||
|
||||
|
||||
def list_pcilib_params():
|
||||
def list_pcilib_params() -> List[PCIAccessParameter]:
|
||||
"""
|
||||
Calls ``lspci -Ohelp`` to list the PCI access parameters the underlying
|
||||
``pcilib`` provides and parse the human-readable list into
|
||||
|
@ -202,14 +204,14 @@ class CommandBuilder(object):
|
|||
_list_access_methods: bool = False
|
||||
_list_pcilib_params: bool = False
|
||||
_list_pcilib_params_raw: bool = False
|
||||
_params: Mapping[str, Any] = {}
|
||||
_parser = None
|
||||
_params: MutableMapping[str, Any] = {}
|
||||
_parser: Optional[Parser] = None
|
||||
|
||||
def __init__(self, **kwargs: Mapping[str, Any]):
|
||||
self._params = kwargs
|
||||
|
||||
def __iter__(self) -> Iterator[Union[str, Device, PCIAccessParameter]]:
|
||||
result = None
|
||||
result: Union[str, List[str], List[Device], List[PCIAccessParameter]]
|
||||
if self._list_access_methods:
|
||||
result = list_access_methods()
|
||||
elif self._list_pcilib_params:
|
||||
|
@ -317,7 +319,9 @@ class CommandBuilder(object):
|
|||
self._list_access_methods = False
|
||||
return self
|
||||
|
||||
def with_pcilib_params(self, *args, **kwargs) -> 'CommandBuilder':
|
||||
def with_pcilib_params(self,
|
||||
*args: Mapping[str, Any],
|
||||
**kwargs: Any) -> 'CommandBuilder':
|
||||
"""
|
||||
Override some pcilib parameters. When given a dict, will rewrite the
|
||||
parameters with the new dict. When given keyword arguments, will update
|
||||
|
@ -448,7 +452,7 @@ class CommandBuilder(object):
|
|||
return self
|
||||
|
||||
def slot_filter(self,
|
||||
*args: Tuple[str],
|
||||
*args: str,
|
||||
domain: Optional[int] = None,
|
||||
bus: Optional[int] = None,
|
||||
device: Optional[int] = None,
|
||||
|
@ -478,7 +482,7 @@ class CommandBuilder(object):
|
|||
return self
|
||||
|
||||
def device_filter(self,
|
||||
*args: Tuple[str],
|
||||
*args: str,
|
||||
cls: Optional[int] = None,
|
||||
vendor: Optional[int] = None,
|
||||
device: Optional[int] = None) -> 'CommandBuilder':
|
||||
|
@ -502,7 +506,7 @@ class CommandBuilder(object):
|
|||
DeviceFilter(cls=cls, vendor=vendor, device=device)
|
||||
return self
|
||||
|
||||
def with_parser(self, parser=None) -> 'CommandBuilder':
|
||||
def with_parser(self, parser: Optional[Parser] = None) -> 'CommandBuilder':
|
||||
"""
|
||||
Use a pylspci parser to get parsed Device instances instead of strings.
|
||||
|
||||
|
|
|
@ -1,18 +1,21 @@
|
|||
from abc import ABC
|
||||
from typing import Optional, Pattern, ClassVar
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import Optional, Pattern, ClassVar, Dict, Type, TypeVar, Any
|
||||
from pylspci.fields import hexstring
|
||||
import re
|
||||
|
||||
T = TypeVar('T', bound='Filter')
|
||||
|
||||
|
||||
class Filter(ABC):
|
||||
|
||||
_REGEX: ClassVar[Pattern]
|
||||
|
||||
@classmethod
|
||||
def parse(cls, value: str) -> 'Filter':
|
||||
def parse(cls: Type[T], value: str) -> T:
|
||||
if not value:
|
||||
return cls()
|
||||
match, data = cls._REGEX.match(value), {}
|
||||
match = cls._REGEX.match(value)
|
||||
data: Dict[str, str] = {}
|
||||
if match:
|
||||
data = {k: v for k, v in match.groupdict().items()
|
||||
if v is not None}
|
||||
|
@ -24,6 +27,10 @@ class Filter(ABC):
|
|||
if v != '' and v != '*'
|
||||
})
|
||||
|
||||
@abstractmethod
|
||||
def __init__(self, **kwargs: Any) -> None:
|
||||
"Create a filter."
|
||||
|
||||
|
||||
class SlotFilter(Filter):
|
||||
"""
|
||||
|
@ -92,9 +99,10 @@ class SlotFilter(Filter):
|
|||
))
|
||||
|
||||
def __eq__(self, other: object) -> bool:
|
||||
return isinstance(other, self.__class__) and \
|
||||
self.domain, self.bus, self.device, self.function == \
|
||||
other.domain, other.bus, other.device, other.function
|
||||
if not isinstance(other, self.__class__):
|
||||
return NotImplemented
|
||||
return (self.domain, self.bus, self.device, self.function) == \
|
||||
(other.domain, other.bus, other.device, other.function)
|
||||
|
||||
|
||||
class DeviceFilter(Filter):
|
||||
|
@ -153,6 +161,7 @@ class DeviceFilter(Filter):
|
|||
))
|
||||
|
||||
def __eq__(self, other: object) -> bool:
|
||||
return isinstance(other, self.__class__) and \
|
||||
self.vendor, self.device, self.cls == \
|
||||
other.vendor, other.device, other.cls
|
||||
if not isinstance(other, self.__class__):
|
||||
return NotImplemented
|
||||
return (self.vendor, self.device, self.cls) == \
|
||||
(other.vendor, other.device, other.cls)
|
||||
|
|
|
@ -1,12 +1,11 @@
|
|||
from abc import ABC, abstractmethod
|
||||
from typing import Union, Iterable, List, Mapping, Any
|
||||
from typing import Union, Mapping, Iterable, List, Dict, Any
|
||||
from pylspci.device import Device
|
||||
from pylspci.command import lspci
|
||||
|
||||
|
||||
class Parser(ABC):
|
||||
|
||||
default_lspci_args: Mapping[str, Any] = {}
|
||||
default_lspci_args: Dict[str, Any] = {}
|
||||
"""
|
||||
The default arguments that, when sent to :func:`lspci`, should provide the
|
||||
best output for this parser.
|
||||
|
@ -15,13 +14,18 @@ class Parser(ABC):
|
|||
"""
|
||||
|
||||
@abstractmethod
|
||||
def parse(self, data: Union[str, Iterable[str]]) -> List[Device]:
|
||||
def parse(
|
||||
self,
|
||||
data: Union[str, Iterable[str], Iterable[Iterable[str]]]
|
||||
) -> List[Device]:
|
||||
"""
|
||||
Parse a string or list of strings as a list of devices.
|
||||
|
||||
:param data: A string holding multiple devices
|
||||
or a list of strings, one for each device.
|
||||
:type data: str or Iterable[str]
|
||||
:param data: A string holding multiple devices,
|
||||
a list of strings, one for each device,
|
||||
or a list of lists of strings, one list for each device, with
|
||||
each list holding each part of the device output.
|
||||
:type data: str or Iterable[str] or Iterable[Iterable[str]]
|
||||
:returns: A list of parsed devices.
|
||||
:rtype: List[Device]
|
||||
"""
|
||||
|
@ -38,6 +42,7 @@ class Parser(ABC):
|
|||
:returns: A list of parsed devices.
|
||||
:rtype: List[Device]
|
||||
"""
|
||||
from pylspci.command import lspci
|
||||
lspci_kwargs = self.default_lspci_args.copy()
|
||||
lspci_kwargs.update(kwargs)
|
||||
return self.parse(lspci(**lspci_kwargs))
|
||||
|
|
|
@ -53,13 +53,19 @@ class SimpleParser(Parser):
|
|||
)
|
||||
return p
|
||||
|
||||
def parse(self, data: Union[str, Iterable[str]]) -> List[Device]:
|
||||
def parse(
|
||||
self,
|
||||
data: Union[str, Iterable[str], Iterable[Iterable[str]]],
|
||||
) -> List[Device]:
|
||||
"""
|
||||
Parse a multiline string or a list of single-line strings
|
||||
from lspci -mm into devices.
|
||||
|
||||
:param data: String or list of strings to parse from.
|
||||
:type data: str or Iterable[str]
|
||||
:param data: A string holding multiple devices,
|
||||
a list of strings, one for each device,
|
||||
or a list of lists of strings, one list for each device, with
|
||||
each list holding each part of the device output.
|
||||
:type data: str or Iterable[str] or Iterable[Iterable[str]]
|
||||
:return: A list of parsed devices.
|
||||
:rtype: List[Device]
|
||||
"""
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
from typing import Union, List, Iterable, NamedTuple, Callable, Any
|
||||
from typing import Union, List, Dict, Iterable, NamedTuple, Callable, Any
|
||||
from pylspci.parsers.base import Parser
|
||||
from pylspci.device import Device
|
||||
from pylspci.fields import hexstring, Slot, NameWithID
|
||||
|
@ -68,7 +68,7 @@ class VerboseParser(Parser):
|
|||
}
|
||||
|
||||
def _parse_device(self, device_data: Union[str, Iterable[str]]) -> Device:
|
||||
devdict = {}
|
||||
devdict: Dict[str, Any] = {}
|
||||
if isinstance(device_data, str):
|
||||
device_data = device_data.splitlines()
|
||||
|
||||
|
@ -85,21 +85,30 @@ class VerboseParser(Parser):
|
|||
|
||||
return Device(**devdict)
|
||||
|
||||
def parse(self, data: Union[str, Iterable[str]]) -> List[Device]:
|
||||
def parse(
|
||||
self,
|
||||
data: Union[str, Iterable[str], Iterable[Iterable[str]]],
|
||||
) -> List[Device]:
|
||||
"""
|
||||
Parse an lspci -vvvmm[nnk] output, either as a single string holding
|
||||
multiple devices separated by two newlines,
|
||||
or as a list of multiline strings holding one device each.
|
||||
|
||||
:param data: One string holding a full lspci output,
|
||||
or multiple strings holding one device each.
|
||||
:type data: str or Iterable[str]
|
||||
:param data: A string holding multiple devices,
|
||||
a list of strings, one for each device,
|
||||
or a list of lists of strings, one list for each device, with
|
||||
each list holding each part of the device output.
|
||||
:type data: str or Iterable[str] or Iterable[Iterable[str]]
|
||||
:return: A list of parsed devices.
|
||||
:rtype: List[Device]
|
||||
"""
|
||||
if isinstance(data, str):
|
||||
data = data.split('\n\n')
|
||||
return list(map(
|
||||
self._parse_device,
|
||||
filter(bool, map(str.strip, data)), # Ignore empty strings
|
||||
))
|
||||
result: List[Device] = []
|
||||
for line in data:
|
||||
if isinstance(line, str):
|
||||
line = str.strip(line)
|
||||
if not line: # Ignore empty strings and lists
|
||||
continue
|
||||
result.append(self._parse_device(line))
|
||||
return result
|
||||
|
|
|
@ -8,14 +8,14 @@ from pylspci.parsers import SimpleParser, VerboseParser
|
|||
class TestCommandBuilder(TestCase):
|
||||
|
||||
@patch('pylspci.command.lspci')
|
||||
def test_default(self, lspci_mock):
|
||||
def test_default(self, lspci_mock: MagicMock) -> None:
|
||||
lspci_mock.return_value = ['a', 'b']
|
||||
self.assertListEqual(list(CommandBuilder()), ['a', 'b'])
|
||||
self.assertEqual(lspci_mock.call_count, 1)
|
||||
self.assertEqual(lspci_mock.call_args, call())
|
||||
|
||||
@patch('pylspci.command.lspci')
|
||||
def test_iter_str(self, lspci_mock):
|
||||
def test_iter_str(self, lspci_mock: MagicMock) -> None:
|
||||
"""
|
||||
Test iterating the CommandBuilder when lspci returns a single string
|
||||
returns an iterator for a list made of a single string, not an iterator
|
||||
|
@ -28,7 +28,9 @@ class TestCommandBuilder(TestCase):
|
|||
|
||||
@patch('pylspci.command.Path.is_file')
|
||||
@patch('pylspci.command.lspci')
|
||||
def test_use_pciids(self, lspci_mock, isfile_mock):
|
||||
def test_use_pciids(self,
|
||||
lspci_mock: MagicMock,
|
||||
isfile_mock: MagicMock) -> None:
|
||||
lspci_mock.return_value = ['a', 'b']
|
||||
isfile_mock.return_value = True
|
||||
builder = CommandBuilder().use_pciids('somefile')
|
||||
|
@ -39,7 +41,9 @@ class TestCommandBuilder(TestCase):
|
|||
|
||||
@patch('pylspci.command.Path.is_file')
|
||||
@patch('pylspci.command.lspci')
|
||||
def test_use_pciids_check(self, lspci_mock, isfile_mock):
|
||||
def test_use_pciids_check(self,
|
||||
lspci_mock: MagicMock,
|
||||
isfile_mock: MagicMock) -> None:
|
||||
lspci_mock.return_value = ['a', 'b']
|
||||
isfile_mock.return_value = False
|
||||
with self.assertRaisesRegex(AssertionError, 'not found'):
|
||||
|
@ -48,7 +52,9 @@ class TestCommandBuilder(TestCase):
|
|||
|
||||
@patch('pylspci.command.Path.is_file')
|
||||
@patch('pylspci.command.lspci')
|
||||
def test_use_pciids_no_check(self, lspci_mock, isfile_mock):
|
||||
def test_use_pciids_no_check(self,
|
||||
lspci_mock: MagicMock,
|
||||
isfile_mock: MagicMock) -> None:
|
||||
lspci_mock.return_value = ['a', 'b']
|
||||
isfile_mock.return_value = False
|
||||
builder = CommandBuilder().use_pciids('somefile', check=False)
|
||||
|
@ -59,7 +65,9 @@ class TestCommandBuilder(TestCase):
|
|||
|
||||
@patch('pylspci.command.Path.is_file')
|
||||
@patch('pylspci.command.lspci')
|
||||
def test_use_pcimap(self, lspci_mock, isfile_mock):
|
||||
def test_use_pcimap(self,
|
||||
lspci_mock: MagicMock,
|
||||
isfile_mock: MagicMock) -> None:
|
||||
lspci_mock.return_value = ['a', 'b']
|
||||
isfile_mock.return_value = True
|
||||
builder = CommandBuilder().use_pcimap('somefile')
|
||||
|
@ -70,7 +78,9 @@ class TestCommandBuilder(TestCase):
|
|||
|
||||
@patch('pylspci.command.Path.is_file')
|
||||
@patch('pylspci.command.lspci')
|
||||
def test_use_pcimap_check(self, lspci_mock, isfile_mock):
|
||||
def test_use_pcimap_check(self,
|
||||
lspci_mock: MagicMock,
|
||||
isfile_mock: MagicMock) -> None:
|
||||
lspci_mock.return_value = ['a', 'b']
|
||||
isfile_mock.return_value = False
|
||||
with self.assertRaisesRegex(AssertionError, 'not found'):
|
||||
|
@ -79,7 +89,9 @@ class TestCommandBuilder(TestCase):
|
|||
|
||||
@patch('pylspci.command.Path.is_file')
|
||||
@patch('pylspci.command.lspci')
|
||||
def test_use_pcimap_no_check(self, lspci_mock, isfile_mock):
|
||||
def test_use_pcimap_no_check(self,
|
||||
lspci_mock: MagicMock,
|
||||
isfile_mock: MagicMock) -> None:
|
||||
lspci_mock.return_value = ['a', 'b']
|
||||
isfile_mock.return_value = False
|
||||
builder = CommandBuilder().use_pcimap('somefile', check=False)
|
||||
|
@ -89,7 +101,7 @@ class TestCommandBuilder(TestCase):
|
|||
self.assertFalse(isfile_mock.called)
|
||||
|
||||
@patch('pylspci.command.lspci')
|
||||
def test_use_access_method(self, lspci_mock):
|
||||
def test_use_access_method(self, lspci_mock: MagicMock) -> None:
|
||||
lspci_mock.return_value = ['a', 'b']
|
||||
builder = CommandBuilder() \
|
||||
.use_access_method('one') \
|
||||
|
@ -99,7 +111,7 @@ class TestCommandBuilder(TestCase):
|
|||
self.assertEqual(lspci_mock.call_args, call(access_method='two'))
|
||||
|
||||
@patch('pylspci.command.list_access_methods')
|
||||
def test_list_access_methods(self, list_mock):
|
||||
def test_list_access_methods(self, list_mock: MagicMock) -> None:
|
||||
list_mock.return_value = ['a', 'b']
|
||||
builder = CommandBuilder().list_pcilib_params().list_access_methods()
|
||||
self.assertListEqual(list(builder), ['a', 'b'])
|
||||
|
@ -107,7 +119,7 @@ class TestCommandBuilder(TestCase):
|
|||
self.assertEqual(list_mock.call_args, call())
|
||||
|
||||
@patch('pylspci.command.list_pcilib_params')
|
||||
def test_list_pcilib_params(self, list_mock):
|
||||
def test_list_pcilib_params(self, list_mock: MagicMock) -> None:
|
||||
list_mock.return_value = ['a', 'b']
|
||||
builder = CommandBuilder().list_access_methods().list_pcilib_params()
|
||||
self.assertListEqual(list(builder), ['a', 'b'])
|
||||
|
@ -115,7 +127,7 @@ class TestCommandBuilder(TestCase):
|
|||
self.assertEqual(list_mock.call_args, call())
|
||||
|
||||
@patch('pylspci.command.list_pcilib_params_raw')
|
||||
def test_list_pcilib_params_raw(self, list_mock):
|
||||
def test_list_pcilib_params_raw(self, list_mock: MagicMock) -> None:
|
||||
list_mock.return_value = ['a', 'b']
|
||||
builder = CommandBuilder() \
|
||||
.list_access_methods() \
|
||||
|
@ -125,7 +137,7 @@ class TestCommandBuilder(TestCase):
|
|||
self.assertEqual(list_mock.call_args, call())
|
||||
|
||||
@patch('pylspci.command.lspci')
|
||||
def test_with_pcilib_params_dict(self, lspci_mock):
|
||||
def test_with_pcilib_params_dict(self, lspci_mock: MagicMock) -> None:
|
||||
with self.assertRaisesRegex(AssertionError, 'dict or keyword'):
|
||||
CommandBuilder().with_pcilib_params({'a': 'b'}, c='d')
|
||||
with self.assertRaisesRegex(AssertionError, 'Only one positional'):
|
||||
|
@ -138,7 +150,7 @@ class TestCommandBuilder(TestCase):
|
|||
self.assertEqual(lspci_mock.call_args, call(pcilib_params={'a': 'b'}))
|
||||
|
||||
@patch('pylspci.command.lspci')
|
||||
def test_with_pcilib_params_kwargs(self, lspci_mock):
|
||||
def test_with_pcilib_params_kwargs(self, lspci_mock: MagicMock) -> None:
|
||||
lspci_mock.return_value = ['a', 'b']
|
||||
builder = CommandBuilder() \
|
||||
.with_pcilib_params(a='1', b='2') \
|
||||
|
@ -151,7 +163,9 @@ class TestCommandBuilder(TestCase):
|
|||
|
||||
@patch('pylspci.command.Path.is_file')
|
||||
@patch('pylspci.command.lspci')
|
||||
def test_from_file(self, lspci_mock, isfile_mock):
|
||||
def test_from_file(self,
|
||||
lspci_mock: MagicMock,
|
||||
isfile_mock: MagicMock) -> None:
|
||||
lspci_mock.return_value = ['a', 'b']
|
||||
isfile_mock.return_value = True
|
||||
builder = CommandBuilder().from_file('somefile')
|
||||
|
@ -162,7 +176,9 @@ class TestCommandBuilder(TestCase):
|
|||
|
||||
@patch('pylspci.command.Path.is_file')
|
||||
@patch('pylspci.command.lspci')
|
||||
def test_from_file_check(self, lspci_mock, isfile_mock):
|
||||
def test_from_file_check(self,
|
||||
lspci_mock: MagicMock,
|
||||
isfile_mock: MagicMock) -> None:
|
||||
lspci_mock.return_value = ['a', 'b']
|
||||
isfile_mock.return_value = False
|
||||
with self.assertRaisesRegex(AssertionError, 'not found'):
|
||||
|
@ -171,7 +187,9 @@ class TestCommandBuilder(TestCase):
|
|||
|
||||
@patch('pylspci.command.Path.is_file')
|
||||
@patch('pylspci.command.lspci')
|
||||
def test_from_file_no_check(self, lspci_mock, isfile_mock):
|
||||
def test_from_file_no_check(self,
|
||||
lspci_mock: MagicMock,
|
||||
isfile_mock: MagicMock) -> None:
|
||||
lspci_mock.return_value = ['a', 'b']
|
||||
isfile_mock.return_value = False
|
||||
builder = CommandBuilder().from_file('somefile', check=False)
|
||||
|
@ -181,7 +199,7 @@ class TestCommandBuilder(TestCase):
|
|||
self.assertFalse(isfile_mock.called)
|
||||
|
||||
@patch('pylspci.command.lspci')
|
||||
def test_verbose(self, lspci_mock):
|
||||
def test_verbose(self, lspci_mock: MagicMock) -> None:
|
||||
lspci_mock.return_value = ['a', 'b']
|
||||
builder = CommandBuilder().verbose()
|
||||
self.assertListEqual(list(builder), ['a', 'b'])
|
||||
|
@ -189,7 +207,7 @@ class TestCommandBuilder(TestCase):
|
|||
self.assertEqual(lspci_mock.call_args, call(verbose=True))
|
||||
|
||||
@patch('pylspci.command.lspci')
|
||||
def test_include_kernel_drivers(self, lspci_mock):
|
||||
def test_include_kernel_drivers(self, lspci_mock: MagicMock) -> None:
|
||||
lspci_mock.return_value = ['a', 'b']
|
||||
builder = CommandBuilder() \
|
||||
.include_kernel_drivers(False) \
|
||||
|
@ -202,7 +220,7 @@ class TestCommandBuilder(TestCase):
|
|||
))
|
||||
|
||||
@patch('pylspci.command.lspci')
|
||||
def test_include_bridge_paths(self, lspci_mock):
|
||||
def test_include_bridge_paths(self, lspci_mock: MagicMock) -> None:
|
||||
lspci_mock.return_value = ['a', 'b']
|
||||
builder = CommandBuilder() \
|
||||
.include_bridge_paths(False) \
|
||||
|
@ -214,7 +232,7 @@ class TestCommandBuilder(TestCase):
|
|||
))
|
||||
|
||||
@patch('pylspci.command.lspci')
|
||||
def test_hide_single_domain(self, lspci_mock):
|
||||
def test_hide_single_domain(self, lspci_mock: MagicMock) -> None:
|
||||
lspci_mock.return_value = ['a', 'b']
|
||||
builder = CommandBuilder().hide_single_domain()
|
||||
self.assertListEqual(list(builder), ['a', 'b'])
|
||||
|
@ -222,7 +240,7 @@ class TestCommandBuilder(TestCase):
|
|||
self.assertEqual(lspci_mock.call_args, call(hide_single_domain=True))
|
||||
|
||||
@patch('pylspci.command.lspci')
|
||||
def test_with_ids(self, lspci_mock):
|
||||
def test_with_ids(self, lspci_mock: MagicMock) -> None:
|
||||
lspci_mock.return_value = ['a', 'b']
|
||||
builder = CommandBuilder().with_ids(False)
|
||||
self.assertListEqual(list(builder), ['a', 'b'])
|
||||
|
@ -240,7 +258,7 @@ class TestCommandBuilder(TestCase):
|
|||
))
|
||||
|
||||
@patch('pylspci.command.lspci')
|
||||
def test_with_names(self, lspci_mock):
|
||||
def test_with_names(self, lspci_mock: MagicMock) -> None:
|
||||
lspci_mock.return_value = ['a', 'b']
|
||||
builder = CommandBuilder().with_names(False)
|
||||
self.assertListEqual(list(builder), ['a', 'b'])
|
||||
|
@ -259,11 +277,13 @@ class TestCommandBuilder(TestCase):
|
|||
|
||||
@patch('pylspci.command.SlotFilter')
|
||||
@patch('pylspci.command.lspci')
|
||||
def test_slot_filter_str(self, lspci_mock, filter_mock):
|
||||
def test_slot_filter_str(self,
|
||||
lspci_mock: MagicMock,
|
||||
filter_mock: MagicMock) -> None:
|
||||
with self.assertRaisesRegex(AssertionError, 'Only one positional'):
|
||||
CommandBuilder().slot_filter('something', 'something else')
|
||||
with self.assertRaisesRegex(AssertionError, 'Use either'):
|
||||
CommandBuilder().slot_filter('something', domain='a')
|
||||
CommandBuilder().slot_filter('something', domain=0xa)
|
||||
|
||||
filter_mock.parse.return_value = 'lefilter'
|
||||
lspci_mock.return_value = ['a', 'b']
|
||||
|
@ -276,33 +296,37 @@ class TestCommandBuilder(TestCase):
|
|||
|
||||
@patch('pylspci.command.SlotFilter')
|
||||
@patch('pylspci.command.lspci')
|
||||
def test_slot_filter_kwargs(self, lspci_mock, filter_mock):
|
||||
def test_slot_filter_kwargs(self,
|
||||
lspci_mock: MagicMock,
|
||||
filter_mock: MagicMock) -> None:
|
||||
filter_mock.return_value = 'lefilter'
|
||||
lspci_mock.return_value = ['a', 'b']
|
||||
builder = CommandBuilder().slot_filter(
|
||||
domain='a',
|
||||
bus='b',
|
||||
device='c',
|
||||
function='d',
|
||||
domain=0xa,
|
||||
bus=0xb,
|
||||
device=0xc,
|
||||
function=0xd,
|
||||
)
|
||||
self.assertListEqual(list(builder), ['a', 'b'])
|
||||
self.assertEqual(lspci_mock.call_count, 1)
|
||||
self.assertEqual(lspci_mock.call_args, call(slot_filter='lefilter'))
|
||||
self.assertEqual(filter_mock.call_count, 1)
|
||||
self.assertEqual(filter_mock.call_args, call(
|
||||
domain='a',
|
||||
bus='b',
|
||||
device='c',
|
||||
function='d',
|
||||
domain=0xa,
|
||||
bus=0xb,
|
||||
device=0xc,
|
||||
function=0xd,
|
||||
))
|
||||
|
||||
@patch('pylspci.command.DeviceFilter')
|
||||
@patch('pylspci.command.lspci')
|
||||
def test_device_filter_str(self, lspci_mock, filter_mock):
|
||||
def test_device_filter_str(self,
|
||||
lspci_mock: MagicMock,
|
||||
filter_mock: MagicMock) -> None:
|
||||
with self.assertRaisesRegex(AssertionError, 'Only one positional'):
|
||||
CommandBuilder().device_filter('something', 'something else')
|
||||
with self.assertRaisesRegex(AssertionError, 'Use either'):
|
||||
CommandBuilder().device_filter('something', vendor='b')
|
||||
CommandBuilder().device_filter('something', vendor=0xb)
|
||||
|
||||
filter_mock.parse.return_value = 'lefilter'
|
||||
lspci_mock.return_value = ['a', 'b']
|
||||
|
@ -315,25 +339,27 @@ class TestCommandBuilder(TestCase):
|
|||
|
||||
@patch('pylspci.command.DeviceFilter')
|
||||
@patch('pylspci.command.lspci')
|
||||
def test_device_filter_kwargs(self, lspci_mock, filter_mock):
|
||||
def test_device_filter_kwargs(self,
|
||||
lspci_mock: MagicMock,
|
||||
filter_mock: MagicMock) -> None:
|
||||
filter_mock.return_value = 'lefilter'
|
||||
lspci_mock.return_value = ['a', 'b']
|
||||
builder = CommandBuilder().device_filter(
|
||||
cls='a',
|
||||
vendor='b',
|
||||
device='c',
|
||||
cls=0xa,
|
||||
vendor=0xb,
|
||||
device=0xc,
|
||||
)
|
||||
self.assertListEqual(list(builder), ['a', 'b'])
|
||||
self.assertEqual(lspci_mock.call_count, 1)
|
||||
self.assertEqual(lspci_mock.call_args, call(device_filter='lefilter'))
|
||||
self.assertEqual(filter_mock.call_count, 1)
|
||||
self.assertEqual(filter_mock.call_args, call(
|
||||
cls='a',
|
||||
vendor='b',
|
||||
device='c',
|
||||
cls=0xa,
|
||||
vendor=0xb,
|
||||
device=0xc,
|
||||
))
|
||||
|
||||
def test_with_default_parser(self):
|
||||
def test_with_default_parser(self) -> None:
|
||||
builder = CommandBuilder()
|
||||
self.assertIsNone(builder._parser)
|
||||
|
||||
|
@ -344,7 +370,7 @@ class TestCommandBuilder(TestCase):
|
|||
self.assertIsInstance(builder._parser, VerboseParser)
|
||||
|
||||
@patch('pylspci.command.lspci')
|
||||
def test_with_parser(self, lspci_mock):
|
||||
def test_with_parser(self, lspci_mock: MagicMock) -> None:
|
||||
lspci_mock.return_value = ['a', 'b']
|
||||
parser_mock = MagicMock(spec=SimpleParser)
|
||||
parser_mock.parse.return_value = ('parsed_a', 'parsed_b')
|
||||
|
|
|
@ -18,7 +18,9 @@ class TestCommand(TestCase):
|
|||
|
||||
@patch('pylspci.command.Path.is_file')
|
||||
@patch('pylspci.command.subprocess.check_output')
|
||||
def test_pciids(self, cmd_mock: MagicMock, is_file_mock: MagicMock):
|
||||
def test_pciids(self,
|
||||
cmd_mock: MagicMock,
|
||||
is_file_mock: MagicMock) -> None:
|
||||
cmd_mock.return_value = 'something'
|
||||
is_file_mock.return_value = True
|
||||
self.assertEqual(lspci(pciids='/somewhere'), 'something')
|
||||
|
@ -30,7 +32,7 @@ class TestCommand(TestCase):
|
|||
self.assertEqual(is_file_mock.call_count, 1)
|
||||
|
||||
@patch('pylspci.command.Path.is_file')
|
||||
def test_pciids_missing(self, is_file_mock: MagicMock):
|
||||
def test_pciids_missing(self, is_file_mock: MagicMock) -> None:
|
||||
is_file_mock.return_value = False
|
||||
with self.assertRaises(AssertionError):
|
||||
lspci(pciids='/nowhere')
|
||||
|
@ -38,7 +40,9 @@ class TestCommand(TestCase):
|
|||
|
||||
@patch('pylspci.command.Path.is_file')
|
||||
@patch('pylspci.command.subprocess.check_output')
|
||||
def test_pcimap(self, cmd_mock: MagicMock, is_file_mock: MagicMock):
|
||||
def test_pcimap(self,
|
||||
cmd_mock: MagicMock,
|
||||
is_file_mock: MagicMock) -> None:
|
||||
cmd_mock.return_value = 'something'
|
||||
is_file_mock.return_value = True
|
||||
self.assertEqual(lspci(pcimap='/somewhere'), 'something')
|
||||
|
@ -50,7 +54,7 @@ class TestCommand(TestCase):
|
|||
self.assertEqual(is_file_mock.call_count, 1)
|
||||
|
||||
@patch('pylspci.command.Path.is_file')
|
||||
def test_pcimap_missing(self, is_file_mock: MagicMock):
|
||||
def test_pcimap_missing(self, is_file_mock: MagicMock) -> None:
|
||||
is_file_mock.return_value = False
|
||||
with self.assertRaises(AssertionError):
|
||||
lspci(pcimap='/nowhere')
|
||||
|
@ -77,7 +81,7 @@ class TestCommand(TestCase):
|
|||
|
||||
@patch('pylspci.command.Path.is_file')
|
||||
@patch('pylspci.command.subprocess.check_output')
|
||||
def test_file(self, cmd_mock: MagicMock, is_file_mock: MagicMock):
|
||||
def test_file(self, cmd_mock: MagicMock, is_file_mock: MagicMock) -> None:
|
||||
cmd_mock.return_value = 'something'
|
||||
is_file_mock.return_value = True
|
||||
self.assertEqual(lspci(file='/somewhere'), 'something')
|
||||
|
@ -89,7 +93,7 @@ class TestCommand(TestCase):
|
|||
self.assertEqual(is_file_mock.call_count, 1)
|
||||
|
||||
@patch('pylspci.command.Path.is_file')
|
||||
def test_file_missing(self, is_file_mock: MagicMock):
|
||||
def test_file_missing(self, is_file_mock: MagicMock) -> None:
|
||||
is_file_mock.return_value = False
|
||||
with self.assertRaises(AssertionError):
|
||||
lspci(file='/nowhere')
|
||||
|
@ -189,7 +193,9 @@ class TestCommand(TestCase):
|
|||
|
||||
@patch('pylspci.command.Path.is_file')
|
||||
@patch('pylspci.command.subprocess.check_output')
|
||||
def test_everything(self, cmd_mock: MagicMock, is_file_mock: MagicMock):
|
||||
def test_everything(self,
|
||||
cmd_mock: MagicMock,
|
||||
is_file_mock: MagicMock) -> None:
|
||||
cmd_mock.return_value = 'something'
|
||||
is_file_mock.return_value = True
|
||||
|
||||
|
|
|
@ -7,6 +7,8 @@ from pylspci.parsers import SimpleParser
|
|||
|
||||
class TestSimpleParser(TestCase):
|
||||
|
||||
parser: SimpleParser
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls) -> None:
|
||||
super().setUpClass()
|
||||
|
@ -24,8 +26,10 @@ class TestSimpleParser(TestCase):
|
|||
self.assertEqual(dev.vendor.name, 'Intel Corporation')
|
||||
self.assertEqual(dev.device.id, 0x244e)
|
||||
self.assertEqual(dev.device.name, '82801 PCI Bridge')
|
||||
assert dev.subsystem_vendor is not None
|
||||
self.assertEqual(dev.subsystem_vendor.id, 0x8086)
|
||||
self.assertEqual(dev.subsystem_vendor.name, 'Intel Corporation')
|
||||
assert dev.subsystem_device is not None
|
||||
self.assertEqual(dev.subsystem_device.id, 0x244e)
|
||||
self.assertEqual(dev.subsystem_device.name, '82801 PCI Bridge')
|
||||
self.assertEqual(dev.revision, 0xd5)
|
||||
|
@ -71,14 +75,16 @@ class TestSimpleParser(TestCase):
|
|||
self.assertEqual(dev.vendor.name, '')
|
||||
self.assertIsNone(dev.device.id)
|
||||
self.assertEqual(dev.device.name, '')
|
||||
assert dev.subsystem_vendor is not None
|
||||
self.assertIsNone(dev.subsystem_vendor.id)
|
||||
self.assertEqual(dev.subsystem_vendor.name, '')
|
||||
assert dev.subsystem_device is not None
|
||||
self.assertIsNone(dev.subsystem_device.id)
|
||||
self.assertEqual(dev.subsystem_device.name, '')
|
||||
self.assertIsNone(dev.revision)
|
||||
self.assertIsNone(dev.progif)
|
||||
|
||||
@patch('pylspci.parsers.base.lspci')
|
||||
@patch('pylspci.command.lspci')
|
||||
def test_command(self, cmd_mock: MagicMock) -> None:
|
||||
cmd_mock.return_value = \
|
||||
'00:1c.3 "PCI bridge [0604]" "Intel Corporation [8086]" ' \
|
||||
|
|
|
@ -16,11 +16,13 @@ ProgIf: 01
|
|||
Driver: pcieport
|
||||
Module: nouveau
|
||||
Module: nvidia
|
||||
""".strip()
|
||||
"""
|
||||
|
||||
|
||||
class TestVerboseParser(TestCase):
|
||||
|
||||
parser: VerboseParser
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls) -> None:
|
||||
super().setUpClass()
|
||||
|
@ -38,8 +40,10 @@ class TestVerboseParser(TestCase):
|
|||
self.assertEqual(dev.vendor.name, 'Intel Corporation')
|
||||
self.assertEqual(dev.device.id, 0x244e)
|
||||
self.assertEqual(dev.device.name, '82801 PCI Bridge')
|
||||
assert dev.subsystem_vendor is not None
|
||||
self.assertEqual(dev.subsystem_vendor.id, 0x8086)
|
||||
self.assertEqual(dev.subsystem_vendor.name, 'Intel Corporation')
|
||||
assert dev.subsystem_device is not None
|
||||
self.assertEqual(dev.subsystem_device.id, 0x244e)
|
||||
self.assertEqual(dev.subsystem_device.name, '82801 PCI Bridge')
|
||||
self.assertEqual(dev.revision, 0xd5)
|
||||
|
@ -57,7 +61,7 @@ class TestVerboseParser(TestCase):
|
|||
self.assertEqual(len(devices), 1)
|
||||
self._check_device(devices[0])
|
||||
|
||||
@patch('pylspci.parsers.base.lspci')
|
||||
@patch('pylspci.command.lspci')
|
||||
def test_command(self, cmd_mock: MagicMock) -> None:
|
||||
cmd_mock.return_value = '{0}\n\n{0}'.format(SAMPLE_DEVICE)
|
||||
|
||||
|
|
|
@ -60,11 +60,13 @@ class TestSlot(TestCase):
|
|||
self.assertEqual(s.device, 0x06)
|
||||
self.assertEqual(s.function, 0x6)
|
||||
self.assertIsInstance(s.parent, Slot)
|
||||
assert s.parent is not None # Required for type checks
|
||||
self.assertEqual(s.parent.domain, 0xcafe)
|
||||
self.assertEqual(s.parent.bus, 0xc0)
|
||||
self.assertEqual(s.parent.device, 0x0f)
|
||||
self.assertEqual(s.parent.function, 0x3)
|
||||
self.assertIsInstance(s.parent.parent, Slot)
|
||||
assert s.parent.parent is not None # Required for type checks
|
||||
self.assertEqual(s.parent.parent.domain, 0xabcd)
|
||||
self.assertEqual(s.parent.parent.bus, 0x13)
|
||||
self.assertEqual(s.parent.parent.device, 0x07)
|
||||
|
|
|
@ -4,7 +4,7 @@ from pylspci.filters import SlotFilter, DeviceFilter
|
|||
|
||||
class TestSlotFilter(TestCase):
|
||||
|
||||
def test_empty(self):
|
||||
def test_empty(self) -> None:
|
||||
f = SlotFilter()
|
||||
self.assertIsNone(f.domain)
|
||||
self.assertIsNone(f.bus)
|
||||
|
@ -15,7 +15,7 @@ class TestSlotFilter(TestCase):
|
|||
'SlotFilter(domain=None, bus=None, device=None, function=None)',
|
||||
)
|
||||
|
||||
def test_str(self):
|
||||
def test_str(self) -> None:
|
||||
self.assertEqual(str(SlotFilter()), '::.')
|
||||
self.assertEqual(str(SlotFilter(domain=0xcafe)), 'cafe::.')
|
||||
self.assertEqual(
|
||||
|
@ -23,7 +23,7 @@ class TestSlotFilter(TestCase):
|
|||
'c0ff:e:e.7',
|
||||
)
|
||||
|
||||
def test_parse(self):
|
||||
def test_parse(self) -> None:
|
||||
self.assertEqual(SlotFilter.parse(''), SlotFilter())
|
||||
self.assertEqual(SlotFilter.parse('::.'), SlotFilter())
|
||||
self.assertEqual(SlotFilter.parse('*:*:*.*'), SlotFilter())
|
||||
|
@ -40,10 +40,24 @@ class TestSlotFilter(TestCase):
|
|||
with self.assertRaises(ValueError):
|
||||
SlotFilter.parse('g')
|
||||
|
||||
def test_eq(self) -> None:
|
||||
self.assertEqual(
|
||||
SlotFilter(domain=0xc0ff, bus=0xe, device=0xe, function=7),
|
||||
SlotFilter(domain=0xc0ff, bus=0xe, device=0xe, function=7),
|
||||
)
|
||||
self.assertNotEqual(
|
||||
SlotFilter(domain=0xc0ff, bus=0xf, device=0xe, function=7),
|
||||
SlotFilter(domain=0xc0ff, bus=0xe, device=0xe, function=7),
|
||||
)
|
||||
self.assertNotEqual(
|
||||
SlotFilter(domain=0xc0ff, bus=0xf, device=0xe, function=7),
|
||||
'not a filter',
|
||||
)
|
||||
|
||||
|
||||
class TestDeviceFilter(TestCase):
|
||||
|
||||
def test_empty(self):
|
||||
def test_empty(self) -> None:
|
||||
f = DeviceFilter()
|
||||
self.assertIsNone(f.vendor)
|
||||
self.assertIsNone(f.device)
|
||||
|
@ -53,7 +67,7 @@ class TestDeviceFilter(TestCase):
|
|||
'DeviceFilter(cls=None, vendor=None, device=None)',
|
||||
)
|
||||
|
||||
def test_str(self):
|
||||
def test_str(self) -> None:
|
||||
self.assertEqual(str(DeviceFilter()), '::')
|
||||
self.assertEqual(str(DeviceFilter(vendor=0xcafe)), 'cafe::')
|
||||
self.assertEqual(
|
||||
|
@ -61,7 +75,7 @@ class TestDeviceFilter(TestCase):
|
|||
'c0ff:e:e',
|
||||
)
|
||||
|
||||
def test_parse(self):
|
||||
def test_parse(self) -> None:
|
||||
self.assertEqual(DeviceFilter.parse(''), DeviceFilter())
|
||||
self.assertEqual(DeviceFilter.parse('::'), DeviceFilter())
|
||||
self.assertEqual(DeviceFilter.parse('*:*:*'), DeviceFilter())
|
||||
|
@ -78,3 +92,17 @@ class TestDeviceFilter(TestCase):
|
|||
DeviceFilter.parse('4')
|
||||
with self.assertRaises(ValueError):
|
||||
DeviceFilter.parse('g')
|
||||
|
||||
def test_eq(self) -> None:
|
||||
self.assertEqual(
|
||||
DeviceFilter(vendor=0xc0ff, device=0xe, cls=0xe),
|
||||
DeviceFilter(vendor=0xc0ff, device=0xe, cls=0xe),
|
||||
)
|
||||
self.assertNotEqual(
|
||||
DeviceFilter(vendor=0xc0ff, device=0xf, cls=0xe),
|
||||
DeviceFilter(vendor=0xc0ff, device=0xe, cls=0xe),
|
||||
)
|
||||
self.assertNotEqual(
|
||||
DeviceFilter(vendor=0xc0ff, device=0xe, cls=0xe),
|
||||
'not a filter',
|
||||
)
|
||||
|
|
|
@ -3,3 +3,4 @@ doc8>=0.8.0
|
|||
Sphinx>=1.8.1
|
||||
coverage>=4.5
|
||||
codecov>=2.0
|
||||
mypy>=0.720
|
||||
|
|
|
@ -1,5 +1,12 @@
|
|||
[flake8]
|
||||
exclude = .git,__pycache__,docs,*.pyc,venv
|
||||
exclude=.git,__pycache__,docs,*.pyc,venv
|
||||
|
||||
[doc8]
|
||||
ignore-path=**/*.txt,*.txt,*.egg-info,docs/_build,venv,.git
|
||||
|
||||
[mypy]
|
||||
ignore_missing_imports=True
|
||||
disallow_incomplete_defs=True
|
||||
disallow_untyped_defs=True
|
||||
check_untyped_defs=True
|
||||
no_implicit_optional=True
|
||||
|
|
Loading…
Reference in New Issue