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
268 lines
8.9 KiB
Go
268 lines
8.9 KiB
Go
package languages
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"github.com/zzet/gortex/internal/graph"
|
|
)
|
|
|
|
// refEdge finds the first edge with the given kind whose To matches target
|
|
// and (when useKind != "") whose Meta["ref_context"] matches.
|
|
func findRefEdge(edges []*graph.Edge, kind graph.EdgeKind, target, useKind string) *graph.Edge {
|
|
for _, e := range edges {
|
|
if e.Kind != kind || e.To != target {
|
|
continue
|
|
}
|
|
if useKind != "" {
|
|
if uk, _ := e.Meta["ref_context"].(string); uk != useKind {
|
|
continue
|
|
}
|
|
}
|
|
return e
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func hasEdgeTo(edges []*graph.Edge, target string) bool {
|
|
for _, e := range edges {
|
|
if e.To == target {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// TestPyRefForm_Instantiation: `HttpResponse(...)` emits an instantiates
|
|
// edge to the class, stamped OriginASTResolved with ref_context=instantiate.
|
|
func TestPyRefForm_Instantiation(t *testing.T) {
|
|
src := `from django.http import HttpResponse
|
|
|
|
def view(request):
|
|
return HttpResponse("ok")
|
|
`
|
|
_, edges := runPyExtract(t, "app/views.py", src)
|
|
e := findRefEdge(edges, graph.EdgeInstantiates, "unresolved::HttpResponse", "instantiate")
|
|
if e == nil {
|
|
t.Fatalf("expected EdgeInstantiates -> unresolved::HttpResponse (ref_context=instantiate); edges=%v", edgeDump(edges))
|
|
}
|
|
if e.Origin != graph.OriginASTResolved {
|
|
t.Errorf("instantiate edge Origin = %q, want OriginASTResolved", e.Origin)
|
|
}
|
|
if e.From != "app/views.py::view" {
|
|
t.Errorf("instantiate edge From = %q, want app/views.py::view", e.From)
|
|
}
|
|
}
|
|
|
|
// TestPyRefForm_DottedInstantiation: `http.HttpResponse(...)` — the
|
|
// Capitalized leaf attribute is the constructed type.
|
|
func TestPyRefForm_DottedInstantiation(t *testing.T) {
|
|
src := `import django.http as http
|
|
|
|
def view(request):
|
|
return http.HttpResponse("ok")
|
|
`
|
|
_, edges := runPyExtract(t, "app/v.py", src)
|
|
if findRefEdge(edges, graph.EdgeInstantiates, "unresolved::HttpResponse", "instantiate") == nil {
|
|
t.Fatalf("expected instantiate edge for http.HttpResponse(...); edges=%v", edgeDump(edges))
|
|
}
|
|
}
|
|
|
|
// TestPyRefForm_Inheritance: `class V(HttpResponse):` emits a references
|
|
// edge (ref_context=inherit), OriginASTResolved.
|
|
func TestPyRefForm_Inheritance(t *testing.T) {
|
|
src := `from django.http import HttpResponse
|
|
|
|
class MyResponse(HttpResponse):
|
|
pass
|
|
`
|
|
_, edges := runPyExtract(t, "app/r.py", src)
|
|
e := findRefEdge(edges, graph.EdgeReferences, "unresolved::HttpResponse", "inherit")
|
|
if e == nil {
|
|
t.Fatalf("expected EdgeReferences -> unresolved::HttpResponse (ref_context=inherit); edges=%v", edgeDump(edges))
|
|
}
|
|
if e.Origin != graph.OriginASTResolved {
|
|
t.Errorf("inherit edge Origin = %q, want OriginASTResolved (else cross_pkg_guard reverts it)", e.Origin)
|
|
}
|
|
}
|
|
|
|
// TestPyRefForm_DottedInheritance: `class V(http.HttpResponse):`.
|
|
func TestPyRefForm_DottedInheritance(t *testing.T) {
|
|
src := `import django.http as http
|
|
|
|
class MyResponse(http.HttpResponse):
|
|
pass
|
|
`
|
|
_, edges := runPyExtract(t, "app/r2.py", src)
|
|
if findRefEdge(edges, graph.EdgeReferences, "unresolved::HttpResponse", "inherit") == nil {
|
|
t.Fatalf("expected inherit edge for http.HttpResponse base; edges=%v", edgeDump(edges))
|
|
}
|
|
}
|
|
|
|
// TestPyRefForm_IsInstance: `isinstance(x, HttpResponse)` references the
|
|
// 2nd argument as a type (ref_context=cast). Tuple second arg references each.
|
|
func TestPyRefForm_IsInstance(t *testing.T) {
|
|
src := `from django.http import HttpResponse, JsonResponse
|
|
|
|
def check(x):
|
|
if isinstance(x, HttpResponse):
|
|
return True
|
|
return issubclass(type(x), (HttpResponse, JsonResponse))
|
|
`
|
|
_, edges := runPyExtract(t, "app/c.py", src)
|
|
e := findRefEdge(edges, graph.EdgeReferences, "unresolved::HttpResponse", "cast")
|
|
if e == nil {
|
|
t.Fatalf("expected EdgeReferences -> unresolved::HttpResponse (ref_context=cast); edges=%v", edgeDump(edges))
|
|
}
|
|
if e.Origin != graph.OriginASTResolved {
|
|
t.Errorf("cast edge Origin = %q, want OriginASTResolved", e.Origin)
|
|
}
|
|
if findRefEdge(edges, graph.EdgeReferences, "unresolved::JsonResponse", "cast") == nil {
|
|
t.Errorf("expected cast edge for JsonResponse in the issubclass tuple; edges=%v", edgeDump(edges))
|
|
}
|
|
// The first arg of isinstance (`x`) must NOT produce a type ref.
|
|
if hasEdgeTo(edges, "unresolved::x") {
|
|
t.Errorf("first isinstance arg `x` must not emit a type reference")
|
|
}
|
|
}
|
|
|
|
// TestPyRefForm_StaticAccess: `HttpResponse.status_code` emits a
|
|
// references edge (ref_context=static_access) to the class.
|
|
func TestPyRefForm_StaticAccess(t *testing.T) {
|
|
src := `from django.http import HttpResponse
|
|
|
|
def f():
|
|
return HttpResponse.status_code
|
|
`
|
|
_, edges := runPyExtract(t, "app/s.py", src)
|
|
e := findRefEdge(edges, graph.EdgeReferences, "unresolved::HttpResponse", "static_access")
|
|
if e == nil {
|
|
t.Fatalf("expected EdgeReferences -> unresolved::HttpResponse (ref_context=static_access); edges=%v", edgeDump(edges))
|
|
}
|
|
if e.Origin != graph.OriginASTResolved {
|
|
t.Errorf("static_access edge Origin = %q, want OriginASTResolved", e.Origin)
|
|
}
|
|
}
|
|
|
|
// TestPyRefForm_Decorator: `@Validator(...)` / `@Validator` references the
|
|
// Capitalized decorator name; lowercase `@property` / `@app.route` do not.
|
|
func TestPyRefForm_Decorator(t *testing.T) {
|
|
src := `from x import Validator
|
|
|
|
@Validator
|
|
def a():
|
|
pass
|
|
|
|
@Validator("strict")
|
|
def b():
|
|
pass
|
|
|
|
@property
|
|
def c(self):
|
|
pass
|
|
|
|
@app.route("/x")
|
|
def d():
|
|
pass
|
|
`
|
|
_, edges := runPyExtract(t, "app/d.py", src)
|
|
if findRefEdge(edges, graph.EdgeReferences, "unresolved::Validator", "static_access") == nil {
|
|
t.Fatalf("expected reference edge for @Validator decorator; edges=%v", edgeDump(edges))
|
|
}
|
|
if hasEdgeTo(edges, "unresolved::property") {
|
|
t.Errorf("lowercase decorator @property must not emit a type reference")
|
|
}
|
|
if hasEdgeTo(edges, "unresolved::route") {
|
|
t.Errorf("lowercase decorator @app.route must not emit a type reference")
|
|
}
|
|
}
|
|
|
|
// TestPyRefForm_NegativeCases: lowercase calls, method calls on instances,
|
|
// and bare builtins must not emit reference/instantiate edges.
|
|
func TestPyRefForm_NegativeCases(t *testing.T) {
|
|
src := `def run(obj):
|
|
foo()
|
|
obj.method()
|
|
x = dict()
|
|
y = list()
|
|
z = helper.compute()
|
|
return x, y, z
|
|
`
|
|
_, edges := runPyExtract(t, "app/n.py", src)
|
|
|
|
for _, bad := range []string{
|
|
"unresolved::foo", "unresolved::method", "unresolved::compute",
|
|
"unresolved::helper",
|
|
} {
|
|
for _, e := range edges {
|
|
if (e.Kind == graph.EdgeInstantiates || (e.Kind == graph.EdgeReferences && e.Meta["ref_context"] != nil)) && e.To == bad {
|
|
t.Errorf("lowercase callee/method %q must not emit an instantiate/reference type edge (got kind=%s)", bad, e.Kind)
|
|
}
|
|
}
|
|
}
|
|
// Lowercase builtins dict()/list() must not produce instantiate edges.
|
|
for _, b := range []string{"unresolved::dict", "unresolved::list"} {
|
|
if findRefEdge(edges, graph.EdgeInstantiates, b, "instantiate") != nil {
|
|
t.Errorf("lowercase builtin %q must not emit an instantiate edge", b)
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestPyRefForm_GenericArgs: element types inside a type-annotation
|
|
// subscript (`List[Foo]`, `Dict[str, Bar]`, nested `Dict[str, List[Foo]]`,
|
|
// and a parameter annotation) emit a "generic_arg" reference to each named
|
|
// type, OriginASTResolved. Builtins (str/int) inside the subscript and a
|
|
// runtime subscript (`arr[0]`) emit NO type reference.
|
|
func TestPyRefForm_GenericArgs(t *testing.T) {
|
|
src := `from m import Foo, Bar, Baz
|
|
|
|
xs: List[Foo] = []
|
|
mapping: Dict[str, Bar] = {}
|
|
nested: Dict[str, List[Foo]] = {}
|
|
|
|
def handle(items: List[Baz]) -> None:
|
|
arr = [1, 2, 3]
|
|
return arr[0]
|
|
`
|
|
_, edges := runPyExtract(t, "app/g.py", src)
|
|
|
|
for _, want := range []string{"Foo", "Bar", "Baz"} {
|
|
e := findRefEdge(edges, graph.EdgeReferences, "unresolved::"+want, "generic_arg")
|
|
if e == nil {
|
|
t.Fatalf("expected EdgeReferences -> unresolved::%s (ref_context=generic_arg); edges=%v", want, edgeDump(edges))
|
|
}
|
|
if e.Origin != graph.OriginASTResolved {
|
|
t.Errorf("generic_arg edge for %s Origin = %q, want OriginASTResolved", want, e.Origin)
|
|
}
|
|
}
|
|
|
|
// The element type inside the parameter annotation is attributed to the
|
|
// enclosing function, not the file.
|
|
if e := findRefEdge(edges, graph.EdgeReferences, "unresolved::Baz", "generic_arg"); e != nil && e.From != "app/g.py::handle" {
|
|
t.Errorf("parameter generic_arg From = %q, want app/g.py::handle", e.From)
|
|
}
|
|
|
|
// Builtins inside the subscript (str, int) must not produce a type ref.
|
|
for _, bad := range []string{"unresolved::str", "unresolved::int"} {
|
|
if hasEdgeTo(edges, bad) {
|
|
t.Errorf("builtin %q inside a subscript must not emit a type reference", bad)
|
|
}
|
|
}
|
|
|
|
// A runtime subscript `arr[0]` must not emit any generic_arg reference.
|
|
for _, e := range edges {
|
|
if uk, _ := e.Meta["ref_context"].(string); uk == "generic_arg" && e.To == "unresolved::arr" {
|
|
t.Errorf("runtime subscript arr[0] must not emit a generic_arg reference")
|
|
}
|
|
}
|
|
}
|
|
|
|
// edgeDump renders edges compactly for failure messages.
|
|
func edgeDump(edges []*graph.Edge) []string {
|
|
out := make([]string, 0, len(edges))
|
|
for _, e := range edges {
|
|
uk, _ := e.Meta["ref_context"].(string)
|
|
out = append(out, string(e.Kind)+" "+e.From+" -> "+e.To+" ["+uk+"]")
|
|
}
|
|
return out
|
|
}
|