qemu

FORK: QEMU emulator
git clone https://git.neptards.moe/neptards/qemu.git
Log | Files | Refs | Submodules | LICENSE

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 
     19 import os
     20 import glob
     21 import re
     22 from collections import defaultdict
     23 from contextlib import contextmanager
     24 from typing import Optional, List, Iterator, Set
     25 
     26 
     27 @contextmanager
     28 def 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 
     41 class 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