chore: import upstream snapshot with attribution
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

This commit is contained in:
wehub-resource-sync
2026-07-13 12:33:42 +08:00
commit a06f331eb8
3186 changed files with 689843 additions and 0 deletions
+517
View File
@@ -0,0 +1,517 @@
package languages
import (
"strings"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/zzet/gortex/internal/graph"
)
func TestPyExtractor_Function(t *testing.T) {
src := []byte(`def greet(name):
return f"Hello {name}"
`)
e := NewPythonExtractor()
result, err := e.Extract("app.py", src)
require.NoError(t, err)
funcs := nodesOfKind(result.Nodes, graph.KindFunction)
require.Len(t, funcs, 1)
assert.Equal(t, "greet", funcs[0].Name)
}
func TestPyExtractor_Class(t *testing.T) {
src := []byte(`class UserService:
def __init__(self):
self.users = []
def get_user(self, user_id):
return None
`)
e := NewPythonExtractor()
result, err := e.Extract("service.py", src)
require.NoError(t, err)
types := nodesOfKind(result.Nodes, graph.KindType)
require.Len(t, types, 1)
assert.Equal(t, "UserService", types[0].Name)
// Class methods are extracted as KindMethod, not KindFunction.
methods := nodesOfKind(result.Nodes, graph.KindMethod)
assert.Len(t, methods, 2) // __init__, get_user
// No top-level functions.
funcs := nodesOfKind(result.Nodes, graph.KindFunction)
assert.Len(t, funcs, 0)
// EdgeMemberOf edges link methods to the class.
memberEdges := edgesOfKind(result.Edges, graph.EdgeMemberOf)
assert.Len(t, memberEdges, 2)
for _, e := range memberEdges {
assert.Equal(t, "service.py::UserService", e.To)
}
}
func TestPyExtractor_Imports(t *testing.T) {
src := []byte(`import os
from pathlib import Path
`)
e := NewPythonExtractor()
result, err := e.Extract("app.py", src)
require.NoError(t, err)
imports := edgesOfKind(result.Edges, graph.EdgeImports)
require.Len(t, imports, 2)
}
func TestPyExtractor_TypeEnv_TypeHint(t *testing.T) {
src := []byte(`
class UserService:
def save(self):
pass
def main():
svc: UserService = get_service()
svc.save()
`)
e := NewPythonExtractor()
result, err := e.Extract("app.py", src)
require.NoError(t, err)
calls := edgesOfKind(result.Edges, graph.EdgeCalls)
var saveCall *graph.Edge
for _, c := range calls {
if strings.HasSuffix(c.To, "save") {
saveCall = c
break
}
}
require.NotNil(t, saveCall, "expected a call edge to save")
require.NotNil(t, saveCall.Meta, "expected Meta on save call edge")
assert.Equal(t, "UserService", saveCall.Meta["receiver_type"])
}
func TestPyExtractor_TypeEnv_ClassConstructor(t *testing.T) {
src := []byte(`
class Client:
def connect(self):
pass
def main():
client = Client()
client.connect()
`)
e := NewPythonExtractor()
result, err := e.Extract("app.py", src)
require.NoError(t, err)
calls := edgesOfKind(result.Edges, graph.EdgeCalls)
var connectCall *graph.Edge
for _, c := range calls {
if strings.HasSuffix(c.To, "connect") {
connectCall = c
break
}
}
require.NotNil(t, connectCall)
require.NotNil(t, connectCall.Meta)
assert.Equal(t, "Client", connectCall.Meta["receiver_type"])
}
func TestPyExtractor_TypeEnv_Chain(t *testing.T) {
src := []byte(`
class Connection:
def query(self) -> Result:
return Result()
class Result:
def first(self) -> User:
return User()
class User:
def save(self):
pass
def main():
conn = Connection()
conn.query().first().save()
`)
e := NewPythonExtractor()
result, err := e.Extract("app.py", src)
require.NoError(t, err)
calls := edgesOfKind(result.Edges, graph.EdgeCalls)
var saveCall *graph.Edge
for _, c := range calls {
if strings.HasSuffix(c.To, "save") {
saveCall = c
break
}
}
require.NotNil(t, saveCall, "expected a call edge to save")
require.NotNil(t, saveCall.Meta, "expected Meta on chained save call edge")
assert.Equal(t, "User", saveCall.Meta["receiver_type"])
}
func TestPyExtractor_TypeEnv_Unknown(t *testing.T) {
src := []byte(`
def get_service():
return None
def main():
svc = get_service()
svc.process()
`)
e := NewPythonExtractor()
result, err := e.Extract("app.py", src)
require.NoError(t, err)
calls := edgesOfKind(result.Edges, graph.EdgeCalls)
var processCall *graph.Edge
for _, c := range calls {
if strings.HasSuffix(c.To, "process") {
processCall = c
break
}
}
require.NotNil(t, processCall)
assert.NotContains(t, processCall.Meta, "receiver_type", "unknown type should not produce a receiver_type hint")
}
func TestPythonExtractor_FastAPIDepends(t *testing.T) {
// Depends(target) in a parameter default (or Annotated[T, Depends(target)])
// should produce a direct call edge from the handler to target, not
// just to the generic Depends function. Without this pass,
// callers(target) is empty for any DI-only factory.
src := []byte(`
from fastapi import Depends
from typing import Annotated
def get_settings():
return {"db": "x"}
def handler(settings: Annotated[dict, Depends(get_settings)]):
return settings
`)
e := NewPythonExtractor()
result, err := e.Extract("app.py", src)
require.NoError(t, err)
var found *graph.Edge
for _, c := range edgesOfKind(result.Edges, graph.EdgeCalls) {
if c.Meta == nil {
continue
}
if v, _ := c.Meta["via"].(string); v == "fastapi.Depends" {
if strings.HasSuffix(c.To, "get_settings") {
found = c
break
}
}
}
require.NotNil(t, found, "expected a fastapi.Depends edge to get_settings")
assert.Equal(t, "app.py::handler", found.From)
}
func TestPythonExtractor_DependsOnlyOnIdentifierArg(t *testing.T) {
// Depends() with a non-identifier argument (lambda, attribute access)
// shouldn't produce a bogus edge — we can only statically resolve
// plain identifier targets.
src := []byte(`
from fastapi import Depends
def handler(x = Depends(lambda: 42), y = Depends(obj.method)):
return x
`)
e := NewPythonExtractor()
result, err := e.Extract("app.py", src)
require.NoError(t, err)
for _, c := range edgesOfKind(result.Edges, graph.EdgeCalls) {
if c.Meta == nil {
continue
}
if v, _ := c.Meta["via"].(string); v == "fastapi.Depends" {
t.Fatalf("unexpected fastapi.Depends edge: %+v", c)
}
}
}
func TestPythonExtractor_DocAndVisibility(t *testing.T) {
src := []byte(`def foo():
"""Foo does the thing.
Some details.
"""
return 1
def _helper():
"""Internal helper."""
return 2
class Server:
"""The server."""
def start(self):
"""Start it."""
return None
def _stop(self):
return None
`)
e := NewPythonExtractor()
result, err := e.Extract("server.py", src)
require.NoError(t, err)
byID := map[string]*graph.Node{}
for _, n := range result.Nodes {
byID[n.ID] = n
}
foo := byID["server.py::foo"]
require.NotNil(t, foo)
if foo.Meta["doc"] != "Foo does the thing." {
t.Fatalf("foo.doc = %q", foo.Meta["doc"])
}
if foo.Meta["visibility"] != "public" {
t.Fatalf("foo.vis = %q", foo.Meta["visibility"])
}
helper := byID["server.py::_helper"]
require.NotNil(t, helper)
if helper.Meta["visibility"] != "private" {
t.Fatalf("_helper.vis = %q", helper.Meta["visibility"])
}
server := byID["server.py::Server"]
require.NotNil(t, server)
if server.Meta["doc"] != "The server." {
t.Fatalf("Server.doc = %q", server.Meta["doc"])
}
if server.Meta["visibility"] != "public" {
t.Fatalf("Server.vis = %q", server.Meta["visibility"])
}
start := byID["server.py::Server.start"]
require.NotNil(t, start)
if start.Meta["doc"] != "Start it." {
t.Fatalf("Server.start.doc = %q", start.Meta["doc"])
}
stop := byID["server.py::Server._stop"]
require.NotNil(t, stop)
if stop.Meta["visibility"] != "private" {
t.Fatalf("Server._stop.vis = %q", stop.Meta["visibility"])
}
}
func TestPythonExtractor_AnnotationEdges(t *testing.T) {
src := []byte(`from app import app
from functools import lru_cache
@app.route("/users/:id")
@lru_cache
def get_user(id):
return {}
@dataclass
class User:
name: str
@deprecated
def old_helper():
pass
`)
e := NewPythonExtractor()
result, err := e.Extract("svc.py", src)
require.NoError(t, err)
annNames := map[string]bool{}
for _, n := range result.Nodes {
if v, _ := n.Meta["kind"].(string); v == "annotation" {
annNames[n.Name] = true
}
}
for _, want := range []string{"app.route", "lru_cache", "dataclass", "deprecated"} {
if !annNames[want] {
t.Fatalf("missing annotation node %q (got %v)", want, annNames)
}
}
edges := map[string][]string{}
argsByEdge := map[string]string{}
for _, e := range result.Edges {
if e.Kind != graph.EdgeAnnotated {
continue
}
edges[e.From] = append(edges[e.From], e.To)
if v, ok := e.Meta["args"].(string); ok {
argsByEdge[e.From+"->"+e.To] = v
}
}
if !pyContains(edges["svc.py::get_user"], "annotation::python::app.route") {
t.Fatalf("missing app.route edge on get_user, got %v", edges["svc.py::get_user"])
}
if argsByEdge["svc.py::get_user->annotation::python::app.route"] != `"/users/:id"` {
t.Fatalf("app.route args = %q", argsByEdge["svc.py::get_user->annotation::python::app.route"])
}
if !pyContains(edges["svc.py::User"], "annotation::python::dataclass") {
t.Fatalf("missing dataclass edge on User, got %v", edges["svc.py::User"])
}
}
func pyContains(haystack []string, needle string) bool {
for _, h := range haystack {
if h == needle {
return true
}
}
return false
}
func TestPythonExtractor_ThrowsRaise(t *testing.T) {
src := []byte(`def parse(s):
if not s:
raise ValueError("empty")
if len(s) > 10:
raise errors.TooLong()
return int(s)
def silent():
try:
x = 1
except Exception:
pass
`)
e := NewPythonExtractor()
result, err := e.Extract("p.py", src)
require.NoError(t, err)
throws := edgesOfKind(result.Edges, graph.EdgeThrows)
parseTargets := map[string]bool{}
for _, e := range throws {
if e.From == "p.py::parse" {
parseTargets[e.To] = true
}
}
assert.True(t, parseTargets["unresolved::ValueError"], "ValueError missing")
assert.True(t, parseTargets["unresolved::TooLong"], "TooLong (attribute access) missing")
for _, e := range throws {
if e.From == "p.py::silent" {
t.Fatalf("silent() doesn't raise; got %v", e)
}
}
}
func TestPythonExtractor_ImportNodes(t *testing.T) {
// Note: relative imports (`from .local import x`) aren't covered
// by the current import query — pre-existing limitation. The
// import-node promotion only sees absolute paths today.
src := []byte(`import os
import requests
from app import service
`)
e := NewPythonExtractor()
result, err := e.Extract("svc.py", src)
require.NoError(t, err)
imports := nodesOfKind(result.Nodes, graph.KindImport)
require.GreaterOrEqual(t, len(imports), 3)
byID := map[string]*graph.Node{}
for _, n := range imports {
byID[n.ID] = n
}
osNode := byID["svc.py::import::os"]
require.NotNil(t, osNode)
assert.Equal(t, false, osNode.Meta["is_external"], "os is stdlib")
req := byID["svc.py::import::requests"]
require.NotNil(t, req)
assert.Equal(t, true, req.Meta["is_external"])
}
// Decorated methods (@property, @staticmethod, @contextmanager, ...) are
// wrapped in a decorated_definition node between the def and the class
// body. They must still classify as class members: two same-named
// decorated properties on two classes in one file used to collapse into
// bare colliding `<file>::<name>` IDs (httpx's Headers.encoding vs
// Response.encoding), so every consumer addressing the class-qualified
// ID silently landed on the wrong declaration.
func TestPyExtractor_DecoratedMethodsAreClassMembers(t *testing.T) {
src := []byte(`class Headers:
@property
def encoding(self):
return "ascii"
class Response:
@property
def encoding(self):
return "utf-8"
@staticmethod
def build():
return Response()
def caller():
@wraps
def inner():
pass
return inner
`)
e := NewPythonExtractor()
result, err := e.Extract("m.py", src)
require.NoError(t, err)
byID := map[string]*graph.Node{}
for _, n := range result.Nodes {
byID[n.ID] = n
}
he := byID["m.py::Headers.encoding"]
require.NotNil(t, he, "decorated property must get a class-qualified ID (got IDs: %v)", pyNodeIDs(result.Nodes))
assert.Equal(t, graph.KindMethod, he.Kind)
assert.Equal(t, 3, he.StartLine)
assert.Equal(t, "Headers", he.Meta["receiver"])
re := byID["m.py::Response.encoding"]
require.NotNil(t, re, "same-named decorated property on a second class must stay distinct")
assert.Equal(t, graph.KindMethod, re.Kind)
assert.Equal(t, 8, re.StartLine)
assert.Equal(t, "Response", re.Meta["receiver"])
rb := byID["m.py::Response.build"]
require.NotNil(t, rb, "@staticmethod def must classify as a method")
assert.Equal(t, graph.KindMethod, rb.Kind)
// A decorated function nested inside a function is NOT a class
// member — it stays in the free-function bucket.
in := byID["m.py::inner"]
require.NotNil(t, in)
assert.Equal(t, graph.KindFunction, in.Kind)
// member_of edges link each decorated method to its class.
memberTo := map[string]string{}
for _, ed := range edgesOfKind(result.Edges, graph.EdgeMemberOf) {
memberTo[ed.From] = ed.To
}
assert.Equal(t, "m.py::Headers", memberTo["m.py::Headers.encoding"])
assert.Equal(t, "m.py::Response", memberTo["m.py::Response.encoding"])
assert.Equal(t, "m.py::Response", memberTo["m.py::Response.build"])
}
func pyNodeIDs(nodes []*graph.Node) []string {
out := make([]string, 0, len(nodes))
for _, n := range nodes {
out = append(out, n.ID)
}
return out
}