From cd717b97327b0cd061252350930c4f2d52118a0a Mon Sep 17 00:00:00 2001 From: Michael Howitz Date: Wed, 29 Sep 2021 11:30:38 +0200 Subject: [PATCH 1/4] Fix `LiveServer` for in-memory SQLite database `allow_thread_sharing` is no longer writeable since Django 2.2. --- AUTHORS | 1 + docs/changelog.rst | 10 ++++++++++ pytest_django/live_server_helper.py | 6 +++++- 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/AUTHORS b/AUTHORS index 2272df0f..2a64d064 100644 --- a/AUTHORS +++ b/AUTHORS @@ -14,3 +14,4 @@ Donald Stufft Nicolas Delaby Daniel Hahler Hasan Ramezani +Michael Howitz diff --git a/docs/changelog.rst b/docs/changelog.rst index 5b04c3ba..d8acf172 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -1,6 +1,16 @@ Changelog ========= +unreleased +---------- + +Bugfixes +^^^^^^^^ + +* Fix :fixture:`live_server` when using an in-memory SQLite database on + Django >= 3.0. + + v4.4.0 (2021-06-06) ------------------- diff --git a/pytest_django/live_server_helper.py b/pytest_django/live_server_helper.py index 943198d7..38fdbeeb 100644 --- a/pytest_django/live_server_helper.py +++ b/pytest_django/live_server_helper.py @@ -24,7 +24,7 @@ def __init__(self, addr: str) -> None: and conn.settings_dict["NAME"] == ":memory:" ): # Explicitly enable thread-shareability for this connection - conn.allow_thread_sharing = True + conn.inc_thread_sharing() connections_override[conn.alias] = conn liveserver_kwargs["connections_override"] = connections_override @@ -60,8 +60,12 @@ def __init__(self, addr: str) -> None: def stop(self) -> None: """Stop the server""" + # Terminate the live server's thread. self.thread.terminate() self.thread.join() + # Restore shared connections' non-shareability. + for conn in self.thread.connections_override.values(): + conn.dec_thread_sharing() @property def url(self) -> str: From 55f95fea1d84d0ce4c0060f02f2663051cc30def Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Wed, 1 Dec 2021 11:53:37 +0200 Subject: [PATCH 2/4] live_server_helper: remove redundant join() terminate() already does it. --- pytest_django/live_server_helper.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pytest_django/live_server_helper.py b/pytest_django/live_server_helper.py index 38fdbeeb..f1f6b21f 100644 --- a/pytest_django/live_server_helper.py +++ b/pytest_django/live_server_helper.py @@ -62,7 +62,6 @@ def stop(self) -> None: """Stop the server""" # Terminate the live server's thread. self.thread.terminate() - self.thread.join() # Restore shared connections' non-shareability. for conn in self.thread.connections_override.values(): conn.dec_thread_sharing() From 7cd9a01fe9be0fe3f2a87c52d3e9cb28111e20be Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Wed, 1 Dec 2021 12:04:32 +0200 Subject: [PATCH 3/4] live_server_helper: clean up on thread error --- pytest_django/live_server_helper.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pytest_django/live_server_helper.py b/pytest_django/live_server_helper.py index f1f6b21f..1dbac614 100644 --- a/pytest_django/live_server_helper.py +++ b/pytest_django/live_server_helper.py @@ -50,13 +50,17 @@ def __init__(self, addr: str) -> None: self._live_server_modified_settings = modify_settings( ALLOWED_HOSTS={"append": host} ) + # `_live_server_modified_settings` is enabled and disabled by + # `_live_server_helper`. self.thread.daemon = True self.thread.start() self.thread.is_ready.wait() if self.thread.error: - raise self.thread.error + error = self.thread.error + self.stop() + raise error def stop(self) -> None: """Stop the server""" From 77f35572cc0bbd33a1d17717bb30ba15154fa842 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Wed, 1 Dec 2021 12:25:55 +0200 Subject: [PATCH 4/4] live_server_helper: improve sqlite in-memory check This will make it so it's covered by our tests. --- pytest_django/live_server_helper.py | 7 ++----- pytest_django_test/db_helpers.py | 6 ++++-- pytest_django_test/settings_sqlite.py | 6 +++--- pytest_django_test/settings_sqlite_file.py | 8 ++++++-- 4 files changed, 15 insertions(+), 12 deletions(-) diff --git a/pytest_django/live_server_helper.py b/pytest_django/live_server_helper.py index 1dbac614..0251d24c 100644 --- a/pytest_django/live_server_helper.py +++ b/pytest_django/live_server_helper.py @@ -19,11 +19,8 @@ def __init__(self, addr: str) -> None: for conn in connections.all(): # If using in-memory sqlite databases, pass the connections to # the server thread. - if ( - conn.settings_dict["ENGINE"] == "django.db.backends.sqlite3" - and conn.settings_dict["NAME"] == ":memory:" - ): - # Explicitly enable thread-shareability for this connection + if conn.vendor == 'sqlite' and conn.is_in_memory_db(): + # Explicitly enable thread-shareability for this connection. conn.inc_thread_sharing() connections_override[conn.alias] = conn diff --git a/pytest_django_test/db_helpers.py b/pytest_django_test/db_helpers.py index 14ebe333..d984b1d1 100644 --- a/pytest_django_test/db_helpers.py +++ b/pytest_django_test/db_helpers.py @@ -15,6 +15,8 @@ if _settings["ENGINE"] == "django.db.backends.sqlite3" and TEST_DB_NAME is None: TEST_DB_NAME = ":memory:" + SECOND_DB_NAME = ":memory:" + SECOND_TEST_DB_NAME = ":memory:" else: DB_NAME += "_inner" @@ -25,8 +27,8 @@ # An explicit test db name was given, is that as the base name TEST_DB_NAME = "{}_inner".format(TEST_DB_NAME) -SECOND_DB_NAME = DB_NAME + '_second' if DB_NAME is not None else None -SECOND_TEST_DB_NAME = TEST_DB_NAME + '_second' if DB_NAME is not None else None + SECOND_DB_NAME = DB_NAME + '_second' if DB_NAME is not None else None + SECOND_TEST_DB_NAME = TEST_DB_NAME + '_second' if DB_NAME is not None else None def get_db_engine(): diff --git a/pytest_django_test/settings_sqlite.py b/pytest_django_test/settings_sqlite.py index f58cc7d8..057b8344 100644 --- a/pytest_django_test/settings_sqlite.py +++ b/pytest_django_test/settings_sqlite.py @@ -4,17 +4,17 @@ DATABASES = { "default": { "ENGINE": "django.db.backends.sqlite3", - "NAME": "/should_not_be_accessed", + "NAME": ":memory:", }, "replica": { "ENGINE": "django.db.backends.sqlite3", - "NAME": "/should_not_be_accessed", + "NAME": ":memory:", "TEST": { "MIRROR": "default", }, }, "second": { "ENGINE": "django.db.backends.sqlite3", - "NAME": "/should_not_be_accessed", + "NAME": ":memory:", }, } diff --git a/pytest_django_test/settings_sqlite_file.py b/pytest_django_test/settings_sqlite_file.py index 4f3404a0..206b658a 100644 --- a/pytest_django_test/settings_sqlite_file.py +++ b/pytest_django_test/settings_sqlite_file.py @@ -15,7 +15,9 @@ "default": { "ENGINE": "django.db.backends.sqlite3", "NAME": "/pytest_django_tests_default", - "TEST": {"NAME": _filename_default}, + "TEST": { + "NAME": _filename_default, + }, }, "replica": { "ENGINE": "django.db.backends.sqlite3", @@ -28,6 +30,8 @@ "second": { "ENGINE": "django.db.backends.sqlite3", "NAME": "/pytest_django_tests_second", - "TEST": {"NAME": _filename_second}, + "TEST": { + "NAME": _filename_second, + }, }, }