|
| 1 | +# License: MIT |
| 2 | +# Copyright © 2024 Frequenz Energy-as-a-Service GmbH |
| 3 | + |
| 4 | +"""Tests for the ChangedOnly implementation. |
| 5 | +
|
| 6 | +Most testing is done in the OnlyIfPrevious tests, these tests are limited to the |
| 7 | +specifics of the ChangedOnly implementation. |
| 8 | +""" |
| 9 | + |
| 10 | +from dataclasses import dataclass |
| 11 | +from unittest.mock import MagicMock |
| 12 | + |
| 13 | +import pytest |
| 14 | + |
| 15 | +from frequenz.channels.experimental import ChangedOnly, OnlyIfPrevious |
| 16 | + |
| 17 | + |
| 18 | +@dataclass(frozen=True, kw_only=True) |
| 19 | +class EqualityTestCase: |
| 20 | + """Test case for testing ChangedOnly behavior with tricky equality cases.""" |
| 21 | + |
| 22 | + title: str |
| 23 | + first_value: object |
| 24 | + second_value: object |
| 25 | + expected_second_result: bool |
| 26 | + |
| 27 | + |
| 28 | +EQUALITY_TEST_CASES = [ |
| 29 | + # Python's equality weirdness cases |
| 30 | + EqualityTestCase( |
| 31 | + title="Integer equals float", |
| 32 | + first_value=1, |
| 33 | + second_value=1.0, |
| 34 | + expected_second_result=False, |
| 35 | + ), |
| 36 | + EqualityTestCase( |
| 37 | + title="Boolean equals integer", |
| 38 | + first_value=True, |
| 39 | + second_value=1, |
| 40 | + expected_second_result=False, |
| 41 | + ), |
| 42 | + EqualityTestCase( |
| 43 | + title="Boolean equals float", |
| 44 | + first_value=True, |
| 45 | + second_value=1.0, |
| 46 | + expected_second_result=False, |
| 47 | + ), |
| 48 | + EqualityTestCase( |
| 49 | + title="False equals zero", |
| 50 | + first_value=False, |
| 51 | + second_value=0, |
| 52 | + expected_second_result=False, |
| 53 | + ), |
| 54 | + EqualityTestCase( |
| 55 | + title="Zero equals False", |
| 56 | + first_value=0, |
| 57 | + second_value=False, |
| 58 | + expected_second_result=False, |
| 59 | + ), |
| 60 | + # Edge cases that should be different |
| 61 | + EqualityTestCase( |
| 62 | + title="NaN is never equal to NaN", |
| 63 | + first_value=float("nan"), |
| 64 | + second_value=float("nan"), |
| 65 | + expected_second_result=True, |
| 66 | + ), |
| 67 | + EqualityTestCase( |
| 68 | + title="Different list instances with same content", |
| 69 | + first_value=[1], |
| 70 | + second_value=[1], |
| 71 | + expected_second_result=False, |
| 72 | + ), |
| 73 | +] |
| 74 | + |
| 75 | + |
| 76 | +def test_changed_only_inheritance() -> None: |
| 77 | + """Test that ChangedOnly is properly inheriting from OnlyIfPrevious.""" |
| 78 | + changed_only = ChangedOnly() |
| 79 | + assert isinstance(changed_only, OnlyIfPrevious) |
| 80 | + |
| 81 | + |
| 82 | +def test_changed_only_predicate_implementation() -> None: |
| 83 | + """Test that ChangedOnly properly implements the inequality predicate.""" |
| 84 | + # Create mock objects that we can control the equality comparison for |
| 85 | + old = MagicMock() |
| 86 | + new = MagicMock() |
| 87 | + |
| 88 | + # Set up the inequality comparison |
| 89 | + # mypy doesn't understand mocking __ne__ very well |
| 90 | + old.__ne__.return_value = True # type: ignore[attr-defined] |
| 91 | + |
| 92 | + changed_only = ChangedOnly() |
| 93 | + # Skip the first message as it's handled by first_is_true |
| 94 | + changed_only(old) |
| 95 | + changed_only(new) |
| 96 | + |
| 97 | + # Verify that __ne__ was called with the correct argument |
| 98 | + old.__ne__.assert_called_once_with(new) # type: ignore[attr-defined] |
| 99 | + |
| 100 | + |
| 101 | +@pytest.mark.parametrize( |
| 102 | + "test_case", |
| 103 | + EQUALITY_TEST_CASES, |
| 104 | + ids=lambda test_case: test_case.title, |
| 105 | +) |
| 106 | +def test_changed_only_equality_cases(test_case: EqualityTestCase) -> None: |
| 107 | + """Test ChangedOnly behavior with Python's tricky equality cases. |
| 108 | +
|
| 109 | + Args: |
| 110 | + test_case: The test case containing the input values and expected result. |
| 111 | + """ |
| 112 | + changed_only = ChangedOnly() |
| 113 | + assert changed_only(test_case.first_value) is True # First is always True |
| 114 | + assert changed_only(test_case.second_value) is test_case.expected_second_result |
| 115 | + |
| 116 | + |
| 117 | +def test_changed_only_representation() -> None: |
| 118 | + """Test the string representation of ChangedOnly.""" |
| 119 | + changed_only = ChangedOnly() |
| 120 | + assert str(changed_only) == "ChangedOnly" |
| 121 | + assert repr(changed_only) == "ChangedOnly(first_is_true=True)" |
0 commit comments