782 lines
14 KiB
Go
782 lines
14 KiB
Go
package goexpression
|
|
|
|
import (
|
|
"fmt"
|
|
"testing"
|
|
|
|
"github.com/google/go-cmp/cmp"
|
|
)
|
|
|
|
var ifTests = []testInput{
|
|
{
|
|
name: "basic if",
|
|
input: `true`,
|
|
},
|
|
{
|
|
name: "if function call",
|
|
input: `pkg.Func()`,
|
|
},
|
|
{
|
|
name: "compound",
|
|
input: "x := val(); x > 3",
|
|
},
|
|
{
|
|
name: "if multiple",
|
|
input: `x && y && (!z)`,
|
|
},
|
|
}
|
|
|
|
func TestIf(t *testing.T) {
|
|
prefix := "if "
|
|
suffixes := []string{
|
|
"{\n<div>\nif true content\n\t</div>}",
|
|
" {\n<div>\nif true content\n\t</div>}",
|
|
}
|
|
for _, test := range ifTests {
|
|
for i, suffix := range suffixes {
|
|
t.Run(fmt.Sprintf("%s_%d", test.name, i), run(test, prefix, suffix, If))
|
|
}
|
|
}
|
|
}
|
|
|
|
func FuzzIf(f *testing.F) {
|
|
suffixes := []string{
|
|
"{\n<div>\nif true content\n\t</div>}",
|
|
" {\n<div>\nif true content\n\t</div>}",
|
|
}
|
|
for _, test := range ifTests {
|
|
for _, suffix := range suffixes {
|
|
f.Add("if " + test.input + suffix)
|
|
}
|
|
}
|
|
f.Fuzz(func(t *testing.T, src string) {
|
|
start, end, err := If(src)
|
|
if err != nil {
|
|
t.Skip()
|
|
return
|
|
}
|
|
panicIfInvalid(src, start, end)
|
|
})
|
|
}
|
|
|
|
func panicIfInvalid(src string, start, end int) {
|
|
_ = src[start:end]
|
|
}
|
|
|
|
var forTests = []testInput{
|
|
{
|
|
name: "three component",
|
|
input: `i := 0; i < 100; i++`,
|
|
},
|
|
{
|
|
name: "three component, empty",
|
|
input: `; ; i++`,
|
|
},
|
|
{
|
|
name: "while",
|
|
input: `n < 5`,
|
|
},
|
|
{
|
|
name: "infinite",
|
|
input: ``,
|
|
},
|
|
{
|
|
name: "range with index",
|
|
input: `k, v := range m`,
|
|
},
|
|
{
|
|
name: "range with key only",
|
|
input: `k := range m`,
|
|
},
|
|
{
|
|
name: "channel receive",
|
|
input: `x := range channel`,
|
|
},
|
|
}
|
|
|
|
func TestFor(t *testing.T) {
|
|
prefix := "for "
|
|
suffixes := []string{
|
|
" {\n<div>\nloop content\n\t</div>}",
|
|
}
|
|
for _, test := range forTests {
|
|
for i, suffix := range suffixes {
|
|
t.Run(fmt.Sprintf("%s_%d", test.name, i), run(test, prefix, suffix, For))
|
|
}
|
|
}
|
|
}
|
|
|
|
func FuzzFor(f *testing.F) {
|
|
suffixes := []string{
|
|
"",
|
|
" {",
|
|
" {}",
|
|
" {\n<div>\nloop content\n\t</div>}",
|
|
}
|
|
for _, test := range forTests {
|
|
for _, suffix := range suffixes {
|
|
f.Add("for " + test.input + suffix)
|
|
}
|
|
}
|
|
f.Fuzz(func(t *testing.T, src string) {
|
|
start, end, err := For(src)
|
|
if err != nil {
|
|
t.Skip()
|
|
return
|
|
}
|
|
panicIfInvalid(src, start, end)
|
|
})
|
|
}
|
|
|
|
var switchTests = []testInput{
|
|
{
|
|
name: "switch",
|
|
input: ``,
|
|
},
|
|
{
|
|
name: "switch with expression",
|
|
input: `x`,
|
|
},
|
|
{
|
|
name: "switch with function call",
|
|
input: `pkg.Func()`,
|
|
},
|
|
{
|
|
name: "type switch",
|
|
input: `x := x.(type)`,
|
|
},
|
|
}
|
|
|
|
func TestSwitch(t *testing.T) {
|
|
prefix := "switch "
|
|
suffixes := []string{
|
|
" {\ncase 1:\n\t<div>\n\tcase 2:\n\t\t<div>\n\tdefault:\n\t\t<div>\n\t</div>}",
|
|
" {\ndefault:\n\t<div>\n\t</div>}",
|
|
" {\n}",
|
|
}
|
|
for _, test := range switchTests {
|
|
for i, suffix := range suffixes {
|
|
t.Run(fmt.Sprintf("%s_%d", test.name, i), run(test, prefix, suffix, Switch))
|
|
}
|
|
}
|
|
}
|
|
|
|
func FuzzSwitch(f *testing.F) {
|
|
suffixes := []string{
|
|
"",
|
|
" {",
|
|
" {}",
|
|
" {\n<div>\nloop content\n\t</div>}",
|
|
}
|
|
for _, test := range switchTests {
|
|
for _, suffix := range suffixes {
|
|
f.Add(test.input + suffix)
|
|
}
|
|
}
|
|
f.Fuzz(func(t *testing.T, s string) {
|
|
src := "switch " + s
|
|
start, end, err := For(src)
|
|
if err != nil {
|
|
t.Skip()
|
|
return
|
|
}
|
|
panicIfInvalid(src, start, end)
|
|
})
|
|
}
|
|
|
|
var caseTests = []testInput{
|
|
{
|
|
name: "case",
|
|
input: `case 1:`,
|
|
},
|
|
{
|
|
name: "case with expression",
|
|
input: `case x > 3:`,
|
|
},
|
|
{
|
|
name: "case with function call",
|
|
input: `case pkg.Func():`,
|
|
},
|
|
{
|
|
name: "case with multiple expressions",
|
|
input: `case x > 3, x < 4:`,
|
|
},
|
|
{
|
|
name: "case with multiple expressions and default",
|
|
input: `case x > 3, x < 4, x == 5:`,
|
|
},
|
|
{
|
|
name: "case with type switch",
|
|
input: `case bool:`,
|
|
},
|
|
}
|
|
|
|
func TestCase(t *testing.T) {
|
|
suffixes := []string{
|
|
"\n<div>\ncase 1 content\n\t</div>\n\tcase 3:",
|
|
"\ndefault:\n\t<div>\n\t</div>}",
|
|
"\n}",
|
|
}
|
|
for _, test := range caseTests {
|
|
for i, suffix := range suffixes {
|
|
t.Run(fmt.Sprintf("%s_%d", test.name, i), run(test, "", suffix, Case))
|
|
}
|
|
}
|
|
}
|
|
|
|
func FuzzCaseStandard(f *testing.F) {
|
|
suffixes := []string{
|
|
"",
|
|
"\n<div>\ncase 1 content\n\t</div>\n\tcase 3:",
|
|
"\ndefault:\n\t<div>\n\t</div>}",
|
|
"\n}",
|
|
}
|
|
for _, test := range caseTests {
|
|
for _, suffix := range suffixes {
|
|
f.Add(test.input + suffix)
|
|
}
|
|
}
|
|
f.Fuzz(func(t *testing.T, src string) {
|
|
start, end, err := Case(src)
|
|
if err != nil {
|
|
t.Skip()
|
|
return
|
|
}
|
|
panicIfInvalid(src, start, end)
|
|
})
|
|
}
|
|
|
|
func TestCaseDefault(t *testing.T) {
|
|
suffixes := []string{
|
|
"\n<div>\ncase 1 content\n\t</div>\n\tcase 3:",
|
|
"\ncase:\n\t<div>\n\t</div>}",
|
|
"\n}",
|
|
}
|
|
tests := []testInput{
|
|
{
|
|
name: "default",
|
|
input: `default:`,
|
|
},
|
|
{
|
|
name: "default with padding",
|
|
input: `default :`,
|
|
},
|
|
{
|
|
name: "default with padding",
|
|
input: `default :`,
|
|
},
|
|
}
|
|
for _, test := range tests {
|
|
for i, suffix := range suffixes {
|
|
t.Run(fmt.Sprintf("%s_%d", test.name, i), run(test, "", suffix, Case))
|
|
}
|
|
}
|
|
}
|
|
|
|
func FuzzCaseDefault(f *testing.F) {
|
|
suffixes := []string{
|
|
"",
|
|
" ",
|
|
"\n<div>\ncase 1 content\n\t</div>\n\tcase 3:",
|
|
"\ncase:\n\t<div>\n\t</div>}",
|
|
"\n}",
|
|
}
|
|
for _, suffix := range suffixes {
|
|
f.Add("default:" + suffix)
|
|
}
|
|
f.Fuzz(func(t *testing.T, src string) {
|
|
start, end, err := Case(src)
|
|
if err != nil {
|
|
t.Skip()
|
|
return
|
|
}
|
|
panicIfInvalid(src, start, end)
|
|
})
|
|
}
|
|
|
|
var expressionTests = []testInput{
|
|
{
|
|
name: "string literal",
|
|
input: `"hello"`,
|
|
},
|
|
{
|
|
name: "string literal with escape",
|
|
input: `"hello\n"`,
|
|
},
|
|
{
|
|
name: "backtick string literal",
|
|
input: "`hello`",
|
|
},
|
|
{
|
|
name: "backtick string literal containing double quote",
|
|
input: "`hello" + `"` + `world` + "`",
|
|
},
|
|
{
|
|
name: "function call in package",
|
|
input: `components.Other()`,
|
|
},
|
|
{
|
|
name: "slice index call",
|
|
input: `components[0].Other()`,
|
|
},
|
|
{
|
|
name: "map index function call",
|
|
input: `components["name"].Other()`,
|
|
},
|
|
{
|
|
name: "function literal",
|
|
input: `components["name"].Other(func() bool { return true })`,
|
|
},
|
|
{
|
|
name: "multiline function call",
|
|
input: `component(map[string]string{
|
|
"namea": "name_a",
|
|
"nameb": "name_b",
|
|
})`,
|
|
},
|
|
{
|
|
name: "call with braces and brackets",
|
|
input: `templates.New(test{}, other())`,
|
|
},
|
|
{
|
|
name: "bare variable",
|
|
input: `component`,
|
|
},
|
|
{
|
|
name: "boolean expression",
|
|
input: `direction == "newest"`,
|
|
},
|
|
{
|
|
name: "boolean expression with parens",
|
|
input: `len(data.previousPageUrl) == 0`,
|
|
},
|
|
{
|
|
name: "string concat",
|
|
input: `direction + "newest"`,
|
|
},
|
|
{
|
|
name: "function call",
|
|
input: `SplitRule(types.GroupMember{
|
|
UserID: uuid.NewString(),
|
|
Username: "user me",
|
|
}, []types.GroupMember{
|
|
{
|
|
UserID: uuid.NewString(),
|
|
Username: "user 1",
|
|
},
|
|
})`,
|
|
},
|
|
}
|
|
|
|
func TestExpression(t *testing.T) {
|
|
prefix := ""
|
|
suffixes := []string{
|
|
"",
|
|
"}",
|
|
"\t}",
|
|
" }",
|
|
}
|
|
for _, test := range expressionTests {
|
|
for i, suffix := range suffixes {
|
|
t.Run(fmt.Sprintf("%s_%d", test.name, i), run(test, prefix, suffix, Expression))
|
|
}
|
|
}
|
|
}
|
|
|
|
var templExpressionTests = []testInput{
|
|
{
|
|
name: "function call in package",
|
|
input: `components.Other()`,
|
|
},
|
|
{
|
|
name: "slice index call",
|
|
input: `components[0].Other()`,
|
|
},
|
|
{
|
|
name: "map index function call",
|
|
input: `components["name"].Other()`,
|
|
},
|
|
{
|
|
name: "multiline chain call",
|
|
input: `components.
|
|
Other()`,
|
|
},
|
|
{
|
|
name: "map index function call backtick literal",
|
|
input: "components[`name" + `"` + "`].Other()",
|
|
},
|
|
{
|
|
name: "function literal",
|
|
input: `components["name"].Other(func() bool { return true })`,
|
|
},
|
|
{
|
|
name: "multiline function call",
|
|
input: `component(map[string]string{
|
|
"namea": "name_a",
|
|
"nameb": "name_b",
|
|
})`,
|
|
},
|
|
{
|
|
name: "function call with slice of complex types",
|
|
input: `tabs([]TabData{
|
|
{Name: "A"},
|
|
{Name: "B"},
|
|
})`,
|
|
},
|
|
{
|
|
name: "function call with slice of explicitly named complex types",
|
|
input: `tabs([]TabData{
|
|
TabData{Name: "A"},
|
|
TabData{Name: "B"},
|
|
})`,
|
|
},
|
|
{
|
|
name: "function call with empty slice of strings",
|
|
input: `Inner([]string{})`,
|
|
},
|
|
{
|
|
name: "function call with empty slice of maps",
|
|
input: `Inner([]map[string]any{})`,
|
|
},
|
|
{
|
|
name: "function call with empty slice of anon structs",
|
|
input: `Inner([]map[string]struct{}{})`,
|
|
},
|
|
{
|
|
name: "function call with slice of pointers to complex types",
|
|
input: `tabs([]*TabData{
|
|
&{Name: "A"},
|
|
&{Name: "B"},
|
|
})`,
|
|
},
|
|
{
|
|
name: "function call with slice of pointers to explicitly named complex types",
|
|
input: `tabs([]*TabData{
|
|
&TabData{Name: "A"},
|
|
&TabData{Name: "B"},
|
|
})`,
|
|
},
|
|
{
|
|
name: "function call with array of explicit length",
|
|
input: `tabs([2]TabData{
|
|
{Name: "A"},
|
|
{Name: "B"},
|
|
})`,
|
|
},
|
|
{
|
|
name: "function call with array of inferred length",
|
|
input: `tabs([...]TabData{
|
|
{Name: "A"},
|
|
{Name: "B"},
|
|
})`,
|
|
},
|
|
{
|
|
name: "function call with function arg",
|
|
input: `componentA(func(y []int) string {
|
|
return "hi"
|
|
})`,
|
|
},
|
|
{
|
|
name: "function call with function called arg",
|
|
input: `componentA(func(y []int) string {
|
|
return "hi"
|
|
}())`,
|
|
},
|
|
{
|
|
name: "call with braces and brackets",
|
|
input: `templates.New(test{}, other())`,
|
|
},
|
|
{
|
|
name: "generic call",
|
|
input: `templates.New(toString[[]int](data))`,
|
|
},
|
|
{
|
|
name: "struct method call",
|
|
input: `typeName{}.Method()`,
|
|
},
|
|
{
|
|
name: "struct method call in other package",
|
|
input: "layout.DefaultLayout{}.Compile()",
|
|
},
|
|
{
|
|
name: "bare variable",
|
|
input: `component`,
|
|
},
|
|
}
|
|
|
|
func TestTemplExpression(t *testing.T) {
|
|
prefix := ""
|
|
suffixes := []string{
|
|
"",
|
|
"}",
|
|
"\t}",
|
|
" }",
|
|
"</div>",
|
|
"<p>/</p>",
|
|
" just some text",
|
|
" { <div>Child content</div> }",
|
|
}
|
|
for _, test := range templExpressionTests {
|
|
for i, suffix := range suffixes {
|
|
t.Run(fmt.Sprintf("%s_%d", test.name, i), run(test, prefix, suffix, TemplExpression))
|
|
}
|
|
}
|
|
}
|
|
|
|
func FuzzTemplExpression(f *testing.F) {
|
|
suffixes := []string{
|
|
"",
|
|
" }",
|
|
" }}</a>\n}",
|
|
"...",
|
|
}
|
|
for _, test := range expressionTests {
|
|
for _, suffix := range suffixes {
|
|
f.Add(test.input + suffix)
|
|
}
|
|
}
|
|
f.Fuzz(func(t *testing.T, s string) {
|
|
src := "switch " + s
|
|
start, end, err := TemplExpression(src)
|
|
if err != nil {
|
|
t.Skip()
|
|
return
|
|
}
|
|
panicIfInvalid(src, start, end)
|
|
})
|
|
}
|
|
|
|
func FuzzExpression(f *testing.F) {
|
|
suffixes := []string{
|
|
"",
|
|
" }",
|
|
" }}</a>\n}",
|
|
"...",
|
|
}
|
|
for _, test := range expressionTests {
|
|
for _, suffix := range suffixes {
|
|
f.Add(test.input + suffix)
|
|
}
|
|
}
|
|
f.Fuzz(func(t *testing.T, s string) {
|
|
src := "switch " + s
|
|
start, end, err := Expression(src)
|
|
if err != nil {
|
|
t.Skip()
|
|
return
|
|
}
|
|
panicIfInvalid(src, start, end)
|
|
})
|
|
}
|
|
|
|
var sliceArgsTests = []testInput{
|
|
{
|
|
name: "no input",
|
|
input: ``,
|
|
},
|
|
{
|
|
name: "single input",
|
|
input: `nil`,
|
|
},
|
|
{
|
|
name: "inputs to function call",
|
|
input: `a, b, "c"`,
|
|
},
|
|
{
|
|
name: "function call in package",
|
|
input: `components.Other()`,
|
|
},
|
|
{
|
|
name: "slice index call",
|
|
input: `components[0].Other()`,
|
|
},
|
|
{
|
|
name: "map index function call",
|
|
input: `components["name"].Other()`,
|
|
},
|
|
{
|
|
name: "function literal",
|
|
input: `components["name"].Other(func() bool { return true })`,
|
|
},
|
|
{
|
|
name: "multiline function call",
|
|
input: `component(map[string]string{
|
|
"namea": "name_a",
|
|
"nameb": "name_b",
|
|
})`,
|
|
},
|
|
{
|
|
name: "package name, but no variable or function",
|
|
input: `fmt.`,
|
|
},
|
|
}
|
|
|
|
func TestSliceArgs(t *testing.T) {
|
|
suffixes := []string{
|
|
"",
|
|
"}",
|
|
"}</a>\n}\nvar x = []struct {}{}",
|
|
}
|
|
for _, test := range sliceArgsTests {
|
|
for i, suffix := range suffixes {
|
|
t.Run(fmt.Sprintf("%s_%d", test.name, i), func(t *testing.T) {
|
|
expr, err := SliceArgs(test.input + suffix)
|
|
if err != nil {
|
|
t.Errorf("failed to parse slice args: %v", err)
|
|
}
|
|
if diff := cmp.Diff(test.input, expr); diff != "" {
|
|
t.Error(diff)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
func FuzzSliceArgs(f *testing.F) {
|
|
suffixes := []string{
|
|
"",
|
|
"}",
|
|
" }",
|
|
"}</a>\n}\nvar x = []struct {}{}",
|
|
}
|
|
for _, test := range sliceArgsTests {
|
|
for _, suffix := range suffixes {
|
|
f.Add(test.input + suffix)
|
|
}
|
|
}
|
|
f.Fuzz(func(t *testing.T, s string) {
|
|
_, err := SliceArgs(s)
|
|
if err != nil {
|
|
t.Skip()
|
|
return
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestChildren(t *testing.T) {
|
|
prefix := ""
|
|
suffixes := []string{
|
|
" }",
|
|
" } <div>Other content</div>",
|
|
"", // End of file.
|
|
}
|
|
tests := []testInput{
|
|
{
|
|
name: "children",
|
|
input: `children...`,
|
|
},
|
|
{
|
|
name: "function",
|
|
input: `components.Spread()...`,
|
|
},
|
|
{
|
|
name: "alternative variable",
|
|
input: `components...`,
|
|
},
|
|
{
|
|
name: "index",
|
|
input: `groups[0]...`,
|
|
},
|
|
{
|
|
name: "map",
|
|
input: `components["name"]...`,
|
|
},
|
|
{
|
|
name: "map func key",
|
|
input: `components[getKey(ctx)]...`,
|
|
},
|
|
}
|
|
for _, test := range tests {
|
|
for i, suffix := range suffixes {
|
|
t.Run(fmt.Sprintf("%s_%d", test.name, i), run(test, prefix, suffix, Expression))
|
|
}
|
|
}
|
|
}
|
|
|
|
var funcTests = []testInput{
|
|
{
|
|
name: "void func",
|
|
input: `myfunc()`,
|
|
},
|
|
{
|
|
name: "receiver func",
|
|
input: `(r recv) myfunc()`,
|
|
},
|
|
}
|
|
|
|
func FuzzFuncs(f *testing.F) {
|
|
prefix := "func "
|
|
suffixes := []string{
|
|
"",
|
|
"}",
|
|
" }",
|
|
"}</a>\n}\nvar x = []struct {}{}",
|
|
}
|
|
for _, test := range funcTests {
|
|
for _, suffix := range suffixes {
|
|
f.Add(prefix + test.input + suffix)
|
|
}
|
|
}
|
|
f.Fuzz(func(t *testing.T, s string) {
|
|
_, _, err := Func(s)
|
|
if err != nil {
|
|
t.Skip()
|
|
return
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestFunc(t *testing.T) {
|
|
prefix := "func "
|
|
suffixes := []string{
|
|
"",
|
|
"}",
|
|
"}\nvar x = []struct {}{}",
|
|
"}\nfunc secondFunc() {}",
|
|
}
|
|
for _, test := range funcTests {
|
|
for i, suffix := range suffixes {
|
|
t.Run(fmt.Sprintf("%s_%d", test.name, i), func(t *testing.T) {
|
|
name, expr, err := Func(prefix + test.input + suffix)
|
|
if err != nil {
|
|
t.Errorf("failed to parse slice args: %v", err)
|
|
}
|
|
if diff := cmp.Diff(test.input, expr); diff != "" {
|
|
t.Error(diff)
|
|
}
|
|
if diff := cmp.Diff("myfunc", name); diff != "" {
|
|
t.Error(diff)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
type testInput struct {
|
|
name string
|
|
input string
|
|
expectedErr error
|
|
}
|
|
|
|
type extractor func(content string) (start, end int, err error)
|
|
|
|
func run(test testInput, prefix, suffix string, e extractor) func(t *testing.T) {
|
|
return func(t *testing.T) {
|
|
src := prefix + test.input + suffix
|
|
start, end, err := e(src)
|
|
if test.expectedErr == nil && err != nil {
|
|
t.Fatalf("expected nil error got error type %T: %v", err, err)
|
|
}
|
|
if test.expectedErr != nil && err == nil {
|
|
t.Fatalf("expected err %q, got %v", test.expectedErr.Error(), err)
|
|
}
|
|
if test.expectedErr != nil && err != nil && test.expectedErr.Error() != err.Error() {
|
|
t.Fatalf("expected err %q, got %q", test.expectedErr.Error(), err.Error())
|
|
}
|
|
actual := src[start:end]
|
|
if diff := cmp.Diff(test.input, actual); diff != "" {
|
|
t.Error(diff)
|
|
}
|
|
}
|
|
}
|