interpret_test.go 9.6 KB

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