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