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 }