Add some intrinsics tests
This change adds some machinery to make it easier to write evaluator tests, and also implements some tests for the lumi:runtime/dynamic:isFunction intrinsic.
This commit is contained in:
parent
bad62854a9
commit
2bbc4739bd
|
@ -218,16 +218,16 @@ var _ Node = (*CallArgument)(nil)
|
|||
|
||||
const CallArgumentKind NodeKind = "CallArgument"
|
||||
|
||||
type callExpressionNode struct {
|
||||
type CallExpressionNode struct {
|
||||
ExpressionNode
|
||||
Arguments *[]*CallArgument `json:"arguments,omitempty"`
|
||||
}
|
||||
|
||||
func (node *callExpressionNode) GetArguments() *[]*CallArgument { return node.Arguments }
|
||||
func (node *CallExpressionNode) GetArguments() *[]*CallArgument { return node.Arguments }
|
||||
|
||||
// NewExpression allocates a new object and calls its constructor.
|
||||
type NewExpression struct {
|
||||
callExpressionNode
|
||||
CallExpressionNode
|
||||
Type *TypeToken `json:"type"` // the object type to allocate.
|
||||
}
|
||||
|
||||
|
@ -239,7 +239,7 @@ const NewExpressionKind NodeKind = "NewExpression"
|
|||
|
||||
// InvokeFunctionExpression invokes a target expression that must evaluate to a function.
|
||||
type InvokeFunctionExpression struct {
|
||||
callExpressionNode
|
||||
CallExpressionNode
|
||||
Function Expression `json:"function"` // a function to invoke (of a function type).
|
||||
}
|
||||
|
||||
|
|
|
@ -26,6 +26,8 @@ import (
|
|||
)
|
||||
|
||||
func (b *binder) bindModuleDeclarations(node *ast.Module, parent *symbols.Package) *symbols.Module {
|
||||
contract.Assert(node != nil)
|
||||
contract.Assert(parent != nil)
|
||||
glog.V(3).Infof("Binding package '%v' module '%v' decls", parent.Name(), node.Name.Ident)
|
||||
|
||||
// Create the module symbol and register it.
|
||||
|
|
113
pkg/eval/eval.go
113
pkg/eval/eval.go
|
@ -42,11 +42,11 @@ type Interpreter interface {
|
|||
Ctx() *binder.Context // the binding context object.
|
||||
|
||||
// EvaluatePackage performs evaluation on the given blueprint package.
|
||||
EvaluatePackage(pkg *symbols.Package, args core.Args)
|
||||
EvaluatePackage(pkg *symbols.Package, args core.Args) (*rt.Object, *rt.Unwind)
|
||||
// EvaluateModule performs evaluation on the given module's entrypoint function.
|
||||
EvaluateModule(mod *symbols.Module, args core.Args)
|
||||
EvaluateModule(mod *symbols.Module, args core.Args) (*rt.Object, *rt.Unwind)
|
||||
// EvaluateFunction performs an evaluation of the given function, using the provided arguments.
|
||||
EvaluateFunction(fnc symbols.Function, this *rt.Object, args core.Args)
|
||||
EvaluateFunction(fnc symbols.Function, this *rt.Object, args core.Args) (*rt.Object, *rt.Unwind)
|
||||
|
||||
// LoadLocation loads a location by symbol; lval controls whether it is an l-value or just a value.
|
||||
LoadLocation(tree diag.Diagable, sym symbols.Symbol, this *rt.Object, lval bool) *Location
|
||||
|
@ -108,7 +108,7 @@ func (e *evaluator) Ctx() *binder.Context { return e.ctx }
|
|||
func (e *evaluator) Diag() diag.Sink { return e.ctx.Diag }
|
||||
|
||||
// EvaluatePackage performs evaluation on the given blueprint package.
|
||||
func (e *evaluator) EvaluatePackage(pkg *symbols.Package, args core.Args) {
|
||||
func (e *evaluator) EvaluatePackage(pkg *symbols.Package, args core.Args) (*rt.Object, *rt.Unwind) {
|
||||
glog.Infof("Evaluating package '%v'", pkg.Name())
|
||||
if e.hooks != nil {
|
||||
if leave := e.hooks.OnEnterPackage(pkg); leave != nil {
|
||||
|
@ -122,16 +122,19 @@ func (e *evaluator) EvaluatePackage(pkg *symbols.Package, args core.Args) {
|
|||
}
|
||||
|
||||
// Search the package for a default module to evaluate.
|
||||
var ret *rt.Object
|
||||
var uw *rt.Unwind
|
||||
defmod := pkg.Default()
|
||||
if defmod == nil {
|
||||
e.Diag().Errorf(errors.ErrorPackageHasNoDefaultModule.At(pkg.Tree()), pkg.Name())
|
||||
} else {
|
||||
e.EvaluateModule(defmod, args)
|
||||
ret, uw = e.EvaluateModule(defmod, args)
|
||||
}
|
||||
return ret, uw
|
||||
}
|
||||
|
||||
// EvaluateModule performs evaluation on the given module's entrypoint function.
|
||||
func (e *evaluator) EvaluateModule(mod *symbols.Module, args core.Args) {
|
||||
func (e *evaluator) EvaluateModule(mod *symbols.Module, args core.Args) (*rt.Object, *rt.Unwind) {
|
||||
glog.Infof("Evaluating module '%v'", mod.Token())
|
||||
if e.hooks != nil {
|
||||
if leave := e.hooks.OnEnterModule(mod); leave != nil {
|
||||
|
@ -145,10 +148,12 @@ func (e *evaluator) EvaluateModule(mod *symbols.Module, args core.Args) {
|
|||
}
|
||||
|
||||
// Fetch the module's entrypoint function, erroring out if it doesn't have one.
|
||||
var ret *rt.Object
|
||||
var uw *rt.Unwind
|
||||
hadEntry := false
|
||||
if entry, has := mod.Members[tokens.EntryPointFunction]; has {
|
||||
if entryfnc, ok := entry.(symbols.Function); ok {
|
||||
e.EvaluateFunction(entryfnc, nil, args)
|
||||
ret, uw = e.EvaluateFunction(entryfnc, nil, args)
|
||||
hadEntry = true
|
||||
}
|
||||
}
|
||||
|
@ -156,10 +161,12 @@ func (e *evaluator) EvaluateModule(mod *symbols.Module, args core.Args) {
|
|||
if !hadEntry {
|
||||
e.Diag().Errorf(errors.ErrorModuleHasNoEntryPoint.At(mod.Tree()), mod.Name())
|
||||
}
|
||||
|
||||
return ret, uw
|
||||
}
|
||||
|
||||
// EvaluateFunction performs an evaluation of the given function, using the provided arguments, returning its graph.
|
||||
func (e *evaluator) EvaluateFunction(fnc symbols.Function, this *rt.Object, args core.Args) {
|
||||
func (e *evaluator) EvaluateFunction(fnc symbols.Function, this *rt.Object, args core.Args) (*rt.Object, *rt.Unwind) {
|
||||
glog.Infof("Evaluating function '%v'", fnc.Token())
|
||||
if e.hooks != nil {
|
||||
if leave := e.hooks.OnEnterFunction(fnc); leave != nil {
|
||||
|
@ -217,9 +224,11 @@ func (e *evaluator) EvaluateFunction(fnc symbols.Function, this *rt.Object, args
|
|||
}
|
||||
}
|
||||
|
||||
var ret *rt.Object
|
||||
var uw *rt.Unwind
|
||||
if e.Diag().Success() {
|
||||
// If the arguments bound correctly, make the call.
|
||||
if _, uw := e.evalCallSymbol(fnc.Tree(), fnc, this, argos...); uw != nil {
|
||||
if ret, uw = e.evalCallSymbol(fnc.Tree(), fnc, this, argos...); uw != nil {
|
||||
// If the call had an unwind out of it, then presumably we have an unhandled exception.
|
||||
e.issueUnhandledException(uw, errors.ErrorUnhandledException.At(fnc.Tree()))
|
||||
}
|
||||
|
@ -227,6 +236,8 @@ func (e *evaluator) EvaluateFunction(fnc symbols.Function, this *rt.Object, args
|
|||
|
||||
// Dump the evaluation state at log-level 5, if it is enabled.
|
||||
e.dumpEvalState(5)
|
||||
|
||||
return ret, uw
|
||||
}
|
||||
|
||||
// Utility functions
|
||||
|
@ -609,48 +620,54 @@ func (e *evaluator) evalCall(node diag.Diagable,
|
|||
var thisVariable *symbols.LocalVariable
|
||||
var superVariable *symbols.LocalVariable
|
||||
if sym != nil {
|
||||
// Set up the appropriate this/super variables, and also ensure that we enter the right module/class context
|
||||
// (otherwise module-sensitive binding won't work).
|
||||
switch f := sym.(type) {
|
||||
case *symbols.ClassMethod:
|
||||
if f.Static() {
|
||||
for fsym, done := sym, false; !done; {
|
||||
contract.Assert(fsym != nil)
|
||||
// Set up the appropriate this/super variables, and also ensure that we enter the right module/class
|
||||
// context (otherwise module-sensitive binding won't work).
|
||||
switch f := fsym.(type) {
|
||||
case *symbols.ClassMethod:
|
||||
if f.Static() {
|
||||
if this != nil {
|
||||
// A non-nil `this` is okay if we loaded this function from a prototype object.
|
||||
prototy, isproto := this.Type().(*symbols.PrototypeType)
|
||||
contract.Assert(isproto)
|
||||
contract.Assert(prototy.Type == f.Parent)
|
||||
}
|
||||
} else {
|
||||
contract.Assertf(this != nil, "Expect non-nil this to invoke '%v'", f)
|
||||
if uw := e.checkThis(node, this); uw != nil {
|
||||
return nil, uw
|
||||
}
|
||||
thisVariable = f.Parent.This
|
||||
superVariable = f.Parent.Super
|
||||
}
|
||||
|
||||
popModule := e.pushModuleScope(f.Parent.Parent)
|
||||
defer popModule()
|
||||
popClass := e.pushClassScope(f.Parent, this)
|
||||
defer popClass()
|
||||
done = true
|
||||
|
||||
case *symbols.ModuleMethod:
|
||||
if this != nil {
|
||||
// A non-nil `this` is okay if we loaded this function from a prototype object.
|
||||
prototy, isproto := this.Type().(*symbols.PrototypeType)
|
||||
contract.Assert(isproto)
|
||||
contract.Assert(prototy.Type == f.Parent)
|
||||
// A non-nil `this` is okay if we loaded this function from a module object. Because modules can
|
||||
// re-export members from other modules, we cannot require that the type's parent matches.
|
||||
_, ismod := this.Type().(*symbols.ModuleType)
|
||||
contract.Assert(ismod)
|
||||
this = nil // the this parameter isn't required during invocation.
|
||||
}
|
||||
} else {
|
||||
contract.Assertf(this != nil, "Expect non-nil this to invoke '%v'", f)
|
||||
if uw := e.checkThis(node, this); uw != nil {
|
||||
return nil, uw
|
||||
}
|
||||
thisVariable = f.Parent.This
|
||||
superVariable = f.Parent.Super
|
||||
|
||||
popModule := e.pushModuleScope(f.Parent)
|
||||
defer popModule()
|
||||
done = true
|
||||
|
||||
case *Intrinsic:
|
||||
intrinsic = true
|
||||
fsym = f.Func // swap in the underlying symbol for purposes of this/super/scoping.
|
||||
|
||||
default:
|
||||
contract.Failf("Unrecognized function type during call: %v", reflect.TypeOf(fnc))
|
||||
}
|
||||
|
||||
popModule := e.pushModuleScope(f.Parent.Parent)
|
||||
defer popModule()
|
||||
popClass := e.pushClassScope(f.Parent, this)
|
||||
defer popClass()
|
||||
|
||||
case *symbols.ModuleMethod:
|
||||
if this != nil {
|
||||
// A non-nil `this` is okay if we loaded this function from a module object. Note that because modules
|
||||
// can re-export members from other modules, we cannot require that the type's parent matches.
|
||||
_, ismod := this.Type().(*symbols.ModuleType)
|
||||
contract.Assert(ismod)
|
||||
}
|
||||
|
||||
popModule := e.pushModuleScope(f.Parent)
|
||||
defer popModule()
|
||||
|
||||
case *Intrinsic:
|
||||
contract.Assert(this == nil)
|
||||
intrinsic = true
|
||||
|
||||
default:
|
||||
contract.Failf("Unrecognized function type during call: %v", reflect.TypeOf(fnc))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -41,8 +41,8 @@ func init() {
|
|||
|
||||
// Intrinsic is a special intrinsic function whose behavior is implemented by the runtime.
|
||||
type Intrinsic struct {
|
||||
Node diag.Diagable // the contextual node representing the place where this intrinsic got created.
|
||||
Func ast.Function // the underlying function's node (before mapping to an intrinsic).
|
||||
Node diag.Diagable // the contextual node representing the place where this intrinsic got created.
|
||||
Func symbols.Function // the underlying function symbol (before mapping to an intrinsic).
|
||||
Nm tokens.Name
|
||||
Tok tokens.Token
|
||||
Sig *symbols.FunctionType
|
||||
|
@ -56,8 +56,8 @@ func (node *Intrinsic) Name() tokens.Name { return node.Nm }
|
|||
func (node *Intrinsic) Token() tokens.Token { return node.Tok }
|
||||
func (node *Intrinsic) Special() bool { return false }
|
||||
func (node *Intrinsic) SpecialModInit() bool { return false }
|
||||
func (node *Intrinsic) Tree() diag.Diagable { return node.Func }
|
||||
func (node *Intrinsic) Function() ast.Function { return node.Func }
|
||||
func (node *Intrinsic) Tree() diag.Diagable { return node.Func.Function() }
|
||||
func (node *Intrinsic) Function() ast.Function { return node.Func.Function() }
|
||||
func (node *Intrinsic) Signature() *symbols.FunctionType { return node.Sig }
|
||||
func (node *Intrinsic) String() string { return string(node.Name()) }
|
||||
|
||||
|
@ -68,7 +68,7 @@ func (node *Intrinsic) Invoke(e *evaluator, this *rt.Object, args []*rt.Object)
|
|||
}
|
||||
|
||||
// NewIntrinsic returns a new intrinsic function symbol with the given information.
|
||||
func NewIntrinsic(tree diag.Diagable, fnc ast.Function, tok tokens.Token, nm tokens.Name,
|
||||
func NewIntrinsic(tree diag.Diagable, fnc symbols.Function, tok tokens.Token, nm tokens.Name,
|
||||
sig *symbols.FunctionType, invoker Invoker) *Intrinsic {
|
||||
return &Intrinsic{
|
||||
Node: tree,
|
||||
|
@ -93,7 +93,7 @@ func MaybeIntrinsic(tree diag.Diagable, sym symbols.Symbol) symbols.Symbol {
|
|||
if invoker, isintrinsic := Intrinsics[tok]; isintrinsic {
|
||||
contract.Assertf(tok.HasModuleMember(), "only module member intrinsics currently supported")
|
||||
name := tokens.Name(tokens.ModuleMember(tok).Name())
|
||||
sym = NewIntrinsic(tree, s.Function(), tok, name, s.Signature(), invoker)
|
||||
sym = NewIntrinsic(tree, s, tok, name, s.Signature(), invoker)
|
||||
}
|
||||
}
|
||||
return sym
|
||||
|
|
199
pkg/eval/intrinsics_dynamic_test.go
Normal file
199
pkg/eval/intrinsics_dynamic_test.go
Normal file
|
@ -0,0 +1,199 @@
|
|||
// Licensed to Pulumi Corporation ("Pulumi") under one or more
|
||||
// contributor license agreements. See the NOTICE file distributed with
|
||||
// this work for additional information regarding copyright ownership.
|
||||
// Pulumi licenses this file to You under the Apache License, Version 2.0
|
||||
// (the "License"); you may not use this file except in compliance with
|
||||
// the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package eval
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/pulumi/lumi/pkg/compiler/ast"
|
||||
"github.com/pulumi/lumi/pkg/compiler/binder"
|
||||
"github.com/pulumi/lumi/pkg/compiler/core"
|
||||
"github.com/pulumi/lumi/pkg/compiler/metadata"
|
||||
"github.com/pulumi/lumi/pkg/compiler/types"
|
||||
"github.com/pulumi/lumi/pkg/pack"
|
||||
"github.com/pulumi/lumi/pkg/tokens"
|
||||
"github.com/pulumi/lumi/pkg/util/contract"
|
||||
"github.com/pulumi/lumi/pkg/workspace"
|
||||
)
|
||||
|
||||
// newTestEval makes an interpreter that can be used for testing purposes.
|
||||
func newTestEval() (binder.Binder, Interpreter) {
|
||||
pwd, err := os.Getwd()
|
||||
contract.Assert(err == nil)
|
||||
ctx := core.NewContext(pwd, core.DefaultSink(pwd), nil)
|
||||
w, err := workspace.New(ctx)
|
||||
contract.Assert(err == nil)
|
||||
reader := metadata.NewReader(ctx)
|
||||
b := binder.New(w, ctx, reader)
|
||||
return b, New(b.Ctx(), nil)
|
||||
}
|
||||
|
||||
var isFunctionIntrin = tokens.ModuleMember("lumi:runtime/dynamic:isFunction")
|
||||
|
||||
func makeIsFunctionExprAST(dynamic bool) ast.Expression {
|
||||
if dynamic {
|
||||
var loadLumiMod ast.Expression = &ast.LoadDynamicExpression{
|
||||
Name: &ast.StringLiteral{
|
||||
Value: isFunctionIntrin.Module().Name().String(),
|
||||
},
|
||||
}
|
||||
return &ast.LoadDynamicExpression{
|
||||
Object: &loadLumiMod,
|
||||
Name: &ast.StringLiteral{
|
||||
Value: isFunctionIntrin.Name().String(),
|
||||
},
|
||||
}
|
||||
}
|
||||
return &ast.LoadLocationExpression{
|
||||
Name: &ast.Token{
|
||||
Tok: tokens.Token(isFunctionIntrin),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func makeTestIsFunctionAST(dynamic bool, realFunc bool) *pack.Package {
|
||||
|
||||
// Make the function body.
|
||||
var body []ast.Statement
|
||||
|
||||
// If an intrinsic, we need to import the module so that it's available dynamically through a name.
|
||||
if dynamic {
|
||||
body = append(body, &ast.Import{
|
||||
Referent: &ast.Token{
|
||||
Tok: tokens.Token(isFunctionIntrin.Module()),
|
||||
},
|
||||
Name: &ast.Identifier{
|
||||
Ident: tokens.Name(isFunctionIntrin.Module().Name().String()),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
var invokeArg *ast.CallArgument
|
||||
if realFunc {
|
||||
// for real functions, just pass the isFunction function object itself.
|
||||
loadFuncExpr := makeIsFunctionExprAST(dynamic)
|
||||
invokeArg = &ast.CallArgument{
|
||||
Expr: loadFuncExpr,
|
||||
}
|
||||
} else {
|
||||
// for others, just pass a null literal.
|
||||
invokeArg = &ast.CallArgument{
|
||||
Expr: &ast.NullLiteral{},
|
||||
}
|
||||
}
|
||||
|
||||
loadFuncExpr := makeIsFunctionExprAST(dynamic)
|
||||
var invokeExpr ast.Expression = &ast.InvokeFunctionExpression{
|
||||
CallExpressionNode: ast.CallExpressionNode{
|
||||
Arguments: &[]*ast.CallArgument{invokeArg},
|
||||
},
|
||||
Function: loadFuncExpr,
|
||||
}
|
||||
body = append(body, &ast.ReturnStatement{
|
||||
Expression: &invokeExpr,
|
||||
})
|
||||
|
||||
// Now return a package with a default module and single entrypoint main function.
|
||||
return &pack.Package{
|
||||
Name: "testIsFunction",
|
||||
Dependencies: &pack.Dependencies{
|
||||
"lumi": "*",
|
||||
},
|
||||
Modules: &ast.Modules{
|
||||
tokens.ModuleName(".default"): &ast.Module{
|
||||
DefinitionNode: ast.DefinitionNode{
|
||||
Name: &ast.Identifier{
|
||||
Ident: tokens.Name(".default"),
|
||||
},
|
||||
},
|
||||
Members: &ast.ModuleMembers{
|
||||
tokens.ModuleMemberName(".main"): &ast.ModuleMethod{
|
||||
FunctionNode: ast.FunctionNode{
|
||||
ReturnType: &ast.TypeToken{
|
||||
Tok: types.Bool.TypeToken(),
|
||||
},
|
||||
Body: &ast.Block{
|
||||
Statements: body,
|
||||
},
|
||||
},
|
||||
ModuleMemberNode: ast.ModuleMemberNode{
|
||||
DefinitionNode: ast.DefinitionNode{
|
||||
Name: &ast.Identifier{
|
||||
Ident: tokens.Name(".main"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// TestIsFunction verifies the `lumi:runtime/dynamic:isFunction` intrinsic.
|
||||
func TestIsFunction(t *testing.T) {
|
||||
// variant #1: invoke the function statically, passing a null literal; expect a false return.
|
||||
{
|
||||
b, e := newTestEval()
|
||||
pack := makeTestIsFunctionAST(false, false)
|
||||
sym := b.BindPackage(pack)
|
||||
ret, uw := e.EvaluatePackage(sym, nil)
|
||||
assert.True(t, b.Diag().Success(), "Expected a successful evaluation")
|
||||
assert.Nil(t, uw, "Did not expect a out-of-the-ordinary unwind to occur (expected a return)")
|
||||
assert.NotNil(t, ret, "Expected a non-nil return value")
|
||||
assert.True(t, ret.IsBool(), "Expected a bool return value; got %v", ret.Type())
|
||||
assert.Equal(t, ret.BoolValue(), false, "Expected a return value of false; got %v", ret.BoolValue())
|
||||
}
|
||||
// variant #2: invoke the function dynamically, passing a null literal; expect a false return.
|
||||
{
|
||||
b, e := newTestEval()
|
||||
pack := makeTestIsFunctionAST(true, false)
|
||||
sym := b.BindPackage(pack)
|
||||
ret, uw := e.EvaluatePackage(sym, nil)
|
||||
assert.True(t, b.Diag().Success(), "Expected a successful evaluation")
|
||||
assert.Nil(t, uw, "Did not expect a out-of-the-ordinary unwind to occur (expected a return)")
|
||||
assert.NotNil(t, ret, "Expected a non-nil return value")
|
||||
assert.True(t, ret.IsBool(), "Expected a bool return value; got %v", ret.Type())
|
||||
assert.Equal(t, ret.BoolValue(), false, "Expected a return value of false; got %v", ret.BoolValue())
|
||||
}
|
||||
// variant #3: invoke the function statically, passing a real function; expect a true return.
|
||||
{
|
||||
b, e := newTestEval()
|
||||
pack := makeTestIsFunctionAST(false, true)
|
||||
sym := b.BindPackage(pack)
|
||||
ret, uw := e.EvaluatePackage(sym, nil)
|
||||
assert.True(t, b.Diag().Success(), "Expected a successful evaluation")
|
||||
assert.Nil(t, uw, "Did not expect a out-of-the-ordinary unwind to occur (expected a return)")
|
||||
assert.NotNil(t, ret, "Expected a non-nil return value")
|
||||
assert.True(t, ret.IsBool(), "Expected a bool return value; got %v", ret.Type())
|
||||
assert.Equal(t, ret.BoolValue(), true, "Expected a return value of true; got %v", ret.BoolValue())
|
||||
}
|
||||
// variant #4: invoke the function dynamically, passing a real function; expect a true return.
|
||||
{
|
||||
b, e := newTestEval()
|
||||
pack := makeTestIsFunctionAST(true, true)
|
||||
sym := b.BindPackage(pack)
|
||||
ret, uw := e.EvaluatePackage(sym, nil)
|
||||
assert.True(t, b.Diag().Success(), "Expected a successful evaluation")
|
||||
assert.Nil(t, uw, "Did not expect a out-of-the-ordinary unwind to occur (expected a return)")
|
||||
assert.NotNil(t, ret, "Expected a non-nil return value")
|
||||
assert.True(t, ret.IsBool(), "Expected a bool return value; got %v", ret.Type())
|
||||
assert.Equal(t, ret.BoolValue(), true, "Expected a return value of true; got %v", ret.BoolValue())
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue