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.

Files changed (56) hide show
  1. holado/common/context/session_context.py +6 -5
  2. {holado-0.8.4.dist-info → holado-0.9.1.dist-info}/METADATA +1 -1
  3. {holado-0.8.4.dist-info → holado-0.9.1.dist-info}/RECORD +56 -41
  4. holado_core/common/block/base.py +3 -3
  5. holado_core/common/resource/persisted_data_manager.py +10 -10
  6. holado_core/common/resource/resource_manager.py +34 -37
  7. holado_core/common/resource/table_data_manager.py +110 -0
  8. holado_core/common/tools/path_manager.py +31 -6
  9. holado_django/server/django_projects/rest_api/rest_api/urls.py +1 -1
  10. holado_docker/tests/behave/steps/tools/docker_controller/client_steps.py +2 -2
  11. holado_docker/tools/docker_controller/client/rest/docker_controller_client.py +38 -1
  12. holado_docker/tools/docker_controller/server/run_docker_controller_in_docker.sh +3 -3
  13. holado_examples/tests/behave/testing_solution/environment.py +3 -3
  14. holado_examples/tests/behave/testing_solution/src/context/session_context.py +2 -2
  15. holado_examples/tests/behave/testing_solution/steps/config_steps.py +1 -1
  16. holado_multitask/multithreading/loopthread.py +4 -4
  17. holado_python/common/tools/datetime.py +37 -16
  18. holado_python/standard_library/ssl/resources/certificates/tcpbin.crt +16 -16
  19. holado_python/standard_library/ssl/resources/certificates/tcpbin.key +26 -26
  20. holado_python/tests/behave/steps/standard_library/datetime_steps.py +2 -2
  21. holado_rabbitmq/tools/rabbitmq/rabbitmq_manager.py +3 -3
  22. holado_report/__init__.py +1 -0
  23. holado_report/campaign/campaign_manager.py +194 -0
  24. holado_report/report/builders/detailed_scenario_failed_report_builder.py +11 -7
  25. holado_report/report/builders/failure_report_builder.py +4 -3
  26. holado_report/report/builders/short_scenario_failed_report_builder.py +6 -5
  27. holado_report/report/builders/summary_report_builder.py +1 -1
  28. holado_report/report/builders/summary_scenario_failed_report_builder.py +6 -5
  29. holado_report/report/builders/summary_scenario_report_builder.py +12 -11
  30. holado_report/report/execution_historic.py +4 -2
  31. holado_report/report/report_manager.py +65 -13
  32. holado_rest/api/rest/rest_client.py +5 -1
  33. holado_system/system/filesystem/file.py +5 -2
  34. holado_test/__init__.py +3 -0
  35. holado_test/common/context/feature_context.py +3 -3
  36. holado_test/common/context/scenario_context.py +3 -3
  37. holado_test/common/context/step_context.py +3 -3
  38. holado_test/test_server/client/rest/test_server_client.py +117 -0
  39. holado_test/test_server/server/Dockerfile +70 -0
  40. holado_test/test_server/server/core/server_context.py +42 -0
  41. holado_test/test_server/server/core/server_manager.py +54 -0
  42. holado_test/test_server/server/requirements.txt +2 -0
  43. holado_test/test_server/server/rest/README +2 -0
  44. holado_test/test_server/server/rest/api/__init__.py +23 -0
  45. holado_docker/tools/docker_controller/docker_controller_manager.py → holado_test/test_server/server/rest/api/campaign/__init__.py +10 -28
  46. holado_test/test_server/server/rest/api/campaign/scenario.py +33 -0
  47. holado_test/test_server/server/rest/initialize_holado.py +72 -0
  48. holado_test/test_server/server/rest/logging.conf +50 -0
  49. holado_test/test_server/server/rest/openapi.yaml +46 -0
  50. holado_test/test_server/server/rest/run.py +40 -0
  51. holado_test/test_server/server/run_test_server_in_docker.sh +101 -0
  52. holado_value/common/tools/unique_value_manager.py +2 -1
  53. test_holado/environment.py +1 -1
  54. test_holado/logging.conf +1 -0
  55. {holado-0.8.4.dist-info → holado-0.9.1.dist-info}/WHEEL +0 -0
  56. {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
- step_failed, step_nb = cls.get_step_failed_info(scenario)
202
-
203
- 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:
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
- return [status, step_failed, step_nb]
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
- return self.internal_file.readlines(hint)
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 = datetime.now()
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 = datetime.now()
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 = datetime.now()
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 = datetime.now()
153
+ self.__end_date = DateTime.now()
154
154
 
155
155
 
156
156