Files
wehub-resource-sync a06f331eb8
CI / benchmark (push) Has been skipped
install-script / posix-syntax (push) Successful in 6m1s
CI / build-onnx (push) Failing after 6m43s
init-smoke / dry-run (push) Failing after 15m57s
security / govulncheck (push) Has been cancelled
security / trivy-fs (push) Has been cancelled
CI / test (1.26, ubuntu-latest) (push) Has been cancelled
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled
CI / test (1.26, macos-latest) (push) Has been cancelled
CI / build-windows (push) Has been cancelled
CI / lint (push) Has been cancelled
install-script / powershell-syntax (push) Has been cancelled
install-script / install (macos-14) (push) Has been cancelled
install-script / install (ubuntu-latest) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:33:42 +08:00

227 lines
8.3 KiB
Go

package languages
import (
"testing"
"github.com/zzet/gortex/internal/graph"
)
// edgeKey identifies a reference-form edge by target, kind, and
// ref_context for assertions.
type cppRefEdge struct {
to string
kind graph.EdgeKind
refContext string
}
func cppRefEdges(t *testing.T, src string) []cppRefEdge {
t.Helper()
res, err := NewCppExtractor().Extract("x.cpp", []byte(src))
if err != nil {
t.Fatalf("extract: %v", err)
}
var out []cppRefEdge
for _, e := range res.Edges {
rc := ""
if e.Meta != nil {
rc, _ = e.Meta["ref_context"].(string)
}
out = append(out, cppRefEdge{to: e.To, kind: e.Kind, refContext: rc})
}
return out
}
func hasCppRefEdge(edges []cppRefEdge, to string, kind graph.EdgeKind, rc string) bool {
for _, e := range edges {
if e.to == to && e.kind == kind && e.refContext == rc {
return true
}
}
return false
}
func countCppRefEdge(edges []cppRefEdge, to string, kind graph.EdgeKind, rc string) int {
n := 0
for _, e := range edges {
if e.to == to && e.kind == kind && e.refContext == rc {
n++
}
}
return n
}
func TestCppRefForm_NewInstantiation(t *testing.T) {
edges := cppRefEdges(t, `void f() { Foo* p = new Foo(1, 2); }`)
if !hasCppRefEdge(edges, "unresolved::Foo", graph.EdgeInstantiates, "") {
t.Fatalf("want EdgeInstantiates -> unresolved::Foo; got %+v", edges)
}
}
func TestCppRefForm_StackConstructionParen(t *testing.T) {
edges := cppRefEdges(t, `void f() { Bar b(3); }`)
if !hasCppRefEdge(edges, "unresolved::Bar", graph.EdgeInstantiates, "") {
t.Fatalf("want EdgeInstantiates -> unresolved::Bar; got %+v", edges)
}
}
func TestCppRefForm_StackConstructionBrace(t *testing.T) {
edges := cppRefEdges(t, `void f() { Baz z{4}; }`)
if !hasCppRefEdge(edges, "unresolved::Baz", graph.EdgeInstantiates, "") {
t.Fatalf("want EdgeInstantiates -> unresolved::Baz; got %+v", edges)
}
}
func TestCppRefForm_Inheritance(t *testing.T) {
edges := cppRefEdges(t, `class X : public Base, private Mixin {};`)
if !hasCppRefEdge(edges, "unresolved::Base", graph.EdgeReferences, graph.RefContextInherit) {
t.Fatalf("want inherit -> unresolved::Base; got %+v", edges)
}
if !hasCppRefEdge(edges, "unresolved::Mixin", graph.EdgeReferences, graph.RefContextInherit) {
t.Fatalf("want inherit -> unresolved::Mixin; got %+v", edges)
}
// All inherit edges must be OriginASTResolved so the cross-pkg guard
// doesn't revert them.
res, err := NewCppExtractor().Extract("x.cpp", []byte(`class X : public Base {};`))
if err != nil {
t.Fatal(err)
}
for _, e := range res.Edges {
if e.Kind == graph.EdgeReferences && e.To == "unresolved::Base" {
if e.Origin != graph.OriginASTResolved {
t.Fatalf("inherit edge origin = %q, want %q", e.Origin, graph.OriginASTResolved)
}
}
}
}
func TestCppRefForm_StructInheritance(t *testing.T) {
edges := cppRefEdges(t, `struct Derived : Base {};`)
if !hasCppRefEdge(edges, "unresolved::Base", graph.EdgeReferences, graph.RefContextInherit) {
t.Fatalf("want struct inherit -> unresolved::Base; got %+v", edges)
}
}
func TestCppRefForm_StaticCast(t *testing.T) {
edges := cppRefEdges(t, `void f() { auto a = static_cast<Foo>(x); }`)
if !hasCppRefEdge(edges, "unresolved::Foo", graph.EdgeReferences, graph.RefContextCast) {
t.Fatalf("want cast -> unresolved::Foo; got %+v", edges)
}
}
func TestCppRefForm_DynamicAndReinterpretCast(t *testing.T) {
edges := cppRefEdges(t, `void f() { auto a = dynamic_cast<Foo*>(x); auto b = reinterpret_cast<Bar>(y); }`)
if !hasCppRefEdge(edges, "unresolved::Foo", graph.EdgeReferences, graph.RefContextCast) {
t.Fatalf("want dynamic_cast -> unresolved::Foo; got %+v", edges)
}
if !hasCppRefEdge(edges, "unresolved::Bar", graph.EdgeReferences, graph.RefContextCast) {
t.Fatalf("want reinterpret_cast -> unresolved::Bar; got %+v", edges)
}
}
func TestCppRefForm_CStyleCast(t *testing.T) {
edges := cppRefEdges(t, `void f() { auto c = (Quux)x; }`)
if !hasCppRefEdge(edges, "unresolved::Quux", graph.EdgeReferences, graph.RefContextCast) {
t.Fatalf("want C-style cast -> unresolved::Quux; got %+v", edges)
}
}
func TestCppRefForm_StaticMemberAccess(t *testing.T) {
edges := cppRefEdges(t, `void f() { int n = Color::RED; }`)
if !hasCppRefEdge(edges, "unresolved::Color", graph.EdgeReferences, graph.RefContextStaticAccess) {
t.Fatalf("want static_access -> unresolved::Color; got %+v", edges)
}
}
func TestCppRefForm_StaticMethodCall(t *testing.T) {
edges := cppRefEdges(t, `void f() { Thing::method(); }`)
if !hasCppRefEdge(edges, "unresolved::Thing", graph.EdgeReferences, graph.RefContextStaticAccess) {
t.Fatalf("want static_access -> unresolved::Thing; got %+v", edges)
}
// The scope must not be double-emitted (bare qid + call qid).
if n := countCppRefEdge(edges, "unresolved::Thing", graph.EdgeReferences, graph.RefContextStaticAccess); n != 1 {
t.Fatalf("static method scope emitted %d times, want 1; got %+v", n, edges)
}
}
func TestCppRefForm_Negatives(t *testing.T) {
// Free-function call, plain typed local, std:: helper, primitive cast,
// and a lowercase scope must emit no reference-form edges.
cases := []string{
`void f() { foo(); }`,
`void f() { int x = 5; }`,
`void f() { std::move(b); }`,
`void f() { std::vector<int> v; }`,
`void f() { int n = detail::flag; }`,
`void f() { Foo x; }`, // plain declaration, no ctor init -> no instantiate
}
for _, src := range cases {
edges := cppRefEdges(t, src)
for _, e := range edges {
if e.kind == graph.EdgeInstantiates ||
(e.kind == graph.EdgeReferences && (e.refContext == graph.RefContextInherit ||
e.refContext == graph.RefContextCast || e.refContext == graph.RefContextStaticAccess)) {
t.Fatalf("src %q: unexpected reference-form edge %+v", src, e)
}
}
}
}
// TestCppRefForm_GenericArgs: a type named inside a template_argument_list
// is a generic_arg reference, in every position — a variable declaration
// (`std::vector<Foo>`), a function parameter (`std::map<std::string, Bar>`),
// and a nested template (`std::map<int, std::vector<Widget>>`). A primitive
// / non-type argument (`int`, the integer constant `5`) emits nothing.
func TestCppRefForm_GenericArgs(t *testing.T) {
edges := cppRefEdges(t, `void use(std::map<std::string, Bar> m) {
std::vector<Foo> x;
std::map<int, std::vector<Widget>> nested;
std::array<int, 5> a;
}`)
// Variable-decl template arg.
if !hasCppRefEdge(edges, "unresolved::Foo", graph.EdgeReferences, graph.RefContextGenericArg) {
t.Fatalf("want generic_arg -> unresolved::Foo (variable decl); got %+v", edges)
}
// Parameter template arg (the non-wrapper map's second argument).
if !hasCppRefEdge(edges, "unresolved::Bar", graph.EdgeReferences, graph.RefContextGenericArg) {
t.Fatalf("want generic_arg -> unresolved::Bar (parameter); got %+v", edges)
}
// Nested template arg.
if !hasCppRefEdge(edges, "unresolved::Widget", graph.EdgeReferences, graph.RefContextGenericArg) {
t.Fatalf("want generic_arg -> unresolved::Widget (nested template); got %+v", edges)
}
// Primitives / std aliases / integer constants must not appear as
// generic_arg references.
for _, e := range edges {
if e.refContext != graph.RefContextGenericArg {
continue
}
switch e.to {
case "unresolved::int", "unresolved::string", "unresolved::5",
"unresolved::vector", "unresolved::map", "unresolved::array":
t.Fatalf("false positive: generic_arg edge %+v for a non-user-type argument", e)
}
}
// generic_arg edges must be OriginASTResolved so the cross-pkg guard
// doesn't revert them.
res, err := NewCppExtractor().Extract("x.cpp", []byte(`void use() { std::vector<Foo> x; }`))
if err != nil {
t.Fatal(err)
}
for _, e := range res.Edges {
if e.Kind == graph.EdgeReferences && e.To == "unresolved::Foo" {
if rc, _ := e.Meta["ref_context"].(string); rc == graph.RefContextGenericArg && e.Origin != graph.OriginASTResolved {
t.Fatalf("generic_arg edge origin = %q, want %q", e.Origin, graph.OriginASTResolved)
}
}
}
}
func TestCppRefForm_StdQualifiedTypeReducesToTrailing(t *testing.T) {
// A std::-qualified path whose trailing segment is a Capitalized type
// reduces to that type; a lowercase trailing one emits nothing.
edges := cppRefEdges(t, `void f() { int n = std::String::npos; }`)
if hasCppRefEdge(edges, "unresolved::std", graph.EdgeReferences, graph.RefContextStaticAccess) {
t.Fatalf("must not emit lowercase std scope; got %+v", edges)
}
}