Skip to content

Commit 428ed07

Browse files
committed
twister: use filter cache
Signed-off-by: Anas Nashif <[email protected]>
1 parent fbe79e5 commit 428ed07

File tree

3 files changed

+96
-4
lines changed

3 files changed

+96
-4
lines changed

scripts/pylib/twister/twisterlib/environment.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -291,6 +291,11 @@ def add_parse_arguments(parser = None):
291291
files in the directory will be processed. The directory should have the same
292292
structure in the main Zephyr tree: boards/<vendor>/<board_name>/""")
293293

294+
parser.add_argument(
295+
"--filter-cache",
296+
help="Use existing filter cache to speed up test selection."
297+
)
298+
294299
parser.add_argument(
295300
"--allow-installed-plugin", action="store_true", default=None,
296301
help="Allow to use pytest plugin installed by pip for pytest tests."

scripts/pylib/twister/twisterlib/runner.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -690,7 +690,6 @@ def run_cmake(self, args="", filter_stages=[]):
690690
with open(os.path.join(self.build_dir, self.log), "a", encoding=self.default_encoding) as log:
691691
log_msg = out.decode(self.default_encoding)
692692
log.write(log_msg)
693-
694693
return ret
695694

696695

@@ -1731,10 +1730,11 @@ def add_tasks_to_queue(self, pipeline, build_only=False, test_only=False, retry_
17311730
if self.results.iteration > 1:
17321731
ProjectBuilder._add_instance_testcases_to_status_counts(instance, self.results, decrement=True)
17331732

1734-
# Check if cmake package_helper script can be run in advance.
17351733
instance.filter_stages = []
1736-
if instance.testsuite.filter:
1737-
instance.filter_stages = self.get_cmake_filter_stages(instance.testsuite.filter, expr_parser.reserved.keys())
1734+
if not self.env.options.filter_cache:
1735+
# Check if cmake package_helper script can be run in advance.
1736+
if instance.testsuite.filter:
1737+
instance.filter_stages = self.get_cmake_filter_stages(instance.testsuite.filter, expr_parser.reserved.keys())
17381738

17391739
if test_only and instance.run:
17401740
pipeline.put({"op": "run", "test": instance})

scripts/pylib/twister/twisterlib/testplan.py

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import sys
88
import re
99
import subprocess
10+
import pickle
1011
import glob
1112
import json
1213
import collections
@@ -35,6 +36,7 @@
3536
from twisterlib.statuses import TwisterStatus
3637
from twisterlib.testinstance import TestInstance
3738
from twisterlib.quarantine import Quarantine
39+
from twisterlib.cmakecache import CMakeCache
3840

3941
import list_boards
4042
from zephyr_module import parse_modules
@@ -51,6 +53,8 @@
5153
sys.path.insert(0, os.path.join(ZEPHYR_BASE, "scripts/"))
5254

5355
import scl
56+
import expr_parser
57+
5458
class Filters:
5559
# platform keys
5660
PLATFORM_KEY = 'platform key filter'
@@ -99,6 +103,7 @@ def __init__(self, env=None):
99103

100104
self.options = env.options
101105
self.env = env
106+
self.filter_cache = {}
102107

103108
# Keep track of which test cases we've filtered out and why
104109
self.testsuites = {}
@@ -267,6 +272,9 @@ def load(self):
267272

268273
self.generate_subset(subset, int(sets))
269274

275+
if self.options.filter_cache:
276+
logger.info(f"Using filter cache: {self.options.filter_cache}")
277+
270278
def generate_subset(self, subset, sets):
271279
# Test instances are sorted depending on the context. For CI runs
272280
# the execution order is: "plat1-testA, plat1-testB, ...,
@@ -721,6 +729,80 @@ def check_platform(self, platform, platform_list):
721729
return True
722730
return False
723731

732+
def parse_generated(self, instance):
733+
config_re = re.compile('(CONFIG_[A-Za-z0-9_]+)[=]\"?([^\"]*)\"?$')
734+
dt_re = re.compile('([A-Za-z0-9_]+)[=]\"?([^\"]*)\"?$')
735+
736+
if instance.platform.name == "unit_testing":
737+
return {}
738+
739+
if not self.filter_cache.get(instance.platform.name):
740+
cache_dir = os.path.join(self.options.filter_cache, instance.platform.normalized_name)
741+
cmake_cache_path = os.path.join(cache_dir, "CMakeCache.txt")
742+
defconfig_path = os.path.join(cache_dir, ".config")
743+
edt_pickle = os.path.join(cache_dir, "edt.pickle")
744+
defconfig = {}
745+
746+
if not os.path.exists(defconfig_path) and not os.path.exists(edt_pickle):
747+
return None
748+
749+
if os.path.exists(defconfig_path):
750+
with open(defconfig_path, "r") as fp:
751+
for line in fp.readlines():
752+
m = config_re.match(line)
753+
if not m:
754+
if line.strip() and not line.startswith("#"):
755+
sys.stderr.write("Unrecognized line %s\n" % line)
756+
continue
757+
defconfig[m.group(1)] = m.group(2).strip()
758+
759+
cmake_conf = {}
760+
if os.path.exists(cmake_cache_path):
761+
762+
try:
763+
cache = CMakeCache.from_file(cmake_cache_path)
764+
except FileNotFoundError:
765+
cache = {}
766+
767+
for k in iter(cache):
768+
cmake_conf[k.name] = k.value
769+
770+
filter_data = {
771+
"ARCH": instance.platform.arch,
772+
"PLATFORM": instance.platform.name
773+
}
774+
filter_data.update(os.environ)
775+
filter_data.update(defconfig)
776+
filter_data.update(cmake_conf)
777+
778+
if os.path.exists(edt_pickle):
779+
with open(edt_pickle, 'rb') as f:
780+
edt = pickle.load(f)
781+
else:
782+
edt = None
783+
784+
self.filter_cache[instance.platform.name] = {'filter': filter_data, 'edt': edt}
785+
786+
if instance.testsuite and instance.testsuite.filter:
787+
try:
788+
edt = self.filter_cache[instance.platform.name]['edt']
789+
filter_data = self.filter_cache[instance.platform.name]['filter']
790+
791+
ret = expr_parser.parse(instance.testsuite.filter, filter_data, edt)
792+
793+
except (ValueError, SyntaxError) as se:
794+
sys.stderr.write(
795+
"Failed processing %s\n" % instance.testsuite.yamlfile)
796+
raise se
797+
798+
if not ret:
799+
return {os.path.join(instance.platform.name, instance.testsuite.name): True}
800+
else:
801+
return {os.path.join(instance.platform.name, instance.testsuite.name): False}
802+
else:
803+
instance.platform.filter_data = filter_data
804+
return None
805+
724806
def apply_filters(self, **kwargs):
725807

726808
toolchain = self.env.toolchain
@@ -868,6 +950,11 @@ def apply_filters(self, **kwargs):
868950
if self.options.integration and ts.integration_platforms and plat.name not in ts.integration_platforms:
869951
instance.add_filter("Not part of integration platforms", Filters.TESTSUITE)
870952

953+
if ts.filter and self.options.filter_cache and not instance.sysbuild:
954+
filter_data = self.parse_generated(instance)
955+
if filter_data and filter_data.get(instance.name, False):
956+
instance.add_filter("Generated filter", Filters.TESTSUITE)
957+
871958
if ts.skip:
872959
instance.add_filter("Skip filter", Filters.SKIP)
873960

0 commit comments

Comments
 (0)