1 # TestFinder class, define set of tests to run.
3 # Copyright (c) 2020-2021 Virtuozzo International GmbH
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.
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.
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/>.
22 from collections
import defaultdict
23 from contextlib
import contextmanager
24 from typing
import Optional
, List
, Iterator
, Set
28 def chdir(path
: Optional
[str] = None) -> Iterator
[None]:
33 saved_dir
= os
.getcwd()
42 def __init__(self
, test_dir
: Optional
[str] = None) -> None:
43 self
.groups
= defaultdict(set)
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')]
51 for t
in self
.all_tests
:
52 with
open(t
, encoding
="utf-8") as f
:
54 if line
.startswith('# group: '):
55 for g
in line
.split()[2:]:
59 def add_group_file(self
, fname
: str) -> None:
60 with
open(fname
, encoding
="utf-8") as f
:
64 if (not line
) or line
[0] == '#':
68 test_file
= self
.parse_test_name(words
[0])
72 self
.groups
[g
].add(test_file
)
74 def parse_test_name(self
, name
: str) -> str:
76 raise ValueError('Paths are unsupported for test selection, '
77 f
'requiring "{name}" is wrong')
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}'
84 # Named tests all should be in tests/ subdirectory
85 name
= os
.path
.join('tests', name
)
87 if name
not in self
.all_tests
:
88 raise ValueError(f
'Test "{name}" is not found')
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]:
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)
106 b. else, if some @tests specified:
107 b.1 exclude_groups must be not specified, so just take @tests
109 c. else (only @exclude_groups list is non-empty):
111 c.2 Drop tests, which are in at least one of @exclude_groups or in
116 3. If start_from specified, drop tests from first one to @start_from
121 if exclude_groups
is None:
126 res
: Set
[str] = set()
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
))
132 # Some individual tests specified, but no groups. In this case
133 # we don't support exclude_groups.
135 raise ValueError("Can't exclude from individually specified "
138 # No tests no groups: start from all tests, exclude_groups
140 res
.update(self
.all_tests
)
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']
146 res
= res
.difference(*(self
.groups
[g
] for g
in exclude_groups
))
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.
152 res
.add(self
.parse_test_name(t
))
154 sequence
= sorted(res
)
156 if start_from
is not None:
157 del sequence
[:sequence
.index(self
.parse_test_name(start_from
))]