argus-alm 0.12.4b1__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.
@@ -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()
@@ -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] = [
@@ -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.utcnow, custom_index=True)
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.append(*[assignee.assignee for assignee in assignees])
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 or 0 + val.errors or 0), self.test_collection, 0)
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
- self.status = new_status
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(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)
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(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)
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(default=lambda: 0.0)
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"
@@ -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