github.com/lab47/exprcore@v0.0.0-20210525052339-fb7d6bd9331e/exprcoretest/exprcoretest.go (about)

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