123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388 |
- /*
- * ECAL
- *
- * Copyright 2020 Matthias Ladkau. All rights reserved.
- *
- * This Source Code Form is subject to the terms of the MIT
- * License, If a copy of the MIT License was not distributed with this
- * file, You can obtain one at https://opensource.org/licenses/MIT.
- */
- package tool
- import (
- "bytes"
- "flag"
- "fmt"
- "io/ioutil"
- "os"
- "path/filepath"
- "reflect"
- "strconv"
- "strings"
- "testing"
- "devt.de/krotik/common/errorutil"
- "devt.de/krotik/common/fileutil"
- "devt.de/krotik/ecal/config"
- "devt.de/krotik/ecal/interpreter"
- "devt.de/krotik/ecal/stdlib"
- "devt.de/krotik/ecal/util"
- )
- const testDir = "tooltest"
- var testLogOut *bytes.Buffer
- var testTerm *testConsoleLineTerminal
- func newTestInterpreter() *CLIInterpreter {
- tin := NewCLIInterpreter()
- // Redirect I/O bits into internal buffers
- testTerm = &testConsoleLineTerminal{nil, bytes.Buffer{}}
- tin.Term = testTerm
- testLogOut = &bytes.Buffer{}
- tin.LogOut = testLogOut
- return tin
- }
- func newTestInterpreterWithConfig() *CLIInterpreter {
- tin := newTestInterpreter()
- if res, _ := fileutil.PathExists(testDir); res {
- os.RemoveAll(testDir)
- }
- err := os.Mkdir(testDir, 0770)
- if err != nil {
- fmt.Print("Could not create test directory:", err.Error())
- os.Exit(1)
- }
- l := testDir
- tin.Dir = &l
- tin.CustomWelcomeMessage = "123"
- return tin
- }
- func tearDown() {
- err := os.RemoveAll(testDir)
- if err != nil {
- fmt.Print("Could not remove test directory:", err.Error())
- }
- flag.CommandLine = flag.NewFlagSet(os.Args[0], flag.ExitOnError) // Reset CLI parsing
- }
- func TestInterpretBasicFunctions(t *testing.T) {
- flag.CommandLine = flag.NewFlagSet(os.Args[0], flag.ExitOnError) // Reset CLI parsing
- // Test normal initialisation
- tin := NewCLIInterpreter()
- if err := tin.CreateTerm(); err != nil {
- t.Error("Unexpected result:", err)
- return
- }
- tin = newTestInterpreter()
- // Test help output
- osArgs = []string{"foo", "bar", "-help"}
- flag.CommandLine.SetOutput(&testTerm.out)
- if stop := tin.ParseArgs(); !stop {
- t.Error("Asking for help should request to stop the program")
- return
- }
- if !strings.Contains(testTerm.out.String(), "Root directory for ECAL interpreter") {
- t.Error("Helptext does not contain expected string - output:", testTerm.out.String())
- return
- }
- flag.CommandLine = flag.NewFlagSet(os.Args[0], flag.ExitOnError) // Reset CLI parsing
- // Test interpret
- tin = newTestInterpreter()
- osArgs = []string{"foo", "bar", "-help"}
- flag.CommandLine.SetOutput(&testTerm.out)
- errorutil.AssertOk(tin.Interpret(true))
- flag.CommandLine = flag.NewFlagSet(os.Args[0], flag.ExitOnError) // Reset CLI parsing
- // Test entry file parsing
- tin = NewCLIInterpreter()
- osArgs = []string{"foo", "bar", "myfile"}
- if stop := tin.ParseArgs(); stop {
- t.Error("Giving an entry file should not stop the program")
- return
- }
- if stop := tin.ParseArgs(); stop {
- t.Error("Giving an entry file should not stop the program")
- return
- }
- if tin.EntryFile != "myfile" {
- t.Error("Unexpected entryfile:", tin.EntryFile)
- return
- }
- flag.CommandLine = flag.NewFlagSet(os.Args[0], flag.ExitOnError) // Reset CLI parsing
- osArgs = []string{"foo", "bar"}
- // Try to load non-existing plugins (success case is tested in stdlib)
- tin = newTestInterpreterWithConfig()
- defer tearDown()
- l1 := ""
- tin.LogFile = &l1
- l2 := ""
- tin.LogLevel = &l2
- ioutil.WriteFile(filepath.Join(testDir, ".ecal.json"), []byte(`{
- "stdlibPlugins" : [{
- "package" : "mypkg",
- "name" : "myfunc",
- "path" : "./myfunc.so",
- "symbol" : "ECALmyfunc"
- }]
- }`), 0666)
- err := tin.Interpret(true)
- if err == nil || err.Error() != "Could not load plugins defined in .ecal.json" {
- t.Error("Unexpected result:", err.Error())
- return
- }
- if !strings.Contains(testLogOut.String(), "Error loading plugins") {
- t.Error("Unexpected result:", testLogOut.String())
- return
- }
- }
- func TestCreateRuntimeProvider(t *testing.T) {
- tin := newTestInterpreterWithConfig()
- defer tearDown()
- l := filepath.Join(testDir, "test.log")
- tin.LogFile = &l
- if err := tin.CreateRuntimeProvider("foo"); err != nil {
- t.Error("Unexpected result:", err)
- return
- }
- if _, ok := tin.RuntimeProvider.Logger.(*util.BufferLogger); !ok {
- t.Errorf("Unexpected logger: %#v", tin.RuntimeProvider.Logger)
- return
- }
- tin = newTestInterpreterWithConfig()
- defer tearDown()
- l = "error"
- tin.LogLevel = &l
- if err := tin.CreateRuntimeProvider("foo"); err != nil {
- t.Error("Unexpected result:", err)
- return
- }
- if _, ok := tin.RuntimeProvider.Logger.(*util.LogLevelLogger); !ok {
- t.Errorf("Unexpected logger: %#v", tin.RuntimeProvider.Logger)
- return
- }
- if err := tin.CreateRuntimeProvider("foo"); err != nil {
- t.Error("Unexpected result:", err)
- return
- }
- if _, ok := tin.RuntimeProvider.Logger.(*util.LogLevelLogger); !ok {
- t.Errorf("Unexpected logger: %#v", tin.RuntimeProvider.Logger)
- return
- }
- }
- func TestLoadInitialFile(t *testing.T) {
- tin := NewCLIDebugInterpreter(newTestInterpreterWithConfig())
- defer tearDown()
- if err := tin.CreateRuntimeProvider("foo"); err != nil {
- t.Error("Unexpected result:", err)
- return
- }
- tin.RuntimeProvider.Debugger = interpreter.NewECALDebugger(tin.GlobalVS)
- tin.RuntimeProvider.Logger = util.NewMemoryLogger(10)
- tin.RuntimeProvider.ImportLocator = &util.MemoryImportLocator{}
- tin.EntryFile = filepath.Join(testDir, "foo.ecal")
- ioutil.WriteFile(tin.EntryFile, []byte("a := 1"), 0777)
- if err := tin.CLIInterpreter.LoadInitialFile(1); err != nil {
- t.Error("Unexpected result:", err)
- return
- }
- if tin.GlobalVS.String() != `GlobalScope {
- a (float64) : 1
- }` {
- t.Error("Unexpected scope:", tin.GlobalVS)
- return
- }
- }
- func TestInterpret(t *testing.T) {
- tin := newTestInterpreterWithConfig()
- defer tearDown()
- if err := tin.CreateRuntimeProvider("foo"); err != nil {
- t.Error("Unexpected result:", err)
- return
- }
- tin.RuntimeProvider.Logger, _ = util.NewLogLevelLogger(util.NewMemoryLogger(10), "info")
- tin.RuntimeProvider.ImportLocator = &util.MemoryImportLocator{
- Files: map[string]string{
- "foo": "a := 1",
- },
- }
- l1 := ""
- tin.LogFile = &l1
- l2 := ""
- tin.LogLevel = &l2
- testTerm.in = []string{"xxx := 1", "q"}
- if err := tin.Interpret(true); err != nil {
- t.Error("Unexpected result:", err)
- return
- }
- if testLogOut.String() != `ECAL `+config.ProductVersion+`
- Log level: info - Root directory: tooltest
- 123
- Type 'q' or 'quit' to exit the shell and '?' to get help
- ` {
- t.Error("Unexpected result:", testLogOut.String())
- return
- }
- if tin.GlobalVS.String() != `GlobalScope {
- xxx (float64) : 1
- }` {
- t.Error("Unexpected scope:", tin.GlobalVS)
- return
- }
- }
- func TestHandleInput(t *testing.T) {
- tin := newTestInterpreterWithConfig()
- defer tearDown()
- tin.CustomHandler = &testCustomHandler{}
- if err := tin.CreateRuntimeProvider("foo"); err != nil {
- t.Error("Unexpected result:", err)
- return
- }
- stdlib.AddStdlibPkg("foo", "bar")
- stdlib.AddStdlibFunc("foo", "Println",
- stdlib.NewECALFunctionAdapter(reflect.ValueOf(fmt.Println), "xxx"))
- stdlib.AddStdlibFunc("foo", "Atoi",
- stdlib.NewECALFunctionAdapter(reflect.ValueOf(strconv.Atoi), "xxx"))
- tin.RuntimeProvider.Logger, _ = util.NewLogLevelLogger(util.NewMemoryLogger(10), "info")
- tin.RuntimeProvider.ImportLocator = &util.MemoryImportLocator{}
- tin.CustomHelpString = "123"
- l1 := ""
- tin.LogFile = &l1
- l2 := ""
- tin.LogLevel = &l2
- testTerm.in = []string{"?", "@reload", "@sym", "@std", "@cus", "q"}
- if err := tin.Interpret(true); err != nil {
- t.Error("Unexpected result:", err)
- return
- }
- // Just check for a simple string no need for the whole thing
- if !strings.Contains(testTerm.out.String(), "New creates a new object instance.") {
- t.Error("Unexpected result:", testTerm.out.String())
- return
- }
- testTerm.out.Reset()
- testTerm.in = []string{"@sym raise", "@std math.Phi", "@std foo Print", "q"}
- if err := tin.Interpret(true); err != nil {
- t.Error("Unexpected result:", err)
- return
- }
- if strings.HasSuffix(testTerm.out.String(), `╒═════════════════╤═══════════════════════════════╕
- │Inbuild function │Description │
- ╞═════════════════╪═══════════════════════════════╡
- │raise │Raise returns an error object. │
- │ │ │
- ╘═════════════════╧═══════════════════════════════╛
- ╒═════════╤══════════════════╕
- │Constant │Value │
- ╞═════════╪══════════════════╡
- │math.Phi │1.618033988749895 │
- │ │ │
- ╘═════════╧══════════════════╛
- ╒════════════╤════════════╕
- │Function │Description │
- ╞════════════╪════════════╡
- │foo.Println │xxx │
- │ │ │
- ╘════════════╧════════════╛
- `) {
- t.Error("Unexpected result:", testTerm.out.String())
- return
- }
- testTerm.out.Reset()
- testTerm.in = []string{"1", "raise(123)", "q"}
- if err := tin.Interpret(true); err != nil {
- t.Error("Unexpected result:", err)
- return
- }
- if testTerm.out.String() != `1
- ECAL error in foo: 123 () (Line:1 Pos:1)
- ` {
- t.Error("Unexpected result:", testTerm.out.String())
- return
- }
- }
|