diff --git a/.flake8 b/.flake8 index 791f075..65cca96 100644 --- a/.flake8 +++ b/.flake8 @@ -1,2 +1,4 @@ [flake8] +ignore = E203, W503, E701 +exclude = .git,venv,env max-line-length = 119 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 78aedf3..d7acdc4 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -20,14 +20,14 @@ on: - '.github/**' - CHANGELOG.md - README.md - - CONTRIBUTING.rst + - ../../CONTRIBUTING.md env: VERSION_FILE: setup.py VERSION_EXTRACT_PATTERN: >- - __version__\s*=\s*'([^']+) + __version__\s*=\s*"([^"]+) VERSION_REPLACE_PATTERN: >- - __version__ = '\1' + __version__ = "\1" TMP_SUFFIX: _updated CHANGE_LOG_FILE: CHANGELOG.md @@ -38,6 +38,13 @@ jobs: - name: Checkout repository uses: actions/checkout@v4 + - name: Generate versions + uses: HardNorth/github-version-generate@v1 + with: + version-source: file + version-file: ${{ env.VERSION_FILE }} + version-file-extraction-pattern: ${{ env.VERSION_EXTRACT_PATTERN }} + - name: Set up Python uses: actions/setup-python@v5 with: @@ -55,13 +62,6 @@ jobs: user: ${{ secrets.PYPI_USERNAME }} password: ${{ secrets.PYPI_PASSWORD }} - - name: Generate versions - uses: HardNorth/github-version-generate@v1 - with: - version-source: file - version-file: ${{ env.VERSION_FILE }} - version-file-extraction-pattern: ${{ env.VERSION_EXTRACT_PATTERN }} - - name: Setup git credentials uses: oleksiyrudenko/gha-git-credentials@v2-latest with: @@ -119,8 +119,8 @@ jobs: - name: Update version file id: versionFileUpdate run: | - export CURRENT_VERSION_VALUE=`echo '${{ env.CURRENT_VERSION }}' | sed -E "s/(.*)/${{ env.VERSION_REPLACE_PATTERN }}/"` - export NEXT_VERSION_VALUE=`echo '${{ env.NEXT_VERSION }}' | sed -E "s/(.*)/${{ env.VERSION_REPLACE_PATTERN }}/"` + export CURRENT_VERSION_VALUE=`echo '${{ env.CURRENT_VERSION }}' | sed -E 's/(.*)/${{ env.VERSION_REPLACE_PATTERN }}/'` + export NEXT_VERSION_VALUE=`echo '${{ env.NEXT_VERSION }}' | sed -E 's/(.*)/${{ env.VERSION_REPLACE_PATTERN }}/'` sed "s/${CURRENT_VERSION_VALUE}/${NEXT_VERSION_VALUE}/g" ${{ env.VERSION_FILE }} > ${{ env.VERSION_FILE }}${{ env.TMP_SUFFIX }} rm ${{ env.VERSION_FILE }} mv ${{ env.VERSION_FILE }}${{ env.TMP_SUFFIX }} ${{ env.VERSION_FILE }} diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 82b645b..f4bb757 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,37 +1,31 @@ # See https://pre-commit.com for more information # See https://pre-commit.com/hooks.html for more hooks repos: -- repo: https://github.com/pre-commit/pre-commit-hooks - rev: v3.4.0 + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v5.0.0 hooks: - - id: trailing-whitespace - - id: end-of-file-fixer - - id: check-yaml - - id: check-added-large-files -- repo: https://github.com/PyCQA/pydocstyle - rev: 6.0.0 + - id: trailing-whitespace + - id: end-of-file-fixer + - id: check-yaml + - id: check-added-large-files + - repo: https://github.com/PyCQA/pydocstyle + rev: 6.3.0 hooks: - - id: pydocstyle - # Temporary exclude files in which are in charge of offline reporting + - id: pydocstyle exclude: | - (?x)^( - tests/*| - robotframework_reportportal/result_visitor.py| - robotframework_reportportal/post_report.py| - robotframework_reportportal/time_visitor.py - ) -- repo: https://github.com/Lucas-C/pre-commit-hooks-markup - rev: v1.0.1 + (?x)^( + tests/.* + ) + - repo: https://github.com/psf/black + rev: 24.10.0 hooks: - - id: rst-linter -- repo: https://github.com/pycqa/flake8 - rev: 5.0.4 + - id: black + args: [ 'reportportal_client', 'tests' ] + - repo: https://github.com/pycqa/isort + rev: 5.13.2 hooks: - - id: flake8 - # Temporary exclude files in which are in charge of offline reporting - exclude: | - (?x)^( - robotframework_reportportal/result_visitor.py| - robotframework_reportportal/post_report.py| - robotframework_reportportal/time_visitor.py - )$ + - id: isort + - repo: https://github.com/pycqa/flake8 + rev: 7.1.1 + hooks: + - id: flake8 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..b45f883 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,136 @@ +# Contribution + +Contributions are highly welcomed and appreciated. + +## Contents + +- [Feature requests](#feature-requests) +- [Bug reports](#bug-reports) +- [Bug fixes](#bug-fixes) +- [Implement features](#implement-features) +- [Preparing Pull Requests](#preparing-pull-requests) + +## Feature requests + +We'd also like to hear about your thoughts and suggestions. Feel free to [submit them as issues](https://github.com/reportportal/agent-Python-RobotFramework/issues) and: + +* Explain in detail how they should work. +* Keep the scope as narrow as possible. It will make it easier to implement. + +## Bug reports + +Report bugs for the agent in the [issue tracker](https://github.com/reportportal/agent-Python-RobotFramework/issues). + +If you are reporting a new bug, please include: + +* Your operating system name and version. +* Python interpreter version, installed libraries, reportportal-client, and agent-Python-RobotFramework version. +* Detailed steps to reproduce the bug. + +## Bug fixes + +Look through the [GitHub issues for bugs](https://github.com/reportportal/agent-Python-RobotFramework/labels/bug). + +If you are going to fix any of existing bugs, assign that bug to yourself and specify preliminary milestones. Talk to [contributors](https://github.com/reportportal/agent-Python-RobotFramework/graphs/contributors) in case you need a consultancy regarding implementation. + +## Implement features + +Look through the [GitHub issues for enhancements](https://github.com/reportportal/agent-Python-RobotFramework/labels/enhancement). + +Talk to [contributors](https://github.com/reportportal/agent-Python-RobotFramework/graphs/contributors) in case you need a consultancy regarding implementation. + +## Preparing Pull Requests + +What is a "pull request"? It informs the project's core developers about the changes you want to review and merge. Pull requests are stored on [GitHub servers](https://github.com/reportportal/agent-Python-RobotFramework/pulls). Once you send a pull request, we can discuss its potential modifications and even add more commits to it later on. There's an excellent tutorial on how Pull Requests work in the [GitHub Help Center](https://help.github.com/articles/using-pull-requests/). + +Here is a simple overview below: + +1. Fork the [agent-Python-RobotFramework GitHub repository](https://github.com/reportportal/agent-Python-RobotFramework). + +2. Clone your fork locally using [git](https://git-scm.com/) and create a branch: + + ```sh + $ git clone git@github.com:YOUR_GITHUB_USERNAME/agent-Python-RobotFramework.git + $ cd agent-Python-RobotFramework + # now, create your own branch off the "master": + $ git checkout -b your-bugfix-branch-name + ``` + + If you need some help with Git, follow this quick start guide: https://git.wiki.kernel.org/index.php/QuickStart + +3. Install [pre-commit](https://pre-commit.com) and its hook on the agent-Python-RobotFramework repo: + + **Note: pre-commit must be installed as admin, as it will not function otherwise**: + + ```sh + $ pip install --user pre-commit + $ pre-commit install + ``` + + Afterward `pre-commit` will run whenever you commit. + + [https://pre-commit.com](https://pre-commit.com) is a framework for managing and maintaining multi-language pre-commit hooks to ensure code-style and code formatting is consistent. + +4. Install tox + + Tox is used to run all the tests and will automatically set up virtualenvs to run the tests in. (will implicitly use http://www.virtualenv.org/en/latest/): + + ```sh + $ pip install tox + ``` + +5. Run all the tests + + You need to have Python 3.10 available in your system. Now running tests is as simple as issuing this command: + + ```sh + $ tox -e pep,py310 + ``` + + This command will run tests via the "tox" tool against Python 3.10 and also perform code style checks. + +6. You can now edit your local working copy and run the tests again as necessary. Please follow PEP-8 recommendations. + + You can pass different options to `tox`. For example, to run tests on Python 3.10 and pass options to pytest (e.g. enter pdb on failure) to pytest you can do: + + ```sh + $ tox -e py310 -- --pdb + ``` + + Or to only run tests in a particular test module on Python 3.10: + + ```sh + $ tox -e py310 -- tests/test_service.py + ``` + + When committing, `pre-commit` will re-format the files if necessary. + +7. If instead of using `tox` you prefer to run the tests directly, then we suggest to create a virtual environment and use an editable installation with the `testing` extra: + + ```sh + $ python3 -m venv .venv + $ source .venv/bin/activate # Linux + $ .venv/Scripts/activate.bat # Windows + $ pip install -e ".[testing]" + ``` + + Afterwards, you can edit the files and run pytest normally: + + ```sh + $ pytest tests/test_service.py + ``` + +8. Commit and push once your tests pass and you are happy with your change(s): + + ```sh + $ git commit -m "" + $ git push -u + ``` + +9. Finally, submit a pull request through the GitHub website using this data: + + head-fork: YOUR_GITHUB_USERNAME/agent-Python-RobotFramework + compare: your-branch-name + + base-fork: reportportal/agent-Python-RobotFramework + base: master diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst deleted file mode 100644 index cafcc20..0000000 --- a/CONTRIBUTING.rst +++ /dev/null @@ -1,146 +0,0 @@ -============ -Contribution -============ - -Contributions are highly welcomed and appreciated. - -.. contents:: - :depth: 2 - :backlinks: none - -Feature requests ----------------- - -We'd also like to hear about your thoughts and suggestions. Feel free to -`submit them as issues `_ and: - -* Explain in detail how they should work. -* Keep the scope as narrow as possible. It will make it easier to implement. - -Bug reports ------------ - -Report bugs for the agent in the `issue tracker `_. - -If you are reporting a new bug, please include: - -* Your operating system name and version. -* Python interpreter version, installed libraries, reportportal-client, and agent-Python-RobotFramework - version. -* Detailed steps to reproduce the bug. - -Bug fixes ---------- - -Look through the `GitHub issues for bugs `_. - -If you are gonna fix any of existing bugs, assign that bug to yourself and specify preliminary milestones. -Talk to `contributors `_ in case you need a -consultancy regarding implementation. - -Implement features ------------------- - -Look through the `GitHub issues for enhancements `_. - -Talk to `contributors `_ in case you need a -consultancy regarding implementation. - -Preparing Pull Requests ------------------------ - -What is a "pull request"? It informs the project's core developers about the -changes you want to review and merge. Pull requests are stored on -`GitHub servers `_. -Once you send a pull request, we can discuss its potential modifications and -even add more commits to it later on. There's an excellent tutorial on how Pull -Requests work in the -`GitHub Help Center `_. - -Here is a simple overview below: - -#. Fork the - `agent-Python-RobotFramework GitHub repository `_. - -#. Clone your fork locally using `git `_ and create a branch:: - - $ git clone git@github.com:YOUR_GITHUB_USERNAME/agent-Python-RobotFramework.git - $ cd agent-Python-RobotFramework - # now, create your own branch off the "master": - - $ git checkout -b your-bugfix-branch-name - - If you need some help with Git, follow this quick start - guide: https://git.wiki.kernel.org/index.php/QuickStart - -#. Install `pre-commit `_ and its hook on the agent-Python-RobotFramework repo: - - **Note: pre-commit must be installed as admin, as it will not function otherwise**:: - - - $ pip install --user pre-commit - $ pre-commit install - - Afterwards ``pre-commit`` will run whenever you commit. - - https://pre-commit.com/ is a framework for managing and maintaining multi-language pre-commit hooks - to ensure code-style and code formatting is consistent. - -#. Install tox - - Tox is used to run all the tests and will automatically setup virtualenvs - to run the tests in. - (will implicitly use http://www.virtualenv.org/en/latest/):: - - $ pip install tox - -#. Run all the tests - - You need to have Python 3.6 available in your system. Now - running tests is as simple as issuing this command:: - - $ tox -e pep,py36 - - This command will run tests via the "tox" tool against Python 3.6 - and also perform code style checks. - -#. You can now edit your local working copy and run the tests again as necessary. Please follow PEP-8 recommendations. - - You can pass different options to ``tox``. For example, to run tests on Python 3.6 and pass options to pytest - (e.g. enter pdb on failure) to pytest you can do:: - - $ tox -e py36 -- --pdb - - Or to only run tests in a particular test module on Python 3.6:: - - $ tox -e py36 -- tests/test_service.py - - - When committing, ``pre-commit`` will re-format the files if necessary. - -#. If instead of using ``tox`` you prefer to run the tests directly, then we suggest to create a virtual environment and use - an editable install with the ``testing`` extra:: - - $ python3 -m venv .venv - $ source .venv/bin/activate # Linux - $ .venv/Scripts/activate.bat # Windows - $ pip install -e ".[testing]" - - Afterwards, you can edit the files and run pytest normally:: - - $ pytest tests/test_service.py - - -#. Commit and push once your tests pass and you are happy with your change(s):: - - $ git commit -m "" - $ git push -u - - -#. Finally, submit a pull request through the GitHub website using this data:: - - head-fork: YOUR_GITHUB_USERNAME/agent-Python-RobotFramework - compare: your-branch-name - - base-fork: reportportal/agent-Python-RobotFramework - base: master diff --git a/pyproject.toml b/pyproject.toml index bc432f1..c398821 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,3 +6,11 @@ requires = [ "wheel==0.37.1", ] build-backend = "setuptools.build_meta" + +[tool.isort] +line_length = 119 +profile = "black" + +[tool.black] +line-length = 119 +target-version = ["py310"] diff --git a/robotframework_reportportal/helpers.py b/robotframework_reportportal/helpers.py index 695348b..1188142 100644 --- a/robotframework_reportportal/helpers.py +++ b/robotframework_reportportal/helpers.py @@ -28,15 +28,15 @@ def replace_patterns(text: str, patterns: Iterable[Tuple[re.Pattern, str]]) -> s return result -BARE_LINK_PATTERN = re.compile(r'\[\s*([^]|]+)]') -NAMED_LINK_PATTERN = re.compile(r'\[\s*([^]|]+)\|\s*([^]]+)]') +BARE_LINK_PATTERN = re.compile(r"\[\s*([^]|]+)]") +NAMED_LINK_PATTERN = re.compile(r"\[\s*([^]|]+)\|\s*([^]]+)]") ROBOT_MARKUP_REPLACEMENT_PATTERS = [ - (BARE_LINK_PATTERN, r'<\1>'), - (NAMED_LINK_PATTERN, r'[\2](\1)'), + (BARE_LINK_PATTERN, r"<\1>"), + (NAMED_LINK_PATTERN, r"[\2](\1)"), ] -PATTERN_MATCHES_EMPTY_STRING: re.Pattern = re.compile('^$') +PATTERN_MATCHES_EMPTY_STRING: re.Pattern = re.compile("^$") def robot_markup_to_markdown(text: str) -> str: @@ -52,7 +52,7 @@ def translate_glob_to_regex(pattern: Optional[str]) -> Optional[re.Pattern]: """ if pattern is None: return None - if pattern == '': + if pattern == "": return PATTERN_MATCHES_EMPTY_STRING return re.compile(fnmatch.translate(pattern)) @@ -86,22 +86,22 @@ def _unescape(binary_string: str, stop_at: int = -1): continue else: if len(join_list) > 0: - for bb in binascii.unhexlify(''.join(join_list)): + for bb in binascii.unhexlify("".join(join_list)): result.append(bb) if stop_at > 0: if len(result) >= stop_at: break join_list = list() - if b == '\\' and binary_string[i + 1] == 'x': + if b == "\\" and binary_string[i + 1] == "x": skip_next = True join_idx = i + 2 continue - for bb in b.encode('utf-8'): + for bb in b.encode("utf-8"): result.append(bb) if stop_at > 0: if len(result) >= stop_at: break if len(join_list) > 0: - for bb in binascii.unhexlify(''.join(join_list)): + for bb in binascii.unhexlify("".join(join_list)): result.append(bb) return result diff --git a/robotframework_reportportal/listener.py b/robotframework_reportportal/listener.py index a8ae65c..955022c 100644 --- a/robotframework_reportportal/listener.py +++ b/robotframework_reportportal/listener.py @@ -33,17 +33,18 @@ from robotframework_reportportal.variables import Variables logger = logging.getLogger(__name__) -VARIABLE_PATTERN = re.compile(r'^\s*\${[^}]*}\s*=\s*') +VARIABLE_PATTERN = re.compile(r"^\s*\${[^}]*}\s*=\s*") IMAGE_PATTERN = re.compile( r'' - r'') + r'' +) -DEFAULT_BINARY_FILE_TYPE = 'application/octet-stream' +DEFAULT_BINARY_FILE_TYPE = "application/octet-stream" TRUNCATION_SIGN = "...'" -REMOVED_KEYWORD_LOG = 'Keyword data removed using --RemoveKeywords option.' -WKUS_KEYWORD_NAME = 'BuiltIn.Wait Until Keyword Succeeds' -FOR_KEYWORD_NAME = 'BuiltIn.For' -WHILE_KEYWORD_NAME = 'BuiltIn.While' +REMOVED_KEYWORD_LOG = "Keyword data removed using --RemoveKeywords option." +WKUS_KEYWORD_NAME = "BuiltIn.Wait Until Keyword Succeeds" +FOR_KEYWORD_NAME = "BuiltIn.For" +WHILE_KEYWORD_NAME = "BuiltIn.While" def check_rp_enabled(func): @@ -61,8 +62,7 @@ def wrap(*args, **kwargs): class _KeywordMatch(ABC): @abstractmethod - def match(self, kw: Keyword) -> bool: - ... + def match(self, kw: Keyword) -> bool: ... class _KeywordNameMatch(_KeywordMatch): @@ -123,51 +123,53 @@ def _build_msg_struct(self, message: Dict[str, Any]) -> LogMessage: :param message: Message passed by the Robot Framework """ - if isinstance(message['message'], LogMessage): - msg = message['message'] + if isinstance(message["message"], LogMessage): + msg = message["message"] else: - msg = LogMessage(message['message']) - msg.level = message['level'] + msg = LogMessage(message["message"]) + msg.level = message["level"] if not msg.launch_log: - msg.item_id = getattr(self.current_item, 'rp_item_id', None) + msg.item_id = getattr(self.current_item, "rp_item_id", None) message_str = msg.message if is_binary(message_str): variable_match = VARIABLE_PATTERN.search(message_str) if variable_match: # Treat as partial binary data - msg_content = message_str[variable_match.end():] + msg_content = message_str[variable_match.end() :] # remove trailing `'"...`, add `...'` - msg.message = (message_str[variable_match.start():variable_match.end()] - + str(msg_content.encode('utf-8'))[:-5] + TRUNCATION_SIGN) + msg.message = ( + message_str[variable_match.start() : variable_match.end()] + + str(msg_content.encode("utf-8"))[:-5] + + TRUNCATION_SIGN + ) else: # Do not log full binary data, since it's usually corrupted content_type = guess_content_type_from_bytes(_unescape(message_str, 128)) - msg.message = (f'Binary data of type "{content_type}" logging skipped, as it was processed as text and' - ' hence corrupted.') - msg.level = 'WARN' - elif message.get('html', 'no') == 'yes': + msg.message = ( + f'Binary data of type "{content_type}" logging skipped, as it was processed as text and' + " hence corrupted." + ) + msg.level = "WARN" + elif message.get("html", "no") == "yes": image_match = IMAGE_PATTERN.match(message_str) if image_match: image_path = image_match.group(1) - msg.message = f'Image attached: {image_path}' + msg.message = f"Image attached: {image_path}" if os.path.exists(image_path): image_type_by_name = guess_type(image_path)[0] - with open(image_path, 'rb') as fh: + with open(image_path, "rb") as fh: image_data = fh.read() image_type_by_data = guess_content_type_from_bytes(image_data) if image_type_by_name and image_type_by_data and image_type_by_name != image_type_by_data: logger.warning( f'Image type mismatch: type by file name "{image_type_by_name}" ' - f'!= type by file content "{image_type_by_data}"') + f'!= type by file content "{image_type_by_data}"' + ) mime_type = DEFAULT_BINARY_FILE_TYPE else: mime_type = image_type_by_name or image_type_by_data or DEFAULT_BINARY_FILE_TYPE - msg.attachment = { - 'name': os.path.basename(image_path), - 'data': image_data, - 'mime': mime_type - } + msg.attachment = {"name": os.path.basename(image_path), "data": image_data, "mime": mime_type} return msg def _add_current_item(self, item: Union[Keyword, Launch, Suite, Test]) -> None: @@ -185,7 +187,7 @@ def current_item(self) -> Optional[Union[Keyword, Launch, Suite, Test]]: def __post_skipped_keyword(self, kwd: Keyword) -> None: self._do_start_keyword(kwd) - skipped_logs = getattr(kwd, 'skipped_logs', []) + skipped_logs = getattr(kwd, "skipped_logs", []) for log_message in skipped_logs: self._log_message(log_message) skipped_kwds = kwd.skipped_keywords @@ -200,7 +202,7 @@ def _post_skipped_keywords(self, to_post: Optional[Any]) -> None: if isinstance(to_post, Keyword): if not to_post.posted: self._do_start_keyword(to_post) - skipped_kwds = getattr(to_post, 'skipped_keywords', None) + skipped_kwds = getattr(to_post, "skipped_keywords", None) if skipped_kwds: to_post.skipped_keywords = [] for skipped_kwd in skipped_kwds: @@ -212,14 +214,14 @@ def _log_message(self, message: LogMessage) -> None: :param message: Internal message object to send """ if message.attachment: - logger.debug(f'ReportPortal - Log Message with Attachment: {message}') + logger.debug(f"ReportPortal - Log Message with Attachment: {message}") else: - logger.debug(f'ReportPortal - Log Message: {message}') + logger.debug(f"ReportPortal - Log Message: {message}") current_item = self.current_item - if current_item and not getattr(current_item, 'posted', True) and message.level not in ['ERROR', 'WARN']: + if current_item and not getattr(current_item, "posted", True) and message.level not in ["ERROR", "WARN"]: self.current_item.skipped_logs.append(message) - elif getattr(current_item, 'matched_filter', None) is not WKUS_KEYWORD_MATCH: + elif getattr(current_item, "matched_filter", None) is not WKUS_KEYWORD_MATCH: # Post everything skipped by '--removekeywords' option self._post_skipped_keywords(current_item) self.service.log(message=message) @@ -241,18 +243,18 @@ def log_message_with_image(self, msg: Dict, image: str): :param image: Path to image """ mes = self._build_msg_struct(msg) - with open(image, 'rb') as fh: + with open(image, "rb") as fh: mes.attachment = { - 'name': os.path.basename(image), - 'data': fh.read(), - 'mime': guess_type(image)[0] or DEFAULT_BINARY_FILE_TYPE + "name": os.path.basename(image), + "data": fh.read(), + "mime": guess_type(image)[0] or DEFAULT_BINARY_FILE_TYPE, } self._log_message(mes) @property def parent_id(self) -> Optional[str]: """Get rp_item_id attribute of the current item.""" - return getattr(self.current_item, 'rp_item_id', None) + return getattr(self.current_item, "rp_item_id", None) @property def service(self) -> RobotService: @@ -273,34 +275,35 @@ def _process_keyword_skip(self): try: # noinspection PyUnresolvedReferences from robot.running.context import EXECUTION_CONTEXTS + current_context = EXECUTION_CONTEXTS.current if current_context: # noinspection PyProtectedMember for pattern_str in set(current_context.output._settings.remove_keywords): pattern_str_upper = pattern_str.upper() - if 'ALL' == pattern_str_upper: + if "ALL" == pattern_str_upper: self._remove_keyword_data = True break - if 'PASSED' == pattern_str_upper: + if "PASSED" == pattern_str_upper: self._remove_keywords = True break - if pattern_str_upper in {'NOT_RUN', 'NOTRUN', 'NOT RUN'}: - self._keyword_filters.append(_KeywordStatusMatch('NOT RUN')) + if pattern_str_upper in {"NOT_RUN", "NOTRUN", "NOT RUN"}: + self._keyword_filters.append(_KeywordStatusMatch("NOT RUN")) continue - if pattern_str_upper in {'FOR', 'WHILE', 'WUKS'}: - if pattern_str_upper == 'WUKS': + if pattern_str_upper in {"FOR", "WHILE", "WUKS"}: + if pattern_str_upper == "WUKS": self._keyword_filters.append(WKUS_KEYWORD_MATCH) - elif pattern_str_upper == 'FOR': + elif pattern_str_upper == "FOR": self._keyword_filters.append(FOR_KEYWORD_MATCH) else: self._keyword_filters.append(WHILE_KEYWORD_NAME) continue - if ':' in pattern_str: - pattern_type, pattern = pattern_str.split(':', 1) + if ":" in pattern_str: + pattern_type, pattern = pattern_str.split(":", 1) pattern_type = pattern_type.strip().upper() - if 'NAME' == pattern_type.upper(): + if "NAME" == pattern_type.upper(): self._keyword_filters.append(_KeywordNameMatch(pattern.strip())) - elif 'TAG' == pattern_type.upper(): + elif "TAG" == pattern_type.upper(): self._keyword_filters.append(_KeywordTagMatch(pattern.strip())) except ImportError: warn('Unable to locate Robot Framework context. "removekeywords" feature will not work.', stacklevel=2) @@ -317,13 +320,14 @@ def start_launch(self, attributes: Dict[str, Any], ts: Optional[Any] = None) -> launch.doc = self.variables.launch_doc or launch.doc if self.variables.pabot_used and not self._variables.launch_id: warn(PABOT_WITHOUT_LAUNCH_ID_MSG, stacklevel=2) - logger.debug(f'ReportPortal - Start Launch: {launch.robot_attributes}') + logger.debug(f"ReportPortal - Start Launch: {launch.robot_attributes}") self.service.start_launch( launch=launch, mode=self.variables.mode, ts=ts, rerun=self.variables.rerun, - rerun_of=self.variables.rerun_of) + rerun_of=self.variables.rerun_of, + ) def finish_launch(self, attributes: Dict[str, Any], ts: Optional[Any] = None) -> None: """Finish started launch at the ReportPortal. @@ -332,7 +336,7 @@ def finish_launch(self, attributes: Dict[str, Any], ts: Optional[Any] = None) -> :param ts: Timestamp(used by the ResultVisitor) """ launch = Launch(self.variables.launch_name, attributes, None) - logger.debug(f'ReportPortal - End Launch: {launch.robot_attributes}') + logger.debug(f"ReportPortal - End Launch: {launch.robot_attributes}") self.service.finish_launch(launch=launch, ts=ts) @check_rp_enabled @@ -343,13 +347,13 @@ def start_suite(self, name: str, attributes: Dict, ts: Optional[Any] = None) -> :param attributes: Dictionary passed by the Robot Framework :param ts: Timestamp(used by the ResultVisitor) """ - if attributes['id'] == MAIN_SUITE_ID: + if attributes["id"] == MAIN_SUITE_ID: self.start_launch(attributes, ts) if self.variables.pabot_used: - name = f'{name}.{self.variables.pabot_pool_id}' - logger.debug(f'ReportPortal - Create global Suite: {attributes}') + name = f"{name}.{self.variables.pabot_pool_id}" + logger.debug(f"ReportPortal - Create global Suite: {attributes}") else: - logger.debug(f'ReportPortal - Start Suite: {attributes}') + logger.debug(f"ReportPortal - Start Suite: {attributes}") suite = Suite(name, attributes) suite.remove_data = self._remove_keywords suite.rp_parent_item_id = self.parent_id @@ -367,16 +371,16 @@ def end_suite(self, _: Optional[str], attributes: Dict, ts: Optional[Any] = None :param ts: Timestamp(used by the ResultVisitor) """ suite = self._remove_current_item().update(attributes) - if suite.remove_data and attributes['status'] == 'FAIL': + if suite.remove_data and attributes["status"] == "FAIL": self._post_skipped_keywords(suite) - logger.debug(f'ReportPortal - End Suite: {suite.robot_attributes}') + logger.debug(f"ReportPortal - End Suite: {suite.robot_attributes}") self.service.finish_suite(suite=suite, ts=ts) - if attributes['id'] == MAIN_SUITE_ID: + if attributes["id"] == MAIN_SUITE_ID: self.finish_launch(attributes, ts) def _log_keyword_data_removed(self, item_id: str) -> None: msg = LogMessage(REMOVED_KEYWORD_LOG) - msg.level = 'INFO' + msg.level = "INFO" msg.item_id = item_id self._log_message(msg) @@ -388,13 +392,13 @@ def start_test(self, name: str, attributes: Dict, ts: Optional[Any] = None) -> N :param attributes: Dictionary passed by the Robot Framework :param ts: Timestamp(used by the ResultVisitor) """ - if 'source' not in attributes: + if "source" not in attributes: # no 'source' parameter at this level for Robot versions < 4 attributes = attributes.copy() - attributes['source'] = getattr(self.current_item, 'source', None) + attributes["source"] = getattr(self.current_item, "source", None) test = Test(name=name, robot_attributes=attributes, test_attributes=self.variables.test_attributes) test.remove_data = self._remove_keywords - logger.debug(f'ReportPortal - Start Test: {attributes}') + logger.debug(f"ReportPortal - Start Test: {attributes}") test.rp_parent_item_id = self.parent_id test.rp_item_id = self.service.start_test(test=test, ts=ts) self._add_current_item(test) @@ -410,18 +414,18 @@ def end_test(self, _: Optional[str], attributes: Dict, ts: Optional[Any] = None) :param ts: Timestamp(used by the ResultVisitor) """ test = self.current_item.update(attributes) - if not test.critical and test.status == 'FAIL': - test.status = 'SKIP' - if test.remove_data and attributes['status'] == 'FAIL': + if not test.critical and test.status == "FAIL": + test.status = "SKIP" + if test.remove_data and attributes["status"] == "FAIL": self._post_skipped_keywords(test) if test.message: - self.log_message({'message': test.message, 'level': 'DEBUG'}) - logger.debug(f'ReportPortal - End Test: {test.robot_attributes}') + self.log_message({"message": test.message, "level": "DEBUG"}) + logger.debug(f"ReportPortal - End Test: {test.robot_attributes}") self._remove_current_item() self.service.finish_test(test=test, ts=ts) def _do_start_keyword(self, keyword: Keyword, ts: Optional[str] = None) -> None: - logger.debug(f'ReportPortal - Start Keyword: {keyword.robot_attributes}') + logger.debug(f"ReportPortal - Start Keyword: {keyword.robot_attributes}") keyword.rp_item_id = self.service.start_keyword(keyword=keyword, ts=ts) keyword.posted = True @@ -440,7 +444,7 @@ def start_keyword(self, name: str, attributes: Dict, ts: Optional[Any] = None) - kwd.remove_data = skip_kwd or self._remove_keyword_data if kwd.remove_data: - kwd.matched_filter = getattr(parent, 'matched_filter', None) + kwd.matched_filter = getattr(parent, "matched_filter", None) else: for m in self._keyword_filters: if m.match(kwd): @@ -460,7 +464,7 @@ def start_keyword(self, name: str, attributes: Dict, ts: Optional[Any] = None) - self._add_current_item(kwd) def _do_end_keyword(self, keyword: Keyword, ts: Optional[str] = None) -> None: - logger.debug(f'ReportPortal - End Keyword: {keyword.robot_attributes}') + logger.debug(f"ReportPortal - End Keyword: {keyword.robot_attributes}") self.service.finish_keyword(keyword=keyword, ts=ts) @check_rp_enabled @@ -472,7 +476,7 @@ def end_keyword(self, _: Optional[str], attributes: Dict, ts: Optional[Any] = No :param ts: Timestamp(used by the ResultVisitor) """ kwd = self.current_item.update(attributes) - if kwd.status == 'FAIL' and not kwd.posted and kwd.matched_filter is not WKUS_KEYWORD_MATCH: + if kwd.status == "FAIL" and not kwd.posted and kwd.matched_filter is not WKUS_KEYWORD_MATCH: self._post_skipped_keywords(kwd) if kwd.matched_filter is WKUS_KEYWORD_MATCH and WKUS_KEYWORD_MATCH.match(kwd): @@ -491,7 +495,7 @@ def log_file(self, log_path: str) -> None: :param log_path: Path to the log file """ if self.variables.attach_log: - message = {'message': 'Execution log', 'level': 'INFO'} + message = {"message": "Execution log", "level": "INFO"} self.log_message_with_image(message, log_path) def report_file(self, report_path: str) -> None: @@ -500,7 +504,7 @@ def report_file(self, report_path: str) -> None: :param report_path: Path to the report file """ if self.variables.attach_report: - message = {'message': 'Execution report', 'level': 'INFO'} + message = {"message": "Execution report", "level": "INFO"} self.log_message_with_image(message, report_path) def xunit_file(self, xunit_path: str) -> None: @@ -509,7 +513,7 @@ def xunit_file(self, xunit_path: str) -> None: :param xunit_path: Path to the XUnit file """ if self.variables.attach_xunit: - message = {'message': 'XUnit result file', 'level': 'INFO'} + message = {"message": "XUnit result file", "level": "INFO"} self.log_message_with_image(message, xunit_path) @check_rp_enabled diff --git a/robotframework_reportportal/logger.py b/robotframework_reportportal/logger.py index e895325..7373c09 100644 --- a/robotframework_reportportal/logger.py +++ b/robotframework_reportportal/logger.py @@ -42,8 +42,13 @@ def log_free_memory(self): from robotframework_reportportal.model import LogMessage -def write(msg: str, level: str = 'INFO', html: bool = False, attachment: Optional[Dict[str, str]] = None, - launch_log: bool = False) -> None: +def write( + msg: str, + level: str = "INFO", + html: bool = False, + attachment: Optional[Dict[str, str]] = None, + launch_log: bool = False, +) -> None: """Write the message to the log file using the given level. Valid log levels are ``TRACE``, ``DEBUG``, ``INFO`` (default since RF 2.9.1), ``WARN``, @@ -78,8 +83,13 @@ def debug(msg: str, html: bool = False, attachment: Optional[Dict[str, str]] = N write(msg, "DEBUG", html, attachment, launch_log) -def info(msg: str, html: bool = False, also_console: bool = False, attachment: Optional[Dict[str, str]] = None, - launch_log: bool = False): +def info( + msg: str, + html: bool = False, + also_console: bool = False, + attachment: Optional[Dict[str, str]] = None, + launch_log: bool = False, +): """Write the message to the log file using the ``INFO`` level. If ``also_console`` argument is set to ``True``, the message is written both to the log file and to the console. @@ -91,15 +101,15 @@ def info(msg: str, html: bool = False, also_console: bool = False, attachment: O def warn(msg: str, html: bool = False, attachment: Optional[Dict[str, str]] = None, launch_log: bool = False) -> None: """Write the message to the log file using the ``WARN`` level.""" - write(msg, 'WARN', html, attachment, launch_log) + write(msg, "WARN", html, attachment, launch_log) def error(msg: str, html: bool = False, attachment: Optional[Dict[str, str]] = None, launch_log: bool = False) -> None: """Write the message to the log file using the ``ERROR`` level.""" - write(msg, 'ERROR', html, attachment, launch_log) + write(msg, "ERROR", html, attachment, launch_log) -def console(msg: str, newline: bool = True, stream: str = 'stdout') -> None: +def console(msg: str, newline: bool = True, stream: str = "stdout") -> None: """Write the message to the console. If the ``newline`` argument is ``True``, a newline character is automatically added to the message. diff --git a/robotframework_reportportal/model.py b/robotframework_reportportal/model.py index b5b2d43..a2c3101 100644 --- a/robotframework_reportportal/model.py +++ b/robotframework_reportportal/model.py @@ -20,7 +20,7 @@ from robotframework_reportportal.helpers import robot_markup_to_markdown from reportportal_client.helpers import gen_attributes -TEST_CASE_ID_SIGN = 'test_case_id:' +TEST_CASE_ID_SIGN = "test_case_id:" class LogMessage(str): @@ -36,7 +36,7 @@ def __init__(self, message: str): """Initialize required attributes.""" self.attachment = None self.item_id = None - self.level = 'INFO' + self.level = "INFO" self.launch_log = False self.message = message @@ -59,9 +59,9 @@ class Keyword: start_time: str status: str tags: List[str] - type: str = 'KEYWORD' + type: str = "KEYWORD" skipped_logs: List[LogMessage] - skipped_keywords: List['Keyword'] + skipped_keywords: List["Keyword"] posted: bool remove_data: bool matched_filter: Any @@ -74,21 +74,21 @@ def __init__(self, name: str, robot_attributes: Dict[str, Any], parent_type: Opt :param parent_type: Type of the parent test item """ self.robot_attributes = robot_attributes - self.args = robot_attributes['args'] - self.assign = robot_attributes['assign'] - self.doc = robot_markup_to_markdown(robot_attributes['doc']) - self.end_time = robot_attributes.get('endtime') - self.keyword_name = robot_attributes['kwname'] - self.keyword_type = robot_attributes['type'] - self.libname = robot_attributes['libname'] + self.args = robot_attributes["args"] + self.assign = robot_attributes["assign"] + self.doc = robot_markup_to_markdown(robot_attributes["doc"]) + self.end_time = robot_attributes.get("endtime") + self.keyword_name = robot_attributes["kwname"] + self.keyword_type = robot_attributes["type"] + self.libname = robot_attributes["libname"] self.name = name self.rp_item_id = None self.rp_parent_item_id = None self.parent_type = parent_type - self.start_time = robot_attributes['starttime'] - self.status = robot_attributes.get('status') - self.tags = robot_attributes['tags'] - self.type = 'KEYWORD' + self.start_time = robot_attributes["starttime"] + self.status = robot_attributes.get("status") + self.tags = robot_attributes["tags"] + self.type = "KEYWORD" self.skipped_keywords = [] self.skipped_logs = [] self.posted = True @@ -97,31 +97,31 @@ def __init__(self, name: str, robot_attributes: Dict[str, Any], parent_type: Opt def get_name(self) -> str: """Get name of the keyword suitable for ReportPortal.""" - assign = ', '.join(self.assign) - assignment = '{0} = '.format(assign) if self.assign else '' - arguments = ', '.join(self.args) - full_name = f'{assignment}{self.name} ({arguments})' + assign = ", ".join(self.assign) + assignment = "{0} = ".format(assign) if self.assign else "" + arguments = ", ".join(self.args) + full_name = f"{assignment}{self.name} ({arguments})" return full_name[:256] def get_type(self) -> str: """Get keyword type.""" - if self.keyword_type.lower() in ('setup', 'teardown'): - if self.parent_type.lower() == 'keyword': - return 'STEP' - if self.keyword_type.lower() == 'setup': - return 'BEFORE_{0}'.format(self.parent_type.upper()) - if self.keyword_type.lower() == 'teardown': - return 'AFTER_{0}'.format(self.parent_type.upper()) + if self.keyword_type.lower() in ("setup", "teardown"): + if self.parent_type.lower() == "keyword": + return "STEP" + if self.keyword_type.lower() == "setup": + return "BEFORE_{0}".format(self.parent_type.upper()) + if self.keyword_type.lower() == "teardown": + return "AFTER_{0}".format(self.parent_type.upper()) else: - return 'STEP' + return "STEP" - def update(self, attributes: Dict[str, Any]) -> 'Keyword': + def update(self, attributes: Dict[str, Any]) -> "Keyword": """Update keyword attributes on keyword finish. :param attributes: Suite attributes passed through the listener """ - self.end_time = attributes.get('endtime', '') - self.status = attributes.get('status') + self.end_time = attributes.get("endtime", "") + self.status = attributes.get("status") return self @@ -144,7 +144,7 @@ class Suite: suites: List[str] tests: List[str] total_tests: int - type: str = 'SUITE' + type: str = "SUITE" skipped_keywords: List[Keyword] remove_data: bool @@ -155,22 +155,22 @@ def __init__(self, name: str, robot_attributes: Dict[str, Any]): :param robot_attributes: Suite attributes passed through the listener """ self.robot_attributes = robot_attributes - self.doc = robot_markup_to_markdown(robot_attributes['doc']) - self.end_time = robot_attributes.get('endtime', '') - self.longname = robot_attributes['longname'] - self.message = robot_attributes.get('message') - self.metadata = robot_attributes['metadata'] + self.doc = robot_markup_to_markdown(robot_attributes["doc"]) + self.end_time = robot_attributes.get("endtime", "") + self.longname = robot_attributes["longname"] + self.message = robot_attributes.get("message") + self.metadata = robot_attributes["metadata"] self.name = name - self.robot_id = robot_attributes['id'] + self.robot_id = robot_attributes["id"] self.rp_item_id = None self.rp_parent_item_id = None - self.start_time = robot_attributes.get('starttime') - self.statistics = robot_attributes.get('statistics') - self.status = robot_attributes.get('status') - self.suites = robot_attributes['suites'] - self.tests = robot_attributes['tests'] - self.total_tests = robot_attributes['totaltests'] - self.type = 'SUITE' + self.start_time = robot_attributes.get("starttime") + self.statistics = robot_attributes.get("statistics") + self.status = robot_attributes.get("status") + self.suites = robot_attributes["suites"] + self.tests = robot_attributes["tests"] + self.total_tests = robot_attributes["totaltests"] + self.type = "SUITE" self.skipped_keywords = [] self.remove_data = False @@ -179,23 +179,23 @@ def attributes(self) -> Optional[List[Dict[str, str]]]: """Get Suite attributes.""" if self.metadata is None or not self.metadata: return None - return [{'key': key, 'value': value} for key, value in self.metadata.items()] + return [{"key": key, "value": value} for key, value in self.metadata.items()] @property def source(self) -> str: """Return the test case source file path.""" - if self.robot_attributes.get('source') is not None: - return os.path.relpath(self.robot_attributes['source'], os.getcwd()) + if self.robot_attributes.get("source") is not None: + return os.path.relpath(self.robot_attributes["source"], os.getcwd()) - def update(self, attributes: Dict[str, Any]) -> 'Suite': + def update(self, attributes: Dict[str, Any]) -> "Suite": """Update suite attributes on suite finish. :param attributes: Suite attributes passed through the listener """ - self.end_time = attributes.get('endtime', '') - self.message = attributes.get('message') - self.status = attributes.get('status') - self.statistics = attributes.get('statistics') + self.end_time = attributes.get("endtime", "") + self.message = attributes.get("message") + self.status = attributes.get("status") + self.statistics = attributes.get("statistics") return self @@ -203,7 +203,7 @@ class Launch(Suite): """Class represents Robot Framework test suite.""" launch_attributes: Optional[List[Dict[str, str]]] - type: str = 'LAUNCH' + type: str = "LAUNCH" def __init__(self, name: str, robot_attributes: Dict[str, Any], launch_attributes: Optional[List[str]]): """Initialize required attributes. @@ -214,7 +214,7 @@ def __init__(self, name: str, robot_attributes: Dict[str, Any], launch_attribute """ super().__init__(name, robot_attributes) self.launch_attributes = gen_attributes(launch_attributes or []) - self.type = 'LAUNCH' + self.type = "LAUNCH" @property def attributes(self) -> Optional[List[Dict[str, str]]]: @@ -240,7 +240,7 @@ class Test: start_time: str status: str template: str - type: str = 'TEST' + type: str = "TEST" skipped_keywords: List[Keyword] remove_data: bool @@ -252,29 +252,29 @@ def __init__(self, name: str, robot_attributes: Dict[str, Any], test_attributes: """ # for backward compatibility with Robot < 4.0 mark every test case # as critical if not set - self._critical = robot_attributes.get('critical', 'yes') - self._tags = robot_attributes['tags'] + self._critical = robot_attributes.get("critical", "yes") + self._tags = robot_attributes["tags"] self.test_attributes = gen_attributes(test_attributes) self.robot_attributes = robot_attributes - self.doc = robot_markup_to_markdown(robot_attributes['doc']) - self.end_time = robot_attributes.get('endtime', '') - self.longname = robot_attributes['longname'] - self.message = robot_attributes.get('message') + self.doc = robot_markup_to_markdown(robot_attributes["doc"]) + self.end_time = robot_attributes.get("endtime", "") + self.longname = robot_attributes["longname"] + self.message = robot_attributes.get("message") self.name = name - self.robot_id = robot_attributes['id'] + self.robot_id = robot_attributes["id"] self.rp_item_id = None self.rp_parent_item_id = None - self.start_time = robot_attributes['starttime'] - self.status = robot_attributes.get('status') - self.template = robot_attributes['template'] - self.type = 'TEST' + self.start_time = robot_attributes["starttime"] + self.status = robot_attributes.get("status") + self.template = robot_attributes["template"] + self.type = "TEST" self.skipped_keywords = [] self.remove_data = False @property def critical(self) -> bool: """Form unique value for RF 4.0+ and older versions.""" - return self._critical in ('yes', True) + return self._critical in ("yes", True) @property def tags(self) -> List[str]: @@ -289,8 +289,8 @@ def attributes(self) -> Optional[List[Dict[str, str]]]: @property def source(self) -> str: """Return the test case source file path.""" - if self.robot_attributes['source'] is not None: - return os.path.relpath(self.robot_attributes['source'], os.getcwd()) + if self.robot_attributes["source"] is not None: + return os.path.relpath(self.robot_attributes["source"], os.getcwd()) @property def code_ref(self) -> str: @@ -300,8 +300,8 @@ def code_ref(self) -> str: """ line_number = self.robot_attributes.get("lineno") if line_number is not None: - return '{0}:{1}'.format(self.source, line_number) - return '{0}:{1}'.format(self.source, self.name) + return "{0}:{1}".format(self.source, line_number) + return "{0}:{1}".format(self.source, self.name) @property def test_case_id(self) -> Optional[str]: @@ -309,17 +309,17 @@ def test_case_id(self) -> Optional[str]: # use test case id from tags if specified for tag in self._tags: if tag.startswith(TEST_CASE_ID_SIGN): - return tag.split(':')[1] + return tag.split(":")[1] # generate it if not - return '{0}:{1}'.format(self.source, self.name) + return "{0}:{1}".format(self.source, self.name) - def update(self, attributes: Dict[str, Any]) -> 'Test': + def update(self, attributes: Dict[str, Any]) -> "Test": """Update test attributes on test finish. :param attributes: Suite attributes passed through the listener """ - self._tags = attributes.get('tags', self._tags) - self.end_time = attributes.get('endtime', '') - self.message = attributes.get('message') - self.status = attributes.get('status') + self._tags = attributes.get("tags", self._tags) + self.end_time = attributes.get("endtime", "") + self.message = attributes.get("message") + self.status = attributes.get("status") return self diff --git a/robotframework_reportportal/post_report.py b/robotframework_reportportal/post_report.py index 6176bb3..9f74c38 100644 --- a/robotframework_reportportal/post_report.py +++ b/robotframework_reportportal/post_report.py @@ -50,6 +50,7 @@ from robotframework_reportportal.result_visitor import RobotResultsVisitor from robotframework_reportportal.time_visitor import TimeVisitor, corrections + # noinspection PyUnresolvedReferences from robotframework_reportportal.variables import _variables @@ -58,16 +59,18 @@ def process(infile="output.xml"): test_run = ExecutionResult(infile) test_run.visit(TimeVisitor()) if corrections: - logging.warning("{0} is missing some of its starttime/endtime. " - "This might cause inconsistencies with your " - "duration report.".format(infile)) + logging.warning( + "{0} is missing some of its starttime/endtime. " + "This might cause inconsistencies with your " + "duration report.".format(infile) + ) test_run.visit(RobotResultsVisitor()) def main(): argument_list = sys.argv[1:] short_options = "hv:" - long_options = ["help", "variable=", "loglevel=", 'timezone='] + long_options = ["help", "variable=", "loglevel=", "timezone="] try: arguments, values = getopt.getopt(argument_list, short_options, long_options) except getopt.error: @@ -84,7 +87,7 @@ def main(): numeric_level = getattr(logging, current_value.upper(), None) logging.basicConfig(level=numeric_level) elif current_argument == "--timezone": - _variables['RP_TIME_ZONE_OFFSET'] = current_value + _variables["RP_TIME_ZONE_OFFSET"] = current_value try: process(*values) diff --git a/robotframework_reportportal/result_visitor.py b/robotframework_reportportal/result_visitor.py index 6dd010f..20e6421 100644 --- a/robotframework_reportportal/result_visitor.py +++ b/robotframework_reportportal/result_visitor.py @@ -26,6 +26,7 @@ from robotframework_reportportal import listener from robotframework_reportportal.time_visitor import corrections + # noinspection PyUnresolvedReferences from robotframework_reportportal.variables import _variables @@ -40,22 +41,22 @@ def to_timestamp(time_str: str) -> Optional[str]: if not time_str: return None - timezone_offset_str: Optional[str] = _variables.get('RP_TIME_ZONE_OFFSET', None) - dt = datetime.strptime(time_str, '%Y%m%d %H:%M:%S.%f') + timezone_offset_str: Optional[str] = _variables.get("RP_TIME_ZONE_OFFSET", None) + dt = datetime.strptime(time_str, "%Y%m%d %H:%M:%S.%f") if timezone_offset_str: if timezone_offset_str in AVAILABLE_TIMEZONES: tz = ZoneInfo(timezone_offset_str) dt = dt.replace(tzinfo=tz) else: - hours, minutes = map(int, timezone_offset_str.split(':')) + hours, minutes = map(int, timezone_offset_str.split(":")) offset = timedelta(hours=hours, minutes=minutes) dt = dt.replace(tzinfo=timezone(offset)) return str(int(dt.timestamp() * 1000)) class RobotResultsVisitor(ResultVisitor): - _link_pattern: Pattern = re.compile("src=[\"\']([^\"\']+)[\"\']") + _link_pattern: Pattern = re.compile("src=[\"']([^\"']+)[\"']") def start_result(self, result: Result) -> bool: if "RP_LAUNCH" not in _variables: @@ -67,15 +68,15 @@ def start_result(self, result: Result) -> bool: def start_suite(self, suite: TestSuite) -> bool: ts = to_timestamp(suite.starttime if suite.id not in corrections else corrections[suite.id][0]) attrs = { - 'id': suite.id, - 'longname': suite.longname, - 'doc': suite.doc, - 'metadata': suite.metadata, - 'source': suite.source, - 'suites': suite.suites, - 'tests': suite.tests, - 'totaltests': getattr(suite.statistics, 'all', suite.statistics).total, - 'starttime': ts + "id": suite.id, + "longname": suite.longname, + "doc": suite.doc, + "metadata": suite.metadata, + "source": suite.source, + "suites": suite.suites, + "tests": suite.tests, + "totaltests": getattr(suite.statistics, "all", suite.statistics).total, + "starttime": ts, } listener.start_suite(suite.name, attrs, ts) return True @@ -83,37 +84,37 @@ def start_suite(self, suite: TestSuite) -> bool: def end_suite(self, suite: TestSuite) -> None: ts = to_timestamp(suite.endtime if suite.id not in corrections else corrections[suite.id][1]) attrs = { - 'id': suite.id, - 'longname': suite.longname, - 'doc': suite.doc, - 'metadata': suite.metadata, - 'source': suite.source, - 'suites': suite.suites, - 'tests': suite.tests, - 'totaltests': getattr(suite.statistics, 'all', suite.statistics).total, - 'endtime': ts, - 'elapsedtime': suite.elapsedtime, - 'status': suite.status, - 'statistics': suite.statistics, - 'message': suite.message, + "id": suite.id, + "longname": suite.longname, + "doc": suite.doc, + "metadata": suite.metadata, + "source": suite.source, + "suites": suite.suites, + "tests": suite.tests, + "totaltests": getattr(suite.statistics, "all", suite.statistics).total, + "endtime": ts, + "elapsedtime": suite.elapsedtime, + "status": suite.status, + "statistics": suite.statistics, + "message": suite.message, } listener.end_suite(None, attrs, ts) def start_test(self, test: TestCase) -> bool: ts = to_timestamp(test.starttime if test.id not in corrections else corrections[test.id][0]) attrs = { - 'id': test.id, - 'longname': test.longname, + "id": test.id, + "longname": test.longname, # 'originalname': test.originalname, - 'doc': test.doc, - 'tags': list(test.tags), + "doc": test.doc, + "tags": list(test.tags), # for backward compatibility with Robot < 4.0 mark every test case # as critical if not set - 'critical': getattr(test, 'critical', 'yes'), - 'source': test.source, - 'template': '', + "critical": getattr(test, "critical", "yes"), + "source": test.source, + "template": "", # 'lineno': test.lineno, - 'starttime': ts, + "starttime": ts, } listener.start_test(test.name, attrs, ts) return True @@ -121,35 +122,35 @@ def start_test(self, test: TestCase) -> bool: def end_test(self, test: TestCase) -> None: ts = to_timestamp(test.endtime if test.id not in corrections else corrections[test.id][1]) attrs = { - 'id': test.id, - 'longname': test.longname, + "id": test.id, + "longname": test.longname, # 'originalname': test.originalname, - 'doc': test.doc, - 'tags': list(test.tags), + "doc": test.doc, + "tags": list(test.tags), # for backward compatibility with Robot < 4.0 mark every test case # as critical if not set - 'critical': getattr(test, 'critical', 'yes'), - 'template': '', + "critical": getattr(test, "critical", "yes"), + "template": "", # 'lineno': test.lineno, - 'endtime': ts, - 'elapsedtime': test.elapsedtime, - 'source': test.source, - 'status': test.status, - 'message': test.message, + "endtime": ts, + "elapsedtime": test.elapsedtime, + "source": test.source, + "status": test.status, + "message": test.message, } listener.end_test(test.name, attrs, ts) def start_keyword(self, kw: Keyword) -> bool: ts = to_timestamp(kw.starttime if kw.id not in corrections else corrections[kw.id][0]) attrs = { - 'type': string.capwords(kw.type), - 'kwname': kw.kwname, - 'libname': kw.libname, - 'doc': kw.doc, - 'args': kw.args, - 'assign': kw.assign, - 'tags': kw.tags, - 'starttime': ts, + "type": string.capwords(kw.type), + "kwname": kw.kwname, + "libname": kw.libname, + "doc": kw.doc, + "args": kw.args, + "assign": kw.assign, + "tags": kw.tags, + "starttime": ts, } listener.start_keyword(kw.name, attrs, ts) return True @@ -157,27 +158,27 @@ def start_keyword(self, kw: Keyword) -> bool: def end_keyword(self, kw: Keyword) -> None: ts = to_timestamp(kw.endtime if kw.id not in corrections else corrections[kw.id][1]) attrs = { - 'type': string.capwords(kw.type), - 'kwname': kw.kwname, - 'libname': kw.libname, - 'doc': kw.doc, - 'args': kw.args, - 'assign': kw.assign, - 'tags': kw.tags, - 'endtime': ts, - 'elapsedtime': kw.elapsedtime, - 'status': 'PASS' if kw.assign else kw.status, + "type": string.capwords(kw.type), + "kwname": kw.kwname, + "libname": kw.libname, + "doc": kw.doc, + "args": kw.args, + "assign": kw.assign, + "tags": kw.tags, + "endtime": ts, + "elapsedtime": kw.elapsedtime, + "status": "PASS" if kw.assign else kw.status, } listener.end_keyword(kw.name, attrs, ts) def start_message(self, msg: Message) -> bool: if msg.message: message = { - 'message': msg.message, - 'level': msg.level, + "message": msg.message, + "level": msg.level, } try: - m = self.parse_message(message['message']) + m = self.parse_message(message["message"]) message["message"] = m[0] listener.log_message_with_image(message, m[1]) except (AttributeError, IOError): diff --git a/robotframework_reportportal/service.py b/robotframework_reportportal/service.py index 2e46ee0..3b7f97c 100644 --- a/robotframework_reportportal/service.py +++ b/robotframework_reportportal/service.py @@ -19,12 +19,7 @@ from dateutil.parser import parse from reportportal_client import RP, create_client -from reportportal_client.helpers import ( - dict_to_payload, - get_launch_sys_attrs, - get_package_version, - timestamp -) +from reportportal_client.helpers import dict_to_payload, get_launch_sys_attrs, get_package_version, timestamp from robotframework_reportportal.model import Launch, Suite, Test, Keyword, LogMessage from robotframework_reportportal.static import LOG_LEVEL_MAPPING, STATUS_MAPPING @@ -32,7 +27,7 @@ logger = logging.getLogger(__name__) -TOP_LEVEL_ITEMS = {'BEFORE_SUITE', 'AFTER_SUITE'} +TOP_LEVEL_ITEMS = {"BEFORE_SUITE", "AFTER_SUITE"} def to_epoch(date: Optional[str]) -> Optional[str]: @@ -43,10 +38,10 @@ def to_epoch(date: Optional[str]) -> Optional[str]: parsed_date = parse(date) except ValueError: return None - if hasattr(parsed_date, 'timestamp'): + if hasattr(parsed_date, "timestamp"): epoch_time = parsed_date.timestamp() else: - epoch_time = float(parsed_date.strftime('%s')) + parsed_date.microsecond / 1e6 + epoch_time = float(parsed_date.strftime("%s")) + parsed_date.microsecond / 1e6 return str(int(epoch_time * 1000)) @@ -59,7 +54,7 @@ class RobotService: def __init__(self) -> None: """Initialize service attributes.""" - self.agent_name = 'robotframework-reportportal' + self.agent_name = "robotframework-reportportal" self.agent_version = get_package_version(self.agent_name) self.rp = None @@ -70,8 +65,7 @@ def _get_launch_attributes(self, cmd_attrs: list) -> list: """ attributes = cmd_attrs or [] system_attributes = get_launch_sys_attrs() - system_attributes['agent'] = ( - '{}|{}'.format(self.agent_name, self.agent_version)) + system_attributes["agent"] = "{}|{}".format(self.agent_name, self.agent_version) return attributes + dict_to_payload(system_attributes) def init_service(self, variables: Variables) -> None: @@ -80,8 +74,10 @@ def init_service(self, variables: Variables) -> None: :param variables: ReportPortal variables """ if self.rp is None: - logger.debug(f'ReportPortal - Init service: endpoint={variables.endpoint}, ' - f'project={variables.project}, api_key={variables.api_key}') + logger.debug( + f"ReportPortal - Init service: endpoint={variables.endpoint}, " + f"project={variables.project}, api_key={variables.api_key}" + ) self.rp = create_client( client_type=variables.client_type, @@ -97,7 +93,7 @@ def init_service(self, variables: Variables) -> None: launch_uuid=variables.launch_id, launch_uuid_print=variables.launch_uuid_print, print_output=variables.launch_uuid_print_output, - http_timeout=variables.http_timeout + http_timeout=variables.http_timeout, ) def terminate_service(self) -> None: @@ -105,9 +101,14 @@ def terminate_service(self) -> None: if self.rp: self.rp.close() - def start_launch(self, launch: Launch, mode: Optional[str] = None, rerun: bool = False, - rerun_of: Optional[str] = None, - ts: Optional[str] = None) -> Optional[str]: + def start_launch( + self, + launch: Launch, + mode: Optional[str] = None, + rerun: bool = False, + rerun_of: Optional[str] = None, + ts: Optional[str] = None, + ) -> Optional[str]: """Call start_launch method of the common client. :param launch: Instance of the Launch class @@ -119,15 +120,15 @@ def start_launch(self, launch: Launch, mode: Optional[str] = None, rerun: bool = :return: launch UUID """ sl_pt = { - 'attributes': self._get_launch_attributes(launch.attributes), - 'description': launch.doc, - 'name': launch.name, - 'mode': mode, - 'rerun': rerun, - 'rerun_of': rerun_of, - 'start_time': ts or to_epoch(launch.start_time) or timestamp() + "attributes": self._get_launch_attributes(launch.attributes), + "description": launch.doc, + "name": launch.name, + "mode": mode, + "rerun": rerun, + "rerun_of": rerun_of, + "start_time": ts or to_epoch(launch.start_time) or timestamp(), } - logger.debug('ReportPortal - Start launch: request_body={0}'.format(sl_pt)) + logger.debug("ReportPortal - Start launch: request_body={0}".format(sl_pt)) return self.rp.start_launch(**sl_pt) def finish_launch(self, launch: Launch, ts: Optional[str] = None) -> None: @@ -136,11 +137,8 @@ def finish_launch(self, launch: Launch, ts: Optional[str] = None) -> None: :param launch: Launch name :param ts: End time """ - fl_rq = { - 'end_time': ts or to_epoch(launch.end_time) or timestamp(), - 'status': STATUS_MAPPING[launch.status] - } - logger.debug('ReportPortal - Finish launch: request_body={0}'.format(fl_rq)) + fl_rq = {"end_time": ts or to_epoch(launch.end_time) or timestamp(), "status": STATUS_MAPPING[launch.status]} + logger.debug("ReportPortal - Finish launch: request_body={0}".format(fl_rq)) self.rp.finish_launch(**fl_rq) def start_suite(self, suite: Suite, ts: Optional[str] = None) -> Optional[str]: @@ -151,18 +149,17 @@ def start_suite(self, suite: Suite, ts: Optional[str] = None) -> Optional[str]: :return: Suite UUID """ start_rq = { - 'attributes': suite.attributes, - 'description': suite.doc, - 'item_type': suite.type, - 'name': suite.name, - 'parent_item_id': suite.rp_parent_item_id, - 'start_time': ts or to_epoch(suite.start_time) or timestamp() + "attributes": suite.attributes, + "description": suite.doc, + "item_type": suite.type, + "name": suite.name, + "parent_item_id": suite.rp_parent_item_id, + "start_time": ts or to_epoch(suite.start_time) or timestamp(), } - logger.debug('ReportPortal - Start suite: request_body={0}'.format(start_rq)) + logger.debug("ReportPortal - Start suite: request_body={0}".format(start_rq)) return self.rp.start_test_item(**start_rq) - def finish_suite(self, suite: Suite, issue: Optional[str] = None, - ts: Optional[str] = None) -> None: + def finish_suite(self, suite: Suite, issue: Optional[str] = None, ts: Optional[str] = None) -> None: """Finish started suite. :param suite: Instance of the started suite item @@ -170,12 +167,12 @@ def finish_suite(self, suite: Suite, issue: Optional[str] = None, :param ts: End time """ fta_rq = { - 'end_time': ts or to_epoch(suite.end_time) or timestamp(), - 'issue': issue, - 'item_id': suite.rp_item_id, - 'status': STATUS_MAPPING[suite.status] + "end_time": ts or to_epoch(suite.end_time) or timestamp(), + "issue": issue, + "item_id": suite.rp_item_id, + "status": STATUS_MAPPING[suite.status], } - logger.debug('ReportPortal - Finish suite: request_body={0}'.format(fta_rq)) + logger.debug("ReportPortal - Finish suite: request_body={0}".format(fta_rq)) self.rp.finish_test_item(**fta_rq) def start_test(self, test: Test, ts: Optional[str] = None): @@ -188,16 +185,16 @@ def start_test(self, test: Test, ts: Optional[str] = None): # Details at: # https://github.com/reportportal/agent-Python-RobotFramework/issues/56 start_rq = { - 'attributes': test.attributes, - 'code_ref': test.code_ref, - 'description': test.doc, - 'item_type': 'STEP', - 'name': test.name, - 'parent_item_id': test.rp_parent_item_id, - 'start_time': ts or to_epoch(test.start_time) or timestamp(), - 'test_case_id': test.test_case_id + "attributes": test.attributes, + "code_ref": test.code_ref, + "description": test.doc, + "item_type": "STEP", + "name": test.name, + "parent_item_id": test.rp_parent_item_id, + "start_time": ts or to_epoch(test.start_time) or timestamp(), + "test_case_id": test.test_case_id, } - logger.debug('ReportPortal - Start test: request_body={0}'.format(start_rq)) + logger.debug("ReportPortal - Start test: request_body={0}".format(start_rq)) return self.rp.start_test_item(**start_rq) def finish_test(self, test: Test, issue: Optional[str] = None, ts: Optional[str] = None): @@ -208,14 +205,14 @@ def finish_test(self, test: Test, issue: Optional[str] = None, ts: Optional[str] :param ts: End time """ fta_rq = { - 'attributes': test.attributes, - 'end_time': ts or to_epoch(test.end_time) or timestamp(), - 'issue': issue, - 'item_id': test.rp_item_id, - 'status': STATUS_MAPPING[test.status], - 'test_case_id': test.test_case_id + "attributes": test.attributes, + "end_time": ts or to_epoch(test.end_time) or timestamp(), + "issue": issue, + "item_id": test.rp_item_id, + "status": STATUS_MAPPING[test.status], + "test_case_id": test.test_case_id, } - logger.debug('ReportPortal - Finish test: request_body={0}'.format(fta_rq)) + logger.debug("ReportPortal - Finish test: request_body={0}".format(fta_rq)) self.rp.finish_test_item(**fta_rq) def start_keyword(self, keyword: Keyword, ts: Optional[str] = None): @@ -225,16 +222,16 @@ def start_keyword(self, keyword: Keyword, ts: Optional[str] = None): :param ts: Start time """ start_rq = { - 'description': keyword.doc, - 'has_stats': keyword.get_type() in TOP_LEVEL_ITEMS, - 'item_type': keyword.get_type(), - 'name': keyword.get_name(), - 'parent_item_id': keyword.rp_parent_item_id, - 'start_time': ts or to_epoch(keyword.start_time) or timestamp() + "description": keyword.doc, + "has_stats": keyword.get_type() in TOP_LEVEL_ITEMS, + "item_type": keyword.get_type(), + "name": keyword.get_name(), + "parent_item_id": keyword.rp_parent_item_id, + "start_time": ts or to_epoch(keyword.start_time) or timestamp(), } if keyword.rp_item_id: - start_rq['uuid'] = keyword.rp_item_id - logger.debug('ReportPortal - Start keyword: request_body={0}'.format(start_rq)) + start_rq["uuid"] = keyword.rp_item_id + logger.debug("ReportPortal - Start keyword: request_body={0}".format(start_rq)) return self.rp.start_test_item(**start_rq) def finish_keyword(self, keyword: Keyword, issue: Optional[str] = None, ts: Optional[str] = None): @@ -245,12 +242,12 @@ def finish_keyword(self, keyword: Keyword, issue: Optional[str] = None, ts: Opti :param ts: End time """ fta_rq = { - 'end_time': ts or to_epoch(keyword.end_time) or timestamp(), - 'issue': issue, - 'item_id': keyword.rp_item_id, - 'status': STATUS_MAPPING[keyword.status] + "end_time": ts or to_epoch(keyword.end_time) or timestamp(), + "issue": issue, + "item_id": keyword.rp_item_id, + "status": STATUS_MAPPING[keyword.status], } - logger.debug('ReportPortal - Finish keyword: request_body={0}'.format(fta_rq)) + logger.debug("ReportPortal - Finish keyword: request_body={0}".format(fta_rq)) self.rp.finish_test_item(**fta_rq) def log(self, message: LogMessage, ts: Optional[str] = None): @@ -260,10 +257,10 @@ def log(self, message: LogMessage, ts: Optional[str] = None): :param ts: Timestamp """ sl_rq = { - 'attachment': message.attachment, - 'item_id': message.item_id, - 'level': LOG_LEVEL_MAPPING.get(message.level, 'INFO'), - 'message': message.message, - 'time': ts or timestamp() + "attachment": message.attachment, + "item_id": message.item_id, + "level": LOG_LEVEL_MAPPING.get(message.level, "INFO"), + "message": message.message, + "time": ts or timestamp(), } self.rp.log(**sl_rq) diff --git a/robotframework_reportportal/static.py b/robotframework_reportportal/static.py index 05b578d..5d6d9af 100644 --- a/robotframework_reportportal/static.py +++ b/robotframework_reportportal/static.py @@ -17,21 +17,18 @@ from typing import Dict LOG_LEVEL_MAPPING: Dict[str, str] = { - 'INFO': 'INFO', - 'FAIL': 'ERROR', - 'TRACE': 'TRACE', - 'DEBUG': 'DEBUG', - 'HTML': 'INFO', - 'WARN': 'WARN', - 'ERROR': 'ERROR', - 'SKIP': 'INFO' -} -MAIN_SUITE_ID: str = 's1' -PABOT_WITHOUT_LAUNCH_ID_MSG: str = ('Pabot library is used but RP_LAUNCH_UUID was not provided. Please, ' - 'initialize listener with the RP_LAUNCH_UUID argument.') -STATUS_MAPPING: Dict[str, str] = { - 'PASS': 'PASSED', - 'FAIL': 'FAILED', - 'NOT RUN': 'SKIPPED', - 'SKIP': 'SKIPPED' + "INFO": "INFO", + "FAIL": "ERROR", + "TRACE": "TRACE", + "DEBUG": "DEBUG", + "HTML": "INFO", + "WARN": "WARN", + "ERROR": "ERROR", + "SKIP": "INFO", } +MAIN_SUITE_ID: str = "s1" +PABOT_WITHOUT_LAUNCH_ID_MSG: str = ( + "Pabot library is used but RP_LAUNCH_UUID was not provided. Please, " + "initialize listener with the RP_LAUNCH_UUID argument." +) +STATUS_MAPPING: Dict[str, str] = {"PASS": "PASSED", "FAIL": "FAILED", "NOT RUN": "SKIPPED", "SKIP": "SKIPPED"} diff --git a/robotframework_reportportal/time_visitor.py b/robotframework_reportportal/time_visitor.py index 15c9ab2..92103b1 100644 --- a/robotframework_reportportal/time_visitor.py +++ b/robotframework_reportportal/time_visitor.py @@ -31,14 +31,13 @@ def _correct_starts(o, node_class): if o.starttime: corrected = False for parent_id in _stack: - if corrections[parent_id][0] is None or \ - corrections[parent_id][0] > o.starttime: + if corrections[parent_id][0] is None or corrections[parent_id][0] > o.starttime: corrections[parent_id][0] = o.starttime corrected = True if corrected: logging.debug( - "Correcting parents' starttime to {0} based on {2}={1}" - .format(o.starttime, o.id, node_class)) + "Correcting parents' starttime to {0} based on {2}={1}".format(o.starttime, o.id, node_class) + ) else: _stack.append(o.id) corrections[o.id] = [None, None] @@ -52,14 +51,13 @@ def _correct_ends(o, node_class): if o.endtime: corrected = False for parent_id in _stack: - if corrections[parent_id][1] is None or \ - corrections[parent_id][1] < o.endtime: + if corrections[parent_id][1] is None or corrections[parent_id][1] < o.endtime: corrections[parent_id][1] = o.endtime corrected = True if corrected: logging.debug( - "Correcting parents' endtime to {0} based on {2}={1}" - .format(o.endtime, o.id, node_class)) + "Correcting parents' endtime to {0} based on {2}={1}".format(o.endtime, o.id, node_class) + ) if _stack and o.id == _stack[-1]: _stack.pop() diff --git a/robotframework_reportportal/variables.py b/robotframework_reportportal/variables.py index 488c101..eb61aba 100644 --- a/robotframework_reportportal/variables.py +++ b/robotframework_reportportal/variables.py @@ -70,37 +70,37 @@ class Variables: def __init__(self) -> None: """Initialize instance attributes.""" - self.endpoint = get_variable('RP_ENDPOINT') - self.launch_name = get_variable('RP_LAUNCH') - self.project = get_variable('RP_PROJECT') + self.endpoint = get_variable("RP_ENDPOINT") + self.launch_name = get_variable("RP_LAUNCH") + self.project = get_variable("RP_PROJECT") self._pabot_pool_id = None self._pabot_used = None - self.attach_log = to_bool(get_variable('RP_ATTACH_LOG', default='False')) - self.attach_report = to_bool(get_variable('RP_ATTACH_REPORT', default='False')) - self.attach_xunit = to_bool(get_variable('RP_ATTACH_XUNIT', default='False')) - self.launch_attributes = get_variable('RP_LAUNCH_ATTRIBUTES', default='').split() - self.launch_id = get_variable('RP_LAUNCH_UUID') - self.launch_doc = get_variable('RP_LAUNCH_DOC') - self.log_batch_size = int(get_variable( - 'RP_LOG_BATCH_SIZE', default='20')) - self.mode = get_variable('RP_MODE') - self.pool_size = int(get_variable('RP_MAX_POOL_SIZE', default='50')) - self.rerun = to_bool(get_variable('RP_RERUN', default='False')) - self.rerun_of = get_variable('RP_RERUN_OF', default=None) - self.skipped_issue = to_bool(get_variable('RP_SKIPPED_ISSUE', default='True')) - self.test_attributes = get_variable('RP_TEST_ATTRIBUTES', default='').split() - self.log_batch_payload_size = int(get_variable('RP_LOG_BATCH_PAYLOAD_SIZE', - default=str(MAX_LOG_BATCH_PAYLOAD_SIZE))) - self.launch_uuid_print = to_bool(get_variable('RP_LAUNCH_UUID_PRINT', default='False')) - output_type = get_variable('RP_LAUNCH_UUID_PRINT_OUTPUT') + self.attach_log = to_bool(get_variable("RP_ATTACH_LOG", default="False")) + self.attach_report = to_bool(get_variable("RP_ATTACH_REPORT", default="False")) + self.attach_xunit = to_bool(get_variable("RP_ATTACH_XUNIT", default="False")) + self.launch_attributes = get_variable("RP_LAUNCH_ATTRIBUTES", default="").split() + self.launch_id = get_variable("RP_LAUNCH_UUID") + self.launch_doc = get_variable("RP_LAUNCH_DOC") + self.log_batch_size = int(get_variable("RP_LOG_BATCH_SIZE", default="20")) + self.mode = get_variable("RP_MODE") + self.pool_size = int(get_variable("RP_MAX_POOL_SIZE", default="50")) + self.rerun = to_bool(get_variable("RP_RERUN", default="False")) + self.rerun_of = get_variable("RP_RERUN_OF", default=None) + self.skipped_issue = to_bool(get_variable("RP_SKIPPED_ISSUE", default="True")) + self.test_attributes = get_variable("RP_TEST_ATTRIBUTES", default="").split() + self.log_batch_payload_size = int( + get_variable("RP_LOG_BATCH_PAYLOAD_SIZE", default=str(MAX_LOG_BATCH_PAYLOAD_SIZE)) + ) + self.launch_uuid_print = to_bool(get_variable("RP_LAUNCH_UUID_PRINT", default="False")) + output_type = get_variable("RP_LAUNCH_UUID_PRINT_OUTPUT") self.launch_uuid_print_output = OutputType[output_type.upper()] if output_type else None - client_type = get_variable('RP_CLIENT_TYPE') + client_type = get_variable("RP_CLIENT_TYPE") self.client_type = ClientType[client_type.upper()] if client_type else ClientType.SYNC - connect_timeout = get_variable('RP_CONNECT_TIMEOUT') + connect_timeout = get_variable("RP_CONNECT_TIMEOUT") connect_timeout = float(connect_timeout) if connect_timeout else None - read_timeout = get_variable('RP_READ_TIMEOUT') + read_timeout = get_variable("RP_READ_TIMEOUT") read_timeout = float(read_timeout) if read_timeout else None if connect_timeout is None and read_timeout is None: @@ -110,34 +110,34 @@ def __init__(self) -> None: else: self.http_timeout = connect_timeout or read_timeout - self.api_key = get_variable('RP_API_KEY') + self.api_key = get_variable("RP_API_KEY") if not self.api_key: - token = get_variable('RP_UUID') + token = get_variable("RP_UUID") if token: warn( message="Argument `RP_UUID` is deprecated since version 5.3.3 and will be subject for " - "removing in the next major version. Use `RP_API_KEY` argument instead.", + "removing in the next major version. Use `RP_API_KEY` argument instead.", category=DeprecationWarning, - stacklevel=2 + stacklevel=2, ) self.api_key = token else: warn( message="Argument `RP_API_KEY` is `None` or empty string, that's not supposed to happen " - "because ReportPortal is usually requires an authorization key. Please check your" - " configuration.", + "because ReportPortal is usually requires an authorization key. Please check your" + " configuration.", category=RuntimeWarning, - stacklevel=2 + stacklevel=2, ) cond = (self.endpoint, self.launch_name, self.project, self.api_key) self.enabled = all(cond) if not self.enabled: warn( - 'One or required parameter is missing, ReportPortal listener will be disabled. ' - 'Please check agent documentation.', + "One or required parameter is missing, ReportPortal listener will be disabled. " + "Please check agent documentation.", RuntimeWarning, - 2 + 2, ) @property @@ -147,7 +147,7 @@ def pabot_pool_id(self) -> int: :return: Pool id for the current Robot Framework executor """ if not self._pabot_pool_id: - self._pabot_pool_id = get_variable(name='PABOTEXECUTIONPOOLID') + self._pabot_pool_id = get_variable(name="PABOTEXECUTIONPOOLID") return self._pabot_pool_id @property @@ -157,13 +157,13 @@ def pabot_used(self) -> Optional[str]: :return: Cached value of the Pabotlib URI """ if not self._pabot_used: - self._pabot_used = get_variable(name='PABOTLIBURI') + self._pabot_used = get_variable(name="PABOTLIBURI") return self._pabot_used @property def verify_ssl(self) -> Union[bool, str]: """Get value of the verify_ssl parameter for the client.""" - verify_ssl = get_variable('RP_VERIFY_SSL', default='True') + verify_ssl = get_variable("RP_VERIFY_SSL", default="True") if path.exists(verify_ssl): return verify_ssl return to_bool(verify_ssl) diff --git a/setup.py b/setup.py index 625f935..3ac0479 100644 --- a/setup.py +++ b/setup.py @@ -15,10 +15,10 @@ """Setup instructions for the package.""" import os -from setuptools import setup +from setuptools import setup -__version__ = '5.6.0' +__version__ = "5.6.0" def read_file(fname): @@ -32,34 +32,29 @@ def read_file(fname): setup( - name='robotframework-reportportal', - packages=['robotframework_reportportal'], - package_data={'robotframework_reportportal': ['*.pyi']}, + name="robotframework-reportportal", + packages=["robotframework_reportportal"], + package_data={"robotframework_reportportal": ["*.pyi"]}, version=__version__, - description='Agent for reporting RobotFramework test results to ReportPortal', - long_description=read_file('README.md'), - long_description_content_type='text/markdown', - author='ReportPortal Team', - author_email='support@reportportal.io', - url='https://github.com/reportportal/agent-Python-RobotFramework', + description="Agent for reporting RobotFramework test results to ReportPortal", + long_description=read_file("README.md"), + long_description_content_type="text/markdown", + author="ReportPortal Team", + author_email="support@reportportal.io", + url="https://github.com/reportportal/agent-Python-RobotFramework", download_url=( - 'https://github.com/reportportal/agent-Python-RobotFramework/' - 'tarball/{version}'.format(version=__version__)), - keywords=['testing', 'reporting', 'robot framework', 'reportportal', - 'agent'], + "https://github.com/reportportal/agent-Python-RobotFramework/" "tarball/{version}".format(version=__version__) + ), + keywords=["testing", "reporting", "robot framework", "reportportal", "agent"], classifiers=[ - 'Framework :: Robot Framework', - 'Programming Language :: Python :: 3.8', - 'Programming Language :: Python :: 3.9', - 'Programming Language :: Python :: 3.10', - 'Programming Language :: Python :: 3.11', - 'Programming Language :: Python :: 3.12', - 'Programming Language :: Python :: 3.13', - ], - install_requires=read_file('requirements.txt').splitlines(), - entry_points={ - 'console_scripts': [ - 'post_report=robotframework_reportportal.post_report:main' - ] - }, + "Framework :: Robot Framework", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + ], + install_requires=read_file("requirements.txt").splitlines(), + entry_points={"console_scripts": ["post_report=robotframework_reportportal.post_report:main"]}, ) diff --git a/tests/__init__.py b/tests/__init__.py index 37c9923..ea257eb 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -14,5 +14,5 @@ limitations under the License """ -REPORT_PORTAL_SERVICE = 'reportportal_client.RPClient' -REQUESTS_SERVICE = 'reportportal_client.client.requests.Session' +REPORT_PORTAL_SERVICE = "reportportal_client.RPClient" +REQUESTS_SERVICE = "reportportal_client.client.requests.Session" diff --git a/tests/helpers/utils.py b/tests/helpers/utils.py index 8fdfe21..7ac2d20 100644 --- a/tests/helpers/utils.py +++ b/tests/helpers/utils.py @@ -16,24 +16,26 @@ import random import time -from typing import List, Optional, Dict, Any, Tuple +from typing import Any, Dict, List, Optional, Tuple from robot.run import RobotFramework DEFAULT_VARIABLES: Dict[str, Any] = { - 'RP_LAUNCH': 'Robot Framework', - 'RP_ENDPOINT': 'http://localhost:8080', - 'RP_PROJECT': 'default_personal', - 'RP_API_KEY': 'test_api_key', - 'RP_ATTACH_REPORT': False + "RP_LAUNCH": "Robot Framework", + "RP_ENDPOINT": "http://localhost:8080", + "RP_PROJECT": "default_personal", + "RP_API_KEY": "test_api_key", + "RP_ATTACH_REPORT": False, } -def run_robot_tests(tests: List[str], - listener: str = 'robotframework_reportportal.listener', - variables: Optional[Dict[str, Any]] = None, - arguments: Optional[Dict[str, Any]] = None) -> int: - cmd_arguments = ['--listener', listener] +def run_robot_tests( + tests: List[str], + listener: str = "robotframework_reportportal.listener", + variables: Optional[Dict[str, Any]] = None, + arguments: Optional[Dict[str, Any]] = None, +) -> int: + cmd_arguments = ["--listener", listener] if arguments: for k, v in arguments.items(): cmd_arguments.append(k) @@ -43,10 +45,10 @@ def run_robot_tests(tests: List[str], variables = DEFAULT_VARIABLES for k, v in variables.items(): - cmd_arguments.append('--variable') + cmd_arguments.append("--variable") if type(v) is not str: v = str(v) - cmd_arguments.append(k + ':' + v) + cmd_arguments.append(k + ":" + v) for t in tests: cmd_arguments.append(t) @@ -55,14 +57,12 @@ def run_robot_tests(tests: List[str], def get_launch_log_calls(mock) -> List[Tuple[List[Any], Dict[str, Any]]]: - return [e for e in mock.log.call_args_list - if 'item_id' in e[1] and e[1]['item_id'] is None] + return [e for e in mock.log.call_args_list if "item_id" in e[1] and e[1]["item_id"] is None] def get_log_calls(mock) -> List[Tuple[List[Any], Dict[str, Any]]]: - return [e for e in mock.log.call_args_list if 'item_id' in e[1] and e[1]['item_id']] + return [e for e in mock.log.call_args_list if "item_id" in e[1] and e[1]["item_id"]] def item_id_gen(**kwargs) -> str: - return "{}-{}-{}".format(kwargs['name'], str(round(time.time() * 1000)), - random.randint(0, 9999)) + return "{}-{}-{}".format(kwargs["name"], str(round(time.time() * 1000)), random.randint(0, 9999)) diff --git a/tests/integration/conftest.py b/tests/integration/conftest.py index 4fc1b08..a2ce7da 100644 --- a/tests/integration/conftest.py +++ b/tests/integration/conftest.py @@ -16,34 +16,37 @@ import sys -KEYWORDS_EXPECTED_TEST_NAMES = ['Invalid Password'] -KEYWORDS_EXPECTED_CODE_REF_SUFFIXES = ['6'] * 6 +KEYWORDS_EXPECTED_TEST_NAMES = ["Invalid Password"] +KEYWORDS_EXPECTED_CODE_REF_SUFFIXES = ["6"] * 6 -SETTINGS_EXPECTED_TEST_NAMES = ['Invalid User Name', 'Invalid Password', - 'Invalid User Name and Password', - 'Empty User Name', - 'Empty Password', - 'Empty User Name and Password'] +SETTINGS_EXPECTED_TEST_NAMES = [ + "Invalid User Name", + "Invalid Password", + "Invalid User Name and Password", + "Empty User Name", + "Empty Password", + "Empty User Name and Password", +] SETTINGS_EXPECTED_CODE_REF_SUFFIXES = [str(x) for x in range(7, 14)] -DATADRIVER_EXPECTED_TEST_NAMES = \ - ['Login with user \'invalid\' and password \'Password\'', - 'Login with user \'User\' and password \'invalid\'', - 'Login with user \'invalid\' and password \'invalid\'', - 'Login with user \'\' and password \'Password\'', - 'Login with user \'User\' and password \'\'', - 'Login with user \'\' and password \'\''] -DATADRIVER_EXPECTED_CODE_REF_SUFFIXES = ['8'] * 6 +DATADRIVER_EXPECTED_TEST_NAMES = [ + "Login with user 'invalid' and password 'Password'", + "Login with user 'User' and password 'invalid'", + "Login with user 'invalid' and password 'invalid'", + "Login with user '' and password 'Password'", + "Login with user 'User' and password ''", + "Login with user '' and password ''", +] +DATADRIVER_EXPECTED_CODE_REF_SUFFIXES = ["8"] * 6 def pytest_generate_tests(metafunc): - if metafunc.function.__name__ == 'test_code_reference_template': - func_options = 'test,test_names,code_ref_suffixes' + if metafunc.function.__name__ == "test_code_reference_template": + func_options = "test,test_names,code_ref_suffixes" option_args = [ - ('examples/templates/keyword.robot', KEYWORDS_EXPECTED_TEST_NAMES, - KEYWORDS_EXPECTED_CODE_REF_SUFFIXES), - ('examples/templates/settings.robot', SETTINGS_EXPECTED_TEST_NAMES, - SETTINGS_EXPECTED_CODE_REF_SUFFIXES)] + ("examples/templates/keyword.robot", KEYWORDS_EXPECTED_TEST_NAMES, KEYWORDS_EXPECTED_CODE_REF_SUFFIXES), + ("examples/templates/settings.robot", SETTINGS_EXPECTED_TEST_NAMES, SETTINGS_EXPECTED_CODE_REF_SUFFIXES), + ] if sys.version_info >= (3, 6): pass # TODO: Uncomment as soon as DataDriver fix its compatibility with diff --git a/tests/integration/test_attachments.py b/tests/integration/test_attachments.py index 9e07d3f..1a72bee 100644 --- a/tests/integration/test_attachments.py +++ b/tests/integration/test_attachments.py @@ -26,47 +26,42 @@ def verify_attachment(mock_client_init, result, message, name, content_type): assert len(calls) == 1 call_params = calls[0][1] - assert 'attachment' in call_params.keys(), 'log entry does not contain attachment' - assert 'level' in call_params.keys(), 'log entry does not contain level' - assert 'message' in call_params.keys(), 'log entry does not contain message' + assert "attachment" in call_params.keys(), "log entry does not contain attachment" + assert "level" in call_params.keys(), "log entry does not contain level" + assert "message" in call_params.keys(), "log entry does not contain message" - assert call_params['level'] == 'INFO' - assert call_params['message'] == message - assert call_params['attachment']['name'] == name - assert call_params['attachment']['mime'] == content_type - assert len(call_params['attachment']['data']) > 0 + assert call_params["level"] == "INFO" + assert call_params["message"] == message + assert call_params["attachment"]["name"] == name + assert call_params["attachment"]["mime"] == content_type + assert len(call_params["attachment"]["data"]) > 0 @mock.patch(REPORT_PORTAL_SERVICE) def test_agent_attaches_report(mock_client_init): variables = utils.DEFAULT_VARIABLES.copy() - variables['RP_ATTACH_REPORT'] = True - result = utils.run_robot_tests(['examples/templates/settings.robot'], - variables=variables) - verify_attachment(mock_client_init, result, 'Execution report', - 'report.html', 'text/html') + variables["RP_ATTACH_REPORT"] = True + result = utils.run_robot_tests(["examples/templates/settings.robot"], variables=variables) + verify_attachment(mock_client_init, result, "Execution report", "report.html", "text/html") @mock.patch(REPORT_PORTAL_SERVICE) def test_agent_attaches_log(mock_client_init): variables = utils.DEFAULT_VARIABLES.copy() - variables['RP_ATTACH_LOG'] = True - result = utils.run_robot_tests(['examples/templates/settings.robot'], - variables=variables) - verify_attachment(mock_client_init, result, 'Execution log', - 'log.html', 'text/html') + variables["RP_ATTACH_LOG"] = True + result = utils.run_robot_tests(["examples/templates/settings.robot"], variables=variables) + verify_attachment(mock_client_init, result, "Execution log", "log.html", "text/html") assert result == 0 # the test successfully passed -XUNIT_FILE_NAME = 'xunit.xml' +XUNIT_FILE_NAME = "xunit.xml" @mock.patch(REPORT_PORTAL_SERVICE) def test_agent_attaches_xunit(mock_client_init): variables = utils.DEFAULT_VARIABLES.copy() - variables['RP_ATTACH_XUNIT'] = True - result = utils.run_robot_tests(['examples/templates/settings.robot'], - variables=variables, - arguments={'-x': XUNIT_FILE_NAME}) - verify_attachment(mock_client_init, result, 'XUnit result file', - XUNIT_FILE_NAME, 'application/xml') + variables["RP_ATTACH_XUNIT"] = True + result = utils.run_robot_tests( + ["examples/templates/settings.robot"], variables=variables, arguments={"-x": XUNIT_FILE_NAME} + ) + verify_attachment(mock_client_init, result, "XUnit result file", XUNIT_FILE_NAME, "application/xml") diff --git a/tests/integration/test_before_after.py b/tests/integration/test_before_after.py index c6dcfc1..af6622c 100644 --- a/tests/integration/test_before_after.py +++ b/tests/integration/test_before_after.py @@ -12,23 +12,23 @@ # See the License for the specific language governing permissions and # limitations under the License. -import pytest from unittest import mock +import pytest + from tests import REPORT_PORTAL_SERVICE from tests.helpers import utils @pytest.mark.parametrize( - 'test, idx_to_check, step_name, suite_name', [ - ('examples/before_after/before_suite_with_steps.robot', 1, - 'Log suite setup', 'Before Suite With Steps'), - ('examples/before_after/after_suite_with_steps.robot', 3, - 'Log suite tear down', 'After Suite With Steps') - ]) + "test, idx_to_check, step_name, suite_name", + [ + ("examples/before_after/before_suite_with_steps.robot", 1, "Log suite setup", "Before Suite With Steps"), + ("examples/before_after/after_suite_with_steps.robot", 3, "Log suite tear down", "After Suite With Steps"), + ], +) @mock.patch(REPORT_PORTAL_SERVICE) -def test_before_after_suite_with_steps(mock_client_init, test, idx_to_check, - step_name, suite_name): +def test_before_after_suite_with_steps(mock_client_init, test, idx_to_check, step_name, suite_name): mock_client = mock_client_init.return_value mock_client.start_test_item.side_effect = utils.item_id_gen @@ -43,10 +43,10 @@ def test_before_after_suite_with_steps(mock_client_init, test, idx_to_check, item_finish_calls = mock_client.finish_test_item.call_args_list assert len(item_start_calls) == len(item_finish_calls) == 5 - statuses = [finish[1]['status'] for finish in item_finish_calls] - assert statuses == ['PASSED'] * 5 + statuses = [finish[1]["status"] for finish in item_finish_calls] + assert statuses == ["PASSED"] * 5 before_suite_start = item_start_calls[idx_to_check][1] - assert before_suite_start['name'].startswith(step_name) - assert before_suite_start['has_stats'] - assert before_suite_start['parent_item_id'].startswith(suite_name) + assert before_suite_start["name"].startswith(step_name) + assert before_suite_start["has_stats"] + assert before_suite_start["parent_item_id"].startswith(suite_name) diff --git a/tests/integration/test_case_id.py b/tests/integration/test_case_id.py index 160fe98..8318966 100644 --- a/tests/integration/test_case_id.py +++ b/tests/integration/test_case_id.py @@ -17,7 +17,7 @@ from tests import REPORT_PORTAL_SERVICE from tests.helpers import utils -SIMPLE_TEST = 'examples/simple.robot' +SIMPLE_TEST = "examples/simple.robot" @mock.patch(REPORT_PORTAL_SERVICE) @@ -28,10 +28,10 @@ def test_case_id_simple(mock_client_init): mock_client = mock_client_init.return_value item_start_calls = mock_client.start_test_item.call_args_list test_item = item_start_calls[-2] - assert test_item[1]['test_case_id'] == SIMPLE_TEST + ':Simple test' + assert test_item[1]["test_case_id"] == SIMPLE_TEST + ":Simple test" -CUSTOM_TEST_CASE_ID = 'examples/custom_test_case_id.robot' +CUSTOM_TEST_CASE_ID = "examples/custom_test_case_id.robot" @mock.patch(REPORT_PORTAL_SERVICE) @@ -49,4 +49,4 @@ def test_case_id_custom_definition(mock_client_init): assert len(item_start_calls) == len(item_finish_calls) == 3 test_item = item_start_calls[-2] - assert test_item[1]['test_case_id'] == 'custom' + assert test_item[1]["test_case_id"] == "custom" diff --git a/tests/integration/test_code_reference.py b/tests/integration/test_code_reference.py index b03df8b..61a2f75 100644 --- a/tests/integration/test_code_reference.py +++ b/tests/integration/test_code_reference.py @@ -12,13 +12,12 @@ # See the License for the specific language governing permissions and # limitations under the License. -from tests.helpers import utils from unittest import mock from tests import REPORT_PORTAL_SERVICE +from tests.helpers import utils - -SIMPLE_TEST = 'examples/simple.robot' +SIMPLE_TEST = "examples/simple.robot" @mock.patch(REPORT_PORTAL_SERVICE) @@ -36,4 +35,4 @@ def test_code_reference_simple(mock_client_init): assert len(item_start_calls) == len(item_finish_calls) == 3 test_item = item_start_calls[-2] - assert test_item[1]['code_ref'] == SIMPLE_TEST + ':3' + assert test_item[1]["code_ref"] == SIMPLE_TEST + ":3" diff --git a/tests/integration/test_dynamic_tags.py b/tests/integration/test_dynamic_tags.py index 16b527d..88d58a0 100644 --- a/tests/integration/test_dynamic_tags.py +++ b/tests/integration/test_dynamic_tags.py @@ -13,18 +13,17 @@ # limitations under the License. import uuid - from unittest import mock from tests import REPORT_PORTAL_SERVICE from tests.helpers import utils -TEST_CASES = ['Test tag set', 'Test no tag', 'Test set multiple tags'] +TEST_CASES = ["Test tag set", "Test no tag", "Test set multiple tags"] TEST_CASE_UUIDS = [str(uuid.uuid5(uuid.NAMESPACE_OID, i)) for i in TEST_CASES] def generate_id(*args, **kwargs): - return str(uuid.uuid5(uuid.NAMESPACE_OID, str(kwargs['name']))) + return str(uuid.uuid5(uuid.NAMESPACE_OID, str(kwargs["name"]))) @mock.patch(REPORT_PORTAL_SERVICE) @@ -32,19 +31,21 @@ def test_launch_log(mock_client_init): mock_client = mock_client_init.return_value mock_client.start_test_item.side_effect = generate_id - result = utils.run_robot_tests(['examples/dynamic_tags.robot']) + result = utils.run_robot_tests(["examples/dynamic_tags.robot"]) assert result == 0 # the test successfully passed start_tests = [ - call for call in mock_client.start_test_item.call_args_list if - call[1]['item_type'] == 'STEP' and call[1].get('has_stats', True)] - finish_tests = [call for call in - mock_client.finish_test_item.call_args_list if - call[1]['item_id'] in TEST_CASE_UUIDS] + call + for call in mock_client.start_test_item.call_args_list + if call[1]["item_type"] == "STEP" and call[1].get("has_stats", True) + ] + finish_tests = [ + call for call in mock_client.finish_test_item.call_args_list if call[1]["item_id"] in TEST_CASE_UUIDS + ] for start in start_tests: - assert len(start[1]['attributes']) == 0 + assert len(start[1]["attributes"]) == 0 - assert finish_tests[0][1]['attributes'] == [{'value': 'dynamic_tag'}] - assert finish_tests[1][1]['attributes'] == [] - assert finish_tests[2][1]['attributes'] == [{'value': 'multiple_tags_one'}, {'value': 'multiple_tags_two'}] + assert finish_tests[0][1]["attributes"] == [{"value": "dynamic_tag"}] + assert finish_tests[1][1]["attributes"] == [] + assert finish_tests[2][1]["attributes"] == [{"value": "multiple_tags_one"}, {"value": "multiple_tags_two"}] diff --git a/tests/integration/test_dynamic_test_case_id.py b/tests/integration/test_dynamic_test_case_id.py index 9fb8196..7730802 100644 --- a/tests/integration/test_dynamic_test_case_id.py +++ b/tests/integration/test_dynamic_test_case_id.py @@ -17,12 +17,12 @@ from tests import REPORT_PORTAL_SERVICE from tests.helpers import utils -EXAMPLE_TEST = 'examples/dynamic_test_case_id.robot' +EXAMPLE_TEST = "examples/dynamic_test_case_id.robot" @mock.patch(REPORT_PORTAL_SERVICE) def test_case_id_simple(mock_client_init): - result = utils.run_robot_tests([EXAMPLE_TEST], arguments={'--metadata': 'Scope:Smoke'}) + result = utils.run_robot_tests([EXAMPLE_TEST], arguments={"--metadata": "Scope:Smoke"}) assert result == 0 # the test successfully passed mock_client = mock_client_init.return_value @@ -35,7 +35,7 @@ def test_case_id_simple(mock_client_init): assert len(item_start_calls) == len(item_finish_calls) == 3 test_item_start = item_start_calls[-2] - assert test_item_start[1]['test_case_id'] == f'{EXAMPLE_TEST}:Test set dynamic Test Case ID' + assert test_item_start[1]["test_case_id"] == f"{EXAMPLE_TEST}:Test set dynamic Test Case ID" test_item_finish = item_finish_calls[-2] - assert test_item_finish[1]['test_case_id'] == 'dynamic_tags.robot[Smoke]' + assert test_item_finish[1]["test_case_id"] == "dynamic_tags.robot[Smoke]" diff --git a/tests/integration/test_logger.py b/tests/integration/test_logger.py index c6d69a1..2a704bc 100644 --- a/tests/integration/test_logger.py +++ b/tests/integration/test_logger.py @@ -12,34 +12,34 @@ # See the License for the specific language governing permissions and # limitations under the License. -from tests.helpers import utils from unittest import mock from tests import REPORT_PORTAL_SERVICE +from tests.helpers import utils @mock.patch(REPORT_PORTAL_SERVICE) def test_launch_log(mock_client_init): - result = utils.run_robot_tests(['examples/launch_log.robot']) + result = utils.run_robot_tests(["examples/launch_log.robot"]) assert result == 0 # the test successfully passed mock_client = mock_client_init.return_value calls = utils.get_launch_log_calls(mock_client) assert len(calls) == 3 - messages = set(map(lambda x: x[1]['message'], calls)) - assert messages == {'Hello, world!', 'Goodbye, world!', 'Enjoy my pug!'} + messages = set(map(lambda x: x[1]["message"], calls)) + assert messages == {"Hello, world!", "Goodbye, world!", "Enjoy my pug!"} @mock.patch(REPORT_PORTAL_SERVICE) def test_binary_file_log(mock_client_init): - result = utils.run_robot_tests(['examples/binary_file_read.robot']) + result = utils.run_robot_tests(["examples/binary_file_read.robot"]) assert result == 0 # the test successfully passed mock_client = mock_client_init.return_value calls = utils.get_log_calls(mock_client) assert len(calls) == 3 - messages = set(map(lambda x: x[1]['message'], calls)) + messages = set(map(lambda x: x[1]["message"], calls)) error_msg = 'Binary data of type "image/jpeg" logging skipped, as it was processed as text and hence corrupted.' assert error_msg in messages diff --git a/tests/integration/test_no_keyword_message.py b/tests/integration/test_no_keyword_message.py index 32751ea..1304947 100644 --- a/tests/integration/test_no_keyword_message.py +++ b/tests/integration/test_no_keyword_message.py @@ -13,14 +13,12 @@ # limitations under the License.s import re -from tests.helpers import utils from unittest import mock from tests import REPORT_PORTAL_SERVICE +from tests.helpers import utils - -NO_KEYWORDS_MESSAGE_PATTERN = \ - re.compile(r'Test(?: case)? (?:contains no keywords|cannot be empty)\.') +NO_KEYWORDS_MESSAGE_PATTERN = re.compile(r"Test(?: case)? (?:contains no keywords|cannot be empty)\.") @mock.patch(REPORT_PORTAL_SERVICE) @@ -28,19 +26,19 @@ def test_no_keyword_message(mock_client_init): mock_client = mock_client_init.return_value mock_client.start_test_item.side_effect = utils.item_id_gen - result = utils.run_robot_tests(['examples/no_keywords.robot']) + result = utils.run_robot_tests(["examples/no_keywords.robot"]) assert result == 1 log_calls = mock_client.log.call_args_list assert len(log_calls) == 1 log_call = log_calls[0][1] - assert NO_KEYWORDS_MESSAGE_PATTERN.match(log_call['message']) - assert log_call['item_id'].startswith('No keyword test case') + assert NO_KEYWORDS_MESSAGE_PATTERN.match(log_call["message"]) + assert log_call["item_id"].startswith("No keyword test case") item_start_calls = mock_client.start_test_item.call_args_list item_finish_calls = mock_client.finish_test_item.call_args_list assert len(item_start_calls) == len(item_finish_calls) == 2 - statuses = [finish[1]['status'] for finish in item_finish_calls] - assert statuses == ['FAILED', 'FAILED'] + statuses = [finish[1]["status"] for finish in item_finish_calls] + assert statuses == ["FAILED", "FAILED"] diff --git a/tests/integration/test_rkie_keyword.py b/tests/integration/test_rkie_keyword.py index a39dd73..4c5cb9c 100644 --- a/tests/integration/test_rkie_keyword.py +++ b/tests/integration/test_rkie_keyword.py @@ -23,7 +23,7 @@ def test_before_after_suite_with_steps(mock_client_init): mock_client = mock_client_init.return_value mock_client.start_test_item.side_effect = utils.item_id_gen - result = utils.run_robot_tests(['examples/rkie_keyword.robot']) + result = utils.run_robot_tests(["examples/rkie_keyword.robot"]) assert result == 0 launch_start = mock_client.start_launch.call_args_list @@ -34,6 +34,5 @@ def test_before_after_suite_with_steps(mock_client_init): item_finish_calls = mock_client.finish_test_item.call_args_list assert len(item_start_calls) == len(item_finish_calls) == 13 - statuses = [finish[1]['status'] for finish in item_finish_calls] - assert statuses == ['PASSED'] * 2 + ['FAILED'] * 3 + ['PASSED'] * 3 + [ - 'SKIPPED'] * 2 + ['PASSED'] * 3 + statuses = [finish[1]["status"] for finish in item_finish_calls] + assert statuses == ["PASSED"] * 2 + ["FAILED"] * 3 + ["PASSED"] * 3 + ["SKIPPED"] * 2 + ["PASSED"] * 3 diff --git a/tests/integration/test_screenshot.py b/tests/integration/test_screenshot.py index 1b2b5b4..de4402c 100644 --- a/tests/integration/test_screenshot.py +++ b/tests/integration/test_screenshot.py @@ -12,14 +12,14 @@ # See the License for the specific language governing permissions and # limitations under the License. -from tests.helpers import utils from unittest import mock from tests import REPORT_PORTAL_SERVICE +from tests.helpers import utils -EXAMPLE_TEST = 'examples/screenshot.robot' -SELENIUM_SCREENSHOT = 'examples/res/selenium-screenshot-1.png' -PLAYWRIGHT_SCREENSHOT = 'examples/res/Screenshot_test_FAILURE_SCREENSHOT_1.png' +EXAMPLE_TEST = "examples/screenshot.robot" +SELENIUM_SCREENSHOT = "examples/res/selenium-screenshot-1.png" +PLAYWRIGHT_SCREENSHOT = "examples/res/Screenshot_test_FAILURE_SCREENSHOT_1.png" SCREENSHOTS = [SELENIUM_SCREENSHOT, PLAYWRIGHT_SCREENSHOT] @@ -33,12 +33,12 @@ def test_screenshot_log(mock_client_init): assert len(calls) == 2 for i, call in enumerate(calls): - message = call[1]['message'] - assert message == f'Image attached: {SCREENSHOTS[i]}' + message = call[1]["message"] + assert message == f"Image attached: {SCREENSHOTS[i]}" - attachment = call[1]['attachment'] + attachment = call[1]["attachment"] - assert attachment['name'] == SCREENSHOTS[i].split('/')[-1] - assert attachment['mime'] == 'image/png' - with open(SCREENSHOTS[i], 'rb') as file: - assert attachment['data'] == file.read() + assert attachment["name"] == SCREENSHOTS[i].split("/")[-1] + assert attachment["mime"] == "image/png" + with open(SCREENSHOTS[i], "rb") as file: + assert attachment["data"] == file.read() diff --git a/tests/integration/test_suite_doc_with_urls.py b/tests/integration/test_suite_doc_with_urls.py index c09dd85..a4a797a 100644 --- a/tests/integration/test_suite_doc_with_urls.py +++ b/tests/integration/test_suite_doc_with_urls.py @@ -17,7 +17,7 @@ from tests import REPORT_PORTAL_SERVICE from tests.helpers import utils -SIMPLE_TEST = 'examples/suite_doc_with_urls.robot' +SIMPLE_TEST = "examples/suite_doc_with_urls.robot" @mock.patch(REPORT_PORTAL_SERVICE) @@ -35,5 +35,6 @@ def test_suite_doc_with_urls(mock_client_init): assert len(item_start_calls) == len(item_finish_calls) == 3 test_suite = item_start_calls[0] - assert test_suite[1]['description'] == ('This is a test suite with URLs: [Google](https://www.google.com ) and ' - '') + assert test_suite[1]["description"] == ( + "This is a test suite with URLs: [Google](https://www.google.com ) and " "" + ) diff --git a/tests/integration/test_suite_metadata.py b/tests/integration/test_suite_metadata.py index 2eaa7e7..e1ed714 100644 --- a/tests/integration/test_suite_metadata.py +++ b/tests/integration/test_suite_metadata.py @@ -12,13 +12,12 @@ # See the License for the specific language governing permissions and # limitations under the License. -from tests.helpers import utils from unittest import mock from tests import REPORT_PORTAL_SERVICE +from tests.helpers import utils - -SIMPLE_TEST = 'examples/suite_metadata.robot' +SIMPLE_TEST = "examples/suite_metadata.robot" @mock.patch(REPORT_PORTAL_SERVICE) @@ -36,12 +35,12 @@ def test_suite_metadata_simple(mock_client_init): assert len(item_start_calls) == len(item_finish_calls) == 3 test_suite = item_start_calls[0] - assert test_suite[1]['attributes'] == [{'key': 'Author', 'value': 'John Doe'}] + assert test_suite[1]["attributes"] == [{"key": "Author", "value": "John Doe"}] @mock.patch(REPORT_PORTAL_SERVICE) def test_suite_metadata_command_line_simple(mock_client_init): - result = utils.run_robot_tests([SIMPLE_TEST], arguments={'--metadata': 'Scope:Smoke'}) + result = utils.run_robot_tests([SIMPLE_TEST], arguments={"--metadata": "Scope:Smoke"}) assert result == 0 # the test successfully passed mock_client = mock_client_init.return_value @@ -54,7 +53,7 @@ def test_suite_metadata_command_line_simple(mock_client_init): assert len(item_start_calls) == len(item_finish_calls) == 3 test_suite = item_start_calls[0] - attributes = test_suite[1]['attributes'] + attributes = test_suite[1]["attributes"] assert len(attributes) == 2 - assert {'value': 'Smoke', 'key': 'Scope'} in attributes - assert {'key': 'Author', 'value': 'John Doe'} in attributes + assert {"value": "Smoke", "key": "Scope"} in attributes + assert {"key": "Author", "value": "John Doe"} in attributes diff --git a/tests/integration/test_templates_test.py b/tests/integration/test_templates_test.py index 80a0a36..e88f834 100644 --- a/tests/integration/test_templates_test.py +++ b/tests/integration/test_templates_test.py @@ -19,20 +19,20 @@ @mock.patch(REPORT_PORTAL_SERVICE) -def test_code_reference_template(mock_client_init, test, test_names, - code_ref_suffixes): +def test_code_reference_template(mock_client_init, test, test_names, code_ref_suffixes): result = utils.run_robot_tests([test]) assert result == 0 # the test successfully passed mock_client = mock_client_init.return_value - calls = [call for call in mock_client.start_test_item.call_args_list if - call[1]['item_type'] == 'STEP' and call[1].get('has_stats', True) - is True] + calls = [ + call + for call in mock_client.start_test_item.call_args_list + if call[1]["item_type"] == "STEP" and call[1].get("has_stats", True) is True + ] assert len(calls) == len(test_names) - for call, test_name, code_ref_suff in zip(calls, test_names, - code_ref_suffixes): - code_ref = call[1]['code_ref'] - test_case_id = call[1]['test_case_id'] - assert test_case_id == test + ':' + test_name - assert code_ref == test + ':' + code_ref_suff + for call, test_name, code_ref_suff in zip(calls, test_names, code_ref_suffixes): + code_ref = call[1]["code_ref"] + test_case_id = call[1]["test_case_id"] + assert test_case_id == test + ":" + test_name + assert code_ref == test + ":" + code_ref_suff diff --git a/tests/integration/test_variables.py b/tests/integration/test_variables.py index 124f79d..1bb83e6 100644 --- a/tests/integration/test_variables.py +++ b/tests/integration/test_variables.py @@ -17,7 +17,7 @@ # noinspection PyPackageRequirements import pytest -from reportportal_client import OutputType, RPClient, ThreadedRPClient, BatchedRPClient +from reportportal_client import BatchedRPClient, OutputType, RPClient, ThreadedRPClient from tests import REPORT_PORTAL_SERVICE, REQUESTS_SERVICE from tests.helpers import utils @@ -27,44 +27,40 @@ def test_agent_pass_batch_payload_size_variable(mock_client_init): variables = utils.DEFAULT_VARIABLES.copy() payload_size = 100 - variables['RP_LOG_BATCH_PAYLOAD_SIZE'] = payload_size - result = utils.run_robot_tests(['examples/simple.robot'], - variables=variables) + variables["RP_LOG_BATCH_PAYLOAD_SIZE"] = payload_size + result = utils.run_robot_tests(["examples/simple.robot"], variables=variables) assert result == 0 # the test successfully passed - payload_variable = 'log_batch_payload_size' + payload_variable = "log_batch_payload_size" assert payload_variable in mock_client_init.call_args_list[0][1] - assert mock_client_init.call_args_list[0][1][ - payload_variable] == payload_size + assert mock_client_init.call_args_list[0][1][payload_variable] == payload_size @mock.patch(REQUESTS_SERVICE) def test_agent_pass_launch_uuid_variable(mock_requests_init): variables = utils.DEFAULT_VARIABLES.copy() - test_launch_id = 'my_test_launch' - variables['RP_LAUNCH_UUID'] = test_launch_id - result = utils.run_robot_tests(['examples/simple.robot'], - variables=variables) + test_launch_id = "my_test_launch" + variables["RP_LAUNCH_UUID"] = test_launch_id + result = utils.run_robot_tests(["examples/simple.robot"], variables=variables) assert result == 0 # the test successfully passed mock_requests = mock_requests_init.return_value assert mock_requests.post.call_count == 3 suite_start = mock_requests.post.call_args_list[0] - assert suite_start[0][0].endswith('/item') - assert suite_start[1]['json']['launchUuid'] == test_launch_id + assert suite_start[0][0].endswith("/item") + assert suite_start[1]["json"]["launchUuid"] == test_launch_id -@pytest.mark.parametrize('variable, warn_num', - [('RP_PROJECT', 1), ('RP_API_KEY', 2), - ('RP_ENDPOINT', 1), ('RP_LAUNCH', 1)]) +@pytest.mark.parametrize( + "variable, warn_num", [("RP_PROJECT", 1), ("RP_API_KEY", 2), ("RP_ENDPOINT", 1), ("RP_LAUNCH", 1)] +) @mock.patch(REPORT_PORTAL_SERVICE) def test_no_required_variable_warning(mock_client_init, variable, warn_num): variables = utils.DEFAULT_VARIABLES.copy() del variables[variable] with warnings.catch_warnings(record=True) as w: - result = utils.run_robot_tests(['examples/simple.robot'], - variables=variables) + result = utils.run_robot_tests(["examples/simple.robot"], variables=variables) assert result == 0 # the test successfully passed assert len(w) == warn_num @@ -78,87 +74,77 @@ def test_no_required_variable_warning(mock_client_init, variable, warn_num): def filter_agent_call(warn): - category = getattr(warn, 'category', None) + category = getattr(warn, "category", None) if category: - return category.__name__ == 'DeprecationWarning' \ - or category.__name__ == 'RuntimeWarning' + return category.__name__ == "DeprecationWarning" or category.__name__ == "RuntimeWarning" return False def filter_agent_calls(warning_list): - return list( - filter( - lambda call: filter_agent_call(call), - warning_list - ) - ) + return list(filter(lambda call: filter_agent_call(call), warning_list)) @mock.patch(REPORT_PORTAL_SERVICE) def test_rp_api_key(mock_client_init): - api_key = 'rp_api_key' + api_key = "rp_api_key" variables = dict(utils.DEFAULT_VARIABLES) - variables.update({'RP_API_KEY': api_key}.items()) + variables.update({"RP_API_KEY": api_key}.items()) with warnings.catch_warnings(record=True) as w: - result = utils.run_robot_tests(['examples/simple.robot'], - variables=variables) - assert int(result) == 0, 'Exit code should be 0 (no errors)' + result = utils.run_robot_tests(["examples/simple.robot"], variables=variables) + assert int(result) == 0, "Exit code should be 0 (no errors)" assert mock_client_init.call_count == 1 constructor_args = mock_client_init.call_args_list[0][1] - assert constructor_args['api_key'] == api_key + assert constructor_args["api_key"] == api_key assert len(filter_agent_calls(w)) == 0 @mock.patch(REPORT_PORTAL_SERVICE) def test_rp_uuid(mock_client_init): - api_key = 'rp_api_key' + api_key = "rp_api_key" variables = dict(utils.DEFAULT_VARIABLES) - del variables['RP_API_KEY'] - variables.update({'RP_UUID': api_key}.items()) + del variables["RP_API_KEY"] + variables.update({"RP_UUID": api_key}.items()) with warnings.catch_warnings(record=True) as w: - result = utils.run_robot_tests(['examples/simple.robot'], - variables=variables) - assert int(result) == 0, 'Exit code should be 0 (no errors)' + result = utils.run_robot_tests(["examples/simple.robot"], variables=variables) + assert int(result) == 0, "Exit code should be 0 (no errors)" assert mock_client_init.call_count == 1 constructor_args = mock_client_init.call_args_list[0][1] - assert constructor_args['api_key'] == api_key + assert constructor_args["api_key"] == api_key assert len(filter_agent_calls(w)) == 1 @mock.patch(REPORT_PORTAL_SERVICE) def test_rp_api_key_priority(mock_client_init): - api_key = 'rp_api_key' + api_key = "rp_api_key" variables = dict(utils.DEFAULT_VARIABLES) - variables.update({'RP_API_KEY': api_key, 'RP_UUID': 'rp_uuid'}.items()) + variables.update({"RP_API_KEY": api_key, "RP_UUID": "rp_uuid"}.items()) with warnings.catch_warnings(record=True) as w: - result = utils.run_robot_tests(['examples/simple.robot'], - variables=variables) - assert int(result) == 0, 'Exit code should be 0 (no errors)' + result = utils.run_robot_tests(["examples/simple.robot"], variables=variables) + assert int(result) == 0, "Exit code should be 0 (no errors)" assert mock_client_init.call_count == 1 constructor_args = mock_client_init.call_args_list[0][1] - assert constructor_args['api_key'] == api_key + assert constructor_args["api_key"] == api_key assert len(filter_agent_calls(w)) == 0 @mock.patch(REPORT_PORTAL_SERVICE) def test_rp_api_key_empty(mock_client_init): - api_key = '' + api_key = "" variables = dict(utils.DEFAULT_VARIABLES) - variables.update({'RP_API_KEY': api_key}.items()) + variables.update({"RP_API_KEY": api_key}.items()) with warnings.catch_warnings(record=True) as w: - result = utils.run_robot_tests(['examples/simple.robot'], - variables=variables) - assert int(result) == 0, 'Exit code should be 0 (no errors)' + result = utils.run_robot_tests(["examples/simple.robot"], variables=variables) + assert int(result) == 0, "Exit code should be 0 (no errors)" assert mock_client_init.call_count == 0 assert len(filter_agent_calls(w)) == 2 @@ -168,43 +154,39 @@ def test_rp_api_key_empty(mock_client_init): def test_launch_uuid_print(mock_client_init): print_uuid = True variables = utils.DEFAULT_VARIABLES.copy() - variables.update({'RP_LAUNCH_UUID_PRINT': str(print_uuid)}.items()) + variables.update({"RP_LAUNCH_UUID_PRINT": str(print_uuid)}.items()) - result = utils.run_robot_tests(['examples/simple.robot'], - variables=variables) + result = utils.run_robot_tests(["examples/simple.robot"], variables=variables) - assert int(result) == 0, 'Exit code should be 0 (no errors)' + assert int(result) == 0, "Exit code should be 0 (no errors)" assert mock_client_init.call_count == 1 - assert mock_client_init.call_args_list[0][1]['launch_uuid_print'] == print_uuid - assert mock_client_init.call_args_list[0][1]['print_output'] is None + assert mock_client_init.call_args_list[0][1]["launch_uuid_print"] == print_uuid + assert mock_client_init.call_args_list[0][1]["print_output"] is None @mock.patch(REPORT_PORTAL_SERVICE) def test_launch_uuid_print_stderr(mock_client_init): print_uuid = True variables = utils.DEFAULT_VARIABLES.copy() - variables.update( - {'RP_LAUNCH_UUID_PRINT': str(print_uuid), 'RP_LAUNCH_UUID_PRINT_OUTPUT': 'stderr'}.items()) + variables.update({"RP_LAUNCH_UUID_PRINT": str(print_uuid), "RP_LAUNCH_UUID_PRINT_OUTPUT": "stderr"}.items()) - result = utils.run_robot_tests(['examples/simple.robot'], variables=variables) + result = utils.run_robot_tests(["examples/simple.robot"], variables=variables) - assert int(result) == 0, 'Exit code should be 0 (no errors)' + assert int(result) == 0, "Exit code should be 0 (no errors)" assert mock_client_init.call_count == 1 - assert mock_client_init.call_args_list[0][1]['launch_uuid_print'] == print_uuid - assert mock_client_init.call_args_list[0][1]['print_output'] is OutputType.STDERR + assert mock_client_init.call_args_list[0][1]["launch_uuid_print"] == print_uuid + assert mock_client_init.call_args_list[0][1]["print_output"] is OutputType.STDERR @mock.patch(REPORT_PORTAL_SERVICE) def test_launch_uuid_print_invalid_output(mock_client_init): print_uuid = True variables = utils.DEFAULT_VARIABLES.copy() - variables.update({'RP_LAUNCH_UUID_PRINT': str(print_uuid), - 'RP_LAUNCH_UUID_PRINT_OUTPUT': 'something'}.items()) + variables.update({"RP_LAUNCH_UUID_PRINT": str(print_uuid), "RP_LAUNCH_UUID_PRINT_OUTPUT": "something"}.items()) - result = utils.run_robot_tests(['examples/simple.robot'], - variables=variables) + result = utils.run_robot_tests(["examples/simple.robot"], variables=variables) - assert int(result) == 0, 'Exit code should be 0 (no errors)' + assert int(result) == 0, "Exit code should be 0 (no errors)" assert mock_client_init.call_count == 0 @@ -212,29 +194,28 @@ def test_launch_uuid_print_invalid_output(mock_client_init): def test_no_launch_uuid_print(mock_client_init): variables = utils.DEFAULT_VARIABLES.copy() - result = utils.run_robot_tests(['examples/simple.robot'], variables=variables) + result = utils.run_robot_tests(["examples/simple.robot"], variables=variables) - assert int(result) == 0, 'Exit code should be 0 (no errors)' + assert int(result) == 0, "Exit code should be 0 (no errors)" assert mock_client_init.call_count == 1 - assert mock_client_init.call_args_list[0][1]['launch_uuid_print'] is False - assert mock_client_init.call_args_list[0][1]['print_output'] is None + assert mock_client_init.call_args_list[0][1]["launch_uuid_print"] is False + assert mock_client_init.call_args_list[0][1]["print_output"] is None @pytest.mark.parametrize( - 'variable_value, expected_type', - [('SYNC', RPClient), ('ASYNC_THREAD', ThreadedRPClient), - ('ASYNC_BATCHED', BatchedRPClient), (None, RPClient)] + "variable_value, expected_type", + [("SYNC", RPClient), ("ASYNC_THREAD", ThreadedRPClient), ("ASYNC_BATCHED", BatchedRPClient), (None, RPClient)], ) -@mock.patch('reportportal_client.aio.client.Client') +@mock.patch("reportportal_client.aio.client.Client") @mock.patch(REPORT_PORTAL_SERVICE) def test_client_types(mock_client_init, mock_async_client_init, variable_value, expected_type): variables = utils.DEFAULT_VARIABLES.copy() if variable_value: - variables['RP_CLIENT_TYPE'] = variable_value + variables["RP_CLIENT_TYPE"] = variable_value - result = utils.run_robot_tests(['examples/simple.robot'], variables=variables) + result = utils.run_robot_tests(["examples/simple.robot"], variables=variables) - assert int(result) == 0, 'Exit code should be 0 (no errors)' + assert int(result) == 0, "Exit code should be 0 (no errors)" if expected_type is RPClient: assert mock_async_client_init.call_count == 0 assert mock_client_init.call_count == 1 @@ -244,25 +225,19 @@ def test_client_types(mock_client_init, mock_async_client_init, variable_value, @pytest.mark.parametrize( - 'connect_value, read_value, expected_result', - [ - ('5', '15', (5.0, 15.0)), - ('5.5', '15.5', (5.5, 15.5)), - (None, None, None), - (None, '5', 5), - ('5', None, 5) - ] + "connect_value, read_value, expected_result", + [("5", "15", (5.0, 15.0)), ("5.5", "15.5", (5.5, 15.5)), (None, None, None), (None, "5", 5), ("5", None, 5)], ) @mock.patch(REPORT_PORTAL_SERVICE) def test_client_timeouts(mock_client_init, connect_value, read_value, expected_result): variables = utils.DEFAULT_VARIABLES.copy() if connect_value: - variables['RP_CONNECT_TIMEOUT'] = connect_value + variables["RP_CONNECT_TIMEOUT"] = connect_value if read_value: - variables['RP_READ_TIMEOUT'] = read_value + variables["RP_READ_TIMEOUT"] = read_value - result = utils.run_robot_tests(['examples/simple.robot'], variables=variables) + result = utils.run_robot_tests(["examples/simple.robot"], variables=variables) - assert int(result) == 0, 'Exit code should be 0 (no errors)' + assert int(result) == 0, "Exit code should be 0 (no errors)" assert mock_client_init.call_count == 1 - assert mock_client_init.call_args_list[0][1]['http_timeout'] == expected_result + assert mock_client_init.call_args_list[0][1]["http_timeout"] == expected_result diff --git a/tests/integration/test_wuks_keyword.py b/tests/integration/test_wuks_keyword.py index 014e39f..96950a1 100644 --- a/tests/integration/test_wuks_keyword.py +++ b/tests/integration/test_wuks_keyword.py @@ -23,7 +23,7 @@ def test_before_after_suite_with_steps(mock_client_init): mock_client = mock_client_init.return_value mock_client.start_test_item.side_effect = utils.item_id_gen - result = utils.run_robot_tests(['examples/wuks_keyword.robot']) + result = utils.run_robot_tests(["examples/wuks_keyword.robot"]) assert result == 0 launch_start = mock_client.start_launch.call_args_list @@ -34,6 +34,5 @@ def test_before_after_suite_with_steps(mock_client_init): item_finish_calls = mock_client.finish_test_item.call_args_list assert len(item_start_calls) == len(item_finish_calls) == 13 - statuses = [finish[1]['status'] for finish in item_finish_calls] - assert statuses == ['PASSED'] * 2 + ['FAILED'] * 3 + ['PASSED'] * 2 + [ - 'SKIPPED'] * 2 + ['PASSED'] * 4 + statuses = [finish[1]["status"] for finish in item_finish_calls] + assert statuses == ["PASSED"] * 2 + ["FAILED"] * 3 + ["PASSED"] * 2 + ["SKIPPED"] * 2 + ["PASSED"] * 4 diff --git a/tests/unit/conftest.py b/tests/unit/conftest.py index 54d3146..0bd7d11 100644 --- a/tests/unit/conftest.py +++ b/tests/unit/conftest.py @@ -13,14 +13,15 @@ See the License for the specific language governing permissions and limitations under the License """ + import os +from unittest import mock from pytest import fixture -from unittest import mock from robotframework_reportportal.listener import listener -from robotframework_reportportal.variables import Variables from robotframework_reportportal.result_visitor import RobotResultsVisitor +from robotframework_reportportal.variables import Variables @fixture() @@ -28,16 +29,16 @@ def visitor(): return RobotResultsVisitor() -@mock.patch('robotframework_reportportal.variables.strtobool', mock.Mock()) -@mock.patch('robotframework_reportportal.variables.get_variable', mock.Mock()) +@mock.patch("robotframework_reportportal.variables.strtobool", mock.Mock()) +@mock.patch("robotframework_reportportal.variables.get_variable", mock.Mock()) @fixture() def mock_variables(): mock_variables = Variables() - mock_variables.endpoint = 'http://localhost:8080' - mock_variables.launch_name = 'Robot' - mock_variables.project = 'default_personal' - mock_variables.api_key = 'test_api_key' - mock_variables.launch_attributes = '' + mock_variables.endpoint = "http://localhost:8080" + mock_variables.launch_name = "Robot" + mock_variables.project = "default_personal" + mock_variables.api_key = "test_api_key" + mock_variables.launch_attributes = "" mock_variables.launch_id = None mock_variables.launch_doc = None mock_variables.log_batch_size = 1 @@ -63,40 +64,40 @@ def mock_listener(mock_variables): def kwd_attributes(): """Keyword attributes.""" return { - 'args': ('Kw Body Start',), - 'assign': (), - 'doc': 'Logs the given message with the given level.', - 'kwname': 'Log', - 'libname': 'BuiltIn', - 'starttime': '1621947055434', - 'tags': [], - 'type': 'Keyword' + "args": ("Kw Body Start",), + "assign": (), + "doc": "Logs the given message with the given level.", + "kwname": "Log", + "libname": "BuiltIn", + "starttime": "1621947055434", + "tags": [], + "type": "Keyword", } @fixture() def suite_attributes(): return { - 'id': 's1', - 'doc': '', - 'longname': 'Suite', - 'metadata': {}, - 'source': os.getcwd() + '/robot/test.robot', - 'suites': [], - 'tests': ['Test'], - 'starttime': '20210407 12:24:27.116', - 'totaltests': 1 + "id": "s1", + "doc": "", + "longname": "Suite", + "metadata": {}, + "source": os.getcwd() + "/robot/test.robot", + "suites": [], + "tests": ["Test"], + "starttime": "20210407 12:24:27.116", + "totaltests": 1, } @fixture() def test_attributes(): return { - 'id': 's1-t1', - 'doc': '', - 'longname': 'Suite.Test', - 'tags': [], - 'source': os.getcwd() + '/robot/test.robot', - 'template': '', - 'starttime': '20210407 12:24:27.116' + "id": "s1-t1", + "doc": "", + "longname": "Suite.Test", + "tags": [], + "source": os.getcwd() + "/robot/test.robot", + "template": "", + "starttime": "20210407 12:24:27.116", } diff --git a/tests/unit/test_listener.py b/tests/unit/test_listener.py index 1a4184e..77bb691 100644 --- a/tests/unit/test_listener.py +++ b/tests/unit/test_listener.py @@ -13,9 +13,10 @@ limitations under the License """ -import pytest from unittest import mock +import pytest + from robotframework_reportportal.listener import listener from tests import REPORT_PORTAL_SERVICE @@ -23,144 +24,121 @@ class TestListener: @mock.patch(REPORT_PORTAL_SERVICE) - def test_code_ref(self, mock_client_init, mock_listener, - test_attributes): - mock_listener.start_test('Test', test_attributes) + def test_code_ref(self, mock_client_init, mock_listener, test_attributes): + mock_listener.start_test("Test", test_attributes) mock_client = mock_client_init.return_value assert mock_client.start_test_item.call_count == 1 args, kwargs = mock_client.start_test_item.call_args - assert (kwargs['code_ref'] == - '{0}:{1}'.format('robot/test.robot', 'Test')) + assert kwargs["code_ref"] == "{0}:{1}".format("robot/test.robot", "Test") # Robot Framework of versions < 4 does not bypass 'source' attribute on # 'start_test' method call @mock.patch(REPORT_PORTAL_SERVICE) - def test_code_ref_robot_3_2_2(self, mock_client_init, mock_listener, - suite_attributes, test_attributes): + def test_code_ref_robot_3_2_2(self, mock_client_init, mock_listener, suite_attributes, test_attributes): test_attributes = test_attributes.copy() - del test_attributes['source'] - mock_listener.start_suite('Suite', suite_attributes) - mock_listener.start_test('Test', test_attributes) + del test_attributes["source"] + mock_listener.start_suite("Suite", suite_attributes) + mock_listener.start_test("Test", test_attributes) mock_client = mock_client_init.return_value assert mock_client.start_test_item.call_count == 2 args, kwargs = mock_client.start_test_item.call_args - assert (kwargs['code_ref'] == - '{0}:{1}'.format('robot/test.robot', 'Test')) + assert kwargs["code_ref"] == "{0}:{1}".format("robot/test.robot", "Test") @mock.patch(REPORT_PORTAL_SERVICE) - def test_code_ref_robot_3_2_2_no_source_in_parent(self, mock_client_init, - mock_listener, - test_attributes): + def test_code_ref_robot_3_2_2_no_source_in_parent(self, mock_client_init, mock_listener, test_attributes): test_attributes = test_attributes.copy() - del test_attributes['source'] - mock_listener.start_test('Test', test_attributes) + del test_attributes["source"] + mock_listener.start_test("Test", test_attributes) mock_client = mock_client_init.return_value assert mock_client.start_test_item.call_count == 1 args, kwargs = mock_client.start_test_item.call_args - assert (kwargs['code_ref'] == '{0}:{1}'.format(None, 'Test')) + assert kwargs["code_ref"] == "{0}:{1}".format(None, "Test") @mock.patch(REPORT_PORTAL_SERVICE) - def test_suite_no_source_attribute(self, mock_client_init, mock_listener, - suite_attributes, test_attributes): + def test_suite_no_source_attribute(self, mock_client_init, mock_listener, suite_attributes, test_attributes): suite_attributes = suite_attributes.copy() - del suite_attributes['source'] - del test_attributes['source'] - mock_listener.start_suite('Suite', suite_attributes) - mock_listener.start_test('Test', test_attributes) + del suite_attributes["source"] + del test_attributes["source"] + mock_listener.start_suite("Suite", suite_attributes) + mock_listener.start_test("Test", test_attributes) mock_client = mock_client_init.return_value assert mock_client.start_test_item.call_count == 2 args, kwargs = mock_client.start_test_item.call_args - assert (kwargs['code_ref'] == '{0}:{1}'.format(None, 'Test')) + assert kwargs["code_ref"] == "{0}:{1}".format(None, "Test") @mock.patch(REPORT_PORTAL_SERVICE) - def test_critical_test_failure(self, mock_client_init, mock_listener, - test_attributes): - mock_listener.start_test('Test', test_attributes) - test_attributes['status'] = 'FAIL' - mock_listener.end_test('Test', test_attributes) + def test_critical_test_failure(self, mock_client_init, mock_listener, test_attributes): + mock_listener.start_test("Test", test_attributes) + test_attributes["status"] = "FAIL" + mock_listener.end_test("Test", test_attributes) mock_client = mock_client_init.return_value assert mock_client.finish_test_item.call_count == 1 args, kwargs = mock_client.finish_test_item.call_args - assert kwargs['status'] == 'FAILED' + assert kwargs["status"] == "FAILED" @mock.patch(REPORT_PORTAL_SERVICE) - def test_dynamic_attributes(self, mock_client_init, mock_listener, - test_attributes): - test_attributes['tags'] = ['simple'] - mock_listener.start_test('Test', test_attributes) - test_attributes['tags'] = ['simple', 'SLID:12345'] - test_attributes['status'] = 'PASS' - mock_listener.end_test('Test', test_attributes) + def test_dynamic_attributes(self, mock_client_init, mock_listener, test_attributes): + test_attributes["tags"] = ["simple"] + mock_listener.start_test("Test", test_attributes) + test_attributes["tags"] = ["simple", "SLID:12345"] + test_attributes["status"] = "PASS" + mock_listener.end_test("Test", test_attributes) mock_client = mock_client_init.return_value assert mock_client.start_test_item.call_count == 1 assert mock_client.finish_test_item.call_count == 1 args, kwargs = mock_client.start_test_item.call_args - assert kwargs['attributes'] == [{'value': 'simple'}] + assert kwargs["attributes"] == [{"value": "simple"}] args, kwargs = mock_client.finish_test_item.call_args - assert kwargs['attributes'] == [{'value': 'simple'}, - {'key': 'SLID', 'value': '12345'}] + assert kwargs["attributes"] == [{"value": "simple"}, {"key": "SLID", "value": "12345"}] @mock.patch(REPORT_PORTAL_SERVICE) - @pytest.mark.parametrize('critical, expected_status', [ - (True, 'FAILED'), ('yes', 'FAILED'), ('no', 'SKIPPED')]) - def test_non_critical_test_skip( - self, mock_client_init, mock_listener, - test_attributes, critical, expected_status): - test_attributes['critical'] = critical - mock_listener.start_test('Test', test_attributes) - test_attributes['status'] = 'FAIL' - mock_listener.end_test('Test', test_attributes) + @pytest.mark.parametrize("critical, expected_status", [(True, "FAILED"), ("yes", "FAILED"), ("no", "SKIPPED")]) + def test_non_critical_test_skip(self, mock_client_init, mock_listener, test_attributes, critical, expected_status): + test_attributes["critical"] = critical + mock_listener.start_test("Test", test_attributes) + test_attributes["status"] = "FAIL" + mock_listener.end_test("Test", test_attributes) mock_client = mock_client_init.return_value assert mock_client.finish_test_item.call_count == 1 args, kwargs = mock_client.finish_test_item.call_args - assert kwargs['status'] == expected_status + assert kwargs["status"] == expected_status @mock.patch(REPORT_PORTAL_SERVICE) - @pytest.mark.parametrize('skipped_issue_value', [True, False]) - def test_skipped_issue_variable_bypass(self, mock_client_init, - mock_variables, - skipped_issue_value): + @pytest.mark.parametrize("skipped_issue_value", [True, False]) + def test_skipped_issue_variable_bypass(self, mock_client_init, mock_variables, skipped_issue_value): mock_variables.skipped_issue = skipped_issue_value mock_listener = listener() mock_listener._variables = mock_variables _ = mock_listener.service assert mock_client_init.call_count == 1 args, kwargs = mock_client_init.call_args - assert kwargs['is_skipped_an_issue'] == skipped_issue_value + assert kwargs["is_skipped_an_issue"] == skipped_issue_value - @mock.patch('robotframework_reportportal.variables.get_variable') + @mock.patch("robotframework_reportportal.variables.get_variable") @mock.patch(REPORT_PORTAL_SERVICE) @pytest.mark.parametrize( - 'get_var_value, verify_ssl_value, path_exists_value', [ - ('True', True, False), - ('False', False, False), - ('/path/to/cert', '/path/to/cert', True) - ]) - def test_verify_ssl_variable_bypass(self, - mock_client_init, - get_var_mock, - mock_variables, - get_var_value, - verify_ssl_value, - path_exists_value): + "get_var_value, verify_ssl_value, path_exists_value", + [("True", True, False), ("False", False, False), ("/path/to/cert", "/path/to/cert", True)], + ) + def test_verify_ssl_variable_bypass( + self, mock_client_init, get_var_mock, mock_variables, get_var_value, verify_ssl_value, path_exists_value + ): """Test case for the RP_VERIFY_SSL bypass.""" get_var_mock.return_value = get_var_value - with mock.patch('robotframework_reportportal.variables.path.exists', - return_value=path_exists_value): + with mock.patch("robotframework_reportportal.variables.path.exists", return_value=path_exists_value): mock_listener = listener() mock_listener._variables = mock_variables _ = mock_listener.service assert mock_client_init.call_count == 1 args, kwargs = mock_client_init.call_args - assert kwargs['verify_ssl'] == verify_ssl_value + assert kwargs["verify_ssl"] == verify_ssl_value @mock.patch(REPORT_PORTAL_SERVICE) - def test_test_case_id(self, mock_client_init, mock_listener, - test_attributes): - test_attributes['tags'] = ['simple', 'test_case_id:12345'] - mock_listener.start_test('Test', test_attributes) + def test_test_case_id(self, mock_client_init, mock_listener, test_attributes): + test_attributes["tags"] = ["simple", "test_case_id:12345"] + mock_listener.start_test("Test", test_attributes) mock_client = mock_client_init.return_value assert mock_client.start_test_item.call_count == 1 args, kwargs = mock_client.start_test_item.call_args - assert kwargs['test_case_id'] == '12345' - assert kwargs['attributes'] == [{'value': 'simple'}] + assert kwargs["test_case_id"] == "12345" + assert kwargs["attributes"] == [{"value": "simple"}] diff --git a/tests/unit/test_logger.py b/tests/unit/test_logger.py index fcb77d3..648b4de 100644 --- a/tests/unit/test_logger.py +++ b/tests/unit/test_logger.py @@ -13,57 +13,57 @@ limitations under the License """ -import pytest from unittest import mock +import pytest + from robotframework_reportportal import logger -ATTACHMENT = {'name': 'test_screenshot.png', 'data': b'x0x0', - 'mime': 'image/png'} +ATTACHMENT = {"name": "test_screenshot.png", "data": b"x0x0", "mime": "image/png"} TEST_DATA_METHODS = [ - ('trace', ['Test', False, None, False]), - ('trace', [None, True, None, False]), - ('trace', [None, False, ATTACHMENT, False]), - ('trace', [None, False, None, True]), - ('debug', ['Test', False, None, False]), - ('debug', [None, True, None, False]), - ('debug', [None, False, ATTACHMENT, False]), - ('debug', [None, False, None, True]), - ('warn', ['Test', False, None, False]), - ('warn', [None, True, None, False]), - ('warn', [None, False, ATTACHMENT, False]), - ('warn', [None, False, None, True]), - ('error', ['Test', False, None, False]), - ('error', [None, True, None, False]), - ('error', [None, False, ATTACHMENT, False]), - ('error', [None, False, None, True]), - ('info', ['Test', False, False, None, False]), - ('info', [None, True, False, None, False]), - ('info', [None, False, True, None, False]), - ('info', [None, False, False, ATTACHMENT, False]), - ('info', [None, False, False, None, True]), + ("trace", ["Test", False, None, False]), + ("trace", [None, True, None, False]), + ("trace", [None, False, ATTACHMENT, False]), + ("trace", [None, False, None, True]), + ("debug", ["Test", False, None, False]), + ("debug", [None, True, None, False]), + ("debug", [None, False, ATTACHMENT, False]), + ("debug", [None, False, None, True]), + ("warn", ["Test", False, None, False]), + ("warn", [None, True, None, False]), + ("warn", [None, False, ATTACHMENT, False]), + ("warn", [None, False, None, True]), + ("error", ["Test", False, None, False]), + ("error", [None, True, None, False]), + ("error", [None, False, ATTACHMENT, False]), + ("error", [None, False, None, True]), + ("info", ["Test", False, False, None, False]), + ("info", [None, True, False, None, False]), + ("info", [None, False, True, None, False]), + ("info", [None, False, False, ATTACHMENT, False]), + ("info", [None, False, False, None, True]), ] -@pytest.mark.parametrize('method, params', TEST_DATA_METHODS) -@mock.patch('robotframework_reportportal.logger.logger') +@pytest.mark.parametrize("method, params", TEST_DATA_METHODS) +@mock.patch("robotframework_reportportal.logger.logger") def test_logger_params_bypass(mock_logger, method, params): getattr(logger, method)(*params) assert mock_logger.write.call_count == 1 - if method == 'info': + if method == "info": attachment = params[3] launch_log = params[4] else: attachment = params[2] launch_log = params[3] - if method == 'info' and params[2]: + if method == "info" and params[2]: assert mock_logger.console.call_count == 1 assert mock_logger.console.call_args[0][0] == params[0] assert mock_logger.console.call_args[0][1] is True - assert mock_logger.console.call_args[0][2] == 'stdout' + assert mock_logger.console.call_args[0][2] == "stdout" else: assert mock_logger.console.call_count == 0 diff --git a/tests/unit/test_model.py b/tests/unit/test_model.py index 12a8cc7..2ef8428 100644 --- a/tests/unit/test_model.py +++ b/tests/unit/test_model.py @@ -17,15 +17,17 @@ from robotframework_reportportal.model import Keyword -@pytest.mark.parametrize('self_type, parent_type, expected', [ - ('SETUP', 'KEYWORD', 'STEP'), - ('SETUP', 'TEST', 'BEFORE_TEST'), - ('TEARDOWN', 'SUITE', 'AFTER_SUITE'), - ('TEST', 'SUITE', 'STEP') -]) +@pytest.mark.parametrize( + "self_type, parent_type, expected", + [ + ("SETUP", "KEYWORD", "STEP"), + ("SETUP", "TEST", "BEFORE_TEST"), + ("TEARDOWN", "SUITE", "AFTER_SUITE"), + ("TEST", "SUITE", "STEP"), + ], +) def test_keyword_get_type(kwd_attributes, self_type, parent_type, expected): """Test for the get_type() method of the Keyword model.""" - kwd = Keyword(name='Test keyword', robot_attributes=kwd_attributes, - parent_type=parent_type) + kwd = Keyword(name="Test keyword", robot_attributes=kwd_attributes, parent_type=parent_type) kwd.keyword_type = self_type assert kwd.get_type() == expected diff --git a/tests/unit/test_result_visitor.py b/tests/unit/test_result_visitor.py index 3ebaef2..2c05f3a 100644 --- a/tests/unit/test_result_visitor.py +++ b/tests/unit/test_result_visitor.py @@ -14,6 +14,7 @@ """ import sys + import pytest from robotframework_reportportal.result_visitor import to_timestamp @@ -22,38 +23,36 @@ def test_parse_message_no_img_tag(visitor): with pytest.raises(AttributeError): - visitor.parse_message('usual test comment without image') + visitor.parse_message("usual test comment without image") def test_parse_message_bad_img_tag(visitor): with pytest.raises(AttributeError): - visitor.parse_message('') + visitor.parse_message("') + assert ['src="any.png"', "any.png"] == visitor.parse_message('') def test_parse_message_contains_image_with_space(visitor): - assert ['src="any%20image.png"', 'any image.png'] == \ - visitor.parse_message('') + assert ['src="any%20image.png"', "any image.png"] == visitor.parse_message('') TIMESTAMP_TEST_CASES = [ - ('20240920 00:00:00.000', '+3:00', '1726779600000'), - ('20240919 18:00:00.000', '-3:00', '1726779600000') + ("20240920 00:00:00.000", "+3:00", "1726779600000"), + ("20240919 18:00:00.000", "-3:00", "1726779600000"), ] if sys.version_info >= (3, 9): TIMESTAMP_TEST_CASES += [ - ('20240919 23:00:00.000', 'Europe/Warsaw', '1726779600000'), - ('20240920 00:00:00.000', 'UTC', '1726790400000'), - ('20240919 19:00:00.000', 'EST', '1726790400000') + ("20240919 23:00:00.000", "Europe/Warsaw", "1726779600000"), + ("20240920 00:00:00.000", "UTC", "1726790400000"), + ("20240919 19:00:00.000", "EST", "1726790400000"), ] -@pytest.mark.parametrize('time_str, time_shift, expected', TIMESTAMP_TEST_CASES) +@pytest.mark.parametrize("time_str, time_shift, expected", TIMESTAMP_TEST_CASES) def test_time_stamp_conversion(time_str, time_shift, expected): - _variables['RP_TIME_ZONE_OFFSET'] = time_shift + _variables["RP_TIME_ZONE_OFFSET"] = time_shift assert to_timestamp(time_str) == expected