forked from gipit/gips
-
Notifications
You must be signed in to change notification settings - Fork 5
/
Copy pathconftest.py
189 lines (157 loc) · 7.53 KB
/
conftest.py
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
import os
import shutil
import pytest
from _pytest.assertion.util import _compare_eq_dict, _diff_text
from gips.test.sys.util import GipsProcResult, set_constants
@pytest.fixture
def mpo(mocker):
"""Just to save typing."""
yield mocker.patch.object
@pytest.fixture
def mock_context_manager(mocker, mpo):
"""Mocks a given context manager.
Generate the mock by calling the value of this fixture in situ:
'with location.cm_name() as return_value:'. location should be a module,
cm_name is a string naming the context manager to mock, and return_value
is the mock value emitted by the context manager at runtime.
"""
def inner(location, cm_name, return_value=None):
m_context_manager = mpo(location, cm_name).return_value
if return_value is not None:
m_context_manager.__enter__.return_value = return_value
return m_context_manager
return inner
# pytest_* functions are hooks automatically detected by pytest
def pytest_addoption(parser):
"""Add custom options & settings to py.test."""
help_str = "Set up a test data repo & download data for test purposes."
parser.addoption("--setup-repo", action="store_true", help=help_str)
help_str = (
"Replace the data repo with a new empty one before testing begins. "
"Implies --setup-repo."
)
parser.addoption("--clear-repo", action="store_true", help=help_str)
help_str = ("The directory housing the data repo for testing purposes. "
"MUST match GIPS' configured REPOS setting.")
parser.addini('data-repo', help=help_str)
parser.addini('output-dir',
help="The directory housing output files from test runs.")
# old bits for ftp access
#parser.addini('artifact-store-user', help="FTP artifact store username")
#parser.addini('artifact-store-password',
# help="FTP artifact store password")
#parser.addini('artifact-store-host', help="FTP artifact store hostname")
# local file store
parser.addini('artifact-store-path',
help="artifact store root path; files are presumed to be"
" stored driver-wise beneath this level,"
" eg path/sar/asset.tgz")
parser.addoption(
"--slow", action="store_true", help="Do not skip @slow tests.")
parser.addoption(
"--acolite", action="store_true", help="Don't skip ACOLITE tests."
)
help_str = ("Do not skip @src_altering system tests, which are tests that "
"may alter or remove source data in the repo.")
parser.addoption("--src-altering", action="store_true", help=help_str)
help_str = ("Do not skip @system tests, which are tests that may "
"change elements of the environment as they work.")
parser.addoption("--sys", action="store_true", help=help_str)
help_str = ("Whenever a subprocess' output streams are captured via "
"GipsTestFileEnv, print them out in a format suitable for "
"cutpasting into an expectations file.")
parser.addoption(
"--expectation-format", action="store_true", help=help_str)
parser.addoption('--record', action='store', default=None,
help="Pass in a filename for expecations"
" to be written to that filename.")
# cleanup may not need to be implemented at all
#parser.addoption('--cleanup-on-failure', action='store_true',
# help="Normally cleanup is skipped on failure so you can examine"
# " files; pass this option to cleanup even on failure.")
def pytest_configure(config):
"""Process user config & command-line options."""
# pytest.config was deprecated, and the only alternatives are worse,
# so monkey-patch in our own
pytest._config_saved_by_gips = config
record_path = config.getoption('record')
if record_path and os.path.lexists(record_path):
raise IOError("Record file already exists at {}".format(record_path))
dr = str(config.getini('data-repo'))
if not dr:
raise ValueError("No value specified for 'data-repo' in pytest.ini")
set_constants(config)
if config.getoption("clear_repo"):
print("--clear-repo detected; trashing and rebuilding data"
" repo & inventory DB")
path = config.getini('data-repo')
os.path.lexists(path) and shutil.rmtree(path)
setup_data_repo()
config.option.setup_repo = True
elif config.getoption("setup_repo"):
print("--setup-repo detected; setting up data repo")
setup_data_repo()
else:
print("Skipping repo setup per lack of --setup-repo.")
def setup_data_repo():
"""Construct the data repo if it is absent."""
# confirm the user's done basic config
# TODO do these checks every run if fast enough
# TODO replace with sh:
# sh.gips_config('print', _err='/dev/stderr', _out='/dev/stdout')
import envoy
gcp = envoy.run("gips_config print")
if gcp.status_code != 0:
raise RuntimeError("config check via `gips_config print` failed",
gcp.std_out, gcp.std_err, gcp)
# set up data root if it isn't there already
gcp = envoy.run("gips_config env")
if gcp.status_code != 0:
print("data root setup via `gips_config env` failed; stdout:")
print(gcp.std_out)
print("data root setup via `gips_config env` failed; stderr:")
print(gcp.std_err)
raise RuntimeError("data root setup via `gips_config env` failed")
def pytest_assertrepr_compare(config, op, left, right):
"""
When asserting equality between process results, show detailed differences.
"""
checks = (op == '==',
isinstance(left, GipsProcResult),
isinstance(right, GipsProcResult))
if not all(checks):
return
def header_and_indent(header, lines):
if lines:
return [header + ':'] + [' ' + line for line in lines]
else:
return [header + ': matches']
verbose = config.getoption('verbose')
output = ['GipsProcResult == GipsProcResult:']
oper = {True: '==', False: '!='}[left.exit_status == right.exit_status]
output += ['exit_status: {} {} {}'
.format(left.exit_status, oper, right.exit_status)]
for s in ('stdout', 'stderr'):
l_compare = getattr(left, 'compare_' + s)
r_compare = getattr(right, 'compare_' + s)
if l_compare and r_compare:
(l_stream, r_stream) = (getattr(left, s), getattr(right, s))
# TODO text diff breaks due to terminal control sequences (I
# think it's escaping them wrong or not at all).
output += header_and_indent(s, _diff_text(l_stream, r_stream, verbose))
else:
output += [s + ': ignored']
updated_diff = _compare_eq_dict(left.strip_ignored(left.updated),
left.strip_ignored(right.updated),
verbose)
deleted_diff = _compare_eq_dict(left.strip_ignored(left.deleted),
left.strip_ignored(right.deleted),
verbose)
created_diff = _compare_eq_dict(left.strip_ignored(left.created),
left.strip_ignored(right.created),
verbose)
output += header_and_indent('updated', updated_diff)
output += header_and_indent('deleted', deleted_diff)
output += header_and_indent('created', created_diff)
output += header_and_indent('ignored', left.ignored)
return output