1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98
|
from dataclasses import dataclass
from typing import Any, Optional, Dict
from unittest import SkipTest
from sybil import Example, Document
from sybil.example import NotEvaluated
class If:
def __init__(self, default_reason: str) -> None:
self.default_reason = default_reason
def __call__(self, condition: Any, reason: Optional[str] = None) -> Optional[str]:
if condition:
return reason or self.default_reason
return None
@dataclass
class SkipState:
active: bool = True
remove: bool = False
exception: Optional[Exception] = None
last_action: Optional[str] = None
class Skipper:
def __init__(self, directive: str) -> None:
self.document_state: Dict[Document, SkipState] = {}
self.directive = directive
def state_for(self, example: Example) -> SkipState:
document = example.document
if document not in self.document_state:
self.document_state[document] = SkipState()
return self.document_state[example.document]
def install(self, example: Example, state: SkipState, reason: Optional[str]) -> None:
document = example.document
document.push_evaluator(self)
if reason:
namespace = document.namespace.copy()
reason = reason.lstrip()
if reason.startswith('if'):
condition = reason[2:]
reason = 'if_' + condition
namespace['if_'] = If(condition)
reason = eval(reason, namespace)
if reason:
state.exception = SkipTest(reason)
else:
state.active = False
def remove(self, example: Example) -> None:
document = example.document
document.pop_evaluator(self)
del self.document_state[document]
def evaluate_skip_example(self, example: Example) -> None:
state = self.state_for(example)
directive = self.directive
action, reason = example.parsed
if action not in ('start', 'next', 'end'):
raise ValueError('Bad skip action: ' + action)
if state.last_action is None and action not in ('start', 'next'):
raise ValueError(f"'{directive}: {action}' must follow '{directive}: start'")
elif state.last_action and action != 'end':
raise ValueError(f"'{directive}: {action}' cannot follow '{directive}: {state.last_action}'")
state.last_action = action
if action == 'start':
self.install(example, state, reason)
elif action == 'next':
self.install(example, state, reason)
state.remove = True
elif action == 'end':
self.remove(example)
if reason:
raise ValueError("Cannot have condition on 'skip: end'")
def evaluate_other_example(self, example: Example) -> None:
state = self.state_for(example)
if state.remove:
self.remove(example)
if not state.active:
raise NotEvaluated()
if state.exception is not None:
raise state.exception
def __call__(self, example: Example) -> None:
if example.region.evaluator is self:
self.evaluate_skip_example(example)
else:
self.evaluate_other_example(example)
|