def isolated_logger(request, logger, monkeypatch): # In Python the common idiom of using logging is to share the same log # globally, even between threads. While this is usually OK because # internally Python takes care of locking the shared resources, it also # makes very difficult to build things on top of the logging system without # using the same global approach. # For simplicity, to make things easier to extension developers and because # PyScaffold not really uses multiple threads, this is the case in # `pyscaffold.log`. # On the other hand, shared state and streams can make the testing # environment a real pain, since we are messing with everything all the # time, specially when running tests in parallel (so we not guarantee the # execution order). # This fixture do a huge effort in trying to isolate as much as possible # each test function regarding logging. We keep the global object, so the # tests can be seamless, but internally replace the underlying native # loggers and handlers for "one-shot" ones. # (Of course, we can keep the same global object just because the plugins # for running tests in parallel are based in multiple processes instead of # threads, otherwise we would need another strategy) if "original_logger" in request.keywords: # Some tests need to check the original implementation to make sure # side effects of the shared object are consistent. We have to try to # make them as few as possible. yield logger return # Get a fresh new logger, not used anywhere raw_logger = logging.getLogger(uniqstr()) # ^ Python docs advert against instantiating Loggers directly and instruct # devs to use `getLogger`. So we use a unique name to guarantee we get a # new logger each time. raw_logger.setLevel(logging.NOTSET) new_handler = logging.StreamHandler() # Replace the internals of the LogAdapter # --> Messing with global state: don't try this at home ... # (if we start to use threads, we cannot do this) # Be lazy to import modules due to coverage warnings # (see @FlorianWilhelm comments on #174) from pyscaffold.log import ReportFormatter monkeypatch.setattr(logger, "propagate", True) monkeypatch.setattr(logger, "nesting", 0) monkeypatch.setattr(logger, "wrapped", raw_logger) monkeypatch.setattr(logger, "handler", new_handler) monkeypatch.setattr(logger, "formatter", ReportFormatter()) # <-- try: yield logger finally: new_handler.close()
def test_format(): formatter = ReportFormatter() def format(*args, **kwargs): return formatter.format(make_record(*args, **kwargs)).lstrip() assert format('run', 'ls -lf .') == 'run ls -lf .' assert format('run', 'ls', context=parent_dir()) == "run ls from '..'" assert (format('copy', getcwd(), target='../dir/../dir') == "copy . to '../dir'") assert format('create', 'my/file', nesting=1) == 'create my/file'
def test_format_path(): formatter = ReportFormatter() format = formatter.format_path # Formatter should abbrev paths but keep other subjects unchanged assert format('not a command') == 'not a command' assert format('git commit') == 'git commit' assert format('a random message') == 'a random message' assert format(getcwd()) == '.' assert format('../dir/../dir/..') == '..' assert format('../dir/../dir/../foo') == '../foo' assert format('/a') == '/a' # shorter absolute is better than relative
def test_format_path(): formatter = ReportFormatter() format = formatter.format_path # Formatter should abbrev paths but keep other subjects unchanged assert format("not a command") == "not a command" assert format("git commit") == "git commit" assert format("a random message") == "a random message" assert format(getcwd()) == "." assert format(lp("../dir/../dir/..")) == lp("..") assert format(lp("../dir/../dir/../foo")) == lp("../foo") # shorter absolute is better than relative assert format(lp("/a")) == lp("/a")
def test_format(): formatter = ReportFormatter() def format(*args, **kwargs): return formatter.format(make_record(*args, **kwargs)).lstrip() assert format("run", "ls -lf .") == "run ls -lf ." assert format("run", "ls", context=parent_dir()) == "run ls from '..'" assert format("copy", getcwd(), target=lp("../dir/../dir")) == "copy . to '{}'".format( lp("../dir")) fmt_out = format("create", lp("my/file"), nesting=1) assert fmt_out == "create {}".format(lp("my/file"))
def test_reconfigure(monkeypatch, caplog, uniq_raw_logger): # Given an environment that supports color, and a restrictive logger caplog.set_level(logging.NOTSET) monkeypatch.setattr("pyscaffold.termui.supports_color", lambda *_: True) new_logger = ReportLogger(uniq_raw_logger, formatter=ReportFormatter()) new_logger.level = logging.INFO # when the logger is reconfigured new_logger.reconfigure() name = uniqstr() # then the messages should be displayed and use colors new_logger.report("some1", name) out = caplog.messages[-1] assert re.search(ansi_pattern("some1") + ".+" + name, out) # when the logger is reconfigured with a higher level new_logger.reconfigure(log_level=logging.CRITICAL) # then the messages should not be displayed name = uniqstr() new_logger.report("some2", name) assert not re.search(ansi_pattern("some2") + ".+" + name, caplog.text)
def test_format_context(): formatter = ReportFormatter() format = formatter.format_context assert format(None) == '' assert format(getcwd()) == '' assert format(parent_dir()) == "from '..'"
def test_format_target(): formatter = ReportFormatter() format = formatter.format_target assert format(None) == '' assert format(getcwd()) == '' assert format(parent_dir()) == "to '..'"
def test_create_padding(): formatter = ReportFormatter() for text in ['abcd', 'abcdefg', 'ab']: padding = formatter.create_padding(text) # Formatter should ensure activates are right padded assert len(padding + text) == formatter.ACTIVITY_MAXLEN