| // Copyright 2009 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| package eval |
| |
| import ( |
| "big" |
| "flag" |
| "fmt" |
| "go/token" |
| "log" |
| "os" |
| "reflect" |
| "regexp" |
| "testing" |
| ) |
| |
| // All tests are done using the same file set. |
| var fset = token.NewFileSet() |
| |
| // Print each statement or expression before parsing it |
| var noisy = false |
| |
| func init() { flag.BoolVar(&noisy, "noisy", false, "chatter during eval tests") } |
| |
| /* |
| * Generic statement/expression test framework |
| */ |
| |
| type test []job |
| |
| type job struct { |
| code string |
| cerr string |
| rterr string |
| val Value |
| noval bool |
| } |
| |
| func runTests(t *testing.T, baseName string, tests []test) { |
| for i, test := range tests { |
| name := fmt.Sprintf("%s[%d]", baseName, i) |
| test.run(t, name) |
| } |
| } |
| |
| func (a test) run(t *testing.T, name string) { |
| w := newTestWorld() |
| for _, j := range a { |
| src := j.code + ";" // trailing semicolon to finish statement |
| if noisy { |
| println("code:", src) |
| } |
| |
| code, err := w.Compile(fset, src) |
| if err != nil { |
| if j.cerr == "" { |
| t.Errorf("%s: Compile %s: %v", name, src, err) |
| break |
| } |
| if !match(t, err, j.cerr) { |
| t.Errorf("%s: Compile %s = error %s; want %v", name, src, err, j.cerr) |
| break |
| } |
| continue |
| } |
| if j.cerr != "" { |
| t.Errorf("%s: Compile %s succeeded; want %s", name, src, j.cerr) |
| break |
| } |
| |
| val, err := code.Run() |
| if err != nil { |
| if j.rterr == "" { |
| t.Errorf("%s: Run %s: %v", name, src, err) |
| break |
| } |
| if !match(t, err, j.rterr) { |
| t.Errorf("%s: Run %s = error %s; want %v", name, src, err, j.rterr) |
| break |
| } |
| continue |
| } |
| if j.rterr != "" { |
| t.Errorf("%s: Run %s succeeded; want %s", name, src, j.rterr) |
| break |
| } |
| |
| if !j.noval && !reflect.DeepEqual(val, j.val) { |
| t.Errorf("%s: Run %s = %T(%v) want %T(%v)", name, src, val, val, j.val, j.val) |
| } |
| } |
| } |
| |
| func match(t *testing.T, err os.Error, pat string) bool { |
| ok, err1 := regexp.MatchString(pat, err.String()) |
| if err1 != nil { |
| t.Fatalf("compile regexp %s: %v", pat, err1) |
| } |
| return ok |
| } |
| |
| |
| /* |
| * Test constructors |
| */ |
| |
| // Expression compile error |
| func CErr(expr string, cerr string) test { return test([]job{{code: expr, cerr: cerr}}) } |
| |
| // Expression runtime error |
| func RErr(expr string, rterr string) test { return test([]job{{code: expr, rterr: rterr}}) } |
| |
| // Expression value |
| func Val(expr string, val interface{}) test { |
| return test([]job{{code: expr, val: toValue(val)}}) |
| } |
| |
| // Statement runs without error |
| func Run(stmts string) test { return test([]job{{code: stmts, noval: true}}) } |
| |
| // Two statements without error. |
| // TODO(rsc): Should be possible with Run but the parser |
| // won't let us do both top-level and non-top-level statements. |
| func Run2(stmt1, stmt2 string) test { |
| return test([]job{{code: stmt1, noval: true}, {code: stmt2, noval: true}}) |
| } |
| |
| // Statement runs and test one expression's value |
| func Val1(stmts string, expr1 string, val1 interface{}) test { |
| return test([]job{ |
| {code: stmts, noval: true}, |
| {code: expr1, val: toValue(val1)}, |
| }) |
| } |
| |
| // Statement runs and test two expressions' values |
| func Val2(stmts string, expr1 string, val1 interface{}, expr2 string, val2 interface{}) test { |
| return test([]job{ |
| {code: stmts, noval: true}, |
| {code: expr1, val: toValue(val1)}, |
| {code: expr2, val: toValue(val2)}, |
| }) |
| } |
| |
| /* |
| * Value constructors |
| */ |
| |
| type vstruct []interface{} |
| |
| type varray []interface{} |
| |
| type vslice struct { |
| arr varray |
| len, cap int |
| } |
| |
| func toValue(val interface{}) Value { |
| switch val := val.(type) { |
| case bool: |
| r := boolV(val) |
| return &r |
| case uint8: |
| r := uint8V(val) |
| return &r |
| case uint: |
| r := uintV(val) |
| return &r |
| case int: |
| r := intV(val) |
| return &r |
| case *big.Int: |
| return &idealIntV{val} |
| case float64: |
| r := float64V(val) |
| return &r |
| case *big.Rat: |
| return &idealFloatV{val} |
| case string: |
| r := stringV(val) |
| return &r |
| case vstruct: |
| elems := make([]Value, len(val)) |
| for i, e := range val { |
| elems[i] = toValue(e) |
| } |
| r := structV(elems) |
| return &r |
| case varray: |
| elems := make([]Value, len(val)) |
| for i, e := range val { |
| elems[i] = toValue(e) |
| } |
| r := arrayV(elems) |
| return &r |
| case vslice: |
| return &sliceV{Slice{toValue(val.arr).(ArrayValue), int64(val.len), int64(val.cap)}} |
| case Func: |
| return &funcV{val} |
| } |
| log.Panicf("toValue(%T) not implemented", val) |
| panic("unreachable") |
| } |
| |
| /* |
| * Default test scope |
| */ |
| |
| type testFunc struct{} |
| |
| func (*testFunc) NewFrame() *Frame { return &Frame{nil, make([]Value, 2)} } |
| |
| func (*testFunc) Call(t *Thread) { |
| n := t.f.Vars[0].(IntValue).Get(t) |
| |
| res := n + 1 |
| |
| t.f.Vars[1].(IntValue).Set(t, res) |
| } |
| |
| type oneTwoFunc struct{} |
| |
| func (*oneTwoFunc) NewFrame() *Frame { return &Frame{nil, make([]Value, 2)} } |
| |
| func (*oneTwoFunc) Call(t *Thread) { |
| t.f.Vars[0].(IntValue).Set(t, 1) |
| t.f.Vars[1].(IntValue).Set(t, 2) |
| } |
| |
| type voidFunc struct{} |
| |
| func (*voidFunc) NewFrame() *Frame { return &Frame{nil, []Value{}} } |
| |
| func (*voidFunc) Call(t *Thread) {} |
| |
| func newTestWorld() *World { |
| w := NewWorld() |
| |
| def := func(name string, t Type, val interface{}) { w.DefineVar(name, t, toValue(val)) } |
| |
| w.DefineConst("c", IdealIntType, toValue(big.NewInt(1))) |
| def("i", IntType, 1) |
| def("i2", IntType, 2) |
| def("u", UintType, uint(1)) |
| def("f", Float64Type, 1.0) |
| def("s", StringType, "abc") |
| def("t", NewStructType([]StructField{{"a", IntType, false}}), vstruct{1}) |
| def("ai", NewArrayType(2, IntType), varray{1, 2}) |
| def("aai", NewArrayType(2, NewArrayType(2, IntType)), varray{varray{1, 2}, varray{3, 4}}) |
| def("aai2", NewArrayType(2, NewArrayType(2, IntType)), varray{varray{5, 6}, varray{7, 8}}) |
| def("fn", NewFuncType([]Type{IntType}, false, []Type{IntType}), &testFunc{}) |
| def("oneTwo", NewFuncType([]Type{}, false, []Type{IntType, IntType}), &oneTwoFunc{}) |
| def("void", NewFuncType([]Type{}, false, []Type{}), &voidFunc{}) |
| def("sli", NewSliceType(IntType), vslice{varray{1, 2, 3}, 2, 3}) |
| |
| return w |
| } |