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

122 lines
3.8 KiB
Go

package languages
import "testing"
// countByName returns how many extracted nodes carry the given name, and the
// set of their (distinct) IDs.
func countByName(nodes []*nodeForCount, name string) (int, []string) {
var ids []string
for _, n := range nodes {
if n.name == name {
ids = append(ids, n.id)
}
}
return len(ids), ids
}
type nodeForCount struct {
id string
name string
}
// TestDisambiguateID exercises the helper directly: first use keeps the base
// ID, a genuine collision on a different line gains a line suffix, and an exact
// re-match of the same declaration is reported as a drop.
func TestDisambiguateID(t *testing.T) {
seen := map[string]bool{}
id1, ok1 := disambiguateID(seen, "f.go::Foo", 10)
if id1 != "f.go::Foo" || !ok1 {
t.Fatalf("first use: got (%q,%v) want (f.go::Foo,true)", id1, ok1)
}
id2, ok2 := disambiguateID(seen, "f.go::Foo", 20)
if id2 != "f.go::Foo_L20" || !ok2 {
t.Fatalf("collision on a different line: got (%q,%v) want (f.go::Foo_L20,true)", id2, ok2)
}
if _, ok3 := disambiguateID(seen, "f.go::Foo", 10); ok3 {
t.Fatalf("exact re-match (same id + line) must be dropped, got ok=true")
}
id4, ok4 := disambiguateID(seen, "f.go::Foo", 30)
if id4 != "f.go::Foo_L30" || !ok4 {
t.Fatalf("third collision: got (%q,%v) want (f.go::Foo_L30,true)", id4, ok4)
}
// A different base ID is unaffected.
id5, ok5 := disambiguateID(seen, "f.go::Bar", 5)
if id5 != "f.go::Bar" || !ok5 {
t.Fatalf("unrelated base: got (%q,%v) want (f.go::Bar,true)", id5, ok5)
}
}
// TestInitCollisionSurvive proves the Go extractor no longer drops a second
// func init(): both survive as distinct nodes, the collider gaining a line
// suffix, instead of one silently overwriting the other in the graph.
func TestInitCollisionSurvive(t *testing.T) {
src := []byte("package p\n" +
"func init() { a() }\n" +
"func init() { b() }\n" +
"func a() {}\n" +
"func b() {}\n")
res, err := NewGoExtractor().Extract("p.go", src)
if err != nil {
t.Fatalf("extract: %v", err)
}
var nodes []*nodeForCount
for _, n := range res.Nodes {
nodes = append(nodes, &nodeForCount{id: n.ID, name: n.Name})
}
n, ids := countByName(nodes, "init")
if n != 2 {
t.Fatalf("expected 2 surviving init nodes, got %d (%v)", n, ids)
}
if ids[0] == ids[1] {
t.Fatalf("the two init nodes share an ID %q — one would overwrite the other", ids[0])
}
}
// TestOverloadSurvive proves redefinition/overload sets are no longer collapsed
// to a single node across the dynamic-language extractors: a Python @overload
// triple and a Ruby method redeclared in one class both keep every definition.
func TestOverloadSurvive(t *testing.T) {
t.Run("python_overload", func(t *testing.T) {
src := []byte("from typing import overload\n" +
"@overload\n" +
"def f(x: int) -> int: ...\n" +
"@overload\n" +
"def f(x: str) -> str: ...\n" +
"def f(x): return x\n")
res, err := NewPythonExtractor().Extract("m.py", src)
if err != nil {
t.Fatalf("extract: %v", err)
}
var nodes []*nodeForCount
for _, n := range res.Nodes {
nodes = append(nodes, &nodeForCount{id: n.ID, name: n.Name})
}
if n, ids := countByName(nodes, "f"); n != 3 {
t.Fatalf("expected 3 surviving overloads of f, got %d (%v)", n, ids)
}
})
t.Run("ruby_redefinition", func(t *testing.T) {
src := []byte("class Greeter\n" +
" def greet\n" +
" 'hi'\n" +
" end\n" +
" def greet\n" +
" 'hello'\n" +
" end\n" +
"end\n")
res, err := NewRubyExtractor().Extract("g.rb", src)
if err != nil {
t.Fatalf("extract: %v", err)
}
var nodes []*nodeForCount
for _, n := range res.Nodes {
nodes = append(nodes, &nodeForCount{id: n.ID, name: n.Name})
}
if n, ids := countByName(nodes, "greet"); n != 2 {
t.Fatalf("expected 2 surviving greet definitions, got %d (%v)", n, ids)
}
})
}