Skip to content
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ env:
install:
- if [[ "$TEST_SOCKETCAN" ]]; then sudo bash test/open_vcan.sh ; fi
- travis_retry pip install .[test]
- pip freeze

script:
- |
Expand Down Expand Up @@ -52,6 +53,14 @@ jobs:

# Unit Testing Stage

# testing socketcan on Trusty & Python 2.7, since it is not available on Xenial
- stage: test
name: Socketcan
os: linux
dist: trusty
python: "2.7"
sudo: required
env: TEST_SOCKETCAN=TRUE
# testing socketcan on Trusty & Python 3.6, since it is not available on Xenial
- stage: test
name: Socketcan
Expand Down
12 changes: 12 additions & 0 deletions CHANGELOG.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,15 @@
Version 3.3.4
====

Last call for Python2 support.

* #850 Fix socket.error is a deprecated alias of OSError used on Python versions lower than 3.3.
* #846 Use inter-process mutex to prevent concurrent neoVI device open.
* #879 Updating incorrect api documentation.
* #885 Fix recursion message in Message.__getattr__
* #845 Fix socketcan issue


Version 3.3.3
====

Expand Down
2 changes: 1 addition & 1 deletion can/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

import logging

__version__ = "3.3.3"
__version__ = "3.3.4"

log = logging.getLogger('can')

Expand Down
6 changes: 4 additions & 2 deletions can/interfaces/ixxat/canlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -328,10 +328,12 @@ def __init__(self, channel, can_filters=None, **kwargs):
else:
raise VCIDeviceNotFoundError("Unique HW ID {} not connected or not available.".format(UniqueHardwareId))
else:
if (UniqueHardwareId is None) or (self._device_info.UniqueHardwareId.AsChar == bytes(UniqueHardwareId, 'ascii')):
if (UniqueHardwareId is None) or (
self._device_info.UniqueHardwareId.AsChar == UniqueHardwareId.encode("ascii")):
break
else:
log.debug("Ignoring IXXAT with hardware id '%s'.", self._device_info.UniqueHardwareId.AsChar.decode("ascii"))
log.debug("Ignoring IXXAT with hardware id '%s'.",
self._device_info.UniqueHardwareId.AsChar.decode("ascii"))
_canlib.vciEnumDeviceClose(self._device_handle)
_canlib.vciDeviceOpen(ctypes.byref(self._device_info.VciObjectId), ctypes.byref(self._device_handle))
log.info("Using unique HW ID %s", self._device_info.UniqueHardwareId.AsChar)
Expand Down
2 changes: 1 addition & 1 deletion can/interfaces/socketcan/socketcan.py
Original file line number Diff line number Diff line change
Expand Up @@ -363,7 +363,7 @@ def _tx_setup(self, message):
)
try:
self.bcm_socket.send(check_header)
except OSError as e:
except (socket.error, OSError) as e:
if e.errno != errno.EINVAL:
raise e
else:
Expand Down
5 changes: 4 additions & 1 deletion can/message.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,9 @@ def __getattr__(self, key):
# TODO keep this for a version, in order to not break old code
# this entire method (as well as the _dict attribute in __slots__ and the __setattr__ method)
# can be removed in 4.0
# this method is only called if the attribute was not found elsewhere, like in __slots__
# this method is only called if the attribute was not found elsewhere, like in __slots_
if key not in self.__slots__:
raise AttributeError
try:
warnings.warn("Custom attributes of messages are deprecated and will be removed in 4.0", DeprecationWarning)
return self._dict[key]
Expand Down Expand Up @@ -110,6 +112,7 @@ def __init__(self, timestamp=0.0, arbitration_id=0, is_extended_id=None,
if is_extended_id is not None:
self.is_extended_id = is_extended_id
else:
# Default behaviour is to create extended id messages
self.is_extended_id = True if extended_id is None else extended_id

self.is_remote_frame = is_remote_frame
Expand Down
2 changes: 1 addition & 1 deletion doc/configuration.rst
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ the **interface** and **channel** before importing from ``can.interfaces``.
can.rc['interface'] = 'socketcan'
can.rc['channel'] = 'vcan0'
can.rc['bitrate'] = 500000
from can.interfaces.interface import Bus
from can.interface import Bus

bus = Bus()

Expand Down
4 changes: 2 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

from __future__ import absolute_import

import platform
from os import listdir
from os.path import isfile, join
import re
Expand All @@ -31,7 +32,7 @@

tests_require = [
'mock~=2.0',
'pytest~=4.3',
'pytest~=4.6',
'pytest-timeout~=1.3',
'pytest-cov~=2.8',
# coveragepy==5.0 fails with `Safety level may not be changed inside a transaction`
Expand All @@ -53,7 +54,6 @@
needs_pytest = {"pytest", "test", "ptr"}.intersection(sys.argv)
pytest_runner = ["pytest-runner"] if needs_pytest else []


setup(
# Description
name="python-can",
Expand Down
176 changes: 176 additions & 0 deletions test/test_cyclic_socketcan.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
"""
This module tests multiple message cyclic send tasks.
"""
import unittest

import time
import can

from .config import TEST_INTERFACE_SOCKETCAN


@unittest.skipUnless(TEST_INTERFACE_SOCKETCAN, "skip testing of socketcan")
class CyclicSocketCan(unittest.TestCase):
BITRATE = 500000
TIMEOUT = 0.1

INTERFACE_1 = "socketcan"
CHANNEL_1 = "vcan0"
INTERFACE_2 = "socketcan"
CHANNEL_2 = "vcan0"

PERIOD = 1.0

DELTA = 0.01

def _find_start_index(self, tx_messages, message):
"""
:param tx_messages:
The list of messages that were passed to the periodic backend
:param message:
The message whose data we wish to match and align to

:returns: start index in the tx_messages
"""
start_index = -1
for index, tx_message in enumerate(tx_messages):
if tx_message.data == message.data:
start_index = index
break
return start_index

def setUp(self):
self._send_bus = can.Bus(
interface=self.INTERFACE_1, channel=self.CHANNEL_1, bitrate=self.BITRATE
)
self._recv_bus = can.Bus(
interface=self.INTERFACE_2, channel=self.CHANNEL_2, bitrate=self.BITRATE
)

def tearDown(self):
self._send_bus.shutdown()
self._recv_bus.shutdown()

def test_cyclic_initializer_message(self):
message = can.Message(
arbitration_id=0x401,
data=[0x11, 0x11, 0x11, 0x11, 0x11, 0x11],
is_extended_id=False,
)

task = self._send_bus.send_periodic(message, self.PERIOD)
self.assertIsInstance(task, can.broadcastmanager.CyclicSendTaskABC)

# Take advantage of kernel's queueing mechanisms
time.sleep(4 * self.PERIOD)
task.stop()

for _ in range(4):
tx_message = message
rx_message = self._recv_bus.recv(self.TIMEOUT)

self.assertIsNotNone(rx_message)
self.assertEqual(tx_message.arbitration_id, rx_message.arbitration_id)
self.assertEqual(tx_message.dlc, rx_message.dlc)
self.assertEqual(tx_message.data, rx_message.data)
self.assertEqual(tx_message.is_extended_id, rx_message.is_extended_id)
self.assertEqual(tx_message.is_remote_frame, rx_message.is_remote_frame)
self.assertEqual(tx_message.is_error_frame, rx_message.is_error_frame)
self.assertEqual(tx_message.is_fd, rx_message.is_fd)

def test_create_same_id_raises_exception(self):
messages_a = can.Message(
arbitration_id=0x401,
data=[0x11, 0x11, 0x11, 0x11, 0x11, 0x11],
is_extended_id=False,
)

messages_b = can.Message(
arbitration_id=0x401,
data=[0x22, 0x22, 0x22, 0x22, 0x22, 0x22],
is_extended_id=False,
)

task_a = self._send_bus.send_periodic(messages_a, 1)
self.assertIsInstance(task_a, can.broadcastmanager.CyclicSendTaskABC)

# The second one raises a ValueError when we attempt to create a new
# Task, since it has the same arbitration ID.
with self.assertRaises(ValueError):
task_b = self._send_bus.send_periodic(messages_b, 1)

def test_modify_data_message(self):
message_odd = can.Message(
arbitration_id=0x401,
data=[0x11, 0x11, 0x11, 0x11, 0x11, 0x11],
is_extended_id=False,
)
message_even = can.Message(
arbitration_id=0x401,
data=[0x22, 0x22, 0x22, 0x22, 0x22, 0x22],
is_extended_id=False,
)
task = self._send_bus.send_periodic(message_odd, self.PERIOD)
self.assertIsInstance(task, can.broadcastmanager.ModifiableCyclicTaskABC)

results_odd = []
results_even = []
for _ in range(1 * 4):
result = self._recv_bus.recv(self.PERIOD * 2)
if result:
results_odd.append(result)

task.modify_data(message_even)
for _ in range(1 * 4):
result = self._recv_bus.recv(self.PERIOD * 2)
if result:
results_even.append(result)

task.stop()

# Now go through the partitioned results and assert that they're equal
for rx_index, rx_message in enumerate(results_even):
tx_message = message_even

self.assertEqual(tx_message.arbitration_id, rx_message.arbitration_id)
self.assertEqual(tx_message.dlc, rx_message.dlc)
self.assertEqual(tx_message.data, rx_message.data)
self.assertEqual(tx_message.is_extended_id, rx_message.is_extended_id)
self.assertEqual(tx_message.is_remote_frame, rx_message.is_remote_frame)
self.assertEqual(tx_message.is_error_frame, rx_message.is_error_frame)
self.assertEqual(tx_message.is_fd, rx_message.is_fd)

if rx_index != 0:
prev_rx_message = results_even[rx_index - 1]
# Assert timestamps are within the expected period
self.assertTrue(
abs(
(rx_message.timestamp - prev_rx_message.timestamp) - self.PERIOD
)
<= self.DELTA
)

for rx_index, rx_message in enumerate(results_odd):
tx_message = message_odd

self.assertEqual(tx_message.arbitration_id, rx_message.arbitration_id)
self.assertEqual(tx_message.dlc, rx_message.dlc)
self.assertEqual(tx_message.data, rx_message.data)
self.assertEqual(tx_message.is_extended_id, rx_message.is_extended_id)
self.assertEqual(tx_message.is_remote_frame, rx_message.is_remote_frame)
self.assertEqual(tx_message.is_error_frame, rx_message.is_error_frame)
self.assertEqual(tx_message.is_fd, rx_message.is_fd)

if rx_index != 0:
prev_rx_message = results_odd[rx_index - 1]
# Assert timestamps are within the expected period
self.assertTrue(
abs(
(rx_message.timestamp - prev_rx_message.timestamp) - self.PERIOD
)
<= self.DELTA
)


if __name__ == "__main__":
unittest.main()
33 changes: 31 additions & 2 deletions test/test_message_class.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,15 @@
import sys
from math import isinf, isnan
from copy import copy, deepcopy
import pickle

from hypothesis import given, settings, reproduce_failure
import hypothesis.strategies as st

from can import Message

from .message_helper import ComparingMessagesTestCase


class TestMessageClass(unittest.TestCase):
"""
Expand Down Expand Up @@ -70,7 +73,7 @@ def test_methods(self, **kwargs):

# check copies and equalities
if is_valid:
self.assertEqual(message, message)
self.assertEqual(message, message)
normal_copy = copy(message)
deep_copy = deepcopy(message)
for other in (normal_copy, deep_copy, message):
Expand All @@ -79,5 +82,31 @@ def test_methods(self, **kwargs):
self.assertTrue(message.equals(other, timestamp_delta=0))


if __name__ == '__main__':
class MessageSerialization(unittest.TestCase, ComparingMessagesTestCase):
def __init__(self, *args, **kwargs):
unittest.TestCase.__init__(self, *args, **kwargs)
ComparingMessagesTestCase.__init__(
self, allowed_timestamp_delta=0.016, preserves_channel=True
)

def test_serialization(self):
message = Message(
timestamp=1.0,
arbitration_id=0x401,
is_extended_id=False,
is_remote_frame=False,
is_error_frame=False,
channel=1,
dlc=6,
data=bytearray([0x01, 0x02, 0x03, 0x04, 0x05, 0x06]),
is_fd=False,
)

serialized = pickle.dumps(message, -1)
deserialized = pickle.loads(serialized)

self.assertMessageEqual(message, deserialized)


if __name__ == "__main__":
unittest.main()