File: conftest.py

package info (click to toggle)
python-public 4.1.0-2
  • links: PTS, VCS
  • area: main
  • in suites: sid, trixie
  • size: 200 kB
  • sloc: python: 366; makefile: 11; sh: 3
file content (105 lines) | stat: -rw-r--r-- 3,439 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
99
100
101
102
103
104
105
import os
import sys

from contextlib import ExitStack, contextmanager
from doctest import ELLIPSIS, REPORT_NDIFF, NORMALIZE_WHITESPACE
from sybil import Sybil
from sybil.parsers.codeblock import PythonCodeBlockParser
from sybil.parsers.doctest import DocTestParser
from tempfile import TemporaryDirectory
from types import ModuleType

import pytest

DOCTEST_FLAGS = ELLIPSIS | NORMALIZE_WHITESPACE | REPORT_NDIFF


@contextmanager
def syspath(directory):
    try:
        sys.path.insert(0, directory)
        yield
    finally:
        assert sys.path[0] == directory
        del sys.path[0]


@contextmanager
def sysmodules():
    modules = sys.modules.copy()
    try:
        yield
    finally:
        sys.modules = modules


class ExampleModule:
    def __init__(self, path):
        self.path = path

    def __call__(self, contents):
        with open(self.path, 'w', encoding='utf-8') as fp:
            fp.write(contents)


@pytest.fixture
def example():
    with ExitStack() as resources:
        tmpdir = resources.enter_context(TemporaryDirectory())
        resources.enter_context(sysmodules())
        resources.enter_context(syspath(tmpdir))
        path = os.path.join(tmpdir, 'example.py')
        yield ExampleModule(path)


class DoctestNamespace:
    def setup(self, namespace):
        # The doctests in .rst files require that they mimic being executed in
        # a particular module.  The stdlib doctest functionality creates its
        # own globals namespace, unattached to any specific module object.
        # This causes coordination problems between the apparent globals that
        # the doctest sees, and public()'s implementation.
        #
        # We can't make them the same namespace because doing so violates
        # other assumptions in the public() function's code, but we can set
        # things up to be close enough for the doctest to pass.
        #
        # We use two techniques to make this work.  First, we create a test
        # module and ensure that its string name is assigned to the
        # namespace's __name__ attribute.  We also ensure that the module by
        # that name is in the sys.modules cache (and cleaned up in the
        # teardown).
        #
        # The second thing we need to do is to ensure that the module and the
        # namespace the doctest is executed in, share the same list object in
        # their __all__ attribute.  Now, generally public() will create
        # __all__ if it doesn't exist, but we can test that in the unittests,
        # so it's good enough to just initialize both name bindings to the
        # same list object here.
        #
        # There is some further discussion in this Sybil ticket:
        # https://github.com/cjw296/sybil/issues/21
        self._testmod = ModuleType('testmod')
        namespace['__name__'] = self._testmod.__name__
        sys.modules[self._testmod.__name__] = self._testmod
        # Used in the doctests to provide a clean __all__.
        def reset():
            self._testmod.__all__ = namespace['__all__'] = []
        reset()
        namespace['reset'] = reset

    def teardown(self, namespace):
        del sys.modules[self._testmod.__name__]


namespace = DoctestNamespace()


pytest_collect_file = Sybil(
    parsers=[
        DocTestParser(optionflags=DOCTEST_FLAGS),
        PythonCodeBlockParser(),
        ],
    pattern='*.rst',
    setup=namespace.setup,
    ).pytest()