micro/tools/testgen.go
2020-03-01 13:20:10 +05:30

255 lines
5.9 KiB
Go

package main
import (
"fmt"
"io/ioutil"
"log"
"os"
"regexp"
"strings"
"github.com/robertkrimen/otto/ast"
"github.com/robertkrimen/otto/parser"
)
type walker struct {
nodes []ast.Node
}
func (w *walker) Enter(node ast.Node) ast.Visitor {
w.nodes = append(w.nodes, node)
return w
}
func (w *walker) Exit(node ast.Node) {
}
func getAllNodes(node ast.Node) []ast.Node {
w := &walker{}
ast.Walk(w, node)
return w.nodes
}
func getCalls(node ast.Node, name string) []*ast.CallExpression {
nodes := []*ast.CallExpression{}
for _, n := range getAllNodes(node) {
if ce, ok := n.(*ast.CallExpression); ok {
var calleeName string
switch callee := ce.Callee.(type) {
case *ast.Identifier:
calleeName = callee.Name
case *ast.DotExpression:
calleeName = callee.Identifier.Name
default:
continue
}
if calleeName == name {
nodes = append(nodes, ce)
}
}
}
return nodes
}
func getPropertyValue(node ast.Node, key string) ast.Expression {
for _, p := range node.(*ast.ObjectLiteral).Value {
if p.Key == key {
return p.Value
}
}
return nil
}
type operation struct {
startLine int
startColumn int
endLine int
endColumn int
text []string
}
type check struct {
before []string
operations []operation
after []string
}
type test struct {
description string
checks []check
}
func stringSliceToGoSource(slice []string) string {
var b strings.Builder
b.WriteString("[]string{\n")
for _, s := range slice {
b.WriteString(fmt.Sprintf("%#v,\n", s))
}
b.WriteString("}")
return b.String()
}
func testToGoTest(test test, name string) string {
var b strings.Builder
b.WriteString("func Test")
b.WriteString(name)
b.WriteString("(t *testing.T) {\n")
for _, c := range test.checks {
b.WriteString("check(\n")
b.WriteString("t,\n")
b.WriteString(fmt.Sprintf("%v,\n", stringSliceToGoSource(c.before)))
b.WriteString("[]operation{\n")
for _, op := range c.operations {
b.WriteString("operation{\n")
b.WriteString(fmt.Sprintf("start: Loc{%v, %v},\n", op.startColumn, op.startLine))
b.WriteString(fmt.Sprintf("end: Loc{%v, %v},\n", op.endColumn, op.endLine))
b.WriteString(fmt.Sprintf("text: %v,\n", stringSliceToGoSource(op.text)))
b.WriteString("},\n")
}
b.WriteString("},\n")
b.WriteString(fmt.Sprintf("%v,\n", stringSliceToGoSource(c.after)))
b.WriteString(")\n")
}
b.WriteString("}\n")
return b.String()
}
func nodeToStringSlice(node ast.Node) []string {
var result []string
for _, s := range node.(*ast.ArrayLiteral).Value {
result = append(result, s.(*ast.StringLiteral).Value)
}
return result
}
func nodeToStringSlice2(node ast.Node) []string {
var result []string
for _, o := range node.(*ast.ArrayLiteral).Value {
result = append(result, getPropertyValue(o, "text").(*ast.StringLiteral).Value)
}
return result
}
func nodeToInt(node ast.Node) int {
return int(node.(*ast.NumberLiteral).Value.(int64))
}
func getChecks(node ast.Node) []check {
checks := []check{}
for _, ce := range getCalls(node, "testApplyEdits") {
if len(ce.ArgumentList) != 3 {
// Wrong function
continue
}
before := nodeToStringSlice2(ce.ArgumentList[0])
after := nodeToStringSlice2(ce.ArgumentList[2])
var operations []operation
for _, op := range ce.ArgumentList[1].(*ast.ArrayLiteral).Value {
args := getPropertyValue(op, "range").(*ast.NewExpression).ArgumentList
operations = append(operations, operation{
startLine: nodeToInt(args[0]) - 1,
startColumn: nodeToInt(args[1]) - 1,
endLine: nodeToInt(args[2]) - 1,
endColumn: nodeToInt(args[3]) - 1,
text: []string{getPropertyValue(op, "text").(*ast.StringLiteral).Value},
})
}
checks = append(checks, check{before, operations, after})
}
for _, ce := range getCalls(node, "testApplyEditsWithSyncedModels") {
if len(ce.ArgumentList) > 3 && ce.ArgumentList[3].(*ast.BooleanLiteral).Value {
// inputEditsAreInvalid == true
continue
}
before := nodeToStringSlice(ce.ArgumentList[0])
after := nodeToStringSlice(ce.ArgumentList[2])
var operations []operation
for _, op := range getCalls(ce.ArgumentList[1], "editOp") {
operations = append(operations, operation{
startLine: nodeToInt(op.ArgumentList[0]) - 1,
startColumn: nodeToInt(op.ArgumentList[1]) - 1,
endLine: nodeToInt(op.ArgumentList[2]) - 1,
endColumn: nodeToInt(op.ArgumentList[3]) - 1,
text: nodeToStringSlice(op.ArgumentList[4]),
})
}
checks = append(checks, check{before, operations, after})
}
return checks
}
func getTests(node ast.Node) []test {
tests := []test{}
for _, ce := range getCalls(node, "test") {
description := ce.ArgumentList[0].(*ast.StringLiteral).Value
body := ce.ArgumentList[1].(*ast.FunctionLiteral).Body
checks := getChecks(body)
if len(checks) > 0 {
tests = append(tests, test{description, checks})
}
}
return tests
}
func main() {
var tests []test
for _, filename := range os.Args[1:] {
source, err := ioutil.ReadFile(filename)
if err != nil {
log.Fatalln(err)
}
program, err := parser.ParseFile(nil, "", source, parser.IgnoreRegExpErrors)
if err != nil {
log.Fatalln(err)
}
tests = append(tests, getTests(program)...)
}
if len(tests) == 0 {
log.Fatalln("no tests found!")
}
fmt.Println("// This file is generated from VSCode model tests by the testgen tool.")
fmt.Println("// DO NOT EDIT THIS FILE BY HAND; your changes will be overwritten!\n")
fmt.Println("package buffer")
fmt.Println(`import "testing"`)
re := regexp.MustCompile(`[^\w]`)
usedNames := map[string]bool{}
for _, test := range tests {
name := strings.Title(strings.ToLower(test.description))
name = re.ReplaceAllLiteralString(name, "")
if name == "" {
name = "Unnamed"
}
if usedNames[name] {
for i := 2; ; i++ {
newName := fmt.Sprintf("%v_%v", name, i)
if !usedNames[newName] {
name = newName
break
}
}
}
usedNames[name] = true
fmt.Println(testToGoTest(test, name))
}
}