Skip to content

Commit d95eab1

Browse files
gchwiernashif
authored andcommitted
twister: Refactor and extend quarantine implementation in twister
Implementation ported from TwisterV2. - quarantine handled by separate module - multiple yaml allowed from args: --quarantine-list - scenarios, platforms, architectures keywords in quarantine yaml are optional, if not given - means take it all Signed-off-by: Grzegorz Chwierut <[email protected]>
1 parent 2272263 commit d95eab1

File tree

4 files changed

+130
-48
lines changed

4 files changed

+130
-48
lines changed

scripts/pylib/twister/twisterlib/environment.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -475,6 +475,7 @@ def add_parse_arguments(parser = None):
475475

476476
parser.add_argument(
477477
"--quarantine-list",
478+
action="append",
478479
metavar="FILENAME",
479480
help="Load list of test scenarios under quarantine. The entries in "
480481
"the file need to correspond to the test scenarios names as in "
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
# Copyright (c) 2022 Nordic Semiconductor ASA
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
from __future__ import annotations
5+
6+
import logging
7+
8+
from pathlib import Path
9+
from yaml import safe_load
10+
from dataclasses import dataclass, field
11+
12+
13+
logger = logging.getLogger(__name__)
14+
15+
16+
class QuarantineException(Exception):
17+
pass
18+
19+
20+
class Quarantine:
21+
"""Handle tests under quarantine."""
22+
23+
def __init__(self, quarantine_list=[]) -> None:
24+
self.quarantine = QuarantineData()
25+
for quarantine_file in quarantine_list:
26+
self.quarantine.extend(QuarantineData.load_data_from_yaml(quarantine_file))
27+
28+
def get_matched_quarantine(self, testname, platform, architecture):
29+
qelem = self.quarantine.get_matched_quarantine(testname, platform, architecture)
30+
if qelem:
31+
return qelem.comment
32+
return None
33+
34+
35+
@dataclass
36+
class QuarantineElement:
37+
scenarios: list[str] = field(default_factory=list)
38+
platforms: list[str] = field(default_factory=list)
39+
architectures: list[str] = field(default_factory=list)
40+
comment: str = 'under quarantine'
41+
42+
def __post_init__(self):
43+
if 'all' in self.scenarios:
44+
self.scenarios = []
45+
if 'all' in self.platforms:
46+
self.platforms = []
47+
if 'all' in self.architectures:
48+
self.architectures = []
49+
if not any([self.scenarios, self.platforms, self.architectures]):
50+
raise QuarantineException("At least one of filters ('scenarios', 'platforms', "
51+
"'architectures') must be specified")
52+
53+
54+
@dataclass
55+
class QuarantineData:
56+
qlist: list[QuarantineElement] = field(default_factory=list)
57+
58+
def __post_init__(self):
59+
qelements = []
60+
for qelem in self.qlist:
61+
qelements.append(QuarantineElement(**qelem))
62+
self.qlist = qelements
63+
64+
@classmethod
65+
def load_data_from_yaml(cls, filename: str | Path) -> QuarantineData:
66+
"""Load quarantine from yaml file."""
67+
with open(filename, 'r', encoding='UTF-8') as yaml_fd:
68+
qlist: list(dict) = safe_load(yaml_fd)
69+
try:
70+
return cls(qlist)
71+
72+
except Exception as e:
73+
logger.error(f'When loading {filename} received error: {e}')
74+
raise QuarantineException('Cannot load Quarantine data') from e
75+
76+
def extend(self, qdata: QuarantineData) -> list[QuarantineElement]:
77+
self.qlist.extend(qdata.qlist)
78+
79+
def get_matched_quarantine(self, scenario: str, platform: str,
80+
architecture: str) -> QuarantineElement | None:
81+
"""Return quarantine element if test is matched to quarantine rules"""
82+
for qelem in self.qlist:
83+
matched: bool = False
84+
if qelem.scenarios:
85+
if scenario in qelem.scenarios:
86+
matched = True
87+
else:
88+
matched = False
89+
continue
90+
if qelem.platforms:
91+
if platform in qelem.platforms:
92+
matched = True
93+
else:
94+
matched = False
95+
continue
96+
if qelem.architectures:
97+
if architecture in qelem.architectures:
98+
matched = True
99+
else:
100+
matched = False
101+
continue
102+
if matched:
103+
return qelem
104+
105+
return None

scripts/pylib/twister/twisterlib/testplan.py

Lines changed: 16 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
from twisterlib.platform import Platform
3030
from twisterlib.config_parser import TwisterConfigParser
3131
from twisterlib.testinstance import TestInstance
32+
from twisterlib.quarantine import Quarantine
3233

3334

3435
from zephyr_module import parse_modules
@@ -79,7 +80,7 @@ def __init__(self, env=None):
7980

8081
# Keep track of which test cases we've filtered out and why
8182
self.testsuites = {}
82-
self.quarantine = {}
83+
self.quarantine = None
8384
self.platforms = []
8485
self.platform_names = []
8586
self.selected_platforms = []
@@ -128,15 +129,12 @@ def discover(self):
128129

129130
# handle quarantine
130131
ql = self.options.quarantine_list
131-
if ql:
132-
self.load_quarantine(ql)
133-
134132
qv = self.options.quarantine_verify
135-
if qv:
136-
if not ql:
137-
logger.error("No quarantine list given to be verified")
138-
raise TwisterRuntimeError("No quarantine list given to be verified")
139-
133+
if qv and not ql:
134+
logger.error("No quarantine list given to be verified")
135+
raise TwisterRuntimeError("No quarantine list given to be verified")
136+
if ql:
137+
self.quarantine = Quarantine(ql)
140138

141139
def load(self):
142140

@@ -463,35 +461,6 @@ def get_platform(self, name):
463461
break
464462
return selected_platform
465463

466-
def load_quarantine(self, file):
467-
"""
468-
Loads quarantine list from the given yaml file. Creates a dictionary
469-
of all tests configurations (platform + scenario: comment) that shall be
470-
skipped due to quarantine
471-
"""
472-
473-
# Load yaml into quarantine_yaml
474-
quarantine_yaml = scl.yaml_load_verify(file, self.quarantine_schema)
475-
476-
# Create quarantine_list with a product of the listed
477-
# platforms and scenarios for each entry in quarantine yaml
478-
quarantine_list = []
479-
for quar_dict in quarantine_yaml:
480-
if quar_dict['platforms'][0] == "all":
481-
plat = self.platform_names
482-
else:
483-
plat = quar_dict['platforms']
484-
self.verify_platforms_existence(plat, "quarantine-list")
485-
comment = quar_dict.get('comment', "NA")
486-
quarantine_list.append([{".".join([p, s]): comment}
487-
for p in plat for s in quar_dict['scenarios']])
488-
489-
# Flatten the quarantine_list
490-
quarantine_list = [it for sublist in quarantine_list for it in sublist]
491-
# Change quarantine_list into a dictionary
492-
for d in quarantine_list:
493-
self.quarantine.update(d)
494-
495464
def load_from_file(self, file, filter_platform=[]):
496465
with open(file, "r") as json_test_plan:
497466
jtp = json.load(json_test_plan)
@@ -775,14 +744,15 @@ def apply_filters(self, **kwargs):
775744
else:
776745
instance.add_filter(f"Excluded platform missing key fields demanded by test {key_fields}", Filters.PLATFORM)
777746

778-
test_configuration = ".".join([instance.platform.name,
779-
instance.testsuite.id])
780-
# skip quarantined tests
781-
if test_configuration in self.quarantine and not self.options.quarantine_verify:
782-
instance.add_filter(f"Quarantine: {self.quarantine[test_configuration]}", Filters.QUARENTINE)
783-
# run only quarantined test to verify their statuses (skip everything else)
784-
if self.options.quarantine_verify and test_configuration not in self.quarantine:
785-
instance.add_filter("Not under quarantine", Filters.QUARENTINE)
747+
# handle quarantined tests
748+
if self.quarantine:
749+
matched_quarantine = self.quarantine.get_matched_quarantine(
750+
instance.testsuite.id, plat.name, plat.arch
751+
)
752+
if matched_quarantine and not self.options.quarantine_verify:
753+
instance.add_filter(matched_quarantine, Filters.QUARENTINE)
754+
if not matched_quarantine and self.options.quarantine_verify:
755+
instance.add_filter("Not under quarantine", Filters.QUARENTINE)
786756

787757
# if nothing stopped us until now, it means this configuration
788758
# needs to be added.

scripts/schemas/twister/quarantine-schema.yaml

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,18 @@ sequence:
1616
mapping:
1717
"scenarios":
1818
type: seq
19-
required: true
19+
required: false
2020
sequence:
2121
- type: str
2222
- unique: true
2323
"platforms":
24-
required: true
24+
required: false
25+
type: seq
26+
sequence:
27+
- type: str
28+
- unique: True
29+
"architectures":
30+
required: false
2531
type: seq
2632
sequence:
2733
- type: str

0 commit comments

Comments
 (0)