opentf-toolkit-nightly 0.55.0.dev938__py3-none-any.whl → 0.55.0.dev950__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.
- opentf/commons/__init__.py +10 -5
- opentf/commons/datasources.py +64 -53
- {opentf_toolkit_nightly-0.55.0.dev938.dist-info → opentf_toolkit_nightly-0.55.0.dev950.dist-info}/METADATA +1 -1
- {opentf_toolkit_nightly-0.55.0.dev938.dist-info → opentf_toolkit_nightly-0.55.0.dev950.dist-info}/RECORD +7 -7
- {opentf_toolkit_nightly-0.55.0.dev938.dist-info → opentf_toolkit_nightly-0.55.0.dev950.dist-info}/LICENSE +0 -0
- {opentf_toolkit_nightly-0.55.0.dev938.dist-info → opentf_toolkit_nightly-0.55.0.dev950.dist-info}/WHEEL +0 -0
- {opentf_toolkit_nightly-0.55.0.dev938.dist-info → opentf_toolkit_nightly-0.55.0.dev950.dist-info}/top_level.txt +0 -0
opentf/commons/__init__.py
CHANGED
|
@@ -585,7 +585,10 @@ def is_uuid(uuid: str) -> bool:
|
|
|
585
585
|
|
|
586
586
|
|
|
587
587
|
def make_status_response(
|
|
588
|
-
reason: str,
|
|
588
|
+
reason: str,
|
|
589
|
+
message: str,
|
|
590
|
+
details: Optional[Dict[str, Any]] = None,
|
|
591
|
+
silent: bool = False,
|
|
589
592
|
) -> Response:
|
|
590
593
|
"""Return a new status response object.
|
|
591
594
|
|
|
@@ -597,6 +600,7 @@ def make_status_response(
|
|
|
597
600
|
# Optional parameters:
|
|
598
601
|
|
|
599
602
|
- details: a dictionary or None (None by default)
|
|
603
|
+
- silent: a boolean (False by default)
|
|
600
604
|
|
|
601
605
|
# Returned value
|
|
602
606
|
|
|
@@ -613,10 +617,11 @@ def make_status_response(
|
|
|
613
617
|
- code: an integer (derived from `reason`)
|
|
614
618
|
"""
|
|
615
619
|
code = REASON_STATUS[reason]
|
|
616
|
-
if
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
+
if not silent:
|
|
621
|
+
if code // 100 == 4:
|
|
622
|
+
logging.warning(message)
|
|
623
|
+
elif code // 100 == 5:
|
|
624
|
+
logging.error(message)
|
|
620
625
|
return make_response(
|
|
621
626
|
{
|
|
622
627
|
'kind': 'Status',
|
opentf/commons/datasources.py
CHANGED
|
@@ -14,7 +14,8 @@
|
|
|
14
14
|
|
|
15
15
|
"""Test case metadata retrieval helpers"""
|
|
16
16
|
|
|
17
|
-
from typing import Any, Dict,
|
|
17
|
+
from typing import Any, Dict, List, Optional
|
|
18
|
+
|
|
18
19
|
from collections import defaultdict
|
|
19
20
|
|
|
20
21
|
from opentf.commons.expressions import evaluate_bool
|
|
@@ -68,17 +69,14 @@ def _as_list(what) -> List[str]:
|
|
|
68
69
|
## Datasource: Testcases
|
|
69
70
|
|
|
70
71
|
|
|
71
|
-
def in_scope(expr: str, contexts: Dict[str, Any]
|
|
72
|
-
"""Safely evaluate
|
|
72
|
+
def in_scope(expr: str, contexts: Dict[str, Any]) -> bool:
|
|
73
|
+
"""Safely evaluate datasource scope."""
|
|
73
74
|
try:
|
|
74
75
|
return evaluate_bool(expr, contexts)
|
|
75
76
|
except ValueError as err:
|
|
76
|
-
|
|
77
|
-
scopes_errors.add(msg)
|
|
77
|
+
raise ValueError(f'Invalid conditional {expr}: {err}.')
|
|
78
78
|
except KeyError as err:
|
|
79
|
-
|
|
80
|
-
scopes_errors.add(msg)
|
|
81
|
-
return False
|
|
79
|
+
raise ValueError(f'Nonexisting context entry in expression {expr}: {err}.')
|
|
82
80
|
|
|
83
81
|
|
|
84
82
|
def get_testresults(events: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
|
|
@@ -300,10 +298,9 @@ def _get_timestamp(
|
|
|
300
298
|
|
|
301
299
|
def _get_testresult_timestamps(
|
|
302
300
|
events: List[Dict[str, Any]],
|
|
303
|
-
|
|
304
|
-
testcase_metadata: Dict[str, Any],
|
|
301
|
+
testcases: Dict[str, Any],
|
|
305
302
|
):
|
|
306
|
-
"""Set timestamp for each testcase in
|
|
303
|
+
"""Set timestamp for each testcase in testcases.
|
|
307
304
|
|
|
308
305
|
The timestamp is the one of the originating ProviderResult.
|
|
309
306
|
"""
|
|
@@ -314,19 +311,42 @@ def _get_testresult_timestamps(
|
|
|
314
311
|
}
|
|
315
312
|
|
|
316
313
|
origins_results = defaultdict(list)
|
|
317
|
-
for
|
|
318
|
-
|
|
319
|
-
origins_results[result['attachment_origin']].append(result['id'])
|
|
314
|
+
for uuid, data in testcases.items():
|
|
315
|
+
origins_results[data['execution']].append(uuid)
|
|
320
316
|
|
|
321
317
|
for event in filter(lambda event: event['kind'] == 'ExecutionResult', events):
|
|
322
318
|
for attachment in event['metadata'].get('attachments', {}).values():
|
|
323
319
|
if attachment['uuid'] in origins_results:
|
|
324
320
|
timestamp = _get_timestamp(event, providerid_creationtimestamps)
|
|
325
321
|
for result_id in origins_results[attachment['uuid']]:
|
|
326
|
-
|
|
322
|
+
testcases[result_id]['timestamp'] = timestamp
|
|
327
323
|
|
|
328
324
|
|
|
329
|
-
def
|
|
325
|
+
def _make_testcase_from_testresult(
|
|
326
|
+
item: Dict[str, Any], execution_id: str, labels: Dict[str, Any], scope: str
|
|
327
|
+
) -> Dict[str, Any]:
|
|
328
|
+
testcase = {
|
|
329
|
+
'name': item['name'],
|
|
330
|
+
'status': item['status'],
|
|
331
|
+
'duration': item.get('duration', 0),
|
|
332
|
+
'execution': execution_id,
|
|
333
|
+
'test': labels.copy(),
|
|
334
|
+
}
|
|
335
|
+
testcase['test']['status'] = item['status']
|
|
336
|
+
if item['status'] in FAILURE_STATUSES:
|
|
337
|
+
for key in DETAILS_KEYS:
|
|
338
|
+
if item.get(key):
|
|
339
|
+
testcase[key] = item[key]
|
|
340
|
+
if item.get('errorsList'):
|
|
341
|
+
testcase['errorsList'] = item['errorsList']
|
|
342
|
+
if not in_scope(scope, testcase):
|
|
343
|
+
return {}
|
|
344
|
+
return testcase
|
|
345
|
+
|
|
346
|
+
|
|
347
|
+
def get_testcases(
|
|
348
|
+
events: List[Dict[str, Any]], scope: str = 'true'
|
|
349
|
+
) -> Dict[str, Dict[str, Any]]:
|
|
330
350
|
"""Extract metadata for each test result.
|
|
331
351
|
|
|
332
352
|
Test results are Notification events with a `.spec.testResults`
|
|
@@ -344,12 +364,13 @@ def get_testcases(events: List[Dict[str, Any]]) -> Dict[str, Dict[str, Any]]:
|
|
|
344
364
|
- name: a string, the test case name
|
|
345
365
|
- status: a string, the test case status
|
|
346
366
|
- duration: a string, the test case execution time in ms
|
|
367
|
+
- execution: a string, the test case execution (=related attachment) uuid
|
|
347
368
|
- timestamp: a string, provider creation timestamp
|
|
348
369
|
- test: a dictionary, the test case metadata
|
|
349
370
|
- failureDetails|errorDetails|warningDetails: a dictionary with test
|
|
350
371
|
case failure details
|
|
351
|
-
- errorsList: a
|
|
352
|
-
errors
|
|
372
|
+
- errorsList: a provider generated attachment specific list with execution general
|
|
373
|
+
errors (currently for Robot Framework only)
|
|
353
374
|
|
|
354
375
|
`testcases` is a dictionary of entries like:
|
|
355
376
|
|
|
@@ -358,7 +379,8 @@ def get_testcases(events: List[Dict[str, Any]]) -> Dict[str, Dict[str, Any]]:
|
|
|
358
379
|
"name": "<<<[Test suite#]Test case name>>>",
|
|
359
380
|
"status": "<<<SUCCESS|FAILURE|ERROR|SKIPPED>>>",
|
|
360
381
|
"duration": "<<<test execution time in ms>>>",
|
|
361
|
-
"timestamp": "<<<provider creation timestamp>>>"
|
|
382
|
+
"timestamp": "<<<provider creation timestamp>>>",
|
|
383
|
+
"execution": "<<<execution ID>>>",
|
|
362
384
|
"test": {
|
|
363
385
|
"job": "<<<job name>>>",
|
|
364
386
|
"uses": "<<<provider function>>>",
|
|
@@ -384,39 +406,32 @@ def get_testcases(events: List[Dict[str, Any]]) -> Dict[str, Dict[str, Any]]:
|
|
|
384
406
|
# Raised exceptions
|
|
385
407
|
|
|
386
408
|
A _ValueError_ exception is raised if there were no test results in
|
|
387
|
-
`events
|
|
409
|
+
`events` or some scope errors occured retrieving test results.
|
|
388
410
|
"""
|
|
389
|
-
testcases = {}
|
|
390
|
-
results = False
|
|
391
411
|
if _uses_inception(events):
|
|
392
412
|
testresults = _get_inception_testresults(events)
|
|
393
413
|
else:
|
|
394
414
|
testresults = get_testresults(events)
|
|
395
415
|
|
|
416
|
+
if not testresults:
|
|
417
|
+
raise ValueError('No test results in events.')
|
|
418
|
+
|
|
419
|
+
testcases = {}
|
|
396
420
|
for testresult in testresults:
|
|
397
|
-
|
|
398
|
-
labels = _get_testresult_labels(
|
|
399
|
-
testresult['metadata']['attachment_origin'][0], events
|
|
400
|
-
)
|
|
421
|
+
execution_id = testresult['metadata']['attachment_origin'][0]
|
|
422
|
+
labels = _get_testresult_labels(execution_id, events)
|
|
401
423
|
if not labels:
|
|
402
424
|
continue
|
|
403
|
-
for
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
'
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
data = {key: testcase[key] for key in DETAILS_KEYS if testcase.get(key)}
|
|
414
|
-
if testcase.get('errorsList'):
|
|
415
|
-
data['errorsList'] = testcase['errorsList']
|
|
416
|
-
testcases[testcase['id']].update(data)
|
|
417
|
-
if not results:
|
|
418
|
-
raise ValueError('No test results in events.')
|
|
419
|
-
_get_testresult_timestamps(events, testresults, testcases)
|
|
425
|
+
for item in testresult['spec']['testResults']:
|
|
426
|
+
if testcase := _make_testcase_from_testresult(
|
|
427
|
+
item, execution_id, labels, scope
|
|
428
|
+
):
|
|
429
|
+
testcases[item['id']] = testcase
|
|
430
|
+
if not testcases:
|
|
431
|
+
raise ValueError(f'No test cases matching scope `{scope}`.')
|
|
432
|
+
|
|
433
|
+
_get_testresult_timestamps(events, testcases)
|
|
434
|
+
|
|
420
435
|
return testcases
|
|
421
436
|
|
|
422
437
|
|
|
@@ -424,9 +439,7 @@ def get_testcases(events: List[Dict[str, Any]]) -> Dict[str, Dict[str, Any]]:
|
|
|
424
439
|
## Datasource: Tags
|
|
425
440
|
|
|
426
441
|
|
|
427
|
-
def get_tags(
|
|
428
|
-
events: List[Dict[str, Any]], testcase_metadata: Optional[Dict[str, Any]] = None
|
|
429
|
-
) -> Dict[str, Any]:
|
|
442
|
+
def get_tags(events: List[Dict[str, Any]], scope: str = 'true') -> Dict[str, Any]:
|
|
430
443
|
"""Extract metadata for each execution environment tag.
|
|
431
444
|
|
|
432
445
|
# Required parameters:
|
|
@@ -452,7 +465,7 @@ def get_tags(
|
|
|
452
465
|
```
|
|
453
466
|
"""
|
|
454
467
|
try:
|
|
455
|
-
testcase_metadata =
|
|
468
|
+
testcase_metadata = get_testcases(events, scope)
|
|
456
469
|
except ValueError as err:
|
|
457
470
|
raise ValueError(str(err) + ' Cannot extract metadata for tags.')
|
|
458
471
|
tags = {}
|
|
@@ -471,7 +484,7 @@ def get_tags(
|
|
|
471
484
|
## Datasource: Jobs
|
|
472
485
|
|
|
473
486
|
|
|
474
|
-
def _evaluate_test_results(jobs_testcases: Dict[str, Any]) -> Dict[str,
|
|
487
|
+
def _evaluate_test_results(jobs_testcases: Dict[str, Any]) -> Dict[str, Dict[str, int]]:
|
|
475
488
|
"""Summarize job testcases.
|
|
476
489
|
|
|
477
490
|
# Returned value
|
|
@@ -494,9 +507,7 @@ def _evaluate_test_results(jobs_testcases: Dict[str, Any]) -> Dict[str, Any]:
|
|
|
494
507
|
return summaries
|
|
495
508
|
|
|
496
509
|
|
|
497
|
-
def get_jobs(
|
|
498
|
-
events: List[Dict[str, Any]], testcase_metadata: Optional[Dict[str, Any]] = None
|
|
499
|
-
) -> Dict[str, Any]:
|
|
510
|
+
def get_jobs(events: List[Dict[str, Any]], scope: str = 'true') -> Dict[str, Any]:
|
|
500
511
|
"""Extract metadata for each job.
|
|
501
512
|
|
|
502
513
|
# Required parameters:
|
|
@@ -534,7 +545,7 @@ def get_jobs(
|
|
|
534
545
|
```
|
|
535
546
|
"""
|
|
536
547
|
try:
|
|
537
|
-
testcase_metadata =
|
|
548
|
+
testcase_metadata = get_testcases(events, scope)
|
|
538
549
|
except ValueError as err:
|
|
539
550
|
raise ValueError(str(err) + ' Cannot extract metadata for jobs.')
|
|
540
551
|
jobs_testcases = _get_sorted_testcases(testcase_metadata, ['test', 'job'])
|
|
@@ -555,7 +566,7 @@ def get_jobs(
|
|
|
555
566
|
job['metadata']['name'],
|
|
556
567
|
{
|
|
557
568
|
'runs-on': job['runs-on'],
|
|
558
|
-
'counts':
|
|
569
|
+
'counts': results[job_name],
|
|
559
570
|
'variables': {
|
|
560
571
|
name: value for name, value in job.get('variables', {}).items()
|
|
561
572
|
},
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
opentf/commons/__init__.py,sha256=
|
|
1
|
+
opentf/commons/__init__.py,sha256=KRY8ShQw_0ZZ0oEOiKi4-xnWofE_QsFjEe1T8wUxJ-w,21952
|
|
2
2
|
opentf/commons/auth.py,sha256=bM2Z3kxm2Wku1lKXaRAIg37LHvXWAXIZIqjplDfN2P8,15899
|
|
3
3
|
opentf/commons/config.py,sha256=GmvInVnUsXIwlNfgTQeQ_pPs97GeGTGn2S2QZEFwss8,7828
|
|
4
|
-
opentf/commons/datasources.py,sha256=
|
|
4
|
+
opentf/commons/datasources.py,sha256=EIssc6CyC8s5FYVZtdEo7IpCQxX92mFntYWmsoO62go,18412
|
|
5
5
|
opentf/commons/expressions.py,sha256=A68F27Our8oVVphUrRvB5haSlqj2YCrH2OxHPNLBio4,19251
|
|
6
6
|
opentf/commons/pubsub.py,sha256=7khxAHVZiwJRcwIBJ6MPR-f3xY9144-2eNLROwq5F-4,5894
|
|
7
7
|
opentf/commons/schemas.py,sha256=lokZCU-wmsIkzVA-TVENtC7Io_GmYxrP-FQaOOowg4s,4044
|
|
@@ -48,8 +48,8 @@ opentf/scripts/startup.py,sha256=Da2zo93pBWbdRmj-wgekgLcF94rpNc3ZkbvR8R0w8XY,212
|
|
|
48
48
|
opentf/toolkit/__init__.py,sha256=g3DiTZlSvvzZWKgM8qU47muLqjQrpWZ6M6PWZ-sBsvQ,19610
|
|
49
49
|
opentf/toolkit/channels.py,sha256=Cng3b4LUsxvCHUbp_skys9CFcKZMfcKhA_ODg_EAlIE,17156
|
|
50
50
|
opentf/toolkit/core.py,sha256=L1fT4YzwZjqE7PUXhJL6jSVQge3ohBQv5UBb9DAC6oo,9320
|
|
51
|
-
opentf_toolkit_nightly-0.55.0.
|
|
52
|
-
opentf_toolkit_nightly-0.55.0.
|
|
53
|
-
opentf_toolkit_nightly-0.55.0.
|
|
54
|
-
opentf_toolkit_nightly-0.55.0.
|
|
55
|
-
opentf_toolkit_nightly-0.55.0.
|
|
51
|
+
opentf_toolkit_nightly-0.55.0.dev950.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
52
|
+
opentf_toolkit_nightly-0.55.0.dev950.dist-info/METADATA,sha256=gT2hqExdA5awFpPmq43fIso434TuFdAQOrSlqz1EsG4,1945
|
|
53
|
+
opentf_toolkit_nightly-0.55.0.dev950.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
|
|
54
|
+
opentf_toolkit_nightly-0.55.0.dev950.dist-info/top_level.txt,sha256=_gPuE6GTT6UNXy1DjtmQSfCcZb_qYA2vWmjg7a30AGk,7
|
|
55
|
+
opentf_toolkit_nightly-0.55.0.dev950.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|