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.

Creating base host class with feature property
 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.

Add shortcut to the host feature to the role class
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.

Example use of SSSD’s builtwith marker
 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    ...