Ben Bass avatar Ben Bass committed 776ec41

test updates and refactoring

Comments (0)

Files changed (8)

 pylibftdi changes
 =================
 
+0.8.1
+ * fix issue with bitbang following API changes in 0.8
+ * add tests for bitbang mode
+ * refactor tests; fix text-based tests in Python3
+ * slight refactor Device() to improve testability (_read and _write methods)
 0.8
  * added some unit tests
  * API changes:

pylibftdi/__init__.py

 rather than a problem with the libftdi library.
 """
 
-__VERSION__ = "0.8"
+__VERSION__ = "0.8.1"
 __AUTHOR__ = "Ben Bass"
 
 

pylibftdi/_base.py

 class FtdiError(Exception):
     pass
 
-
-

pylibftdi/driver.py

         if result == 0:
             self._baudrate = value
 
+    def _read(self, length):
+        """
+        actually do the low level reading
+
+        returns a 'bytes' object
+        """
+        buf = create_string_buffer(length)
+        rlen = self.fdll.ftdi_read_data(byref(self.ctx), byref(buf), length)
+        if rlen == -1:
+            raise FtdiError(self.get_error_string())
+        byte_data = buf.raw[:rlen]
+
+        return byte_data
 
     def read(self, length):
         """
         if not self._opened:
             raise FtdiError("read() on closed Device")
 
-        buf = create_string_buffer(length)
-        rlen = self.fdll.ftdi_read_data(byref(self.ctx), byref(buf), length)
-        if rlen == -1:
-            raise FtdiError(self.get_error_string())
-        byte_data = buf.raw[:rlen]
+        # read the data
+        byte_data = self._read(length)
         if self.mode == 'b':
             return byte_data
         else:
             # buffer the remainder for next time.
             return byte_data.decode(self.encoding)
 
+    def _write(self, byte_data):
+        """
+        actually do the low level writing
+        """
+        buf = create_string_buffer(byte_data)
+        written = self.fdll.ftdi_write_data(byref(self.ctx),
+                                            byref(buf), len(byte_data))
+        return written
+
     def write(self, data):
         """
         write(data) -> count of bytes actually written
         except TypeError:
             # this will happen if we are Python3 and data is a str.
             byte_data = data.encode(self.encoding)
-        buf = create_string_buffer(byte_data)
-        written = self.fdll.ftdi_write_data(byref(self.ctx),
-                                            byref(buf), len(data))
+
+        # actually write it
+        written = self._write(byte_data)
         if written == -1:
             raise FtdiError(self.get_error_string())
         return written
     def readline(self, size=0):
         """
         readline() for file-like compatibility.
+
+        This only works for mode='t' on Python3
         """
         lsl = len(os.linesep)
         line_buffer = []

pylibftdi/test_bitbang.py

+"""
+pylibftdi - python wrapper for libftdi
+
+Copyright (c) 2010-2011 Ben Bass <benbass@codedstructure.net>
+See LICENSE file for details and (absence of) warranty
+
+pylibftdi: http://bitbucket.org/codedstructure/pylibftdi
+
+This module contains some basic tests for the higher-level
+functionality without requiring an actual hardware device
+to be attached.
+"""
+
+from test_common import (LoopDevice, CallCheckMixin, unittest)
+from pylibftdi.bitbang import BitBangDevice
+from pylibftdi import FtdiError
+
+class TestBitBangDevice(BitBangDevice, LoopDevice):
+    pass
+
+BitBangDevice = TestBitBangDevice
+
+# and now some test cases...
+class BitBangFunctions(unittest.TestCase, CallCheckMixin):
+
+    def testContextManager(self):
+        def _():
+            with BitBangDevice() as dev:
+                pass
+        self.assertCallsExact(_,
+                ['ftdi_init', 'ftdi_usb_open',
+                 'ftdi_set_bitmode',
+                 'ftdi_usb_close', 'ftdi_deinit'])
+
+    def testOpen(self):
+        """
+        check same opening things as a normal Device still work
+        for BitBangDevice
+        """
+        # a lazy_open open() shouldn't do anything
+        self.assertCallsExact(lambda: BitBangDevice(lazy_open=True), [])
+        # a non-lazy_open open() should open the port...
+        self.assertCalls(lambda: BitBangDevice(), 'ftdi_usb_open')
+        # and set the bit mode
+        self.assertCalls(lambda: BitBangDevice(), 'ftdi_set_bitmode')
+        # and given a device_id, it should do a open_desc
+        self.assertCalls(lambda: BitBangDevice('bogus'), 'ftdi_usb_open_desc')
+
+    def testInitDirection(self):
+        # check that a direction can be given on open and is honoured
+        for dir_test in (0,1,4,12,120,255):
+
+            dev = BitBangDevice(direction = dir_test)
+            self.assertEqual(dev.direction, dir_test)
+            self.assertEqual(dev._direction, dir_test)
+            self.assertEqual(dev._last_set_dir, dir_test)
+        # check an invalid direction on open gives error
+        self.assertRaises(FtdiError, lambda: BitBangDevice(direction=300))
+
+    def testDirection(self):
+        dev = BitBangDevice()
+        # check that a direction can be given on open and is honoured
+        for dir_test in (0,1,4,12,120,255):
+            def _(dt):
+                dev.direction = dt
+            self.assertCalls(lambda : _(dir_test), 'ftdi_set_bitmode')
+            self.assertEqual(dev.direction, dir_test)
+            self.assertEqual(dev._direction, dir_test)
+            self.assertEqual(dev._last_set_dir, dir_test)
+        # check an invalid direction on open gives error
+        def _():
+            dev.direction = 300
+        self.assertRaises(FtdiError, _)
+
+    def testPort(self):
+        dev = BitBangDevice()
+        # check that a direction can be given on open and is honoured
+        for port_test in (0,1,4,12,120,255):
+            def _(pt):
+                dev.port = pt
+            self.assertCalls(lambda : _(port_test), 'ftdi_write_data')
+            self.assertEqual(dev._latch, port_test)
+            self.assertEqual(dev.port, port_test)
+        # XXX: this is incomplete.
+        # could check for various directions and how that impacts
+        # port read / write, as well as r/m/w operations.
+
+if __name__ == "__main__":
+    unittest.main()
+

pylibftdi/test_common.py

+"""
+pylibftdi - python wrapper for libftdi
+
+Copyright (c) 2010-2011 Ben Bass <benbass@codedstructure.net>
+See LICENSE file for details and (absence of) warranty
+
+pylibftdi: http://bitbucket.org/codedstructure/pylibftdi
+
+This module contains some basic tests for the higher-level
+functionality without requiring an actual hardware device
+to be attached.
+"""
+
+import sys
+if sys.version_info < (2,7):
+    try:
+        import unittest2 as unittest
+    except ImportError:
+        raise SystemExit("The test functionality is only supported in"
+                "Python 2.7+ unless unittest2 is installed")
+else:
+    import unittest
+
+VERBOSE = False
+
+class SimpleMock(object):
+    """
+    This is a simple mock plugin for fdll which logs any calls
+    made through it to fn_log, which is currently rather ugly
+    global state.
+    """
+    def __init__(self, name="<base>"):
+        self.__name = name
+
+    def __getattr__(self, key):
+        return SimpleMock(key)
+
+    def __call__(self, *o, **k):
+        CallLog.append(self.__name)
+        if VERBOSE:
+            print("%s(*%s, **%s)" % (self.__name, o, k))
+        return 0
+
+class CallLog(object):
+
+    fn_log = []
+
+    @classmethod
+    def reset(cls):
+        del cls.fn_log[:]
+
+    @classmethod
+    def append(cls, value):
+        cls.fn_log.append(value)
+
+    @classmethod
+    def get(cls):
+        return cls.fn_log[:]
+
+class CallCheckMixin(object):
+    """
+    this should be used as a mixin for unittest.TestCase classes,
+    where it allows the calls through the MockDriver to be checked
+    this does not support multi-threading.
+    """
+    def assertCalls(self, fn, methodname):
+        CallLog.reset()
+        fn()
+        self.assertIn(methodname, CallLog.get())
+
+    def assertCallsExact(self, fn, call_list):
+        CallLog.reset()
+        fn()
+        self.assertEqual(call_list, CallLog.get())
+
+
+# monkey patch the Driver class to be the mock thing above.
+class MockDriver(object):
+    def __init__(self, *o, **k):
+        self.fdll = SimpleMock()
+
+
+import pylibftdi.driver
+from pylibftdi.driver import Device
+
+class LoopDevice(Device):
+    """
+    a mock device object which overrides read and write
+    to operate as an unbounded loopback pair
+    """
+    def __init__(self, *o, **k):
+        super(LoopDevice, self).__init__(*o, **k)
+        self.__buffer = []
+
+    def _read(self, size):
+        super(LoopDevice, self)._read(size)  # discard result
+        result = bytes(bytearray(self.__buffer[:size]))
+        self.__buffer = self.__buffer[size:]
+        return result
+
+    def _write(self, data):
+        super(LoopDevice, self)._write(data)  # discard result
+        self.__buffer.extend(bytearray(data))
+        return len(data)
+
+# importing this _does_ things...
+pylibftdi.driver.Driver = MockDriver
+
+if set(['-v', '--verbose']) & set(sys.argv):
+    VERBOSE = True

pylibftdi/test_driver.py

 to be attached.
 """
 
-import sys
-if sys.version_info < (2,7):
-    try:
-        import unittest2 as unittest
-    except ImportError:
-        raise SystemExit("The test functionality is only supported in"
-                "Python 2.7+ unless unittest2 is installed")
-else:
-    import unittest
-
-VERBOSE = False
-
-fn_log = []
-class SimpleMock(object):
-    """
-    This is a simple mock plugin for fdll which logs any calls
-    made through it to fn_log, which is currently rather ugly
-    global state.
-    """
-    def __init__(self, name="<base>"):
-        self.__name = name
-
-    def __getattr__(self, key):
-        return SimpleMock(key)
-
-    def __call__(self, *o, **k):
-        fn_log.append(self.__name)
-        if VERBOSE:
-            print("%s(*%s, **%s)" % (self.__name, o, k))
-        return 0
-
-def get_calls(fn):
-    "return the called function names which the fdll mock object made"
-    del fn_log[:]
-    fn()
-    return fn_log
-
-
-
-# monkey patch the Driver class to be the mock thing above.
-class MockDriver(object):
-    def __init__(self, *o, **k):
-        self.fdll = SimpleMock()
-
-
-import pylibftdi.driver
-from pylibftdi.driver import Device
-
-class LoopDevice(Device):
-    """
-    a mock device object which overrides read and write
-    to operate as an unbounded loopback pair
-    """
-    def __init__(self, *o, **k):
-        Device.__init__(self, *o, **k)
-        self.__buffer = []
-
-    def read(self, size=None):
-        Device.read(self, size)
-        if size is None:
-            result = ''.join(self.__buffer)
-            self.__buffer = []
-        else:
-            result = ''.join(self.__buffer[:size])
-            self.__buffer = self.__buffer[size:]
-        return result
-
-    def write(self, data):
-        Device.write(self, data)
-        self.__buffer.extend(list(data))
-        return len(data)
-
+from test_common import (LoopDevice, Device, CallCheckMixin, unittest)
 
 # and now some test cases...
 
-class DeviceFunctions(unittest.TestCase):
-
-    def setUp(self):
-        pylibftdi.driver.Driver = MockDriver
-
-    def assertCalls(self, fn, methodname):
-        del fn_log[:]
-        fn()
-        self.assertIn(methodname, fn_log)
+class DeviceFunctions(unittest.TestCase, CallCheckMixin):
 
     def testContextManager(self):
         def _():
             with Device() as dev:
                 pass
-        self.assertEqual(get_calls(_),
+        self.assertCallsExact(_,
                 ['ftdi_init', 'ftdi_usb_open',
                  'ftdi_usb_close', 'ftdi_deinit'])
 
     def testOpen(self):
         # a lazy_open open() shouldn't do anything
-        self.assertEqual(get_calls(lambda: Device(lazy_open=True)), [])
+        self.assertCallsExact(lambda: Device(lazy_open=True), [])
         # a non-lazy_open open() should open the port...
         self.assertCalls(lambda: Device(), 'ftdi_usb_open')
         # and given a device_id, it should do a open_desc
             self.assertCalls(dev.flush, 'ftdi_usb_purge_buffers')
 
 class LoopbackTest(unittest.TestCase):
-
-    def setUp(self):
-        pylibftdi.driver.Driver = MockDriver
+    """
+    these all require mode='t' to pass in Python3
+    """
 
     def testPrint(self):
-        d = LoopDevice()
+        d = LoopDevice(mode='t')
         d.write('Hello')
         d.write(' World\n')
         d.write('Bye')
         self.assertEqual(d.readline(), 'Bye')
 
     def testLines(self):
-        d = LoopDevice()
+        d = LoopDevice(mode='t')
         lines = ['Hello\n', 'World\n', 'And\n', 'Goodbye\n']
         d.writelines(lines)
         self.assertEqual(d.readlines(), lines)
 
     def testIterate(self):
-        d = LoopDevice()
+        d = LoopDevice(mode='t')
         lines = ['Hello\n', 'World\n', 'And\n', 'Goodbye\n']
         d.writelines(lines)
         for idx,line in enumerate(d):
 
 
 if __name__ == "__main__":
-    if set(['-v', '--verbose']) & set(sys.argv):
-        VERBOSE = True
     unittest.main()
 
 setup(
     name="pylibftdi",
-    version="0.8",
+    version="0.8.1",
     description="Pythonic interface to FTDI devices using libftdi",
     long_description=open('README.txt').read(),
     author="Ben Bass",
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.