diff options
author | Mike Bayer <mike_mp@zzzcomputing.com> | 2014-07-26 18:26:22 -0400 |
---|---|---|
committer | Mike Bayer <mike_mp@zzzcomputing.com> | 2014-07-26 18:26:22 -0400 |
commit | c85fa9fa50ca32523c160eaab58ab6d2b97aacc6 (patch) | |
tree | 15a1582ef6883027c1bedeee6f5c0a95e46984d1 /lib/sqlalchemy/testing/exclusions.py | |
parent | 759e8aec138138683ed15a264aaa392d499c2b34 (diff) | |
download | sqlalchemy-c85fa9fa50ca32523c160eaab58ab6d2b97aacc6.tar.gz |
- rework the exclusions system to have much better support for compound
rules, better message formatting
Diffstat (limited to 'lib/sqlalchemy/testing/exclusions.py')
-rw-r--r-- | lib/sqlalchemy/testing/exclusions.py | 274 |
1 files changed, 164 insertions, 110 deletions
diff --git a/lib/sqlalchemy/testing/exclusions.py b/lib/sqlalchemy/testing/exclusions.py index fd43865aa..f6ef72408 100644 --- a/lib/sqlalchemy/testing/exclusions.py +++ b/lib/sqlalchemy/testing/exclusions.py @@ -11,83 +11,137 @@ from .plugin.plugin_base import SkipTest from ..util import decorator from . import config from .. import util -import contextlib import inspect +import contextlib + + +def skip_if(predicate, reason=None): + rule = compound() + pred = _as_predicate(predicate, reason) + rule.skips.add(pred) + return rule -class skip_if(object): - def __init__(self, predicate, reason=None): - self.predicate = _as_predicate(predicate) - self.reason = reason +def fails_if(predicate, reason=None): + rule = compound() + pred = _as_predicate(predicate, reason) + rule.fails.add(pred) + return rule - _fails_on = None + +class compound(object): + def __init__(self): + self.fails = set() + self.skips = set() def __add__(self, other): - def decorate(fn): - return other(self(fn)) - return decorate + return self.add(other) + + def add(self, *others): + copy = compound() + copy.fails.update(self.fails) + copy.skips.update(self.skips) + for other in others: + copy.fails.update(other.fails) + copy.skips.update(other.skips) + return copy + + def not_(self): + copy = compound() + copy.fails.update(NotPredicate(fail) for fail in self.fails) + copy.skips.update(NotPredicate(skip) for skip in self.skips) + return copy @property def enabled(self): return self.enabled_for_config(config._current) def enabled_for_config(self, config): - return not self.predicate(config) - - @contextlib.contextmanager - def fail_if(self, name='block'): - try: - yield - except Exception as ex: - if self.predicate(config._current): - print(("%s failed as expected (%s): %s " % ( - name, self.predicate, str(ex)))) - else: - raise + for predicate in self.skips.union(self.fails): + if predicate(config): + return False else: - if self.predicate(config._current): - raise AssertionError( - "Unexpected success for '%s' (%s)" % - (name, self.predicate)) + return True + + def matching_config_reasons(self, config): + return [ + predicate._as_string(config) for predicate + in self.skips.union(self.fails) + if predicate(config) + ] def __call__(self, fn): + if hasattr(fn, '_sa_exclusion_extend'): + fn._sa_exclusion_extend(self) + return fn + + def extend(other): + self.skips.update(other.skips) + self.fails.update(other.fails) + @decorator def decorate(fn, *args, **kw): - if self.predicate(config._current): - if self.reason: - msg = "'%s' : %s" % ( - fn.__name__, - self.reason - ) - else: - msg = "'%s': %s" % ( - fn.__name__, self.predicate - ) - raise SkipTest(msg) - else: - if self._fails_on: - with self._fails_on.fail_if(name=fn.__name__): - return fn(*args, **kw) - else: - return fn(*args, **kw) - return decorate(fn) + return self._do(config._current, fn, *args, **kw) + decorated = decorate(fn) + decorated._sa_exclusion_extend = extend + return decorated - def fails_on(self, other, reason=None): - self._fails_on = skip_if(other, reason) - return self - def fails_on_everything_except(self, *dbs): - self._fails_on = skip_if(fails_on_everything_except(*dbs)) - return self + @contextlib.contextmanager + def fail_if(self): + all_fails = compound() + all_fails.fails.update(self.skips.union(self.fails)) + try: + yield + except Exception as ex: + all_fails._expect_failure(config._current, ex) + else: + all_fails._expect_success(config._current) + + def _do(self, config, fn, *args, **kw): + for skip in self.skips: + if skip(config): + msg = "'%s' : %s" % ( + fn.__name__, + skip._as_string(config) + ) + raise SkipTest(msg) -class fails_if(skip_if): - def __call__(self, fn): - @decorator - def decorate(fn, *args, **kw): - with self.fail_if(name=fn.__name__): - return fn(*args, **kw) - return decorate(fn) + try: + return_value = fn(*args, **kw) + except Exception as ex: + self._expect_failure(config, ex, name=fn.__name__) + else: + self._expect_success(config, name=fn.__name__) + return return_value + + def _expect_failure(self, config, ex, name='block'): + for fail in self.fails: + if fail(config): + print(("%s failed as expected (%s): %s " % ( + name, fail._as_string(config), str(ex)))) + break + else: + raise ex + + def _expect_success(self, config, name='block'): + if not self.fails: + return + for fail in self.fails: + if not fail(config): + break + else: + raise AssertionError( + "Unexpected success for '%s' (%s)" % + ( + name, + " and ".join( + fail._as_string(config) + for fail in self.fails + ) + ) + ) def only_if(predicate, reason=None): @@ -102,13 +156,18 @@ def succeeds_if(predicate, reason=None): class Predicate(object): @classmethod - def as_predicate(cls, predicate): - if isinstance(predicate, skip_if): - return NotPredicate(predicate.predicate) + def as_predicate(cls, predicate, description=None): + if isinstance(predicate, compound): + return cls.as_predicate(predicate.fails.union(predicate.skips)) + elif isinstance(predicate, Predicate): + if description and predicate.description is None: + predicate.description = description return predicate - elif isinstance(predicate, list): - return OrPredicate([cls.as_predicate(pred) for pred in predicate]) + elif isinstance(predicate, (list, set)): + return OrPredicate( + [cls.as_predicate(pred) for pred in predicate], + description) elif isinstance(predicate, tuple): return SpecPredicate(*predicate) elif isinstance(predicate, util.string_types): @@ -119,12 +178,26 @@ class Predicate(object): op = tokens.pop(0) if tokens: spec = tuple(int(d) for d in tokens.pop(0).split(".")) - return SpecPredicate(db, op, spec) + return SpecPredicate(db, op, spec, description=description) elif util.callable(predicate): - return LambdaPredicate(predicate) + return LambdaPredicate(predicate, description) else: assert False, "unknown predicate type: %s" % predicate + def _format_description(self, config, negate=False): + bool_ = self(config) + if negate: + bool_ = not negate + return self.description % { + "driver": config.db.url.get_driver_name(), + "database": config.db.url.get_backend_name(), + "doesnt_support": "doesn't support" if bool_ else "does support", + "does_support": "does support" if bool_ else "doesn't support" + } + + def _as_string(self, config=None, negate=False): + raise NotImplementedError() + class BooleanPredicate(Predicate): def __init__(self, value, description=None): @@ -134,14 +207,8 @@ class BooleanPredicate(Predicate): def __call__(self, config): return self.value - def _as_string(self, negate=False): - if negate: - return "not " + self.description - else: - return self.description - - def __str__(self): - return self._as_string() + def _as_string(self, config, negate=False): + return self._format_description(config, negate=negate) class SpecPredicate(Predicate): @@ -185,9 +252,9 @@ class SpecPredicate(Predicate): else: return True - def _as_string(self, negate=False): + def _as_string(self, config, negate=False): if self.description is not None: - return self.description + return self._format_description(config) elif self.op is None: if negate: return "not %s" % self.db @@ -207,9 +274,6 @@ class SpecPredicate(Predicate): self.spec ) - def __str__(self): - return self._as_string() - class LambdaPredicate(Predicate): def __init__(self, lambda_, description=None, args=None, kw=None): @@ -230,25 +294,23 @@ class LambdaPredicate(Predicate): def __call__(self, config): return self.lambda_(config) - def _as_string(self, negate=False): - if negate: - return "not " + self.description - else: - return self.description - - def __str__(self): - return self._as_string() + def _as_string(self, config, negate=False): + return self._format_description(config) class NotPredicate(Predicate): - def __init__(self, predicate): + def __init__(self, predicate, description=None): self.predicate = predicate + self.description = description def __call__(self, config): return not self.predicate(config) - def __str__(self): - return self.predicate._as_string(True) + def _as_string(self, config, negate=False): + if self.description: + return self._format_description(config, not negate) + else: + return self.predicate._as_string(config, not negate) class OrPredicate(Predicate): @@ -259,40 +321,32 @@ class OrPredicate(Predicate): def __call__(self, config): for pred in self.predicates: if pred(config): - self._str = pred return True return False - _str = None - - def _eval_str(self, negate=False): - if self._str is None: - if negate: - conjunction = " and " - else: - conjunction = " or " - return conjunction.join(p._as_string(negate=negate) - for p in self.predicates) + def _eval_str(self, config, negate=False): + if negate: + conjunction = " and " else: - return self._str._as_string(negate=negate) + conjunction = " or " + return conjunction.join(p._as_string(config, negate=negate) + for p in self.predicates) - def _negation_str(self): + def _negation_str(self, config): if self.description is not None: - return "Not " + (self.description % {"spec": self._str}) + return "Not " + self._format_description(config) else: - return self._eval_str(negate=True) + return self._eval_str(config, negate=True) - def _as_string(self, negate=False): + def _as_string(self, config, negate=False): if negate: - return self._negation_str() + return self._negation_str(config) else: if self.description is not None: - return self.description % {"spec": self._str} + return self._format_description(config) else: - return self._eval_str() + return self._eval_str(config) - def __str__(self): - return self._as_string() _as_predicate = Predicate.as_predicate @@ -341,8 +395,8 @@ def fails_on(db, reason=None): def fails_on_everything_except(*dbs): return succeeds_if( OrPredicate([ - SpecPredicate(db) for db in dbs - ]) + SpecPredicate(db) for db in dbs + ]) ) |