from .pigpio_mocks import patch_pigpio_base, patch_pigpio_i2c, patch_pigpio_gpio, mock_i2c_hardware, patch_bad_socket
from .pigpio_mocks import MockHardwareDevice
from pvp.io.devices import ADS1115, ADS1015, SPIDevice, PigpioConnection, IODeviceBase, I2CDevice
from secrets import token_bytes
import pigpio
import pytest
import random
[docs]def test_mock_pigpio_base(patch_pigpio_base):
"""__________________________________________________________________________________________________________TEST #1
Tests that the base fixture of pigpio mocks is indeed enough to instantiate pigpio.pi()
"""
pig = PigpioConnection()
assert isinstance(pig, pigpio.pi)
assert pig.connected
pig.stop()
assert not pig.connected
"""__________________________________________________________________________________________________________
"""
[docs]def test_pigpio_connection_exception(patch_pigpio_base, patch_bad_socket):
"""__________________________________________________________________________________________________________TEST #2
Tests to make sure an exception is thrown if, upon init, a PigpioConnection finds it is not connected.
"""
with pytest.raises(RuntimeError):
PigpioConnection()
with pytest.raises(RuntimeError):
IODeviceBase()
[docs]def test_io_device_base_no_handles_to_close(patch_pigpio_base, monkeypatch):
"""__________________________________________________________________________________________________________TEST #3
Tests that IODeviceBase._close() returns cleanly when it has no handles
"""
device = IODeviceBase()
device._close()
[docs]@pytest.mark.parametrize("seed", [token_bytes(8) for _ in range(128)])
def test_mock_pigpio_i2c(patch_pigpio_i2c, mock_i2c_hardware, seed):
"""__________________________________________________________________________________________________________TEST #4
Tests the functionality of the mock pigpio i2c device interface. More specifically, tests that:
- mock hardware devices are initialized correctly
- mock handles, i2c_open(), and i2c_close() can be used to add, interact with, and remove mock hardware devices
- correct functioning of i2c_open is implied by the read/write tests
- the correct functioning of i2c_close() is ascertained from the assertion that pig.mock_i2c[i2c_bus] is empty
- mock i2c read and write functions work as intended:
- This is three tests in one. For each register of each mock hardware device:
1) Read the register with i2c_read and store the result in results['init']. We expect it to be the value
from the correct register of the matching mock_device (expected['init'])
2) Write two random bytes to the register with i2c_write. Look at the actual contents of the target register
and put them in results['write']. We expect them to be the same as the two random bytes we generated.
3) Read the register with i2c_read again and put the results in expected['read']. These should also match
the two random bytes from the 'write' test.
-> Assert that the results match what we expect
- if there is only one register on the device, test read_device() and write_device() instead of read_register()
and write_register() -> this matches how such a device would be interacted with in practice.
"""
random.seed(seed)
n_devices = random.randint(1, 20)
address_pool = random.sample(range(128), k=n_devices)
pig = PigpioConnection()
mocks = []
for i in range(n_devices):
mock = mock_i2c_hardware(i2c_address=address_pool.pop())
pig.add_mock_hardware(mock['device'], mock['i2c_address'], mock['i2c_bus'])
mock['handle'] = pig.i2c_open(mock['i2c_bus'], mock['i2c_address'])
mocks.append(mock)
for mock_device in mocks:
handle = mock_device['handle']
bus = mock_device['i2c_bus']
address = mock_device['i2c_address']
results = {'init': [], 'write': [], 'read': []}
expected = {'init': [], 'write': [], 'read': []}
for reg in range(len(mock_device['values'])):
expected['init'] = mock_device['values'][reg]
expected['write'] = token_bytes(2)
expected['read'] = expected['write']
if len(mock_device['values']) == 1:
results['init'] = pig.i2c_read_device(handle, 2)[1]
pig.i2c_write_device(handle, expected['write'])
results['write'] = pig.mock_i2c[bus][address].registers[0][-1]
results['read'] = pig.i2c_read_device(handle, 2)[1]
else:
results['init'] = pig.i2c_read_i2c_block_data(handle, reg, count=2)[1]
pig.i2c_write_i2c_block_data(handle, reg, expected['write'])
results['write'] = pig.mock_i2c[bus][address].registers[reg][-1]
results['read'] = pig.i2c_read_i2c_block_data(handle, reg, count=2)[1]
assert results == expected
pig.i2c_close(handle)
assert not pig.mock_i2c[0]
assert not pig.mock_i2c[1]
assert not pig.mock_i2c['spi']
"""__________________________________________________________________________________________________________
"""
[docs]@pytest.mark.parametrize("seed", [token_bytes(8) for _ in range(128)])
def test_i2c_device(patch_pigpio_i2c, mock_i2c_hardware, seed):
"""__________________________________________________________________________________________________________TEST #5
Tests the various basic I2CDevice methods, open, close, read, write, etc.
- Note: i2c_device.read_device() returns raw bytes (assumed to have big-endian ordering.) read_register, on the
other hand, returns the integer representation of those bytes. write_device and write_register take an integer
argument which is the written to the device register as a big-endian two's complement.
- The RPi is native little endian, so to simulate/test the byte-swapping that occurs over I2C we generate a
random 16-bit integer (`data`), write it to the register, and read it back.
- If the int->byte-> int conversions in i2c_device are working correctly, we should expect one of two possible
values to be returned (depending on whether read_device() or read_register() is called)
- read_device() should return the big-endian two's complement of `data`
- read_register() should just return `data`
"""
results = []
random.seed(seed)
mock = mock_i2c_hardware()
n_registers = len(mock['values'])
data = [random.getrandbits(15) for _ in range(n_registers)]
signed = random.getrandbits(1)
expected = [data[0].to_bytes(2, 'big', signed=signed)] if n_registers == 1 else data
pig = PigpioConnection()
pig.add_mock_hardware(mock['device'], mock['i2c_address'], mock['i2c_bus'])
i2c_device = I2CDevice(mock['i2c_address'], mock['i2c_bus'], pig=pig)
assert i2c_device.pigpiod_ok
if n_registers == 1:
i2c_device.write_device(data[0], signed=signed)
results.append(i2c_device.read_device()[1])
else:
for register in range(n_registers):
i2c_device.write_register(register, data[register], signed=signed)
results.append(i2c_device.read_register(register, signed=signed))
i2c_device._close()
assert not i2c_device._pig.mock_i2c[mock['i2c_bus']]
assert results == expected
"""__________________________________________________________________________________________________________
"""
[docs]def test_register_arg_exception():
"""__________________________________________________________________________________________________________TEST #6
Tests that I2CDevice.Register throws an exception if it tries to init with a mismatched number of arguments
"""
with pytest.raises(ValueError):
I2CDevice.Register(fields=('one', 'two', 'three'), values=((0, 1, 3), (4, 5)))
[docs]def test_value_field_pack_unknown_value_exception():
"""__________________________________________________________________________________________________________TEST #7
Tests that I2CDevice.Register throws an exception if it tries to init with a mismatched number of arguments
"""
reg = I2CDevice.Register(fields=('one', 'two', 'three'), values=((0, 1, 3), (4, 5), ('hi', 'mark')))
with pytest.raises(ValueError):
reg.three.pack('oh')
[docs]def test_value_field_insert_unknown_value_exception():
"""__________________________________________________________________________________________________________TEST #8
Tests that I2CDevice.Register throws an exception if it tries to init with a mismatched number of arguments
"""
reg = I2CDevice.Register(fields=('one', 'two', 'three'), values=((0, 1, 3), (4, 5), ('hi', 'mark')))
with pytest.raises(ValueError):
reg.three.insert(0x0, 'oh')
[docs]def test_spi_device(patch_pigpio_i2c):
"""__________________________________________________________________________________________________________TEST #9
Tests that an SPI device can be created without issue. That's about all you can do with one of these anyway.
"""
channel = random.randint(1, 20)
device = SPIDevice(channel=channel, baudrate=100)
mock_device = MockHardwareDevice(token_bytes(2))
device.pig.add_mock_hardware(mock_device, i2c_address=channel, i2c_bus='spi')
assert device._handle >= 0
device._close()
[docs]@pytest.mark.parametrize("ads1x15", [ADS1115, ADS1015])
@pytest.mark.parametrize("seed", [token_bytes(8) for _ in range(128)])
def test_read_conversion(patch_pigpio_i2c, mock_i2c_hardware, ads1x15, seed):
"""_________________________________________________________________________________________________________TEST #10
Tests that the proper cfg is generated and written to the config register given kwargs, and tests that the
conversion register is properly read & converted to signed int
"""
random.seed(seed)
kwargs = {
"MUX": random.choice(ads1x15._CONFIG_VALUES[1]),
"PGA": random.choice(ads1x15._CONFIG_VALUES[2]),
"MODE": random.choice(ads1x15._CONFIG_VALUES[3]),
"DR": random.choice(ads1x15._CONFIG_VALUES[4])}
conversion_bytes = token_bytes(2)
expected_val = int.from_bytes(conversion_bytes, 'big', signed=True) * kwargs['PGA'] / 32767
mock = mock_i2c_hardware(
i2c_bus=1,
i2c_address=ads1x15._DEFAULT_ADDRESS,
n_registers=4,
reg_values=[
conversion_bytes,
b'\x85\x83'
]
)
pig = PigpioConnection()
pig.add_mock_hardware(mock['device'], mock['i2c_address'], mock['i2c_bus'])
ads = ads1x15(pig=pig)
expected_default_config = ads.config.unpack(0xC3E3)
expected_config = ads.config.unpack(ads.config.pack(0xC3E3, **kwargs))
assert ads.print_config() == expected_default_config
result = ads.read_conversion(**kwargs)
assert ads.print_config() == expected_config
assert result == expected_val
"""__________________________________________________________________________________________________________
"""