// 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 evaltestimport ()// TestCase is a test case for Test.typeTestCasestruct {codes []stringwantResult}typeResultstruct {ValueOut []interface{}BytesOut []byteStderrOut []byteCompilationErrorerrorExceptionerror}// 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 {returnTestCase{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 {varResult , := capturePort() , := capturePort() := []*eval.Port{eval.DummyInputPort, , }for , := range { := .Eval(parse.Source{Name: "[test]", Code: },eval.EvalCfg{Ports: , Interrupt: eval.ListenInterrupts})ifparse.GetError() != nil { .Fatalf("Parse(%q) error: %s", , ) } elseifeval.GetCompilationError() != nil {// NOTE: If multiple code pieces have compilation errors, only the // last one compilation error is saved. .CompilationError = } elseif != 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( <-chaninterface{}) {for := range { = append(, ) } },func( *os.File) { = testutil.MustReadAllAndClose() })if != nil {panic() }return , func() ([]interface{}, []byte) { ()return , }}func (, []interface{}) bool {iflen() != len() {returnfalse }for := range {if !match([], []) {returnfalse } }returntrue}func (, interface{}) bool {switch got := .(type) {casefloat64:// Special-case float64 to correctly handle NaN and support // approximate comparison.switch want := .(type) {casefloat64:returnmatchFloat64(, , 0)caseApproximately:returnmatchFloat64(, .F, ApproximatelyThreshold) }casestring:switch want := .(type) {caseMatchingRegexp:returnmatchRegexp(.Pattern, ) } }returnvals.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() }returnreflect.DeepEqual(, )}
The pages are generated with Goldsv0.2.8-preview. (GOOS=darwin GOARCH=arm64)