Refactor exec tests further
This commit is contained in:
parent
22492ee956
commit
6d77def7dd
234
exec_test.go
234
exec_test.go
|
@ -10,118 +10,113 @@ import (
|
|||
"github.com/tyler-sommer/stick/parse"
|
||||
)
|
||||
|
||||
// execTest is an extensible template execution test.
|
||||
type execTest interface {
|
||||
name() string
|
||||
tpl() string
|
||||
ctx() map[string]Value
|
||||
expected() expectedChecker
|
||||
// A testValidator checks that the result of a test execution is as expected.
|
||||
type testValidator func(actual string, err error) error
|
||||
|
||||
// execTest is a configurable template execution test.
|
||||
type execTest struct {
|
||||
name string
|
||||
tpl string
|
||||
ctx map[string]Value
|
||||
|
||||
checkResult testValidator
|
||||
|
||||
visitNode func(parse.Node) // When visitNode is set, it will be called after each node is parsed.
|
||||
}
|
||||
|
||||
// _execTest is the standard implementation of execTest.
|
||||
type _execTest struct {
|
||||
_name string
|
||||
_tpl string
|
||||
_ctx map[string]Value
|
||||
_expected expectedChecker
|
||||
type testOption func(t *execTest)
|
||||
|
||||
func newExecTest(name string, tpl string, v testValidator, opts ...testOption) execTest {
|
||||
t := execTest{name: name, tpl: tpl, checkResult: v}
|
||||
for _, o := range opts {
|
||||
o(&t)
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
func (t _execTest) name() string { return t._name }
|
||||
func (t _execTest) tpl() string { return t._tpl }
|
||||
func (t _execTest) ctx() map[string]Value { return t._ctx }
|
||||
func (t _execTest) expected() expectedChecker { return t._expected }
|
||||
|
||||
func newExecTest(name string, tpl string, ctx map[string]Value, expected expectedChecker) execTest {
|
||||
return _execTest{name, tpl, ctx, expected}
|
||||
// withContext sets the context variables for the template.
|
||||
func withContext(ctx map[string]Value) testOption {
|
||||
return func(t *execTest) {
|
||||
t.ctx = ctx
|
||||
}
|
||||
}
|
||||
|
||||
// execTestWithPatch is an execTest with a monkey patch function defined.
|
||||
type execTestWithPatch struct {
|
||||
execTest
|
||||
|
||||
patchNode func(parse.Node) // When patchNode is set, it will be called during parsing after each node is parsed.
|
||||
}
|
||||
|
||||
// withPatch enhances a test with the ability to monkey patch the parsed nodes.
|
||||
// withNodeVisitor enhances a test with the ability to inspect parsed nodes.
|
||||
//
|
||||
// Patching is used when a test needs to create alter the internal structure of a template.
|
||||
// This can be used to create an invalid tree that would not normally be parsed, but may be
|
||||
// necessary to test a certain error condition.
|
||||
func withPatch(t execTest, patch func(parse.Node)) execTestWithPatch {
|
||||
return execTestWithPatch{t, patch}
|
||||
// For the purposes of this testing, this provides the ability to muck around with the internal
|
||||
// structure of a template. This can be used to create an invalid tree that would not normally
|
||||
// be parsed, but may be necessary to test a certain error condition.
|
||||
func withNodeVisitor(visitFunc func(parse.Node)) testOption {
|
||||
return func(t *execTest) {
|
||||
t.visitNode = visitFunc
|
||||
}
|
||||
}
|
||||
|
||||
var tests = []execTest{
|
||||
newExecTest("Hello, World", "Hello, World!", nil, expect("Hello, World!")),
|
||||
newExecTest("Hello, Tyler!", "Hello, {{ name }}!", map[string]Value{"name": "Tyler"}, expect("Hello, Tyler!")),
|
||||
newExecTest("Simple if", `<li class="{% if active %}active{% endif %}">`, map[string]Value{"active": true}, expect(`<li class="active">`)),
|
||||
newExecTest("Simple inheritance", `{% extends 'Hello, {% block test %}universe{% endblock %}!' %}{% block test %}world{% endblock %}`, nil, expect(`Hello, world!`)),
|
||||
newExecTest("Simple include", `This is a test. {% include 'Hello, {{ name }}!' %} This concludes the test.`, map[string]Value{"name": "John"}, expect(`This is a test. Hello, John! This concludes the test.`)),
|
||||
newExecTest("Include with", `{% include 'Hello, {{ name }}{{ value }}' with vars %}`, map[string]Value{"value": "!", "vars": map[string]Value{"name": "Adam"}}, expect(`Hello, Adam!`)),
|
||||
newExecTest("Include with literal", `{% include 'Hello, {{ name }}{{ value }}' with {"name": "world", "value": "!"} only %}`, nil, expect(`Hello, world!`)),
|
||||
newExecTest("Embed", `Well. {% embed 'Hello, {% block name %}World{% endblock %}!' %}{% block name %}Tyler{% endblock %}{% endembed %}`, nil, expect(`Well. Hello, Tyler!`)),
|
||||
newExecTest("Constant null", `{% if test == null %}Yes{% else %}no{% endif %}`, map[string]Value{"test": nil}, expect(`Yes`)),
|
||||
newExecTest("Constant bool", `{% if test == true %}Yes{% else %}no{% endif %}`, map[string]Value{"test": false}, expect(`no`)),
|
||||
newExecTest("Chained attributes", `{{ entity.attr.Name }}`, map[string]Value{"entity": map[string]Value{"attr": struct{ Name string }{"Tyler"}}}, expect(`Tyler`)),
|
||||
newExecTest("Attribute method call", `{{ entity.Name('lower') }}`, map[string]Value{"entity": &fakePerson{"Johnny"}}, expect(`lowerJohnny`)),
|
||||
newExecTest("For loop", `{% for i in 1..3 %}{{ i }}{% endfor %}`, nil, expect(`123`)),
|
||||
newExecTest("Hello, World", "Hello, World!", expect("Hello, World!")),
|
||||
newExecTest("Hello, Tyler!", "Hello, {{ name }}!", expect("Hello, Tyler!"), withContext(map[string]Value{"name": "Tyler"})),
|
||||
newExecTest("Simple if", `<li class="{% if active %}active{% endif %}">`, expect(`<li class="active">`), withContext(map[string]Value{"active": true})),
|
||||
newExecTest("Simple inheritance", `{% extends 'Hello, {% block test %}universe{% endblock %}!' %}{% block test %}world{% endblock %}`, expect(`Hello, world!`)),
|
||||
newExecTest("Simple include", `This is a test. {% include 'Hello, {{ name }}!' %} This concludes the test.`, expect(`This is a test. Hello, John! This concludes the test.`), withContext(map[string]Value{"name": "John"})),
|
||||
newExecTest("Include with", `{% include 'Hello, {{ name }}{{ value }}' with vars %}`, expect(`Hello, Adam!`), withContext(map[string]Value{"value": "!", "vars": map[string]Value{"name": "Adam"}})),
|
||||
newExecTest("Include with literal", `{% include 'Hello, {{ name }}{{ value }}' with {"name": "world", "value": "!"} only %}`, expect(`Hello, world!`)),
|
||||
newExecTest("Embed", `Well. {% embed 'Hello, {% block name %}World{% endblock %}!' %}{% block name %}Tyler{% endblock %}{% endembed %}`, expect(`Well. Hello, Tyler!`)),
|
||||
newExecTest("Constant null", `{% if test == null %}Yes{% else %}no{% endif %}`, expect(`Yes`), withContext(map[string]Value{"test": nil})),
|
||||
newExecTest("Constant bool", `{% if test == true %}Yes{% else %}no{% endif %}`, expect(`no`), withContext(map[string]Value{"test": false})),
|
||||
newExecTest("Chained attributes", `{{ entity.attr.Name }}`, expect(`Tyler`), withContext(map[string]Value{"entity": map[string]Value{"attr": struct{ Name string }{"Tyler"}}})),
|
||||
newExecTest("Attribute method call", `{{ entity.Name('lower') }}`, expect(`lowerJohnny`), withContext(map[string]Value{"entity": &fakePerson{"Johnny"}})),
|
||||
newExecTest("For loop", `{% for i in 1..3 %}{{ i }}{% endfor %}`, expect(`123`)),
|
||||
newExecTest(
|
||||
"For loop with inner loop",
|
||||
`{% for i in test %}{% for j in i %}{{ j }}{{ loop.index }}{{ loop.parent.index }}{% if loop.first %},{% endif %}{% if loop.last %};{% endif %}{% endfor %}{% if loop.first %}f{% endif %}{% if loop.last %}l{% endif %}:{% endfor %}`,
|
||||
map[string]Value{
|
||||
expect(`111,221331;f:412,522632;:713,823933;l:`),
|
||||
withContext(map[string]Value{
|
||||
"test": [][]int{
|
||||
{1, 2, 3},
|
||||
{4, 5, 6},
|
||||
{7, 8, 9}},
|
||||
},
|
||||
expect(`111,221331;f:412,522632;:713,823933;l:`),
|
||||
}),
|
||||
),
|
||||
newExecTest(
|
||||
"For loop variables",
|
||||
`{% for i in 1..3 %}{{ i }}{{ loop.index }}{{ loop.index0 }}{{ loop.revindex }}{{ loop.revindex0 }}{{ loop.length }}{% if loop.first %}f{% endif %}{% if loop.last %}l{% endif %}{% endfor %}`,
|
||||
nil,
|
||||
expect(`110323f221213332103l`),
|
||||
),
|
||||
newExecTest("For else", `{% for i in emptySet %}{{ i }}{% else %}No results.{% endfor %}`, map[string]Value{"emptySet": []int{}}, expect(`No results.`)),
|
||||
newExecTest("For else", `{% for i in emptySet %}{{ i }}{% else %}No results.{% endfor %}`, expect(`No results.`), withContext(map[string]Value{"emptySet": []int{}})),
|
||||
newExecTest(
|
||||
"For map",
|
||||
`{% for k, v in data %}Record {{ loop.index }}: {{ k }}: {{ v }}{% if not loop.last %} - {% endif %}{% endfor %}`,
|
||||
map[string]Value{"data": map[string]float64{"Group A": 5.12, "Group B": 5.09}},
|
||||
expect(`Record 1: Group A: 5.12 - Record 2: Group B: 5.09`, `Record 1: Group B: 5.09 - Record 2: Group A: 5.12`),
|
||||
withContext(map[string]Value{"data": map[string]float64{"Group A": 5.12, "Group B": 5.09}}),
|
||||
),
|
||||
newExecTest(
|
||||
"Some operators",
|
||||
`{{ 4.5 * 10 }} - {{ 3 + true }} - {{ 3 + 4 == 7.0 }} - {{ 10 % 2 == 0 }} - {{ 10 ** 2 > 99.9 and 10 ** 2 <= 100 }}`,
|
||||
nil,
|
||||
expect(`45 - 4 - 1 - 1 - 1`),
|
||||
),
|
||||
newExecTest("In and not in", `{{ 5 in set and 4 not in set }}`, map[string]Value{"set": []int{5, 10}}, expect(`1`)),
|
||||
newExecTest("Function call", `{{ multiply(num, 5) }}`, map[string]Value{"num": 10}, expect(`50`)),
|
||||
newExecTest("Filter call", `Welcome, {{ name }}`, nil, expect(`Welcome, `)),
|
||||
newExecTest("Filter call", `Welcome, {{ name|default('User') }}`, map[string]Value{"name": nil}, expect(`Welcome, User`)),
|
||||
newExecTest("Filter call", `Welcome, {{ surname|default('User') }}`, map[string]Value{"name": nil}, expect(`Welcome, User`)),
|
||||
newExecTest("In and not in", `{{ 5 in set and 4 not in set }}`, expect(`1`), withContext(map[string]Value{"set": []int{5, 10}})),
|
||||
newExecTest("Function call", `{{ multiply(num, 5) }}`, expect(`50`), withContext(map[string]Value{"num": 10})),
|
||||
newExecTest("Filter call", `Welcome, {{ name }}`, expect(`Welcome, `)),
|
||||
newExecTest("Filter call", `Welcome, {{ name|default('User') }}`, expect(`Welcome, User`), withContext(map[string]Value{"name": nil})),
|
||||
newExecTest("Filter call", `Welcome, {{ surname|default('User') }}`, expect(`Welcome, User`), withContext(map[string]Value{"name": nil})),
|
||||
newExecTest(
|
||||
"Basic use statement",
|
||||
`{% extends '{% block message %}{% endblock %}' %}{% use '{% block message %}Hello{% endblock %}' %}`,
|
||||
nil,
|
||||
expect("Hello"),
|
||||
),
|
||||
newExecTest(
|
||||
"Extended use statement",
|
||||
`{% extends '{% block message %}{% endblock %}' %}{% use '{% block message %}Hello{% endblock %}' with message as base_message %}{% block message %}{{ block('base_message') }}, World!{% endblock %}`,
|
||||
nil,
|
||||
expect("Hello, World!"),
|
||||
),
|
||||
newExecTest(
|
||||
"Set statement",
|
||||
`{% set val = 'a value' %}{{ val }}`,
|
||||
nil,
|
||||
expect("a value"),
|
||||
),
|
||||
newExecTest(
|
||||
"Set statement with body",
|
||||
`{% set var1 = 'Hello,' %}{% set var2 %}{{ var1 }} World!{% endset %}{{ var2 }}`,
|
||||
nil,
|
||||
expect("Hello, World!"),
|
||||
),
|
||||
newExecTest(
|
||||
|
@ -135,107 +130,107 @@ var tests = []execTest{
|
|||
{{ var0 }}
|
||||
{{ var2 }}
|
||||
{{ var0 }}`,
|
||||
nil,
|
||||
expectContains("Hello, World!\n1\n\n\nHello, World!\n1"),
|
||||
),
|
||||
withPatch(newExecTest(
|
||||
newExecTest(
|
||||
"Set statement invalid expr type",
|
||||
`{% set v = 10 %}`,
|
||||
nil,
|
||||
expectErrorContains("unable to evaluate unsupported Expr type: *parse.TextNode"),
|
||||
), func(n parse.Node) {
|
||||
if sn, ok := n.(*parse.SetNode); ok {
|
||||
sn.X = parse.NewTextNode("Hello", sn.X.Start())
|
||||
}
|
||||
}),
|
||||
withNodeVisitor(func(n parse.Node) {
|
||||
if sn, ok := n.(*parse.SetNode); ok {
|
||||
sn.X = parse.NewTextNode("Hello", sn.X.Start())
|
||||
}
|
||||
}),
|
||||
),
|
||||
newExecTest(
|
||||
"Do statement",
|
||||
`{% do p.Name('Mister ') %}{{ p.Name('') }}`,
|
||||
map[string]Value{"p": &fakePerson{"Meeseeks"}},
|
||||
expect("Mister Meeseeks"),
|
||||
withContext(map[string]Value{"p": &fakePerson{"Meeseeks"}}),
|
||||
),
|
||||
newExecTest(
|
||||
"Filter statement",
|
||||
`{% filter upper %}hello, world!{% endfilter %}`,
|
||||
nil,
|
||||
expect("HELLO, WORLD!"),
|
||||
),
|
||||
newExecTest(
|
||||
"Import statement",
|
||||
`{% import 'macros.twig' as mac %}{{ mac.test("hi") }}`,
|
||||
nil,
|
||||
expect("test: hi"),
|
||||
),
|
||||
newExecTest(
|
||||
"From statement",
|
||||
`{% from 'macros.twig' import test, def as other %}{{ other("", "HI!") }}`,
|
||||
nil,
|
||||
expect("HI!"),
|
||||
),
|
||||
newExecTest(
|
||||
"Ternary if",
|
||||
`{{ false ? (true ? "Hello" : "World") : "Words" }}`,
|
||||
nil,
|
||||
expect("Words"),
|
||||
),
|
||||
newExecTest(
|
||||
"Hash literal",
|
||||
`{{ {"test": 1}["test"] }}`,
|
||||
nil,
|
||||
expect("1"),
|
||||
),
|
||||
newExecTest(
|
||||
"Another hash literal",
|
||||
`{% set v = {quadruple: "to the power of four!", 0: "ew", "0": "it's not that bad"} %}ew? {{ v.0 }} {{ v.quadruple }}`,
|
||||
nil,
|
||||
expect("ew? it's not that bad to the power of four!"),
|
||||
),
|
||||
newExecTest(
|
||||
"Array literal",
|
||||
`{{ ["test", 1, "bar"][2] }}`,
|
||||
nil,
|
||||
expect("bar"),
|
||||
),
|
||||
newExecTest(
|
||||
"Another Array literal",
|
||||
`{{ ["test", 1, "bar"].1 }}`,
|
||||
nil,
|
||||
expect("1"),
|
||||
),
|
||||
newExecTest(
|
||||
"Comparison with or",
|
||||
`{% if item1 == "banana" or item2 == "apple" %}At least one item is correct{% else %}neither item is correct{% endif %}`,
|
||||
map[string]Value{"item1": "orange", "item2": "apple"},
|
||||
expect("At least one item is correct"),
|
||||
withContext(map[string]Value{"item1": "orange", "item2": "apple"}),
|
||||
),
|
||||
newExecTest(
|
||||
"Non-existent map element without default",
|
||||
`{{ data.A }} {{ data.NotThere }} {{ data.B }}`,
|
||||
map[string]Value{"data": map[string]string{"A": "Foo", "B": "Bar"}},
|
||||
expect("Foo Bar"),
|
||||
withContext(map[string]Value{"data": map[string]string{"A": "Foo", "B": "Bar"}}),
|
||||
),
|
||||
newExecTest(
|
||||
"Non-existent map element with default",
|
||||
`{{ data.A }} {{ data.NotThere|default("default value") }} {{ data.B }}`,
|
||||
map[string]Value{"data": map[string]string{"A": "Foo", "B": "Bar"}},
|
||||
expect("Foo default value Bar"),
|
||||
withContext(map[string]Value{"data": map[string]string{"A": "Foo", "B": "Bar"}}),
|
||||
),
|
||||
newExecTest(
|
||||
"Accessing templateName on _self",
|
||||
`Template: {{ _self.templateName }}`,
|
||||
nil,
|
||||
expect("Template: Template: {{ _self.templateName }}"),
|
||||
),
|
||||
withPatch(_execTest{
|
||||
newExecTest(
|
||||
"Unsupported binary operator",
|
||||
`{{ 1 + 2 }}`,
|
||||
nil,
|
||||
expectErrorContains("unsupported binary operator: _"),
|
||||
}, func(n parse.Node) {
|
||||
if bn, ok := n.(*parse.BinaryExpr); ok {
|
||||
bn.Op = "_"
|
||||
}
|
||||
}),
|
||||
withNodeVisitor(func(n parse.Node) {
|
||||
if bn, ok := n.(*parse.BinaryExpr); ok {
|
||||
bn.Op = "_"
|
||||
}
|
||||
}),
|
||||
),
|
||||
newExecTest(
|
||||
"Unsupported binary operator",
|
||||
`{{ 1 + 2 }}`,
|
||||
expectErrorContains("unable to evaluate unsupported Expr type: *parse.TextNode"),
|
||||
withNodeVisitor(func(n parse.Node) {
|
||||
if bn, ok := n.(*parse.BinaryExpr); ok {
|
||||
bn.Right = parse.NewTextNode("foo", bn.Right.Start())
|
||||
}
|
||||
}),
|
||||
),
|
||||
}
|
||||
|
||||
func joinExpected(expected []string) string {
|
||||
|
@ -277,7 +272,7 @@ func (err *expectErrorMismatchError) Error() string {
|
|||
if len(err.expected) == 0 {
|
||||
if err.actual == nil {
|
||||
// shouldn't happen in practice, but technically possible
|
||||
return fmt.Sprint("expected error mismatch but there was no actual error and no error was expected! (bug?)")
|
||||
return fmt.Sprint("error mismatch expected but there was no actual error and no error was expected! (bug?)")
|
||||
}
|
||||
return fmt.Sprintf("unexpected error %#v", err.actual.Error())
|
||||
}
|
||||
|
@ -292,9 +287,7 @@ func newExpectErrorMismatchError(actual error, expected ...string) error {
|
|||
return &expectErrorMismatchError{actual, expected}
|
||||
}
|
||||
|
||||
type expectedChecker func(actual string, err error) error
|
||||
|
||||
func expectChained(expects ...expectedChecker) expectedChecker {
|
||||
func expectChained(expects ...testValidator) testValidator {
|
||||
return func(actual string, err error) error {
|
||||
for _, expect := range expects {
|
||||
if e := expect(actual, err); e != nil {
|
||||
|
@ -305,7 +298,7 @@ func expectChained(expects ...expectedChecker) expectedChecker {
|
|||
}
|
||||
}
|
||||
|
||||
func expect(expected ...string) expectedChecker {
|
||||
func expect(expected ...string) testValidator {
|
||||
return expectChained(expectNoError(), func(actual string, err error) error {
|
||||
for _, exp := range expected {
|
||||
if actual == exp {
|
||||
|
@ -316,7 +309,7 @@ func expect(expected ...string) expectedChecker {
|
|||
})
|
||||
}
|
||||
|
||||
func expectContains(matches string) expectedChecker {
|
||||
func expectContains(matches string) testValidator {
|
||||
return expectChained(expectNoError(), func(actual string, err error) error {
|
||||
if !strings.Contains(actual, matches) {
|
||||
return newExpectFailedError(true, actual, matches)
|
||||
|
@ -325,7 +318,7 @@ func expectContains(matches string) expectedChecker {
|
|||
})
|
||||
}
|
||||
|
||||
func expectNoError() expectedChecker {
|
||||
func expectNoError() testValidator {
|
||||
return func(actual string, err error) error {
|
||||
if err != nil {
|
||||
return newExpectErrorMismatchError(err)
|
||||
|
@ -334,7 +327,7 @@ func expectNoError() expectedChecker {
|
|||
}
|
||||
}
|
||||
|
||||
func expectErrorContains(expected ...string) expectedChecker {
|
||||
func expectErrorContains(expected ...string) testValidator {
|
||||
return func(_ string, err error) error {
|
||||
if err == nil && len(expected) == 0 {
|
||||
return nil
|
||||
|
@ -354,17 +347,6 @@ func expectErrorContains(expected ...string) expectedChecker {
|
|||
}
|
||||
}
|
||||
|
||||
func evaluateTest(t *testing.T, env *Env, test execTest) {
|
||||
w := &bytes.Buffer{}
|
||||
err := execute(test.tpl(), w, test.ctx(), env)
|
||||
check := test.expected()
|
||||
|
||||
out := w.String()
|
||||
if err := check(out, err); err != nil {
|
||||
t.Errorf("%s: %s", test.name(), err)
|
||||
}
|
||||
}
|
||||
|
||||
type testLoader struct {
|
||||
templates map[string]Template
|
||||
}
|
||||
|
@ -402,16 +384,29 @@ func (t *testLoader) Load(name string) (Template, error) {
|
|||
return tpl(name, name), nil
|
||||
}
|
||||
|
||||
// nodeMonkeyPatcher is a parse.NodeVisitor to enable tests to arbitrarily modify a parsed tree.
|
||||
type nodeMonkeyPatcher struct {
|
||||
patch func(parse.Node)
|
||||
// testVisitor is a parse.NodeVisitor to enable tests to arbitrarily modify a parsed tree.
|
||||
type testVisitor struct {
|
||||
// visit is called when the visitor leaves a node
|
||||
visit func(parse.Node)
|
||||
}
|
||||
|
||||
func (v *nodeMonkeyPatcher) Enter(parse.Node) {}
|
||||
func (v *testVisitor) Enter(parse.Node) {
|
||||
// only Leave is used for simplicity's sake
|
||||
}
|
||||
|
||||
func (v *nodeMonkeyPatcher) Leave(n parse.Node) {
|
||||
if v.patch != nil {
|
||||
v.patch(n)
|
||||
func (v *testVisitor) Leave(n parse.Node) {
|
||||
if v.visit != nil {
|
||||
v.visit(n)
|
||||
}
|
||||
}
|
||||
|
||||
func evaluateTest(t *testing.T, env *Env, test execTest) {
|
||||
w := &bytes.Buffer{}
|
||||
err := execute(test.tpl, w, test.ctx, env)
|
||||
|
||||
out := w.String()
|
||||
if err := test.checkResult(out, err); err != nil {
|
||||
t.Errorf("%s: %s", test.name, err)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -446,13 +441,10 @@ func TestExec(t *testing.T) {
|
|||
}
|
||||
return val
|
||||
}
|
||||
patcher := &nodeMonkeyPatcher{}
|
||||
env.Visitors = append(env.Visitors, patcher)
|
||||
tv := &testVisitor{}
|
||||
env.Visitors = append(env.Visitors, tv)
|
||||
for _, test := range tests {
|
||||
patcher.patch = nil
|
||||
if t, ok := test.(execTestWithPatch); ok {
|
||||
patcher.patch = t.patchNode
|
||||
}
|
||||
tv.visit = test.visitNode
|
||||
evaluateTest(t, env, test)
|
||||
}
|
||||
}
|
||||
|
|
Reference in a new issue