mirror of
https://github.com/django/django.git
synced 2025-03-06 23:42:33 +00:00
Co-authored-by: Salvo Polizzi <salvopolizzi03@gmail.com> Backport of 0597e8ad1e55b565292ead732916aa0e39bdf37b from main.
349 lines
12 KiB
Python
349 lines
12 KiB
Python
import sys
|
|
import unittest
|
|
from unittest import mock
|
|
|
|
from django import __version__
|
|
from django.contrib.auth.models import Group, Permission, User
|
|
from django.contrib.contenttypes.models import ContentType
|
|
from django.core.management import CommandError, call_command
|
|
from django.core.management.commands import shell
|
|
from django.db import connection, models
|
|
from django.test import SimpleTestCase
|
|
from django.test.utils import (
|
|
captured_stdin,
|
|
captured_stdout,
|
|
isolate_apps,
|
|
override_settings,
|
|
)
|
|
from django.urls.base import resolve, reverse
|
|
|
|
from .models import Marker, Phone
|
|
|
|
|
|
class ShellCommandTestCase(SimpleTestCase):
|
|
script_globals = 'print("__name__" in globals() and "Phone" in globals())'
|
|
script_with_inline_function = (
|
|
"import django\ndef f():\n print(django.__version__)\nf()"
|
|
)
|
|
|
|
def test_command_option(self):
|
|
with self.assertLogs("test", "INFO") as cm:
|
|
call_command(
|
|
"shell",
|
|
command=(
|
|
"import django; from logging import getLogger; "
|
|
'getLogger("test").info(django.__version__)'
|
|
),
|
|
)
|
|
self.assertEqual(cm.records[0].getMessage(), __version__)
|
|
|
|
def test_command_option_globals(self):
|
|
with captured_stdout() as stdout:
|
|
call_command("shell", command=self.script_globals)
|
|
self.assertEqual(stdout.getvalue().strip(), "True")
|
|
|
|
def test_command_option_inline_function_call(self):
|
|
with captured_stdout() as stdout:
|
|
call_command("shell", command=self.script_with_inline_function)
|
|
self.assertEqual(stdout.getvalue().strip(), __version__)
|
|
|
|
@unittest.skipIf(
|
|
sys.platform == "win32", "Windows select() doesn't support file descriptors."
|
|
)
|
|
@mock.patch("django.core.management.commands.shell.select")
|
|
def test_stdin_read(self, select):
|
|
with captured_stdin() as stdin, captured_stdout() as stdout:
|
|
stdin.write("print(100)\n")
|
|
stdin.seek(0)
|
|
call_command("shell")
|
|
self.assertEqual(stdout.getvalue().strip(), "100")
|
|
|
|
@unittest.skipIf(
|
|
sys.platform == "win32",
|
|
"Windows select() doesn't support file descriptors.",
|
|
)
|
|
@mock.patch("django.core.management.commands.shell.select") # [1]
|
|
def test_stdin_read_globals(self, select):
|
|
with captured_stdin() as stdin, captured_stdout() as stdout:
|
|
stdin.write(self.script_globals)
|
|
stdin.seek(0)
|
|
call_command("shell")
|
|
self.assertEqual(stdout.getvalue().strip(), "True")
|
|
|
|
@unittest.skipIf(
|
|
sys.platform == "win32",
|
|
"Windows select() doesn't support file descriptors.",
|
|
)
|
|
@mock.patch("django.core.management.commands.shell.select") # [1]
|
|
def test_stdin_read_inline_function_call(self, select):
|
|
with captured_stdin() as stdin, captured_stdout() as stdout:
|
|
stdin.write(self.script_with_inline_function)
|
|
stdin.seek(0)
|
|
call_command("shell")
|
|
self.assertEqual(stdout.getvalue().strip(), __version__)
|
|
|
|
def test_ipython(self):
|
|
cmd = shell.Command()
|
|
mock_ipython = mock.Mock(start_ipython=mock.MagicMock())
|
|
options = {"verbosity": 0, "no_imports": False}
|
|
|
|
with mock.patch.dict(sys.modules, {"IPython": mock_ipython}):
|
|
cmd.ipython(options)
|
|
|
|
self.assertEqual(
|
|
mock_ipython.start_ipython.mock_calls,
|
|
[mock.call(argv=[], user_ns=cmd.get_and_report_namespace(**options))],
|
|
)
|
|
|
|
@mock.patch("django.core.management.commands.shell.select.select") # [1]
|
|
@mock.patch.dict("sys.modules", {"IPython": None})
|
|
def test_shell_with_ipython_not_installed(self, select):
|
|
select.return_value = ([], [], [])
|
|
with self.assertRaisesMessage(
|
|
CommandError, "Couldn't import ipython interface."
|
|
):
|
|
call_command("shell", interface="ipython")
|
|
|
|
def test_bpython(self):
|
|
cmd = shell.Command()
|
|
mock_bpython = mock.Mock(embed=mock.MagicMock())
|
|
options = {"verbosity": 0, "no_imports": False}
|
|
|
|
with mock.patch.dict(sys.modules, {"bpython": mock_bpython}):
|
|
cmd.bpython(options)
|
|
|
|
self.assertEqual(
|
|
mock_bpython.embed.mock_calls,
|
|
[mock.call(cmd.get_and_report_namespace(**options))],
|
|
)
|
|
|
|
@mock.patch("django.core.management.commands.shell.select.select") # [1]
|
|
@mock.patch.dict("sys.modules", {"bpython": None})
|
|
def test_shell_with_bpython_not_installed(self, select):
|
|
select.return_value = ([], [], [])
|
|
with self.assertRaisesMessage(
|
|
CommandError, "Couldn't import bpython interface."
|
|
):
|
|
call_command("shell", interface="bpython")
|
|
|
|
def test_python(self):
|
|
cmd = shell.Command()
|
|
mock_code = mock.Mock(interact=mock.MagicMock())
|
|
options = {"verbosity": 0, "no_startup": True, "no_imports": False}
|
|
|
|
with mock.patch.dict(sys.modules, {"code": mock_code}):
|
|
cmd.python(options)
|
|
|
|
self.assertEqual(
|
|
mock_code.interact.mock_calls,
|
|
[mock.call(local=cmd.get_and_report_namespace(**options))],
|
|
)
|
|
|
|
# [1] Patch select to prevent tests failing when the test suite is run
|
|
# in parallel mode. The tests are run in a subprocess and the subprocess's
|
|
# stdin is closed and replaced by /dev/null. Reading from /dev/null always
|
|
# returns EOF and so select always shows that sys.stdin is ready to read.
|
|
# This causes problems because of the call to select.select() toward the
|
|
# end of shell's handle() method.
|
|
|
|
|
|
class ShellCommandAutoImportsTestCase(SimpleTestCase):
|
|
|
|
@override_settings(
|
|
INSTALLED_APPS=["shell", "django.contrib.auth", "django.contrib.contenttypes"]
|
|
)
|
|
def test_get_namespace(self):
|
|
namespace = shell.Command().get_namespace()
|
|
|
|
self.assertEqual(
|
|
namespace,
|
|
{
|
|
"Marker": Marker,
|
|
"Phone": Phone,
|
|
"ContentType": ContentType,
|
|
"Group": Group,
|
|
"Permission": Permission,
|
|
"User": User,
|
|
},
|
|
)
|
|
|
|
@override_settings(INSTALLED_APPS=["basic", "shell"])
|
|
@isolate_apps("basic", "shell", kwarg_name="apps")
|
|
def test_get_namespace_precedence(self, apps):
|
|
class Article(models.Model):
|
|
class Meta:
|
|
app_label = "basic"
|
|
|
|
winner_article = Article
|
|
|
|
class Article(models.Model):
|
|
class Meta:
|
|
app_label = "shell"
|
|
|
|
with mock.patch("django.apps.apps.get_models", return_value=apps.get_models()):
|
|
namespace = shell.Command().get_namespace()
|
|
self.assertEqual(namespace, {"Article": winner_article})
|
|
|
|
@override_settings(
|
|
INSTALLED_APPS=["shell", "django.contrib.auth", "django.contrib.contenttypes"]
|
|
)
|
|
def test_get_namespace_overridden(self):
|
|
class TestCommand(shell.Command):
|
|
def get_namespace(self):
|
|
from django.urls.base import resolve, reverse
|
|
|
|
return {
|
|
**super().get_namespace(),
|
|
"resolve": resolve,
|
|
"reverse": reverse,
|
|
}
|
|
|
|
namespace = TestCommand().get_namespace()
|
|
|
|
self.assertEqual(
|
|
namespace,
|
|
{
|
|
"resolve": resolve,
|
|
"reverse": reverse,
|
|
"Marker": Marker,
|
|
"Phone": Phone,
|
|
"ContentType": ContentType,
|
|
"Group": Group,
|
|
"Permission": Permission,
|
|
"User": User,
|
|
},
|
|
)
|
|
|
|
@override_settings(
|
|
INSTALLED_APPS=["shell", "django.contrib.auth", "django.contrib.contenttypes"]
|
|
)
|
|
def test_no_imports_flag(self):
|
|
for verbosity in (0, 1, 2, 3):
|
|
with self.subTest(verbosity=verbosity), captured_stdout() as stdout:
|
|
namespace = shell.Command().get_and_report_namespace(
|
|
verbosity=verbosity, no_imports=True
|
|
)
|
|
self.assertEqual(namespace, {})
|
|
self.assertEqual(stdout.getvalue().strip(), "")
|
|
|
|
@override_settings(
|
|
INSTALLED_APPS=["shell", "django.contrib.auth", "django.contrib.contenttypes"]
|
|
)
|
|
def test_verbosity_zero(self):
|
|
with captured_stdout() as stdout:
|
|
cmd = shell.Command()
|
|
namespace = cmd.get_and_report_namespace(verbosity=0)
|
|
self.assertEqual(namespace, cmd.get_namespace())
|
|
self.assertEqual(stdout.getvalue().strip(), "")
|
|
|
|
@override_settings(
|
|
INSTALLED_APPS=["shell", "django.contrib.auth", "django.contrib.contenttypes"]
|
|
)
|
|
def test_verbosity_one(self):
|
|
with captured_stdout() as stdout:
|
|
cmd = shell.Command()
|
|
namespace = cmd.get_and_report_namespace(verbosity=1)
|
|
self.assertEqual(namespace, cmd.get_namespace())
|
|
self.assertEqual(
|
|
stdout.getvalue().strip(),
|
|
"6 objects imported automatically (use -v 2 for details).",
|
|
)
|
|
|
|
@override_settings(INSTALLED_APPS=["shell", "django.contrib.contenttypes"])
|
|
@mock.patch.dict(sys.modules, {"isort": None})
|
|
def test_message_with_stdout_listing_objects_with_isort_not_installed(self):
|
|
class TestCommand(shell.Command):
|
|
def get_namespace(self):
|
|
class MyClass:
|
|
pass
|
|
|
|
constant = "constant"
|
|
|
|
return {
|
|
**super().get_namespace(),
|
|
"MyClass": MyClass,
|
|
"constant": constant,
|
|
}
|
|
|
|
with captured_stdout() as stdout:
|
|
TestCommand().get_and_report_namespace(verbosity=2)
|
|
|
|
self.assertEqual(
|
|
stdout.getvalue().strip(),
|
|
"5 objects imported automatically, including:\n\n"
|
|
" from django.contrib.contenttypes.models import ContentType\n"
|
|
" from shell.models import Phone, Marker",
|
|
)
|
|
|
|
def test_message_with_stdout_one_object(self):
|
|
class TestCommand(shell.Command):
|
|
def get_namespace(self):
|
|
return {"connection": connection}
|
|
|
|
with captured_stdout() as stdout:
|
|
TestCommand().get_and_report_namespace(verbosity=2)
|
|
|
|
cases = {
|
|
0: "",
|
|
1: "1 object imported automatically (use -v 2 for details).",
|
|
2: (
|
|
"1 object imported automatically, including:\n\n"
|
|
" from django.utils.connection import connection"
|
|
),
|
|
}
|
|
for verbosity, expected in cases.items():
|
|
with self.subTest(verbosity=verbosity):
|
|
with captured_stdout() as stdout:
|
|
TestCommand().get_and_report_namespace(verbosity=verbosity)
|
|
self.assertEqual(stdout.getvalue().strip(), expected)
|
|
|
|
def test_message_with_stdout_zero_objects(self):
|
|
class TestCommand(shell.Command):
|
|
def get_namespace(self):
|
|
return {}
|
|
|
|
cases = {
|
|
0: "",
|
|
1: "0 objects imported automatically.",
|
|
2: "0 objects imported automatically.",
|
|
}
|
|
for verbosity, expected in cases.items():
|
|
with self.subTest(verbosity=verbosity):
|
|
with captured_stdout() as stdout:
|
|
TestCommand().get_and_report_namespace(verbosity=verbosity)
|
|
self.assertEqual(stdout.getvalue().strip(), expected)
|
|
|
|
@override_settings(INSTALLED_APPS=["shell", "django.contrib.contenttypes"])
|
|
def test_message_with_stdout_listing_objects_with_isort(self):
|
|
sorted_imports = (
|
|
" from shell.models import Marker, Phone\n\n"
|
|
" from django.contrib.contenttypes.models import ContentType"
|
|
)
|
|
mock_isort_code = mock.Mock(code=mock.MagicMock(return_value=sorted_imports))
|
|
|
|
class TestCommand(shell.Command):
|
|
def get_namespace(self):
|
|
class MyClass:
|
|
pass
|
|
|
|
constant = "constant"
|
|
|
|
return {
|
|
**super().get_namespace(),
|
|
"MyClass": MyClass,
|
|
"constant": constant,
|
|
}
|
|
|
|
with (
|
|
mock.patch.dict(sys.modules, {"isort": mock_isort_code}),
|
|
captured_stdout() as stdout,
|
|
):
|
|
TestCommand().get_and_report_namespace(verbosity=2)
|
|
|
|
self.assertEqual(
|
|
stdout.getvalue().strip(),
|
|
"5 objects imported automatically, including:\n\n"
|
|
" from shell.models import Marker, Phone\n\n"
|
|
" from django.contrib.contenttypes.models import ContentType",
|
|
)
|