plugin.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236
  1. import argparse
  2. import allure
  3. import allure_commons
  4. import os
  5. from allure_commons.types import LabelType, Severity
  6. from allure_commons.logger import AllureFileLogger
  7. from allure_commons.utils import get_testplan
  8. from allure_pytest.utils import allure_label, allure_labels, allure_full_name
  9. from allure_pytest.helper import AllureTestHelper, AllureTitleHelper
  10. from allure_pytest.listener import AllureListener
  11. from allure_pytest.utils import ALLURE_DESCRIPTION_MARK, ALLURE_DESCRIPTION_HTML_MARK
  12. from allure_pytest.utils import ALLURE_LABEL_MARK, ALLURE_LINK_MARK
  13. def pytest_addoption(parser):
  14. parser.getgroup("reporting").addoption('--alluredir',
  15. action="store",
  16. dest="allure_report_dir",
  17. metavar="DIR",
  18. default=None,
  19. help="Generate Allure report in the specified directory (may not exist)")
  20. parser.getgroup("reporting").addoption('--clean-alluredir',
  21. action="store_true",
  22. dest="clean_alluredir",
  23. help="Clean alluredir folder if it exists")
  24. parser.getgroup("reporting").addoption('--allure-no-capture',
  25. action="store_false",
  26. dest="attach_capture",
  27. help="Do not attach pytest captured logging/stdout/stderr to report")
  28. parser.getgroup("reporting").addoption('--inversion',
  29. action="store",
  30. dest="inversion",
  31. default=False,
  32. help="Run tests not in testplan")
  33. def label_type(type_name, legal_values=set()):
  34. def a_label_type(string):
  35. atoms = set(string.split(','))
  36. if type_name is LabelType.SEVERITY:
  37. if not atoms <= legal_values:
  38. raise argparse.ArgumentTypeError('Illegal {} values: {}, only [{}] are allowed'.format(
  39. type_name,
  40. ', '.join(atoms - legal_values),
  41. ', '.join(legal_values)
  42. ))
  43. return set((type_name, allure.severity_level(atom)) for atom in atoms)
  44. return set((type_name, atom) for atom in atoms)
  45. return a_label_type
  46. severities = [x.value for x in list(allure.severity_level)]
  47. formatted_severities = ', '.join(severities)
  48. parser.getgroup("general").addoption('--allure-severities',
  49. action="store",
  50. dest="allure_severities",
  51. metavar="SEVERITIES_SET",
  52. default={},
  53. type=label_type(LabelType.SEVERITY, legal_values=set(severities)),
  54. help=f"""Comma-separated list of severity names.
  55. Tests only with these severities will be run.
  56. Possible values are: {formatted_severities}.""")
  57. parser.getgroup("general").addoption('--allure-epics',
  58. action="store",
  59. dest="allure_epics",
  60. metavar="EPICS_SET",
  61. default={},
  62. type=label_type(LabelType.EPIC),
  63. help="""Comma-separated list of epic names.
  64. Run tests that have at least one of the specified feature labels.""")
  65. parser.getgroup("general").addoption('--allure-features',
  66. action="store",
  67. dest="allure_features",
  68. metavar="FEATURES_SET",
  69. default={},
  70. type=label_type(LabelType.FEATURE),
  71. help="""Comma-separated list of feature names.
  72. Run tests that have at least one of the specified feature labels.""")
  73. parser.getgroup("general").addoption('--allure-stories',
  74. action="store",
  75. dest="allure_stories",
  76. metavar="STORIES_SET",
  77. default={},
  78. type=label_type(LabelType.STORY),
  79. help="""Comma-separated list of story names.
  80. Run tests that have at least one of the specified story labels.""")
  81. parser.getgroup("general").addoption('--allure-ids',
  82. action="store",
  83. dest="allure_ids",
  84. metavar="IDS_SET",
  85. default={},
  86. type=label_type(LabelType.ID),
  87. help="""Comma-separated list of IDs.
  88. Run tests that have at least one of the specified id labels.""")
  89. def cf_type(string):
  90. type_name, values = string.split("=", 1)
  91. atoms = set(values.split(","))
  92. return [(type_name, atom) for atom in atoms]
  93. parser.getgroup("general").addoption('--allure-label',
  94. action="append",
  95. dest="allure_labels",
  96. metavar="LABELS_SET",
  97. default=[],
  98. type=cf_type,
  99. help="""List of labels to run in format label_name=value1,value2.
  100. "Run tests that have at least one of the specified labels.""")
  101. def link_pattern(string):
  102. pattern = string.split(':', 1)
  103. if not pattern[0]:
  104. raise argparse.ArgumentTypeError('Link type is mandatory.')
  105. if len(pattern) != 2:
  106. raise argparse.ArgumentTypeError('Link pattern is mandatory')
  107. return pattern
  108. parser.getgroup("general").addoption('--allure-link-pattern',
  109. action="append",
  110. dest="allure_link_pattern",
  111. metavar="LINK_TYPE:LINK_PATTERN",
  112. default=[],
  113. type=link_pattern,
  114. help="""Url pattern for link type. Allows short links in test,
  115. like 'issue-1'. Text will be formatted to full url with python
  116. str.format().""")
  117. def cleanup_factory(plugin):
  118. def clean_up():
  119. name = allure_commons.plugin_manager.get_name(plugin)
  120. allure_commons.plugin_manager.unregister(name=name)
  121. return clean_up
  122. def pytest_addhooks(pluginmanager):
  123. # Need register title hooks before conftest init
  124. title_helper = AllureTitleHelper()
  125. allure_commons.plugin_manager.register(title_helper)
  126. def pytest_configure(config):
  127. report_dir = config.option.allure_report_dir
  128. clean = False if config.option.collectonly else config.option.clean_alluredir
  129. test_helper = AllureTestHelper(config)
  130. allure_commons.plugin_manager.register(test_helper)
  131. config.add_cleanup(cleanup_factory(test_helper))
  132. if report_dir:
  133. report_dir = os.path.abspath(report_dir)
  134. test_listener = AllureListener(config)
  135. config.pluginmanager.register(test_listener, 'allure_listener')
  136. allure_commons.plugin_manager.register(test_listener)
  137. config.add_cleanup(cleanup_factory(test_listener))
  138. file_logger = AllureFileLogger(report_dir, clean)
  139. allure_commons.plugin_manager.register(file_logger)
  140. config.add_cleanup(cleanup_factory(file_logger))
  141. config.addinivalue_line("markers", f"{ALLURE_LABEL_MARK}: allure label marker")
  142. config.addinivalue_line("markers", f"{ALLURE_LINK_MARK}: allure link marker")
  143. config.addinivalue_line("markers", f"{ALLURE_DESCRIPTION_MARK}: allure description")
  144. config.addinivalue_line("markers", f"{ALLURE_DESCRIPTION_HTML_MARK}: allure description html")
  145. def select_by_labels(items, config):
  146. arg_labels = set().union(
  147. config.option.allure_epics,
  148. config.option.allure_features,
  149. config.option.allure_stories,
  150. config.option.allure_ids,
  151. config.option.allure_severities,
  152. *config.option.allure_labels
  153. )
  154. if arg_labels:
  155. selected, deselected = [], []
  156. for item in items:
  157. test_labels = set(allure_labels(item))
  158. test_severity = allure_label(item, LabelType.SEVERITY)
  159. if not test_severity:
  160. test_labels.add((LabelType.SEVERITY, Severity.NORMAL))
  161. if arg_labels & test_labels:
  162. selected.append(item)
  163. else:
  164. deselected.append(item)
  165. return selected, deselected
  166. else:
  167. return items, []
  168. def select_by_testcase(items, config):
  169. planned_tests = get_testplan()
  170. is_inversion = config.option.inversion
  171. if planned_tests:
  172. def is_planed(item):
  173. allure_ids = allure_label(item, LabelType.ID)
  174. allure_string_ids = list(map(str, allure_ids))
  175. for planed_item in planned_tests:
  176. planed_item_string_id = str(planed_item.get("id"))
  177. planed_item_selector = planed_item.get("selector")
  178. if (
  179. planed_item_string_id in allure_string_ids
  180. or planed_item_selector == allure_full_name(item)
  181. ):
  182. return True if not is_inversion else False
  183. return False if not is_inversion else True
  184. selected, deselected = [], []
  185. for item in items:
  186. selected.append(item) if is_planed(item) else deselected.append(item)
  187. return selected, deselected
  188. else:
  189. return items, []
  190. def pytest_collection_modifyitems(items, config):
  191. selected, deselected_by_testcase = select_by_testcase(items, config)
  192. selected, deselected_by_labels = select_by_labels(selected, config)
  193. items[:] = selected
  194. if deselected_by_testcase or deselected_by_labels:
  195. config.hook.pytest_deselected(items=[*deselected_by_testcase, *deselected_by_labels])