├── django_slowtests ├── models.py ├── tests │ ├── models.py │ ├── __init__.py │ ├── urls.py │ ├── fake.py │ └── tests.py ├── __init__.py └── testrunner.py ├── _examples ├── django1.10 │ ├── polls │ │ ├── __init__.py │ │ ├── migrations │ │ │ ├── __init__.py │ │ │ ├── 0002_auto_20170316_1720.py │ │ │ └── 0001_initial.py │ │ ├── static │ │ │ ├── images │ │ │ │ └── background.png │ │ │ └── polls │ │ │ │ └── style.css │ │ ├── apps.py │ │ ├── templates │ │ │ ├── polls │ │ │ │ ├── results.html │ │ │ │ ├── index.html │ │ │ │ └── detail.html │ │ │ └── admin │ │ │ │ └── base_site.html │ │ ├── urls.py │ │ ├── admin.py │ │ ├── models.py │ │ ├── views.py │ │ └── tests.py │ ├── django_polls │ │ ├── __init__.py │ │ ├── wsgi.py │ │ ├── urls.py │ │ └── settings.py │ ├── requirements.txt │ ├── db.sqlite3 │ └── manage.py ├── django1.6 │ ├── polls │ │ ├── __init__.py │ │ ├── static │ │ │ └── polls │ │ │ │ └── style.css │ │ ├── templates │ │ │ └── polls │ │ │ │ ├── results.html │ │ │ │ ├── index.html │ │ │ │ └── detail.html │ │ ├── urls.py │ │ ├── admin.py │ │ ├── models.py │ │ ├── views.py │ │ └── tests.py │ ├── firstapp │ │ ├── __init__.py │ │ ├── urls.py │ │ ├── wsgi.py │ │ └── settings.py │ ├── requirements.txt │ ├── db.sqlite3 │ └── manage.py ├── django1.7 │ ├── polls │ │ ├── __init__.py │ │ ├── static │ │ │ └── polls │ │ │ │ └── style.css │ │ ├── templates │ │ │ └── polls │ │ │ │ ├── results.html │ │ │ │ ├── index.html │ │ │ │ └── detail.html │ │ ├── urls.py │ │ ├── admin.py │ │ ├── models.py │ │ ├── views.py │ │ └── tests.py │ ├── firstapp │ │ ├── __init__.py │ │ ├── urls.py │ │ ├── wsgi.py │ │ └── settings.py │ ├── requirements.txt │ ├── .DS_Store │ ├── db.sqlite3 │ └── manage.py ├── django1.8 │ ├── mysite │ │ ├── __init__.py │ │ ├── wsgi.py │ │ ├── urls.py │ │ └── settings.py │ ├── polls │ │ ├── __init__.py │ │ ├── static │ │ │ └── polls │ │ │ │ └── style.css │ │ ├── templates │ │ │ └── polls │ │ │ │ ├── results.html │ │ │ │ ├── index.html │ │ │ │ └── detail.html │ │ ├── urls.py │ │ ├── admin.py │ │ ├── models.py │ │ ├── views.py │ │ └── tests.py │ ├── requirements.txt │ ├── db.sqlite3 │ └── manage.py └── django1.9 │ ├── mysite │ ├── __init__.py │ ├── wsgi.py │ ├── urls.py │ └── settings.py │ ├── polls │ ├── __init__.py │ ├── apps.py │ ├── admin.py │ ├── templates │ │ └── polls │ │ │ ├── detail.html │ │ │ └── index.html │ ├── urls.py │ ├── models.py │ ├── views.py │ └── tests.py │ ├── requirements.txt │ ├── db.sqlite3 │ └── manage.py ├── AUTHORS ├── .coveragerc ├── MANIFEST.in ├── docs ├── index.rst ├── installation.rst ├── conf.py └── Makefile ├── Makefile ├── tox.ini ├── bumpr.rc ├── .gitignore ├── CHANGELOG.rst ├── LICENSE ├── runtests.py ├── .travis.yml ├── setup.py ├── README.rst └── CONTRIBUTING.md /django_slowtests/models.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /django_slowtests/tests/models.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /_examples/django1.10/polls/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /_examples/django1.6/polls/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /_examples/django1.7/polls/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /_examples/django1.8/mysite/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /_examples/django1.8/polls/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /_examples/django1.9/mysite/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /_examples/django1.9/polls/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /django_slowtests/tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /_examples/django1.6/firstapp/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /_examples/django1.7/firstapp/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /_examples/django1.10/django_polls/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /_examples/django1.6/polls/static/polls/style.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /_examples/django1.7/polls/static/polls/style.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /_examples/django1.8/polls/static/polls/style.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /_examples/django1.10/polls/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /_examples/django1.10/requirements.txt: -------------------------------------------------------------------------------- 1 | Django==1.10.6 2 | -------------------------------------------------------------------------------- /django_slowtests/tests/urls.py: -------------------------------------------------------------------------------- 1 | 2 | urlpatterns = [] 3 | -------------------------------------------------------------------------------- /_examples/django1.9/requirements.txt: -------------------------------------------------------------------------------- 1 | Django==1.9 2 | django-slowtests 3 | -------------------------------------------------------------------------------- /_examples/django1.6/requirements.txt: -------------------------------------------------------------------------------- 1 | Django==1.6.11 2 | django-slowtests 3 | -------------------------------------------------------------------------------- /_examples/django1.7/requirements.txt: -------------------------------------------------------------------------------- 1 | Django==1.7.11 2 | django-slowtests 3 | -------------------------------------------------------------------------------- /_examples/django1.8/requirements.txt: -------------------------------------------------------------------------------- 1 | Django==1.8.7 2 | django-slowtests 3 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | Michael Herman 2 | Hugo Defrance 3 | -------------------------------------------------------------------------------- /.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | source = django_slowtests 3 | omit = django_slowtests/tests/* 4 | branch = 1 5 | -------------------------------------------------------------------------------- /_examples/django1.6/db.sqlite3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iheanyi/django-slow-tests/master/_examples/django1.6/db.sqlite3 -------------------------------------------------------------------------------- /_examples/django1.7/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iheanyi/django-slow-tests/master/_examples/django1.7/.DS_Store -------------------------------------------------------------------------------- /_examples/django1.7/db.sqlite3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iheanyi/django-slow-tests/master/_examples/django1.7/db.sqlite3 -------------------------------------------------------------------------------- /_examples/django1.8/db.sqlite3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iheanyi/django-slow-tests/master/_examples/django1.8/db.sqlite3 -------------------------------------------------------------------------------- /_examples/django1.9/db.sqlite3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iheanyi/django-slow-tests/master/_examples/django1.9/db.sqlite3 -------------------------------------------------------------------------------- /_examples/django1.10/db.sqlite3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iheanyi/django-slow-tests/master/_examples/django1.10/db.sqlite3 -------------------------------------------------------------------------------- /_examples/django1.9/polls/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class PollsConfig(AppConfig): 5 | name = 'polls' 6 | -------------------------------------------------------------------------------- /_examples/django1.9/polls/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | from .models import Question 4 | 5 | admin.site.register(Question) 6 | -------------------------------------------------------------------------------- /_examples/django1.10/polls/static/images/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iheanyi/django-slow-tests/master/_examples/django1.10/polls/static/images/background.png -------------------------------------------------------------------------------- /_examples/django1.10/polls/static/polls/style.css: -------------------------------------------------------------------------------- 1 | li a { 2 | color: green; 3 | } 4 | body { 5 | background: white url("images/background.png") no-repeat right bottom; 6 | } -------------------------------------------------------------------------------- /_examples/django1.10/polls/apps.py: -------------------------------------------------------------------------------- 1 | from __future__ import unicode_literals 2 | 3 | from django.apps import AppConfig 4 | 5 | 6 | class PollsConfig(AppConfig): 7 | name = 'polls' 8 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include AUTHORS 2 | include LICENSE 3 | include README.rst 4 | include CHANGELOG.rst 5 | recursive-include django_slowtests/static * 6 | recursive-include django_slowtests/templates * 7 | -------------------------------------------------------------------------------- /_examples/django1.9/polls/templates/polls/detail.html: -------------------------------------------------------------------------------- 1 |

{{ question.question_text }}

2 | 7 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | ==================== 2 | django-slowtests 3 | ==================== 4 | 5 | locate your slowest tests 6 | 7 | 8 | Contents 9 | ======== 10 | 11 | .. toctree:: 12 | :maxdepth: 2 13 | 14 | installation 15 | changelog 16 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | all: init docs test 2 | 3 | init: 4 | python setup.py develop 5 | pip install tox coverage Sphinx 6 | 7 | test: 8 | coverage erase 9 | tox 10 | coverage html 11 | 12 | docs: documentation 13 | 14 | documentation: 15 | python setup.py build_sphinx -------------------------------------------------------------------------------- /_examples/django1.6/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import sys 4 | 5 | if __name__ == "__main__": 6 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "firstapp.settings") 7 | 8 | from django.core.management import execute_from_command_line 9 | 10 | execute_from_command_line(sys.argv) 11 | -------------------------------------------------------------------------------- /_examples/django1.7/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import sys 4 | 5 | if __name__ == "__main__": 6 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "firstapp.settings") 7 | 8 | from django.core.management import execute_from_command_line 9 | 10 | execute_from_command_line(sys.argv) 11 | -------------------------------------------------------------------------------- /_examples/django1.8/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import sys 4 | 5 | if __name__ == "__main__": 6 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "mysite.settings") 7 | 8 | from django.core.management import execute_from_command_line 9 | 10 | execute_from_command_line(sys.argv) 11 | -------------------------------------------------------------------------------- /_examples/django1.9/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import sys 4 | 5 | if __name__ == "__main__": 6 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "mysite.settings") 7 | 8 | from django.core.management import execute_from_command_line 9 | 10 | execute_from_command_line(sys.argv) 11 | -------------------------------------------------------------------------------- /_examples/django1.6/firstapp/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls import patterns, include, url 2 | 3 | from django.contrib import admin 4 | admin.autodiscover() 5 | 6 | urlpatterns = patterns( 7 | '', 8 | url(r'polls/', include('polls.urls', namespace="polls")), 9 | url(r'^admin/', include(admin.site.urls)), 10 | ) 11 | -------------------------------------------------------------------------------- /_examples/django1.7/firstapp/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls import patterns, include, url 2 | 3 | from django.contrib import admin 4 | admin.autodiscover() 5 | 6 | urlpatterns = patterns( 7 | '', 8 | url(r'polls/', include('polls.urls', namespace="polls")), 9 | url(r'^admin/', include(admin.site.urls)), 10 | ) 11 | -------------------------------------------------------------------------------- /_examples/django1.10/polls/templates/polls/results.html: -------------------------------------------------------------------------------- 1 |

{{ question.question_text }}

2 | 3 | 8 | 9 | Vote again? -------------------------------------------------------------------------------- /_examples/django1.8/polls/templates/polls/results.html: -------------------------------------------------------------------------------- 1 |

{{ question.question_text }}

2 | 3 | 8 | 9 | Vote again? -------------------------------------------------------------------------------- /_examples/django1.9/polls/templates/polls/index.html: -------------------------------------------------------------------------------- 1 | {% if latest_questions_list %} 2 | 7 | {% else %} 8 |

No polls are available.

9 | {% endif %} 10 | -------------------------------------------------------------------------------- /django_slowtests/__init__.py: -------------------------------------------------------------------------------- 1 | try: 2 | from django.core.exceptions import ImproperlyConfigured 3 | except ImportError: 4 | class ImproperlyConfigured(Exception): 5 | pass 6 | 7 | try: 8 | from testrunner import DiscoverSlowestTestsRunner # NOQA 9 | except (ImportError, ImproperlyConfigured): 10 | pass 11 | 12 | __version__ = "1.1.1.dev" 13 | -------------------------------------------------------------------------------- /_examples/django1.10/polls/templates/admin/base_site.html: -------------------------------------------------------------------------------- 1 | {% extends "admin/base.html" %} 2 | 3 | {% block title %}{{ title }} | {{ site_title|default:_('Django site admin') }}{% endblock %} 4 | 5 | {% block branding %} 6 |

Polls Administration

7 | {% endblock %} 8 | 9 | {% block nav-global %}{% endblock %} 10 | -------------------------------------------------------------------------------- /_examples/django1.6/polls/templates/polls/results.html: -------------------------------------------------------------------------------- 1 |

{{ poll.question }}

2 | 3 | 4 | 5 | {% for choice in poll.choice_set.all %} 6 | 7 | {% endfor %} 8 |
ChoiceVotes
{{choice.choice_text}}{{choice.votes}} vote{{ choice.votes|pluralize }}
9 | 10 | Vote again? 11 | -------------------------------------------------------------------------------- /_examples/django1.7/polls/templates/polls/results.html: -------------------------------------------------------------------------------- 1 |

{{ poll.question }}

2 | 3 | 4 | 5 | {% for choice in poll.choice_set.all %} 6 | 7 | {% endfor %} 8 |
ChoiceVotes
{{choice.choice_text}}{{choice.votes}} vote{{ choice.votes|pluralize }}
9 | 10 | Vote again? 11 | -------------------------------------------------------------------------------- /_examples/django1.9/polls/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls import url 2 | from . import views 3 | 4 | app_name = 'polls' 5 | urlpatterns = [ 6 | url(r'^$', views.index, name='index'), 7 | url(r'^(?P[0-9]+)/$', views.detail, name='detail'), 8 | url(r'^(?P[0-9]+)/results/$', views.results, name='results'), 9 | url(r'^(?P[0-9]+)/vote/$', views.vote, name='vote') 10 | ] 11 | -------------------------------------------------------------------------------- /_examples/django1.8/polls/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls import url 2 | 3 | from . import views 4 | 5 | urlpatterns = [ 6 | url(r'^$', views.IndexView.as_view(), name='index'), 7 | url(r'^(?P[0-9]+)/$', views.DetailView.as_view(), name='detail'), 8 | url(r'^(?P[0-9]+)/results/$', views.ResultsView.as_view(), name='results'), 9 | url(r'^(?P[0-9]+)/vote/$', views.vote, name='vote'), 10 | ] 11 | -------------------------------------------------------------------------------- /_examples/django1.6/polls/templates/polls/index.html: -------------------------------------------------------------------------------- 1 | {% load staticfiles %} 2 | 3 | 4 | {% if latest_poll_list %} 5 | 10 | {% else %} 11 |

No polls are available.

12 | {% endif %} -------------------------------------------------------------------------------- /_examples/django1.7/polls/templates/polls/index.html: -------------------------------------------------------------------------------- 1 | {% load staticfiles %} 2 | 3 | 4 | {% if latest_poll_list %} 5 | 10 | {% else %} 11 |

No polls are available.

12 | {% endif %} -------------------------------------------------------------------------------- /_examples/django1.10/polls/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls import url 2 | 3 | from . import views 4 | 5 | app_name = 'polls' 6 | urlpatterns = [ 7 | url(r'^$', views.IndexView.as_view(), name='index'), 8 | url(r'^(?P[0-9]+)/$', views.DetailView.as_view(), name='detail'), 9 | url(r'^(?P[0-9]+)/results/$', views.ResultsView.as_view(), name='results'), 10 | url(r'^(?P[0-9]+)/vote/$', views.vote, name='vote'), 11 | ] -------------------------------------------------------------------------------- /_examples/django1.6/polls/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls import patterns, url 2 | 3 | from polls import views 4 | 5 | urlpatterns = patterns( 6 | '', 7 | url(r'^$', views.IndexView.as_view(), name='index'), 8 | url(r'^(?P\d+)/$', views.DetailView.as_view(), name='detail'), 9 | url(r'^(?P\d+)/results/$', views.ResultsView.as_view(), name='results'), 10 | url(r'^(?P\d+)/vote/$', views.vote, name='vote'), 11 | ) 12 | -------------------------------------------------------------------------------- /_examples/django1.7/polls/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls import patterns, url 2 | 3 | from polls import views 4 | 5 | urlpatterns = patterns( 6 | '', 7 | url(r'^$', views.IndexView.as_view(), name='index'), 8 | url(r'^(?P\d+)/$', views.DetailView.as_view(), name='detail'), 9 | url(r'^(?P\d+)/results/$', views.ResultsView.as_view(), name='results'), 10 | url(r'^(?P\d+)/vote/$', views.vote, name='vote'), 11 | ) 12 | -------------------------------------------------------------------------------- /docs/installation.rst: -------------------------------------------------------------------------------- 1 | .. _installation: 2 | 3 | ============ 4 | Installation 5 | ============ 6 | 7 | * Install the development version:: 8 | 9 | pip install django-slowtests 10 | 11 | * Add the following setting:: 12 | 13 | # Custom test runner 14 | TEST_RUNNER = 'django_slowtests.testrunner.DiscoverSlowestTestsRunner' 15 | NUM_SLOW_TESTS = 10 16 | 17 | 18 | 19 | .. _dependencies: 20 | 21 | Dependencies 22 | ============ 23 | 24 | -------------------------------------------------------------------------------- /_examples/django1.10/polls/templates/polls/index.html: -------------------------------------------------------------------------------- 1 | {% load static %} 2 | 3 | 4 | 5 | {% if latest_question_list %} 6 | 11 | {% else %} 12 |

No polls are available.

13 | {% endif %} -------------------------------------------------------------------------------- /_examples/django1.8/polls/templates/polls/index.html: -------------------------------------------------------------------------------- 1 | {% load staticfiles %} 2 | 3 | 4 | {% if latest_question_list %} 5 | 10 | {% else %} 11 |

No polls are available.

12 | {% endif %} -------------------------------------------------------------------------------- /_examples/django1.6/firstapp/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for firstapp project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/1.6/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "firstapp.settings") 12 | 13 | from django.core.wsgi import get_wsgi_application 14 | application = get_wsgi_application() 15 | -------------------------------------------------------------------------------- /_examples/django1.7/firstapp/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for mysite project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/1.7/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "firstapp.settings") 12 | 13 | from django.core.wsgi import get_wsgi_application 14 | application = get_wsgi_application() 15 | -------------------------------------------------------------------------------- /_examples/django1.8/mysite/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for mysite project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/1.8/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "mysite.settings") 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /_examples/django1.9/mysite/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for mysite project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/1.9/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "mysite.settings") 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /_examples/django1.10/django_polls/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for django_polls project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/1.10/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "django_polls.settings") 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = py{27}-dj{1.6,1.7,1.8,1.9,1.10,1.11}, py{36}-dj{1.8,1.9,1.10,1.11,2.0,2.1}, py{37}-dj{1.8,1.9,1.10,1.11,2.0,2.1} 3 | 4 | [testenv] 5 | deps = 6 | coverage == 4.0 7 | dj1.6: Django>=1.6,<1.7 8 | dj1.7: Django>=1.7,<1.8 9 | dj1.8: Django>=1.8,<1.9 10 | dj1.9: Django>=1.9,<1.10 11 | dj1.10: Django>=1.10,<1.11 12 | dj1.11: Django>=1.11,<1.12 13 | dj2.0: Django>=2.0,<2.1 14 | dj2.1: Django>=2.1,<2.2 15 | freezegun>=0.1.8 16 | commands = coverage run setup.py test 17 | -------------------------------------------------------------------------------- /bumpr.rc: -------------------------------------------------------------------------------- 1 | [bumpr] 2 | file = django_slowtests/__init__.py 3 | vcs = git 4 | tests = tox 5 | publish = python setup.py sdist register upload 6 | clean = 7 | python setup.py clean 8 | rm -rf *egg-info build dist 9 | files = README.rst 10 | 11 | [bump] 12 | unsuffix = true 13 | message = Bump version {version} 14 | 15 | [prepare] 16 | suffix = dev 17 | message = Prepare version {version} for next development cycle 18 | 19 | [changelog] 20 | file = CHANGELOG.rst 21 | bump = {version} ({date:%Y-%m-%d}) 22 | prepare = In development 23 | -------------------------------------------------------------------------------- /_examples/django1.6/polls/templates/polls/detail.html: -------------------------------------------------------------------------------- 1 |

{{ poll.question }}

2 | 3 | {% if error_message %}

{{ error_message }}

{% endif %} 4 | 5 |
6 | {% csrf_token %} 7 | {% for choice in poll.choice_set.all %} 8 | 9 |
10 | {% endfor %} 11 | 12 |
13 | -------------------------------------------------------------------------------- /_examples/django1.7/polls/templates/polls/detail.html: -------------------------------------------------------------------------------- 1 |

{{ poll.question }}

2 | 3 | {% if error_message %}

{{ error_message }}

{% endif %} 4 | 5 |
6 | {% csrf_token %} 7 | {% for choice in poll.choice_set.all %} 8 | 9 |
10 | {% endfor %} 11 | 12 |
13 | -------------------------------------------------------------------------------- /_examples/django1.10/polls/templates/polls/detail.html: -------------------------------------------------------------------------------- 1 |

{{ question.question_text }}

2 | 3 | {% if error_message %}

{{ error_message }}

{% endif %} 4 | 5 |
6 | {% csrf_token %} 7 | {% for choice in question.choice_set.all %} 8 | 9 |
10 | {% endfor %} 11 | 12 |
-------------------------------------------------------------------------------- /_examples/django1.8/polls/templates/polls/detail.html: -------------------------------------------------------------------------------- 1 |

{{ question.question_text }}

2 | 3 | {% if error_message %}

{{ error_message }}

{% endif %} 4 | 5 |
6 | {% csrf_token %} 7 | {% for choice in question.choice_set.all %} 8 | 9 |
10 | {% endfor %} 11 | 12 |
-------------------------------------------------------------------------------- /_examples/django1.10/polls/migrations/0002_auto_20170316_1720.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10.6 on 2017-03-16 15:20 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('polls', '0001_initial'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AlterField( 16 | model_name='question', 17 | name='pub_date', 18 | field=models.DateTimeField(verbose_name='date published'), 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /_examples/django1.6/polls/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from polls.models import Poll, Choice 3 | 4 | 5 | class ChoiceInline(admin.TabularInline): 6 | model = Choice 7 | extra = 3 8 | 9 | 10 | class PollAdmin(admin.ModelAdmin): 11 | list_display = ["question", "pub_date", "was_published_recently"] 12 | list_filter = ['pub_date'] 13 | search_fields = ['question'] 14 | fieldsets = [ 15 | (None, {'fields': ['question']}), 16 | ('Date information', {'fields': ['pub_date'], 'classes': ['collapse']}), 17 | ] 18 | inlines = [ChoiceInline] 19 | 20 | admin.site.register(Poll, PollAdmin) 21 | -------------------------------------------------------------------------------- /_examples/django1.7/polls/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from polls.models import Poll, Choice 3 | 4 | 5 | class ChoiceInline(admin.TabularInline): 6 | model = Choice 7 | extra = 3 8 | 9 | 10 | class PollAdmin(admin.ModelAdmin): 11 | list_display = ["question", "pub_date", "was_published_recently"] 12 | list_filter = ['pub_date'] 13 | search_fields = ['question'] 14 | fieldsets = [ 15 | (None, {'fields': ['question']}), 16 | ('Date information', {'fields': ['pub_date'], 'classes': ['collapse']}), 17 | ] 18 | inlines = [ChoiceInline] 19 | 20 | admin.site.register(Poll, PollAdmin) 21 | -------------------------------------------------------------------------------- /_examples/django1.8/polls/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | from .models import Choice, Question 4 | 5 | 6 | class ChoiceInline(admin.TabularInline): 7 | model = Choice 8 | extra = 3 9 | 10 | 11 | class QuestionAdmin(admin.ModelAdmin): 12 | fieldsets = [ 13 | (None, {'fields': ['question_text']}), 14 | ('Date information', {'fields': ['pub_date'], 'classes': ['collapse']}), 15 | ] 16 | inlines = [ChoiceInline] 17 | list_display = ('question_text', 'pub_date', 'was_published_recently') 18 | list_filter = ['pub_date'] 19 | search_fields = ['question_text'] 20 | 21 | admin.site.register(Question, QuestionAdmin) 22 | -------------------------------------------------------------------------------- /_examples/django1.10/polls/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | from .models import Choice, Question 4 | 5 | 6 | class ChoiceInline(admin.TabularInline): 7 | model = Choice 8 | extra = 3 9 | 10 | 11 | class QuestionAdmin(admin.ModelAdmin): 12 | fieldsets = [ 13 | (None, {'fields': ['question_text']}), 14 | ('Date information', {'fields': ['pub_date'], 'classes': ['collapse']}), 15 | ] 16 | inlines = [ChoiceInline] 17 | list_display = ('question_text', 'pub_date', 'was_published_recently') 18 | list_filter = ['pub_date'] 19 | search_fields = ['question_text'] 20 | 21 | admin.site.register(Question, QuestionAdmin) -------------------------------------------------------------------------------- /_examples/django1.9/polls/models.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | 3 | from django.db import models 4 | from django.utils import timezone 5 | 6 | 7 | class Question(models.Model): 8 | question_text = models.CharField(max_length=200) 9 | pub_date = models.DateTimeField('date published') 10 | 11 | def was_published_recently(self): 12 | return self.pub_date >= timezone.now() - datetime.timedelta(days=1) 13 | 14 | def __str__(self): 15 | return self.question_text 16 | 17 | 18 | class Choice(models.Model): 19 | question = models.ForeignKey(Question, on_delete=models.CASCADE) 20 | choice_text = models.CharField(max_length=200) 21 | votes = models.IntegerField(default=0) 22 | 23 | def __str__(self): 24 | return self.choice_text 25 | -------------------------------------------------------------------------------- /_examples/django1.8/polls/models.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | 3 | from django.db import models 4 | from django.utils import timezone 5 | 6 | 7 | class Question(models.Model): 8 | question_text = models.CharField(max_length=200) 9 | pub_date = models.DateTimeField('date published') 10 | 11 | def __str__(self): 12 | return self.question_text 13 | 14 | def was_published_recently(self): 15 | now = timezone.now() 16 | return now - datetime.timedelta(days=1) <= self.pub_date <= now 17 | 18 | 19 | class Choice(models.Model): 20 | question = models.ForeignKey(Question) 21 | choice_text = models.CharField(max_length=200) 22 | votes = models.IntegerField(default=0) 23 | 24 | def __str__(self): 25 | return self.choice_text 26 | -------------------------------------------------------------------------------- /_examples/django1.9/polls/views.py: -------------------------------------------------------------------------------- 1 | from django.http import HttpResponse 2 | from django.shortcuts import render, get_object_or_404 3 | 4 | from .models import Question 5 | 6 | 7 | def index(request): 8 | latest_questions_list = Question.objects.order_by('-pub_date')[:5] 9 | context = {'latest_questions_list': latest_questions_list} 10 | return render(request, 'polls/index.html', context) 11 | 12 | 13 | def detail(request, question_id): 14 | question = get_object_or_404(Question, pk=question_id) 15 | return render(request, 'polls/detail.html', {'question': question}) 16 | 17 | 18 | def results(request, question_id): 19 | response = "You're looking at the results of question %s." 20 | return HttpResponse(response % question_id) 21 | 22 | 23 | def vote(request, question_id): 24 | return HttpResponse("You're voting on question %s." % question_id) 25 | -------------------------------------------------------------------------------- /django_slowtests/tests/fake.py: -------------------------------------------------------------------------------- 1 | import time 2 | from django.test import TestCase 3 | from freezegun import freeze_time 4 | 5 | 6 | class FakeTestCase(TestCase): 7 | _setupClassRan = False 8 | 9 | @classmethod 10 | def setUpClass(cls): 11 | super(FakeTestCase, cls).setUpClass() 12 | cls._setupClassRan = True 13 | 14 | def test_slow_thing(self): 15 | time.sleep(1) 16 | 17 | def test_setup_class_was_run(self): 18 | self.assertTrue(self._setupClassRan) 19 | 20 | 21 | @freeze_time('2016-02-03 12:34:56') 22 | class FakeFrozenInPastTestCase(TestCase): 23 | 24 | def test_this_should_not_have_a_negative_duration(self): 25 | self.assertTrue(True) 26 | 27 | 28 | @freeze_time('3017-02-03 12:34:56') 29 | class FakeFrozenInFutureTestCase(TestCase): 30 | 31 | def test_this_should_not_have_very_long_duration(self): 32 | self.assertTrue(True) 33 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | from __future__ import unicode_literals 2 | 3 | import os 4 | import sys 5 | 6 | 7 | extensions = [] 8 | templates_path = [] 9 | source_suffix = ".rst" 10 | master_doc = "index" 11 | project = "django-slowtests" 12 | copyright_holder = "Michael Herman" 13 | copyright = "2015, {0}".format(copyright_holder) 14 | exclude_patterns = ["_build"] 15 | pygments_style = "sphinx" 16 | html_theme = "default" 17 | htmlhelp_basename = "{0}doc".format(project) 18 | latex_documents = [( 19 | "index", 20 | "{0}.tex".format(project), 21 | "{0} Documentation".format(project), 22 | "Pinax", 23 | "manual" 24 | ), ] 25 | man_pages = [( 26 | "index", 27 | project, 28 | "{0} Documentation".format(project), 29 | ["Pinax"], 30 | 1 31 | ), ] 32 | 33 | sys.path.insert(0, os.pardir) 34 | m = __import__("django_slowtests") 35 | 36 | version = m.__version__ 37 | release = version 38 | -------------------------------------------------------------------------------- /_examples/django1.10/polls/models.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | 3 | from django.db import models 4 | from django.utils import timezone 5 | 6 | 7 | 8 | class Question(models.Model): 9 | question_text = models.CharField(max_length=200) 10 | pub_date = models.DateTimeField('date published') 11 | 12 | def __str__(self): 13 | return self.question_text 14 | 15 | def was_published_recently(self): 16 | now = timezone.now() 17 | return now - datetime.timedelta(days=1) <= self.pub_date <= now 18 | was_published_recently.admin_order_field = 'pub_date' 19 | was_published_recently.boolean = True 20 | was_published_recently.short_description = 'Published recently?' 21 | 22 | 23 | class Choice(models.Model): 24 | question = models.ForeignKey(Question, on_delete=models.CASCADE) 25 | choice_text = models.CharField(max_length=200) 26 | votes = models.IntegerField(default=0) -------------------------------------------------------------------------------- /_examples/django1.10/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import sys 4 | 5 | if __name__ == "__main__": 6 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "django_polls.settings") 7 | try: 8 | from django.core.management import execute_from_command_line 9 | except ImportError: 10 | # The above import may fail for some other reason. Ensure that the 11 | # issue is really that Django is missing to avoid masking other 12 | # exceptions on Python 2. 13 | try: 14 | import django 15 | except ImportError: 16 | raise ImportError( 17 | "Couldn't import Django. Are you sure it's installed and " 18 | "available on your PYTHONPATH environment variable? Did you " 19 | "forget to activate a virtual environment?" 20 | ) 21 | raise 22 | execute_from_command_line(sys.argv) 23 | -------------------------------------------------------------------------------- /_examples/django1.10/django_polls/urls.py: -------------------------------------------------------------------------------- 1 | """django_polls URL Configuration 2 | 3 | The `urlpatterns` list routes URLs to views. For more information please see: 4 | https://docs.djangoproject.com/en/1.10/topics/http/urls/ 5 | Examples: 6 | Function views 7 | 1. Add an import: from my_app import views 8 | 2. Add a URL to urlpatterns: url(r'^$', views.home, name='home') 9 | Class-based views 10 | 1. Add an import: from other_app.views import Home 11 | 2. Add a URL to urlpatterns: url(r'^$', Home.as_view(), name='home') 12 | Including another URLconf 13 | 1. Import the include() function: from django.conf.urls import url, include 14 | 2. Add a URL to urlpatterns: url(r'^blog/', include('blog.urls')) 15 | """ 16 | from django.conf.urls import include, url 17 | from django.contrib import admin 18 | 19 | urlpatterns = [ 20 | url(r'^polls/', include('polls.urls')), 21 | url(r'^admin/', admin.site.urls), 22 | ] -------------------------------------------------------------------------------- /_examples/django1.8/mysite/urls.py: -------------------------------------------------------------------------------- 1 | """mysite URL Configuration 2 | 3 | The `urlpatterns` list routes URLs to views. For more information please see: 4 | https://docs.djangoproject.com/en/1.8/topics/http/urls/ 5 | Examples: 6 | Function views 7 | 1. Add an import: from my_app import views 8 | 2. Add a URL to urlpatterns: url(r'^$', views.home, name='home') 9 | Class-based views 10 | 1. Add an import: from other_app.views import Home 11 | 2. Add a URL to urlpatterns: url(r'^$', Home.as_view(), name='home') 12 | Including another URLconf 13 | 1. Add an import: from blog import urls as blog_urls 14 | 2. Add a URL to urlpatterns: url(r'^blog/', include(blog_urls)) 15 | """ 16 | from django.conf.urls import include, url 17 | from django.contrib import admin 18 | 19 | urlpatterns = [ 20 | url(r'^polls/', include('polls.urls', namespace="polls")), 21 | url(r'^admin/', include(admin.site.urls)), 22 | ] 23 | -------------------------------------------------------------------------------- /_examples/django1.7/polls/models.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | from django.db import models 3 | from django.utils import timezone 4 | 5 | 6 | class Poll(models.Model): 7 | question = models.CharField(max_length=200) 8 | pub_date = models.DateTimeField('date published') 9 | 10 | def __unicode__(self): 11 | return self.question 12 | 13 | def was_published_recently(self): 14 | now = timezone.now() 15 | return now - datetime.timedelta(days=1) <= self.pub_date <= now 16 | 17 | was_published_recently.admin_order_field = 'pub_date' 18 | was_published_recently.boolean = True 19 | was_published_recently.short_description = 'Published recently?' 20 | 21 | 22 | class Choice(models.Model): 23 | poll = models.ForeignKey(Poll) 24 | choice_text = models.CharField(max_length=200) 25 | votes = models.IntegerField(default=0) 26 | 27 | def __unicode__(self): 28 | return self.choice_text 29 | -------------------------------------------------------------------------------- /_examples/django1.6/polls/models.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | from django.db import models 3 | from django.utils import timezone 4 | # dize was here 5 | 6 | class Poll(models.Model): 7 | question = models.CharField(max_length=200) 8 | pub_date = models.DateTimeField('date published') 9 | 10 | def __unicode__(self): 11 | return self.question 12 | 13 | def was_published_recently(self): 14 | now = timezone.now() 15 | return now - datetime.timedelta(days=1) <= self.pub_date <= now 16 | 17 | was_published_recently.admin_order_field = 'pub_date' 18 | was_published_recently.boolean = True 19 | was_published_recently.short_description = 'Published recently?' 20 | 21 | 22 | class Choice(models.Model): 23 | poll = models.ForeignKey(Poll) 24 | choice_text = models.CharField(max_length=200) 25 | votes = models.IntegerField(default=0) 26 | 27 | def __unicode__(self): 28 | return self.choice_text 29 | -------------------------------------------------------------------------------- /_examples/django1.9/mysite/urls.py: -------------------------------------------------------------------------------- 1 | """mysite URL Configuration 2 | 3 | The `urlpatterns` list routes URLs to views. For more information please see: 4 | https://docs.djangoproject.com/en/1.9/topics/http/urls/ 5 | Examples: 6 | Function views 7 | 1. Add an import: from my_app import views 8 | 2. Add a URL to urlpatterns: url(r'^$', views.home, name='home') 9 | Class-based views 10 | 1. Add an import: from other_app.views import Home 11 | 2. Add a URL to urlpatterns: url(r'^$', Home.as_view(), name='home') 12 | Including another URLconf 13 | 1. Add an import: from blog import urls as blog_urls 14 | 2. Import the include() function: from django.conf.urls import url, include 15 | 3. Add a URL to urlpatterns: url(r'^blog/', include(blog_urls)) 16 | """ 17 | from django.conf.urls import url, include 18 | from django.contrib import admin 19 | 20 | urlpatterns = [ 21 | url(r'^polls/', include('polls.urls')), 22 | url(r'^admin/', admin.site.urls), 23 | ] 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | venv/ 13 | build/ 14 | develop-eggs/ 15 | dist/ 16 | downloads/ 17 | eggs/ 18 | .eggs/ 19 | lib/ 20 | lib64/ 21 | parts/ 22 | sdist/ 23 | var/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *,cover 47 | 48 | # Translations 49 | *.mo 50 | *.pot 51 | 52 | # Django stuff: 53 | *.log 54 | 55 | # Sphinx documentation 56 | docs/_build/ 57 | 58 | # PyBuilder 59 | target/ 60 | 61 | .sqlite3 62 | update.txt 63 | .pypirc 64 | -------------------------------------------------------------------------------- /CHANGELOG.rst: -------------------------------------------------------------------------------- 1 | Changelog 2 | ========= 3 | 4 | Current 5 | ------- 6 | - Nothing yet 7 | 8 | 1.1.1 (2019-02-12) 9 | - Return error code to shell in case of failed tests 10 | 11 | 1.1.0 (2019-02-07) 12 | - Added option to generate full tests reports by passing None to NUM_SLOW_TESTS 13 | 14 | 1.0.3 (2019-02-05) 15 | - Fixed cast error, leading to slow test ordering issues 16 | - Fixed test case 17 | 18 | 1.0.2 (2019-02-05) 19 | - Handle django not installed case to ease installation 20 | 21 | 1.0.1 (2019-02-05) 22 | - Fixed report printed to console 23 | 24 | 1.0.0 (2019-02-05) 25 | ------- 26 | - Dropped Django 1.5* support 27 | - Dropped Python 3.3* support 28 | - Added Django 1.11.* support 29 | - Fixed an issue preventing settings to be taken into account in some case (#24) 30 | - Added an option to generate a report on demand only using a command line 31 | parameter 32 | - Reports are now compatible with django tests' --parallel option 33 | - Added an option to generate a json file containing the report, instead of 34 | printing in to console. 35 | 36 | 0.5.1 (2019-02-04) 37 | ------------------ 38 | - Initial release at changelog creation. 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 - 2017 Michael Herman 4 | 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /_examples/django1.10/polls/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10 on 2016-08-19 07:30 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | import django.db.models.deletion 7 | 8 | 9 | class Migration(migrations.Migration): 10 | 11 | initial = True 12 | 13 | dependencies = [ 14 | ] 15 | 16 | operations = [ 17 | migrations.CreateModel( 18 | name='Choice', 19 | fields=[ 20 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 21 | ('choice_text', models.CharField(max_length=200)), 22 | ('votes', models.IntegerField(default=0)), 23 | ], 24 | ), 25 | migrations.CreateModel( 26 | name='Question', 27 | fields=[ 28 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 29 | ('question_text', models.CharField(max_length=200)), 30 | ('pub_date', models.DateTimeField(verbose_name=b'date published')), 31 | ], 32 | ), 33 | migrations.AddField( 34 | model_name='choice', 35 | name='question', 36 | field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='polls.Question'), 37 | ), 38 | ] 39 | -------------------------------------------------------------------------------- /_examples/django1.6/polls/views.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import render, get_object_or_404 2 | from django.http import HttpResponseRedirect 3 | from django.core.urlresolvers import reverse 4 | from django.views import generic 5 | from django.utils import timezone 6 | from polls.models import Poll 7 | 8 | 9 | class IndexView(generic.ListView): 10 | template_name = 'polls/index.html' 11 | context_object_name = 'latest_poll_list' 12 | 13 | def get_queryset(self): 14 | return Poll.objects.filter(pub_date__lte=timezone.now())\ 15 | .order_by('-pub_date')[:5] 16 | 17 | 18 | class DetailView(generic.DetailView): 19 | model = Poll 20 | template_name = 'polls/detail.html' 21 | 22 | def get_queryset(self): 23 | return Poll.objects.filter(pub_date__lte=timezone.now()) 24 | 25 | 26 | class ResultsView(generic.DetailView): 27 | model = Poll 28 | template_name = 'polls/results.html' 29 | 30 | 31 | def vote(request, poll_id): 32 | p = get_object_or_404(Poll, pk=poll_id) 33 | try: 34 | selected_choice = p.choice_set.get(pk=request.POST['choice']) 35 | except (KeyError, selected_choice.DoesNotExist): 36 | # Redisplay the poll voting form. 37 | return render(request, 'polls/detail.html', { 38 | 'poll': p, 39 | 'error_message': "You didn't select a choice.", 40 | }) 41 | else: 42 | selected_choice.votes += 1 43 | selected_choice.save() 44 | return HttpResponseRedirect(reverse('polls:results', args=(p.id,))) 45 | -------------------------------------------------------------------------------- /_examples/django1.7/polls/views.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import render, get_object_or_404 2 | from django.http import HttpResponseRedirect 3 | from django.core.urlresolvers import reverse 4 | from django.views import generic 5 | from django.utils import timezone 6 | from polls.models import Poll 7 | 8 | 9 | class IndexView(generic.ListView): 10 | template_name = 'polls/index.html' 11 | context_object_name = 'latest_poll_list' 12 | 13 | def get_queryset(self): 14 | return Poll.objects.filter(pub_date__lte=timezone.now())\ 15 | .order_by('-pub_date')[:5] 16 | 17 | 18 | class DetailView(generic.DetailView): 19 | model = Poll 20 | template_name = 'polls/detail.html' 21 | 22 | def get_queryset(self): 23 | return Poll.objects.filter(pub_date__lte=timezone.now()) 24 | 25 | 26 | class ResultsView(generic.DetailView): 27 | model = Poll 28 | template_name = 'polls/results.html' 29 | 30 | 31 | def vote(request, poll_id): 32 | p = get_object_or_404(Poll, pk=poll_id) 33 | try: 34 | selected_choice = p.choice_set.get(pk=request.POST['choice']) 35 | except (KeyError, selected_choice.DoesNotExist): 36 | # Redisplay the poll voting form. 37 | return render(request, 'polls/detail.html', { 38 | 'poll': p, 39 | 'error_message': "You didn't select a choice.", 40 | }) 41 | else: 42 | selected_choice.votes += 1 43 | selected_choice.save() 44 | return HttpResponseRedirect(reverse('polls:results', args=(p.id,))) 45 | -------------------------------------------------------------------------------- /runtests.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import sys 4 | 5 | import django 6 | 7 | from django.conf import settings 8 | 9 | 10 | DEFAULT_SETTINGS = dict( 11 | INSTALLED_APPS=[ 12 | "django.contrib.auth", 13 | "django.contrib.contenttypes", 14 | "django.contrib.sites", 15 | "django_slowtests", 16 | "django_slowtests.tests" 17 | ], 18 | DATABASES={ 19 | "default": { 20 | "ENGINE": "django.db.backends.sqlite3", 21 | "NAME": ":memory:", 22 | } 23 | }, 24 | SITE_ID=1, 25 | ROOT_URLCONF="django_slowtests.tests.urls", 26 | SECRET_KEY="notasecret", 27 | ) 28 | 29 | 30 | def runtests(*test_args): 31 | if not settings.configured: 32 | settings.configure(**DEFAULT_SETTINGS) 33 | 34 | # Compatibility with Django 1.7's stricter initialization 35 | if hasattr(django, "setup"): 36 | django.setup() 37 | 38 | parent = os.path.dirname(os.path.abspath(__file__)) 39 | sys.path.insert(0, parent) 40 | 41 | try: 42 | from django.test.runner import DiscoverRunner 43 | runner_class = DiscoverRunner 44 | test_args = ["django_slowtests.tests"] 45 | except ImportError: 46 | from django.test.simple import DjangoTestSuiteRunner 47 | runner_class = DjangoTestSuiteRunner 48 | test_args = ["tests"] 49 | 50 | failures = runner_class( 51 | verbosity=1, interactive=True, failfast=False).run_tests(test_args) 52 | sys.exit(failures) 53 | 54 | 55 | if __name__ == "__main__": 56 | runtests(*sys.argv[1:]) 57 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | # 3.7 available only on xenial with sudo as of 02062019 3 | dist: xenial 4 | sudo: true 5 | 6 | python: 7 | - "2.7" 8 | - "3.4" 9 | - "3.5" 10 | - "3.6" 11 | - "3.7" 12 | 13 | env: 14 | matrix: 15 | - DJANGO=Django==2.1.5 16 | - DJANGO=Django==2.0.10 17 | - DJANGO=Django==1.11.18 18 | - DJANGO=Django==1.10.8 19 | - DJANGO=Django==1.9.13 20 | - DJANGO=Django==1.8.19 21 | - DJANGO=Django==1.7.11 22 | - DJANGO=Django==1.6.11 23 | 24 | install: 25 | - pip install flake8 coverage coveralls $DJANGO freezegun 26 | - pip install -e . 27 | 28 | matrix: 29 | exclude: 30 | # python 3.7 supported by Django 1.11+ only 31 | - python: "3.7" 32 | env: DJANGO=Django==1.10.8 33 | - python: "3.7" 34 | env: DJANGO=Django==1.9.13 35 | - python: "3.7" 36 | env: DJANGO=Django==1.8.19 37 | - python: "3.7" 38 | env: DJANGO=Django==1.7.11 39 | - python: "3.7" 40 | env: DJANGO=Django==1.6.11 41 | - python: "3.6" 42 | env: DJANGO=Django==1.7.11 43 | - python: "3.6" 44 | env: DJANGO=Django==1.6.11 45 | - python: "3.5" 46 | env: DJANGO=Django==1.7.11 47 | - python: "3.5" 48 | env: DJANGO=Django==1.6.11 49 | - python: "3.4" 50 | env: DJANGO=Django==2.1.5 51 | - python: "2.7" 52 | env: DJANGO=Django==2.0.10 53 | - python: "2.7" 54 | env: DJANGO=Django==2.1.5 55 | 56 | script: 57 | - flake8 --max-line-length=100 --max-complexity=13 --statistics --benchmark django_slowtests 58 | - coverage run setup.py test 59 | - coverage report 60 | 61 | after_success: coveralls 62 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import codecs 2 | 3 | from os import path 4 | from setuptools import find_packages, setup 5 | 6 | from django_slowtests import __version__ 7 | 8 | 9 | def read(*parts): 10 | filename = path.join(path.dirname(__file__), *parts) 11 | with codecs.open(filename, encoding="utf-8") as fp: 12 | return fp.read() 13 | 14 | 15 | setup( 16 | author="Michael Herman", 17 | author_email="michael@realpython.com", 18 | description="locate your slowest tests", 19 | name="django-slowtests", 20 | long_description=read("README.rst"), 21 | version=__version__, 22 | url="https://github.com/realpython/django-slow-tests", 23 | license="MIT", 24 | packages=find_packages(), 25 | tests_require=["Django>=1.6", "mock"], 26 | install_requires=["django>=1.6"], 27 | test_suite="runtests.runtests", 28 | classifiers=[ 29 | "Environment :: Web Environment", 30 | "Framework :: Django", 31 | "Intended Audience :: Developers", 32 | "License :: OSI Approved :: MIT License", 33 | "Operating System :: OS Independent", 34 | "Programming Language :: Python", 35 | "Programming Language :: Python :: 2.7", 36 | "Programming Language :: Python :: 3.3", 37 | "Programming Language :: Python :: 3.4", 38 | "Programming Language :: Python :: 3.5", 39 | "Programming Language :: Python :: 3.6", 40 | "Topic :: Internet :: WWW/HTTP", 41 | "Topic :: Internet :: WWW/HTTP :: Dynamic Content", 42 | "Topic :: Software Development :: Testing", 43 | "Topic :: Software Development :: Libraries :: Python Modules", 44 | ], 45 | zip_safe=False 46 | ) 47 | -------------------------------------------------------------------------------- /_examples/django1.8/polls/views.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import get_object_or_404, render 2 | from django.http import HttpResponseRedirect 3 | from django.core.urlresolvers import reverse 4 | from django.views import generic 5 | from django.utils import timezone 6 | from .models import Choice, Question 7 | 8 | 9 | class IndexView(generic.ListView): 10 | template_name = 'polls/index.html' 11 | context_object_name = 'latest_question_list' 12 | 13 | def get_queryset(self): 14 | """ 15 | Return the last five published questions (not including those set to be 16 | published in the future). 17 | """ 18 | return Question.objects.filter( 19 | pub_date__lte=timezone.now() 20 | ).order_by('-pub_date')[:5] 21 | 22 | 23 | class DetailView(generic.DetailView): 24 | model = Question 25 | template_name = 'polls/detail.html' 26 | 27 | def get_queryset(self): 28 | """ 29 | Excludes any questions that aren't published yet. 30 | """ 31 | return Question.objects.filter(pub_date__lte=timezone.now()) 32 | 33 | 34 | class ResultsView(generic.DetailView): 35 | model = Question 36 | template_name = 'polls/results.html' 37 | 38 | 39 | def vote(request, question_id): 40 | p = get_object_or_404(Question, pk=question_id) 41 | try: 42 | selected_choice = p.choice_set.get(pk=request.POST['choice']) 43 | except (KeyError, Choice.DoesNotExist): 44 | # Redisplay the question voting form. 45 | return render(request, 'polls/detail.html', { 46 | 'question': p, 47 | 'error_message': "You didn't select a choice.", 48 | }) 49 | else: 50 | selected_choice.votes += 1 51 | selected_choice.save() 52 | # Always return an HttpResponseRedirect after successfully dealing 53 | # with POST data. This prevents data from being posted twice if a 54 | # user hits the Back button. 55 | return HttpResponseRedirect(reverse('polls:results', args=(p.id,))) 56 | -------------------------------------------------------------------------------- /_examples/django1.10/polls/views.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import get_object_or_404, render 2 | from django.http import HttpResponseRedirect 3 | from django.urls import reverse 4 | from django.views import generic 5 | from django.utils import timezone 6 | 7 | from .models import Choice, Question 8 | 9 | 10 | class IndexView(generic.ListView): 11 | template_name = 'polls/index.html' 12 | context_object_name = 'latest_question_list' 13 | 14 | def get_queryset(self): 15 | """ 16 | Return the last five published questions (not including those set to be 17 | published in the future). 18 | """ 19 | return Question.objects.filter( 20 | pub_date__lte=timezone.now() 21 | ).order_by('-pub_date')[:5] 22 | 23 | 24 | class DetailView(generic.DetailView): 25 | model = Question 26 | template_name = 'polls/detail.html' 27 | 28 | def get_queryset(self): 29 | """ 30 | Excludes any questions that aren't published yet. 31 | """ 32 | return Question.objects.filter(pub_date__lte=timezone.now()) 33 | 34 | 35 | class ResultsView(generic.DetailView): 36 | model = Question 37 | template_name = 'polls/results.html' 38 | 39 | 40 | def vote(request, question_id): 41 | question = get_object_or_404(Question, pk=question_id) 42 | try: 43 | selected_choice = question.choice_set.get(pk=request.POST['choice']) 44 | except (KeyError, Choice.DoesNotExist): 45 | # Redisplay the question voting form. 46 | return render(request, 'polls/detail.html', { 47 | 'question': question, 48 | 'error_message': "You didn't select a choice.", 49 | }) 50 | else: 51 | selected_choice.votes += 1 52 | selected_choice.save() 53 | # Always return an HttpResponseRedirect after successfully dealing 54 | # with POST data. This prevents data from being posted twice if a 55 | # user hits the Back button. 56 | return HttpResponseRedirect(reverse('polls:results', args=(question.id,))) -------------------------------------------------------------------------------- /django_slowtests/tests/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | from mock import patch 3 | from unittest import TestResult 4 | from ..testrunner import TimingSuite 5 | 6 | 7 | class TimingSuiteTests(TestCase): 8 | def test_add_a_test(self): 9 | from .fake import FakeTestCase 10 | suite = TimingSuite() 11 | result = TestResult() 12 | suite.addTest(FakeTestCase('test_slow_thing')) 13 | suite.addTest(FakeTestCase('test_setup_class_was_run')) 14 | with patch.object(suite, 'save_test_time') as mock: 15 | suite.run(result) 16 | self.assertEquals(len(suite._tests), 2) 17 | self.assertEquals(len(result.errors), 0) 18 | self.assertEqual(mock.call_count, 2) 19 | 20 | def test_timing_is_correct_when_freezegun_sets_time_in_past(self): 21 | from .fake import FakeFrozenInPastTestCase 22 | suite = TimingSuite() 23 | result = TestResult() 24 | suite.addTest(FakeFrozenInPastTestCase( 25 | 'test_this_should_not_have_a_negative_duration' 26 | )) 27 | with patch.object(suite, 'save_test_time') as mock: 28 | suite.run(result) 29 | test_name = str(suite._tests[0]) 30 | mock.assert_called_once() 31 | self.assertEqual(mock.call_args_list[0][0][0], test_name) 32 | self.assertTrue(mock.call_args_list[0][0][1] > 0) 33 | self.assertTrue(mock.call_args_list[0][0][1] < 1) 34 | 35 | def test_timing_is_correct_when_freezegun_sets_time_in_future(self): 36 | from .fake import FakeFrozenInFutureTestCase 37 | suite = TimingSuite() 38 | result = TestResult() 39 | suite.addTest(FakeFrozenInFutureTestCase( 40 | 'test_this_should_not_have_very_long_duration' 41 | )) 42 | with patch.object(suite, 'save_test_time') as mock: 43 | suite.run(result) 44 | test_name = str(suite._tests[0]) 45 | self.assertEqual(mock.call_args_list[0][0][0], test_name) 46 | self.assertTrue(mock.call_args_list[0][0][1] > 0) 47 | self.assertTrue(mock.call_args_list[0][0][1] < 1) 48 | -------------------------------------------------------------------------------- /_examples/django1.6/firstapp/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for firstapp project. 3 | 4 | For more information on this file, see 5 | https://docs.djangoproject.com/en/1.6/topics/settings/ 6 | 7 | For the full list of settings and their values, see 8 | https://docs.djangoproject.com/en/1.6/ref/settings/ 9 | """ 10 | 11 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...) 12 | import os 13 | BASE_DIR = os.path.dirname(os.path.dirname(__file__)) 14 | 15 | 16 | # Quick-start development settings - unsuitable for production 17 | # See https://docs.djangoproject.com/en/1.6/howto/deployment/checklist/ 18 | 19 | # SECURITY WARNING: keep the secret key used in production secret! 20 | SECRET_KEY = '7o8afshdpw9!7ybfrmg+*6r4=k8p_s!%u(mr-=$_^q(4od(uv4' 21 | 22 | # SECURITY WARNING: don't run with debug turned on in production! 23 | DEBUG = True 24 | 25 | TEMPLATE_DEBUG = True 26 | 27 | ALLOWED_HOSTS = [] 28 | 29 | 30 | # Application definition 31 | 32 | INSTALLED_APPS = ( 33 | 'django.contrib.admin', 34 | 'django.contrib.auth', 35 | 'django.contrib.contenttypes', 36 | 'django.contrib.sessions', 37 | 'django.contrib.messages', 38 | 'django.contrib.staticfiles', 39 | 'polls', 40 | ) 41 | 42 | MIDDLEWARE_CLASSES = ( 43 | 'django.contrib.sessions.middleware.SessionMiddleware', 44 | 'django.middleware.common.CommonMiddleware', 45 | 'django.middleware.csrf.CsrfViewMiddleware', 46 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 47 | 'django.contrib.messages.middleware.MessageMiddleware', 48 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 49 | ) 50 | 51 | ROOT_URLCONF = 'firstapp.urls' 52 | 53 | WSGI_APPLICATION = 'firstapp.wsgi.application' 54 | 55 | 56 | # Database 57 | # https://docs.djangoproject.com/en/1.6/ref/settings/#databases 58 | 59 | DATABASES = { 60 | 'default': { 61 | 'ENGINE': 'django.db.backends.sqlite3', 62 | 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), 63 | } 64 | } 65 | 66 | # Internationalization 67 | # https://docs.djangoproject.com/en/1.6/topics/i18n/ 68 | 69 | LANGUAGE_CODE = 'en-us' 70 | 71 | TIME_ZONE = 'America/Los_Angeles' 72 | 73 | USE_I18N = True 74 | 75 | USE_L10N = True 76 | 77 | USE_TZ = True 78 | 79 | 80 | # Static files (CSS, JavaScript, Images) 81 | # https://docs.djangoproject.com/en/1.6/howto/static-files/ 82 | 83 | STATIC_URL = '/static/' 84 | 85 | TEMPLATE_DIRS = [os.path.join(BASE_DIR, 'templates')] 86 | 87 | # Custom test runner 88 | TEST_RUNNER = 'django_slowtests.testrunner.DiscoverSlowestTestsRunner' 89 | -------------------------------------------------------------------------------- /_examples/django1.7/firstapp/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for firstapp project. 3 | 4 | For more information on this file, see 5 | https://docs.djangoproject.com/en/1.7/topics/settings/ 6 | 7 | For the full list of settings and their values, see 8 | https://docs.djangoproject.com/en/1.7/ref/settings/ 9 | """ 10 | 11 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...) 12 | import os 13 | BASE_DIR = os.path.dirname(os.path.dirname(__file__)) 14 | 15 | 16 | # Quick-start development settings - unsuitable for production 17 | # See https://docs.djangoproject.com/en/1.7/howto/deployment/checklist/ 18 | 19 | # SECURITY WARNING: keep the secret key used in production secret! 20 | SECRET_KEY = '@3tnaq+y$8)jr2x6v6ju$jq!7josxdgdo4yd5xaw7#)(&6@^!m' 21 | 22 | # SECURITY WARNING: don't run with debug turned on in production! 23 | DEBUG = True 24 | 25 | TEMPLATE_DEBUG = True 26 | 27 | ALLOWED_HOSTS = [] 28 | 29 | 30 | # Application definition 31 | 32 | INSTALLED_APPS = ( 33 | 'django.contrib.admin', 34 | 'django.contrib.auth', 35 | 'django.contrib.contenttypes', 36 | 'django.contrib.sessions', 37 | 'django.contrib.messages', 38 | 'django.contrib.staticfiles', 39 | 'polls', 40 | ) 41 | 42 | MIDDLEWARE_CLASSES = ( 43 | 'django.contrib.sessions.middleware.SessionMiddleware', 44 | 'django.middleware.common.CommonMiddleware', 45 | 'django.middleware.csrf.CsrfViewMiddleware', 46 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 47 | 'django.contrib.auth.middleware.SessionAuthenticationMiddleware', 48 | 'django.contrib.messages.middleware.MessageMiddleware', 49 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 50 | ) 51 | 52 | ROOT_URLCONF = 'firstapp.urls' 53 | 54 | WSGI_APPLICATION = 'firstapp.wsgi.application' 55 | 56 | 57 | # Database 58 | # https://docs.djangoproject.com/en/1.7/ref/settings/#databases 59 | 60 | DATABASES = { 61 | 'default': { 62 | 'ENGINE': 'django.db.backends.sqlite3', 63 | 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), 64 | } 65 | } 66 | 67 | # Internationalization 68 | # https://docs.djangoproject.com/en/1.7/topics/i18n/ 69 | 70 | LANGUAGE_CODE = 'en-us' 71 | 72 | TIME_ZONE = 'UTC' 73 | 74 | USE_I18N = True 75 | 76 | USE_L10N = True 77 | 78 | USE_TZ = True 79 | 80 | 81 | # Static files (CSS, JavaScript, Images) 82 | # https://docs.djangoproject.com/en/1.7/howto/static-files/ 83 | 84 | STATIC_URL = '/static/' 85 | 86 | TEMPLATE_DIRS = [os.path.join(BASE_DIR, 'templates')] 87 | 88 | # Custom test runner 89 | TEST_RUNNER = 'django_slowtests.testrunner.DiscoverSlowestTestsRunner' 90 | -------------------------------------------------------------------------------- /_examples/django1.8/mysite/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for mysite project. 3 | 4 | Generated by 'django-admin startproject' using Django 1.8.2. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/1.8/topics/settings/ 8 | 9 | For the full list of settings and their values, see 10 | https://docs.djangoproject.com/en/1.8/ref/settings/ 11 | """ 12 | 13 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...) 14 | import os 15 | 16 | BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 17 | 18 | 19 | # Quick-start development settings - unsuitable for production 20 | # See https://docs.djangoproject.com/en/1.8/howto/deployment/checklist/ 21 | 22 | # SECURITY WARNING: keep the secret key used in production secret! 23 | SECRET_KEY = 'vikmt@b6)=_z^3a3ji%2&#znmz)ure%k7xrz@phly(0#&as84z' 24 | 25 | # SECURITY WARNING: don't run with debug turned on in production! 26 | DEBUG = True 27 | 28 | ALLOWED_HOSTS = [] 29 | 30 | 31 | # Application definition 32 | 33 | INSTALLED_APPS = ( 34 | 'django.contrib.admin', 35 | 'django.contrib.auth', 36 | 'django.contrib.contenttypes', 37 | 'django.contrib.sessions', 38 | 'django.contrib.messages', 39 | 'django.contrib.staticfiles', 40 | 'polls', 41 | ) 42 | 43 | MIDDLEWARE_CLASSES = ( 44 | 'django.contrib.sessions.middleware.SessionMiddleware', 45 | 'django.middleware.common.CommonMiddleware', 46 | 'django.middleware.csrf.CsrfViewMiddleware', 47 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 48 | 'django.contrib.auth.middleware.SessionAuthenticationMiddleware', 49 | 'django.contrib.messages.middleware.MessageMiddleware', 50 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 51 | 'django.middleware.security.SecurityMiddleware', 52 | ) 53 | 54 | ROOT_URLCONF = 'mysite.urls' 55 | 56 | TEMPLATES = [ 57 | { 58 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 59 | 'DIRS': [os.path.join(BASE_DIR, 'templates')], 60 | 'APP_DIRS': True, 61 | 'OPTIONS': { 62 | 'context_processors': [ 63 | 'django.template.context_processors.debug', 64 | 'django.template.context_processors.request', 65 | 'django.contrib.auth.context_processors.auth', 66 | 'django.contrib.messages.context_processors.messages', 67 | ], 68 | }, 69 | }, 70 | ] 71 | 72 | WSGI_APPLICATION = 'mysite.wsgi.application' 73 | 74 | 75 | # Database 76 | # https://docs.djangoproject.com/en/1.8/ref/settings/#databases 77 | 78 | DATABASES = { 79 | 'default': { 80 | 'ENGINE': 'django.db.backends.sqlite3', 81 | 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), 82 | } 83 | } 84 | 85 | 86 | # Internationalization 87 | # https://docs.djangoproject.com/en/1.8/topics/i18n/ 88 | 89 | LANGUAGE_CODE = 'en-us' 90 | 91 | TIME_ZONE = 'UTC' 92 | 93 | USE_I18N = True 94 | 95 | USE_L10N = True 96 | 97 | USE_TZ = True 98 | 99 | 100 | # Static files (CSS, JavaScript, Images) 101 | # https://docs.djangoproject.com/en/1.8/howto/static-files/ 102 | 103 | STATIC_URL = '/static/' 104 | 105 | # Custom test runner 106 | TEST_RUNNER = 'django_slowtests.testrunner.DiscoverSlowestTestsRunner' 107 | NUM_SLOW_TESTS = 5 108 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | django-slowtests 2 | ======================== 3 | 4 | .. image:: https://travis-ci.org/realpython/django-slow-tests.svg?branch=master 5 | :target: https://travis-ci.org/realpython/django-slow-tests 6 | 7 | .. image:: https://coveralls.io/repos/realpython/django-slow-tests/badge.svg?branch=master 8 | :target: https://coveralls.io/r/realpython/django-slow-tests?branch=master 9 | 10 | .. image:: https://img.shields.io/pypi/v/django-slowtests.svg 11 | :target: https://pypi.python.org/pypi/django-slowtests/ 12 | 13 | .. image:: https://img.shields.io/badge/license-MIT-blue.svg 14 | :target: https://pypi.python.org/pypi/django-slowtests/ 15 | 16 | Welcome! 17 | -------- 18 | 19 | Welcome to the documentation for django-slowtests! 20 | 21 | *Code tested on Django 1.6, 1.7, 1.8, 1.9, 1.10 and 1.11 with Python 2.7 and 3.6 22 | 23 | 24 | Instructions 25 | ------------- 26 | 27 | 1. Install:: 28 | 29 | $ pip install django-slowtests 30 | 31 | 2. Add the following settings:: 32 | 33 | TEST_RUNNER = 'django_slowtests.testrunner.DiscoverSlowestTestsRunner' 34 | NUM_SLOW_TESTS = 10 35 | 36 | # (Optional) 37 | SLOW_TEST_THRESHOLD_MS = 200 # Only show tests slower than 200ms 38 | 39 | # (Optional) 40 | ALWAYS_GENERATE_SLOW_REPORT = False # Generate report only when requested using --slowreport flag 41 | 42 | 3. Run test suite:: 43 | 44 | $ python manage.py test 45 | 46 | 47 | 3.1. Save report to file:: 48 | $ python manage.py test --slowreportpath report.json 49 | 50 | 3.2. Generating full reports to file:: 51 | In some situations, you may need to generate full tests reports. To do so, 52 | set NUM_SLOW_TESTS to None in your settings and run the following command: 53 | $ python manage.py test --slowreportpath report.json 54 | 55 | 56 | 4. Sample output:: 57 | 58 | 59 | $ python manage.py test 60 | Creating test database for alias 'default'... 61 | .......... 62 | ---------------------------------------------------------------------- 63 | Ran 10 tests in 0.413s 64 | 65 | OK 66 | Destroying test database for alias 'default'... 67 | 68 | Ten slowest tests: 69 | 0.3597s test_detail_view_with_a_future_poll (polls.tests.PollIndexDetailTests) 70 | 0.0284s test_detail_view_with_a_past_poll (polls.tests.PollIndexDetailTests) 71 | 0.0068s test_index_view_with_a_future_poll (polls.tests.PollViewTests) 72 | 0.0047s test_index_view_with_a_past_poll (polls.tests.PollViewTests) 73 | 0.0045s test_index_view_with_two_past_polls (polls.tests.PollViewTests) 74 | 0.0041s test_index_view_with_future_poll_and_past_poll (polls.tests.PollViewTests) 75 | 0.0036s test_index_view_with_no_polls (polls.tests.PollViewTests) 76 | 0.0003s test_was_published_recently_with_future_poll (polls.tests.PollMethodTests) 77 | 0.0002s test_was_published_recently_with_recent_poll (polls.tests.PollMethodTests) 78 | 0.0002s test_was_published_recently_with_old_poll (polls.tests.PollMethodTests) 79 | 80 | 81 | 82 | Running the Tests 83 | ------------------------------------ 84 | 85 | You can run the tests via:: 86 | 87 | $ python setup.py test 88 | 89 | or:: 90 | 91 | $ make test 92 | 93 | or:: 94 | 95 | $ make all 96 | 97 | or:: 98 | 99 | $ python runtests.py 100 | 101 | 102 | Known Issues 103 | ------------ 104 | 105 | 106 | 107 | License 108 | ------- 109 | 110 | This code is distributed under the terms of the MIT license. See the `LICENSE` file. 111 | -------------------------------------------------------------------------------- /_examples/django1.9/mysite/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for mysite project. 3 | 4 | Generated by 'django-admin startproject' using Django 1.9. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/1.9/topics/settings/ 8 | 9 | For the full list of settings and their values, see 10 | https://docs.djangoproject.com/en/1.9/ref/settings/ 11 | """ 12 | 13 | import os 14 | 15 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...) 16 | BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 17 | 18 | 19 | # Quick-start development settings - unsuitable for production 20 | # See https://docs.djangoproject.com/en/1.9/howto/deployment/checklist/ 21 | 22 | # SECURITY WARNING: keep the secret key used in production secret! 23 | SECRET_KEY = 'vtg32vxrtz!dkcpmxm6h9-^q!7v6b5-q09fqznov!4sm5+^rp&' 24 | 25 | # SECURITY WARNING: don't run with debug turned on in production! 26 | DEBUG = True 27 | 28 | ALLOWED_HOSTS = [] 29 | 30 | 31 | # Application definition 32 | 33 | INSTALLED_APPS = [ 34 | 'polls.apps.PollsConfig', 35 | 'django.contrib.admin', 36 | 'django.contrib.auth', 37 | 'django.contrib.contenttypes', 38 | 'django.contrib.sessions', 39 | 'django.contrib.messages', 40 | 'django.contrib.staticfiles', 41 | ] 42 | 43 | MIDDLEWARE_CLASSES = [ 44 | 'django.middleware.security.SecurityMiddleware', 45 | 'django.contrib.sessions.middleware.SessionMiddleware', 46 | 'django.middleware.common.CommonMiddleware', 47 | 'django.middleware.csrf.CsrfViewMiddleware', 48 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 49 | 'django.contrib.auth.middleware.SessionAuthenticationMiddleware', 50 | 'django.contrib.messages.middleware.MessageMiddleware', 51 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 52 | ] 53 | 54 | ROOT_URLCONF = 'mysite.urls' 55 | 56 | TEMPLATES = [ 57 | { 58 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 59 | 'DIRS': [], 60 | 'APP_DIRS': True, 61 | 'OPTIONS': { 62 | 'context_processors': [ 63 | 'django.template.context_processors.debug', 64 | 'django.template.context_processors.request', 65 | 'django.contrib.auth.context_processors.auth', 66 | 'django.contrib.messages.context_processors.messages', 67 | ], 68 | }, 69 | }, 70 | ] 71 | 72 | WSGI_APPLICATION = 'mysite.wsgi.application' 73 | 74 | 75 | # Database 76 | # https://docs.djangoproject.com/en/1.9/ref/settings/#databases 77 | 78 | DATABASES = { 79 | 'default': { 80 | 'ENGINE': 'django.db.backends.sqlite3', 81 | 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), 82 | } 83 | } 84 | 85 | 86 | # Password validation 87 | # https://docs.djangoproject.com/en/1.9/ref/settings/#auth-password-validators 88 | 89 | AUTH_PASSWORD_VALIDATORS = [ 90 | { 91 | 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', 92 | }, 93 | { 94 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', 95 | }, 96 | { 97 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', 98 | }, 99 | { 100 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', 101 | }, 102 | ] 103 | 104 | 105 | # Internationalization 106 | # https://docs.djangoproject.com/en/1.9/topics/i18n/ 107 | 108 | LANGUAGE_CODE = 'en-us' 109 | 110 | TIME_ZONE = 'UTC' 111 | 112 | USE_I18N = True 113 | 114 | USE_L10N = True 115 | 116 | USE_TZ = True 117 | 118 | 119 | # Static files (CSS, JavaScript, Images) 120 | # https://docs.djangoproject.com/en/1.9/howto/static-files/ 121 | 122 | STATIC_URL = '/static/' 123 | 124 | # Custom test runner 125 | TEST_RUNNER = 'django_slowtests.testrunner.DiscoverSlowestTestsRunner' 126 | NUM_SLOW_TESTS = 5 127 | -------------------------------------------------------------------------------- /_examples/django1.10/django_polls/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for django_polls project. 3 | 4 | Generated by 'django-admin startproject' using Django 1.10. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/1.10/topics/settings/ 8 | 9 | For the full list of settings and their values, see 10 | https://docs.djangoproject.com/en/1.10/ref/settings/ 11 | """ 12 | 13 | import os 14 | 15 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...) 16 | BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 17 | 18 | 19 | # Quick-start development settings - unsuitable for production 20 | # See https://docs.djangoproject.com/en/1.10/howto/deployment/checklist/ 21 | 22 | # SECURITY WARNING: keep the secret key used in production secret! 23 | SECRET_KEY = 'd9gr^r=$cyhf$qq&wx%up$_am0x17+2-o82*isvhkqy_uzutyp' 24 | 25 | # SECURITY WARNING: don't run with debug turned on in production! 26 | DEBUG = True 27 | 28 | ALLOWED_HOSTS = [] 29 | 30 | 31 | # Application definition 32 | 33 | INSTALLED_APPS = [ 34 | 'polls.apps.PollsConfig', 35 | 'django.contrib.admin', 36 | 'django.contrib.auth', 37 | 'django.contrib.contenttypes', 38 | 'django.contrib.sessions', 39 | 'django.contrib.messages', 40 | 'django.contrib.staticfiles', 41 | ] 42 | 43 | MIDDLEWARE = [ 44 | 'django.middleware.security.SecurityMiddleware', 45 | 'django.contrib.sessions.middleware.SessionMiddleware', 46 | 'django.middleware.common.CommonMiddleware', 47 | 'django.middleware.csrf.CsrfViewMiddleware', 48 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 49 | 'django.contrib.messages.middleware.MessageMiddleware', 50 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 51 | ] 52 | 53 | ROOT_URLCONF = 'django_polls.urls' 54 | 55 | TEMPLATES = [ 56 | { 57 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 58 | 'DIRS': [os.path.join(BASE_DIR, 'templates')], 59 | 'APP_DIRS': True, 60 | 'OPTIONS': { 61 | 'context_processors': [ 62 | 'django.template.context_processors.debug', 63 | 'django.template.context_processors.request', 64 | 'django.contrib.auth.context_processors.auth', 65 | 'django.contrib.messages.context_processors.messages', 66 | ], 67 | }, 68 | }, 69 | ] 70 | 71 | WSGI_APPLICATION = 'django_polls.wsgi.application' 72 | 73 | 74 | # Database 75 | # https://docs.djangoproject.com/en/1.10/ref/settings/#databases 76 | 77 | DATABASES = { 78 | 'default': { 79 | 'ENGINE': 'django.db.backends.sqlite3', 80 | 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), 81 | } 82 | } 83 | 84 | 85 | # Password validation 86 | # https://docs.djangoproject.com/en/1.10/ref/settings/#auth-password-validators 87 | 88 | AUTH_PASSWORD_VALIDATORS = [ 89 | { 90 | 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', 91 | }, 92 | { 93 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', 94 | }, 95 | { 96 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', 97 | }, 98 | { 99 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', 100 | }, 101 | ] 102 | 103 | 104 | # Internationalization 105 | # https://docs.djangoproject.com/en/1.10/topics/i18n/ 106 | 107 | LANGUAGE_CODE = 'en-us' 108 | 109 | TIME_ZONE = 'Europe/Kiev' 110 | 111 | USE_I18N = True 112 | 113 | USE_L10N = True 114 | 115 | USE_TZ = True 116 | 117 | 118 | # Static files (CSS, JavaScript, Images) 119 | # https://docs.djangoproject.com/en/1.10/howto/static-files/ 120 | 121 | STATIC_URL = '/static/' 122 | 123 | 124 | TEST_RUNNER = 'django_slowtests.testrunner.DiscoverSlowestTestsRunner' 125 | NUM_SLOW_TESTS = 10 126 | 127 | # (Optional) 128 | SLOW_TEST_THRESHOLD_MS = 200 # Only show tests slower than 200ms 129 | -------------------------------------------------------------------------------- /_examples/django1.10/polls/tests.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | 3 | from django.urls import reverse 4 | from django.utils import timezone 5 | from django.test import TestCase 6 | 7 | from .models import Question 8 | 9 | 10 | def create_question(question_text, days): 11 | """ 12 | Creates a question with the given `question_text` and published the 13 | given number of `days` offset to now (negative for questions published 14 | in the past, positive for questions that have yet to be published). 15 | """ 16 | time = timezone.now() + datetime.timedelta(days=days) 17 | return Question.objects.create(question_text=question_text, pub_date=time) 18 | 19 | 20 | class QuestionViewTests(TestCase): 21 | def test_index_view_with_no_questions(self): 22 | """ 23 | If no questions exist, an appropriate message should be displayed. 24 | """ 25 | response = self.client.get(reverse('polls:index')) 26 | self.assertEqual(response.status_code, 200) 27 | self.assertContains(response, "No polls are available.") 28 | self.assertQuerysetEqual(response.context['latest_question_list'], []) 29 | 30 | def test_index_view_with_a_past_question(self): 31 | """ 32 | Questions with a pub_date in the past should be displayed on the 33 | index page. 34 | """ 35 | create_question(question_text="Past question.", days=-30) 36 | response = self.client.get(reverse('polls:index')) 37 | self.assertQuerysetEqual( 38 | response.context['latest_question_list'], 39 | [''] 40 | ) 41 | 42 | def test_index_view_with_a_future_question(self): 43 | """ 44 | Questions with a pub_date in the future should not be displayed on 45 | the index page. 46 | """ 47 | create_question(question_text="Future question.", days=30) 48 | response = self.client.get(reverse('polls:index')) 49 | self.assertContains(response, "No polls are available.") 50 | self.assertQuerysetEqual(response.context['latest_question_list'], []) 51 | 52 | def test_index_view_with_future_question_and_past_question(self): 53 | """ 54 | Even if both past and future questions exist, only past questions 55 | should be displayed. 56 | """ 57 | create_question(question_text="Past question.", days=-30) 58 | create_question(question_text="Future question.", days=30) 59 | response = self.client.get(reverse('polls:index')) 60 | self.assertQuerysetEqual( 61 | response.context['latest_question_list'], 62 | [''] 63 | ) 64 | 65 | def test_index_view_with_two_past_questions(self): 66 | """ 67 | The questions index page may display multiple questions. 68 | """ 69 | create_question(question_text="Past question 1.", days=-30) 70 | create_question(question_text="Past question 2.", days=-5) 71 | response = self.client.get(reverse('polls:index')) 72 | self.assertQuerysetEqual( 73 | response.context['latest_question_list'], 74 | ['', ''] 75 | ) 76 | 77 | 78 | class QuestionIndexDetailTests(TestCase): 79 | def test_detail_view_with_a_future_question(self): 80 | """ 81 | The detail view of a question with a pub_date in the future should 82 | return a 404 not found. 83 | """ 84 | future_question = create_question(question_text='Future question.', days=5) 85 | url = reverse('polls:detail', args=(future_question.id,)) 86 | response = self.client.get(url) 87 | self.assertEqual(response.status_code, 404) 88 | 89 | def test_detail_view_with_a_past_question(self): 90 | """ 91 | The detail view of a question with a pub_date in the past should 92 | display the question's text. 93 | """ 94 | past_question = create_question(question_text='Past Question.', days=-5) 95 | url = reverse('polls:detail', args=(past_question.id,)) 96 | response = self.client.get(url) 97 | self.assertContains(response, past_question.question_text) -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = _build 9 | 10 | # Internal variables. 11 | PAPEROPT_a4 = -D latex_paper_size=a4 12 | PAPEROPT_letter = -D latex_paper_size=letter 13 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 14 | 15 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest 16 | 17 | help: 18 | @echo "Please use \`make ' where is one of" 19 | @echo " html to make standalone HTML files" 20 | @echo " dirhtml to make HTML files named index.html in directories" 21 | @echo " singlehtml to make a single large HTML file" 22 | @echo " pickle to make pickle files" 23 | @echo " json to make JSON files" 24 | @echo " htmlhelp to make HTML files and a HTML help project" 25 | @echo " qthelp to make HTML files and a qthelp project" 26 | @echo " devhelp to make HTML files and a Devhelp project" 27 | @echo " epub to make an epub" 28 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 29 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 30 | @echo " text to make text files" 31 | @echo " man to make manual pages" 32 | @echo " changes to make an overview of all changed/added/deprecated items" 33 | @echo " linkcheck to check all external links for integrity" 34 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 35 | 36 | clean: 37 | -rm -rf $(BUILDDIR)/* 38 | 39 | html: 40 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 41 | @echo 42 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 43 | 44 | dirhtml: 45 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 46 | @echo 47 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 48 | 49 | singlehtml: 50 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 51 | @echo 52 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 53 | 54 | pickle: 55 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 56 | @echo 57 | @echo "Build finished; now you can process the pickle files." 58 | 59 | json: 60 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 61 | @echo 62 | @echo "Build finished; now you can process the JSON files." 63 | 64 | htmlhelp: 65 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 66 | @echo 67 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 68 | ".hhp project file in $(BUILDDIR)/htmlhelp." 69 | 70 | qthelp: 71 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 72 | @echo 73 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 74 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 75 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/{{ app_name }}.qhcp" 76 | @echo "To view the help file:" 77 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/{{ app_name }}.qhc" 78 | 79 | devhelp: 80 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 81 | @echo 82 | @echo "Build finished." 83 | @echo "To view the help file:" 84 | @echo "# mkdir -p $$HOME/.local/share/devhelp/{{ app_name }}" 85 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/{{ app_name }}" 86 | @echo "# devhelp" 87 | 88 | epub: 89 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 90 | @echo 91 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 92 | 93 | latex: 94 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 95 | @echo 96 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 97 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 98 | "(use \`make latexpdf' here to do that automatically)." 99 | 100 | latexpdf: 101 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 102 | @echo "Running LaTeX files through pdflatex..." 103 | make -C $(BUILDDIR)/latex all-pdf 104 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 105 | 106 | text: 107 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 108 | @echo 109 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 110 | 111 | man: 112 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 113 | @echo 114 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 115 | 116 | changes: 117 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 118 | @echo 119 | @echo "The overview file is in $(BUILDDIR)/changes." 120 | 121 | linkcheck: 122 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 123 | @echo 124 | @echo "Link check complete; look for any errors in the above output " \ 125 | "or in $(BUILDDIR)/linkcheck/output.txt." 126 | 127 | doctest: 128 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 129 | @echo "Testing of doctests in the sources finished, look at the " \ 130 | "results in $(BUILDDIR)/doctest/output.txt." -------------------------------------------------------------------------------- /_examples/django1.6/polls/tests.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | from django.utils import timezone 3 | from django.test import TestCase 4 | from django.core.urlresolvers import reverse 5 | 6 | from polls.models import Poll 7 | 8 | 9 | def create_poll(question, days): 10 | """ 11 | Creates a poll with the given `question` published the given number of 12 | `days` offset to now (negative for polls published in the past, 13 | positive for polls that have yet to be published). 14 | """ 15 | return Poll.objects.create( 16 | question=question, 17 | pub_date=timezone.now() + datetime.timedelta(days=days) 18 | ) 19 | 20 | 21 | class PollViewTests(TestCase): 22 | 23 | def test_index_view_with_no_polls(self): 24 | """ 25 | If no polls exist, an appropriate message should be displayed. 26 | """ 27 | response = self.client.get(reverse('polls:index')) 28 | self.assertEqual(response.status_code, 200) 29 | self.assertContains(response, "No polls are available.") 30 | self.assertQuerysetEqual(response.context['latest_poll_list'], []) 31 | 32 | def test_index_view_with_a_past_poll(self): 33 | """ 34 | Polls with a pub_date in the past should be displayed on the index page. 35 | """ 36 | create_poll(question="Past poll.", days=-30) 37 | response = self.client.get(reverse('polls:index')) 38 | self.assertQuerysetEqual( 39 | response.context['latest_poll_list'], 40 | [''] 41 | ) 42 | 43 | def test_index_view_with_a_future_poll(self): 44 | """ 45 | Polls with a pub_date in the future should not be displayed on the 46 | index page. 47 | """ 48 | create_poll(question="Future poll.", days=30) 49 | response = self.client.get(reverse('polls:index')) 50 | self.assertContains( 51 | response, 52 | "No polls are available.", 53 | status_code=200 54 | ) 55 | self.assertQuerysetEqual(response.context['latest_poll_list'], []) 56 | 57 | def test_index_view_with_future_poll_and_past_poll(self): 58 | """ 59 | Even if both past and future polls exist, only past polls should be 60 | displayed. 61 | """ 62 | create_poll(question="Past poll.", days=-30) 63 | create_poll(question="Future poll.", days=30) 64 | response = self.client.get(reverse('polls:index')) 65 | self.assertQuerysetEqual( 66 | response.context['latest_poll_list'], 67 | [''] 68 | ) 69 | 70 | def test_index_view_with_two_past_polls(self): 71 | """ 72 | The polls index page may display multiple polls. 73 | """ 74 | create_poll(question="Past poll 1.", days=-30) 75 | create_poll(question="Past poll 2.", days=-5) 76 | response = self.client.get(reverse('polls:index')) 77 | self.assertQuerysetEqual( 78 | response.context['latest_poll_list'], 79 | ['', ''] 80 | ) 81 | 82 | 83 | class PollIndexDetailTests(TestCase): 84 | 85 | def test_detail_view_with_a_future_poll(self): 86 | """ 87 | The detail view of a poll with a pub_date in the future should 88 | return a 404 not found. 89 | """ 90 | future_poll = create_poll(question='Future poll.', days=5) 91 | response = self.client.get( 92 | reverse('polls:detail', args=(future_poll.id,)) 93 | ) 94 | self.assertEqual(response.status_code, 404) 95 | 96 | def test_detail_view_with_a_past_poll(self): 97 | """ 98 | The detail view of a poll with a pub_date in the past should display 99 | the poll's question. 100 | """ 101 | past_poll = create_poll(question='Past Poll.', days=-5) 102 | response = self.client.get( 103 | reverse('polls:detail', args=(past_poll.id,)) 104 | ) 105 | self.assertContains(response, past_poll.question, status_code=200) 106 | 107 | 108 | class PollMethodTests(TestCase): 109 | 110 | def test_was_published_recently_with_future_poll(self): 111 | """ 112 | was_published_recently() should return False for polls whose 113 | pub_date is in the future 114 | """ 115 | future_poll = Poll( 116 | pub_date=timezone.now() + datetime.timedelta(days=30) 117 | ) 118 | self.assertEqual(future_poll.was_published_recently(), False) 119 | 120 | def test_was_published_recently_with_old_poll(self): 121 | """ 122 | was_published_recently() should return False for polls whose pub_date 123 | is older than 1 day 124 | """ 125 | old_poll = Poll(pub_date=timezone.now() - datetime.timedelta(days=30)) 126 | self.assertEqual(old_poll.was_published_recently(), False) 127 | 128 | def test_was_published_recently_with_recent_poll(self): 129 | """ 130 | was_published_recently() should return True for polls whose pub_date 131 | is within the last day 132 | """ 133 | recent_poll = Poll( 134 | pub_date=timezone.now() - datetime.timedelta(hours=1) 135 | ) 136 | self.assertEqual(recent_poll.was_published_recently(), True) 137 | -------------------------------------------------------------------------------- /_examples/django1.7/polls/tests.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | from django.utils import timezone 3 | from django.test import TestCase 4 | from django.core.urlresolvers import reverse 5 | 6 | from polls.models import Poll 7 | 8 | 9 | def create_poll(question, days): 10 | """ 11 | Creates a poll with the given `question` published the given number of 12 | `days` offset to now (negative for polls published in the past, 13 | positive for polls that have yet to be published). 14 | """ 15 | return Poll.objects.create( 16 | question=question, 17 | pub_date=timezone.now() + datetime.timedelta(days=days) 18 | ) 19 | 20 | 21 | class PollViewTests(TestCase): 22 | 23 | def test_index_view_with_no_polls(self): 24 | """ 25 | If no polls exist, an appropriate message should be displayed. 26 | """ 27 | response = self.client.get(reverse('polls:index')) 28 | self.assertEqual(response.status_code, 200) 29 | self.assertContains(response, "No polls are available.") 30 | self.assertQuerysetEqual(response.context['latest_poll_list'], []) 31 | 32 | def test_index_view_with_a_past_poll(self): 33 | """ 34 | Polls with a pub_date in the past should be displayed on the index page. 35 | """ 36 | create_poll(question="Past poll.", days=-30) 37 | response = self.client.get(reverse('polls:index')) 38 | self.assertQuerysetEqual( 39 | response.context['latest_poll_list'], 40 | [''] 41 | ) 42 | 43 | def test_index_view_with_a_future_poll(self): 44 | """ 45 | Polls with a pub_date in the future should not be displayed on the 46 | index page. 47 | """ 48 | create_poll(question="Future poll.", days=30) 49 | response = self.client.get(reverse('polls:index')) 50 | self.assertContains( 51 | response, 52 | "No polls are available.", 53 | status_code=200 54 | ) 55 | self.assertQuerysetEqual(response.context['latest_poll_list'], []) 56 | 57 | def test_index_view_with_future_poll_and_past_poll(self): 58 | """ 59 | Even if both past and future polls exist, only past polls should be 60 | displayed. 61 | """ 62 | create_poll(question="Past poll.", days=-30) 63 | create_poll(question="Future poll.", days=30) 64 | response = self.client.get(reverse('polls:index')) 65 | self.assertQuerysetEqual( 66 | response.context['latest_poll_list'], 67 | [''] 68 | ) 69 | 70 | def test_index_view_with_two_past_polls(self): 71 | """ 72 | The polls index page may display multiple polls. 73 | """ 74 | create_poll(question="Past poll 1.", days=-30) 75 | create_poll(question="Past poll 2.", days=-5) 76 | response = self.client.get(reverse('polls:index')) 77 | self.assertQuerysetEqual( 78 | response.context['latest_poll_list'], 79 | ['', ''] 80 | ) 81 | 82 | # dize was here 83 | class PollIndexDetailTests(TestCase): 84 | 85 | def test_detail_view_with_a_future_poll(self): 86 | """ 87 | The detail view of a poll with a pub_date in the future should 88 | return a 404 not found. 89 | """ 90 | future_poll = create_poll(question='Future poll.', days=5) 91 | response = self.client.get( 92 | reverse('polls:detail', args=(future_poll.id,)) 93 | ) 94 | self.assertEqual(response.status_code, 404) 95 | 96 | def test_detail_view_with_a_past_poll(self): 97 | """ 98 | The detail view of a poll with a pub_date in the past should display 99 | the poll's question. 100 | """ 101 | past_poll = create_poll(question='Past Poll.', days=-5) 102 | response = self.client.get( 103 | reverse('polls:detail', args=(past_poll.id,)) 104 | ) 105 | self.assertContains(response, past_poll.question, status_code=200) 106 | 107 | 108 | class PollMethodTests(TestCase): 109 | 110 | def test_was_published_recently_with_future_poll(self): 111 | """ 112 | was_published_recently() should return False for polls whose 113 | pub_date is in the future 114 | """ 115 | future_poll = Poll( 116 | pub_date=timezone.now() + datetime.timedelta(days=30) 117 | ) 118 | self.assertEqual(future_poll.was_published_recently(), False) 119 | 120 | def test_was_published_recently_with_old_poll(self): 121 | """ 122 | was_published_recently() should return False for polls whose pub_date 123 | is older than 1 day 124 | """ 125 | old_poll = Poll(pub_date=timezone.now() - datetime.timedelta(days=30)) 126 | self.assertEqual(old_poll.was_published_recently(), False) 127 | 128 | def test_was_published_recently_with_recent_poll(self): 129 | """ 130 | was_published_recently() should return True for polls whose pub_date 131 | is within the last day 132 | """ 133 | recent_poll = Poll( 134 | pub_date=timezone.now() - datetime.timedelta(hours=1) 135 | ) 136 | self.assertEqual(recent_poll.was_published_recently(), True) 137 | -------------------------------------------------------------------------------- /_examples/django1.8/polls/tests.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | 3 | from django.utils import timezone 4 | from django.test import TestCase 5 | 6 | from django.core.urlresolvers import reverse 7 | 8 | from .models import Question 9 | 10 | 11 | def create_question(question_text, days): 12 | """ 13 | Creates a question with the given `question_text` published the given 14 | number of `days` offset to now (negative for questions published 15 | in the past, positive for questions that have yet to be published). 16 | """ 17 | time = timezone.now() + datetime.timedelta(days=days) 18 | return Question.objects.create(question_text=question_text, pub_date=time) 19 | 20 | 21 | class QuestionMethodTests(TestCase): 22 | 23 | def test_was_published_recently_with_future_question(self): 24 | """ 25 | was_published_recently() should return False for questions whose 26 | pub_date is in the future. 27 | """ 28 | time = timezone.now() + datetime.timedelta(days=30) 29 | future_question = Question(pub_date=time) 30 | self.assertEqual(future_question.was_published_recently(), False) 31 | 32 | def test_was_published_recently_with_old_question(self): 33 | """ 34 | was_published_recently() should return False for questions whose 35 | pub_date is older than 1 day. 36 | """ 37 | time = timezone.now() - datetime.timedelta(days=30) 38 | old_question = Question(pub_date=time) 39 | self.assertEqual(old_question.was_published_recently(), False) 40 | 41 | def test_was_published_recently_with_recent_question(self): 42 | """ 43 | was_published_recently() should return True for questions whose 44 | pub_date is within the last day. 45 | """ 46 | time = timezone.now() - datetime.timedelta(hours=1) 47 | recent_question = Question(pub_date=time) 48 | self.assertEqual(recent_question.was_published_recently(), True) 49 | 50 | 51 | class QuestionViewTests(TestCase): 52 | 53 | def test_index_view_with_no_questions(self): 54 | """ 55 | If no questions exist, an appropriate message should be displayed. 56 | """ 57 | response = self.client.get(reverse('polls:index')) 58 | self.assertEqual(response.status_code, 200) 59 | self.assertContains(response, "No polls are available.") 60 | self.assertQuerysetEqual(response.context['latest_question_list'], []) 61 | 62 | def test_index_view_with_a_past_question(self): 63 | """ 64 | Questions with a pub_date in the past should be displayed on the 65 | index page. 66 | """ 67 | create_question(question_text="Past question.", days=-30) 68 | response = self.client.get(reverse('polls:index')) 69 | self.assertQuerysetEqual( 70 | response.context['latest_question_list'], 71 | [''] 72 | ) 73 | 74 | def test_index_view_with_a_future_question(self): 75 | """ 76 | Questions with a pub_date in the future should not be displayed on 77 | the index page. 78 | """ 79 | create_question(question_text="Future question.", days=30) 80 | response = self.client.get(reverse('polls:index')) 81 | self.assertContains( 82 | response, "No polls are available.", status_code=200 83 | ) 84 | self.assertQuerysetEqual(response.context['latest_question_list'], []) 85 | 86 | def test_index_view_with_future_question_and_past_question(self): 87 | """ 88 | Even if both past and future questions exist, only past questions 89 | should be displayed. 90 | """ 91 | create_question(question_text="Past question.", days=-30) 92 | create_question(question_text="Future question.", days=30) 93 | response = self.client.get(reverse('polls:index')) 94 | self.assertQuerysetEqual( 95 | response.context['latest_question_list'], 96 | [''] 97 | ) 98 | 99 | def test_index_view_with_two_past_questions(self): 100 | """ 101 | The questions index page may display multiple questions. 102 | """ 103 | create_question(question_text="Past question 1.", days=-30) 104 | create_question(question_text="Past question 2.", days=-5) 105 | response = self.client.get(reverse('polls:index')) 106 | self.assertQuerysetEqual( 107 | response.context['latest_question_list'], 108 | ['', ''] 109 | ) 110 | 111 | 112 | class QuestionIndexDetailTests(TestCase): 113 | 114 | def test_detail_view_with_a_future_question(self): 115 | """ 116 | The detail view of a question with a pub_date in the future should 117 | return a 404 not found. 118 | """ 119 | future_question = create_question(question_text='Future question.', days=5) 120 | response = self.client.get(reverse('polls:detail', args=(future_question.id,))) 121 | self.assertEqual(response.status_code, 404) 122 | 123 | def test_detail_view_with_a_past_question(self): 124 | """ 125 | The detail view of a question with a pub_date in the past should 126 | display the question's text. 127 | """ 128 | past_question = create_question(question_text='Past Question.', days=-5) 129 | response = self.client.get(reverse('polls:detail', args=(past_question.id,))) 130 | self.assertContains(response, past_question.question_text, status_code=200) 131 | -------------------------------------------------------------------------------- /_examples/django1.9/polls/tests.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | 3 | from django.utils import timezone 4 | from django.test import TestCase 5 | 6 | from django.core.urlresolvers import reverse 7 | 8 | from .models import Question 9 | 10 | 11 | def create_question(question_text, days): 12 | """ 13 | Creates a question with the given `question_text` published the given 14 | number of `days` offset to now (negative for questions published 15 | in the past, positive for questions that have yet to be published). 16 | """ 17 | time = timezone.now() + datetime.timedelta(days=days) 18 | return Question.objects.create(question_text=question_text, pub_date=time) 19 | 20 | 21 | class QuestionMethodTests(TestCase): 22 | 23 | def test_was_published_recently_with_future_question(self): 24 | """ 25 | was_published_recently() should return False for questions whose 26 | pub_date is in the future. 27 | """ 28 | time = timezone.now() + datetime.timedelta(days=30) 29 | future_question = Question(pub_date=time) 30 | self.assertEqual(future_question.was_published_recently(), False) 31 | 32 | def test_was_published_recently_with_old_question(self): 33 | """ 34 | was_published_recently() should return False for questions whose 35 | pub_date is older than 1 day. 36 | """ 37 | time = timezone.now() - datetime.timedelta(days=30) 38 | old_question = Question(pub_date=time) 39 | self.assertEqual(old_question.was_published_recently(), False) 40 | 41 | def test_was_published_recently_with_recent_question(self): 42 | """ 43 | was_published_recently() should return True for questions whose 44 | pub_date is within the last day. 45 | """ 46 | time = timezone.now() - datetime.timedelta(hours=1) 47 | recent_question = Question(pub_date=time) 48 | self.assertEqual(recent_question.was_published_recently(), True) 49 | 50 | 51 | class QuestionViewTests(TestCase): 52 | 53 | def test_index_view_with_no_questions(self): 54 | """ 55 | If no questions exist, an appropriate message should be displayed. 56 | """ 57 | response = self.client.get(reverse('polls:index')) 58 | self.assertEqual(response.status_code, 200) 59 | self.assertContains(response, "No polls are available.") 60 | self.assertQuerysetEqual(response.context['latest_question_list'], []) 61 | 62 | def test_index_view_with_a_past_question(self): 63 | """ 64 | Questions with a pub_date in the past should be displayed on the 65 | index page. 66 | """ 67 | create_question(question_text="Past question.", days=-30) 68 | response = self.client.get(reverse('polls:index')) 69 | self.assertQuerysetEqual( 70 | response.context['latest_question_list'], 71 | [''] 72 | ) 73 | 74 | def test_index_view_with_a_future_question(self): 75 | """ 76 | Questions with a pub_date in the future should not be displayed on 77 | the index page. 78 | """ 79 | create_question(question_text="Future question.", days=30) 80 | response = self.client.get(reverse('polls:index')) 81 | self.assertContains( 82 | response, "No polls are available.", status_code=200 83 | ) 84 | self.assertQuerysetEqual(response.context['latest_question_list'], []) 85 | 86 | def test_index_view_with_future_question_and_past_question(self): 87 | """ 88 | Even if both past and future questions exist, only past questions 89 | should be displayed. 90 | """ 91 | create_question(question_text="Past question.", days=-30) 92 | create_question(question_text="Future question.", days=30) 93 | response = self.client.get(reverse('polls:index')) 94 | self.assertQuerysetEqual( 95 | response.context['latest_question_list'], 96 | [''] 97 | ) 98 | 99 | def test_index_view_with_two_past_questions(self): 100 | """ 101 | The questions index page may display multiple questions. 102 | """ 103 | create_question(question_text="Past question 1.", days=-30) 104 | create_question(question_text="Past question 2.", days=-5) 105 | response = self.client.get(reverse('polls:index')) 106 | self.assertQuerysetEqual( 107 | response.context['latest_question_list'], 108 | ['', ''] 109 | ) 110 | 111 | 112 | class QuestionIndexDetailTests(TestCase): 113 | 114 | def test_detail_view_with_a_future_question(self): 115 | """ 116 | The detail view of a question with a pub_date in the future should 117 | return a 404 not found. 118 | """ 119 | future_question = create_question(question_text='Future question.', days=5) 120 | response = self.client.get(reverse('polls:detail', args=(future_question.id,))) 121 | self.assertEqual(response.status_code, 404) 122 | 123 | def test_detail_view_with_a_past_question(self): 124 | """ 125 | The detail view of a question with a pub_date in the past should 126 | display the question's text. 127 | """ 128 | past_question = create_question(question_text='Past Question.', days=-5) 129 | response = self.client.get(reverse('polls:detail', args=(past_question.id,))) 130 | self.assertContains(response, past_question.question_text, status_code=200) 131 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to Contribute 2 | 3 | There are many ways you can help contribute to this project. Contributing 4 | code, writing documentation, reporting bugs, as well as reading and providing 5 | feedback on issues and pull requests, all are valid and necessary ways to 6 | help. 7 | 8 | ## Committing Code 9 | 10 | The great thing about using a distributed versioning control system like git 11 | is that everyone becomes a committer. When other people write good patches 12 | it makes it very easy to include their fixes/features and give them proper 13 | credit for the work. 14 | 15 | We recommend that you do all your work in a separate branch. When you 16 | are ready to work on a bug or a new feature create yourself a new branch. The 17 | reason why this is important is you can commit as often you like. When you are 18 | ready you can merge in the change. Let's take a look at a common workflow: 19 | 20 | git checkout -b task-566 21 | ... fix and git commit often ... 22 | git push origin task-566 23 | 24 | The reason we have created two new branches is to stay off of `master`. 25 | Keeping master clean of only upstream changes makes yours and ours lives 26 | easier. You can then send us a pull request for the fix/feature. Then we can 27 | easily review it and merge it when ready. 28 | 29 | 30 | ### Writing Commit Messages 31 | 32 | Writing a good commit message makes it simple for us to identify what your 33 | commit does from a high-level. There are some basic guidelines we'd like to 34 | ask you to follow. 35 | 36 | A critical part is that you keep the **first** line as short and sweet 37 | as possible. This line is important because when git shows commits and it has 38 | limited space or a different formatting option is used the first line becomes 39 | all someone might see. If your change isn't something non-trivial or there 40 | reasoning behind the change is not obvious, then please write up an extended 41 | message explaining the fix, your rationale, and anything else relevant for 42 | someone else that might be reviewing the change. Lastly, if there is a 43 | corresponding issue in Github issues for it, use the final line to provide 44 | a message that will link the commit message to the issue and auto-close it 45 | if appropriate. 46 | 47 | Add ability to travel back in time 48 | 49 | You need to be driving 88 miles per hour to generate 1.21 gigawatts of 50 | power to properly use this feature. 51 | 52 | Fixes #88 53 | 54 | 55 | ## Coding style 56 | 57 | When writing code to be included in django-slowtests keep our style in mind: 58 | 59 | * Follow [PEP8](http://www.python.org/dev/peps/pep-0008/) there are some 60 | cases where we do not follow PEP8. It is an excellent starting point. 61 | * Follow [Django's coding style](http://docs.djangoproject.com/en/dev/internals/contributing/#coding-style) 62 | we're pretty much in agreement on Django style outlined there. 63 | 64 | We would like to enforce a few more strict guides not outlined by PEP8 or 65 | Django's coding style: 66 | 67 | * PEP8 tries to keep line length at 80 characters. We follow it when we can, 68 | but not when it makes a line harder to read. It is okay to go a little bit 69 | over 80 characters if not breaking the line improves readability. 70 | * Use double quotes not single quotes. Single quotes are allowed in cases 71 | where a double quote is needed in the string. This makes the code read 72 | cleaner in those cases. 73 | * Blank lines should contain no whitespace. 74 | * Docstrings always use three double quotes on a line of their own, so, for 75 | example, a single line docstring should take up three lines not one. 76 | * Imports are grouped specifically and ordered alphabetically. This is shown 77 | in the example below. 78 | * Always use `reverse` and never `@models.permalink`. 79 | * Tuples should be reserved for positional data structures and not used 80 | where a list is more appropriate. 81 | * URL patterns should use the `url()` function rather than a tuple. 82 | 83 | Here is an example of these rules applied: 84 | 85 | # first set of imports are stdlib imports 86 | # non-from imports go first then from style import in their own group 87 | import csv 88 | 89 | # second set of imports are Django imports with contrib in their own 90 | # group. 91 | from django.core.urlresolvers import reverse 92 | from django.db import models 93 | from django.utils import timezone 94 | from django.utils.translation import ugettext_lazy as _ 95 | 96 | from django.contrib.auth.models import User 97 | 98 | # third set of imports are external apps (if applicable) 99 | from tagging.fields import TagField 100 | 101 | # fourth set of imports are local apps 102 | from .fields import MarkupField 103 | 104 | 105 | class Task(models.Model): 106 | """ 107 | A model for storing a task. 108 | """ 109 | 110 | creator = models.ForeignKey(User) 111 | created = models.DateTimeField(default=timezone.now) 112 | modified = models.DateTimeField(default=timezone.now) 113 | 114 | objects = models.Manager() 115 | 116 | class Meta: 117 | verbose_name = _("task") 118 | verbose_name_plural = _("tasks") 119 | 120 | def __unicode__(self): 121 | return self.summary 122 | 123 | def save(self, **kwargs): 124 | self.modified = datetime.now() 125 | super(Task, self).save(**kwargs) 126 | 127 | def get_absolute_url(self): 128 | return reverse("task_detail", kwargs={"task_id": self.pk}) 129 | 130 | # custom methods 131 | 132 | 133 | class TaskComment(models.Model): 134 | # ... you get the point ... 135 | pass 136 | 137 | 138 | ## Pull Requests 139 | 140 | Please keep your pull requests focused on one specific thing only. If you 141 | have a number of contributions to make, then please send seperate pull 142 | requests. It is much easier on maintainers to receive small, well defined, 143 | pull requests, than it is to have a single large one that batches up a 144 | lot of unrelated commits. 145 | 146 | If you ended up making multiple commits for one logical change, please 147 | rebase into a single commit. 148 | 149 | git rebase -i HEAD~10 # where 10 is the number of commits back you need 150 | 151 | This will pop up an editor with your commits and some instructions you want 152 | to squash commits down by replacing 'pick' with 's' to have it combined with 153 | the commit before it. You can squash multiple ones at the same time. 154 | 155 | When you save and exit the text editor where you were squashing commits, git 156 | will squash them down and then present you with another editor with commit 157 | messages. Choose the one to apply to the squashed commit (or write a new 158 | one entirely.) Save and exit will complete the rebase. Use a forced push to 159 | your fork. 160 | 161 | git push -f 162 | -------------------------------------------------------------------------------- /django_slowtests/testrunner.py: -------------------------------------------------------------------------------- 1 | import glob 2 | import json 3 | import time 4 | import os 5 | import os.path 6 | from unittest import TestSuite, TestLoader 7 | from unittest.runner import TextTestRunner, registerResult 8 | from unittest.suite import _isnotsuite 9 | from django.test.runner import DiscoverRunner 10 | from django.conf import settings 11 | 12 | 13 | try: # pragma: no cover 14 | import freezegun 15 | 16 | def _time(): 17 | return freezegun.api.real_time() 18 | except ImportError: # pragma: no cover 19 | def _time(): 20 | return time.time() 21 | 22 | 23 | class TimingTextTestRunner(TextTestRunner): 24 | def run(self, test): 25 | "Run the given test case or test suite." 26 | result = self._makeResult() 27 | registerResult(result) 28 | result.failfast = self.failfast 29 | result.buffer = self.buffer 30 | startTime = time.time() 31 | startTestRun = getattr(result, 'startTestRun', None) 32 | if startTestRun is not None: 33 | startTestRun() 34 | try: 35 | test(result) 36 | finally: 37 | stopTestRun = getattr(result, 'stopTestRun', None) 38 | if stopTestRun is not None: 39 | stopTestRun() 40 | stopTime = time.time() 41 | timeTaken = stopTime - startTime 42 | result.printErrors() 43 | if hasattr(result, 'separator2'): 44 | self.stream.writeln(result.separator2) 45 | run = result.testsRun 46 | self.stream.writeln("Ran %d test%s in %.3fs" % 47 | (run, run != 1 and "s" or "", timeTaken)) 48 | self.stream.writeln() 49 | 50 | expectedFails = unexpectedSuccesses = skipped = 0 51 | try: 52 | results = map(len, (result.expectedFailures, 53 | result.unexpectedSuccesses, 54 | result.skipped)) 55 | except AttributeError: 56 | pass 57 | else: 58 | expectedFails, unexpectedSuccesses, skipped = results 59 | 60 | infos = [] 61 | if not result.wasSuccessful(): 62 | self.stream.write("FAILED") 63 | failed, errored = map(len, (result.failures, result.errors)) 64 | if failed: 65 | infos.append("failures=%d" % failed) 66 | if errored: 67 | infos.append("errors=%d" % errored) 68 | else: 69 | self.stream.write("OK") 70 | if skipped: 71 | infos.append("skipped=%d" % skipped) 72 | if expectedFails: 73 | infos.append("expected failures=%d" % expectedFails) 74 | if unexpectedSuccesses: 75 | infos.append("unexpected successes=%d" % unexpectedSuccesses) 76 | if infos: 77 | self.stream.writeln(" (%s)" % (", ".join(infos),)) 78 | else: 79 | self.stream.write("\n") 80 | # save timeTaken for later use in report 81 | result.timeTaken = timeTaken 82 | return result 83 | 84 | 85 | class TimingSuite(TestSuite): 86 | """ 87 | TestSuite wrapper that times each test. 88 | """ 89 | def save_test_time(self, test_name, duration): 90 | file_prefix = getattr( 91 | settings, 'TESTS_REPORT_TMP_FILES_PREFIX', '_tests_report_' 92 | ) 93 | file_name = '{}{}.txt'.format(file_prefix, os.getpid()) 94 | with open(file_name, "a+") as f: 95 | f.write("{name},{duration:.6f}\n".format( 96 | name=test_name, duration=duration 97 | )) 98 | 99 | def run(self, result, debug=False): 100 | topLevel = False 101 | if getattr(result, '_testRunEntered', False) is False: 102 | result._testRunEntered = topLevel = True 103 | 104 | for test in self: 105 | if result.shouldStop: 106 | break 107 | 108 | start_time = _time() 109 | 110 | if _isnotsuite(test): 111 | self._tearDownPreviousClass(test, result) 112 | self._handleModuleFixture(test, result) 113 | self._handleClassSetUp(test, result) 114 | result._previousTestClass = test.__class__ 115 | 116 | if (getattr(test.__class__, '_classSetupFailed', False) or 117 | getattr(result, '_moduleSetUpFailed', False)): 118 | continue 119 | 120 | if not debug: 121 | test(result) 122 | else: 123 | test.debug() 124 | self.save_test_time(str(test), _time() - start_time) 125 | 126 | if topLevel: 127 | self._tearDownPreviousClass(None, result) 128 | self._handleModuleTearDown(result) 129 | result._testRunEntered = False 130 | 131 | return result 132 | 133 | 134 | class TimingLoader(TestLoader): 135 | suiteClass = TimingSuite 136 | 137 | 138 | class DiscoverSlowestTestsRunner(DiscoverRunner): 139 | """ 140 | Runner that extends Django's DiscoverRunner to time the tests. 141 | """ 142 | test_suite = TimingSuite 143 | test_loader = TimingLoader() 144 | test_runner = TimingTextTestRunner 145 | 146 | def __init__(self, report_path=None, generate_report=False, **kwargs): 147 | super(DiscoverSlowestTestsRunner, self).__init__(**kwargs) 148 | self.report_path = report_path[0] if report_path else None 149 | self.should_generate_report = generate_report 150 | 151 | @classmethod 152 | def add_arguments(cls, parser): 153 | DiscoverRunner.add_arguments(parser) 154 | parser.add_argument( 155 | '--slowreport', 156 | action='store_true', 157 | dest='generate_report', 158 | help='Generate a report of slowest tests', 159 | ) 160 | parser.add_argument( 161 | '--slowreportpath', 162 | nargs=1, 163 | dest='report_path', 164 | help='Save report to given file' 165 | ) 166 | 167 | def generate_report(self, test_results, result): 168 | test_result_count = len(test_results) 169 | SLOW_TEST_THRESHOLD_MS = getattr(settings, 'SLOW_TEST_THRESHOLD_MS', 0) 170 | 171 | if self.report_path: 172 | data = { 173 | 'threshold': SLOW_TEST_THRESHOLD_MS, 174 | 'slower_tests': [ 175 | {"name": func_name, "execution_time": float(timing)} 176 | for func_name, timing in test_results 177 | ], 178 | 'nb_tests': result.testsRun, 179 | 'nb_failed': len(result.errors + result.failures), 180 | 'total_execution_time': result.timeTaken, 181 | } 182 | with open(self.report_path, 'w') as outfile: 183 | json.dump(data, outfile) 184 | else: 185 | if test_result_count: 186 | if SLOW_TEST_THRESHOLD_MS: 187 | print("\n{r} slowest tests over {ms}ms:".format( 188 | r=test_result_count, ms=SLOW_TEST_THRESHOLD_MS) 189 | ) 190 | else: 191 | print("\n{r} slowest tests:".format(r=test_result_count)) 192 | 193 | for func_name, timing in test_results: 194 | print("{t:.4f}s {f}".format(f=func_name, t=float(timing))) 195 | 196 | if not len(test_results) and SLOW_TEST_THRESHOLD_MS: 197 | print("\nNo tests slower than {ms}ms".format( 198 | ms=SLOW_TEST_THRESHOLD_MS) 199 | ) 200 | 201 | def read_timing_files(self): 202 | file_prefix = getattr( 203 | settings, 'TESTS_REPORT_TMP_FILES_PREFIX', '_tests_report_' 204 | ) 205 | files = glob.glob("{}*.txt".format(file_prefix)) 206 | for report_file in files: 207 | yield report_file 208 | 209 | def get_timings(self): 210 | timings = [] 211 | for report_file in self.read_timing_files(): 212 | with open(report_file, "r") as f: 213 | for line in f: 214 | name, duration = line.strip('\n').split(',') 215 | timings.append((name, float(duration))) 216 | os.remove(report_file) 217 | return timings 218 | 219 | def remove_timing_tmp_files(self): 220 | for report_file in self.read_timing_files(): 221 | os.remove(report_file) 222 | 223 | def suite_result(self, suite, result): 224 | return_value = super(DiscoverSlowestTestsRunner, self).suite_result( 225 | suite, result 226 | ) 227 | NUM_SLOW_TESTS = getattr(settings, 'NUM_SLOW_TESTS', 10) 228 | SLOW_TEST_THRESHOLD_MS = getattr(settings, 'SLOW_TEST_THRESHOLD_MS', 0) 229 | 230 | should_generate_report = ( 231 | getattr(settings, 'ALWAYS_GENERATE_SLOW_REPORT', True) or 232 | self.should_generate_report 233 | ) 234 | if not should_generate_report: 235 | self.remove_timing_tmp_files() 236 | return return_value 237 | 238 | # Grab slowest tests 239 | timings = self.get_timings() 240 | by_time = sorted(timings, key=lambda x: x[1], reverse=True) 241 | if by_time is not None: 242 | by_time = by_time[:NUM_SLOW_TESTS] 243 | test_results = by_time 244 | 245 | if SLOW_TEST_THRESHOLD_MS: 246 | # Filter tests by threshold 247 | test_results = [] 248 | 249 | for result in by_time: 250 | # Convert test time from seconds to miliseconds for comparison 251 | result_time_ms = result[1] * 1000 252 | 253 | # If the test was under the threshold 254 | # don't show it to the user 255 | if result_time_ms < SLOW_TEST_THRESHOLD_MS: 256 | continue 257 | 258 | test_results.append(result) 259 | 260 | self.generate_report(test_results, result) 261 | return return_value 262 | --------------------------------------------------------------------------------