class PytestBDDListener(object):
    def __init__(self):
        self.lifecycle = AllureLifecycle()
        self.host = host_tag()
        self.thread = thread_tag()

    def _scenario_finalizer(self, scenario):
        for step in scenario.steps:
            step_uuid = get_uuid(str(id(step)))
            with self.lifecycle.update_step(uuid=step_uuid) as step_result:
                if step_result:
                    step_result.status = Status.SKIPPED
                    self.lifecycle.stop_step(uuid=step_uuid)

    @pytest.hookimpl
    def pytest_bdd_before_scenario(self, request, feature, scenario):
        uuid = get_uuid(request.node.nodeid)
        full_name = get_full_name(feature, scenario)
        name = get_name(request.node, scenario)
        with self.lifecycle.schedule_test_case(uuid=uuid) as test_result:
            test_result.fullName = full_name
            test_result.name = name
            test_result.start = now()
            test_result.labels.append(Label(name=LabelType.HOST, value=self.host))
            test_result.labels.append(Label(name=LabelType.THREAD, value=self.thread))
            test_result.labels.append(Label(name=LabelType.FRAMEWORK, value="pytest-bdd"))
            test_result.labels.append(Label(name=LabelType.LANGUAGE, value=platform_label()))
            test_result.labels.append(Label(name=LabelType.FEATURE, value=feature.name))
            test_result.parameters = get_params(request.node)

        finalizer = partial(self._scenario_finalizer, scenario)
        request.node.addfinalizer(finalizer)

    @pytest.hookimpl
    def pytest_bdd_after_scenario(self, request, feature, scenario):
        uuid = get_uuid(request.node.nodeid)
        with self.lifecycle.update_test_case(uuid=uuid) as test_result:
            test_result.stop = now()

    @pytest.hookimpl
    def pytest_bdd_before_step_call(self, request, feature, scenario, step, step_func, step_func_args):
        parent_uuid = get_uuid(request.node.nodeid)
        uuid = get_uuid(str(id(step)))
        with self.lifecycle.start_step(parent_uuid=parent_uuid, uuid=uuid) as step_result:
            step_result.name = get_step_name(request.node, step)

    @pytest.hookimpl
    def pytest_bdd_after_step(self, request, feature, scenario, step, step_func, step_func_args):
        uuid = get_uuid(str(id(step)))
        with self.lifecycle.update_step(uuid=uuid) as step_result:
            step_result.status = Status.PASSED
        self.lifecycle.stop_step(uuid=uuid)

    @pytest.hookimpl
    def pytest_bdd_step_error(self, request, feature, scenario, step, step_func, step_func_args, exception):
        uuid = get_uuid(str(id(step)))
        with self.lifecycle.update_step(uuid=uuid) as step_result:
            step_result.status = Status.FAILED
            step_result.statusDetails = get_status_details(exception)
        self.lifecycle.stop_step(uuid=uuid)

    @pytest.hookimpl
    def pytest_bdd_step_func_lookup_error(self, request, feature, scenario, step, exception):
        uuid = get_uuid(str(id(step)))
        with self.lifecycle.update_step(uuid=uuid) as step_result:
            step_result.status = Status.BROKEN
        self.lifecycle.stop_step(uuid=uuid)

    @pytest.hookimpl(hookwrapper=True)
    def pytest_runtest_makereport(self, item, call):
        report = (yield).get_result()

        status = get_pytest_report_status(report)

        status_details = StatusDetails(
            message=call.excinfo.exconly(),
            trace=report.longreprtext) if call.excinfo else None

        uuid = get_uuid(report.nodeid)
        with self.lifecycle.update_test_case(uuid=uuid) as test_result:

            if test_result and report.when == "setup":
                test_result.status = status
                test_result.statusDetails = status_details

            if report.when == "call" and test_result:
                if test_result.status not in [Status.PASSED, Status.FAILED]:
                    test_result.status = status
                    test_result.statusDetails = status_details

            if report.when == "teardown" and test_result:
                if test_result.status == Status.PASSED and status != Status.PASSED:
                    test_result.status = status
                    test_result.statusDetails = status_details

        if report.when == 'teardown':
            self.lifecycle.write_test_case(uuid=uuid)

    @allure_commons.hookimpl
    def attach_data(self, body, name, attachment_type, extension):
        self.lifecycle.attach_data(uuid4(), body, name=name, attachment_type=attachment_type, extension=extension)

    @allure_commons.hookimpl
    def attach_file(self, source, name, attachment_type, extension):
        self.lifecycle.attach_file(uuid4(), source, name=name, attachment_type=attachment_type, extension=extension)
Exemple #2
0
class AllureWriter:
    def __init__(self, results_path):
        safe_makedirs(results_path)
        self.lifecycle = AllureLifecycle()
        self.logger = AllureFileLogger(results_path)
        self.listener = AllureListener(self.lifecycle)

    def process(self, structured_data, file_modication_datetime=None):
        plugin_manager.register(self.listener)
        plugin_manager.register(self.logger)

        for test_case in structured_data["tests"]:
            self.process_test_case(
                test_case, file_modication_datetime=file_modication_datetime)

        plugin_manager.unregister(plugin=self.listener)
        plugin_manager.unregister(plugin=self.logger)

    def process_test_case(self, test_case, file_modication_datetime=None):

        with self.lifecycle.schedule_test_case() as test_result:
            test_index = test_case["id"]
            test_data = test_case.get("data") or {}
            job = test_data.get("job") or {}
            test_result.name = test_index
            self._record_start_stop(test_result, file_modication_datetime, job)

            test_result.fullName = test_index
            test_result.testCaseId = md5(test_index)
            test_result.historyId = md5(test_index)
            tool_id = self._record_suite_labels(test_result, test_data, job)

            self._attach_data("test_data",
                              json.dumps(test_data, indent=JSON_INDENT),
                              attachment_type=AttachmentType.JSON)
            for key in [
                    "stderr", "stdout", "command_line", "external_id",
                    "job_messages"
            ]:
                val = job.get(key)
                if not val:
                    continue
                if isinstance(val, list):
                    attachment_type = AttachmentType.JSON
                    # job messages
                    val = json.dumps(val, indent=JSON_INDENT)
                else:
                    if not val.strip():
                        continue
                    attachment_type = AttachmentType.TEXT
                self._attach_data(key, val, attachment_type=attachment_type)

            problem_message = None
            for key in ["execution_problem", "output_problems"]:
                val = test_data.get(key)
                if not val:
                    continue
                if isinstance(val, list) and val:
                    # remove duplicated messages...
                    val = list(set(val))

                    attachment_type = AttachmentType.HTML
                    as_html_list = "<ul>"
                    as_html_list += "\n".join(
                        [f"<li><pre>{v}</pre></li>" for v in val])
                    as_html_list += "</ul>"
                    problem_message = val[0]
                    val = as_html_list
                else:
                    if not val.strip():
                        continue
                    attachment_type = AttachmentType.TEXT
                    problem_message = val
                self._attach_data(key, val, attachment_type=attachment_type)

            if problem_message is None and "job_messages" in job:
                job_messages = job.get("job_messages")
                if job_messages:
                    problem_message = str(job_messages)

            test_result.labels.append(
                Label(name=LabelType.FRAMEWORK, value='planemo'))
            test_result.labels.append(
                Label(name=LabelType.LANGUAGE, value=platform_label()))

            self._record_tool_link(test_result, tool_id)
            self._record_status(test_result, test_data)
            if test_result.status in [Status.BROKEN, Status.FAILED]:
                test_result.statusDetails = StatusDetails(
                    message=escape_non_unicode_symbols(problem_message
                                                       or "Unknown problem"),
                    trace=None)

        self.lifecycle.write_test_case()

    def _record_start_stop(self, test_result, file_modication_datetime, job):
        start_datetime = file_modication_datetime
        end_datetime = file_modication_datetime
        if "create_time" in job:
            start_datetime = parser.parse(job["create_time"])

        if "update_time" in job:
            end_datetime = parser.parse(job["update_time"])

        if start_datetime is not None:
            test_result.start = int(round(start_datetime.timestamp() * 1000))
        if end_datetime is not None:
            test_result.stop = int(round(end_datetime.timestamp() * 1000))

    def _record_suite_labels(self, test_result, test_data, job):
        tool_id = None
        if "tool_id" in test_data:
            tool_id = test_data["tool_id"]
            test_result.labels.append(
                Label(name=LabelType.PARENT_SUITE, value=tool_id))
        elif "tool_id" in job:
            tool_id = job["tool_id"]
            test_result.labels.append(
                Label(name=LabelType.PARENT_SUITE, value=tool_id))

        if "tool_version" in test_data:
            test_result.labels.append(
                Label(name=LabelType.SUITE, value=test_data["tool_version"]))
        elif "tool_version" in job:
            test_result.labels.append(
                Label(name=LabelType.SUITE, value=job["tool_version"]))

        if "test_index" in test_data:
            test_result.labels.append(
                Label(name=LabelType.SUB_SUITE,
                      value=str(test_data["test_index"])))
        return tool_id

    def _record_tool_link(self, test_result, tool_id):
        if tool_id and 'repos' in tool_id:
            tool_parts = tool_id.split("/")
            if len(tool_parts) >= 4:
                link = Link(LinkType.LINK,
                            "https://%s" % "/".join(tool_parts[0:4]),
                            "Tool Repository")
                test_result.links.append(link)

    def _record_status(self, test_result, test_data):
        status = test_data.get("status", "error")
        if status == "success":
            test_result.status = Status.PASSED
        elif status == "failure":
            test_result.status = Status.FAILED
        elif status == "skip":
            test_result.status = Status.SKIPPED
        else:
            test_result.status = Status.BROKEN

    def _attach_data(self, key, val, attachment_type=AttachmentType.TEXT):
        self.lifecycle.attach_data(uuid4(),
                                   val,
                                   name=key,
                                   attachment_type=attachment_type,
                                   extension=None)