import io import re import sys import pytest from werkzeug.debug import console from werkzeug.debug import DebuggedApplication from werkzeug.debug import get_machine_id from werkzeug.debug.console import HTMLStringO from werkzeug.debug.repr import debug_repr from werkzeug.debug.repr import DebugReprGenerator from werkzeug.debug.repr import dump from werkzeug.debug.repr import helper from werkzeug.debug.tbtools import Traceback from werkzeug.test import Client from werkzeug.wrappers import Request class TestDebugRepr: def test_basic_repr(self): assert debug_repr([]) == "[]" assert debug_repr([1, 2]) == ( '[1, 2]' ) assert debug_repr([1, "test"]) == ( '[1,' ' 'test']' ) assert debug_repr([None]) == '[None]' def test_string_repr(self): assert debug_repr("") == '''' assert debug_repr("foo") == ''foo'' assert debug_repr("s" * 80) == ( f''{"s" * 69}' f'{"s" * 11}'' ) assert debug_repr("<" * 80) == ( f''{"<" * 69}' f'{"<" * 11}'' ) def test_string_subclass_repr(self): class Test(str): pass assert debug_repr(Test("foo")) == ( 'test_debug.' 'Test('foo')' ) def test_sequence_repr(self): assert debug_repr(list(range(20))) == ( '[0, 1, ' '2, 3, ' '4, 5, ' '6, 7, ' '8, ' '9, 10, ' '11, 12, ' '13, 14, ' '15, 16, ' '17, 18, ' '19]' ) def test_mapping_repr(self): assert debug_repr({}) == "{}" assert debug_repr({"foo": 42}) == ( '{'foo'' ': 42' "}" ) assert debug_repr(dict(zip(range(10), [None] * 10))) == ( '{0' ': None' ", " '1' ': None' ", " '2' ': None' ", " '3' ': None' ", " '' '4' ': None' ", " '5' ': None' ", " '6' ': None' ", " '7' ': None' ", " '8' ': None' ", " '9' ': None' "}" ) assert debug_repr((1, "zwei", "drei")) == ( '(1, '' 'zwei', 'drei')' ) def test_custom_repr(self): class Foo: def __repr__(self): return "" assert debug_repr(Foo()) == '<Foo 42>' def test_list_subclass_repr(self): class MyList(list): pass assert debug_repr(MyList([1, 2])) == ( 'test_debug.MyList([' '1, 2])' ) def test_regex_repr(self): assert ( debug_repr(re.compile(r"foo\d")) == "re.compile(r'foo\\d')" ) # No ur'' in Py3 # https://bugs.python.org/issue15096 assert debug_repr(re.compile("foo\\d")) == ( "re.compile(r'foo\\d')" ) def test_set_repr(self): assert ( debug_repr(frozenset("x")) == 'frozenset(['x'])' ) assert debug_repr(set("x")) == ( 'set(['x'])' ) def test_recursive_repr(self): a = [1] a.append(a) assert debug_repr(a) == '[1, [...]]' def test_broken_repr(self): class Foo: def __repr__(self): raise Exception("broken!") assert debug_repr(Foo()) == ( '<broken repr (Exception: ' "broken!)>" ) class Foo: x = 42 y = 23 def __init__(self): self.z = 15 class TestDebugHelpers: def test_object_dumping(self): drg = DebugReprGenerator() out = drg.dump_object(Foo()) assert re.search("Details for test_debug.Foo object at", out) assert re.search('x.*42', out, flags=re.DOTALL) assert re.search('y.*23', out, flags=re.DOTALL) assert re.search('z.*15', out, flags=re.DOTALL) out = drg.dump_object({"x": 42, "y": 23}) assert re.search("Contents of", out) assert re.search('x.*42', out, flags=re.DOTALL) assert re.search('y.*23', out, flags=re.DOTALL) out = drg.dump_object({"x": 42, "y": 23, 23: 11}) assert not re.search("Contents of", out) out = drg.dump_locals({"x": 42, "y": 23}) assert re.search("Local variables in frame", out) assert re.search('x.*42', out, flags=re.DOTALL) assert re.search('y.*23', out, flags=re.DOTALL) def test_debug_dump(self): old = sys.stdout sys.stdout = HTMLStringO() try: dump([1, 2, 3]) x = sys.stdout.reset() dump() y = sys.stdout.reset() finally: sys.stdout = old assert "Details for list object at" in x assert '1' in x assert "Local variables in frame" in y assert "x" in y assert "old" in y def test_debug_help(self): old = sys.stdout sys.stdout = HTMLStringO() try: helper([1, 2, 3]) x = sys.stdout.reset() finally: sys.stdout = old assert "Help on list object" in x assert "__delitem__" in x def test_exc_divider_found_on_chained_exception(self): @Request.application def app(request): def do_something(): raise ValueError("inner") try: do_something() except ValueError: raise KeyError("outer") debugged = DebuggedApplication(app) client = Client(debugged) response = client.get("/") data = response.get_data(as_text=True) assert 'raise ValueError("inner")' in data assert '
' in data assert 'raise KeyError("outer")' in data class TestTraceback: def test_log(self): try: 1 / 0 except ZeroDivisionError: traceback = Traceback(*sys.exc_info()) buffer_ = io.StringIO() traceback.log(buffer_) assert buffer_.getvalue().strip() == traceback.plaintext.strip() def test_sourcelines_encoding(self): source = ( "# -*- coding: latin1 -*-\n\n" "def foo():\n" ' """höhö"""\n' " 1 / 0\n" "foo()" ).encode("latin1") code = compile(source, filename="lol.py", mode="exec") try: eval(code) except ZeroDivisionError: traceback = Traceback(*sys.exc_info()) frames = traceback.frames assert len(frames) == 3 assert frames[1].filename == "lol.py" assert frames[2].filename == "lol.py" class Loader: def get_source(self, module): return source frames[1].loader = frames[2].loader = Loader() assert frames[1].sourcelines == frames[2].sourcelines assert [line.code for line in frames[1].get_annotated_lines()] == [ line.code for line in frames[2].get_annotated_lines() ] assert "höhö" in frames[1].sourcelines[3] def test_filename_encoding(self, tmpdir, monkeypatch): moduledir = tmpdir.mkdir("föö") moduledir.join("bar.py").write("def foo():\n 1/0\n") monkeypatch.syspath_prepend(str(moduledir)) import bar try: bar.foo() except ZeroDivisionError: traceback = Traceback(*sys.exc_info()) assert "föö" in "\n".join(frame.render() for frame in traceback.frames) def test_get_machine_id(): rv = get_machine_id() assert isinstance(rv, bytes) @pytest.mark.parametrize("crash", (True, False)) def test_basic(dev_server, crash): c = dev_server(use_debugger=True) r = c.request("/crash" if crash else "") assert r.status == (500 if crash else 200) if crash: assert b"The debugger caught an exception in your WSGI application" in r.data else: assert r.json["PATH_INFO"] == "/" def test_console_closure_variables(monkeypatch): # restore the original display hook monkeypatch.setattr(sys, "displayhook", console._displayhook) c = console.Console() c.eval("y = 5") c.eval("x = lambda: y") ret = c.eval("x()") assert ret == ">>> x()\n5\n" @pytest.mark.timeout(2) def test_chained_exception_cycle(): try: try: raise ValueError() except ValueError: raise TypeError() except TypeError as e: # create a cycle and make it available outside the except block e.__context__.__context__ = error = e # if cycles aren't broken, this will time out tb = Traceback(TypeError, error, error.__traceback__) assert len(tb.groups) == 2 def test_non_hashable_exception(): class MutableException(ValueError): __hash__ = None try: raise MutableException() except MutableException: # previously crashed: `TypeError: unhashable type 'MutableException'` Traceback(*sys.exc_info())