def test_plain_text_output_format(self): """Inspect the plain text output of coloredlogs.""" logger = VerboseLogger(random_string(25)) stream = StringIO() install(level=logging.NOTSET, logger=logger, stream=stream) # Test that filtering on severity works. logger.setLevel(logging.INFO) logger.debug("No one should see this message.") assert len(stream.getvalue().strip()) == 0 # Test that the default output format looks okay in plain text. logger.setLevel(logging.NOTSET) for method, severity in ((logger.debug, 'DEBUG'), (logger.info, 'INFO'), (logger.verbose, 'VERBOSE'), (logger.warning, 'WARNING'), (logger.error, 'ERROR'), (logger.critical, 'CRITICAL')): # Prepare the text. text = "This is a message with severity %r." % severity.lower() # Log the message with the given severity. method(text) # Get the line of output generated by the handler. output = stream.getvalue() lines = output.splitlines() last_line = lines[-1] assert text in last_line assert severity in last_line assert PLAIN_TEXT_PATTERN.match(last_line)
def test_plain_text_output_format(self): """Inspect the plain text output of coloredlogs.""" logger = VerboseLogger(random_string(25)) stream = StringIO() install(level=logging.NOTSET, logger=logger, stream=stream) # Test that filtering on severity works. logger.setLevel(logging.INFO) logger.debug("No one should see this message.") assert len(stream.getvalue().strip()) == 0 # Test that the default output format looks okay in plain text. logger.setLevel(logging.NOTSET) for method, severity in ((logger.debug, 'DEBUG'), (logger.info, 'INFO'), (logger.verbose, 'VERBOSE'), (logger.warning, 'WARN'), (logger.error, 'ERROR'), (logger.critical, 'CRITICAL')): # Prepare the text. text = "This is a message with severity %r." % severity.lower() # Log the message with the given severity. method(text) # Get the line of output generated by the handler. output = stream.getvalue() lines = output.splitlines() last_line = lines[-1] assert text in last_line assert severity in last_line assert PLAIN_TEXT_PATTERN.match(last_line)
def test_support_for_milliseconds(self): """Make sure milliseconds are hidden by default but can be easily enabled.""" # Check that the default log format doesn't include milliseconds. stream = StringIO() install(reconfigure=True, stream=stream) logging.info("This should not include milliseconds.") assert all(map(PLAIN_TEXT_PATTERN.match, stream.getvalue().splitlines())) # Check that milliseconds can be enabled via a shortcut. stream = StringIO() install(milliseconds=True, reconfigure=True, stream=stream) logging.info("This should include milliseconds.") assert all(map(PATTERN_INCLUDING_MILLISECONDS.match, stream.getvalue().splitlines()))
def test_dynamic_stderr_lookup(self): """Make sure coloredlogs.install() uses StandardErrorHandler when possible.""" coloredlogs.install() # Redirect sys.stderr to a temporary buffer. initial_stream = StringIO() initial_text = "Which stream will receive this text?" with PatchedAttribute(sys, 'stderr', initial_stream): logging.info(initial_text) assert initial_text in initial_stream.getvalue() # Redirect sys.stderr again, to a different destination. subsequent_stream = StringIO() subsequent_text = "And which stream will receive this other text?" with PatchedAttribute(sys, 'stderr', subsequent_stream): logging.info(subsequent_text) assert subsequent_text in subsequent_stream.getvalue()
def render_usage(text): """ Reformat a command line program's usage message to reStructuredText_. :param text: The plain text usage message (a string). :returns: The usage message rendered to reStructuredText_ (a string). """ meta_variables = find_meta_variables(text) introduction, options = parse_usage(text) output = [render_paragraph(p, meta_variables) for p in introduction] if options: output.append('\n'.join([ '.. csv-table::', ' :header: Option, Description', ' :widths: 30, 70', '', ])) csv_buffer = StringIO() csv_writer = csv.writer(csv_buffer) while options: variants = options.pop(0) description = options.pop(0) csv_writer.writerow([ render_paragraph(variants, meta_variables), ('\n\n'.join( render_paragraph(p, meta_variables) for p in split_paragraphs(description))).rstrip(), ]) csv_lines = csv_buffer.getvalue().splitlines() output.append('\n'.join(' %s' % l for l in csv_lines)) logger.debug("Rendered output: %s", output) return '\n\n'.join(trim_empty_lines(o) for o in output)
def render_usage(text): """ Reformat a command line program's usage message to reStructuredText_. :param text: The plain text usage message (a string). :returns: The usage message rendered to reStructuredText_ (a string). """ meta_variables = find_meta_variables(text) introduction, options = parse_usage(text) output = [render_paragraph(p, meta_variables) for p in introduction] if options: output.append('\n'.join([ '.. csv-table::', ' :header: Option, Description', ' :widths: 30, 70', '', ])) csv_buffer = StringIO() csv_writer = csv.writer(csv_buffer) while options: variants = options.pop(0) description = options.pop(0) csv_writer.writerow([ render_paragraph(variants, meta_variables), '\n\n'.join(render_paragraph(p, meta_variables) for p in split_paragraphs(description)), ]) csv_lines = csv_buffer.getvalue().splitlines() output.append('\n'.join(' %s' % l for l in csv_lines)) logger.debug("Rendered output: %s", output) return '\n\n'.join(trim_empty_lines(o) for o in output)
def test_support_for_milliseconds_directive(self): """Make sure milliseconds using the ``%f`` directive are supported.""" stream = StringIO() install(reconfigure=True, stream=stream, datefmt='%Y-%m-%dT%H:%M:%S.%f%z') logging.info("This should be timestamped according to #45.") assert re.match( r'^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}[+-]\d{4}\s', stream.getvalue())
def test_plain_text_output_format(self): """Inspect the plain text output of coloredlogs.""" logger = VerboseLogger(random_string(25)) stream = StringIO() install(level=logging.NOTSET, logger=logger, stream=stream) # Test that filtering on severity works. logger.setLevel(logging.INFO) logger.debug("No one should see this message.") assert len(stream.getvalue().strip()) == 0 # Test that the default output format looks okay in plain text. logger.setLevel(logging.NOTSET) for method, severity in ((logger.debug, 'DEBUG'), (logger.info, 'INFO'), (logger.verbose, 'VERBOSE'), (logger.warning, 'WARNING'), (logger.error, 'ERROR'), (logger.critical, 'CRITICAL')): # XXX Workaround for a regression in Python 3.7 caused by the # Logger.isEnabledFor() method using stale cache entries. If we # don't clear the cache then logger.isEnabledFor(logging.DEBUG) # returns False and no DEBUG message is emitted. try: logger._cache.clear() except AttributeError: pass # Prepare the text. text = "This is a message with severity %r." % severity.lower() # Log the message with the given severity. method(text) # Get the line of output generated by the handler. output = stream.getvalue() lines = output.splitlines() last_line = lines[-1] assert text in last_line assert severity in last_line assert PLAIN_TEXT_PATTERN.match(last_line)
def test_spinner(self): """Test :func:`humanfriendly.Spinner`.""" stream = StringIO() spinner = humanfriendly.Spinner('test spinner', total=4, stream=stream, interactive=True) for progress in [1, 2, 3, 4]: spinner.step(progress=progress) time.sleep(0.2) spinner.clear() output = stream.getvalue() output = (output.replace(humanfriendly.show_cursor_code, '') .replace(humanfriendly.hide_cursor_code, '')) lines = [line for line in output.split(humanfriendly.erase_line_code) if line] self.assertTrue(len(lines) > 0) self.assertTrue(all('test spinner' in l for l in lines)) self.assertTrue(all('%' in l for l in lines)) self.assertEqual(sorted(set(lines)), sorted(lines))
class CaptureOutput(ContextManager): """Context manager that captures what's written to :data:`sys.stdout` and :data:`sys.stderr`.""" def __init__(self, merged=False, input=''): """ Initialize a :class:`CaptureOutput` object. :param merged: :data:`True` to merge the streams, :data:`False` to capture them separately. :param input: The data that reads from :data:`sys.stdin` should return (a string). """ self.stdin = StringIO(input) self.stdout = StringIO() self.stderr = self.stdout if merged else StringIO() self.patched_attributes = [ PatchedAttribute(sys, name, getattr(self, name)) for name in ('stdin', 'stdout', 'stderr') ] stdin = None """The :class:`~humanfriendly.compat.StringIO` object used to feed the standard input stream.""" stdout = None """The :class:`~humanfriendly.compat.StringIO` object used to capture the standard output stream.""" stderr = None """The :class:`~humanfriendly.compat.StringIO` object used to capture the standard error stream.""" def __enter__(self): """Start capturing what's written to :data:`sys.stdout` and :data:`sys.stderr`.""" super(CaptureOutput, self).__enter__() for context in self.patched_attributes: context.__enter__() return self def __exit__(self, exc_type=None, exc_value=None, traceback=None): """Stop capturing what's written to :data:`sys.stdout` and :data:`sys.stderr`.""" super(CaptureOutput, self).__exit__(exc_type, exc_value, traceback) for context in self.patched_attributes: context.__exit__(exc_type, exc_value, traceback) def getvalue(self): """Get the text written to :data:`sys.stdout`.""" return self.stdout.getvalue()
def main(*args, **kw): """Utility function to test the command line interface without invoking a subprocess.""" returncode = 0 input_buffer = StringIO(kw.get('input', '')) output_buffer = StringIO() saved_argv = sys.argv saved_stdin = sys.stdin saved_stdout = sys.stdout try: sys.argv = [sys.argv[0]] + list(args) sys.stdin = input_buffer sys.stdout = output_buffer cli.main() except SystemExit as e: returncode = e.code or 1 finally: sys.argv = saved_argv sys.stdin = saved_stdin sys.stdout = saved_stdout return returncode, output_buffer.getvalue()
def test_support_for_milliseconds_directive(self): """Make sure milliseconds using the ``%f`` directive are supported.""" stream = StringIO() install(reconfigure=True, stream=stream, datefmt='%Y-%m-%dT%H:%M:%S.%f%z') logging.info("This should be timestamped according to #45.") assert re.match(r'^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}\+\d{4}\s', stream.getvalue())