// Copyright 2011 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package gomock_test

import (
	"fmt"
	"reflect"
	"testing"

	"strings"

	"github.com/golang/mock/gomock"
)

type ErrorReporter struct {
	t          *testing.T
	log        []string
	failed     bool
	fatalToken struct{}
}

func NewErrorReporter(t *testing.T) *ErrorReporter {
	return &ErrorReporter{t: t}
}

func (e *ErrorReporter) reportLog() {
	for _, entry := range e.log {
		e.t.Log(entry)
	}
}

func (e *ErrorReporter) assertPass(msg string) {
	if e.failed {
		e.t.Errorf("Expected pass, but got failure(s): %s", msg)
		e.reportLog()
	}
}

func (e *ErrorReporter) assertFail(msg string) {
	if !e.failed {
		e.t.Errorf("Expected failure, but got pass: %s", msg)
	}
}

// Use to check that code triggers a fatal test failure.
func (e *ErrorReporter) assertFatal(fn func(), expectedErrMsgs ...string) {
	defer func() {
		err := recover()
		if err == nil {
			var actual string
			if e.failed {
				actual = "non-fatal failure"
			} else {
				actual = "pass"
			}
			e.t.Error("Expected fatal failure, but got a", actual)
		} else if token, ok := err.(*struct{}); ok && token == &e.fatalToken {
			// This is okay - the panic is from Fatalf().
			if expectedErrMsgs != nil {
				// assert that the actual error message
				// contains expectedErrMsgs

				// check the last actualErrMsg, because the previous messages come from previous errors
				actualErrMsg := e.log[len(e.log)-1]
				for _, expectedErrMsg := range expectedErrMsgs {
					if !strings.Contains(actualErrMsg, expectedErrMsg) {
						e.t.Errorf("Error message:\ngot: %q\nwant to contain: %q\n", actualErrMsg, expectedErrMsg)
					}
				}
			}
			return
		} else {
			// Some other panic.
			panic(err)
		}
	}()

	fn()
}

// recoverUnexpectedFatal can be used as a deferred call in test cases to
// recover from and display a call to ErrorReporter.Fatalf().
func (e *ErrorReporter) recoverUnexpectedFatal() {
	err := recover()
	if err == nil {
		// No panic.
	} else if token, ok := err.(*struct{}); ok && token == &e.fatalToken {
		// Unexpected fatal error happened.
		e.t.Error("Got unexpected fatal error(s). All errors up to this point:")
		e.reportLog()
		return
	} else {
		// Some other panic.
		panic(err)
	}
}

func (e *ErrorReporter) Logf(format string, args ...interface{}) {
	e.log = append(e.log, fmt.Sprintf(format, args...))
}

func (e *ErrorReporter) Errorf(format string, args ...interface{}) {
	e.Logf(format, args...)
	e.failed = true
}

func (e *ErrorReporter) Fatalf(format string, args ...interface{}) {
	e.Logf(format, args...)
	e.failed = true
	panic(&e.fatalToken)
}

// A type purely for use as a receiver in testing the Controller.
type Subject struct{}

func (s *Subject) FooMethod(arg string) int {
	return 0
}

func (s *Subject) BarMethod(arg string) int {
	return 0
}

func (s *Subject) VariadicMethod(arg int, vararg ...string) {}

// A type purely for ActOnTestStructMethod
type TestStruct struct {
	Number  int
	Message string
}

func (s *Subject) ActOnTestStructMethod(arg TestStruct, arg1 int) int {
	return 0
}

func (s *Subject) SetArgMethod(sliceArg []byte, ptrArg *int) {}

func assertEqual(t *testing.T, expected interface{}, actual interface{}) {
	if !reflect.DeepEqual(expected, actual) {
		t.Errorf("Expected %+v, but got %+v", expected, actual)
	}
}

func createFixtures(t *testing.T) (reporter *ErrorReporter, ctrl *gomock.Controller) {
	// reporter acts as a testing.T-like object that we pass to the
	// Controller. We use it to test that the mock considered tests
	// successful or failed.
	reporter = NewErrorReporter(t)
	ctrl = gomock.NewController(reporter)
	return
}

func TestNoCalls(t *testing.T) {
	reporter, ctrl := createFixtures(t)
	ctrl.Finish()
	reporter.assertPass("No calls expected or made.")
}

func TestNoRecordedCallsForAReceiver(t *testing.T) {
	reporter, ctrl := createFixtures(t)
	subject := new(Subject)

	reporter.assertFatal(func() {
		ctrl.Call(subject, "NotRecordedMethod", "argument")
	}, "Unexpected call to", "there are no expected calls of the method \"NotRecordedMethod\" for that receiver")
	ctrl.Finish()
}

func TestNoRecordedMatchingMethodNameForAReceiver(t *testing.T) {
	reporter, ctrl := createFixtures(t)
	subject := new(Subject)

	ctrl.RecordCall(subject, "FooMethod", "argument")
	reporter.assertFatal(func() {
		ctrl.Call(subject, "NotRecordedMethod", "argument")
	}, "Unexpected call to", "there are no expected calls of the method \"NotRecordedMethod\" for that receiver")
	reporter.assertFatal(func() {
		// The expected call wasn't made.
		ctrl.Finish()
	})
}

// This tests that a call with an arguments of some primitive type matches a recorded call.
func TestExpectedMethodCall(t *testing.T) {
	reporter, ctrl := createFixtures(t)
	subject := new(Subject)

	ctrl.RecordCall(subject, "FooMethod", "argument")
	ctrl.Call(subject, "FooMethod", "argument")
	ctrl.Finish()

	reporter.assertPass("Expected method call made.")
}

func TestUnexpectedMethodCall(t *testing.T) {
	reporter, ctrl := createFixtures(t)
	subject := new(Subject)

	reporter.assertFatal(func() {
		ctrl.Call(subject, "FooMethod", "argument")
	})

	ctrl.Finish()
}

func TestRepeatedCall(t *testing.T) {
	reporter, ctrl := createFixtures(t)
	subject := new(Subject)

	ctrl.RecordCall(subject, "FooMethod", "argument").Times(3)
	ctrl.Call(subject, "FooMethod", "argument")
	ctrl.Call(subject, "FooMethod", "argument")
	ctrl.Call(subject, "FooMethod", "argument")
	reporter.assertPass("After expected repeated method calls.")
	reporter.assertFatal(func() {
		ctrl.Call(subject, "FooMethod", "argument")
	})
	ctrl.Finish()
	reporter.assertFail("After calling one too many times.")
}

func TestUnexpectedArgCount(t *testing.T) {
	reporter, ctrl := createFixtures(t)
	defer reporter.recoverUnexpectedFatal()
	subject := new(Subject)

	ctrl.RecordCall(subject, "FooMethod", "argument")
	reporter.assertFatal(func() {
		// This call is made with the wrong number of arguments...
		ctrl.Call(subject, "FooMethod", "argument", "extra_argument")
	}, "Unexpected call to", "wrong number of arguments", "Got: 2, want: 1")
	reporter.assertFatal(func() {
		// ... so is this.
		ctrl.Call(subject, "FooMethod")
	}, "Unexpected call to", "wrong number of arguments", "Got: 0, want: 1")
	reporter.assertFatal(func() {
		// The expected call wasn't made.
		ctrl.Finish()
	})
}

// This tests that a call with complex arguments (a struct and some primitive type) matches a recorded call.
func TestExpectedMethodCall_CustomStruct(t *testing.T) {
	reporter, ctrl := createFixtures(t)
	subject := new(Subject)

	expectedArg0 := TestStruct{Number: 123, Message: "hello"}
	ctrl.RecordCall(subject, "ActOnTestStructMethod", expectedArg0, 15)
	ctrl.Call(subject, "ActOnTestStructMethod", expectedArg0, 15)

	reporter.assertPass("Expected method call made.")
}

func TestUnexpectedArgValue_FirstArg(t *testing.T) {
	reporter, ctrl := createFixtures(t)
	defer reporter.recoverUnexpectedFatal()
	subject := new(Subject)

	expectedArg0 := TestStruct{Number: 123, Message: "hello"}
	ctrl.RecordCall(subject, "ActOnTestStructMethod", expectedArg0, 15)

	reporter.assertFatal(func() {
		// the method argument (of TestStruct type) has 1 unexpected value (for the Message field)
		ctrl.Call(subject, "ActOnTestStructMethod", TestStruct{Number: 123, Message: "no message"}, 15)
	}, "Unexpected call to", "doesn't match the argument at index 0",
		"Got: {123 no message}\nWant: is equal to {123 hello}")

	reporter.assertFatal(func() {
		// the method argument (of TestStruct type) has 2 unexpected values (for both fields)
		ctrl.Call(subject, "ActOnTestStructMethod", TestStruct{Number: 11, Message: "no message"}, 15)
	}, "Unexpected call to", "doesn't match the argument at index 0",
		"Got: {11 no message}\nWant: is equal to {123 hello}")

	reporter.assertFatal(func() {
		// The expected call wasn't made.
		ctrl.Finish()
	})
}

func TestUnexpectedArgValue_SecondtArg(t *testing.T) {
	reporter, ctrl := createFixtures(t)
	defer reporter.recoverUnexpectedFatal()
	subject := new(Subject)

	expectedArg0 := TestStruct{Number: 123, Message: "hello"}
	ctrl.RecordCall(subject, "ActOnTestStructMethod", expectedArg0, 15)

	reporter.assertFatal(func() {
		ctrl.Call(subject, "ActOnTestStructMethod", TestStruct{Number: 123, Message: "hello"}, 3)
	}, "Unexpected call to", "doesn't match the argument at index 1",
		"Got: 3\nWant: is equal to 15")

	reporter.assertFatal(func() {
		// The expected call wasn't made.
		ctrl.Finish()
	})
}

func TestAnyTimes(t *testing.T) {
	reporter, ctrl := createFixtures(t)
	subject := new(Subject)

	ctrl.RecordCall(subject, "FooMethod", "argument").AnyTimes()
	for i := 0; i < 100; i++ {
		ctrl.Call(subject, "FooMethod", "argument")
	}
	reporter.assertPass("After 100 method calls.")
	ctrl.Finish()
}

func TestMinTimes1(t *testing.T) {
	// It fails if there are no calls
	reporter, ctrl := createFixtures(t)
	subject := new(Subject)
	ctrl.RecordCall(subject, "FooMethod", "argument").MinTimes(1)
	reporter.assertFatal(func() {
		ctrl.Finish()
	})

	// It succeeds if there is one call
	reporter, ctrl = createFixtures(t)
	subject = new(Subject)
	ctrl.RecordCall(subject, "FooMethod", "argument").MinTimes(1)
	ctrl.Call(subject, "FooMethod", "argument")
	ctrl.Finish()

	// It succeeds if there are many calls
	reporter, ctrl = createFixtures(t)
	subject = new(Subject)
	ctrl.RecordCall(subject, "FooMethod", "argument").MinTimes(1)
	for i := 0; i < 100; i++ {
		ctrl.Call(subject, "FooMethod", "argument")
	}
	ctrl.Finish()
}

func TestMaxTimes1(t *testing.T) {
	// It succeeds if there are no calls
	_, ctrl := createFixtures(t)
	subject := new(Subject)
	ctrl.RecordCall(subject, "FooMethod", "argument").MaxTimes(1)
	ctrl.Finish()

	// It succeeds if there is one call
	_, ctrl = createFixtures(t)
	subject = new(Subject)
	ctrl.RecordCall(subject, "FooMethod", "argument").MaxTimes(1)
	ctrl.Call(subject, "FooMethod", "argument")
	ctrl.Finish()

	//It fails if there are more
	reporter, ctrl := createFixtures(t)
	subject = new(Subject)
	ctrl.RecordCall(subject, "FooMethod", "argument").MaxTimes(1)
	ctrl.Call(subject, "FooMethod", "argument")
	reporter.assertFatal(func() {
		ctrl.Call(subject, "FooMethod", "argument")
	})
	ctrl.Finish()
}

func TestMinMaxTimes(t *testing.T) {
	// It fails if there are less calls than specified
	reporter, ctrl := createFixtures(t)
	subject := new(Subject)
	ctrl.RecordCall(subject, "FooMethod", "argument").MinTimes(2).MaxTimes(2)
	ctrl.Call(subject, "FooMethod", "argument")
	reporter.assertFatal(func() {
		ctrl.Finish()
	})

	// It fails if there are more calls than specified
	reporter, ctrl = createFixtures(t)
	subject = new(Subject)
	ctrl.RecordCall(subject, "FooMethod", "argument").MinTimes(2).MaxTimes(2)
	ctrl.Call(subject, "FooMethod", "argument")
	ctrl.Call(subject, "FooMethod", "argument")
	reporter.assertFatal(func() {
		ctrl.Call(subject, "FooMethod", "argument")
	})

	// It succeeds if there is just the right number of calls
	reporter, ctrl = createFixtures(t)
	subject = new(Subject)
	ctrl.RecordCall(subject, "FooMethod", "argument").MaxTimes(2).MinTimes(2)
	ctrl.Call(subject, "FooMethod", "argument")
	ctrl.Call(subject, "FooMethod", "argument")
	ctrl.Finish()
}

func TestDo(t *testing.T) {
	_, ctrl := createFixtures(t)
	subject := new(Subject)

	doCalled := false
	var argument string
	ctrl.RecordCall(subject, "FooMethod", "argument").Do(
		func(arg string) {
			doCalled = true
			argument = arg
		})
	if doCalled {
		t.Error("Do() callback called too early.")
	}

	ctrl.Call(subject, "FooMethod", "argument")

	if !doCalled {
		t.Error("Do() callback not called.")
	}
	if "argument" != argument {
		t.Error("Do callback received wrong argument.")
	}

	ctrl.Finish()
}

func TestDoAndReturn(t *testing.T) {
	_, ctrl := createFixtures(t)
	subject := new(Subject)

	doCalled := false
	var argument string
	ctrl.RecordCall(subject, "FooMethod", "argument").DoAndReturn(
		func(arg string) int {
			doCalled = true
			argument = arg
			return 5
		})
	if doCalled {
		t.Error("Do() callback called too early.")
	}

	rets := ctrl.Call(subject, "FooMethod", "argument")

	if !doCalled {
		t.Error("Do() callback not called.")
	}
	if "argument" != argument {
		t.Error("Do callback received wrong argument.")
	}
	if len(rets) != 1 {
		t.Fatalf("Return values from Call: got %d, want 1", len(rets))
	}
	if ret, ok := rets[0].(int); !ok {
		t.Fatalf("Return value is not an int")
	} else if ret != 5 {
		t.Errorf("DoAndReturn return value: got %d, want 5", ret)
	}

	ctrl.Finish()
}

func TestSetArgSlice(t *testing.T) {
	_, ctrl := createFixtures(t)
	subject := new(Subject)

	var in = []byte{4, 5, 6}
	var set = []byte{1, 2, 3}
	ctrl.RecordCall(subject, "SetArgMethod", in, nil).SetArg(0, set)
	ctrl.Call(subject, "SetArgMethod", in, nil)

	if !reflect.DeepEqual(in, set) {
		t.Error("Expected SetArg() to modify input slice argument")
	}

	ctrl.Finish()
}

func TestSetArgPtr(t *testing.T) {
	_, ctrl := createFixtures(t)
	subject := new(Subject)

	var in int = 43
	const set = 42
	ctrl.RecordCall(subject, "SetArgMethod", nil, &in).SetArg(1, set)
	ctrl.Call(subject, "SetArgMethod", nil, &in)

	if in != set {
		t.Error("Expected SetArg() to modify value pointed to by argument")
	}

	ctrl.Finish()
}

func TestReturn(t *testing.T) {
	_, ctrl := createFixtures(t)
	subject := new(Subject)

	// Unspecified return should produce "zero" result.
	ctrl.RecordCall(subject, "FooMethod", "zero")
	ctrl.RecordCall(subject, "FooMethod", "five").Return(5)

	assertEqual(
		t,
		[]interface{}{0},
		ctrl.Call(subject, "FooMethod", "zero"))

	assertEqual(
		t,
		[]interface{}{5},
		ctrl.Call(subject, "FooMethod", "five"))
	ctrl.Finish()
}

func TestUnorderedCalls(t *testing.T) {
	reporter, ctrl := createFixtures(t)
	defer reporter.recoverUnexpectedFatal()
	subjectTwo := new(Subject)
	subjectOne := new(Subject)

	ctrl.RecordCall(subjectOne, "FooMethod", "1")
	ctrl.RecordCall(subjectOne, "BarMethod", "2")
	ctrl.RecordCall(subjectTwo, "FooMethod", "3")
	ctrl.RecordCall(subjectTwo, "BarMethod", "4")

	// Make the calls in a different order, which should be fine.
	ctrl.Call(subjectOne, "BarMethod", "2")
	ctrl.Call(subjectTwo, "FooMethod", "3")
	ctrl.Call(subjectTwo, "BarMethod", "4")
	ctrl.Call(subjectOne, "FooMethod", "1")

	reporter.assertPass("After making all calls in different order")

	ctrl.Finish()

	reporter.assertPass("After finish")
}

func commonTestOrderedCalls(t *testing.T) (reporter *ErrorReporter, ctrl *gomock.Controller, subjectOne, subjectTwo *Subject) {
	reporter, ctrl = createFixtures(t)

	subjectOne = new(Subject)
	subjectTwo = new(Subject)

	gomock.InOrder(
		ctrl.RecordCall(subjectOne, "FooMethod", "1").AnyTimes(),
		ctrl.RecordCall(subjectTwo, "FooMethod", "2"),
		ctrl.RecordCall(subjectTwo, "BarMethod", "3"),
	)

	return
}

func TestOrderedCallsCorrect(t *testing.T) {
	reporter, ctrl, subjectOne, subjectTwo := commonTestOrderedCalls(t)

	ctrl.Call(subjectOne, "FooMethod", "1")
	ctrl.Call(subjectTwo, "FooMethod", "2")
	ctrl.Call(subjectTwo, "BarMethod", "3")

	ctrl.Finish()

	reporter.assertPass("After finish")
}

func TestOrderedCallsInCorrect(t *testing.T) {
	reporter, ctrl, subjectOne, subjectTwo := commonTestOrderedCalls(t)

	ctrl.Call(subjectOne, "FooMethod", "1")
	reporter.assertFatal(func() {
		// FooMethod(2) should be called before BarMethod(3)
		ctrl.Call(subjectTwo, "BarMethod", "3")
	}, "Unexpected call to", "Subject.BarMethod([3])", "doesn't have a prerequisite call satisfied")
}

// Test that calls that are prerequisites to other calls but have maxCalls >
// minCalls are removed from the expected call set.
func TestOrderedCallsWithPreReqMaxUnbounded(t *testing.T) {
	reporter, ctrl, subjectOne, subjectTwo := commonTestOrderedCalls(t)

	// Initially we should be able to call FooMethod("1") as many times as we
	// want.
	ctrl.Call(subjectOne, "FooMethod", "1")
	ctrl.Call(subjectOne, "FooMethod", "1")

	// But calling something that has it as a prerequite should remove it from
	// the expected call set. This allows tests to ensure that FooMethod("1") is
	// *not* called after FooMethod("2").
	ctrl.Call(subjectTwo, "FooMethod", "2")

	// Therefore this call should fail:
	reporter.assertFatal(func() {
		ctrl.Call(subjectOne, "FooMethod", "1")
	})
}

func TestCallAfterLoopPanic(t *testing.T) {
	_, ctrl := createFixtures(t)

	subject := new(Subject)

	firstCall := ctrl.RecordCall(subject, "FooMethod", "1")
	secondCall := ctrl.RecordCall(subject, "FooMethod", "2")
	thirdCall := ctrl.RecordCall(subject, "FooMethod", "3")

	gomock.InOrder(firstCall, secondCall, thirdCall)

	defer func() {
		err := recover()
		if err == nil {
			t.Error("Call.After creation of dependency loop did not panic.")
		}
	}()

	// This should panic due to dependency loop.
	firstCall.After(thirdCall)
}

func TestPanicOverridesExpectationChecks(t *testing.T) {
	ctrl := gomock.NewController(t)
	reporter := NewErrorReporter(t)

	reporter.assertFatal(func() {
		ctrl.RecordCall(new(Subject), "FooMethod", "1")
		defer ctrl.Finish()
		reporter.Fatalf("Intentional panic")
	})
}

func TestSetArgWithBadType(t *testing.T) {
	rep, ctrl := createFixtures(t)
	defer ctrl.Finish()

	s := new(Subject)
	// This should catch a type error:
	rep.assertFatal(func() {
		ctrl.RecordCall(s, "FooMethod", "1").SetArg(0, "blah")
	})
	ctrl.Call(s, "FooMethod", "1")
}

func TestTimes0(t *testing.T) {
	rep, ctrl := createFixtures(t)
	defer ctrl.Finish()

	s := new(Subject)
	ctrl.RecordCall(s, "FooMethod", "arg").Times(0)
	rep.assertFatal(func() {
		ctrl.Call(s, "FooMethod", "arg")
	})
}

func TestVariadicMatching(t *testing.T) {
	rep, ctrl := createFixtures(t)
	defer rep.recoverUnexpectedFatal()

	s := new(Subject)
	ctrl.RecordCall(s, "VariadicMethod", 0, "1", "2")
	ctrl.Call(s, "VariadicMethod", 0, "1", "2")
	ctrl.Finish()
	rep.assertPass("variadic matching works")
}

func TestVariadicNoMatch(t *testing.T) {
	rep, ctrl := createFixtures(t)
	defer rep.recoverUnexpectedFatal()

	s := new(Subject)
	ctrl.RecordCall(s, "VariadicMethod", 0)
	rep.assertFatal(func() {
		ctrl.Call(s, "VariadicMethod", 1)
	}, "Expected call at", "doesn't match the argument at index 0",
		"Got: 1\nWant: is equal to 0")
	ctrl.Call(s, "VariadicMethod", 0)
	ctrl.Finish()
}

func TestVariadicMatchingWithSlice(t *testing.T) {
	testCases := [][]string{
		{"1"},
		{"1", "2"},
	}
	for _, tc := range testCases {
		t.Run(fmt.Sprintf("%d arguments", len(tc)), func(t *testing.T) {
			rep, ctrl := createFixtures(t)
			defer rep.recoverUnexpectedFatal()

			s := new(Subject)
			ctrl.RecordCall(s, "VariadicMethod", 1, tc)
			args := make([]interface{}, len(tc)+1)
			args[0] = 1
			for i, arg := range tc {
				args[i+1] = arg
			}
			ctrl.Call(s, "VariadicMethod", args...)
			ctrl.Finish()
			rep.assertPass("slices can be used as matchers for variadic arguments")
		})
	}
}

func TestDuplicateFinishCallFails(t *testing.T) {
	rep, ctrl := createFixtures(t)

	ctrl.Finish()
	rep.assertPass("the first Finish call should succeed")

	rep.assertFatal(ctrl.Finish, "Controller.Finish was called more than once. It has to be called exactly once.")
}
