File: test_document.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 (150 lines) | stat: -rw-r--r-- 5,594 bytes parent folder | download | duplicates (2)
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
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
"""
This is a module doc string.
"""  # This is a comment on how annoying 3.7 and earlier are!
import re
from functools import partial
from pathlib import Path
from collections.abc import Iterable

from testfixtures import compare, ShouldRaise

from sybil import Region, Example
from sybil.document import PythonDocStringDocument, Document
from sybil.example import NotEvaluated, SybilFailure
from sybil.parsers.abstract.lexers import LexingException
from .helpers import ast_docstrings, parse, sample_path


def test_extract_docstring():
    """
    This is a function docstring.
    """
    python_source_code = Path(__file__).read_text()
    expected = list(ast_docstrings(python_source_code))
    actual = list(PythonDocStringDocument.extract_docstrings(python_source_code))
    compare(expected, actual=[text for _, _, text in actual], show_whitespace=True)
    compare(expected, actual=[python_source_code[s:e] for s, e, _ in actual], show_whitespace=True)


def test_all_docstrings_extracted_correctly(python_file):
    problems = []
    path, source = python_file
    expected = list(ast_docstrings(source))
    actual = list(PythonDocStringDocument.extract_docstrings(source))
    actual_from_start_end = [source[s:e].replace('\\"', '"') for s, e, _ in actual]
    check = partial(compare, raises=False, show_whitespace=True)
    for check_result in (
        check(expected=len(expected), actual=len(actual), prefix=f'{path}: number'),
        check(expected, actual=[text for _, _, text in actual], prefix=f'{path}: content'),
        check(expected, actual=actual_from_start_end, prefix=f'{path}: start/end'),
    ):
        if check_result:  # pragma: no cover - Only on failures!
            problems.append(check_result)
    if problems:  # pragma: no cover - Only on failures!
        raise AssertionError('docstrings not correctly extracted:\n\n'+'\n\n'.join(problems))


def test_evaluator_returns_non_string():

    def evaluator(example: Example) -> NotEvaluated:
        # This is a bug!
        return NotEvaluated()

    def parser(document: Document) -> Iterable[Region]:
        yield Region(0, 1, None, evaluator)

    examples, namespace = parse('sample1.txt', parser, expected=1)
    with ShouldRaise(SybilFailure(examples[0], 'NotEvaluated()')):
        examples[0].evaluate()


def test_nested_evaluators():

    def record(example: Example, evaluator_name):
        (instruction, id_, param) = example.parsed
        example.namespace['results'].append((instruction, id_, param, evaluator_name))

    def normal_evaluator(example: Example):
        record(example, 'normal')

    class InstructionEvaluator:
        def __init__(self, id_, mode):
            self.name = f'{id_}-{mode}'
            self.mode = mode
            assert hasattr(self, mode), mode

        def __call__(self, example: Example):
            (instruction, id_, param) = example.parsed
            if instruction in ('install', 'remove'):
                raise NotEvaluated()
            record(example, self.name)
            return getattr(self, self.mode)(id_)

        def install(self, example: Example):
            record(example, self.name+'-install')
            example.document.push_evaluator(self)

        def remove(self, example: Example):
            record(example, self.name + '-remove')
            example.document.pop_evaluator(self)

        def passthrough(self, id_: int):
            raise NotEvaluated()

        def all(self, id_: int):
            return ''

        def even(self, id_: int):
            if id_ % 2:
                raise NotEvaluated()

    evaluators = {}

    def parser(document: Document) -> Iterable[Region]:
        for match in re.finditer(r'(example|install|remove)-(\d+)(.+)?', document.text):
            instruction, id_, param = match.groups()
            id_ = int(id_)
            param = param.lstrip('-') if param else None
            if instruction == 'example':
                evaluator = normal_evaluator
            else:
                obj = evaluators.get(id_)
                if obj is None:
                    obj = evaluators.setdefault(id_, InstructionEvaluator(id_, param))
                evaluator = getattr(obj, instruction)
            yield Region(match.start(), match.end(), (instruction, id_, param), evaluator)

    examples, namespace = parse('nested-evaluators.txt', parser, expected=12)
    namespace['results'] = results = []
    for e in examples:
        e.evaluate()
    compare(results, expected=[
        ('example', 0, None, 'normal'),
        ('install', 1, 'passthrough', '1-passthrough-install'),
        ('example', 2, None, '1-passthrough'),
        ('example', 2, None, 'normal'),
        ('install', 3, 'all', '3-all-install'),
        ('example', 4, None, '3-all'),
        ('install', 5, 'even', '5-even-install'),
        ('example', 6, None, '5-even'),
        ('example', 7, None, '5-even'),
        ('example', 7, None, '3-all'),
        ('remove', 5, None, '5-even-remove'),
        ('example', 8, None, '3-all'),
        ('remove', 3, None, '3-all-remove'),
        ('example', 9, None, '1-passthrough'),
        ('example', 9, None, 'normal'),
    ])


def test_nested_evaluators_not_evaluated_from_region():

    def evaluator(example: Example):
        raise NotEvaluated()

    def parser(document: Document) -> Iterable[Region]:
        yield Region(0, 1, None, evaluator)

    examples, namespace = parse('sample1.txt', parser, expected=1)
    with ShouldRaise(SybilFailure(examples[0], f'{evaluator!r} should not raise NotEvaluated()')):
        examples[0].evaluate()