Skip to content

Commit 496dfda

Browse files
Improve context storage to share data through whole run (#396)
* Improve storage in context * Fix in changelog * add run storage * Fix in changelog * Update CHANGELOG.rst * fix linter * fix linter * Improve _get_initial_value_from_context * Improve _get_initial_value_from_context * Rename store_key_in_storage * Improve store_key_in_storage * Added test to check storage capabilities * Fix in test * Added test to check storage capabilities * Added test to check storage capabilities * Added test to check storage capabilities * Improved managing of storages using ChainMap * Update version and changelog * Debugging * Improved managing of storages when dynamic env are not used * Fix lint warning * Fix lint warning * Fix lint warning * Fix lint warning * Updated Changelog * Remove dict() from a ChainMap in before_feature --------- Co-authored-by: Jose Miguel Rodriguez Naranjo <josemiguel.rodrigueznaranjo@telefonica.com>
1 parent ae618e9 commit 496dfda

File tree

5 files changed

+137
-17
lines changed

5 files changed

+137
-17
lines changed

CHANGELOG.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ v3.1.6
66

77
*Release date: In development*
88

9+
- Added `run_storage` to store information during the whole test execution
10+
- ChainMap storages (context.storage, context.feature_storage and context.run_storage)
11+
- In steps, be able to store values into desire storage by using [key], [FEATURE:key] and [RUN:key]
12+
913
v3.1.5
1014
------
1115

VERSION

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1-
3.1.6.dev0
1+
3.1.6.dev1
2+

toolium/behave/environment.py

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import logging
2020
import os
2121
import re
22+
import collections
2223

2324
from toolium.utils import dataset
2425
from toolium.config_files import ConfigFiles
@@ -50,6 +51,13 @@ def before_all(context):
5051
context.global_status = {'test_passed': True}
5152
create_and_configure_wrapper(context)
5253

54+
# Dictionary to store information during the whole test execution
55+
context.run_storage = dict()
56+
context.storage = context.run_storage
57+
58+
# Method in context to store values in context.storage, context.feature_storage or context.run_storage from steps
59+
context.store_key_in_storage = dataset.store_key_in_storage
60+
5361
# Behave dynamic environment
5462
context.dyn_env = DynamicEnvironment(logger=context.logger)
5563

@@ -72,10 +80,9 @@ def before_feature(context, feature):
7280
no_driver = 'no_driver' in feature.tags
7381
start_driver(context, no_driver)
7482

75-
# Dictionary to store information between steps
76-
context.storage = dict()
7783
# Dictionary to store information between features
7884
context.feature_storage = dict()
85+
context.storage = collections.ChainMap(context.feature_storage, context.run_storage)
7986

8087
# Behave dynamic environment
8188
context.dyn_env.get_steps_from_feature_description(feature.description)
@@ -129,8 +136,8 @@ def before_scenario(context, scenario):
129136

130137
context.logger.info("Running new scenario: %s", scenario.name)
131138

132-
# Make sure storage dict are empty in each scenario
133-
context.storage = dict()
139+
# Make sure context storage dict is empty in each scenario and merge with the rest of storages
140+
context.storage = collections.ChainMap(dict(), context.feature_storage, context.run_storage)
134141

135142
# Behave dynamic environment
136143
context.dyn_env.execute_before_scenario_steps(context)

toolium/test/utils/test_dataset_map_param_context.py

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,79 @@ class Context(object):
9090
assert expected_st == result_st
9191

9292

93+
def test_a_context_param_storage_and_run_storage():
94+
"""
95+
Verification of a mapped parameter as CONTEXT saved in storage and run storage
96+
"""
97+
class Context(object):
98+
pass
99+
context = Context()
100+
context.attribute = "attribute value"
101+
context.storage = {"storage_key": "storage entry value"}
102+
context.run_storage = {"storage_key": "run storage entry value"}
103+
dataset.behave_context = context
104+
105+
result_st = map_param("[CONTEXT:storage_key]")
106+
expected_st = "storage entry value"
107+
assert expected_st == result_st
108+
109+
110+
def test_store_key_in_feature_storage():
111+
"""
112+
Verification of method store_key_in_storage with a mapped parameter as FEATURE saved in feature storage
113+
"""
114+
class Context(object):
115+
pass
116+
context = Context()
117+
context.attribute = "attribute value"
118+
context.storage = {"storage_key": "storage entry value"}
119+
context.feature_storage = {}
120+
dataset.store_key_in_storage(context, "[FEATURE:storage_key]", "feature storage entry value")
121+
dataset.behave_context = context
122+
123+
result_st = map_param("[CONTEXT:storage_key]")
124+
expected_st = "feature storage entry value"
125+
assert expected_st == result_st
126+
127+
128+
def test_store_key_in_run_storage():
129+
"""
130+
Verification of method store_key_in_storage with a mapped parameter as RUN saved in run storage
131+
"""
132+
class Context(object):
133+
pass
134+
context = Context()
135+
context.attribute = "attribute value"
136+
context.storage = {"storage_key": "storage entry value"}
137+
context.run_storage = {}
138+
context.feature_storage = {}
139+
dataset.store_key_in_storage(context, "[RUN:storage_key]", "run storage entry value")
140+
dataset.behave_context = context
141+
142+
result_st = map_param("[CONTEXT:storage_key]")
143+
expected_st = "run storage entry value"
144+
assert expected_st == result_st
145+
146+
147+
def test_a_context_param_using_store_key_in_storage():
148+
"""
149+
Verification of a mapped parameter as CONTEXT saved in storage and run storage
150+
"""
151+
class Context(object):
152+
pass
153+
context = Context()
154+
context.attribute = "attribute value"
155+
context.feature_storage = {}
156+
context.storage = {"storage_key": "previous storage entry value"}
157+
dataset.store_key_in_storage(context, "[FEATURE:storage_key]", "feature storage entry value")
158+
dataset.store_key_in_storage(context, "[storage_key]", "storage entry value")
159+
dataset.behave_context = context
160+
161+
result_st = map_param("[CONTEXT:storage_key]")
162+
expected_st = "storage entry value"
163+
assert expected_st == result_st
164+
165+
93166
def test_a_context_param_without_storage_and_feature_storage():
94167
"""
95168
Verification of a mapped parameter as CONTEXT when before_feature and before_scenario have not been executed, so

toolium/utils/dataset.py

Lines changed: 47 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -682,19 +682,24 @@ def _get_initial_value_from_context(initial_key, context):
682682
:param context: behave context
683683
:return: mapped value
684684
"""
685-
context_storage = context.storage if hasattr(context, 'storage') else {}
686-
if hasattr(context, 'feature_storage'):
687-
# context.feature_storage is initialized only when before_feature method is called
688-
context_storage = collections.ChainMap(context.storage, context.feature_storage)
685+
# If dynamic env is not initialized, the storages are initialized if needed
686+
687+
context_storage = getattr(context, 'storage', {})
688+
run_storage = getattr(context, 'run_storage', {})
689+
feature_storage = getattr(context, 'feature_storage', {})
690+
691+
if not isinstance(context_storage, collections.ChainMap):
692+
context_storage = collections.ChainMap(context_storage, run_storage, feature_storage)
693+
689694
if initial_key in context_storage:
690-
value = context_storage[initial_key]
691-
elif hasattr(context, initial_key):
692-
value = getattr(context, initial_key)
693-
else:
694-
msg = f"'{initial_key}' key not found in context"
695-
logger.error(msg)
696-
raise Exception(msg)
697-
return value
695+
return context_storage[initial_key]
696+
697+
if hasattr(context, initial_key):
698+
return getattr(context, initial_key)
699+
700+
msg = f"'{initial_key}' key not found in context"
701+
logger.error(msg)
702+
raise Exception(msg)
698703

699704

700705
def get_message_property(param, language_terms, language_key):
@@ -813,3 +818,33 @@ def convert_file_to_base64(file_path):
813818
except Exception as e:
814819
raise Exception(f' ERROR - converting the "{file_path}" file to Base64...: {e}')
815820
return file_content
821+
822+
823+
def store_key_in_storage(context, key, value):
824+
"""
825+
Store values in context.storage, context.feature_storage or context.run_storage,
826+
using [key], [FEATURE:key] OR [RUN:key] from steps.
827+
context.storage is also updated with given key,value
828+
By default, values are stored in context.storage.
829+
830+
:param key: key to store the value in proper storage
831+
:param value: value to store in key
832+
:param context: behave context
833+
:return:
834+
"""
835+
clean_key = re.sub(r'[\[\]]', '', key)
836+
if ":" in clean_key:
837+
context_type = clean_key.split(":")[0]
838+
context_key = clean_key.split(":")[1]
839+
acccepted_context_types = ["FEATURE", "RUN"]
840+
assert context_type in acccepted_context_types, (f"Invalid key: {context_key}. "
841+
f"Accepted keys: {acccepted_context_types}")
842+
if context_type == "RUN":
843+
context.run_storage[context_key] = value
844+
elif context_type == "FEATURE":
845+
context.feature_storage[context_key] = value
846+
# If dynamic env is not initialized linked or key exists in context.storage, the value is updated in it
847+
if hasattr(context.storage, context_key) or not isinstance(context.storage, collections.ChainMap):
848+
context.storage[context_key] = value
849+
else:
850+
context.storage[clean_key] = value

0 commit comments

Comments
 (0)