#16622: django.test.TestCase slow using PostgreSQL 8.4.4 -------------------------------------+------------------------------------- Reporter: | Owner: nobody valhallasw | Status: new Type: | Component: Database layer Cleanup/optimization | (models, ORM) Milestone: | Severity: Normal Version: 1.3 | Keywords: Resolution: | Has patch: 0 Triage Stage: Accepted | Needs tests: 0 Needs documentation: 0 | Easy pickings: 0 Patch needs improvement: 0 | UI/UX: 0 | -------------------------------------+------------------------------------- Changes (by valhallasw):
* type: Bug => Cleanup/optimization * component: Testing framework => Database layer (models, ORM) Comment: tl;dr: it was our fault. sorry for wasting your time :-( In the end, it was caused because we used a buggy way of adding a database programmatically (i.e. not using the configuration screen) to be able to test on different data sets (this is useful, for instance, for testing statistical functions). This method did not call [https://code.djangoproject.com/browser/django/tags/releases/1.3/django/db/backends/__init__.py#L335 features.confirm](). This, in turn means the testing framework got `None` when it asked for `features.supports_transactions` . which returned None (instead of True). This was intepreted as 'it's not True', and thus transactions were not used. I'm not quite sure how sensible returning None is: IMO is would make more sense to raise an Exception if no data is available, or calling confrm() to actually /get/ the data (although creating and deleting a table while in a query might not be the most sensible thing to do). To give you a full log of my debugging: {{{ (Pdb) b /lib/python2.6/site- packages/django/db/backends/postgresql_psycopg2/base.py:43, "TRUNCATE" in query }}} the breakpoint is hit a few times. The first time, this is the stack trace: {{{ (Pdb) w /usr/lib/python2.6/bdb.py(372)run() -> exec cmd in globals, locals <string>(1)<module>() /home/project/src/manage.py(16)<module>() -> execute_manager(settings) /lib/python2.6/site- packages/django/core/management/__init__.py(438)execute_manager() -> utility.execute() /lib/python2.6/site- packages/django/core/management/__init__.py(379)execute() -> self.fetch_command(subcommand).run_from_argv(self.argv) /lib/python2.6/site- packages/django/core/management/base.py(191)run_from_argv() -> self.execute(*args, **options.__dict__) /lib/python2.6/site- packages/django/core/management/base.py(220)execute() -> output = self.handle(*args, **options) /lib/python2.6/site- packages/django/core/management/commands/test.py(37)handle() -> failures = test_runner.run_tests(test_labels) /lib/python2.6/site-packages/django/test/simple.py(359)run_tests() -> old_config = self.setup_databases() /home/project/src/djatabase/testing/PassableTestRunner.py(16)setup_databases() -> retval = DjangoTestSuiteRunner.setup_databases(self, **kwargs) /lib/python2.6/site-packages/django/test/simple.py(296)setup_databases() -> test_db_name = connection.creation.create_test_db(self.verbosity, autoclobber=not self.interactive) /lib/python2.6/site- packages/django/db/backends/creation.py(376)create_test_db() -> database=self.connection.alias) /lib/python2.6/site- packages/django/core/management/__init__.py(166)call_command() -> return klass.execute(*args, **defaults) /lib/python2.6/site- packages/django/core/management/base.py(220)execute() -> output = self.handle(*args, **options) /lib/python2.6/site-packages/django/core/management/base.py(351)handle() -> return self.handle_noargs(**options) /lib/python2.6/site- packages/django/core/management/commands/flush.py(54)handle_noargs() -> cursor.execute(sql) > /lib/python2.6/site- packages/django/db/backends/postgresql_psycopg2/base.py(43)execute() -> try: }}} which is the `TRUNCATE` called by the test database creation. The second and all later times, this is the stack trace: {{{ (Pdb) w /usr/lib/python2.6/bdb.py(372)run() -> exec cmd in globals, locals <string>(1)<module>() /home/project/src/manage.py(16)<module>() -> execute_manager(settings) /lib/python2.6/site- packages/django/core/management/__init__.py(438)execute_manager() -> utility.execute() /lib/python2.6/site- packages/django/core/management/__init__.py(379)execute() -> self.fetch_command(subcommand).run_from_argv(self.argv) /lib/python2.6/site- packages/django/core/management/base.py(191)run_from_argv() -> self.execute(*args, **options.__dict__) /lib/python2.6/site- packages/django/core/management/base.py(220)execute() -> output = self.handle(*args, **options) /lib/python2.6/site- packages/django/core/management/commands/test.py(37)handle() -> failures = test_runner.run_tests(test_labels) /lib/python2.6/site-packages/django/test/simple.py(360)run_tests() -> result = self.run_suite(suite) /lib/python2.6/site-packages/django/test/simple.py(316)run_suite() -> return unittest.TextTestRunner(verbosity=self.verbosity, failfast=self.failfast).run(suite) /lib/python2.6/site-packages/django/utils/unittest/runner.py(162)run() -> test(result) /lib/python2.6/site- packages/django/utils/unittest/suite.py(64)__call__() -> return self.run(*args, **kwds) /lib/python2.6/site-packages/django/utils/unittest/suite.py(84)run() -> self._wrapped_run(result) /lib/python2.6/site- packages/django/utils/unittest/suite.py(116)_wrapped_run() -> test(result) /lib/python2.6/site-packages/django/test/testcases.py(292)__call__() -> self._pre_setup() /lib/python2.6/site-packages/django/test/testcases.py(259)_pre_setup() -> self._fixture_setup() /lib/python2.6/site- packages/django/test/testcases.py(569)_fixture_setup() -> return super(TestCase, self)._fixture_setup() /lib/python2.6/site- packages/django/test/testcases.py(271)_fixture_setup() -> call_command('flush', verbosity=0, interactive=False, database=db) /lib/python2.6/site- packages/django/core/management/__init__.py(166)call_command() -> return klass.execute(*args, **defaults) /lib/python2.6/site- packages/django/core/management/base.py(220)execute() -> output = self.handle(*args, **options) /lib/python2.6/site-packages/django/core/management/base.py(351)handle() -> return self.handle_noargs(**options) /lib/python2.6/site- packages/django/core/management/commands/flush.py(54)handle_noargs() -> cursor.execute(sql) > /lib/python2.6/site- packages/django/db/backends/postgresql_psycopg2/base.py(43)execute() -> try: }}} Traversing the chain up to {{{ > /lib/python2.6/site- packages/django/test/testcases.py(271)_fixture_setup() -> call_command('flush', verbosity=0, interactive=False, database=db) }}} https://code.djangoproject.com/browser/django/tags/releases/1.3/django/test/testcases.py#L260 where it's clear flush is called if _fixture_setup() is called. traversing one more step up to {{{ -> self._fixture_setup() /lib/python2.6/site- packages/django/test/testcases.py(569)_fixture_setup() -> return super(TestCase, self)._fixture_setup() }}} https://code.djangoproject.com/browser/django/tags/releases/1.3/django/test/testcases.py#L560 with the relevant code {{{#!python if not connections_support_transactions(): return super(TestCase, self)._fixture_setup() }}} and indeed: {{{ (Pdb) connections_support_transactions() False }}} [https://code.djangoproject.com/browser/django/tags/releases/1.3/django/test/testcases.py#L551 connections_support_transactions()]: {{{#!python return all(conn.features.supports_transactions for conn in connections.all()) }}} and if I request [conn.features.supports_transactions for conn in connections.all()]: {{{ (Pdb) [conn.features.supports_transactions for conn in connections.all()] [True, None] }}} OK, so that is the reason I'm seeing flushes. But why is `support_transactions` `None`? Apparently it's not being filled. Calling {{{ [conn.features.confirm() for conn in djconn.all()] }}} seems to fix that. End result: {{{ $ ./manage.py test Creating test database for alias 'default'... ..... ---------------------------------------------------------------------- Ran 5 tests in 0.567s OK }}} yay! -- Ticket URL: <https://code.djangoproject.com/ticket/16622#comment:10> 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 post to this group, send email to django-updates@googlegroups.com. To unsubscribe from this group, send email to django-updates+unsubscr...@googlegroups.com. For more options, visit this group at http://groups.google.com/group/django-updates?hl=en.