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
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:
@@ -0,0 +1,224 @@
|
||||
package languages
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/zzet/gortex/internal/graph"
|
||||
)
|
||||
|
||||
func runPyExtract(t *testing.T, path, src string) ([]*graph.Node, []*graph.Edge) {
|
||||
t.Helper()
|
||||
ext := NewPythonExtractor()
|
||||
result, err := ext.Extract(path, []byte(src))
|
||||
if err != nil {
|
||||
t.Fatalf("extract: %v", err)
|
||||
}
|
||||
return result.Nodes, result.Edges
|
||||
}
|
||||
|
||||
func TestPyFunctionShape_ParamsAndReturn(t *testing.T) {
|
||||
src := `def fetch(client: Client, ttl: int = 30) -> User:
|
||||
return User()
|
||||
`
|
||||
nodes, edges := runPyExtract(t, "x.py", src)
|
||||
|
||||
params := nodesOfKind(nodes, graph.KindParam)
|
||||
if len(params) != 2 {
|
||||
t.Fatalf("expected 2 params, got %d: %v", len(params), nodeNames(params))
|
||||
}
|
||||
|
||||
paramOf := edgesByKind(edges, graph.EdgeParamOf)
|
||||
for _, e := range paramOf {
|
||||
if e.To != "x.py::fetch" {
|
||||
t.Errorf("param->%q, want fetch", e.To)
|
||||
}
|
||||
}
|
||||
|
||||
typed := edgesByKind(edges, graph.EdgeTypedAs)
|
||||
hasClient := false
|
||||
for _, e := range typed {
|
||||
if e.To == "unresolved::Client" {
|
||||
hasClient = true
|
||||
}
|
||||
}
|
||||
if !hasClient {
|
||||
t.Errorf("expected EdgeTypedAs → unresolved::Client; got %v", edgeTargets(typed))
|
||||
}
|
||||
|
||||
returns := edgesByKind(edges, graph.EdgeReturns)
|
||||
hasUser := false
|
||||
for _, e := range returns {
|
||||
if e.To == "unresolved::User" {
|
||||
hasUser = true
|
||||
}
|
||||
}
|
||||
if !hasUser {
|
||||
t.Errorf("expected EdgeReturns → unresolved::User; got %v", edgeTargets(returns))
|
||||
}
|
||||
}
|
||||
|
||||
func TestPyFunctionShape_OptionalUnwrapped(t *testing.T) {
|
||||
src := `def find_user(uid: int) -> Optional[User]:
|
||||
return None
|
||||
`
|
||||
_, edges := runPyExtract(t, "x.py", src)
|
||||
|
||||
returns := edgesByKind(edges, graph.EdgeReturns)
|
||||
hasUser := false
|
||||
for _, e := range returns {
|
||||
if e.To == "unresolved::User" {
|
||||
hasUser = true
|
||||
}
|
||||
}
|
||||
if !hasUser {
|
||||
t.Errorf("expected unwrapped EdgeReturns → User; got %v", edgeTargets(returns))
|
||||
}
|
||||
}
|
||||
|
||||
func TestPyFunctionShape_PEP604Union(t *testing.T) {
|
||||
src := `def lookup(uid: int) -> User | None:
|
||||
return None
|
||||
`
|
||||
_, edges := runPyExtract(t, "x.py", src)
|
||||
|
||||
returns := edgesByKind(edges, graph.EdgeReturns)
|
||||
hasUser := false
|
||||
for _, e := range returns {
|
||||
if e.To == "unresolved::User" {
|
||||
hasUser = true
|
||||
}
|
||||
}
|
||||
if !hasUser {
|
||||
t.Errorf("expected EdgeReturns → User from PEP-604 union; got %v", edgeTargets(returns))
|
||||
}
|
||||
}
|
||||
|
||||
func TestPyFunctionShape_VariadicSplat(t *testing.T) {
|
||||
src := `def fn(*args, **kwargs):
|
||||
pass
|
||||
`
|
||||
nodes, _ := runPyExtract(t, "x.py", src)
|
||||
params := nodesOfKind(nodes, graph.KindParam)
|
||||
if len(params) != 2 {
|
||||
t.Fatalf("expected 2 params, got %d", len(params))
|
||||
}
|
||||
for _, p := range params {
|
||||
if v, _ := p.Meta["variadic"].(bool); !v {
|
||||
t.Errorf("%s should be variadic", p.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestPyFunctionShape_SkipsSelfAndCls(t *testing.T) {
|
||||
src := `class C:
|
||||
def m(self, x: int) -> int:
|
||||
return x
|
||||
|
||||
@classmethod
|
||||
def c(cls, y: int) -> int:
|
||||
return y
|
||||
`
|
||||
nodes, _ := runPyExtract(t, "x.py", src)
|
||||
for _, n := range nodes {
|
||||
if n.Kind == graph.KindParam && (n.Name == "self" || n.Name == "cls") {
|
||||
t.Errorf("KindParam %q should not be emitted (receiver)", n.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestPyFunctionShape_ClassLevelPEP695Generic(t *testing.T) {
|
||||
// PEP 695: `class Foo[T]:`. Depending on the bundled tree-sitter-
|
||||
// python version the type_parameters child may or may not be
|
||||
// present at the class level. We assert no panic + (best-effort)
|
||||
// the generic parameter is attached to the class. When the
|
||||
// grammar doesn't recognise it, the helper silently emits
|
||||
// nothing — which is acceptable since callers are tolerant of
|
||||
// missing class generics.
|
||||
src := `class Repo[T]:
|
||||
def get(self, x: T) -> T:
|
||||
return x
|
||||
`
|
||||
nodes, _ := runPyExtract(t, "x.py", src)
|
||||
gp := nodesOfKind(nodes, graph.KindGenericParam)
|
||||
for _, n := range gp {
|
||||
if n.Name == "T" && strings.Contains(n.ID, "x.py::Repo#tparam") {
|
||||
return // grammar supports PEP 695: assertion satisfied.
|
||||
}
|
||||
}
|
||||
t.Logf("PEP-695 class generic not surfaced; tree-sitter-python "+
|
||||
"version may predate the syntax — got %v", nodeIDs(gp))
|
||||
}
|
||||
|
||||
func TestPyAsyncSpawns_Await(t *testing.T) {
|
||||
src := `async def load(uid):
|
||||
user = await fetch_user(uid)
|
||||
rows = await db.query("...")
|
||||
return user
|
||||
`
|
||||
_, edges := runPyExtract(t, "x.py", src)
|
||||
|
||||
spawns := edgesByKind(edges, graph.EdgeSpawns)
|
||||
want := map[string]bool{"unresolved::fetch_user": false, "unresolved::query": false}
|
||||
for _, e := range spawns {
|
||||
if mode, _ := e.Meta["mode"].(string); mode != "async" {
|
||||
continue
|
||||
}
|
||||
if _, ok := want[e.To]; ok {
|
||||
want[e.To] = true
|
||||
}
|
||||
}
|
||||
for tgt, found := range want {
|
||||
if !found {
|
||||
t.Errorf("expected EdgeSpawns mode=async → %s; got %v", tgt, edgeTargets(spawns))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestPyAsyncSpawns_AsyncioGather(t *testing.T) {
|
||||
src := `import asyncio
|
||||
|
||||
async def loadAll():
|
||||
return await asyncio.gather(loadA(), loadB())
|
||||
`
|
||||
_, edges := runPyExtract(t, "x.py", src)
|
||||
spawns := edgesByKind(edges, graph.EdgeSpawns)
|
||||
hasGather := false
|
||||
for _, e := range spawns {
|
||||
if e.To == "unresolved::asyncio.gather" {
|
||||
hasGather = true
|
||||
}
|
||||
}
|
||||
if !hasGather {
|
||||
t.Errorf("expected EdgeSpawns → asyncio.gather; got %v", edgeTargets(spawns))
|
||||
}
|
||||
}
|
||||
|
||||
func TestCanonicalizePyTypeRef(t *testing.T) {
|
||||
cases := []struct {
|
||||
in, out string
|
||||
}{
|
||||
{"User", "User"},
|
||||
{"Optional[User]", "User"},
|
||||
{"List[User]", "User"},
|
||||
{"list[User]", "User"},
|
||||
{"Tuple[User, int]", "User"},
|
||||
{"User | None", "User"},
|
||||
{"None | User", "User"},
|
||||
{"models.User", "User"},
|
||||
{"pkg.mod.User", "User"},
|
||||
}
|
||||
for _, c := range cases {
|
||||
if got := canonicalizePyTypeRef(c.in); got != c.out {
|
||||
t.Errorf("canonicalizePyTypeRef(%q) = %q, want %q", c.in, got, c.out)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func nodeNames(nodes []*graph.Node) []string {
|
||||
out := make([]string, 0, len(nodes))
|
||||
for _, n := range nodes {
|
||||
out = append(out, n.Name)
|
||||
}
|
||||
return out
|
||||
}
|
||||
Reference in New Issue
Block a user