Source code for suricata_check.tests.checker

  1"""`GenericChecker`."""
  2
  3import logging
  4import warnings
  5from functools import lru_cache
  6from typing import Optional
  7
  8import idstools.rule
  9import pytest
 10
 11from suricata_check.checkers.interface.checker import CheckerInterface
 12from suricata_check.utils.checker_typing import ISSUES_TYPE, Issue
 13from suricata_check.utils.regex import get_regex_provider
 14
 15_regex_provider = get_regex_provider()
 16
 17_CODE_STRUCTURE_REGEX = _regex_provider.compile(r"[A-Z]{1,}[0-9]{3}")
 18
 19
[docs] 20class GenericChecker: 21 """The GenericChecker class can be extended by tests to test classes implementing `CheckerInterface`.""" 22 23 checker: CheckerInterface 24 25 @pytest.fixture(autouse=True) 26 def __run_around_tests(self: "GenericChecker") -> None: 27 logging.basicConfig(level=logging.DEBUG) 28 29 def _set_log_level(self: "GenericChecker", level: int) -> None: 30 logger = logging.getLogger() 31 logger.setLevel(level) 32 for handler in logger.handlers: 33 handler.setLevel(level) 34 35 @lru_cache(maxsize=1) 36 def _check_rule( 37 self: "GenericChecker", 38 rule: idstools.rule.Rule, 39 ) -> ISSUES_TYPE: 40 return self.checker.check_rule(rule) 41 42 def _test_issue( 43 self: "GenericChecker", 44 rule: Optional[idstools.rule.Rule], 45 code: str, 46 raised: bool, 47 fail: bool = True, 48 ) -> Optional[bool]: 49 """Checks whether a rule raises or does not raise an issue with a given code. 50 51 Raises a pytest failure, warning, or returns a boolean based on the provided arguments. 52 """ 53 if rule is None: 54 pytest.fail("Rule is None") 55 56 correct, issue = self.check_issue(rule, code, raised) 57 58 if correct is not True: 59 msg = f"""\ 60{'Unexpected' if not raised else 'Missing'} code {code}. 61{rule['raw']} 62{issue}\ 63""" 64 if fail: 65 pytest.fail(msg) 66 else: 67 warnings.warn(RuntimeWarning(msg)) 68
[docs] 69 def check_issue( 70 self: "GenericChecker", 71 rule: Optional[idstools.rule.Rule], 72 code: str, 73 raised: bool, 74 ) -> tuple[Optional[bool], Optional[Issue]]: 75 """Checks whether a rule raises an issue with a certain code and returns whether the expectation is met.""" 76 if rule is None: 77 pytest.fail("Rule is None") 78 79 issues: ISSUES_TYPE = self._check_rule(rule) 80 81 self._test_no_undeclared_codes(issues) 82 self._test_issue_metadata(issues) 83 84 correct: Optional[bool] = None 85 issue: Optional[Issue] = None 86 87 if raised: 88 correct = False 89 for issue in issues: 90 if issue.code == code: 91 correct = True 92 break 93 issue = None 94 elif not raised: 95 correct = True 96 for issue in issues: 97 if issue.code == code: 98 correct = False 99 break 100 101 return correct, issue if not correct else None
102 103 def _test_no_undeclared_codes(self: "GenericChecker", issues: ISSUES_TYPE) -> None: 104 """Asserts the checker emits no undeclared codes.""" 105 assert self.checker is not None 106 107 codes = set() 108 for issue in issues: 109 codes.add(issue.code) 110 111 for code in codes: 112 if code not in self.checker.codes: 113 pytest.fail(code) 114
[docs] 115 @pytest.hookimpl(trylast=True) 116 def test_code_structure(self: "GenericChecker") -> None: 117 """Asserts the checker only emits codes following the allowed structure.""" 118 for code in self.checker.codes: 119 if _CODE_STRUCTURE_REGEX.match(code) is None: 120 pytest.fail(code)
121 122 def _test_issue_metadata(self: "GenericChecker", issues: ISSUES_TYPE) -> None: 123 """Asserts the checker adds required metadata to emitted issues.""" 124 for issue in issues: 125 if not hasattr(issue, "checker"): 126 pytest.fail( 127 "Issue with code {} did not specify checker.".format( 128 str(issue.code) 129 ) 130 ) 131 if not hasattr(issue, "severity"): 132 pytest.fail( 133 "Issue with code {} did not specify severity.".format( 134 str(issue.code) 135 ) 136 ) 137 if issue.message.strip() != issue.message: 138 pytest.fail( 139 'Issue with code {} starts with or ends with whitespace in message: """{}"""'.format( 140 str(issue.code), str(issue.message) 141 ) 142 )