Skip to content

Commit

Permalink
Merge pull request #31 from TLINDEN/internal/fuzzytesting
Browse files Browse the repository at this point in the history
* reorganized Eval() to return errors and call EvalItem() on each item
* fix negative shift amount error, found with fuzzy testing :)
* added fuzzy testing
  • Loading branch information
TLINDEN authored Dec 8, 2023
2 parents cb774b3 + 222dc3a commit e81be12
Show file tree
Hide file tree
Showing 9 changed files with 196 additions and 98 deletions.
9 changes: 6 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,13 @@ install: buildlocal
install -o $(UID) -g $(GID) -m 444 $(tool).1 $(PREFIX)/man/man1/

clean:
rm -rf $(tool) coverage.out
rm -rf $(tool) coverage.out testdata

test:
go test -v ./...
test: clean
go test ./... $(ARGS)

testfuzzy: clean
go test -fuzz ./... $(ARGS)

singletest:
@echo "Call like this: make singletest TEST=TestPrepareColumns ARGS=-v"
Expand Down
180 changes: 95 additions & 85 deletions calc.go
Original file line number Diff line number Diff line change
Expand Up @@ -222,12 +222,12 @@ func (c *Calc) Prompt() string {
}

// the actual work horse, evaluate a line of calc command[s]
func (c *Calc) Eval(line string) {
func (c *Calc) Eval(line string) error {
// remove surrounding whitespace and comments, if any
line = strings.TrimSpace(c.Comment.ReplaceAllString(line, ""))

if line == "" {
return
return nil
}

items := c.Space.Split(line, -1)
Expand All @@ -239,110 +239,120 @@ func (c *Calc) Eval(line string) {
c.notdone = false
}

num, err := strconv.ParseFloat(item, 64)
if err := c.EvalItem(item); err != nil {
return err
}
}

if c.showstack && !c.stdin {
dots := ""

if c.stack.Len() > 5 {
dots = "... "
}
last := c.stack.Last(5)
fmt.Printf("stack: %s%s\n", dots, list2str(last))
}

return nil
}

func (c *Calc) EvalItem(item string) error {
num, err := strconv.ParseFloat(item, 64)

if err == nil {
c.stack.Backup()
c.stack.Push(num)
} else {
// try hex
var i int
_, err := fmt.Sscanf(item, "0x%x", &i)
if err == nil {
c.stack.Backup()
c.stack.Push(num)
} else {
// try hex
var i int
_, err := fmt.Sscanf(item, "0x%x", &i)
if err == nil {
c.stack.Backup()
c.stack.Push(float64(i))
continue
}

if contains(c.Constants, item) {
// put the constant onto the stack
c.stack.Backup()
c.stack.Push(const2num(item))
continue
}
c.stack.Push(float64(i))
return nil
}

if exists(c.Funcalls, item) {
if err := c.DoFuncall(item); err != nil {
fmt.Println(err)
} else {
c.Result()
}
continue
}
if contains(c.Constants, item) {
// put the constant onto the stack
c.stack.Backup()
c.stack.Push(const2num(item))
return nil
}

if exists(c.BatchFuncalls, item) {
if !c.batch {
fmt.Println("only supported in batch mode")
continue
}

if err := c.DoFuncall(item); err != nil {
fmt.Println(err)
} else {
c.Result()
}
continue
if exists(c.Funcalls, item) {
if err := c.DoFuncall(item); err != nil {
return Error(err.Error())
} else {
c.Result()
}
return nil
}

if contains(c.LuaFunctions, item) {
// user provided custom lua functions
c.EvalLuaFunction(item)
continue
if exists(c.BatchFuncalls, item) {
if !c.batch {
return Error("only supported in batch mode")
}

regmatches := c.Register.FindStringSubmatch(item)
if len(regmatches) == 3 {
switch regmatches[1] {
case ">":
c.PutVar(regmatches[2])
case "<":
c.GetVar(regmatches[2])
}
continue
if err := c.DoFuncall(item); err != nil {
return Error(err.Error())
} else {
c.Result()
}
return nil
}

// internal commands
if exists(c.Commands, item) {
c.Commands[item].Func(c)
continue
}
if contains(c.LuaFunctions, item) {
// user provided custom lua functions
c.EvalLuaFunction(item)
return nil
}

if exists(c.ShowCommands, item) {
c.ShowCommands[item].Func(c)
continue
regmatches := c.Register.FindStringSubmatch(item)
if len(regmatches) == 3 {
switch regmatches[1] {
case ">":
c.PutVar(regmatches[2])
case "<":
c.GetVar(regmatches[2])
}
return nil
}

if exists(c.StackCommands, item) {
c.StackCommands[item].Func(c)
continue
}
// internal commands
// FIXME: propagate errors
if exists(c.Commands, item) {
c.Commands[item].Func(c)
return nil
}

if exists(c.SettingsCommands, item) {
c.SettingsCommands[item].Func(c)
continue
}
if exists(c.ShowCommands, item) {
c.ShowCommands[item].Func(c)
return nil
}

switch item {
case "?":
fallthrough
case "help":
c.PrintHelp()
if exists(c.StackCommands, item) {
c.StackCommands[item].Func(c)
return nil
}

default:
fmt.Println("unknown command or operator!")
}
if exists(c.SettingsCommands, item) {
c.SettingsCommands[item].Func(c)
return nil
}
}

if c.showstack && !c.stdin {
dots := ""
switch item {
case "?":
fallthrough
case "help":
c.PrintHelp()

if c.stack.Len() > 5 {
dots = "... "
default:
return Error("unknown command or operator")
}
last := c.stack.Last(5)
fmt.Printf("stack: %s%s\n", dots, list2str(last))
}

return nil
}

// Execute a math function, check if it is defined just in case
Expand All @@ -355,7 +365,7 @@ func (c *Calc) DoFuncall(funcname string) error {
}

if function == nil {
panic("function not defined but in completion list")
return Error("function not defined but in completion list")
}

var args Numbers
Expand Down
67 changes: 65 additions & 2 deletions calc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ package main

import (
"fmt"
"strconv"
"strings"
"testing"

lua "github.com/yuin/gopher-lua"
Expand Down Expand Up @@ -75,7 +77,9 @@ func TestCommentsAndWhitespace(t *testing.T) {

t.Run(testname, func(t *testing.T) {
for _, line := range tt.cmd {
calc.Eval(line)
if err := calc.Eval(line); err != nil {
t.Errorf(err.Error())
}
}
got := calc.stack.Last()

Expand Down Expand Up @@ -288,7 +292,9 @@ func TestCalc(t *testing.T) {

t.Run(testname, func(t *testing.T) {
calc.batch = tt.batch
calc.Eval(tt.cmd)
if err := calc.Eval(tt.cmd); err != nil {
t.Errorf(err.Error())
}
got := calc.Result()
calc.stack.Clear()
if got != tt.exp {
Expand Down Expand Up @@ -350,3 +356,60 @@ func TestCalcLua(t *testing.T) {
})
}
}

func FuzzEval(f *testing.F) {
legal := []string{
"dump",
"showstack",
"help",
"Pi 31 *",
"SqrtE Pi /",
"55.5 yards-to-meters",
"2 4 +",
"7 8 batch sum",
"7 8 %-",
"7 8 clear",
"7 8 /",
"b",
"#444",
"<X",
}

for _, item := range legal {
f.Add(item)
}

calc := NewCalc()
var i int

f.Fuzz(func(t *testing.T, line string) {
t.Logf("Stack:\n%v\n", calc.stack.All())
if err := calc.EvalItem(line); err == nil {
t.Logf("given: <%s>", line)
// not corpus and empty?
if !contains(legal, line) && len(line) > 0 {
item := strings.TrimSpace(calc.Comment.ReplaceAllString(line, ""))
_, hexerr := fmt.Sscanf(item, "0x%x", &i)
// no comment?
if len(item) > 0 {
// no known command or function?
if _, err := strconv.ParseFloat(item, 64); err != nil {
if !contains(calc.Constants, item) &&
!exists(calc.Funcalls, item) &&
!exists(calc.BatchFuncalls, item) &&
!contains(calc.LuaFunctions, item) &&
!exists(calc.Commands, item) &&
!exists(calc.ShowCommands, item) &&
!exists(calc.SettingsCommands, item) &&
!exists(calc.StackCommands, item) &&
!calc.Register.MatchString(item) &&
item != "?" && item != "help" &&
hexerr != nil {
t.Errorf("Fuzzy input accepted: <%s>", line)
}
}
}
}
}
})
}
8 changes: 8 additions & 0 deletions funcs.go
Original file line number Diff line number Diff line change
Expand Up @@ -457,12 +457,20 @@ func DefineFunctions() Funcalls {

"<": NewFuncall(
func(arg Numbers) R {
// Shift by negative number provibited, so check it.
// Note that we check agains uint64 overflow as well here
if arg[1] < 0 || uint64(arg[1]) > math.MaxInt64 {
return NewR(0, errors.New("negative shift amount"))
}
return NewR(float64(int(arg[0])<<int(arg[1])), nil)
},
2),

">": NewFuncall(
func(arg Numbers) R {
if arg[1] < 0 || uint64(arg[1]) > math.MaxInt64 {
return NewR(0, errors.New("negative shift amount"))
}
return NewR(float64(int(arg[0])>>int(arg[1])), nil)
},
2),
Expand Down
Loading

0 comments on commit e81be12

Please sign in to comment.