@@ -2,10 +2,13 @@ package ferret
22
33import (
44 "context"
5+ "errors"
56 "strings"
67 "testing"
78
9+ "github.com/MontFerret/ferret/v2/pkg/debugger"
810 "github.com/MontFerret/ferret/v2/pkg/diagnostics"
11+ "github.com/MontFerret/ferret/v2/pkg/runtime"
912 "github.com/MontFerret/ferret/v2/pkg/source"
1013)
1114
@@ -303,4 +306,140 @@ RETURN y`
303306 if ! strings .Contains (formatted , "udf-error.fql:4" ) || ! strings .Contains (formatted , "RETURN b / 0" ) {
304307 t .Fatalf ("expected UDF error location, got:\n %s" , formatted )
305308 }
309+ assertFrameValues (t , session , 1 , map [string ]string {"x" : "1" })
310+ value , err := session .EvaluateFrame (context .Background (), 1 , "x + 1" )
311+ if err != nil || value .Display != "2" {
312+ t .Fatalf ("unexpected runtime-error caller evaluation: %#v, %v" , value , err )
313+ }
314+ }
315+
316+ func TestDebugSessionCallerFramesExposeBindingsCellsAndParameters (t * testing.T ) {
317+ engine , err := New ()
318+ if err != nil {
319+ t .Fatal (err )
320+ }
321+ defer engine .Close ()
322+
323+ query := `VAR shared = 1
324+ FUNC outer(p) {
325+ VAR x = p + shared - shared
326+ FUNC inner(q) {
327+ shared = shared + q
328+ LET x = shared + q
329+ RETURN x
330+ }
331+ LET result = inner(3)
332+ RETURN result
333+ }
334+ LET box = {value: 10}
335+ LET x = 10
336+ RETURN outer(2) + x + @input + box.value - 10`
337+ plan , err := engine .CompileDebug (context .Background (), source .New ("caller-frames.fql" , query ))
338+ if err != nil {
339+ t .Fatal (err )
340+ }
341+ defer plan .Close ()
342+
343+ session , err := plan .NewDebugSession (context .Background (), WithSessionParam ("input" , 5 ))
344+ if err != nil {
345+ t .Fatal (err )
346+ }
347+ defer session .Close ()
348+
349+ breakpoint , err := session .SetBreakpoint ("caller-frames.fql" , 5 )
350+ if err != nil {
351+ t .Fatal (err )
352+ }
353+ if ! breakpoint .Bound || breakpoint .Line != 5 {
354+ t .Fatalf ("unexpected inner breakpoint: %#v" , breakpoint )
355+ }
356+
357+ if _ , err := session .Start (context .Background ()); err != nil {
358+ t .Fatal (err )
359+ }
360+ event , err := session .Continue (context .Background ())
361+ if err != nil {
362+ t .Fatal (err )
363+ }
364+ if event .Reason != DebugReasonBreakpoint || event .Location .Line != 5 || event .Depth != 2 {
365+ t .Fatalf ("unexpected nested stop: %#v" , event )
366+ }
367+
368+ frames , err := session .Frames ()
369+ if err != nil {
370+ t .Fatal (err )
371+ }
372+ if len (frames ) != 3 || frames [0 ].Name != "inner" || frames [1 ].Name != "outer" || frames [2 ].Name != "<main>" {
373+ t .Fatalf ("unexpected nested frames: %#v" , frames )
374+ }
375+
376+ assertFrameValues (t , session , 0 , map [string ]string {"q" : "3" , "shared" : "1" , "@input" : "5" })
377+ assertFrameValues (t , session , 1 , map [string ]string {"p" : "2" , "x" : "2" , "shared" : "1" , "@input" : "5" })
378+ assertFrameValues (t , session , 2 , map [string ]string {"x" : "10" , "shared" : "1" , "@input" : "5" })
379+
380+ callerLocals , err := session .FrameLocals (2 )
381+ if err != nil {
382+ t .Fatal (err )
383+ }
384+ var callerReference debugger.ValueReference
385+ for _ , local := range callerLocals {
386+ if local .Name == "box" {
387+ callerReference = local .Value .Reference
388+ }
389+ }
390+ if ! callerReference .Valid () {
391+ t .Fatalf ("expected expandable caller value: %#v" , callerLocals )
392+ }
393+
394+ value , err := session .EvaluateFrame (context .Background (), 2 , "x + shared + @input" )
395+ if err != nil || value .Display != "16" {
396+ t .Fatalf ("unexpected caller evaluation: %#v, %v" , value , err )
397+ }
398+ if _ , err := session .FrameLocals (- 1 ); ! errors .Is (err , runtime .ErrInvalidArgument ) {
399+ t .Fatalf ("expected negative frame rejection, got %v" , err )
400+ }
401+ if _ , err := session .FrameLocals (len (frames )); ! errors .Is (err , runtime .ErrNotFound ) {
402+ t .Fatalf ("expected missing frame rejection, got %v" , err )
403+ }
404+
405+ event , err = session .Step (context .Background ())
406+ if err != nil {
407+ t .Fatal (err )
408+ }
409+ if event .Location .Line != 6 {
410+ t .Fatalf ("unexpected mutable-cell step: %#v" , event )
411+ }
412+ if _ , err := session .Variables (callerReference ); ! errors .Is (err , runtime .ErrNotFound ) {
413+ t .Fatalf ("expected caller reference to become stale on resume, got %v" , err )
414+ }
415+ assertFrameValues (t , session , 0 , map [string ]string {"shared" : "4" , "@input" : "5" })
416+ assertFrameValues (t , session , 1 , map [string ]string {"x" : "2" , "shared" : "4" , "@input" : "5" })
417+ assertFrameValues (t , session , 2 , map [string ]string {"x" : "10" , "shared" : "4" , "@input" : "5" })
418+
419+ value , err = session .EvaluateFrame (context .Background (), 1 , "x + shared + @input" )
420+ if err != nil || value .Display != "11" {
421+ t .Fatalf ("unexpected mutable caller evaluation: %#v, %v" , value , err )
422+ }
423+ }
424+
425+ func assertFrameValues (t * testing.T , session interface {
426+ FrameLocals (int ) ([]debugger.Variable , error )
427+ }, frame int , expected map [string ]string ) {
428+ t .Helper ()
429+
430+ locals , err := session .FrameLocals (frame )
431+ if err != nil {
432+ t .Fatal (err )
433+ }
434+
435+ actual := make (map [string ]string , len (locals ))
436+ for _ , local := range locals {
437+ actual [local .Name ] = local .Value .Display
438+ }
439+
440+ for name , value := range expected {
441+ if actual [name ] != value {
442+ t .Fatalf ("frame %d binding %q: expected %q, got %q in %#v" , frame , name , value , actual [name ], locals )
443+ }
444+ }
306445}
0 commit comments