2016-03-08 2 views
0

У нас есть большой проект Django с приблизительно 10 000 тестов Django + Nose. Мы очень редко используем транзакции в нашей кодовой базе. Вероятно, 99% нашей кодовой базы не используют транзакции. Проект находится на Django 1.5.8 и Django Nose 1.4.1. (Да, я знаю, что это очень старый. В настоящее время мы занимаемся разработкой проекта Django 1.6 на 18 месяцев, но он еще не завершен. Итак, если решение моей проблемы - «обновить Django», m будет нуждаться в способе его исправления, потому что эта проблема происходит сейчас, и это будет еще несколько месяцев, прежде чем мы сможем закончить обновление Django.)Тест-драйв для носа не настроен на «Управляемый блок транзакции, завершенный с ожиданием COMMIT/ROLLBACK»

Сегодня мы столкнулись с новой ошибкой, которую мы никогда не видели. Мы добавили новую базу данных (и необходимые настройки DATABASES['geo']), которая содержит большой статический набор данных, который приложение не обновляет. Это база данных, доступная только для чтения, которая живет в MySQL. Как и все другие наши базы данных, Django Nose начал создавать тестовую копию новой базы данных (и уничтожить указанную тестовую базу данных) в начале каждого тестового прогона. Это вызвало множество проблем, включая проблемы с дисковым пространством и проблемы с потерями времени, но тесты выполнялись и проходили.

Для решения этой проблемы мы добавили 'TEST_MIRROR': 'geo' в настройки DATABASES['geo']. И тут началась эта головная боль. Только что изменение привело к крохотной случайной части наших тестов неудовлетворительную каждый тестовый прогон:

<nose.suite.ContextSuite context=TestFacebookApiVersion>:setup 
<nose.suite.ContextSuite context=RegisterPageTests>:setup 
<nose.suite.ContextSuite context=CommonCeleryTasks>:setup 
<nose.suite.ContextSuite context=CommonCeleryTestTasks>:setup 
<nose.suite.ContextSuite context=S3PublishTestCase>:setup 
<nose.suite.ContextSuite context=TestCEP>:setup 
<nose.suite.ContextSuite context=AdbInvitesJsonTests>:setup 

Ошибка и трассировки стека идентичны для каждого теста:

Transaction managed block ended with pending COMMIT/ROLLBACK 
Traceback (most recent call last): 
    File "/var/lib/jenkins/workspace/my_workspace/my_project/lib/python2.7/site-packages/nose/suite.py", line 209, in run 
    self.setUp() 
    File "/var/lib/jenkins/workspace/my_workspace/my_project/lib/python2.7/site-packages/nose/suite.py", line 292, in setUp 
    self.setupContext(ancestor) 
    File "/var/lib/jenkins/workspace/my_workspace /my_project/lib/python2.7/site-packages/nose/suite.py", line 315, in setupContext 
    try_run(context, names) 
    File "/var/lib/jenkins/workspace/my_workspace/my_project/lib/python2.7/site-packages/nose/util.py", line 471, in try_run 
    return func() 
    File "/var/lib/jenkins/workspace/my_workspace/my_project/lib/python2.7/site-packages/django_nose/testcases.py", line 43, in setUpClass 
    if not test.testcases.connections_support_transactions(): 
    File "/var/lib/jenkins/workspace/my_workspace /my_project/lib/python2.7/site-packages/django/test/testcases.py", line 827, in connections_support_transactions 
    for conn in connections.all()) 
    File "/var/lib/jenkins/workspace/my_workspace/my_project/lib/python2.7/site-packages/django/test/testcases.py", line 827, in <genexpr> 
    for conn in connections.all()) 
    File "/var/lib/jenkins/workspace/my_workspace/my_project/lib/python2.7/site-packages/django/utils/functional.py", line 45, in __get__ 
    res = instance.__dict__[self.func.__name__] = self.func(instance) 
    File "/var/lib/jenkins/workspace/my_workspace/my_project/lib/python2.7/site-packages/django/db/backends/__init__.py", line 455, in supports_transactions 
    self.connection.leave_transaction_management() 
    File "/var/lib/jenkins/workspace/my_workspace/my_project/lib/python2.7/site-packages/django/db/backends/__init__.py", line 138, in leave_transaction_management 
    "Transaction managed block ended with pending COMMIT/ROLLBACK") 

И, что еще хуже, небольшое количество тестовых примеров, которые терпят неудачу, различны каждый раз. Вот неисправности во второй раз, когда я его запустил:

<nose.suite.ContextSuite context=BaseTemplateContainerTests>:setup 
<nose.suite.ContextSuite context=MessageServiceNotifierTests>:setup 
<nose.suite.ContextSuite context=TestFormatShortAddress>:setup 
<nose.suite.ContextSuite context=RevisionableTestCase>:setup 
<nose.suite.ContextSuite context=TestSoaHelpers>:setup 
<nose.suite.ContextSuite context=TestDateUtils>:setup 

И так далее.

Как вы можете видеть из трассировки стека, выполнение даже не приводит к исходному коду. Он не работает в исходном коде Django Nose, прежде чем наши тестовые примеры даже начнут выполняться. И, опять же, это всего лишь крошечная часть наших тестов. Другие 9,600 + юнит-тесты все проходят с летающими цветами.

Я в затруднении, что делать. Я не намеренно создаю какие-либо транзакции, и мне не имеет смысла, что добавление 'TEST_MIRROR': 'geo' в конфигурацию DATABASES['geo'] вызовет эту проблему, но это так.

Как это исправить?

+1

Вы попробуйте использовать стандартный тест Джанго бегун? – kmmbvnr

ответ

1

Ну, это мне потребовалось много отладки, но я понял, проблема ...

Наши 10000 тестов занять очень долгое время, чтобы бежать, если не запускать их в параллельных процессах. Поэтому мы используем инструмент, который разбивает тесты на нос на 20 параллельных процессов и запускает тесты в группах. (Это займет примерно 20 минут, но это лучше, чем почти два часа.)

Мы используем FastFixtureTestCase, который распространяется на TransactionTestCase. В начале каждого теста FastFixtureTestCase звонки django.test.testcases: connections_support_transactions(). Эта функция перебирает все соединения DATABASES и вызывает на каждом из них supports_transactions. Моя ошибка предполагала, что supports_transactions должен быть неотъемлемо безопасной. Это не.

supports_transactions делает следующие вещи:

  1. создает новую таблицу
  2. коммиты
  3. Вставки значения в эту таблицу
  4. откатывает
  5. Выбирает количество строк в этой таблице
  6. Капли стола
  7. Commits
  8. Возвращает True, если количество строк в таблице равно 0 (это означает, что откат удался, поэтому транзакции должны поддерживаться).

Это не безопасно. Это очень опасно. Никакие два процесса или серверы не могут одновременно запускать это приложение в отношении одной и той же базы данных. Если два или более процесса запускают эту функцию в одно и то же время, в лучшем случае один возвращает True, а другой (-ы) вызовет исключение. В худшем случае все вызовут исключение.

В моем случае, поскольку у нас так много тестовых примеров, в большинстве случаев процессы избегали выполнения connections_support_transactions в то же время, но когда они это делали, это приводило к небольшому количеству случайных сбоев, которые были разными каждый раз ,

Одним из возможных решений является использование SimpleTestCase вместо FastFixtureTestCase, так как @kmmbvnr указано. Однако это не вариант для нас, так как вся наша тестовая инфраструктура зависит от того, что делает FastFixtureTestCase для остальной части наших баз данных. Таким образом, вместо этого, я отменяю supports_transactions только для статических, общей базы данных с помощью следующей строки коды, и ошибки ушли:

connections['geo'].features.supports_transactions = True 

 Смежные вопросы

  • Нет связанных вопросов^_^