[mousetrap/gnome3-wip] Support Travis-CI
- From: Heidi Ellis <heidiellis src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [mousetrap/gnome3-wip] Support Travis-CI
- Date: Tue, 3 Feb 2015 18:11:39 +0000 (UTC)
commit a0b19dd223111cc84b8d491210dc43394e2e5f43
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]