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
195
196
197
198
199
200
|
# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
"""
Test class mixins
Some of these are transitional while working toward pure-pytest style.
"""
import functools
import os
import os.path
import sys
import types
import textwrap
import pytest
from coverage import env
from coverage.misc import StopEverything
class PytestBase(object):
"""A base class to connect to pytest in a test class hierarchy."""
@pytest.fixture(autouse=True)
def connect_to_pytest(self, request, monkeypatch):
"""Captures pytest facilities for use by other test helpers."""
# pylint: disable=attribute-defined-outside-init
self._pytest_request = request
self._monkeypatch = monkeypatch
self.setup_test()
# Can't call this setUp or setup because pytest sniffs out unittest and
# nosetest special names, and does things with them.
# https://github.com/pytest-dev/pytest/issues/8424
def setup_test(self):
"""Per-test initialization. Override this as you wish."""
pass
def addCleanup(self, fn, *args):
"""Like unittest's addCleanup: code to call when the test is done."""
self._pytest_request.addfinalizer(lambda: fn(*args))
def set_environ(self, name, value):
"""Set an environment variable `name` to be `value`."""
self._monkeypatch.setenv(name, value)
def del_environ(self, name):
"""Delete an environment variable, unless we set it."""
self._monkeypatch.delenv(name)
class TempDirMixin(object):
"""Provides temp dir and data file helpers for tests."""
# Our own setting: most of these tests run in their own temp directory.
# Set this to False in your subclass if you don't want a temp directory
# created.
run_in_temp_dir = True
# Set this if you aren't creating any files with make_file, but still want
# the temp directory. This will stop the test behavior checker from
# complaining.
no_files_in_temp_dir = False
@pytest.fixture(autouse=True)
def _temp_dir(self, tmpdir_factory):
"""Create a temp dir for the tests, if they want it."""
old_dir = None
if self.run_in_temp_dir:
tmpdir = tmpdir_factory.mktemp("")
self.temp_dir = str(tmpdir)
old_dir = os.getcwd()
tmpdir.chdir()
# Modules should be importable from this temp directory. We don't
# use '' because we make lots of different temp directories and
# nose's caching importer can get confused. The full path prevents
# problems.
sys.path.insert(0, os.getcwd())
try:
yield None
finally:
if old_dir is not None:
os.chdir(old_dir)
@pytest.fixture(autouse=True)
def _save_sys_path(self):
"""Restore sys.path at the end of each test."""
old_syspath = sys.path[:]
try:
yield
finally:
sys.path = old_syspath
@pytest.fixture(autouse=True)
def _module_saving(self):
"""Remove modules we imported during the test."""
old_modules = list(sys.modules)
try:
yield
finally:
added_modules = [m for m in sys.modules if m not in old_modules]
for m in added_modules:
del sys.modules[m]
def make_file(self, filename, text="", bytes=b"", newline=None):
"""Create a file for testing.
`filename` is the relative path to the file, including directories if
desired, which will be created if need be.
`text` is the content to create in the file, a native string (bytes in
Python 2, unicode in Python 3), or `bytes` are the bytes to write.
If `newline` is provided, it is a string that will be used as the line
endings in the created file, otherwise the line endings are as provided
in `text`.
Returns `filename`.
"""
# pylint: disable=redefined-builtin # bytes
if bytes:
data = bytes
else:
text = textwrap.dedent(text)
if newline:
text = text.replace("\n", newline)
if env.PY3:
data = text.encode('utf8')
else:
data = text
# Make sure the directories are available.
dirs, _ = os.path.split(filename)
if dirs and not os.path.exists(dirs):
os.makedirs(dirs)
# Create the file.
with open(filename, 'wb') as f:
f.write(data)
return filename
def convert_skip_exceptions(method):
"""A decorator for test methods to convert StopEverything to skips."""
@functools.wraps(method)
def _wrapper(*args, **kwargs):
try:
result = method(*args, **kwargs)
except StopEverything:
pytest.skip("StopEverything!")
return result
return _wrapper
class SkipConvertingMetaclass(type):
"""Decorate all test methods to convert StopEverything to skips."""
def __new__(cls, name, bases, attrs):
for attr_name, attr_value in attrs.items():
if attr_name.startswith('test_') and isinstance(attr_value, types.FunctionType):
attrs[attr_name] = convert_skip_exceptions(attr_value)
return super(SkipConvertingMetaclass, cls).__new__(cls, name, bases, attrs)
StopEverythingMixin = SkipConvertingMetaclass('StopEverythingMixin', (), {})
class StdStreamCapturingMixin:
"""
Adapter from the pytest capsys fixture to more convenient methods.
This doesn't also output to the real stdout, so we probably want to move
to "real" capsys when we can use fixtures in test methods.
Once you've used one of these methods, the capturing is reset, so another
invocation will only return the delta.
"""
@pytest.fixture(autouse=True)
def _capcapsys(self, capsys):
"""Grab the fixture so our methods can use it."""
self.capsys = capsys
def stdouterr(self):
"""Returns (out, err), two strings for stdout and stderr."""
return self.capsys.readouterr()
def stdout(self):
"""Returns a string, the captured stdout."""
return self.capsys.readouterr().out
def stderr(self):
"""Returns a string, the captured stderr."""
return self.capsys.readouterr().err
|