diff options
author | Alan McIntyre <alan.mcintyre@local> | 2009-02-05 20:11:40 +0000 |
---|---|---|
committer | Alan McIntyre <alan.mcintyre@local> | 2009-02-05 20:11:40 +0000 |
commit | aeb090d5f7081a166357fa850950da89feb25e97 (patch) | |
tree | 394185643a227b2b23fdbeddb575d5720bfa4ed5 /numpy/testing | |
parent | 911e94dd0a64adae9fb2057fb0210e512a9b7d4a (diff) | |
download | numpy-aeb090d5f7081a166357fa850950da89feb25e97.tar.gz |
Issue #957:
- Fix problems with test decorators when used on test generators.
- The skip/fail arguments for skipif and knownfailureif can now be
either a bool or a callable that returns a bool.
- Added tests for the test decorators.
Diffstat (limited to 'numpy/testing')
-rw-r--r-- | numpy/testing/decorators.py | 81 | ||||
-rw-r--r-- | numpy/testing/tests/test_decorators.py | 156 |
2 files changed, 221 insertions, 16 deletions
diff --git a/numpy/testing/decorators.py b/numpy/testing/decorators.py index 418c1d9fa..70301f250 100644 --- a/numpy/testing/decorators.py +++ b/numpy/testing/decorators.py @@ -51,8 +51,11 @@ def skipif(skip_condition, msg=None): Parameters --------- - skip_condition : bool - Flag to determine whether to skip test (True) or not (False) + 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 @@ -69,28 +72,66 @@ def skipif(skip_condition, msg=None): decorator with the nose.tools.make_decorator function in order to transmit function name, and various other metadata. ''' - if msg is None: - msg = 'Test skipped due to test condition' + 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 - def skipper(*args, **kwargs): - if skip_condition: - raise nose.SkipTest, msg + + # 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 = '\n'+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 decorator. + if nose.util.isgenerator(f): + skipper = skipper_gen + else: + skipper = skipper_func + return nose.tools.make_decorator(f)(skipper) + return skip_decorator -def knownfailureif(skip_condition, msg=None): - ''' Make function raise KnownFailureTest exception if skip_condition is true + +def knownfailureif(fail_condition, msg=None): + ''' Make function raise KnownFailureTest exception if fail_condition is true Parameters --------- - skip_condition : bool + fail_condition : bool or callable. Flag to determine whether to mark test as known failure (True) - or not (False) + or not (False). 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 KnownFailureTest exception @@ -109,15 +150,23 @@ def knownfailureif(skip_condition, msg=None): ''' if msg is None: msg = 'Test skipped due to known failure' - def skip_decorator(f): + + # Allow for both boolean or callable known failure conditions. + if callable(fail_condition): + fail_val = lambda : fail_condition() + else: + fail_val = lambda : fail_condition + + def knownfail_decorator(f): # Local import to avoid a hard nose dependency and only incur the # import time overhead at actual test-time. import nose from noseclasses import KnownFailureTest - def skipper(*args, **kwargs): - if skip_condition: + def knownfailer(*args, **kwargs): + if fail_val(): raise KnownFailureTest, msg else: return f(*args, **kwargs) - return nose.tools.make_decorator(f)(skipper) - return skip_decorator + return nose.tools.make_decorator(f)(knownfailer) + + return knownfail_decorator diff --git a/numpy/testing/tests/test_decorators.py b/numpy/testing/tests/test_decorators.py new file mode 100644 index 000000000..504971e61 --- /dev/null +++ b/numpy/testing/tests/test_decorators.py @@ -0,0 +1,156 @@ +import numpy as np +from numpy.testing import * +from numpy.testing.noseclasses import KnownFailureTest +import nose + +def test_slow(): + @dec.slow + def slow_func(x,y,z): + pass + + assert(slow_func.slow) + +def test_setastest(): + @dec.setastest() + def f_default(a): + pass + + @dec.setastest(True) + def f_istest(a): + pass + + @dec.setastest(False) + def f_isnottest(a): + pass + + assert(f_default.__test__) + assert(f_istest.__test__) + assert(not f_isnottest.__test__) + +class DidntSkipException(Exception): + pass + +def test_skip_functions_hardcoded(): + @dec.skipif(True) + def f1(x): + raise DidntSkipException + + try: + f1('a') + except DidntSkipException: + raise Exception('Failed to skip') + except nose.SkipTest: + pass + + @dec.skipif(False) + def f2(x): + raise DidntSkipException + + try: + f2('a') + except DidntSkipException: + pass + except nose.SkipTest: + raise Exception('Skipped when not expected to') + + +def test_skip_functions_callable(): + def skip_tester(): + return skip_flag == 'skip me!' + + @dec.skipif(skip_tester) + def f1(x): + raise DidntSkipException + + try: + skip_flag = 'skip me!' + f1('a') + except DidntSkipException: + raise Exception('Failed to skip') + except nose.SkipTest: + pass + + @dec.skipif(skip_tester) + def f2(x): + raise DidntSkipException + + try: + skip_flag = 'five is right out!' + f2('a') + except DidntSkipException: + pass + except nose.SkipTest: + raise Exception('Skipped when not expected to') + + +def test_skip_generators_hardcoded(): + @dec.knownfailureif(True, "This test is known to fail") + def g1(x): + for i in xrange(x): + yield i + + try: + for j in g1(10): + pass + except KnownFailureTest: + pass + else: + raise Exception('Failed to mark as known failure') + + + @dec.knownfailureif(False, "This test is NOT known to fail") + def g2(x): + for i in xrange(x): + yield i + raise DidntSkipException('FAIL') + + try: + for j in g2(10): + pass + except KnownFailureTest: + raise Exception('Marked incorretly as known failure') + except DidntSkipException: + pass + + +def test_skip_generators_callable(): + def skip_tester(): + return skip_flag == 'skip me!' + + @dec.knownfailureif(skip_tester, "This test is known to fail") + def g1(x): + for i in xrange(x): + yield i + + try: + skip_flag = 'skip me!' + for j in g1(10): + pass + except KnownFailureTest: + pass + else: + raise Exception('Failed to mark as known failure') + + + @dec.knownfailureif(skip_tester, "This test is NOT known to fail") + def g2(x): + for i in xrange(x): + yield i + raise DidntSkipException('FAIL') + + try: + skip_flag = 'do not skip' + for j in g2(10): + pass + except KnownFailureTest: + raise Exception('Marked incorretly as known failure') + except DidntSkipException: + pass + + +if __name__ == '__main__': + run_module_suite() + + + + |