Repository: robotframework/robotframework

Issue:
DotDict behaves inconsistent on equality checks. `x == y` != `not x != y` and not `x != y` == `not x == y`
As found in #4955 `==` and `!=` do behave differently on DotDict.

While `==` behaves like `dict` the `!=` does behave like OrderedDict.

Quote:
This `not a == b` vs `a != b` was the reason the utest fail in the branch.
And i would consider this an actual bug in DotDict implementation?


```python
from collections import OrderedDict
from robot.utils import DotDict

# Normal dict: the order isn't relevant
norm_dict = {'a': 1, 'b': 2, 'c': 3, 'd': 4, 'e': 5, 'f': 6, 'g': 7}
norm_dict_reverse = {'g': 7, 'f': 6, 'e': 5, 'd': 4, 'c': 3, 'b': 2, 'a': 1}

norm_dict == norm_dict_reverse    # True
norm_dict != norm_dict_reverse    # False
not norm_dict == norm_dict_reverse    # False
not norm_dict != norm_dict_reverse    # True

# OrderedDict: the order is important
order_dict = OrderedDict([('a', 1), ('b', 2), ('c', 3), ('d', 4), ('e', 5), ('f', 6), ('g', 7)])
order_dict_reverse = OrderedDict([('g', 7), ('f', 6), ('e', 5), ('d', 4), ('c', 3), ('b', 2), ('a', 1)])

order_dict == order_dict_reverse    # False
order_dict != order_dict_reverse    # True
not order_dict == order_dict_reverse    # True
not order_dict != order_dict_reverse    # False

#DotDict : the order isn't relevant with == but is relevant with !=
dot_dict = DotDict({'a': 1, 'b': 2, 'c': 3, 'd': 4, 'e': 5, 'f': 6, 'g': 7})
dot_dict_reverse = DotDict({'g': 7, 'f': 6, 'e': 5, 'd': 4, 'c': 3, 'b': 2, 'a': 1})
dot_dict == dot_dict_reverse    # True because order is not relevant with ==
dot_dict != dot_dict_reverse    # True because order is relevant with !=
not dot_dict == dot_dict_reverse    # False
not dot_dict != dot_dict_reverse    # False
```

This also appears in Robot Framework Usage:

DotDict
![image](https://github.com/robotframework/robotframework/assets/41592183/19adc29d-88ca-4eea-b121-5f08f5e39492)

OrderedDict
![image](https://github.com/robotframework/robotframework/assets/41592183/e35dce29-5552-4f97-9181-45933f672416)

Normal python dict
![image](https://github.com/robotframework/robotframework/assets/41592183/5d6444be-10ff-4d2a-a995-a2e9cffa0abc)


Fix should just be adding this to DotDict:
```python    
    def __ne__(self, other):
        return not self == other
```

I think it is easiest if you quickly add this and a test for it yourself.

Discussion:

Patch:
diff --git a/src/robot/utils/dotdict.py b/src/robot/utils/dotdict.py
index 079de6d25..cbb77005f 100644
--- a/src/robot/utils/dotdict.py
+++ b/src/robot/utils/dotdict.py
@@ -60,6 +60,9 @@ class DotDict(OrderedDict):
     def __eq__(self, other):
         return dict.__eq__(self, other)
 
+    def __ne__(self, other):
+        return not self == other
+
     def __str__(self):
         return '{%s}' % ', '.join('%r: %r' % (key, self[key]) for key in self)
 


Test patch:
diff --git a/utest/utils/test_dotdict.py b/utest/utils/test_dotdict.py
index 50f4aed26..cf9a8cc0d 100644
--- a/utest/utils/test_dotdict.py
+++ b/utest/utils/test_dotdict.py
@@ -2,7 +2,7 @@ import unittest
 from collections import OrderedDict
 
 from robot.utils import DotDict
-from robot.utils.asserts import (assert_equal, assert_false, assert_not_equal,
+from robot.utils.asserts import (assert_equal, assert_false,
                                  assert_raises, assert_true)
 
 
@@ -34,7 +34,8 @@ class TestDotDict(unittest.TestCase):
         assert_true(self.dd != DotDict())
 
     def test_equality_with_normal_dict(self):
-        assert_equal(self.dd, {'z': 1, 2: 'y', 'x': 3})
+        assert_true(self.dd == {'z': 1, 2: 'y', 'x': 3})
+        assert_false(self.dd != {'z': 1, 2: 'y', 'x': 3})
 
     def test_hash(self):
         assert_raises(TypeError, hash, self.dd)
@@ -79,12 +80,17 @@ class TestDotDict(unittest.TestCase):
         dd1 = DotDict(sorted(d.items()))
         dd2 = DotDict(reversed(list(dd1.items())))
         for d1, d2 in [(dd1, dd2), (dd1, d), (dd2, d), (dd1, od1), (dd2, od2)]:
-            assert_equal(d1, d2)
-            assert_equal(d2, d1)
+            assert_true(d1 == d2)
+            assert_false(d1 != d2)
+            assert_true(d2 == d1)
+            assert_false(d2 != d1)
         for d1, d2 in [(dd1, od2), (dd2, od1)]:
-            assert_equal(d1, d2)
-            assert_equal(d2, d1)
-        assert_not_equal(od1, od2)
+            assert_true(d1 == d2)
+            assert_false(d1 != d2)
+            assert_true(d2 == d1)
+            assert_false(d2 != d1)
+        assert_false(od1 == od2)
+        assert_true(od1 != od2)
 
 
 class TestNestedDotDict(unittest.TestCase):