Fixed #18325 -- Wrapped self.stdout/stderr in OutputWrapper class
This commit is contained in:
parent
078ea51b1c
commit
822d6d6dab
|
@ -80,7 +80,7 @@ class Command(BaseCommand):
|
||||||
if default_username and username == '':
|
if default_username and username == '':
|
||||||
username = default_username
|
username = default_username
|
||||||
if not RE_VALID_USERNAME.match(username):
|
if not RE_VALID_USERNAME.match(username):
|
||||||
sys.stderr.write("Error: That username is invalid. Use only letters, digits and underscores.\n")
|
self.stderr.write("Error: That username is invalid. Use only letters, digits and underscores.")
|
||||||
username = None
|
username = None
|
||||||
continue
|
continue
|
||||||
try:
|
try:
|
||||||
|
@ -88,7 +88,7 @@ class Command(BaseCommand):
|
||||||
except User.DoesNotExist:
|
except User.DoesNotExist:
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
sys.stderr.write("Error: That username is already taken.\n")
|
self.stderr.write("Error: That username is already taken.")
|
||||||
username = None
|
username = None
|
||||||
|
|
||||||
# Get an email
|
# Get an email
|
||||||
|
@ -98,7 +98,7 @@ class Command(BaseCommand):
|
||||||
try:
|
try:
|
||||||
is_valid_email(email)
|
is_valid_email(email)
|
||||||
except exceptions.ValidationError:
|
except exceptions.ValidationError:
|
||||||
sys.stderr.write("Error: That e-mail address is invalid.\n")
|
self.stderr.write("Error: That e-mail address is invalid.")
|
||||||
email = None
|
email = None
|
||||||
else:
|
else:
|
||||||
break
|
break
|
||||||
|
@ -109,19 +109,19 @@ class Command(BaseCommand):
|
||||||
password = getpass.getpass()
|
password = getpass.getpass()
|
||||||
password2 = getpass.getpass('Password (again): ')
|
password2 = getpass.getpass('Password (again): ')
|
||||||
if password != password2:
|
if password != password2:
|
||||||
sys.stderr.write("Error: Your passwords didn't match.\n")
|
self.stderr.write("Error: Your passwords didn't match.")
|
||||||
password = None
|
password = None
|
||||||
continue
|
continue
|
||||||
if password.strip() == '':
|
if password.strip() == '':
|
||||||
sys.stderr.write("Error: Blank passwords aren't allowed.\n")
|
self.stderr.write("Error: Blank passwords aren't allowed.")
|
||||||
password = None
|
password = None
|
||||||
continue
|
continue
|
||||||
break
|
break
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
sys.stderr.write("\nOperation cancelled.\n")
|
self.stderr.write("\nOperation cancelled.")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
User.objects.db_manager(database).create_superuser(username, email, password)
|
User.objects.db_manager(database).create_superuser(username, email, password)
|
||||||
if verbosity >= 1:
|
if verbosity >= 1:
|
||||||
self.stdout.write("Superuser created successfully.\n")
|
self.stdout.write("Superuser created successfully.")
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@ from optparse import make_option
|
||||||
|
|
||||||
from django.core.files.storage import FileSystemStorage
|
from django.core.files.storage import FileSystemStorage
|
||||||
from django.core.management.base import CommandError, NoArgsCommand
|
from django.core.management.base import CommandError, NoArgsCommand
|
||||||
from django.utils.encoding import smart_str, smart_unicode
|
from django.utils.encoding import smart_unicode
|
||||||
from django.utils.datastructures import SortedDict
|
from django.utils.datastructures import SortedDict
|
||||||
|
|
||||||
from django.contrib.staticfiles import finders, storage
|
from django.contrib.staticfiles import finders, storage
|
||||||
|
@ -178,15 +178,12 @@ Type 'yes' to continue, or 'no' to cancel: """
|
||||||
', %s post-processed'
|
', %s post-processed'
|
||||||
% post_processed_count or ''),
|
% post_processed_count or ''),
|
||||||
}
|
}
|
||||||
self.stdout.write(smart_str(summary))
|
self.stdout.write(summary)
|
||||||
|
|
||||||
def log(self, msg, level=2):
|
def log(self, msg, level=2):
|
||||||
"""
|
"""
|
||||||
Small log helper
|
Small log helper
|
||||||
"""
|
"""
|
||||||
msg = smart_str(msg)
|
|
||||||
if not msg.endswith("\n"):
|
|
||||||
msg += "\n"
|
|
||||||
if self.verbosity >= level:
|
if self.verbosity >= level:
|
||||||
self.stdout.write(msg)
|
self.stdout.write(msg)
|
||||||
|
|
||||||
|
|
|
@ -23,9 +23,7 @@ class Command(LabelCommand):
|
||||||
result = [result]
|
result = [result]
|
||||||
output = u'\n '.join(
|
output = u'\n '.join(
|
||||||
(smart_unicode(os.path.realpath(path)) for path in result))
|
(smart_unicode(os.path.realpath(path)) for path in result))
|
||||||
self.stdout.write(
|
self.stdout.write(u"Found '%s' here:\n %s" % (path, output))
|
||||||
smart_str(u"Found '%s' here:\n %s\n" % (path, output)))
|
|
||||||
else:
|
else:
|
||||||
if verbosity >= 1:
|
if verbosity >= 1:
|
||||||
self.stderr.write(
|
self.stderr.write("No matching file found for '%s'." % path)
|
||||||
smart_str("No matching file found for '%s'.\n" % path))
|
|
||||||
|
|
|
@ -45,6 +45,29 @@ def handle_default_options(options):
|
||||||
sys.path.insert(0, options.pythonpath)
|
sys.path.insert(0, options.pythonpath)
|
||||||
|
|
||||||
|
|
||||||
|
class OutputWrapper(object):
|
||||||
|
"""
|
||||||
|
Wrapper around stdout/stderr
|
||||||
|
"""
|
||||||
|
def __init__(self, out, style_func=None):
|
||||||
|
self._out = out
|
||||||
|
self.style_func = None
|
||||||
|
if hasattr(out, 'isatty') and out.isatty():
|
||||||
|
self.style_func = style_func
|
||||||
|
|
||||||
|
def __getattr__(self, name):
|
||||||
|
return getattr(self._out, name)
|
||||||
|
|
||||||
|
def write(self, msg, style_func=None, ending='\n'):
|
||||||
|
if ending and not msg.endswith(ending):
|
||||||
|
msg += ending
|
||||||
|
if style_func is not None:
|
||||||
|
msg = style_func(msg)
|
||||||
|
elif self.style_func is not None:
|
||||||
|
msg = self.style_func(msg)
|
||||||
|
self._out.write(smart_str(msg))
|
||||||
|
|
||||||
|
|
||||||
class BaseCommand(object):
|
class BaseCommand(object):
|
||||||
"""
|
"""
|
||||||
The base class from which all management commands ultimately
|
The base class from which all management commands ultimately
|
||||||
|
@ -210,6 +233,9 @@ class BaseCommand(object):
|
||||||
# But only do this if we can assume we have a working settings file,
|
# But only do this if we can assume we have a working settings file,
|
||||||
# because django.utils.translation requires settings.
|
# because django.utils.translation requires settings.
|
||||||
saved_lang = None
|
saved_lang = None
|
||||||
|
self.stdout = OutputWrapper(options.get('stdout', sys.stdout))
|
||||||
|
self.stderr = OutputWrapper(options.get('stderr', sys.stderr), self.style.ERROR)
|
||||||
|
|
||||||
if self.can_import_settings:
|
if self.can_import_settings:
|
||||||
try:
|
try:
|
||||||
from django.utils import translation
|
from django.utils import translation
|
||||||
|
@ -221,12 +247,10 @@ class BaseCommand(object):
|
||||||
if show_traceback:
|
if show_traceback:
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
else:
|
else:
|
||||||
sys.stderr.write(smart_str(self.style.ERROR('Error: %s\n' % e)))
|
self.stderr.write('Error: %s' % e)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self.stdout = options.get('stdout', sys.stdout)
|
|
||||||
self.stderr = options.get('stderr', sys.stderr)
|
|
||||||
if self.requires_model_validation and not options.get('skip_validation'):
|
if self.requires_model_validation and not options.get('skip_validation'):
|
||||||
self.validate()
|
self.validate()
|
||||||
output = self.handle(*args, **options)
|
output = self.handle(*args, **options)
|
||||||
|
@ -237,15 +261,15 @@ class BaseCommand(object):
|
||||||
from django.db import connections, DEFAULT_DB_ALIAS
|
from django.db import connections, DEFAULT_DB_ALIAS
|
||||||
connection = connections[options.get('database', DEFAULT_DB_ALIAS)]
|
connection = connections[options.get('database', DEFAULT_DB_ALIAS)]
|
||||||
if connection.ops.start_transaction_sql():
|
if connection.ops.start_transaction_sql():
|
||||||
self.stdout.write(self.style.SQL_KEYWORD(connection.ops.start_transaction_sql()) + '\n')
|
self.stdout.write(self.style.SQL_KEYWORD(connection.ops.start_transaction_sql()))
|
||||||
self.stdout.write(output)
|
self.stdout.write(output)
|
||||||
if self.output_transaction:
|
if self.output_transaction:
|
||||||
self.stdout.write('\n' + self.style.SQL_KEYWORD("COMMIT;") + '\n')
|
self.stdout.write('\n' + self.style.SQL_KEYWORD("COMMIT;"))
|
||||||
except CommandError as e:
|
except CommandError as e:
|
||||||
if show_traceback:
|
if show_traceback:
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
else:
|
else:
|
||||||
self.stderr.write(smart_str(self.style.ERROR('Error: %s\n' % e)))
|
self.stderr.write('Error: %s' % e)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
finally:
|
finally:
|
||||||
if saved_lang is not None:
|
if saved_lang is not None:
|
||||||
|
@ -266,7 +290,7 @@ class BaseCommand(object):
|
||||||
error_text = s.read()
|
error_text = s.read()
|
||||||
raise CommandError("One or more models did not validate:\n%s" % error_text)
|
raise CommandError("One or more models did not validate:\n%s" % error_text)
|
||||||
if display_num_errors:
|
if display_num_errors:
|
||||||
self.stdout.write("%s error%s found\n" % (num_errors, num_errors != 1 and 's' or ''))
|
self.stdout.write("%s error%s found" % (num_errors, num_errors != 1 and 's' or ''))
|
||||||
|
|
||||||
def handle(self, *args, **options):
|
def handle(self, *args, **options):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -56,8 +56,8 @@ class Command(LabelCommand):
|
||||||
curs.execute("\n".join(full_statement))
|
curs.execute("\n".join(full_statement))
|
||||||
except DatabaseError as e:
|
except DatabaseError as e:
|
||||||
self.stderr.write(
|
self.stderr.write(
|
||||||
self.style.ERROR("Cache table '%s' could not be created.\nThe error was: %s.\n" %
|
"Cache table '%s' could not be created.\nThe error was: %s." %
|
||||||
(tablename, e)))
|
(tablename, e))
|
||||||
transaction.rollback_unless_managed(using=db)
|
transaction.rollback_unless_managed(using=db)
|
||||||
else:
|
else:
|
||||||
for statement in index_output:
|
for statement in index_output:
|
||||||
|
|
|
@ -109,8 +109,9 @@ class Command(BaseCommand):
|
||||||
objects.extend(model._default_manager.using(using).all())
|
objects.extend(model._default_manager.using(using).all())
|
||||||
|
|
||||||
try:
|
try:
|
||||||
return serializers.serialize(format, objects, indent=indent,
|
self.stdout.write(serializers.serialize(format, objects,
|
||||||
use_natural_keys=use_natural_keys)
|
indent=indent, use_natural_keys=use_natural_keys),
|
||||||
|
ending='')
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
if show_traceback:
|
if show_traceback:
|
||||||
raise
|
raise
|
||||||
|
|
|
@ -34,11 +34,11 @@ class Command(BaseCommand):
|
||||||
using = options.get('database')
|
using = options.get('database')
|
||||||
|
|
||||||
connection = connections[using]
|
connection = connections[using]
|
||||||
self.style = no_style()
|
|
||||||
|
|
||||||
if not len(fixture_labels):
|
if not len(fixture_labels):
|
||||||
self.stderr.write(
|
self.stderr.write(
|
||||||
self.style.ERROR("No database fixture specified. Please provide the path of at least one fixture in the command line.\n")
|
"No database fixture specified. Please provide the path of at "
|
||||||
|
"least one fixture in the command line."
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
@ -124,11 +124,11 @@ class Command(BaseCommand):
|
||||||
|
|
||||||
if formats:
|
if formats:
|
||||||
if verbosity >= 2:
|
if verbosity >= 2:
|
||||||
self.stdout.write("Loading '%s' fixtures...\n" % fixture_name)
|
self.stdout.write("Loading '%s' fixtures..." % fixture_name)
|
||||||
else:
|
else:
|
||||||
self.stderr.write(
|
self.stderr.write(
|
||||||
self.style.ERROR("Problem installing fixture '%s': %s is not a known serialization format.\n" %
|
"Problem installing fixture '%s': %s is not a known serialization format." %
|
||||||
(fixture_name, format)))
|
(fixture_name, format))
|
||||||
if commit:
|
if commit:
|
||||||
transaction.rollback(using=using)
|
transaction.rollback(using=using)
|
||||||
transaction.leave_transaction_management(using=using)
|
transaction.leave_transaction_management(using=using)
|
||||||
|
@ -141,7 +141,7 @@ class Command(BaseCommand):
|
||||||
|
|
||||||
for fixture_dir in fixture_dirs:
|
for fixture_dir in fixture_dirs:
|
||||||
if verbosity >= 2:
|
if verbosity >= 2:
|
||||||
self.stdout.write("Checking %s for fixtures...\n" % humanize(fixture_dir))
|
self.stdout.write("Checking %s for fixtures..." % humanize(fixture_dir))
|
||||||
|
|
||||||
label_found = False
|
label_found = False
|
||||||
for combo in product([using, None], formats, compression_formats):
|
for combo in product([using, None], formats, compression_formats):
|
||||||
|
@ -154,7 +154,7 @@ class Command(BaseCommand):
|
||||||
)
|
)
|
||||||
|
|
||||||
if verbosity >= 3:
|
if verbosity >= 3:
|
||||||
self.stdout.write("Trying %s for %s fixture '%s'...\n" % \
|
self.stdout.write("Trying %s for %s fixture '%s'..." % \
|
||||||
(humanize(fixture_dir), file_name, fixture_name))
|
(humanize(fixture_dir), file_name, fixture_name))
|
||||||
full_path = os.path.join(fixture_dir, file_name)
|
full_path = os.path.join(fixture_dir, file_name)
|
||||||
open_method = compression_types[compression_format]
|
open_method = compression_types[compression_format]
|
||||||
|
@ -162,13 +162,13 @@ class Command(BaseCommand):
|
||||||
fixture = open_method(full_path, 'r')
|
fixture = open_method(full_path, 'r')
|
||||||
except IOError:
|
except IOError:
|
||||||
if verbosity >= 2:
|
if verbosity >= 2:
|
||||||
self.stdout.write("No %s fixture '%s' in %s.\n" % \
|
self.stdout.write("No %s fixture '%s' in %s." % \
|
||||||
(format, fixture_name, humanize(fixture_dir)))
|
(format, fixture_name, humanize(fixture_dir)))
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
if label_found:
|
if label_found:
|
||||||
self.stderr.write(self.style.ERROR("Multiple fixtures named '%s' in %s. Aborting.\n" %
|
self.stderr.write("Multiple fixtures named '%s' in %s. Aborting." %
|
||||||
(fixture_name, humanize(fixture_dir))))
|
(fixture_name, humanize(fixture_dir)))
|
||||||
if commit:
|
if commit:
|
||||||
transaction.rollback(using=using)
|
transaction.rollback(using=using)
|
||||||
transaction.leave_transaction_management(using=using)
|
transaction.leave_transaction_management(using=using)
|
||||||
|
@ -178,7 +178,7 @@ class Command(BaseCommand):
|
||||||
objects_in_fixture = 0
|
objects_in_fixture = 0
|
||||||
loaded_objects_in_fixture = 0
|
loaded_objects_in_fixture = 0
|
||||||
if verbosity >= 2:
|
if verbosity >= 2:
|
||||||
self.stdout.write("Installing %s fixture '%s' from %s.\n" % \
|
self.stdout.write("Installing %s fixture '%s' from %s." % \
|
||||||
(format, fixture_name, humanize(fixture_dir)))
|
(format, fixture_name, humanize(fixture_dir)))
|
||||||
|
|
||||||
objects = serializers.deserialize(format, fixture, using=using)
|
objects = serializers.deserialize(format, fixture, using=using)
|
||||||
|
@ -209,8 +209,8 @@ class Command(BaseCommand):
|
||||||
# error was encountered during fixture loading.
|
# error was encountered during fixture loading.
|
||||||
if objects_in_fixture == 0:
|
if objects_in_fixture == 0:
|
||||||
self.stderr.write(
|
self.stderr.write(
|
||||||
self.style.ERROR("No fixture data found for '%s'. (File format may be invalid.)\n" %
|
"No fixture data found for '%s'. (File format may be invalid.)" %
|
||||||
(fixture_name)))
|
(fixture_name))
|
||||||
if commit:
|
if commit:
|
||||||
transaction.rollback(using=using)
|
transaction.rollback(using=using)
|
||||||
transaction.leave_transaction_management(using=using)
|
transaction.leave_transaction_management(using=using)
|
||||||
|
@ -231,16 +231,16 @@ class Command(BaseCommand):
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
else:
|
else:
|
||||||
self.stderr.write(
|
self.stderr.write(
|
||||||
self.style.ERROR("Problem installing fixture '%s': %s\n" %
|
"Problem installing fixture '%s': %s" %
|
||||||
(full_path, ''.join(traceback.format_exception(sys.exc_type,
|
(full_path, ''.join(traceback.format_exception(sys.exc_type,
|
||||||
sys.exc_value, sys.exc_traceback)))))
|
sys.exc_value, sys.exc_traceback))))
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
||||||
# If we found even one object in a fixture, we need to reset the
|
# If we found even one object in a fixture, we need to reset the
|
||||||
# database sequences.
|
# database sequences.
|
||||||
if loaded_object_count > 0:
|
if loaded_object_count > 0:
|
||||||
sequence_sql = connection.ops.sequence_reset_sql(self.style, models)
|
sequence_sql = connection.ops.sequence_reset_sql(no_style(), models)
|
||||||
if sequence_sql:
|
if sequence_sql:
|
||||||
if verbosity >= 2:
|
if verbosity >= 2:
|
||||||
self.stdout.write("Resetting sequences\n")
|
self.stdout.write("Resetting sequences\n")
|
||||||
|
@ -253,10 +253,10 @@ class Command(BaseCommand):
|
||||||
|
|
||||||
if verbosity >= 1:
|
if verbosity >= 1:
|
||||||
if fixture_object_count == loaded_object_count:
|
if fixture_object_count == loaded_object_count:
|
||||||
self.stdout.write("Installed %d object(s) from %d fixture(s)\n" % (
|
self.stdout.write("Installed %d object(s) from %d fixture(s)" % (
|
||||||
loaded_object_count, fixture_count))
|
loaded_object_count, fixture_count))
|
||||||
else:
|
else:
|
||||||
self.stdout.write("Installed %d object(s) (of %d) from %d fixture(s)\n" % (
|
self.stdout.write("Installed %d object(s) (of %d) from %d fixture(s)" % (
|
||||||
loaded_object_count, fixture_object_count, fixture_count))
|
loaded_object_count, fixture_object_count, fixture_count))
|
||||||
|
|
||||||
# Close the DB connection. This is required as a workaround for an
|
# Close the DB connection. This is required as a workaround for an
|
||||||
|
|
|
@ -120,12 +120,12 @@ class Command(BaseCommand):
|
||||||
error_text = ERRORS[e.args[0].args[0]]
|
error_text = ERRORS[e.args[0].args[0]]
|
||||||
except (AttributeError, KeyError):
|
except (AttributeError, KeyError):
|
||||||
error_text = str(e)
|
error_text = str(e)
|
||||||
sys.stderr.write(self.style.ERROR("Error: %s" % error_text) + '\n')
|
sys.stderr.write("Error: %s" % error_text)
|
||||||
# Need to use an OS exit because sys.exit doesn't work in a thread
|
# Need to use an OS exit because sys.exit doesn't work in a thread
|
||||||
os._exit(1)
|
os._exit(1)
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
if shutdown_message:
|
if shutdown_message:
|
||||||
self.stdout.write("%s\n" % shutdown_message)
|
self.stdout.write(shutdown_message)
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -16,7 +16,6 @@ from os import path
|
||||||
import django
|
import django
|
||||||
from django.template import Template, Context
|
from django.template import Template, Context
|
||||||
from django.utils import archive
|
from django.utils import archive
|
||||||
from django.utils.encoding import smart_str
|
|
||||||
from django.utils._os import rmtree_errorhandler
|
from django.utils._os import rmtree_errorhandler
|
||||||
from django.core.management.base import BaseCommand, CommandError
|
from django.core.management.base import BaseCommand, CommandError
|
||||||
from django.core.management.commands.makemessages import handle_extensions
|
from django.core.management.commands.makemessages import handle_extensions
|
||||||
|
@ -166,11 +165,10 @@ class TemplateCommand(BaseCommand):
|
||||||
shutil.copymode(old_path, new_path)
|
shutil.copymode(old_path, new_path)
|
||||||
self.make_writeable(new_path)
|
self.make_writeable(new_path)
|
||||||
except OSError:
|
except OSError:
|
||||||
notice = self.style.NOTICE(
|
self.stderr.write(
|
||||||
"Notice: Couldn't set permission bits on %s. You're "
|
"Notice: Couldn't set permission bits on %s. You're "
|
||||||
"probably using an uncommon filesystem setup. No "
|
"probably using an uncommon filesystem setup. No "
|
||||||
"problem.\n" % new_path)
|
"problem." % new_path, self.style.NOTICE)
|
||||||
sys.stderr.write(smart_str(notice))
|
|
||||||
|
|
||||||
if self.paths_to_remove:
|
if self.paths_to_remove:
|
||||||
if self.verbosity >= 2:
|
if self.verbosity >= 2:
|
||||||
|
|
|
@ -61,7 +61,7 @@ look like this:
|
||||||
poll.opened = False
|
poll.opened = False
|
||||||
poll.save()
|
poll.save()
|
||||||
|
|
||||||
self.stdout.write('Successfully closed poll "%s"\n' % poll_id)
|
self.stdout.write('Successfully closed poll "%s"' % poll_id)
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
When you are using management commands and wish to provide console
|
When you are using management commands and wish to provide console
|
||||||
|
|
|
@ -11,13 +11,13 @@ class CommandTests(TestCase):
|
||||||
out = StringIO()
|
out = StringIO()
|
||||||
management.call_command('dance', stdout=out)
|
management.call_command('dance', stdout=out)
|
||||||
self.assertEqual(out.getvalue(),
|
self.assertEqual(out.getvalue(),
|
||||||
"I don't feel like dancing Rock'n'Roll.")
|
"I don't feel like dancing Rock'n'Roll.\n")
|
||||||
|
|
||||||
def test_command_style(self):
|
def test_command_style(self):
|
||||||
out = StringIO()
|
out = StringIO()
|
||||||
management.call_command('dance', style='Jive', stdout=out)
|
management.call_command('dance', style='Jive', stdout=out)
|
||||||
self.assertEqual(out.getvalue(),
|
self.assertEqual(out.getvalue(),
|
||||||
"I don't feel like dancing Jive.")
|
"I don't feel like dancing Jive.\n")
|
||||||
|
|
||||||
def test_language_preserved(self):
|
def test_language_preserved(self):
|
||||||
out = StringIO()
|
out = StringIO()
|
||||||
|
|
Loading…
Reference in New Issue