[!fuzz] skip

# Tests that a crash caused by a mutator-discovered input writes the bad input
# to testdata, and fails+reports correctly. This tests the end-to-end behavior
# of the mutator finding a crash while fuzzing, adding it as a regression test
# to the seed corpus in testdata, and failing the next time the test is run.

[short] skip
env GOCACHE=$WORK/cache

# Running the seed corpus for all of the targets should pass the first
# time, since nothing in the seed corpus will cause a crash.
go test

# Running the fuzzer should find a crashing input quickly.
! go test -fuzz=FuzzWithBug -fuzztime=100x -fuzzminimizetime=1000x
stdout 'testdata[/\\]fuzz[/\\]FuzzWithBug[/\\]'
stdout 'this input caused a crash!'
go run check_testdata.go FuzzWithBug

# Now, the failing bytes should have been added to the seed corpus for
# the target, and should fail when run without fuzzing.
! go test
stdout 'FuzzWithBug/[a-f0-9]{16}'
stdout 'this input caused a crash!'

! go test -run=FuzzWithNilPanic -fuzz=FuzzWithNilPanic -fuzztime=100x -fuzzminimizetime=1000x
stdout 'testdata[/\\]fuzz[/\\]FuzzWithNilPanic[/\\]'
stdout 'panic called with nil argument|test executed panic.nil. or runtime.Goexit'
go run check_testdata.go FuzzWithNilPanic

! go test -run=FuzzWithGoexit -fuzz=FuzzWithGoexit -fuzztime=100x -fuzzminimizetime=1000x
stdout 'testdata[/\\]fuzz[/\\]FuzzWithGoexit[/\\]'
stdout 'runtime.Goexit'
go run check_testdata.go FuzzWithGoexit

! go test -run=FuzzWithFail -fuzz=FuzzWithFail -fuzztime=100x -fuzzminimizetime=1000x
stdout 'testdata[/\\]fuzz[/\\]FuzzWithFail[/\\]'
go run check_testdata.go FuzzWithFail

! go test -run=FuzzWithLogFail -fuzz=FuzzWithLogFail -fuzztime=100x -fuzzminimizetime=1000x
stdout 'testdata[/\\]fuzz[/\\]FuzzWithLogFail[/\\]'
stdout 'logged something'
go run check_testdata.go FuzzWithLogFail

! go test -run=FuzzWithErrorf -fuzz=FuzzWithErrorf -fuzztime=100x -fuzzminimizetime=1000x
stdout 'testdata[/\\]fuzz[/\\]FuzzWithErrorf[/\\]'
stdout 'errorf was called here'
go run check_testdata.go FuzzWithErrorf

! go test -run=FuzzWithFatalf -fuzz=FuzzWithFatalf -fuzztime=100x -fuzzminimizetime=1000x
stdout 'testdata[/\\]fuzz[/\\]FuzzWithFatalf[/\\]'
stdout 'fatalf was called here'
go run check_testdata.go FuzzWithFatalf

! go test -run=FuzzWithBadExit -fuzz=FuzzWithBadExit -fuzztime=100x -fuzzminimizetime=1000x
stdout 'testdata[/\\]fuzz[/\\]FuzzWithBadExit[/\\]'
stdout '^\s+fuzzing process hung or terminated unexpectedly: exit status'
go run check_testdata.go FuzzWithBadExit

! go test -run=FuzzDeadlock -fuzz=FuzzDeadlock -fuzztime=100x -fuzzminimizetime=0x
stdout 'testdata[/\\]fuzz[/\\]FuzzDeadlock[/\\]'
stdout '^\s+fuzzing process hung or terminated unexpectedly: exit status'
go run check_testdata.go FuzzDeadlock

# Running the fuzzer should find a crashing input quickly for fuzzing two types.
! go test -run=FuzzWithTwoTypes -fuzz=FuzzWithTwoTypes -fuzztime=100x -fuzzminimizetime=1000x
stdout 'testdata[/\\]fuzz[/\\]FuzzWithTwoTypes[/\\]'
stdout 'these inputs caused a crash!'
go run check_testdata.go FuzzWithTwoTypes

# Running the fuzzer should find a crashing input quickly for an integer.
! go test -run=FuzzInt -fuzz=FuzzInt -fuzztime=100x -fuzzminimizetime=1000x
stdout 'testdata[/\\]fuzz[/\\]FuzzInt[/\\]'
stdout 'this input caused a crash!'
go run check_testdata.go FuzzInt

! go test -run=FuzzUint -fuzz=FuzzUint -fuzztime=100x -fuzzminimizetime=1000x
stdout 'testdata[/\\]fuzz[/\\]FuzzUint[/\\]'
stdout 'this input caused a crash!'
go run check_testdata.go FuzzUint

# Running the fuzzer should find a crashing input quickly for a bool.
! go test -run=FuzzBool -fuzz=FuzzBool -fuzztime=100x -fuzzminimizetime=1000x
stdout 'testdata[/\\]fuzz[/\\]FuzzBool[/\\]'
stdout 'this input caused a crash!'
go run check_testdata.go FuzzBool

# Running the fuzzer should find a crashing input quickly for a float.
! go test -run=FuzzFloat -fuzz=FuzzFloat -fuzztime=100x -fuzzminimizetime=1000x
stdout 'testdata[/\\]fuzz[/\\]FuzzFloat[/\\]'
stdout 'this input caused a crash!'
go run check_testdata.go FuzzFloat

# Running the fuzzer should find a crashing input quickly for a byte.
! go test -run=FuzzByte -fuzz=FuzzByte -fuzztime=100x -fuzzminimizetime=1000x
stdout 'testdata[/\\]fuzz[/\\]FuzzByte[/\\]'
stdout 'this input caused a crash!'
go run check_testdata.go FuzzByte

# Running the fuzzer should find a crashing input quickly for a rune.
! go test -run=FuzzRune -fuzz=FuzzRune -fuzztime=100x -fuzzminimizetime=1000x
stdout 'testdata[/\\]fuzz[/\\]FuzzRune[/\\]'
stdout 'this input caused a crash!'
go run check_testdata.go FuzzRune

# Running the fuzzer should find a crashing input quickly for a string.
! go test -run=FuzzString -fuzz=FuzzString -fuzztime=100x -fuzzminimizetime=1000x
stdout 'testdata[/\\]fuzz[/\\]FuzzString[/\\]'
stdout 'this input caused a crash!'
go run check_testdata.go FuzzString

-- go.mod --
module m

go 1.16
-- fuzz_crash_test.go --
package fuzz_crash

import (
	"os"
	"runtime"
	"testing"
)

func FuzzWithBug(f *testing.F) {
	f.Add([]byte("aa"))
	f.Fuzz(func(t *testing.T, b []byte) {
		if string(b) != "aa" {
			panic("this input caused a crash!")
		}
	})
}

func FuzzWithNilPanic(f *testing.F) {
	f.Add([]byte("aa"))
	f.Fuzz(func(t *testing.T, b []byte) {
		if string(b) != "aa" {
			panic(nil)
		}
	})
}

func FuzzWithGoexit(f *testing.F) {
	f.Add([]byte("aa"))
	f.Fuzz(func(t *testing.T, b []byte) {
		if string(b) != "aa" {
			runtime.Goexit()
		}
	})
}

func FuzzWithFail(f *testing.F) {
	f.Add([]byte("aa"))
	f.Fuzz(func(t *testing.T, b []byte) {
		if string(b) != "aa" {
			t.Fail()
		}
	})
}

func FuzzWithLogFail(f *testing.F) {
	f.Add([]byte("aa"))
	f.Fuzz(func(t *testing.T, b []byte) {
		if string(b) != "aa" {
			t.Log("logged something")
			t.Fail()
		}
	})
}

func FuzzWithErrorf(f *testing.F) {
	f.Add([]byte("aa"))
	f.Fuzz(func(t *testing.T, b []byte) {
		if string(b) != "aa" {
			t.Errorf("errorf was called here")
		}
	})
}

func FuzzWithFatalf(f *testing.F) {
	f.Add([]byte("aa"))
	f.Fuzz(func(t *testing.T, b []byte) {
		if string(b) != "aa" {
			t.Fatalf("fatalf was called here")
		}
	})
}

func FuzzWithBadExit(f *testing.F) {
	f.Add([]byte("aa"))
	f.Fuzz(func(t *testing.T, b []byte) {
		if string(b) != "aa" {
			os.Exit(1)
		}
	})
}

func FuzzDeadlock(f *testing.F) {
	f.Add(int(0))
	f.Fuzz(func(t *testing.T, n int) {
		if n != 0 {
			select {}
		}
	})
}

func FuzzWithTwoTypes(f *testing.F) {
	f.Fuzz(func(t *testing.T, a, b []byte) {
		if len(a) > 0 && len(b) > 0 {
			panic("these inputs caused a crash!")
		}
	})
}

func FuzzInt(f *testing.F) {
	f.Add(0)
	f.Fuzz(func(t *testing.T, a int) {
		if a != 0 {
			panic("this input caused a crash!")
		}
	})
}

func FuzzUint(f *testing.F) {
	f.Add(uint(0))
	f.Fuzz(func(t *testing.T, a uint) {
		if a != 0 {
			panic("this input caused a crash!")
		}
	})
}

func FuzzBool(f *testing.F) {
	f.Add(false)
	f.Fuzz(func(t *testing.T, a bool) {
		if a {
			panic("this input caused a crash!")
		}
	})
}

func FuzzFloat(f *testing.F) {
	f.Fuzz(func(t *testing.T, a float64) {
		if a != 0 {
			panic("this input caused a crash!")
		}
	})
}

func FuzzByte(f *testing.F) {
	f.Add(byte(0))
	f.Fuzz(func(t *testing.T, a byte) {
		if a != 0 {
			panic("this input caused a crash!")
		}
	})
}

func FuzzRune(f *testing.F) {
	f.Add(rune(0))
	f.Fuzz(func(t *testing.T, a rune) {
		if a != 0 {
			panic("this input caused a crash!")
		}
	})
}

func FuzzString(f *testing.F) {
	f.Add("")
	f.Fuzz(func(t *testing.T, a string) {
		if a != "" {
			panic("this input caused a crash!")
		}
	})
}

-- check_testdata.go --
// +build ignore

package main

import (
	"bytes"
	"crypto/sha256"
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
)

func main() {
	target := os.Args[1]
	dir := filepath.Join("testdata/fuzz", target)

	files, err := ioutil.ReadDir(dir)
	if err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}

	if len(files) == 0 {
		fmt.Fprintf(os.Stderr, "expect at least one new mutation to be written to testdata\n")
		os.Exit(1)
	}

	fname := files[0].Name()
	contents, err := ioutil.ReadFile(filepath.Join(dir, fname))
	if err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}
	if bytes.Equal(contents, []byte("aa")) {
		fmt.Fprintf(os.Stderr, "newly written testdata entry was not mutated\n")
		os.Exit(1)
	}
	// The hash of the bytes in the file should match the filename.
	h := []byte(fmt.Sprintf("%x", sha256.Sum256(contents)))
	if !bytes.HasPrefix(h, []byte(fname)) {
		fmt.Fprintf(os.Stderr, "hash of bytes %q does not match filename %q\n", h, fname)
		os.Exit(1)
	}
}
