2
0

findtests.py 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159
  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. import os
  19. import glob
  20. import re
  21. from collections import defaultdict
  22. from contextlib import contextmanager
  23. from typing import Optional, List, Iterator, Set
  24. @contextmanager
  25. def chdir(path: Optional[str] = None) -> Iterator[None]:
  26. if path is None:
  27. yield
  28. return
  29. saved_dir = os.getcwd()
  30. os.chdir(path)
  31. try:
  32. yield
  33. finally:
  34. os.chdir(saved_dir)
  35. class TestFinder:
  36. def __init__(self, test_dir: Optional[str] = None) -> None:
  37. self.groups = defaultdict(set)
  38. with chdir(test_dir):
  39. self.all_tests = glob.glob('[0-9][0-9][0-9]')
  40. self.all_tests += [f for f in glob.iglob('tests/*')
  41. if not f.endswith('.out') and
  42. os.path.isfile(f + '.out')]
  43. for t in self.all_tests:
  44. with open(t, encoding="utf-8") as f:
  45. for line in f:
  46. if line.startswith('# group: '):
  47. for g in line.split()[2:]:
  48. self.groups[g].add(t)
  49. break
  50. def add_group_file(self, fname: str) -> None:
  51. with open(fname, encoding="utf-8") as f:
  52. for line in f:
  53. line = line.strip()
  54. if (not line) or line[0] == '#':
  55. continue
  56. words = line.split()
  57. test_file = self.parse_test_name(words[0])
  58. groups = words[1:]
  59. for g in groups:
  60. self.groups[g].add(test_file)
  61. def parse_test_name(self, name: str) -> str:
  62. if '/' in name:
  63. raise ValueError('Paths are unsupported for test selection, '
  64. f'requiring "{name}" is wrong')
  65. if re.fullmatch(r'\d+', name):
  66. # Numbered tests are old naming convention. We should convert them
  67. # to three-digit-length, like 1 --> 001.
  68. name = f'{int(name):03}'
  69. else:
  70. # Named tests all should be in tests/ subdirectory
  71. name = os.path.join('tests', name)
  72. if name not in self.all_tests:
  73. raise ValueError(f'Test "{name}" is not found')
  74. return name
  75. def find_tests(self, groups: Optional[List[str]] = None,
  76. exclude_groups: Optional[List[str]] = None,
  77. tests: Optional[List[str]] = None,
  78. start_from: Optional[str] = None) -> List[str]:
  79. """Find tests
  80. Algorithm:
  81. 1. a. if some @groups specified
  82. a.1 Take all tests from @groups
  83. a.2 Drop tests, which are in at least one of @exclude_groups or in
  84. 'disabled' group (if 'disabled' is not listed in @groups)
  85. a.3 Add tests from @tests (don't exclude anything from them)
  86. b. else, if some @tests specified:
  87. b.1 exclude_groups must be not specified, so just take @tests
  88. c. else (only @exclude_groups list is non-empty):
  89. c.1 Take all tests
  90. c.2 Drop tests, which are in at least one of @exclude_groups or in
  91. 'disabled' group
  92. 2. sort
  93. 3. If start_from specified, drop tests from first one to @start_from
  94. (not inclusive)
  95. """
  96. if groups is None:
  97. groups = []
  98. if exclude_groups is None:
  99. exclude_groups = []
  100. if tests is None:
  101. tests = []
  102. res: Set[str] = set()
  103. if groups:
  104. # Some groups specified. exclude_groups supported, additionally
  105. # selecting some individual tests supported as well.
  106. res.update(*(self.groups[g] for g in groups))
  107. elif tests:
  108. # Some individual tests specified, but no groups. In this case
  109. # we don't support exclude_groups.
  110. if exclude_groups:
  111. raise ValueError("Can't exclude from individually specified "
  112. "tests.")
  113. else:
  114. # No tests no groups: start from all tests, exclude_groups
  115. # supported.
  116. res.update(self.all_tests)
  117. if 'disabled' not in groups and 'disabled' not in exclude_groups:
  118. # Don't want to modify function argument, so create new list.
  119. exclude_groups = exclude_groups + ['disabled']
  120. res = res.difference(*(self.groups[g] for g in exclude_groups))
  121. # We want to add @tests. But for compatibility with old test names,
  122. # we should convert any number < 100 to number padded by
  123. # leading zeroes, like 1 -> 001 and 23 -> 023.
  124. for t in tests:
  125. res.add(self.parse_test_name(t))
  126. sequence = sorted(res)
  127. if start_from is not None:
  128. del sequence[:sequence.index(self.parse_test_name(start_from))]
  129. return sequence