github.com/cnboonhan/delve@v0.0.0-20230908061759-363f2388c2fb/pkg/terminal/starlark_test.go (about)

     1  package terminal
     2  
     3  import (
     4  	"fmt"
     5  	"strings"
     6  	"testing"
     7  )
     8  
     9  func TestStarlarkExamples(t *testing.T) {
    10  	withTestTerminal("testvariables2", t, func(term *FakeTerminal) {
    11  		term.MustExec("continue")
    12  		t.Run("goroutine_start_line", func(t *testing.T) { testStarlarkExampleGoroutineStartLine(t, term) })
    13  		t.Run("create_breakpoint_main", func(t *testing.T) { testStarlarkExampleCreateBreakpointmain(t, term) })
    14  		t.Run("switch_to_main_goroutine", func(t *testing.T) { testStarlarkExampleSwitchToMainGoroutine(t, term) })
    15  		t.Run("linked_list", func(t *testing.T) { testStarlarkExampleLinkedList(t, term) })
    16  		t.Run("echo_expr", func(t *testing.T) { testStarlarkEchoExpr(t, term) })
    17  		t.Run("find_array", func(t *testing.T) { testStarlarkFindArray(t, term) })
    18  		t.Run("map_iteration", func(t *testing.T) { testStarlarkMapIteration(t, term) })
    19  	})
    20  }
    21  
    22  func testStarlarkExampleGoroutineStartLine(t *testing.T, term *FakeTerminal) {
    23  	term.MustExec("source " + findStarFile("goroutine_start_line"))
    24  	out1 := term.MustExec("gsl")
    25  	t.Logf("gsl: %q", out1)
    26  	if !strings.Contains(out1, "func main() {") {
    27  		t.Fatalf("goroutine_start_line example failed")
    28  	}
    29  }
    30  
    31  func testStarlarkExampleCreateBreakpointmain(t *testing.T, term *FakeTerminal) {
    32  	out2p1 := term.MustExec("source " + findStarFile("create_breakpoint_main"))
    33  	t.Logf("create_breakpoint_main: %s", out2p1)
    34  	out2p2 := term.MustExec("breakpoints")
    35  	t.Logf("breakpoints: %q", out2p2)
    36  	if !strings.Contains(out2p2, "main.afunc1") {
    37  		t.Fatalf("create_breakpoint_runtime_func example failed")
    38  	}
    39  }
    40  
    41  func testStarlarkExampleSwitchToMainGoroutine(t *testing.T, term *FakeTerminal) {
    42  	gs, _, err := term.client.ListGoroutines(0, 0)
    43  	if err != nil {
    44  		t.Fatal(err)
    45  	}
    46  	for _, g := range gs {
    47  		if g.ID != 1 {
    48  			t.Logf("switching to goroutine %d\n", g.ID)
    49  			term.MustExec(fmt.Sprintf("goroutine %d", g.ID))
    50  			break
    51  		}
    52  	}
    53  	term.MustExec("source " + findStarFile("switch_to_main_goroutine"))
    54  	out3p1 := term.MustExec("switch_to_main_goroutine")
    55  	out3p2 := term.MustExec("goroutine")
    56  	t.Logf("%q\n", out3p1)
    57  	t.Logf("%q\n", out3p2)
    58  	if !strings.Contains(out3p2, "Goroutine 1:\n") {
    59  		t.Fatalf("switch_to_main_goroutine example failed")
    60  	}
    61  }
    62  
    63  func testStarlarkExampleLinkedList(t *testing.T, term *FakeTerminal) {
    64  	term.MustExec("source " + findStarFile("linked_list"))
    65  	out := term.MustExec("linked_list ll Next 3")
    66  	t.Logf("%q\n", out)
    67  	if n := len(strings.Split(strings.TrimRight(out, "\n"), "\n")); n != 3 {
    68  		t.Fatalf("wrong number of lines in output %d", n)
    69  	}
    70  
    71  	out = term.MustExec("linked_list ll Next 100")
    72  	t.Logf("%q\n", out)
    73  	lines := strings.Split(strings.TrimRight(out, "\n"), "\n")
    74  	for i, line := range lines {
    75  		if i == 5 {
    76  			if line != "5: *main.List nil" {
    77  				t.Errorf("mismatched line %d %q", i, line)
    78  			}
    79  		} else {
    80  			if !strings.HasPrefix(line, fmt.Sprintf("%d: *main.List {N: %d, Next: ", i, i)) {
    81  				t.Errorf("mismatched line %d %q", i, line)
    82  			}
    83  		}
    84  	}
    85  	if len(lines) != 6 {
    86  		t.Fatalf("wrong number of output lines %d", len(lines))
    87  	}
    88  }
    89  
    90  func testStarlarkEchoExpr(t *testing.T, term *FakeTerminal) {
    91  	term.MustExec("source " + findStarFile("echo_expr"))
    92  	out := term.MustExec("echo_expr 2+2, 1-1, 2*3")
    93  	t.Logf("echo_expr %q", out)
    94  	if out != "a 4 b 0 c 6\n" {
    95  		t.Error("output mismatch")
    96  	}
    97  }
    98  
    99  func testStarlarkFindArray(t *testing.T, term *FakeTerminal) {
   100  	term.MustExec("source " + findStarFile("find_array"))
   101  	out := term.MustExec(`find_array "s2", lambda x: x.A == 5`)
   102  	t.Logf("find_array (1) %q", out)
   103  	if out != "found 2\n" {
   104  		t.Error("output mismatch")
   105  	}
   106  	out = term.MustExec(`find_array "s2", lambda x: x.A == 20`)
   107  	t.Logf("find_array (2) %q", out)
   108  	if out != "not found\n" {
   109  		t.Error("output mismatch")
   110  	}
   111  }
   112  
   113  func testStarlarkMapIteration(t *testing.T, term *FakeTerminal) {
   114  	out := term.MustExec("source " + findStarFile("starlark_map_iteration"))
   115  	if !strings.Contains(out, "values=66") {
   116  		t.Fatalf("testStarlarkMapIteration example failed")
   117  	}
   118  	t.Logf("%s", out)
   119  }
   120  
   121  func TestStarlarkVariable(t *testing.T) {
   122  	withTestTerminal("testvariables2", t, func(term *FakeTerminal) {
   123  		term.MustExec("continue")
   124  		for _, tc := range []struct{ expr, tgt string }{
   125  			{`v = eval(None, "i1").Variable; print(v.Value)`, "1"},
   126  			{`v = eval(None, "f1").Variable; print(v.Value)`, "3.0"},
   127  			{`v = eval(None, "as1").Variable; print(v.Value.A)`, "1"},
   128  			{`v = eval(None, "as1").Variable; print(v.Value.B)`, "1"},
   129  			{`v = eval(None, "as1").Variable; print(v.Value["A"])`, "1"},
   130  			{`v = eval(None, "s1").Variable; print(v.Value[0])`, "one"},
   131  			{`v = eval(None, "nilstruct").Variable; print(v.Value)`, "*main.astruct nil"},
   132  			{`v = eval(None, "nilstruct").Variable; print(v.Value[0])`, "None"},
   133  			{`v = eval(None, 'm1["Malone"]').Variable; print(v.Value)`, "main.astruct {A: 2, B: 3}"},
   134  			{`v = eval(None, "m1").Variable; print(v.Value["Malone"])`, "main.astruct {A: 2, B: 3}"},
   135  			{`v = eval(None, "m2").Variable; print(v.Value[1])`, "*main.astruct {A: 10, B: 11}"},
   136  			{`v = eval(None, "c1.pb").Variable; print(v.Value)`, "*main.bstruct {a: main.astruct {A: 1, B: 2}}"},
   137  			{`v = eval(None, "c1.pb").Variable; print(v.Value[0])`, "main.bstruct {a: main.astruct {A: 1, B: 2}}"},
   138  			{`v = eval(None, "c1.pb").Variable; print(v.Value.a.B)`, "2"},
   139  			{`v = eval(None, "iface1").Variable; print(v.Value[0])`, "*main.astruct {A: 1, B: 2}"},
   140  			{`v = eval(None, "iface1").Variable; print(v.Value[0][0])`, "main.astruct {A: 1, B: 2}"},
   141  			{`v = eval(None, "iface1").Variable; print(v.Value.A)`, "1"},
   142  
   143  			{`v = eval(None, "as1", {"MaxStructFields": -1}).Variable; print(v.Value.A)`, "1"},
   144  			{`v = eval({"GoroutineID": -1}, "as1").Variable; print(v.Value.A)`, "1"},
   145  			{`v = eval(cur_scope(), "as1").Variable; print(v.Value.A)`, "1"},
   146  			{`v = eval(None, "as1", default_load_config()).Variable; print(v.Value.A)`, "1"},
   147  
   148  			// From starlark.md's examples
   149  			{`v = eval(None, "s2").Variable; print(v.Value[0])`, "main.astruct {A: 1, B: 2}"},
   150  			{`v = eval(None, "s2").Variable; print(v.Value[1].A)`, "3"},
   151  			{`v = eval(None, "s2").Variable; print(v.Value[1].A + 10)`, "13"},
   152  			{`v = eval(None, "s2").Variable; print(v.Value[1]["B"])`, "4"},
   153  		} {
   154  			out := strings.TrimSpace(term.MustExecStarlark(tc.expr))
   155  			if out != tc.tgt {
   156  				t.Errorf("for %q\nexpected %q\ngot %q", tc.expr, tc.tgt, out)
   157  			}
   158  		}
   159  	})
   160  }
   161  
   162  // Test that pointer variables that were not loaded don't lead to crashes when
   163  // used in Starlark scripts.
   164  func TestStarlarkVariablePointerNotLoaded(t *testing.T) {
   165  	withTestTerminal("testvariables_pointers_not_loaded", t, func(term *FakeTerminal) {
   166  		term.MustExec("continue")
   167  
   168  		// We're going to partially load some variables through the eval()
   169  		// builtin. Then we're going to attempt to evaluate expressions which
   170  		// try to access a field from a pointer variable that wasn't loaded
   171  		// (i.e. a ptrVariableAsStarlarkValue with no children). The tests will
   172  		// verify that we get an error. This is a regression test; we used to
   173  		// panic.
   174  		for _, tc := range []struct{ name, script, expErr string }{
   175  			{
   176  				// In this test, we'll load v. v will have a child (i.e.
   177  				// v.Children[0]), but because v is nil, v.Children[0] will not
   178  				// be loaded. We'll turn v.Children[0] into a
   179  				// ptrVariableAsStarlarkValue, and attempt to access a field on
   180  				// it.
   181  				//
   182  				// v                   -> structAsStarlarkValue<api.Variable<**int>>
   183  				// v.Children[0]       -> structAsStarlarkValue<api.Variable<*int>>
   184  				// v.Children[0].Value -> ptrVariableAsStarlarkValue<*int> ; this pointer variable has not been loaded
   185  				name: "partial load because of nil ptr",
   186  				script: `
   187  v = eval(
   188  		None, "ptrPtr", None
   189  	).Variable
   190  v.Children[0].Value.XXX
   191  `,
   192  				expErr: "*int has no .XXX field or method",
   193  			},
   194  			{
   195  				// In this test, MaxStructFields = 10 and MaxVariableRecurse = 0
   196  				// will cause us to load v, and v.Children[0] (in the sense that
   197  				// v.Children[0] has a child), but _not_ load
   198  				// v.Children[0].Children[0] (because the recursion limit is
   199  				// exhausted by the descent into the top-level struct, so we
   200  				// don't load what hides under the interface).
   201  				//
   202  				// v                               -> structAsStarlarkValue<api.Variable<StructWithInterface>>
   203  				// v.Children[0]                   -> structAsStarlarkValue<api.Variable<interface{}>>
   204  				// v.Children[0].Children[0]       -> structAsStarlarkValue<api.Variable<*int>>
   205  				// v.Children[0].Children[0].Value -> ptrVariableAsStarlarkValue<*int> ; this pointer variable has not been loaded
   206  				name: "partial load because of recursion limit",
   207  				script: `
   208  v = eval(
   209  		None, "ba", {"MaxVariableRecurse": 0, "MaxStructFields": 10}
   210  	).Variable;
   211  v.Children[0].Children[0].Value.XXX
   212  `,
   213  				expErr: "*int has no .XXX field or method",
   214  			},
   215  		} {
   216  			t.Run(tc.name, func(t *testing.T) {
   217  				_, err := term.ExecStarlark(tc.script)
   218  				if err == nil {
   219  					t.Fatalf("expected error %q, got success", tc.expErr)
   220  				}
   221  				if !strings.Contains(err.Error(), tc.expErr) {
   222  					t.Fatalf("expected error %q, got %q", tc.expErr, err)
   223  				}
   224  			})
   225  		}
   226  	})
   227  }