Skip to content

Commit

Permalink
Merge pull request Yelp#98 from blampe/bryce_273_compat
Browse files Browse the repository at this point in the history
restore python 2.5/2.7.3 compatibility
  • Loading branch information
ayust committed Jun 27, 2012
2 parents 27bda64 + 211d999 commit 1db3dae
Show file tree
Hide file tree
Showing 7 changed files with 157 additions and 23 deletions.
2 changes: 2 additions & 0 deletions test/discovery_failure_test.py
@@ -1,3 +1,5 @@
from __future__ import with_statement

import os
import tempfile

Expand Down
8 changes: 7 additions & 1 deletion test/plugins/http_reporter_test.py
@@ -1,4 +1,3 @@
import json
import threading
import tornado.ioloop
import tornado.httpserver
Expand All @@ -9,6 +8,13 @@
from testify.test_runner import TestRunner
from testify.plugins.http_reporter import HTTPReporter

try:
import simplejson as json
_hush_pyflakes = [json]
del _hush_pyflakes
except ImportError:
import json


class DummyTestCase(TestCase):
__test__ = False
Expand Down
9 changes: 9 additions & 0 deletions test/test_case_test.py
Expand Up @@ -290,6 +290,15 @@ def make_sure_i_ran(self):
assert self.foo_ran


class RedefinedFixtureWithNoDecoratorTest(TestCase, FixtureMixin):
def set_attr(self):
pass

def test_foo(self):
# set_attr shouldn't have run because it's no longer decorated
assert not hasattr(self, 'foo')


class TestSubclassedCasesWithFeatureMixinsGetRun(TestFixtureMixinsGetRun):
pass

Expand Down
53 changes: 53 additions & 0 deletions test/utils/inspection_test.py
@@ -0,0 +1,53 @@
from testify import TestCase
from testify import setup
from testify import turtle
from testify.utils import inspection

class DummyTestCase(TestCase):

@setup
def fixture(self):
pass

turtle_method = turtle.Turtle()

def instance(self):
pass

@staticmethod
def static():
pass


class IsFixtureMethodTest(TestCase):

def test_fixture(self):
assert inspection.is_fixture_method(DummyTestCase.fixture)

def test_turtle(self):
"""Turtles are callable but not fixtures!"""
assert not inspection.is_fixture_method(DummyTestCase.turtle_method)

def test_lambda(self):
assert not inspection.is_fixture_method(lambda: None)

def test_static_method(self):
assert not inspection.is_fixture_method(DummyTestCase.static)

def test_instance(self):
assert not inspection.is_fixture_method(DummyTestCase.instance)


class CallableSetattrTest(TestCase):

def test_set_function_attr(self):
function = lambda: None
inspection.callable_setattr(function, 'foo', True)
assert function.foo

def test_set_method_attr(self):
inspection.callable_setattr(DummyTestCase.fixture, 'foo', True)
assert DummyTestCase.fixture.foo



52 changes: 31 additions & 21 deletions testify/test_case.py
Expand Up @@ -33,6 +33,7 @@
from test_result import TestResult
import deprecated_assertions
from testify.utils import class_logger
from testify.utils import inspection

# just a useful list to have
FIXTURE_TYPES = (
Expand Down Expand Up @@ -209,23 +210,34 @@ def __init_fixture_methods(self):
# attributes, so dir() isn't an option; this traverses __bases__/__dict__
# correctly for us.
for classified_attr in inspect.classify_class_attrs(type(self)):
attr_name = classified_attr.name
method = classified_attr.object
defining_class = classified_attr.defining_class
# have to index here for Python 2.5 compatibility
attr_name = classified_attr[0]
unbound_method = classified_attr[3]
defining_class = classified_attr[2]

# skip everything that's not a function/method
if not inspect.isroutine(unbound_method):
continue

# if this is an old setUp/tearDown/etc, tag it as a fixture
if attr_name in DEPRECATED_FIXTURE_TYPE_MAP:
fixture_type = DEPRECATED_FIXTURE_TYPE_MAP[attr_name]
fixture_decorator = globals()[fixture_type]
method = fixture_decorator(method)
unbound_method = fixture_decorator(unbound_method)

# collect all of our fixtures in appropriate buckets
if self.is_fixture_method(method):
if inspection.is_fixture_method(unbound_method):
# where in our MRO this fixture was defined
method._defining_class_depth = reverse_mro_list.index(defining_class)
defining_class_depth = reverse_mro_list.index(defining_class)
inspection.callable_setattr(
unbound_method,
'_defining_class_depth',
defining_class_depth,
)

# we grabbed this from the class and need to bind it to us
instance_method = instancemethod(method, self)
self._fixture_methods[method._fixture_type].append(instance_method)
instance_method = instancemethod(unbound_method, self)
self._fixture_methods[instance_method._fixture_type].append(instance_method)

# arrange our fixture buckets appropriately
for fixture_type, fixture_methods in self._fixture_methods.iteritems():
Expand Down Expand Up @@ -423,14 +435,17 @@ def register_callback(self, event, callback):
The argument to the callback will be the test method object itself.
Fixture objects can be distinguished by the running them through self.is_fixture_method().
Fixture objects can be distinguished by the running them through
inspection.is_fixture_method().
"""
self.__callbacks[event].append(callback)

def __execute_block_recording_exceptions(self, block_fxn, result, is_class_level=False):
"""Excerpted code for executing a block of code that might except and cause us to update a result object.
"""Excerpted code for executing a block of code that might except and
cause us to update a result object.
Return value is a boolean describing whether the block was successfully executed without exceptions.
Return value is a boolean describing whether the block was successfully
executed without exceptions.
"""
try:
block_fxn()
Expand Down Expand Up @@ -478,15 +493,6 @@ def tearDown(self): pass
def classTearDown(self): pass
def runTest(self): pass

def is_fixture_method(self, method, fixture_type=None):
# _fixture_id indicates this method was tagged by us as a fixture,
# and the MethodType check ensures we don't tag turtles (who are all types)
if hasattr(method, '_fixture_type') and isinstance(method, types.FunctionType):
if fixture_type:
return True if (getattr(method, '_fixture_type') == fixture_type) else False
else:
return True


class TestifiedUnitTest(TestCase, unittest.TestCase):

Expand Down Expand Up @@ -585,7 +591,11 @@ class hierarchy, since base classes (and their methods!) are created before
parent class execute setups/teardowns, respectively.
"""

def fixture_decorator(function):
def fixture_decorator(callable_):
# Decorators act on *functions*, so we need to take care when dynamically
# decorating class attributes (which are (un)bound methods).
function = inspection.get_function(callable_)

# record the fixture type and id for this function
function._fixture_type = fixture_type

Expand Down
4 changes: 3 additions & 1 deletion testify/test_result.py
Expand Up @@ -19,6 +19,8 @@
import time
import traceback

from testify.utils import inspection

#If IPython is available, use it for fancy color traceback formatting
try:
try:
Expand Down Expand Up @@ -130,6 +132,6 @@ def to_dict(self):
'class' : self.test_method.im_class.__name__,
'name' : self.test_method.__name__,
'full_name' : '%s %s.%s' % (self.test_method.im_class.__module__, self.test_method.im_class.__name__, self.test_method.__name__),
'fixture_type' : None if not self.test_method.im_self.is_fixture_method(self.test_method) else self.test_method._fixture_type,
'fixture_type' : None if not inspection.is_fixture_method(self.test_method) else self.test_method._fixture_type,
}
}
52 changes: 52 additions & 0 deletions testify/utils/inspection.py
@@ -0,0 +1,52 @@
# Copyright 2012 Yelp
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Helpers for dealing with class and method introspection.
In particular, these functions help to remedy differences in behavior between
Python 2.6.x and 2.7.3+ when getting/setting attributes on instancemethods.
Attributes can be freely set on functions, but not instancemethods. Because we
juggle methods and functions often interchangably, these produce the desired
effect of setting (or getting) the attribute on the function regardless of our
callable's type.
"""

import inspect
import types


def callable_hasattr(callable_, attr_name):
function = get_function(callable_)
return hasattr(function, attr_name)

def callable_setattr(callable_, attr_name, attr_value):
function = get_function(callable_)
setattr(function, attr_name, attr_value)

def get_function(callable_):
"""If given a method, returns its function object; otherwise a no-op."""
if isinstance(callable_, types.MethodType):
return callable_.im_func
return callable_

def is_fixture_method(callable_):
"""Whether Testify has decorated this callable as a test fixture."""
# ensure we don't pick up turtles/mocks as fixtures
if not inspect.isroutine(callable_):
return False

# _fixture_id indicates this method was tagged by us as a fixture
return callable_hasattr(callable_, '_fixture_type')

0 comments on commit 1db3dae

Please sign in to comment.