From d73e8cea88d336f92f9b686a10d142bb770cd2d5 Mon Sep 17 00:00:00 2001 From: Mike Lundy Date: Thu, 14 Apr 2016 10:14:50 -0600 Subject: [PATCH 1/2] Centralize StringIO import in tests --- test.py | 25 ++++--------------------- 1 file changed, 4 insertions(+), 21 deletions(-) diff --git a/test.py b/test.py index f5dc23be..eefe948a 100644 --- a/test.py +++ b/test.py @@ -17,9 +17,12 @@ if IS_PY3: unicode = str python = sh.Command(sh.which("python%d.%d" % sys.version_info[:2])) + from io import StringIO + from io import BytesIO as cStringIO else: from sh import python - + from StringIO import StringIO + from cStringIO import StringIO as cStringIO THIS_DIR = os.path.dirname(os.path.abspath(__file__)) @@ -1217,12 +1220,6 @@ def test_tty_output(self): def test_stringio_output(self): from sh import echo - if IS_PY3: - from io import StringIO - from io import BytesIO as cStringIO - else: - from StringIO import StringIO - from cStringIO import StringIO as cStringIO out = StringIO() echo("-n", "testing 123", _out=out) @@ -1236,13 +1233,6 @@ def test_stringio_output(self): def test_stringio_input(self): from sh import cat - if IS_PY3: - from io import StringIO - from io import BytesIO as cStringIO - else: - from StringIO import StringIO - from cStringIO import StringIO as cStringIO - input = StringIO() input.write("herpderp") input.seek(0) @@ -1502,13 +1492,6 @@ def s(fn): str(fn()) def test_shared_secial_args(self): import sh - if IS_PY3: - from io import StringIO - from io import BytesIO as cStringIO - else: - from StringIO import StringIO - from cStringIO import StringIO as cStringIO - out1 = sh.ls('.') out2 = StringIO() sh_new = sh(_out=out2) From fd0369c2b8d7f5a542d9383d77315743874a30e4 Mon Sep 17 00:00:00 2001 From: Mike Lundy Date: Thu, 14 Apr 2016 10:15:20 -0600 Subject: [PATCH 2/2] Provide a log_msg call_arg to change log message --- sh.py | 28 ++++++++++++++++++---------- test.py | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+), 10 deletions(-) diff --git a/sh.py b/sh.py index fb0f5fe7..35b0f3e4 100644 --- a/sh.py +++ b/sh.py @@ -397,6 +397,17 @@ def friendly_truncate(s, max_len): return s +def default_logger_str(cmd, call_args): + friendly_cmd = friendly_truncate(cmd, 20) + friendly_call_args = friendly_truncate(str(call_args), 20) + + # we're setting up the logger string here, instead of __repr__ because + # we reserve __repr__ to behave as if it was evaluating the child + # process's output + return "" % (friendly_cmd, friendly_call_args) + + + class RunningCommand(object): """ this represents an executing Command object. it is returned as the result of __call__() being executed on a Command instance. this creates a @@ -420,17 +431,10 @@ def __init__(self, cmd, call_args, stdin, stdout, stderr): else: self.ran = " ".join(cmd) - - friendly_cmd = friendly_truncate(self.ran, 20) - friendly_call_args = friendly_truncate(str(call_args), 20) - - # we're setting up the logger string here, instead of __repr__ because - # we reserve __repr__ to behave as if it was evaluating the child - # process's output - logger_str = "" % (friendly_cmd, - friendly_call_args) - + log_msg_cmd = call_args.get('log_msg') or default_logger_str + logger_str = log_msg_cmd(self.ran, call_args) self.log = Logger("command", logger_str) + self.call_args = call_args self.cmd = cmd @@ -745,6 +749,10 @@ class Command(object): # a tuple (rows, columns) of the desired size of both the stdout and # stdin ttys, if ttys are being used "tty_size": (20, 80), + + # a callable that produces a log message from an argument tuple of the + # command and the args + "log_msg": None } # these are arguments that cannot be called together, because they wouldn't diff --git a/test.py b/test.py index eefe948a..2053adf7 100644 --- a/test.py +++ b/test.py @@ -4,6 +4,8 @@ from os.path import exists, join, realpath import unittest import tempfile +import fnmatch +import logging import sys import sh import platform @@ -24,6 +26,21 @@ from StringIO import StringIO from cStringIO import StringIO as cStringIO + +if hasattr(logging, 'NullHandler'): + NullHandler = logging.NullHandler +else: + class NullHandler(logging.Handler): + def handle(self, record): + pass + + def emit(self, record): + pass + + def createLock(self): + self.lock = None + + THIS_DIR = os.path.dirname(os.path.abspath(__file__)) skipUnless = getattr(unittest, "skipUnless", None) @@ -1906,6 +1923,31 @@ def test_signal_exception_aliases(self): self.assertEqual(sig, SignalException_SIGQUIT) + def test_change_log_message(self): + py = create_tmp_test(""" +print("cool") +""") + def log_msg(cmd, call_args): + return 'Hi! I ran ' % (cmd,) + + buf = StringIO() + handler = logging.StreamHandler(buf) + logger = logging.getLogger('sh') + try: + logger.addHandler(handler) + with sh.args(log_msg=log_msg): + python(py.name, "meow", "bark") + finally: + logger.removeHandler(handler) + + loglines = buf.getvalue().split('\n') + self.assertTrue(loglines, 'Log handler captured no messages?') + self.assertTrue( + fnmatch.fnmatch(loglines[0], + 'Hi! I ran : starting process'), + + 'Log line did not match: %r' % (loglines[0],)) + class StreamBuffererTests(unittest.TestCase): def test_unbuffered(self): @@ -1936,6 +1978,10 @@ def test_chunk_buffered(self): if __name__ == "__main__": + root = logging.getLogger() + root.setLevel(logging.DEBUG) + root.addHandler(NullHandler()) + # if we're running a specific test, we can let unittest framework figure out # that test and run it itself. it will also handle setting the return code # of the process if any tests error or fail