feat: added some more test harness.

This commit is contained in:
James Wells 2023-04-03 19:43:35 -07:00
parent 7f9a5777bd
commit bdeaf2ec92
Signed by: jwells
GPG key ID: 73196D10B8E65666
8 changed files with 513 additions and 59 deletions

View file

@ -5,7 +5,6 @@ import (
"fmt"
"strconv"
"strings"
"testing"
)
/*
@ -18,9 +17,9 @@ func incorrectArgCountError(i *Interpreter, name string, argv []string) error {
}
/*
* needleInHaystack returns true if the string is in a slice
* NeedleInHaystack returns true if the string is in a slice
*/
func needleInHaystack(needle string, haystack []string) bool {
func NeedleInHaystack(needle string, haystack []string) bool {
for _, haystackMember := range haystack {
if haystackMember == needle {
return true
@ -29,27 +28,6 @@ func needleInHaystack(needle string, haystack []string) bool {
return false
}
/*
* Test_needleInHaystack tests the return value of needleInHaystack
*/
func Test_needleInHaystack(t *testing.T) {
var haystack = []string{"a", "b", "c"}
var needle = "a"
if !needleInHaystack(needle, haystack) {
t.Errorf("%s not in %s", needle, haystack)
}
needle = "j"
if needleInHaystack(needle, haystack) {
t.Errorf("%s in %s", needle, haystack)
}
needle = "ab"
if needleInHaystack(needle, haystack) {
t.Errorf("%s in %s", needle, haystack)
}
}
/*
* CommandMath is the math command for TCL
*/
@ -274,7 +252,7 @@ func CommandPuts(i *Interpreter, argv []string, pd interface{}) (string, error)
/*
* RegisterCoreCommands is a callable to register TCL commands.
*/
func (i *Interpreter) RegisterCoreCommands() {
func (i *Interpreter) RegisterCoreCommands() error {
name := [...]string{"+", "-", "*", "/", ">", ">=", "<", "<=", "==", "!="}
for _, n := range name {
_ = i.RegisterCommand(n, CommandMath, nil)
@ -289,4 +267,6 @@ func (i *Interpreter) RegisterCoreCommands() {
_ = i.RegisterCommand("return", CommandReturn, nil)
_ = i.RegisterCommand("error", CommandError, nil)
_ = i.RegisterCommand("puts", CommandPuts, nil)
return nil
}

300
pkg/picol/commands_test.go Normal file
View file

@ -0,0 +1,300 @@
package picol_test
import (
"testing"
"github.com/dragonheim/gagent/pkg/picol"
)
func Test_NeedleInHaystack(t *testing.T) {
var haystack = []string{"a", "b", "c"}
var needle = "a"
if !picol.NeedleInHaystack(needle, haystack) {
t.Errorf("%s not in %s", needle, haystack)
}
needle = "j"
if picol.NeedleInHaystack(needle, haystack) {
t.Errorf("%s in %s", needle, haystack)
}
needle = "ab"
if picol.NeedleInHaystack(needle, haystack) {
t.Errorf("%s in %s", needle, haystack)
}
}
func Test_CommandMath(t *testing.T) {
// You can add more test cases for various operations and edge cases
testCases := []struct {
argv []string
result string
err error
}{
{[]string{"+", "2", "3"}, "5", nil},
{[]string{"-", "8", "3"}, "5", nil},
{[]string{"*", "2", "3"}, "6", nil},
{[]string{"/", "6", "3"}, "2", nil},
{[]string{">", "4", "2"}, "1", nil},
}
i := picol.NewInterpreter()
for _, tc := range testCases {
result, err := picol.CommandMath(i, tc.argv, nil)
if result != tc.result || (err != nil && tc.err != nil && err.Error() != tc.err.Error()) {
t.Errorf("CommandMath(%v) = (%v, %v); expected (%v, %v)", tc.argv, result, err, tc.result, tc.err)
}
}
}
func Test_CommandSet(t *testing.T) {
testCases := []struct {
argv []string
result string
err error
}{
{[]string{"set", "x", "42"}, "42", nil},
{[]string{"set", "y", "abc"}, "abc", nil},
}
i := picol.NewInterpreter()
for _, tc := range testCases {
result, err := picol.CommandSet(i, tc.argv, nil)
if result != tc.result || (err != nil && tc.err != nil && err.Error() != tc.err.Error()) {
t.Errorf("CommandSet(%v) = (%v, %v); expected (%v, %v)", tc.argv, result, err, tc.result, tc.err)
}
}
}
func Test_CommandUnset(t *testing.T) {
testCases := []struct {
argv []string
result string
err error
}{
{[]string{"unset", "x"}, "", nil},
{[]string{"unset", "y"}, "", nil},
}
i := picol.NewInterpreter()
i.SetVariable("x", "42")
i.SetVariable("y", "abc")
for _, tc := range testCases {
result, err := picol.CommandUnset(i, tc.argv, nil)
if result != tc.result || (err != nil && tc.err != nil && err.Error() != tc.err.Error()) {
t.Errorf("CommandUnset(%v) = (%v, %v); expected (%v, %v)", tc.argv, result, err, tc.result, tc.err)
}
}
}
func Test_CommandIf(t *testing.T) {
testCases := []struct {
argv []string
result string
err error
}{
{[]string{"unset", "x"}, "", nil},
{[]string{"unset", "y"}, "", nil},
}
i := picol.NewInterpreter()
i.SetVariable("x", "42")
i.SetVariable("y", "abc")
for _, tc := range testCases {
result, err := picol.CommandUnset(i, tc.argv, nil)
if result != tc.result || (err != nil && tc.err != nil && err.Error() != tc.err.Error()) {
t.Errorf("CommandUnset(%v) = (%v, %v); expected (%v, %v)", tc.argv, result, err, tc.result, tc.err)
}
}
}
func Test_CommandWhile(t *testing.T) {
i := picol.NewInterpreter()
// Test simple while loop
err := i.RegisterCoreCommands()
if err != nil {
t.Fatal(err)
}
script := `
set i 0
set result 0
while { < $i 5 } {
set result [+ $result $i]
set i [+ $i 1]
}
`
res, err := i.Eval(script)
if err != nil {
t.Fatalf("Error during while loop evaluation: %s", err)
}
expectedResult := "10"
if res != expectedResult {
t.Errorf("Expected %s, got %s", expectedResult, res)
}
// Test nested while loops
script = `
set i 0
set j 0
set result 0
while { < $i 3 } {
set j 0
while { < $j 3 } {
set result [+ $result $j]
set j [+ $j 1]
}
set i [+ $i 1]
}
`
res, err = i.Eval(script)
if err != nil {
t.Fatalf("Error during nested while loop evaluation: %s", err)
}
expectedResult = "9"
if res != expectedResult {
t.Errorf("Expected %s, got %s", expectedResult, res)
}
}
// You can also add test functions for other commands like CommandProc, CommandReturn, etc.
func Test_CommandRetCodes(t *testing.T) {
i := picol.NewInterpreter()
err := i.RegisterCoreCommands()
if err != nil {
t.Fatal(err)
}
// Test break
script := `
while { 1 } {
break
}
`
_, err = i.Eval(script)
if err != nil {
t.Fatalf("Error during break evaluation: %s", err)
}
// Test continue
script = `
set i 0
set result 0
while { < $i 5 } {
if { == $i 2 } {
continue
}
set result [+ $result $i]
set i [+ $i 1]
}
`
expectedResult := "7"
res, err := i.Eval(script)
if err != nil {
t.Fatalf("Error during continue evaluation: %s", err)
}
if res != expectedResult {
t.Errorf("Expected %s, got %s", expectedResult, res)
}
}
func Test_CommandProc(t *testing.T) {
i := picol.NewInterpreter()
err := i.RegisterCoreCommands()
if err != nil {
t.Fatal(err)
}
script := `
proc sum {a b} {
return [+ $a $b]
}
set res [sum 3 4]
`
expectedResult := "7"
res, err := i.Eval(script)
if err != nil {
t.Fatalf("Error during proc evaluation: %s", err)
}
if res != expectedResult {
t.Errorf("Expected %s, got %s", expectedResult, res)
}
}
func Test_CommandReturn(t *testing.T) {
i := picol.NewInterpreter()
err := i.RegisterCoreCommands()
if err != nil {
t.Fatal(err)
}
script := `
proc testReturn {val} {
return $val
}
set res [testReturn 42]
`
expectedResult := "42"
res, err := i.Eval(script)
if err != nil {
t.Fatalf("Error during return evaluation: %s", err)
}
if res != expectedResult {
t.Errorf("Expected %s, got %s", expectedResult, res)
}
}
func Test_CommandError(t *testing.T) {
i := picol.NewInterpreter()
err := i.RegisterCoreCommands()
if err != nil {
t.Fatal(err)
}
script := `
error "An error occurred"
`
_, err = i.Eval(script)
if err == nil || err.Error() != "An error occurred" {
t.Fatalf("Error not raised or incorrect error message: %s", err)
}
}
func Test_CommandPuts(t *testing.T) {
i := picol.NewInterpreter()
err := i.RegisterCoreCommands()
if err != nil {
t.Fatal(err)
}
// The following test checks if the "puts" command runs without any error.
// However, it doesn't check the printed output since it's not straightforward to capture stdout in tests.
script := `
puts "Hello, world!"
`
_, err = i.Eval(script)
if err != nil {
t.Fatalf("Error during puts evaluation: %s", err)
}
}
func Test_RegisterCoreCommands(t *testing.T) {
i := picol.NewInterpreter()
err := i.RegisterCoreCommands()
if err != nil {
t.Fatalf("Error during core command registration: %s", err)
}
}

View file

@ -7,13 +7,13 @@ import (
// Define parser token types
const (
ptESC = iota
ptSTR
ptCMD
ptVAR
ptSEP
ptEOL
ptEOF
ParserTokenESC = iota
ParserTokenSTR
ParserTokenCMD
ParserTokenVAR
ParserTokenSEP
ParserTokenEOL
ParserTokenEOF
)
// parserStruct represents the parser state
@ -26,7 +26,7 @@ type parserStruct struct {
// initParser initializes a new parserStruct instance
func initParser(text string) *parserStruct {
return &parserStruct{text: text, ln: len(text), Type: ptEOL}
return &parserStruct{text: text, ln: len(text), Type: ParserTokenEOL}
}
// next advances the parser position by one rune
@ -57,7 +57,7 @@ func (p *parserStruct) parseSep() string {
}
}
p.end = p.p
p.Type = ptSEP
p.Type = ParserTokenSEP
return p.token()
}
@ -74,7 +74,7 @@ func (p *parserStruct) parseEol() string {
}
p.end = p.p
p.Type = ptEOL
p.Type = ParserTokenEOL
return p.token()
}
@ -105,7 +105,7 @@ Loop:
p.next()
}
p.end = p.p
p.Type = ptCMD
p.Type = ParserTokenCMD
if p.p < len(p.text) && p.current() == ']' {
p.next()
}
@ -118,7 +118,7 @@ func (p *parserStruct) parseVar() string {
p.start = p.p
if p.current() == '{' {
p.Type = ptVAR
p.Type = ParserTokenVAR
return p.parseBrace()
}
@ -134,10 +134,10 @@ func (p *parserStruct) parseVar() string {
if p.start == p.p { // It's just a single char string "$"
p.start = p.p - 1
p.end = p.p
p.Type = ptSTR
p.Type = ParserTokenSTR
} else {
p.end = p.p
p.Type = ptVAR
p.Type = ParserTokenVAR
}
return p.token()
}
@ -173,10 +173,10 @@ Loop:
// parseString parses a string with or without quotes
func (p *parserStruct) parseString() string {
newword := p.Type == ptSEP || p.Type == ptEOL || p.Type == ptSTR
newword := p.Type == ParserTokenSEP || p.Type == ParserTokenEOL || p.Type == ParserTokenSTR
if c := p.current(); newword && c == '{' {
p.Type = ptSTR
p.Type = ParserTokenSTR
return p.parseBrace()
} else if newword && c == '"' {
p.insidequote = 1
@ -197,7 +197,7 @@ Loop:
case '"':
if p.insidequote != 0 {
p.end = p.p
p.Type = ptESC
p.Type = ParserTokenESC
p.next()
p.insidequote = 0
return p.token()
@ -211,7 +211,7 @@ Loop:
}
p.end = p.p
p.Type = ptESC
p.Type = ParserTokenESC
return p.token()
}
@ -227,10 +227,10 @@ func (p *parserStruct) parseComment() string {
func (p *parserStruct) GetToken() string {
for {
if p.ln == 0 {
if p.Type != ptEOL && p.Type != ptEOF {
p.Type = ptEOL
if p.Type != ParserTokenEOL && p.Type != ParserTokenEOF {
p.Type = ParserTokenEOL
} else {
p.Type = ptEOF
p.Type = ParserTokenEOF
}
return p.token()
}
@ -251,7 +251,7 @@ func (p *parserStruct) GetToken() string {
case '$':
return p.parseVar()
case '#':
if p.Type == ptEOL {
if p.Type == ParserTokenEOL {
p.parseComment()
continue
}

79
pkg/picol/parser_test.go Normal file
View file

@ -0,0 +1,79 @@
package picol_test
import (
"testing"
"github.com/dragonheim/gagent/pkg/picol"
)
func TestParser(t *testing.T) {
testCases := []struct {
name string
input string
expected []int
}{
{
"Simple test",
"set x 10\nincr x",
[]int{
picol.ParserTokenSTR,
picol.ParserTokenSEP,
picol.ParserTokenSTR,
picol.ParserTokenSEP,
picol.ParserTokenSTR,
picol.ParserTokenEOL,
picol.ParserTokenSTR,
picol.ParserTokenSEP,
picol.ParserTokenSTR,
picol.ParserTokenEOL,
picol.ParserTokenEOF,
},
},
{
"Variable and command test",
"set x $y\nputs [expr $x * 2]",
[]int{
picol.ParserTokenSTR,
picol.ParserTokenSEP,
picol.ParserTokenSTR,
picol.ParserTokenSEP,
picol.ParserTokenVAR,
picol.ParserTokenEOL,
picol.ParserTokenSTR,
picol.ParserTokenSEP,
picol.ParserTokenCMD,
picol.ParserTokenEOL,
picol.ParserTokenEOF,
},
},
{
"Braces and quotes test",
`set x {"Hello World"}`,
[]int{
picol.ParserTokenSTR,
picol.ParserTokenSEP,
picol.ParserTokenSTR,
picol.ParserTokenSEP,
picol.ParserTokenSTR,
picol.ParserTokenEOL,
picol.ParserTokenEOF,
},
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
parser := picol.InitParser(tc.input)
for _, expectedType := range tc.expected {
token := parser.GetToken()
if parser.Type != expectedType {
t.Errorf("Expected token type %d, got %d", expectedType, parser.Type)
}
if parser.Type == picol.ParserTokenEOF {
break
}
}
})
}
}

View file

@ -125,33 +125,33 @@ func (interp *Interpreter) Eval(script string) (string, error) {
for {
prevType := parser.Type
token := parser.GetToken()
if parser.Type == ptEOF {
if parser.Type == ParserTokenEOF {
break
}
switch parser.Type {
case ptVAR:
case ParserTokenVAR:
v, ok := interp.Variable(token)
if !ok {
return "", fmt.Errorf("no such variable '%s'", token)
}
token = string(v)
case ptCMD:
case ParserTokenCMD:
result, err = interp.Eval(token)
if err != nil {
return result, err
}
token = result
case ptESC:
case ParserTokenESC:
/*
* TODO: escape handling missing!
*/
case ptSEP:
case ParserTokenSEP:
// prevType = parser.Type
continue
}
if parser.Type == ptEOL {
if parser.Type == ParserTokenEOL {
// prevType = parser.Type
if len(argv) != 0 {
cmd := interp.Command(argv[0])
@ -173,7 +173,7 @@ func (interp *Interpreter) Eval(script string) (string, error) {
/*
* We have a new token, append to the previous or as new arg?
*/
if prevType == ptSEP || prevType == ptEOL {
if prevType == ParserTokenSEP || prevType == ParserTokenEOL {
argv = append(argv, token)
} else { // Interpolation
argv[len(argv)-1] = strings.Join([]string{argv[len(argv)-1], token}, "")

56
pkg/picol/picol_test.go Normal file
View file

@ -0,0 +1,56 @@
package picol_test
import (
"testing"
"github.com/dragonheim/gagent/pkg/picol"
)
func TestInterpreter(t *testing.T) {
interp := picol.NewInterpreter()
// Register a command
err := interp.RegisterCommand("test", testCommand, nil)
if err != nil {
t.Fatalf("Error registering test command: %v", err)
}
// Test command execution
script := "test hello world"
result, err := interp.Eval(script)
if err != nil {
t.Fatalf("Error executing script: %v", err)
}
expected := "hello world"
if result != expected {
t.Errorf("Expected result '%s', got '%s'", expected, result)
}
// Test variable setting
interp.SetVariable("x", "42")
// Test variable retrieval
val, ok := interp.Variable("x")
if !ok {
t.Fatalf("Variable 'x' not found")
}
expectedVar := "42"
if val != picol.Variable(expectedVar) {
t.Errorf("Expected variable value '%s', got '%s'", expectedVar, val)
}
// Test variable unsetting
interp.UnsetVariable("x")
_, ok = interp.Variable("x")
if ok {
t.Fatalf("Variable 'x' should have been unset")
}
}
// testCommand is a simple custom command for testing
func testCommand(interp *picol.Interpreter, argv []string, privdata interface{}) (string, error) {
if len(argv) != 3 {
return "", nil
}
return argv[1] + " " + argv[2], nil
}

View file

@ -12,16 +12,15 @@ import (
var fname = flag.String("f", "", "file name")
func main() {
flag.Parse()
func RunPicol(fname string) error {
interp := picol.NewInterpreter()
interp.RegisterCoreCommands()
buf, err := ioutil.ReadFile(*fname)
buf, err := ioutil.ReadFile(fname)
if err == nil {
result, err := interp.Eval(string(buf))
if err != nil {
fmt.Println("ERRROR", result, err)
return fmt.Errorf("Error: %s, Result: %s", err, result)
}
} else {
for {
@ -30,8 +29,17 @@ func main() {
clibuf, _ := scanner.ReadString('\n')
result, err := interp.Eval(clibuf[:len(clibuf)-1])
if len(result) != 0 {
fmt.Println("ERRROR", result, err)
return fmt.Errorf("Error: %s, Result: %s", err, result)
}
}
}
return nil
}
func main() {
flag.Parse()
err := RunPicol(*fname)
if err != nil {
fmt.Println(err)
}
}

View file

@ -0,0 +1,31 @@
package main_test
import (
"io/ioutil"
"os"
"testing"
picol "github.com/dragonheim/gagent/pkg/picol/picol_unused"
)
func Test_RunPicol(t *testing.T) {
// Create a temporary test file
content := []byte("set a 5\nset b 7\n+ $a $b\n")
tmpfile, err := ioutil.TempFile("", "picol_test")
if err != nil {
t.Fatalf("Error creating temporary test file: %v", err)
}
defer os.Remove(tmpfile.Name()) // clean up
if _, err := tmpfile.Write(content); err != nil {
t.Fatalf("Error writing content to temporary test file: %v", err)
}
if err := tmpfile.Close(); err != nil {
t.Fatalf("Error closing temporary test file: %v", err)
}
err = picol.RunPicol(tmpfile.Name())
if err != nil {
t.Errorf("Error during RunPicol: %v", err)
}
}