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