Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Incompatible scopes of live_server and transactional_db #454

Open
aaugustin opened this issue Jan 28, 2017 · 11 comments
Open

Incompatible scopes of live_server and transactional_db #454

aaugustin opened this issue Jan 28, 2017 · 11 comments
Labels

Comments

@aaugustin
Copy link

The live_server fixture is session-scoped while the transactional_db fixture (which live_server implicitly enables) is function-scoped.

As a consequence, live_server can handle requests during the whole duration of the test suite. These requests can collide with the database flushes that transactional_db performs after each test or with the periods when access to the database is disabled between two tests.

Here's what the sequence of events looks like:

  • Session: live_server fixture starts a LiveServerThread
    • Function: transactional_db fixture runs (triggered by the function-scoped, autouse _live_server_helper fixture) and adds TransactionTestCase._post_teardown to finalizers
    • Function: test makes a request to the live server, perhaps from a selenium-driven browser, and terminates before the live server can respond to that request (this can happen for various reasons; a common one is accessing a page that makes AJAX requests when it loads towards the end of the test and exiting the test function before all these requests have completed)
    • Function: transactional_db finalizer runs and attempts to flush the database, conflicting with the requests the live server is still processing
  • Session: live_server finalizer waits for LiveServerThread to terminate

The conflict can cause deadlocks between any SQL query from a HTTP request handled by the live server and the query that flushes the database, which looks like this:

Exception Database test_xxxxxxxx couldn't be flushed. Possible reasons:
  * The database isn't running or isn't configured correctly.
  * At least one of the expected database tables doesn't exist.
  * The SQL was invalid.
Hint: Look at the output of 'django-admin sqlflush'. That's the SQL this command wasn't able to run.
The full error: deadlock detected
DETAIL:  Process 37253 waits for AccessExclusiveLock on relation 131803 of database 131722; blocked by process 37250.
Process 37250 waits for AccessShareLock on relation 132659 of database 131722; blocked by process 37253.
HINT:  See server log for query details.

This failure mode is particularly annoying because the database isn't flushed, requiring to next test run to re-create the database and thus negating the benefits of --reuse-db.

If database reuse isn't enabled, destroying the test database can fail with:

django.db.utils.OperationalError: database "test_xxxxxxxx" is being accessed by other users
DETAIL:  There is 1 other session using the database.

I've also seen SQL queries from HTTP requests handled by the live server to fail with:

Failed: Database access not allowed, use the "django_db" mark, or the "db" or "transactional_db" fixtures to enable it.

because the transactional_db finalizer has flushed the database and blocked access until the next test.

I'm planning to look for workarounds and will update this ticket with my findings.

@b4handjr
Copy link

b4handjr commented Mar 6, 2017

Running into this as well. Did you find anything?

@aaugustin
Copy link
Author

Nope.

I'll report my findings here if I find time to work on a solution. It isn't near the top of my TODO list.

@mpasternak
Copy link

I'm having the same(?) problem, live_server fixture does not see changes made to the database, running in it's own transaction. @aaugustin , does it sound likely to you?

I was wondering... maybe I should ditch the live_server fixture totally and just write my replacement. It would consist of running "manage.py runserver" in a thread. Except pointing it to a test database, I'd not update project settings. At some point I may need that. Also, LiveServerTestCase does not support custom STATICFILES_FINDERS except the filesystem one...

@Helumpago
Copy link

I'm also running into this issue. Current workaround I'm using is to override the live_server fixture so that it is function scoped. Specifically, my conftest.py file contains the following:

from pytest_django.fixtures import live_server as orig_live_server

@pytest.fixture(scope='function')
def live_server(request):
    """
        Workaround inspired by https://github.com/mozilla/addons-server/pull/4875/files#diff-0223c02758be2ac7967ea22c6fa4b361R96
    """
    return orig_live_server(request)

This seems like it's working, though admittedly, it slows all the test cases that use it way down. If anyone sees a problem with this approach, please let me know.

@andreymal
Copy link

I have similar problem: live_server is not working, it prints error no such table: django_session.

The @Helumpago's workaround fixed this error for me. But pytest prints this warning:

RemovedInPytest4Warning: Fixture "live_server" called directly. Fixtures are not meant to be called directly, are created automatically when test functions request them as parameters.

Is there any other way to use live_server with a database?

@andreymal
Copy link

andreymal commented Dec 17, 2018

The @Helumpago's workaround raises an error instead of warning since pytest 4.0 (2018-11-13) :(

UPD: looks like it works (but this is ugly)

from pytest_django.fixtures import live_server as orig_live_server
live_server = pytest.fixture(scope='function')(orig_live_server.__wrapped__)

@mmcardle
Copy link

mmcardle commented Oct 24, 2019

I have had some success with the code below, but (YMMV)

@pytest.fixture(name='live_server', scope='function')
def _live_server(live_server):
    # This rescopes the live_server fixture to function scope
    # See https://github.com/pytest-dev/pytest-django/issues/454
    return live_server

scope='function' is not technically required as the default scope is function but is added for clarity

@silviogutierrez
Copy link

I'm running into this too. No doubt all the above variants of the fix will work.

But I'm wondering: what is the correct behavior here? It seems like, the root problem is actually those AJAX requests still happening after the test finishes. Rightly so, they should prevent the database from flushing.

Think about it in real life and not a test. If you had a DB and wanted to run a flush command, and had a ton of active requests towards that table, wouldn't you expect an error or to have to handle it, rather than it just working and magically killing all those pending requests?

I can think of a few options:

  1. Have your Selenium client wait for all outstanding AJAX requests to finish before ceding control. I wrap my Selenium usage so this is fairly straightforward, and my app has a way to signal loading is done. But this is impractical to many. Especially so for apps that "poll" in the background. There may always be an outstanding request.
  2. Somehow finagle pytest/flush commands to kill all outstanding processes to the database. Something like this psql postgres -c 'SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE pid <> EVERYTHING_BUT_PYTEST_PID';
  3. Change scope, but as pointed out, this slows down everything.
  4. Maybe there's a magical way Selenium do Fix an RST syntax typo #1 in a more structured, universal way. Something like: wait for all pending AJAX requests to finish but start no new ones. I'm not familiar enough with Selenium to verify this right now.

@blueyed blueyed added the bug label Jan 7, 2020
@atcrank
Copy link

atcrank commented May 25, 2022

I'm not sure if I'm running into this problem or if it is just a well known issue that the docs are hinting at, but after I request a 'live_server' fixture for a test, I do not get the normal db fixture (which I've overloaded to ingest a file of test data) on subsequent tests. I tried mmcardle's suggested wrapper for re-scoping live_server down to 'function', but the problem remained. Putting all live_server tests last is sort of doable with alphabetically ordering the test filenames, but don't feel great about tests that work in one order and not another.

@NathanSmeltzer
Copy link

If the above fixes do not work for anyone else, try temporarily disabling the --reuse-db option (usually in your pytest.ini file) and running the test(s). If it no longer errors, add the --reuse-db option back.

@shulcsm
Copy link

shulcsm commented Apr 13, 2023

Are there any new developments or better workaround?
Waiting for requests to settle works but it's slow and not very reliable.
Terminating or canceling query backend does not work since that just bubbles up the exception in server thread.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests