// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

//go:build darwin || freebsd || linux || windows

package fuzz

import (
	"bytes"
	"context"
	"errors"
	"fmt"
	"reflect"
	"testing"
	"time"
	"unicode"
	"unicode/utf8"
)

func TestMinimizeInput(t *testing.T) {
	type testcase struct {
		name     string
		fn       func(CorpusEntry) error
		input    []any
		expected []any
	}
	cases := []testcase{
		{
			name: "ones_byte",
			fn: func(e CorpusEntry) error {
				b := e.Values[0].([]byte)
				ones := 0
				for _, v := range b {
					if v == 1 {
						ones++
					}
				}
				if ones == 3 {
					return fmt.Errorf("bad %v", e.Values[0])
				}
				return nil
			},
			input:    []any{[]byte{0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}},
			expected: []any{[]byte{1, 1, 1}},
		},
		{
			name: "single_bytes",
			fn: func(e CorpusEntry) error {
				b := e.Values[0].([]byte)
				if len(b) < 2 {
					return nil
				}
				if len(b) == 2 && b[0] == 1 && b[1] == 2 {
					return nil
				}
				return fmt.Errorf("bad %v", e.Values[0])
			},
			input:    []any{[]byte{1, 2, 3, 4, 5}},
			expected: []any{[]byte("00")},
		},
		{
			name: "set_of_bytes",
			fn: func(e CorpusEntry) error {
				b := e.Values[0].([]byte)
				if len(b) < 3 {
					return nil
				}
				if bytes.Equal(b, []byte{0, 1, 2, 3, 4, 5}) || bytes.Equal(b, []byte{0, 4, 5}) {
					return fmt.Errorf("bad %v", e.Values[0])
				}
				return nil
			},
			input:    []any{[]byte{0, 1, 2, 3, 4, 5}},
			expected: []any{[]byte{0, 4, 5}},
		},
		{
			name: "non_ascii_bytes",
			fn: func(e CorpusEntry) error {
				b := e.Values[0].([]byte)
				if len(b) == 3 {
					return fmt.Errorf("bad %v", e.Values[0])
				}
				return nil
			},
			input:    []any{[]byte("ท")}, // ท is 3 bytes
			expected: []any{[]byte("000")},
		},
		{
			name: "ones_string",
			fn: func(e CorpusEntry) error {
				b := e.Values[0].(string)
				ones := 0
				for _, v := range b {
					if v == '1' {
						ones++
					}
				}
				if ones == 3 {
					return fmt.Errorf("bad %v", e.Values[0])
				}
				return nil
			},
			input:    []any{"001010001000000000000000000"},
			expected: []any{"111"},
		},
		{
			name: "string_length",
			fn: func(e CorpusEntry) error {
				b := e.Values[0].(string)
				if len(b) == 5 {
					return fmt.Errorf("bad %v", e.Values[0])
				}
				return nil
			},
			input:    []any{"zzzzz"},
			expected: []any{"00000"},
		},
		{
			name: "string_with_letter",
			fn: func(e CorpusEntry) error {
				b := e.Values[0].(string)
				r, _ := utf8.DecodeRune([]byte(b))
				if unicode.IsLetter(r) {
					return fmt.Errorf("bad %v", e.Values[0])
				}
				return nil
			},
			input:    []any{"ZZZZZ"},
			expected: []any{"A"},
		},
	}

	for _, tc := range cases {
		tc := tc
		t.Run(tc.name, func(t *testing.T) {
			t.Parallel()
			ws := &workerServer{
				fuzzFn: func(e CorpusEntry) (time.Duration, error) {
					return time.Second, tc.fn(e)
				},
			}
			mem := &sharedMem{region: make([]byte, 100)} // big enough to hold value and header
			vals := tc.input
			success, err := ws.minimizeInput(context.Background(), vals, mem, minimizeArgs{})
			if !success {
				t.Errorf("minimizeInput did not succeed")
			}
			if err == nil {
				t.Fatal("minimizeInput didn't provide an error")
			}
			if expected := fmt.Sprintf("bad %v", tc.expected[0]); err.Error() != expected {
				t.Errorf("unexpected error: got %q, want %q", err, expected)
			}
			if !reflect.DeepEqual(vals, tc.expected) {
				t.Errorf("unexpected results: got %v, want %v", vals, tc.expected)
			}
		})
	}
}

// TestMinimizeFlaky checks that if we're minimizing an interesting
// input and a flaky failure occurs, that minimization was not indicated
// to be successful, and the error isn't returned (since it's flaky).
func TestMinimizeFlaky(t *testing.T) {
	ws := &workerServer{fuzzFn: func(e CorpusEntry) (time.Duration, error) {
		return time.Second, errors.New("ohno")
	}}
	mem := &sharedMem{region: make([]byte, 100)} // big enough to hold value and header
	vals := []any{[]byte(nil)}
	args := minimizeArgs{KeepCoverage: make([]byte, len(coverageSnapshot))}
	success, err := ws.minimizeInput(context.Background(), vals, mem, args)
	if success {
		t.Error("unexpected success")
	}
	if err != nil {
		t.Errorf("unexpected error: %v", err)
	}
	if count := mem.header().count; count != 1 {
		t.Errorf("count: got %d, want 1", count)
	}
}
