github.com/k14s/starlark-go@v0.0.0-20200720175618-3a5c849cc368/starlarktest/starlarktest.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 starlarktest defines utilities for testing Starlark 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 starlarktest // import "github.com/k14s/starlark-go/starlarktest" 14 15 import ( 16 "fmt" 17 "go/build" 18 "path/filepath" 19 "regexp" 20 "strings" 21 "sync" 22 23 "github.com/k14s/starlark-go/starlark" 24 "github.com/k14s/starlark-go/starlarkstruct" 25 ) 26 27 const localKey = "Reporter" 28 29 // A Reporter is a value to which errors may be reported. 30 // It is satisfied by *testing.T. 31 type Reporter interface { 32 Error(args ...interface{}) 33 } 34 35 // SetReporter associates an error reporter (such as a testing.T in 36 // a Go test) with the Starlark thread so that Starlark programs may 37 // report errors to it. 38 func SetReporter(thread *starlark.Thread, r Reporter) { 39 thread.SetLocal(localKey, r) 40 } 41 42 // GetReporter returns the Starlark thread's error reporter. 43 // It must be preceded by a call to SetReporter. 44 func GetReporter(thread *starlark.Thread) Reporter { 45 r, ok := thread.Local(localKey).(Reporter) 46 if !ok { 47 panic("internal error: starlarktest.SetReporter was not called") 48 } 49 return r 50 } 51 52 var ( 53 once sync.Once 54 assert starlark.StringDict 55 assertErr error 56 ) 57 58 // LoadAssertModule loads the assert module. 59 // It is concurrency-safe and idempotent. 60 func LoadAssertModule() (starlark.StringDict, error) { 61 once.Do(func() { 62 predeclared := starlark.StringDict{ 63 "error": starlark.NewBuiltin("error", error_), 64 "catch": starlark.NewBuiltin("catch", catch), 65 "matches": starlark.NewBuiltin("matches", matches), 66 "module": starlark.NewBuiltin("module", starlarkstruct.MakeModule), 67 "_freeze": starlark.NewBuiltin("freeze", freeze), 68 } 69 filename := DataFile("starlarktest", "assert.star") 70 thread := new(starlark.Thread) 71 assert, assertErr = starlark.ExecFile(thread, filename, nil, predeclared) 72 }) 73 return assert, assertErr 74 } 75 76 // catch(f) evaluates f() and returns its evaluation error message 77 // if it failed or None if it succeeded. 78 func catch(thread *starlark.Thread, _ *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) { 79 var fn starlark.Callable 80 if err := starlark.UnpackArgs("catch", args, kwargs, "fn", &fn); err != nil { 81 return nil, err 82 } 83 if _, err := starlark.Call(thread, fn, nil, nil); err != nil { 84 return starlark.String(err.Error()), nil 85 } 86 return starlark.None, nil 87 } 88 89 // matches(pattern, str) reports whether string str matches the regular expression pattern. 90 func matches(thread *starlark.Thread, _ *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) { 91 var pattern, str string 92 if err := starlark.UnpackArgs("matches", args, kwargs, "pattern", &pattern, "str", &str); err != nil { 93 return nil, err 94 } 95 ok, err := regexp.MatchString(pattern, str) 96 if err != nil { 97 return nil, fmt.Errorf("matches: %s", err) 98 } 99 return starlark.Bool(ok), nil 100 } 101 102 // error(x) reports an error to the Go test framework. 103 func error_(thread *starlark.Thread, _ *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) { 104 if len(args) != 1 { 105 return nil, fmt.Errorf("error: got %d arguments, want 1", len(args)) 106 } 107 buf := new(strings.Builder) 108 stk := thread.CallStack() 109 stk.Pop() 110 fmt.Fprintf(buf, "%sError: ", stk) 111 if s, ok := starlark.AsString(args[0]); ok { 112 buf.WriteString(s) 113 } else { 114 buf.WriteString(args[0].String()) 115 } 116 GetReporter(thread).Error(buf.String()) 117 return starlark.None, nil 118 } 119 120 // freeze(x) freezes its operand. 121 func freeze(thread *starlark.Thread, _ *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) { 122 if len(kwargs) > 0 { 123 return nil, fmt.Errorf("freeze does not accept keyword arguments") 124 } 125 if len(args) != 1 { 126 return nil, fmt.Errorf("freeze got %d arguments, wants 1", len(args)) 127 } 128 args[0].Freeze() 129 return args[0], nil 130 } 131 132 // DataFile returns the effective filename of the specified 133 // test data resource. The function abstracts differences between 134 // 'go build', under which a test runs in its package directory, 135 // and Blaze, under which a test runs in the root of the tree. 136 var DataFile = func(pkgdir, filename string) string { 137 return filepath.Join(build.Default.GOPATH, "src/go.starlark.net", pkgdir, filename) 138 }