-
Notifications
You must be signed in to change notification settings - Fork 474
/
Copy pathconftest.py
420 lines (329 loc) · 12.8 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
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
#!/usr/bin/env python3
# Copyright (C) 2019 Checkmk GmbH - License: GNU General Public License v2
# This file is part of Checkmk (https://checkmk.com). It is subject to the terms and
# conditions defined in the file COPYING, which is part of this source code package.
import logging
import os
import shutil
from collections.abc import Callable, Generator, Iterable, Iterator
from pathlib import Path
from unittest.mock import patch
import pytest
from fakeredis import FakeRedis
from tests.testlib.repo import (
is_cloud_repo,
is_enterprise_repo,
is_managed_repo,
is_saas_repo,
repo_path,
)
import livestatus
import cmk.ccc.debug
import cmk.ccc.version as cmk_version
from cmk.ccc import store
from cmk.ccc.site import omd_site
import cmk.utils.caching
import cmk.utils.paths
from cmk.utils import redis, tty
from cmk.utils.licensing.handler import (
LicenseState,
LicensingHandler,
NotificationHandler,
UserEffect,
)
from cmk.utils.livestatus_helpers.testing import (
mock_livestatus_communication,
MockLiveStatusConnection,
)
from cmk.base.api.agent_based.register import ( # pylint: disable=cmk-module-layer-violation
AgentBasedPlugins,
get_previously_loaded_plugins,
)
import cmk.crypto.password_hashing
from cmk.agent_based.legacy import discover_legacy_checks, FileLoader, find_plugin_files
logger = logging.getLogger(__name__)
@pytest.fixture(autouse=True)
def enable_debug_fixture():
debug_mode = cmk.ccc.debug.debug_mode
cmk.ccc.debug.enable()
yield
cmk.ccc.debug.debug_mode = debug_mode
@pytest.fixture
def as_path(tmp_path: Path) -> Callable[[str], Path]:
"""See Also:
* https://docs.pytest.org/en/7.2.x/how-to/fixtures.html#factories-as-fixtures
"""
def _as_path(walk: str) -> Path:
data = tmp_path / "data"
data.write_text(walk)
return data
return _as_path
@pytest.fixture
def disable_debug():
debug_mode = cmk.ccc.debug.debug_mode
cmk.ccc.debug.disable()
yield
cmk.ccc.debug.debug_mode = debug_mode
@pytest.fixture(autouse=True, scope="session")
def fixture_umask():
"""Ensure the unit tests always use the same umask"""
old_mask = os.umask(0o0007)
try:
yield
finally:
os.umask(old_mask)
@pytest.fixture(name="capsys")
def fixture_capsys(capsys: pytest.CaptureFixture[str]) -> Iterator[pytest.CaptureFixture[str]]:
"""Ensure that the capsys handling is deterministic even if started via `pytest -s`"""
tty.reinit()
try:
yield capsys
finally:
tty.reinit()
@pytest.fixture(name="edition", params=["cre", "cee", "cme", "cce"])
def fixture_edition(request: pytest.FixtureRequest) -> Iterable[cmk_version.Edition]:
# The param seems to be an optional attribute which mypy can not understand
edition_short = request.param
if edition_short == "cse" and not is_saas_repo():
pytest.skip("Needed files are not available")
if edition_short == "cce" and not is_cloud_repo():
pytest.skip("Needed files are not available")
if edition_short == "cme" and not is_managed_repo():
pytest.skip("Needed files are not available")
if edition_short == "cee" and not is_enterprise_repo():
pytest.skip("Needed files are not available")
yield cmk_version.Edition[edition_short.upper()]
@pytest.fixture(autouse=True, scope="session")
def fixture_omd_site() -> Generator[None, None, None]:
os.environ["OMD_SITE"] = "NO_SITE"
yield
@pytest.fixture
def patch_omd_site(monkeypatch: pytest.MonkeyPatch) -> Iterator[None]:
monkeypatch.setenv("OMD_ROOT", str(cmk.utils.paths.omd_root))
omd_site.cache_clear()
_touch(cmk.utils.paths.htpasswd_file)
store.makedirs(cmk.utils.paths.autochecks_dir)
store.makedirs(cmk.utils.paths.var_dir + "/web")
store.makedirs(cmk.utils.paths.var_dir + "/php-api")
store.makedirs(cmk.utils.paths.var_dir + "/wato/php-api")
store.makedirs(cmk.utils.paths.var_dir + "/wato/auth")
store.makedirs(cmk.utils.paths.tmp_dir / "wato/activation")
store.makedirs(cmk.utils.paths.omd_root / "var/log")
store.makedirs(cmk.utils.paths.omd_root / "tmp/check_mk")
store.makedirs(cmk.utils.paths.default_config_dir + "/conf.d/wato")
store.makedirs(cmk.utils.paths.default_config_dir + "/multisite.d/wato")
store.makedirs(cmk.utils.paths.default_config_dir + "/mkeventd.d/wato")
store.makedirs(cmk.utils.paths.local_dashboards_dir)
store.makedirs(cmk.utils.paths.local_views_dir)
if cmk_version.edition(cmk.utils.paths.omd_root) is not cmk_version.Edition.CRE:
# needed for visuals.load()
store.makedirs(cmk.utils.paths.local_reports_dir)
_touch(cmk.utils.paths.default_config_dir + "/mkeventd.mk")
_touch(cmk.utils.paths.default_config_dir + "/multisite.mk")
omd_config_dir = f"{cmk.utils.paths.omd_root}/etc/omd"
_dump(
omd_config_dir + "/site.conf",
"""CONFIG_ADMIN_MAIL=''
CONFIG_AGENT_RECEIVER='on'
CONFIG_AGENT_RECEIVER_PORT='8000'
CONFIG_APACHE_MODE='own'
CONFIG_APACHE_TCP_ADDR='127.0.0.1'
CONFIG_APACHE_TCP_PORT='5002'
CONFIG_AUTOSTART='off'
CONFIG_CORE='cmc'
CONFIG_LIVEPROXYD='on'
CONFIG_LIVESTATUS_TCP='off'
CONFIG_LIVESTATUS_TCP_ONLY_FROM='0.0.0.0 ::/0'
CONFIG_LIVESTATUS_TCP_PORT='6557'
CONFIG_LIVESTATUS_TCP_TLS='on'
CONFIG_MKEVENTD='on'
CONFIG_MKEVENTD_SNMPTRAP='off'
CONFIG_MKEVENTD_SYSLOG='on'
CONFIG_MKEVENTD_SYSLOG_TCP='off'
CONFIG_MULTISITE_AUTHORISATION='on'
CONFIG_MULTISITE_COOKIE_AUTH='on'
CONFIG_NSCA='off'
CONFIG_NSCA_TCP_PORT='5667'
CONFIG_PNP4NAGIOS='on'
CONFIG_RABBITMQ_PORT='5672'
CONFIG_RABBITMQ_ONLY_FROM='0.0.0.0 ::'
CONFIG_RABBITMQ_DIST_PORT='25672'
CONFIG_TRACE_JAEGER_ADMIN_PORT='14269'
CONFIG_TRACE_JAEGER_UI_PORT='13333'
CONFIG_TRACE_RECEIVE='off'
CONFIG_TRACE_RECEIVE_ADDRESS='[::1]'
CONFIG_TRACE_RECEIVE_PORT='4321'
CONFIG_TRACE_SEND='off'
CONFIG_TRACE_SEND_TARGET='local_site'
CONFIG_TMPFS='on'""",
)
_dump(
cmk.utils.paths.default_config_dir + "/mkeventd.d/wato/rules.mk",
r"""
# Written by WATO
# encoding: utf-8
rule_packs += \
[{'id': 'default', 'title': 'Default rule pack', 'rules': [], 'disabled': False, 'hits': 0}]
""",
)
yield
omd_site.cache_clear()
def _dump(path, data):
p = Path(path)
p.parent.mkdir(parents=True, exist_ok=True)
with open(path, "w") as f:
f.write(data)
def _touch(path):
Path(path).parent.mkdir(parents=True, exist_ok=True)
Path(path).touch()
@pytest.fixture(autouse=True)
def cleanup_after_test():
yield
if cmk.utils.paths.omd_root == Path(""):
logger.warning("OMD_ROOT not set, skipping cleanup")
return
# Ensure there is no file left over in the unit test fake site
# to prevent tests involving eachother
for entry in cmk.utils.paths.omd_root.iterdir():
# This randomly fails for some unclear reasons. Looks like a race condition, but I
# currently have no idea which triggers this since the tests are not executed in
# parallel at the moment. This is meant as quick hack, trying to reduce flaky results.
try:
if entry.is_dir():
shutil.rmtree(str(entry))
else:
entry.unlink()
except OSError as e:
logger.debug("Failed to cleanup %s after test: %s. Keep going anyway", entry, e)
# Unit tests should not be executed in site.
# -> Disabled site fixture for them
@pytest.fixture(scope="session")
def site(request):
pass
def _clear_caches():
cmk.utils.caching.cache_manager.clear()
cmk_version.edition.cache_clear()
@pytest.fixture(autouse=True, scope="module")
def clear_caches_per_module():
"""Ensures that module-scope fixtures are executed with clean caches."""
_clear_caches()
yield
@pytest.fixture(autouse=True)
def clear_caches_per_function():
"""Ensures that each test is executed with a non-polluted cache from a previous test."""
_clear_caches()
yield
@pytest.fixture(scope="session")
def agent_based_plugins() -> AgentBasedPlugins:
# Local import to have faster pytest initialization
from cmk.base import ( # pylint: disable=bad-option-value,import-outside-toplevel,cmk-module-layer-violation
config,
)
errors = config.load_all_plugins(
local_checks_dir=repo_path() / "no-such-path-but-thats-ok",
checks_dir=str(repo_path() / "cmk/base/legacy_checks"),
)
assert not errors
return get_previously_loaded_plugins()
class FixPluginLegacy:
"""Access legacy dicts like `check_info`"""
def __init__(self) -> None:
result = discover_legacy_checks(
find_plugin_files(str(repo_path() / "cmk/base/legacy_checks")),
FileLoader(
precomile_path=cmk.utils.paths.precompiled_checks_dir,
local_path="/not_relevant_for_test",
makedirs=store.makedirs,
),
raise_errors=True,
)
self.check_info = {p.name: p for p in result.sane_check_info}
@pytest.fixture(scope="session")
def fix_plugin_legacy() -> Iterator[FixPluginLegacy]:
yield FixPluginLegacy()
@pytest.fixture(autouse=True, scope="module")
def prevent_livestatus_connect() -> Iterator[None]:
"""Prevent tests from trying to open livestatus connections. This will result in connect
timeouts which slow down our tests."""
orig_init = livestatus.MultiSiteConnection.__init__
def init_mock(self, *args, **kwargs):
orig_init(self, *args, **kwargs)
if self.deadsites:
pytest.fail("Dead sites: %r" % self.deadsites)
with patch.object(
livestatus.SingleSiteConnection,
"_create_socket",
lambda *_: pytest.fail(
"The test tried to use a livestatus connection. This will result in connect timeouts. "
"Use mock_livestatus for mocking away the livestatus API"
),
) as _:
with patch.object(livestatus.MultiSiteConnection, "__init__", init_mock) as _:
yield
@pytest.fixture(name="mock_livestatus")
def fixture_mock_livestatus() -> Iterator[MockLiveStatusConnection]:
"""Mock LiveStatus by patching MultiSiteConnection
Use it like this:
def test_function() -> None:
livestatus.LocalConnection().query("Foo")
def test_foo(mock_livestatus) -> None:
live = mock_livestatus
live.expect_query("Foo")
with live:
# here call a function which does livestatus calls.
test_function()
"""
with mock_livestatus_communication() as mock_live:
yield mock_live
@pytest.fixture(scope="module")
def use_fakeredis_client() -> Iterator[None]:
"""Use fakeredis client instead of redis.Redis"""
with patch.object(redis, "Redis", FakeRedis) as _:
redis.get_redis_client().flushall()
yield
@pytest.fixture(autouse=True, scope="session")
def reduce_password_hashing_rounds() -> Iterator[None]:
"""Reduce the number of rounds for hashing with bcrypt to the allowed minimum"""
with patch.object(cmk.crypto.password_hashing, "BCRYPT_ROUNDS", 4):
yield
@pytest.fixture(name="monkeypatch_module", scope="module")
def fixture_monkeypatch_module() -> Iterator[pytest.MonkeyPatch]:
with pytest.MonkeyPatch.context() as mp:
yield mp
class DummyNotificationHandler(NotificationHandler):
def manage_notification(self) -> None:
pass
class DummyLicensingHandler(LicensingHandler):
@classmethod
def make(cls) -> "DummyLicensingHandler":
return cls()
@property
def state(self) -> LicenseState:
return LicenseState.LICENSED
@property
def message(self) -> str:
return ""
def effect_core(self, num_services: int, num_hosts_shadow: int) -> UserEffect:
return UserEffect(header=None, email=None, block=None)
def effect(self, licensing_settings_link: str | None = None) -> UserEffect:
return UserEffect(header=None, email=None, block=None)
@property
def notification_handler(self) -> NotificationHandler:
return DummyNotificationHandler(email_notification=None)
@pytest.fixture(name="is_licensed", scope="module")
def fixture_is_licensed(monkeypatch_module: pytest.MonkeyPatch) -> None:
monkeypatch_module.setattr(
"cmk.utils.licensing.registry._get_licensing_handler", DummyLicensingHandler
)
@pytest.fixture(name="suppress_license_expiry_header")
def fixture_suppress_license_expiry_header(monkeypatch_module: pytest.MonkeyPatch) -> None:
"""Don't check if message about license expiration should be shown"""
monkeypatch_module.setattr(
"cmk.gui.htmllib.top_heading._may_show_license_expiry", lambda x: None
)
@pytest.fixture(name="suppress_license_banner")
def fixture_suppress_license_banner(monkeypatch_module: pytest.MonkeyPatch) -> None:
"""Don't check if message about license expiration should be shown"""
monkeypatch_module.setattr(
"cmk.gui.htmllib.top_heading._may_show_license_banner", lambda x: None
)