github.com/lab47/exprcore@v0.0.0-20210525052339-fb7d6bd9331e/exprcoretest/exprcoretest.go (about) 1 // Copyright 2017 The Bazel Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 // Package exprcoretest defines utilities for testing exprcore programs. 6 // 7 // Clients can call LoadAssertModule to load a module that defines 8 // several functions useful for testing. See assert.star for its 9 // definition. 10 // 11 // The assert.error function, which reports errors to the current Go 12 // testing.T, requires that clients call SetTest(thread, t) before use. 13 package exprcoretest // import "github.com/lab47/exprcore/exprcoretest" 14 15 import ( 16 "fmt" 17 "go/build" 18 "os" 19 "path/filepath" 20 "regexp" 21 "strings" 22 "sync" 23 24 "github.com/lab47/exprcore/exprcore" 25 "github.com/lab47/exprcore/exprcorestruct" 26 ) 27 28 const localKey = "Reporter" 29 30 // A Reporter is a value to which errors may be reported. 31 // It is satisfied by *testing.T. 32 type Reporter interface { 33 Error(args ...interface{}) 34 } 35 36 // SetReporter associates an error reporter (such as a testing.T in 37 // a Go test) with the exprcore thread so that exprcore programs may 38 // report errors to it. 39 func SetReporter(thread *exprcore.Thread, r Reporter) { 40 thread.SetLocal(localKey, r) 41 } 42 43 // GetReporter returns the exprcore thread's error reporter. 44 // It must be preceded by a call to SetReporter. 45 func GetReporter(thread *exprcore.Thread) Reporter { 46 r, ok := thread.Local(localKey).(Reporter) 47 if !ok { 48 panic("internal error: exprcoretest.SetReporter was not called") 49 } 50 return r 51 } 52 53 var ( 54 once sync.Once 55 assert exprcore.StringDict 56 assertErr error 57 ) 58 59 // LoadAssertModule loads the assert module. 60 // It is concurrency-safe and idempotent. 61 func LoadAssertModule() (exprcore.StringDict, error) { 62 once.Do(func() { 63 predeclared := exprcore.StringDict{ 64 "error": exprcore.NewBuiltin("error", error_), 65 "catch": exprcore.NewBuiltin("catch", catch), 66 "matches": exprcore.NewBuiltin("matches", matches), 67 "module": exprcore.NewBuiltin("module", exprcorestruct.MakeModule), 68 "_freeze": exprcore.NewBuiltin("freeze", freeze), 69 } 70 filename := DataFile("exprcoretest", "assert.star") 71 thread := new(exprcore.Thread) 72 assert, assertErr = exprcore.ExecFile(thread, filename, nil, predeclared) 73 }) 74 return assert, assertErr 75 } 76 77 // catch(f) evaluates f() and returns its evaluation error message 78 // if it failed or None if it succeeded. 79 func catch(thread *exprcore.Thread, _ *exprcore.Builtin, args exprcore.Tuple, kwargs []exprcore.Tuple) (exprcore.Value, error) { 80 var fn exprcore.Callable 81 if err := exprcore.UnpackArgs("catch", args, kwargs, "fn", &fn); err != nil { 82 return nil, err 83 } 84 if _, err := exprcore.Call(thread, fn, nil, nil); err != nil { 85 return exprcore.String(err.Error()), nil 86 } 87 return exprcore.None, nil 88 } 89 90 // matches(pattern, str) reports whether string str matches the regular expression pattern. 91 func matches(thread *exprcore.Thread, _ *exprcore.Builtin, args exprcore.Tuple, kwargs []exprcore.Tuple) (exprcore.Value, error) { 92 var pattern, str string 93 if err := exprcore.UnpackArgs("matches", args, kwargs, "pattern", &pattern, "str", &str); err != nil { 94 return nil, err 95 } 96 ok, err := regexp.MatchString(pattern, str) 97 if err != nil { 98 return nil, fmt.Errorf("matches: %s", err) 99 } 100 return exprcore.Bool(ok), nil 101 } 102 103 // error(x) reports an error to the Go test framework. 104 func error_(thread *exprcore.Thread, _ *exprcore.Builtin, args exprcore.Tuple, kwargs []exprcore.Tuple) (exprcore.Value, error) { 105 if len(args) != 1 { 106 return nil, fmt.Errorf("error: got %d arguments, want 1", len(args)) 107 } 108 buf := new(strings.Builder) 109 stk := thread.CallStack() 110 stk.Pop() 111 fmt.Fprintf(buf, "%sError: ", stk) 112 if s, ok := exprcore.AsString(args[0]); ok { 113 buf.WriteString(s) 114 } else { 115 buf.WriteString(args[0].String()) 116 } 117 GetReporter(thread).Error(buf.String()) 118 return exprcore.None, nil 119 } 120 121 // freeze(x) freezes its operand. 122 func freeze(thread *exprcore.Thread, _ *exprcore.Builtin, args exprcore.Tuple, kwargs []exprcore.Tuple) (exprcore.Value, error) { 123 if len(kwargs) > 0 { 124 return nil, fmt.Errorf("freeze does not accept keyword arguments") 125 } 126 if len(args) != 1 { 127 return nil, fmt.Errorf("freeze got %d arguments, wants 1", len(args)) 128 } 129 args[0].Freeze() 130 return args[0], nil 131 } 132 133 // DataFile returns the effective filename of the specified 134 // test data resource. The function abstracts differences between 135 // 'go build', under which a test runs in its package directory, 136 // and Blaze, under which a test runs in the root of the tree. 137 var DataFile = func(pkgdir, filename string) string { 138 return filepath.Join("..", pkgdir, filename) 139 140 // Check if we're being run by Bazel and change directories if so. 141 // TEST_SRCDIR and TEST_WORKSPACE are set by the Bazel test runner, so that makes a decent check 142 testSrcdir := os.Getenv("TEST_SRCDIR") 143 testWorkspace := os.Getenv("TEST_WORKSPACE") 144 if testSrcdir != "" && testWorkspace != "" { 145 return filepath.Join(testSrcdir, "net_exprcore_go", pkgdir, filename) 146 } 147 148 return filepath.Join(build.Default.GOPATH, "src/github.com/lab47/exprcore", pkgdir, filename) 149 }