diff options
author | Alan Donovan <adonovan@google.com> | 2018-10-23 11:05:09 -0400 |
---|---|---|
committer | Alan Donovan <adonovan@google.com> | 2018-10-23 11:05:09 -0400 |
commit | e3deafefac22db7bfd4877d37614fe5db4b03881 (patch) | |
tree | e3de93e8b63abffb95767055fb392714cc8cc8a1 /starlarktest | |
parent | ce5c2fa1ad6a8fa4beb4eacdbd3bf9997162d144 (diff) | |
download | starlark-go-e3deafefac22db7bfd4877d37614fe5db4b03881.tar.gz |
rename skylark -> starlark
Change-Id: Iebd0e040ff674b2f9da39bf5242c8afaa7f4ddc8
Diffstat (limited to 'starlarktest')
-rw-r--r-- | starlarktest/assert.star | 50 | ||||
-rw-r--r-- | starlarktest/starlarktest.go | 137 |
2 files changed, 187 insertions, 0 deletions
diff --git a/starlarktest/assert.star b/starlarktest/assert.star new file mode 100644 index 0000000..f453aac --- /dev/null +++ b/starlarktest/assert.star @@ -0,0 +1,50 @@ + +# Predeclared built-ins for this module: +# +# error(msg): report an error in Go's test framework without halting execution. +# catch(f): evaluate f() and returns its evaluation error message, if any +# matches(str, pattern): report whether str matches regular expression pattern. +# struct: a constructor for a simple HasFields implementation. +# _freeze(x): freeze the value x and everything reachable from it. +# +# Clients may use these functions to define their own testing abstractions. + +def _eq(x, y): + if x != y: + error("%r != %r" % (x, y)) + +def _ne(x, y): + if x == y: + error("%r == %r" % (x, y)) + +def _true(cond, msg="assertion failed"): + if not cond: + error(msg) + +def _lt(x, y): + if not (x < y): + error("%s is not less than %s" % (x, y)) + +def _contains(x, y): + if y not in x: + error("%s does not contain %s" % (x, y)) + +def _fails(f, pattern): + "assert_fails asserts that evaluation of f() fails with the specified error." + msg = catch(f) + if msg == None: + error("evaluation succeeded unexpectedly (want error matching %r)" % pattern) + elif not matches(pattern, msg): + error("regular expression (%s) did not match error (%s)" % (pattern, msg)) + +freeze = _freeze # an exported global whose value is the built-in freeze function + +assert = struct( + fail = error, + eq = _eq, + ne = _ne, + true = _true, + lt = _lt, + contains = _contains, + fails = _fails, +) diff --git a/starlarktest/starlarktest.go b/starlarktest/starlarktest.go new file mode 100644 index 0000000..d1d38fe --- /dev/null +++ b/starlarktest/starlarktest.go @@ -0,0 +1,137 @@ +// 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 SetTest(thread, t) before use. +package starlarktest + +import ( + "bytes" + "fmt" + "go/build" + "path/filepath" + "regexp" + "sync" + + "github.com/google/starlark" + "github.com/google/starlark/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), + "struct": starlark.NewBuiltin("struct", starlarkstruct.Make), + "_freeze": starlark.NewBuiltin("freeze", freeze), + } + filename := DataFile("starlark/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)) + } + var buf bytes.Buffer + thread.Caller().WriteBacktrace(&buf) + buf.WriteString("Error: ") + 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 { + return filepath.Join(build.Default.GOPATH, "src/github.com/google", pkgdir, filename) +} |