#25746: Isolate inlined test models
-------------------------------------+-------------------------------------
     Reporter:  charettes            |                    Owner:  charettes
         Type:                       |                   Status:  assigned
  Cleanup/optimization               |
    Component:  Testing framework    |                  Version:  master
     Severity:  Normal               |               Resolution:
     Keywords:                       |             Triage Stage:  Accepted
    Has patch:  0                    |      Needs documentation:  0
  Needs tests:  0                    |  Patch needs improvement:  0
Easy pickings:  0                    |                    UI/UX:  0
-------------------------------------+-------------------------------------
Changes (by timgraham):

 * stage:  Unreviewed => Accepted


Old description:

> Many test in Django's own test suite inline model definition in order to
> prevent table creation or simply isolate them.
>
> Unfortunately most of these model don't explicitly declare an explicit
> `apps` and end up being registered to the global `apps` instance. This
> has three downsides:
>
> 1. The call to `register_model` busts the `apps` and registered models
> (1k+) `_meta` cache. I didn't run any benchmark here but I suspect this
> slow down things a bit.
> 2. If a model is already registered for the specified
> `app_label.ModelName` (see the `schema` issue detailed in #25745) a
> `RuntimeWarning` is raised.
> 3. If no model is registered for this particular `app_label.ModelName`
> any future call to `migrate` will end up creating the table since we
> don't have explicit migration defined for the test apps.
>
> To prevent the enumerated issues the inlined models should use a per-test
> isolated registry. Examples of such a pattern can be found on this
> [https://github.com/django/django/pull/5613 PR].
>
> e.g.
>
> {{{#!python
> def test_foo(self):
>     test_apps = Apps(['test_app'])
>     class Foo(models.Model):
>         class Meta:
>             apps = test_apps
> }}}
>
> In order to reduce the boiler plate required to achieve isolation I
> suggest introducing a test decorator/context manager that register models
> declared in it's body in an isolated `Apps()` instance.
>
> e.g.
>
> {{{#!python
> @isolate_apps('test_app', 'test_app_2')
> def test_foo(self):
>     class Foo(models.Model):
>         pass
>
>     with isolate_apps('test_app3') as apps:
>         class Bar(models.Model):
>             class Meta:
>                 app_label = 'test_app3'
>     assert Bar is apps.get_model('test_app3', 'Bar')
> }}}
>
> This would also make fixing the actual and future isolation problems
> quite easy by simply decorating the faulty test.

New description:

 Many tests in Django's own test suite inline model definition in order to
 prevent table creation or simply isolate them.

 Unfortunately most of these model don't explicitly declare an explicit
 `apps` and end up being registered to the global `apps` instance. This has
 three downsides:

 1. The call to `register_model` busts the `apps` and registered models
 (1k+) `_meta` cache. I didn't run any benchmark here but I suspect this
 slow down things a bit.
 2. If a model is already registered for the specified
 `app_label.ModelName` (see the `schema` issue detailed in #25745) a
 `RuntimeWarning` is raised.
 3. If no model is registered for this particular `app_label.ModelName` any
 future call to `migrate` will end up creating the table since we don't
 have explicit migration defined for the test apps.

 To prevent the enumerated issues the inlined models should use a per-test
 isolated registry. Examples of such a pattern can be found on this
 [https://github.com/django/django/pull/5613 PR].

 e.g.

 {{{#!python
 def test_foo(self):
     test_apps = Apps(['test_app'])
     class Foo(models.Model):
         class Meta:
             apps = test_apps
 }}}

 In order to reduce the boiler plate required to achieve isolation I
 suggest introducing a test decorator/context manager that register models
 declared in it's body in an isolated `Apps()` instance.

 e.g.

 {{{#!python
 @isolate_apps('test_app', 'test_app_2')
 def test_foo(self):
     class Foo(models.Model):
         pass

     with isolate_apps('test_app3') as apps:
         class Bar(models.Model):
             class Meta:
                 app_label = 'test_app3'
     assert Bar is apps.get_model('test_app3', 'Bar')
 }}}

 This would also make fixing the actual and future isolation problems quite
 easy by simply decorating the faulty test.

--

--
Ticket URL: <https://code.djangoproject.com/ticket/25746#comment:2>
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 post to this group, send email to [email protected].
To view this discussion on the web visit 
https://groups.google.com/d/msgid/django-updates/067.e353d3e45db019312510dfafbe925926%40djangoproject.com.
For more options, visit https://groups.google.com/d/optout.

Reply via email to