File: skip.py

package info (click to toggle)
python-sybil 9.2.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 1,148 kB
  • sloc: python: 4,510; makefile: 90
file content (98 lines) | stat: -rw-r--r-- 3,324 bytes parent folder | download
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)