debug_test.go 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400
  1. /*
  2. * ECAL
  3. *
  4. * Copyright 2020 Matthias Ladkau. All rights reserved.
  5. *
  6. * This Source Code Form is subject to the terms of the MIT
  7. * License, If a copy of the MIT License was not distributed with this
  8. * file, You can obtain one at https://opensource.org/licenses/MIT.
  9. */
  10. package tool
  11. import (
  12. "bufio"
  13. "bytes"
  14. "flag"
  15. "fmt"
  16. "net"
  17. "os"
  18. "reflect"
  19. "strconv"
  20. "strings"
  21. "sync"
  22. "testing"
  23. "time"
  24. "devt.de/krotik/common/errorutil"
  25. "devt.de/krotik/ecal/config"
  26. "devt.de/krotik/ecal/interpreter"
  27. "devt.de/krotik/ecal/stdlib"
  28. "devt.de/krotik/ecal/util"
  29. )
  30. var testDebugLogOut *bytes.Buffer
  31. func newTestDebugWithConfig() *CLIDebugInterpreter {
  32. tdin := NewCLIDebugInterpreter(newTestInterpreterWithConfig())
  33. testDebugLogOut = &bytes.Buffer{}
  34. tdin.LogOut = testDebugLogOut
  35. return tdin
  36. }
  37. func TestDebugBasicFunctions(t *testing.T) {
  38. tdin := newTestDebugWithConfig()
  39. defer tearDown()
  40. // Test help output
  41. flag.CommandLine = flag.NewFlagSet(os.Args[0], flag.ExitOnError) // Reset CLI parsing
  42. osArgs = []string{"foo", "bar", "-help"}
  43. defer func() { osArgs = []string{} }()
  44. flag.CommandLine.SetOutput(&testTerm.out)
  45. if stop := tdin.ParseArgs(); !stop {
  46. t.Error("Asking for help should request to stop the program")
  47. return
  48. }
  49. if !strings.Contains(testTerm.out.String(), "Root directory for ECAL interpreter") {
  50. t.Error("Helptext does not contain expected string - output:", testTerm.out.String())
  51. return
  52. }
  53. if stop := tdin.ParseArgs(); stop {
  54. t.Error("Asking again should be caught by the short circuit")
  55. return
  56. }
  57. tdin = newTestDebugWithConfig()
  58. flag.CommandLine = flag.NewFlagSet(os.Args[0], flag.ExitOnError) // Reset CLI parsing
  59. flag.CommandLine.SetOutput(&testTerm.out)
  60. if err := tdin.Interpret(); err != nil {
  61. t.Error("Unexpected result:", err)
  62. return
  63. }
  64. }
  65. func TestDebugInterpret(t *testing.T) {
  66. tdin := newTestDebugWithConfig()
  67. defer tearDown()
  68. if stop := tdin.ParseArgs(); stop {
  69. t.Error("Setting default args should be fine")
  70. return
  71. }
  72. if err := tdin.CreateRuntimeProvider("foo"); err != nil {
  73. t.Error("Unexpected result:", err)
  74. return
  75. }
  76. tdin.RuntimeProvider.Logger, _ = util.NewLogLevelLogger(util.NewMemoryLogger(10), "info")
  77. tdin.RuntimeProvider.ImportLocator = &util.MemoryImportLocator{
  78. Files: map[string]string{
  79. "foo": "a := 1",
  80. },
  81. }
  82. l1 := ""
  83. tdin.LogFile = &l1
  84. l2 := ""
  85. tdin.LogLevel = &l2
  86. l3 := true
  87. tdin.Interactive = &l3
  88. tdin.RunDebugServer = &l3
  89. testTerm.in = []string{"xxx := 1", "q"}
  90. // The interpret call takes quite long because the debug server is
  91. // closed by the defer call when the call returns
  92. if err := tdin.Interpret(); err != nil {
  93. t.Error("Unexpected result:", err)
  94. return
  95. }
  96. if testLogOut.String() != `ECAL `+config.ProductVersion+`
  97. Log level: info - Root directory: /home/ml/data/krotik/ecal/cli/tool
  98. Running in debug mode - with debug server on localhost:33274 - prefix debug commands with ##
  99. Type 'q' or 'quit' to exit the shell and '?' to get help
  100. ` {
  101. t.Error("Unexpected result:", testLogOut.String())
  102. return
  103. }
  104. if tdin.GlobalVS.String() != `GlobalScope {
  105. xxx (float64) : 1
  106. }` {
  107. t.Error("Unexpected scope:", tdin.GlobalVS)
  108. return
  109. }
  110. }
  111. func TestDebugHandleInput(t *testing.T) {
  112. tdin := newTestDebugWithConfig()
  113. defer tearDown()
  114. if stop := tdin.ParseArgs(); stop {
  115. t.Error("Setting default args should be fine")
  116. return
  117. }
  118. if err := tdin.CreateRuntimeProvider("foo"); err != nil {
  119. t.Error("Unexpected result:", err)
  120. return
  121. }
  122. stdlib.AddStdlibPkg("foo", "bar")
  123. stdlib.AddStdlibFunc("foo", "Println",
  124. stdlib.NewECALFunctionAdapter(reflect.ValueOf(fmt.Println), "xxx"))
  125. stdlib.AddStdlibFunc("foo", "Atoi",
  126. stdlib.NewECALFunctionAdapter(reflect.ValueOf(strconv.Atoi), "xxx"))
  127. tdin.RuntimeProvider.Logger, _ = util.NewLogLevelLogger(util.NewMemoryLogger(10), "info")
  128. tdin.RuntimeProvider.ImportLocator = &util.MemoryImportLocator{}
  129. tdin.CustomHelpString = "123"
  130. l1 := ""
  131. tdin.LogFile = &l1
  132. l2 := ""
  133. tdin.LogLevel = &l2
  134. l3 := true
  135. tdin.Interactive = &l3
  136. l4 := false
  137. tdin.RunDebugServer = &l4
  138. testTerm.in = []string{"?", "@dbg", "##status", "##foo", "q"}
  139. if err := tdin.Interpret(); err != nil {
  140. t.Error("Unexpected result:", err)
  141. return
  142. }
  143. // Just check for a simple string no need for the whole thing
  144. time.Sleep(1 * time.Second)
  145. if !strings.Contains(testTerm.out.String(), "Set a breakpoint specifying <source>:<line>") {
  146. t.Error("Unexpected result:", testTerm.out.String())
  147. return
  148. }
  149. if !strings.Contains(testTerm.out.String(), "Unknown command: foo") {
  150. t.Error("Unexpected result:", testTerm.out.String())
  151. return
  152. }
  153. testTerm.out.Reset()
  154. testTerm.in = []string{"@dbg status", "q"}
  155. if err := tdin.Interpret(); err != nil {
  156. t.Error("Unexpected result:", err)
  157. return
  158. }
  159. if testTerm.out.String() != `╒══════════════╤═════════════════════════════════════════╕
  160. │Debug command │Description │
  161. ╞══════════════╪═════════════════════════════════════════╡
  162. │status │Shows breakpoints and suspended threads. │
  163. │ │ │
  164. ╘══════════════╧═════════════════════════════════════════╛
  165. ` {
  166. t.Error("Unexpected result:", "#"+testTerm.out.String()+"#")
  167. return
  168. }
  169. testTerm.out.Reset()
  170. testTerm.in = []string{"1", "raise(123)", "q"}
  171. if err := tdin.Interpret(); err != nil {
  172. t.Error("Unexpected result:", err)
  173. return
  174. }
  175. if testTerm.out.String() != `1
  176. ECAL error in foo: 123 () (Line:1 Pos:1)
  177. ` {
  178. t.Error("Unexpected result:", testTerm.out.String())
  179. return
  180. }
  181. }
  182. func TestDebugTelnetServer(t *testing.T) {
  183. tdin := newTestDebugWithConfig()
  184. defer tearDown()
  185. if err := tdin.CreateRuntimeProvider("foo"); err != nil {
  186. t.Error("Unexpected result:", err)
  187. return
  188. }
  189. tdin.RuntimeProvider.Logger = util.NewMemoryLogger(10)
  190. tdin.RuntimeProvider.ImportLocator = &util.MemoryImportLocator{}
  191. tdin.RuntimeProvider.Debugger = interpreter.NewECALDebugger(tdin.GlobalVS)
  192. tdin.RuntimeProvider.Debugger.BreakOnError(false)
  193. tdin.CustomHandler = tdin
  194. addr := "localhost:33274"
  195. mlog := util.NewMemoryLogger(10)
  196. srv := &debugTelnetServer{
  197. address: addr,
  198. logPrefix: "testdebugserver",
  199. listener: nil,
  200. listen: true,
  201. echo: true,
  202. interpreter: tdin,
  203. logger: mlog,
  204. }
  205. defer func() {
  206. srv.listen = false
  207. srv.listener.Close() // Attempt to cleanup
  208. }()
  209. wg := &sync.WaitGroup{}
  210. wg.Add(1)
  211. go srv.Run(wg)
  212. wg.Wait()
  213. conn, err := net.Dial("tcp", addr)
  214. errorutil.AssertOk(err)
  215. reader := bufio.NewReader(conn)
  216. fmt.Fprintf(conn, "a:= 1; a\n")
  217. line, err := reader.ReadString('}')
  218. errorutil.AssertOk(err)
  219. if line != `{
  220. "EncodedOutput": "MQo="
  221. }` {
  222. t.Error("Unexpected output:", line)
  223. return
  224. }
  225. if tdin.GlobalVS.String() != `GlobalScope {
  226. a (float64) : 1
  227. }` {
  228. t.Error("Unexpected result:", tdin.GlobalVS)
  229. return
  230. }
  231. fmt.Fprintf(conn, "##status\n")
  232. line, err = reader.ReadString('}')
  233. errorutil.AssertOk(err)
  234. l, err := reader.ReadString('}')
  235. errorutil.AssertOk(err)
  236. line += l
  237. l, err = reader.ReadString('}')
  238. errorutil.AssertOk(err)
  239. line += l
  240. line = strings.TrimSpace(line)
  241. if line != `{
  242. "breakonstart": false,
  243. "breakpoints": {},
  244. "sources": [
  245. "console input"
  246. ],
  247. "threads": {}
  248. }` {
  249. t.Error("Unexpected output:", line)
  250. return
  251. }
  252. fmt.Fprintf(conn, "@sym\n")
  253. line, err = reader.ReadString('}')
  254. errorutil.AssertOk(err)
  255. if !strings.Contains(line, "KioqKioqKioqKioqKioqKioq") {
  256. t.Error("Unexpected output:", line)
  257. return
  258. }
  259. fmt.Fprintf(conn, "raise(123);1\n")
  260. line, err = reader.ReadString('}')
  261. errorutil.AssertOk(err)
  262. line = strings.TrimSpace(line)
  263. if line != `{
  264. "EncodedOutput": "RUNBTCBlcnJvciBpbiBmb286IDEyMyAoKSAoTGluZToxIFBvczoxKQo="
  265. }` {
  266. t.Error("Unexpected output:", line)
  267. return
  268. }
  269. testDebugLogOut.Reset()
  270. errorutil.AssertOk(conn.Close())
  271. time.Sleep(10 * time.Millisecond)
  272. if !strings.Contains(testDebugLogOut.String(), "Disconnected") {
  273. t.Error("Unexpected output:", testDebugLogOut)
  274. return
  275. }
  276. testDebugLogOut.Reset()
  277. conn, err = net.Dial("tcp", addr)
  278. errorutil.AssertOk(err)
  279. if _, err := fmt.Fprintf(conn, "q\n"); err != nil {
  280. t.Error("Unexpected result:", err)
  281. return
  282. }
  283. // Make sure we can't start a second server on the same port
  284. mlog2 := util.NewMemoryLogger(10)
  285. srv2 := &debugTelnetServer{
  286. address: addr,
  287. logPrefix: "testdebugserver",
  288. listener: nil,
  289. listen: true,
  290. echo: true,
  291. interpreter: tdin,
  292. logger: mlog2,
  293. }
  294. defer func() {
  295. srv2.listen = false
  296. srv2.listener.Close() // Attempt to cleanup
  297. }()
  298. mlog2.Reset()
  299. wg = &sync.WaitGroup{}
  300. wg.Add(1)
  301. go srv2.Run(wg)
  302. wg.Wait()
  303. if !strings.Contains(mlog2.String(), "address already in use") {
  304. t.Error("Unexpected output:", mlog2.String())
  305. return
  306. }
  307. mlog.Reset()
  308. srv.listener.Close()
  309. time.Sleep(5 * time.Millisecond)
  310. if !strings.Contains(mlog.String(), "use of closed network connection") {
  311. t.Error("Unexpected output:", mlog.String())
  312. return
  313. }
  314. }