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()
|