Skip to content

Commit

Permalink
Merge pull request #7 from joshrost/add-actions
Browse files Browse the repository at this point in the history
Add actions for linting and testing
  • Loading branch information
joshuarost committed Jul 22, 2023
2 parents a1deac8 + 24157a5 commit b4e4ea9
Show file tree
Hide file tree
Showing 9 changed files with 148 additions and 101 deletions.
51 changes: 51 additions & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
name: CI

on: [push]

jobs:
lint:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.8", "3.9", "3.10"]
steps:
- uses: actions/checkout@v3
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v3
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
python -m pip install --upgrade black
- name: black format check
run: black --check --line-length 120 traffic_light/ test/ setup.py

test:
strategy:
fail-fast: false
max-parallel: 6
matrix:
python-version: [3.8, 3.9, "3.10"]
os: [ubuntu-latest]
include:
- os: ubuntu-latest

runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v3
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
- name: Setup build and test environment
run: |
python -m pip install --upgrade pip
- name: Build Python Package
run: |
python -m pip install -r requirements-dev.txt
- name: Update pytest on >= Python3.10
if: ${{ matrix.python-version == '3.10' || matrix.python-version == '3.11' }}
run: |
python -m pip install pytest==7.0.1
- name: Unit Test with pytest
run: PYTHONPATH=. pytest test --cov=traffic_light --cov-report term-missing
3 changes: 3 additions & 0 deletions requirements-dev.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
-r requirements.txt
pytest >= 7.2.2
pytest-cov >= 4.0.0
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pyusb >= 1.2.1
20 changes: 10 additions & 10 deletions setup.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
from setuptools import setup, find_packages

with open("README.md", "r", encoding='utf-8') as desc_file:
with open("README.md", "r", encoding="utf-8") as desc_file:
long_description = desc_file.read()

setup(
name='cleware-traffic-light',
version='1.0.8',
description='Python3 modul and cli tool to controll the Cleware traffic light',
name="cleware-traffic-light",
version="1.0.8",
description="Python3 modul and cli tool to controll the Cleware traffic light",
long_description=long_description,
long_description_content_type="text/markdown",
author='Josh Rost',
author_email='joshua.s.rost@gmail.com',
url='https://github.com/joshrost/cleware-traffic-light',
author="Josh Rost",
author_email="joshua.s.rost@gmail.com",
url="https://github.com/joshrost/cleware-traffic-light",
packages=find_packages(),
install_requires=['pyusb'],
install_requires=["pyusb"],
entry_points={
'console_scripts': ['ctl=traffic_light.__main__:main'],
}
"console_scripts": ["ctl=traffic_light.__main__:main"],
},
)
37 changes: 19 additions & 18 deletions test/core_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,37 +34,40 @@ def test_should_fail_for_multiple_lights_whitout_address(usb_find_mock):
usb_find_mock.return_value = [mock.MagicMock(), mock.MagicMock()]

# when & then
with pytest.raises(MultipleTrafficLightsError,
match="No address is given and there are multiple devices conected! "
"Use 'print_devices' to see a list of connected devices."):
with pytest.raises(
MultipleTrafficLightsError,
match="No address is given and there are multiple devices conected! "
"Use 'print_devices' to see a list of connected devices.",
):
ClewareTrafficLight()


@mock.patch("usb.core.find")
def test_should_fail_for_wrong_color_in_getattr(usb_find_mock):
# when & then
with pytest.raises(TrafficLightError,
match="Either the given color or state could not be parsed"):
with pytest.raises(TrafficLightError, match="Either the given color or state could not be parsed"):
ClewareTrafficLight().blue_on()


@mock.patch("usb.core.find")
def test_should_fail_for_wrong_state_in_getattr(usb_find_mock):
# when & then
with pytest.raises(TrafficLightError,
match="Either the given color or state could not be parsed"):
with pytest.raises(TrafficLightError, match="Either the given color or state could not be parsed"):
ClewareTrafficLight().red_foo()


@mock.patch("usb.core.find")
@pytest.mark.parametrize("test_input,expected", [
((Color.RED, State.ON), (0x10, 0x1)),
((Color.RED, State.OFF), (0x10, 0x0)),
((Color.YELLOW, State.ON), (0x11, 0x1)),
((Color.YELLOW, State.OFF), (0x11, 0x0)),
((Color.GREEN, State.ON), (0x12, 0x1)),
((Color.GREEN, State.OFF), (0x12, 0x0))
])
@pytest.mark.parametrize(
"test_input,expected",
[
((Color.RED, State.ON), (0x10, 0x1)),
((Color.RED, State.OFF), (0x10, 0x0)),
((Color.YELLOW, State.ON), (0x11, 0x1)),
((Color.YELLOW, State.OFF), (0x11, 0x0)),
((Color.GREEN, State.ON), (0x12, 0x1)),
((Color.GREEN, State.OFF), (0x12, 0x0)),
],
)
def test_should_turn_on_led(usb_find_mock, test_input, expected):
# given
device_mock = mock.MagicMock(idProduct=ID_PRODUCT_ORIGINAL)
Expand All @@ -75,9 +78,7 @@ def test_should_turn_on_led(usb_find_mock, test_input, expected):
light.set_led(test_input[0], test_input[1])

# then
device_mock.write.assert_called_once_with(
0x02, [0x00, expected[0], expected[1]], timeout=1000
)
device_mock.write.assert_called_once_with(0x02, [0x00, expected[0], expected[1]], timeout=1000)


@mock.patch("usb.core.find")
Expand Down
55 changes: 31 additions & 24 deletions test/main_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,44 +5,50 @@


@mock.patch("usb.core.find", return_value=mock.MagicMock(idProduct=ID_PRODUCT_ORIGINAL))
@pytest.mark.parametrize("test_input,expected", [
(['--all', 'on'], 0),
(['--all', 'off'], 0),
(['-a', 'off'], 0),
])
@pytest.mark.parametrize(
"test_input,expected",
[
(["--all", "on"], 0),
(["--all", "off"], 0),
(["-a", "off"], 0),
],
)
def test_set_all_leds(usb_find_mock, test_input, expected):
assert main(test_input) == expected


@pytest.mark.parametrize("test_input", [
(['--all', 'foo']),
(['--blue', 'on'])
])
@pytest.mark.parametrize("test_input", [(["--all", "foo"]), (["--blue", "on"])])
def test_should_fail_for_wrong_argument_and_value(test_input):
with pytest.raises(SystemExit):
main(test_input)


@mock.patch("usb.core.find", return_value=mock.MagicMock(idProduct=ID_PRODUCT_ORIGINAL))
@pytest.mark.parametrize("test_input,expected", [
(['--red', 'on'], 0),
(['--red', 'off'], 0),
(['-r', 'off'], 0),
(['--yellow', 'on'], 0),
(['--yellow', 'off'], 0),
(['-y', 'off'], 0),
(['--green', 'on'], 0),
(['--green', 'off'], 0),
(['-g', 'off'], 0),
])
@pytest.mark.parametrize(
"test_input,expected",
[
(["--red", "on"], 0),
(["--red", "off"], 0),
(["-r", "off"], 0),
(["--yellow", "on"], 0),
(["--yellow", "off"], 0),
(["-y", "off"], 0),
(["--green", "on"], 0),
(["--green", "off"], 0),
(["-g", "off"], 0),
],
)
def test_set_diffrent_color_leds(usb_find_mock, test_input, expected):
assert main(test_input) == expected


@pytest.mark.parametrize("test_input,expected", [
(['-adr', "2"], 1),
(['--address', "2"], 1),
])
@pytest.mark.parametrize(
"test_input,expected",
[
(["-adr", "2"], 1),
(["--address", "2"], 1),
],
)
def test_should_fail_for_no_given_color(test_input, expected):
assert main(test_input) == expected

Expand All @@ -55,6 +61,7 @@ def side_effect(*args, **kwargs):
return [mock.MagicMock(), mock.MagicMock()]
else:
return mock.MagicMock()

# usb_find_mock.return_value = [mock.MagicMock(), mock.MagicMock()]
usb_find_mock.side_effect = side_effect
# then
Expand Down
6 changes: 1 addition & 5 deletions traffic_light/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,3 @@
from traffic_light.core import ClewareTrafficLight, Color, State

__all__ = [
"ClewareTrafficLight",
"Color",
"State"
]
__all__ = ["ClewareTrafficLight", "Color", "State"]
49 changes: 19 additions & 30 deletions traffic_light/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,47 +6,36 @@


def main(args=sys.argv[1:]):
parser = argparse.ArgumentParser(
prog="ctl",
description='Turns the led of the cleware traffic light on or off'
parser = argparse.ArgumentParser(prog="ctl", description="Turns the led of the cleware traffic light on or off")
parser.add_argument("-r", "--red", choices=["on", "off"], help="Controlls the red led")
parser.add_argument("-y", "--yellow", choices=["on", "off"], help="Controlls the yellow led")
parser.add_argument("-g", "--green", choices=["on", "off"], help="Controlls the green led")
parser.add_argument("-a", "--all", choices=["on", "off"], help="Controlls all leds")
parser.add_argument(
"-d",
"--direction",
choices=["left", "front", "right", "back", "all"],
default="all",
help="Select the direction you want to actuate (requires a trafficlight4)",
)
parser.add_argument('-r', '--red',
choices=["on", "off"],
help="Controlls the red led")
parser.add_argument('-y', '--yellow',
choices=["on", "off"],
help="Controlls the yellow led")
parser.add_argument('-g', '--green',
choices=["on", "off"],
help="Controlls the green led")
parser.add_argument('-a', '--all',
choices=["on", "off"],
help="Controlls all leds")
parser.add_argument('-d', '--direction',
choices=["left", "front", "right", "back", "all"],
default="all",
help="Select the direction you want to actuate (requires a trafficlight4)")
parser.add_argument('-adr', '--address',
type=int,
help="Specifies which traffic light should be used")
parser.add_argument("-adr", "--address", type=int, help="Specifies which traffic light should be used")
args = parser.parse_args(args)

if not vars(args)['all']:
all_given_colors = [(k, v) for k, v in vars(args).items()
if v is not None and k not in ["direction", "address"]]
if not vars(args)["all"]:
all_given_colors = [
(k, v) for k, v in vars(args).items() if v is not None and k not in ["direction", "address"]
]
if not all_given_colors:
parser.print_help()
return 1
else:
all_state = vars(args)["all"]
all_given_colors = [
('red', all_state), ('yellow', all_state), ('green', all_state)
]
all_given_colors = [("red", all_state), ("yellow", all_state), ("green", all_state)]

for color_name, state_name in all_given_colors:
color = Color[color_name.upper()]
state = State[state_name.upper()]
direction = Direction[vars(args)['direction'].upper()]
direction = Direction[vars(args)["direction"].upper()]
address = vars(args)["address"]
try:
ClewareTrafficLight(address).set_led(color, state, direction=direction)
Expand All @@ -61,5 +50,5 @@ def main(args=sys.argv[1:]):
return 0


if __name__ == '__main__':
if __name__ == "__main__":
sys.exit(main())
27 changes: 13 additions & 14 deletions traffic_light/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from traffic_light.error import TrafficLightError, MultipleTrafficLightsError

CTRL_ENDPOINT = 0x02
ID_VENDOR = 0x0d50
ID_VENDOR = 0x0D50
ID_PRODUCT_ORIGINAL = 0x0008
ID_PRODUCT_SWITCH = 0x0030
ID_PRODUCTS = (ID_PRODUCT_ORIGINAL, ID_PRODUCT_SWITCH)
Expand Down Expand Up @@ -47,7 +47,7 @@ def __init__(self, address=None):
else:
self.device = self.find_device()
if self.device is None:
raise TrafficLightError('Cleware traffic light not found!')
raise TrafficLightError("Cleware traffic light not found!")
self.reattach = False

def attach(self):
Expand All @@ -68,8 +68,7 @@ def find_devices():
devices = []

for product_id in ID_PRODUCTS:
if devs := usb.core.find(find_all=True, idVendor=ID_VENDOR,
idProduct=product_id):
if devs := usb.core.find(find_all=True, idVendor=ID_VENDOR, idProduct=product_id):
devices.extend(devs)

return devices
Expand Down Expand Up @@ -120,10 +119,9 @@ def _compute_led_payload(self, color, value, direction):
# - value: 16 bits (big endian)
# - mask: 16 bits (big endian)
#
color_id = color % 0x10 # Red = 0, Yellow = 1, Green = 2
color_id = color % 0x10 # Red = 0, Yellow = 1, Green = 2
color_offset = color_id * 4 # Red = 0-3, Yellow = 4-7, Green = 8-11
return pack(">BHH", 11, (direction.value << color_offset) * value,
direction.value << color_offset)
return pack(">BHH", 11, (direction.value << color_offset) * value, direction.value << color_offset)
else:
raise TrafficLightError("Unknown product ID")

Expand All @@ -146,18 +144,19 @@ def set_led(self, color, value, timeout=1000, direction=Direction.ALL):

def __getattr__(self, name):
"""Parses attribut calls in function"""
args = name.split('_')
args = name.split("_")
try:
color = Color[args[0].upper()]
state = State[args[1].upper()]
except Exception as exc:
raise TrafficLightError("Either the given color or state could not be parsed! Exc: {}"
.format(exc))
raise TrafficLightError("Either the given color or state could not be parsed! Exc: {}".format(exc))
return functools.partial(self.set_led, color, state)

def __str__(self):
"""Converts instance into string with important imformations"""
return ("== Cleware Traffic Light ==\n"
"Address: {} \n"
"IdVendor: {} \n"
"IdProduct: {}".format(self.address, self.device.idVendor, self.device.idProduct))
return (
"== Cleware Traffic Light ==\n"
"Address: {} \n"
"IdVendor: {} \n"
"IdProduct: {}".format(self.address, self.device.idVendor, self.device.idProduct)
)

0 comments on commit b4e4ea9

Please sign in to comment.