Skip to content

Commit

Permalink
tests: Port flate fuzz test (#660)
Browse files Browse the repository at this point in the history
  • Loading branch information
klauspost committed Sep 9, 2022
1 parent 5d754d7 commit c474b64
Show file tree
Hide file tree
Showing 5 changed files with 246 additions and 120 deletions.
113 changes: 113 additions & 0 deletions flate/fuzz_test.go
@@ -0,0 +1,113 @@
//go:build go1.18
// +build go1.18

package flate

import (
"bytes"
"io"
"strconv"
"testing"

"github.com/klauspost/compress/internal/fuzz"
)

func FuzzEncoding(f *testing.F) {
fuzz.AddFromZip(f, "testdata/regression.zip", true, false)
fuzz.AddFromZip(f, "testdata/fuzz/encode-raw-corpus.zip", true, testing.Short())
fuzz.AddFromZip(f, "testdata/fuzz/FuzzEncoding.zip", false, testing.Short())
// Fuzzing tweaks:
const (
// Test a subset of encoders.
startFuzz = HuffmanOnly
endFuzz = BestCompression

// Also tests with dictionaries...
testDicts = true

// Max input size:
maxSize = 1 << 20
)
decoder := NewReader(nil)
buf := new(bytes.Buffer)

f.Fuzz(func(t *testing.T, data []byte) {
if len(data) > maxSize {
return
}
for level := startFuzz; level <= endFuzz; level++ {
msg := "level " + strconv.Itoa(level) + ":"
buf.Reset()
fw, err := NewWriter(buf, level)
if err != nil {
t.Fatal(msg + err.Error())
}
n, err := fw.Write(data)
if n != len(data) {
t.Fatal(msg + "short write")
}
if err != nil {
t.Fatal(msg + err.Error())
}
err = fw.Close()
if err != nil {
t.Fatal(msg + err.Error())
}
decoder.(Resetter).Reset(buf, nil)
data2, err := io.ReadAll(decoder)
if err != nil {
t.Fatal(msg + err.Error())
}
if !bytes.Equal(data, data2) {
t.Fatal(msg + "not equal")
}
// Do it again...
msg = "level " + strconv.Itoa(level) + " (reset):"
buf.Reset()
fw.Reset(buf)
n, err = fw.Write(data)
if n != len(data) {
t.Fatal(msg + "short write")
}
if err != nil {
t.Fatal(msg + err.Error())
}
err = fw.Close()
if err != nil {
t.Fatal(msg + err.Error())
}
decoder.(Resetter).Reset(buf, nil)
data2, err = io.ReadAll(decoder)
if err != nil {
t.Fatal(msg + err.Error())
}
if !bytes.Equal(data, data2) {
t.Fatal(msg + "not equal")
}
}

// Split into two and use history...
buf.Reset()
err := StatelessDeflate(buf, data[:len(data)/2], false, nil)
if err != nil {
t.Error(err)
}

// Use top half as dictionary...
dict := data[:len(data)/2]
err = StatelessDeflate(buf, data[len(data)/2:], true, dict)
if err != nil {
t.Error(err)
}

decoder.(Resetter).Reset(buf, nil)
data2, err := io.ReadAll(decoder)
if err != nil {
t.Error(err)
}
if !bytes.Equal(data, data2) {
//fmt.Printf("want:%x\ngot: %x\n", data1, data2)
t.Error("not equal")
}
})
}
Binary file added flate/testdata/fuzz/FuzzEncoding.zip
Binary file not shown.
Binary file added flate/testdata/fuzz/encode-raw-corpus.zip
Binary file not shown.
124 changes: 124 additions & 0 deletions internal/fuzz/helpers.go
@@ -0,0 +1,124 @@
//go:build go1.18
// +build go1.18

package fuzz

import (
"archive/zip"
"bytes"
"fmt"
"go/ast"
"go/parser"
"go/token"
"io"
"os"
"strconv"
"testing"
)

// AddFromZip will read the supplied zip and add all as corpus for f.
func AddFromZip(f *testing.F, filename string, raw, short bool) {
file, err := os.Open(filename)
if err != nil {
f.Fatal(err)
}
fi, err := file.Stat()
if err != nil {
f.Fatal(err)
}
zr, err := zip.NewReader(file, fi.Size())
if err != nil {
f.Fatal(err)
}
for i, file := range zr.File {
if short && i%10 != 0 {
continue
}
rc, err := file.Open()
if err != nil {
f.Fatal(err)
}

b, err := io.ReadAll(rc)
if err != nil {
f.Fatal(err)
}
rc.Close()
raw := raw
if bytes.HasPrefix(b, []byte("go test fuzz")) {
raw = false
}
if raw {
f.Add(b)
continue
}
vals, err := unmarshalCorpusFile(b)
if err != nil {
f.Fatal(err)
}
for _, v := range vals {
f.Add(v)
}
}
}

// unmarshalCorpusFile decodes corpus bytes into their respective values.
func unmarshalCorpusFile(b []byte) ([][]byte, error) {
if len(b) == 0 {
return nil, fmt.Errorf("cannot unmarshal empty string")
}
lines := bytes.Split(b, []byte("\n"))
if len(lines) < 2 {
return nil, fmt.Errorf("must include version and at least one value")
}
var vals = make([][]byte, 0, len(lines)-1)
for _, line := range lines[1:] {
line = bytes.TrimSpace(line)
if len(line) == 0 {
continue
}
v, err := parseCorpusValue(line)
if err != nil {
return nil, fmt.Errorf("malformed line %q: %v", line, err)
}
vals = append(vals, v)
}
return vals, nil
}

// parseCorpusValue
func parseCorpusValue(line []byte) ([]byte, error) {
fs := token.NewFileSet()
expr, err := parser.ParseExprFrom(fs, "(test)", line, 0)
if err != nil {
return nil, err
}
call, ok := expr.(*ast.CallExpr)
if !ok {
return nil, fmt.Errorf("expected call expression")
}
if len(call.Args) != 1 {
return nil, fmt.Errorf("expected call expression with 1 argument; got %d", len(call.Args))
}
arg := call.Args[0]

if arrayType, ok := call.Fun.(*ast.ArrayType); ok {
if arrayType.Len != nil {
return nil, fmt.Errorf("expected []byte or primitive type")
}
elt, ok := arrayType.Elt.(*ast.Ident)
if !ok || elt.Name != "byte" {
return nil, fmt.Errorf("expected []byte")
}
lit, ok := arg.(*ast.BasicLit)
if !ok || lit.Kind != token.STRING {
return nil, fmt.Errorf("string literal required for type []byte")
}
s, err := strconv.Unquote(lit.Value)
if err != nil {
return nil, err
}
return []byte(s), nil
}
return nil, fmt.Errorf("expected []byte")
}

0 comments on commit c474b64

Please sign in to comment.