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 }