File: test_doctest.py

package info (click to toggle)
python-fs 2.4.16-7
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 1,944 kB
  • sloc: python: 13,048; makefile: 226; sh: 3
file content (194 lines) | stat: -rw-r--r-- 6,233 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
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
# coding: utf-8
"""Test doctest contained tests in every file of the module.
"""
import doctest
import importlib
import os
import pkgutil
import tempfile
import time
import types
import unittest
import warnings
from pprint import pprint

try:
    from unittest import mock
except ImportError:
    import mock

import six

import fs
import fs.opener.parse
from fs.memoryfs import MemoryFS
from fs.subfs import ClosingSubFS

# --- Mocks ------------------------------------------------------------------


def _home_fs():
    """Create a mock filesystem that matches the XDG user-dirs spec."""
    home_fs = MemoryFS()
    home_fs.makedir("Desktop")
    home_fs.makedir("Documents")
    home_fs.makedir("Downloads")
    home_fs.makedir("Music")
    home_fs.makedir("Pictures")
    home_fs.makedir("Public")
    home_fs.makedir("Templates")
    home_fs.makedir("Videos")
    return home_fs


def _open_fs(path):
    """A mock `open_fs` that avoids side effects when running doctests."""
    if "://" not in path:
        path = "osfs://{}".format(path)
    parse_result = fs.opener.parse(path)
    if parse_result.protocol == "osfs" and parse_result.resource == "~":
        home_fs = _home_fs()
        if parse_result.path is not None:
            home_fs = home_fs.opendir(parse_result.path, factory=ClosingSubFS)
        return home_fs
    elif parse_result.protocol in {"ftp", "ftps", "mem", "temp"}:
        return MemoryFS()
    else:
        raise RuntimeError("not allowed in doctests: {}".format(path))


def _my_fs(module):
    """Create a mock filesystem to be used in examples."""
    my_fs = MemoryFS()
    if module == "fs.base":
        my_fs.makedir("Desktop")
        my_fs.makedir("Videos")
        my_fs.touch("Videos/starwars.mov")
        my_fs.touch("file.txt")
    elif module == "fs.info":
        my_fs.touch("foo.tar.gz")
        my_fs.settext("foo.py", "print('Hello, world!')")
        my_fs.makedir("bar")
    elif module in {"fs.walk", "fs.glob"}:
        my_fs.makedir("dir1")
        my_fs.makedir("dir2")
        my_fs.settext("foo.py", "print('Hello, world!')")
        my_fs.touch("foo.pyc")
        my_fs.settext("bar.py", "print('ok')\n\n# this is a comment\n")
        my_fs.touch("bar.pyc")
    return my_fs


def _open(filename, mode="r"):
    """A mock `open` that actually opens a temporary file."""
    return tempfile.NamedTemporaryFile(mode="r+" if mode == "r" else mode)


# --- Loader protocol --------------------------------------------------------


def _load_tests_from_module(tests, module, globs, setUp=None, tearDown=None):
    """Load tests from module, iterating through submodules."""
    for attr in (getattr(module, x) for x in dir(module) if not x.startswith("_")):
        if isinstance(attr, types.ModuleType):
            suite = doctest.DocTestSuite(
                attr,
                globs,
                setUp=setUp,
                tearDown=tearDown,
                optionflags=+doctest.ELLIPSIS,
            )
            tests.addTests(suite)
    return tests


def _load_tests(loader, tests, ignore):
    """`load_test` function used by unittest to find the doctests."""

    # NB (@althonos): we only test docstrings on Python 3 because it's
    # extremely hard to maintain compatibility for both versions without
    # extensively hacking `doctest` and `unittest`.
    if six.PY2:
        return tests

    def setUp(self):
        warnings.simplefilter("ignore")
        self._open_fs_mock = mock.patch.object(fs, "open_fs", new=_open_fs)
        self._open_fs_mock.__enter__()
        self._ftpfs_mock = mock.patch.object(fs.ftpfs, "FTPFS")
        self._ftpfs_mock.__enter__()

    def tearDown(self):
        self._open_fs_mock.__exit__(None, None, None)
        self._ftpfs_mock.__exit__(None, None, None)
        warnings.simplefilter(warnings.defaultaction)

    # recursively traverse all library submodules and load tests from them
    packages = [None, fs]
    for pkg in iter(packages.pop, None):
        for (_, subpkgname, subispkg) in pkgutil.walk_packages(pkg.__path__):
            # import the submodule and add it to the tests
            module = importlib.import_module(".".join([pkg.__name__, subpkgname]))

            # load some useful modules / classes / mocks to the
            # globals so that we don't need to explicitly import
            # them in the doctests
            globs = dict(**module.__dict__)
            globs.update(
                os=os,
                fs=fs,
                my_fs=_my_fs(module.__name__),
                open=_open,
                # NB (@althonos): This allows using OSFS in some examples,
                # while not actually opening the real filesystem
                OSFS=lambda path: MemoryFS(),
                # NB (@althonos): This is for compatibility in `fs.registry`
                print_list=lambda path: None,
                pprint=pprint,
                time=time,
            )

            # load the doctests into the unittest test suite
            tests.addTests(
                doctest.DocTestSuite(
                    module,
                    globs=globs,
                    setUp=setUp,
                    tearDown=tearDown,
                    optionflags=+doctest.ELLIPSIS,
                )
            )

            # if the submodule is a package, we need to process its submodules
            # as well, so we add it to the package queue
            if subispkg:
                packages.append(module)

    return tests


# --- Unit test wrapper ------------------------------------------------------
#
# NB (@althonos): Since pytest doesn't support the `load_tests` protocol
# above, we manually build a `unittest.TestCase` using a dedicated test
# method for each doctest. This should be safe to remove when pytest
# supports it, or if we move away from pytest to run tests.


class TestDoctest(unittest.TestCase):
    pass


def make_wrapper(x):
    def _test_wrapper(self):
        x.setUp()
        try:
            x.runTest()
        finally:
            x.tearDown()

    return _test_wrapper


for x in _load_tests(None, unittest.TestSuite(), False):
    setattr(TestDoctest, "test_{}".format(x.id().replace(".", "_")), make_wrapper(x))