База данных все еще используется после теста на селен в Джанго - PullRequest
0 голосов
/ 15 ноября 2018

У меня есть проект Django, в котором я начинаю писать тесты Selenium. Первый выглядит так:

from django.contrib.staticfiles.testing import StaticLiveServerTestCase
from selenium import webdriver
from selenium.webdriver.common.keys import Keys

from core.models import User
from example import settings

BACH_EMAIL = "johann.sebastian.bach@classics.com"
PASSWORD = "password"


class TestImportCRMData(StaticLiveServerTestCase):
    @classmethod
    def setUpClass(cls):
        super().setUpClass()
        cls.webdriver = webdriver.Chrome()
        cls.webdriver.implicitly_wait(10)

    @classmethod
    def tearDownClass(cls):
        cls.webdriver.close()
        cls.webdriver.quit()
        super().tearDownClass()

    def setUp(self):
        self.admin = User.objects.create_superuser(email=BACH_EMAIL, password=PASSWORD)

    def test_admin_tool(self):
        self.webdriver.get(f"http://{settings.ADMIN_HOST}:{self.server_thread.port}/admin")

        self.webdriver.find_element_by_id("id_username").send_keys(BACH_EMAIL)
        self.webdriver.find_element_by_id("id_password").send_keys(PASSWORD)
        self.webdriver.find_element_by_id("id_password").send_keys(Keys.RETURN)
        self.webdriver.find_element_by_link_text("Users").click()

Когда я запускаю его, тестовый проход, но все еще заканчивается с этой ошибкой:

Traceback (most recent call last):
  File "C:\Users\pupeno\Documents\Eligible\code\example\venv\lib\site-packages\django\db\backends\utils.py", line 83, in _execute
    return self.cursor.execute(sql)
psycopg2.OperationalError: database "test_example" is being accessed by other users
DETAIL:  There is 1 other session using the database.


The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "C:\Program Files\JetBrains\PyCharm 2018.2.4\helpers\pycharm\django_test_manage.py", line 168, in <module>
    utility.execute()
  File "C:\Program Files\JetBrains\PyCharm 2018.2.4\helpers\pycharm\django_test_manage.py", line 142, in execute
    _create_command().run_from_argv(self.argv)
  File "C:\Users\pupeno\Documents\Eligible\code\example\venv\lib\site-packages\django\core\management\commands\test.py", line 26, in run_from_argv
    super().run_from_argv(argv)
  File "C:\Users\pupeno\Documents\Eligible\code\example\venv\lib\site-packages\django\core\management\base.py", line 316, in run_from_argv
    self.execute(*args, **cmd_options)
  File "C:\Users\pupeno\Documents\Eligible\code\example\venv\lib\site-packages\django\core\management\base.py", line 353, in execute
    output = self.handle(*args, **options)
  File "C:\Program Files\JetBrains\PyCharm 2018.2.4\helpers\pycharm\django_test_manage.py", line 104, in handle
    failures = TestRunner(test_labels, **options)
  File "C:\Program Files\JetBrains\PyCharm 2018.2.4\helpers\pycharm\django_test_runner.py", line 255, in run_tests
    extra_tests=extra_tests, **options)
  File "C:\Program Files\JetBrains\PyCharm 2018.2.4\helpers\pycharm\django_test_runner.py", line 156, in run_tests
    return super(DjangoTeamcityTestRunner, self).run_tests(test_labels, extra_tests, **kwargs)
  File "C:\Users\pupeno\Documents\Eligible\code\example\venv\lib\site-packages\django\test\runner.py", line 607, in run_tests
    self.teardown_databases(old_config)
  File "C:\Users\pupeno\Documents\Eligible\code\example\venv\lib\site-packages\django\test\runner.py", line 580, in teardown_databases
    keepdb=self.keepdb,
  File "C:\Users\pupeno\Documents\Eligible\code\example\venv\lib\site-packages\django\test\utils.py", line 297, in teardown_databases
    connection.creation.destroy_test_db(old_name, verbosity, keepdb)
  File "C:\Users\pupeno\Documents\Eligible\code\example\venv\lib\site-packages\django\db\backends\base\creation.py", line 257, in destroy_test_db
    self._destroy_test_db(test_database_name, verbosity)
  File "C:\Users\pupeno\Documents\Eligible\code\example\venv\lib\site-packages\django\db\backends\base\creation.py", line 274, in _destroy_test_db
    % self.connection.ops.quote_name(test_database_name))
  File "C:\Users\pupeno\Documents\Eligible\code\example\venv\lib\site-packages\django\db\backends\utils.py", line 68, in execute
    return self._execute_with_wrappers(sql, params, many=False, executor=self._execute)
  File "C:\Users\pupeno\Documents\Eligible\code\example\venv\lib\site-packages\django\db\backends\utils.py", line 77, in _execute_with_wrappers
    return executor(sql, params, many, context)
  File "C:\Users\pupeno\Documents\Eligible\code\example\venv\lib\site-packages\django\db\backends\utils.py", line 85, in _execute
    return self.cursor.execute(sql, params)
  File "C:\Users\pupeno\Documents\Eligible\code\example\venv\lib\site-packages\django\db\utils.py", line 89, in __exit__
    raise dj_exc_value.with_traceback(traceback) from exc_value
  File "C:\Users\pupeno\Documents\Eligible\code\example\venv\lib\site-packages\django\db\backends\utils.py", line 83, in _execute
    return self.cursor.execute(sql)
django.db.utils.OperationalError: database "test_example" is being accessed by other users
DETAIL:  There is 1 other session using the database.

Проблема, конечно, в том, что при следующем запуске тестов база данных все еще существует, поэтому тесты не запускаются без подтверждения удаления базы данных.

Если я закомментирую последнюю строку:

self.webdriver.find_element_by_link_text("Users").click()

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

select * from pg_stat_activity where datname = 'test_example';

100123  test_example    29892   16393   pupeno  ""  ::1     61967   2018-11-15 17:28:19.552431      2018-11-15 17:28:19.562398  2018-11-15 17:28:19.564623          idle            SELECT "core_user"."id", "core_user"."password", "core_user"."last_login", "core_user"."is_superuser", "core_user"."email", "core_user"."is_staff", "core_user"."is_active", "core_user"."date_joined" FROM "core_user" WHERE "core_user"."id" = 1
100123  test_example    33028   16393   pupeno  ""  ::1     61930   2018-11-15 17:28:18.792466      2018-11-15 17:28:18.843383  2018-11-15 17:28:18.851828          idle            SELECT "django_admin_log"."id", "django_admin_log"."action_time", "django_admin_log"."user_id", "django_admin_log"."content_type_id", "django_admin_log"."object_id", "django_admin_log"."object_repr", "django_admin_log"."action_flag", "django_admin_log"."change_message", "core_user"."id", "core_user"."password", "core_user"."last_login", "core_user"."is_superuser", "core_user"."email", "core_user"."is_staff", "core_user"."is_active", "core_user"."date_joined", "django_content_type"."id", "django_content_type"."app_label", "django_content_type"."model" FROM "django_admin_log" INNER JOIN "core_user" ON ("django_admin_log"."user_id" = "core_user"."id") LEFT OUTER JOIN "django_content_type" ON ("django_admin_log"."content_type_id" = "django_content_type"."id") WHERE "django_admin_log"."user_id" = 1 ORDER BY "django_admin_log"."action_time" DESC  LIMIT 10
100123  test_example    14128   16393   pupeno  ""  ::1     61988   2018-11-15 17:28:19.767225      2018-11-15 17:28:19.776150  2018-11-15 17:28:19.776479          idle            SELECT "core_firm"."id", "core_firm"."name", "core_firm"."host_name" FROM "core_firm" WHERE "core_firm"."id" = 1
100123  test_example    9604    16393   pupeno  ""  ::1     61960   2018-11-15 17:28:19.469197      2018-11-15 17:28:19.478775  2018-11-15 17:28:19.478788          idle            COMMIT

Я пытался найти минимально воспроизводимый пример этой проблемы, но пока мне это не удалось.

Есть идеи, что может быть причиной этого или как узнать больше о том, в чем может быть проблема?

Ответы [ 3 ]

0 голосов
/ 12 июля 2019

Давно сообщалось о проблеме с тем же самым

https://code.djangoproject.com/ticket/22414

Единственное, что вам нужно убедиться, это то, что CONN_MAX_AGE имеет значение 0, а не None

Кроме того, вы можете использовать что-то вроде ниже в вашем демонтаже

@classmethod
def tearDownClass(cls):
    # Workaround for https://code.djangoproject.com/ticket/22414
    # Persistent connections not closed by LiveServerTestCase, preventing dropping test databases
    # https://github.com/cjerdonek/django/commit/b07fbca02688a0f8eb159f0dde132e7498aa40cc
    def close_sessions(conn):
        close_sessions_query = """
            SELECT pg_terminate_backend(pg_stat_activity.pid) FROM pg_stat_activity WHERE
                datname = current_database() AND
                pid <> pg_backend_pid();
        """
        with conn.cursor() as cursor:
            try:
                cursor.execute(close_sessions_query)
            except OperationalError:
                # We get kicked out after closing.
                pass

    for alias in connections:
        connections[alias].close()
        close_sessions(connections[alias])

    print "Forcefully closed database connections."

Над кодом ниже снизу URL

https://github.com/cga-harvard/Hypermap-Registry/blob/cd4efad61f18194ddab2c662aa431aa21dec03f4/hypermap/tests/test_csw.py

0 голосов
/ 18 июля 2019

Это сообщение об ошибке ...

django.db.utils.OperationalError: database "test_example" is being accessed by other users
DETAIL:  There is 1 other session using the database.

... подразумевает, что существует существующий сеанс, использующий базу данных, и новый сеанс не может получить доступ к базе данных.


Еще немного информации относительно Django версии , База данных типа и версии вместе с Selenium *Версии 1018 *, ChromeDriver и Chrome помогли бы нам лучше отладить эту проблему.

Однако вам необходимо позаботиться о нескольких вещах с точки зрения Selenium следующим образом:

  • Поскольку вы начинаете новый сеанс вПри следующем запуске тестов вам нужно удалить строку кода cls.webdriver.close(), так как следующей строки кода cls.webdriver.quit() будет достаточно для завершения существующего сеанса.В соответствии с лучшими практиками, вы должны вызывать метод quit() в tearDown() {}.Вызов quit() DELETE s текущего сеанса просмотра путем отправки "quit" команды с {"flags": ["eForceQuit"]} и, наконец,отправляет запрос GET на / shutdown EndPoint.Вот пример ниже:

    1503397488598   webdriver::server   DEBUG   -> DELETE /session/8e457516-3335-4d3b-9140-53fb52aa8b74 
    1503397488607   geckodriver::marionette TRACE   -> 37:[0,4,"quit",{"flags":["eForceQuit"]}]
    1503397488821   webdriver::server   DEBUG   -> GET /shutdown
    
  • Таким образом, при вызове метода quit() сеанс Web Browser и экземпляр WebDriver полностью уничтожаются.Следовательно, вам не нужно включать какие-либо дополнительные шаги, которые будут непроизводительными.

Подробное обсуждение можно найти в Selenium: Как остановить воздействие на процесс geckodriverПамять ПК без вызова driver.quit ()?

  • Текущая сборка Веб-приложения постепенно движется к динамически отображаемому HTML DOM так что взаимодействие с элементами внутри DOM Tree , ImplicitWait уже не так эффективно, и вам нужно вместо этого использовать WebDriverWait .На этом этапе стоит отметить, что смешивание неявного ожидания и явного ожидания может вызвать непредсказуемое время ожидания
  • Так что вам нужно:

    • Удалить implicitly_wait(10):

      cls.webdriver.implicitly_wait(10)
      
    • Индуцировать WebDriverWait при взаимодействии с элементами:

      WebDriverWait(driver, 20).until(EC.element_to_be_clickable((By.ID, "id_username"))).send_keys(BACH_EMAIL)
      self.webdriver.find_element_by_id("id_password").send_keys(PASSWORD)
      self.webdriver.find_element_by_id("id_password").send_keys(Keys.RETURN)
      WebDriverWait(driver, 20).until(EC.element_to_be_clickable((By.LINK_TEXT, "Users"))).click()
      

Теперь, согласно обсуждению Постоянные соединения не закрыты LiveServerTestCase, предотвращая сброс тестовых баз данных эта проблема наблюдалась, сообщалась, обсуждалась в Djangov1,6 и исправлялась.Основная проблема:

Всякий раз, когда соединение PostgreSQL помечается как постоянное (CONN_MAX_AGE = None) и выполняется LiveServerTestCase, соединение с серверного потока никогда не закрывается, что приводит к невозможности отбросить тестбаза данных.

Именно поэтому вы видите:

select * from pg_stat_activity where datname = 'test_example';

100123  test_example    29892   16393   pupeno  ""  ::1     61967   2018-11-15 17:28:19.552431      2018-11-15 17:28:19.562398  2018-11-15 17:28:19.564623          idle            SELECT "core_user"."id", "core_user"."password", "core_user"."last_login", "core_user"."is_superuser", "core_user"."email", "core_user"."is_staff", "core_user"."is_active", "core_user"."date_joined" FROM "core_user" WHERE "core_user"."id" = 1
100123  test_example    33028   16393   pupeno  ""  ::1     61930   2018-11-15 17:28:18.792466      2018-11-15 17:28:18.843383  2018-11-15 17:28:18.851828          idle            SELECT "django_admin_log"."id", "django_admin_log"."action_time", "django_admin_log"."user_id", "django_admin_log"."content_type_id", "django_admin_log"."object_id", "django_admin_log"."object_repr", "django_admin_log"."action_flag", "django_admin_log"."change_message", "core_user"."id", "core_user"."password", "core_user"."last_login", "core_user"."is_superuser", "core_user"."email", "core_user"."is_staff", "core_user"."is_active", "core_user"."date_joined", "django_content_type"."id", "django_content_type"."app_label", "django_content_type"."model" FROM "django_admin_log" INNER JOIN "core_user" ON ("django_admin_log"."user_id" = "core_user"."id") LEFT OUTER JOIN "django_content_type" ON ("django_admin_log"."content_type_id" = "django_content_type"."id") WHERE "django_admin_log"."user_id" = 1 ORDER BY "django_admin_log"."action_time" DESC  LIMIT 10
100123  test_example    14128   16393   pupeno  ""  ::1     61988   2018-11-15 17:28:19.767225      2018-11-15 17:28:19.776150  2018-11-15 17:28:19.776479          idle            SELECT "core_firm"."id", "core_firm"."name", "core_firm"."host_name" FROM "core_firm" WHERE "core_firm"."id" = 1
100123  test_example    9604    16393   pupeno  ""  ::1     61960   2018-11-15 17:28:19.469197      2018-11-15 17:28:19.478775  2018-11-15 17:28:19.478788          idle            COMMIT

Кроме того, было замечено, что даже с CONN_MAX_AGE=None после LiveServerTestCase.tearDownClass() запрашивается PostgreSQL pg_stat_activity показывает длительное соединение в состоянии idle (которое было соединением, созданным предыдущим тестом в вашем случае).Таким образом, было совершенно очевидно, что холостые соединения не закрываются, когда заканчивается нить и игла всасывания находилась в положении:

  • LiveServerThread(threading.Thread) которые управляют потоками для запуска живого http-сервера во время выполнения тестов:

    class LiveServerThread(threading.Thread):
    
        def __init__(self, host, static_handler, connections_override=None):
            self.host = host
            self.port = None
            self.is_ready = threading.Event()
            self.error = None
            self.static_handler = static_handler
            self.connections_override = connections_override
            super(LiveServerThread, self).__init__()
    
        def run(self):
            """
            Sets up the live server and databases, and then loops over handling
            http requests.
            """
            if self.connections_override:
                # Override this thread's database connections with the ones
                # provided by the main thread.
                for alias, conn in self.connections_override.items():
                    connections[alias] = conn
            try:
                # Create the handler for serving static and media files
                handler = self.static_handler(_MediaFilesHandler(WSGIHandler()))
                self.httpd = self._create_server(0)
                self.port = self.httpd.server_address[1]
                self.httpd.set_app(handler)
                self.is_ready.set()
                self.httpd.serve_forever()
            except Exception as e:
                self.error = e
                self.is_ready.set()
    
        def _create_server(self, port):
            return WSGIServer((self.host, port), QuietWSGIRequestHandler, allow_reuse_address=False)
    
        def terminate(self):
            if hasattr(self, 'httpd'):
                # Stop the WSGI server
                self.httpd.shutdown()
                self.httpd.server_close()
    
  • LiveServerTestCase(TransactionTestCase), что в основном делаетто же самое, что и TransactionTestCase, но также запускает действующий http-сервер в отдельном потоке, так что тесты могут использовать другую среду тестирования, которая будет использоваться Selenium , вместо встроенного фиктивного клиента:

    class LiveServerTestCase(TransactionTestCase):
    
        host = 'localhost'
        static_handler = _StaticFilesHandler
    
        @classproperty
        def live_server_url(cls):
            return 'http://%s:%s' % (cls.host, cls.server_thread.port)
    
        @classmethod
        def setUpClass(cls):
            super(LiveServerTestCase, cls).setUpClass()
            connections_override = {}
            for conn in connections.all():
                # If using in-memory sqlite databases, pass the connections to
                # the server thread.
                if conn.vendor == 'sqlite' and conn.is_in_memory_db(conn.settings_dict['NAME']):
                    # Explicitly enable thread-shareability for this connection
                    conn.allow_thread_sharing = True
                    connections_override[conn.alias] = conn
    
        cls._live_server_modified_settings = modify_settings(
            ALLOWED_HOSTS={'append': cls.host},
        )
        cls._live_server_modified_settings.enable()
        cls.server_thread = cls._create_server_thread(connections_override)
        cls.server_thread.daemon = True
        cls.server_thread.start()
    
        # Wait for the live server to be ready
        cls.server_thread.is_ready.wait()
        if cls.server_thread.error:
            # Clean up behind ourselves, since tearDownClass won't get called in
            # case of errors.
            cls._tearDownClassInternal()
            raise cls.server_thread.error
    
        @classmethod
        def _create_server_thread(cls, connections_override):
            return LiveServerThread(
                cls.host,
                cls.static_handler,
                connections_override=connections_override,
        )
    
        @classmethod
        def _tearDownClassInternal(cls):
            # There may not be a 'server_thread' attribute if setUpClass() for some
            # reasons has raised an exception.
            if hasattr(cls, 'server_thread'):
                # Terminate the live server's thread
                cls.server_thread.terminate()
                cls.server_thread.join()
    
            # Restore sqlite in-memory database connections' non-shareability
            for conn in connections.all():
                if conn.vendor == 'sqlite' and conn.is_in_memory_db(conn.settings_dict['NAME']):
                    conn.allow_thread_sharing = False
    
        @classmethod
        def tearDownClass(cls):
            cls._tearDownClassInternal()
            cls._live_server_modified_settings.disable()
            super(LiveServerTestCase, cls).tearDownClass()
    

Решением было закрыть только неперекрывающиеся соединения и было включено в этот запрос на получение / commit ,Изменения были:

  • В django/test/testcases.py добавить:

    finally:
        connections.close_all() 
    
  • Добавить новый файл tests/servers/test_liveserverthread.py:

    from django.db import DEFAULT_DB_ALIAS, connections
    from django.test import LiveServerTestCase, TestCase
    
    
    class LiveServerThreadTest(TestCase):
    
        def run_live_server_thread(self, connections_override=None):
            thread = LiveServerTestCase._create_server_thread(connections_override)
            thread.daemon = True
            thread.start()
            thread.is_ready.wait()
            thread.terminate()
    
        def test_closes_connections(self):
            conn = connections[DEFAULT_DB_ALIAS]
            if conn.vendor == 'sqlite' and conn.is_in_memory_db():
                self.skipTest("the sqlite backend's close() method is a no-op when using an in-memory database")
            # Pass a connection to the thread to check they are being closed.
            connections_override = {DEFAULT_DB_ALIAS: conn}
    
            saved_sharing = conn.allow_thread_sharing
        try:
            conn.allow_thread_sharing = True
            self.assertTrue(conn.is_usable())
            self.run_live_server_thread(connections_override)
            self.assertFalse(conn.is_usable())
        finally:
            conn.allow_thread_sharing = saved_sharing
    
  • В tests/servers/tests.py удалить:

    finally:
        TestCase.tearDownClass()
    
  • В tests/servers/tests.py добавить:

    finally:
        if hasattr(TestCase, 'server_thread'):
            TestCase.server_thread.terminate()
    

Решение

Шаги:

  • Убедитесь, что вы обновили до последней выпущенной версии Django package.
  • Убедитесь, что вы используете последнюю выпущенную версию selenium v3.141.0 .
  • Убедитесь, что вы используете последнюю выпущенную версию Chrome v76 и ChromeDriver 76.0 .

Outro

Подобное обсуждение можно найти в django.db.utils.IntegrityError: Сбой ограничения FOREIGN KEY при выполнении LiveServerTestCases через Selenium и Python Django

0 голосов
/ 11 июля 2019

Проверьте активные соединения, чтобы узнать, в чем причина проблемы select * from pg_stat_activity;

Вы можете отключить расширения:

    @classmethod
    def setUpClass(cls):
        super().setUpClass()
        options = webdriver.chrome.options.Options()
        options.add_argument("--disable-extensions") 
        cls.webdriver = webdriver.Chrome(chrome_options=options)
        cls.webdriver.implicitly_wait(10)

Тогда в разбор:

    @classmethod
    def tearDownClass(cls):
        cls.webdriver.stop_client()
        cls.webdriver.quit()
        connections.close_all()
        super().tearDownClass()
...