argus-alm 0.12.4b2__py3-none-any.whl → 0.12.5__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.
- argus/backend/controller/client_api.py +11 -0
- argus/backend/controller/testrun_api.py +11 -0
- argus/backend/models/result.py +41 -0
- argus/backend/models/web.py +4 -0
- argus/backend/plugins/core.py +4 -2
- argus/backend/plugins/driver_matrix_tests/controller.py +0 -39
- argus/backend/plugins/driver_matrix_tests/model.py +2 -248
- argus/backend/plugins/driver_matrix_tests/raw_types.py +0 -27
- argus/backend/plugins/driver_matrix_tests/service.py +0 -18
- argus/backend/plugins/driver_matrix_tests/udt.py +13 -14
- argus/backend/plugins/sct/testrun.py +7 -0
- argus/backend/service/client_service.py +18 -0
- argus/backend/service/testrun.py +19 -0
- argus/client/base.py +16 -0
- argus/client/driver_matrix_tests/client.py +193 -56
- argus/client/generic_result.py +99 -0
- argus/client/generic_result_old.py +143 -0
- {argus_alm-0.12.4b2.dist-info → argus_alm-0.12.5.dist-info}/METADATA +1 -1
- {argus_alm-0.12.4b2.dist-info → argus_alm-0.12.5.dist-info}/RECORD +22 -20
- {argus_alm-0.12.4b2.dist-info → argus_alm-0.12.5.dist-info}/entry_points.txt +0 -1
- argus/client/driver_matrix_tests/cli.py +0 -110
- {argus_alm-0.12.4b2.dist-info → argus_alm-0.12.5.dist-info}/LICENSE +0 -0
- {argus_alm-0.12.4b2.dist-info → argus_alm-0.12.5.dist-info}/WHEEL +0 -0
|
@@ -90,3 +90,14 @@ def run_finalize(run_type: str, run_id: str):
|
|
|
90
90
|
"status": "ok",
|
|
91
91
|
"response": result
|
|
92
92
|
}
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
@bp.route("/testrun/<string:run_type>/<string:run_id>/submit_results", methods=["POST"])
|
|
96
|
+
@api_login_required
|
|
97
|
+
def submit_results(run_type: str, run_id: str):
|
|
98
|
+
payload = get_payload(request)
|
|
99
|
+
result = ClientService().submit_results(run_type=run_type, run_id=run_id, results=payload)
|
|
100
|
+
return {
|
|
101
|
+
"status": "ok",
|
|
102
|
+
"response": result
|
|
103
|
+
}
|
|
@@ -63,6 +63,17 @@ def test_run_activity(run_id: str):
|
|
|
63
63
|
}
|
|
64
64
|
|
|
65
65
|
|
|
66
|
+
|
|
67
|
+
@bp.route("/run/<string:test_id>/<string:run_id>/fetch_results", methods=["GET"])
|
|
68
|
+
@api_login_required
|
|
69
|
+
def fetch_results(test_id: str, run_id: str):
|
|
70
|
+
tables = TestRunService().fetch_results(test_id=UUID(test_id), run_id=UUID(run_id))
|
|
71
|
+
return {
|
|
72
|
+
"status": "ok",
|
|
73
|
+
"tables": tables
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
|
|
66
77
|
@bp.route("/test/<string:test_id>/run/<string:run_id>/status/set", methods=["POST"])
|
|
67
78
|
@api_login_required
|
|
68
79
|
def set_testrun_status(test_id: str, run_id: str):
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
from cassandra.cqlengine import columns
|
|
2
|
+
from cassandra.cqlengine.models import Model
|
|
3
|
+
from cassandra.cqlengine.usertype import UserType
|
|
4
|
+
from enum import Enum
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Status(Enum):
|
|
8
|
+
PASS = 0
|
|
9
|
+
WARNING = 1
|
|
10
|
+
ERROR = 2
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class ColumnMetadata(UserType):
|
|
14
|
+
name = columns.Ascii()
|
|
15
|
+
unit = columns.Text()
|
|
16
|
+
type = columns.Ascii()
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class ArgusGenericResultMetadata(Model):
|
|
20
|
+
__table_name__ = "generic_result_metadata_v1"
|
|
21
|
+
test_id = columns.UUID(partition_key=True)
|
|
22
|
+
name = columns.Text(required=True, primary_key=True)
|
|
23
|
+
description = columns.Text()
|
|
24
|
+
columns_meta = columns.List(value_type=columns.UserDefinedType(ColumnMetadata))
|
|
25
|
+
rows_meta = columns.List(value_type=columns.Ascii())
|
|
26
|
+
|
|
27
|
+
def __init__(self, **kwargs):
|
|
28
|
+
kwargs["columns_meta"] = [ColumnMetadata(**col) for col in kwargs.pop('columns_meta', [])]
|
|
29
|
+
super().__init__(**kwargs)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class ArgusGenericResultData(Model):
|
|
33
|
+
__table_name__ = "generic_result_data_v1"
|
|
34
|
+
test_id = columns.UUID(partition_key=True)
|
|
35
|
+
name = columns.Text(partition_key=True)
|
|
36
|
+
run_id = columns.UUID(primary_key=True)
|
|
37
|
+
column = columns.Ascii(primary_key=True, index=True)
|
|
38
|
+
row = columns.Ascii(primary_key=True, index=True)
|
|
39
|
+
sut_timestamp = columns.DateTime() # for sorting
|
|
40
|
+
value = columns.Double()
|
|
41
|
+
status = columns.Ascii()
|
argus/backend/models/web.py
CHANGED
|
@@ -6,6 +6,8 @@ from cassandra.cqlengine.usertype import UserType
|
|
|
6
6
|
from cassandra.cqlengine import columns
|
|
7
7
|
from cassandra.util import uuid_from_time, unix_time_from_uuid1 # pylint: disable=no-name-in-module
|
|
8
8
|
|
|
9
|
+
from argus.backend.models.result import ArgusGenericResultMetadata, ArgusGenericResultData
|
|
10
|
+
|
|
9
11
|
|
|
10
12
|
def uuid_now():
|
|
11
13
|
return uuid_from_time(datetime.utcnow())
|
|
@@ -377,6 +379,8 @@ USED_MODELS: list[Model] = [
|
|
|
377
379
|
ArgusScheduleAssignee,
|
|
378
380
|
ArgusScheduleGroup,
|
|
379
381
|
ArgusScheduleTest,
|
|
382
|
+
ArgusGenericResultMetadata,
|
|
383
|
+
ArgusGenericResultData,
|
|
380
384
|
]
|
|
381
385
|
|
|
382
386
|
USED_TYPES: list[UserType] = [
|
argus/backend/plugins/core.py
CHANGED
|
@@ -30,7 +30,7 @@ class PluginModelBase(Model):
|
|
|
30
30
|
_plugin_name = "unknown"
|
|
31
31
|
# Metadata
|
|
32
32
|
build_id = columns.Text(required=True, partition_key=True)
|
|
33
|
-
start_time = columns.DateTime(required=True, primary_key=True, clustering_order="DESC", default=datetime.
|
|
33
|
+
start_time = columns.DateTime(required=True, primary_key=True, clustering_order="DESC", default=datetime.now, custom_index=True)
|
|
34
34
|
id = columns.UUID(index=True, required=True)
|
|
35
35
|
release_id = columns.UUID(index=True)
|
|
36
36
|
group_id = columns.UUID(index=True)
|
|
@@ -105,7 +105,7 @@ class PluginModelBase(Model):
|
|
|
105
105
|
assignees = ArgusScheduleAssignee.filter(
|
|
106
106
|
schedule_id=schedule.id
|
|
107
107
|
).all()
|
|
108
|
-
assignees_uuids.
|
|
108
|
+
assignees_uuids.extend([assignee.assignee for assignee in assignees])
|
|
109
109
|
|
|
110
110
|
return assignees_uuids[0] if len(assignees_uuids) > 0 else None
|
|
111
111
|
|
|
@@ -213,6 +213,8 @@ class PluginModelBase(Model):
|
|
|
213
213
|
def finish_run(self, payload: dict = None):
|
|
214
214
|
raise NotImplementedError()
|
|
215
215
|
|
|
216
|
+
def sut_timestamp(self) -> float:
|
|
217
|
+
raise NotImplementedError()
|
|
216
218
|
|
|
217
219
|
class PluginInfoBase:
|
|
218
220
|
# pylint: disable=too-few-public-methods
|
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
from flask import Blueprint, request
|
|
2
2
|
|
|
3
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
4
|
from argus.backend.service.user import api_login_required
|
|
6
5
|
from argus.backend.plugins.driver_matrix_tests.service import DriverMatrixService
|
|
7
|
-
from argus.backend.util.common import get_payload
|
|
8
6
|
|
|
9
7
|
bp = Blueprint("driver_matrix_api", __name__, url_prefix="/driver_matrix")
|
|
10
8
|
bp.register_error_handler(Exception, handle_api_exception)
|
|
@@ -24,40 +22,3 @@ def driver_matrix_test_report():
|
|
|
24
22
|
"status": "ok",
|
|
25
23
|
"response": result
|
|
26
24
|
}
|
|
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, driver_type=request_data.driver_type, run_id=request_data.run_id, raw_xml=request_data.raw_xml)
|
|
35
|
-
return {
|
|
36
|
-
"status": "ok",
|
|
37
|
-
"response": result
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
@bp.route("/result/fail", methods=["POST"])
|
|
42
|
-
@api_login_required
|
|
43
|
-
def submit_failure():
|
|
44
|
-
payload = get_payload(request)
|
|
45
|
-
request_data = DriverMatrixSubmitFailureRequest(**payload)
|
|
46
|
-
|
|
47
|
-
result = DriverMatrixService().submit_driver_failure(driver_name=request_data.driver_name, driver_type=request_data.driver_type, run_id=request_data.run_id, failure_reason=request_data.failure_reason)
|
|
48
|
-
return {
|
|
49
|
-
"status": "ok",
|
|
50
|
-
"response": result
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
@bp.route("/env/submit", methods=["POST"])
|
|
54
|
-
@api_login_required
|
|
55
|
-
def submit_env():
|
|
56
|
-
payload = get_payload(request)
|
|
57
|
-
request_data = DriverMatrixSubmitEnvRequest(**payload)
|
|
58
|
-
|
|
59
|
-
result = DriverMatrixService().submit_env_info(run_id=request_data.run_id, raw_env=request_data.raw_env)
|
|
60
|
-
return {
|
|
61
|
-
"status": "ok",
|
|
62
|
-
"response": result
|
|
63
|
-
}
|
|
@@ -1,12 +1,8 @@
|
|
|
1
1
|
from dataclasses import dataclass
|
|
2
2
|
from datetime import datetime
|
|
3
3
|
from functools import reduce
|
|
4
|
-
import logging
|
|
5
|
-
from pprint import pformat
|
|
6
4
|
import re
|
|
7
|
-
from typing import Literal, TypedDict
|
|
8
5
|
from uuid import UUID
|
|
9
|
-
from xml.etree import ElementTree
|
|
10
6
|
from cassandra.cqlengine import columns
|
|
11
7
|
from argus.backend.db import ScyllaCluster
|
|
12
8
|
from argus.backend.models.web import ArgusRelease
|
|
@@ -16,8 +12,6 @@ from argus.backend.plugins.driver_matrix_tests.raw_types import RawMatrixTestRes
|
|
|
16
12
|
from argus.backend.util.enums import TestStatus
|
|
17
13
|
|
|
18
14
|
|
|
19
|
-
LOGGER = logging.getLogger(__name__)
|
|
20
|
-
|
|
21
15
|
class DriverMatrixPluginError(Exception):
|
|
22
16
|
pass
|
|
23
17
|
|
|
@@ -32,65 +26,6 @@ class DriverMatrixRunSubmissionRequest():
|
|
|
32
26
|
matrix_results: list[RawMatrixTestResult]
|
|
33
27
|
|
|
34
28
|
|
|
35
|
-
@dataclass(init=True, repr=True, frozen=True)
|
|
36
|
-
class DriverMatrixRunSubmissionRequestV2():
|
|
37
|
-
schema_version: str
|
|
38
|
-
run_id: str
|
|
39
|
-
job_name: str
|
|
40
|
-
job_url: str
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
TestTypeType = Literal['java', 'cpp', 'python', 'gocql']
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
class AdaptedXUnitData(TypedDict):
|
|
47
|
-
timestamp: str
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
def python_driver_matrix_adapter(xml: ElementTree.ElementTree) -> AdaptedXUnitData:
|
|
51
|
-
testsuites = list(xml.getroot().iter("testsuite"))
|
|
52
|
-
|
|
53
|
-
return {
|
|
54
|
-
"timestamp": testsuites[0].attrib.get("timestamp"),
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
def java_driver_matrix_adapter(xml: ElementTree.ElementTree) -> AdaptedXUnitData:
|
|
59
|
-
testsuites = xml.getroot()
|
|
60
|
-
ts_now = datetime.utcnow().timestamp()
|
|
61
|
-
try:
|
|
62
|
-
time_taken = float(testsuites.attrib.get("time"))
|
|
63
|
-
except ValueError:
|
|
64
|
-
time_taken = 0.0
|
|
65
|
-
|
|
66
|
-
timestamp = datetime.utcfromtimestamp(ts_now - time_taken).isoformat()
|
|
67
|
-
|
|
68
|
-
return {
|
|
69
|
-
"timestamp": timestamp,
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
def cpp_driver_matrix_adapter(xml: ElementTree.ElementTree) -> AdaptedXUnitData:
|
|
74
|
-
testsuites = xml.getroot()
|
|
75
|
-
|
|
76
|
-
return {
|
|
77
|
-
"timestamp": testsuites.attrib.get("timestamp"),
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
def gocql_driver_matrix_adapter(xml: ElementTree.ElementTree) -> AdaptedXUnitData:
|
|
82
|
-
testsuites = list(xml.getroot().iter("testsuite"))
|
|
83
|
-
|
|
84
|
-
return {
|
|
85
|
-
"timestamp": testsuites[0].attrib.get("timestamp"),
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
def generic_adapter(xml: ElementTree.ElementTree) -> AdaptedXUnitData:
|
|
90
|
-
return {
|
|
91
|
-
"timestamp": datetime.utcnow().isoformat()
|
|
92
|
-
}
|
|
93
|
-
|
|
94
29
|
class DriverTestRun(PluginModelBase):
|
|
95
30
|
_plugin_name = "driver-matrix-tests"
|
|
96
31
|
__table_name__ = "driver_test_run"
|
|
@@ -100,14 +35,6 @@ class DriverTestRun(PluginModelBase):
|
|
|
100
35
|
|
|
101
36
|
_no_upstream = ["rust"]
|
|
102
37
|
|
|
103
|
-
_TEST_ADAPTERS = {
|
|
104
|
-
"java": java_driver_matrix_adapter,
|
|
105
|
-
"cpp": cpp_driver_matrix_adapter,
|
|
106
|
-
"python": python_driver_matrix_adapter,
|
|
107
|
-
"gocql": gocql_driver_matrix_adapter,
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
|
|
111
38
|
_artifact_fnames = {
|
|
112
39
|
"cpp": r"TEST-(?P<driver_name>[\w]*)-(?P<version>[\d\.-]*)",
|
|
113
40
|
"gocql": r"xunit\.(?P<driver_name>[\w]*)\.(?P<proto>v\d)\.(?P<version>[v\d\.]*)",
|
|
@@ -151,173 +78,6 @@ class DriverTestRun(PluginModelBase):
|
|
|
151
78
|
|
|
152
79
|
@classmethod
|
|
153
80
|
def submit_run(cls, request_data: dict) -> 'DriverTestRun':
|
|
154
|
-
if request_data["schema_version"] == "v2":
|
|
155
|
-
req = DriverMatrixRunSubmissionRequestV2(**request_data)
|
|
156
|
-
else:
|
|
157
|
-
return cls.submit_matrix_run(request_data)
|
|
158
|
-
|
|
159
|
-
run = cls()
|
|
160
|
-
run.id = req.run_id
|
|
161
|
-
run.build_id = req.job_name
|
|
162
|
-
run.build_job_url = req.job_url
|
|
163
|
-
run.start_time = datetime.utcnow()
|
|
164
|
-
run.assign_categories()
|
|
165
|
-
try:
|
|
166
|
-
run.assignee = run.get_scheduled_assignee()
|
|
167
|
-
except Exception: # pylint: disable=broad-except
|
|
168
|
-
run.assignee = None
|
|
169
|
-
|
|
170
|
-
run.status = TestStatus.CREATED.value
|
|
171
|
-
run.save()
|
|
172
|
-
return run
|
|
173
|
-
|
|
174
|
-
@classmethod
|
|
175
|
-
def submit_driver_result(cls, run_id: UUID, driver_name: str, driver_type: TestTypeType, xml_data: str):
|
|
176
|
-
run: DriverTestRun = cls.get(id=run_id)
|
|
177
|
-
|
|
178
|
-
collection = run.parse_result_xml(driver_name, xml_data, driver_type)
|
|
179
|
-
run.test_collection.append(collection)
|
|
180
|
-
|
|
181
|
-
if run.status == TestStatus.CREATED:
|
|
182
|
-
run.status = TestStatus.RUNNING.value
|
|
183
|
-
|
|
184
|
-
run.save()
|
|
185
|
-
return run
|
|
186
|
-
|
|
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(adapted_data["timestamp"][0:-1] if adapted_data["timestamp"][-1] == "Z" else adapted_data["timestamp"])
|
|
284
|
-
test_collection.name = name
|
|
285
|
-
test_collection.driver = driver_info.get("driver_name")
|
|
286
|
-
test_collection.tests_total = 0
|
|
287
|
-
test_collection.failures = 0
|
|
288
|
-
test_collection.errors = 0
|
|
289
|
-
test_collection.disabled = 0
|
|
290
|
-
test_collection.skipped = 0
|
|
291
|
-
test_collection.passed = 0
|
|
292
|
-
test_collection.time = 0.0
|
|
293
|
-
test_collection.suites = []
|
|
294
|
-
|
|
295
|
-
for xml_suite in testsuites.iter("testsuite"):
|
|
296
|
-
suite = TestSuite()
|
|
297
|
-
suite.name = xml_suite.attrib["name"]
|
|
298
|
-
suite.tests_total = int(xml_suite.attrib.get("tests", 0))
|
|
299
|
-
suite.failures = int(xml_suite.attrib.get("failures", 0))
|
|
300
|
-
suite.disabled = int(0)
|
|
301
|
-
suite.passed = self.get_passed_count(xml_suite.attrib)
|
|
302
|
-
suite.skipped = int(xml_suite.attrib.get("skipped", 0))
|
|
303
|
-
suite.errors = int(xml_suite.attrib.get("errors", 0))
|
|
304
|
-
suite.time = float(xml_suite.attrib["time"])
|
|
305
|
-
suite.cases = self.get_test_cases(xml_suite.findall("testcase"))
|
|
306
|
-
|
|
307
|
-
test_collection.suites.append(suite)
|
|
308
|
-
test_collection.tests_total += suite.tests_total
|
|
309
|
-
test_collection.failures += suite.failures
|
|
310
|
-
test_collection.errors += suite.errors
|
|
311
|
-
test_collection.disabled += suite.disabled
|
|
312
|
-
test_collection.skipped += suite.skipped
|
|
313
|
-
test_collection.passed += suite.passed
|
|
314
|
-
test_collection.time += suite.time
|
|
315
|
-
|
|
316
|
-
return test_collection
|
|
317
|
-
|
|
318
|
-
@classmethod
|
|
319
|
-
def submit_matrix_run(cls, request_data):
|
|
320
|
-
# Legacy method
|
|
321
81
|
req = DriverMatrixRunSubmissionRequest(**request_data)
|
|
322
82
|
run = cls()
|
|
323
83
|
run.id = req.run_id # pylint: disable=invalid-name
|
|
@@ -386,11 +146,6 @@ class DriverTestRun(PluginModelBase):
|
|
|
386
146
|
return []
|
|
387
147
|
|
|
388
148
|
def _determine_run_status(self):
|
|
389
|
-
for collection in self.test_collection:
|
|
390
|
-
# patch failure
|
|
391
|
-
if collection.failure_message:
|
|
392
|
-
return TestStatus.FAILED
|
|
393
|
-
|
|
394
149
|
if len(self.test_collection) < 2:
|
|
395
150
|
return TestStatus.FAILED
|
|
396
151
|
|
|
@@ -398,14 +153,14 @@ class DriverTestRun(PluginModelBase):
|
|
|
398
153
|
if len(driver_types) <= 1 and not any(driver for driver in self._no_upstream if driver in driver_types):
|
|
399
154
|
return TestStatus.FAILED
|
|
400
155
|
|
|
401
|
-
failure_count = reduce(lambda acc, val: acc + (val.failures
|
|
156
|
+
failure_count = reduce(lambda acc, val: acc + (val.failures + val.errors), self.test_collection, 0)
|
|
402
157
|
if failure_count > 0:
|
|
403
158
|
return TestStatus.FAILED
|
|
404
159
|
|
|
405
160
|
return TestStatus.PASSED
|
|
406
161
|
|
|
407
162
|
def change_status(self, new_status: TestStatus):
|
|
408
|
-
|
|
163
|
+
raise DriverMatrixPluginError("This method is obsolete. Status is now determined on submission.")
|
|
409
164
|
|
|
410
165
|
def get_events(self) -> list:
|
|
411
166
|
return []
|
|
@@ -415,7 +170,6 @@ class DriverTestRun(PluginModelBase):
|
|
|
415
170
|
|
|
416
171
|
def finish_run(self, payload: dict = None):
|
|
417
172
|
self.end_time = datetime.utcnow()
|
|
418
|
-
self.status = self._determine_run_status().value
|
|
419
173
|
|
|
420
174
|
def submit_logs(self, logs: list[dict]):
|
|
421
175
|
pass
|
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
from dataclasses import dataclass
|
|
2
1
|
from typing import TypedDict
|
|
3
|
-
from uuid import UUID
|
|
4
2
|
|
|
5
3
|
|
|
6
4
|
class RawMatrixTestCase(TypedDict):
|
|
@@ -35,28 +33,3 @@ class RawMatrixTestResult(TypedDict):
|
|
|
35
33
|
time: float
|
|
36
34
|
timestamp: str
|
|
37
35
|
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
|
|
@@ -1,13 +1,8 @@
|
|
|
1
|
-
import base64
|
|
2
|
-
import logging
|
|
3
|
-
from uuid import UUID
|
|
4
1
|
from argus.backend.db import ScyllaCluster
|
|
5
2
|
from argus.backend.models.web import ArgusRelease, ArgusTest
|
|
6
3
|
from argus.backend.plugins.driver_matrix_tests.model import DriverTestRun
|
|
7
4
|
|
|
8
5
|
|
|
9
|
-
LOGGER = logging.getLogger(__name__)
|
|
10
|
-
|
|
11
6
|
class DriverMatrixService:
|
|
12
7
|
def tested_versions_report(self, build_id: str) -> dict:
|
|
13
8
|
db = ScyllaCluster.get()
|
|
@@ -45,16 +40,3 @@ class DriverMatrixService:
|
|
|
45
40
|
"versions": version_map,
|
|
46
41
|
}
|
|
47
42
|
return response
|
|
48
|
-
|
|
49
|
-
def submit_driver_result(self, run_id: UUID | str, driver_name: str, driver_type: str, raw_xml: str) -> bool:
|
|
50
|
-
xml_data = base64.decodebytes(bytes(raw_xml, encoding="utf-8"))
|
|
51
|
-
DriverTestRun.submit_driver_result(UUID(run_id), driver_name, driver_type, xml_data)
|
|
52
|
-
return True
|
|
53
|
-
|
|
54
|
-
def submit_driver_failure(self, run_id: UUID | str, driver_name: str, driver_type: str, failure_reason: str) -> bool:
|
|
55
|
-
DriverTestRun.submit_driver_failure(UUID(run_id), driver_name, driver_type, failure_reason)
|
|
56
|
-
return True
|
|
57
|
-
|
|
58
|
-
def submit_env_info(self, run_id: UUID | str, raw_env: str) -> bool:
|
|
59
|
-
DriverTestRun.submit_env_info(UUID(run_id), raw_env)
|
|
60
|
-
return True
|
|
@@ -12,12 +12,12 @@ class TestCase(UserType):
|
|
|
12
12
|
|
|
13
13
|
class TestSuite(UserType):
|
|
14
14
|
name = columns.Text()
|
|
15
|
-
tests_total = columns.Integer(
|
|
16
|
-
failures = columns.Integer(
|
|
17
|
-
disabled = columns.Integer(
|
|
18
|
-
skipped = columns.Integer(
|
|
19
|
-
passed = columns.Integer(
|
|
20
|
-
errors = columns.Integer(
|
|
15
|
+
tests_total = columns.Integer()
|
|
16
|
+
failures = columns.Integer()
|
|
17
|
+
disabled = columns.Integer()
|
|
18
|
+
skipped = columns.Integer()
|
|
19
|
+
passed = columns.Integer()
|
|
20
|
+
errors = columns.Integer()
|
|
21
21
|
time = columns.Float()
|
|
22
22
|
cases = columns.List(value_type=columns.UserDefinedType(user_type=TestCase))
|
|
23
23
|
|
|
@@ -25,15 +25,14 @@ class TestSuite(UserType):
|
|
|
25
25
|
class TestCollection(UserType):
|
|
26
26
|
name = columns.Text()
|
|
27
27
|
driver = columns.Text()
|
|
28
|
-
tests_total = columns.Integer(
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
errors = columns.Integer(default=lambda: 0)
|
|
28
|
+
tests_total = columns.Integer()
|
|
29
|
+
failures = columns.Integer()
|
|
30
|
+
disabled = columns.Integer()
|
|
31
|
+
skipped = columns.Integer()
|
|
32
|
+
passed = columns.Integer()
|
|
33
|
+
errors = columns.Integer()
|
|
35
34
|
timestamp = columns.DateTime()
|
|
36
|
-
time = columns.Float(
|
|
35
|
+
time = columns.Float()
|
|
37
36
|
suites = columns.List(value_type=columns.UserDefinedType(user_type=TestSuite))
|
|
38
37
|
|
|
39
38
|
|
|
@@ -249,6 +249,13 @@ class SCTTestRun(PluginModelBase):
|
|
|
249
249
|
|
|
250
250
|
self._collect_event_message(event, event_message)
|
|
251
251
|
|
|
252
|
+
def sut_timestamp(self) -> float:
|
|
253
|
+
"""converts scylla-server date to timestamp and adds revision in subseconds precision to diffirentiate
|
|
254
|
+
scylla versions from the same day. It's not perfect, but we don't know exact version time."""
|
|
255
|
+
scylla_package = [package for package in self.packages if package.name == "scylla-server"][0]
|
|
256
|
+
return (datetime.strptime(scylla_package.date, '%Y%m%d').timestamp()
|
|
257
|
+
+ int(scylla_package.revision_id, 16) % 1000000 / 1000000)
|
|
258
|
+
|
|
252
259
|
|
|
253
260
|
class SCTJunitReports(Model):
|
|
254
261
|
test_id = columns.UUID(primary_key=True, partition_key=True, required=True)
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from uuid import UUID
|
|
2
2
|
from argus.backend.db import ScyllaCluster
|
|
3
|
+
from argus.backend.models.result import ArgusGenericResultMetadata, ArgusGenericResultData
|
|
3
4
|
from argus.backend.plugins.core import PluginModelBase
|
|
4
5
|
from argus.backend.plugins.loader import AVAILABLE_PLUGINS
|
|
5
6
|
from argus.backend.util.enums import TestStatus
|
|
@@ -69,3 +70,20 @@ class ClientService:
|
|
|
69
70
|
run.save()
|
|
70
71
|
|
|
71
72
|
return "Finalized"
|
|
73
|
+
|
|
74
|
+
def submit_results(self, run_type: str, run_id: str, results: dict) -> str:
|
|
75
|
+
model = self.get_model(run_type)
|
|
76
|
+
run = model.load_test_run(UUID(run_id))
|
|
77
|
+
ArgusGenericResultMetadata(test_id=run.test_id, **results["meta"]).save()
|
|
78
|
+
if results.get("sut_timestamp", 0) == 0:
|
|
79
|
+
results["sut_timestamp"] = run.sut_timestamp() # automatic sut_timestamp
|
|
80
|
+
table_name = results["meta"]["name"]
|
|
81
|
+
sut_timestamp = results["sut_timestamp"]
|
|
82
|
+
for cell in results["results"]:
|
|
83
|
+
ArgusGenericResultData(test_id=run.test_id,
|
|
84
|
+
run_id=run.id,
|
|
85
|
+
name=table_name,
|
|
86
|
+
sut_timestamp=sut_timestamp,
|
|
87
|
+
**cell
|
|
88
|
+
).save()
|
|
89
|
+
return "Submitted"
|
argus/backend/service/testrun.py
CHANGED
|
@@ -13,6 +13,7 @@ from flask import g
|
|
|
13
13
|
from cassandra.query import BatchStatement, ConsistencyLevel
|
|
14
14
|
from cassandra.cqlengine.query import BatchQuery
|
|
15
15
|
from argus.backend.db import ScyllaCluster
|
|
16
|
+
from argus.backend.models.result import ArgusGenericResultMetadata, ArgusGenericResultData
|
|
16
17
|
|
|
17
18
|
from argus.backend.models.web import (
|
|
18
19
|
ArgusEvent,
|
|
@@ -306,6 +307,24 @@ class TestRunService:
|
|
|
306
307
|
}
|
|
307
308
|
return response
|
|
308
309
|
|
|
310
|
+
def fetch_results(self, test_id: UUID, run_id: UUID) -> dict:
|
|
311
|
+
query_fields = ["column", "row", "value", "status"]
|
|
312
|
+
tables_meta = ArgusGenericResultMetadata.filter(test_id=test_id)
|
|
313
|
+
tables = []
|
|
314
|
+
for table in tables_meta:
|
|
315
|
+
cells = ArgusGenericResultData.objects.filter(test_id=test_id, run_id=run_id, name=table.name).only(query_fields)
|
|
316
|
+
if not cells:
|
|
317
|
+
continue
|
|
318
|
+
tables.append({'meta': {
|
|
319
|
+
'name': table.name,
|
|
320
|
+
'description': table.description,
|
|
321
|
+
'columns_meta': table.columns_meta,
|
|
322
|
+
'rows_meta': table.rows_meta
|
|
323
|
+
},
|
|
324
|
+
'cells': [{k:v for k,v in cell.items() if k in query_fields} for cell in cells]})
|
|
325
|
+
|
|
326
|
+
return tables
|
|
327
|
+
|
|
309
328
|
def submit_github_issue(self, issue_url: str, test_id: UUID, run_id: UUID):
|
|
310
329
|
user_tokens = UserOauthToken.filter(user_id=g.user.id).all()
|
|
311
330
|
token = None
|