Skip to content

Commit bc4bc83

Browse files
committed
Clear the functests search index when clearing the DB
For speed h does not normally clear the DB between functests unless the test has `@pytest.mark.parametrize("with_clean_db")`, see: #6845 At the time when this change was made the _search index_ was still being cleared before each functest which makes no sense: the search API will not return an annotation if it's not in the search index, even if the annotation is in the DB. If you aren't going to clear the DB before each functest, you shouldn't clear the search index either. A later PR accidentally broke the `always_delete_all_elasticsearch_documents` fixture so that the search index wasn't actually being cleared between functests anyway, see: #9636 This commit: 1. Removes the broken `always_delete_all_elasticsearch_documents` fixture: it doesn't do anything, and since we don't clear the DB between tests it doesn't make sense to clear the search index either. 2. Replaces the `with_clean_db` fixture with `with_clean_db_and_search_index`: it doesn't make sense to clear the DB without also clearing the search index. Longer-term I think we probably want to find a fast way to clear both the DB and search index before each functional test.
1 parent 30adfc2 commit bc4bc83

File tree

6 files changed

+52
-24
lines changed

6 files changed

+52
-24
lines changed

tests/conftest.py

Lines changed: 24 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -57,25 +57,36 @@ def db_session_replica(db_session):
5757
@pytest.fixture
5858
def es_client():
5959
client = _es_client()
60+
6061
yield client
61-
# Push all changes to segments to make sure all annotations that were added get removed.
62-
elasticsearch_dsl.Index(client.index, using=client.conn).refresh()
63-
64-
client.conn.delete_by_query(
65-
index=client.index,
66-
body={"query": {"match_all": {}}},
67-
# This query occasionally fails with a version conflict.
68-
# Forcing the deletion resolves the issue, but the exact
69-
# cause of the version conflict has not been found yet.
70-
conflicts="proceed",
71-
# Add refresh to make deletion changes show up in search results.
72-
refresh=True,
73-
)
7462

7563
# Close connection to ES server to avoid ResourceWarning about a leaked TCP socket.
7664
client.close()
7765

7866

67+
@pytest.fixture
68+
def clear_search_index(es_client):
69+
"""Return a function that deletes all documents from the search index."""
70+
71+
def clear_search_index():
72+
"""Delete all documents from the Elasticsearch index."""
73+
# Push all changes to segments to make sure all annotations that were added get removed.
74+
elasticsearch_dsl.Index(es_client.index, using=es_client.conn).refresh()
75+
76+
es_client.conn.delete_by_query(
77+
index=es_client.index,
78+
body={"query": {"match_all": {}}},
79+
# This query occasionally fails with a version conflict.
80+
# Forcing the deletion resolves the issue, but the exact
81+
# cause of the version conflict has not been found yet.
82+
conflicts="proceed",
83+
# Add refresh to make deletion changes show up in search results.
84+
refresh=True,
85+
)
86+
87+
return clear_search_index
88+
89+
7990
@pytest.fixture
8091
def mock_es_client(es_client):
8192
return create_autospec(

tests/functional/api/bulk/action_test.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
pytestmark = pytest.mark.usefixtures("init_elasticsearch")
1111

1212

13-
@pytest.mark.usefixtures("with_clean_db")
13+
@pytest.mark.usefixtures("with_clean_db_and_search_index")
1414
class TestBulk:
1515
AUTHORITY = "lms.hypothes.is"
1616

tests/functional/api/bulk/annotation_test.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from h.models import GroupMembership
66

77

8-
@pytest.mark.usefixtures("with_clean_db")
8+
@pytest.mark.usefixtures("with_clean_db_and_search_index")
99
class TestBulkAnnotation:
1010
def test_it_requires_authentication(self, make_request):
1111
response = make_request(headers={"bad_auth": "BAD"}, expect_errors=True)

tests/functional/bin/run_data_task_test.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111

1212
class TestRunSQLTask:
1313
# We use "clean DB" here to ensure the schema is created
14-
@pytest.mark.usefixtures("with_clean_db")
14+
@pytest.mark.usefixtures("with_clean_db_and_search_index")
1515
def test_reporting_tasks(self, environ):
1616
for task_name in (
1717
# Run all the jobs in one line in the order they are expected to

tests/functional/conftest.py

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import contextlib
22
import os
33

4+
import elasticsearch.exceptions
45
import pytest
56
from sqlalchemy import text
67
from webtest import TestApp
@@ -51,7 +52,18 @@ def reset_app(app):
5152

5253

5354
@pytest.fixture
54-
def with_clean_db(db_engine):
55+
def with_clean_db_and_search_index(db_engine, clear_search_index):
56+
"""Empty the DB and search index before running the test.
57+
58+
h doesn't normally reset the DB or search index before each functest
59+
because doing so was taking too long, see:
60+
https://github.com/hypothesis/h/pull/6845
61+
62+
This does mean that functests need to be robust against data from previous
63+
tests still being in the DB and search index. Alternatively, a test can use
64+
@pytest.mark.usefixtures("with_clean_db_and_search_index") to have the DB
65+
and search index emptied before running the test.
66+
"""
5567
tables = reversed(db.Base.metadata.sorted_tables)
5668
with contextlib.closing(db_engine.connect()) as conn:
5769
tx = conn.begin()
@@ -65,6 +77,12 @@ def with_clean_db(db_engine):
6577
db.Base.metadata.create_all(db_engine)
6678
db.post_create(db_engine)
6779

80+
# A test may not use the search index but may nonetheless use this
81+
# fixture because it wants a clean DB. In that case trying to clear the
82+
# search index will get a NotFoundError. Just suppress it.
83+
with contextlib.suppress(elasticsearch.exceptions.NotFoundError):
84+
clear_search_index()
85+
6886

6987
@pytest.fixture
7088
def db_session(db_engine, db_sessionfactory):
@@ -84,10 +102,3 @@ def factories(db_session):
84102
@pytest.fixture(scope="session")
85103
def pyramid_app():
86104
return create_app(None, **TEST_SETTINGS)
87-
88-
89-
# Always unconditionally wipe the Elasticsearch index after every functional
90-
# test.
91-
@pytest.fixture(autouse=True)
92-
def always_delete_all_elasticsearch_documents():
93-
pass

tests/unit/h/conftest.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,12 @@
6767
from tests.common import factories as common_factories
6868

6969

70+
@pytest.fixture
71+
def es_client(es_client, clear_search_index):
72+
yield es_client
73+
clear_search_index()
74+
75+
7076
class DummyFeature:
7177
"""
7278
A dummy feature flag looker-upper.

0 commit comments

Comments
 (0)