diff options
Diffstat (limited to 'src/leap/testing/qunittest.py')
-rw-r--r-- | src/leap/testing/qunittest.py | 302 |
1 files changed, 0 insertions, 302 deletions
diff --git a/src/leap/testing/qunittest.py b/src/leap/testing/qunittest.py deleted file mode 100644 index b89ccec3..00000000 --- a/src/leap/testing/qunittest.py +++ /dev/null @@ -1,302 +0,0 @@ -# -*- coding: utf-8 -*- - -# **qunittest** is an standard Python `unittest` enhancement for PyQt4, -# allowing -# you to test asynchronous code using standard synchronous testing facility. -# -# The source for `qunittest` is available on [GitHub][gh], and released under -# the MIT license. -# -# Slightly modified by The Leap Project. - -### Prerequisites - -# Import unittest2 or unittest -try: - import unittest2 as unittest -except ImportError: - import unittest - -# ... and some standard Python libraries -import sys -import functools -import contextlib -import re - -# ... and several PyQt classes -from PyQt4.QtCore import QTimer -from PyQt4.QtTest import QTest -from PyQt4 import QtGui - -### The code - - -# Override standard main method, by invoking it inside PyQt event loop - -def main(*args, **kwargs): - qapplication = QtGui.QApplication(sys.argv) - - QTimer.singleShot(0, unittest.main(*args, **kwargs)) - qapplication.exec_() - -""" -This main substitute does not integrate with unittest. - -Note about mixing the event loop and unittests: - -Unittest will fail if we keep more than one reference to a QApplication. -(pyqt expects to be and only one). -So, for the things that need a QApplication to exist, do something like: - - self.app = QApplication() - QtGui.qApp = self.app - -in the class setUp, and:: - - QtGui.qApp = None - self.app = None - -in the class tearDown. - -For some explanation about this, see - http://stuvel.eu/blog/127/multiple-instances-of-qapplication-in-one-process -and - http://www.riverbankcomputing.com/pipermail/pyqt/2010-September/027705.html -""" - - -# Helper returning the name of a given signal - -def _signal_name(signal): - s = repr(signal) - name_re = "signal (\w+) of (\w+)" - match = re.search(name_re, s, re.I) - if not match: - return "??" - return "%s#%s" % (match.group(2), match.group(1)) - - -class _SignalConnector(object): - """ Encapsulates signal assertion testing """ - def __init__(self, test, signal, callable_): - self.test = test - self.callable_ = callable_ - self.called_with = None - self.emited = False - self.signal = signal - self._asserted = False - - signal.connect(self.on_signal_emited) - - # Store given parameters and mark signal as `emited` - def on_signal_emited(self, *args, **kwargs): - self.called_with = (args, kwargs) - self.emited = True - - def assertEmission(self): - # Assert once wheter signal was emited or not - was_asserted = self._asserted - self._asserted = True - - if not was_asserted: - if not self.emited: - self.test.fail( - "signal %s not emited" % (_signal_name(self.signal))) - - # Call given callable is necessary - if self.callable_: - args, kwargs = self.called_with - self.callable_(*args, **kwargs) - - def __enter__(self): - # Assert emission when context is entered - self.assertEmission() - return self.called_with - - def __exit__(self, *_): - return False - -### Unit Testing - -# `qunittest` does not force much abould how test should look - it just adds -# several helpers for asynchronous code testing. -# -# Common test case may look like this: -# -# import qunittest -# from calculator import Calculator -# -# class TestCalculator(qunittest.TestCase): -# def setUp(self): -# self.calc = Calculator() -# -# def test_should_add_two_numbers_synchronously(self): -# # given -# a, b = 2, 3 -# -# # when -# r = self.calc.add(a, b) -# -# # then -# self.assertEqual(5, r) -# -# def test_should_calculate_factorial_in_background(self): -# # given -# -# # when -# self.calc.factorial(20) -# -# # then -# self.assertEmited(self.calc.done) with (args, kwargs): -# self.assertEqual([2432902008176640000], args) -# -# if __name__ == "__main__": -# main() -# -# Test can be run by typing: -# -# python test_calculator.py -# -# Automatic test discovery is not supported now, because testing PyQt needs -# an instance of `QApplication` and its `exec_` method is blocking. -# - - -### TestCase class - -class TestCase(unittest.TestCase): - """ - Extends standard `unittest.TestCase` with several PyQt4 testing features - useful for asynchronous testing. - """ - def __init__(self, *args, **kwargs): - super(TestCase, self).__init__(*args, **kwargs) - - self._clearSignalConnectors() - self._succeeded = False - self.addCleanup(self._clearSignalConnectors) - self.tearDown = self._decorateTearDown(self.tearDown) - - ### Protected methods - - def _clearSignalConnectors(self): - self._connectedSignals = [] - - def _decorateTearDown(self, tearDown): - @functools.wraps(tearDown) - def decorator(): - self._ensureEmitedSignals() - return tearDown() - return decorator - - def _ensureEmitedSignals(self): - """ - Checks if signals were acually emited. Raises AssertionError if no. - """ - # TODO: add information about line - for signal in self._connectedSignals: - signal.assertEmission() - - ### Assertions - - def assertEmited(self, signal, callable_=None, timeout=1): - """ - Asserts if given `signal` was emited. Waits 1 second by default, - before asserts signal emission. - - If `callable_` is given, it should be a function which takes two - arguments: `args` and `kwargs`. It will be called after blocking - operation or when assertion about signal emission is made and - signal was emited. - - When timeout is not `False`, method call is blocking, and ends - after `timeout` seconds. After that time, it validates wether - signal was emited. - - When timeout is `False`, method is non blocking, and test should wait - for signals afterwards. Otherwise, at the end of the test, all - signal emissions are checked if appeared. - - Function returns context, which yields to list of parameters given - to signal. It can be useful for testing given parameters. Following - code: - - with self.assertEmited(widget.signal) as (args, kwargs): - self.assertEqual(1, len(args)) - self.assertEqual("Hello World!", args[0]) - - will wait 1 second and test for correct parameters, is signal was - emtied. - - Note that code: - - with self.assertEmited(widget.signal, timeout=False) as (a, k): - # Will not be invoked - - will always fail since signal cannot be emited in the time of its - connection - code inside the context will not be invoked at all. - """ - - connector = _SignalConnector(self, signal, callable_) - self._connectedSignals.append(connector) - if timeout: - self.waitFor(timeout) - connector.assertEmission() - - return connector - - ### Helper methods - - @contextlib.contextmanager - def invokeAfter(self, seconds, callable_=None): - """ - Waits given amount of time and executes the context. - - If `callable_` is given, executes it, instead of context. - """ - self.waitFor(seconds) - if callable_: - callable_() - else: - yield - - def waitFor(self, seconds): - """ - Waits given amount of time. - - self.widget.loadImage(url) - self.waitFor(seconds=10) - """ - QTest.qWait(seconds * 1000) - - def succeed(self, bool_=True): - """ Marks test as suceeded for next `failAfter()` invocation. """ - self._succeeded = self._succeeded or bool_ - - def failAfter(self, seconds, message=None): - """ - Waits given amount of time, and fails the test if `succeed(bool)` - is not called - in most common case, `succeed(bool)` should be called - asynchronously (in signal handler): - - self.widget.signal.connect(lambda: self.succeed()) - self.failAfter(1, "signal not emited?") - - After invocation, test is no longer consider as succeeded. - """ - self.waitFor(seconds) - if not self._succeeded: - self.fail(message) - - self._succeeded = False - -### Credits -# -# * **Who is responsible:** [Dawid Fatyga][df] -# * **Source:** [GitHub][gh] -# * **Doc. generator:** [rocco][ro] -# -# [gh]: https://www.github.com/dejw/qunittest -# [df]: https://github.com/dejw -# [ro]: http://rtomayko.github.com/rocco/ -# |