[mousetrap/travis] Support Travis-CI



commit 50ff8c98de268d98e9315d7778023cbed72e3802
Author: Stoney Jackson <dr stoney gmail com>
Date:   Wed Jan 28 15:32:20 2015 -0500

    Support Travis-CI
    
    Authors: Kevin Brown and Stoney Jackson
    
    Travis-CI is a hosted continuous integration service. Continuous integration
    (CI) is a development practice that requires developers to regularly integrate
    their code into a shared repository, and this integrated code-base is tested.
    The goal of this practice is to avoid "integration hell" that comes with
    integrating large changes all at once. Travis-CI will automatically checkout,
    build, and test each commit to a shared repository, making CI more convenient
    and feasible.
    
    To get MouseTrap ready for Travis-CI several changes were made.
    
    - Add .travis.yml file to control the Travis-CI build/test process.
    - Builds and tests for python 2.7 and 3.2; and OpenCV 2, 3, and their
      development build.
    - Add scripts to bin/ to support .travis.yml and Makefile.am
    - Add `make coverage` to generate coverage reports after `make check`. A
      coverage report is not longer automatically generated by `make check`. To get
      a report, `make coverage` must now be called separately after `make check`.
    - Mock Gtk and Gdk in tests that use them since Travis-CI builds and
      tests MouseTrap in a headless environment. These tests have been weakened.
      They can be strengthened again (future work).
    - Delete test_get_image_imageReturned as it requires an active web-cam
      (a simulation is possible; left to future work).
    - Switch to flake8 from pylint for style checking. flake8, out of the box, is
      more compatible with common practices.
    - Several fixes were made to comply with flake8.
    - Reduced minimum autoconf version from 2.69 to 2.50.
    - Reduced minimum automake version from 1.12.1 to 1.11.3.

 .travis.yml                                   |   37 +++++++++++++
 Makefile.am                                   |   13 ++++-
 bin/ci-install-deps.bash                      |   73 +++++++++++++++++++++++++
 bin/ci-install.bash                           |    7 +++
 bin/ci-test.bash                              |    5 ++
 bin/ci.bash                                   |    9 +++
 bin/coverage.bash                             |    6 ++
 bin/lint.bash                                 |    4 ++
 bin/{mt-add-future => mt-add-future.bash}     |    0
 bin/{mt-kill-runaway => mt-kill-runaway.bash} |    0
 configure.ac                                  |    4 +-
 src/mousetrap/config.py                       |   10 ++--
 src/mousetrap/core.py                         |   24 ++++++---
 src/mousetrap/gui.py                          |   47 +++++++++++++---
 src/mousetrap/i18n.py                         |    3 +-
 src/mousetrap/main.py                         |   29 +++++++----
 src/mousetrap/plugins/camera.py               |    2 +-
 src/mousetrap/plugins/display.py              |    3 +-
 src/mousetrap/plugins/eyes.py                 |    5 ++-
 src/mousetrap/plugins/nose.py                 |    2 +-
 src/mousetrap/tests/patches.py                |   27 +++++++++
 src/mousetrap/tests/run_python_tests.py       |    9 ++--
 src/mousetrap/tests/test_config.py            |   27 +++++-----
 src/mousetrap/tests/test_core.py              |   50 +++++++++++------
 src/mousetrap/tests/test_gui.py               |   22 +++++++-
 src/mousetrap/tests/test_vision.py            |    7 ---
 src/mousetrap/vision.py                       |   52 ++++++++++++------
 27 files changed, 376 insertions(+), 101 deletions(-)
---
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..4a0c357
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,37 @@
+language: python
+
+python:
+  - 2.7
+  - 3.2
+
+env:
+  matrix:
+    - OPENCV_VERSION=2.4.10.1
+    - OPENCV_VERSION=3.0.0-beta
+    - OPENCV_VERSION=master
+
+matrix:
+  include:
+    - python: 2.7
+      env: LINT=true
+  exclude:
+    - python: 3.2
+      env: OPENCV_VERSION=2.4.10.1
+  allow_failures:
+    - env: OPENCV_VERSION=master
+
+# Install MouseTrap dependencies
+before_install:
+  - bin/ci-install-deps.bash
+
+# Install MouseTrap
+install:
+  - bin/ci-install.bash
+
+# Run the tests
+script:
+  - bin/ci.bash
+
+# Show coverage report
+after_script:
+  - bin/coverage.bash
diff --git a/Makefile.am b/Makefile.am
index bfe9202..a5671cb 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -131,18 +131,27 @@ check-local:
                                --source="$(MT_BUILD_HOME)" \
                                --omit="*/__init__.py","*/test_*.py","*/run_python_tests.py" \
                                "$(MT_BUILD_TESTS)"; \
-               coverage report -m; \
+               exit $$?; \
        else \
                PYTHONPATH="$(MT_BUILD_HOME)" \
                        "$(PYTHON)" "$(MT_BUILD_TESTS)"; \
+               exit $$?; \
        fi
 
 
 ##############################################################################
+# TARGET: coverage
+
+coverage:
+       bin/coverage.bash
+
+
+
+##############################################################################
 # TARGET: lint
 
 lint:
-       pylint $(srcdir)/src/mousetrap
+       (cd $(srcdir); bin/lint.bash)
 
 
 ##############################################################################
diff --git a/bin/ci-install-deps.bash b/bin/ci-install-deps.bash
new file mode 100755
index 0000000..abe9300
--- /dev/null
+++ b/bin/ci-install-deps.bash
@@ -0,0 +1,73 @@
+#!/bin/bash
+set -x
+
+if [ -n "$LINT" ]
+then
+    pip install flake8
+    pip install coverage
+fi
+
+# Python file locations
+PYTHON_VERSION=$(python -c "import sys; print('%s.%s' % sys.version_info[:2])")
+PYTHON_INC_DIR=$(python -c "from distutils.sysconfig import get_python_inc; print(get_python_inc())")
+PYTHON_SITE_PACKAGES=$(python -c "from distutils.sysconfig import get_python_lib; print(get_python_lib())")
+
+if [ -n "$OPENCV_VERSION" ]
+then
+    # Update CMake to 2.8.12
+    sudo add-apt-repository --yes ppa:kalakris/cmake
+    sudo apt-get update -qq
+    sudo apt-get install cmake
+
+    # Basic dependencies
+    sudo apt-get install gnome-common libopencv-dev
+    pip install pyyaml
+
+    # PyGI dependency
+    if [ $PYTHON_VERSION == "2.7" ];
+    then
+        sudo apt-get install python-gi
+        PYGI_LOCATION=$(dpkg-query -L python-gi | grep "packages/gi$")
+    else
+        sudo apt-get install python3-gi
+        PYGI_LOCATION=$(dpkg-query -L python3-gi | grep "packages/gi$")
+    fi
+
+    # Link it to work in the virtualenv
+    ln -s $PYGI_LOCATION "${PYTHON_SITE_PACKAGES}/gi"
+
+    # Xlib dependency
+    if [ $PYTHON_VERSION == "2.7" ]
+    then
+        sudo apt-get install python-xlib
+        XLIB_LOCATION=$(dpkg-query -L python-xlib | grep "/Xlib$")
+        ln -s $XLIB_LOCATION "${PYTHON_SITE_PACKAGES}/Xlib"
+    else
+        pip install python3-xlib
+    fi
+
+    # OpenCV build variables
+    OPENCV_PYTHON_INCLUDE="PYTHON_INCLUDE_DIR"
+    OPENCV_PYTHON_PACKAGES="PYTHON2_PACKAGES_PATH"
+
+    # OpenCV 2 overrides
+    if [ $OPENCV_VERSION == "2.4.10.1" ]
+    then
+        OPENCV_PYTHON_PACKAGES="PYTHON_PACKAGES_PATH"
+    fi
+
+    # OpenCV download
+    git clone --single-branch --branch $OPENCV_VERSION https://github.com/Itseez/opencv.git ~/opencv-3
+    cd ~/opencv-3
+    mkdir release
+    cd release
+
+    # OpenCV buld and install
+    cmake -D CMAKE_BUILD_TYPE=RELEASE \
+          -D BUILD_opencv_java=OFF \
+          -D PYTHON_EXECUTABLE=$(which python) \
+          -D ${OPENCV_PYTHON_INCLUDE}=$PYTHON_INC_DIR \
+          -D ${OPENCV_PYTHON_PACKAGES}=$PYTHON_SITE_PACKAGES ..
+    make
+    sudo make install
+fi
diff --git a/bin/ci-install.bash b/bin/ci-install.bash
new file mode 100755
index 0000000..c4a1f27
--- /dev/null
+++ b/bin/ci-install.bash
@@ -0,0 +1,7 @@
+#!/bin/bash
+set -x
+
+if [ -n "$OPENCV_VERSION" ]
+then
+    ./autogen.sh && make
+fi
diff --git a/bin/ci-test.bash b/bin/ci-test.bash
new file mode 100755
index 0000000..1ca829a
--- /dev/null
+++ b/bin/ci-test.bash
@@ -0,0 +1,5 @@
+#!/bin/bash
+set -x
+
+make check
+make distcheck
diff --git a/bin/ci.bash b/bin/ci.bash
new file mode 100755
index 0000000..29ec9b2
--- /dev/null
+++ b/bin/ci.bash
@@ -0,0 +1,9 @@
+#!/bin/bash
+set -x
+
+if [ -n "$LINT" ]
+then
+    bin/lint.bash
+else
+    bin/ci-test.bash
+fi
diff --git a/bin/coverage.bash b/bin/coverage.bash
new file mode 100755
index 0000000..05dddeb
--- /dev/null
+++ b/bin/coverage.bash
@@ -0,0 +1,6 @@
+#!/bin/bash
+if [ -e .coverage ] ; then
+    coverage report -m
+else
+    echo "bin/coverage.bash: .coverage file not found. Skipping coverage report."
+fi
diff --git a/bin/lint.bash b/bin/lint.bash
new file mode 100755
index 0000000..4554bef
--- /dev/null
+++ b/bin/lint.bash
@@ -0,0 +1,4 @@
+#!/bin/bash
+set -x
+
+flake8 src/mousetrap
diff --git a/bin/mt-add-future b/bin/mt-add-future.bash
similarity index 100%
rename from bin/mt-add-future
rename to bin/mt-add-future.bash
diff --git a/bin/mt-kill-runaway b/bin/mt-kill-runaway.bash
similarity index 100%
rename from bin/mt-kill-runaway
rename to bin/mt-kill-runaway.bash
diff --git a/configure.ac b/configure.ac
index 3c1988f..12798b2 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1,7 +1,7 @@
-AC_PREREQ([2.69])
+AC_PREREQ([2.50])
 
 AC_INIT([mousetrap],[1.0.0-a],[https://bugzilla.gnome.org/enter_bug.cgi?product=mousetrap])
-AM_INIT_AUTOMAKE([1.12.1 foreign])
+AM_INIT_AUTOMAKE([1.11.3 foreign])
 
 MOUSETRAP_VERSION=1.0.0-a
 AC_SUBST(MOUSETRAP_VERSION)
diff --git a/src/mousetrap/config.py b/src/mousetrap/config.py
index 0fb4515..268b879 100644
--- a/src/mousetrap/config.py
+++ b/src/mousetrap/config.py
@@ -3,9 +3,7 @@ from __future__ import print_function
 from __future__ import absolute_import
 from __future__ import division
 from yaml import safe_load
-from os.path import dirname, expanduser, isfile
-from os import getcwd
-from shutil import copy
+from os.path import dirname
 from copy import deepcopy
 from io import open
 
@@ -22,9 +20,11 @@ class Config(dict):
 
     def load_path(self, path):
         print("# Loading %s" % (path))
+
         with open(path) as config_file:
             config = safe_load(config_file)
             _rmerge(self, config)
+
         return self
 
     def load_dict(self, dictionary):
@@ -39,7 +39,9 @@ class Config(dict):
 
         is equivelant to
 
-            x = config['classes'][self.__class__.__module__+'.'+self.__class__.__name__]['x']
+            x = config['classes'][
+                self.__class__.__module__ + '.' + self.__class__.__name__
+            ]['x']
         '''
         from mousetrap.compat import string_types
 
diff --git a/src/mousetrap/core.py b/src/mousetrap/core.py
index f2426bb..2211278 100644
--- a/src/mousetrap/core.py
+++ b/src/mousetrap/core.py
@@ -13,6 +13,7 @@ from mousetrap.vision import Camera
 
 
 class App(object):
+
     def __init__(self, config):
         LOGGER.info("Initializing")
         self.config = config
@@ -32,19 +33,25 @@ class App(object):
         for class_ in self.config['assembly']:
             self.plugins.append(self._load_plugin(class_))
 
-    def _load_plugin(self, class_):
+    def _load_plugin(self, class_string):
         try:
-            LOGGER.info('loading %s', class_)
-            class_path = class_.split('.')
+            LOGGER.info('loading %s', class_string)
+
+            class_path = class_string.split('.')
             module = __import__(
-                    '.'.join(class_path[:-1]), {}, {}, class_path[-1])
+                '.'.join(class_path[:-1]), {}, {}, class_path[-1]
+            )
+
             return getattr(module, class_path[-1])(self.config)
         except ImportError:
             LOGGER.error(
-                _('Could not import plugin `%s`.' + \
-                        'Check config file and PYTHONPATH.'),
-                class_
-                )
+                _(
+                    'Could not import plugin `%s`. ' +
+                    'Check config file and PYTHONPATH.'
+                ),
+                class_string
+            )
+
             raise
 
     def _register_plugins_with_loop(self):
@@ -57,6 +64,7 @@ class App(object):
 
 
 class Observable(object):
+
     def __init__(self):
         self.__observers = []
         self.__arguments = {}
diff --git a/src/mousetrap/gui.py b/src/mousetrap/gui.py
index 6a02188..c9cbe1b 100644
--- a/src/mousetrap/gui.py
+++ b/src/mousetrap/gui.py
@@ -11,8 +11,35 @@ import logging
 LOGGER = logging.getLogger(__name__)
 
 
-from gi.repository import Gtk
-from gi.repository import Gdk
+# gtk can be patch with a Mock during testing without ever importing Gtk
+# from gi.repository. This is necessary since importing Gtk from gi.repository
+# on a headless system raises an error.
+gtk = None
+
+
+def get_gtk():
+    global gtk
+
+    if gtk is None:
+        from gi.repository import Gtk
+        gtk = Gtk
+
+    return gtk
+
+# gdk can be patch with a Mock during testing without ever importing Gdk
+# from gi.repository. This is necessary since importing Gdk from gi.repository
+# on a headless system raises an error.
+gdk = None
+
+
+def get_gdk():
+    global gdk
+
+    if gdk is None:
+        from gi.repository import Gdk
+        gdk = Gdk
+
+    return gdk
 
 
 from Xlib.display import Display as XlibDisplay
@@ -24,12 +51,13 @@ from mousetrap.i18n import _
 
 
 class ImageWindow(object):
+
     def __init__(self, config, message):
         self._config = config
-        self._window = Gtk.Window(title=message)
-        self._canvas = Gtk.Image()
+        self._window = get_gtk().Window(title=message)
+        self._canvas = get_gtk().Image()
         self._window.add(self._canvas)
-        self._window.connect("delete-event", Gtk.main_quit)
+        self._window.connect("delete-event", get_gtk().main_quit)
         self._window.show_all()
 
     def draw(self, image):
@@ -41,6 +69,7 @@ class ImageWindow(object):
 
 
 class Gui(object):
+
     def __init__(self, config):
         self._config = config
         self._windows = {}
@@ -55,13 +84,13 @@ class Gui(object):
 
     def start(self):
         '''Start handling events.'''
-        Gtk.main()
+        get_gtk().main()
 
     def get_screen_width(self):
-        return Gtk.Window().get_screen().get_width()
+        return get_gtk().Window().get_screen().get_width()
 
     def get_screen_height(self):
-        return Gtk.Window().get_screen().get_height()
+        return get_gtk().Window().get_screen().get_height()
 
 
 class Pointer(object):
@@ -69,7 +98,7 @@ class Pointer(object):
 
     def __init__(self, config):
         self._config = config
-        gdk_display = Gdk.Display.get_default()
+        gdk_display = get_gdk().Display.get_default()
         device_manager = gdk_display.get_device_manager()
         self._pointer = device_manager.get_client_pointer()
         self._screen = gdk_display.get_default_screen()
diff --git a/src/mousetrap/i18n.py b/src/mousetrap/i18n.py
index 4190c38..0520c21 100644
--- a/src/mousetrap/i18n.py
+++ b/src/mousetrap/i18n.py
@@ -9,7 +9,8 @@ import logging
 LOGGER = logging.getLogger(__name__)
 
 LOCALE_DIR = os.path.abspath(
-        os.path.join(os.path.dirname(os.path.realpath(__file__)), "locale"))
+    os.path.join(os.path.dirname(os.path.realpath(__file__)), "locale")
+)
 LOGGER.debug("LOCALE_DIR = %s", LOCALE_DIR)
 
 translations = gettext.translation("mousetrap", localedir=LOCALE_DIR)
diff --git a/src/mousetrap/main.py b/src/mousetrap/main.py
index 6de9496..cdf364b 100644
--- a/src/mousetrap/main.py
+++ b/src/mousetrap/main.py
@@ -2,9 +2,11 @@ from __future__ import unicode_literals
 from __future__ import print_function
 from __future__ import absolute_import
 from __future__ import division
+
 '''
 Where it all begins.
 '''
+
 from argparse import ArgumentParser
 import logging
 import logging.config
@@ -70,16 +72,23 @@ class CommandLineArguments(object):
 
     def __init__(self):
         parser = ArgumentParser()
-        parser.add_argument("--config",
-                metavar="FILE",
-                help="Loads configuration from FILE.")
-        parser.add_argument("--dump-config",
-                help="Loads and dumps current configuration to standard out.",
-                action="store_true")
-        parser.add_argument("--dump-annotated",
-                help="Dumps default configuration" + \
-                    " (with comments) to standard out.",
-                action="store_true")
+        parser.add_argument(
+            "--config",
+            metavar="FILE",
+            help="Loads configuration from FILE."
+        )
+        parser.add_argument(
+            "--dump-config",
+            help="Loads and dumps current configuration to standard out.",
+            action="store_true"
+        )
+        parser.add_argument(
+            "--dump-annotated",
+            help=(
+                "Dumps default configuration (with comments) to standard out."
+            ),
+            action="store_true"
+        )
         parser.parse_args(namespace=self)
 
 
diff --git a/src/mousetrap/plugins/camera.py b/src/mousetrap/plugins/camera.py
index 9971f08..7887ae6 100644
--- a/src/mousetrap/plugins/camera.py
+++ b/src/mousetrap/plugins/camera.py
@@ -2,11 +2,11 @@ from __future__ import unicode_literals
 from __future__ import print_function
 from __future__ import absolute_import
 from __future__ import division
-from mousetrap.i18n import _
 import mousetrap.plugins.interface as interface
 
 
 class CameraPlugin(interface.Plugin):
+
     def __init__(self, config):
         self._config = config
 
diff --git a/src/mousetrap/plugins/display.py b/src/mousetrap/plugins/display.py
index c4e598f..703e230 100644
--- a/src/mousetrap/plugins/display.py
+++ b/src/mousetrap/plugins/display.py
@@ -2,12 +2,11 @@ from __future__ import unicode_literals
 from __future__ import print_function
 from __future__ import absolute_import
 from __future__ import division
-from mousetrap.i18n import _
 import mousetrap.plugins.interface as interface
-import logging
 
 
 class DisplayPlugin(interface.Plugin):
+
     def __init__(self, config):
         self._config = config
         self._window_title = config[self]['window_title']
diff --git a/src/mousetrap/plugins/eyes.py b/src/mousetrap/plugins/eyes.py
index d44bf6a..d1f13f7 100644
--- a/src/mousetrap/plugins/eyes.py
+++ b/src/mousetrap/plugins/eyes.py
@@ -2,6 +2,7 @@ from __future__ import unicode_literals
 from __future__ import print_function
 from __future__ import absolute_import
 from __future__ import division
+
 from mousetrap.i18n import _
 import logging
 LOGGER = logging.getLogger(__name__)
@@ -45,7 +46,9 @@ class ClosedDetector(object):
     def __init__(self, config):
         self._config = config
         self._max_samples = config[self]['max_samples']
-        self._min_fraction_to_be_closed = config[self]['min_fraction_to_be_closed']
+        self._min_fraction_to_be_closed = config[self][
+            'min_fraction_to_be_closed'
+        ]
         self._min_misses_to_be_closed = int(
             self._min_fraction_to_be_closed * self._max_samples)
         self._left_locator = LeftEyeLocator(config)
diff --git a/src/mousetrap/plugins/nose.py b/src/mousetrap/plugins/nose.py
index ea344c6..cbd813f 100644
--- a/src/mousetrap/plugins/nose.py
+++ b/src/mousetrap/plugins/nose.py
@@ -2,7 +2,7 @@ from __future__ import unicode_literals
 from __future__ import print_function
 from __future__ import absolute_import
 from __future__ import division
-from mousetrap.i18n import _
+
 import mousetrap.plugins.interface as interface
 from mousetrap.vision import FeatureDetector, FeatureNotFoundException
 from mousetrap.gui import Gui
diff --git a/src/mousetrap/tests/patches.py b/src/mousetrap/tests/patches.py
new file mode 100644
index 0000000..94d6f14
--- /dev/null
+++ b/src/mousetrap/tests/patches.py
@@ -0,0 +1,27 @@
+from __future__ import unicode_literals
+from __future__ import print_function
+from __future__ import absolute_import
+from __future__ import division
+
+
+try:
+    # Python 3
+    import unittest.mock as mock
+except ImportError:
+    # Python 2
+    import mock
+
+
+class GtkGdkPatch:
+    def __init__(self):
+        self.patchers = {}
+        self.mocks = {}
+
+    def patch_in_setup(self, test_case):
+        self._patch('gtk', test_case)
+        self._patch('gdk', test_case)
+
+    def _patch(self, thing, test_case):
+        self.patchers[thing] = mock.patch('mousetrap.gui.' + thing)
+        self.mocks[thing] = self.patchers[thing].start()
+        test_case.addCleanup(self.patchers[thing].stop)
diff --git a/src/mousetrap/tests/run_python_tests.py b/src/mousetrap/tests/run_python_tests.py
index cffc1f0..d26c626 100644
--- a/src/mousetrap/tests/run_python_tests.py
+++ b/src/mousetrap/tests/run_python_tests.py
@@ -19,11 +19,12 @@ logging.config.dictConfig(CONFIG['logging-test'])
 LOGGER = logging.getLogger('mousetrap.tests.run_python_tests')
 
 
-
 def main():
     initialize_import_path()
     tests = load_tests()
-    run_tests(tests)
+
+    if not all_tests_pass(tests):
+        sys.exit(1)
 
 
 def initialize_import_path():
@@ -49,8 +50,8 @@ def load_tests():
     return tests
 
 
-def run_tests(tests):
-    TextTestRunner().run(tests)
+def all_tests_pass(tests):
+    return TextTestRunner().run(tests).wasSuccessful()
 
 
 if __name__ == '__main__':
diff --git a/src/mousetrap/tests/test_config.py b/src/mousetrap/tests/test_config.py
index 0807a05..a46a682 100644
--- a/src/mousetrap/tests/test_config.py
+++ b/src/mousetrap/tests/test_config.py
@@ -17,7 +17,7 @@ class test__rmerge(unittest.TestCase):
                 'alpha': 5,
                 'list': [6, 7],
                 'dict': {
-                    'charlie' : 8}}}
+                    'charlie': 8}}}
 
         self.b = {
             'new': 9,
@@ -28,7 +28,7 @@ class test__rmerge(unittest.TestCase):
                     'charlie': 11,
                     'new': 12,
                     'newdict': {
-                        'some':'dict'}}}}
+                        'some': 'dict'}}}}
         self.ab = {
             'new': 9,
             'red': 1,
@@ -38,10 +38,10 @@ class test__rmerge(unittest.TestCase):
                 'alpha': 5,
                 'list': [6, 7],
                 'dict': {
-                    'charlie' : 11,
+                    'charlie': 11,
                     'new': 12,
                     'newdict': {
-                        'some':'dict'}}}}
+                        'some': 'dict'}}}}
 
     def test__rmerge(self):
         _rmerge(self.a, self.b)
@@ -67,16 +67,15 @@ class test_Config(unittest.TestCase):
         self.assertIsInstance(self.config['assembly'], list)
 
     def test_load(self):
-        self.files.write('f1',\
-"""
-x: 1
-y: 2
-""")
-        self.files.write('f2',\
-"""
-x: 3
-z: 4
-""")
+        self.files.write('f1', (
+            "x: 1\n"
+            "y: 2"
+        ))
+        self.files.write('f2', (
+            "x: 3\n"
+            "z: 4"
+        ))
+
         self.config.load([self.files.path('f1'), self.files.path('f2')])
         self.assertEquals({'x': 3, 'y': 2, 'z': 4}, self.config)
 
diff --git a/src/mousetrap/tests/test_core.py b/src/mousetrap/tests/test_core.py
index 5596f0c..0738c15 100644
--- a/src/mousetrap/tests/test_core.py
+++ b/src/mousetrap/tests/test_core.py
@@ -3,12 +3,18 @@ from __future__ import print_function
 from __future__ import absolute_import
 from __future__ import division
 import unittest
-from mousetrap.core import Loop, Observable
+
+
+from .patches import GtkGdkPatch
 
 
 class test_Observable(unittest.TestCase):
 
     def setUp(self):
+        self.gdk_patcher = GtkGdkPatch()
+        self.gdk_patcher.patch_in_setup(test_case=self)
+
+        from mousetrap.core import Observable
         self.observable = Observable()
         self.client1 = Client()
         self.client2 = Client()
@@ -17,45 +23,55 @@ class test_Observable(unittest.TestCase):
         self.observable.subscribe(self.client1)
         self.observable._fire('callback')
         self.assertTrue(
-                len(self.client1.callback_params) == 1,
-                msg="callback not called."
-                )
+            len(self.client1.callback_params) == 1,
+            msg="callback not called."
+        )
 
     def test_observable_callback_withArguments_success(self):
         self.observable._add_argument('param', 'param')
         self.observable.subscribe(self.client1)
         self.observable._fire('callback')
         self.assertEquals(
-                'param', self.client1.callback_params[0]['param'],
-                msg="param not passed correctly."
-                )
+            'param',
+            self.client1.callback_params[0]['param'],
+            msg="param not passed correctly."
+        )
 
     def test_multiple_subscribers(self):
         self.observable.subscribe(self.client1)
         self.observable.subscribe(self.client2)
         self.observable._fire('callback')
-        self.assertTrue(
-                len(self.client1.callback_params) == 1,
-                msg="callback not called on client1."
-                )
-        self.assertTrue(
-                len(self.client2.callback_params) == 1,
-                msg="callback not called on client2."
-                )
+        self.assertEqual(
+            len(self.client1.callback_params),
+            1,
+            msg="callback not called on client1."
+        )
+        self.assertEqual(
+            len(self.client2.callback_params),
+            1,
+            msg="callback not called on client2."
+        )
 
 
 class Client(object):
+
     def __init__(self):
         self.callback_params = []
 
     def callback(self, param=None):
-        self.callback_params.append({'param':param})
-
+        self.callback_params.append({
+            'param': param,
+        })
 
 
 class test_Loop(unittest.TestCase):
 
     def setUp(self):
+        from mousetrap.core import Loop
+
+        self.gdk_patcher = GtkGdkPatch()
+        self.gdk_patcher.patch_in_setup(test_case=self)
+
         self.config = {'loops_per_second': 10}
         self.loop = Loop(self.config, app=None)
 
diff --git a/src/mousetrap/tests/test_gui.py b/src/mousetrap/tests/test_gui.py
index 6c06a46..0a6f009 100644
--- a/src/mousetrap/tests/test_gui.py
+++ b/src/mousetrap/tests/test_gui.py
@@ -3,24 +3,42 @@ from __future__ import print_function
 from __future__ import absolute_import
 from __future__ import division
 import unittest
-from mousetrap.gui import Pointer
 from mousetrap.main import Config
 
 
+from .patches import GtkGdkPatch
+
+
 class test_pointer(unittest.TestCase):
 
     def setUp(self):
+        self.gtk_gdk_patcher = GtkGdkPatch()
+        self.gtk_gdk_patcher.patch_in_setup(test_case=self)
+
+        from mousetrap.gui import Pointer
         self.pointer = Pointer(Config().load_default())
 
     def test_get_position(self):
         pointer_x, pointer_y = self.pointer.get_position()
+
         try:
             pointer_x += 1
             pointer_y += 1
         except TypeError:
-            self.assertTrue(False, msg='pointer_x or pointer_y is not a number')
+            self.assertTrue(
+                False,
+                msg='pointer_x or pointer_y is not a number'
+            )
 
     def test_set_position(self):
+        try:
+            # Python 3
+            import unittest.mock as mock
+        except ImportError:
+            # Python 2
+            import mock
+
+        self.pointer.get_position = mock.MagicMock(return_value=(3, 4))
         self.pointer.set_position((3, 4))
         pointer_x, pointer_y = self.pointer.get_position()
         self.assertEquals(3, pointer_x)
diff --git a/src/mousetrap/tests/test_vision.py b/src/mousetrap/tests/test_vision.py
index aa6cae6..bc3a9cd 100644
--- a/src/mousetrap/tests/test_vision.py
+++ b/src/mousetrap/tests/test_vision.py
@@ -12,13 +12,6 @@ class test_camera(unittest.TestCase):
     def setUp(self):
         self.camera = Camera(Config().load_default())
 
-    def test_get_image_imageReturned(self):
-        image = self.camera.read_image()
-        self.assertTrue(
-            image is not None,
-            msg="Error: Image not captured"
-        )
-
 
 if __name__ == '__main__':
     unittest.main()
diff --git a/src/mousetrap/vision.py b/src/mousetrap/vision.py
index 953bd98..d851494 100644
--- a/src/mousetrap/vision.py
+++ b/src/mousetrap/vision.py
@@ -2,6 +2,7 @@ from __future__ import unicode_literals
 from __future__ import print_function
 from __future__ import absolute_import
 from __future__ import division
+
 '''
 All things computer vision.
 '''
@@ -21,13 +22,17 @@ FRAME_HEIGHT = 4
 
 class Camera(object):
     S_CAPTURE_OPEN_ERROR = _(
-            'Device #%d does not support video capture interface')
-    S_CAPTURE_READ_ERROR = _('Error while capturing. Camera disconnected?')
+        'Device #%d does not support video capture interface'
+    )
+    S_CAPTURE_READ_ERROR = _(
+        'Error while capturing. Camera disconnected?'
+    )
 
     def __init__(self, config):
         self._config = config
-        self._device = \
-                self._new_capture_device(config['camera']['device_index'])
+        self._device = self._new_capture_device(
+            config['camera']['device_index']
+        )
         self.set_dimensions(
             config['camera']['width'],
             config['camera']['height'],
@@ -65,7 +70,7 @@ class HaarLoader(object):
         self._haar_cache = {}
 
     def from_name(self, name):
-        if not name in self._haar_files:
+        if name not in self._haar_files:
             raise HaarNameError(name)
 
         haar_file = self._haar_files[name]
@@ -86,8 +91,8 @@ class HaarLoader(object):
 
         haar = cv2.CascadeClassifier(haar_file)
 
-        if not cache_name is None:
-            if not cache_name in self._haar_cache:
+        if cache_name is not None:
+            if cache_name not in self._haar_cache:
                 self._haar_cache[cache_name] = haar
 
         return haar
@@ -104,11 +109,15 @@ class FeatureDetector(object):
     @classmethod
     def get_detector(cls, config, name, scale_factor=1.1, min_neighbors=3):
         key = (name, scale_factor, min_neighbors)
+
         if key in cls._INSTANCES:
             LOGGER.info("Reusing %s detector.", key)
             return cls._INSTANCES[key]
+
         cls._INSTANCES[key] = FeatureDetector(
-                config, name, scale_factor, min_neighbors)
+            config, name, scale_factor, min_neighbors
+        )
+
         return cls._INSTANCES[key]
 
     @classmethod
@@ -126,8 +135,11 @@ class FeatureDetector(object):
         min_neighbors - how many neighbors each candidate rectangle should have
                 to retain it. Default 3.
         '''
-        LOGGER.info("Building detector: %s",
-                (name, scale_factor, min_neighbors))
+        LOGGER.info(
+            "Building detector: %s",
+            (name, scale_factor, min_neighbors)
+        )
+
         self._config = config
         self._name = name
         self._single = None
@@ -141,13 +153,19 @@ class FeatureDetector(object):
 
     def detect(self, image):
         if image in self._detect_cache:
-            message = "Detection cache hit: %(image)d -> %(result)s" % \
-                    {'image':id(image), 'result':self._detect_cache[image]}
+            message = "Detection cache hit: %(image)d -> %(result)s" % {
+                'image': id(image),
+                'result': self._detect_cache[image],
+            }
             LOGGER.debug(message)
+
             if isinstance(self._detect_cache[image], FeatureNotFoundException):
                 message = str(self._detect_cache[image])
-                raise FeatureNotFoundException(message,
-                        cause=self._detect_cache[image])
+                raise FeatureNotFoundException(
+                    message,
+                    cause=self._detect_cache[image]
+                )
+
             return self._detect_cache[image]
         try:
             self._image = image
@@ -157,9 +175,11 @@ class FeatureDetector(object):
             self._extract_image()
             self._calculate_center()
             self._detect_cache[image] = self._single
+
             return self._detect_cache[image]
         except FeatureNotFoundException as exception:
             self._detect_cache[image] = exception
+
             raise
 
     def _detect_plural(self):
@@ -184,8 +204,8 @@ class FeatureDetector(object):
 
     def _unpack_first(self):
         self._single = dict(
-                zip(['x', 'y', 'width', 'height'],
-                self._plural[0]))
+            zip(['x', 'y', 'width', 'height'], self._plural[0])
+        )
 
     def _calculate_center(self):
         self._single["center"] = {


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]