1# -*- coding: utf-8 -*- 2 3# This file is dual licensed under the terms of the Apache License, Version 4# 2.0, and the MIT License. See the LICENSE file in the root of this 5# repository for complete details. 6 7from __future__ import absolute_import, division, print_function 8 9import pytest 10import six 11 12from structlog import dev 13 14 15class TestPad(object): 16 def test_normal(self): 17 """ 18 If chars are missing, adequate number of " " are added. 19 """ 20 assert 100 == len(dev._pad("test", 100)) 21 22 def test_negative(self): 23 """ 24 If string is already too long, don't do anything. 25 """ 26 assert len("test") == len(dev._pad("test", 2)) 27 28 29@pytest.fixture 30def cr(): 31 return dev.ConsoleRenderer(colors=dev._has_colorama) 32 33 34@pytest.fixture 35def styles(cr): 36 return cr._styles 37 38 39@pytest.fixture 40def padded(styles): 41 return ( 42 styles.bright + dev._pad("test", dev._EVENT_WIDTH) + styles.reset + " " 43 ) 44 45 46@pytest.fixture 47def unpadded(styles): 48 return styles.bright + "test" + styles.reset 49 50 51class TestConsoleRenderer(object): 52 @pytest.mark.skipif(dev._has_colorama, reason="Colorama must be missing.") 53 def test_missing_colorama(self): 54 """ 55 ConsoleRenderer(colors=True) raises SystemError on initialization if 56 colorama is missing. 57 """ 58 with pytest.raises(SystemError) as e: 59 dev.ConsoleRenderer() 60 61 assert ( 62 "ConsoleRenderer with `colors=True` requires the colorama package " 63 "installed." 64 ) in e.value.args[0] 65 66 def test_plain(self, cr, styles, unpadded): 67 """ 68 Works with a plain event_dict with only the event. 69 """ 70 rv = cr(None, None, {"event": "test"}) 71 72 assert unpadded == rv 73 74 def test_timestamp(self, cr, styles, unpadded): 75 """ 76 Timestamps get prepended. 77 """ 78 rv = cr(None, None, {"event": "test", "timestamp": 42}) 79 80 assert (styles.timestamp + "42" + styles.reset + " " + unpadded) == rv 81 82 def test_level(self, cr, styles, padded): 83 """ 84 Levels are rendered aligned, in square brackets, and color coded. 85 """ 86 rv = cr( 87 None, None, {"event": "test", "level": "critical", "foo": "bar"} 88 ) 89 90 # fmt: off 91 assert ( 92 "[" + dev.RED + styles.bright + 93 dev._pad("critical", cr._longest_level) + 94 styles.reset + "] " + 95 padded + 96 styles.kv_key + "foo" + styles.reset + "=" + 97 styles.kv_value + "bar" + styles.reset 98 ) == rv 99 # fmt: on 100 101 def test_init_accepts_overriding_levels(self, styles, padded): 102 """ 103 Stdlib levels are rendered aligned, in brackets, and color coded. 104 """ 105 my_styles = dev.ConsoleRenderer.get_default_level_styles( 106 colors=dev._has_colorama 107 ) 108 my_styles["MY_OH_MY"] = my_styles["critical"] 109 cr = dev.ConsoleRenderer( 110 colors=dev._has_colorama, level_styles=my_styles 111 ) 112 113 # this would blow up if the level_styles override failed 114 rv = cr( 115 None, None, {"event": "test", "level": "MY_OH_MY", "foo": "bar"} 116 ) 117 118 # fmt: off 119 assert ( 120 "[" + dev.RED + styles.bright + 121 dev._pad("MY_OH_MY", cr._longest_level) + 122 styles.reset + "] " + 123 padded + 124 styles.kv_key + "foo" + styles.reset + "=" + 125 styles.kv_value + "bar" + styles.reset 126 ) == rv 127 # fmt: on 128 129 def test_logger_name(self, cr, styles, padded): 130 """ 131 Logger names are appended after the event. 132 """ 133 rv = cr(None, None, {"event": "test", "logger": "some_module"}) 134 135 # fmt: off 136 assert ( 137 padded + 138 "[" + dev.BLUE + styles.bright + 139 "some_module" + 140 styles.reset + "] " 141 ) == rv 142 # fmt: on 143 144 def test_key_values(self, cr, styles, padded): 145 """ 146 Key-value pairs go sorted alphabetically to the end. 147 """ 148 rv = cr(None, None, {"event": "test", "key": "value", "foo": "bar"}) 149 150 # fmt: off 151 assert ( 152 padded + 153 styles.kv_key + "foo" + styles.reset + "=" + 154 styles.kv_value + "bar" + 155 styles.reset + " " + 156 styles.kv_key + "key" + styles.reset + "=" + 157 styles.kv_value + "value" + 158 styles.reset 159 ) == rv 160 # fmt: on 161 162 def test_exception(self, cr, padded): 163 """ 164 Exceptions are rendered after a new line. 165 """ 166 exc = "Traceback:\nFake traceback...\nFakeError: yolo" 167 168 rv = cr(None, None, {"event": "test", "exception": exc}) 169 170 assert (padded + "\n" + exc) == rv 171 172 def test_stack_info(self, cr, padded): 173 """ 174 Stack traces are rendered after a new line. 175 """ 176 stack = "fake stack" 177 rv = cr(None, None, {"event": "test", "stack": stack}) 178 179 assert (padded + "\n" + stack) == rv 180 181 def test_pad_event_param(self, styles): 182 """ 183 `pad_event` parameter works. 184 """ 185 rv = dev.ConsoleRenderer(42, dev._has_colorama)( 186 None, None, {"event": "test", "foo": "bar"} 187 ) 188 189 # fmt: off 190 assert ( 191 styles.bright + 192 dev._pad("test", 42) + 193 styles.reset + " " + 194 styles.kv_key + "foo" + styles.reset + "=" + 195 styles.kv_value + "bar" + styles.reset 196 ) == rv 197 # fmt: on 198 199 def test_everything(self, cr, styles, padded): 200 """ 201 Put all cases together. 202 """ 203 exc = "Traceback:\nFake traceback...\nFakeError: yolo" 204 stack = "fake stack trace" 205 206 rv = cr( 207 None, 208 None, 209 { 210 "event": "test", 211 "exception": exc, 212 "key": "value", 213 "foo": "bar", 214 "timestamp": "13:13", 215 "logger": "some_module", 216 "level": "error", 217 "stack": stack, 218 }, 219 ) 220 221 # fmt: off 222 assert ( 223 styles.timestamp + "13:13" + styles.reset + 224 " [" + styles.level_error + styles.bright + 225 dev._pad("error", cr._longest_level) + 226 styles.reset + "] " + 227 padded + 228 "[" + dev.BLUE + styles.bright + 229 "some_module" + 230 styles.reset + "] " + 231 styles.kv_key + "foo" + styles.reset + "=" + 232 styles.kv_value + "bar" + 233 styles.reset + " " + 234 styles.kv_key + "key" + styles.reset + "=" + 235 styles.kv_value + "value" + 236 styles.reset + 237 "\n" + stack + "\n\n" + "=" * 79 + "\n" + 238 "\n" + exc 239 ) == rv 240 # fmt: on 241 242 def test_colorama_colors_false(self): 243 """ 244 If colors is False, don't use colors or styles ever. 245 """ 246 plain_cr = dev.ConsoleRenderer(colors=False) 247 248 rv = plain_cr( 249 None, None, {"event": "event", "level": "info", "foo": "bar"} 250 ) 251 252 assert dev._PlainStyles is plain_cr._styles 253 assert "[info ] event foo=bar" == rv 254 255 def test_colorama_force_colors(self, styles, padded): 256 """ 257 If force_colors is True, use colors even if the destination is non-tty. 258 """ 259 cr = dev.ConsoleRenderer( 260 colors=dev._has_colorama, force_colors=dev._has_colorama 261 ) 262 263 rv = cr( 264 None, None, {"event": "test", "level": "critical", "foo": "bar"} 265 ) 266 267 # fmt: off 268 assert ( 269 "[" + dev.RED + styles.bright + 270 dev._pad("critical", cr._longest_level) + 271 styles.reset + "] " + 272 padded + 273 styles.kv_key + "foo" + styles.reset + "=" + 274 styles.kv_value + "bar" + styles.reset 275 ) == rv 276 # fmt: on 277 278 assert not dev._has_colorama or dev._ColorfulStyles is cr._styles 279 280 @pytest.mark.parametrize("rns", [True, False]) 281 def test_repr_native_str(self, rns): 282 """ 283 repr_native_str=False doesn't repr on native strings. "event" is 284 never repr'ed. 285 """ 286 rv = dev.ConsoleRenderer(colors=False, repr_native_str=rns)( 287 None, None, {"event": "哈", "key": 42, "key2": "哈"} 288 ) 289 290 cnt = rv.count("哈") 291 if rns and six.PY2: 292 assert 1 == cnt 293 else: 294 assert 2 == cnt 295