From df2936c8cf055a4c3268a34f01746c64975f3f8e Mon Sep 17 00:00:00 2001 From: epage Date: Thu, 28 May 2009 03:04:59 +0000 Subject: [PATCH] Adding some unit tests git-svn-id: file:///svnroot/gc-dialer/trunk@337 c39d3808-3fe2-4d86-a59f-b7f623ee9f21 --- src/dc_glade.py | 16 ++--- support/builddeb.py | 8 ++- tests/basic_data/settings.ini | 37 ++++++++++++ tests/test_startup.py | 90 ++++++++++++++++++++++++++++ tests/test_utils.py | 130 +++++++++++++++++++++++++++++++++++++++++ 5 files changed, 272 insertions(+), 9 deletions(-) create mode 100644 tests/basic_data/settings.ini create mode 100644 tests/test_startup.py create mode 100644 tests/test_utils.py diff --git a/src/dc_glade.py b/src/dc_glade.py index 845f57c..520fb92 100755 --- a/src/dc_glade.py +++ b/src/dc_glade.py @@ -19,6 +19,7 @@ License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA @bug Need to add unit tests +@todo Look into an actor system @bug Session timeouts are bad, possible solutions: @li For every X minutes, if logged in, attempt login @li Restructure so code handling login/dial/sms is beneath everything else and login attempts are made if those fail @@ -308,7 +309,7 @@ class Dialcentral(object): with gtk_toolbox.gtk_lock(): self.load_settings(config) - self.attempt_login(2) + gtk_toolbox.asynchronous_gtk_message(self._spawn_attempt_login)(2) def attempt_login(self, numOfAttempts = 10, force = False): """ @@ -341,6 +342,11 @@ class Dialcentral(object): with gtk_toolbox.gtk_lock(): self._errorDisplay.push_exception(e) + def _spawn_attempt_login(self, *args): + backgroundLogin = threading.Thread(target=self.attempt_login, args=args) + backgroundLogin.setDaemon(True) + backgroundLogin.start() + def refresh_session(self): """ @note Thread agnostic @@ -571,9 +577,7 @@ class Dialcentral(object): if status == conic.STATUS_CONNECTED: self._deviceIsOnline = True if self._initDone: - backgroundLogin = threading.Thread(target=self.attempt_login, args=[2]) - backgroundLogin.setDaemon(True) - backgroundLogin.start() + self._spawn_attempt_login(2) elif status == conic.STATUS_DISCONNECTED: self._deviceIsOnline = False if self._initDone: @@ -607,9 +611,7 @@ class Dialcentral(object): self._contactsViews[self._selectedBackendId].clear() self._change_loggedin_status(self.NULL_BACKEND) - backgroundLogin = threading.Thread(target=self.attempt_login, args=[2, True]) - backgroundLogin.setDaemon(True) - backgroundLogin.start() + self._spawn_attempt_login(2, True) def _on_notebook_switch_page(self, notebook, page, page_num): if page_num == self.RECENT_TAB: diff --git a/support/builddeb.py b/support/builddeb.py index fe2925d..6b3da48 100755 --- a/support/builddeb.py +++ b/support/builddeb.py @@ -1,6 +1,10 @@ #!/usr/bin/python2.5 -from py2deb import * +import os +import sys + +import py2deb + import constants @@ -115,7 +119,7 @@ if __name__ == "__main__": except: pass - p = Py2deb(__appname__) + p = py2deb.Py2deb(__appname__) p.description = __description__ p.author = __author__ p.mail = __email__ diff --git a/tests/basic_data/settings.ini b/tests/basic_data/settings.ini new file mode 100644 index 0000000..d9a6c81 --- /dev/null +++ b/tests/basic_data/settings.ini @@ -0,0 +1,37 @@ +[1 - Contacts] + +[2 - Account Info] +callback = + +[1 - Recent Calls] + +[2 - Messages] + +[0 - Messages] + +[1 - Messages] + +[2 - Dialpad] + +[2 - Contacts] + +[0 - Recent Calls] + +[DialCentral] +active = 0 +bin_blob_0 = +bin_blob_1 = + +[1 - Account Info] +callback = + +[1 - Dialpad] + +[0 - Dialpad] + +[0 - Account Info] + +[0 - Contacts] + +[2 - Recent Calls] + diff --git a/tests/test_startup.py b/tests/test_startup.py new file mode 100644 index 0000000..88d8807 --- /dev/null +++ b/tests/test_startup.py @@ -0,0 +1,90 @@ +from __future__ import with_statement + +import os +import time + +import test_utils + +import sys +sys.path.append("../src") + +import dc_glade + + +def test_startup_with_no_data_dir(): + dc_glade.Dialcentral._data_path = os.path.join(os.path.dirname(__file__), "notexistent_data") + dc_glade.Dialcentral._user_settings = "%s/settings.ini" % dc_glade.Dialcentral._data_path + + try: + handle = dc_glade.Dialcentral() + with test_utils.expected(AssertionError("Attempting login before app is fully loaded")): + handle.refresh_session() + + for i in xrange(10): + if handle._initDone: + print "Completed init on iteration %d" % i + break + time.sleep(1) + assert handle._initDone + + with test_utils.expected(RuntimeError("Login Failed")): + handle.refresh_session() + + handle._save_settings() + + del handle + finally: + os.remove(dc_glade.Dialcentral._user_settings) + os.removedirs(dc_glade.Dialcentral._data_path) + + +def test_startup_with_empty_data_dir(): + dc_glade.Dialcentral._data_path = os.path.join(os.path.dirname(__file__), "empty_data") + dc_glade.Dialcentral._user_settings = "%s/settings.ini" % dc_glade.Dialcentral._data_path + + try: + os.makedirs(dc_glade.Dialcentral._data_path) + + handle = dc_glade.Dialcentral() + with test_utils.expected(AssertionError("Attempting login before app is fully loaded")): + handle.refresh_session() + + for i in xrange(10): + if handle._initDone: + print "Completed init on iteration %d" % i + break + time.sleep(1) + assert handle._initDone + + with test_utils.expected(RuntimeError("Login Failed")): + handle.refresh_session() + + handle._save_settings() + + del handle + finally: + os.remove(dc_glade.Dialcentral._user_settings) + os.removedirs(dc_glade.Dialcentral._data_path) + + +def test_startup_with_basic_data_dir(): + dc_glade.Dialcentral._data_path = os.path.join(os.path.dirname(__file__), "basic_data") + dc_glade.Dialcentral._user_settings = "%s/settings.ini" % dc_glade.Dialcentral._data_path + + handle = dc_glade.Dialcentral() + with test_utils.expected(AssertionError("Attempting login before app is fully loaded")): + handle.refresh_session() + + for i in xrange(10): + if handle._initDone: + print "Completed init on iteration %d" % i + break + time.sleep(1) + assert handle._initDone + + with test_utils.expected(RuntimeError("Login Failed")): + handle.refresh_session() + + handle._save_settings() + + del handle diff --git a/tests/test_utils.py b/tests/test_utils.py new file mode 100644 index 0000000..a2da797 --- /dev/null +++ b/tests/test_utils.py @@ -0,0 +1,130 @@ +#!/usr/bin/env python + + +from __future__ import with_statement + +import inspect +import contextlib +import functools + + +def TODO(func): + """ + unittest test method decorator that ignores + exceptions raised by test + + Used to annotate test methods for code that may + not be written yet. Ignores failures in the + annotated test method; fails if the text + unexpectedly succeeds. + !author http://kbyanc.blogspot.com/2007/06/pythons-unittest-module-aint-that-bad.html + + Example: + >>> import unittest + >>> class ExampleTestCase(unittest.TestCase): + ... @TODO + ... def testToDo(self): + ... MyModule.DoesNotExistYet('boo') + ... + """ + + @functools.wraps(func) + def wrapper(*args, **kw): + try: + func(*args, **kw) + succeeded = True + except: + succeeded = False + assert succeeded is False, \ + "%s marked TODO but passed" % func.__name__ + return wrapper + + +def PlatformSpecific(platformList): + """ + unittest test method decorator that only + runs test method if os.name is in the + given list of platforms + !author http://kbyanc.blogspot.com/2007/06/pythons-unittest-module-aint-that-bad.html + Example: + >>> import unittest + >>> class ExampleTestCase(unittest.TestCase): + ... @PlatformSpecific(('mac', )) + ... def testMacOnly(self): + ... MyModule.SomeMacSpecificFunction() + ... + """ + + def decorator(func): + import os + + @functools.wraps(func) + def wrapper(*args, **kw): + if os.name in platformList: + return func(*args, **kw) + return wrapper + return decorator + + +def CheckReferences(func): + """ + !author http://kbyanc.blogspot.com/2007/06/pythons-unittest-module-aint-that-bad.html + """ + + @functools.wraps(func) + def wrapper(*args, **kw): + refCounts = [] + for i in range(5): + func(*args, **kw) + refCounts.append(XXXGetRefCount()) + assert min(refCounts) != max(refCounts), "Reference counts changed - %r" % refCounts + + return wrapper + + +@contextlib.contextmanager +def expected(exception): + """ + >>> with expected2(ZeroDivisionError): + ... 1 / 0 + >>> with expected2(AssertionError("expected ZeroDivisionError to have been thrown")): + ... with expected(ZeroDivisionError): + ... 1 / 2 + Traceback (most recent call last): + File "/usr/lib/python2.5/doctest.py", line 1228, in __run + compileflags, 1) in test.globs + File "", line 3, in + 1 / 2 + File "/media/data/Personal/Development/bzr/Recollection-trunk/src/libraries/recipes/context.py", line 139, in __exit__ + assert t is not None, ("expected {0:%s} to have been thrown" % (self._t.__name__)) + AssertionError: expected {0:ZeroDivisionError} to have been thrown + >>> with expected2(Exception("foo")): + ... raise Exception("foo") + >>> with expected2(Exception("bar")): + ... with expected(Exception("foo")): # this won't catch it + ... raise Exception("bar") + ... assert False, "should not see me" + >>> with expected2(Exception("can specify")): + ... raise Exception("can specify prefixes") + >>> with expected2(Exception("Base class fun")): + True + >>> True + False + """ + if isinstance(exception, Exception): + excType, excValue = type(exception), str(exception) + elif isinstance(exception, type): + excType, excValue = exception, "" + + try: + yield + except Exception, e: + if not (excType in inspect.getmro(type(e)) and str(e).startswith(excValue)): + raise + else: + raise AssertionError("expected {0:%s} to have been thrown" % excType.__name__) + + +if __name__ == "__main__": + import doctest + doctest.testmod() -- 1.7.9.5