pappasam / jedi-language-server

A Python language server exclusively for Jedi. If Jedi supports it well, this language server should too.
MIT License
574 stars 44 forks source link

Upgrading docstring-to-markdown to 0.15 breaks tests #307

Closed iFreilicht closed 3 months ago

iFreilicht commented 3 months ago

In nixpkgs, we manage dependencies independently of project's lockfiles. This led to a build failure after upgrading docstring-to-markdown to 0.15, see also https://github.com/NixOS/nixpkgs/issues/298110

I could confirm this issue by cloning this repo, upgrading the dependency with poetry, and running the tests:

$ poetry update
Updating dependencies
Resolving dependencies... (10.7s)

Package operations: 0 installs, 1 update, 0 removals

  • Updating docstring-to-markdown (0.12 -> 0.15)

Writing lock file
$make test
poetry run black --extend-exclude test_data --check --diff jedi_language_server tests
All done! ✨ 🍰 ✨
28 files would be left unchanged.
poetry run docformatter --exclude test_data --check --recursive jedi_language_server tests
poetry run isort --check jedi_language_server tests/lsp_tests tests/lsp_test_client
poetry run mypy jedi_language_server
Success: no issues found in 9 source files
poetry run pylint jedi_language_server tests

-------------------------------------------------------------------
Your code has been rated at 10.00/10 (previous run: 9.98/10, +0.02)

poetry run pytest tests
============================= test session starts ==============================
platform darwin -- Python 3.10.12, pytest-7.4.2, pluggy-1.3.0
rootdir: /Users/feuh/repos/nixpkgs/source
plugins: typeguard-3.0.2, cov-4.1.0
collected 52 items

tests/test_cli.py ..                                                     [  3%]
tests/test_initialization_options.py .                                   [  5%]
tests/lsp_tests/test_completion.py FFFF                                  [ 13%]
tests/lsp_tests/test_definition.py .                                     [ 15%]
tests/lsp_tests/test_diagnostics.py ...                                  [ 21%]
tests/lsp_tests/test_document_symbol.py ..                               [ 25%]
tests/lsp_tests/test_highlighting.py ..............                      [ 51%]
tests/lsp_tests/test_hover.py FFFF.                                      [ 61%]
tests/lsp_tests/test_init_options.py .                                   [ 63%]
tests/lsp_tests/test_refactoring.py ........                             [ 78%]
tests/lsp_tests/test_references.py ........                              [ 94%]
tests/lsp_tests/test_signature.py FF                                     [ 98%]
tests/lsp_tests/test_workspace_symbol.py .                               [100%]

=================================== FAILURES ===================================
Full error details ``` _____________________________ test_lsp_completion ______________________________ def test_lsp_completion() -> None: """Test a simple completion request. Test Data: tests/test_data/completion/completion_test1.py """ with session.LspSession() as ls_session: ls_session.initialize() uri = as_uri(COMPLETION_TEST_ROOT / "completion_test1.py") actual = ls_session.text_document_completion( { "textDocument": {"uri": uri}, "position": {"line": 8, "character": 2}, "context": {"triggerKind": 1}, } ) expected = { "isIncomplete": False, "items": [ { "label": "my_function", "kind": 3, "sortText": "v0", "filterText": "my_function", "insertText": "my_function()$0", "insertTextFormat": 2, } ], } assert_that(actual, is_(expected)) actual = ls_session.completion_item_resolve( { "label": "my_function", "kind": 3, "sortText": "v0", "filterText": "my_function", "insertText": "my_function()$0", "insertTextFormat": 2, } ) expected = { "label": "my_function", "kind": 3, "detail": "def my_function()", "documentation": { "kind": "markdown", "value": "```text\nSimple test function.\n```", }, "sortText": "v0", "filterText": "my_function", "insertText": "my_function()$0", "insertTextFormat": 2, } > assert_that(actual, is_(expected)) E AssertionError: E Expected: <{'label': 'my_function', 'kind': 3, 'detail': 'def my_function()', 'documentation': {'kind': 'markdown', 'value': '```text\nSimple test function.\n```'}, 'sortText': 'v0', 'filterText': 'my_function', 'insertText': 'my_function()$0', 'insertTextFormat': 2}> E but: was <{'label': 'my_function', 'kind': 3, 'detail': 'def my_function()', 'documentation': {'kind': 'markdown', 'value': 'Simple test function.'}, 'sortText': 'v0', 'filterText': 'my_function', 'insertText': 'my_function()$0', 'insertTextFormat': 2}> tests/lsp_tests/test_completion.py:72: AssertionError ----------------------------- Captured stderr call ----------------------------- Trying to convert docstring to markdown: `Simple test function.` Successfully converted docstring to markdown: `Simple test function.` __________________________ test_eager_lsp_completion ___________________________ def test_eager_lsp_completion() -> None: """Test a simple completion request, with eager resolution. Test Data: tests/test_data/completion/completion_test1.py """ with session.LspSession() as ls_session: # Initialize, asking for eager resolution. initialize_params = copy.deepcopy(VSCODE_DEFAULT_INITIALIZE) initialize_params["initializationOptions"] = { "completion": {"resolveEagerly": True} } ls_session.initialize(initialize_params) uri = as_uri(COMPLETION_TEST_ROOT / "completion_test1.py") actual = ls_session.text_document_completion( { "textDocument": {"uri": uri}, "position": {"line": 8, "character": 2}, "context": {"triggerKind": 1}, } ) # pylint: disable=line-too-long expected = { "isIncomplete": False, "items": [ { "label": "my_function", "kind": 3, "detail": "def my_function()", "documentation": { "kind": "markdown", "value": "```text\nSimple test function.\n```", }, "sortText": "v0", "filterText": "my_function", "insertText": "my_function()$0", "insertTextFormat": 2, } ], } > assert_that(actual, is_(expected)) E AssertionError: E Expected: <{'isIncomplete': False, 'items': [{'label': 'my_function', 'kind': 3, 'detail': 'def my_function()', 'documentation': {'kind': 'markdown', 'value': '```text\nSimple test function.\n```'}, 'sortText': 'v0', 'filterText': 'my_function', 'insertText': 'my_function()$0', 'insertTextFormat': 2}]}> E but: was <{'isIncomplete': False, 'items': [{'label': 'my_function', 'kind': 3, 'detail': 'def my_function()', 'documentation': {'kind': 'markdown', 'value': 'Simple test function.'}, 'sortText': 'v0', 'filterText': 'my_function', 'insertText': 'my_function()$0', 'insertTextFormat': 2}]}> tests/lsp_tests/test_completion.py:117: AssertionError ----------------------------- Captured stderr call ----------------------------- Trying to convert docstring to markdown: `Simple test function.` Successfully converted docstring to markdown: `Simple test function.` _______________________ test_lsp_completion_class_method _______________________ def test_lsp_completion_class_method() -> None: """Checks whether completion returns self unnecessarily. References: https://github.com/pappasam/jedi-language-server/issues/121 Note: I resolve eagerly to make test simpler """ with session.LspSession() as ls_session: # Initialize, asking for eager resolution. initialize_params = copy.deepcopy(VSCODE_DEFAULT_INITIALIZE) initialize_params["initializationOptions"] = { "completion": {"resolveEagerly": True} } ls_session.initialize(initialize_params) uri = as_uri(COMPLETION_TEST_ROOT / "completion_test_class_self.py") actual = ls_session.text_document_completion( { "textDocument": {"uri": uri}, "position": {"line": 7, "character": 13}, "context": {"triggerKind": 1}, } ) # pylint: disable=line-too-long expected = { "isIncomplete": False, "items": [ { "label": "some_method", "kind": 3, "detail": "def some_method(x)", "documentation": { "kind": "markdown", "value": "```text\nGreat method.\n```", }, "sortText": "v0", "filterText": "some_method", "insertText": "some_method(${1:x})$0", "insertTextFormat": 2, } ], } > assert_that(actual, is_(expected)) E AssertionError: E Expected: <{'isIncomplete': False, 'items': [{'label': 'some_method', 'kind': 3, 'detail': 'def some_method(x)', 'documentation': {'kind': 'markdown', 'value': '```text\nGreat method.\n```'}, 'sortText': 'v0', 'filterText': 'some_method', 'insertText': 'some_method(${1:x})$0', 'insertTextFormat': 2}]}> E but: was <{'isIncomplete': False, 'items': [{'label': 'some_method', 'kind': 3, 'detail': 'def some_method(x)', 'documentation': {'kind': 'markdown', 'value': 'Great method.'}, 'sortText': 'v0', 'filterText': 'some_method', 'insertText': 'some_method(${1:x})$0', 'insertTextFormat': 2}]}> tests/lsp_tests/test_completion.py:163: AssertionError ----------------------------- Captured stderr call ----------------------------- Trying to convert docstring to markdown: `Great method.` Successfully converted docstring to markdown: `Great method.` _______________________ test_lsp_completion_class_noargs _______________________ def test_lsp_completion_class_noargs() -> None: """Checks if classes without arguments include parenthesis in signature.""" with session.LspSession() as ls_session: # Initialize, asking for eager resolution. initialize_params = copy.deepcopy(VSCODE_DEFAULT_INITIALIZE) initialize_params["initializationOptions"] = { "completion": {"resolveEagerly": True} } ls_session.initialize(initialize_params) uri = as_uri(COMPLETION_TEST_ROOT / "completion_test2.py") actual = ls_session.text_document_completion( { "textDocument": {"uri": uri}, "position": {"line": 7, "character": 3}, "context": {"triggerKind": 1}, } ) expected = { "isIncomplete": False, "items": [ { "label": "MyClass", "kind": 7, "detail": "class MyClass()", "documentation": { "kind": "markdown", "value": "```text\nSimple class.\n```", }, "sortText": "v0", "filterText": "MyClass", "insertText": "MyClass()$0", "insertTextFormat": 2, } ], } > assert_that(actual, is_(expected)) E AssertionError: E Expected: <{'isIncomplete': False, 'items': [{'label': 'MyClass', 'kind': 7, 'detail': 'class MyClass()', 'documentation': {'kind': 'markdown', 'value': '```text\nSimple class.\n```'}, 'sortText': 'v0', 'filterText': 'MyClass', 'insertText': 'MyClass()$0', 'insertTextFormat': 2}]}> E but: was <{'isIncomplete': False, 'items': [{'label': 'MyClass', 'kind': 7, 'detail': 'class MyClass()', 'documentation': {'kind': 'markdown', 'value': 'Simple class.'}, 'sortText': 'v0', 'filterText': 'MyClass', 'insertText': 'MyClass()$0', 'insertTextFormat': 2}]}> tests/lsp_tests/test_completion.py:203: AssertionError ----------------------------- Captured stderr call ----------------------------- Trying to convert docstring to markdown: `Simple class.` Successfully converted docstring to markdown: `Simple class.` _____________________________ test_hover_on_module _____________________________ def test_hover_on_module(): """Tests hover on the name of a imported module. Test Data: tests/test_data/hover/hover_test1.py """ with session.LspSession() as ls_session: ls_session.initialize() uri = as_uri(HOVER_TEST_ROOT / "hover_test1.py") actual = ls_session.text_document_hover( { "textDocument": {"uri": uri}, "position": {"line": 2, "character": 12}, } ) expected = { "contents": { "kind": "markdown", "value": "```python\nmodule somemodule\n```\n---\n```text\nModule doc string for testing.\n```", }, "range": { "start": {"line": 2, "character": 7}, "end": {"line": 2, "character": 17}, }, } > assert_that(actual, is_(expected)) E AssertionError: E Expected: <{'contents': {'kind': 'markdown', 'value': '```python\nmodule somemodule\n```\n---\n```text\nModule doc string for testing.\n```'}, 'range': {'start': {'line': 2, 'character': 7}, 'end': {'line': 2, 'character': 17}}}> E but: was <{'contents': {'kind': 'markdown', 'value': '```python\nmodule somemodule\n```\n---\nModule doc string for testing.'}, 'range': {'start': {'line': 2, 'character': 7}, 'end': {'line': 2, 'character': 17}}}> tests/lsp_tests/test_hover.py:39: AssertionError ----------------------------- Captured stderr call ----------------------------- Trying to convert docstring to markdown: `Module doc string for testing.` Successfully converted docstring to markdown: `Module doc string for testing.` ____________________________ test_hover_on_function ____________________________ def test_hover_on_function(): """Tests hover on the name of a function. Test Data: tests/test_data/hover/hover_test1.py """ with session.LspSession() as ls_session: ls_session.initialize() uri = as_uri(HOVER_TEST_ROOT / "hover_test1.py") actual = ls_session.text_document_hover( { "textDocument": {"uri": uri}, "position": {"line": 4, "character": 19}, } ) expected = { "contents": { "kind": "markdown", "value": "```python\ndef do_something()\n```\n---\n```text\nFunction doc string for testing.\n```\n**Full name:** `somemodule.do_something`", }, "range": { "start": {"line": 4, "character": 11}, "end": {"line": 4, "character": 23}, }, } > assert_that(actual, is_(expected)) E AssertionError: E Expected: <{'contents': {'kind': 'markdown', 'value': '```python\ndef do_something()\n```\n---\n```text\nFunction doc string for testing.\n```\n**Full name:** `somemodule.do_something`'}, 'range': {'start': {'line': 4, 'character': 11}, 'end': {'line': 4, 'character': 23}}}> E but: was <{'contents': {'kind': 'markdown', 'value': '```python\ndef do_something()\n```\n---\nFunction doc string for testing.\n**Full name:** `somemodule.do_something`'}, 'range': {'start': {'line': 4, 'character': 11}, 'end': {'line': 4, 'character': 23}}}> tests/lsp_tests/test_hover.py:67: AssertionError ----------------------------- Captured stderr call ----------------------------- Trying to convert docstring to markdown: `Function doc string for testing.` Successfully converted docstring to markdown: `Function doc string for testing.` _____________________________ test_hover_on_class ______________________________ def test_hover_on_class(): """Tests hover on the name of a class. Test Data: tests/test_data/hover/hover_test1.py """ with session.LspSession() as ls_session: ls_session.initialize() uri = as_uri(HOVER_TEST_ROOT / "hover_test1.py") actual = ls_session.text_document_hover( { "textDocument": {"uri": uri}, "position": {"line": 6, "character": 21}, } ) expected = { "contents": { "kind": "markdown", "value": "```python\nclass SomeClass()\n```\n---\n```text\nClass doc string for testing.\n```\n**Full name:** `somemodule.SomeClass`", }, "range": { "start": {"line": 6, "character": 15}, "end": {"line": 6, "character": 24}, }, } > assert_that(actual, is_(expected)) E AssertionError: E Expected: <{'contents': {'kind': 'markdown', 'value': '```python\nclass SomeClass()\n```\n---\n```text\nClass doc string for testing.\n```\n**Full name:** `somemodule.SomeClass`'}, 'range': {'start': {'line': 6, 'character': 15}, 'end': {'line': 6, 'character': 24}}}> E but: was <{'contents': {'kind': 'markdown', 'value': '```python\nclass SomeClass()\n```\n---\nClass doc string for testing.\n**Full name:** `somemodule.SomeClass`'}, 'range': {'start': {'line': 6, 'character': 15}, 'end': {'line': 6, 'character': 24}}}> tests/lsp_tests/test_hover.py:95: AssertionError ----------------------------- Captured stderr call ----------------------------- Trying to convert docstring to markdown: `Class doc string for testing.` Successfully converted docstring to markdown: `Class doc string for testing.` _____________________________ test_hover_on_method _____________________________ def test_hover_on_method(): """Tests hover on the name of a class method. Test Data: tests/test_data/hover/hover_test1.py """ with session.LspSession() as ls_session: ls_session.initialize() uri = as_uri(HOVER_TEST_ROOT / "hover_test1.py") actual = ls_session.text_document_hover( { "textDocument": {"uri": uri}, "position": {"line": 8, "character": 6}, } ) expected = { "contents": { "kind": "markdown", "value": "```python\ndef some_method()\n```\n---\n```text\nMethod doc string for testing.\n```\n**Full name:** `somemodule.SomeClass.some_method`", }, "range": { "start": {"line": 8, "character": 2}, "end": {"line": 8, "character": 13}, }, } > assert_that(actual, is_(expected)) E AssertionError: E Expected: <{'contents': {'kind': 'markdown', 'value': '```python\ndef some_method()\n```\n---\n```text\nMethod doc string for testing.\n```\n**Full name:** `somemodule.SomeClass.some_method`'}, 'range': {'start': {'line': 8, 'character': 2}, 'end': {'line': 8, 'character': 13}}}> E but: was <{'contents': {'kind': 'markdown', 'value': '```python\ndef some_method()\n```\n---\nMethod doc string for testing.\n**Full name:** `somemodule.SomeClass.some_method`'}, 'range': {'start': {'line': 8, 'character': 2}, 'end': {'line': 8, 'character': 13}}}> tests/lsp_tests/test_hover.py:123: AssertionError ----------------------------- Captured stderr call ----------------------------- Trying to convert docstring to markdown: `Method doc string for testing.` Successfully converted docstring to markdown: `Method doc string for testing.` _________________________ test_signature_help[(-14-0] __________________________ trigger_char = '(', column = 14, active_param = 0 @pytest.mark.parametrize( ["trigger_char", "column", "active_param"], [("(", 14, 0), (",", 18, 1)] ) def test_signature_help(trigger_char, column, active_param): """Tests signature help response for a function. Test Data: tests/test_data/signature/signature_test1.py """ with session.LspSession() as ls_session: ls_session.initialize() uri = as_uri(SIGNATURE_TEST_ROOT / "signature_test1.py") actual = ls_session.text_document_signature_help( { "textDocument": {"uri": uri}, "position": {"line": 7, "character": column}, "context": { "isRetrigger": False, "triggerCharacter": trigger_char, "triggerKind": 2, }, } ) expected = { "signatures": [ { "label": ( "def some_function(arg1: str, arg2: int, arg3: list)" ), "documentation": { "kind": "markdown", "value": "```text\nThis is a test function.\n```", }, "parameters": [ {"label": "arg1: str"}, {"label": "arg2: int"}, {"label": "arg3: list"}, ], } ], "activeSignature": 0, "activeParameter": active_param, } > assert_that(actual, is_(expected)) E AssertionError: E Expected: <{'signatures': [{'label': 'def some_function(arg1: str, arg2: int, arg3: list)', 'documentation': {'kind': 'markdown', 'value': '```text\nThis is a test function.\n```'}, 'parameters': [{'label': 'arg1: str'}, {'label': 'arg2: int'}, {'label': 'arg3: list'}]}], 'activeSignature': 0, 'activeParameter': 0}> E but: was <{'signatures': [{'label': 'def some_function(arg1: str, arg2: int, arg3: list)', 'documentation': {'kind': 'markdown', 'value': 'This is a test function.'}, 'parameters': [{'label': 'arg1: str'}, {'label': 'arg2: int'}, {'label': 'arg3: list'}]}], 'activeSignature': 0, 'activeParameter': 0}> tests/lsp_tests/test_signature.py:57: AssertionError ----------------------------- Captured stderr call ----------------------------- Trying to convert docstring to markdown: `This is a test function.` Successfully converted docstring to markdown: `This is a test function.` _________________________ test_signature_help[,-18-1] __________________________ trigger_char = ',', column = 18, active_param = 1 @pytest.mark.parametrize( ["trigger_char", "column", "active_param"], [("(", 14, 0), (",", 18, 1)] ) def test_signature_help(trigger_char, column, active_param): """Tests signature help response for a function. Test Data: tests/test_data/signature/signature_test1.py """ with session.LspSession() as ls_session: ls_session.initialize() uri = as_uri(SIGNATURE_TEST_ROOT / "signature_test1.py") actual = ls_session.text_document_signature_help( { "textDocument": {"uri": uri}, "position": {"line": 7, "character": column}, "context": { "isRetrigger": False, "triggerCharacter": trigger_char, "triggerKind": 2, }, } ) expected = { "signatures": [ { "label": ( "def some_function(arg1: str, arg2: int, arg3: list)" ), "documentation": { "kind": "markdown", "value": "```text\nThis is a test function.\n```", }, "parameters": [ {"label": "arg1: str"}, {"label": "arg2: int"}, {"label": "arg3: list"}, ], } ], "activeSignature": 0, "activeParameter": active_param, } > assert_that(actual, is_(expected)) E AssertionError: E Expected: <{'signatures': [{'label': 'def some_function(arg1: str, arg2: int, arg3: list)', 'documentation': {'kind': 'markdown', 'value': '```text\nThis is a test function.\n```'}, 'parameters': [{'label': 'arg1: str'}, {'label': 'arg2: int'}, {'label': 'arg3: list'}]}], 'activeSignature': 0, 'activeParameter': 1}> E but: was <{'signatures': [{'label': 'def some_function(arg1: str, arg2: int, arg3: list)', 'documentation': {'kind': 'markdown', 'value': 'This is a test function.'}, 'parameters': [{'label': 'arg1: str'}, {'label': 'arg2: int'}, {'label': 'arg3: list'}]}], 'activeSignature': 0, 'activeParameter': 1}> tests/lsp_tests/test_signature.py:57: AssertionError ----------------------------- Captured stderr call ----------------------------- Trying to convert docstring to markdown: `This is a test function.` Successfully converted docstring to markdown: `This is a test function.` =========================== short test summary info ============================ FAILED tests/lsp_tests/test_completion.py::test_lsp_completion - AssertionErr... FAILED tests/lsp_tests/test_completion.py::test_eager_lsp_completion - Assert... FAILED tests/lsp_tests/test_completion.py::test_lsp_completion_class_method FAILED tests/lsp_tests/test_completion.py::test_lsp_completion_class_noargs FAILED tests/lsp_tests/test_hover.py::test_hover_on_module - AssertionError: FAILED tests/lsp_tests/test_hover.py::test_hover_on_function - AssertionError: FAILED tests/lsp_tests/test_hover.py::test_hover_on_class - AssertionError: FAILED tests/lsp_tests/test_hover.py::test_hover_on_method - AssertionError: FAILED tests/lsp_tests/test_signature.py::test_signature_help[(-14-0] - Asser... FAILED tests/lsp_tests/test_signature.py::test_signature_help[,-18-1] - Asser... ======================== 10 failed, 42 passed in 25.66s ======================== make: *** [test] Error 1 ```

I already tracked it down to convert_docstring(), specifically:

        try:
            return docstring_to_markdown.convert(docstring_stripped).strip()
        except docstring_to_markdown.UnknownFormatError:
            return _md_text(docstring_stripped, markup_kind)

The try always fails with docstring-to-markdown 0.13, but succeeds with 0.15, meaning the output isn't wrapped anymore like this:

"```text\nThis is a test function.\n```"

But gets returned verbatim:

"This is a test function."

Fixing the tests is easy, but I am wondering if this change in behavior is desired, or whether the code should be changed to keep the same behavior with the new version.

Looking at the changes in docstring-to-markdown, I found that the changes in 0.14 likely caused this issue, especially allowing plain text. Propagating that change might be considered breaking, so a better fix might be to call looks_like_plain_text from that package and use _md_text if it is true.

iFreilicht commented 3 months ago

This was already fixed in #305, I just missed that nixpkgs already updated to the latest version in https://github.com/NixOS/nixpkgs/pull/297795.

Sorry for disturbing you.