"""Decorators for labeling test objects. Decorators that merely return a modified version of the original function object are straightforward. Decorators that return a new function object need to use nose.tools.make_decorator(original_function)(decorator) in returning the decorator, in order to preserve metadata such as function name, setup and teardown functions and so on - see nose.tools for more information. This module provides a set of useful decorators meant to be ready to use in your own tests. See the bottom of the file for the ready-made ones, and if you find yourself writing a new one that may be of generic use, add it here. Included decorators: Lightweight testing that remains unittest-compatible. - @parametric, for parametric test support that is vastly easier to use than nose's for debugging. With ours, if a test fails, the stack under inspection is that of the test and not that of the test framework. - An @as_unittest decorator can be used to tag any normal parameter-less function as a unittest TestCase. Then, both nose and normal unittest will recognize it as such. This will make it easier to migrate away from Nose if we ever need/want to while maintaining very lightweight tests. NOTE: This file contains IPython-specific decorators and imports the numpy.testing.decorators file, which we've copied verbatim. Any of our own code will be added at the bottom if we end up extending this. Authors ------- - Fernando Perez """ #----------------------------------------------------------------------------- # Copyright (C) 2009-2010 The IPython Development Team # # Distributed under the terms of the BSD License. The full license is in # the file COPYING, distributed as part of this software. #----------------------------------------------------------------------------- #----------------------------------------------------------------------------- # Imports #----------------------------------------------------------------------------- # Stdlib imports import inspect import sys import unittest # Third-party imports # This is Michele Simionato's decorator module, kept verbatim. from IPython.external.decorator import decorator, update_wrapper # Our own modules import nosepatch # monkeypatch nose # We already have python3-compliant code for parametric tests if sys.version[0]=='2': from _paramtestpy2 import parametric else: from _paramtestpy3 import parametric # Expose the unittest-driven decorators from ipunittest import ipdoctest, ipdocstring # Grab the numpy-specific decorators which we keep in a file that we # occasionally update from upstream: decorators.py is a copy of # numpy.testing.decorators, we expose all of it here. from IPython.external.decorators import * #----------------------------------------------------------------------------- # Classes and functions #----------------------------------------------------------------------------- # Simple example of the basic idea def as_unittest(func): """Decorator to make a simple function into a normal test via unittest.""" class Tester(unittest.TestCase): def test(self): func() Tester.__name__ = func.__name__ return Tester # Utility functions def apply_wrapper(wrapper,func): """Apply a wrapper to a function for decoration. This mixes Michele Simionato's decorator tool with nose's make_decorator, to apply a wrapper in a decorator so that all nose attributes, as well as function signature and other properties, survive the decoration cleanly. This will ensure that wrapped functions can still be well introspected via IPython, for example. """ import nose.tools return decorator(wrapper,nose.tools.make_decorator(func)(wrapper)) def make_label_dec(label,ds=None): """Factory function to create a decorator that applies one or more labels. Parameters ---------- label : string or sequence One or more labels that will be applied by the decorator to the functions it decorates. Labels are attributes of the decorated function with their value set to True. ds : string An optional docstring for the resulting decorator. If not given, a default docstring is auto-generated. Returns ------- A decorator. Examples -------- A simple labeling decorator: >>> slow = make_label_dec('slow') >>> print slow.__doc__ Labels a test as 'slow'. And one that uses multiple labels and a custom docstring: >>> rare = make_label_dec(['slow','hard'], ... "Mix labels 'slow' and 'hard' for rare tests.") >>> print rare.__doc__ Mix labels 'slow' and 'hard' for rare tests. Now, let's test using this one: >>> @rare ... def f(): pass ... >>> >>> f.slow True >>> f.hard True """ if isinstance(label,basestring): labels = [label] else: labels = label # Validate that the given label(s) are OK for use in setattr() by doing a # dry run on a dummy function. tmp = lambda : None for label in labels: setattr(tmp,label,True) # This is the actual decorator we'll return def decor(f): for label in labels: setattr(f,label,True) return f # Apply the user's docstring, or autogenerate a basic one if ds is None: ds = "Labels a test as %r." % label decor.__doc__ = ds return decor # Inspired by numpy's skipif, but uses the full apply_wrapper utility to # preserve function metadata better and allows the skip condition to be a # callable. def skipif(skip_condition, msg=None): ''' Make function raise SkipTest exception if skip_condition is true Parameters ---------- skip_condition : bool or callable. Flag to determine whether to skip test. If the condition is a callable, it is used at runtime to dynamically make the decision. This is useful for tests that may require costly imports, to delay the cost until the test suite is actually executed. msg : string Message to give on raising a SkipTest exception Returns ------- decorator : function Decorator, which, when applied to a function, causes SkipTest to be raised when the skip_condition was True, and the function to be called normally otherwise. Notes ----- You will see from the code that we had to further decorate the decorator with the nose.tools.make_decorator function in order to transmit function name, and various other metadata. ''' def skip_decorator(f): # Local import to avoid a hard nose dependency and only incur the # import time overhead at actual test-time. import nose # Allow for both boolean or callable skip conditions. if callable(skip_condition): skip_val = lambda : skip_condition() else: skip_val = lambda : skip_condition def get_msg(func,msg=None): """Skip message with information about function being skipped.""" if msg is None: out = 'Test skipped due to test condition.' else: out = msg return "Skipping test: %s. %s" % (func.__name__,out) # We need to define *two* skippers because Python doesn't allow both # return with value and yield inside the same function. def skipper_func(*args, **kwargs): """Skipper for normal test functions.""" if skip_val(): raise nose.SkipTest(get_msg(f,msg)) else: return f(*args, **kwargs) def skipper_gen(*args, **kwargs): """Skipper for test generators.""" if skip_val(): raise nose.SkipTest(get_msg(f,msg)) else: for x in f(*args, **kwargs): yield x # Choose the right skipper to use when building the actual generator. if nose.util.isgenerator(f): skipper = skipper_gen else: skipper = skipper_func return nose.tools.make_decorator(f)(skipper) return skip_decorator # A version with the condition set to true, common case just to attacha message # to a skip decorator def skip(msg=None): """Decorator factory - mark a test function for skipping from test suite. Parameters ---------- msg : string Optional message to be added. Returns ------- decorator : function Decorator, which, when applied to a function, causes SkipTest to be raised, with the optional message added. """ return skipif(True,msg) #----------------------------------------------------------------------------- # Utility functions for decorators def numpy_not_available(): """Can numpy be imported? Returns true if numpy does NOT import. This is used to make a decorator to skip tests that require numpy to be available, but delay the 'import numpy' to test execution time. """ try: import numpy np_not_avail = False except ImportError: np_not_avail = True return np_not_avail #----------------------------------------------------------------------------- # Decorators for public use skip_doctest = make_label_dec('skip_doctest', """Decorator - mark a function or method for skipping its doctest. This decorator allows you to mark a function whose docstring you wish to omit from testing, while preserving the docstring for introspection, help, etc.""") # Decorators to skip certain tests on specific platforms. skip_win32 = skipif(sys.platform == 'win32', "This test does not run under Windows") skip_linux = skipif(sys.platform == 'linux2', "This test does not run under Linux") skip_osx = skipif(sys.platform == 'darwin',"This test does not run under OS X") # Decorators to skip tests if not on specific platforms. skip_if_not_win32 = skipif(sys.platform != 'win32', "This test only runs under Windows") skip_if_not_linux = skipif(sys.platform != 'linux2', "This test only runs under Linux") skip_if_not_osx = skipif(sys.platform != 'darwin', "This test only runs under OSX") # Other skip decorators skipif_not_numpy = skipif(numpy_not_available,"This test requires numpy") skipknownfailure = skip('This test is known to fail')