Skip to content

Commit 965dca0

Browse files
authored
feat(dsl): initialize executor defaults and bridge layer tests (PR-03) (#594)
Part 3/8 of V5 QueryType × VDG Integration. Initialize DataflowExecutor Config/Diagnostics defaults, add bridge layer tests.
1 parent cf87f1b commit 965dca0

File tree

2 files changed

+210
-2
lines changed

2 files changed

+210
-2
lines changed

sast-engine/dsl/bridge_test.go

Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
package dsl
2+
3+
import (
4+
"testing"
5+
6+
"github.com/shivasurya/code-pathfinder/sast-engine/graph/callgraph/core"
7+
)
8+
9+
func TestExtractTargetPatterns_Basic(t *testing.T) {
10+
executor := NewDataflowExecutor(&DataflowIR{}, core.NewCallGraph())
11+
12+
matches := []CallSiteMatch{
13+
{CallSite: core.CallSite{Target: "eval", TargetFQN: "builtins.eval"}, FunctionFQN: "main.foo", Line: 1},
14+
{CallSite: core.CallSite{Target: "exec", TargetFQN: ""}, FunctionFQN: "main.bar", Line: 2},
15+
}
16+
17+
patterns := executor.extractTargetPatterns(matches)
18+
19+
expected := map[string]bool{"eval": true, "builtins.eval": true, "exec": true}
20+
for _, p := range patterns {
21+
if !expected[p] {
22+
t.Errorf("unexpected pattern: %q", p)
23+
}
24+
delete(expected, p)
25+
}
26+
for missing := range expected {
27+
t.Errorf("missing expected pattern: %q", missing)
28+
}
29+
}
30+
31+
func TestExtractTargetPatterns_DottedTarget(t *testing.T) {
32+
executor := NewDataflowExecutor(&DataflowIR{}, core.NewCallGraph())
33+
34+
matches := []CallSiteMatch{
35+
{CallSite: core.CallSite{Target: "cursor.execute"}, FunctionFQN: "main.foo", Line: 1},
36+
}
37+
38+
patterns := executor.extractTargetPatterns(matches)
39+
40+
// Should include "cursor.execute" and bare name "execute"
41+
found := map[string]bool{}
42+
for _, p := range patterns {
43+
found[p] = true
44+
}
45+
if !found["cursor.execute"] {
46+
t.Error("expected 'cursor.execute' in patterns")
47+
}
48+
if !found["execute"] {
49+
t.Error("expected bare name 'execute' in patterns")
50+
}
51+
}
52+
53+
func TestExtractTargetPatterns_Deduplication(t *testing.T) {
54+
executor := NewDataflowExecutor(&DataflowIR{}, core.NewCallGraph())
55+
56+
matches := []CallSiteMatch{
57+
{CallSite: core.CallSite{Target: "eval"}, FunctionFQN: "main.foo", Line: 1},
58+
{CallSite: core.CallSite{Target: "eval"}, FunctionFQN: "main.bar", Line: 2},
59+
}
60+
61+
patterns := executor.extractTargetPatterns(matches)
62+
63+
if len(patterns) != 1 {
64+
t.Errorf("expected 1 pattern after dedup, got %d: %v", len(patterns), patterns)
65+
}
66+
}
67+
68+
func TestExtractTargetPatterns_Empty(t *testing.T) {
69+
executor := NewDataflowExecutor(&DataflowIR{}, core.NewCallGraph())
70+
71+
patterns := executor.extractTargetPatterns(nil)
72+
73+
if len(patterns) != 0 {
74+
t.Errorf("expected 0 patterns for nil input, got %d", len(patterns))
75+
}
76+
}
77+
78+
func TestFindFunctionsWithSourcesAndSinks_Intersection(t *testing.T) {
79+
executor := NewDataflowExecutor(&DataflowIR{}, core.NewCallGraph())
80+
81+
sources := []CallSiteMatch{
82+
{FunctionFQN: "pkg.funcA", Line: 1},
83+
{FunctionFQN: "pkg.funcB", Line: 2},
84+
}
85+
sinks := []CallSiteMatch{
86+
{FunctionFQN: "pkg.funcB", Line: 5},
87+
{FunctionFQN: "pkg.funcC", Line: 3},
88+
}
89+
90+
result := executor.findFunctionsWithSourcesAndSinks(sources, sinks)
91+
92+
if len(result) != 1 || result[0] != "pkg.funcB" {
93+
t.Errorf("expected [pkg.funcB], got %v", result)
94+
}
95+
}
96+
97+
func TestFindFunctionsWithSourcesAndSinks_NoOverlap(t *testing.T) {
98+
executor := NewDataflowExecutor(&DataflowIR{}, core.NewCallGraph())
99+
100+
sources := []CallSiteMatch{
101+
{FunctionFQN: "pkg.funcA", Line: 1},
102+
}
103+
sinks := []CallSiteMatch{
104+
{FunctionFQN: "pkg.funcB", Line: 2},
105+
}
106+
107+
result := executor.findFunctionsWithSourcesAndSinks(sources, sinks)
108+
109+
if len(result) != 0 {
110+
t.Errorf("expected no overlap, got %v", result)
111+
}
112+
}
113+
114+
func TestFindFunctionsWithSourcesAndSinks_Deduplication(t *testing.T) {
115+
executor := NewDataflowExecutor(&DataflowIR{}, core.NewCallGraph())
116+
117+
sources := []CallSiteMatch{
118+
{FunctionFQN: "pkg.funcA", Line: 1},
119+
{FunctionFQN: "pkg.funcA", Line: 3},
120+
}
121+
sinks := []CallSiteMatch{
122+
{FunctionFQN: "pkg.funcA", Line: 5},
123+
{FunctionFQN: "pkg.funcA", Line: 7},
124+
}
125+
126+
result := executor.findFunctionsWithSourcesAndSinks(sources, sinks)
127+
128+
if len(result) != 1 {
129+
t.Errorf("expected 1 function (deduped), got %d: %v", len(result), result)
130+
}
131+
}
132+
133+
func TestResolveMatchers_CallMatcher(t *testing.T) {
134+
cg := core.NewCallGraph()
135+
cg.CallSites["test.module.handler"] = []core.CallSite{
136+
{Target: "os.getenv", TargetFQN: "os.getenv", Location: core.Location{Line: 5}},
137+
{Target: "eval", TargetFQN: "builtins.eval", Location: core.Location{Line: 10}},
138+
}
139+
140+
ir := &DataflowIR{
141+
Sources: toRawMessages(CallMatcherIR{Type: "call_matcher", Patterns: []string{"os.getenv"}}),
142+
Sinks: toRawMessages(CallMatcherIR{Type: "call_matcher", Patterns: []string{"eval"}}),
143+
Sanitizers: emptyRawMessages(),
144+
Scope: "local",
145+
}
146+
147+
executor := NewDataflowExecutor(ir, cg)
148+
149+
sourceMatches := executor.resolveMatchers(ir.Sources)
150+
sinkMatches := executor.resolveMatchers(ir.Sinks)
151+
152+
if len(sourceMatches) == 0 {
153+
t.Fatal("expected source matches for os.getenv")
154+
}
155+
if len(sinkMatches) == 0 {
156+
t.Fatal("expected sink matches for eval")
157+
}
158+
159+
// Verify match properties
160+
if sourceMatches[0].CallSite.Target != "os.getenv" {
161+
t.Errorf("expected source target 'os.getenv', got %q", sourceMatches[0].CallSite.Target)
162+
}
163+
if sourceMatches[0].FunctionFQN != "test.module.handler" {
164+
t.Errorf("expected FunctionFQN 'test.module.handler', got %q", sourceMatches[0].FunctionFQN)
165+
}
166+
}
167+
168+
func TestResolveMatchers_EmptyInput(t *testing.T) {
169+
executor := NewDataflowExecutor(&DataflowIR{}, core.NewCallGraph())
170+
171+
result := executor.resolveMatchers(emptyRawMessages())
172+
173+
if len(result) != 0 {
174+
t.Errorf("expected 0 matches for empty input, got %d", len(result))
175+
}
176+
}
177+
178+
func TestResolveMatchers_NilInput(t *testing.T) {
179+
executor := NewDataflowExecutor(&DataflowIR{}, core.NewCallGraph())
180+
181+
result := executor.resolveMatchers(nil)
182+
183+
if len(result) != 0 {
184+
t.Errorf("expected 0 matches for nil input, got %d", len(result))
185+
}
186+
}
187+
188+
func TestNewDataflowExecutor_InitializesDefaults(t *testing.T) {
189+
ir := &DataflowIR{Scope: "local"}
190+
cg := core.NewCallGraph()
191+
192+
executor := NewDataflowExecutor(ir, cg)
193+
194+
if executor.Config == nil {
195+
t.Error("Config should be initialized with defaults")
196+
}
197+
if executor.Diagnostics == nil {
198+
t.Error("Diagnostics should be initialized with defaults")
199+
}
200+
if executor.IR != ir {
201+
t.Error("IR should be set")
202+
}
203+
if executor.CallGraph != cg {
204+
t.Error("CallGraph should be set")
205+
}
206+
}

sast-engine/dsl/dataflow_executor.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,10 @@ type DataflowExecutor struct {
2121
// NewDataflowExecutor creates a new executor.
2222
func NewDataflowExecutor(ir *DataflowIR, cg *core.CallGraph) *DataflowExecutor {
2323
return &DataflowExecutor{
24-
IR: ir,
25-
CallGraph: cg,
24+
IR: ir,
25+
CallGraph: cg,
26+
Config: DefaultConfig(),
27+
Diagnostics: NewDiagnosticCollector(),
2628
}
2729
}
2830

0 commit comments

Comments
 (0)