Commits

Anonymous committed e031835

Tests refactoring #1.

Comments (0)

Files changed (4)

             return
 
         for logname in os.listdir(self.directory):
-            try:
-                m = re.search(self.logname_regex, logname)
-                if m:
-                    number, package, version, result, execution_time = m.groups()
-                    run = CheesecakeRun(int(number), package, version, result, int(execution_time))
-                    self.save_run(run)
-            except:
-                pass
+            m = re.search(self.logname_regex, logname)
+            if m:
+                number, package, version, result, execution_time = m.groups()
+                run = CheesecakeRun(int(number), package, version, result, int(execution_time))
+                self.save_run(run)
 
     def _read_cheesecake_scores(self):
         execute = lambda query, *args: self.cursor.execute(query, args)
+#
+# (c) Dave Kirby 2001 - 2005
+#     mock@thedeveloperscoach.com
+#
+# Original call interceptor and call assertion code by Phil Dawes (pdawes@users.sourceforge.net)
+# Call interceptor code enhanced by Bruce Cropley (cropleyb@yahoo.com.au)
+#
+# This Python  module and associated files are released under the FreeBSD
+# license. Essentially, you can do what you like with it except pretend you wrote
+# it yourself.
+# 
+# 
+#     Copyright (c) 2005, Dave Kirby
+# 
+#     All rights reserved.
+# 
+#     Redistribution and use in source and binary forms, with or without
+#     modification, are permitted provided that the following conditions are met:
+# 
+#         * Redistributions of source code must retain the above copyright
+#           notice, this list of conditions and the following disclaimer.
+# 
+#         * Redistributions in binary form must reproduce the above copyright
+#           notice, this list of conditions and the following disclaimer in the
+#           documentation and/or other materials provided with the distribution.
+# 
+#         * Neither the name of this library nor the names of its
+#           contributors may be used to endorse or promote products derived from
+#           this software without specific prior written permission.
+# 
+#     THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+#     ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+#     WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+#     DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+#     ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+#     (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+#     LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+#     ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+#     (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+#     SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+# 
+#         mock@thedeveloperscoach.com
+
+
+"""
+Mock object library for Python. Mock objects can be used when unit testing
+to remove a dependency on another production class. They are typically used
+when the dependency would either pull in lots of other classes, or
+significantly slow down the execution of the test.
+They are also used to create exceptional conditions that cannot otherwise
+be easily triggered in the class under test.
+"""
+
+__version__ = "0.1.0"
+
+# Added in Python 2.1
+import inspect
+import re
+
+class MockInterfaceError(Exception):
+    pass
+
+class Mock:
+    """
+    The Mock class emulates any other class for testing purposes.
+    All method calls are stored for later examination.
+    """
+
+    def __init__(self, returnValues=None, realClass=None):
+        """
+        The Mock class constructor takes a dictionary of method names and
+        the values they return.  Methods that are not in the returnValues
+        dictionary will return None.
+        You may also supply a class whose interface is being mocked.
+        All calls will be checked to see if they appear in the original
+        interface. Any calls to methods not appearing in the real class
+        will raise a MockInterfaceError.  Any calls that would fail due to
+        non-matching parameter lists will also raise a MockInterfaceError.
+        Both of these help to prevent the Mock class getting out of sync
+        with the class it is Mocking.
+        """
+        self.mockCalledMethods = {}
+        self.mockAllCalledMethods = []
+        self.mockReturnValues = returnValues or {}
+        self.mockExpectations = {}
+        self.realClassMethods = None
+        if realClass:
+            self.realClassMethods = dict(inspect.getmembers(realClass, inspect.isroutine))
+            for retMethod in self.mockReturnValues.keys():
+                if not self.realClassMethods.has_key(retMethod):
+                    raise MockInterfaceError("Return value supplied for method '%s' that was not in the original class" % retMethod)
+        self._setupSubclassMethodInterceptors()
+     
+    def _setupSubclassMethodInterceptors(self):
+        methods = inspect.getmembers(self.__class__,inspect.isroutine)
+        baseMethods = dict(inspect.getmembers(Mock, inspect.ismethod))
+        for m in methods:
+            name = m[0]
+            # Don't record calls to methods of Mock base class.
+            if not name in baseMethods:
+                self.__dict__[name] = MockCallable(name, self, handcrafted=True)
+ 
+    def __getattr__(self, name):
+        return MockCallable(name, self)
+    
+    def mockAddReturnValues(self, **methodReturnValues ):
+        self.mockReturnValues.update(methodReturnValues)
+        
+    def mockSetExpectation(self, name, testFn, after=0, until=0):
+        self.mockExpectations.setdefault(name, []).append((testFn,after,until))
+
+    def _checkInterfaceCall(self, name, callParams, callKwParams):
+        """
+        Check that a call to a method of the given name to the original
+        class with the given parameters would not fail. If it would fail,
+        raise a MockInterfaceError.
+        Based on the Python 2.3.3 Reference Manual section 5.3.4: Calls.
+        """
+        if self.realClassMethods == None:
+            return
+        if not self.realClassMethods.has_key(name):
+            raise MockInterfaceError("Calling mock method '%s' that was not found in the original class" % name)
+
+        func = self.realClassMethods[name]
+        try:
+            args, varargs, varkw, defaults = inspect.getargspec(func)
+        except TypeError:
+            # func is not a Python function. It is probably a builtin,
+            # such as __repr__ or __coerce__. TODO: Checking?
+            # For now assume params are OK.
+            return
+
+        # callParams doesn't include self; args does include self.
+        numPosCallParams = 1 + len(callParams)
+
+        if numPosCallParams > len(args) and not varargs:
+            raise MockInterfaceError("Original %s() takes at most %s arguments (%s given)" % 
+                (name, len(args), numPosCallParams))
+
+        # Get the number of positional arguments that appear in the call,
+        # also check for duplicate parameters and unknown parameters
+        numPosSeen = _getNumPosSeenAndCheck(numPosCallParams, callKwParams, args, varkw)
+
+        lenArgsNoDefaults = len(args) - len(defaults or [])
+        if numPosSeen < lenArgsNoDefaults:
+            raise MockInterfaceError("Original %s() takes at least %s arguments (%s given)" % (name, lenArgsNoDefaults, numPosSeen))
+
+    def mockGetAllCalls(self):
+        """
+        Return a list of MockCall objects,
+        representing all the methods in the order they were called.
+        """
+        return self.mockAllCalledMethods
+    getAllCalls = mockGetAllCalls  # deprecated - kept for backward compatibility
+
+    def mockGetNamedCalls(self, methodName):
+        """
+        Return a list of MockCall objects,
+        representing all the calls to the named method in the order they were called.
+        """
+        return self.mockCalledMethods.get(methodName, [])
+    getNamedCalls = mockGetNamedCalls  # deprecated - kept for backward compatibility
+
+    def mockCheckCall(self, index, name, *args, **kwargs):
+        '''test that the index-th call had the specified name and parameters'''
+        call = self.mockAllCalledMethods[index]
+        assert name == call.getName(), "%r != %r" % (name, call.getName())
+        call.checkArgs(*args, **kwargs)
+
+
+def _getNumPosSeenAndCheck(numPosCallParams, callKwParams, args, varkw):
+    """
+    Positional arguments can appear as call parameters either named as
+    a named (keyword) parameter, or just as a value to be matched by
+    position. Count the positional arguments that are given by either
+    keyword or position, and check for duplicate specifications.
+    Also check for arguments specified by keyword that do not appear
+    in the method's parameter list.
+    """
+    posSeen = {}
+    for arg in args[:numPosCallParams]:
+        posSeen[arg] = True
+    for kwp in callKwParams:
+        if posSeen.has_key(kwp):
+            raise MockInterfaceError("%s appears as both a positional and named parameter." % kwp)
+        if kwp in args:
+            posSeen[kwp] = True
+        elif not varkw:
+            raise MockInterfaceError("Original method does not have a parameter '%s'" % kwp)
+    return len(posSeen)
+
+class MockCall:
+    """
+    MockCall records the name and parameters of a call to an instance
+    of a Mock class. Instances of MockCall are created by the Mock class,
+    but can be inspected later as part of the test.
+    """
+    def __init__(self, name, params, kwparams ):
+        self.name = name
+        self.params = params
+        self.kwparams = kwparams
+
+    def checkArgs(self, *args, **kwargs):
+        assert args == self.params, "%r != %r" % (args, self.params)
+        assert kwargs == self.kwparams, "%r != %r" % (kwargs, self.kwparams)
+
+    def getParam( self, n ):
+        if isinstance(n, int):
+            return self.params[n]
+        elif isinstance(n, str):
+            return self.kwparams[n]
+        else:
+            raise IndexError, 'illegal index type for getParam'
+
+    def getNumParams(self):
+        return len(self.params)
+
+    def getNumKwParams(self):
+        return len(self.kwparams)
+
+    def getName(self):
+        return self.name
+    
+    #pretty-print the method call
+    def __str__(self):
+        s = self.name + "("
+        sep = ''
+        for p in self.params:
+            s = s + sep + repr(p)
+            sep = ', '
+        items = self.kwparams.items()
+        items.sort()
+        for k,v in items:
+            s = s + sep + k + '=' + repr(v)
+            sep = ', '
+        s = s + ')'
+        return s
+    def __repr__(self):
+        return self.__str__()
+
+class MockCallable:
+    """
+    Intercepts the call and records it, then delegates to either the mock's
+    dictionary of mock return values that was passed in to the constructor,
+    or a handcrafted method of a Mock subclass.
+    """
+    def __init__(self, name, mock, handcrafted=False):
+        self.name = name
+        self.mock = mock
+        self.handcrafted = handcrafted
+
+    def __call__(self,  *params, **kwparams):
+        self.mock._checkInterfaceCall(self.name, params, kwparams)
+        thisCall = self.recordCall(params,kwparams)
+        self.checkExpectations(thisCall, params, kwparams)
+        return self.makeCall(params, kwparams)
+
+    def recordCall(self, params, kwparams):
+        """
+        Record the MockCall in an ordered list of all calls, and an ordered
+        list of calls for that method name.
+        """
+        thisCall = MockCall(self.name, params, kwparams)
+        calls = self.mock.mockCalledMethods.setdefault(self.name, [])
+        calls.append(thisCall)
+        self.mock.mockAllCalledMethods.append(thisCall)
+        return thisCall
+
+    def makeCall(self, params, kwparams):
+        if self.handcrafted:
+            allPosParams = (self.mock,) + params
+            func = _findFunc(self.mock.__class__, self.name)
+            if not func:
+                raise NotImplementedError
+            return func(*allPosParams, **kwparams)
+        else:
+            returnVal = self.mock.mockReturnValues.get(self.name)
+            if isinstance(returnVal, ReturnValuesBase):
+                returnVal = returnVal.next()
+            return returnVal
+
+    def checkExpectations(self, thisCall, params, kwparams):
+        if self.name in self.mock.mockExpectations:
+            callsMade = len(self.mock.mockCalledMethods[self.name])
+            for (expectation, after, until) in self.mock.mockExpectations[self.name]:
+                if callsMade > after and (until==0 or callsMade < until):
+                    assert expectation(self.mock, thisCall, len(self.mock.mockAllCalledMethods)-1), 'Expectation failed: '+str(thisCall)
+
+
+def _findFunc(cl, name):
+    """ Depth first search for a method with a given name. """
+    if cl.__dict__.has_key(name):
+        return cl.__dict__[name]
+    for base in cl.__bases__:
+        func = _findFunc(base, name)
+        if func:
+            return func
+    return None
+
+
+
+class ReturnValuesBase:
+    def next(self):
+        try:
+            return self.iter.next()
+        except StopIteration:
+            raise AssertionError("No more return values")
+    def __iter__(self):
+        return self
+
+class ReturnValues(ReturnValuesBase):
+    def __init__(self, *values):
+        self.iter = iter(values)
+        
+
+class ReturnIterator(ReturnValuesBase):
+    def __init__(self, iterator):
+        self.iter = iter(iterator)
+
+        
+def expectParams(*params, **keywords):
+    '''check that the callObj is called with specified params and keywords
+    '''
+    def fn(mockObj, callObj, idx):
+        return callObj.params == params and callObj.kwparams == keywords
+    return fn
+
+
+def expectAfter(*methods):
+    '''check that the function is only called after all the functions in 'methods'
+    '''
+    def fn(mockObj, callObj, idx):
+        calledMethods = [method.getName() for method in mockObj.mockGetAllCalls()]
+        #skip last entry, since that is the current call
+        calledMethods = calledMethods[:-1]
+        for method in methods:  
+            if method not in calledMethods:
+                return False
+        return True
+    return fn
+
+def expectException(exception, *args, **kwargs):
+    ''' raise an exception when the method is called
+    '''
+    def fn(mockObj, callObj, idx):
+        raise exception(*args, **kwargs)
+    return fn
+
+
+def expectParam(paramIdx, cond):
+    '''check that the callObj is called with parameter specified by paramIdx (a position index or keyword)
+    fulfills the condition specified by cond.
+    cond is a function that takes a single argument, the value to test.
+    '''
+    def fn(mockObj, callObj, idx):
+        param = callObj.getParam(paramIdx)
+        return cond(param)
+    return fn
+
+def EQ(value):
+    def testFn(param):
+        return param == value
+    return testFn
+
+def NE(value):
+    def testFn(param):
+        return param != value
+    return testFn
+
+def GT(value):
+    def testFn(param):
+        return param > value
+    return testFn
+
+def LT(value):
+    def testFn(param):
+        return param < value
+    return testFn
+
+def GE(value):
+    def testFn(param):
+        return param >= value
+    return testFn
+
+def LE(value):
+    def testFn(param):
+        return param <= value
+    return testFn
+
+def AND(*condlist):
+    def testFn(param):
+        for cond in condlist:
+            if not cond(param):
+                return False
+        return True
+    return testFn
+
+def OR(*condlist):
+    def testFn(param):
+        for cond in condlist:
+            if cond(param):
+                return True
+        return False
+    return testFn
+
+def NOT(cond):
+    def testFn(param):
+        return not cond(param)
+    return testFn
+
+def MATCHES(regex, *args, **kwargs):
+    compiled_regex = re.compile(regex, *args, **kwargs)
+    def testFn(param):
+        return compiled_regex.match(param) != None
+    return testFn
+
+def SEQ(*sequence):
+    iterator = iter(sequence)
+    def testFn(param):
+        try:
+            cond = iterator.next()
+        except StopIteration:
+            raise AssertionError('SEQ exhausted')
+        return cond(param)
+    return testFn
+
+def IS(instance):
+    def testFn(param):
+        return param is instance
+    return testFn
+
+def ISINSTANCE(class_):
+    def testFn(param):
+        return isinstance(param, class_) 
+    return testFn
+
+def ISSUBCLASS(class_):
+    def testFn(param):
+        return issubclass(param, class_) 
+    return testFn
+
+def CONTAINS(val):
+    def testFn(param):
+        return val in param 
+    return testFn
+
+def IN(container):
+    def testFn(param):
+        return param in container
+    return testFn
+
+def HASATTR(attr):
+    def testFn(param):
+        return hasattr(param, attr)
+    return testFn
+
+def HASMETHOD(method):
+    def testFn(param):
+        return hasattr(param, method) and callable(getattr(param, method))
+    return testFn
+
+CALLABLE = callable
+
+
+

tests/test_store.py

 import doctest
+import os
 import shutil
 import tempfile
 import time
 
 import psycopg
 
+from _test_case_with_spec import TestCaseWithSpec
+from mock import Mock
 import _path_setup
 
 from store import Store, CheesecakeScore, CheesecakeScoreFromValues, CheesecakeRun
 from util import create_empty_files_in_directory
 from util import set
 
+################################################################################
+## Test helpers.
+################################################################################
+def returning(value):
+    """Return a procedure that returns `value`.
+    """
+    return lambda *a: value
+
+def check_calls_count(mock, name, exactly=None, min=None, max=None):
+    calls_number = len(filter(lambda c: c.name == name, mock.mockGetAllCalls()))
+
+    if exactly is not None:
+        assert calls_number == exactly, "Didn't call %s exactly %d times as expected" % (name, exactly)
+
+    if min is not None:
+        assert calls_number >= min, "Called %s less than %d times" % (name, min)
+
+    if max is not None:
+        assert calls_number <= max, "Called %s more than %d times" % (name, max)
+
 
 ################################################################################
 ## Data.
 ## Test classes.
 ################################################################################
 
-class TestStoreInit(unittest.TestCase):
-    def test_store_init(self):
-        """Test CheesecakeScore.__init__.
-        """
+class TestStoreInit(TestCaseWithSpec):
+    def test_recognizes_all_standard_indexes(self):
         score = CheesecakeScore(cheesecake_run_output)
 
         assert score.installability.absolute == 165
         check_subindices(score.code_kwalitee, sub_scores_code_kwalitee)
 
 
+class TestReadCheesecakeRuns(TestCaseWithSpec):
+    context = "Method _read_cheesecake_runs()"
+
+    class StoreMock(Store, Mock):
+        def __init__(self):
+            Mock.__init__(self)
+            self._runs_by_release = {}
+            self._runs_by_number = {}
+            self._runs_failure = []
+            self._runs_success = []
+            self.directory = '/log_directory'
+
+    def setup(self):
+        self.original_os_listdir = os.listdir
+
+    def teardown(self):
+        os.listdir = self.original_os_listdir
+
+    def test_doesnt_call_save_run_for_empty_log_directory(self):
+        store = self.StoreMock()
+        os.listdir = returning([])
+
+        store._read_cheesecake_runs()
+        check_calls_count(store, 'save_run', exactly=0)
+
+    def test_calls_save_run_four_times_for_four_lognames_in_log_directory(self):
+        store = self.StoreMock()
+        os.listdir = returning(['1-twill==0.8.5-success-42.log',
+                                '2-PIL==1.1.5-failure-42.log',
+                                '3-PIL==1.1.5-failure-42.log',
+                                '4-setuptools==0.6c1-success-42.log'])
+
+        store._read_cheesecake_runs()
+        check_calls_count(store, 'save_run', exactly=4)
+
+    def test_calls_save_run_three_times_for_three_lognames_with_successive_numbers(self):
+        store = self.StoreMock()
+        os.listdir = returning(['12-rope==0.2-success-42.log',
+                                '13-ctypes==1.0.0-failure-42.log',
+                                '101-chimera==0.4.4-failure-42.log'])
+
+        store._read_cheesecake_runs()
+        check_calls_count(store, 'save_run', exactly=3)
+
+    def test_ignores_files_that_doesnt_match_cheesecake_logfile_pattern(self):
+        store = self.StoreMock()
+        os.listdir = returning(['foobar',
+                                '1-twill==0.8.5-success-42.log',
+                                'some random file with spaces',
+                                'different.log'])
+
+        store._read_cheesecake_runs()
+        check_calls_count(store, 'save_run', exactly=1)
+
+    def test_calls_save_run_with_appropriate_cheesecake_run_object(self):
+        store = self.StoreMock()
+        os.listdir = returning(['1-twill==0.8.5-success-42.log'])
+
+        def check_run(mock, call, count):
+            run = call.getParam(0)
+            return isinstance(run, CheesecakeRun) \
+                   and run.number == 1 \
+                   and run.package == 'twill' \
+                   and run.version == '0.8.5' \
+                   and run.result == 'success' \
+                   and run.execution_time == 42
+
+        store.mockSetExpectation('save_run', check_run)
+        store._read_cheesecake_runs()
+
+class TestRunsCount(TestCaseWithSpec):
+    context = "Method _get_runs_count()"
+
+    class StoreMock(Store):
+        def __init__(self):
+            self.closed = False
+
+    def fill_with_keys(self, keys):
+        d = {}
+        for k in keys:
+            d[k] = 'whatever'
+        return d
+
+    def test_returns_one_for_no_runs(self):
+        store = self.StoreMock()
+        store._runs_by_number = {}
+
+        self.assertEqual(store.runs_count, 1)
+
+    def test_returns_five_for_four_successive_runs_1_2_3_and_4(self):
+        store = self.StoreMock()
+        store._runs_by_number = self.fill_with_keys(range(1, 5))
+
+        self.assertEqual(store.runs_count, 5)
+
+    def test_returns_102_for_three_runs_and_last_with_number_101(self):
+        store = self.StoreMock()
+        store._runs_by_number = self.fill_with_keys([12, 13, 101])
+
+        self.assertEqual(store.runs_count, 102)
+
 class TestCaseWithTmpDir(unittest.TestCase):
     def setUp(self):
         self.tmp_dir = tempfile.mkdtemp()
     def tearDown(self):
         shutil.rmtree(self.tmp_dir)
 
-class ReadRunsCountTest(TestCaseWithTmpDir):
-    """Test for Store._get_runs_count.
-    """
-    def _run_test_set(self, lognames, runs_count):
-        create_empty_files_in_directory(lognames, self.tmp_dir)
-        store = Store(directory=self.tmp_dir)
-        self.assertEqual(store.runs_count, runs_count)
-
-    def test_set_1(self):
-        self._run_test_set(successive_lognames, 5)
-
-    def test_set_2(self):
-        lognames = [
-            '12-rope==0.2-success-42.log',
-            '13-ctypes==1.0.0-failure-42.log',
-            '101-chimera==0.4.4-failure-42.log',
-        ]
-        self._run_test_set(lognames, 102)
-
-    def test_empty_set(self):
-        self._run_test_set([], 1)
-
 class ReadFailuresTest(TestCaseWithTmpDir):
     """Test for Store._get_failures.
     """

tests/test_webui.py

         assert '' == put_anchors('')
 
     def test_recognizes_download_section(self):
-        assert '<a name="download"></a>' in put_anchors(log_contents)
+        assert '<a name="download">' in put_anchors(log_contents)
 
     def test_recognizes_install_section(self):
-        assert '<a name="install"></a>' in put_anchors(log_contents)
+        assert '<a name="install">' in put_anchors(log_contents)
 
 
 log_contents = """[cheesecake:logfile] Profile requirements: classes, dirs_list, distance_from_pypi, docformat_cnt, docstring_cnt, doctests_count, download_url, files_list, files_list, files_list, files_list, files_list, found_locally, found_on_cheeseshop, installed, methods, object_cnt, object_cnt, original_package_name, package, package_dir, package_dir, package_dir, pylint_max_execution_time, sandbox_install_dir, unittests_count, unpack_dir, unpacked.