// These tests check that the foundations of gocheck are working properly.
// They already assume that fundamental failing is working already, though,
// since this was tested in bootstrap_test.go. Even then, some care may
// still have to be taken when using external functions, since they should
// of course not rely on functionality tested here.

package check_test

import (
	"fmt"
	"gopkg.in/check.v1"
	"log"
	"os"
	"regexp"
	"strings"
)

// -----------------------------------------------------------------------
// Foundation test suite.

type FoundationS struct{}

var foundationS = check.Suite(&FoundationS{})

func (s *FoundationS) TestCountSuite(c *check.C) {
	suitesRun += 1
}

func (s *FoundationS) TestErrorf(c *check.C) {
	// Do not use checkState() here.  It depends on Errorf() working.
	expectedLog := fmt.Sprintf("foundation_test.go:%d:\n"+
		"    c.Errorf(\"Error %%v!\", \"message\")\n"+
		"... Error: Error message!\n\n",
		getMyLine()+1)
	c.Errorf("Error %v!", "message")
	failed := c.Failed()
	c.Succeed()
	if log := c.GetTestLog(); log != expectedLog {
		c.Logf("Errorf() logged %#v rather than %#v", log, expectedLog)
		c.Fail()
	}
	if !failed {
		c.Logf("Errorf() didn't put the test in a failed state")
		c.Fail()
	}
}

func (s *FoundationS) TestError(c *check.C) {
	expectedLog := fmt.Sprintf("foundation_test.go:%d:\n"+
		"    c\\.Error\\(\"Error \", \"message!\"\\)\n"+
		"\\.\\.\\. Error: Error message!\n\n",
		getMyLine()+1)
	c.Error("Error ", "message!")
	checkState(c, nil,
		&expectedState{
			name:   "Error(`Error `, `message!`)",
			failed: true,
			log:    expectedLog,
		})
}

func (s *FoundationS) TestFailNow(c *check.C) {
	defer (func() {
		if !c.Failed() {
			c.Error("FailNow() didn't fail the test")
		} else {
			c.Succeed()
			if c.GetTestLog() != "" {
				c.Error("Something got logged:\n" + c.GetTestLog())
			}
		}
	})()

	c.FailNow()
	c.Log("FailNow() didn't stop the test")
}

func (s *FoundationS) TestSucceedNow(c *check.C) {
	defer (func() {
		if c.Failed() {
			c.Error("SucceedNow() didn't succeed the test")
		}
		if c.GetTestLog() != "" {
			c.Error("Something got logged:\n" + c.GetTestLog())
		}
	})()

	c.Fail()
	c.SucceedNow()
	c.Log("SucceedNow() didn't stop the test")
}

func (s *FoundationS) TestFailureHeader(c *check.C) {
	output := String{}
	failHelper := FailHelper{}
	check.Run(&failHelper, &check.RunConf{Output: &output})
	header := fmt.Sprintf(""+
		"\n-----------------------------------"+
		"-----------------------------------\n"+
		"FAIL: check_test.go:%d: FailHelper.TestLogAndFail\n",
		failHelper.testLine)
	if strings.Index(output.value, header) == -1 {
		c.Errorf(""+
			"Failure didn't print a proper header.\n"+
			"... Got:\n%s... Expected something with:\n%s",
			output.value, header)
	}
}

func (s *FoundationS) TestFatal(c *check.C) {
	var line int
	defer (func() {
		if !c.Failed() {
			c.Error("Fatal() didn't fail the test")
		} else {
			c.Succeed()
			expected := fmt.Sprintf("foundation_test.go:%d:\n"+
				"    c.Fatal(\"Die \", \"now!\")\n"+
				"... Error: Die now!\n\n",
				line)
			if c.GetTestLog() != expected {
				c.Error("Incorrect log:", c.GetTestLog())
			}
		}
	})()

	line = getMyLine() + 1
	c.Fatal("Die ", "now!")
	c.Log("Fatal() didn't stop the test")
}

func (s *FoundationS) TestFatalf(c *check.C) {
	var line int
	defer (func() {
		if !c.Failed() {
			c.Error("Fatalf() didn't fail the test")
		} else {
			c.Succeed()
			expected := fmt.Sprintf("foundation_test.go:%d:\n"+
				"    c.Fatalf(\"Die %%s!\", \"now\")\n"+
				"... Error: Die now!\n\n",
				line)
			if c.GetTestLog() != expected {
				c.Error("Incorrect log:", c.GetTestLog())
			}
		}
	})()

	line = getMyLine() + 1
	c.Fatalf("Die %s!", "now")
	c.Log("Fatalf() didn't stop the test")
}

func (s *FoundationS) TestCallerLoggingInsideTest(c *check.C) {
	log := fmt.Sprintf(""+
		"foundation_test.go:%d:\n"+
		"    result := c.Check\\(10, check.Equals, 20\\)\n"+
		"\\.\\.\\. obtained int = 10\n"+
		"\\.\\.\\. expected int = 20\n\n",
		getMyLine()+1)
	result := c.Check(10, check.Equals, 20)
	checkState(c, result,
		&expectedState{
			name:   "Check(10, Equals, 20)",
			result: false,
			failed: true,
			log:    log,
		})
}

func (s *FoundationS) TestCallerLoggingInDifferentFile(c *check.C) {
	result, line := checkEqualWrapper(c, 10, 20)
	testLine := getMyLine() - 1
	log := fmt.Sprintf(""+
		"foundation_test.go:%d:\n"+
		"    result, line := checkEqualWrapper\\(c, 10, 20\\)\n"+
		"check_test.go:%d:\n"+
		"    return c.Check\\(obtained, check.Equals, expected\\), getMyLine\\(\\)\n"+
		"\\.\\.\\. obtained int = 10\n"+
		"\\.\\.\\. expected int = 20\n\n",
		testLine, line)
	checkState(c, result,
		&expectedState{
			name:   "Check(10, Equals, 20)",
			result: false,
			failed: true,
			log:    log,
		})
}

// -----------------------------------------------------------------------
// ExpectFailure() inverts the logic of failure.

type ExpectFailureSucceedHelper struct{}

func (s *ExpectFailureSucceedHelper) TestSucceed(c *check.C) {
	c.ExpectFailure("It booms!")
	c.Error("Boom!")
}

type ExpectFailureFailHelper struct{}

func (s *ExpectFailureFailHelper) TestFail(c *check.C) {
	c.ExpectFailure("Bug #XYZ")
}

func (s *FoundationS) TestExpectFailureFail(c *check.C) {
	helper := ExpectFailureFailHelper{}
	output := String{}
	result := check.Run(&helper, &check.RunConf{Output: &output})

	expected := "" +
		"^\n-+\n" +
		"FAIL: foundation_test\\.go:[0-9]+:" +
		" ExpectFailureFailHelper\\.TestFail\n\n" +
		"\\.\\.\\. Error: Test succeeded, but was expected to fail\n" +
		"\\.\\.\\. Reason: Bug #XYZ\n$"

	matched, err := regexp.MatchString(expected, output.value)
	if err != nil {
		c.Error("Bad expression: ", expected)
	} else if !matched {
		c.Error("ExpectFailure() didn't log properly:\n", output.value)
	}

	c.Assert(result.ExpectedFailures, check.Equals, 0)
}

func (s *FoundationS) TestExpectFailureSucceed(c *check.C) {
	helper := ExpectFailureSucceedHelper{}
	output := String{}
	result := check.Run(&helper, &check.RunConf{Output: &output})

	c.Assert(output.value, check.Equals, "")
	c.Assert(result.ExpectedFailures, check.Equals, 1)
}

func (s *FoundationS) TestExpectFailureSucceedVerbose(c *check.C) {
	helper := ExpectFailureSucceedHelper{}
	output := String{}
	result := check.Run(&helper, &check.RunConf{Output: &output, Verbose: true})

	expected := "" +
		"FAIL EXPECTED: foundation_test\\.go:[0-9]+:" +
		" ExpectFailureSucceedHelper\\.TestSucceed \\(It booms!\\)\t *[.0-9]+s\n"

	matched, err := regexp.MatchString(expected, output.value)
	if err != nil {
		c.Error("Bad expression: ", expected)
	} else if !matched {
		c.Error("ExpectFailure() didn't log properly:\n", output.value)
	}

	c.Assert(result.ExpectedFailures, check.Equals, 1)
}

// -----------------------------------------------------------------------
// Skip() allows stopping a test without positive/negative results.

type SkipTestHelper struct{}

func (s *SkipTestHelper) TestFail(c *check.C) {
	c.Skip("Wrong platform or whatever")
	c.Error("Boom!")
}

func (s *FoundationS) TestSkip(c *check.C) {
	helper := SkipTestHelper{}
	output := String{}
	check.Run(&helper, &check.RunConf{Output: &output})

	if output.value != "" {
		c.Error("Skip() logged something:\n", output.value)
	}
}

func (s *FoundationS) TestSkipVerbose(c *check.C) {
	helper := SkipTestHelper{}
	output := String{}
	check.Run(&helper, &check.RunConf{Output: &output, Verbose: true})

	expected := "SKIP: foundation_test\\.go:[0-9]+: SkipTestHelper\\.TestFail" +
		" \\(Wrong platform or whatever\\)"
	matched, err := regexp.MatchString(expected, output.value)
	if err != nil {
		c.Error("Bad expression: ", expected)
	} else if !matched {
		c.Error("Skip() didn't log properly:\n", output.value)
	}
}

// -----------------------------------------------------------------------
// Check minimum *log.Logger interface provided by *check.C.

type minLogger interface {
	Output(calldepth int, s string) error
}

func (s *BootstrapS) TestMinLogger(c *check.C) {
	var logger minLogger
	logger = log.New(os.Stderr, "", 0)
	logger = c
	logger.Output(0, "Hello there")
	expected := `\[LOG\] [0-9]+:[0-9][0-9]\.[0-9][0-9][0-9] +Hello there\n`
	output := c.GetTestLog()
	c.Assert(output, check.Matches, expected)
}

// -----------------------------------------------------------------------
// Ensure that suites with embedded types are working fine, including the
// the workaround for issue 906.

type EmbeddedInternalS struct {
	called bool
}

type EmbeddedS struct {
	EmbeddedInternalS
}

var embeddedS = check.Suite(&EmbeddedS{})

func (s *EmbeddedS) TestCountSuite(c *check.C) {
	suitesRun += 1
}

func (s *EmbeddedInternalS) TestMethod(c *check.C) {
	c.Error("TestMethod() of the embedded type was called!?")
}

func (s *EmbeddedS) TestMethod(c *check.C) {
	// http://code.google.com/p/go/issues/detail?id=906
	c.Check(s.called, check.Equals, false) // Go issue 906 is affecting the runner?
	s.called = true
}
