Skip to content

Commit eb6a3d9

Browse files
committed
Expose user enabled features as pytest markers
This allows guarding tests behind markers.
1 parent e531086 commit eb6a3d9

19 files changed

Lines changed: 87 additions & 50 deletions

docs/developer/testing.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,32 @@ def test_service_port(server):
146146
assert server.addr("localhost").port(6379).is_reachable
147147
```
148148

149+
### Feature guarding
150+
151+
Some functionality can only be tested when a feature is enabled.
152+
153+
You can mark an individual test to be skipped if needed:
154+
155+
```python
156+
@pytest.mark.feature("iop")
157+
def test_ingress_service(server):
158+
service = server.service("iop-core-ingress")
159+
assert service.is_running and service.is_enabled
160+
```
161+
162+
Often it's better to have an entire file dedicated to a feature and mark the entire file as guarded.
163+
164+
```python
165+
pytestmark = pytest.mark.feature("iop")
166+
167+
def test_ingress_service(server):
168+
service = server.service("iop-core-ingress")
169+
assert service.is_running and service.is_enabled
170+
171+
def test_ingress_http_endpoint(server):
172+
# ...
173+
```
174+
149175
### API test
150176

151177
The `foremanapi` fixture is an [apypie](https://github.com/Apipie/apypie) `ForemanApi` client that connects to the deployed Foreman instance(authenticated as `admin`/`changeme`). It maps directly to the Foreman REST API — each method takes a resource name that corresponds to an API endpoint:

tests/conftest.py

Lines changed: 42 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
import os
21
import uuid
2+
import subprocess
3+
from functools import cached_property
34

45
import apypie
56
import paramiko
@@ -16,11 +17,40 @@
1617
SSH_CONFIG = './.tmp/ssh-config'
1718

1819

20+
class UserParameters:
21+
def __init__(self, config):
22+
self._config = config
23+
24+
@cached_property
25+
def features(self):
26+
# foremanctl outputs
27+
# FEATURE STATE DESCRIPTION
28+
# $feature enabled/available $description
29+
output = subprocess.check_output(['./foremanctl', 'features'], cwd=self._config.rootdir,
30+
universal_newlines=True)
31+
lines = output.splitlines(keepends=False)
32+
# feature, status, description
33+
return [line.split(None, 2) for line in lines[1:]]
34+
35+
@cached_property
36+
def available_features(self):
37+
return set(feature for feature, _status, _desc in self.features)
38+
39+
@cached_property
40+
def enabled_features(self):
41+
return set(feature for feature, status, _desc in self.features if status == 'enabled')
42+
43+
1944
def pytest_addoption(parser):
2045
parser.addoption("--certificate-source", action="store", default="default", choices=('default', 'installer', 'custom_server'), help="Certificate source used during deployment")
2146
parser.addoption("--database-mode", action="store", default="internal", choices=('internal', 'external'), help="Whether the database is internal or external")
2247

2348

49+
@pytest.fixture(scope="module")
50+
def enabled_features(pytestconfig):
51+
return pytestconfig.user_parameters.enabled_features
52+
53+
2454
@pytest.fixture(scope="module")
2555
def fixture_dir():
2656
return py.path.local(__file__).realpath() / '..' / 'fixtures'
@@ -206,35 +236,21 @@ def wait_for_metadata_generate(foremanapi):
206236
wait_for_tasks(foremanapi, 'label = Actions::Katello::Repository::MetadataGenerate')
207237

208238

209-
def enabled_features():
210-
test_dir = os.path.dirname(os.path.abspath(__file__))
211-
foremanctl_dir = os.path.dirname(test_dir)
212-
params_file = os.path.join(foremanctl_dir, '.var', 'lib', 'foremanctl', 'parameters.yaml')
213-
if os.path.exists(params_file):
214-
with open(params_file, 'r') as f:
215-
features = yaml.safe_load(f).get('features', [])
216-
if isinstance(features, str):
217-
features = features.split()
218-
return features
219-
return []
220-
221-
222-
def is_iop_enabled():
223-
return 'iop' in enabled_features()
224-
225-
226239
def pytest_configure(config):
227-
config.addinivalue_line("markers", "iop: tests requiring IOP to be enabled")
240+
config.addinivalue_line("markers", "feature(name): mark a test as requiring a feature")
228241

242+
config.user_parameters = UserParameters(config)
229243

230-
def pytest_collection_modifyitems(config, items):
231-
if is_iop_enabled():
232-
return
233244

234-
skip_iop = pytest.mark.skip(reason="IOP not enabled - skipping IOP tests ('iop' not in enabled_features)")
235-
for item in items:
236-
if "iop" in item.keywords:
237-
item.add_marker(skip_iop)
245+
def pytest_runtest_setup(item):
246+
feature_markers = set(mark.args[0] for mark in item.iter_markers(name="feature"))
247+
if feature_markers:
248+
invalid_features = feature_markers - item.config.user_parameters.available_features
249+
if invalid_features:
250+
raise pytest.PytestConfigWarning(f"Invalid feature(s) {invalid_features!r} on {item}")
251+
missing = feature_markers - item.config.user_parameters.enabled_features
252+
if missing:
253+
pytest.skip(f"test requires feature(s) {missing!r}")
238254

239255

240256
class ResolveAdapter(HTTPAdapter):

tests/foreman_proxy_test.py

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,10 @@
22
import json
33

44
import pytest
5-
from conftest import enabled_features
65

76
FOREMAN_PROXY_PORT = 8443
87

98

10-
def is_bmc_enabled():
11-
return 'bmc' in enabled_features()
12-
13-
149
def get_proxy_v2_features(server, certificates, server_fqdn):
1510
cmd = server.run(
1611
f"curl --cacert {certificates['server_ca_certificate']} "
@@ -22,14 +17,14 @@ def get_proxy_v2_features(server, certificates, server_fqdn):
2217
return json.loads(cmd.stdout)
2318

2419

25-
def test_foreman_proxy_features(server, certificates, server_fqdn):
20+
def test_foreman_proxy_features(server, certificates, server_fqdn, enabled_features):
2621
cmd = server.run(f"curl --cacert {certificates['server_ca_certificate']} --silent https://{server_fqdn}:{FOREMAN_PROXY_PORT}/features")
2722
assert cmd.succeeded
2823
features = json.loads(cmd.stdout)
2924
assert "logs" in features
3025
assert "script" in features
3126
assert "dynflow" in features
32-
if is_bmc_enabled():
27+
if 'bmc' in enabled_features:
3328
assert "bmc" in features
3429
else:
3530
assert "bmc" not in features
@@ -60,7 +55,7 @@ def test_foreman_proxy_client_auth_to_foreman(server, certificates, server_fqdn)
6055
assert cmd.stdout == '201'
6156

6257

63-
@pytest.mark.skipif("not is_bmc_enabled()")
58+
@pytest.mark.feature('bmc')
6459
def test_bmc_capabilities(server, certificates, server_fqdn):
6560
features = get_proxy_v2_features(server, certificates, server_fqdn)
6661
assert 'bmc' in features

tests/iop/test_advisor.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import pytest
22

3-
pytestmark = pytest.mark.iop
3+
pytestmark = pytest.mark.feature("iop")
44

55

66
def test_advisor_backend_api_service(server):

tests/iop/test_advisor_frontend.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import pytest
22

3-
pytestmark = pytest.mark.iop
3+
pytestmark = pytest.mark.feature("iop")
44

55

66
def test_advisor_frontend_assets_directory(server):

tests/iop/test_cvemap_downloader.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import pytest
22

3-
pytestmark = pytest.mark.iop
3+
pytestmark = pytest.mark.feature("iop")
44

55

66
def test_cvemap_download_script(server):

tests/iop/test_engine.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import pytest
22

3-
pytestmark = pytest.mark.iop
3+
pytestmark = pytest.mark.feature("iop")
44

55

66
def test_engine_service(server):

tests/iop/test_gateway.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import pytest
22

3-
pytestmark = pytest.mark.iop
3+
pytestmark = pytest.mark.feature("iop")
44

55

66
def test_gateway_service(server):

tests/iop/test_ingress.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import pytest
22

3-
pytestmark = pytest.mark.iop
3+
pytestmark = pytest.mark.feature("iop")
44

55

66
def test_ingress_service(server):

tests/iop/test_integration.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import pytest
22

3-
pytestmark = pytest.mark.iop
3+
pytestmark = pytest.mark.feature("iop")
44

55

66
def test_iop_core_kafka_service(server):

0 commit comments

Comments
 (0)