Skip to content

Bug: tests_for query uses wrong edge direction for TESTED_BY, always falls back to naming convention #515

@vincent9917

Description

@vincent9917

Summary

The tests_for query pattern (and get_transitive_tests in graph.py) looks up TESTED_BY edges using target_qualified = <production_function>, but the parser stores these edges with source = production_function and target = test_function. Because the direction is inverted between build and query, tests_for never matches any TESTED_BY edges from the graph and silently falls back to naming-convention heuristics (test_{name} / Test{name}).

This means query_graph(pattern=\"tests_for\", target=\"...::add\") returns results only when a test happens to be named test_add or TestAdd, not when the parser actually discovered a TESTED_BY relationship.

Build side (parser.py)

In every parser path that handles test files, TESTED_BY edges are created by reversing a CALLS edge:

# code_review_graph/parser.py:977-993
if test_file:
    test_qnames = set()
    for n in nodes:
        if n.is_test:
            qn = self._qualify(n.name, n.file_path, n.parent_name)
            test_qnames.add(qn)
    for edge in list(edges):
        if edge.kind == "CALLS" and edge.source in test_qnames:
            edges.append(EdgeInfo(
                kind="TESTED_BY",
                source=edge.target,        # production function
                target=edge.source,        # test function
                file_path=edge.file_path,
                line=edge.line,
            ))

So the stored edge is:

  • source_qualified = src/calc.py::add (production)
  • target_qualified = src/test_calc.py::test_add (test)

Query side (tools/query.py)

# code_review_graph/tools/query.py:295-308
elif pattern == "tests_for":
    for e in store.get_edges_by_target(qn):
        if e.kind == "TESTED_BY":
            test = store.get_node(e.source_qualified)

qn is the production function. get_edges_by_target(qn) searches WHERE target_qualified = <production_function>. But the parser stored the production function in source_qualified, not target_qualified. Therefore this loop never yields any edges.

The same inverted assumption exists in graph.py:get_transitive_tests:

# code_review_graph/graph.py:418-430
for row in conn.execute(
    \"SELECT source_qualified FROM edges \"
    \"WHERE target_qualified = ? AND kind = 'TESTED_BY'\",
    (qn,)
).fetchall():

Test data is also inconsistent

  • tests/test_tools.py:1165 creates TESTED_BY with source=\"auth.py::login\" (production) and target=\"tests/test_auth.py::test_login\" (test) — matching the parser.
  • tests/test_graph.py:382 creates TESTED_BY with source=test_qn (test) and target=callee_qn (production) — the opposite direction.

Expected behavior

tests_for should look up TESTED_BY edges by source (get_edges_by_source(qn)) because the parser stores the production function in source_qualified:

for e in store.get_edges_by_source(qn):
    if e.kind == "TESTED_BY":
        test = store.get_node(e.target_qualified)   # target = test function

Alternatively, the parser could be changed to store source = test_function, target = production_function to match the current query convention. Either way, build and query must agree on the same direction.

Environment

  • code-review-graph v2.3.5
  • Python 3.10+

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions