From 494ba051bb0d3ebbdbea7598251b1ee6fe9b69b4 Mon Sep 17 00:00:00 2001 From: Loic Bistuer Date: Wed, 22 Oct 2014 00:15:10 +0700 Subject: [PATCH] Made testing of stdout and stderr more consistent. Refs #23663. --- tests/admin_scripts/tests.py | 53 ++++++------ tests/cache/tests.py | 15 ++-- tests/createsuperuser/tests.py | 9 +- tests/fixtures_regress/tests.py | 30 +++---- tests/i18n/test_extraction.py | 18 ++-- tests/migrations/test_commands.py | 133 +++++++++++++++--------------- 6 files changed, 123 insertions(+), 135 deletions(-) diff --git a/tests/admin_scripts/tests.py b/tests/admin_scripts/tests.py index f2c0d598cf..eabaf5fc15 100644 --- a/tests/admin_scripts/tests.py +++ b/tests/admin_scripts/tests.py @@ -1524,40 +1524,36 @@ class CommandTypes(AdminScriptTestCase): Test run_from_argv properly terminates even with custom execute() (#19665) Also test proper traceback display. """ - command = BaseCommand() + err = StringIO() + command = BaseCommand(stderr=err) def raise_command_error(*args, **kwargs): raise CommandError("Custom error") - old_stderr = sys.stderr - sys.stderr = err = StringIO() - try: - command.execute = lambda args: args # This will trigger TypeError + command.execute = lambda args: args # This will trigger TypeError - # If the Exception is not CommandError it should always - # raise the original exception. - with self.assertRaises(TypeError): - command.run_from_argv(['', '']) + # If the Exception is not CommandError it should always + # raise the original exception. + with self.assertRaises(TypeError): + command.run_from_argv(['', '']) - # If the Exception is CommandError and --traceback is not present - # this command should raise a SystemExit and don't print any - # traceback to the stderr. - command.execute = raise_command_error - err.truncate(0) - with self.assertRaises(SystemExit): - command.run_from_argv(['', '']) - err_message = err.getvalue() - self.assertNotIn("Traceback", err_message) - self.assertIn("CommandError", err_message) + # If the Exception is CommandError and --traceback is not present + # this command should raise a SystemExit and don't print any + # traceback to the stderr. + command.execute = raise_command_error + err.truncate(0) + with self.assertRaises(SystemExit): + command.run_from_argv(['', '']) + err_message = err.getvalue() + self.assertNotIn("Traceback", err_message) + self.assertIn("CommandError", err_message) - # If the Exception is CommandError and --traceback is present - # this command should raise the original CommandError as if it - # were not a CommandError. - err.truncate(0) - with self.assertRaises(CommandError): - command.run_from_argv(['', '', '--traceback']) - finally: - sys.stderr = old_stderr + # If the Exception is CommandError and --traceback is present + # this command should raise the original CommandError as if it + # were not a CommandError. + err.truncate(0) + with self.assertRaises(CommandError): + command.run_from_argv(['', '', '--traceback']) def test_run_from_argv_non_ascii_error(self): """ @@ -1567,9 +1563,8 @@ class CommandTypes(AdminScriptTestCase): def raise_command_error(*args, **kwargs): raise CommandError("Erreur personnalisée") - command = BaseCommand() + command = BaseCommand(stderr=StringIO()) command.execute = raise_command_error - command.stderr = StringIO() with self.assertRaises(SystemExit): command.run_from_argv(['', '']) diff --git a/tests/cache/tests.py b/tests/cache/tests.py index 6410e0b0be..ee003e9cac 100644 --- a/tests/cache/tests.py +++ b/tests/cache/tests.py @@ -912,12 +912,9 @@ class DBCacheTests(BaseCacheTests, TransactionTestCase): self._perform_cull_test(caches['zero_cull'], 50, 18) def test_second_call_doesnt_crash(self): - stdout = six.StringIO() - management.call_command( - 'createcachetable', - stdout=stdout - ) - self.assertEqual(stdout.getvalue(), + out = six.StringIO() + management.call_command('createcachetable', stdout=out) + self.assertEqual(out.getvalue(), "Cache table 'test cache table' already exists.\n" * len(settings.CACHES)) def test_createcachetable_with_table_argument(self): @@ -926,14 +923,14 @@ class DBCacheTests(BaseCacheTests, TransactionTestCase): specifying the table name). """ self.drop_table() - stdout = six.StringIO() + out = six.StringIO() management.call_command( 'createcachetable', 'test cache table', verbosity=2, - stdout=stdout + stdout=out, ) - self.assertEqual(stdout.getvalue(), + self.assertEqual(out.getvalue(), "Cache table 'test cache table' created.\n") def test_clear_commits_transaction(self): diff --git a/tests/createsuperuser/tests.py b/tests/createsuperuser/tests.py index b5036c85de..08f2ec5322 100644 --- a/tests/createsuperuser/tests.py +++ b/tests/createsuperuser/tests.py @@ -10,10 +10,6 @@ class MultiDBChangepasswordManagementCommandTestCase(TestCase): def setUp(self): self.user = models.User.objects.db_manager('other').create_user(username='joe', password='qwerty') - self.stdout = StringIO() - - def tearDown(self): - self.stdout.close() def test_that_changepassword_command_with_database_option_uses_given_db(self): """ @@ -24,8 +20,9 @@ class MultiDBChangepasswordManagementCommandTestCase(TestCase): command = changepassword.Command() command._get_pass = lambda *args: 'not qwerty' - command.execute(username="joe", database='other', stdout=self.stdout) - command_output = self.stdout.getvalue().strip() + out = StringIO() + command.execute(username="joe", database='other', stdout=out) + command_output = out.getvalue().strip() self.assertEqual(command_output, "Changing password for user 'joe'\nPassword changed successfully for user 'joe'") self.assertTrue(models.User.objects.using('other').get(username="joe").check_password("not qwerty")) diff --git a/tests/fixtures_regress/tests.py b/tests/fixtures_regress/tests.py index 62b1dca3a0..c5dfe3b3d4 100644 --- a/tests/fixtures_regress/tests.py +++ b/tests/fixtures_regress/tests.py @@ -60,7 +60,7 @@ class TestFixtures(TestCase): name='Platypus', latin_name='Ornithorhynchus anatinus', count=2, - weight=2.2 + weight=2.2, ) animal.save() self.assertGreater(animal.id, 1) @@ -75,7 +75,7 @@ class TestFixtures(TestCase): management.call_command( 'loaddata', 'sequence_extra', - verbosity=0 + verbosity=0, ) def test_loaddata_not_found_fields_ignore(self): @@ -359,20 +359,20 @@ class TestFixtures(TestCase): name='Platypus', latin_name='Ornithorhynchus anatinus', count=2, - weight=2.2 + weight=2.2, ) animal.save() - stdout = StringIO() + out = StringIO() management.call_command( 'dumpdata', 'fixtures_regress.animal', format='json', - stdout=stdout + stdout=out, ) # Output order isn't guaranteed, so check for parts - data = stdout.getvalue() + data = out.getvalue() # Get rid of artifacts like '000000002' to eliminate the differences # between different Python versions. @@ -393,7 +393,7 @@ class TestFixtures(TestCase): """ Regression for #11428 - Proxy models aren't included when you dumpdata """ - stdout = StringIO() + out = StringIO() # Create an instance of the concrete class widget = Widget.objects.create(name='grommet') management.call_command( @@ -401,10 +401,10 @@ class TestFixtures(TestCase): 'fixtures_regress.widget', 'fixtures_regress.widgetproxy', format='json', - stdout=stdout + stdout=out, ) self.assertJSONEqual( - stdout.getvalue(), + out.getvalue(), """[{"pk": %d, "model": "fixtures_regress.widget", "fields": {"name": "grommet"}}]""" % widget.pk ) @@ -554,7 +554,7 @@ class NaturalKeyFixtureTests(TestCase): verbosity=0, ) - stdout = StringIO() + out = StringIO() management.call_command( 'dumpdata', 'fixtures_regress.book', @@ -564,10 +564,10 @@ class NaturalKeyFixtureTests(TestCase): format='json', use_natural_foreign_keys=True, use_natural_primary_keys=True, - stdout=stdout, + stdout=out, ) self.assertJSONEqual( - stdout.getvalue(), + out.getvalue(), """[{"fields": {"main": null, "name": "Amazon"}, "model": "fixtures_regress.store"}, {"fields": {"main": null, "name": "Borders"}, "model": "fixtures_regress.store"}, {"fields": {"name": "Neal Stephenson"}, "model": "fixtures_regress.person"}, {"pk": 1, "model": "fixtures_regress.book", "fields": {"stores": [["Amazon"], ["Borders"]], "name": "Cryptonomicon", "author": ["Neal Stephenson"]}}]""" ) @@ -800,19 +800,19 @@ class M2MNaturalKeyFixtureTests(TestCase): a.b_set.add(b1) a.b_set.add(b2) - stdout = StringIO() + out = StringIO() management.call_command( 'dumpdata', 'fixtures_regress.M2MSimpleA', 'fixtures_regress.M2MSimpleB', use_natural_foreign_keys=True, - stdout=stdout + stdout=out, ) for model in [M2MSimpleA, M2MSimpleB]: model.objects.all().delete() - objects = serializers.deserialize("json", stdout.getvalue()) + objects = serializers.deserialize("json", out.getvalue()) for obj in objects: obj.save() diff --git a/tests/i18n/test_extraction.py b/tests/i18n/test_extraction.py index e569d8c561..c0c03ee506 100644 --- a/tests/i18n/test_extraction.py +++ b/tests/i18n/test_extraction.py @@ -57,10 +57,10 @@ class ExtractorTests(SimpleTestCase): def _run_makemessages(self, **options): os.chdir(self.test_dir) - stdout = StringIO() + out = StringIO() management.call_command('makemessages', locale=[LOCALE], verbosity=2, - stdout=stdout, **options) - output = stdout.getvalue() + stdout=out, **options) + output = out.getvalue() self.assertTrue(os.path.exists(self.PO_FILE)) with open(self.PO_FILE, 'r') as fp: po_contents = fp.read() @@ -224,19 +224,19 @@ class BasicExtractorTests(ExtractorTests): os.chdir(self.test_dir) shutil.copyfile('./not_utf8.sample', './not_utf8.txt') self.addCleanup(self.rmfile, os.path.join(self.test_dir, 'not_utf8.txt')) - stdout = StringIO() - management.call_command('makemessages', locale=[LOCALE], stdout=stdout) + out = StringIO() + management.call_command('makemessages', locale=[LOCALE], stdout=out) self.assertIn("UnicodeDecodeError: skipped file not_utf8.txt in .", - force_text(stdout.getvalue())) + force_text(out.getvalue())) def test_extraction_warning(self): """test xgettext warning about multiple bare interpolation placeholders""" os.chdir(self.test_dir) shutil.copyfile('./code.sample', './code_sample.py') self.addCleanup(self.rmfile, os.path.join(self.test_dir, 'code_sample.py')) - stdout = StringIO() - management.call_command('makemessages', locale=[LOCALE], stdout=stdout) - self.assertIn("code_sample.py:4", force_text(stdout.getvalue())) + out = StringIO() + management.call_command('makemessages', locale=[LOCALE], stdout=out) + self.assertIn("code_sample.py:4", force_text(out.getvalue())) def test_template_message_context_extractor(self): """ diff --git a/tests/migrations/test_commands.py b/tests/migrations/test_commands.py index 88f58341f2..1091603d1b 100644 --- a/tests/migrations/test_commands.py +++ b/tests/migrations/test_commands.py @@ -56,20 +56,20 @@ class MigrateTests(MigrationTestBase): """ Tests --list output of migrate command """ - stdout = six.StringIO() - call_command("migrate", list=True, stdout=stdout, verbosity=0) - self.assertIn("migrations", stdout.getvalue().lower()) - self.assertIn("[ ] 0001_initial", stdout.getvalue().lower()) - self.assertIn("[ ] 0002_second", stdout.getvalue().lower()) + out = six.StringIO() + call_command("migrate", list=True, stdout=out, verbosity=0) + self.assertIn("migrations", out.getvalue().lower()) + self.assertIn("[ ] 0001_initial", out.getvalue().lower()) + self.assertIn("[ ] 0002_second", out.getvalue().lower()) call_command("migrate", "migrations", "0001", verbosity=0) - stdout = six.StringIO() + out = six.StringIO() # Giving the explicit app_label tests for selective `show_migration_list` in the command - call_command("migrate", "migrations", list=True, stdout=stdout, verbosity=0) - self.assertIn("migrations", stdout.getvalue().lower()) - self.assertIn("[x] 0001_initial", stdout.getvalue().lower()) - self.assertIn("[ ] 0002_second", stdout.getvalue().lower()) + call_command("migrate", "migrations", list=True, stdout=out, verbosity=0) + self.assertIn("migrations", out.getvalue().lower()) + self.assertIn("[x] 0001_initial", out.getvalue().lower()) + self.assertIn("[ ] 0002_second", out.getvalue().lower()) # Cleanup by unmigrating everything call_command("migrate", "migrations", "zero", verbosity=0) @@ -87,24 +87,24 @@ class MigrateTests(MigrationTestBase): Makes sure that sqlmigrate does something. """ # Make sure the output is wrapped in a transaction - stdout = six.StringIO() - call_command("sqlmigrate", "migrations", "0001", stdout=stdout) - output = stdout.getvalue() + out = six.StringIO() + call_command("sqlmigrate", "migrations", "0001", stdout=out) + output = out.getvalue() self.assertIn(connection.ops.start_transaction_sql(), output) self.assertIn(connection.ops.end_transaction_sql(), output) # Test forwards. All the databases agree on CREATE TABLE, at least. - stdout = six.StringIO() - call_command("sqlmigrate", "migrations", "0001", stdout=stdout) - self.assertIn("create table", stdout.getvalue().lower()) + out = six.StringIO() + call_command("sqlmigrate", "migrations", "0001", stdout=out) + self.assertIn("create table", out.getvalue().lower()) # Cannot generate the reverse SQL unless we've applied the migration. call_command("migrate", "migrations", verbosity=0) # And backwards is a DROP TABLE - stdout = six.StringIO() - call_command("sqlmigrate", "migrations", "0001", stdout=stdout, backwards=True) - self.assertIn("drop table", stdout.getvalue().lower()) + out = six.StringIO() + call_command("sqlmigrate", "migrations", "0001", stdout=out, backwards=True) + self.assertIn("drop table", out.getvalue().lower()) # Cleanup by unmigrating everything call_command("migrate", "migrations", "zero", verbosity=0) @@ -128,8 +128,7 @@ class MigrateTests(MigrationTestBase): "B" was not included in the ProjectState that is used to detect soft-applied migrations. """ - stdout = six.StringIO() - call_command("migrate", "migrated_unapplied_app", stdout=stdout) + call_command("migrate", "migrated_unapplied_app", stdout=six.StringIO()) class MakeMigrationsTests(MigrationTestBase): @@ -235,21 +234,21 @@ class MakeMigrationsTests(MigrationTestBase): """ Makes sure that makemigrations exits if in merge mode with no conflicts. """ - stdout = six.StringIO() + out = six.StringIO() try: - call_command("makemigrations", merge=True, stdout=stdout) + call_command("makemigrations", merge=True, stdout=out) except CommandError: self.fail("Makemigrations errored in merge mode with no conflicts") - self.assertIn("No conflicts detected to merge.", stdout.getvalue()) + self.assertIn("No conflicts detected to merge.", out.getvalue()) def test_makemigrations_no_app_sys_exit(self): """ Makes sure that makemigrations exits if a non-existent app is specified. """ - stderr = six.StringIO() + err = six.StringIO() with self.assertRaises(SystemExit): - call_command("makemigrations", "this_app_does_not_exist", stderr=stderr) - self.assertIn("'this_app_does_not_exist' could not be found.", stderr.getvalue()) + call_command("makemigrations", "this_app_does_not_exist", stderr=err) + self.assertIn("'this_app_does_not_exist' could not be found.", err.getvalue()) def test_makemigrations_empty_no_app_specified(self): """ @@ -286,27 +285,27 @@ class MakeMigrationsTests(MigrationTestBase): """ Makes sure that makemigrations exits when there are no changes and no apps are specified. """ - stdout = six.StringIO() - call_command("makemigrations", stdout=stdout) - self.assertIn("No changes detected", stdout.getvalue()) + out = six.StringIO() + call_command("makemigrations", stdout=out) + self.assertIn("No changes detected", out.getvalue()) @override_settings(MIGRATION_MODULES={"migrations": "migrations.test_migrations_no_changes"}) def test_makemigrations_no_changes(self): """ Makes sure that makemigrations exits when there are no changes to an app. """ - stdout = six.StringIO() - call_command("makemigrations", "migrations", stdout=stdout) - self.assertIn("No changes detected in app 'migrations'", stdout.getvalue()) + out = six.StringIO() + call_command("makemigrations", "migrations", stdout=out) + self.assertIn("No changes detected in app 'migrations'", out.getvalue()) def test_makemigrations_migrations_announce(self): """ Makes sure that makemigrations announces the migration at the default verbosity level. """ - stdout = six.StringIO() + out = six.StringIO() with override_settings(MIGRATION_MODULES={"migrations": self.migration_pkg}): - call_command("makemigrations", "migrations", stdout=stdout) - self.assertIn("Migrations for 'migrations'", stdout.getvalue()) + call_command("makemigrations", "migrations", stdout=out) + self.assertIn("Migrations for 'migrations'", out.getvalue()) @override_settings(MIGRATION_MODULES={"migrations": "migrations.test_migrations_no_ancestor"}) def test_makemigrations_no_common_ancestor(self): @@ -345,9 +344,9 @@ class MakeMigrationsTests(MigrationTestBase): # Monkeypatch interactive questioner to auto accept old_input = questioner.input questioner.input = lambda _: "y" - stdout = six.StringIO() + out = six.StringIO() try: - call_command("makemigrations", "migrations", merge=True, interactive=True, stdout=stdout) + call_command("makemigrations", "migrations", merge=True, interactive=True, stdout=out) merge_file = os.path.join(self.test_dir, 'test_migrations_conflict', '0003_merge.py') self.assertTrue(os.path.exists(merge_file)) os.remove(merge_file) @@ -356,23 +355,23 @@ class MakeMigrationsTests(MigrationTestBase): self.fail("Makemigrations failed while running interactive questioner") finally: questioner.input = old_input - self.assertIn("Created new merge migration", stdout.getvalue()) + self.assertIn("Created new merge migration", out.getvalue()) @override_settings(MIGRATION_MODULES={"migrations": "migrations.test_migrations_conflict"}) def test_makemigrations_handle_merge(self): """ Makes sure that makemigrations properly merges the conflicting migrations with --noinput. """ - stdout = six.StringIO() - call_command("makemigrations", "migrations", merge=True, interactive=False, stdout=stdout) - self.assertIn("Merging migrations", stdout.getvalue()) - self.assertIn("Branch 0002_second", stdout.getvalue()) - self.assertIn("Branch 0002_conflicting_second", stdout.getvalue()) + out = six.StringIO() + call_command("makemigrations", "migrations", merge=True, interactive=False, stdout=out) + self.assertIn("Merging migrations", out.getvalue()) + self.assertIn("Branch 0002_second", out.getvalue()) + self.assertIn("Branch 0002_conflicting_second", out.getvalue()) merge_file = os.path.join(self.test_dir, 'test_migrations_conflict', '0003_merge.py') self.assertTrue(os.path.exists(merge_file)) os.remove(merge_file) self.assertFalse(os.path.exists(merge_file)) - self.assertIn("Created new merge migration", stdout.getvalue()) + self.assertIn("Created new merge migration", out.getvalue()) @override_settings(MIGRATION_MODULES={"migrations": "migrations.test_migrations_no_default"}) def test_makemigrations_dry_run(self): @@ -387,10 +386,10 @@ class MakeMigrationsTests(MigrationTestBase): class Meta: app_label = "migrations" - stdout = six.StringIO() - call_command("makemigrations", "migrations", dry_run=True, stdout=stdout) + out = six.StringIO() + call_command("makemigrations", "migrations", dry_run=True, stdout=out) # Output the expected changes directly, without asking for defaults - self.assertIn("Add field silly_date to sillymodel", stdout.getvalue()) + self.assertIn("Add field silly_date to sillymodel", out.getvalue()) @override_settings(MIGRATION_MODULES={"migrations": "migrations.test_migrations_no_default"}) def test_makemigrations_dry_run_verbosity_3(self): @@ -406,21 +405,21 @@ class MakeMigrationsTests(MigrationTestBase): class Meta: app_label = "migrations" - stdout = six.StringIO() - call_command("makemigrations", "migrations", dry_run=True, stdout=stdout, verbosity=3) + out = six.StringIO() + call_command("makemigrations", "migrations", dry_run=True, stdout=out, verbosity=3) # Normal --dry-run output - self.assertIn("- Add field silly_char to sillymodel", stdout.getvalue()) + self.assertIn("- Add field silly_char to sillymodel", out.getvalue()) # Additional output caused by verbosity 3 # The complete migrations file that would be written - self.assertIn("# -*- coding: utf-8 -*-", stdout.getvalue()) - self.assertIn("class Migration(migrations.Migration):", stdout.getvalue()) - self.assertIn("dependencies = [", stdout.getvalue()) - self.assertIn("('migrations', '0001_initial'),", stdout.getvalue()) - self.assertIn("migrations.AddField(", stdout.getvalue()) - self.assertIn("model_name='sillymodel',", stdout.getvalue()) - self.assertIn("name='silly_char',", stdout.getvalue()) + self.assertIn("# -*- coding: utf-8 -*-", out.getvalue()) + self.assertIn("class Migration(migrations.Migration):", out.getvalue()) + self.assertIn("dependencies = [", out.getvalue()) + self.assertIn("('migrations', '0001_initial'),", out.getvalue()) + self.assertIn("migrations.AddField(", out.getvalue()) + self.assertIn("model_name='sillymodel',", out.getvalue()) + self.assertIn("name='silly_char',", out.getvalue()) @override_settings(MIGRATION_MODULES={"migrations": "migrations.test_migrations_path_doesnt_exist.foo.bar"}) def test_makemigrations_migrations_modules_path_not_exist(self): @@ -436,11 +435,11 @@ class MakeMigrationsTests(MigrationTestBase): class Meta: app_label = "migrations" - stdout = six.StringIO() - call_command("makemigrations", "migrations", stdout=stdout) + out = six.StringIO() + call_command("makemigrations", "migrations", stdout=out) # Command output indicates the migration is created. - self.assertIn(" - Create model SillyModel", stdout.getvalue()) + self.assertIn(" - Create model SillyModel", out.getvalue()) # Migrations file is actually created in the expected path. self.assertTrue(os.path.isfile(os.path.join(self.test_dir, @@ -457,10 +456,10 @@ class MakeMigrationsTests(MigrationTestBase): # Monkeypatch interactive questioner to auto reject old_input = questioner.input questioner.input = lambda _: "N" - stdout = six.StringIO() + out = six.StringIO() merge_file = os.path.join(self.test_dir, 'test_migrations_conflict', '0003_merge.py') try: - call_command("makemigrations", "migrations", merge=True, stdout=stdout) + call_command("makemigrations", "migrations", merge=True, stdout=out) # This will fail if interactive is False by default self.assertFalse(os.path.exists(merge_file)) except CommandError: @@ -469,7 +468,7 @@ class MakeMigrationsTests(MigrationTestBase): questioner.input = old_input if os.path.exists(merge_file): os.remove(merge_file) - self.assertNotIn("Created new merge migration", stdout.getvalue()) + self.assertNotIn("Created new merge migration", out.getvalue()) @override_settings( MIGRATION_MODULES={"migrations": "migrations.test_migrations_no_changes"}, @@ -498,16 +497,16 @@ class MakeMigrationsTests(MigrationTestBase): # Monkeypatch interactive questioner to auto accept old_input = questioner.input questioner.input = lambda _: "y" - stdout = six.StringIO() + out = six.StringIO() merge_file = os.path.join(self.test_dir, 'migrations_test_apps', 'unspecified_app_with_conflict', 'migrations', '0003_merge.py') try: - call_command("makemigrations", "migrated_app", merge=True, interactive=True, stdout=stdout) + call_command("makemigrations", "migrated_app", merge=True, interactive=True, stdout=out) self.assertFalse(os.path.exists(merge_file)) - self.assertIn("No conflicts detected to merge.", stdout.getvalue()) + self.assertIn("No conflicts detected to merge.", out.getvalue()) except CommandError: self.fail("Makemigrations fails resolving conflicts in an unspecified app") finally: