Skip to content

Commit

Permalink
Merge pull request #2 from skx/fuzzing
Browse files Browse the repository at this point in the history
This pull-request adds fuzz-testing, with the standard golang tools, and resolves many many issues that were discovered by that.
  • Loading branch information
skx authored Jul 16, 2022
2 parents 1d4199f + 57dc9f3 commit a0aeb76
Show file tree
Hide file tree
Showing 38 changed files with 542 additions and 26 deletions.
33 changes: 32 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ We have a reasonable number of functions implemented in our golang core:
* Comparison functions:
* `<`, `<=`, `>`, `>=`, `=`, & `eq`.
* Misc features
* `str`, `print`, & `type`
* `error`, `str`, `print`, & `type`
* Special forms
* `begin`, `cond`, `define`, `eval`, `if`, `lambda`, `let`, `set!`, `quote`,
* Tail recursion optimization.
Expand All @@ -126,6 +126,37 @@ Notable omissions here:
* No vectors/hashes/records.


## Fuzz Testing

If you're working with go 1.18+ you'll be able to run a fuzz-test of the interpreter, without the need for any external tools.

Run the included driver like so:

```sh
go test -fuzztime=300s -parallel=1 -fuzz=FuzzYAL -v
```

Sample output will look like this:

```
=== FUZZ FuzzYAL
fuzz: elapsed: 0s, gathering baseline coverage: 0/38 completed
fuzz: elapsed: 0s, gathering baseline coverage: 38/38 completed, now fuzzing with 1 workers
fuzz: elapsed: 3s, execs: 39 (13/sec), new interesting: 0 (total: 38)
fuzz: elapsed: 6s, execs: 39 (0/sec), new interesting: 0 (total: 38)
fuzz: elapsed: 9s, execs: 39 (0/sec), new interesting: 0 (total: 38)
fuzz: elapsed: 12s, execs: 39 (0/sec), new interesting: 0 (total: 38)
fuzz: elapsed: 15s, execs: 39 (0/sec), new interesting: 0 (total: 38)
fuzz: elapsed: 18s, execs: 39 (0/sec), new interesting: 0 (total: 38)
fuzz: elapsed: 21s, execs: 39 (0/sec), new interesting: 0 (total: 38)
fuzz: elapsed: 24s, execs: 39 (0/sec), new interesting: 0 (total: 38)
fuzz: elapsed: 27s, execs: 39 (0/sec), new interesting: 0 (total: 38)
```

If you find a crash then it is either a bug which needs to be fixed, or a false-positive (i.e. a function reports an error which is expected) in which case the fuzz-test should be updated to add it to the list of known-OK results. (For example "division by zero" is a fatal error, so that's a known-OK result).




## References

Expand Down
23 changes: 20 additions & 3 deletions builtins/builtins.go
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,13 @@ func modFn(args []primitive.Primitive) primitive.Primitive {
if _, ok := args[1].(primitive.Number); !ok {
return primitive.Error("argument not a number")
}
return primitive.Number(int(args[0].(primitive.Number)) % int(args[1].(primitive.Number)))

a := int(args[0].(primitive.Number))
b := int(args[1].(primitive.Number))
if b == 0 {
return primitive.Error("attempted division by zero")
}
return primitive.Number(a % b)
}

// expnFn implements "#"
Expand Down Expand Up @@ -306,7 +312,14 @@ func carFn(args []primitive.Primitive) primitive.Primitive {
return primitive.Error("argument not a list")
}

return args[0].(primitive.List)[0]
// If we have at least one entry then return the first
lst := args[0].(primitive.List)
if len(lst) > 0 {
return lst[0]
}

// Otherwise return nil
return primitive.Nil{}
}

// cdrFn implements "cdr"
Expand All @@ -321,7 +334,11 @@ func cdrFn(args []primitive.Primitive) primitive.Primitive {
return primitive.Error("argument not a list")
}

return args[0].(primitive.List)[1:]
lst := args[0].(primitive.List)
if len(lst) > 0 {
return lst[1:]
}
return primitive.Nil{}
}

// errorFn implements "error"
Expand Down
40 changes: 40 additions & 0 deletions builtins/builtins_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,23 @@ func TestMod(t *testing.T) {
t.Fatalf("got error, but wrong one %v", out)
}

//
// Mod 0
//
out = modFn([]primitive.Primitive{
primitive.Number(32),
primitive.Number(0),
})

// Will lead to an error
e, ok = out.(primitive.Error)
if !ok {
t.Fatalf("expected error, got %v", out)
}
if !strings.Contains(string(e), "division by zero") {
t.Fatalf("got error, but wrong one %v", out)
}

//
// Now a real one
//
Expand Down Expand Up @@ -650,6 +667,18 @@ func TestCar(t *testing.T) {
if r.ToString() != "3" {
t.Fatalf("got wrong result : %v", r)
}

// Now a list which is empty
out = carFn([]primitive.Primitive{
primitive.List{},
})

// No error
_, ok3 := out.(primitive.Nil)
if !ok3 {
t.Fatalf("expected nil, got %v", out)
}

}

// Test (cdr
Expand Down Expand Up @@ -698,6 +727,17 @@ func TestCdr(t *testing.T) {
if r.ToString() != "(4 5)" {
t.Fatalf("got wrong result : %v", r)
}

// Now a list which is empty
out = cdrFn([]primitive.Primitive{
primitive.List{},
})

// No error
_, ok3 := out.(primitive.Nil)
if !ok3 {
t.Fatalf("expected nil, got %v", out)
}
}

func TestStr(t *testing.T) {
Expand Down
Loading

0 comments on commit a0aeb76

Please sign in to comment.