interpret_test.go 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355
  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. "bytes"
  13. "flag"
  14. "fmt"
  15. "io/ioutil"
  16. "os"
  17. "path/filepath"
  18. "reflect"
  19. "strconv"
  20. "strings"
  21. "testing"
  22. "devt.de/krotik/common/errorutil"
  23. "devt.de/krotik/common/fileutil"
  24. "devt.de/krotik/ecal/config"
  25. "devt.de/krotik/ecal/interpreter"
  26. "devt.de/krotik/ecal/stdlib"
  27. "devt.de/krotik/ecal/util"
  28. )
  29. const testDir = "tooltest"
  30. var testLogOut *bytes.Buffer
  31. var testTerm *testConsoleLineTerminal
  32. func newTestInterpreter() *CLIInterpreter {
  33. tin := NewCLIInterpreter()
  34. // Redirect I/O bits into internal buffers
  35. testTerm = &testConsoleLineTerminal{nil, bytes.Buffer{}}
  36. tin.Term = testTerm
  37. testLogOut = &bytes.Buffer{}
  38. tin.LogOut = testLogOut
  39. return tin
  40. }
  41. func newTestInterpreterWithConfig() *CLIInterpreter {
  42. tin := newTestInterpreter()
  43. // Setup
  44. if res, _ := fileutil.PathExists(testDir); res {
  45. os.RemoveAll(testDir)
  46. }
  47. err := os.Mkdir(testDir, 0770)
  48. if err != nil {
  49. fmt.Print("Could not create test directory:", err.Error())
  50. os.Exit(1)
  51. }
  52. l := testDir
  53. tin.Dir = &l
  54. tin.CustomWelcomeMessage = "123"
  55. // Teardown
  56. return tin
  57. }
  58. func tearDown() {
  59. err := os.RemoveAll(testDir)
  60. if err != nil {
  61. fmt.Print("Could not remove test directory:", err.Error())
  62. }
  63. flag.CommandLine = flag.NewFlagSet(os.Args[0], flag.ExitOnError) // Reset CLI parsing
  64. }
  65. func TestInterpretBasicFunctions(t *testing.T) {
  66. flag.CommandLine = flag.NewFlagSet(os.Args[0], flag.ExitOnError) // Reset CLI parsing
  67. // Test normal initialisation
  68. tin := NewCLIInterpreter()
  69. if err := tin.CreateTerm(); err != nil {
  70. t.Error("Unexpected result:", err)
  71. return
  72. }
  73. tin = newTestInterpreter()
  74. // Test help output
  75. osArgs = []string{"foo", "bar", "-help"}
  76. flag.CommandLine.SetOutput(&testTerm.out)
  77. if stop := tin.ParseArgs(); !stop {
  78. t.Error("Asking for help should request to stop the program")
  79. return
  80. }
  81. if !strings.Contains(testTerm.out.String(), "Root directory for ECAL interpreter") {
  82. t.Error("Helptext does not contain expected string - output:", testTerm.out.String())
  83. return
  84. }
  85. flag.CommandLine = flag.NewFlagSet(os.Args[0], flag.ExitOnError) // Reset CLI parsing
  86. // Test interpret
  87. tin = newTestInterpreter()
  88. osArgs = []string{"foo", "bar", "-help"}
  89. flag.CommandLine.SetOutput(&testTerm.out)
  90. errorutil.AssertOk(tin.Interpret(true))
  91. flag.CommandLine = flag.NewFlagSet(os.Args[0], flag.ExitOnError) // Reset CLI parsing
  92. // Test entry file parsing
  93. tin = NewCLIInterpreter()
  94. osArgs = []string{"foo", "bar", "myfile"}
  95. if stop := tin.ParseArgs(); stop {
  96. t.Error("Giving an entry file should not stop the program")
  97. return
  98. }
  99. if stop := tin.ParseArgs(); stop {
  100. t.Error("Giving an entry file should not stop the program")
  101. return
  102. }
  103. if tin.EntryFile != "myfile" {
  104. t.Error("Unexpected entryfile:", tin.EntryFile)
  105. return
  106. }
  107. }
  108. func TestCreateRuntimeProvider(t *testing.T) {
  109. tin := newTestInterpreterWithConfig()
  110. defer tearDown()
  111. l := filepath.Join(testDir, "test.log")
  112. tin.LogFile = &l
  113. if err := tin.CreateRuntimeProvider("foo"); err != nil {
  114. t.Error("Unexpected result:", err)
  115. return
  116. }
  117. if _, ok := tin.RuntimeProvider.Logger.(*util.BufferLogger); !ok {
  118. t.Errorf("Unexpected logger: %#v", tin.RuntimeProvider.Logger)
  119. return
  120. }
  121. tin = newTestInterpreterWithConfig()
  122. defer tearDown()
  123. l = "error"
  124. tin.LogLevel = &l
  125. if err := tin.CreateRuntimeProvider("foo"); err != nil {
  126. t.Error("Unexpected result:", err)
  127. return
  128. }
  129. if _, ok := tin.RuntimeProvider.Logger.(*util.LogLevelLogger); !ok {
  130. t.Errorf("Unexpected logger: %#v", tin.RuntimeProvider.Logger)
  131. return
  132. }
  133. if err := tin.CreateRuntimeProvider("foo"); err != nil {
  134. t.Error("Unexpected result:", err)
  135. return
  136. }
  137. if _, ok := tin.RuntimeProvider.Logger.(*util.LogLevelLogger); !ok {
  138. t.Errorf("Unexpected logger: %#v", tin.RuntimeProvider.Logger)
  139. return
  140. }
  141. }
  142. func TestLoadInitialFile(t *testing.T) {
  143. tin := NewCLIDebugInterpreter(newTestInterpreterWithConfig())
  144. defer tearDown()
  145. if err := tin.CreateRuntimeProvider("foo"); err != nil {
  146. t.Error("Unexpected result:", err)
  147. return
  148. }
  149. tin.RuntimeProvider.Debugger = interpreter.NewECALDebugger(tin.GlobalVS)
  150. tin.RuntimeProvider.Logger = util.NewMemoryLogger(10)
  151. tin.RuntimeProvider.ImportLocator = &util.MemoryImportLocator{}
  152. tin.EntryFile = filepath.Join(testDir, "foo.ecal")
  153. ioutil.WriteFile(tin.EntryFile, []byte("a := 1"), 0777)
  154. if err := tin.CLIInterpreter.LoadInitialFile(1); err != nil {
  155. t.Error("Unexpected result:", err)
  156. return
  157. }
  158. if tin.GlobalVS.String() != `GlobalScope {
  159. a (float64) : 1
  160. }` {
  161. t.Error("Unexpected scope:", tin.GlobalVS)
  162. return
  163. }
  164. }
  165. func TestInterpret(t *testing.T) {
  166. tin := newTestInterpreterWithConfig()
  167. defer tearDown()
  168. if err := tin.CreateRuntimeProvider("foo"); err != nil {
  169. t.Error("Unexpected result:", err)
  170. return
  171. }
  172. tin.RuntimeProvider.Logger, _ = util.NewLogLevelLogger(util.NewMemoryLogger(10), "info")
  173. tin.RuntimeProvider.ImportLocator = &util.MemoryImportLocator{
  174. Files: map[string]string{
  175. "foo": "a := 1",
  176. },
  177. }
  178. l1 := ""
  179. tin.LogFile = &l1
  180. l2 := ""
  181. tin.LogLevel = &l2
  182. testTerm.in = []string{"xxx := 1", "q"}
  183. if err := tin.Interpret(true); err != nil {
  184. t.Error("Unexpected result:", err)
  185. return
  186. }
  187. if testLogOut.String() != `ECAL `+config.ProductVersion+`
  188. Log level: info - Root directory: tooltest
  189. 123
  190. Type 'q' or 'quit' to exit the shell and '?' to get help
  191. ` {
  192. t.Error("Unexpected result:", testLogOut.String())
  193. return
  194. }
  195. if tin.GlobalVS.String() != `GlobalScope {
  196. xxx (float64) : 1
  197. }` {
  198. t.Error("Unexpected scope:", tin.GlobalVS)
  199. return
  200. }
  201. }
  202. func TestHandleInput(t *testing.T) {
  203. tin := newTestInterpreterWithConfig()
  204. defer tearDown()
  205. tin.CustomHandler = &testCustomHandler{}
  206. if err := tin.CreateRuntimeProvider("foo"); err != nil {
  207. t.Error("Unexpected result:", err)
  208. return
  209. }
  210. stdlib.AddStdlibPkg("foo", "bar")
  211. stdlib.AddStdlibFunc("foo", "Println",
  212. stdlib.NewECALFunctionAdapter(reflect.ValueOf(fmt.Println), "xxx"))
  213. stdlib.AddStdlibFunc("foo", "Atoi",
  214. stdlib.NewECALFunctionAdapter(reflect.ValueOf(strconv.Atoi), "xxx"))
  215. tin.RuntimeProvider.Logger, _ = util.NewLogLevelLogger(util.NewMemoryLogger(10), "info")
  216. tin.RuntimeProvider.ImportLocator = &util.MemoryImportLocator{}
  217. tin.CustomHelpString = "123"
  218. l1 := ""
  219. tin.LogFile = &l1
  220. l2 := ""
  221. tin.LogLevel = &l2
  222. testTerm.in = []string{"?", "@reload", "@sym", "@std", "@cus", "q"}
  223. if err := tin.Interpret(true); err != nil {
  224. t.Error("Unexpected result:", err)
  225. return
  226. }
  227. // Just check for a simple string no need for the whole thing
  228. if !strings.Contains(testTerm.out.String(), "New creates a new object instance.") {
  229. t.Error("Unexpected result:", testTerm.out.String())
  230. return
  231. }
  232. testTerm.out.Reset()
  233. testTerm.in = []string{"@sym raise", "@std math.Phi", "@std foo Print", "q"}
  234. if err := tin.Interpret(true); err != nil {
  235. t.Error("Unexpected result:", err)
  236. return
  237. }
  238. if testTerm.out.String() != `╒═════════════════╤═══════════════════════════════╕
  239. │Inbuild function │Description │
  240. ╞═════════════════╪═══════════════════════════════╡
  241. │raise │Raise returns an error object. │
  242. │ │ │
  243. ╘═════════════════╧═══════════════════════════════╛
  244. ╒═════════╤══════════════════╕
  245. │Constant │Value │
  246. ╞═════════╪══════════════════╡
  247. │math.Phi │1.618033988749895 │
  248. │ │ │
  249. ╘═════════╧══════════════════╛
  250. ╒════════════╤════════════╕
  251. │Function │Description │
  252. ╞════════════╪════════════╡
  253. │foo.Println │xxx │
  254. │ │ │
  255. ╘════════════╧════════════╛
  256. ` {
  257. t.Error("Unexpected result:", testTerm.out.String())
  258. return
  259. }
  260. testTerm.out.Reset()
  261. testTerm.in = []string{"1", "raise(123)", "q"}
  262. if err := tin.Interpret(true); err != nil {
  263. t.Error("Unexpected result:", err)
  264. return
  265. }
  266. if testTerm.out.String() != `1
  267. ECAL error in foo: 123 () (Line:1 Pos:1)
  268. ` {
  269. t.Error("Unexpected result:", testTerm.out.String())
  270. return
  271. }
  272. }