Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
5ed6550
Started implementing argspec validation. See #1 for details.
thedrow Oct 11, 2013
d672cbb
Moved are_argspecs_identical to the utils module.
thedrow Oct 12, 2013
8848c3f
Renamed test's function name.
thedrow Oct 12, 2013
265106e
Fake callables now optionally inspect the arguments of the live objec…
thedrow Oct 12, 2013
82d13c0
The PyPy version travis installed has no alias for __globals__.
thedrow Oct 12, 2013
c227c9f
Some of the old code was left by mistake. This build should pass.
thedrow Oct 12, 2013
d3e2aaf
Some of the old code was left by mistake. This build will pass.
thedrow Oct 12, 2013
a854081
Added tests that verify that if the argspecs are not completely ident…
thedrow Nov 1, 2013
04dcf1e
Started working on matching arguments.
thedrow Nov 1, 2013
467d6d6
Arguments will now match correctly and will consider varargs as a match.
thedrow Nov 1, 2013
4d5ec99
Improved code readability.
thedrow Nov 1, 2013
df7e6ab
Added tests that verify that if a keyword argument was provided on th…
thedrow Nov 1, 2013
6279c3f
Made tests to work again on Python 2.6.
thedrow Nov 1, 2013
5ad1378
Finished the argspec matching feature. Closes #3 and #4.
thedrow Nov 1, 2013
dffcb28
Reformatted code.
thedrow Nov 1, 2013
5cc588e
Added tests that verify that if one method has no arguments and the o…
thedrow Nov 1, 2013
8a42a68
Added tests that verify that if one method has no arguments and the o…
thedrow Nov 1, 2013
e639e91
Added a functional test that verifies that two identical callables ha…
thedrow Nov 2, 2013
7629f62
Added a functional test that verifies that two callables have the sam…
thedrow Nov 2, 2013
3d7589d
Added a functional test that verifies that two callables have the sam…
thedrow Nov 2, 2013
3f3f018
Added a functional test that verifies that two callables are not iden…
thedrow Nov 2, 2013
8ffb9c9
Added a functional test that verifies that two callables are not iden…
thedrow Nov 2, 2013
07238be
Added a functional test that verifies that two callables are identica…
thedrow Nov 2, 2013
61e0367
Added a functional test that verifies that two callables are identica…
thedrow Nov 2, 2013
293e938
Added a functional test that verifies that two callables are not iden…
thedrow Nov 4, 2013
f70e6a3
Added a functional test that verifies that two callables with differe…
thedrow Nov 4, 2013
73c97d2
Just replaced the positional arguments' names to keep consistency.
thedrow Nov 4, 2013
ca361e2
Improved code readability for fake callable unit tests.
thedrow Nov 4, 2013
68c2f37
Improved code readability for fake callable unit tests some more.
thedrow Nov 4, 2013
7cefe27
Improved code readability for fake callable unit tests some more. Add…
thedrow Nov 4, 2013
6780b1c
A fake callable will now have the live callable's name.
thedrow Nov 4, 2013
0e3324d
A fake callable will now have the live callable's name when the calla…
thedrow Nov 4, 2013
8ed41c8
getargspec raises a type error when the argument is not a method.
thedrow Nov 4, 2013
4d67424
Fake callables now contain a reference to the instance they are bound…
thedrow Nov 4, 2013
b127ead
Fake callables now contain a reference to the instance's type they ar…
thedrow Nov 4, 2013
ddee2e5
Fake callables now contain a reference to the fake unbound version of…
thedrow Nov 5, 2013
bbcf8b5
Fix tests for python 3. I forgot to mock getargspec.
thedrow Nov 5, 2013
77df8ab
Renamed tests to clarify what is being tested.
thedrow Nov 5, 2013
923d65e
Added functional tests for the bound method's internal attributes.
thedrow Nov 5, 2013
4907ec6
Using the compat module so Python 2.6 will also be able to run tests.
thedrow Nov 5, 2013
323a342
Tests will now run under Python 2.6.
thedrow Nov 5, 2013
b6a3e9b
Fake callables will now return the live callable's docstring instead …
thedrow Nov 5, 2013
09a6f9f
Fake callables will now return the fake callable's code object. Added…
thedrow Nov 5, 2013
68e2db6
Fake callables now have the func_name alias for Python 2.x.
thedrow Nov 5, 2013
04d06c6
Fake callables will now return the fake callable's default keyword ar…
thedrow Nov 5, 2013
4fd1596
Fake callables will now return the fake callable's globals. Added Pyt…
thedrow Nov 5, 2013
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file modified .noseids
Binary file not shown.
4 changes: 4 additions & 0 deletions testdoubles/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,3 +109,7 @@ def default_implementation(*args, **kwargs):
obj = type(obj.__name__, obj.__bases__, attrs)

return substitute(obj, qualified_name, spec)

from testdoubles import utils

__all__ = ('fake', 'utils')
94 changes: 92 additions & 2 deletions testdoubles/fakes/callables.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,55 @@
# -*- coding: utf-8 -*-
import inspect
import sys
from testdoubles.utils import are_argspecs_identical

python3 = sys.version_info[0] == 3


class CallableInternalAttributesBaseMixin(object):
@property
def __name__(self):
if not self.is_instance_method and not inspect.isfunction(self.live):
return self.live.__class__.__name__

return self.live.__name__

@property
def __self__(self):
try:
return self.live.__self__
except AttributeError:
raise AttributeError("'function' object has no attribute '__self__'")

@property
def __func__(self):
if not self.is_instance_method:
raise AttributeError("'function' object has no attribute '__func__'")

if self.is_unbound_instance_method:
return None

return self.fake.__func__

@property
def __code__(self):
return self.fake.__code__

@property
def __defaults__(self):
return self.fake.__defaults__

@property
def __globals__(self):
return self.fake.__globals__

def __getattribute__(self, item):
if item == '__doc__':
return self.live.__doc__

return super(CallableInternalAttributesBaseMixin, self).__getattribute__(item)


if python3:
class CallableIntrospectionMixin(object):
@property
Expand All @@ -14,10 +60,15 @@ def is_unbound_instance_method(self):
return args[0] == 'self' and not inspect.ismethod(self.live)
except IndexError:
return False
except TypeError:
return False

@property
def is_instance_method(self):
return inspect.ismethod(self.live) or self.is_unbound_instance_method

class CallableInternalAttributesMixin(CallableInternalAttributesBaseMixin):
pass
else:
class CallableIntrospectionMixin(object):
@property
Expand All @@ -28,8 +79,38 @@ def is_unbound_instance_method(self):
def is_instance_method(self):
return inspect.ismethod(self.live) or self.is_unbound_instance_method

class FakeCallable(CallableIntrospectionMixin):
def __init__(self, live):
class CallableInternalAttributesMixin(CallableInternalAttributesBaseMixin):
@property
def im_self(self):
return self.__self__

@property
def im_class(self):
return self.__self__.__class__

@property
def func_code(self):
return self.__code__

@property
def func_doc(self):
return self.__doc__

@property
def func_name(self):
return self.live.__name__

@property
def func_defaults(self):
return self.__defaults__

@property
def func_globals(self):
return self.__globals__


class FakeCallable(CallableIntrospectionMixin, CallableInternalAttributesMixin):
def __init__(self, live, inspect_args=False):
if not callable(live):
try:
raise TypeError('%s is not callable.' % live.__name__)
Expand All @@ -38,6 +119,15 @@ def __init__(self, live):

self._live = live

if inspect_args:
if inspect.isbuiltin(self.live):
raise ValueError('Cannot inspect arguments of a builtin live object.')

if not are_argspecs_identical(self.live, self.fake):
raise ValueError("The provided live object's arguments %s does not match %s" % (
inspect.getargspec(self.live), inspect.getargspec(self.fake)))


@property
def live(self):
return self._live
Expand Down
46 changes: 46 additions & 0 deletions testdoubles/utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import inspect


def get_keyword_arguments(argspec):
return argspec.args[-len(argspec.defaults):] if argspec.defaults else []


def are_arguments_identical(argspec1, argspec2):
kwargs1 = get_keyword_arguments(argspec1)
kwargs2 = get_keyword_arguments(argspec2)

arguments1 = set(argspec1.args) - set(kwargs1)
arguments2 = set(argspec2.args) - set(kwargs2)

if len(arguments1) == len(arguments2) and not (argspec1.varargs or argspec2.varargs):
return True
elif any(_ for _ in arguments1) and argspec2.varargs or any(_ for _ in arguments2) and argspec1.varargs:
return True

return False


def are_keyword_arguments_identical(argspec1, argspec2):
kwargs1 = get_keyword_arguments(argspec1)
kwargs2 = get_keyword_arguments(argspec2)

if kwargs1 == kwargs2 and not (argspec1.keywords or argspec2.keywords):
return True
if any(_ for _ in kwargs1) and argspec2.keywords or any(_ for _ in kwargs2) and argspec1.keywords:
return True

return False


def are_argspecs_identical(callable1, callable2):
argspec1 = inspect.getargspec(callable1)
argspec2 = inspect.getargspec(callable2)

if argspec1 == argspec2:
return True
else:
return are_arguments_identical(argspec1, argspec2) and are_keyword_arguments_identical(argspec1, argspec2)

__all__ = ('are_argspecs_identical', )
16 changes: 11 additions & 5 deletions tests/common/compat.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import six
import sys

try:
from unittest import mock
except ImportError:
import mock

try:
import unittest
except ImportError:
import unittest2 as unittest
if not six.PY3 and sys.version_info[1] == 6:
import unittest2 as unittest
else:
try:
import unittest
except ImportError:
import unittest2 as unittest

__all__ = ['mock']
__all__ = ['mock', 'unittest']
Loading