tests.py 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537
  1. import os
  2. from argparse import ArgumentDefaultsHelpFormatter
  3. from io import StringIO
  4. from unittest import mock
  5. from admin_scripts.tests import AdminScriptTestCase
  6. from django.apps import apps
  7. from django.core import management
  8. from django.core.checks import Tags
  9. from django.core.management import BaseCommand, CommandError, find_commands
  10. from django.core.management.utils import (
  11. find_command,
  12. get_random_secret_key,
  13. is_ignored_path,
  14. normalize_path_patterns,
  15. popen_wrapper,
  16. )
  17. from django.db import connection
  18. from django.test import SimpleTestCase, override_settings
  19. from django.test.utils import captured_stderr, extend_sys_path
  20. from django.utils import translation
  21. from .management.commands import dance
  22. # A minimal set of apps to avoid system checks running on all apps.
  23. @override_settings(
  24. INSTALLED_APPS=[
  25. "django.contrib.auth",
  26. "django.contrib.contenttypes",
  27. "user_commands",
  28. ],
  29. )
  30. class CommandTests(SimpleTestCase):
  31. def test_command(self):
  32. out = StringIO()
  33. management.call_command("dance", stdout=out)
  34. self.assertIn("I don't feel like dancing Rock'n'Roll.\n", out.getvalue())
  35. def test_command_style(self):
  36. out = StringIO()
  37. management.call_command("dance", style="Jive", stdout=out)
  38. self.assertIn("I don't feel like dancing Jive.\n", out.getvalue())
  39. # Passing options as arguments also works (thanks argparse)
  40. management.call_command("dance", "--style", "Jive", stdout=out)
  41. self.assertIn("I don't feel like dancing Jive.\n", out.getvalue())
  42. def test_language_preserved(self):
  43. with translation.override("fr"):
  44. management.call_command("dance", verbosity=0)
  45. self.assertEqual(translation.get_language(), "fr")
  46. def test_explode(self):
  47. """An unknown command raises CommandError"""
  48. with self.assertRaisesMessage(CommandError, "Unknown command: 'explode'"):
  49. management.call_command(("explode",))
  50. def test_system_exit(self):
  51. """Exception raised in a command should raise CommandError with
  52. call_command, but SystemExit when run from command line
  53. """
  54. with self.assertRaises(CommandError) as cm:
  55. management.call_command("dance", example="raise")
  56. self.assertEqual(cm.exception.returncode, 3)
  57. dance.Command.requires_system_checks = []
  58. try:
  59. with captured_stderr() as stderr, self.assertRaises(SystemExit) as cm:
  60. management.ManagementUtility(
  61. ["manage.py", "dance", "--example=raise"]
  62. ).execute()
  63. self.assertEqual(cm.exception.code, 3)
  64. finally:
  65. dance.Command.requires_system_checks = "__all__"
  66. self.assertIn("CommandError", stderr.getvalue())
  67. def test_no_translations_deactivate_translations(self):
  68. """
  69. When the Command handle method is decorated with @no_translations,
  70. translations are deactivated inside the command.
  71. """
  72. current_locale = translation.get_language()
  73. with translation.override("pl"):
  74. result = management.call_command("no_translations")
  75. self.assertIsNone(result)
  76. self.assertEqual(translation.get_language(), current_locale)
  77. def test_find_command_without_PATH(self):
  78. """
  79. find_command should still work when the PATH environment variable
  80. doesn't exist (#22256).
  81. """
  82. current_path = os.environ.pop("PATH", None)
  83. try:
  84. self.assertIsNone(find_command("_missing_"))
  85. finally:
  86. if current_path is not None:
  87. os.environ["PATH"] = current_path
  88. def test_discover_commands_in_eggs(self):
  89. """
  90. Management commands can also be loaded from Python eggs.
  91. """
  92. egg_dir = "%s/eggs" % os.path.dirname(__file__)
  93. egg_name = "%s/basic.egg" % egg_dir
  94. with extend_sys_path(egg_name):
  95. with self.settings(INSTALLED_APPS=["commandegg"]):
  96. cmds = find_commands(
  97. os.path.join(apps.get_app_config("commandegg").path, "management")
  98. )
  99. self.assertEqual(cmds, ["eggcommand"])
  100. def test_call_command_option_parsing(self):
  101. """
  102. When passing the long option name to call_command, the available option
  103. key is the option dest name (#22985).
  104. """
  105. out = StringIO()
  106. management.call_command("dance", stdout=out, opt_3=True)
  107. self.assertIn("option3", out.getvalue())
  108. self.assertNotIn("opt_3", out.getvalue())
  109. self.assertNotIn("opt-3", out.getvalue())
  110. def test_call_command_option_parsing_non_string_arg(self):
  111. """
  112. It should be possible to pass non-string arguments to call_command.
  113. """
  114. out = StringIO()
  115. management.call_command("dance", 1, verbosity=0, stdout=out)
  116. self.assertIn("You passed 1 as a positional argument.", out.getvalue())
  117. def test_calling_a_command_with_only_empty_parameter_should_ends_gracefully(self):
  118. out = StringIO()
  119. management.call_command("hal", "--empty", stdout=out)
  120. self.assertEqual(out.getvalue(), "\nDave, I can't do that.\n")
  121. def test_calling_command_with_app_labels_and_parameters_should_be_ok(self):
  122. out = StringIO()
  123. management.call_command("hal", "myapp", "--verbosity", "3", stdout=out)
  124. self.assertIn(
  125. "Dave, my mind is going. I can feel it. I can feel it.\n", out.getvalue()
  126. )
  127. def test_calling_command_with_parameters_and_app_labels_at_the_end_should_be_ok(
  128. self,
  129. ):
  130. out = StringIO()
  131. management.call_command("hal", "--verbosity", "3", "myapp", stdout=out)
  132. self.assertIn(
  133. "Dave, my mind is going. I can feel it. I can feel it.\n", out.getvalue()
  134. )
  135. def test_calling_a_command_with_no_app_labels_and_parameters_raise_command_error(
  136. self,
  137. ):
  138. with self.assertRaises(CommandError):
  139. management.call_command("hal")
  140. def test_output_transaction(self):
  141. output = management.call_command(
  142. "transaction", stdout=StringIO(), no_color=True
  143. )
  144. self.assertTrue(
  145. output.strip().startswith(connection.ops.start_transaction_sql())
  146. )
  147. self.assertTrue(output.strip().endswith(connection.ops.end_transaction_sql()))
  148. def test_call_command_no_checks(self):
  149. """
  150. By default, call_command should not trigger the check framework, unless
  151. specifically asked.
  152. """
  153. self.counter = 0
  154. def patched_check(self_, **kwargs):
  155. self.counter += 1
  156. self.kwargs = kwargs
  157. saved_check = BaseCommand.check
  158. BaseCommand.check = patched_check
  159. try:
  160. management.call_command("dance", verbosity=0)
  161. self.assertEqual(self.counter, 0)
  162. management.call_command("dance", verbosity=0, skip_checks=False)
  163. self.assertEqual(self.counter, 1)
  164. self.assertEqual(self.kwargs, {})
  165. finally:
  166. BaseCommand.check = saved_check
  167. def test_requires_system_checks_empty(self):
  168. with mock.patch(
  169. "django.core.management.base.BaseCommand.check"
  170. ) as mocked_check:
  171. management.call_command("no_system_checks")
  172. self.assertIs(mocked_check.called, False)
  173. def test_requires_system_checks_specific(self):
  174. with mock.patch(
  175. "django.core.management.base.BaseCommand.check"
  176. ) as mocked_check:
  177. management.call_command("specific_system_checks", skip_checks=False)
  178. mocked_check.assert_called_once_with(tags=[Tags.staticfiles, Tags.models])
  179. def test_requires_system_checks_invalid(self):
  180. class Command(BaseCommand):
  181. requires_system_checks = "x"
  182. msg = "requires_system_checks must be a list or tuple."
  183. with self.assertRaisesMessage(TypeError, msg):
  184. Command()
  185. def test_check_migrations(self):
  186. requires_migrations_checks = dance.Command.requires_migrations_checks
  187. self.assertIs(requires_migrations_checks, False)
  188. try:
  189. with mock.patch.object(BaseCommand, "check_migrations") as check_migrations:
  190. management.call_command("dance", verbosity=0)
  191. self.assertFalse(check_migrations.called)
  192. dance.Command.requires_migrations_checks = True
  193. management.call_command("dance", verbosity=0)
  194. self.assertTrue(check_migrations.called)
  195. finally:
  196. dance.Command.requires_migrations_checks = requires_migrations_checks
  197. def test_call_command_unrecognized_option(self):
  198. msg = (
  199. "Unknown option(s) for dance command: unrecognized. Valid options "
  200. "are: example, force_color, help, integer, no_color, opt_3, "
  201. "option3, pythonpath, settings, skip_checks, stderr, stdout, "
  202. "style, traceback, verbosity, version."
  203. )
  204. with self.assertRaisesMessage(TypeError, msg):
  205. management.call_command("dance", unrecognized=1)
  206. msg = (
  207. "Unknown option(s) for dance command: unrecognized, unrecognized2. "
  208. "Valid options are: example, force_color, help, integer, no_color, "
  209. "opt_3, option3, pythonpath, settings, skip_checks, stderr, "
  210. "stdout, style, traceback, verbosity, version."
  211. )
  212. with self.assertRaisesMessage(TypeError, msg):
  213. management.call_command("dance", unrecognized=1, unrecognized2=1)
  214. def test_call_command_with_required_parameters_in_options(self):
  215. out = StringIO()
  216. management.call_command(
  217. "required_option", need_me="foo", needme2="bar", stdout=out
  218. )
  219. self.assertIn("need_me", out.getvalue())
  220. self.assertIn("needme2", out.getvalue())
  221. def test_call_command_with_required_parameters_in_mixed_options(self):
  222. out = StringIO()
  223. management.call_command(
  224. "required_option", "--need-me=foo", needme2="bar", stdout=out
  225. )
  226. self.assertIn("need_me", out.getvalue())
  227. self.assertIn("needme2", out.getvalue())
  228. def test_command_add_arguments_after_common_arguments(self):
  229. out = StringIO()
  230. management.call_command("common_args", stdout=out)
  231. self.assertIn("Detected that --version already exists", out.getvalue())
  232. def test_mutually_exclusive_group_required_options(self):
  233. out = StringIO()
  234. management.call_command("mutually_exclusive_required", foo_id=1, stdout=out)
  235. self.assertIn("foo_id", out.getvalue())
  236. management.call_command(
  237. "mutually_exclusive_required", foo_name="foo", stdout=out
  238. )
  239. self.assertIn("foo_name", out.getvalue())
  240. msg = (
  241. "Error: one of the arguments --foo-id --foo-name --foo-list "
  242. "--append_const --const --count --flag_false --flag_true is "
  243. "required"
  244. )
  245. with self.assertRaisesMessage(CommandError, msg):
  246. management.call_command("mutually_exclusive_required", stdout=out)
  247. def test_mutually_exclusive_group_required_const_options(self):
  248. tests = [
  249. ("append_const", [42]),
  250. ("const", 31),
  251. ("count", 1),
  252. ("flag_false", False),
  253. ("flag_true", True),
  254. ]
  255. for arg, value in tests:
  256. out = StringIO()
  257. expected_output = "%s=%s" % (arg, value)
  258. with self.subTest(arg=arg):
  259. management.call_command(
  260. "mutually_exclusive_required",
  261. "--%s" % arg,
  262. stdout=out,
  263. )
  264. self.assertIn(expected_output, out.getvalue())
  265. out.truncate(0)
  266. management.call_command(
  267. "mutually_exclusive_required",
  268. **{arg: value, "stdout": out},
  269. )
  270. self.assertIn(expected_output, out.getvalue())
  271. def test_mutually_exclusive_group_required_with_same_dest_options(self):
  272. tests = [
  273. {"until": "2"},
  274. {"for": "1", "until": "2"},
  275. ]
  276. msg = (
  277. "Cannot pass the dest 'until' that matches multiple arguments via "
  278. "**options."
  279. )
  280. for options in tests:
  281. with self.subTest(options=options):
  282. with self.assertRaisesMessage(TypeError, msg):
  283. management.call_command(
  284. "mutually_exclusive_required_with_same_dest",
  285. **options,
  286. )
  287. def test_mutually_exclusive_group_required_with_same_dest_args(self):
  288. tests = [
  289. ("--until=1",),
  290. ("--until", 1),
  291. ("--for=1",),
  292. ("--for", 1),
  293. ]
  294. for args in tests:
  295. out = StringIO()
  296. with self.subTest(options=args):
  297. management.call_command(
  298. "mutually_exclusive_required_with_same_dest",
  299. *args,
  300. stdout=out,
  301. )
  302. output = out.getvalue()
  303. self.assertIn("until=1", output)
  304. def test_required_list_option(self):
  305. tests = [
  306. (("--foo-list", [1, 2]), {}),
  307. ((), {"foo_list": [1, 2]}),
  308. ]
  309. for command in ["mutually_exclusive_required", "required_list_option"]:
  310. for args, kwargs in tests:
  311. with self.subTest(command=command, args=args, kwargs=kwargs):
  312. out = StringIO()
  313. management.call_command(
  314. command,
  315. *args,
  316. **kwargs,
  317. stdout=out,
  318. )
  319. self.assertIn("foo_list=[1, 2]", out.getvalue())
  320. def test_required_const_options(self):
  321. args = {
  322. "append_const": [42],
  323. "const": 31,
  324. "count": 1,
  325. "flag_false": False,
  326. "flag_true": True,
  327. }
  328. expected_output = "\n".join(
  329. "%s=%s" % (arg, value) for arg, value in args.items()
  330. )
  331. out = StringIO()
  332. management.call_command(
  333. "required_constant_option",
  334. "--append_const",
  335. "--const",
  336. "--count",
  337. "--flag_false",
  338. "--flag_true",
  339. stdout=out,
  340. )
  341. self.assertIn(expected_output, out.getvalue())
  342. out.truncate(0)
  343. management.call_command("required_constant_option", **args, stdout=out)
  344. self.assertIn(expected_output, out.getvalue())
  345. def test_subparser(self):
  346. out = StringIO()
  347. management.call_command("subparser", "foo", 12, stdout=out)
  348. self.assertIn("bar", out.getvalue())
  349. def test_subparser_dest_args(self):
  350. out = StringIO()
  351. management.call_command("subparser_dest", "foo", bar=12, stdout=out)
  352. self.assertIn("bar", out.getvalue())
  353. def test_subparser_dest_required_args(self):
  354. out = StringIO()
  355. management.call_command(
  356. "subparser_required", "foo_1", "foo_2", bar=12, stdout=out
  357. )
  358. self.assertIn("bar", out.getvalue())
  359. def test_subparser_invalid_option(self):
  360. msg = "invalid choice: 'test' (choose from 'foo')"
  361. with self.assertRaisesMessage(CommandError, msg):
  362. management.call_command("subparser", "test", 12)
  363. msg = "Error: the following arguments are required: subcommand"
  364. with self.assertRaisesMessage(CommandError, msg):
  365. management.call_command("subparser_dest", subcommand="foo", bar=12)
  366. def test_create_parser_kwargs(self):
  367. """BaseCommand.create_parser() passes kwargs to CommandParser."""
  368. epilog = "some epilog text"
  369. parser = BaseCommand().create_parser(
  370. "prog_name",
  371. "subcommand",
  372. epilog=epilog,
  373. formatter_class=ArgumentDefaultsHelpFormatter,
  374. )
  375. self.assertEqual(parser.epilog, epilog)
  376. self.assertEqual(parser.formatter_class, ArgumentDefaultsHelpFormatter)
  377. def test_outputwrapper_flush(self):
  378. out = StringIO()
  379. with mock.patch.object(out, "flush") as mocked_flush:
  380. management.call_command("outputwrapper", stdout=out)
  381. self.assertIn("Working...", out.getvalue())
  382. self.assertIs(mocked_flush.called, True)
  383. class CommandRunTests(AdminScriptTestCase):
  384. """
  385. Tests that need to run by simulating the command line, not by call_command.
  386. """
  387. def test_script_prefix_set_in_commands(self):
  388. self.write_settings(
  389. "settings.py",
  390. apps=["user_commands"],
  391. sdict={
  392. "ROOT_URLCONF": '"user_commands.urls"',
  393. "FORCE_SCRIPT_NAME": '"/PREFIX/"',
  394. },
  395. )
  396. out, err = self.run_manage(["reverse_url"])
  397. self.assertNoOutput(err)
  398. self.assertEqual(out.strip(), "/PREFIX/some/url/")
  399. def test_disallowed_abbreviated_options(self):
  400. """
  401. To avoid conflicts with custom options, commands don't allow
  402. abbreviated forms of the --setting and --pythonpath options.
  403. """
  404. self.write_settings("settings.py", apps=["user_commands"])
  405. out, err = self.run_manage(["set_option", "--set", "foo"])
  406. self.assertNoOutput(err)
  407. self.assertEqual(out.strip(), "Set foo")
  408. def test_skip_checks(self):
  409. self.write_settings(
  410. "settings.py",
  411. apps=["django.contrib.staticfiles", "user_commands"],
  412. sdict={
  413. # (staticfiles.E001) The STATICFILES_DIRS setting is not a tuple or
  414. # list.
  415. "STATICFILES_DIRS": '"foo"',
  416. },
  417. )
  418. out, err = self.run_manage(["set_option", "--skip-checks", "--set", "foo"])
  419. self.assertNoOutput(err)
  420. self.assertEqual(out.strip(), "Set foo")
  421. def test_subparser_error_formatting(self):
  422. self.write_settings("settings.py", apps=["user_commands"])
  423. out, err = self.run_manage(["subparser", "foo", "twelve"])
  424. self.maxDiff = None
  425. self.assertNoOutput(out)
  426. err_lines = err.splitlines()
  427. self.assertEqual(len(err_lines), 2)
  428. self.assertEqual(
  429. err_lines[1],
  430. "manage.py subparser foo: error: argument bar: invalid int value: 'twelve'",
  431. )
  432. def test_subparser_non_django_error_formatting(self):
  433. self.write_settings("settings.py", apps=["user_commands"])
  434. out, err = self.run_manage(["subparser_vanilla", "foo", "seven"])
  435. self.assertNoOutput(out)
  436. err_lines = err.splitlines()
  437. self.assertEqual(len(err_lines), 2)
  438. self.assertEqual(
  439. err_lines[1],
  440. "manage.py subparser_vanilla foo: error: argument bar: invalid int value: "
  441. "'seven'",
  442. )
  443. class UtilsTests(SimpleTestCase):
  444. def test_no_existent_external_program(self):
  445. msg = "Error executing a_42_command_that_doesnt_exist_42"
  446. with self.assertRaisesMessage(CommandError, msg):
  447. popen_wrapper(["a_42_command_that_doesnt_exist_42"])
  448. def test_get_random_secret_key(self):
  449. key = get_random_secret_key()
  450. self.assertEqual(len(key), 50)
  451. for char in key:
  452. self.assertIn(char, "abcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*(-_=+)")
  453. def test_is_ignored_path_true(self):
  454. patterns = (
  455. ["foo/bar/baz"],
  456. ["baz"],
  457. ["foo/bar/baz"],
  458. ["*/baz"],
  459. ["*"],
  460. ["b?z"],
  461. ["[abc]az"],
  462. ["*/ba[!z]/baz"],
  463. )
  464. for ignore_patterns in patterns:
  465. with self.subTest(ignore_patterns=ignore_patterns):
  466. self.assertIs(
  467. is_ignored_path("foo/bar/baz", ignore_patterns=ignore_patterns),
  468. True,
  469. )
  470. def test_is_ignored_path_false(self):
  471. self.assertIs(
  472. is_ignored_path(
  473. "foo/bar/baz", ignore_patterns=["foo/bar/bat", "bar", "flub/blub"]
  474. ),
  475. False,
  476. )
  477. def test_normalize_path_patterns_truncates_wildcard_base(self):
  478. expected = [os.path.normcase(p) for p in ["foo/bar", "bar/*/"]]
  479. self.assertEqual(normalize_path_patterns(["foo/bar/*", "bar/*/"]), expected)