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