summaryrefslogtreecommitdiffstats
path: root/test/TestMatchError.py
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-14 20:04:50 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-14 20:04:50 +0000
commit782f8df6e41f29dce2db4970a3ad84aaeb7d8c5f (patch)
tree3a88a542cd8074743d251881131510157cfc510b /test/TestMatchError.py
parentInitial commit. (diff)
downloadansible-lint-upstream/4.3.7.tar.xz
ansible-lint-upstream/4.3.7.zip
Adding upstream version 4.3.7.upstream/4.3.7upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'test/TestMatchError.py')
-rw-r--r--test/TestMatchError.py178
1 files changed, 178 insertions, 0 deletions
diff --git a/test/TestMatchError.py b/test/TestMatchError.py
new file mode 100644
index 0000000..f17d865
--- /dev/null
+++ b/test/TestMatchError.py
@@ -0,0 +1,178 @@
+"""Tests for MatchError."""
+
+import operator
+
+import pytest
+
+from ansiblelint.errors import MatchError
+from ansiblelint.rules import AnsibleLintRule
+from ansiblelint.rules.AlwaysRunRule import AlwaysRunRule
+from ansiblelint.rules.BecomeUserWithoutBecomeRule import BecomeUserWithoutBecomeRule
+
+
+class DummyTestObject:
+ """A dummy object for equality tests."""
+
+ def __repr__(self):
+ """Return a dummy object representation for parmetrize."""
+ return '{self.__class__.__name__}()'.format(self=self)
+
+ def __eq__(self, other):
+ """Report the equality check failure with any object."""
+ return False
+
+ def __ne__(self, other):
+ """Report the confirmation of inequality with any object."""
+ return True
+
+
+class DummySentinelTestObject:
+ """A dummy object for equality protocol tests with sentinel."""
+
+ def __eq__(self, other):
+ """Return sentinel as result of equality check w/ anything."""
+ return 'EQ_SENTINEL'
+
+ def __ne__(self, other):
+ """Return sentinel as result of inequality check w/ anything."""
+ return 'NE_SENTINEL'
+
+ def __lt__(self, other):
+ """Return sentinel as result of less than check w/ anything."""
+ return 'LT_SENTINEL'
+
+ def __gt__(self, other):
+ """Return sentinel as result of greater than chk w/ anything."""
+ return 'GT_SENTINEL'
+
+
+@pytest.mark.parametrize(
+ ('left_match_error', 'right_match_error'),
+ (
+ (MatchError("foo"), MatchError("foo")),
+ (MatchError("a", details="foo"), MatchError("a", details="foo")),
+ ),
+)
+def test_matcherror_compare(left_match_error, right_match_error):
+ """Check that MatchError instances with similar attrs are equivalent."""
+ assert left_match_error == right_match_error
+
+
+class AnsibleLintRuleWithStringId(AnsibleLintRule):
+ id = "ANSIBLE200"
+
+
+def test_matcherror_invalid():
+ """Ensure that MatchError requires message or rule."""
+ expected_err = r"^MatchError\(\) missing a required argument: one of 'message' or 'rule'$"
+ with pytest.raises(TypeError, match=expected_err):
+ MatchError()
+
+
+@pytest.mark.parametrize(
+ ('left_match_error', 'right_match_error'), (
+ # sorting by message
+ (MatchError("z"), MatchError("a")),
+ # filenames takes priority in sorting
+ (MatchError("a", filename="b"), MatchError("a", filename="a")),
+ # rule id 501 > rule id 101
+ (MatchError(rule=BecomeUserWithoutBecomeRule), MatchError(rule=AlwaysRunRule)),
+ # rule id "200" > rule id 101
+ (MatchError(rule=AnsibleLintRuleWithStringId), MatchError(rule=AlwaysRunRule)),
+ # details are taken into account
+ (MatchError("a", details="foo"), MatchError("a", details="bar")),
+ ))
+class TestMatchErrorCompare:
+
+ def test_match_error_less_than(self, left_match_error, right_match_error):
+ """Check 'less than' protocol implementation in MatchError."""
+ assert right_match_error < left_match_error
+
+ def test_match_error_greater_than(self, left_match_error, right_match_error):
+ """Check 'greater than' protocol implementation in MatchError."""
+ assert left_match_error > right_match_error
+
+ def test_match_error_not_equal(self, left_match_error, right_match_error):
+ """Check 'not equals' protocol implementation in MatchError."""
+ assert left_match_error != right_match_error
+
+
+@pytest.mark.parametrize(
+ 'other',
+ (
+ None,
+ "foo",
+ 42,
+ Exception("foo"),
+ ),
+ ids=repr,
+)
+@pytest.mark.parametrize(
+ ('operation', 'operator_char'),
+ (
+ pytest.param(operator.le, '<=', id='<='),
+ pytest.param(operator.gt, '>', id='>'),
+ ),
+)
+def test_matcherror_compare_no_other_fallback(other, operation, operator_char):
+ """Check that MatchError comparison with other types causes TypeError."""
+ expected_error = (
+ r'^('
+ r'unsupported operand type\(s\) for {operator!s}:|'
+ r"'{operator!s}' not supported between instances of"
+ r") 'MatchError' and '{other_type!s}'$".
+ format(other_type=type(other).__name__, operator=operator_char)
+ )
+ with pytest.raises(TypeError, match=expected_error):
+ operation(MatchError("foo"), other)
+
+
+@pytest.mark.parametrize(
+ 'other',
+ (
+ None,
+ 'foo',
+ 42,
+ Exception('foo'),
+ DummyTestObject(),
+ ),
+ ids=repr,
+)
+@pytest.mark.parametrize(
+ ('operation', 'expected_value'),
+ (
+ (operator.eq, False),
+ (operator.ne, True),
+ ),
+ ids=('==', '!=')
+)
+def test_matcherror_compare_with_other_fallback(
+ other,
+ operation,
+ expected_value,
+):
+ """Check that MatchError comparison runs other types fallbacks."""
+ assert operation(MatchError("foo"), other) is expected_value
+
+
+@pytest.mark.parametrize(
+ ('operation', 'expected_value'),
+ (
+ (operator.eq, 'EQ_SENTINEL'),
+ (operator.ne, 'NE_SENTINEL'),
+ # NOTE: these are swapped because when we do `x < y`, and `x.__lt__(y)`
+ # NOTE: returns `NotImplemented`, Python will reverse the check into
+ # NOTE: `y > x`, and so `y.__gt__(x) is called.
+ # Ref: https://docs.python.org/3/reference/datamodel.html#object.__lt__
+ (operator.lt, 'GT_SENTINEL'),
+ (operator.gt, 'LT_SENTINEL'),
+ ),
+ ids=('==', '!=', '<', '>'),
+)
+def test_matcherror_compare_with_dummy_sentinel(operation, expected_value):
+ """Check that MatchError comparison runs other types fallbacks."""
+ dummy_obj = DummySentinelTestObject()
+ # NOTE: This assertion abuses the CPython property to cache short string
+ # NOTE: objects because the identity check is more presice and we don't
+ # NOTE: want extra operator protocol methods to influence the test.
+ assert operation(MatchError("foo"), dummy_obj) is expected_value