tests.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306
  1. import sys
  2. import unittest
  3. from unittest import mock
  4. from django import __version__
  5. from django.contrib.auth.models import Group, Permission, User
  6. from django.contrib.contenttypes.models import ContentType
  7. from django.core.management import CommandError, call_command
  8. from django.core.management.commands import shell
  9. from django.db import models
  10. from django.test import SimpleTestCase
  11. from django.test.utils import (
  12. captured_stdin,
  13. captured_stdout,
  14. isolate_apps,
  15. override_settings,
  16. )
  17. from django.urls.base import resolve, reverse
  18. from .models import Marker, Phone
  19. class ShellCommandTestCase(SimpleTestCase):
  20. script_globals = 'print("__name__" in globals() and "Phone" in globals())'
  21. script_with_inline_function = (
  22. "import django\ndef f():\n print(django.__version__)\nf()"
  23. )
  24. def test_command_option(self):
  25. with self.assertLogs("test", "INFO") as cm:
  26. call_command(
  27. "shell",
  28. command=(
  29. "import django; from logging import getLogger; "
  30. 'getLogger("test").info(django.__version__)'
  31. ),
  32. )
  33. self.assertEqual(cm.records[0].getMessage(), __version__)
  34. def test_command_option_globals(self):
  35. with captured_stdout() as stdout:
  36. call_command("shell", command=self.script_globals)
  37. self.assertEqual(stdout.getvalue().strip(), "True")
  38. def test_command_option_inline_function_call(self):
  39. with captured_stdout() as stdout:
  40. call_command("shell", command=self.script_with_inline_function)
  41. self.assertEqual(stdout.getvalue().strip(), __version__)
  42. @unittest.skipIf(
  43. sys.platform == "win32", "Windows select() doesn't support file descriptors."
  44. )
  45. @mock.patch("django.core.management.commands.shell.select")
  46. def test_stdin_read(self, select):
  47. with captured_stdin() as stdin, captured_stdout() as stdout:
  48. stdin.write("print(100)\n")
  49. stdin.seek(0)
  50. call_command("shell")
  51. self.assertEqual(stdout.getvalue().strip(), "100")
  52. @unittest.skipIf(
  53. sys.platform == "win32",
  54. "Windows select() doesn't support file descriptors.",
  55. )
  56. @mock.patch("django.core.management.commands.shell.select") # [1]
  57. def test_stdin_read_globals(self, select):
  58. with captured_stdin() as stdin, captured_stdout() as stdout:
  59. stdin.write(self.script_globals)
  60. stdin.seek(0)
  61. call_command("shell")
  62. self.assertEqual(stdout.getvalue().strip(), "True")
  63. @unittest.skipIf(
  64. sys.platform == "win32",
  65. "Windows select() doesn't support file descriptors.",
  66. )
  67. @mock.patch("django.core.management.commands.shell.select") # [1]
  68. def test_stdin_read_inline_function_call(self, select):
  69. with captured_stdin() as stdin, captured_stdout() as stdout:
  70. stdin.write(self.script_with_inline_function)
  71. stdin.seek(0)
  72. call_command("shell")
  73. self.assertEqual(stdout.getvalue().strip(), __version__)
  74. def test_ipython(self):
  75. cmd = shell.Command()
  76. mock_ipython = mock.Mock(start_ipython=mock.MagicMock())
  77. with mock.patch.dict(sys.modules, {"IPython": mock_ipython}):
  78. cmd.ipython({"verbosity": 0, "no_imports": False})
  79. self.assertEqual(
  80. mock_ipython.start_ipython.mock_calls,
  81. [mock.call(argv=[], user_ns=cmd.get_and_report_namespace(0))],
  82. )
  83. @mock.patch("django.core.management.commands.shell.select.select") # [1]
  84. @mock.patch.dict("sys.modules", {"IPython": None})
  85. def test_shell_with_ipython_not_installed(self, select):
  86. select.return_value = ([], [], [])
  87. with self.assertRaisesMessage(
  88. CommandError, "Couldn't import ipython interface."
  89. ):
  90. call_command("shell", interface="ipython")
  91. def test_bpython(self):
  92. cmd = shell.Command()
  93. mock_bpython = mock.Mock(embed=mock.MagicMock())
  94. with mock.patch.dict(sys.modules, {"bpython": mock_bpython}):
  95. cmd.bpython({"verbosity": 0, "no_imports": False})
  96. self.assertEqual(
  97. mock_bpython.embed.mock_calls, [mock.call(cmd.get_and_report_namespace(0))]
  98. )
  99. @mock.patch("django.core.management.commands.shell.select.select") # [1]
  100. @mock.patch.dict("sys.modules", {"bpython": None})
  101. def test_shell_with_bpython_not_installed(self, select):
  102. select.return_value = ([], [], [])
  103. with self.assertRaisesMessage(
  104. CommandError, "Couldn't import bpython interface."
  105. ):
  106. call_command("shell", interface="bpython")
  107. def test_python(self):
  108. cmd = shell.Command()
  109. mock_code = mock.Mock(interact=mock.MagicMock())
  110. with mock.patch.dict(sys.modules, {"code": mock_code}):
  111. cmd.python({"verbosity": 0, "no_startup": True, "no_imports": False})
  112. self.assertEqual(
  113. mock_code.interact.mock_calls,
  114. [mock.call(local=cmd.get_and_report_namespace(0))],
  115. )
  116. # [1] Patch select to prevent tests failing when the test suite is run
  117. # in parallel mode. The tests are run in a subprocess and the subprocess's
  118. # stdin is closed and replaced by /dev/null. Reading from /dev/null always
  119. # returns EOF and so select always shows that sys.stdin is ready to read.
  120. # This causes problems because of the call to select.select() toward the
  121. # end of shell's handle() method.
  122. class ShellCommandAutoImportsTestCase(SimpleTestCase):
  123. @override_settings(
  124. INSTALLED_APPS=["shell", "django.contrib.auth", "django.contrib.contenttypes"]
  125. )
  126. def test_get_namespace(self):
  127. namespace = shell.Command().get_namespace()
  128. self.assertEqual(
  129. namespace,
  130. {
  131. "Marker": Marker,
  132. "Phone": Phone,
  133. "ContentType": ContentType,
  134. "Group": Group,
  135. "Permission": Permission,
  136. "User": User,
  137. },
  138. )
  139. @override_settings(INSTALLED_APPS=["basic", "shell"])
  140. @isolate_apps("basic", "shell", kwarg_name="apps")
  141. def test_get_namespace_precedence(self, apps):
  142. class Article(models.Model):
  143. class Meta:
  144. app_label = "basic"
  145. winner_article = Article
  146. class Article(models.Model):
  147. class Meta:
  148. app_label = "shell"
  149. with mock.patch("django.apps.apps.get_models", return_value=apps.get_models()):
  150. namespace = shell.Command().get_namespace()
  151. self.assertEqual(namespace, {"Article": winner_article})
  152. @override_settings(
  153. INSTALLED_APPS=["shell", "django.contrib.auth", "django.contrib.contenttypes"]
  154. )
  155. def test_get_namespace_overridden(self):
  156. class TestCommand(shell.Command):
  157. def get_namespace(self):
  158. from django.urls.base import resolve, reverse
  159. return {
  160. **super().get_namespace(),
  161. "resolve": resolve,
  162. "reverse": reverse,
  163. }
  164. namespace = TestCommand().get_namespace()
  165. self.assertEqual(
  166. namespace,
  167. {
  168. "resolve": resolve,
  169. "reverse": reverse,
  170. "Marker": Marker,
  171. "Phone": Phone,
  172. "ContentType": ContentType,
  173. "Group": Group,
  174. "Permission": Permission,
  175. "User": User,
  176. },
  177. )
  178. @override_settings(
  179. INSTALLED_APPS=["shell", "django.contrib.auth", "django.contrib.contenttypes"]
  180. )
  181. def test_no_imports_flag(self):
  182. for verbosity in (0, 1, 2, 3):
  183. with self.subTest(verbosity=verbosity), captured_stdout() as stdout:
  184. namespace = shell.Command().get_and_report_namespace(
  185. verbosity=verbosity, no_imports=True
  186. )
  187. self.assertEqual(namespace, {})
  188. self.assertEqual(stdout.getvalue().strip(), "")
  189. @override_settings(
  190. INSTALLED_APPS=["shell", "django.contrib.auth", "django.contrib.contenttypes"]
  191. )
  192. def test_verbosity_zero(self):
  193. with captured_stdout() as stdout:
  194. cmd = shell.Command()
  195. namespace = cmd.get_and_report_namespace(verbosity=0)
  196. self.assertEqual(namespace, cmd.get_namespace())
  197. self.assertEqual(stdout.getvalue().strip(), "")
  198. @override_settings(
  199. INSTALLED_APPS=["shell", "django.contrib.auth", "django.contrib.contenttypes"]
  200. )
  201. def test_verbosity_one(self):
  202. with captured_stdout() as stdout:
  203. cmd = shell.Command()
  204. namespace = cmd.get_and_report_namespace(verbosity=1)
  205. self.assertEqual(namespace, cmd.get_namespace())
  206. self.assertEqual(
  207. stdout.getvalue().strip(),
  208. "6 objects imported automatically (use -v 2 for details).",
  209. )
  210. @override_settings(INSTALLED_APPS=["shell", "django.contrib.contenttypes"])
  211. @mock.patch.dict(sys.modules, {"isort": None})
  212. def test_message_with_stdout_listing_objects_with_isort_not_installed(self):
  213. class TestCommand(shell.Command):
  214. def get_namespace(self):
  215. class MyClass:
  216. pass
  217. constant = "constant"
  218. return {
  219. **super().get_namespace(),
  220. "MyClass": MyClass,
  221. "constant": constant,
  222. }
  223. with captured_stdout() as stdout:
  224. TestCommand().get_and_report_namespace(verbosity=2)
  225. self.assertEqual(
  226. stdout.getvalue().strip(),
  227. "5 objects imported automatically, including:\n\n"
  228. " from django.contrib.contenttypes.models import ContentType\n"
  229. " from shell.models import Phone, Marker",
  230. )
  231. @override_settings(INSTALLED_APPS=["shell", "django.contrib.contenttypes"])
  232. def test_message_with_stdout_listing_objects_with_isort(self):
  233. sorted_imports = (
  234. " from shell.models import Marker, Phone\n\n"
  235. " from django.contrib.contenttypes.models import ContentType"
  236. )
  237. mock_isort_code = mock.Mock(code=mock.MagicMock(return_value=sorted_imports))
  238. class TestCommand(shell.Command):
  239. def get_namespace(self):
  240. class MyClass:
  241. pass
  242. constant = "constant"
  243. return {
  244. **super().get_namespace(),
  245. "MyClass": MyClass,
  246. "constant": constant,
  247. }
  248. with (
  249. mock.patch.dict(sys.modules, {"isort": mock_isort_code}),
  250. captured_stdout() as stdout,
  251. ):
  252. TestCommand().get_and_report_namespace(verbosity=2)
  253. self.assertEqual(
  254. stdout.getvalue().strip(),
  255. "5 objects imported automatically, including:\n\n"
  256. " from shell.models import Marker, Phone\n\n"
  257. " from django.contrib.contenttypes.models import ContentType",
  258. )