"""Tests for the Google-style docstring parser used by ``@tool``.""" from __future__ import annotations import pytest from omnigent_client.tools._docstring import ParsedDocstring, parse_google_docstring def test_parse_empty_docstring() -> None: """Empty input returns empty description and no params.""" result = parse_google_docstring("") assert result == ParsedDocstring(description="", param_descriptions={}) def test_parse_none_docstring_via_empty_string() -> None: """The parser is documented to treat falsy doc as empty.""" result = parse_google_docstring("") assert result.description == "" assert result.param_descriptions == {} def test_parse_description_only() -> None: """A docstring with no Args section returns just the description.""" doc = """ Calculate the sum of two numbers. Multi-paragraph descriptions are joined with double newlines. """ result = parse_google_docstring(doc) assert result.description == ( "Calculate the sum of two numbers.\n\n" "Multi-paragraph descriptions are joined with double newlines." ) assert result.param_descriptions == {} def test_parse_google_args_section() -> None: """Args section produces per-param descriptions.""" doc = """ Compute something. Args: x: The first operand. y: The second operand. """ result = parse_google_docstring(doc) assert result.description == "Compute something." assert result.param_descriptions == { "x": "The first operand.", "y": "The second operand.", } def test_parse_arguments_synonym() -> None: """``Arguments:`` is treated identically to ``Args:``.""" doc = """ Top desc. Arguments: a: Description of a. """ result = parse_google_docstring(doc) assert result.param_descriptions == {"a": "Description of a."} def test_parse_parameters_synonym() -> None: """``Parameters:`` is treated identically to ``Args:``.""" doc = """ Top. Parameters: z: zee. """ result = parse_google_docstring(doc) assert result.param_descriptions == {"z": "zee."} def test_parse_google_args_with_types_in_parens() -> None: """The ``(type)`` part of param entries is stripped from the name.""" doc = """ Compute. Args: x (int): An integer parameter. y (str | None): A nullable string. """ result = parse_google_docstring(doc) # The descriptions should include only the right-of-colon text. # The name is extracted from the part before the parens. assert result.param_descriptions == { "x": "An integer parameter.", "y": "A nullable string.", } def test_parse_google_args_multi_line_descriptions() -> None: """Continuation lines join the previous param's description.""" doc = """ Top. Args: x: First line of x's description and second line that continues it. y: Single-line y description. """ result = parse_google_docstring(doc) # Continuation lines join with single space; whitespace collapsed. assert ( result.param_descriptions["x"] == "First line of x's description and second line that continues it." ) assert result.param_descriptions["y"] == "Single-line y description." def test_parse_google_args_followed_by_returns_section() -> None: """Args parsing stops at the next section header.""" doc = """ Compute. Args: x: The input. y: The other input. Returns: The sum of x and y. """ result = parse_google_docstring(doc) # The Returns: section content should NOT leak into the y description. assert result.param_descriptions == { "x": "The input.", "y": "The other input.", } # Description excludes the Returns: section entirely. assert result.description == "Compute." def test_parse_args_section_with_no_entries() -> None: """An empty Args: section produces no param entries.""" doc = """ Top. Args: Returns: nothing. """ result = parse_google_docstring(doc) assert result.param_descriptions == {} @pytest.mark.parametrize( "header", ["Args:", "Arguments:", "Parameters:"], ) def test_parse_recognizes_all_args_headers(header: str) -> None: """All three accepted header forms produce the same parsed param.""" doc = f""" Top. {header} a: A description. """ result = parse_google_docstring(doc) assert result.param_descriptions == {"a": "A description."} def test_parse_skips_malformed_param_lines() -> None: """Lines without a colon or with non-identifier names are ignored.""" doc = """ Top. Args: valid_one: This is valid. not-an-identifier: This isn't valid. also-bad valid_two: This is the second valid one. """ result = parse_google_docstring(doc) # Non-identifier names are skipped; valid ones survive. # Note: "also-bad" has no colon, so it's skipped. assert "valid_one" in result.param_descriptions assert "valid_two" in result.param_descriptions assert result.param_descriptions["valid_one"] == "This is valid." assert result.param_descriptions["valid_two"] == "This is the second valid one." # Confirm the malformed names did NOT appear under any sanitized form. assert "not-an-identifier" not in result.param_descriptions assert "also-bad" not in result.param_descriptions