Changing Test Status

Sometimes, it is required to run some additional checks when a test is finished and maybe even change the test result from success to fail, or change the test result category or how its result is displayed in the verbose output.

This is possible by invoking a pytest built-in hook pytest_report_teststatus(). This hook can be added to MultihostUtility, see: MultihostUtility.pytest_report_teststatus

See also

This feature is used in Auditd and Coredumpd in order to set the result category to a custom value and optionally also fail the test.

If an AVC denial occurred during test, it is moved to AVC DENIALS category. If a coredump occured, it is moved to COREDUMPS category.

Example source code
Modifying the test result in Auditd
    def pytest_report_teststatus(
        self, report: pytest.CollectReport | pytest.TestReport, config: pytest.Config
    ) -> tuple[str, str, str | tuple[str, dict[str, bool]]] | None:
        """
        Report AVC denial error if found and matches requested filter.

        :param report: Pytest report
        :type report: pytest.CollectReport | pytest.TestReport
        :param config: Pytest config
        :type config: pytest.Config
        :return: Pytest test status
        :rtype: tuple[str, str, str | tuple[str, dict[str, bool]]] | None
        """
        if report.when != "call":
            return None

        if not self._auditd_running or self.avc_mode == "ignore" or report.outcome == "skipped":
            return None

        self.logger.info("Checking for AVC denials")

        result = self.host.conn.run(
            "ausearch --input-logs -m AVC,USER_AVC", raise_on_error=False, log_level=ProcessLogLevel.Silent
        )
        if result.rc:
            return None

        records = result.stdout
        if not records:
            return None

        # Ignore if no message matches the filter
        if self.avc_filter:
            match = re.search(self.avc_filter, records)
            if match is None:
                return None

        original_outcome = report.outcome

        # Fail the test if fail mode is selected
        if report.outcome == "passed" and self.avc_mode == "fail":
            report.outcome = "failed"

        # Count this test into "AVC DENIALS" category in the final summary,
        # mark it with "A"/"AVC DENIAL" in short/verbose listing.
        return ("AVC DENIALS", "A", f"{original_outcome.upper()}/AVC DENIAL")
Modifying the test result in Coredumpd
    def pytest_report_teststatus(
        self, report: pytest.CollectReport | pytest.TestReport, config: pytest.Config
    ) -> tuple[str, str, str | tuple[str, dict[str, bool]]] | None:
        """
        Report core file found error if found and matches requested filter.

        :param report: Pytest report
        :type report: pytest.CollectReport | pytest.TestReport
        :param config: Pytest config
        :type config: pytest.Config
        :return: Pytest test status
        :rtype: tuple[str, str, str | tuple[str, dict[str, bool]]] | None
        """
        if report.when != "call":
            return None

        if self.mode == "ignore" or report.outcome == "skipped":
            return None

        self.logger.info("Checking for core files")

        if self._corefiles is None:
            self._corefiles = self.list_core_files()

        if not self._corefiles:
            return None

        # Ignore if no core files matches the filter
        if self.filter:
            match = re.search(self.filter, "\n".join(self._corefiles))
            if match is None:
                return None

        original_outcome = report.outcome

        # Fail the test if fail mode is selected
        if report.outcome == "passed" and self.mode == "fail":
            report.outcome = "failed"

        # Count this test into "COREDUMPS" category in the final summary,
        # mark it with "C"/"COREDUMP" in short/verbose listing.
        return ("COREDUMPS", "C", f"{original_outcome.upper()}/COREDUMP")