You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
148 lines
4.9 KiB
148 lines
4.9 KiB
// Copyright 2017 The Bazel Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
// Package starlarktest defines utilities for testing Starlark programs.
|
|
//
|
|
// Clients can call LoadAssertModule to load a module that defines
|
|
// several functions useful for testing. See assert.star for its
|
|
// definition.
|
|
//
|
|
// The assert.error function, which reports errors to the current Go
|
|
// testing.T, requires that clients call SetReporter(thread, t) before use.
|
|
package starlarktest // import "go.starlark.net/starlarktest"
|
|
|
|
import (
|
|
"fmt"
|
|
"go/build"
|
|
"os"
|
|
"path/filepath"
|
|
"regexp"
|
|
"strings"
|
|
"sync"
|
|
|
|
"go.starlark.net/starlark"
|
|
"go.starlark.net/starlarkstruct"
|
|
)
|
|
|
|
const localKey = "Reporter"
|
|
|
|
// A Reporter is a value to which errors may be reported.
|
|
// It is satisfied by *testing.T.
|
|
type Reporter interface {
|
|
Error(args ...interface{})
|
|
}
|
|
|
|
// SetReporter associates an error reporter (such as a testing.T in
|
|
// a Go test) with the Starlark thread so that Starlark programs may
|
|
// report errors to it.
|
|
func SetReporter(thread *starlark.Thread, r Reporter) {
|
|
thread.SetLocal(localKey, r)
|
|
}
|
|
|
|
// GetReporter returns the Starlark thread's error reporter.
|
|
// It must be preceded by a call to SetReporter.
|
|
func GetReporter(thread *starlark.Thread) Reporter {
|
|
r, ok := thread.Local(localKey).(Reporter)
|
|
if !ok {
|
|
panic("internal error: starlarktest.SetReporter was not called")
|
|
}
|
|
return r
|
|
}
|
|
|
|
var (
|
|
once sync.Once
|
|
assert starlark.StringDict
|
|
assertErr error
|
|
)
|
|
|
|
// LoadAssertModule loads the assert module.
|
|
// It is concurrency-safe and idempotent.
|
|
func LoadAssertModule() (starlark.StringDict, error) {
|
|
once.Do(func() {
|
|
predeclared := starlark.StringDict{
|
|
"error": starlark.NewBuiltin("error", error_),
|
|
"catch": starlark.NewBuiltin("catch", catch),
|
|
"matches": starlark.NewBuiltin("matches", matches),
|
|
"module": starlark.NewBuiltin("module", starlarkstruct.MakeModule),
|
|
"_freeze": starlark.NewBuiltin("freeze", freeze),
|
|
}
|
|
filename := DataFile("starlarktest", "assert.star")
|
|
thread := new(starlark.Thread)
|
|
assert, assertErr = starlark.ExecFile(thread, filename, nil, predeclared)
|
|
})
|
|
return assert, assertErr
|
|
}
|
|
|
|
// catch(f) evaluates f() and returns its evaluation error message
|
|
// if it failed or None if it succeeded.
|
|
func catch(thread *starlark.Thread, _ *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
|
|
var fn starlark.Callable
|
|
if err := starlark.UnpackArgs("catch", args, kwargs, "fn", &fn); err != nil {
|
|
return nil, err
|
|
}
|
|
if _, err := starlark.Call(thread, fn, nil, nil); err != nil {
|
|
return starlark.String(err.Error()), nil
|
|
}
|
|
return starlark.None, nil
|
|
}
|
|
|
|
// matches(pattern, str) reports whether string str matches the regular expression pattern.
|
|
func matches(thread *starlark.Thread, _ *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
|
|
var pattern, str string
|
|
if err := starlark.UnpackArgs("matches", args, kwargs, "pattern", &pattern, "str", &str); err != nil {
|
|
return nil, err
|
|
}
|
|
ok, err := regexp.MatchString(pattern, str)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("matches: %s", err)
|
|
}
|
|
return starlark.Bool(ok), nil
|
|
}
|
|
|
|
// error(x) reports an error to the Go test framework.
|
|
func error_(thread *starlark.Thread, _ *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
|
|
if len(args) != 1 {
|
|
return nil, fmt.Errorf("error: got %d arguments, want 1", len(args))
|
|
}
|
|
buf := new(strings.Builder)
|
|
stk := thread.CallStack()
|
|
stk.Pop()
|
|
fmt.Fprintf(buf, "%sError: ", stk)
|
|
if s, ok := starlark.AsString(args[0]); ok {
|
|
buf.WriteString(s)
|
|
} else {
|
|
buf.WriteString(args[0].String())
|
|
}
|
|
GetReporter(thread).Error(buf.String())
|
|
return starlark.None, nil
|
|
}
|
|
|
|
// freeze(x) freezes its operand.
|
|
func freeze(thread *starlark.Thread, _ *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
|
|
if len(kwargs) > 0 {
|
|
return nil, fmt.Errorf("freeze does not accept keyword arguments")
|
|
}
|
|
if len(args) != 1 {
|
|
return nil, fmt.Errorf("freeze got %d arguments, wants 1", len(args))
|
|
}
|
|
args[0].Freeze()
|
|
return args[0], nil
|
|
}
|
|
|
|
// DataFile returns the effective filename of the specified
|
|
// test data resource. The function abstracts differences between
|
|
// 'go build', under which a test runs in its package directory,
|
|
// and Blaze, under which a test runs in the root of the tree.
|
|
var DataFile = func(pkgdir, filename string) string {
|
|
// Check if we're being run by Bazel and change directories if so.
|
|
// TEST_SRCDIR and TEST_WORKSPACE are set by the Bazel test runner, so that makes a decent check
|
|
testSrcdir := os.Getenv("TEST_SRCDIR")
|
|
testWorkspace := os.Getenv("TEST_WORKSPACE")
|
|
if testSrcdir != "" && testWorkspace != "" {
|
|
return filepath.Join(testSrcdir, "net_starlark_go", pkgdir, filename)
|
|
}
|
|
|
|
return filepath.Join(build.Default.GOPATH, "src/go.starlark.net", pkgdir, filename)
|
|
}
|