argus-alm 0.14.2__py3-none-any.whl → 0.15.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.
Files changed (118) hide show
  1. argus/_version.py +21 -0
  2. argus/backend/.gitkeep +0 -0
  3. argus/backend/__init__.py +0 -0
  4. argus/backend/cli.py +57 -0
  5. argus/backend/controller/__init__.py +0 -0
  6. argus/backend/controller/admin.py +20 -0
  7. argus/backend/controller/admin_api.py +355 -0
  8. argus/backend/controller/api.py +589 -0
  9. argus/backend/controller/auth.py +67 -0
  10. argus/backend/controller/client_api.py +109 -0
  11. argus/backend/controller/main.py +316 -0
  12. argus/backend/controller/notification_api.py +72 -0
  13. argus/backend/controller/notifications.py +13 -0
  14. argus/backend/controller/planner_api.py +194 -0
  15. argus/backend/controller/team.py +129 -0
  16. argus/backend/controller/team_ui.py +19 -0
  17. argus/backend/controller/testrun_api.py +513 -0
  18. argus/backend/controller/view_api.py +188 -0
  19. argus/backend/controller/views_widgets/__init__.py +0 -0
  20. argus/backend/controller/views_widgets/graphed_stats.py +54 -0
  21. argus/backend/controller/views_widgets/graphs.py +68 -0
  22. argus/backend/controller/views_widgets/highlights.py +135 -0
  23. argus/backend/controller/views_widgets/nemesis_stats.py +26 -0
  24. argus/backend/controller/views_widgets/summary.py +43 -0
  25. argus/backend/db.py +98 -0
  26. argus/backend/error_handlers.py +41 -0
  27. argus/backend/events/event_processors.py +34 -0
  28. argus/backend/models/__init__.py +0 -0
  29. argus/backend/models/argus_ai.py +24 -0
  30. argus/backend/models/github_issue.py +60 -0
  31. argus/backend/models/plan.py +24 -0
  32. argus/backend/models/result.py +187 -0
  33. argus/backend/models/runtime_store.py +58 -0
  34. argus/backend/models/view_widgets.py +25 -0
  35. argus/backend/models/web.py +403 -0
  36. argus/backend/plugins/__init__.py +0 -0
  37. argus/backend/plugins/core.py +248 -0
  38. argus/backend/plugins/driver_matrix_tests/controller.py +66 -0
  39. argus/backend/plugins/driver_matrix_tests/model.py +429 -0
  40. argus/backend/plugins/driver_matrix_tests/plugin.py +21 -0
  41. argus/backend/plugins/driver_matrix_tests/raw_types.py +62 -0
  42. argus/backend/plugins/driver_matrix_tests/service.py +61 -0
  43. argus/backend/plugins/driver_matrix_tests/udt.py +42 -0
  44. argus/backend/plugins/generic/model.py +86 -0
  45. argus/backend/plugins/generic/plugin.py +15 -0
  46. argus/backend/plugins/generic/types.py +14 -0
  47. argus/backend/plugins/loader.py +39 -0
  48. argus/backend/plugins/sct/controller.py +224 -0
  49. argus/backend/plugins/sct/plugin.py +37 -0
  50. argus/backend/plugins/sct/resource_setup.py +177 -0
  51. argus/backend/plugins/sct/service.py +682 -0
  52. argus/backend/plugins/sct/testrun.py +288 -0
  53. argus/backend/plugins/sct/udt.py +100 -0
  54. argus/backend/plugins/sirenada/model.py +118 -0
  55. argus/backend/plugins/sirenada/plugin.py +16 -0
  56. argus/backend/service/admin.py +26 -0
  57. argus/backend/service/argus_service.py +696 -0
  58. argus/backend/service/build_system_monitor.py +185 -0
  59. argus/backend/service/client_service.py +127 -0
  60. argus/backend/service/event_service.py +18 -0
  61. argus/backend/service/github_service.py +233 -0
  62. argus/backend/service/jenkins_service.py +269 -0
  63. argus/backend/service/notification_manager.py +159 -0
  64. argus/backend/service/planner_service.py +608 -0
  65. argus/backend/service/release_manager.py +229 -0
  66. argus/backend/service/results_service.py +690 -0
  67. argus/backend/service/stats.py +610 -0
  68. argus/backend/service/team_manager_service.py +82 -0
  69. argus/backend/service/test_lookup.py +172 -0
  70. argus/backend/service/testrun.py +489 -0
  71. argus/backend/service/user.py +308 -0
  72. argus/backend/service/views.py +219 -0
  73. argus/backend/service/views_widgets/__init__.py +0 -0
  74. argus/backend/service/views_widgets/graphed_stats.py +180 -0
  75. argus/backend/service/views_widgets/highlights.py +374 -0
  76. argus/backend/service/views_widgets/nemesis_stats.py +34 -0
  77. argus/backend/template_filters.py +27 -0
  78. argus/backend/tests/__init__.py +0 -0
  79. argus/backend/tests/client_service/__init__.py +0 -0
  80. argus/backend/tests/client_service/test_submit_results.py +79 -0
  81. argus/backend/tests/conftest.py +180 -0
  82. argus/backend/tests/results_service/__init__.py +0 -0
  83. argus/backend/tests/results_service/test_best_results.py +178 -0
  84. argus/backend/tests/results_service/test_cell.py +65 -0
  85. argus/backend/tests/results_service/test_chartjs_additional_functions.py +259 -0
  86. argus/backend/tests/results_service/test_create_chartjs.py +220 -0
  87. argus/backend/tests/results_service/test_result_metadata.py +100 -0
  88. argus/backend/tests/results_service/test_results_service.py +203 -0
  89. argus/backend/tests/results_service/test_validation_rules.py +213 -0
  90. argus/backend/tests/view_widgets/__init__.py +0 -0
  91. argus/backend/tests/view_widgets/test_highlights_api.py +532 -0
  92. argus/backend/util/common.py +65 -0
  93. argus/backend/util/config.py +38 -0
  94. argus/backend/util/encoders.py +56 -0
  95. argus/backend/util/logsetup.py +80 -0
  96. argus/backend/util/module_loaders.py +30 -0
  97. argus/backend/util/send_email.py +91 -0
  98. argus/client/base.py +1 -3
  99. argus/client/driver_matrix_tests/cli.py +17 -8
  100. argus/client/generic/cli.py +4 -2
  101. argus/client/generic/client.py +1 -0
  102. argus/client/generic_result.py +48 -9
  103. argus/client/sct/client.py +1 -3
  104. argus/client/sirenada/client.py +4 -1
  105. argus/client/tests/__init__.py +0 -0
  106. argus/client/tests/conftest.py +19 -0
  107. argus/client/tests/test_package.py +45 -0
  108. argus/client/tests/test_results.py +224 -0
  109. argus/common/sct_types.py +3 -0
  110. argus/common/sirenada_types.py +1 -1
  111. {argus_alm-0.14.2.dist-info → argus_alm-0.15.1.dist-info}/METADATA +43 -19
  112. argus_alm-0.15.1.dist-info/RECORD +122 -0
  113. {argus_alm-0.14.2.dist-info → argus_alm-0.15.1.dist-info}/WHEEL +2 -1
  114. argus_alm-0.15.1.dist-info/entry_points.txt +3 -0
  115. argus_alm-0.15.1.dist-info/top_level.txt +1 -0
  116. argus_alm-0.14.2.dist-info/RECORD +0 -20
  117. argus_alm-0.14.2.dist-info/entry_points.txt +0 -4
  118. {argus_alm-0.14.2.dist-info → argus_alm-0.15.1.dist-info/licenses}/LICENSE +0 -0
@@ -0,0 +1,66 @@
1
+ from flask import Blueprint, request
2
+
3
+ from argus.backend.error_handlers import handle_api_exception
4
+ from argus.backend.plugins.driver_matrix_tests.raw_types import DriverMatrixSubmitEnvRequest, DriverMatrixSubmitFailureRequest, DriverMatrixSubmitResultRequest
5
+ from argus.backend.service.user import api_login_required
6
+ from argus.backend.plugins.driver_matrix_tests.service import DriverMatrixService
7
+ from argus.backend.util.common import get_payload
8
+
9
+ bp = Blueprint("driver_matrix_api", __name__, url_prefix="/driver_matrix")
10
+ bp.register_error_handler(Exception, handle_api_exception)
11
+
12
+
13
+ @bp.route("/test_report", methods=["GET"])
14
+ @api_login_required
15
+ def driver_matrix_test_report():
16
+
17
+ build_id = request.args.get("buildId")
18
+ if not build_id:
19
+ raise Exception("No build id provided")
20
+
21
+ result = DriverMatrixService().tested_versions_report(build_id=build_id)
22
+ return {
23
+ "status": "ok",
24
+ "response": result
25
+ }
26
+
27
+
28
+ @bp.route("/result/submit", methods=["POST"])
29
+ @api_login_required
30
+ def submit_result():
31
+ payload = get_payload(request)
32
+ request_data = DriverMatrixSubmitResultRequest(**payload)
33
+
34
+ result = DriverMatrixService().submit_driver_result(driver_name=request_data.driver_name,
35
+ driver_type=request_data.driver_type, run_id=request_data.run_id, raw_xml=request_data.raw_xml)
36
+ return {
37
+ "status": "ok",
38
+ "response": result
39
+ }
40
+
41
+
42
+ @bp.route("/result/fail", methods=["POST"])
43
+ @api_login_required
44
+ def submit_failure():
45
+ payload = get_payload(request)
46
+ request_data = DriverMatrixSubmitFailureRequest(**payload)
47
+
48
+ result = DriverMatrixService().submit_driver_failure(driver_name=request_data.driver_name,
49
+ driver_type=request_data.driver_type, run_id=request_data.run_id, failure_reason=request_data.failure_reason)
50
+ return {
51
+ "status": "ok",
52
+ "response": result
53
+ }
54
+
55
+
56
+ @bp.route("/env/submit", methods=["POST"])
57
+ @api_login_required
58
+ def submit_env():
59
+ payload = get_payload(request)
60
+ request_data = DriverMatrixSubmitEnvRequest(**payload)
61
+
62
+ result = DriverMatrixService().submit_env_info(run_id=request_data.run_id, raw_env=request_data.raw_env)
63
+ return {
64
+ "status": "ok",
65
+ "response": result
66
+ }
@@ -0,0 +1,429 @@
1
+ from dataclasses import dataclass
2
+ from datetime import datetime
3
+ from functools import reduce
4
+ import logging
5
+ from pprint import pformat
6
+ import re
7
+ from typing import Literal, TypedDict
8
+ from uuid import UUID
9
+ from xml.etree import ElementTree
10
+ from cassandra.cqlengine import columns
11
+ from cassandra.cqlengine.models import Model
12
+ from argus.backend.db import ScyllaCluster
13
+ from argus.backend.models.web import ArgusRelease
14
+ from argus.backend.plugins.core import PluginModelBase
15
+ from argus.backend.plugins.driver_matrix_tests.udt import TestCollection, TestSuite, TestCase, EnvironmentInfo
16
+ from argus.backend.plugins.driver_matrix_tests.raw_types import RawMatrixTestResult
17
+ from argus.common.enums import TestStatus
18
+
19
+
20
+ LOGGER = logging.getLogger(__name__)
21
+
22
+
23
+ class DriverMatrixPluginError(Exception):
24
+ pass
25
+
26
+
27
+ @dataclass(init=True, repr=True, frozen=True)
28
+ class DriverMatrixRunSubmissionRequest():
29
+ schema_version: str
30
+ run_id: str
31
+ job_name: str
32
+ job_url: str
33
+ test_environment: dict[str, str]
34
+ matrix_results: list[RawMatrixTestResult]
35
+
36
+
37
+ @dataclass(init=True, repr=True, frozen=True)
38
+ class DriverMatrixRunSubmissionRequestV2():
39
+ schema_version: str
40
+ run_id: str
41
+ job_name: str
42
+ job_url: str
43
+
44
+
45
+ TestTypeType = Literal['java', 'cpp', 'python', 'gocql']
46
+
47
+
48
+ class AdaptedXUnitData(TypedDict):
49
+ timestamp: str
50
+
51
+
52
+ def python_driver_matrix_adapter(xml: ElementTree.ElementTree) -> AdaptedXUnitData:
53
+ testsuites = list(xml.getroot().iter("testsuite"))
54
+
55
+ return {
56
+ "timestamp": testsuites[0].attrib.get("timestamp"),
57
+ }
58
+
59
+
60
+ def java_driver_matrix_adapter(xml: ElementTree.ElementTree) -> AdaptedXUnitData:
61
+ testsuites = xml.getroot()
62
+ ts_now = datetime.utcnow().timestamp()
63
+ try:
64
+ time_taken = float(testsuites.attrib.get("time"))
65
+ except ValueError:
66
+ time_taken = 0.0
67
+
68
+ timestamp = datetime.utcfromtimestamp(ts_now - time_taken).isoformat()
69
+
70
+ return {
71
+ "timestamp": timestamp,
72
+ }
73
+
74
+
75
+ def cpp_driver_matrix_adapter(xml: ElementTree.ElementTree) -> AdaptedXUnitData:
76
+ testsuites = xml.getroot()
77
+
78
+ return {
79
+ "timestamp": testsuites.attrib.get("timestamp"),
80
+ }
81
+
82
+
83
+ def gocql_driver_matrix_adapter(xml: ElementTree.ElementTree) -> AdaptedXUnitData:
84
+ testsuites = list(xml.getroot().iter("testsuite"))
85
+
86
+ return {
87
+ "timestamp": testsuites[0].attrib.get("timestamp"),
88
+ }
89
+
90
+
91
+ def generic_adapter(xml: ElementTree.ElementTree) -> AdaptedXUnitData:
92
+ return {
93
+ "timestamp": datetime.utcnow().isoformat()
94
+ }
95
+
96
+
97
+ class DriverTestRun(PluginModelBase):
98
+ _plugin_name = "driver-matrix-tests"
99
+ __table_name__ = "driver_test_run"
100
+ scylla_version = columns.Text()
101
+ test_collection = columns.List(value_type=columns.UserDefinedType(user_type=TestCollection))
102
+ environment_info = columns.List(value_type=columns.UserDefinedType(user_type=EnvironmentInfo))
103
+
104
+ _no_upstream = ["rust"]
105
+
106
+ _TEST_ADAPTERS = {
107
+ "java": java_driver_matrix_adapter,
108
+ "cpp": cpp_driver_matrix_adapter,
109
+ "python": python_driver_matrix_adapter,
110
+ "gocql": gocql_driver_matrix_adapter,
111
+ }
112
+
113
+ _artifact_fnames = {
114
+ "cpp": r"TEST-(?P<driver_name>[\w]*)-(?P<version>[\d\.-]*)",
115
+ "gocql": r"xunit\.(?P<driver_name>[\w]*)\.(?P<proto>v\d)\.(?P<version>[v\d\.]*)",
116
+ "python": r"pytest\.(?P<driver_name>[\w]*)\.(?P<proto>v\d)\.(?P<version>[\d\.]*)",
117
+ "java": r"TEST-(?P<version>[\d\.\w-]*)",
118
+ "rust": r"(?P<driver_name>rust)_results_v(?P<version>[\d\w\-.]*)",
119
+ }
120
+
121
+ @classmethod
122
+ def _stats_query(cls) -> str:
123
+ return ("SELECT id, test_id, group_id, release_id, status, start_time, build_job_url, build_id, "
124
+ f"assignee, end_time, investigation_status, heartbeat, scylla_version FROM {cls.table_name()} WHERE build_id IN ? PER PARTITION LIMIT 15")
125
+
126
+ @classmethod
127
+ def get_distinct_product_versions(cls, release: ArgusRelease) -> list[str]:
128
+ cluster = ScyllaCluster.get()
129
+ statement = cluster.prepare(f"SELECT scylla_version FROM {cls.table_name()} WHERE release_id = ?")
130
+ rows = cluster.session.execute(query=statement, parameters=(release.id,))
131
+ unique_versions = {r["scylla_version"] for r in rows if r["scylla_version"]}
132
+
133
+ return sorted(list(unique_versions), reverse=True)
134
+
135
+ @classmethod
136
+ def load_test_run(cls, run_id: UUID) -> 'DriverTestRun':
137
+ return cls.get(id=run_id)
138
+
139
+ @classmethod
140
+ def parse_driver_name(cls, raw_file_name: str) -> str:
141
+ for test, pattern in cls._artifact_fnames.items():
142
+ match = re.match(pattern, raw_file_name)
143
+ if not match:
144
+ continue
145
+ driver_info = match.groupdict()
146
+ if test == "java":
147
+ version = driver_info["version"]
148
+ return "scylla" if len(version.split(".")) > 3 or "scylla" in version else "datastax"
149
+ else:
150
+ return driver_info["driver_name"]
151
+ return "unknown_driver"
152
+
153
+ @classmethod
154
+ def submit_run(cls, request_data: dict) -> 'DriverTestRun':
155
+ if request_data["schema_version"] == "v2":
156
+ req = DriverMatrixRunSubmissionRequestV2(**request_data)
157
+ else:
158
+ return cls.submit_matrix_run(request_data)
159
+
160
+ run = cls()
161
+ run.id = req.run_id
162
+ run.build_id = req.job_name
163
+ run.build_job_url = req.job_url
164
+ run.start_time = datetime.utcnow()
165
+ run.assign_categories()
166
+ try:
167
+ run.assignee = run.get_scheduled_assignee()
168
+ except Exception:
169
+ run.assignee = None
170
+
171
+ run.status = TestStatus.CREATED.value
172
+ run.save()
173
+ return run
174
+
175
+ @classmethod
176
+ def submit_driver_result(cls, run_id: UUID, driver_name: str, driver_type: TestTypeType, xml_data: str):
177
+ run: DriverTestRun = cls.get(id=run_id)
178
+
179
+ collection = run.parse_result_xml(driver_name, xml_data, driver_type)
180
+ run.test_collection.append(collection)
181
+
182
+ if run.status == TestStatus.CREATED:
183
+ run.status = TestStatus.RUNNING.value
184
+
185
+ run.save()
186
+ return run
187
+
188
+ @classmethod
189
+ def submit_driver_failure(cls, run_id: UUID, driver_name: str, driver_type: TestTypeType, fail_message: str):
190
+ run: DriverTestRun = cls.get(id=run_id)
191
+
192
+ collection = TestCollection()
193
+ collection.failures = 1
194
+ collection.failure_message = fail_message
195
+ collection.name = driver_name
196
+ driver_info = run.get_driver_info(driver_name, driver_type)
197
+ collection.driver = driver_info.get("driver_name")
198
+ collection.tests_total = 1
199
+ run.test_collection.append(collection)
200
+
201
+ if run.status == TestStatus.CREATED:
202
+ run.status = TestStatus.RUNNING.value
203
+
204
+ run.save()
205
+ return run
206
+
207
+ @classmethod
208
+ def submit_env_info(cls, run_id: UUID, env_data: str):
209
+ run: DriverTestRun = cls.get(id=run_id)
210
+ env = run.parse_build_environment(env_data)
211
+
212
+ for key, value in env.items():
213
+ env_info = EnvironmentInfo()
214
+ env_info.key = key
215
+ env_info.value = value
216
+ run.environment_info.append(env_info)
217
+
218
+ run.scylla_version = env.get("scylla-version")
219
+
220
+ run.save()
221
+ return run
222
+
223
+ def parse_build_environment(self, raw_env: str) -> dict[str, str]:
224
+ result = {}
225
+ for line in raw_env.split("\n"):
226
+ if not line:
227
+ continue
228
+ LOGGER.debug("ENV: %s", line)
229
+ key, val = line.split(": ")
230
+ result[key] = val.strip()
231
+
232
+ return result
233
+
234
+ def get_test_cases(self, cases: list[ElementTree.Element]) -> list[TestCase]:
235
+ result = []
236
+ for raw_case in cases:
237
+ children = list(raw_case.findall("./*"))
238
+ if len(children) > 0:
239
+ status = children[0].tag
240
+ message = f"{children[0].attrib.get('message', 'no-message')} ({children[0].attrib.get('type', 'no-type')})"
241
+ else:
242
+ status = "passed"
243
+ message = ""
244
+
245
+ case = TestCase()
246
+ case.name = raw_case.attrib["name"]
247
+ case.status = status
248
+ case.time = float(raw_case.attrib.get("time", 0.0))
249
+ case.classname = raw_case.attrib.get("classname", "")
250
+ case.message = message
251
+ result.append(case)
252
+
253
+ return result
254
+
255
+ def get_driver_info(self, xml_name: str, test_type: TestTypeType) -> dict[str, str]:
256
+ if test_type == "cpp":
257
+ filename_re = r"TEST-(?P<driver_name>[\w]*)-(?P<version>[\d\.]*)(\.xml)?"
258
+ else:
259
+ filename_re = r"(?P<name>[\w]*)\.(?P<driver_name>[\w]*)\.(?P<proto>v\d)\.(?P<version>[\d\.]*)(\.xml)?"
260
+
261
+ match = re.match(filename_re, xml_name)
262
+
263
+ return match.groupdict() if match else {}
264
+
265
+ def get_passed_count(self, suite_attribs: dict[str, str]) -> int:
266
+ if (pass_count := suite_attribs.get("passed")):
267
+ return int(pass_count)
268
+ total = int(suite_attribs.get("tests", 0))
269
+ errors = int(suite_attribs.get("errors", 0))
270
+ skipped = int(suite_attribs.get("skipped", 0))
271
+ failures = int(suite_attribs.get("failures", 0))
272
+
273
+ return total - errors - skipped - failures
274
+
275
+ def parse_result_xml(self, name: str, xml_data: str, test_type: TestTypeType) -> TestCollection:
276
+ xml: ElementTree.ElementTree = ElementTree.ElementTree(ElementTree.fromstring(xml_data))
277
+ LOGGER.debug("%s", pformat(xml))
278
+ testsuites = xml.getroot()
279
+ adapted_data = self._TEST_ADAPTERS.get(test_type, generic_adapter)(xml)
280
+
281
+ driver_info = self.get_driver_info(name, test_type)
282
+ test_collection = TestCollection()
283
+ test_collection.timestamp = datetime.fromisoformat(
284
+ adapted_data["timestamp"][0:-1] if adapted_data["timestamp"][-1] == "Z" else adapted_data["timestamp"])
285
+ test_collection.name = name
286
+ test_collection.driver = driver_info.get("driver_name")
287
+ test_collection.tests_total = 0
288
+ test_collection.failures = 0
289
+ test_collection.errors = 0
290
+ test_collection.disabled = 0
291
+ test_collection.skipped = 0
292
+ test_collection.passed = 0
293
+ test_collection.time = 0.0
294
+ test_collection.suites = []
295
+
296
+ for xml_suite in testsuites.iter("testsuite"):
297
+ suite = TestSuite()
298
+ suite.name = xml_suite.attrib["name"]
299
+ suite.tests_total = int(xml_suite.attrib.get("tests", 0))
300
+ suite.failures = int(xml_suite.attrib.get("failures", 0))
301
+ suite.disabled = int(0)
302
+ suite.passed = self.get_passed_count(xml_suite.attrib)
303
+ suite.skipped = int(xml_suite.attrib.get("skipped", 0))
304
+ suite.errors = int(xml_suite.attrib.get("errors", 0))
305
+ suite.time = float(xml_suite.attrib["time"])
306
+ suite.cases = self.get_test_cases(xml_suite.findall("testcase"))
307
+
308
+ test_collection.suites.append(suite)
309
+ test_collection.tests_total += suite.tests_total
310
+ test_collection.failures += suite.failures
311
+ test_collection.errors += suite.errors
312
+ test_collection.disabled += suite.disabled
313
+ test_collection.skipped += suite.skipped
314
+ test_collection.passed += suite.passed
315
+ test_collection.time += suite.time
316
+
317
+ return test_collection
318
+
319
+ @classmethod
320
+ def submit_matrix_run(cls, request_data):
321
+ # Legacy method
322
+ req = DriverMatrixRunSubmissionRequest(**request_data)
323
+ run = cls()
324
+ run.id = req.run_id
325
+ run.build_id = req.job_name
326
+ run.build_job_url = req.job_url
327
+ run.assign_categories()
328
+ try:
329
+ run.assignee = run.get_scheduled_assignee()
330
+ except Exception:
331
+ run.assignee = None
332
+ for key, value in req.test_environment.items():
333
+ env_info = EnvironmentInfo()
334
+ env_info.key = key
335
+ env_info.value = value
336
+ run.environment_info.append(env_info)
337
+
338
+ run.scylla_version = req.test_environment.get("scylla-version")
339
+ run.test_collection = []
340
+
341
+ for result in req.matrix_results:
342
+ collection = TestCollection()
343
+ collection.name = result.get("name")
344
+ collection.driver = cls.parse_driver_name(collection.name)
345
+ collection.tests_total = result.get("tests")
346
+ collection.failures = result.get("failures")
347
+ collection.errors = result.get("errors")
348
+ collection.skipped = result.get("skipped")
349
+ collection.passed = result.get("passed")
350
+ collection.disabled = result.get("disabled")
351
+ collection.time = result.get("time")
352
+ timestamp = result.get("timestamp")
353
+ # TODO: Not needed once python>=3.11
354
+ timestamp = timestamp[0:-1] if timestamp[-1] == "Z" else timestamp
355
+ collection.timestamp = datetime.fromisoformat(timestamp)
356
+ for raw_suite in result.get("suites"):
357
+ suite = TestSuite()
358
+ suite.name = raw_suite.get("name")
359
+ suite.tests_total = raw_suite.get("tests")
360
+ suite.failures = raw_suite.get("failures")
361
+ suite.errors = raw_suite.get("errors")
362
+ suite.skipped = raw_suite.get("skipped")
363
+ suite.passed = raw_suite.get("passed")
364
+ suite.disabled = raw_suite.get("disabled")
365
+ suite.time = raw_suite.get("time")
366
+
367
+ for raw_case in raw_suite.get("cases"):
368
+ case = TestCase()
369
+ case.name = raw_case.get("name")
370
+ case.status = raw_case.get("status")
371
+ case.time = raw_case.get("time")
372
+ case.classname = raw_case.get("classname")
373
+ case.message = raw_case.get("message")
374
+ suite.cases.append(case)
375
+
376
+ collection.suites.append(suite)
377
+ run.test_collection.append(collection)
378
+
379
+ run.status = run._determine_run_status().value
380
+ run.save()
381
+ return run
382
+
383
+ def get_resources(self) -> list:
384
+ return []
385
+
386
+ def get_nemeses(self) -> list:
387
+ return []
388
+
389
+ def _determine_run_status(self):
390
+ for collection in self.test_collection:
391
+ # patch failure
392
+ if collection.failure_message:
393
+ return TestStatus.FAILED
394
+
395
+ if len(self.test_collection) < 2:
396
+ return TestStatus.FAILED
397
+
398
+ driver_types = {collection.driver for collection in self.test_collection}
399
+ if len(driver_types) <= 1 and not any(driver for driver in self._no_upstream if driver in driver_types):
400
+ return TestStatus.FAILED
401
+
402
+ failure_count = reduce(lambda acc, val: acc + (val.failures or 0 + val.errors or 0), self.test_collection, 0)
403
+ if failure_count > 0:
404
+ return TestStatus.FAILED
405
+
406
+ return TestStatus.PASSED
407
+
408
+ def change_status(self, new_status: TestStatus):
409
+ self.status = new_status
410
+
411
+ def get_events(self) -> list:
412
+ return []
413
+
414
+ def submit_product_version(self, version: str):
415
+ self.scylla_version = version
416
+ try:
417
+ new_assignee = self.get_assignment(version)
418
+ except Model.DoesNotExist:
419
+ new_assignee = None
420
+ if new_assignee:
421
+ self.assignee = new_assignee
422
+
423
+ def finish_run(self, payload: dict = None):
424
+ self.end_time = datetime.utcnow()
425
+ status = payload.get("status", "passed")
426
+ self.status = TestStatus(status).value
427
+
428
+ def submit_logs(self, logs: list[dict]):
429
+ pass
@@ -0,0 +1,21 @@
1
+ from flask import Blueprint
2
+
3
+ from argus.backend.plugins.core import PluginInfoBase, PluginModelBase
4
+ from argus.backend.plugins.driver_matrix_tests.model import DriverTestRun
5
+ from argus.backend.plugins.driver_matrix_tests.controller import bp as driver_matrix_api_bp
6
+ from argus.backend.plugins.driver_matrix_tests.udt import TestCollection, EnvironmentInfo, TestCase, TestSuite
7
+
8
+
9
+ class PluginInfo(PluginInfoBase):
10
+ name: str = "driver-matrix-tests"
11
+ model: PluginModelBase = DriverTestRun
12
+ controller: Blueprint = driver_matrix_api_bp
13
+ all_models = [
14
+ DriverTestRun,
15
+ ]
16
+ all_types = [
17
+ TestCollection,
18
+ TestSuite,
19
+ TestCase,
20
+ EnvironmentInfo
21
+ ]
@@ -0,0 +1,62 @@
1
+ from dataclasses import dataclass
2
+ from typing import TypedDict
3
+ from uuid import UUID
4
+
5
+
6
+ class RawMatrixTestCase(TypedDict):
7
+ name: str
8
+ status: str
9
+ time: float
10
+ classname: str
11
+ message: str
12
+
13
+
14
+ class RawMatrixTestSuite(TypedDict):
15
+ name: str
16
+ tests: int
17
+ failures: int
18
+ disabled: int
19
+ skipped: int
20
+ passed: int
21
+ errors: int
22
+ time: float
23
+ cases: list[RawMatrixTestCase]
24
+
25
+
26
+ class RawMatrixTestResult(TypedDict):
27
+ name: str
28
+ driver_name: str
29
+ tests: int
30
+ failures: int
31
+ errors: int
32
+ disabled: int
33
+ skipped: int
34
+ passed: int
35
+ time: float
36
+ timestamp: str
37
+ suites: list[RawMatrixTestSuite]
38
+
39
+
40
+ @dataclass(init=True, frozen=True)
41
+ class DriverMatrixSubmitResultRequest():
42
+ schema_version: str
43
+ run_id: UUID
44
+ driver_type: str
45
+ driver_name: str
46
+ raw_xml: str
47
+
48
+
49
+ @dataclass(init=True, frozen=True)
50
+ class DriverMatrixSubmitFailureRequest():
51
+ schema_version: str
52
+ run_id: UUID
53
+ driver_type: str
54
+ driver_name: str
55
+ failure_reason: str
56
+
57
+
58
+ @dataclass(init=True, frozen=True)
59
+ class DriverMatrixSubmitEnvRequest():
60
+ schema_version: str
61
+ run_id: UUID
62
+ raw_env: str
@@ -0,0 +1,61 @@
1
+ import base64
2
+ import logging
3
+ from uuid import UUID
4
+ from argus.backend.db import ScyllaCluster
5
+ from argus.backend.models.web import ArgusRelease, ArgusTest
6
+ from argus.backend.plugins.driver_matrix_tests.model import DriverTestRun
7
+
8
+
9
+ LOGGER = logging.getLogger(__name__)
10
+
11
+
12
+ class DriverMatrixService:
13
+ def tested_versions_report(self, build_id: str) -> dict:
14
+ db = ScyllaCluster.get()
15
+ all_runs_for_test_query = db.prepare(f"SELECT * FROM {DriverTestRun.table_name()} WHERE build_id = ?")
16
+
17
+ rows = list(db.session.execute(all_runs_for_test_query, parameters=(build_id,)).all())
18
+
19
+ if len(rows) == 0:
20
+ raise Exception(f"No results for build_id {build_id}", build_id)
21
+
22
+ latest = rows[0]
23
+ try:
24
+ test: ArgusTest = ArgusTest.get(id=latest["test_id"])
25
+ release: ArgusRelease = ArgusRelease.get(id=latest["release_id"])
26
+ except (ArgusTest.DoesNotExist, ArgusRelease.DoesNotExist):
27
+ raise Exception(
28
+ f"Unable to find release and test information for build_id {build_id} and run_id {latest['id']}", build_id, latest["id"])
29
+
30
+ version_map = {}
31
+
32
+ for row in rows:
33
+ driver_versions = [(col["name"], col["driver"]) for col in row["test_collection"]]
34
+ for version, driver_type in driver_versions:
35
+ driver_type = driver_type if driver_type else "Unknown"
36
+ versions: list = version_map.get(driver_type, [])
37
+ if version not in versions:
38
+ versions.append(version)
39
+ versions.sort()
40
+ version_map[driver_type] = versions
41
+
42
+ response = {
43
+ "release": release.name,
44
+ "test": test.name,
45
+ "build_id": build_id,
46
+ "versions": version_map,
47
+ }
48
+ return response
49
+
50
+ def submit_driver_result(self, run_id: UUID | str, driver_name: str, driver_type: str, raw_xml: str) -> bool:
51
+ xml_data = base64.decodebytes(bytes(raw_xml, encoding="utf-8"))
52
+ DriverTestRun.submit_driver_result(UUID(run_id), driver_name, driver_type, xml_data)
53
+ return True
54
+
55
+ def submit_driver_failure(self, run_id: UUID | str, driver_name: str, driver_type: str, failure_reason: str) -> bool:
56
+ DriverTestRun.submit_driver_failure(UUID(run_id), driver_name, driver_type, failure_reason)
57
+ return True
58
+
59
+ def submit_env_info(self, run_id: UUID | str, raw_env: str) -> bool:
60
+ DriverTestRun.submit_env_info(UUID(run_id), raw_env)
61
+ return True
@@ -0,0 +1,42 @@
1
+ from cassandra.cqlengine.usertype import UserType
2
+ from cassandra.cqlengine import columns
3
+
4
+
5
+ class TestCase(UserType):
6
+ name = columns.Text()
7
+ status = columns.Text()
8
+ time = columns.Float()
9
+ classname = columns.Text()
10
+ message = columns.Text()
11
+
12
+
13
+ class TestSuite(UserType):
14
+ name = columns.Text()
15
+ tests_total = columns.Integer(default=lambda: 0)
16
+ failures = columns.Integer(default=lambda: 0)
17
+ disabled = columns.Integer(default=lambda: 0)
18
+ skipped = columns.Integer(default=lambda: 0)
19
+ passed = columns.Integer(default=lambda: 0)
20
+ errors = columns.Integer(default=lambda: 0)
21
+ time = columns.Float()
22
+ cases = columns.List(value_type=columns.UserDefinedType(user_type=TestCase))
23
+
24
+
25
+ class TestCollection(UserType):
26
+ name = columns.Text()
27
+ driver = columns.Text()
28
+ tests_total = columns.Integer(default=lambda: 0)
29
+ failure_message = columns.Text()
30
+ failures = columns.Integer(default=lambda: 0)
31
+ disabled = columns.Integer(default=lambda: 0)
32
+ skipped = columns.Integer(default=lambda: 0)
33
+ passed = columns.Integer(default=lambda: 0)
34
+ errors = columns.Integer(default=lambda: 0)
35
+ timestamp = columns.DateTime()
36
+ time = columns.Float(default=lambda: 0.0)
37
+ suites = columns.List(value_type=columns.UserDefinedType(user_type=TestSuite))
38
+
39
+
40
+ class EnvironmentInfo(UserType):
41
+ key = columns.Text()
42
+ value = columns.Text()