edq.testing.asserts

More complex testing assertions. Often used as output checks in CLI tests.

 1"""
 2More complex testing assertions.
 3Often used as output checks in CLI tests.
 4"""
 5
 6import re
 7import typing
 8
 9import edq.testing.unittest
10
11TRACEBACK_LINE_REGEX: str = r'^\s*File "[^"]+", line \d+,.*$\n.*$(\n\s*[\^~]+\s*$)?'
12TRACEBACK_LINE_REPLACEMENT: str = '<TRACEBACK_LINE>'
13
14TEXT_NORMALIZATIONS: typing.List[typing.Tuple[str, str]] = [
15    (r'^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2},\d+ \[\S+ *\] - .*\.py:\d+ -- ', '<LOG_PREFIX> -- '),
16    (r'\d+\.\d+ seconds', '<DURATION_SECONDS>'),
17    (r'\bv\d+\.\d+\.\d+\b', '<VERSION>'),
18    (r'^\s*File "[^"]+", line \d+,.*$\n.*$(\n\s*[\^~]+\s*$)?', '<TRACEBACK_LINE>'),
19    (rf'{TRACEBACK_LINE_REPLACEMENT}(\n{TRACEBACK_LINE_REPLACEMENT})*', '<TRACEBACK>'),
20]
21"""
22Normalization to make to the CLI output.
23Formatted as: [(regex, replacement), ...]
24"""
25
26@typing.runtime_checkable
27class StringComparisonAssertion(typing.Protocol):
28    """
29    A function that can be used as a comparison assertion for a test.
30    """
31
32    def __call__(self,
33            test: edq.testing.unittest.BaseTest,
34            expected: str, actual: str,
35            **kwargs: typing.Any) -> None:
36        """
37        Perform an assertion between expected and actual data.
38        """
39
40
41def content_equals_raw(test: edq.testing.unittest.BaseTest, expected: str, actual: str, **kwargs: typing.Any) -> None:
42    """ Check for equality using a simple string comparison. """
43
44    test.assertEqual(expected, actual)
45
46def content_equals_normalize(test: edq.testing.unittest.BaseTest, expected: str, actual: str, **kwargs: typing.Any) -> None:
47    """
48    Perform some standard text normalizations (see TEXT_NORMALIZATIONS) before using simple string comparison.
49    """
50
51    for (regex, replacement) in TEXT_NORMALIZATIONS:
52        expected = re.sub(regex, replacement, expected, flags = re.MULTILINE)
53        actual = re.sub(regex, replacement, actual, flags = re.MULTILINE)
54
55    content_equals_raw(test, expected, actual)
56
57def has_content_100(test: edq.testing.unittest.BaseTest, expected: str, actual: str, **kwargs: typing.Any) -> None:
58    """ Check the that output has at least 100 characters. """
59
60    return has_content(test, expected, actual, min_length = 100)
61
62def has_content(test: edq.testing.unittest.BaseTest, expected: str, actual: str, min_length: int = 100) -> None:
63    """ Ensure that the output has content of at least some length. """
64
65    message = f"Output does not meet minimum length of {min_length}, it is only {len(actual)}."
66    test.assertTrue((len(actual) >= min_length), msg = message)
TRACEBACK_LINE_REGEX: str = '^\\s*File "[^"]+", line \\d+,.*$\\n.*$(\\n\\s*[\\^~]+\\s*$)?'
TRACEBACK_LINE_REPLACEMENT: str = '<TRACEBACK_LINE>'
TEXT_NORMALIZATIONS: List[Tuple[str, str]] = [('^\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2},\\d+ \\[\\S+ *\\] - .*\\.py:\\d+ -- ', '<LOG_PREFIX> -- '), ('\\d+\\.\\d+ seconds', '<DURATION_SECONDS>'), ('\\bv\\d+\\.\\d+\\.\\d+\\b', '<VERSION>'), ('^\\s*File "[^"]+", line \\d+,.*$\\n.*$(\\n\\s*[\\^~]+\\s*$)?', '<TRACEBACK_LINE>'), ('<TRACEBACK_LINE>(\\n<TRACEBACK_LINE>)*', '<TRACEBACK>')]

Normalization to make to the CLI output. Formatted as: [(regex, replacement), ...]

@typing.runtime_checkable
class StringComparisonAssertion(typing.Protocol):
27@typing.runtime_checkable
28class StringComparisonAssertion(typing.Protocol):
29    """
30    A function that can be used as a comparison assertion for a test.
31    """
32
33    def __call__(self,
34            test: edq.testing.unittest.BaseTest,
35            expected: str, actual: str,
36            **kwargs: typing.Any) -> None:
37        """
38        Perform an assertion between expected and actual data.
39        """

A function that can be used as a comparison assertion for a test.

StringComparisonAssertion(*args, **kwargs)
1953def _no_init_or_replace_init(self, *args, **kwargs):
1954    cls = type(self)
1955
1956    if cls._is_protocol:
1957        raise TypeError('Protocols cannot be instantiated')
1958
1959    # Already using a custom `__init__`. No need to calculate correct
1960    # `__init__` to call. This can lead to RecursionError. See bpo-45121.
1961    if cls.__init__ is not _no_init_or_replace_init:
1962        return
1963
1964    # Initially, `__init__` of a protocol subclass is set to `_no_init_or_replace_init`.
1965    # The first instantiation of the subclass will call `_no_init_or_replace_init` which
1966    # searches for a proper new `__init__` in the MRO. The new `__init__`
1967    # replaces the subclass' old `__init__` (ie `_no_init_or_replace_init`). Subsequent
1968    # instantiation of the protocol subclass will thus use the new
1969    # `__init__` and no longer call `_no_init_or_replace_init`.
1970    for base in cls.__mro__:
1971        init = base.__dict__.get('__init__', _no_init_or_replace_init)
1972        if init is not _no_init_or_replace_init:
1973            cls.__init__ = init
1974            break
1975    else:
1976        # should not happen
1977        cls.__init__ = object.__init__
1978
1979    cls.__init__(self, *args, **kwargs)
def content_equals_raw( test: edq.testing.unittest.BaseTest, expected: str, actual: str, **kwargs: Any) -> None:
42def content_equals_raw(test: edq.testing.unittest.BaseTest, expected: str, actual: str, **kwargs: typing.Any) -> None:
43    """ Check for equality using a simple string comparison. """
44
45    test.assertEqual(expected, actual)

Check for equality using a simple string comparison.

def content_equals_normalize( test: edq.testing.unittest.BaseTest, expected: str, actual: str, **kwargs: Any) -> None:
47def content_equals_normalize(test: edq.testing.unittest.BaseTest, expected: str, actual: str, **kwargs: typing.Any) -> None:
48    """
49    Perform some standard text normalizations (see TEXT_NORMALIZATIONS) before using simple string comparison.
50    """
51
52    for (regex, replacement) in TEXT_NORMALIZATIONS:
53        expected = re.sub(regex, replacement, expected, flags = re.MULTILINE)
54        actual = re.sub(regex, replacement, actual, flags = re.MULTILINE)
55
56    content_equals_raw(test, expected, actual)

Perform some standard text normalizations (see TEXT_NORMALIZATIONS) before using simple string comparison.

def has_content_100( test: edq.testing.unittest.BaseTest, expected: str, actual: str, **kwargs: Any) -> None:
58def has_content_100(test: edq.testing.unittest.BaseTest, expected: str, actual: str, **kwargs: typing.Any) -> None:
59    """ Check the that output has at least 100 characters. """
60
61    return has_content(test, expected, actual, min_length = 100)

Check the that output has at least 100 characters.

def has_content( test: edq.testing.unittest.BaseTest, expected: str, actual: str, min_length: int = 100) -> None:
63def has_content(test: edq.testing.unittest.BaseTest, expected: str, actual: str, min_length: int = 100) -> None:
64    """ Ensure that the output has content of at least some length. """
65
66    message = f"Output does not meet minimum length of {min_length}, it is only {len(actual)}."
67    test.assertTrue((len(actual) >= min_length), msg = message)

Ensure that the output has content of at least some length.