holado 0.8.4__py3-none-any.whl → 0.9.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of holado might be problematic. Click here for more details.
- holado/common/context/session_context.py +6 -5
- {holado-0.8.4.dist-info → holado-0.9.1.dist-info}/METADATA +1 -1
- {holado-0.8.4.dist-info → holado-0.9.1.dist-info}/RECORD +56 -41
- holado_core/common/block/base.py +3 -3
- holado_core/common/resource/persisted_data_manager.py +10 -10
- holado_core/common/resource/resource_manager.py +34 -37
- holado_core/common/resource/table_data_manager.py +110 -0
- holado_core/common/tools/path_manager.py +31 -6
- holado_django/server/django_projects/rest_api/rest_api/urls.py +1 -1
- holado_docker/tests/behave/steps/tools/docker_controller/client_steps.py +2 -2
- holado_docker/tools/docker_controller/client/rest/docker_controller_client.py +38 -1
- holado_docker/tools/docker_controller/server/run_docker_controller_in_docker.sh +3 -3
- holado_examples/tests/behave/testing_solution/environment.py +3 -3
- holado_examples/tests/behave/testing_solution/src/context/session_context.py +2 -2
- holado_examples/tests/behave/testing_solution/steps/config_steps.py +1 -1
- holado_multitask/multithreading/loopthread.py +4 -4
- holado_python/common/tools/datetime.py +37 -16
- holado_python/standard_library/ssl/resources/certificates/tcpbin.crt +16 -16
- holado_python/standard_library/ssl/resources/certificates/tcpbin.key +26 -26
- holado_python/tests/behave/steps/standard_library/datetime_steps.py +2 -2
- holado_rabbitmq/tools/rabbitmq/rabbitmq_manager.py +3 -3
- holado_report/__init__.py +1 -0
- holado_report/campaign/campaign_manager.py +194 -0
- holado_report/report/builders/detailed_scenario_failed_report_builder.py +11 -7
- holado_report/report/builders/failure_report_builder.py +4 -3
- holado_report/report/builders/short_scenario_failed_report_builder.py +6 -5
- holado_report/report/builders/summary_report_builder.py +1 -1
- holado_report/report/builders/summary_scenario_failed_report_builder.py +6 -5
- holado_report/report/builders/summary_scenario_report_builder.py +12 -11
- holado_report/report/execution_historic.py +4 -2
- holado_report/report/report_manager.py +65 -13
- holado_rest/api/rest/rest_client.py +5 -1
- holado_system/system/filesystem/file.py +5 -2
- holado_test/__init__.py +3 -0
- holado_test/common/context/feature_context.py +3 -3
- holado_test/common/context/scenario_context.py +3 -3
- holado_test/common/context/step_context.py +3 -3
- holado_test/test_server/client/rest/test_server_client.py +117 -0
- holado_test/test_server/server/Dockerfile +70 -0
- holado_test/test_server/server/core/server_context.py +42 -0
- holado_test/test_server/server/core/server_manager.py +54 -0
- holado_test/test_server/server/requirements.txt +2 -0
- holado_test/test_server/server/rest/README +2 -0
- holado_test/test_server/server/rest/api/__init__.py +23 -0
- holado_docker/tools/docker_controller/docker_controller_manager.py → holado_test/test_server/server/rest/api/campaign/__init__.py +10 -28
- holado_test/test_server/server/rest/api/campaign/scenario.py +33 -0
- holado_test/test_server/server/rest/initialize_holado.py +72 -0
- holado_test/test_server/server/rest/logging.conf +50 -0
- holado_test/test_server/server/rest/openapi.yaml +46 -0
- holado_test/test_server/server/rest/run.py +40 -0
- holado_test/test_server/server/run_test_server_in_docker.sh +101 -0
- holado_value/common/tools/unique_value_manager.py +2 -1
- test_holado/environment.py +1 -1
- test_holado/logging.conf +1 -0
- {holado-0.8.4.dist-info → holado-0.9.1.dist-info}/WHEEL +0 -0
- {holado-0.8.4.dist-info → holado-0.9.1.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
|
|
2
|
+
#################################################
|
|
3
|
+
# HolAdo (Holistic Automation do)
|
|
4
|
+
#
|
|
5
|
+
# (C) Copyright 2021-2025 by Eric Klumpp
|
|
6
|
+
#
|
|
7
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
8
|
+
#
|
|
9
|
+
# The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
10
|
+
|
|
11
|
+
# The Software is provided “as is”, without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose and noninfringement. In no event shall the authors or copyright holders be liable for any claim, damages or other liability, whether in an action of contract, tort or otherwise, arising from, out of or in connection with the software or the use or other dealings in the Software.
|
|
12
|
+
#################################################
|
|
13
|
+
|
|
14
|
+
from holado.common.context.session_context import SessionContext
|
|
15
|
+
import logging
|
|
16
|
+
from holado_core.common.resource.table_data_manager import TableDataManager
|
|
17
|
+
from holado_python.common.tools.datetime import DateTime, TIMEZONE_LOCAL
|
|
18
|
+
import os
|
|
19
|
+
from holado_system.system.filesystem.file import File
|
|
20
|
+
import re
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
logger = logging.getLogger(__name__)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class CampaignManager(object):
|
|
28
|
+
""" Manage all campaigns
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
def __init__(self, db_name="campaigns"):
|
|
32
|
+
super().__init__()
|
|
33
|
+
|
|
34
|
+
self.__db_name = db_name
|
|
35
|
+
self.__resource_manager = None
|
|
36
|
+
|
|
37
|
+
self.__campaigns_table_manager = TableDataManager('campaign', 'campaigns', self.__get_campaigns_table_sql_create(), db_name=self.__db_name)
|
|
38
|
+
self.__campaign_scenarios_table_manager = TableDataManager('campaign scenario', 'campaign_scenarios', self.__get_campaign_scenarios_table_sql_create(), db_name=self.__db_name)
|
|
39
|
+
|
|
40
|
+
def initialize(self, resource_manager):
|
|
41
|
+
self.__resource_manager = resource_manager
|
|
42
|
+
|
|
43
|
+
self.__campaigns_table_manager.initialize(resource_manager)
|
|
44
|
+
self.__campaigns_table_manager.ensure_db_exists()
|
|
45
|
+
self.__campaign_scenarios_table_manager.initialize(resource_manager)
|
|
46
|
+
self.__campaign_scenarios_table_manager.ensure_db_exists()
|
|
47
|
+
|
|
48
|
+
def __get_db_client(self):
|
|
49
|
+
return self.__resource_manager.get_db_client(self.__db_name)
|
|
50
|
+
|
|
51
|
+
def __get_campaigns_table_sql_create(self):
|
|
52
|
+
return """CREATE TABLE campaigns (
|
|
53
|
+
id INTEGER PRIMARY KEY,
|
|
54
|
+
name TEXT NOT NULL,
|
|
55
|
+
report_path TEXT NOT NULL
|
|
56
|
+
)"""
|
|
57
|
+
|
|
58
|
+
def __get_campaign_scenarios_table_sql_create(self):
|
|
59
|
+
return """CREATE TABLE campaign_scenarios (
|
|
60
|
+
id INTEGER PRIMARY KEY,
|
|
61
|
+
campaign_id INTEGER NOT NULL,
|
|
62
|
+
name TEXT NOT NULL,
|
|
63
|
+
report_path TEXT,
|
|
64
|
+
status TEXT,
|
|
65
|
+
status_at TEXT,
|
|
66
|
+
details TEXT
|
|
67
|
+
)"""
|
|
68
|
+
|
|
69
|
+
def update_stored_campaigns(self):
|
|
70
|
+
""" Update stored reports in DB with new campaigns
|
|
71
|
+
"""
|
|
72
|
+
# Get report paths of campaigns to import
|
|
73
|
+
dt_last_camp = self.__get_last_campaign_scenario_status_datetime()
|
|
74
|
+
report_paths = self.__get_campaigns_report_paths(since_datetime=dt_last_camp)
|
|
75
|
+
|
|
76
|
+
# Sort reports in time order
|
|
77
|
+
report_paths = sorted(report_paths, key=lambda p: os.path.getmtime(os.path.join(p, 'report_summary_scenario_all.txt')))
|
|
78
|
+
logger.info(f"reports to import: {report_paths}", msg_size_limit=None)
|
|
79
|
+
|
|
80
|
+
# Import reports
|
|
81
|
+
for report_path in report_paths:
|
|
82
|
+
self.import_campaign_reports(report_path)
|
|
83
|
+
|
|
84
|
+
def __get_campaigns_report_paths(self, since_datetime):
|
|
85
|
+
reports_path = SessionContext.instance().path_manager.get_reports_path()
|
|
86
|
+
file_paths = SessionContext.instance().path_manager.find_files(reports_path, subdir_relative_path='report_summary_scenario_all.txt', since_datetime=since_datetime)
|
|
87
|
+
return [os.path.dirname(p) for p in file_paths]
|
|
88
|
+
|
|
89
|
+
def __get_last_campaign_scenario_status_datetime(self):
|
|
90
|
+
""" From stored campaigns, return the datetime of the last scenario with an execution status
|
|
91
|
+
"""
|
|
92
|
+
client = self.__get_db_client()
|
|
93
|
+
|
|
94
|
+
query_str = f'''
|
|
95
|
+
SELECT status_at
|
|
96
|
+
FROM campaign_scenarios
|
|
97
|
+
ORDER BY status_at DESC
|
|
98
|
+
LIMIT 1
|
|
99
|
+
'''
|
|
100
|
+
res_dict_list = client.execute(query_str, result_as_dict_list=True, as_generator=False)
|
|
101
|
+
|
|
102
|
+
status_dt_str = res_dict_list[0]['status_at'] if res_dict_list else None
|
|
103
|
+
status_dt = DateTime.str_2_datetime(status_dt_str, tz=TIMEZONE_LOCAL) if status_dt_str else None
|
|
104
|
+
return status_dt
|
|
105
|
+
|
|
106
|
+
def import_campaign_reports(self, report_path):
|
|
107
|
+
""" Import reports of a campaign
|
|
108
|
+
@param report_path Path to the campaign report
|
|
109
|
+
"""
|
|
110
|
+
logger.info(f"Import campaign report '{report_path}'")
|
|
111
|
+
|
|
112
|
+
# Add campaign
|
|
113
|
+
camp_name = os.path.basename(report_path)
|
|
114
|
+
camp_id = self.add_campaign_if_needed(camp_name, report_path)
|
|
115
|
+
|
|
116
|
+
# Import scenario status
|
|
117
|
+
self.__import_campaign_report_summary_scenario_all(report_path, camp_id)
|
|
118
|
+
|
|
119
|
+
def get_scenario_history(self, scenario_name=None, size=None):
|
|
120
|
+
client = self.__get_db_client()
|
|
121
|
+
placeholder = client._get_sql_placeholder()
|
|
122
|
+
|
|
123
|
+
# Get data from DB
|
|
124
|
+
where_clause = ""
|
|
125
|
+
where_data = []
|
|
126
|
+
if scenario_name is not None:
|
|
127
|
+
where_clause = f"where name = {placeholder}"
|
|
128
|
+
where_data.append(scenario_name)
|
|
129
|
+
|
|
130
|
+
query_str = f'''
|
|
131
|
+
SELECT *
|
|
132
|
+
FROM campaign_scenarios
|
|
133
|
+
{where_clause}
|
|
134
|
+
ORDER BY name, status_at DESC
|
|
135
|
+
'''
|
|
136
|
+
camp_scenarios_gen = client.execute(query_str, *where_data, result_as_dict_list=True, as_generator=True)
|
|
137
|
+
|
|
138
|
+
# Build result
|
|
139
|
+
res = []
|
|
140
|
+
cur_scenario_name = None
|
|
141
|
+
cur_scenario_statuses = None
|
|
142
|
+
for cs in camp_scenarios_gen:
|
|
143
|
+
# Manage new scenario
|
|
144
|
+
if cur_scenario_name is None or cur_scenario_name != cs['name']:
|
|
145
|
+
cur_scenario_statuses = []
|
|
146
|
+
cur_scenario_name = cs['name']
|
|
147
|
+
res.append({'name':cur_scenario_name, 'statuses':cur_scenario_statuses})
|
|
148
|
+
|
|
149
|
+
# Add campaign info for this scenario execution if size limit is not reached
|
|
150
|
+
if size is None or len(cur_scenario_statuses) < size:
|
|
151
|
+
cur_scenario_statuses.append({'at':cs['status_at'], 'status':cs['status']})
|
|
152
|
+
|
|
153
|
+
return res
|
|
154
|
+
|
|
155
|
+
def add_campaign_if_needed(self, name, report_path):
|
|
156
|
+
filter_data = {'report_path': report_path}
|
|
157
|
+
if not self.__campaigns_table_manager.has_data(filter_data):
|
|
158
|
+
self.__campaigns_table_manager.add_data(filter_data, {'name': name})
|
|
159
|
+
camp = self.__campaigns_table_manager.get_data(filter_data)
|
|
160
|
+
return camp['id']
|
|
161
|
+
|
|
162
|
+
def update_or_add_campaign_scenario(self, campaign_id, name, *, report_path=None, status=None, status_at_str=None, details=None):
|
|
163
|
+
filter_data = {'campaign_id': campaign_id, 'name': name}
|
|
164
|
+
data = {}
|
|
165
|
+
if report_path is not None:
|
|
166
|
+
data['report_path'] = report_path
|
|
167
|
+
if status is not None:
|
|
168
|
+
data['status'] = status
|
|
169
|
+
if status_at_str is not None:
|
|
170
|
+
data['status_at'] = status_at_str
|
|
171
|
+
if details is not None:
|
|
172
|
+
data['details'] = details
|
|
173
|
+
|
|
174
|
+
self.__campaign_scenarios_table_manager.update_or_add_data(filter_data, data)
|
|
175
|
+
# camp_sce = self.__campaign_scenarios_table_manager.get_data(filter_data)
|
|
176
|
+
# return camp_sce['id']
|
|
177
|
+
|
|
178
|
+
def __import_campaign_report_summary_scenario_all(self, report_path, camp_id):
|
|
179
|
+
file_path = os.path.join(report_path, 'report_summary_scenario_all.txt')
|
|
180
|
+
lines = File(file_path, mode='rt').readlines(strip_newline=True)
|
|
181
|
+
|
|
182
|
+
for line in lines:
|
|
183
|
+
parts = line.split(' - ')
|
|
184
|
+
status_dt_str = parts[0]
|
|
185
|
+
scenario_name = parts[1]
|
|
186
|
+
status_info = parts[-1]
|
|
187
|
+
|
|
188
|
+
m = re.match(r"^(.*?)(?: \(.*\))?$", status_info)
|
|
189
|
+
status = m.group(1)
|
|
190
|
+
|
|
191
|
+
self.update_or_add_campaign_scenario(camp_id, scenario_name, status=status, status_at_str=status_dt_str)
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
|
|
@@ -33,10 +33,10 @@ class DetailedScenarioFailedReportBuilder(ReportBuilder):
|
|
|
33
33
|
|
|
34
34
|
def after_scenario(self, scenario, scenario_report=None):
|
|
35
35
|
from holado_report.report.report_manager import ReportManager
|
|
36
|
-
status_validation, step_failed, step_number = ReportManager.get_current_scenario_status_information(scenario)
|
|
36
|
+
category_validation, status_validation, step_failed, step_number = ReportManager.get_current_scenario_status_information(scenario)
|
|
37
37
|
|
|
38
38
|
if status_validation != "Passed":
|
|
39
|
-
self.__file_fail_add_scenario(scenario, scenario_report, status_validation, step_failed, step_number)
|
|
39
|
+
self.__file_fail_add_scenario(scenario, scenario_report, category_validation, status_validation, step_failed, step_number)
|
|
40
40
|
|
|
41
41
|
def after_all(self):
|
|
42
42
|
# Manage file fail
|
|
@@ -44,13 +44,13 @@ class DetailedScenarioFailedReportBuilder(ReportBuilder):
|
|
|
44
44
|
self.__file_fail.close()
|
|
45
45
|
self.__file_fail = None
|
|
46
46
|
|
|
47
|
-
def __file_fail_add_scenario(self, scenario, scenario_report, status_validation, step_failed, step_number):
|
|
47
|
+
def __file_fail_add_scenario(self, scenario, scenario_report, category_validation, status_validation, step_failed, step_number):
|
|
48
48
|
if self.__is_format_xml:
|
|
49
|
-
self.__file_fail_add_scenario_xml(scenario, scenario_report, status_validation, step_failed, step_number)
|
|
49
|
+
self.__file_fail_add_scenario_xml(scenario, scenario_report, category_validation, status_validation, step_failed, step_number)
|
|
50
50
|
else:
|
|
51
|
-
self.__file_fail_add_scenario_txt(scenario, scenario_report, status_validation, step_failed, step_number)
|
|
51
|
+
self.__file_fail_add_scenario_txt(scenario, scenario_report, category_validation, status_validation, step_failed, step_number)
|
|
52
52
|
|
|
53
|
-
def __file_fail_add_scenario_xml(self, scenario, scenario_report, status_validation, step_failed, step_number):
|
|
53
|
+
def __file_fail_add_scenario_xml(self, scenario, scenario_report, category_validation, status_validation, step_failed, step_number):
|
|
54
54
|
from holado_report.report.report_manager import ReportManager
|
|
55
55
|
|
|
56
56
|
self.__open_file_if_needed()
|
|
@@ -61,6 +61,8 @@ class DetailedScenarioFailedReportBuilder(ReportBuilder):
|
|
|
61
61
|
msg_list.append(f" <scenario>{scenario.name}</scenario>")
|
|
62
62
|
msg_list.append(f" <report>{scenario_report.report_path}</report>")
|
|
63
63
|
msg_list.append(f" <tags>-t " + " -t ".join(scenario.feature.tags + scenario.tags) + "</tags>")
|
|
64
|
+
if category_validation:
|
|
65
|
+
msg_list.append(f" <validation_category>{category_validation}</validation_category>")
|
|
64
66
|
msg_list.append(f" <validation_status>{status_validation}</validation_status>")
|
|
65
67
|
if step_failed is not None:
|
|
66
68
|
msg_list.append(f" <failure>")
|
|
@@ -102,7 +104,7 @@ class DetailedScenarioFailedReportBuilder(ReportBuilder):
|
|
|
102
104
|
self.__file_fail.write(msg)
|
|
103
105
|
self.__file_fail.flush()
|
|
104
106
|
|
|
105
|
-
def __file_fail_add_scenario_txt(self, scenario, scenario_report, status_validation, step_failed, step_number):
|
|
107
|
+
def __file_fail_add_scenario_txt(self, scenario, scenario_report, category_validation, status_validation, step_failed, step_number):
|
|
106
108
|
from holado_report.report.report_manager import ReportManager
|
|
107
109
|
|
|
108
110
|
self.__open_file_if_needed()
|
|
@@ -112,6 +114,8 @@ class DetailedScenarioFailedReportBuilder(ReportBuilder):
|
|
|
112
114
|
msg_list.append(f" Scenario: {scenario.name}")
|
|
113
115
|
msg_list.append(f" Report: {scenario_report.report_path}")
|
|
114
116
|
msg_list.append(f" Tags: -t " + " -t ".join(scenario.feature.tags + scenario.tags))
|
|
117
|
+
if category_validation:
|
|
118
|
+
msg_list.append(f" Validation category: {category_validation}")
|
|
115
119
|
msg_list.append(f" Validation status: {status_validation}")
|
|
116
120
|
if step_failed is not None:
|
|
117
121
|
msg_list.append(f" Failure:")
|
|
@@ -41,16 +41,17 @@ class FailureReportBuilder(ReportBuilder):
|
|
|
41
41
|
|
|
42
42
|
def after_scenario(self, scenario, scenario_report=None):
|
|
43
43
|
from holado_report.report.report_manager import ReportManager
|
|
44
|
-
status_validation, step_failed, step_number = ReportManager.get_current_scenario_status_information(scenario)
|
|
44
|
+
category_validation, status_validation, step_failed, step_number = ReportManager.get_current_scenario_status_information(scenario)
|
|
45
45
|
|
|
46
46
|
if status_validation != "Passed":
|
|
47
47
|
step_error_message = ReportManager.get_step_error_message(step_failed).strip()
|
|
48
48
|
if step_error_message not in self.__failures:
|
|
49
49
|
self.__failures[step_error_message] = []
|
|
50
50
|
|
|
51
|
+
category_str = f" ({category_validation})" if category_validation else ""
|
|
51
52
|
if self.__file_format == 'txt':
|
|
52
53
|
msg_list = []
|
|
53
|
-
msg_list.append(f"scenario in {scenario.filename} at l.{scenario.line} - step {step_number} (l.{step_failed.line}) - {status_validation}")
|
|
54
|
+
msg_list.append(f"scenario in {scenario.filename} at l.{scenario.line} - step {step_number} (l.{step_failed.line}) - {status_validation}{category_str}")
|
|
54
55
|
msg_list.append(f" Feature/Scenario: {scenario.feature.name} => {scenario.name}")
|
|
55
56
|
msg_list.append(f" Report: {scenario_report.report_path}")
|
|
56
57
|
msg_list.append(f" Tags: -t " + " -t ".join(scenario.feature.tags + scenario.tags))
|
|
@@ -59,7 +60,7 @@ class FailureReportBuilder(ReportBuilder):
|
|
|
59
60
|
self.__failures[step_error_message].append(msg_scenario)
|
|
60
61
|
elif self.__file_format in ['json', 'xml']:
|
|
61
62
|
scenario_info = {
|
|
62
|
-
'title': f"{scenario.filename} - l.{scenario.line} - step {step_number} (l.{step_failed.line}) - {status_validation}",
|
|
63
|
+
'title': f"{scenario.filename} - l.{scenario.line} - step {step_number} (l.{step_failed.line}) - {status_validation}{category_str}",
|
|
63
64
|
'scenario': f"{scenario.feature.name} => {scenario.name}",
|
|
64
65
|
'report': scenario_report.report_path,
|
|
65
66
|
'tags': "-t " + " -t ".join(scenario.feature.tags + scenario.tags),
|
|
@@ -32,10 +32,10 @@ class ShortScenarioFailedReportBuilder(ReportBuilder):
|
|
|
32
32
|
|
|
33
33
|
def after_scenario(self, scenario, scenario_report=None):
|
|
34
34
|
from holado_report.report.report_manager import ReportManager
|
|
35
|
-
status_validation, step_failed, step_number = ReportManager.get_current_scenario_status_information(scenario)
|
|
35
|
+
category_validation, status_validation, step_failed, step_number = ReportManager.get_current_scenario_status_information(scenario)
|
|
36
36
|
|
|
37
37
|
if status_validation != "Passed":
|
|
38
|
-
self.__file_fail_add_scenario(scenario, scenario_report, status_validation, step_failed, step_number)
|
|
38
|
+
self.__file_fail_add_scenario(scenario, scenario_report, category_validation, status_validation, step_failed, step_number)
|
|
39
39
|
|
|
40
40
|
def after_all(self):
|
|
41
41
|
# Manage file fail
|
|
@@ -43,16 +43,17 @@ class ShortScenarioFailedReportBuilder(ReportBuilder):
|
|
|
43
43
|
self.__file_fail.close()
|
|
44
44
|
self.__file_fail = None
|
|
45
45
|
|
|
46
|
-
def __file_fail_add_scenario(self, scenario, scenario_report, status_validation, step_failed, step_number):
|
|
46
|
+
def __file_fail_add_scenario(self, scenario, scenario_report, category_validation, status_validation, step_failed, step_number):
|
|
47
47
|
from holado_report.report.report_manager import ReportManager
|
|
48
48
|
|
|
49
49
|
self.__open_file_if_needed()
|
|
50
50
|
|
|
51
51
|
msg_list = []
|
|
52
|
+
category_str = f" ({category_validation})" if category_validation else ""
|
|
52
53
|
if step_failed is not None:
|
|
53
|
-
msg_list.append(f"scenario in {scenario.filename} at l.{scenario.line} - step {step_number} (l.{step_failed.line}) - {status_validation}")
|
|
54
|
+
msg_list.append(f"scenario in {scenario.filename} at l.{scenario.line} - step {step_number} (l.{step_failed.line}) - {status_validation}{category_str}")
|
|
54
55
|
else:
|
|
55
|
-
msg_list.append(f"scenario in {scenario.filename} at l.{scenario.line} - step ? (missing step implementation ?) - {status_validation}")
|
|
56
|
+
msg_list.append(f"scenario in {scenario.filename} at l.{scenario.line} - step ? (missing step implementation ?) - {status_validation}{category_str}")
|
|
56
57
|
msg_list.append(f" Feature/Scenario: {scenario.feature.name} => {scenario.name}")
|
|
57
58
|
msg_list.append(f" Report: {scenario_report.report_path}")
|
|
58
59
|
msg_list.append(f" Tags: -t " + " -t ".join(scenario.feature.tags + scenario.tags))
|
|
@@ -63,7 +63,7 @@ class SummaryReportBuilder(ReportBuilder):
|
|
|
63
63
|
|
|
64
64
|
def after_scenario(self, scenario, scenario_report=None):
|
|
65
65
|
from holado_report.report.report_manager import ReportManager
|
|
66
|
-
status_name, _, _ = ReportManager.get_current_scenario_status_information(scenario)
|
|
66
|
+
_, status_name, _, _ = ReportManager.get_current_scenario_status_information(scenario)
|
|
67
67
|
|
|
68
68
|
if status_name not in self.__scenarios:
|
|
69
69
|
self.__scenarios[status_name] = 0
|
|
@@ -31,10 +31,10 @@ class SummaryScenarioFailedReportBuilder(ReportBuilder):
|
|
|
31
31
|
|
|
32
32
|
def after_scenario(self, scenario, scenario_report=None):
|
|
33
33
|
from holado_report.report.report_manager import ReportManager
|
|
34
|
-
status_validation, step_failed, step_number = ReportManager.get_current_scenario_status_information(scenario)
|
|
34
|
+
category_validation, status_validation, step_failed, step_number = ReportManager.get_current_scenario_status_information(scenario)
|
|
35
35
|
|
|
36
36
|
if status_validation != "Passed":
|
|
37
|
-
self.__file_fail_add_scenario(scenario, status_validation, step_failed, step_number)
|
|
37
|
+
self.__file_fail_add_scenario(scenario, category_validation, status_validation, step_failed, step_number)
|
|
38
38
|
|
|
39
39
|
def after_all(self):
|
|
40
40
|
# Manage file fail
|
|
@@ -42,12 +42,13 @@ class SummaryScenarioFailedReportBuilder(ReportBuilder):
|
|
|
42
42
|
self.__file_fail.close()
|
|
43
43
|
self.__file_fail = None
|
|
44
44
|
|
|
45
|
-
def __file_fail_add_scenario(self, scenario, status_validation, step_failed, step_number):
|
|
45
|
+
def __file_fail_add_scenario(self, scenario, category_validation, status_validation, step_failed, step_number):
|
|
46
46
|
self.__open_file_if_needed()
|
|
47
|
+
category_str = f" ({category_validation})" if category_validation else ""
|
|
47
48
|
if step_failed is not None:
|
|
48
|
-
self.__file_fail.write(f"scenario in {scenario.filename} at l.{scenario.line} - step {step_number} (l.{step_failed.line}) - {status_validation}\n")
|
|
49
|
+
self.__file_fail.write(f"scenario in {scenario.filename} at l.{scenario.line} - step {step_number} (l.{step_failed.line}) - {status_validation}{category_str}\n")
|
|
49
50
|
else:
|
|
50
|
-
self.__file_fail.write(f"scenario in {scenario.filename} at l.{scenario.line} - step ? (missing step implementation ?) - {status_validation}\n")
|
|
51
|
+
self.__file_fail.write(f"scenario in {scenario.filename} at l.{scenario.line} - step ? (missing step implementation ?) - {status_validation}{category_str}\n")
|
|
51
52
|
self.__file_fail.flush()
|
|
52
53
|
|
|
53
54
|
def __open_file_if_needed(self):
|
|
@@ -33,12 +33,12 @@ class SummaryScenarioReportBuilder(ReportBuilder):
|
|
|
33
33
|
|
|
34
34
|
def after_scenario(self, scenario, scenario_report=None):
|
|
35
35
|
from holado_report.report.report_manager import ReportManager
|
|
36
|
-
status_validation, step_failed, step_number = ReportManager.get_current_scenario_status_information(scenario)
|
|
36
|
+
category_validation, status_validation, step_failed, step_number = ReportManager.get_current_scenario_status_information(scenario)
|
|
37
37
|
|
|
38
38
|
if status_validation == "Passed":
|
|
39
|
-
self.__file_add_scenario_success(scenario, status_validation)
|
|
39
|
+
self.__file_add_scenario_success(scenario, category_validation, status_validation)
|
|
40
40
|
else:
|
|
41
|
-
self.__file_add_scenario_fail(scenario, status_validation, step_failed, step_number)
|
|
41
|
+
self.__file_add_scenario_fail(scenario, category_validation, status_validation, step_failed, step_number)
|
|
42
42
|
|
|
43
43
|
def after_all(self):
|
|
44
44
|
# Manage file fail
|
|
@@ -46,27 +46,28 @@ class SummaryScenarioReportBuilder(ReportBuilder):
|
|
|
46
46
|
self.__file.close()
|
|
47
47
|
self.__file = None
|
|
48
48
|
|
|
49
|
-
def __file_add_scenario_success(self, scenario, status_validation):
|
|
49
|
+
def __file_add_scenario_success(self, scenario, category_validation, status_validation):
|
|
50
50
|
self.__open_file_if_needed()
|
|
51
|
-
self.__file_write(scenario, None, status_validation)
|
|
51
|
+
self.__file_write(scenario, None, category_validation, status_validation)
|
|
52
52
|
self.__file.flush()
|
|
53
53
|
|
|
54
|
-
def __file_add_scenario_fail(self, scenario, status_validation, step_failed, step_number):
|
|
54
|
+
def __file_add_scenario_fail(self, scenario, category_validation, status_validation, step_failed, step_number):
|
|
55
55
|
self.__open_file_if_needed()
|
|
56
56
|
if step_failed is not None:
|
|
57
|
-
self.__file_write(scenario, f"step {step_number} (l.{step_failed.line})", status_validation)
|
|
57
|
+
self.__file_write(scenario, f"step {step_number} (l.{step_failed.line})", category_validation, status_validation)
|
|
58
58
|
else:
|
|
59
|
-
self.__file_write(scenario, f"step ? (missing step implementation ?)", status_validation)
|
|
59
|
+
self.__file_write(scenario, f"step ? (missing step implementation ?)", category_validation, status_validation)
|
|
60
60
|
self.__file.flush()
|
|
61
61
|
|
|
62
|
-
def __file_write(self, scenario, text, status_validation):
|
|
62
|
+
def __file_write(self, scenario, text, category_validation, status_validation):
|
|
63
63
|
dt = DateTime.now()
|
|
64
64
|
dt_str = DateTime.datetime_2_str(dt, FORMAT_DATETIME_HUMAN_SECOND)
|
|
65
65
|
|
|
66
|
+
category_str = f" ({category_validation})" if category_validation else ""
|
|
66
67
|
if text:
|
|
67
|
-
self.__file.write(f"{dt_str} - {scenario.filename} at l.{scenario.line} - {text} - {status_validation}\n")
|
|
68
|
+
self.__file.write(f"{dt_str} - {scenario.filename} at l.{scenario.line} - {text} - {status_validation}{category_str}\n")
|
|
68
69
|
else:
|
|
69
|
-
self.__file.write(f"{dt_str} - {scenario.filename} at l.{scenario.line} - {status_validation}\n")
|
|
70
|
+
self.__file.write(f"{dt_str} - {scenario.filename} at l.{scenario.line} - {status_validation}{category_str}\n")
|
|
70
71
|
|
|
71
72
|
def __open_file_if_needed(self):
|
|
72
73
|
if self.__file is None:
|
|
@@ -56,11 +56,12 @@ class ExecutionHistoric():
|
|
|
56
56
|
self.__get_execution_historic_current_feature_scenarios().append(seh)
|
|
57
57
|
|
|
58
58
|
def __new_ScenarioExecutionHistoric(self, scenario_context, scenario, scenario_report):
|
|
59
|
-
res = NamedTuple("ScenarioExecutionHistoric", scenario_context=object, scenario=object, scenario_report=object, steps_by_scope=dict, status_validation=str, step_failed=object, step_failed_number=int)
|
|
59
|
+
res = NamedTuple("ScenarioExecutionHistoric", scenario_context=object, scenario=object, scenario_report=object, steps_by_scope=dict, category_validation=str, status_validation=str, step_failed=object, step_failed_number=int)
|
|
60
60
|
res.scenario_context = scenario_context
|
|
61
61
|
res.scenario = scenario
|
|
62
62
|
res.scenario_report = scenario_report
|
|
63
63
|
res.steps_by_scope = {}
|
|
64
|
+
res.category_validation = None
|
|
64
65
|
res.status_validation = None
|
|
65
66
|
res.step_failed = None
|
|
66
67
|
res.step_failed_number = None
|
|
@@ -87,10 +88,11 @@ class ExecutionHistoric():
|
|
|
87
88
|
|
|
88
89
|
def after_scenario(self, scenario, scenario_report=None):
|
|
89
90
|
from holado_report.report.report_manager import ReportManager
|
|
90
|
-
status_validation, step_failed, step_number = ReportManager.get_current_scenario_status_information(scenario)
|
|
91
|
+
category_validation, status_validation, step_failed, step_number = ReportManager.get_current_scenario_status_information(scenario)
|
|
91
92
|
|
|
92
93
|
# Update execution historic
|
|
93
94
|
current_scenario = self.__get_execution_historic_current_scenario()
|
|
95
|
+
current_scenario.category_validation = category_validation
|
|
94
96
|
current_scenario.status_validation = status_validation
|
|
95
97
|
current_scenario.step_failed = step_failed
|
|
96
98
|
current_scenario.step_failed_number = step_number
|
|
@@ -12,7 +12,6 @@
|
|
|
12
12
|
#################################################
|
|
13
13
|
|
|
14
14
|
from holado.common.context.session_context import SessionContext
|
|
15
|
-
from behave.model_core import Status
|
|
16
15
|
from holado_core.common.tools.tools import Tools
|
|
17
16
|
import logging
|
|
18
17
|
# from holado_report.report.builders.json_execution_historic_report_builder import JsonExecutionHistoricReportBuilder
|
|
@@ -32,9 +31,13 @@ logger = logging.getLogger(__name__)
|
|
|
32
31
|
|
|
33
32
|
|
|
34
33
|
class ReportManager(BaseReport):
|
|
34
|
+
""" Manage reports of current session
|
|
35
|
+
"""
|
|
35
36
|
TFeatureReport = None
|
|
36
37
|
TStepTools = None
|
|
37
38
|
|
|
39
|
+
_scenario_status_information_by_uid = {}
|
|
40
|
+
|
|
38
41
|
def __init__(self):
|
|
39
42
|
super().__init__()
|
|
40
43
|
|
|
@@ -183,6 +186,10 @@ class ReportManager(BaseReport):
|
|
|
183
186
|
# scenario_duration_tags = sdm.compute_scenario_duration_tags(duration_limit_tags, "long", missing_tag=True, new_tag=True, unchanged_tag=True, with_failed=True)
|
|
184
187
|
# sdm.create_file_scenario_duration_tags(fn, scenario_duration_tags)
|
|
185
188
|
|
|
189
|
+
# Update campaigns stored in test server
|
|
190
|
+
if self._get_test_server_client().is_available:
|
|
191
|
+
self._get_test_server_client().update_stored_campaigns()
|
|
192
|
+
|
|
186
193
|
def __enter_current_scenario_log_file(self):
|
|
187
194
|
if SessionContext.instance().log_manager.in_file and self.has_report_path:
|
|
188
195
|
log_filename = self.current_scenario_report.get_path("logs", "report.log")
|
|
@@ -195,21 +202,66 @@ class ReportManager(BaseReport):
|
|
|
195
202
|
if SessionContext.instance().log_manager.in_file and self.has_report_path:
|
|
196
203
|
log_filename = self.current_scenario_report.get_path("logs", "report.log")
|
|
197
204
|
SessionContext.instance().log_manager.leave_log_file(log_filename, do_remove_log_file=True)
|
|
198
|
-
|
|
205
|
+
|
|
206
|
+
@classmethod
|
|
207
|
+
def _get_test_server_client(cls):
|
|
208
|
+
return SessionContext.instance().test_server_client
|
|
209
|
+
|
|
210
|
+
@classmethod
|
|
211
|
+
def _get_scenario_uid(cls, scenario):
|
|
212
|
+
return f"{scenario.filename} at l.{scenario.line}"
|
|
213
|
+
|
|
199
214
|
@classmethod
|
|
200
215
|
def get_current_scenario_status_information(cls, scenario):
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
status = "Failed in Preconditions"
|
|
205
|
-
elif step_failed is not None and step_failed.keyword == "Given":
|
|
206
|
-
status = "Failed in Given"
|
|
207
|
-
elif step_failed is not None or scenario.status.has_failed():
|
|
208
|
-
status = "Failed"
|
|
209
|
-
else:
|
|
210
|
-
status = scenario.status.name.capitalize()
|
|
216
|
+
scenario_uid = cls._get_scenario_uid(scenario)
|
|
217
|
+
if scenario_uid not in cls._scenario_status_information_by_uid:
|
|
218
|
+
step_failed, step_nb = cls.get_step_failed_info(scenario)
|
|
211
219
|
|
|
212
|
-
|
|
220
|
+
# Define scenario status
|
|
221
|
+
if step_failed is not None and hasattr(SessionContext.instance().get_scenario_context(), "is_in_preconditions") and SessionContext.instance().get_scenario_context().is_in_preconditions:
|
|
222
|
+
status = "Failed in Preconditions"
|
|
223
|
+
elif step_failed is not None and step_failed.keyword == "Given":
|
|
224
|
+
status = "Failed in Given"
|
|
225
|
+
elif step_failed is not None or scenario.status.has_failed():
|
|
226
|
+
status = "Failed"
|
|
227
|
+
else:
|
|
228
|
+
status = scenario.status.name.capitalize()
|
|
229
|
+
|
|
230
|
+
# Define scenario category
|
|
231
|
+
category = None
|
|
232
|
+
if cls._get_test_server_client().is_available:
|
|
233
|
+
# Get scenario execution statuses
|
|
234
|
+
scenario_name = f"{scenario.filename} at l.{scenario.line}"
|
|
235
|
+
sce_hist = cls._get_test_server_client().get_scenario_history(scenario_name=scenario_name, size=10)
|
|
236
|
+
statuses = [s['status'] for s in sce_hist[0]['statuses']] if sce_hist else []
|
|
237
|
+
statuses.append(status)
|
|
238
|
+
|
|
239
|
+
# Get scenario status sequences
|
|
240
|
+
passed_sequences = []
|
|
241
|
+
for status in statuses:
|
|
242
|
+
if status == 'Passed':
|
|
243
|
+
passed = True
|
|
244
|
+
elif status.startswith("Failed"):
|
|
245
|
+
passed = False
|
|
246
|
+
else:
|
|
247
|
+
continue
|
|
248
|
+
if len(passed_sequences) == 0 or passed != passed_sequences[-1]:
|
|
249
|
+
passed_sequences.append(passed)
|
|
250
|
+
|
|
251
|
+
# Compute category
|
|
252
|
+
if passed_sequences:
|
|
253
|
+
if len(passed_sequences) == 1:
|
|
254
|
+
category = 'Success' if passed_sequences[0] else 'Failed'
|
|
255
|
+
elif len(passed_sequences) > 2:
|
|
256
|
+
category = 'Randomly Failed'
|
|
257
|
+
elif passed_sequences[-1]:
|
|
258
|
+
category = 'Newly Success'
|
|
259
|
+
else:
|
|
260
|
+
category = 'Newly Failed'
|
|
261
|
+
|
|
262
|
+
cls._scenario_status_information_by_uid[scenario_uid] = [category, status, step_failed, step_nb]
|
|
263
|
+
|
|
264
|
+
return cls._scenario_status_information_by_uid[scenario_uid]
|
|
213
265
|
|
|
214
266
|
@classmethod
|
|
215
267
|
def get_step_failed_info(cls, scenario):
|
|
@@ -55,7 +55,11 @@ class RestClient(Object):
|
|
|
55
55
|
self.__kwargs = {}
|
|
56
56
|
|
|
57
57
|
self.__request_log_level = Config.log_level_rest_request
|
|
58
|
-
|
|
58
|
+
|
|
59
|
+
@property
|
|
60
|
+
def url(self):
|
|
61
|
+
return self.__url
|
|
62
|
+
|
|
59
63
|
@property
|
|
60
64
|
def request_log_level(self):
|
|
61
65
|
return self.__request_log_level
|
|
@@ -81,8 +81,11 @@ class File(DeleteableObject):
|
|
|
81
81
|
def readline(self, limit: int = -1) -> AnyStr:
|
|
82
82
|
return self.internal_file.readline(limit)
|
|
83
83
|
|
|
84
|
-
def readlines(self, hint: int = -1) -> List[AnyStr]:
|
|
85
|
-
|
|
84
|
+
def readlines(self, hint: int = -1, strip_newline=False) -> List[AnyStr]:
|
|
85
|
+
res = self.internal_file.readlines(hint)
|
|
86
|
+
if strip_newline:
|
|
87
|
+
res = [l.strip('\n') for l in res]
|
|
88
|
+
return res
|
|
86
89
|
|
|
87
90
|
|
|
88
91
|
|
holado_test/__init__.py
CHANGED
|
@@ -23,5 +23,8 @@ def register():
|
|
|
23
23
|
from holado_test.behave.behave_manager import BehaveManager
|
|
24
24
|
SessionContext.instance().services.register_service_type("behave_manager", BehaveManager)
|
|
25
25
|
|
|
26
|
+
from holado_test.test_server.client.rest.test_server_client import TestServerClient
|
|
27
|
+
SessionContext.instance().services.register_service_type("test_server_client", TestServerClient.new_client)
|
|
28
|
+
|
|
26
29
|
|
|
27
30
|
|
|
@@ -13,10 +13,10 @@
|
|
|
13
13
|
|
|
14
14
|
from builtins import super
|
|
15
15
|
from holado.common.context.context import Context
|
|
16
|
-
from datetime import datetime
|
|
17
16
|
from holado_core.common.exceptions.technical_exception import TechnicalException
|
|
18
17
|
from holado_scripting.common.tools.variable_manager import VariableManager
|
|
19
18
|
from holado.common.context.session_context import SessionContext
|
|
19
|
+
from holado_python.common.tools.datetime import DateTime
|
|
20
20
|
|
|
21
21
|
class FeatureContext(Context):
|
|
22
22
|
def __init__(self, feature):
|
|
@@ -24,7 +24,7 @@ class FeatureContext(Context):
|
|
|
24
24
|
|
|
25
25
|
self.__feature = feature
|
|
26
26
|
|
|
27
|
-
self.__start_date =
|
|
27
|
+
self.__start_date = DateTime.now()
|
|
28
28
|
self.__end_date = None
|
|
29
29
|
self.__scenarios = []
|
|
30
30
|
|
|
@@ -64,7 +64,7 @@ class FeatureContext(Context):
|
|
|
64
64
|
self.__scenarios.append(scenario_context)
|
|
65
65
|
|
|
66
66
|
def end(self):
|
|
67
|
-
self.__end_date =
|
|
67
|
+
self.__end_date = DateTime.now()
|
|
68
68
|
|
|
69
69
|
def has_variable_manager(self):
|
|
70
70
|
return self.has_object("variable_manager")
|
|
@@ -21,10 +21,10 @@ from holado_scripting.common.tools.expression_evaluator import ExpressionEvaluat
|
|
|
21
21
|
from holado.common.context.session_context import SessionContext
|
|
22
22
|
from holado_core.common.block.scope_manager import ScopeManager
|
|
23
23
|
from holado_core.common.block.block_manager import BlockManager
|
|
24
|
-
from datetime import datetime
|
|
25
24
|
from holado_core.common.exceptions.technical_exception import TechnicalException
|
|
26
25
|
from holado_scripting.text.verifier.text_verifier import TextVerifier
|
|
27
26
|
from holado_multitask.multitasking.multitask_manager import MultitaskManager
|
|
27
|
+
from holado_python.common.tools.datetime import DateTime
|
|
28
28
|
|
|
29
29
|
logger = logging.getLogger(__name__)
|
|
30
30
|
|
|
@@ -35,7 +35,7 @@ class ScenarioContext(Context):
|
|
|
35
35
|
|
|
36
36
|
self.__scenario = scenario
|
|
37
37
|
|
|
38
|
-
self.__start_date =
|
|
38
|
+
self.__start_date = DateTime.now()
|
|
39
39
|
self.__end_date = None
|
|
40
40
|
|
|
41
41
|
self.__main_thread_uid = SessionContext.instance().multitask_manager.main_thread_uid
|
|
@@ -150,7 +150,7 @@ class ScenarioContext(Context):
|
|
|
150
150
|
return self.get_object("expression_evaluator")
|
|
151
151
|
|
|
152
152
|
def end(self):
|
|
153
|
-
self.__end_date =
|
|
153
|
+
self.__end_date = DateTime.now()
|
|
154
154
|
|
|
155
155
|
|
|
156
156
|
|