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

Fixed #25251 -- Made data migrations available in TransactionTestCase. #6137

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions django/test/testcases.py
Original file line number Diff line number Diff line change
Expand Up @@ -928,6 +928,16 @@ def _fixture_teardown(self):
allow_cascade=self.available_apps is not None,
inhibit_post_migrate=inhibit_post_migrate)

# If there was some initial data from migrated apps, restore them for the next test.
if self.serialized_rollback and hasattr(connections[db_name], "_test_serialized_contents"):
if self.available_apps is not None:
apps.unset_available_apps()
connections[db_name].creation.deserialize_db_from_string(
connections[db_name]._test_serialized_contents
)
if self.available_apps is not None:
apps.set_available_apps(self.available_apps)

def assertQuerysetEqual(self, qs, values, transform=repr, ordered=True, msg=None):
items = six.moves.map(transform, qs)
if not ordered:
Expand Down
11 changes: 11 additions & 0 deletions docs/releases/1.10.txt
Original file line number Diff line number Diff line change
Expand Up @@ -600,6 +600,17 @@ You can check if your database has any of the removed hashers like this::
# Unsalted MD5 passwords might not have an 'md5$$' prefix:
User.objects.filter(password__length=32)

``TransactionTestCase`` serialized data loading
-----------------------------------------------

Initial data migrations are now loaded in ``TransactionTestCase`` at the end of
the test, after the database flush.
In older versions, these data were loaded at the beginning of the test, but
this behavior was preventing ``--keepdb`` option to work properly (the
database was empty at the end of the whole test suite).
It shouldn't have any impact on your test suite if you have not customized
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't it make migration data available in all TransactionTestCases (in older versions, it was only available in the first one, I believe)?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that my explanation is partially wrong. It won't have any impact on test suites if people have defined all their TransactionTestCase with serialised_rollback option to True.

If somebody is using serialized_rollback in just one test, he is expecting to have the initial data loaded at the beginning of the test, not the end. And it makes hard to predict: by loading the data at the end of the test, we know that we prepare a clean environment for the next test, which is unknown by the developer. It then makes sense only if all the TransactionTestCase have the same serialized_rollback logic.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe another way to fix it is not to impact TransactionTestCase class, but TRUNCATE and load initial data migrations at the end of the test suite run, if --keepdb option has been activated.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure it's feasible but you could try. It's possible might have to document the limitation.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If it's possible to salvage the test changes you've made here to write a regression test for incorrect behavior the current patch introduces, that would be great. I think serialized_rollback doesn't really have any tests right now.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@timgraham I would be happy to add some tests for serialized_rollback option. These added tests should probably live in another MR, right ?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, I will create it.

Some feelings about the loading before and after ?

It could also be a new parameter in TransactionTestCase, like serialized_rollback_after (needs a better naming) to be used in --keepdb configuration, so that it's loading twice only when people want to use --keepdb.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are already some tests in migration_test_data_persistance.tests.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Loading afterward seems redundant -- as you noted, this won't work if there are any TransactionTestCases without serialized_rollback=True. It might be possible to take care of the data loading in destroy_test_db() (if keepdb=True).

``TransactionTestCase`` internal logic.

Miscellaneous
-------------

Expand Down
7 changes: 7 additions & 0 deletions docs/topics/testing/overview.txt
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,13 @@ To prevent serialized data from being loaded twice, setting
:data:`~django.db.models.signals.post_migrate` signal when flushing the test
database.

.. versionchanged:: 1.10
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't the first sentence of this section need to be revised, "Any initial data loaded in migrations will only be available in TestCase tests and not in TransactionTestCase tests"?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure, as this logic is still dependant on serialized_rollback parameter, as explained on second paragraph:

"Django can reload that data for you on a per-testcase basis by setting the serialized_rollback option to True in the body of the TestCase or TransactionTestCase"

Not really sure if it makes sense to use serialized_rollback with TestCase, as these tests are always launched before TransactionTestCase ones, and so the initial data migrations should not be impacted by TestCase tests, isn't it ?

Just wondering, a mix of TransactionTestCase tests with serialised_rollback to True and False won't guarantee the --keepdb option to work.

If the last test of the suite has no serialised_rollback option, then the database will be empty at the end. And as we cannot guarantee test ordering, it means that somebody who wants to reuse the database with --keepdb option should absolutely use serialized_rollback option to True on every TransactionTestCase he is defining...


Data are now loaded at the end of a ``TransactionTestCase``, after
the database flush (it was previously loaded at the beginning of the test).
This change makes ``--keepdb`` option work properly if your test suite
contains ``TransactionTestCase`` tests.

Other test conditions
---------------------

Expand Down
58 changes: 57 additions & 1 deletion tests/test_utils/test_transactiontestcase.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,46 @@
import json

from django.apps import apps
from django.db import connections
from django.test import TransactionTestCase, mock

from .models import Car


class TestSerializedContentMockMixin(object):
"""
This mixin should be used every time we want to test something involving
TransactionTestCase and serialized_rollback = True option to avoid test dependencies.
We mock what would be serialized by Django after initial data migrations, and restore it
at the end of the test.
"""
initial_data_migration = '[]'
_connections_test_serialized_content = {}

def _pre_setup(self):
for db_name in self._databases_names(include_mirrors=False):
self._connections_test_serialized_content[db_name] = connections[db_name]._test_serialized_contents
connections[db_name]._test_serialized_contents = self.initial_data_migration
super(TestSerializedContentMockMixin, self)._pre_setup()

def _post_teardown(self):
super(TestSerializedContentMockMixin, self)._post_teardown()
for db_name in self._databases_names(include_mirrors=False):
connections[db_name]._test_serialized_contents = self._connections_test_serialized_content[db_name]

@classmethod
def tearDownClass(cls):
super(TestSerializedContentMockMixin, cls).tearDownClass()

# Cleaning any data that has been created by our mixin class
# to not impact any other django suite test.
json_data = json.loads(cls.initial_data_migration)
for data in json_data:
Klass = apps.get_model(*data['model'].split('.'))
Klass.objects.get(pk=data['pk']).delete()

class TestSerializedRollbackInhibitsPostMigrate(TransactionTestCase):

class TestSerializedRollbackInhibitsPostMigrate(TestSerializedContentMockMixin, TransactionTestCase):
"""
TransactionTestCase._fixture_teardown() inhibits the post_migrate signal
for test classes with serialized_rollback=True.
Expand All @@ -26,3 +65,20 @@ def test(self, call_command):
reset_sequences=False, inhibit_post_migrate=True,
database='default', verbosity=0,
)


class TestDataConsistencyOnTearDown(TestSerializedContentMockMixin, TransactionTestCase):
"""
Initial data should be recreated in TransactionTestCase._fixture_teardown()
after the database is flushed so it's available in all tests.
"""
available_apps = ['test_utils']
serialized_rollback = True
initial_data_migration = '[{"model": "test_utils.car", "pk": 666, "fields": {"name": "K 2000"}}]'

def _post_teardown(self):
super(TestDataConsistencyOnTearDown, self)._post_teardown()
self.assertTrue(Car.objects.exists())

def test_that_should_be_the_only_one_in_this_test_case(self):
pass