test_creation.py 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138
  1. import subprocess
  2. import unittest
  3. from io import BytesIO, StringIO
  4. from unittest import mock
  5. from django.db import DatabaseError, connection
  6. from django.db.backends.base.creation import BaseDatabaseCreation
  7. from django.db.backends.mysql.creation import DatabaseCreation
  8. from django.test import SimpleTestCase
  9. from django.test.utils import captured_stderr
  10. @unittest.skipUnless(connection.vendor == "mysql", "MySQL tests")
  11. class DatabaseCreationTests(SimpleTestCase):
  12. def _execute_raise_database_exists(self, cursor, parameters, keepdb=False):
  13. raise DatabaseError(
  14. 1007, "Can't create database '%s'; database exists" % parameters["dbname"]
  15. )
  16. def _execute_raise_access_denied(self, cursor, parameters, keepdb=False):
  17. raise DatabaseError(1044, "Access denied for user")
  18. def patch_test_db_creation(self, execute_create_test_db):
  19. return mock.patch.object(
  20. BaseDatabaseCreation, "_execute_create_test_db", execute_create_test_db
  21. )
  22. @mock.patch("sys.stdout", new_callable=StringIO)
  23. @mock.patch("sys.stderr", new_callable=StringIO)
  24. def test_create_test_db_database_exists(self, *mocked_objects):
  25. # Simulate test database creation raising "database exists"
  26. creation = DatabaseCreation(connection)
  27. with self.patch_test_db_creation(self._execute_raise_database_exists):
  28. with mock.patch("builtins.input", return_value="no"):
  29. with self.assertRaises(SystemExit):
  30. # SystemExit is raised if the user answers "no" to the
  31. # prompt asking if it's okay to delete the test database.
  32. creation._create_test_db(
  33. verbosity=0, autoclobber=False, keepdb=False
  34. )
  35. # "Database exists" shouldn't appear when keepdb is on
  36. creation._create_test_db(verbosity=0, autoclobber=False, keepdb=True)
  37. @mock.patch("sys.stdout", new_callable=StringIO)
  38. @mock.patch("sys.stderr", new_callable=StringIO)
  39. def test_create_test_db_unexpected_error(self, *mocked_objects):
  40. # Simulate test database creation raising unexpected error
  41. creation = DatabaseCreation(connection)
  42. with self.patch_test_db_creation(self._execute_raise_access_denied):
  43. with self.assertRaises(SystemExit):
  44. creation._create_test_db(verbosity=0, autoclobber=False, keepdb=False)
  45. def test_clone_test_db_database_exists(self):
  46. creation = DatabaseCreation(connection)
  47. with self.patch_test_db_creation(self._execute_raise_database_exists):
  48. with mock.patch.object(DatabaseCreation, "_clone_db") as _clone_db:
  49. creation._clone_test_db("suffix", verbosity=0, keepdb=True)
  50. _clone_db.assert_not_called()
  51. def test_clone_test_db_options_ordering(self):
  52. creation = DatabaseCreation(connection)
  53. mock_subprocess_call = mock.MagicMock()
  54. mock_subprocess_call.returncode = 0
  55. try:
  56. saved_settings = connection.settings_dict
  57. connection.settings_dict = {
  58. "NAME": "source_db",
  59. "USER": "",
  60. "PASSWORD": "",
  61. "PORT": "",
  62. "HOST": "",
  63. "ENGINE": "django.db.backends.mysql",
  64. "OPTIONS": {
  65. "read_default_file": "my.cnf",
  66. },
  67. }
  68. with mock.patch.object(subprocess, "Popen") as mocked_popen:
  69. mocked_popen.return_value.__enter__.return_value = mock_subprocess_call
  70. creation._clone_db("source_db", "target_db")
  71. mocked_popen.assert_has_calls(
  72. [
  73. mock.call(
  74. [
  75. "mysqldump",
  76. "--defaults-file=my.cnf",
  77. "--routines",
  78. "--events",
  79. "source_db",
  80. ],
  81. stdout=subprocess.PIPE,
  82. stderr=subprocess.PIPE,
  83. env=None,
  84. ),
  85. ]
  86. )
  87. finally:
  88. connection.settings_dict = saved_settings
  89. def test_clone_test_db_subprocess_mysqldump_error(self):
  90. creation = DatabaseCreation(connection)
  91. mock_subprocess_call = mock.MagicMock()
  92. mock_subprocess_call.returncode = 0
  93. # Simulate mysqldump in test database cloning raises an error.
  94. msg = "Couldn't execute 'SELECT ...'"
  95. mock_subprocess_call_error = mock.MagicMock()
  96. mock_subprocess_call_error.returncode = 2
  97. mock_subprocess_call_error.stderr = BytesIO(msg.encode())
  98. with mock.patch.object(subprocess, "Popen") as mocked_popen:
  99. mocked_popen.return_value.__enter__.side_effect = [
  100. mock_subprocess_call_error, # mysqldump mock
  101. mock_subprocess_call, # load mock
  102. ]
  103. with captured_stderr() as err, self.assertRaises(SystemExit) as cm:
  104. creation._clone_db("source_db", "target_db")
  105. self.assertEqual(cm.exception.code, 2)
  106. self.assertIn(
  107. f"Got an error on mysqldump when cloning the test database: {msg}",
  108. err.getvalue(),
  109. )
  110. def test_clone_test_db_subprocess_mysql_error(self):
  111. creation = DatabaseCreation(connection)
  112. mock_subprocess_call = mock.MagicMock()
  113. mock_subprocess_call.returncode = 0
  114. # Simulate load in test database cloning raises an error.
  115. msg = "Some error"
  116. mock_subprocess_call_error = mock.MagicMock()
  117. mock_subprocess_call_error.returncode = 3
  118. mock_subprocess_call_error.stderr = BytesIO(msg.encode())
  119. with mock.patch.object(subprocess, "Popen") as mocked_popen:
  120. mocked_popen.return_value.__enter__.side_effect = [
  121. mock_subprocess_call, # mysqldump mock
  122. mock_subprocess_call_error, # load mock
  123. ]
  124. with captured_stderr() as err, self.assertRaises(SystemExit) as cm:
  125. creation._clone_db("source_db", "target_db")
  126. self.assertEqual(cm.exception.code, 3)
  127. self.assertIn(f"Got an error cloning the test database: {msg}", err.getvalue())