mirror of
https://github.com/tiennm99/serena.git
synced 2026-08-06 00:23:41 +00:00
Improvements and renamings of symbol name_path matching
This commit is contained in:
+49
-56
@@ -10,11 +10,11 @@ import sys
|
||||
import traceback
|
||||
from abc import ABC
|
||||
from collections import defaultdict
|
||||
from collections.abc import Callable, Generator, Iterable
|
||||
from collections.abc import Callable, Generator, Iterable, Sequence
|
||||
from dataclasses import dataclass, field
|
||||
from logging import Logger
|
||||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING, Any, Self, TypeVar, Union, cast
|
||||
from typing import TYPE_CHECKING, Any, Self, TypeVar, Union
|
||||
|
||||
import yaml
|
||||
from sensai.util import logging
|
||||
@@ -778,7 +778,7 @@ class FindSymbolTool(Tool):
|
||||
|
||||
def apply(
|
||||
self,
|
||||
name: str,
|
||||
name_path: str,
|
||||
depth: int = 0,
|
||||
within_relative_path: str | None = None,
|
||||
include_body: bool = False,
|
||||
@@ -788,63 +788,56 @@ class FindSymbolTool(Tool):
|
||||
max_answer_chars: int = _DEFAULT_MAX_ANSWER_LENGTH,
|
||||
) -> str:
|
||||
"""
|
||||
Retrieves information on all symbols/code entities, i.e. classes, methods, attributes, variables, etc.
|
||||
with the given name.
|
||||
The returned symbol location information can subsequently be used to edit the returned symbols
|
||||
or to retrieve further information using other tools.
|
||||
If you already anticipate that you will need to reference children of the symbol (like methods or fields contained in a class),
|
||||
you can specify a depth > 0.
|
||||
Retrieves information on all symbols/code entities (classes, methods, etc.) based on the given `name_path`,
|
||||
which represents a pattern for the symbol's path within the symbol tree of a single file.
|
||||
The returned symbol location can be used for edits or further queries.
|
||||
Specify `depth > 0` to retrieve children (e.g., methods of a class).
|
||||
|
||||
The name matching behavior depends on whether a qualified name or a simple name is provided.
|
||||
It is assumed that the provided name is a qualified name if it contains the `/` character.
|
||||
If substring matching is allowed, only the last element of the qualified name will be checked against
|
||||
the symbol name using substring matching.
|
||||
The matching behavior is determined by the structure of `name_path`, which can
|
||||
either be a simple name (e.g. "method") or a name path like "class/method" (relative name path)
|
||||
or "/class/method" (absolute name path). Note that the name path is not a path in the file system
|
||||
but rather a path in the symbol tree **within a single file**. Thus, file or directory names should never
|
||||
be included in the `name_path`. For restricting the search to a single file or directory,
|
||||
the `within_relative_path` parameter should be used instead. The retrieved symbols' `name_path` attribute
|
||||
will always be composed of symbol names, never file or directory names.
|
||||
|
||||
Examples:
|
||||
- Providing "foo" will find all symbols named "foo" regardless where they are contained in the symbol tree.
|
||||
- Providing "bar/foo" will only find symbols named "foo" that are direct children of a symbol called "bar".
|
||||
- Providing "foo/" will only find symbols named "foo" that are top-level symbols (have no parent).
|
||||
- Allowing substring matching with "bar" will find symbols with names containing "foo" anywhere in the symbol tree.
|
||||
- Allowing substring matching with "foo/" will find only top-level symbols with names containing "foo".
|
||||
- Allowing substring matching with "bar/foo" will find only symbols with names containing "foo" that are direct children of a symbol named "bar".
|
||||
Key aspects of the name path matching behavior:
|
||||
- Trailing slashes in `name_path` play no role and are ignored.
|
||||
- The name of the retrieved symbols will match (either exactly or as a substring)
|
||||
the last segment of `name_path`, while other segments will restrict the search to symbols that
|
||||
have a desired sequence of ancestors.
|
||||
- If there is no starting or intermediate slash in `name_path`, there is no
|
||||
restriction on the ancestor symbols. For example, passing `method` will match
|
||||
against symbols with name paths like `method`, `class/method`, `class/nested_class/method`, etc.
|
||||
- If `name_path` contains a `/` but doesn't start with a `/`, the matching is restricted to symbols
|
||||
with the same ancestors as the last segment of `name_path`. For example, passing `class/method` will match against
|
||||
`class/method` as well as `nested_class/class/method` but not `method`.
|
||||
- If `name_path` starts with a `/`, it will be treated as an absolute name path pattern, meaning
|
||||
that the first segment of it must match the first segment of the symbol's name path.
|
||||
For example, passing `/class` will match only against top-level symbols like `class` but not against `nested_class/class`.
|
||||
Passing `/class/method` will match against `class/method` but not `nested_class/class/method` or `method`.
|
||||
|
||||
:param name: the name of the symbols to find. A "qualified" name that includes the symbol's parents
|
||||
separated by `/` (e.g. "class/method/inner_function") can be used to restrict the search.
|
||||
:param depth: specifies the depth up to which descendants of the symbol are to be retrieved
|
||||
(e.g. depth 1 will retrieve methods and attributes for the case where the symbol refers to a class).
|
||||
Provide a non-zero depth if you intend to subsequently query symbols that are contained in the
|
||||
retrieved symbol.
|
||||
:param within_relative_path: pass a relative path to only consider symbols within this path.
|
||||
If a file is passed, only the symbols within this file will be considered.
|
||||
If a directory is passed, all files within this directory will be considered.
|
||||
If None, the entire codebase will be considered.
|
||||
:param include_body: whether to include the body of all symbols in the result. You should only use this
|
||||
if you actually need the body of the symbol for the task at hand (for example, for a deep analysis
|
||||
of the functionality or for an editing task).
|
||||
:param include_kinds: an optional list of ints representing the LSP symbol kind.
|
||||
If provided, only symbols of the given kinds will be included in the result.
|
||||
Valid kinds:
|
||||
1=file, 2=module, 3=namespace, 4=package, 5=class, 6=method, 7=property, 8=field, 9=constructor, 10=enum,
|
||||
|
||||
:param name_path: The name path pattern to search for, see above for details.
|
||||
:param depth: Depth to retrieve descendants (e.g., 1 for class methods/attributes).
|
||||
:param within_relative_path: Optional. Restrict search to this file or directory. If None, searches entire codebase.
|
||||
:param include_body: If True, include the symbol's source code. Use judiciously.
|
||||
:param include_kinds: Optional. List of LSP symbol kind integers to include. (e.g., 5 for Class, 12 for Function).
|
||||
Valid kinds: 1=file, 2=module, 3=namespace, 4=package, 5=class, 6=method, 7=property, 8=field, 9=constructor, 10=enum,
|
||||
11=interface, 12=function, 13=variable, 14=constant, 15=string, 16=number, 17=boolean, 18=array, 19=object,
|
||||
20=key, 21=null, 22=enum member, 23=struct, 24=event, 25=operator, 26=type parameter
|
||||
:param exclude_kinds: If provided, symbols of the given kinds will be excluded from the result.
|
||||
Takes precedence over include_kinds.
|
||||
:param substring_matching: whether to use substring matching for the symbol name.
|
||||
If True, the symbol name will be matched if it contains the given name as a substring.
|
||||
:param max_answer_chars: if the output is longer than this number of characters,
|
||||
no content will be returned. Don't adjust unless there is really no other way to get the content
|
||||
required for the task. Instead, if the output is too long, you should
|
||||
make a stricter query.
|
||||
:return: a list of symbols (with symbol locations) that match the given name in JSON format
|
||||
|
||||
:param exclude_kinds: Optional. List of LSP symbol kind integers to exclude. Takes precedence over `include_kinds`.
|
||||
:param substring_matching: If True, use substring matching for the last segment of `name`.
|
||||
:param max_answer_chars: Max characters for the JSON result. If exceeded, no content is returned.
|
||||
:return: JSON string: a list of symbols (with locations) matching the name.
|
||||
"""
|
||||
include_kinds = cast(list[SymbolKind] | None, include_kinds)
|
||||
exclude_kinds = cast(list[SymbolKind] | None, exclude_kinds)
|
||||
parsed_include_kinds: Sequence[SymbolKind] | None = [SymbolKind(k) for k in include_kinds] if include_kinds else None
|
||||
parsed_exclude_kinds: Sequence[SymbolKind] | None = [SymbolKind(k) for k in exclude_kinds] if exclude_kinds else None
|
||||
symbols = self.symbol_manager.find_by_name(
|
||||
name,
|
||||
name_path,
|
||||
include_body=include_body,
|
||||
include_kinds=include_kinds,
|
||||
exclude_kinds=exclude_kinds,
|
||||
include_kinds=parsed_include_kinds,
|
||||
exclude_kinds=parsed_exclude_kinds,
|
||||
substring_matching=substring_matching,
|
||||
within_relative_path=within_relative_path,
|
||||
)
|
||||
@@ -895,13 +888,13 @@ class FindReferencingSymbolsTool(Tool):
|
||||
make a stricter query.
|
||||
:return: a list of JSON objects with the symbols referencing the requested symbol
|
||||
"""
|
||||
include_kinds = cast(list[SymbolKind] | None, include_kinds)
|
||||
exclude_kinds = cast(list[SymbolKind] | None, exclude_kinds)
|
||||
parsed_include_kinds: Sequence[SymbolKind] | None = [SymbolKind(k) for k in include_kinds] if include_kinds else None
|
||||
parsed_exclude_kinds: Sequence[SymbolKind] | None = [SymbolKind(k) for k in exclude_kinds] if exclude_kinds else None
|
||||
symbols = self.symbol_manager.find_referencing_symbols(
|
||||
SymbolLocation(relative_path, line, column),
|
||||
include_body=include_body,
|
||||
include_kinds=include_kinds,
|
||||
exclude_kinds=exclude_kinds,
|
||||
include_kinds=parsed_include_kinds,
|
||||
exclude_kinds=parsed_exclude_kinds,
|
||||
)
|
||||
symbol_dicts = [s.to_dict(kind=True, location=True, depth=0, include_body=include_body) for s in symbols]
|
||||
result = json.dumps(symbol_dicts)
|
||||
|
||||
+496
-508
File diff suppressed because it is too large
Load Diff
@@ -89,7 +89,7 @@ class TestSerenaAgent:
|
||||
), f"Expected to find reference to {symbol_name} in {ref_file} for {agent.project_config.language.name}. refs={refs}"
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"serena_agent,qualified_name,substring_matching,expected_symbol_name,expected_kind,expected_file",
|
||||
"serena_agent,name_path,substring_matching,expected_symbol_name,expected_kind,expected_file",
|
||||
[
|
||||
pytest.param(
|
||||
Language.PYTHON,
|
||||
@@ -131,13 +131,33 @@ class TestSerenaAgent:
|
||||
id="substring_qualname_method",
|
||||
marks=pytest.mark.python,
|
||||
),
|
||||
pytest.param(
|
||||
Language.PYTHON,
|
||||
"/OuterClass", # Absolute path
|
||||
False,
|
||||
"OuterClass",
|
||||
"Class",
|
||||
os.path.join("test_repo", "nested.py"),
|
||||
id="absolute_qualname_class",
|
||||
marks=pytest.mark.python,
|
||||
),
|
||||
pytest.param(
|
||||
Language.PYTHON,
|
||||
"/OuterClass/NestedClass/find_m", # Absolute path with substring
|
||||
True,
|
||||
"find_me",
|
||||
"Method",
|
||||
os.path.join("test_repo", "nested.py"),
|
||||
id="absolute_substring_qualname_method",
|
||||
marks=pytest.mark.python,
|
||||
),
|
||||
],
|
||||
indirect=["serena_agent"],
|
||||
)
|
||||
def test_find_symbol_qualified_name(
|
||||
def test_find_symbol_name_path(
|
||||
self,
|
||||
serena_agent: SerenaAgent,
|
||||
qualified_name: str,
|
||||
name_path: str,
|
||||
substring_matching: bool,
|
||||
expected_symbol_name: str,
|
||||
expected_kind: str,
|
||||
@@ -146,7 +166,7 @@ class TestSerenaAgent:
|
||||
agent = serena_agent
|
||||
find_symbol_tool = agent.get_tool(FindSymbolTool)
|
||||
result = find_symbol_tool.apply(
|
||||
name=qualified_name,
|
||||
name_path=name_path,
|
||||
depth=0,
|
||||
within_relative_path=None,
|
||||
include_body=False,
|
||||
@@ -160,4 +180,39 @@ class TestSerenaAgent:
|
||||
and expected_kind.lower() in s["kind"].lower()
|
||||
and expected_file in s["location"]["relative_path"]
|
||||
for s in symbols
|
||||
), f"Expected to find {qualified_name} ({expected_kind}) in {expected_file} for {agent.project_config.language.name}. Symbols: {symbols}"
|
||||
), f"Expected to find {name_path} ({expected_kind}) in {expected_file} for {agent.project_config.language.name}. Symbols: {symbols}"
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"serena_agent,name_path",
|
||||
[
|
||||
pytest.param(
|
||||
Language.PYTHON,
|
||||
"/NestedClass", # Absolute path, NestedClass is not top-level
|
||||
id="absolute_path_non_top_level_no_match",
|
||||
marks=pytest.mark.python,
|
||||
),
|
||||
pytest.param(
|
||||
Language.PYTHON,
|
||||
"/NoSuchParent/NestedClass", # Absolute path with non-existent parent
|
||||
id="absolute_path_non_existent_parent_no_match",
|
||||
marks=pytest.mark.python,
|
||||
),
|
||||
],
|
||||
indirect=["serena_agent"],
|
||||
)
|
||||
def test_find_symbol_name_path_no_match(
|
||||
self,
|
||||
serena_agent: SerenaAgent,
|
||||
name_path: str,
|
||||
):
|
||||
agent = serena_agent
|
||||
find_symbol_tool = agent.get_tool(FindSymbolTool)
|
||||
result = find_symbol_tool.apply(
|
||||
name_path=name_path,
|
||||
depth=0,
|
||||
substring_matching=True,
|
||||
)
|
||||
symbols = json.loads(result)
|
||||
assert (
|
||||
not symbols
|
||||
), f"Expected to find no symbols for {name_path} for {agent.project_config.language.name}. Symbols found: {symbols}"
|
||||
|
||||
+113
-84
@@ -6,128 +6,157 @@ from src.serena.symbol import Symbol
|
||||
class TestSymbolNameMatching:
|
||||
def _create_assertion_error_message(
|
||||
self,
|
||||
name_pattern: str,
|
||||
qual_name_parts: list[str],
|
||||
name_path_pattern: str,
|
||||
symbol_name_path_parts: list[str],
|
||||
is_substring_match: bool,
|
||||
expected_result: bool,
|
||||
actual_result: bool,
|
||||
) -> str:
|
||||
"""Helper to create a detailed error message for assertions."""
|
||||
qnp_repr = "/".join(qual_name_parts)
|
||||
qnp_repr = "/".join(symbol_name_path_parts)
|
||||
|
||||
return (
|
||||
f"Pattern '{name_pattern}' (substring: {is_substring_match}) vs "
|
||||
f"Qualname parts {qual_name_parts} (as '{qnp_repr}'). "
|
||||
f"Pattern '{name_path_pattern}' (substring: {is_substring_match}) vs "
|
||||
f"Qualname parts {symbol_name_path_parts} (as '{qnp_repr}'). "
|
||||
f"Expected: {expected_result}, Got: {actual_result}"
|
||||
)
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"name_pattern, qual_name_parts, is_substring_match, expected",
|
||||
"name_path_pattern, symbol_name_path_parts, is_substring_match, expected",
|
||||
[
|
||||
# Exact matches (is_substring_match=False)
|
||||
pytest.param("foo", ["foo"], False, True, id="'foo' matches 'foo' exactly"),
|
||||
pytest.param("foo", ["bar", "foo"], False, True, id="'foo' matches 'bar,foo' exactly"),
|
||||
pytest.param("foo", ["foobar"], False, False, id="'foo' does not match 'foobar' exactly"),
|
||||
pytest.param("foo", ["bar", "foobar"], False, False, id="'foo' does not match 'bar,foobar' exactly"),
|
||||
pytest.param("foo", ["path", "to", "foo"], False, True, id="'foo' matches 'path,to,foo' exactly"),
|
||||
# Substring matches (is_substring_match=True)
|
||||
pytest.param("foo", ["foobar"], True, True, id="'foo' matches 'foobar' as substring"),
|
||||
pytest.param("foo", ["bar", "foobar"], True, True, id="'foo' matches 'bar,foobar' as substring"),
|
||||
pytest.param("foo", ["barfoo"], True, True, id="'foo' matches 'barfoo' as substring"),
|
||||
pytest.param("foo", ["baz"], True, False, id="'foo' does not match 'baz' as substring"),
|
||||
pytest.param("foo", ["bar", "baz"], True, False, id="'foo' does not match 'bar,baz' as substring"),
|
||||
pytest.param("foo", ["my_foobar_func"], True, True, id="'foo' matches 'my_foobar_func' as substring"),
|
||||
pytest.param("foo", ["ClassA", "my_foobar_method"], True, True, id="'foo' matches 'ClassA,my_foobar_method' as substring"),
|
||||
pytest.param("foo", ["my_bar_func"], True, False, id="'foo' does not match 'my_bar_func' as substring"),
|
||||
# Exact matches, anywhere in the name (is_substring_match=False)
|
||||
pytest.param("foo", ["foo"], False, True, id="'foo' matches 'foo' exactly (simple)"),
|
||||
pytest.param("foo/", ["foo"], False, True, id="'foo/' matches 'foo' exactly (simple)"),
|
||||
pytest.param("foo", ["bar", "foo"], False, True, id="'foo' matches ['bar', 'foo'] exactly (simple, last element)"),
|
||||
pytest.param("foo", ["foobar"], False, False, id="'foo' does not match 'foobar' exactly (simple)"),
|
||||
pytest.param(
|
||||
"foo", ["bar", "foobar"], False, False, id="'foo' does not match ['bar', 'foobar'] exactly (simple, last element)"
|
||||
),
|
||||
pytest.param(
|
||||
"foo", ["path", "to", "foo"], False, True, id="'foo' matches ['path', 'to', 'foo'] exactly (simple, last element)"
|
||||
),
|
||||
# Exact matches, absolute patterns (is_substring_match=False)
|
||||
pytest.param("/foo", ["foo"], False, True, id="'/foo' matches ['foo'] exactly (absolute simple)"),
|
||||
pytest.param("/foo", ["foo", "bar"], False, False, id="'/foo' does not match ['foo', 'bar'] (absolute simple, len mismatch)"),
|
||||
pytest.param("/foo", ["bar"], False, False, id="'/foo' does not match ['bar'] (absolute simple, name mismatch)"),
|
||||
pytest.param(
|
||||
"/foo", ["bar", "foo"], False, False, id="'/foo' does not match ['bar', 'foo'] (absolute simple, position mismatch)"
|
||||
),
|
||||
# Substring matches, anywhere in the name (is_substring_match=True)
|
||||
pytest.param("foo", ["foobar"], True, True, id="'foo' matches 'foobar' as substring (simple)"),
|
||||
pytest.param("foo", ["bar", "foobar"], True, True, id="'foo' matches ['bar', 'foobar'] as substring (simple, last element)"),
|
||||
pytest.param(
|
||||
"foo", ["barfoo"], True, True, id="'foo' matches 'barfoo' as substring (simple)"
|
||||
), # This was potentially ambiguous before
|
||||
pytest.param("foo", ["baz"], True, False, id="'foo' does not match 'baz' as substring (simple)"),
|
||||
pytest.param("foo", ["bar", "baz"], True, False, id="'foo' does not match ['bar', 'baz'] as substring (simple, last element)"),
|
||||
pytest.param("foo", ["my_foobar_func"], True, True, id="'foo' matches 'my_foobar_func' as substring (simple)"),
|
||||
pytest.param(
|
||||
"foo",
|
||||
["ClassA", "my_foobar_method"],
|
||||
True,
|
||||
True,
|
||||
id="'foo' matches ['ClassA', 'my_foobar_method'] as substring (simple, last element)",
|
||||
),
|
||||
pytest.param("foo", ["my_bar_func"], True, False, id="'foo' does not match 'my_bar_func' as substring (simple)"),
|
||||
# Substring matches, absolute patterns (is_substring_match=True)
|
||||
pytest.param("/foo", ["foobar"], True, True, id="'/foo' matches ['foobar'] as substring (absolute simple)"),
|
||||
pytest.param("/foo/", ["foobar"], True, True, id="'/foo/' matches ['foobar'] as substring (absolute simple, last element)"),
|
||||
pytest.param("/foo", ["barfoobaz"], True, True, id="'/foo' matches ['barfoobaz'] as substring (absolute simple)"),
|
||||
pytest.param(
|
||||
"/foo", ["foo", "bar"], True, False, id="'/foo' does not match ['foo', 'bar'] as substring (absolute simple, len mismatch)"
|
||||
),
|
||||
pytest.param("/foo", ["bar"], True, False, id="'/foo' does not match ['bar'] (absolute simple, no substr)"),
|
||||
pytest.param(
|
||||
"/foo", ["bar", "foo"], True, False, id="'/foo' does not match ['bar', 'foo'] (absolute simple, position mismatch)"
|
||||
),
|
||||
pytest.param(
|
||||
"/foo/", ["bar", "foo"], True, False, id="'/foo/' does not match ['bar', 'foo'] (absolute simple, position mismatch)"
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_match_simple_name(self, name_pattern, qual_name_parts, is_substring_match, expected):
|
||||
def test_match_simple_name(self, name_path_pattern, symbol_name_path_parts, is_substring_match, expected):
|
||||
"""Tests matching for simple names (no '/' in pattern)."""
|
||||
result = Symbol.match_against_qualname(name_pattern, qual_name_parts, is_substring_match)
|
||||
error_msg = self._create_assertion_error_message(name_pattern, qual_name_parts, is_substring_match, expected, result)
|
||||
result = Symbol.match_name_path(name_path_pattern, symbol_name_path_parts, is_substring_match)
|
||||
error_msg = self._create_assertion_error_message(name_path_pattern, symbol_name_path_parts, is_substring_match, expected, result)
|
||||
assert result == expected, error_msg
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"name_pattern, qual_name_parts, is_substring_match, expected",
|
||||
"name_path_pattern, symbol_name_path_parts, is_substring_match, expected",
|
||||
[
|
||||
# Exact matches (is_substring_match=False)
|
||||
pytest.param("bar/foo", ["bar", "foo"], False, True, id="'bar/foo' matches 'bar,foo' exactly"),
|
||||
# --- Relative patterns (suffix matching) ---
|
||||
# Exact matches, relative patterns (is_substring_match=False)
|
||||
pytest.param("bar/foo", ["bar", "foo"], False, True, id="R: 'bar/foo' matches ['bar', 'foo'] exactly"),
|
||||
pytest.param("bar/foo", ["mod", "bar", "foo"], False, True, id="R: 'bar/foo' matches ['mod', 'bar', 'foo'] exactly (suffix)"),
|
||||
pytest.param(
|
||||
"bar/foo", ["bar", "foo", "baz"], False, False, id="'bar/foo' does not match 'bar,foo,baz' exactly (len mismatch)"
|
||||
"bar/foo", ["bar", "foo", "baz"], False, False, id="R: 'bar/foo' does not match ['bar', 'foo', 'baz'] (pattern shorter)"
|
||||
),
|
||||
pytest.param("bar/foo", ["bar"], False, False, id="'bar/foo' does not match 'bar' exactly (len mismatch)"),
|
||||
pytest.param("bar/foo", ["baz", "foo"], False, False, id="'bar/foo' does not match 'baz,foo' exactly (first mismatch)"),
|
||||
pytest.param("bar/foo", ["bar", "baz"], False, False, id="'bar/foo' does not match 'bar,baz' exactly (last mismatch)"),
|
||||
# from docstring examples
|
||||
pytest.param("bar/foo", ["foo"], False, False, id="'bar/foo' does not match 'foo' exactly"),
|
||||
pytest.param("bar/foo", ["other", "foo"], False, False, id="'bar/foo' does not match 'other,foo' exactly"),
|
||||
pytest.param("bar/foo", ["bar", "otherfoo"], False, False, id="'bar/foo' does not match 'bar,otherfoo' exactly"),
|
||||
# Substring matches (is_substring_match=True)
|
||||
pytest.param("bar/foo", ["bar", "foobar"], True, True, id="'bar/foo' matches 'bar,foobar' as substring"),
|
||||
pytest.param("bar/foo", ["bar", "bazfoo"], True, True, id="'bar/foo' matches 'bar,bazfoo' as substring"),
|
||||
pytest.param("bar/fo", ["bar", "foo"], True, True, id="'bar/fo' matches 'bar,foo' as substring"),
|
||||
pytest.param("bar/foo", ["bar", "baz"], True, False, id="'bar/foo' does not match 'bar,baz' as substring (last no substr)"),
|
||||
pytest.param("bar/foo", ["bar"], False, False, id="R: 'bar/foo' does not match ['bar'] (pattern longer)"),
|
||||
pytest.param("bar/foo", ["baz", "foo"], False, False, id="R: 'bar/foo' does not match ['baz', 'foo'] (first part mismatch)"),
|
||||
pytest.param("bar/foo", ["bar", "baz"], False, False, id="R: 'bar/foo' does not match ['bar', 'baz'] (last part mismatch)"),
|
||||
pytest.param("bar/foo", ["foo"], False, False, id="R: 'bar/foo' does not match ['foo'] (pattern longer)"),
|
||||
pytest.param(
|
||||
"bar/foo", ["baz", "foobar"], True, False, id="'bar/foo' does not match 'baz,foobar' as substring (first mismatch)"
|
||||
),
|
||||
# from docstring examples
|
||||
pytest.param("bar/foo", ["bar", "my_foobar_method"], True, True, id="'bar/foo' matches 'bar,my_foobar_method' as substring"),
|
||||
pytest.param(
|
||||
"bar/foo", ["bar", "another_method"], True, False, id="'bar/foo' does not match 'bar,another_method' as substring"
|
||||
"bar/foo", ["other", "foo"], False, False, id="R: 'bar/foo' does not match ['other', 'foo'] (first part mismatch)"
|
||||
),
|
||||
pytest.param(
|
||||
"bar/foo", ["other", "my_foobar_method"], True, False, id="'bar/foo' does not match 'other,my_foobar_method' as substring"
|
||||
"bar/foo", ["bar", "otherfoo"], False, False, id="R: 'bar/foo' does not match ['bar', 'otherfoo'] (last part mismatch)"
|
||||
),
|
||||
pytest.param("bar/f", ["bar", "foo"], True, True, id="'bar/f' matches 'bar,foo' as substring"),
|
||||
],
|
||||
)
|
||||
def test_match_name_pattern_ends_without_slash(self, name_pattern, qual_name_parts, is_substring_match, expected):
|
||||
"""Tests matching for qualified names (e.g. 'module/class/func')."""
|
||||
result = Symbol.match_against_qualname(name_pattern, qual_name_parts, is_substring_match)
|
||||
error_msg = self._create_assertion_error_message(name_pattern, qual_name_parts, is_substring_match, expected, result)
|
||||
assert result == expected, error_msg
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"name_pattern, qual_name_parts, is_substring_match, expected",
|
||||
[
|
||||
# Exact matches (is_substring_match=False)
|
||||
pytest.param("foo/", ["foo"], False, True, id="'foo/' matches 'foo' exactly"),
|
||||
pytest.param("bar/foo/", ["bar", "foo"], False, True, id="'bar/foo/' matches 'bar,foo' exactly"),
|
||||
pytest.param("foo/", ["foobar"], False, False, id="'foo/' does not match 'foobar' exactly"),
|
||||
pytest.param("foo/", ["bar", "foo"], False, False, id="'foo/' does not match 'bar,foo' exactly (not toplevel)"),
|
||||
# Substring matches (is_substring_match=True)
|
||||
pytest.param("foo/", ["foobar"], True, True, id="'foo/' matches 'foobar' as substring"),
|
||||
# Substring matches, relative patterns (is_substring_match=True)
|
||||
pytest.param("bar/foo", ["bar", "foobar"], True, True, id="R: 'bar/foo' matches ['bar', 'foobar'] as substring"),
|
||||
pytest.param(
|
||||
"bar/foo/", ["bar", "the_foobar_method"], True, True, id="'bar/foo/' matches 'bar,the_foobar_method' as substring"
|
||||
"bar/foo", ["mod", "bar", "foobar"], True, True, id="R: 'bar/foo' matches ['mod', 'bar', 'foobar'] as substring (suffix)"
|
||||
),
|
||||
pytest.param("bar/foo", ["bar", "bazfoo"], True, True, id="R: 'bar/foo' matches ['bar', 'bazfoo'] as substring"),
|
||||
pytest.param("bar/fo", ["bar", "foo"], True, True, id="R: 'bar/fo' matches ['bar', 'foo'] as substring"),
|
||||
pytest.param("bar/foo", ["bar", "baz"], True, False, id="R: 'bar/foo' does not match ['bar', 'baz'] (last no substr)"),
|
||||
pytest.param(
|
||||
"bar/foo", ["baz", "foobar"], True, False, id="R: 'bar/foo' does not match ['baz', 'foobar'] (first part mismatch)"
|
||||
),
|
||||
pytest.param(
|
||||
"bar/foo/",
|
||||
"bar/foo", ["bar", "my_foobar_method"], True, True, id="R: 'bar/foo' matches ['bar', 'my_foobar_method'] as substring"
|
||||
),
|
||||
pytest.param(
|
||||
"bar/foo",
|
||||
["mod", "bar", "my_foobar_method"],
|
||||
True,
|
||||
True,
|
||||
id="R: 'bar/foo' matches ['mod', 'bar', 'my_foobar_method'] as substring (suffix)",
|
||||
),
|
||||
pytest.param(
|
||||
"bar/foo",
|
||||
["bar", "another_method"],
|
||||
True,
|
||||
False,
|
||||
id="'bar/foo/' does not match 'bar,another_method' as substring (last no substr)",
|
||||
id="R: 'bar/foo' does not match ['bar', 'another_method'] (last no substr)",
|
||||
),
|
||||
pytest.param(
|
||||
"bar/foo/",
|
||||
["baz", "the_foobar_method"],
|
||||
"bar/foo",
|
||||
["other", "my_foobar_method"],
|
||||
True,
|
||||
False,
|
||||
id="'bar/foo/' does not match 'baz,the_foobar_method' as substring (first mismatch)",
|
||||
id="R: 'bar/foo' does not match ['other', 'my_foobar_method'] (first part mismatch)",
|
||||
),
|
||||
# from docstring examples
|
||||
pytest.param("foo/", ["my_foobar_func"], True, True, id="'foo/' matches 'my_foobar_func' as substring"),
|
||||
pytest.param("bar/f", ["bar", "foo"], True, True, id="R: 'bar/f' matches ['bar', 'foo'] as substring"),
|
||||
# Exact matches, absolute patterns (is_substring_match=False)
|
||||
pytest.param("/bar/foo", ["bar", "foo"], False, True, id="A: '/bar/foo' matches ['bar', 'foo'] exactly"),
|
||||
pytest.param(
|
||||
"foo/",
|
||||
["ClassA", "my_foobar_method"],
|
||||
True,
|
||||
False,
|
||||
id="'foo/' does not match 'ClassA,my_foobar_method' as substring (not toplevel)",
|
||||
"/bar/foo", ["bar", "foo", "baz"], False, False, id="A: '/bar/foo' does not match ['bar', 'foo', 'baz'] (pattern shorter)"
|
||||
),
|
||||
pytest.param("/bar/foo", ["bar"], False, False, id="A: '/bar/foo' does not match ['bar'] (pattern longer)"),
|
||||
pytest.param("/bar/foo", ["baz", "foo"], False, False, id="A: '/bar/foo' does not match ['baz', 'foo'] (first part mismatch)"),
|
||||
pytest.param("/bar/foo", ["bar", "baz"], False, False, id="A: '/bar/foo' does not match ['bar', 'baz'] (last part mismatch)"),
|
||||
# Substring matches (is_substring_match=True)
|
||||
pytest.param("/bar/foo", ["bar", "foobar"], True, True, id="A: '/bar/foo' matches ['bar', 'foobar'] as substring"),
|
||||
pytest.param("/bar/foo", ["bar", "bazfoo"], True, True, id="A: '/bar/foo' matches ['bar', 'bazfoo'] as substring"),
|
||||
pytest.param("/bar/fo", ["bar", "foo"], True, True, id="A: '/bar/fo' matches ['bar', 'foo'] as substring"),
|
||||
pytest.param("/bar/foo", ["bar", "baz"], True, False, id="A: '/bar/foo' does not match ['bar', 'baz'] (last no substr)"),
|
||||
pytest.param(
|
||||
"/bar/foo", ["baz", "foobar"], True, False, id="A: '/bar/foo' does not match ['baz', 'foobar'] (first part mismatch)"
|
||||
),
|
||||
pytest.param("foo/", ["foo"], True, True, id="'foo/' matches 'foo' as substring (exact is substr)"),
|
||||
],
|
||||
)
|
||||
def test_match_name_pattern_ending_in_slash(self, name_pattern, qual_name_parts, is_substring_match, expected):
|
||||
"""Tests matching for patterns ending with '/' (prefix/namespace style)."""
|
||||
result = Symbol.match_against_qualname(name_pattern, qual_name_parts, is_substring_match)
|
||||
error_msg = self._create_assertion_error_message(name_pattern, qual_name_parts, is_substring_match, expected, result)
|
||||
def test_match_name_path_pattern_path_len_2(self, name_path_pattern, symbol_name_path_parts, is_substring_match, expected):
|
||||
"""Tests matching for qualified names (e.g. 'module/class/func')."""
|
||||
result = Symbol.match_name_path(name_path_pattern, symbol_name_path_parts, is_substring_match)
|
||||
error_msg = self._create_assertion_error_message(name_path_pattern, symbol_name_path_parts, is_substring_match, expected, result)
|
||||
assert result == expected, error_msg
|
||||
|
||||
Reference in New Issue
Block a user