diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 75b669f..7d5c4cd 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -58,9 +58,9 @@ jobs: - run: pip freeze - name: test with extras - run: | - make test - coverage xml + run: make test + + - run: coverage xml - uses: codecov/codecov-action@v2.0.3 with: @@ -71,9 +71,9 @@ jobs: run: pip uninstall -y multidict numpy pydantic asyncpg - name: test without extras - run: | - make test - coverage xml + run: make test + + - run: coverage xml - uses: codecov/codecov-action@v2.0.3 with: diff --git a/devtools/debug.py b/devtools/debug.py index b9f7182..1954bf5 100644 --- a/devtools/debug.py +++ b/devtools/debug.py @@ -141,15 +141,16 @@ def _process(self, args, kwargs) -> DebugOutput: warning=self._show_warnings and 'error parsing code, call stack too shallow', ) - filename = call_frame.f_code.co_filename function = call_frame.f_code.co_name - if filename.startswith('/'): - # make the path relative - from pathlib import Path + from pathlib import Path + + path = Path(call_frame.f_code.co_filename) + if path.is_absolute(): + # make the path relative cwd = Path('.').resolve() try: - filename = str(Path(filename).relative_to(cwd)) + path = path.relative_to(cwd) except ValueError: # happens if filename path is not within CWD pass @@ -173,7 +174,7 @@ def _process(self, args, kwargs) -> DebugOutput: arguments = list(self._process_args(ex, args, kwargs)) return self.output_class( - filename=filename, + filename=str(path), lineno=lineno, frame=function, arguments=arguments, diff --git a/tests/test_expr_render.py b/tests/test_expr_render.py index 7d66268..9ff5fa6 100644 --- a/tests/test_expr_render.py +++ b/tests/test_expr_render.py @@ -1,21 +1,21 @@ import asyncio -import re import sys import pytest from devtools import Debug, debug +from .utils import normalise_output + def foobar(a, b, c): return a + b + c -@pytest.mark.xfail(sys.platform == 'win32', reason='as yet unknown windows problem') def test_simple(): a = [1, 2, 3] v = debug.format(len(a)) - s = re.sub(r':\d{2,}', ':', str(v)) + s = normalise_output(str(v)) # print(s) assert ( 'tests/test_expr_render.py: test_simple\n' @@ -23,18 +23,16 @@ def test_simple(): ) == s -@pytest.mark.xfail(sys.platform == 'win32', reason='as yet unknown windows problem') def test_subscription(): a = {1: 2} v = debug.format(a[1]) - s = re.sub(r':\d{2,}', ':', str(v)) + s = normalise_output(str(v)) assert ( 'tests/test_expr_render.py: test_subscription\n' ' a[1]: 2 (int)' ) == s -@pytest.mark.xfail(sys.platform == 'win32', reason='as yet unknown windows problem') def test_exotic_types(): aa = [1, 2, 3] v = debug.format( @@ -49,8 +47,7 @@ def test_exotic_types(): {a: a + 1 for a in aa}, (a for a in aa), ) - s = re.sub(r':\d{2,}', ':', str(v)) - s = re.sub(r'(at 0x)\w+', r'\1', s) + s = normalise_output(str(v)) print('\n---\n{}\n---'.format(v)) # Generator expression source changed in 3.8 to include parentheses, see: @@ -83,11 +80,10 @@ def test_exotic_types(): ) == s -@pytest.mark.xfail(sys.platform == 'win32', reason='as yet unknown windows problem') def test_newline(): v = debug.format( foobar(1, 2, 3)) - s = re.sub(r':\d{2,}', ':', str(v)) + s = normalise_output(str(v)) # print(s) assert ( 'tests/test_expr_render.py: test_newline\n' @@ -95,12 +91,11 @@ def test_newline(): ) == s -@pytest.mark.xfail(sys.platform == 'win32', reason='as yet unknown windows problem') def test_trailing_bracket(): v = debug.format( foobar(1, 2, 3) ) - s = re.sub(r':\d{2,}', ':', str(v)) + s = normalise_output(str(v)) # print(s) assert ( 'tests/test_expr_render.py: test_trailing_bracket\n' @@ -108,14 +103,13 @@ def test_trailing_bracket(): ) == s -@pytest.mark.xfail(sys.platform == 'win32', reason='as yet unknown windows problem') def test_multiline(): v = debug.format( foobar(1, 2, 3) ) - s = re.sub(r':\d{2,}', ':', str(v)) + s = normalise_output(str(v)) # print(s) assert ( 'tests/test_expr_render.py: test_multiline\n' @@ -123,12 +117,11 @@ def test_multiline(): ) == s -@pytest.mark.xfail(sys.platform == 'win32', reason='as yet unknown windows problem') def test_multiline_trailing_bracket(): v = debug.format( foobar(1, 2, 3 )) - s = re.sub(r':\d{2,}', ':', str(v)) + s = normalise_output(str(v)) # print(s) assert ( 'tests/test_expr_render.py: test_multiline_trailing_bracket\n' @@ -136,7 +129,6 @@ def test_multiline_trailing_bracket(): ) == s -@pytest.mark.xfail(sys.platform == 'win32', reason='as yet unknown windows problem') @pytest.mark.skipif(sys.version_info < (3, 6), reason='kwarg order is not guaranteed for 3.5') def test_kwargs(): v = debug.format( @@ -144,7 +136,7 @@ def test_kwargs(): a=6, b=7 ) - s = re.sub(r':\d{2,}', ':', str(v)) + s = normalise_output(str(v)) assert ( 'tests/test_expr_render.py: test_kwargs\n' ' foobar(1, 2, 3): 6 (int)\n' @@ -153,7 +145,6 @@ def test_kwargs(): ) == s -@pytest.mark.xfail(sys.platform == 'win32', reason='as yet unknown windows problem') @pytest.mark.skipif(sys.version_info < (3, 6), reason='kwarg order is not guaranteed for 3.5') def test_kwargs_multiline(): v = debug.format( @@ -162,7 +153,7 @@ def test_kwargs_multiline(): a=6, b=7 ) - s = re.sub(r':\d{2,}', ':', str(v)) + s = normalise_output(str(v)) assert ( 'tests/test_expr_render.py: test_kwargs_multiline\n' ' foobar(1, 2, 3): 6 (int)\n' @@ -171,20 +162,18 @@ def test_kwargs_multiline(): ) == s -@pytest.mark.xfail(sys.platform == 'win32', reason='as yet unknown windows problem') def test_multiple_trailing_lines(): v = debug.format( foobar( 1, 2, 3 ), ) - s = re.sub(r':\d{2,}', ':', str(v)) + s = normalise_output(str(v)) assert ( 'tests/test_expr_render.py: test_multiple_trailing_lines\n foobar( 1, 2, 3 ): 6 (int)' ) == s -@pytest.mark.xfail(sys.platform == 'win32', reason='as yet unknown windows problem') def test_very_nested_last_statement(): def func(): return debug.format( @@ -201,14 +190,13 @@ def func(): v = func() # check only the original code is included in the warning - s = re.sub(r':\d{2,}', ':', str(v)) + s = normalise_output(str(v)) assert s == ( 'tests/test_expr_render.py: func\n' ' abs( abs( abs( abs( -1 ) ) ) ): 1 (int)' ) -@pytest.mark.xfail(sys.platform == 'win32', reason='as yet unknown windows problem') def test_syntax_warning(): def func(): return debug.format( @@ -227,7 +215,7 @@ def func(): v = func() # check only the original code is included in the warning - s = re.sub(r':\d{2,}', ':', str(v)) + s = normalise_output(str(v)) assert s == ( 'tests/test_expr_render.py: func\n' ' abs( abs( abs( abs( abs( -1 ) ) ) ) ): 1 (int)' @@ -254,14 +242,13 @@ def func(): ) v = func() - s = re.sub(r':\d{2,}', ':', str(v)) + s = normalise_output(str(v)) assert s == ( 'tests/test_expr_render.py: func\n' ' abs( abs( abs( abs( abs( -1 ) ) ) ) ): 1 (int)' ) -@pytest.mark.xfail(sys.platform == 'win32', reason='as yet unknown windows problem') def test_await(): async def foo(): return 1 @@ -272,7 +259,7 @@ async def bar(): loop = asyncio.new_event_loop() v = loop.run_until_complete(bar()) loop.close() - s = re.sub(r':\d{2,}', ':', str(v)) + s = normalise_output(str(v)) assert ( 'tests/test_expr_render.py: bar\n' ' await foo(): 1 (int)' @@ -284,7 +271,7 @@ def test_other_debug_arg(): v = debug.format([1, 2]) # check only the original code is included in the warning - s = re.sub(r':\d{2,}', ':', str(v)) + s = normalise_output(str(v)) assert s == ( 'tests/test_expr_render.py: test_other_debug_arg\n' ' [1, 2] (list) len=2' @@ -297,7 +284,7 @@ def test_other_debug_arg_not_literal(): y = 2 v = debug.format([x, y]) - s = re.sub(r':\d{2,}', ':', str(v)) + s = normalise_output(str(v)) assert s == ( 'tests/test_expr_render.py: test_other_debug_arg_not_literal\n' ' [x, y]: [1, 2] (list) len=2' @@ -310,7 +297,7 @@ def test_executing_failure(): y = 2 # executing fails inside a pytest assert ast the AST is modified - assert re.sub(r':\d{2,}', ':', str(debug.format([x, y]))) == ( + assert normalise_output(str(debug.format([x, y]))) == ( 'tests/test_expr_render.py: test_executing_failure ' '(executing failed to find the calling node)\n' ' [1, 2] (list) len=2' @@ -326,7 +313,7 @@ def test_format_inside_error(): except RuntimeError as e: v = str(e) - s = re.sub(r':\d{2,}', ':', str(v)) + s = normalise_output(str(v)) assert s == ( 'tests/test_expr_render.py: test_format_inside_error\n' ' [x, y]: [1, 2] (list) len=2' diff --git a/tests/test_main.py b/tests/test_main.py index 9b51b72..667a79a 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -9,7 +9,7 @@ from devtools import Debug, debug from devtools.ansi import strip_ansi -pytestmark = pytest.mark.xfail(sys.platform == 'win32', reason='as yet unknown windows problem') +from .utils import normalise_output def test_print(capsys): @@ -18,7 +18,7 @@ def test_print(capsys): result = debug(a, b) stdout, stderr = capsys.readouterr() print(stdout) - assert re.sub(r':\d{2,}', ':', stdout) == ( + assert normalise_output(stdout) == ( 'tests/test_main.py: test_print\n' ' a: 1 (int)\n' ' b: 2 (int)\n' @@ -33,7 +33,7 @@ def test_print_kwargs(capsys): result = debug(a, b, foo=[1, 2, 3]) stdout, stderr = capsys.readouterr() print(stdout) - assert re.sub(r':\d{2,}', ':', stdout) == ( + assert normalise_output(stdout) == ( 'tests/test_main.py: test_print_kwargs\n' ' a: 1 (int)\n' ' b: 2 (int)\n' @@ -49,7 +49,7 @@ def test_print_generator(capsys): result = debug(gen) stdout, stderr = capsys.readouterr() print(stdout) - assert re.sub(r':\d{2,}', ':', stdout) == ( + assert normalise_output(stdout) == ( 'tests/test_main.py: test_print_generator\n' ' gen: (\n' ' 1,\n' @@ -66,7 +66,7 @@ def test_format(): a = b'i might bite' b = "hello this is a test" v = debug.format(a, b) - s = re.sub(r':\d{2,}', ':', str(v)) + s = normalise_output(str(v)) print(s) assert s == ( "tests/test_main.py: test_format\n" @@ -75,6 +75,10 @@ def test_format(): ) +@pytest.mark.xfail( + sys.platform == 'win32', + reason='Fatal Python error: _Py_HashRandomization_Init: failed to get random numbers to initialize Python', +) def test_print_subprocess(tmpdir): f = tmpdir.join('test.py') f.write("""\ @@ -109,7 +113,12 @@ def test_odd_path(mocker): mocked_relative_to = mocker.patch('pathlib.Path.relative_to') mocked_relative_to.side_effect = ValueError() v = debug.format('test') - assert re.search(r"/.*?/test_main.py:\d{2,} test_odd_path\n 'test' \(str\) len=4", str(v)), v + if sys.platform == "win32": + pattern = r"\w:\\.*?\\" + else: + pattern = r"/.*?/" + pattern += r"test_main.py:\d{2,} test_odd_path\n 'test' \(str\) len=4" + assert re.search(pattern, str(v)), v def test_small_call_frame(): @@ -119,7 +128,7 @@ def test_small_call_frame(): 2, 3, ) - assert re.sub(r':\d{2,}', ':', str(v)) == ( + assert normalise_output(str(v)) == ( 'tests/test_main.py: test_small_call_frame\n' ' 1 (int)\n' ' 2 (int)\n' @@ -135,7 +144,7 @@ def test_small_call_frame_warning(): 3, ) print('\n---\n{}\n---'.format(v)) - assert re.sub(r':\d{2,}', ':', str(v)) == ( + assert normalise_output(str(v)) == ( 'tests/test_main.py: test_small_call_frame_warning\n' ' 1 (int)\n' ' 2 (int)\n' @@ -147,7 +156,7 @@ def test_small_call_frame_warning(): def test_kwargs(): a = 'variable' v = debug.format(first=a, second='literal') - s = re.sub(r':\d{2,}', ':', str(v)) + s = normalise_output(str(v)) print(s) assert s == ( "tests/test_main.py: test_kwargs\n" @@ -160,7 +169,7 @@ def test_kwargs_orderless(): # for python3.5 a = 'variable' v = debug.format(first=a, second='literal') - s = re.sub(r':\d{2,}', ':', str(v)) + s = normalise_output(str(v)) assert set(s.split('\n')) == { "tests/test_main.py: test_kwargs_orderless", " first: 'variable' (str) len=8 variable=a", @@ -170,14 +179,14 @@ def test_kwargs_orderless(): def test_simple_vars(): v = debug.format('test', 1, 2) - s = re.sub(r':\d{2,}', ':', str(v)) + s = normalise_output(str(v)) assert s == ( "tests/test_main.py: test_simple_vars\n" " 'test' (str) len=4\n" " 1 (int)\n" " 2 (int)" ) - r = re.sub(r':\d{2,}', ':', repr(v)) + r = normalise_output(repr(v)) assert r == ( " test_simple_vars arguments: 'test' (str) len=4 1 (int) 2 (int)>" ) @@ -239,17 +248,17 @@ def test_exec(capsys): def test_colours(): v = debug.format(range(6)) - s = re.sub(r':\d{2,}', ':', v.str(True)) + s = v.str(True) assert s.startswith('\x1b[35mtests'), repr(s) - s2 = strip_ansi(s) - assert s2 == v.str(), repr(s2) + s2 = normalise_output(strip_ansi(s)) + assert s2 == normalise_output(v.str()), repr(s2) def test_colours_warnings(mocker): mocked_getframe = mocker.patch('sys._getframe') mocked_getframe.side_effect = ValueError() v = debug.format('x') - s = re.sub(r':\d{2,}', ':', v.str(True)) + s = normalise_output(v.str(True)) assert s.startswith('\x1b[35m'), repr(s) s2 = strip_ansi(s) assert s2 == v.str(), repr(s2) @@ -270,10 +279,14 @@ def test_breakpoint(mocker): assert mocked_set_trace.called +@pytest.mark.xfail( + sys.platform == 'win32' and sys.version_info >= (3, 9), + reason='see https://github.com/alexmojaki/executing/issues/27', +) def test_starred_kwargs(): v = {'foo': 1, 'bar': 2} v = debug.format(**v) - s = re.sub(r':\d{2,}', ':', v.str()) + s = normalise_output(v.str()) assert set(s.split('\n')) == { 'tests/test_main.py: test_starred_kwargs', ' foo: 1 (int)', @@ -289,11 +302,10 @@ def __getattr__(self, item): b = BadPretty() v = debug.format(b) - s = re.sub(r':\d{2,}', ':', str(v)) - s = re.sub(r'0x[0-9a-f]+', '0x000', s) + s = normalise_output(str(v)) assert s == ( "tests/test_main.py: test_pretty_error\n" - " b: .BadPretty object at 0x000> (BadPretty)\n" + " b: .BadPretty object at 0x> (BadPretty)\n" " !!! error pretty printing value: RuntimeError('this is an error')" ) @@ -302,7 +314,7 @@ def test_multiple_debugs(): debug.format([i * 2 for i in range(2)]) debug.format([i * 2 for i in range(2)]) v = debug.format([i * 2 for i in range(2)]) - s = re.sub(r':\d{2,}', ':', str(v)) + s = normalise_output(str(v)) assert s == ( 'tests/test_main.py: test_multiple_debugs\n' ' [i * 2 for i in range(2)]: [0, 2] (list) len=2' diff --git a/tests/test_prettier.py b/tests/test_prettier.py index 88b5ec3..cdabcd6 100644 --- a/tests/test_prettier.py +++ b/tests/test_prettier.py @@ -354,7 +354,8 @@ def test_cimultidict(): def test_os_environ(): v = pformat(os.environ) assert v.startswith('<_Environ({') - assert " 'HOME': '" in v + for key in os.environ: + assert f" '{key}': " in v class Foo: diff --git a/tests/test_utils.py b/tests/test_utils.py index 9c455b0..d7c5681 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -43,8 +43,4 @@ def test_use_highlight_auto_win(monkeypatch): monkeypatch.delenv('TEST_DONT_USE_HIGHLIGHT', raising=False) monkeypatch.setattr(devtools.utils, 'isatty', lambda _=None: True) - monkeypatch.setattr(devtools.utils, 'color_active', False) - assert use_highlight() is False - - monkeypatch.setattr(devtools.utils, 'color_active', True) assert use_highlight() is True diff --git a/tests/utils.py b/tests/utils.py new file mode 100644 index 0000000..d254c63 --- /dev/null +++ b/tests/utils.py @@ -0,0 +1,8 @@ +import re + + +def normalise_output(s): + s = re.sub(r':\d{2,}', ':', s) + s = re.sub(r'(at 0x)\w+', r'\1', s) + s = s.replace('\\', '/') + return s