#36083: LiveServerTestCase fails in parallel test runner if
django.contrib.auth.backends has not yet been imported
-------------------------------------+-------------------------------------
     Reporter:  Adam Zapletal        |                    Owner:  (none)
         Type:  Bug                  |                   Status:  new
    Component:  Testing framework    |                  Version:  dev
     Severity:  Release blocker      |               Resolution:
     Keywords:  TransactionTestCase  |             Triage Stage:  Accepted
  setupclass available_apps          |
    Has patch:  0                    |      Needs documentation:  0
  Needs tests:  0                    |  Patch needs improvement:  0
Easy pickings:  0                    |                    UI/UX:  0
-------------------------------------+-------------------------------------
Changes (by Jacob Walls):

 * summary:
     TransactionTestCase available_apps not set correctly for parallel test
     runner
     =>
     LiveServerTestCase fails in parallel test runner if
     django.contrib.auth.backends has not yet been imported

Comment:

 This was a fun one. Here's what I found:

 - The commit I blamed exposed this by lifting the first call to
 `set_available_apps()` up into `TransactionTestCase.setUpClass()` (via
 `_pre_setup()`), and so the order of operations in `LiveServerTestCase`
 became different.
 - The reported issue goes away by moving `super().setUpClass()` to the
 bottom of `LiveServerTestCase.setUpClass()`.

 Why?
 - The `check_middleware` system check registered by `django.contrib.auth`
 has the side effect of importing `django.contrib.auth.backends`
 - The parallel test runner does not run the system checks per-process
 - When a parallel test runner worker tries to `setUpClass()` a
 `LiveServerTestCase`, it may be importing `django.contrib.auth.backends`
 for the first time and thus running `get_user_model()`
 - `get_user_model()` will fail (as designed) if `django.contrib.auth` is
 not in `available_apps`, which is now the case based on the change in the
 order of operations
 ----
 I tested two solutions, both of which work:
 1. Running system checks inside each worker thread, to ensure the side
 effect described above is executed in each process:
 {{{#!diff
 diff --git a/django/test/runner.py b/django/test/runner.py
 index b83cd37343..360bba4fd7 100644
 --- a/django/test/runner.py
 +++ b/django/test/runner.py
 @@ -419,6 +419,7 @@ def _init_worker(
      process_setup_args=None,
      debug_mode=None,
      used_aliases=None,
 +    verbosity=1,
  ):
      """
      Switch to databases dedicated to this worker.
 @@ -442,6 +443,7 @@ def _init_worker(
              process_setup(*process_setup_args)
          django.setup()
          setup_test_environment(debug=debug_mode)
 +        call_command("check", verbosity=verbosity,
 databases=used_aliases)

      db_aliases = used_aliases if used_aliases is not None else
 connections
      for alias in db_aliases:
 @@ -496,7 +498,13 @@ class ParallelTestSuite(unittest.TestSuite):
      runner_class = RemoteTestRunner

      def __init__(
 -        self, subsuites, processes, failfast=False, debug_mode=False,
 buffer=False
 +        self,
 +        subsuites,
 +        processes,
 +        failfast=False,
 +        debug_mode=False,
 +        buffer=False,
 +        verbosity=1,
      ):
          self.subsuites = subsuites
          self.processes = processes
 @@ -506,6 +514,7 @@ class ParallelTestSuite(unittest.TestSuite):
          self.initial_settings = None
          self.serialized_contents = None
          self.used_aliases = None
 +        self.verbosity = verbosity
          super().__init__()

      def run(self, result):
 @@ -536,6 +545,7 @@ class ParallelTestSuite(unittest.TestSuite):
                  self.process_setup_args,
                  self.debug_mode,
                  self.used_aliases,
 +                self.verbosity,
              ],
          )
          args = [
 @@ -983,6 +993,7 @@ class DiscoverRunner:
                      self.failfast,
                      self.debug_mode,
                      self.buffer,
 +                    self.verbosity,
                  )
          return suite
 }}}
 2. Adjusting the order of operations in `LiveServerTestCase.setUpClass()`
 to act as before:
 {{{#!diff
 diff --git a/django/test/testcases.py b/django/test/testcases.py
 index 36366bd777..d641bb3c6b 100644
 --- a/django/test/testcases.py
 +++ b/django/test/testcases.py
 @@ -1817,11 +1817,11 @@ class LiveServerTestCase(TransactionTestCase):

      @classmethod
      def setUpClass(cls):
 -        super().setUpClass()
          cls.enterClassContext(
              modify_settings(ALLOWED_HOSTS={"append": cls.allowed_host})
          )
          cls._start_server_thread()
 +        super().setUpClass()

      @classmethod
      def _start_server_thread(cls):
 }}}
 Then of course there is:
 3. Fiddle with `available_apps` and/or `MIDDLEWARE` so that
 `LiveServerTestCase` subclasses always either have contrib.auth in
 available_apps or else no middleware that relies on it.
 ----
 Option 1 feels like a workaround for the fact that a certain system check
 has a side effect. It's a more invasive diff, and it adds performance
 drag.
 Option 2 feels like the simplest thing to do for now to preserve prior
 behavior, although it's a workaround for the fact that a system check has
 a side effect
 Option 3 feels more semantically correct but would need some thought
 around the design to make it ergonomic (fiddling with this in every
 subclass doesn't seem right)

 Adam, what do you think? Would you like to prepare a patch with a
 regression test?
-- 
Ticket URL: <https://code.djangoproject.com/ticket/36083#comment:3>
Django <https://code.djangoproject.com/>
The Web framework for perfectionists with deadlines.

-- 
You received this message because you are subscribed to the Google Groups 
"Django updates" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
To view this discussion visit 
https://groups.google.com/d/msgid/django-updates/010701945696edc3-00f34816-7edf-46c1-a53b-d10e2db3022a-000000%40eu-central-1.amazonses.com.

Reply via email to