Ctrl K
ComunidadGuías…

tests/test_cpp_split_header_owner.py

Rama v8 · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
"""Bind qualified C++ definitions through explicit included declarations."""
import pytest
from graphify.extract import extract


@pytest.mark.parametrize("reverse", [False, True])
def test_split_header_member_call_never_binds_unrelated_free_function(tmp_path, reverse):
    (tmp_path / "include").mkdir()
    (tmp_path / "src").mkdir()
    h = tmp_path / "include/foo.h"
    h.write_text("class Foo {public: void run(); void say();};", encoding="utf-8")
    impl = tmp_path / "src/foo.cpp"
    impl.write_text('#include "foo.h"\nvoid Foo::say() {}\nvoid Foo::run() {say(); utility();}', encoding="utf-8")
    other = tmp_path / "src/bar.cpp"
    other.write_text('namespace { void say() {} }\nvoid utility() {}', encoding="utf-8")
    paths = [h, impl, other]
    r = extract(list(reversed(paths)) if reverse else paths, root=tmp_path, parallel=False, cache_root=tmp_path)
    nodes = {n["id"]: n for n in r["nodes"]}
    caller = next(n["id"] for n in r["nodes"] if n["label"] == "Foo::run()")
    calls = [e for e in r["edges"] if e["source"] == caller and e["relation"] == "calls"]
    assert any(nodes[e["target"]]["label"] in (".say()", "Foo::say()") for e in calls)
    assert not any(nodes[e["target"]]["label"] == "say()" for e in calls)
    assert any(nodes[e["target"]]["label"] == "utility()" for e in calls)
    owner = next(n["id"] for n in r["nodes"] if n["label"] == "Foo")
    assert any(e["source"] == owner and e["target"] == caller and e["relation"] == "method" for e in r["edges"])
    # An incremental caller must see the same included owner without copying
    # the unchanged header's nodes into the fresh extraction.
    impl.write_text(impl.read_text(encoding="utf-8") + "\n// edit\n", encoding="utf-8")
    fresh = extract([impl], root=tmp_path, parallel=False, cache_root=tmp_path,
        resolution_context_nodes=[n for n in r["nodes"] if n.get("source_file") != "src/foo.cpp"],
        resolution_context_edges=[e for e in r["edges"] if e.get("source_file") != "src/foo.cpp"])
    assert any(e["source"] == caller and e["target"] in nodes and nodes[e["target"]]["label"] == ".say()"
               and e["relation"] == "calls" for e in fresh["edges"])
    assert not any(n["label"] == "Foo" for n in fresh["nodes"])


def test_namespace_qualified_owner_is_not_a_same_named_included_type(tmp_path):
    a = tmp_path / "left.h"
    a.write_text("namespace Left {class Foo {public: void run(); void say();};}", encoding="utf-8")
    b = tmp_path / "right.h"
    b.write_text("namespace Right {class Foo {public: void run(); void say();};}", encoding="utf-8")
    cpp = tmp_path / "main.cpp"
    cpp.write_text('#include "left.h"\n#include "right.h"\nvoid Left::Foo::run(){say();}\nvoid Left::Foo::say(){}', encoding="utf-8")
    r = extract([a, b, cpp], root=tmp_path, parallel=False, cache_root=tmp_path)
    nodes = {n["id"]: n for n in r["nodes"]}
    caller = next(n["id"] for n in r["nodes"] if n.get("_cpp_qualified") == "Left::Foo::run")
    calls = [e for e in r["edges"] if e["source"] == caller and e["relation"] == "calls"]
    assert calls
    assert all(nodes[e["target"]]["source_file"] == "left.h" for e in calls)


def test_ambiguous_included_owner_fails_closed(tmp_path):
    paths = []
    for name in ["a.h", "b.h"]:
        p = tmp_path / name
        p.write_text("class Foo {public: void run(); void say();};", encoding="utf-8")
        paths.append(p)
    cpp = tmp_path / "main.cpp"
    cpp.write_text('#include "a.h"\n#include "b.h"\nvoid Foo::run(){say();}', encoding="utf-8")
    other = tmp_path / "other.cpp"
    other.write_text("void say(){}", encoding="utf-8")
    r = extract([*paths, cpp, other], root=tmp_path, parallel=False, cache_root=tmp_path)
    caller = next(n["id"] for n in r["nodes"] if n["label"] == "Foo::run()")
    assert not any(e["source"] == caller and e["relation"] == "calls" for e in r["edges"])


def test_colliding_namespace_types_do_not_manufacture_an_owner(tmp_path):
    header = tmp_path / "foo.h"
    header.write_text("namespace Left {class Foo {public: void run(); void say();};}\n"
                      "namespace Right {class Foo {public: void run(); void say();};}", encoding="utf-8")
    cpp = tmp_path / "main.cpp"
    cpp.write_text('#include "foo.h"\nvoid Right::Foo::run(){say();}', encoding="utf-8")
    r = extract([header, cpp], root=tmp_path, parallel=False, cache_root=tmp_path)
    caller = next(n["id"] for n in r["nodes"] if n["label"] == "Right::Foo::run()")
    # The existing declaration-id collision is outside this fix. Incomplete
    # class identity must not turn into positive evidence of an owning type.
    assert not any(e["relation"] == "method" and e["target"] == caller for e in r["edges"])