Repository: alteryx/featuretools

Issue:
EntitySet.__eq__ bug fix and test coverage improvement
The `EntitySet.__eq__` method contains code that will never evaluate to False. This could cause two entitysets that have different relationships defined to evaluate as equal when they should be not equal.

This code block
```python
for r in other.relationships:
    if r not in other.relationships:
        return False
```

should be changed to:
```python
for r in self.relationships:
    if r not in other.relationships:
        return False
```

Also, there are other lines in the `__eq__` method that are not currently covered by the existing unit tests. New tests should be added to improve coverage for this method.

Discussion:
codecov[bot]: # [Codecov](https://codecov.io/gh/alteryx/featuretools/pull/1323?src=pr&el=h1) Report
> Merging [#1323](https://codecov.io/gh/alteryx/featuretools/pull/1323?src=pr&el=desc) (4542d4c) into [main](https://codecov.io/gh/alteryx/featuretools/commit/6f7cead9b32e6f625ec824a8e9e4a0dfa0495ce1?el=desc) (6f7cead) will **increase** coverage by `0.15%`.
> The diff coverage is `100.00%`.

[![Impacted file tree graph](https://codecov.io/gh/alteryx/featuretools/pull/1323/graphs/tree.svg?width=650&height=150&src=pr&token=PqDLD96eXJ)](https://codecov.io/gh/alteryx/featuretools/pull/1323?src=pr&el=tree)

```diff
@@            Coverage Diff             @@
##             main    #1323      +/-   ##
==========================================
+ Coverage   98.38%   98.54%   +0.15%     
==========================================
  Files         135      135              
  Lines       14496    14467      -29     
==========================================
- Hits        14262    14256       -6     
+ Misses        234      211      -23     
```


| [Impacted Files](https://codecov.io/gh/alteryx/featuretools/pull/1323?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [featuretools/computational\_backends/utils.py](https://codecov.io/gh/alteryx/featuretools/pull/1323/diff?src=pr&el=tree#diff-ZmVhdHVyZXRvb2xzL2NvbXB1dGF0aW9uYWxfYmFja2VuZHMvdXRpbHMucHk=) | `95.54% <ø> (ø)` | |
| [...mputational\_backend/test\_feature\_set\_calculator.py](https://codecov.io/gh/alteryx/featuretools/pull/1323/diff?src=pr&el=tree#diff-ZmVhdHVyZXRvb2xzL3Rlc3RzL2NvbXB1dGF0aW9uYWxfYmFja2VuZC90ZXN0X2ZlYXR1cmVfc2V0X2NhbGN1bGF0b3IucHk=) | `100.00% <ø> (+0.36%)` | :arrow_up: |
| [featuretools/tests/conftest.py](https://codecov.io/gh/alteryx/featuretools/pull/1323/diff?src=pr&el=tree#diff-ZmVhdHVyZXRvb2xzL3Rlc3RzL2NvbmZ0ZXN0LnB5) | `100.00% <ø> (+3.40%)` | :arrow_up: |
| [...uretools/tests/entityset\_tests/test\_es\_metadata.py](https://codecov.io/gh/alteryx/featuretools/pull/1323/diff?src=pr&el=tree#diff-ZmVhdHVyZXRvb2xzL3Rlc3RzL2VudGl0eXNldF90ZXN0cy90ZXN0X2VzX21ldGFkYXRhLnB5) | `100.00% <ø> (+0.59%)` | :arrow_up: |
| [...eaturetools/tests/entityset\_tests/test\_plotting.py](https://codecov.io/gh/alteryx/featuretools/pull/1323/diff?src=pr&el=tree#diff-ZmVhdHVyZXRvb2xzL3Rlc3RzL2VudGl0eXNldF90ZXN0cy90ZXN0X3Bsb3R0aW5nLnB5) | `100.00% <ø> (+1.63%)` | :arrow_up: |
| [...ols/tests/synthesis/test\_deep\_feature\_synthesis.py](https://codecov.io/gh/alteryx/featuretools/pull/1323/diff?src=pr&el=tree#diff-ZmVhdHVyZXRvb2xzL3Rlc3RzL3N5bnRoZXNpcy90ZXN0X2RlZXBfZmVhdHVyZV9zeW50aGVzaXMucHk=) | `99.29% <ø> (+0.13%)` | :arrow_up: |
| [featuretools/utils/plot\_utils.py](https://codecov.io/gh/alteryx/featuretools/pull/1323/diff?src=pr&el=tree#diff-ZmVhdHVyZXRvb2xzL3V0aWxzL3Bsb3RfdXRpbHMucHk=) | `92.00% <ø> (ø)` | |
| [featuretools/entityset/entityset.py](https://codecov.io/gh/alteryx/featuretools/pull/1323/diff?src=pr&el=tree#diff-ZmVhdHVyZXRvb2xzL2VudGl0eXNldC9lbnRpdHlzZXQucHk=) | `98.77% <100.00%> (+0.81%)` | :arrow_up: |
| [featuretools/tests/entityset\_tests/test\_es.py](https://codecov.io/gh/alteryx/featuretools/pull/1323/diff?src=pr&el=tree#diff-ZmVhdHVyZXRvb2xzL3Rlc3RzL2VudGl0eXNldF90ZXN0cy90ZXN0X2VzLnB5) | `100.00% <100.00%> (+1.28%)` | :arrow_up: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/alteryx/featuretools/pull/1323?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/alteryx/featuretools/pull/1323?src=pr&el=footer). Last update [02d960f...a4863b6](https://codecov.io/gh/alteryx/featuretools/pull/1323?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/docs/source/release_notes.rst b/docs/source/release_notes.rst
index 6aec9616..6752626d 100644
--- a/docs/source/release_notes.rst
+++ b/docs/source/release_notes.rst
@@ -6,6 +6,7 @@ Release Notes
     * Enhancements
     * Fixes
         * Calculate direct features uses default value if parent missing (:pr:`1312`)
+        * Fix bug and improve tests for ``EntitySet.__eq__`` and ``Entity.__eq__`` (:pr:`1323`)
     * Changes
     * Documentation Changes
         * Update Twitter link to documentation toolbar (:pr:`1322`)
diff --git a/featuretools/entityset/entityset.py b/featuretools/entityset/entityset.py
index 89cbbb7b..18ad9a48 100644
--- a/featuretools/entityset/entityset.py
+++ b/featuretools/entityset/entityset.py
@@ -117,7 +117,7 @@ class EntitySet(object):
                 return False
             if not e.__eq__(other[eid], deep=deep):
                 return False
-        for r in other.relationships:
+        for r in self.relationships:
             if r not in other.relationships:
                 return False
         return True


Test patch:
diff --git a/featuretools/tests/entityset_tests/test_entity.py b/featuretools/tests/entityset_tests/test_entity.py
index 55ff26b2..77029e66 100644
--- a/featuretools/tests/entityset_tests/test_entity.py
+++ b/featuretools/tests/entityset_tests/test_entity.py
@@ -50,22 +50,49 @@ def test_eq(es):
     assert es['log'].__eq__(other_es['log'], deep=True)
     assert all(to_pandas(es['log'].df['latlong']).eq(to_pandas(latlong)))
 
-    other_es['log'].add_interesting_values()
-    assert not es['log'].__eq__(other_es['log'], deep=True)
+    # Test different index
+    other_es['log'].index = None
+    assert not es['log'].__eq__(other_es['log'])
+    other_es['log'].index = 'id'
+    assert es['log'].__eq__(other_es['log'])
+
+    # Test different time index
+    other_es['log'].time_index = None
+    assert not es['log'].__eq__(other_es['log'])
+    other_es['log'].time_index = 'datetime'
+    assert es['log'].__eq__(other_es['log'])
+
+    # Test different secondary time index
+    other_es['customers'].secondary_time_index = {}
+    assert not es['customers'].__eq__(other_es['customers'])
+    other_es['customers'].secondary_time_index = {
+        'cancel_date': ['cancel_reason', 'cancel_date']}
+    assert es['customers'].__eq__(other_es['customers'])
 
-    es['log'].id = 'customers'
-    es['log'].index = 'notid'
-    assert not es['customers'].__eq__(es['log'], deep=True)
+    original_variables = es['sessions'].variables
+    # Test different variable list length
+    other_es['sessions'].variables = original_variables[:-1]
+    assert not es['sessions'].__eq__(other_es['sessions'])
+    # Test different variable list contents
+    other_es['sessions'].variables = original_variables[:-1] + [original_variables[0]]
+    assert not es['sessions'].__eq__(other_es['sessions'])
 
-    es['log'].index = 'id'
-    assert not es['customers'].__eq__(es['log'], deep=True)
+    # Test different interesting values
+    assert es['log'].__eq__(other_es['log'], deep=True)
+    other_es['log'].add_interesting_values()
+    assert not es['log'].__eq__(other_es['log'], deep=True)
 
-    es['log'].time_index = 'signup_date'
-    assert not es['customers'].__eq__(es['log'], deep=True)
+    # Check one with last time index, one without
+    other_es['log'].last_time_index = other_es['log'].df['datetime']
+    assert not other_es['log'].__eq__(es['log'], deep=True)
+    assert not es['log'].__eq__(other_es['log'], deep=True)
+    # Both set with different values
+    es['log'].last_time_index = other_es['log'].last_time_index + pd.Timedelta('1h')
+    assert not other_es['log'].__eq__(es['log'], deep=True)
 
-    es['log'].secondary_time_index = {
-        'cancel_date': ['cancel_reason', 'cancel_date']}
-    assert not es['customers'].__eq__(es['log'], deep=True)
+    # Check different dataframes
+    other_es['stores'].df = other_es['stores'].df.head(0)
+    assert not other_es['stores'].__eq__(es['stores'], deep=True)
 
 
 def test_update_data(es):
diff --git a/featuretools/tests/entityset_tests/test_es.py b/featuretools/tests/entityset_tests/test_es.py
index a1e79c3e..487ac414 100644
--- a/featuretools/tests/entityset_tests/test_es.py
+++ b/featuretools/tests/entityset_tests/test_es.py
@@ -1407,3 +1407,39 @@ def test_entityset_init():
     es_copy.add_relationship(relationship)
     assert es['cards'].__eq__(es_copy['cards'], deep=True)
     assert es['transactions'].__eq__(es_copy['transactions'], deep=True)
+
+
+def test_entityset_equality(es):
+    first_es = EntitySet()
+    second_es = EntitySet()
+    assert first_es == second_es
+
+    first_es.entity_from_dataframe(entity_id='customers',
+                                   dataframe=es['customers'].df,
+                                   index='id',
+                                   time_index='signup_date',
+                                   variable_types=es['customers'].variable_types)
+    assert first_es != second_es
+
+    second_es.entity_from_dataframe(entity_id='sessions',
+                                    dataframe=es['sessions'].df,
+                                    index='id',
+                                    variable_types=es['sessions'].variable_types)
+    assert first_es != second_es
+
+    first_es.entity_from_dataframe(entity_id='sessions',
+                                   dataframe=es['sessions'].df,
+                                   index='id',
+                                   variable_types=es['sessions'].variable_types)
+    second_es.entity_from_dataframe(entity_id='customers',
+                                    dataframe=es['customers'].df,
+                                    index='id',
+                                    time_index='signup_date',
+                                    variable_types=es['customers'].variable_types)
+    assert first_es == second_es
+
+    first_es.add_relationship(ft.Relationship(es['customers']['id'], es['sessions']['customer_id']))
+    assert first_es != second_es
+
+    second_es.add_relationship(ft.Relationship(es['customers']['id'], es['sessions']['customer_id']))
+    assert first_es == second_es