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 }