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
151 lines
4.9 KiB
Go
151 lines
4.9 KiB
Go
package languages
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
"github.com/zzet/gortex/internal/graph"
|
|
)
|
|
|
|
func TestRExtractor_Functions(t *testing.T) {
|
|
src := []byte(`add <- function(a, b) {
|
|
a + b
|
|
}
|
|
|
|
multiply = function(x, y) {
|
|
x * y
|
|
}
|
|
`)
|
|
e := NewRExtractor()
|
|
result, err := e.Extract("math.R", src)
|
|
require.NoError(t, err)
|
|
|
|
funcs := nodesOfKind(result.Nodes, graph.KindFunction)
|
|
names := make([]string, len(funcs))
|
|
for i, f := range funcs {
|
|
names[i] = f.Name
|
|
}
|
|
assert.Contains(t, names, "add")
|
|
assert.Contains(t, names, "multiply")
|
|
}
|
|
|
|
func TestRExtractor_Imports(t *testing.T) {
|
|
src := []byte(`library(ggplot2)
|
|
require(dplyr)
|
|
source("utils.R")
|
|
`)
|
|
e := NewRExtractor()
|
|
result, err := e.Extract("main.R", src)
|
|
require.NoError(t, err)
|
|
|
|
imports := edgesOfKind(result.Edges, graph.EdgeImports)
|
|
assert.Len(t, imports, 3)
|
|
}
|
|
|
|
func TestRExtractor_Variables(t *testing.T) {
|
|
src := []byte(`max_size <- 100
|
|
threshold = 0.5
|
|
name <- "test"
|
|
`)
|
|
e := NewRExtractor()
|
|
result, err := e.Extract("config.R", src)
|
|
require.NoError(t, err)
|
|
|
|
vars := nodesOfKind(result.Nodes, graph.KindVariable)
|
|
varNames := make([]string, len(vars))
|
|
for i, v := range vars {
|
|
varNames[i] = v.Name
|
|
}
|
|
assert.Contains(t, varNames, "max_size")
|
|
assert.Contains(t, varNames, "threshold")
|
|
assert.Contains(t, varNames, "name")
|
|
}
|
|
|
|
// TestRClassSystemsAndDispatch is the C6 test: S4 (setClass+contains,
|
|
// setMethod), R6/Reference classes, and S3 generic.class methods all extract,
|
|
// and a call to a generic reaches its methods through dispatch edges.
|
|
func TestRClassSystemsAndDispatch(t *testing.T) {
|
|
src := []byte("setClass(\"Circle\", contains = \"Shape\")\n" +
|
|
"setGeneric(\"area\", function(obj) standardGeneric(\"area\"))\n" +
|
|
"setMethod(\"area\", \"Circle\", function(obj) pi)\n" +
|
|
"Counter <- R6Class(\"Counter\", public = list())\n" +
|
|
"Acc <- setRefClass(\"Account\")\n" +
|
|
"print.Circle <- function(x) cat(\"c\")\n")
|
|
res, err := NewRExtractor().Extract("m.R", src)
|
|
require.NoError(t, err)
|
|
|
|
kind := map[string]graph.NodeKind{}
|
|
system := map[string]string{}
|
|
for _, n := range res.Nodes {
|
|
kind[n.ID] = n.Kind
|
|
if n.Meta != nil {
|
|
if s, _ := n.Meta["class_system"].(string); s != "" {
|
|
system[n.ID] = s
|
|
}
|
|
}
|
|
}
|
|
assert.Equal(t, graph.KindType, kind["m.R::Circle"], "S4 class")
|
|
assert.Equal(t, "S4", system["m.R::Circle"])
|
|
assert.Equal(t, graph.KindMethod, kind["m.R::area.Circle"], "S4 method")
|
|
assert.Equal(t, graph.KindType, kind["m.R::Counter"], "R6 class")
|
|
assert.Equal(t, "R6", system["m.R::Counter"])
|
|
assert.Equal(t, graph.KindType, kind["m.R::Account"], "Reference class")
|
|
|
|
var s4Dispatch, s3Dispatch, inherit bool
|
|
for _, e := range res.Edges {
|
|
if e.Kind == graph.EdgeCalls && e.From == "m.R::area" && e.To == "m.R::area.Circle" {
|
|
s4Dispatch = true
|
|
}
|
|
if e.Kind == graph.EdgeCalls && e.From == "m.R::print" && e.To == "m.R::print.Circle" {
|
|
s3Dispatch = true
|
|
}
|
|
if e.Kind == graph.EdgeExtends && e.From == "m.R::Circle" && e.To == "unresolved::Shape" {
|
|
inherit = true
|
|
}
|
|
}
|
|
assert.True(t, s4Dispatch, "the S4 generic area should dispatch to its Circle method")
|
|
assert.True(t, s3Dispatch, "the S3 generic print should dispatch to print.Circle")
|
|
assert.True(t, inherit, "setClass contains= should produce an inheritance edge")
|
|
}
|
|
|
|
// TestRPkgFnDispatchPreserved proves the namespace + extract-dispatch recall
|
|
// the tag pass dropped: a `pkg::fn(...)` call preserves its package qualifier
|
|
// in the edge target (so `dplyr::filter` stays distinct from a base-R
|
|
// `filter`), and an `obj$method(...)` extract-dispatch — which the tag pass
|
|
// records as nothing — gets its own receiver-tagged call edge.
|
|
func TestRPkgFnDispatchPreserved(t *testing.T) {
|
|
src := []byte("result <- dplyr::filter(df, x > 1)\nobj$method(a)\nval <- base::sum(v)\nplain(z)\n")
|
|
res, err := NewRExtractor().Extract("a.R", src)
|
|
require.NoError(t, err)
|
|
|
|
namespace := map[string]string{} // call target -> package
|
|
var dollar *graph.Edge
|
|
for _, e := range res.Edges {
|
|
if e.Kind != graph.EdgeCalls {
|
|
continue
|
|
}
|
|
if ns, _ := e.Meta["r_namespace"].(string); ns != "" {
|
|
namespace[e.To] = ns
|
|
}
|
|
if via, _ := e.Meta["via"].(string); via == "dollar_dispatch" {
|
|
dollar = e
|
|
}
|
|
}
|
|
|
|
// Package qualifier preserved, in-place (no bare duplicate).
|
|
assert.Equal(t, "dplyr", namespace["unresolved::dplyr::filter"], "dplyr::filter must carry its package")
|
|
assert.Equal(t, "base", namespace["unresolved::base::sum"], "base::sum must carry its package")
|
|
for _, e := range res.Edges {
|
|
if e.Kind == graph.EdgeCalls && (e.To == "unresolved::filter" || e.To == "unresolved::sum") {
|
|
t.Errorf("namespace call left a stripped bare edge %q (should be rewritten)", e.To)
|
|
}
|
|
}
|
|
|
|
// $-dispatch edge exists and preserves the receiver.
|
|
require.NotNil(t, dollar, "obj$method() must emit a dollar_dispatch call edge")
|
|
assert.Equal(t, "unresolved::method", dollar.To)
|
|
r, _ := dollar.Meta["r_receiver"].(string)
|
|
assert.Equal(t, "obj", r, "dollar dispatch must preserve the receiver")
|
|
}
|