cachepc-qemu

Fork of AMDESE/qemu with changes for cachepc side-channel attack
git clone https://git.sinitax.com/sinitax/cachepc-qemu
Log | Files | Refs | Submodules | LICENSE | sfeed.txt

findtests.py (5621B)


      1# TestFinder class, define set of tests to run.
      2#
      3# Copyright (c) 2020-2021 Virtuozzo International GmbH
      4#
      5# This program is free software; you can redistribute it and/or modify
      6# it under the terms of the GNU General Public License as published by
      7# the Free Software Foundation; either version 2 of the License, or
      8# (at your option) any later version.
      9#
     10# This program is distributed in the hope that it will be useful,
     11# but WITHOUT ANY WARRANTY; without even the implied warranty of
     12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     13# GNU General Public License for more details.
     14#
     15# You should have received a copy of the GNU General Public License
     16# along with this program.  If not, see <http://www.gnu.org/licenses/>.
     17#
     18
     19import os
     20import glob
     21import re
     22from collections import defaultdict
     23from contextlib import contextmanager
     24from typing import Optional, List, Iterator, Set
     25
     26
     27@contextmanager
     28def chdir(path: Optional[str] = None) -> Iterator[None]:
     29    if path is None:
     30        yield
     31        return
     32
     33    saved_dir = os.getcwd()
     34    os.chdir(path)
     35    try:
     36        yield
     37    finally:
     38        os.chdir(saved_dir)
     39
     40
     41class TestFinder:
     42    def __init__(self, test_dir: Optional[str] = None) -> None:
     43        self.groups = defaultdict(set)
     44
     45        with chdir(test_dir):
     46            self.all_tests = glob.glob('[0-9][0-9][0-9]')
     47            self.all_tests += [f for f in glob.iglob('tests/*')
     48                               if not f.endswith('.out') and
     49                               os.path.isfile(f + '.out')]
     50
     51            for t in self.all_tests:
     52                with open(t, encoding="utf-8") as f:
     53                    for line in f:
     54                        if line.startswith('# group: '):
     55                            for g in line.split()[2:]:
     56                                self.groups[g].add(t)
     57                            break
     58
     59    def add_group_file(self, fname: str) -> None:
     60        with open(fname, encoding="utf-8") as f:
     61            for line in f:
     62                line = line.strip()
     63
     64                if (not line) or line[0] == '#':
     65                    continue
     66
     67                words = line.split()
     68                test_file = self.parse_test_name(words[0])
     69                groups = words[1:]
     70
     71                for g in groups:
     72                    self.groups[g].add(test_file)
     73
     74    def parse_test_name(self, name: str) -> str:
     75        if '/' in name:
     76            raise ValueError('Paths are unsupported for test selection, '
     77                             f'requiring "{name}" is wrong')
     78
     79        if re.fullmatch(r'\d+', name):
     80            # Numbered tests are old naming convention. We should convert them
     81            # to three-digit-length, like 1 --> 001.
     82            name = f'{int(name):03}'
     83        else:
     84            # Named tests all should be in tests/ subdirectory
     85            name = os.path.join('tests', name)
     86
     87        if name not in self.all_tests:
     88            raise ValueError(f'Test "{name}" is not found')
     89
     90        return name
     91
     92    def find_tests(self, groups: Optional[List[str]] = None,
     93                   exclude_groups: Optional[List[str]] = None,
     94                   tests: Optional[List[str]] = None,
     95                   start_from: Optional[str] = None) -> List[str]:
     96        """Find tests
     97
     98        Algorithm:
     99
    100        1. a. if some @groups specified
    101             a.1 Take all tests from @groups
    102             a.2 Drop tests, which are in at least one of @exclude_groups or in
    103                 'disabled' group (if 'disabled' is not listed in @groups)
    104             a.3 Add tests from @tests (don't exclude anything from them)
    105
    106           b. else, if some @tests specified:
    107             b.1 exclude_groups must be not specified, so just take @tests
    108
    109           c. else (only @exclude_groups list is non-empty):
    110             c.1 Take all tests
    111             c.2 Drop tests, which are in at least one of @exclude_groups or in
    112                 'disabled' group
    113
    114        2. sort
    115
    116        3. If start_from specified, drop tests from first one to @start_from
    117           (not inclusive)
    118        """
    119        if groups is None:
    120            groups = []
    121        if exclude_groups is None:
    122            exclude_groups = []
    123        if tests is None:
    124            tests = []
    125
    126        res: Set[str] = set()
    127        if groups:
    128            # Some groups specified. exclude_groups supported, additionally
    129            # selecting some individual tests supported as well.
    130            res.update(*(self.groups[g] for g in groups))
    131        elif tests:
    132            # Some individual tests specified, but no groups. In this case
    133            # we don't support exclude_groups.
    134            if exclude_groups:
    135                raise ValueError("Can't exclude from individually specified "
    136                                 "tests.")
    137        else:
    138            # No tests no groups: start from all tests, exclude_groups
    139            # supported.
    140            res.update(self.all_tests)
    141
    142        if 'disabled' not in groups and 'disabled' not in exclude_groups:
    143            # Don't want to modify function argument, so create new list.
    144            exclude_groups = exclude_groups + ['disabled']
    145
    146        res = res.difference(*(self.groups[g] for g in exclude_groups))
    147
    148        # We want to add @tests. But for compatibility with old test names,
    149        # we should convert any number < 100 to number padded by
    150        # leading zeroes, like 1 -> 001 and 23 -> 023.
    151        for t in tests:
    152            res.add(self.parse_test_name(t))
    153
    154        sequence = sorted(res)
    155
    156        if start_from is not None:
    157            del sequence[:sequence.index(self.parse_test_name(start_from))]
    158
    159        return sequence