Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,9 @@ curl -v http://127.0.0.1:49152/wait-completion?testRunId=${{ env.TMS_TEST_RUN_ID
```
5. You can read the sync-storage logs from the service.log file.

## Legacy behavior

(Hint) To enable legacy behavior without sync-storage you can use flag `legacyWorkflow=true` (`TMS_LEGACY_WORKFLOW`)

### General

Expand Down
2 changes: 1 addition & 1 deletion testit-adapter-behave/setup.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from setuptools import find_packages, setup

VERSION = "4.1.7"
VERSION = "4.1.8"

setup(
name='testit-adapter-behave',
Expand Down
2 changes: 1 addition & 1 deletion testit-adapter-nose/setup.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from setuptools import setup, find_packages

VERSION = "4.1.7"
VERSION = "4.1.8"

setup(
name='testit-adapter-nose',
Expand Down
2 changes: 1 addition & 1 deletion testit-adapter-pytest/setup.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from setuptools import find_packages, setup

VERSION = "4.1.7"
VERSION = "4.1.8"

setup(
name='testit-adapter-pytest',
Expand Down
2 changes: 1 addition & 1 deletion testit-adapter-robotframework/setup.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from setuptools import find_packages, setup

VERSION = "4.1.7"
VERSION = "4.1.8"

setup(
name='testit-adapter-robotframework',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,25 @@ def order(self):
return asdict(self)


def _clean_value(value):
return str(value).replace("'", "").replace('"', '')


def _parse_testit_tag(tag):
tag = str(tag).strip()
if not tag.lower().startswith('testit.'):
return None, None

body = tag.split('.', 1)[-1]
separator = ':' if ':' in body else ('=' if '=' in body else None)
if not separator:
logger.error(f"[TestIt] Wrong tag format: {tag}")
return None, None

attr, value = body.split(separator, 1)
return attr.strip().lower(), value.strip()


@s
class StepResult(Default):
title = attrib(default='')
Expand Down Expand Up @@ -104,17 +123,16 @@ def add_attributes(self, attrs):
self.template = attrs['template']
self.classname = attrs['longname'].split('.')[-2]
for tag in attrs['tags']:
if tag.lower().startswith('testit.'):
attr = re.findall(r'(?<=\.).*?(?=:)', tag)[0].strip().lower()
value = tag.split(':', 1)[-1].strip()
attr, value = _parse_testit_tag(tag)
if attr:
if attr == 'externalid':
self.externalID = str(value).replace("'", "").replace('"', '')
self.externalID = _clean_value(value)
elif attr == 'displayname':
self.autoTestName = str(value).replace("'", "").replace('"', '')
self.autoTestName = _clean_value(value)
elif attr == 'title':
self.title = str(value).replace("'", "").replace('"', '')
self.title = _clean_value(value)
elif attr == 'description':
self.description = str(value).replace("'", "").replace('"', '')
self.description = _clean_value(value)
elif attr == 'workitemsid' or attr == 'workitemsids':
value = ast.literal_eval(value)
if isinstance(value, (str, int)):
Expand Down Expand Up @@ -153,9 +171,9 @@ def add_attributes(self, attrs):
elif isinstance(value, list):
self.tags.extend([str(item) for item in value if isinstance(item, (str, int))])
elif attr == 'namespace':
self.namespace = str(value).replace("'", "").replace('"', '')
self.namespace = _clean_value(value)
elif attr == 'classname':
self.classname = str(value).replace("'", "").replace('"', '')
self.classname = _clean_value(value)
else:
logger.error(f"[TestIt] Unknown attribute: {attr}")
if not self.externalID:
Expand Down
43 changes: 42 additions & 1 deletion testit-adapter-robotframework/tests/test_listeners.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,19 @@
import re
import sys
import types
from pytest_mock import MockerFixture
from unittest.mock import MagicMock

if "robot.api" not in sys.modules:
robot_module = types.ModuleType("robot")
robot_api_module = types.ModuleType("robot.api")
robot_api_module.logger = MagicMock()
robot_module.api = robot_api_module
sys.modules["robot"] = robot_module
sys.modules["robot.api"] = robot_api_module

from testit_adapter_robotframework.models import Autotest


class TestAutotestAdapter:
def test_parse_arguments(self, mocker: MockerFixture):
Expand Down Expand Up @@ -44,4 +56,33 @@ def parse_arguments(args):
mocker.call("${var2}"),
mocker.call("${var3_with_long_value}")
]
mock_builtin_instance.get_variable_value.assert_has_calls(expected_calls, any_order=True)
mock_builtin_instance.get_variable_value.assert_has_calls(expected_calls, any_order=True)


class TestAutotestTagParsing:
@staticmethod
def _build_attrs(tags):
return {
"originalname": "HeaderName",
"doc": "Doc",
"template": None,
"longname": "Suite.HeaderName",
"tags": tags,
}

def test_display_name_does_not_override_title_when_title_absent(self):
autotest = Autotest(autoTestName="Initial")
autotest.add_attributes(self._build_attrs(["testit.displayName:DisplayName"]))

assert autotest.autoTestName == "DisplayName"
assert autotest.title == "HeaderName"

def test_title_has_priority_over_display_name(self):
autotest = Autotest(autoTestName="Initial")
autotest.add_attributes(self._build_attrs([
"testit.displayName:DisplayName",
"testit.title:CardTitle",
]))

assert autotest.autoTestName == "DisplayName"
assert autotest.title == "CardTitle"
2 changes: 1 addition & 1 deletion testit-python-commons/setup.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from setuptools import find_packages, setup

VERSION = "4.1.7"
VERSION = "4.1.8"

setup(
name='testit-python-commons',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,9 @@ def __check_properties(cls, properties: dict):
# import realtime false by default
properties[PropertiesNames.SYNC_STORAGE_PORT] = '49152'

if not cls.__check_property_value(properties.get(PropertiesNames.LEGACY_WORKFLOW)):
properties[PropertiesNames.LEGACY_WORKFLOW] = 'false'

@classmethod
def __load_file_properties_from_toml(cls) -> dict:
properties = {}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ def __init__(self, app_properties: dict):
self.__automatic_updation_links_to_test_cases = Utils.convert_value_str_to_bool(
app_properties.get(PropertiesNames.AUTOMATIC_UPDATION_LINKS_TO_TEST_CASES).lower())
self.__sync_storage_port = app_properties.get(PropertiesNames.SYNC_STORAGE_PORT)
self.__legacy_workflow = Utils.convert_value_str_to_bool(
app_properties.get(PropertiesNames.LEGACY_WORKFLOW).lower())

@adapter_logger
def get_url(self):
Expand Down Expand Up @@ -67,3 +69,6 @@ def get_automatic_updation_links_to_test_cases(self) -> bool:

def get_sync_storage_port(self) -> str:
return self.__sync_storage_port

def is_legacy_workflow(self) -> bool:
return self.__legacy_workflow
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ class PropertiesNames:
AUTOMATIC_UPDATION_LINKS_TO_TEST_CASES = 'automaticupdationlinkstotestcases'
IMPORT_REALTIME = 'importrealtime'
SYNC_STORAGE_PORT = 'syncstorageport'
LEGACY_WORKFLOW = 'legacyworkflow'

ENV_TO_PROPERTY = {
'TMS_URL': PropertiesNames.URL,
Expand All @@ -29,6 +30,7 @@ class PropertiesNames:
'TMS_AUTOMATIC_UPDATION_LINKS_TO_TEST_CASES': PropertiesNames.AUTOMATIC_UPDATION_LINKS_TO_TEST_CASES,
'TMS_IMPORT_REALTIME': PropertiesNames.IMPORT_REALTIME,
'TMS_SYNC_STORAGE_PORT': PropertiesNames.SYNC_STORAGE_PORT,
'TMS_LEGACY_WORKFLOW': PropertiesNames.LEGACY_WORKFLOW,
}

OPTION_TO_PROPERTY = {
Expand All @@ -45,4 +47,5 @@ class PropertiesNames:
'set_automatic_updation_links_to_test_cases': PropertiesNames.AUTOMATIC_UPDATION_LINKS_TO_TEST_CASES,
'set_import_realtime': PropertiesNames.IMPORT_REALTIME,
'set_sync_storage_port': PropertiesNames.SYNC_STORAGE_PORT,
'set_legacy_workflow': PropertiesNames.LEGACY_WORKFLOW,
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,5 @@
from pluggy import HookimplMarker

from testit_python_commons.services.adapter_manager import AdapterManager
from testit_python_commons.services.fixture_manager import FixtureManager
from testit_python_commons.services.plugin_manager import TmsPluginManager
from testit_python_commons.services.step_manager import StepManager
from testit_python_commons.services.utils import Utils
from .sync_storage import (SyncStorageRunner)

__all__ = [
"AdapterManager",
"TmsPluginManager",
Expand All @@ -18,3 +11,31 @@
]

hookimpl = HookimplMarker("testit")


def __getattr__(name):
if name == "AdapterManager":
from testit_python_commons.services.adapter_manager import AdapterManager

return AdapterManager
if name == "TmsPluginManager":
from testit_python_commons.services.plugin_manager import TmsPluginManager

return TmsPluginManager
if name == "FixtureManager":
from testit_python_commons.services.fixture_manager import FixtureManager

return FixtureManager
if name == "StepManager":
from testit_python_commons.services.step_manager import StepManager

return StepManager
if name == "Utils":
from testit_python_commons.services.utils import Utils

return Utils
if name == "SyncStorageRunner":
from testit_python_commons.services.sync_storage import SyncStorageRunner

return SyncStorageRunner
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,9 @@ def __init__(

# Sync Storage integration
self.__sync_storage_runner = None
legacy_workflow_enabled = client_configuration.is_legacy_workflow() is True
# Initialize Sync Storage if available and enabled
if SYNC_STORAGE_AVAILABLE:
if SYNC_STORAGE_AVAILABLE and not legacy_workflow_enabled:
self.__sync_storage_runner = self._initialize_sync_storage(
client_configuration
)
Expand Down Expand Up @@ -146,11 +147,11 @@ def write_test(self, test_result: TestResult) -> None:

# for realtime false
# if
logging.warning("Is already in progress: " + str(self.__is_already_in_progress()))
logging.debug("Is already in progress: " + str(self.__is_already_in_progress()))
# Handle Sync Storage integration if available
# Check if current worker is master and no test is in progress
if self.__is_active_syncstorage_instance() and self.__is_master_and_no_in_progress():
logging.warning(f"Outcome: {test_result.get_outcome()}")
logging.debug(f"Outcome: {test_result.get_outcome()}")
is_ok = self.on_master_no_already_in_progress(test_result)
if is_ok:
return
Expand All @@ -167,9 +168,9 @@ def on_master_no_already_in_progress(self, test_result: TestResult) -> bool:
tr_cut_api_model = SyncStorageRunner.test_result_to_test_result_cut_api_model(
test_result, project_id
)
logging.warning("Set as in progress status_code, auto_test_external_id: "
logging.debug("Set as in progress status_code, auto_test_external_id: "
+ tr_cut_api_model.status_code + " " + tr_cut_api_model.auto_test_external_id)
logging.warning("Started_on: " + str(test_result.get_started_on()))
logging.debug("Started_on: " + str(test_result.get_started_on()))

# Send test result to Sync Storage
success = self.__sync_storage_runner.send_in_progress_test_result(
Expand All @@ -184,7 +185,7 @@ def on_master_no_already_in_progress(self, test_result: TestResult) -> bool:

try:
# Write test result normally (mark as IN PROGRESS in Test IT)
logging.warning("Write internally, change status to in progress")
logging.debug("Write internally, change status to in progress")
test_result.set_outcome(IN_PROGRESS_LITERAL)
self._write_test_realtime_internal(test_result)
return True
Expand All @@ -199,7 +200,7 @@ def on_master_no_already_in_progress(self, test_result: TestResult) -> bool:
@adapter_logger
def __write_test_realtime(self, test_result: TestResult) -> None:

logging.warning("Is already in progress: " + str(self.__is_already_in_progress()))
logging.debug("Is already in progress: " + str(self.__is_already_in_progress()))

# Handle Sync Storage integration if available
if self.__is_active_syncstorage_instance() and self.__is_master_and_no_in_progress():
Expand Down Expand Up @@ -284,7 +285,7 @@ def on_running_started(self):

@adapter_logger
def set_worker_status(self, status: str):
logging.info(f"Set worker_status to {status}")
logging.debug(f"Set worker_status to {status}")
if not self.__is_active_syncstorage_instance():
return

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,18 @@
import logging
import re
import warnings
from typing import TYPE_CHECKING

<<<<<<< feat/small-fixes
=======
from testit_python_commons.models.link import Link
from testit_python_commons.models.link_type import LinkType
>>>>>>> main
from testit_python_commons.services.logger import adapter_logger

if TYPE_CHECKING:
from testit_python_commons.models.link import Link


class Utils:
@staticmethod
Expand Down Expand Up @@ -94,7 +101,9 @@ def collect_parameters_in_string_attribute(attribute: str, all_parameters: dict)

@staticmethod
@adapter_logger
def convert_link_dict_to_link_model(link_dict: dict) -> Link:
def convert_link_dict_to_link_model(link_dict: dict) -> "Link":
from testit_python_commons.models.link import Link

link_model = Link()
link_model.set_url(link_dict['url'])

Expand Down
27 changes: 26 additions & 1 deletion testit-python-commons/tests/services/test_adapter_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -190,4 +190,29 @@ def test_create_attachment_with_name(self, adapter_manager, mock_api_client_work
mock_utils_convert.assert_called_once_with(file_body)
mock_api_client_worker.load_attachments.assert_called_once_with((expected_file_path,))
mock_os_remove.assert_called_once_with(expected_file_path)
assert attachment_id == expected_attachment_id
assert attachment_id == expected_attachment_id

def test_init_does_not_start_sync_storage_in_legacy_workflow(
self,
mocker,
mock_adapter_config,
mock_client_config,
mock_fixture_manager):
mocker.patch(
'testit_python_commons.services.adapter_manager.ApiClientWorker',
autospec=True,
)
mock_client_config.is_legacy_workflow.return_value = True
init_sync_storage = mocker.patch.object(
AdapterManager,
"_initialize_sync_storage",
return_value=None,
)

AdapterManager(
adapter_configuration=mock_adapter_config,
client_configuration=mock_client_config,
fixture_manager=mock_fixture_manager,
)

init_sync_storage.assert_not_called()
Loading
Loading