Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

0.6.3: pyupgrade --py38-plus breaks test suite #57

Open
kloczek opened this issue May 19, 2024 · 3 comments
Open

0.6.3: pyupgrade --py38-plus breaks test suite #57

kloczek opened this issue May 19, 2024 · 3 comments

Comments

@kloczek
Copy link

kloczek commented May 19, 2024

I've been trying to prepare PR to drop python<=3.7 (which has been EOSed almost year ago) support by filter all code over pyupgrade --py38-plus and found that generated patch breaks test suite.

Here is pytest output:
+ PYTHONPATH=/home/tkloczko/rpmbuild/BUILDROOT/python-stack-data-0.6.3-8.fc37.x86_64/usr/lib64/python3.10/site-packages:/home/tkloczko/rpmbuild/BUILDROOT/python-stack-data-0.6.3-8.fc37.x86_64/usr/lib/python3.10/site-packages
+ /usr/bin/pytest -ra -m 'not network' --deselect tests/test_core.py::test_pygments_example
============================= test session starts ==============================
platform linux -- Python 3.10.14, pytest-8.1.1, pluggy-1.4.0
rootdir: /home/tkloczko/rpmbuild/BUILD/stack_data-0.6.3
configfile: pyproject.toml
plugins: typeguard-4.2.1
collected 21 items / 1 deselected / 20 selected

tests/test_core.py ....F.........                                        [ 70%]
tests/test_formatter.py F.                                               [ 80%]
tests/test_serializer.py .                                               [ 85%]
tests/test_utils.py ...                                                  [100%]

=================================== FAILURES ===================================
_________________________________ test_pieces __________________________________

    def test_pieces():
        filename = samples_dir / "pieces.py"
        source = Source.for_filename(str(filename))
        pieces = [
            [
                source.lines[i - 1]
                for i in piece
            ]
            for piece in source.pieces
        ]
>       assert pieces == [
            ['import math'],
            ['def foo(x=1, y=2):'],
            ['    """',
             '    a docstring',
             '    """'],
            ['    z = 0'],
            ['    for i in range(5):'],
            ['        z += i * x * math.sin(y)'],
            ['        # comment1',
             '        # comment2'],
            ['        z += math.copysign(',
             '            -1,',
             '            2,',
             '        )'],
            ['    for i in range(',
             '            0,',
             '            6',
             '    ):'],
            ['        try:'],
            ['            str(i)'],
            ['        except:'],
            ['            pass'],
            ['        try:'],
            ['            int(i)'],
            ['        except (ValueError,',
             '                TypeError):'],
            ['            pass'],
            ['        finally:'],
            ['            str("""',
             '            foo',
             '            """)'],
            ['            str(f"""',
             '            {str(str)}',
             '            """)'],
            ['            str(f"""',
             '            foo',
             '            {',
             '                str(',
             '                    str',
             '                )',
             '            }',
             '            bar',
             '            {str(str)}',
             '            baz',
             '            {',
             '                str(',
             '                    str',
             '                )',
             '            }',
             '            spam',
             '            """)'],
            ['def foo2(',
             '        x=1,',
             '        y=2,', '):'],
            ['    while 9:'],
            ['        while (',
             '                9 + 9',
             '        ):'],
            ['            if 1:'],
            ['                pass'],
            ['            elif 2:'],
            ['                pass'],
            ['            elif (',
             '                    3 + 3',
             '            ):'],
            ['                pass'],
            ['            else:'],
            ['                pass'],
            ['class Foo(object):'],
            ['    @property',
             '    def foo(self):'],
            ['        return 3'],
            ['# noinspection PyTrailingSemicolon'],
            ['def semicolons():'],
            ['    if 1:'],
            ['        print(1,',
             '         2); print(3,',
             '              4); print(5,',
             '                   6)'],
            ['        if 2:'],
            ['            print(1,',
             '             2); print(3, 4); print(5,',
             '                       6)'],
            ['            print(1, 2); print(3,',
             '                  4); print(5, 6)'],
            ['            print(1, 2);print(3, 4);print(5, 6)']
        ]
E       assert [['import mat...sin(y)'], ...] == [['import mat...sin(y)'], ...]
E
E         At index 18 diff: ['            """', '            foo', '            """'] != ['            str("""', '            foo', '            """)']
E         Use -v to get more diff

tests/test_core.py:329: AssertionError
_________________________________ test_example _________________________________

capsys = <_pytest.capture.CaptureFixture object at 0x7f067b0d49a0>

    def test_example(capsys):
        from .samples.formatter_example import bar, print_stack1, format_stack1, format_frame, f_string, blank_lines

        @contextmanager
        def check_example(name):
            yield
            stderr = capsys.readouterr().err
            compare_to_file(stderr, name)

        with check_example("variables"):
            try:
                bar()
            except Exception:
                MyFormatter(show_variables=True).print_exception()

        with check_example("pygmented"):
            try:
                bar()
            except Exception:
                MyFormatter(pygmented=True).print_exception()

        with check_example("plain"):
            MyFormatter().set_hook()
            try:
                bar()
            except Exception:
                sys.excepthook(*sys.exc_info())

        with check_example("pygmented_error"):
            h = pygments.highlight
            pygments.highlight = lambda *args, **kwargs: 1/0
            try:
                bar()
            except Exception:
                MyFormatter(pygmented=True).print_exception()
            finally:
                pygments.highlight = h

        with check_example("print_stack"):
            print_stack1(MyFormatter())

        with check_example("format_stack"):
            formatter = MyFormatter()
            formatted = format_stack1(formatter)
            formatter.print_lines(formatted)

        with check_example("format_frame"):
            formatter = BaseFormatter()
            formatted = format_frame(formatter)
            formatter.print_lines(formatted)

        if sys.version_info[:2] < (3, 8):
            f_string_suffix = 'old'
        elif not fstring_positions_work():
            f_string_suffix = '3.8'
        else:
            f_string_suffix = 'new'

        with check_example(f"f_string_{f_string_suffix}"):
            try:
                f_string()
            except Exception:
                MyFormatter().print_exception()

        from .samples.formatter_example import block_right, block_left

>       with check_example(f"block_right_{'old' if sys.version_info[:2] < (3, 8) else 'new'}"):

tests/test_formatter.py:99:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
/usr/lib64/python3.10/contextlib.py:142: in __exit__
    next(self.gen)
tests/test_formatter.py:40: in check_example
    compare_to_file(stderr, name)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

text = 'Traceback (most recent call last):\n File "formatter_example.py", line 65, in block_right\n      64 | def block_right...  68 |              "words")\n                        ^^^^^^^^\nTypeError: object of type \'generator\' has no len()\n'
name = 'block_right_new'

    def compare_to_file(text, name):
        if old_pygments and "pygment" in name:
            return
        filename = os.path.join(
            os.path.dirname(__file__),
            'golden_files',
            name + '.txt',
        )
        if os.environ.get('FIX_STACK_DATA_TESTS'):
            string_to_file(text, filename)
        else:
            expected_output = file_to_string(filename)
>           assert text == expected_output
E           AssertionError

tests/utils.py:26: AssertionError
=========================== short test summary info ============================
FAILED tests/test_core.py::test_pieces - assert [['import mat...sin(y)'], ......
FAILED tests/test_formatter.py::test_example - AssertionError
================== 2 failed, 18 passed, 1 deselected in 2.51s ==================

It would be god to prepare stack_data code ready for pyupgrade and than drop EOSed python version support.
Here is generated by pyupgrade patch

--- a/stack_data/core.py
+++ b/stack_data/core.py
@@ -21,10 +21,10 @@
     frame_and_lineno, iter_stack, collapse_repeated, group_by_key_func,
     cached_property, is_frame, _pygmented_with_ranges, assert_)

-RangeInLine = NamedTuple('RangeInLine',
-                         [('start', int),
-                          ('end', int),
-                          ('data', Any)])
+class RangeInLine(NamedTuple):
+    start: int
+    end: int
+    data: Any
 RangeInLine.__doc__ = """
 Represents a range of characters within one line of source code,
 and some associated data.
@@ -32,10 +32,10 @@
 Typically this will be converted to a pair of markers by markers_from_ranges.
 """

-MarkerInLine = NamedTuple('MarkerInLine',
-                          [('position', int),
-                           ('is_start', bool),
-                           ('string', str)])
+class MarkerInLine(NamedTuple):
+    position: int
+    is_start: bool
+    string: str
 MarkerInLine.__doc__ = """
 A string that is meant to be inserted at a given position in a line of source code.
 For example, this could be an ANSI code or the opening or closing of an HTML tag.
@@ -209,11 +209,11 @@

     def __repr__(self):
         keys = sorted(self.__dict__)
-        items = ("{}={!r}".format(k, self.__dict__[k]) for k in keys)
+        items = (f"{k}={self.__dict__[k]!r}" for k in keys)
         return "{}({})".format(type(self).__name__, ", ".join(items))


-class LineGap(object):
+class LineGap:
     """
     A singleton representing one or more lines of source code that were skipped
     in FrameInfo.lines.
@@ -240,7 +240,7 @@
         self.end_lineno = end_lineno


-class Line(object):
+class Line:
     """
     A single line of source code for a particular stack frame.

@@ -508,7 +508,7 @@
         return '<{self.__class__.__name__} {self.description}>'.format(self=self)


-class FrameInfo(object):
+class FrameInfo:
     """
     Information about a frame!
     Pass either a frame object or a traceback object,
--- a/stack_data/formatting.py
+++ b/stack_data/formatting.py
@@ -214,7 +214,7 @@
             result = "   "
         if blank_line.begin_lineno == blank_line.end_lineno:
             return result + self.line_number_format_string.format(blank_line.begin_lineno) + "\n"
-        return result + "   {}\n".format(self.line_number_gap_string)
+        return result + f"   {self.line_number_gap_string}\n"


     def format_variables(self, frame_info: FrameInfo) -> Iterable[str]:
--- a/stack_data/utils.py
+++ b/stack_data/utils.py
@@ -126,7 +126,7 @@
     return result


-class cached_property(object):
+class cached_property:
     """
     A property that is only computed once per instance and then replaces itself
     with an ordinary attribute. Deleting the attribute resets the property.
--- a/tests/samples/pieces.py
+++ b/tests/samples/pieces.py
@@ -30,9 +30,9 @@
                 TypeError):
             pass
         finally:
-            str("""
+            """
             foo
-            """)
+            """
             str(f"""
             {str(str)}
             """)
@@ -75,7 +75,7 @@
                 pass


-class Foo(object):
+class Foo:
     @property
     def foo(self):
         return 3
--- a/tests/samples/pygments_example.py
+++ b/tests/samples/pygments_example.py
@@ -36,7 +36,7 @@
         HtmlFormatter,
     ]:
         for style in ["native", style_with_executing_node("native", "bg:#444400")]:
-            result += "{formatter_cls.__name__} {style}:\n\n".format(**locals())
+            result += f"{formatter_cls.__name__} {style}:\n\n"
             formatter = formatter_cls(style=style)
             options = Options(pygments_formatter=formatter)
             frame = inspect.currentframe().f_back
--- a/tests/test_core.py
+++ b/tests/test_core.py
@@ -28,7 +28,7 @@
     def gather_lines():
         frame = inspect.currentframe().f_back
         frame_info = FrameInfo(frame, options)
-        assert repr(frame_info) == "FrameInfo({})".format(frame)
+        assert repr(frame_info) == f"FrameInfo({frame})"
         lines[:] = [
             line.render(strip_leading_indent=dedented)
             if isinstance(line, Line) else line
@@ -174,7 +174,7 @@
            '    [[line]] = [[only]]([[FrameInfo]]([[inspect]].[[currentframe]](), [[options]]).[[lines]])'

     def convert_variable_range(r):
-        return '[[', ' of type {}]]'.format(r.data[0].value.__class__.__name__)
+        return '[[', f' of type {r.data[0].value.__class__.__name__}]]'

     markers = markers_from_ranges(line.variable_ranges, convert_variable_range)
     assert sorted(markers) == [
@alexmojaki
Copy link
Owner

If you don't change any code inside tests/samples the tests will probably pass.

@kloczek
Copy link
Author

kloczek commented May 20, 2024

OK will try.
What is the propose of those sample files? 🤔

@alexmojaki
Copy link
Owner

Some tests use the sample files as input.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants