src.elv.sh@v0.21.0-dev.0.20240515223629-06979efb9a2a/pkg/eval/transcripts_test.go (about)

     1  package eval_test
     2  
     3  import (
     4  	"embed"
     5  	"errors"
     6  	"fmt"
     7  	"math/rand"
     8  	"strconv"
     9  	"strings"
    10  	"testing"
    11  	"time"
    12  
    13  	"src.elv.sh/pkg/eval"
    14  	"src.elv.sh/pkg/eval/evaltest"
    15  	"src.elv.sh/pkg/eval/vals"
    16  	"src.elv.sh/pkg/eval/vars"
    17  	"src.elv.sh/pkg/fsutil"
    18  	"src.elv.sh/pkg/must"
    19  	"src.elv.sh/pkg/prog"
    20  	"src.elv.sh/pkg/testutil"
    21  )
    22  
    23  //go:embed *.elvts *.elv
    24  var transcripts embed.FS
    25  
    26  func TestTranscripts(t *testing.T) {
    27  	evaltest.TestTranscriptsInFS(t, transcripts,
    28  		"args", func(ev *eval.Evaler, arg string) {
    29  			ev.Args = vals.MakeListSlice(strings.Fields(arg))
    30  		},
    31  		"recv-bg-job-notification-in-global", func(ev *eval.Evaler) {
    32  			noteCh := make(chan string, 10)
    33  			ev.BgJobNotify = func(s string) { noteCh <- s }
    34  			ev.ExtendGlobal(eval.BuildNs().
    35  				AddGoFn("recv-bg-job-notification", func() any { return <-noteCh }))
    36  		},
    37  		"with-temp-home", func(t *testing.T) { testutil.TempHome(t) },
    38  		"reseed-afterwards", func(t *testing.T) {
    39  			t.Cleanup(func() {
    40  				//lint:ignore SA1019 Reseed to make other RNG-dependent tests non-deterministic
    41  				rand.Seed(time.Now().UTC().UnixNano())
    42  			})
    43  		},
    44  		"check-exit-code-afterwards", func(t *testing.T, arg string) {
    45  			var exitCodes []int
    46  			testutil.Set(t, eval.OSExit, func(i int) {
    47  				exitCodes = append(exitCodes, i)
    48  			})
    49  			wantExitCode := must.OK1(strconv.Atoi(arg))
    50  			t.Cleanup(func() {
    51  				if len(exitCodes) != 1 {
    52  					t.Errorf("os.Exit called %d times, want once", len(exitCodes))
    53  				} else if exitCodes[0] != wantExitCode {
    54  					t.Errorf("os.Exit called with %d, want %d", exitCodes[0], wantExitCode)
    55  				}
    56  			})
    57  		},
    58  		"check-pre-exit-hook-afterwards", func(t *testing.T, ev *eval.Evaler) {
    59  			testutil.Set(t, eval.OSExit, func(int) {})
    60  			calls := 0
    61  			ev.PreExitHooks = append(ev.PreExitHooks, func() { calls++ })
    62  			t.Cleanup(func() {
    63  				if calls != 1 {
    64  					t.Errorf("pre-exit hook called %v times, want 1", calls)
    65  				}
    66  			})
    67  		},
    68  		"add-bad-var", func(ev *eval.Evaler, arg string) {
    69  			name, allowedSetsString, _ := strings.Cut(arg, " ")
    70  			allowedSets := must.OK1(strconv.Atoi(allowedSetsString))
    71  			ev.ExtendGlobal(eval.BuildNs().AddVar(name, &badVar{allowedSets}))
    72  		},
    73  		"tmp-lib-dir", func(t *testing.T, ev *eval.Evaler) {
    74  			libdir := testutil.TempDir(t)
    75  			ev.LibDirs = []string{libdir}
    76  			ev.ExtendGlobal(eval.BuildNs().
    77  				AddVar("lib", vars.NewReadOnly(libdir)))
    78  		},
    79  		"two-tmp-lib-dirs", func(t *testing.T, ev *eval.Evaler) {
    80  			libdir1 := testutil.TempDir(t)
    81  			libdir2 := testutil.TempDir(t)
    82  			ev.LibDirs = []string{libdir1, libdir2}
    83  			ev.ExtendGlobal(eval.BuildNs().
    84  				AddVar("lib1", vars.NewReadOnly(libdir1)).
    85  				AddVar("lib2", vars.NewReadOnly(libdir2)))
    86  		},
    87  		"add-var-in-builtin", func(ev *eval.Evaler) {
    88  			addVar := func(name string, val any) {
    89  				ev.ExtendGlobal(eval.BuildNs().AddVar(name, vars.FromInit(val)))
    90  			}
    91  			ev.ExtendBuiltin(eval.BuildNs().AddGoFn("add-var", addVar))
    92  		},
    93  		"test-time-scale-in-global", func(ev *eval.Evaler) {
    94  			ev.ExtendGlobal(eval.BuildNs().
    95  				AddVar("test-time-scale", vars.NewReadOnly(testutil.TestTimeScale())))
    96  		},
    97  		"mock-get-home-error", func(t *testing.T, msg string) {
    98  			err := errors.New(msg)
    99  			testutil.Set(t, eval.GetHome,
   100  				func(name string) (string, error) { return "", err })
   101  		},
   102  		"force-eval-source-count", func(t *testing.T, arg string) {
   103  			c := must.OK1(strconv.Atoi(arg))
   104  			testutil.Set(t, eval.NextEvalCount, func() int { return c })
   105  		},
   106  		"with-deprecation-level", func(t *testing.T, arg string) {
   107  			testutil.Set(t, &prog.DeprecationLevel, must.OK1(strconv.Atoi(arg)))
   108  		},
   109  		"mock-time-after", func(t *testing.T) {
   110  			testutil.Set(t, eval.TimeAfter,
   111  				func(fm *eval.Frame, d time.Duration) <-chan time.Time {
   112  					fmt.Fprintf(fm.ByteOutput(), "slept for %s\n", d)
   113  					return time.After(0)
   114  				})
   115  		},
   116  		"mock-benchmark-run-durations", func(t *testing.T, arg string) {
   117  			// The benchmark command calls time.Now once before a run and once
   118  			// after a run.
   119  			var ticks []int64
   120  			for i, field := range strings.Fields(arg) {
   121  				d := must.OK1(strconv.ParseInt(field, 0, 64))
   122  				if i == 0 {
   123  					ticks = append(ticks, 0, d)
   124  				} else {
   125  					last := ticks[len(ticks)-1]
   126  					ticks = append(ticks, last, last+d)
   127  				}
   128  			}
   129  			testutil.Set(t, eval.TimeNow, func() time.Time {
   130  				if len(ticks) == 0 {
   131  					panic("mock TimeNow called more than len(ticks)")
   132  				}
   133  				v := ticks[0]
   134  				ticks = ticks[1:]
   135  				return time.Unix(v, 0)
   136  			})
   137  		},
   138  		"inject-time-after-with-sigint-or-skip", injectTimeAfterWithSIGINTOrSkip,
   139  		"mock-getwd-error", func(t *testing.T, msg string) {
   140  			err := errors.New(msg)
   141  			testutil.Set(t, eval.Getwd, func() (string, error) { return "", err })
   142  		},
   143  		"mock-no-other-home", func(t *testing.T) {
   144  			testutil.Set(t, eval.GetHome, func(name string) (string, error) {
   145  				switch name {
   146  				case "":
   147  					return fsutil.GetHome("")
   148  				default:
   149  					return "", fmt.Errorf("don't know home of %v", name)
   150  				}
   151  			})
   152  		},
   153  		"mock-one-other-home", func(t *testing.T, ev *eval.Evaler) {
   154  			otherHome := testutil.TempDir(t)
   155  			ev.ExtendGlobal(eval.BuildNs().AddVar("other-home", vars.NewReadOnly(otherHome)))
   156  			testutil.Set(t, eval.GetHome, func(name string) (string, error) {
   157  				switch name {
   158  				case "":
   159  					return fsutil.GetHome("")
   160  				case "other":
   161  					return otherHome, nil
   162  				default:
   163  					return "", fmt.Errorf("don't know home of %v", name)
   164  				}
   165  			})
   166  		},
   167  		"go-fns-mod-in-global", func(ev *eval.Evaler) {
   168  			ev.ExtendGlobal(eval.BuildNs().AddNs("go-fns", goFnsMod))
   169  		},
   170  		"call-hook-in-global", func(ev *eval.Evaler) {
   171  			callHook := func(fm *eval.Frame, name string, hook vals.List, args ...any) {
   172  				evalCfg := &eval.EvalCfg{Ports: []*eval.Port{fm.Port(0), fm.Port(1), fm.Port(2)}}
   173  				eval.CallHook(fm.Evaler, evalCfg, name, hook, args...)
   174  			}
   175  			ev.ExtendGlobal(eval.BuildNs().AddGoFn("call-hook", callHook))
   176  		},
   177  	)
   178  }
   179  
   180  var errBadVar = errors.New("bad var")
   181  
   182  type badVar struct{ allowedSets int }
   183  
   184  func (v *badVar) Get() any { return nil }
   185  
   186  func (v *badVar) Set(any) error {
   187  	if v.allowedSets == 0 {
   188  		return errBadVar
   189  	}
   190  	v.allowedSets--
   191  	return nil
   192  }