debug_test.go 9.0 KB

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