On 3 syys, 07:40, Anssi Kääriäinen <anssi.kaariai...@thl.fi> wrote:
> I would like to make the TransactionTestCase faster. Currently when
> running Django's test suite, for every test ran you will truncate
> around 1000 tables, then create around 4000 objects (permissions +
> content types). Likely you will write to one or two tables in the
> test, and then do the truncate/recreate dance again. There is room for
> improvement. Track which tables have been modified and refresh only
> those tables.

I now have a pretty good WIP approach of tracking changes in testing.
The changes can be found from here: [https://github.com/akaariai/
django/tree/fast_tests_merged]. The approach relies on existing
signals + a new "model_changed" signal, which is used to signal
bulk_create and .update() changes, and could be used for raw SQL
changes, too.

The results are promising:
  - PostgreSQL with selenium tests, master 33m4s, patched: 9m51s
  - SQLite, no selenium tests, master: 7m35s, patched: 3m6s.

So, more than 3x speedup and more than 2x speedup. PostgreSQL without
Selenium tests is taking 7m14s, so it is slightly faster than SQLite
on master.

The current approach uses two flags in TransactionTestCase to control
state tracking: flush_all, and detect_leaks. The first instructs the
tester to do a full DB flush after the test, the latter can be used to
check if dirty state is leaked. It however tracks only object counts
currently, so updates could be missed. Making it more intelligent will
be somewhat hard as we can't compare the objects directly - the
autoincrement primary keys will not match.

The default for flush_all is False in the patched version. Defaulting
to that isn't of course acceptable if this was included in core.
Existing applications would break. IMO it should be a setting which
defaults to True.

The detect_leaks flag in TransactionTestCase should instead be a flag
to manage.py test, this way if there are mysterious failures it should
be somewhat straightforward to detect them by just giving that flag to
the test runner.

The patch also contains a cleanup to loaddata command. In master the
command is slow and complex. Now it is faster (around 50s removed by
the loaddata cleanup alone IIRC), and somewhat less complex. The patch
isn't 100% ready. Once ready I am planning to commit this separate of
the state tracking work.

Still, there is the __deepcopy__ removal patch from #16759 in there.
It removes just 15 seconds of runtime compared to loaddata + state
tracking, but I like to keep bringing this patch up... :)

I also tried to track installed fixture state across tests. We often
repeatedly load the same fixtures. This however got too complex, as
now we need to track state between tests instead of inside one test.
For my needs a better approach would be a "base_data" fixture which is
loaded for all tests, and a test that dirties the base data would be
responsible for cleaning up and reloading the fixture. One could
already implement this using the syncdb signal, as this is basically
what is done for permissions and contenttypes. The use case for me
would be mass-loading external data (medical ICD codes for example)
before testing. Reloading such codes for each test case is
horrendously slow.

I would like to get feedback on the suggested API
(settings.TRACK_TEST_STATE, --detect-state-leaks flag for manage.py
test), and if we want something like this at all in Django. This could
work as external test class, too, if we added some of the needed hooks
to Django. This would of course mean the benefits would not be there
for Django core testing. The ability to restrict flushing to subset of
models and the new signal would at least need to be in core.

 - Anssi

Raw test data:
----------------------------
Patched, Postgres, Selenium:
Ran 4850 tests in 520.710s

OK (skipped=145, expected failures=4)
Destroying test database for alias 'default'...
Destroying test database for alias 'other'...

real    9m50.534s
user    4m3.283s
sys 0m19.497s

---------------------------
Master, Postgres, Selenium:
Ran 4848 tests in 1932.700s

OK (skipped=145, expected failures=4)
Destroying test database for alias 'default'...
Destroying test database for alias 'other'...

real    33m3.587s
user    17m55.659s
sys 0m50.519s

-------------------------------
Patched, Postgres, no selenium:
Ran 4850 tests in 389.648s

OK (skipped=145, expected failures=4)
Destroying test database for alias 'default'...
Destroying test database for alias 'other'...

real    7m14.441s
user    4m4.699s
sys 0m19.853s

-----------------------------
Patched, SQLite, no selenium:
Ran 4850 tests in 176.807s

OK (skipped=173, expected failures=4)
Destroying test database for alias 'default'...
Destroying test database for alias 'other'...

real    3m6.210s
user    2m21.041s
sys 0m12.521s

----------------------------
Master, SQLite, no selenium:
Ran 4848 tests in 444.018s

OK (skipped=173, expected failures=4)
Destroying test database for alias 'default'...
Destroying test database for alias 'other'...

real    7m34.566s
user    6m45.165s
sys 0m16.777s

-- 
You received this message because you are subscribed to the Google Groups 
"Django developers" group.
To post to this group, send email to django-developers@googlegroups.com.
To unsubscribe from this group, send email to 
django-developers+unsubscr...@googlegroups.com.
For more options, visit this group at 
http://groups.google.com/group/django-developers?hl=en.

Reply via email to