gitlab.com/apertussolutions/u-root@v7.0.0+incompatible/cmds/core/elvish/eval/testutils.go (about)

     1  // Common testing utilities. This file does not file a _test.go suffix so that
     2  // it can be used from other packages that also want to test the modules they
     3  // implement (e.g. edit: and re:).
     4  
     5  package eval
     6  
     7  import (
     8  	"bytes"
     9  	"errors"
    10  	"fmt"
    11  	"os"
    12  	"reflect"
    13  
    14  	"github.com/u-root/u-root/cmds/core/elvish/eval/vals"
    15  	"github.com/u-root/u-root/cmds/core/elvish/parse"
    16  )
    17  
    18  // Test is a test case for TestEval.
    19  type Test struct {
    20  	text string
    21  	want
    22  }
    23  
    24  type want struct {
    25  	out      []interface{}
    26  	bytesOut []byte
    27  	err      error
    28  }
    29  
    30  // A special value for want.err to indicate that any error, as long as not nil,
    31  // is OK
    32  var errAny = errors.New("any error")
    33  
    34  // The following functions and methods are used to build Test structs. They are
    35  // supposed to read like English, so a test that "put x" should put "x" reads:
    36  //
    37  // That("put x").Puts("x")
    38  
    39  // That returns a new Test with the specified source code.
    40  func That(text string) Test {
    41  	return Test{text: text}
    42  }
    43  
    44  // DoesNothing returns t unchanged. It is used to mark that a piece of code
    45  // should simply does nothing. In particular, it shouldn't have any output and
    46  // does not error.
    47  func (t Test) DoesNothing() Test {
    48  	return t
    49  }
    50  
    51  // Puts returns an altered Test that requires the source code to produce the
    52  // specified values in the value channel when evaluated.
    53  func (t Test) Puts(vs ...interface{}) Test {
    54  	t.want.out = vs
    55  	return t
    56  }
    57  
    58  // Puts returns an altered Test that requires the source code to produce the
    59  // specified strings in the value channel when evaluated.
    60  func (t Test) PutsStrings(ss []string) Test {
    61  	t.want.out = make([]interface{}, len(ss))
    62  	for i, s := range ss {
    63  		t.want.out[i] = s
    64  	}
    65  	return t
    66  }
    67  
    68  // Prints returns an altered test that requires the source code to produce
    69  // the specified output in the byte pipe when evaluated.
    70  func (t Test) Prints(s string) Test {
    71  	t.want.bytesOut = []byte(s)
    72  	return t
    73  }
    74  
    75  // ErrorsWith returns an altered Test that requires the source code to result in
    76  // the specified error when evaluted.
    77  func (t Test) ErrorsWith(err error) Test {
    78  	t.want.err = err
    79  	return t
    80  }
    81  
    82  // Errors returns an altered Test that requires the source code to result in any
    83  // error when evaluated.
    84  func (t Test) Errors() Test {
    85  	return t.ErrorsWith(errAny)
    86  }
    87  
    88  // RunTests runs test cases. For each test case, a new Evaler is made by calling
    89  // makeEvaler.
    90  func RunTests(evalTests []Test, makeEvaler func() *Evaler) error {
    91  	for _, tt := range evalTests {
    92  		// fmt.Printf("eval %q\n", tt.text)
    93  
    94  		ev := makeEvaler()
    95  		defer ev.Close()
    96  		out, bytesOut, err := evalAndCollect(ev, []string{tt.text}, len(tt.want.out))
    97  
    98  		first := true
    99  		errorf := func(format string, args ...interface{}) error {
   100  			if first {
   101  				first = false
   102  				return fmt.Errorf("eval(%q) fails: "+format, append([]interface{}{tt.text}, args...)...)
   103  			}
   104  			return fmt.Errorf("  "+format, args...)
   105  		}
   106  
   107  		if !matchOut(tt.want.out, out) {
   108  			if err := errorf("got out=%v, want %v", out, tt.want.out); err != nil {
   109  				return err
   110  			}
   111  		}
   112  		if !bytes.Equal(tt.want.bytesOut, bytesOut) {
   113  			if err := errorf("got bytesOut=%q, want %q", bytesOut, tt.want.bytesOut); err != nil {
   114  				return err
   115  			}
   116  		}
   117  		if !matchErr(tt.want.err, err) {
   118  			if err := errorf("got err=%v, want %v", err, tt.want.err); err != nil {
   119  				return err
   120  			}
   121  		}
   122  	}
   123  	return nil
   124  }
   125  
   126  func evalAndCollect(ev *Evaler, texts []string, chsize int) ([]interface{}, []byte, error) {
   127  	// Collect byte output
   128  	bytesOut := []byte{}
   129  	pr, pw, _ := os.Pipe()
   130  	bytesDone := make(chan struct{})
   131  	go func() {
   132  		for {
   133  			var buf [64]byte
   134  			nr, err := pr.Read(buf[:])
   135  			bytesOut = append(bytesOut, buf[:nr]...)
   136  			if err != nil {
   137  				break
   138  			}
   139  		}
   140  		close(bytesDone)
   141  	}()
   142  
   143  	// Channel output
   144  	outs := []interface{}{}
   145  
   146  	// Eval error. Only that of the last text is saved.
   147  	var ex error
   148  
   149  	for i, text := range texts {
   150  		name := fmt.Sprintf("test%d.elv", i)
   151  		src := NewScriptSource(name, name, text)
   152  
   153  		op, err := mustParseAndCompile(ev, src)
   154  		if err != nil {
   155  			return nil, nil, err
   156  		}
   157  
   158  		outCh := make(chan interface{}, chsize)
   159  		outDone := make(chan struct{})
   160  		go func() {
   161  			for v := range outCh {
   162  				outs = append(outs, v)
   163  			}
   164  			close(outDone)
   165  		}()
   166  
   167  		ports := []*Port{
   168  			{File: os.Stdin, Chan: ClosedChan},
   169  			{File: pw, Chan: outCh},
   170  			{File: os.Stderr, Chan: BlackholeChan},
   171  		}
   172  
   173  		ex = ev.eval(op, ports, src)
   174  		close(outCh)
   175  		<-outDone
   176  	}
   177  
   178  	pw.Close()
   179  	<-bytesDone
   180  	pr.Close()
   181  
   182  	return outs, bytesOut, ex
   183  }
   184  
   185  func mustParseAndCompile(ev *Evaler, src *Source) (Op, error) {
   186  	n, err := parse.Parse(src.name, src.code)
   187  	if err != nil {
   188  		return Op{}, fmt.Errorf("Parse(%q) error: %s", src.code, err)
   189  	}
   190  	op, err := ev.Compile(n, src)
   191  	if err != nil {
   192  		return Op{}, fmt.Errorf("Compile(Parse(%q)) error: %s", src.code, err)
   193  	}
   194  	return op, nil
   195  }
   196  
   197  func matchOut(want, got []interface{}) bool {
   198  	if len(got) == 0 && len(want) == 0 {
   199  		return true
   200  	}
   201  	if len(got) != len(want) {
   202  		return false
   203  	}
   204  	for i := range got {
   205  		if !vals.Equal(got[i], want[i]) {
   206  			return false
   207  		}
   208  	}
   209  	return true
   210  }
   211  
   212  func matchErr(want, got error) bool {
   213  	if got == nil {
   214  		return want == nil
   215  	}
   216  	return want == errAny || reflect.DeepEqual(got.(*Exception).Cause, want)
   217  }
   218  
   219  // compareValues compares two slices, using equals for each element.
   220  func compareSlice(wantValues, gotValues []interface{}) error {
   221  	if len(wantValues) != len(gotValues) {
   222  		return fmt.Errorf("want %d values, got %d",
   223  			len(wantValues), len(gotValues))
   224  	}
   225  	for i, want := range wantValues {
   226  		if !vals.Equal(want, gotValues[i]) {
   227  			return fmt.Errorf("want [%d] = %s, got %s", i, want, gotValues[i])
   228  		}
   229  	}
   230  	return nil
   231  }