diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 8599d043..2208938a 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -6,6 +6,10 @@ v3.1.6 *Release date: In development* +- Added `run_storage` to store information during the whole test execution +- ChainMap storages (context.storage, context.feature_storage and context.run_storage) +- In steps, be able to store values into desire storage by using [key], [FEATURE:key] and [RUN:key] + v3.1.5 ------ diff --git a/VERSION b/VERSION index 63300303..488d53f0 100644 --- a/VERSION +++ b/VERSION @@ -1 +1,2 @@ -3.1.6.dev0 +3.1.6.dev1 + diff --git a/toolium/behave/environment.py b/toolium/behave/environment.py index b6f66f09..57ba8f46 100644 --- a/toolium/behave/environment.py +++ b/toolium/behave/environment.py @@ -19,6 +19,7 @@ import logging import os import re +import collections from toolium.utils import dataset from toolium.config_files import ConfigFiles @@ -50,6 +51,13 @@ def before_all(context): context.global_status = {'test_passed': True} create_and_configure_wrapper(context) + # Dictionary to store information during the whole test execution + context.run_storage = dict() + context.storage = context.run_storage + + # Method in context to store values in context.storage, context.feature_storage or context.run_storage from steps + context.store_key_in_storage = dataset.store_key_in_storage + # Behave dynamic environment context.dyn_env = DynamicEnvironment(logger=context.logger) @@ -72,10 +80,9 @@ def before_feature(context, feature): no_driver = 'no_driver' in feature.tags start_driver(context, no_driver) - # Dictionary to store information between steps - context.storage = dict() # Dictionary to store information between features context.feature_storage = dict() + context.storage = collections.ChainMap(context.feature_storage, context.run_storage) # Behave dynamic environment context.dyn_env.get_steps_from_feature_description(feature.description) @@ -129,8 +136,8 @@ def before_scenario(context, scenario): context.logger.info("Running new scenario: %s", scenario.name) - # Make sure storage dict are empty in each scenario - context.storage = dict() + # Make sure context storage dict is empty in each scenario and merge with the rest of storages + context.storage = collections.ChainMap(dict(), context.feature_storage, context.run_storage) # Behave dynamic environment context.dyn_env.execute_before_scenario_steps(context) diff --git a/toolium/test/utils/test_dataset_map_param_context.py b/toolium/test/utils/test_dataset_map_param_context.py index 39873535..222d91ff 100644 --- a/toolium/test/utils/test_dataset_map_param_context.py +++ b/toolium/test/utils/test_dataset_map_param_context.py @@ -90,6 +90,79 @@ class Context(object): assert expected_st == result_st +def test_a_context_param_storage_and_run_storage(): + """ + Verification of a mapped parameter as CONTEXT saved in storage and run storage + """ + class Context(object): + pass + context = Context() + context.attribute = "attribute value" + context.storage = {"storage_key": "storage entry value"} + context.run_storage = {"storage_key": "run storage entry value"} + dataset.behave_context = context + + result_st = map_param("[CONTEXT:storage_key]") + expected_st = "storage entry value" + assert expected_st == result_st + + +def test_store_key_in_feature_storage(): + """ + Verification of method store_key_in_storage with a mapped parameter as FEATURE saved in feature storage + """ + class Context(object): + pass + context = Context() + context.attribute = "attribute value" + context.storage = {"storage_key": "storage entry value"} + context.feature_storage = {} + dataset.store_key_in_storage(context, "[FEATURE:storage_key]", "feature storage entry value") + dataset.behave_context = context + + result_st = map_param("[CONTEXT:storage_key]") + expected_st = "feature storage entry value" + assert expected_st == result_st + + +def test_store_key_in_run_storage(): + """ + Verification of method store_key_in_storage with a mapped parameter as RUN saved in run storage + """ + class Context(object): + pass + context = Context() + context.attribute = "attribute value" + context.storage = {"storage_key": "storage entry value"} + context.run_storage = {} + context.feature_storage = {} + dataset.store_key_in_storage(context, "[RUN:storage_key]", "run storage entry value") + dataset.behave_context = context + + result_st = map_param("[CONTEXT:storage_key]") + expected_st = "run storage entry value" + assert expected_st == result_st + + +def test_a_context_param_using_store_key_in_storage(): + """ + Verification of a mapped parameter as CONTEXT saved in storage and run storage + """ + class Context(object): + pass + context = Context() + context.attribute = "attribute value" + context.feature_storage = {} + context.storage = {"storage_key": "previous storage entry value"} + dataset.store_key_in_storage(context, "[FEATURE:storage_key]", "feature storage entry value") + dataset.store_key_in_storage(context, "[storage_key]", "storage entry value") + dataset.behave_context = context + + result_st = map_param("[CONTEXT:storage_key]") + expected_st = "storage entry value" + assert expected_st == result_st + + def test_a_context_param_without_storage_and_feature_storage(): """ Verification of a mapped parameter as CONTEXT when before_feature and before_scenario have not been executed, so diff --git a/toolium/utils/dataset.py b/toolium/utils/dataset.py index 2750f81c..42774160 100644 --- a/toolium/utils/dataset.py +++ b/toolium/utils/dataset.py @@ -682,19 +682,24 @@ def _get_initial_value_from_context(initial_key, context): :param context: behave context :return: mapped value """ - context_storage = context.storage if hasattr(context, 'storage') else {} - if hasattr(context, 'feature_storage'): - # context.feature_storage is initialized only when before_feature method is called - context_storage = collections.ChainMap(context.storage, context.feature_storage) + # If dynamic env is not initialized, the storages are initialized if needed + + context_storage = getattr(context, 'storage', {}) + run_storage = getattr(context, 'run_storage', {}) + feature_storage = getattr(context, 'feature_storage', {}) + + if not isinstance(context_storage, collections.ChainMap): + context_storage = collections.ChainMap(context_storage, run_storage, feature_storage) + if initial_key in context_storage: - value = context_storage[initial_key] - elif hasattr(context, initial_key): - value = getattr(context, initial_key) - else: - msg = f"'{initial_key}' key not found in context" - logger.error(msg) - raise Exception(msg) - return value + return context_storage[initial_key] + + if hasattr(context, initial_key): + return getattr(context, initial_key) + + msg = f"'{initial_key}' key not found in context" + logger.error(msg) + raise Exception(msg) def get_message_property(param, language_terms, language_key): @@ -813,3 +818,33 @@ def convert_file_to_base64(file_path): except Exception as e: raise Exception(f' ERROR - converting the "{file_path}" file to Base64...: {e}') return file_content + + +def store_key_in_storage(context, key, value): + """ + Store values in context.storage, context.feature_storage or context.run_storage, + using [key], [FEATURE:key] OR [RUN:key] from steps. + context.storage is also updated with given key,value + By default, values are stored in context.storage. + + :param key: key to store the value in proper storage + :param value: value to store in key + :param context: behave context + :return: + """ + clean_key = re.sub(r'[\[\]]', '', key) + if ":" in clean_key: + context_type = clean_key.split(":")[0] + context_key = clean_key.split(":")[1] + acccepted_context_types = ["FEATURE", "RUN"] + assert context_type in acccepted_context_types, (f"Invalid key: {context_key}. " + f"Accepted keys: {acccepted_context_types}") + if context_type == "RUN": + context.run_storage[context_key] = value + elif context_type == "FEATURE": + context.feature_storage[context_key] = value + # If dynamic env is not initialized linked or key exists in context.storage, the value is updated in it + if hasattr(context.storage, context_key) or not isinstance(context.storage, collections.ChainMap): + context.storage[context_key] = value + else: + context.storage[clean_key] = value