github.com/google/skylark@v0.0.0-20181101142754-a5f7082aabed/skylarktest/skylarktest.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 skylarktest defines utilities for testing Skylark programs.
     6  //
     7  // Clients can call LoadAssertModule to load a module that defines
     8  // several functions useful for testing.  See assert.sky 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 skylarktest
    14  
    15  import (
    16  	"bytes"
    17  	"fmt"
    18  	"go/build"
    19  	"path/filepath"
    20  	"regexp"
    21  	"sync"
    22  
    23  	"github.com/google/skylark"
    24  	"github.com/google/skylark/skylarkstruct"
    25  )
    26  
    27  const localKey = "Reporter"
    28  
    29  // A Reporter is a value to which errors may be reported.
    30  // It is satisfied by *testing.T.
    31  type Reporter interface {
    32  	Error(args ...interface{})
    33  }
    34  
    35  // SetReporter associates an error reporter (such as a testing.T in
    36  // a Go test) with the Skylark thread so that Skylark programs may
    37  // report errors to it.
    38  func SetReporter(thread *skylark.Thread, r Reporter) {
    39  	thread.SetLocal(localKey, r)
    40  }
    41  
    42  // GetReporter returns the Skylark thread's error reporter.
    43  // It must be preceded by a call to SetReporter.
    44  func GetReporter(thread *skylark.Thread) Reporter {
    45  	r, ok := thread.Local(localKey).(Reporter)
    46  	if !ok {
    47  		panic("internal error: skylarktest.SetReporter was not called")
    48  	}
    49  	return r
    50  }
    51  
    52  var (
    53  	once      sync.Once
    54  	assert    skylark.StringDict
    55  	assertErr error
    56  )
    57  
    58  // LoadAssertModule loads the assert module.
    59  // It is concurrency-safe and idempotent.
    60  func LoadAssertModule() (skylark.StringDict, error) {
    61  	once.Do(func() {
    62  		predeclared := skylark.StringDict{
    63  			"error":   skylark.NewBuiltin("error", error_),
    64  			"catch":   skylark.NewBuiltin("catch", catch),
    65  			"matches": skylark.NewBuiltin("matches", matches),
    66  			"struct":  skylark.NewBuiltin("struct", skylarkstruct.Make),
    67  			"_freeze": skylark.NewBuiltin("freeze", freeze),
    68  		}
    69  		filename := DataFile("skylark/skylarktest", "assert.sky")
    70  		thread := new(skylark.Thread)
    71  		assert, assertErr = skylark.ExecFile(thread, filename, nil, predeclared)
    72  	})
    73  	return assert, assertErr
    74  }
    75  
    76  // catch(f) evaluates f() and returns its evaluation error message
    77  // if it failed or None if it succeeded.
    78  func catch(thread *skylark.Thread, _ *skylark.Builtin, args skylark.Tuple, kwargs []skylark.Tuple) (skylark.Value, error) {
    79  	var fn skylark.Callable
    80  	if err := skylark.UnpackArgs("catch", args, kwargs, "fn", &fn); err != nil {
    81  		return nil, err
    82  	}
    83  	if _, err := skylark.Call(thread, fn, nil, nil); err != nil {
    84  		return skylark.String(err.Error()), nil
    85  	}
    86  	return skylark.None, nil
    87  }
    88  
    89  // matches(pattern, str) reports whether string str matches the regular expression pattern.
    90  func matches(thread *skylark.Thread, _ *skylark.Builtin, args skylark.Tuple, kwargs []skylark.Tuple) (skylark.Value, error) {
    91  	var pattern, str string
    92  	if err := skylark.UnpackArgs("matches", args, kwargs, "pattern", &pattern, "str", &str); err != nil {
    93  		return nil, err
    94  	}
    95  	ok, err := regexp.MatchString(pattern, str)
    96  	if err != nil {
    97  		return nil, fmt.Errorf("matches: %s", err)
    98  	}
    99  	return skylark.Bool(ok), nil
   100  }
   101  
   102  // error(x) reports an error to the Go test framework.
   103  func error_(thread *skylark.Thread, _ *skylark.Builtin, args skylark.Tuple, kwargs []skylark.Tuple) (skylark.Value, error) {
   104  	if len(args) != 1 {
   105  		return nil, fmt.Errorf("error: got %d arguments, want 1", len(args))
   106  	}
   107  	var buf bytes.Buffer
   108  	thread.Caller().WriteBacktrace(&buf)
   109  	buf.WriteString("Error: ")
   110  	if s, ok := skylark.AsString(args[0]); ok {
   111  		buf.WriteString(s)
   112  	} else {
   113  		buf.WriteString(args[0].String())
   114  	}
   115  	GetReporter(thread).Error(buf.String())
   116  	return skylark.None, nil
   117  }
   118  
   119  // freeze(x) freezes its operand.
   120  func freeze(thread *skylark.Thread, _ *skylark.Builtin, args skylark.Tuple, kwargs []skylark.Tuple) (skylark.Value, error) {
   121  	if len(kwargs) > 0 {
   122  		return nil, fmt.Errorf("freeze does not accept keyword arguments")
   123  	}
   124  	if len(args) != 1 {
   125  		return nil, fmt.Errorf("freeze got %d arguments, wants 1", len(args))
   126  	}
   127  	args[0].Freeze()
   128  	return args[0], nil
   129  }
   130  
   131  // DataFile returns the effective filename of the specified
   132  // test data resource.  The function abstracts differences between
   133  // 'go build', under which a test runs in its package directory,
   134  // and Blaze, under which a test runs in the root of the tree.
   135  var DataFile = func(pkgdir, filename string) string {
   136  	return filepath.Join(build.Default.GOPATH, "src/github.com/google", pkgdir, filename)
   137  }