123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159 |
- # TestFinder class, define set of tests to run.
- #
- # Copyright (c) 2020-2021 Virtuozzo International GmbH
- #
- # This program is free software; you can redistribute it and/or modify
- # it under the terms of the GNU General Public License as published by
- # the Free Software Foundation; either version 2 of the License, or
- # (at your option) any later version.
- #
- # This program is distributed in the hope that it will be useful,
- # but WITHOUT ANY WARRANTY; without even the implied warranty of
- # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- # GNU General Public License for more details.
- #
- # You should have received a copy of the GNU General Public License
- # along with this program. If not, see <http://www.gnu.org/licenses/>.
- #
- import os
- import glob
- import re
- from collections import defaultdict
- from contextlib import contextmanager
- from typing import Optional, List, Iterator, Set
- @contextmanager
- def chdir(path: Optional[str] = None) -> Iterator[None]:
- if path is None:
- yield
- return
- saved_dir = os.getcwd()
- os.chdir(path)
- try:
- yield
- finally:
- os.chdir(saved_dir)
- class TestFinder:
- def __init__(self, test_dir: Optional[str] = None) -> None:
- self.groups = defaultdict(set)
- with chdir(test_dir):
- self.all_tests = glob.glob('[0-9][0-9][0-9]')
- self.all_tests += [f for f in glob.iglob('tests/*')
- if not f.endswith('.out') and
- os.path.isfile(f + '.out')]
- for t in self.all_tests:
- with open(t, encoding="utf-8") as f:
- for line in f:
- if line.startswith('# group: '):
- for g in line.split()[2:]:
- self.groups[g].add(t)
- break
- def add_group_file(self, fname: str) -> None:
- with open(fname, encoding="utf-8") as f:
- for line in f:
- line = line.strip()
- if (not line) or line[0] == '#':
- continue
- words = line.split()
- test_file = self.parse_test_name(words[0])
- groups = words[1:]
- for g in groups:
- self.groups[g].add(test_file)
- def parse_test_name(self, name: str) -> str:
- if '/' in name:
- raise ValueError('Paths are unsupported for test selection, '
- f'requiring "{name}" is wrong')
- if re.fullmatch(r'\d+', name):
- # Numbered tests are old naming convention. We should convert them
- # to three-digit-length, like 1 --> 001.
- name = f'{int(name):03}'
- else:
- # Named tests all should be in tests/ subdirectory
- name = os.path.join('tests', name)
- if name not in self.all_tests:
- raise ValueError(f'Test "{name}" is not found')
- return name
- def find_tests(self, groups: Optional[List[str]] = None,
- exclude_groups: Optional[List[str]] = None,
- tests: Optional[List[str]] = None,
- start_from: Optional[str] = None) -> List[str]:
- """Find tests
- Algorithm:
- 1. a. if some @groups specified
- a.1 Take all tests from @groups
- a.2 Drop tests, which are in at least one of @exclude_groups or in
- 'disabled' group (if 'disabled' is not listed in @groups)
- a.3 Add tests from @tests (don't exclude anything from them)
- b. else, if some @tests specified:
- b.1 exclude_groups must be not specified, so just take @tests
- c. else (only @exclude_groups list is non-empty):
- c.1 Take all tests
- c.2 Drop tests, which are in at least one of @exclude_groups or in
- 'disabled' group
- 2. sort
- 3. If start_from specified, drop tests from first one to @start_from
- (not inclusive)
- """
- if groups is None:
- groups = []
- if exclude_groups is None:
- exclude_groups = []
- if tests is None:
- tests = []
- res: Set[str] = set()
- if groups:
- # Some groups specified. exclude_groups supported, additionally
- # selecting some individual tests supported as well.
- res.update(*(self.groups[g] for g in groups))
- elif tests:
- # Some individual tests specified, but no groups. In this case
- # we don't support exclude_groups.
- if exclude_groups:
- raise ValueError("Can't exclude from individually specified "
- "tests.")
- else:
- # No tests no groups: start from all tests, exclude_groups
- # supported.
- res.update(self.all_tests)
- if 'disabled' not in groups and 'disabled' not in exclude_groups:
- # Don't want to modify function argument, so create new list.
- exclude_groups = exclude_groups + ['disabled']
- res = res.difference(*(self.groups[g] for g in exclude_groups))
- # We want to add @tests. But for compatibility with old test names,
- # we should convert any number < 100 to number padded by
- # leading zeroes, like 1 -> 001 and 23 -> 023.
- for t in tests:
- res.add(self.parse_test_name(t))
- sequence = sorted(res)
- if start_from is not None:
- del sequence[:sequence.index(self.parse_test_name(start_from))]
- return sequence
|