exclusions.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443
  1. # testing/exclusions.py
  2. # Copyright (C) 2005-2017 the SQLAlchemy authors and contributors
  3. # <see AUTHORS file>
  4. #
  5. # This module is part of SQLAlchemy and is released under
  6. # the MIT License: http://www.opensource.org/licenses/mit-license.php
  7. import operator
  8. from ..util import decorator
  9. from . import config
  10. from .. import util
  11. import inspect
  12. import contextlib
  13. from sqlalchemy.util.compat import inspect_getargspec
  14. def skip_if(predicate, reason=None):
  15. rule = compound()
  16. pred = _as_predicate(predicate, reason)
  17. rule.skips.add(pred)
  18. return rule
  19. def fails_if(predicate, reason=None):
  20. rule = compound()
  21. pred = _as_predicate(predicate, reason)
  22. rule.fails.add(pred)
  23. return rule
  24. class compound(object):
  25. def __init__(self):
  26. self.fails = set()
  27. self.skips = set()
  28. self.tags = set()
  29. def __add__(self, other):
  30. return self.add(other)
  31. def add(self, *others):
  32. copy = compound()
  33. copy.fails.update(self.fails)
  34. copy.skips.update(self.skips)
  35. copy.tags.update(self.tags)
  36. for other in others:
  37. copy.fails.update(other.fails)
  38. copy.skips.update(other.skips)
  39. copy.tags.update(other.tags)
  40. return copy
  41. def not_(self):
  42. copy = compound()
  43. copy.fails.update(NotPredicate(fail) for fail in self.fails)
  44. copy.skips.update(NotPredicate(skip) for skip in self.skips)
  45. copy.tags.update(self.tags)
  46. return copy
  47. @property
  48. def enabled(self):
  49. return self.enabled_for_config(config._current)
  50. def enabled_for_config(self, config):
  51. for predicate in self.skips.union(self.fails):
  52. if predicate(config):
  53. return False
  54. else:
  55. return True
  56. def matching_config_reasons(self, config):
  57. return [
  58. predicate._as_string(config) for predicate
  59. in self.skips.union(self.fails)
  60. if predicate(config)
  61. ]
  62. def include_test(self, include_tags, exclude_tags):
  63. return bool(
  64. not self.tags.intersection(exclude_tags) and
  65. (not include_tags or self.tags.intersection(include_tags))
  66. )
  67. def _extend(self, other):
  68. self.skips.update(other.skips)
  69. self.fails.update(other.fails)
  70. self.tags.update(other.tags)
  71. def __call__(self, fn):
  72. if hasattr(fn, '_sa_exclusion_extend'):
  73. fn._sa_exclusion_extend._extend(self)
  74. return fn
  75. @decorator
  76. def decorate(fn, *args, **kw):
  77. return self._do(config._current, fn, *args, **kw)
  78. decorated = decorate(fn)
  79. decorated._sa_exclusion_extend = self
  80. return decorated
  81. @contextlib.contextmanager
  82. def fail_if(self):
  83. all_fails = compound()
  84. all_fails.fails.update(self.skips.union(self.fails))
  85. try:
  86. yield
  87. except Exception as ex:
  88. all_fails._expect_failure(config._current, ex)
  89. else:
  90. all_fails._expect_success(config._current)
  91. def _do(self, cfg, fn, *args, **kw):
  92. for skip in self.skips:
  93. if skip(cfg):
  94. msg = "'%s' : %s" % (
  95. fn.__name__,
  96. skip._as_string(cfg)
  97. )
  98. config.skip_test(msg)
  99. try:
  100. return_value = fn(*args, **kw)
  101. except Exception as ex:
  102. self._expect_failure(cfg, ex, name=fn.__name__)
  103. else:
  104. self._expect_success(cfg, name=fn.__name__)
  105. return return_value
  106. def _expect_failure(self, config, ex, name='block'):
  107. for fail in self.fails:
  108. if fail(config):
  109. print(("%s failed as expected (%s): %s " % (
  110. name, fail._as_string(config), str(ex))))
  111. break
  112. else:
  113. util.raise_from_cause(ex)
  114. def _expect_success(self, config, name='block'):
  115. if not self.fails:
  116. return
  117. for fail in self.fails:
  118. if not fail(config):
  119. break
  120. else:
  121. raise AssertionError(
  122. "Unexpected success for '%s' (%s)" %
  123. (
  124. name,
  125. " and ".join(
  126. fail._as_string(config)
  127. for fail in self.fails
  128. )
  129. )
  130. )
  131. def requires_tag(tagname):
  132. return tags([tagname])
  133. def tags(tagnames):
  134. comp = compound()
  135. comp.tags.update(tagnames)
  136. return comp
  137. def only_if(predicate, reason=None):
  138. predicate = _as_predicate(predicate)
  139. return skip_if(NotPredicate(predicate), reason)
  140. def succeeds_if(predicate, reason=None):
  141. predicate = _as_predicate(predicate)
  142. return fails_if(NotPredicate(predicate), reason)
  143. class Predicate(object):
  144. @classmethod
  145. def as_predicate(cls, predicate, description=None):
  146. if isinstance(predicate, compound):
  147. return cls.as_predicate(predicate.enabled_for_config, description)
  148. elif isinstance(predicate, Predicate):
  149. if description and predicate.description is None:
  150. predicate.description = description
  151. return predicate
  152. elif isinstance(predicate, (list, set)):
  153. return OrPredicate(
  154. [cls.as_predicate(pred) for pred in predicate],
  155. description)
  156. elif isinstance(predicate, tuple):
  157. return SpecPredicate(*predicate)
  158. elif isinstance(predicate, util.string_types):
  159. tokens = predicate.split(" ", 2)
  160. op = spec = None
  161. db = tokens.pop(0)
  162. if tokens:
  163. op = tokens.pop(0)
  164. if tokens:
  165. spec = tuple(int(d) for d in tokens.pop(0).split("."))
  166. return SpecPredicate(db, op, spec, description=description)
  167. elif util.callable(predicate):
  168. return LambdaPredicate(predicate, description)
  169. else:
  170. assert False, "unknown predicate type: %s" % predicate
  171. def _format_description(self, config, negate=False):
  172. bool_ = self(config)
  173. if negate:
  174. bool_ = not negate
  175. return self.description % {
  176. "driver": config.db.url.get_driver_name()
  177. if config else "<no driver>",
  178. "database": config.db.url.get_backend_name()
  179. if config else "<no database>",
  180. "doesnt_support": "doesn't support" if bool_ else "does support",
  181. "does_support": "does support" if bool_ else "doesn't support"
  182. }
  183. def _as_string(self, config=None, negate=False):
  184. raise NotImplementedError()
  185. class BooleanPredicate(Predicate):
  186. def __init__(self, value, description=None):
  187. self.value = value
  188. self.description = description or "boolean %s" % value
  189. def __call__(self, config):
  190. return self.value
  191. def _as_string(self, config, negate=False):
  192. return self._format_description(config, negate=negate)
  193. class SpecPredicate(Predicate):
  194. def __init__(self, db, op=None, spec=None, description=None):
  195. self.db = db
  196. self.op = op
  197. self.spec = spec
  198. self.description = description
  199. _ops = {
  200. '<': operator.lt,
  201. '>': operator.gt,
  202. '==': operator.eq,
  203. '!=': operator.ne,
  204. '<=': operator.le,
  205. '>=': operator.ge,
  206. 'in': operator.contains,
  207. 'between': lambda val, pair: val >= pair[0] and val <= pair[1],
  208. }
  209. def __call__(self, config):
  210. engine = config.db
  211. if "+" in self.db:
  212. dialect, driver = self.db.split('+')
  213. else:
  214. dialect, driver = self.db, None
  215. if dialect and engine.name != dialect:
  216. return False
  217. if driver is not None and engine.driver != driver:
  218. return False
  219. if self.op is not None:
  220. assert driver is None, "DBAPI version specs not supported yet"
  221. version = _server_version(engine)
  222. oper = hasattr(self.op, '__call__') and self.op \
  223. or self._ops[self.op]
  224. return oper(version, self.spec)
  225. else:
  226. return True
  227. def _as_string(self, config, negate=False):
  228. if self.description is not None:
  229. return self._format_description(config)
  230. elif self.op is None:
  231. if negate:
  232. return "not %s" % self.db
  233. else:
  234. return "%s" % self.db
  235. else:
  236. if negate:
  237. return "not %s %s %s" % (
  238. self.db,
  239. self.op,
  240. self.spec
  241. )
  242. else:
  243. return "%s %s %s" % (
  244. self.db,
  245. self.op,
  246. self.spec
  247. )
  248. class LambdaPredicate(Predicate):
  249. def __init__(self, lambda_, description=None, args=None, kw=None):
  250. spec = inspect_getargspec(lambda_)
  251. if not spec[0]:
  252. self.lambda_ = lambda db: lambda_()
  253. else:
  254. self.lambda_ = lambda_
  255. self.args = args or ()
  256. self.kw = kw or {}
  257. if description:
  258. self.description = description
  259. elif lambda_.__doc__:
  260. self.description = lambda_.__doc__
  261. else:
  262. self.description = "custom function"
  263. def __call__(self, config):
  264. return self.lambda_(config)
  265. def _as_string(self, config, negate=False):
  266. return self._format_description(config)
  267. class NotPredicate(Predicate):
  268. def __init__(self, predicate, description=None):
  269. self.predicate = predicate
  270. self.description = description
  271. def __call__(self, config):
  272. return not self.predicate(config)
  273. def _as_string(self, config, negate=False):
  274. if self.description:
  275. return self._format_description(config, not negate)
  276. else:
  277. return self.predicate._as_string(config, not negate)
  278. class OrPredicate(Predicate):
  279. def __init__(self, predicates, description=None):
  280. self.predicates = predicates
  281. self.description = description
  282. def __call__(self, config):
  283. for pred in self.predicates:
  284. if pred(config):
  285. return True
  286. return False
  287. def _eval_str(self, config, negate=False):
  288. if negate:
  289. conjunction = " and "
  290. else:
  291. conjunction = " or "
  292. return conjunction.join(p._as_string(config, negate=negate)
  293. for p in self.predicates)
  294. def _negation_str(self, config):
  295. if self.description is not None:
  296. return "Not " + self._format_description(config)
  297. else:
  298. return self._eval_str(config, negate=True)
  299. def _as_string(self, config, negate=False):
  300. if negate:
  301. return self._negation_str(config)
  302. else:
  303. if self.description is not None:
  304. return self._format_description(config)
  305. else:
  306. return self._eval_str(config)
  307. _as_predicate = Predicate.as_predicate
  308. def _is_excluded(db, op, spec):
  309. return SpecPredicate(db, op, spec)(config._current)
  310. def _server_version(engine):
  311. """Return a server_version_info tuple."""
  312. # force metadata to be retrieved
  313. conn = engine.connect()
  314. version = getattr(engine.dialect, 'server_version_info', ())
  315. conn.close()
  316. return version
  317. def db_spec(*dbs):
  318. return OrPredicate(
  319. [Predicate.as_predicate(db) for db in dbs]
  320. )
  321. def open():
  322. return skip_if(BooleanPredicate(False, "mark as execute"))
  323. def closed():
  324. return skip_if(BooleanPredicate(True, "marked as skip"))
  325. def fails(reason=None):
  326. return fails_if(BooleanPredicate(True, reason or "expected to fail"))
  327. @decorator
  328. def future(fn, *arg):
  329. return fails_if(LambdaPredicate(fn), "Future feature")
  330. def fails_on(db, reason=None):
  331. return fails_if(Predicate.as_predicate(db), reason)
  332. def fails_on_everything_except(*dbs):
  333. return succeeds_if(
  334. OrPredicate([
  335. Predicate.as_predicate(db) for db in dbs
  336. ])
  337. )
  338. def skip(db, reason=None):
  339. return skip_if(Predicate.as_predicate(db), reason)
  340. def only_on(dbs, reason=None):
  341. return only_if(
  342. OrPredicate([Predicate.as_predicate(db) for db in util.to_list(dbs)])
  343. )
  344. def exclude(db, op, spec, reason=None):
  345. return skip_if(SpecPredicate(db, op, spec), reason)
  346. def against(config, *queries):
  347. assert queries, "no queries sent!"
  348. return OrPredicate([
  349. Predicate.as_predicate(query)
  350. for query in queries
  351. ])(config)