xref: /aosp_15_r20/external/starlark-go/starlarktest/starlarktest.go (revision 4947cdc739c985f6d86941e22894f5cefe7c9e9a)
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