|
7 | 7 | import sys |
8 | 8 | import re |
9 | 9 | import subprocess |
| 10 | +import pickle |
10 | 11 | import glob |
11 | 12 | import json |
12 | 13 | import collections |
|
35 | 36 | from twisterlib.statuses import TwisterStatus |
36 | 37 | from twisterlib.testinstance import TestInstance |
37 | 38 | from twisterlib.quarantine import Quarantine |
| 39 | +from twisterlib.cmakecache import CMakeCache |
38 | 40 |
|
39 | 41 | import list_boards |
40 | 42 | from zephyr_module import parse_modules |
|
51 | 53 | sys.path.insert(0, os.path.join(ZEPHYR_BASE, "scripts/")) |
52 | 54 |
|
53 | 55 | import scl |
| 56 | +import expr_parser |
| 57 | + |
54 | 58 | class Filters: |
55 | 59 | # platform keys |
56 | 60 | PLATFORM_KEY = 'platform key filter' |
@@ -99,6 +103,7 @@ def __init__(self, env=None): |
99 | 103 |
|
100 | 104 | self.options = env.options |
101 | 105 | self.env = env |
| 106 | + self.filter_cache = {} |
102 | 107 |
|
103 | 108 | # Keep track of which test cases we've filtered out and why |
104 | 109 | self.testsuites = {} |
@@ -267,6 +272,9 @@ def load(self): |
267 | 272 |
|
268 | 273 | self.generate_subset(subset, int(sets)) |
269 | 274 |
|
| 275 | + if self.options.filter_cache: |
| 276 | + logger.info(f"Using filter cache: {self.options.filter_cache}") |
| 277 | + |
270 | 278 | def generate_subset(self, subset, sets): |
271 | 279 | # Test instances are sorted depending on the context. For CI runs |
272 | 280 | # the execution order is: "plat1-testA, plat1-testB, ..., |
@@ -721,6 +729,80 @@ def check_platform(self, platform, platform_list): |
721 | 729 | return True |
722 | 730 | return False |
723 | 731 |
|
| 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 | + |
724 | 806 | def apply_filters(self, **kwargs): |
725 | 807 |
|
726 | 808 | toolchain = self.env.toolchain |
@@ -868,6 +950,11 @@ def apply_filters(self, **kwargs): |
868 | 950 | if self.options.integration and ts.integration_platforms and plat.name not in ts.integration_platforms: |
869 | 951 | instance.add_filter("Not part of integration platforms", Filters.TESTSUITE) |
870 | 952 |
|
| 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 | + |
871 | 958 | if ts.skip: |
872 | 959 | instance.add_filter("Skip filter", Filters.SKIP) |
873 | 960 |
|
|
0 commit comments