tests.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348
  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 connection, 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. options = {"verbosity": 0, "no_imports": False}
  78. with mock.patch.dict(sys.modules, {"IPython": mock_ipython}):
  79. cmd.ipython(options)
  80. self.assertEqual(
  81. mock_ipython.start_ipython.mock_calls,
  82. [mock.call(argv=[], user_ns=cmd.get_and_report_namespace(**options))],
  83. )
  84. @mock.patch("django.core.management.commands.shell.select.select") # [1]
  85. @mock.patch.dict("sys.modules", {"IPython": None})
  86. def test_shell_with_ipython_not_installed(self, select):
  87. select.return_value = ([], [], [])
  88. with self.assertRaisesMessage(
  89. CommandError, "Couldn't import ipython interface."
  90. ):
  91. call_command("shell", interface="ipython")
  92. def test_bpython(self):
  93. cmd = shell.Command()
  94. mock_bpython = mock.Mock(embed=mock.MagicMock())
  95. options = {"verbosity": 0, "no_imports": False}
  96. with mock.patch.dict(sys.modules, {"bpython": mock_bpython}):
  97. cmd.bpython(options)
  98. self.assertEqual(
  99. mock_bpython.embed.mock_calls,
  100. [mock.call(cmd.get_and_report_namespace(**options))],
  101. )
  102. @mock.patch("django.core.management.commands.shell.select.select") # [1]
  103. @mock.patch.dict("sys.modules", {"bpython": None})
  104. def test_shell_with_bpython_not_installed(self, select):
  105. select.return_value = ([], [], [])
  106. with self.assertRaisesMessage(
  107. CommandError, "Couldn't import bpython interface."
  108. ):
  109. call_command("shell", interface="bpython")
  110. def test_python(self):
  111. cmd = shell.Command()
  112. mock_code = mock.Mock(interact=mock.MagicMock())
  113. options = {"verbosity": 0, "no_startup": True, "no_imports": False}
  114. with mock.patch.dict(sys.modules, {"code": mock_code}):
  115. cmd.python(options)
  116. self.assertEqual(
  117. mock_code.interact.mock_calls,
  118. [mock.call(local=cmd.get_and_report_namespace(**options))],
  119. )
  120. # [1] Patch select to prevent tests failing when the test suite is run
  121. # in parallel mode. The tests are run in a subprocess and the subprocess's
  122. # stdin is closed and replaced by /dev/null. Reading from /dev/null always
  123. # returns EOF and so select always shows that sys.stdin is ready to read.
  124. # This causes problems because of the call to select.select() toward the
  125. # end of shell's handle() method.
  126. class ShellCommandAutoImportsTestCase(SimpleTestCase):
  127. @override_settings(
  128. INSTALLED_APPS=["shell", "django.contrib.auth", "django.contrib.contenttypes"]
  129. )
  130. def test_get_namespace(self):
  131. namespace = shell.Command().get_namespace()
  132. self.assertEqual(
  133. namespace,
  134. {
  135. "Marker": Marker,
  136. "Phone": Phone,
  137. "ContentType": ContentType,
  138. "Group": Group,
  139. "Permission": Permission,
  140. "User": User,
  141. },
  142. )
  143. @override_settings(INSTALLED_APPS=["basic", "shell"])
  144. @isolate_apps("basic", "shell", kwarg_name="apps")
  145. def test_get_namespace_precedence(self, apps):
  146. class Article(models.Model):
  147. class Meta:
  148. app_label = "basic"
  149. winner_article = Article
  150. class Article(models.Model):
  151. class Meta:
  152. app_label = "shell"
  153. with mock.patch("django.apps.apps.get_models", return_value=apps.get_models()):
  154. namespace = shell.Command().get_namespace()
  155. self.assertEqual(namespace, {"Article": winner_article})
  156. @override_settings(
  157. INSTALLED_APPS=["shell", "django.contrib.auth", "django.contrib.contenttypes"]
  158. )
  159. def test_get_namespace_overridden(self):
  160. class TestCommand(shell.Command):
  161. def get_namespace(self):
  162. from django.urls.base import resolve, reverse
  163. return {
  164. **super().get_namespace(),
  165. "resolve": resolve,
  166. "reverse": reverse,
  167. }
  168. namespace = TestCommand().get_namespace()
  169. self.assertEqual(
  170. namespace,
  171. {
  172. "resolve": resolve,
  173. "reverse": reverse,
  174. "Marker": Marker,
  175. "Phone": Phone,
  176. "ContentType": ContentType,
  177. "Group": Group,
  178. "Permission": Permission,
  179. "User": User,
  180. },
  181. )
  182. @override_settings(
  183. INSTALLED_APPS=["shell", "django.contrib.auth", "django.contrib.contenttypes"]
  184. )
  185. def test_no_imports_flag(self):
  186. for verbosity in (0, 1, 2, 3):
  187. with self.subTest(verbosity=verbosity), captured_stdout() as stdout:
  188. namespace = shell.Command().get_and_report_namespace(
  189. verbosity=verbosity, no_imports=True
  190. )
  191. self.assertEqual(namespace, {})
  192. self.assertEqual(stdout.getvalue().strip(), "")
  193. @override_settings(
  194. INSTALLED_APPS=["shell", "django.contrib.auth", "django.contrib.contenttypes"]
  195. )
  196. def test_verbosity_zero(self):
  197. with captured_stdout() as stdout:
  198. cmd = shell.Command()
  199. namespace = cmd.get_and_report_namespace(verbosity=0)
  200. self.assertEqual(namespace, cmd.get_namespace())
  201. self.assertEqual(stdout.getvalue().strip(), "")
  202. @override_settings(
  203. INSTALLED_APPS=["shell", "django.contrib.auth", "django.contrib.contenttypes"]
  204. )
  205. def test_verbosity_one(self):
  206. with captured_stdout() as stdout:
  207. cmd = shell.Command()
  208. namespace = cmd.get_and_report_namespace(verbosity=1)
  209. self.assertEqual(namespace, cmd.get_namespace())
  210. self.assertEqual(
  211. stdout.getvalue().strip(),
  212. "6 objects imported automatically (use -v 2 for details).",
  213. )
  214. @override_settings(INSTALLED_APPS=["shell", "django.contrib.contenttypes"])
  215. @mock.patch.dict(sys.modules, {"isort": None})
  216. def test_message_with_stdout_listing_objects_with_isort_not_installed(self):
  217. class TestCommand(shell.Command):
  218. def get_namespace(self):
  219. class MyClass:
  220. pass
  221. constant = "constant"
  222. return {
  223. **super().get_namespace(),
  224. "MyClass": MyClass,
  225. "constant": constant,
  226. }
  227. with captured_stdout() as stdout:
  228. TestCommand().get_and_report_namespace(verbosity=2)
  229. self.assertEqual(
  230. stdout.getvalue().strip(),
  231. "5 objects imported automatically, including:\n\n"
  232. " from django.contrib.contenttypes.models import ContentType\n"
  233. " from shell.models import Phone, Marker",
  234. )
  235. def test_message_with_stdout_one_object(self):
  236. class TestCommand(shell.Command):
  237. def get_namespace(self):
  238. return {"connection": connection}
  239. with captured_stdout() as stdout:
  240. TestCommand().get_and_report_namespace(verbosity=2)
  241. cases = {
  242. 0: "",
  243. 1: "1 object imported automatically (use -v 2 for details).",
  244. 2: (
  245. "1 object imported automatically, including:\n\n"
  246. " from django.utils.connection import connection"
  247. ),
  248. }
  249. for verbosity, expected in cases.items():
  250. with self.subTest(verbosity=verbosity):
  251. with captured_stdout() as stdout:
  252. TestCommand().get_and_report_namespace(verbosity=verbosity)
  253. self.assertEqual(stdout.getvalue().strip(), expected)
  254. def test_message_with_stdout_zero_objects(self):
  255. class TestCommand(shell.Command):
  256. def get_namespace(self):
  257. return {}
  258. cases = {
  259. 0: "",
  260. 1: "0 objects imported automatically.",
  261. 2: "0 objects imported automatically.",
  262. }
  263. for verbosity, expected in cases.items():
  264. with self.subTest(verbosity=verbosity):
  265. with captured_stdout() as stdout:
  266. TestCommand().get_and_report_namespace(verbosity=verbosity)
  267. self.assertEqual(stdout.getvalue().strip(), expected)
  268. @override_settings(INSTALLED_APPS=["shell", "django.contrib.contenttypes"])
  269. def test_message_with_stdout_listing_objects_with_isort(self):
  270. sorted_imports = (
  271. " from shell.models import Marker, Phone\n\n"
  272. " from django.contrib.contenttypes.models import ContentType"
  273. )
  274. mock_isort_code = mock.Mock(code=mock.MagicMock(return_value=sorted_imports))
  275. class TestCommand(shell.Command):
  276. def get_namespace(self):
  277. class MyClass:
  278. pass
  279. constant = "constant"
  280. return {
  281. **super().get_namespace(),
  282. "MyClass": MyClass,
  283. "constant": constant,
  284. }
  285. with (
  286. mock.patch.dict(sys.modules, {"isort": mock_isort_code}),
  287. captured_stdout() as stdout,
  288. ):
  289. TestCommand().get_and_report_namespace(verbosity=2)
  290. self.assertEqual(
  291. stdout.getvalue().strip(),
  292. "5 objects imported automatically, including:\n\n"
  293. " from shell.models import Marker, Phone\n\n"
  294. " from django.contrib.contenttypes.models import ContentType",
  295. )