From 5737a23fa2e12e80912ff920e458c8cc7fedbaea Mon Sep 17 00:00:00 2001 From: Dmitrii Gridnev Date: Thu, 3 Oct 2024 10:35:29 +0200 Subject: [PATCH] feature: support `pabot` If you use the `pabot` library to run tests in parallel, the reporter will send the results --- qase-robotframework/changelog.md | 8 +++ qase-robotframework/pyproject.toml | 3 +- .../src/qase/robotframework/listener.py | 56 +++++++++++++++++-- 3 files changed, 62 insertions(+), 5 deletions(-) diff --git a/qase-robotframework/changelog.md b/qase-robotframework/changelog.md index 171db733..334fe308 100644 --- a/qase-robotframework/changelog.md +++ b/qase-robotframework/changelog.md @@ -1,3 +1,11 @@ +# qase-pytest 3.2.0 + +## What's new + +Minor release that includes all changes from beta versions 3.2.0b. + +Support `pabot` library. If you use the `pabot` library to run tests in parallel, the reporter will send the results + # qase-pytest 3.2.0b3 ## What's new diff --git a/qase-robotframework/pyproject.toml b/qase-robotframework/pyproject.toml index ae138588..7eff556f 100644 --- a/qase-robotframework/pyproject.toml +++ b/qase-robotframework/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "qase-robotframework" -version = "3.2.0b3" +version = "3.2.0" description = "Qase Robot Framework Plugin" readme = "README.md" authors = [{name = "Qase Team", email = "support@qase.io"}] @@ -18,6 +18,7 @@ urls = {"Homepage" = "https://github.com/qase-tms/qase-python/tree/master/qase-r requires-python = ">=3.7" dependencies = [ "qase-python-commons~=3.1.3", + "filelock~=3.12.2", ] [project.optional-dependencies] diff --git a/qase-robotframework/src/qase/robotframework/listener.py b/qase-robotframework/src/qase/robotframework/listener.py index 0a6eb1a5..88feffa5 100644 --- a/qase-robotframework/src/qase/robotframework/listener.py +++ b/qase-robotframework/src/qase/robotframework/listener.py @@ -1,10 +1,13 @@ import logging +import pathlib import uuid +from filelock import FileLock from qase.commons import ConfigManager from qase.commons.models import Result, Suite, Step, Field from qase.commons.models.step import StepType, StepGherkinData from qase.commons.reporters import QaseCoreReporter +from robot.libraries.BuiltIn import BuiltIn from .filter import Filter from .plugin import QaseRuntimeSingleton @@ -15,15 +18,22 @@ logger = logging.getLogger("qase-robotframework") +def get_pool_id(): + return BuiltIn().get_variable_value('${PABOTQUEUEINDEX}', None) + + class Listener: ROBOT_LISTENER_API_VERSION = 3 + meta_run_file = pathlib.Path("src.run") + def __init__(self): config = ConfigManager() self.reporter = QaseCoreReporter(config) self.runtime = QaseRuntimeSingleton.get_instance() self.step_uuid = None self.tests = {} + self.pabot_index = None if config.config.debug: logger.setLevel(logging.DEBUG) @@ -34,9 +44,26 @@ def __init__(self): ch.setFormatter(formatter) logger.addHandler(ch) - self.reporter.start_run() - def start_suite(self, suite, result): + self.pabot_index = get_pool_id() + if self.pabot_index is not None: + try: + if int(self.pabot_index) == 0: + test_run_id = self.reporter.start_run() + with FileLock("qase.lock"): + if test_run_id: + with open(self.meta_run_file, "w") as lock_file: + lock_file.write(str(test_run_id)) + else: + while True: + if Listener.meta_run_file.exists(): + self.__load_run_from_lock() + break + except RuntimeError: + logger.error("Failed to create or read lock file") + else: + self.reporter.start_run() + execution_plan = self.reporter.get_execution_plan() if execution_plan: selector = Filter(*execution_plan) @@ -99,8 +126,15 @@ def end_test(self, test, result): ) def close(self): - logger.info("complete run executing") - self.reporter.complete_run() + if self.pabot_index is not None: + if int(self.pabot_index) == 0: + Listener.drop_run_id() + else: + self.reporter.complete_worker() + + if not Listener.meta_run_file.exists(): + logger.info("complete run executing") + self.reporter.complete_run() def __extract_tests_with_suites(self, suite, parent_suites=None): if parent_suites is None: @@ -184,3 +218,17 @@ def __parse_condition_steps(self, result_step) -> List[Step]: steps.append(step) return steps + + def __load_run_from_lock(self): + if Listener.meta_run_file.exists(): + with open(Listener.meta_run_file, "r") as lock_file: + try: + test_run_id = str(lock_file.read()) + self.reporter.set_run_id(test_run_id) + except ValueError: + pass + + @staticmethod + def drop_run_id(): + if Listener.meta_run_file.exists(): + Listener.meta_run_file.unlink()