Features Detection
Many projects can be built and distributed differently on different systems, some features may be disabled or use different (usually hard-coded) settings. This is quite common for compiled programs, that may choose to built or omit some parts of the code.
pytest-mh does not provide any built-in support of feature detection as this
functionality is highly project specific, but it is possible to use the
following code snippets as a guideline or inspiration.
Add feature property to the host class
Most of the time, it is desirable to detect the features at start up, since
the application does not change the built-time features during testing.
Therefore, the code can be safely added to the host class and use
pytest_setup() to make sure it is run only once.
1class BaseFeatureDetectionHost(MultihostHost[MyProjectDomain]):
2 def __init__(*args, **kwargs):
3 super().__init__(*args, **kwargs)
4
5 self.features: dict[str, bool] = {}
6 """
7 Features supported by the host.
8 """
9
10class MyProjectHost(BaseFeatureDetectionHost):
11 def pytest_setup(self) -> None:
12 super().pytest_setup()
13
14 # the following is a pseudocode which yields list of available
15 # features to stdout
16 result = self.conn.run("detect-project-features")
17
18 for feature_name in result.stdout_lines:
19 self.features[name] = True
Add feature property to the role class
Since tests have only indirect access to the host object via roles, it may be
nice to provide a shortcut to make the code smaller. It would be possible to
assign self.features = self.host.features directly in the constructor,
however it might be better to use the @property decorator to also cover the
case when host.features changes reference to different dictionary/object.
1class MyProjectRole(MyProjectHost):
2 @property
3 def features(self) -> dict[str, bool]:
4 """
5 Features supported by the role.
6 """
7 return self.host.features
Skipping tests
Check for a feature presence using @pytest.mark.require, if the feature is not available the test will be skipped.
1@pytest.mark.topology(KnownTopology.LDAP)
2@pytest.mark.require(
3 lambda ldap: "password_policy" in ldap.features,
4 "Server is not built with password policy support"
5)
6def test_skip__lambda(client: Client, ldap: LDAP):
7 pass
See also
You can also take inspiration from the SSSD project that has a syntactic sugar over the
@pytest.mark.require marker and introduces @pytest.mark.builtwith,
which internally translates into the require marker. You can check out
the code here
and here.
1# require files-provider feature built in the client
2@pytest.mark.builtwith("files-provider")
3@pytest.mark.topology(KnownTopology.Client)
4def test_files__root_user_is_ignored_on_lookups(client: Client):
5 ...
6
7# require passkey feature built in the client and the provider
8@pytest.mark.builtwith(client="passkey", provider="passkey")
9@pytest.mark.topology(KnownTopologyGroup.AnyProvider)
10def test_passkey__su_user(client: Client, provider: GenericProvider, moduledatadir: str, testdatadir: str):
11 ...