// Framework for testing Elvish script. This file does not have a _test.go
// suffix so that it can be used from other packages that also want to test the
// modules they implement (e.g. edit: and re:).
//
// The entry point for the framework is the Test function, which accepts a
// *testing.T and a variadic number of test cases. Test cases are constructed
// using the That function followed by methods that add constraints on the test
// case. Overall, a test looks like:
//
//     Test(t,
//         That("put x").Puts("x"),
//         That("echo x").Prints("x\n"))
//
// If some setup is needed, use the TestWithSetup function instead.

package evaltest

import (
	
	
	
	
	

	
	
	
	
)

// TestCase is a test case for Test.
type TestCase struct {
	codes []string
	want  Result
}

type Result struct {
	ValueOut  []interface{}
	BytesOut  []byte
	StderrOut []byte

	CompilationError error
	Exception        error
}

// The following functions and methods are used to build Test structs. They are
// supposed to read like English, so a test that "put x" should put "x" reads:
//
// That("put x").Puts("x")

// That returns a new Test with the specified source code. Multiple arguments
// are joined with newlines. To specify multiple pieces of code that are
// executed separately, use the Then method to append code pieces.
func ( ...string) TestCase {
	return TestCase{codes: []string{strings.Join(, "\n")}}
}

// Then returns a new Test that executes the given code in addition. Multiple
// arguments are joined with newlines.
func ( TestCase) ( ...string) TestCase {
	.codes = append(.codes, strings.Join(, "\n"))
	return 
}

// DoesNothing returns t unchanged. It is used to mark that a piece of code
// should simply does nothing. In particular, it shouldn't have any output and
// does not error.
func ( TestCase) () TestCase {
	return 
}

// Puts returns an altered TestCase that requires the source code to produce the
// specified values in the value channel when evaluated.
func ( TestCase) ( ...interface{}) TestCase {
	.want.ValueOut = 
	return 
}

// Prints returns an altered TestCase that requires the source code to produce
// the specified output in the byte pipe when evaluated.
func ( TestCase) ( string) TestCase {
	.want.BytesOut = []byte()
	return 
}

// PrintsStderrWith returns an altered TestCase that requires the stderr
// output to contain the given text.
func ( TestCase) ( string) TestCase {
	.want.StderrOut = []byte()
	return 
}

// Throws returns an altered TestCase that requires the source code to throw an
// exception with the given reason. The reason supports special matcher values
// constructed by functions like ErrorWithMessage.
//
// If at least one stacktrace string is given, the exception must also have a
// stacktrace matching the given source fragments, frame by frame (innermost
// frame first). If no stacktrace string is given, the stack trace of the
// exception is not checked.
func ( TestCase) ( error,  ...string) TestCase {
	.want.Exception = exc{, }
	return 
}

// DoesNotCompile returns an altered TestCase that requires the source code to
// fail compilation.
func ( TestCase) () TestCase {
	.want.CompilationError = anyError{}
	return 
}

// Test runs test cases. For each test case, a new Evaler is created with
// NewEvaler.
func ( *testing.T,  ...TestCase) {
	.Helper()
	TestWithSetup(, func(*eval.Evaler) {}, ...)
}

// TestWithSetup runs test cases. For each test case, a new Evaler is created
// with NewEvaler and passed to the setup function.
func ( *testing.T,  func(*eval.Evaler),  ...TestCase) {
	.Helper()
	for ,  := range  {
		.Run(strings.Join(.codes, "\n"), func( *testing.T) {
			.Helper()
			 := eval.NewEvaler()
			()

			 := evalAndCollect(, , .codes)

			if !matchOut(.want.ValueOut, .ValueOut) {
				.Errorf("got value out %v, want %v",
					reprs(.ValueOut), reprs(.want.ValueOut))
			}
			if !bytes.Equal(.want.BytesOut, .BytesOut) {
				.Errorf("got bytes out %q, want %q", .BytesOut, .want.BytesOut)
			}
			if !bytes.Contains(.StderrOut, .want.StderrOut) {
				.Errorf("got stderr out %q, want %q", .StderrOut, .want.StderrOut)
			}
			if !matchErr(.want.CompilationError, .CompilationError) {
				.Errorf("got compilation error %v, want %v",
					.CompilationError, .want.CompilationError)
			}
			if !matchErr(.want.Exception, .Exception) {
				.Errorf("unexpected exception")
				.Logf("got: %v", .Exception)
				if ,  := .Exception.(eval.Exception);  {
					.Logf("stack trace: %#v", getStackTexts(.StackTrace()))
				}
				.Errorf("want: %v", .want.Exception)
			}
		})
	}
}

func ( *testing.T,  *eval.Evaler,  []string) Result {
	var  Result

	,  := capturePort()
	,  := capturePort()
	 := []*eval.Port{eval.DummyInputPort, , }

	for ,  := range  {
		 := .Eval(parse.Source{Name: "[test]", Code: },
			eval.EvalCfg{Ports: , Interrupt: eval.ListenInterrupts})

		if parse.GetError() != nil {
			.Fatalf("Parse(%q) error: %s", , )
		} else if eval.GetCompilationError() != nil {
			// NOTE: If multiple code pieces have compilation errors, only the
			// last one compilation error is saved.
			.CompilationError = 
		} else if  != nil {
			// NOTE: If multiple code pieces throw exceptions, only the last one
			// is saved.
			.Exception = 
		}
	}

	.ValueOut, .BytesOut = ()
	_, .StderrOut = ()
	return 
}

// Like eval.CapturePort, but captures values and bytes separately. Also panics
// if it cannot create a pipe.
func () (*eval.Port, func() ([]interface{}, []byte)) {
	var  []interface{}
	var  []byte
	, ,  := eval.PipePort(
		func( <-chan interface{}) {
			for  := range  {
				 = append(, )
			}
		},
		func( *os.File) {
			 = testutil.MustReadAllAndClose()
		})
	if  != nil {
		panic()
	}
	return , func() ([]interface{}, []byte) {
		()
		return , 
	}
}

func (,  []interface{}) bool {
	if len() != len() {
		return false
	}
	for  := range  {
		if !match([], []) {
			return false
		}
	}
	return true
}

func (,  interface{}) bool {
	switch got := .(type) {
	case float64:
		// Special-case float64 to correctly handle NaN and support
		// approximate comparison.
		switch want := .(type) {
		case float64:
			return matchFloat64(, , 0)
		case Approximately:
			return matchFloat64(, .F, ApproximatelyThreshold)
		}
	case string:
		switch want := .(type) {
		case MatchingRegexp:
			return matchRegexp(.Pattern, )
		}
	}
	return vals.Equal(, )
}

func ( []interface{}) []string {
	 := make([]string, len())
	for ,  := range  {
		[] = vals.Repr(, vals.NoPretty)
	}
	return 
}

func (,  error) bool {
	if  == nil {
		return  == nil
	}
	if ,  := .(errorMatcher);  {
		return .matchError()
	}
	return reflect.DeepEqual(, )
}