tests.py 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463
  1. import os
  2. import subprocess
  3. import sys
  4. import unittest
  5. from unittest import mock
  6. from django import __version__
  7. from django.contrib.auth.models import Group, Permission, User
  8. from django.contrib.contenttypes.models import ContentType
  9. from django.core.management import CommandError, call_command
  10. from django.core.management.commands import shell
  11. from django.db import connection
  12. from django.test import SimpleTestCase
  13. from django.test.utils import captured_stdin, captured_stdout, override_settings
  14. from django.urls import resolve, reverse
  15. from .models import Marker, Phone
  16. class ShellCommandTestCase(SimpleTestCase):
  17. script_globals = 'print("__name__" in globals() and "Phone" in globals())'
  18. script_with_inline_function = (
  19. "import django\ndef f():\n print(django.__version__)\nf()"
  20. )
  21. def test_command_option(self):
  22. with self.assertLogs("test", "INFO") as cm:
  23. with captured_stdout():
  24. call_command(
  25. "shell",
  26. command=(
  27. "import django; from logging import getLogger; "
  28. 'getLogger("test").info(django.__version__)'
  29. ),
  30. )
  31. self.assertEqual(cm.records[0].getMessage(), __version__)
  32. def test_command_option_globals(self):
  33. with captured_stdout() as stdout:
  34. call_command("shell", command=self.script_globals, verbosity=0)
  35. self.assertEqual(stdout.getvalue().strip(), "True")
  36. def test_command_option_inline_function_call(self):
  37. with captured_stdout() as stdout:
  38. call_command("shell", command=self.script_with_inline_function, verbosity=0)
  39. self.assertEqual(stdout.getvalue().strip(), __version__)
  40. @override_settings(INSTALLED_APPS=["shell"])
  41. def test_no_settings(self):
  42. test_environ = os.environ.copy()
  43. if "DJANGO_SETTINGS_MODULE" in test_environ:
  44. del test_environ["DJANGO_SETTINGS_MODULE"]
  45. error = (
  46. "Automatic imports are disabled since settings are not configured.\n"
  47. "DJANGO_SETTINGS_MODULE value is None.\n"
  48. "HINT: Ensure that the settings module is configured and set.\n\n"
  49. )
  50. for verbosity, assertError in [
  51. ("0", self.assertNotIn),
  52. ("1", self.assertIn),
  53. ("2", self.assertIn),
  54. ]:
  55. with self.subTest(verbosity=verbosity, get_auto_imports="models"):
  56. p = subprocess.run(
  57. [
  58. sys.executable,
  59. "-m",
  60. "django",
  61. "shell",
  62. "-c",
  63. "print(globals())",
  64. "-v",
  65. verbosity,
  66. ],
  67. capture_output=True,
  68. env=test_environ,
  69. text=True,
  70. umask=-1,
  71. )
  72. assertError(error, p.stdout)
  73. self.assertNotIn("Marker", p.stdout)
  74. with self.subTest(verbosity=verbosity, get_auto_imports="without-models"):
  75. with mock.patch(
  76. "django.core.management.commands.shell.Command.get_auto_imports",
  77. return_value=["django.urls.resolve"],
  78. ):
  79. p = subprocess.run(
  80. [
  81. sys.executable,
  82. "-m",
  83. "django",
  84. "shell",
  85. "-c",
  86. "print(globals())",
  87. "-v",
  88. verbosity,
  89. ],
  90. capture_output=True,
  91. env=test_environ,
  92. text=True,
  93. umask=-1,
  94. )
  95. assertError(error, p.stdout)
  96. self.assertNotIn("resolve", p.stdout)
  97. @unittest.skipIf(
  98. sys.platform == "win32", "Windows select() doesn't support file descriptors."
  99. )
  100. @mock.patch("django.core.management.commands.shell.select")
  101. def test_stdin_read(self, select):
  102. with captured_stdin() as stdin, captured_stdout() as stdout:
  103. stdin.write("print(100)\n")
  104. stdin.seek(0)
  105. call_command("shell", verbosity=0)
  106. self.assertEqual(stdout.getvalue().strip(), "100")
  107. @unittest.skipIf(
  108. sys.platform == "win32",
  109. "Windows select() doesn't support file descriptors.",
  110. )
  111. @mock.patch("django.core.management.commands.shell.select") # [1]
  112. def test_stdin_read_globals(self, select):
  113. with captured_stdin() as stdin, captured_stdout() as stdout:
  114. stdin.write(self.script_globals)
  115. stdin.seek(0)
  116. call_command("shell", verbosity=0)
  117. self.assertEqual(stdout.getvalue().strip(), "True")
  118. @unittest.skipIf(
  119. sys.platform == "win32",
  120. "Windows select() doesn't support file descriptors.",
  121. )
  122. @mock.patch("django.core.management.commands.shell.select") # [1]
  123. def test_stdin_read_inline_function_call(self, select):
  124. with captured_stdin() as stdin, captured_stdout() as stdout:
  125. stdin.write(self.script_with_inline_function)
  126. stdin.seek(0)
  127. call_command("shell", verbosity=0)
  128. self.assertEqual(stdout.getvalue().strip(), __version__)
  129. def test_ipython(self):
  130. cmd = shell.Command()
  131. mock_ipython = mock.Mock(start_ipython=mock.MagicMock())
  132. options = {"verbosity": 0, "no_imports": False}
  133. with mock.patch.dict(sys.modules, {"IPython": mock_ipython}):
  134. cmd.ipython(options)
  135. self.assertEqual(
  136. mock_ipython.start_ipython.mock_calls,
  137. [mock.call(argv=[], user_ns=cmd.get_namespace(**options))],
  138. )
  139. @mock.patch("django.core.management.commands.shell.select.select") # [1]
  140. @mock.patch.dict("sys.modules", {"IPython": None})
  141. def test_shell_with_ipython_not_installed(self, select):
  142. select.return_value = ([], [], [])
  143. with self.assertRaisesMessage(
  144. CommandError, "Couldn't import ipython interface."
  145. ):
  146. call_command("shell", interface="ipython")
  147. def test_bpython(self):
  148. cmd = shell.Command()
  149. mock_bpython = mock.Mock(embed=mock.MagicMock())
  150. options = {"verbosity": 0, "no_imports": False}
  151. with mock.patch.dict(sys.modules, {"bpython": mock_bpython}):
  152. cmd.bpython(options)
  153. self.assertEqual(
  154. mock_bpython.embed.mock_calls, [mock.call(cmd.get_namespace(**options))]
  155. )
  156. @mock.patch("django.core.management.commands.shell.select.select") # [1]
  157. @mock.patch.dict("sys.modules", {"bpython": None})
  158. def test_shell_with_bpython_not_installed(self, select):
  159. select.return_value = ([], [], [])
  160. with self.assertRaisesMessage(
  161. CommandError, "Couldn't import bpython interface."
  162. ):
  163. call_command("shell", interface="bpython")
  164. def test_python(self):
  165. cmd = shell.Command()
  166. mock_code = mock.Mock(interact=mock.MagicMock())
  167. options = {"verbosity": 0, "no_startup": True, "no_imports": False}
  168. with mock.patch.dict(sys.modules, {"code": mock_code}):
  169. cmd.python(options)
  170. self.assertEqual(
  171. mock_code.interact.mock_calls,
  172. [mock.call(local=cmd.get_namespace(**options))],
  173. )
  174. # [1] Patch select to prevent tests failing when the test suite is run
  175. # in parallel mode. The tests are run in a subprocess and the subprocess's
  176. # stdin is closed and replaced by /dev/null. Reading from /dev/null always
  177. # returns EOF and so select always shows that sys.stdin is ready to read.
  178. # This causes problems because of the call to select.select() toward the
  179. # end of shell's handle() method.
  180. class ShellCommandAutoImportsTestCase(SimpleTestCase):
  181. @override_settings(
  182. INSTALLED_APPS=["shell", "django.contrib.auth", "django.contrib.contenttypes"]
  183. )
  184. def test_get_namespace(self):
  185. namespace = shell.Command().get_namespace()
  186. self.assertEqual(
  187. namespace,
  188. {
  189. "Marker": Marker,
  190. "Phone": Phone,
  191. "ContentType": ContentType,
  192. "Group": Group,
  193. "Permission": Permission,
  194. "User": User,
  195. },
  196. )
  197. @override_settings(
  198. INSTALLED_APPS=["model_forms", "contenttypes_tests", "forms_tests"]
  199. )
  200. def test_get_namespace_precedence(self):
  201. # All of these apps define an `Article` model. The one defined first in
  202. # INSTALLED_APPS, takes precedence.
  203. import model_forms.models
  204. namespace = shell.Command().get_namespace()
  205. self.assertIs(namespace.get("Article"), model_forms.models.Article)
  206. @override_settings(
  207. INSTALLED_APPS=["shell", "django.contrib.auth", "django.contrib.contenttypes"]
  208. )
  209. def test_get_namespace_overridden(self):
  210. class TestCommand(shell.Command):
  211. def get_auto_imports(self):
  212. return super().get_auto_imports() + [
  213. "django.urls.reverse",
  214. "django.urls.resolve",
  215. "django.db.connection",
  216. ]
  217. namespace = TestCommand().get_namespace()
  218. self.assertEqual(
  219. namespace,
  220. {
  221. "connection": connection,
  222. "resolve": resolve,
  223. "reverse": reverse,
  224. "Marker": Marker,
  225. "Phone": Phone,
  226. "ContentType": ContentType,
  227. "Group": Group,
  228. "Permission": Permission,
  229. "User": User,
  230. },
  231. )
  232. @override_settings(
  233. INSTALLED_APPS=["shell", "django.contrib.auth", "django.contrib.contenttypes"]
  234. )
  235. def test_no_imports_flag(self):
  236. for verbosity in (0, 1, 2, 3):
  237. with self.subTest(verbosity=verbosity), captured_stdout() as stdout:
  238. namespace = shell.Command().get_namespace(
  239. verbosity=verbosity, no_imports=True
  240. )
  241. self.assertEqual(namespace, {})
  242. self.assertEqual(stdout.getvalue().strip(), "")
  243. @override_settings(
  244. INSTALLED_APPS=["shell", "django.contrib.auth", "django.contrib.contenttypes"]
  245. )
  246. def test_verbosity_zero(self):
  247. with captured_stdout() as stdout:
  248. cmd = shell.Command()
  249. namespace = cmd.get_namespace(verbosity=0)
  250. self.assertEqual(len(namespace), len(cmd.get_auto_imports()))
  251. self.assertEqual(stdout.getvalue().strip(), "")
  252. @override_settings(
  253. INSTALLED_APPS=["shell", "django.contrib.auth", "django.contrib.contenttypes"]
  254. )
  255. def test_verbosity_one(self):
  256. with captured_stdout() as stdout:
  257. cmd = shell.Command()
  258. namespace = cmd.get_namespace(verbosity=1)
  259. self.assertEqual(len(namespace), len(cmd.get_auto_imports()))
  260. self.assertEqual(
  261. stdout.getvalue().strip(),
  262. "6 objects imported automatically (use -v 2 for details).",
  263. )
  264. @override_settings(INSTALLED_APPS=["shell", "django.contrib.contenttypes"])
  265. @mock.patch.dict(sys.modules, {"isort": None})
  266. def test_message_with_stdout_listing_objects_with_isort_not_installed(self):
  267. class TestCommand(shell.Command):
  268. def get_auto_imports(self):
  269. # Include duplicate import strings to ensure proper handling,
  270. # independent of isort's deduplication (#36252).
  271. return super().get_auto_imports() + [
  272. "django.urls.reverse",
  273. "django.urls.resolve",
  274. "shell",
  275. "django",
  276. "django.urls.reverse",
  277. "shell",
  278. "django",
  279. ]
  280. with captured_stdout() as stdout:
  281. TestCommand().get_namespace(verbosity=2)
  282. self.assertEqual(
  283. stdout.getvalue().strip(),
  284. "7 objects imported automatically:\n\n"
  285. " import shell\n"
  286. " import django\n"
  287. " from django.contrib.contenttypes.models import ContentType\n"
  288. " from shell.models import Phone, Marker\n"
  289. " from django.urls import reverse, resolve",
  290. )
  291. def test_message_with_stdout_one_object(self):
  292. class TestCommand(shell.Command):
  293. def get_auto_imports(self):
  294. return ["django.db.connection"]
  295. with captured_stdout() as stdout:
  296. TestCommand().get_namespace(verbosity=2)
  297. cases = {
  298. 0: "",
  299. 1: "1 object imported automatically (use -v 2 for details).",
  300. 2: (
  301. "1 object imported automatically:\n\n"
  302. " from django.db import connection"
  303. ),
  304. }
  305. for verbosity, expected in cases.items():
  306. with self.subTest(verbosity=verbosity):
  307. with captured_stdout() as stdout:
  308. TestCommand().get_namespace(verbosity=verbosity)
  309. self.assertEqual(stdout.getvalue().strip(), expected)
  310. @override_settings(INSTALLED_APPS=[])
  311. def test_message_with_stdout_no_installed_apps(self):
  312. cases = {
  313. 0: "",
  314. 1: "0 objects imported automatically.",
  315. 2: "0 objects imported automatically.",
  316. }
  317. for verbosity, expected in cases.items():
  318. with self.subTest(verbosity=verbosity):
  319. with captured_stdout() as stdout:
  320. shell.Command().get_namespace(verbosity=verbosity)
  321. self.assertEqual(stdout.getvalue().strip(), expected)
  322. def test_message_with_stdout_overriden_none_result(self):
  323. class TestCommand(shell.Command):
  324. def get_auto_imports(self):
  325. return None
  326. for verbosity in [0, 1, 2]:
  327. with self.subTest(verbosity=verbosity):
  328. with captured_stdout() as stdout:
  329. result = TestCommand().get_namespace(verbosity=verbosity)
  330. self.assertEqual(result, {})
  331. self.assertEqual(stdout.getvalue().strip(), "")
  332. @override_settings(INSTALLED_APPS=["shell", "django.contrib.contenttypes"])
  333. def test_message_with_stdout_listing_objects_with_isort(self):
  334. sorted_imports = (
  335. " from shell.models import Marker, Phone\n\n"
  336. " from django.contrib.contenttypes.models import ContentType"
  337. )
  338. mock_isort_code = mock.Mock(code=mock.MagicMock(return_value=sorted_imports))
  339. class TestCommand(shell.Command):
  340. def get_auto_imports(self):
  341. return super().get_auto_imports() + [
  342. "django.urls.reverse",
  343. "django.urls.resolve",
  344. "django",
  345. ]
  346. with (
  347. mock.patch.dict(sys.modules, {"isort": mock_isort_code}),
  348. captured_stdout() as stdout,
  349. ):
  350. TestCommand().get_namespace(verbosity=2)
  351. self.assertEqual(
  352. stdout.getvalue().strip(),
  353. "6 objects imported automatically:\n\n" + sorted_imports,
  354. )
  355. def test_override_get_auto_imports(self):
  356. class TestCommand(shell.Command):
  357. def get_auto_imports(self):
  358. return [
  359. "model_forms",
  360. "shell",
  361. "does.not.exist",
  362. "doesntexisteither",
  363. ]
  364. with captured_stdout() as stdout:
  365. TestCommand().get_namespace(verbosity=2)
  366. expected = (
  367. "2 objects could not be automatically imported:\n\n"
  368. " does.not.exist\n"
  369. " doesntexisteither\n\n"
  370. "2 objects imported automatically:\n\n"
  371. " import model_forms\n"
  372. " import shell\n\n"
  373. )
  374. self.assertEqual(stdout.getvalue(), expected)
  375. def test_override_get_auto_imports_one_error(self):
  376. class TestCommand(shell.Command):
  377. def get_auto_imports(self):
  378. return [
  379. "foo",
  380. ]
  381. expected = (
  382. "1 object could not be automatically imported:\n\n foo\n\n"
  383. "0 objects imported automatically.\n\n"
  384. )
  385. for verbosity, expected in [(0, ""), (1, expected), (2, expected)]:
  386. with self.subTest(verbosity=verbosity):
  387. with captured_stdout() as stdout:
  388. TestCommand().get_namespace(verbosity=verbosity)
  389. self.assertEqual(stdout.getvalue(), expected)
  390. def test_override_get_auto_imports_many_errors(self):
  391. class TestCommand(shell.Command):
  392. def get_auto_imports(self):
  393. return [
  394. "does.not.exist",
  395. "doesntexisteither",
  396. ]
  397. expected = (
  398. "2 objects could not be automatically imported:\n\n"
  399. " does.not.exist\n"
  400. " doesntexisteither\n\n"
  401. "0 objects imported automatically.\n\n"
  402. )
  403. for verbosity, expected in [(0, ""), (1, expected), (2, expected)]:
  404. with self.subTest(verbosity=verbosity):
  405. with captured_stdout() as stdout:
  406. TestCommand().get_namespace(verbosity=verbosity)
  407. self.assertEqual(stdout.getvalue(), expected)