Skip to content

Commit

Permalink
Test most of linux.I2C.
Browse files Browse the repository at this point in the history
WIP
  • Loading branch information
Crozzers committed Oct 15, 2023
1 parent 677ea14 commit 20fabd6
Show file tree
Hide file tree
Showing 3 changed files with 148 additions and 12 deletions.
25 changes: 15 additions & 10 deletions pytests/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ def freeze_display_info(
method: Type[BrightnessMethod],
patch_get_display_info
) -> List[dict]:
'''
Calls `get_display_info`, stores the result, and mocks it to return the same
result every time it's called
'''
displays = method.get_display_info()
mocker.patch.object(method, 'get_display_info', Mock(return_value=displays), spec=True)
return displays
Expand All @@ -30,12 +34,12 @@ def freeze_display_info(
@pytest.fixture
def patch_get_brightness(self, mocker: MockerFixture, patch_get_display_info):
'''Applies patches to get `get_brightness` working'''
...
raise NotImplementedError()

@pytest.fixture
def patch_set_brightness(self, mocker: MockerFixture, patch_get_display_info):
'''Applies patches to get `set_brightness` working'''
...
raise NotImplementedError()

@pytest.fixture
def method(self) -> Type[BrightnessMethod]:
Expand Down Expand Up @@ -97,6 +101,7 @@ def brightness(self, method: Type[BrightnessMethod]) -> List[int]:
return method.get_brightness()

class TestDisplayKwarg(ABC):
# TODO: most of these tests are generic enough they could be implemented in a parent class
def test_with(self):
'''Test what happens when display kwarg is given. Only one display should be polled'''
raise NotImplementedError()
Expand All @@ -105,20 +110,20 @@ def test_without(self):
'''Test what happens when no display kwarg is given. All displays should be polled'''
raise NotImplementedError()

def test_only_returns_brightness_of_requested_display(self, method: Type[BrightnessMethod]):
for i in range(len(method.get_display_info())):
brightness = method.get_brightness(display=i)
assert isinstance(brightness, list)
assert len(brightness) == 1
assert isinstance(brightness[0], int)
assert 0 <= brightness[0] <= 100

def test_returns_list_of_integers(self, method: Type[BrightnessMethod], brightness):
assert isinstance(brightness, list)
assert all(isinstance(i, int) for i in brightness)
assert all(0 <= i <= 100 for i in brightness)
assert len(brightness) == len(method.get_display_info())

def test_only_returns_brightness_of_requested_display(self, method: Type[BrightnessMethod]):
for i in range(len(method.get_display_info())):
brightness = method.get_brightness(display=i)
assert isinstance(brightness, list)
assert len(brightness) == 1
assert isinstance(brightness[0], int)
assert 0 <= brightness[0] <= 100

class TestSetBrightness(ABC):
@pytest.fixture(autouse=True)
def patch(self, patch_set_brightness):
Expand Down
69 changes: 69 additions & 0 deletions pytests/mocks/linux_mock.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import re
from typing import Dict, Optional, Tuple
from screen_brightness_control.linux import I2C

def fake_edid(mfg_id: str, name: str, serial: str) -> str:
def descriptor(string: str) -> str:
return string.encode('utf-8').hex() + ('20' * (13 - len(string)))

mfg_ords = [ord(i) - 64 for i in mfg_id]
mfg = mfg_ords[0] << 10 | mfg_ords[1] << 5 | mfg_ords[2]

return ''.join((
'00ffffffffffff00', # header
f'{mfg:04x}', # 'DEL' mfg id
'00' * 44, # product id -> edid timings
'00' * 18, # empty descriptor block
f'000000fc00{descriptor(name)}', # name descriptor
f'000000ff00{descriptor(serial)}', # serial descriptor
'00' * 18, # empty descriptor
'00' # extension flag
'00' # checksum - TODO: make this actually work
))


class MockI2C:
class MockI2CDevice:
_fake_devices = (
('DEL', 'Dell ABC123', 'serial123'),
('BNQ', 'BenQ DEF456', 'serial456')
)

def __init__(self, path: str, addr: int):
match = re.match(r'/dev/i2c-(\d+)', path)
assert match, 'device path does not match expected format'
self._index = int(match.group(1))
assert addr in (I2C.HOST_ADDR_R, I2C.DDCCI_ADDR)
self._path = path
self._addr = addr

def read(self, length: int) -> bytes:
if self._addr == I2C.HOST_ADDR_R:
edid = fake_edid(*self._fake_devices[self._index])
assert len(edid) == 256, '128 bytes is 256 string chars'
return bytes.fromhex(('00' * 128) + edid + ('00' * 128))
raise NotImplementedError()

def write(self, data: bytes) -> int:
return len(data)

class MockDDCInterface(MockI2CDevice):
def __init__(self, i2c_path: str):
super().__init__(i2c_path, I2C.DDCCI_ADDR)

self._vcp_state: Dict[int, int] = {}

def write(self, *args) -> int:
raise NotImplementedError()

def read(self, amount: int) -> bytes:
raise NotImplementedError()

def setvcp(self, vcp_code: int, value: int) -> int:
self._vcp_state[vcp_code] = value
return 0

def getvcp(self, vcp_code: int) -> Tuple[int, int]:
assert vcp_code == 0x10, 'should only be getting the brightness'
# current and max brightness
return self._vcp_state.get(vcp_code, 100), 100
66 changes: 64 additions & 2 deletions pytests/test_linux.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
import glob
import os
from typing import Type
from unittest.mock import MagicMock, Mock
import re
from typing import Dict, Optional, Type
from unittest.mock import Mock

import pytest
from .mocks.linux_mock import MockI2C
from pytest_mock import MockerFixture

import screen_brightness_control as sbc
from screen_brightness_control import linux
from screen_brightness_control.helpers import BrightnessMethod

from .helpers import BrightnessMethodTest
Expand Down Expand Up @@ -89,3 +93,61 @@ def test_without(self, mocker: MockerFixture, method: Type[BrightnessMethod], fr
for index, display in enumerate(freeze_display_info):
mock.assert_any_call(os.path.join(display['path'], 'brightness'), 'w')
assert write.call_args_list[index][0][0] == '100'


class TestI2C(BrightnessMethodTest):
@pytest.fixture
def patch_get_display_info(self, mocker: MockerFixture):
def path_exists(path: str):
return re.match(r'/dev/i2c-\d+', path) is not None

mocker.patch.object(glob, 'glob', Mock(return_value=['/dev/i2c-0', '/dev/i2c-1']), spec=True)
mocker.patch.object(os.path, 'exists', Mock(side_effect=path_exists), spec=True)
mocker.patch.object(linux.I2C, 'I2CDevice', MockI2C.MockI2CDevice, spec=True)

@pytest.fixture
def patch_get_brightness(self, mocker: MockerFixture, patch_get_display_info):
mocker.patch.object(linux.I2C, 'DDCInterface', MockI2C.MockDDCInterface, spec=True)

@pytest.fixture
def method(self):
return linux.I2C

class TestGetDisplayInfo(BrightnessMethodTest.TestGetDisplayInfo):
def test_returned_dicts_contain_required_keys(self, method: type[BrightnessMethod]):
return super().test_returned_dicts_contain_required_keys(method, {'i2c_bus': str})

def test_display_filtering(self, mocker: MockerFixture, original_os_module, method):
return super().test_display_filtering(mocker, original_os_module, method, {'include': ['i2c_bus']})

class TestGetBrightness(BrightnessMethodTest.TestGetBrightness):
class TestDisplayKwarg(BrightnessMethodTest.TestGetBrightness.TestDisplayKwarg):
def test_with(self, mocker: MockerFixture, method: Type[BrightnessMethod], freeze_display_info):
spy = mocker.spy(method, 'DDCInterface')
for index, display in enumerate(freeze_display_info):
method.get_brightness(display=index)
spy.assert_called_once_with(display['i2c_bus'])
spy.reset_mock()

def test_without(self, mocker: MockerFixture, method: Type[BrightnessMethod], freeze_display_info):
spy = mocker.spy(method, 'DDCInterface')
method.get_brightness()
paths = [device['i2c_bus'] for device in freeze_display_info]
called_devices = [i[0][0] for i in spy.call_args_list]
assert paths == called_devices

class TestSetBrightness(BrightnessMethodTest.TestSetBrightness):
class TestDisplayKwarg(BrightnessMethodTest.TestSetBrightness.TestDisplayKwarg):
def test_with(self, mocker: MockerFixture, method: Type[BrightnessMethod], freeze_display_info):
spy = mocker.spy(method, 'DDCInterface')
for index, display in enumerate(freeze_display_info):
method.set_brightness(100, display=index)
spy.assert_called_once_with(display['i2c_bus'])
spy.reset_mock()

def test_without(self, mocker: MockerFixture, method: Type[BrightnessMethod], freeze_display_info):
spy = mocker.spy(method, 'DDCInterface')
method.set_brightness(100)
paths = [device['i2c_bus'] for device in freeze_display_info]
called_devices = [i[0][0] for i in spy.call_args_list]
assert paths == called_devices

0 comments on commit 20fabd6

Please sign in to comment.