diff --git a/.appveyor.yml b/.appveyor.yml index 58b3eebe2..efa47c7d8 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -17,10 +17,8 @@ environment: - PYTHON: "C:\\Python36-x64" install: - # We need our usual libraries - - "%PYTHON%\\python.exe -m pip install -r requirements.txt" # We need to install the python-can library itself - - "%PYTHON%\\python.exe -m pip install ." + - "%PYTHON%\\python.exe -m pip install .[test]" build: off @@ -28,4 +26,4 @@ test_script: # Note that you must use the environment variable %PYTHON% to refer to # the interpreter you're using - Appveyor does not do anything special # to put the Python version you want to use on PATH. - - "%PYTHON%\\python.exe setup.py test" + - "%PYTHON%\\python.exe setup.py test -v" diff --git a/.travis.yml b/.travis.yml index 5ccaa3ff7..0a26c32bf 100644 --- a/.travis.yml +++ b/.travis.yml @@ -43,8 +43,8 @@ matrix: - python: "nightly" install: - - travis_retry pip install . - - travis_retry pip install -r requirements.txt + - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then sudo bash test/open_vcan.sh ; fi + - travis_retry pip install .[test] script: - py.test -v diff --git a/can/interfaces/virtual.py b/can/interfaces/virtual.py index 2a7186b77..0f68e3438 100644 --- a/can/interfaces/virtual.py +++ b/can/interfaces/virtual.py @@ -46,7 +46,7 @@ def recv(self, timeout=None): except queue.Empty: return None - logger.log(9, 'Received message:\n%s', msg) + #logger.log(9, 'Received message:\n%s', msg) return msg def send(self, msg, timeout=None): @@ -55,7 +55,7 @@ def send(self, msg, timeout=None): for bus_queue in self.channel: if bus_queue is not self.queue or self.receive_own_messages: bus_queue.put(msg) - logger.log(9, 'Transmitted message:\n%s', msg) + #logger.log(9, 'Transmitted message:\n%s', msg) def shutdown(self): self.channel.remove(self.queue) diff --git a/can/io/logger.py b/can/io/logger.py index 1a1a6683b..9be09d82f 100755 --- a/can/io/logger.py +++ b/can/io/logger.py @@ -5,6 +5,8 @@ See the :class:`Logger` class. """ +import logging + from .asc import ASCWriter from .blf import BLFWriter from .csv import CSVWriter @@ -12,6 +14,8 @@ from .sqlite import SqliteWriter from .stdout import Printer +log = logging.getLogger("can.io.logger") + class Logger(object): """ @@ -22,6 +26,7 @@ class Logger(object): * .blf :class:`can.BLFWriter` * .csv: :class:`can.CSVWriter` * .db: :class:`can.SqliteWriter` + * .log :class:`can.CanutilsLogWriter` * other: :class:`can.Printer` Note this class itself is just a dispatcher, @@ -35,13 +40,14 @@ def __new__(cls, other, filename): return Printer() elif filename.endswith(".asc"): return ASCWriter(filename) - elif filename.endswith(".log"): - return CanutilsLogWriter(filename) elif filename.endswith(".blf"): return BLFWriter(filename) elif filename.endswith(".csv"): return CSVWriter(filename) elif filename.endswith(".db"): return SqliteWriter(filename) + elif filename.endswith(".log"): + return CanutilsLogWriter(filename) else: + log.info('unknown file type "%s", falling pack to can.Printer', filename) return Printer(filename) diff --git a/can/io/player.py b/can/io/player.py index 3b839a63a..4d4f17a93 100755 --- a/can/io/player.py +++ b/can/io/player.py @@ -44,7 +44,9 @@ class LogReader(object): @classmethod def __new__(cls, other, filename): - if filename.endswith(".asc"): + if not filename: + raise TypeError("a filename must be given") + elif filename.endswith(".asc"): return ASCReader(filename) elif filename.endswith(".blf"): return BLFReader(filename) diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index 74371374e..000000000 --- a/requirements.txt +++ /dev/null @@ -1,2 +0,0 @@ -pyserial >= 3.0 -#Deprecated >= 1.1.0 diff --git a/setup.py b/setup.py index 68ba1ad4d..03ee7945f 100644 --- a/setup.py +++ b/setup.py @@ -5,10 +5,13 @@ python-can requires the setuptools package to be installed. """ +from sys import version_info import re import logging from setuptools import setup, find_packages +logging.basicConfig(level=logging.WARNING) + with open('can/__init__.py', 'r') as fd: version = re.search(r'^__version__\s*=\s*[\'"]([^\'"]*)[\'"]', fd.read(), re.MULTILINE).group(1) @@ -16,10 +19,15 @@ with open('README.rst', 'r') as f: long_description = f.read() -logging.basicConfig(level=logging.WARNING) +tests_require = [ + 'mock', + 'nose', + 'pyserial >= 3.0', +] +if version_info.major < 3: + tests_require += ['subprocess32 >= 3.2'] setup( - # Description name="python-can", url="https://github.com/hardbyte/python-can", @@ -29,7 +37,9 @@ # Code version=version, packages=find_packages(), - + # see https://www.python.org/dev/peps/pep-0345/#version-specifiers + python_requires='>=2.7,!=3.0,!=3.1,!=3.2', + # Author author="Brian Thorne", author_email="brian@thorne.link", @@ -45,18 +55,16 @@ # Installation install_requires=[ + 'setuptools', #'Deprecated >= 1.1.0', ], extras_require={ 'serial': ['pyserial >= 3.0'], - 'neovi': ['python-ics'], + 'neovi': ['python-ics >= 2.8'], + 'test': tests_require }, # Testing test_suite="nose.collector", - tests_require=[ - 'mock', - 'nose', - 'pyserial >= 3.0' - ], + tests_require=tests_require, ) diff --git a/test/back2back_test.py b/test/back2back_test.py index 2b2f70198..5a2ab07e4 100644 --- a/test/back2back_test.py +++ b/test/back2back_test.py @@ -5,14 +5,16 @@ This module tests two virtual busses attached to each other. """ -from __future__ import absolute_import +from __future__ import absolute_import, print_function +import sys import unittest -import time +from time import sleep import can from .config import * +from .data.example_data import generate_message BITRATE = 500000 TIMEOUT = 0.1 @@ -80,12 +82,12 @@ def test_no_message(self): def test_timestamp(self): self.bus2.send(can.Message()) recv_msg1 = self.bus1.recv(TIMEOUT) - time.sleep(5) + sleep(2.0) self.bus2.send(can.Message()) recv_msg2 = self.bus1.recv(TIMEOUT) delta_time = recv_msg2.timestamp - recv_msg1.timestamp - self.assertTrue(4.8 < delta_time < 5.2, - 'Time difference should have been 5s +/- 200ms.' + self.assertTrue(1.75 <= delta_time <= 2.25, + 'Time difference should have been 2s +/- 250ms.' 'But measured {}'.format(delta_time)) def test_standard_message(self): @@ -130,6 +132,39 @@ def test_fd_message_with_brs(self): data=[0xff] * 48) self._send_and_receive(msg) +# FIXME +@unittest.skip("skip until CAN FD support is fixed, see issue #274") +#@unittest.skipUnless(TEST_INTERFACE_SOCKETCAN, "skip testing of socketcan") +class BasicTestSocketCan(unittest.TestCase): + + def setUp(self): + socketcan_version = can.util.choose_socketcan_implementation() + print("testing python-can's socketcan version:", + socketcan_version) + + self.bus1 = can.interface.Bus(channel="vcan0", + bustype=socketcan_version, + bitrate=250000, + fd=TEST_CAN_FD) + self.bus2 = can.interface.Bus(channel="vcan0", + bustype=socketcan_version, + bitrate=250000, + fd=TEST_CAN_FD) + + def tearDown(self): + self.bus1.shutdown() + self.bus2.shutdown() + + def test_basics(self): + reader = can.BufferedReader() + notifier = can.Notifier(self.bus2, [reader]) + + message = can.Message(arbitration_id=0x4321, data=[1, 2, 3], extended_id=True) + self.bus1.send(message) + + self.assertEqual(message, reader.get_message(timeout=2.0)) + notifier.stop() + if __name__ == '__main__': unittest.main() diff --git a/test/config.py b/test/config.py index a25aff627..a48d8c72c 100644 --- a/test/config.py +++ b/test/config.py @@ -2,14 +2,18 @@ # coding: utf-8 """ -This module contains some configuration for the tests. +This module contains various configuration for the tests. Some tests are skipped when run on a CI server because they are not reproducible, see #243 (https://github.com/hardbyte/python-can/issues/243). """ +import platform from os import environ as environment + +# ############################## Continuos integration + # see here for the environment variables that are set on the CI servers: # - https://docs.travis-ci.com/user/environment-variables/ # - https://www.appveyor.com/docs/environment-variables/ @@ -21,4 +25,27 @@ environment.get('CI', '').lower() == 'true' or \ environment.get('CONTINUOUS_INTEGRATION', '').lower() == 'true' +if IS_APPVEYOR and IS_TRAVIS: + raise EnvironmentError("IS_APPVEYOR and IS_TRAVIS cannot be both True at the same time") + +# ############################## Platforms + +_sys = platform.system().lower() +IS_WINDOWS = "windows" in _sys or ("win" in _sys and "darwin" not in _sys) +IS_LINUX = "linux" in _sys +IS_OSX = "darwin" in _sys + +if (IS_WINDOWS and IS_LINUX) or (IS_LINUX and IS_OSX) or (IS_WINDOWS and IS_OSX): + raise EnvironmentError( + "only one of IS_WINDOWS ({}), IS_LINUX ({}) and IS_OSX ({}) ".format(IS_WINDOWS, IS_LINUX, IS_OSX) + + "can be True at the same time " + + '(platform.system() == "{}")'.format(platform.system()) + ) +elif not IS_WINDOWS and not IS_LINUX and not IS_OSX: + raise EnvironmentError("one of IS_WINDOWS, IS_LINUX, IS_OSX has to be True") + +# ############################## What tests to run + TEST_CAN_FD = True + +TEST_INTERFACE_SOCKETCAN = IS_CI and IS_LINUX diff --git a/test/data/example_data.py b/test/data/example_data.py index a66da1f36..b84aa2c4e 100644 --- a/test/data/example_data.py +++ b/test/data/example_data.py @@ -124,5 +124,5 @@ def generate_message(arbitration_id): Generates a new message with the given ID, some random data and a non-extended ID. """ - data = bytes([random.randrange(0, 2 ** 8 - 1) for _ in range(8)]) + data = bytearray([random.randrange(0, 2 ** 8 - 1) for _ in range(8)]) return Message(arbitration_id=arbitration_id, data=data, extended_id=False) diff --git a/test/listener_test.py b/test/listener_test.py index e9495c066..f13a5f17a 100755 --- a/test/listener_test.py +++ b/test/listener_test.py @@ -41,6 +41,7 @@ def testClassesImportable(self): self.assertTrue(hasattr(can, 'BLFReader')) self.assertTrue(hasattr(can, 'BLFWriter')) + self.assertTrue(hasattr(can, 'CSVReader')) self.assertTrue(hasattr(can, 'CSVWriter')) self.assertTrue(hasattr(can, 'CanutilsLogWriter')) @@ -73,23 +74,46 @@ def testBasicListenerCanBeAddedToNotifier(self): notifier.stop() self.assertIn(a_listener, notifier.listeners) - def testLogger(self): + def testPlayerTypeResolution(self): + def test_filetype_to_instance(extension, klass): + can_player = can.LogReader("test.{}".format(extension)) + self.assertIsInstance(can_player, klass) + if hasattr(can_player, "stop"): + can_player.stop() + + test_filetype_to_instance("asc", can.ASCReader) + test_filetype_to_instance("blf", can.BLFReader) + test_filetype_to_instance("csv", can.CSVReader) + test_filetype_to_instance("db" , can.SqliteReader) + test_filetype_to_instance("log", can.CanutilsLogReader) + + # test file extensions that are not supported + with self.assertRaisesRegexp(NotImplementedError, "xyz_42"): + test_filetype_to_instance("xyz_42", can.Printer) + with self.assertRaises(BaseException): + test_filetype_to_instance(None, can.Printer) + + def testLoggerTypeResolution(self): def test_filetype_to_instance(extension, klass): can_logger = can.Logger("test.{}".format(extension)) self.assertIsInstance(can_logger, klass) can_logger.stop() - test_filetype_to_instance('asc', can.ASCWriter) - test_filetype_to_instance('log', can.CanutilsLogWriter) + test_filetype_to_instance("asc", can.ASCWriter) test_filetype_to_instance("blf", can.BLFWriter) test_filetype_to_instance("csv", can.CSVWriter) - test_filetype_to_instance("db", can.SqliteWriter) + test_filetype_to_instance("db" , can.SqliteWriter) + test_filetype_to_instance("log", can.CanutilsLogWriter) test_filetype_to_instance("txt", can.Printer) + # test file extensions that should usa a fallback + test_filetype_to_instance(None, can.Printer) + test_filetype_to_instance("some_unknown_extention_42", can.Printer) + def testBufferedListenerReceives(self): a_listener = can.BufferedReader() a_listener(generate_message(0xDADADA)) - m = a_listener.get_message(0.2) + m = a_listener.get_message(0.1) self.assertIsNotNone(m) diff --git a/test/network_test.py b/test/network_test.py index ae6c9a81a..830adceca 100644 --- a/test/network_test.py +++ b/test/network_test.py @@ -58,7 +58,7 @@ def producer(self, ready_event, msg_read): extended_id=self.extended_flags[i], data=self.data[i] ) - logging.debug("writing message: {}".format(m)) + #logging.debug("writing message: {}".format(m)) if msg_read is not None: # Don't send until the other thread is ready msg_read.wait() @@ -92,15 +92,14 @@ def testProducerConsumer(self): print("No messages... lets go") break else: - print("received messages before the test has started...") - self.assertTrue(False) + self.fail("received messages before the test has started ...") ready.set() i = 0 while i < self.num_messages: msg_read.set() msg = self.server_bus.recv(timeout=0.5) self.assertIsNotNone(msg, "Didn't receive a message") - logging.debug("Received message {} with data: {}".format(i, msg.data)) + #logging.debug("Received message {} with data: {}".format(i, msg.data)) self.assertEqual(msg.id_type, self.extended_flags[i]) if not msg.is_remote_frame: diff --git a/test/open_vcan.sh b/test/open_vcan.sh new file mode 100755 index 000000000..0e9ba95c1 --- /dev/null +++ b/test/open_vcan.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +# Used by .travis.yml (which is executed with sudo privileges) + +modprobe vcan +ip link add dev vcan0 type vcan +ip link set up vcan0 diff --git a/test/simplecyclic_test.py b/test/simplecyclic_test.py index f4ab7cab2..8763174a8 100644 --- a/test/simplecyclic_test.py +++ b/test/simplecyclic_test.py @@ -18,17 +18,17 @@ class SimpleCyclicSendTaskTest(unittest.TestCase): @unittest.skipIf(IS_CI, "the timing sensitive behaviour cannot be reproduced reliably on a CI server") def test_cycle_time(self): - msg = can.Message(extended_id=False, arbitration_id=0x100, data=[0,1,2,3,4,5,6,7]) + msg = can.Message(extended_id=False, arbitration_id=0x123, data=[0,1,2,3,4,5,6,7]) bus1 = can.interface.Bus(bustype='virtual') bus2 = can.interface.Bus(bustype='virtual') task = bus1.send_periodic(msg, 0.01, 1) self.assertIsInstance(task, can.broadcastmanager.CyclicSendTaskABC) - sleep(5) + sleep(2) size = bus2.queue.qsize() # About 100 messages should have been transmitted - self.assertTrue(90 < size < 110, - '100 +/- 10 messages should have been transmitted. But queue contained {}'.format(size)) + self.assertTrue(80 <= size <= 120, + '100 +/- 20 messages should have been transmitted. But queue contained {}'.format(size)) last_msg = bus2.recv() self.assertEqual(last_msg, msg)