]> git.lizzy.rs Git - micro.git/blob - tools/testgen.go
Add autoretab
[micro.git] / tools / testgen.go
1 //+build ignore
2
3 package main
4
5 import (
6         "fmt"
7         "io/ioutil"
8         "log"
9         "os"
10         "regexp"
11         "strings"
12
13         "github.com/robertkrimen/otto/ast"
14         "github.com/robertkrimen/otto/parser"
15 )
16
17 type walker struct {
18         nodes []ast.Node
19 }
20
21 func (w *walker) Enter(node ast.Node) ast.Visitor {
22         w.nodes = append(w.nodes, node)
23         return w
24 }
25
26 func (w *walker) Exit(node ast.Node) {
27 }
28
29 func getAllNodes(node ast.Node) []ast.Node {
30         w := &walker{}
31         ast.Walk(w, node)
32         return w.nodes
33 }
34
35 func getCalls(node ast.Node, name string) []*ast.CallExpression {
36         nodes := []*ast.CallExpression{}
37         for _, n := range getAllNodes(node) {
38                 if ce, ok := n.(*ast.CallExpression); ok {
39                         var calleeName string
40                         switch callee := ce.Callee.(type) {
41                         case *ast.Identifier:
42                                 calleeName = callee.Name
43                         case *ast.DotExpression:
44                                 calleeName = callee.Identifier.Name
45                         default:
46                                 continue
47                         }
48                         if calleeName == name {
49                                 nodes = append(nodes, ce)
50                         }
51                 }
52         }
53         return nodes
54 }
55
56 func getPropertyValue(node ast.Node, key string) ast.Expression {
57         for _, p := range node.(*ast.ObjectLiteral).Value {
58                 if p.Key == key {
59                         return p.Value
60                 }
61         }
62         return nil
63 }
64
65 type operation struct {
66         startLine   int
67         startColumn int
68         endLine     int
69         endColumn   int
70         text        []string
71 }
72
73 type check struct {
74         before     []string
75         operations []operation
76         after      []string
77 }
78
79 type test struct {
80         description string
81         checks      []check
82 }
83
84 func stringSliceToGoSource(slice []string) string {
85         var b strings.Builder
86         b.WriteString("[]string{\n")
87         for _, s := range slice {
88                 b.WriteString(fmt.Sprintf("%#v,\n", s))
89         }
90         b.WriteString("}")
91         return b.String()
92 }
93
94 func testToGoTest(test test, name string) string {
95         var b strings.Builder
96
97         b.WriteString("func Test")
98         b.WriteString(name)
99         b.WriteString("(t *testing.T) {\n")
100
101         for _, c := range test.checks {
102                 b.WriteString("check(\n")
103                 b.WriteString("t,\n")
104                 b.WriteString(fmt.Sprintf("%v,\n", stringSliceToGoSource(c.before)))
105                 b.WriteString("[]operation{\n")
106                 for _, op := range c.operations {
107                         b.WriteString("operation{\n")
108                         b.WriteString(fmt.Sprintf("start: Loc{%v, %v},\n", op.startColumn, op.startLine))
109                         b.WriteString(fmt.Sprintf("end: Loc{%v, %v},\n", op.endColumn, op.endLine))
110                         b.WriteString(fmt.Sprintf("text: %v,\n", stringSliceToGoSource(op.text)))
111                         b.WriteString("},\n")
112                 }
113                 b.WriteString("},\n")
114                 b.WriteString(fmt.Sprintf("%v,\n", stringSliceToGoSource(c.after)))
115                 b.WriteString(")\n")
116         }
117
118         b.WriteString("}\n")
119
120         return b.String()
121 }
122
123 func nodeToStringSlice(node ast.Node) []string {
124         var result []string
125         for _, s := range node.(*ast.ArrayLiteral).Value {
126                 result = append(result, s.(*ast.StringLiteral).Value)
127         }
128         return result
129 }
130
131 func nodeToStringSlice2(node ast.Node) []string {
132         var result []string
133         for _, o := range node.(*ast.ArrayLiteral).Value {
134                 result = append(result, getPropertyValue(o, "text").(*ast.StringLiteral).Value)
135         }
136         return result
137 }
138
139 func nodeToInt(node ast.Node) int {
140         return int(node.(*ast.NumberLiteral).Value.(int64))
141 }
142
143 func getChecks(node ast.Node) []check {
144         checks := []check{}
145
146         for _, ce := range getCalls(node, "testApplyEdits") {
147                 if len(ce.ArgumentList) != 3 {
148                         // Wrong function
149                         continue
150                 }
151
152                 before := nodeToStringSlice2(ce.ArgumentList[0])
153                 after := nodeToStringSlice2(ce.ArgumentList[2])
154
155                 var operations []operation
156                 for _, op := range ce.ArgumentList[1].(*ast.ArrayLiteral).Value {
157                         args := getPropertyValue(op, "range").(*ast.NewExpression).ArgumentList
158                         operations = append(operations, operation{
159                                 startLine:   nodeToInt(args[0]) - 1,
160                                 startColumn: nodeToInt(args[1]) - 1,
161                                 endLine:     nodeToInt(args[2]) - 1,
162                                 endColumn:   nodeToInt(args[3]) - 1,
163                                 text:        []string{getPropertyValue(op, "text").(*ast.StringLiteral).Value},
164                         })
165                 }
166
167                 checks = append(checks, check{before, operations, after})
168         }
169
170         for _, ce := range getCalls(node, "testApplyEditsWithSyncedModels") {
171                 if len(ce.ArgumentList) > 3 && ce.ArgumentList[3].(*ast.BooleanLiteral).Value {
172                         // inputEditsAreInvalid == true
173                         continue
174                 }
175
176                 before := nodeToStringSlice(ce.ArgumentList[0])
177                 after := nodeToStringSlice(ce.ArgumentList[2])
178
179                 var operations []operation
180                 for _, op := range getCalls(ce.ArgumentList[1], "editOp") {
181                         operations = append(operations, operation{
182                                 startLine:   nodeToInt(op.ArgumentList[0]) - 1,
183                                 startColumn: nodeToInt(op.ArgumentList[1]) - 1,
184                                 endLine:     nodeToInt(op.ArgumentList[2]) - 1,
185                                 endColumn:   nodeToInt(op.ArgumentList[3]) - 1,
186                                 text:        nodeToStringSlice(op.ArgumentList[4]),
187                         })
188                 }
189
190                 checks = append(checks, check{before, operations, after})
191         }
192
193         return checks
194 }
195
196 func getTests(node ast.Node) []test {
197         tests := []test{}
198         for _, ce := range getCalls(node, "test") {
199                 description := ce.ArgumentList[0].(*ast.StringLiteral).Value
200                 body := ce.ArgumentList[1].(*ast.FunctionLiteral).Body
201                 checks := getChecks(body)
202                 if len(checks) > 0 {
203                         tests = append(tests, test{description, checks})
204                 }
205         }
206         return tests
207 }
208
209 func main() {
210         var tests []test
211
212         for _, filename := range os.Args[1:] {
213                 source, err := ioutil.ReadFile(filename)
214                 if err != nil {
215                         log.Fatalln(err)
216                 }
217
218                 program, err := parser.ParseFile(nil, "", source, parser.IgnoreRegExpErrors)
219                 if err != nil {
220                         log.Fatalln(err)
221                 }
222
223                 tests = append(tests, getTests(program)...)
224         }
225
226         if len(tests) == 0 {
227                 log.Fatalln("no tests found!")
228         }
229
230         fmt.Println("// This file is generated from VSCode model tests by the testgen tool.")
231         fmt.Println("// DO NOT EDIT THIS FILE BY HAND; your changes will be overwritten!\n")
232         fmt.Println("package buffer")
233         fmt.Println(`import "testing"`)
234
235         re := regexp.MustCompile(`[^\w]`)
236         usedNames := map[string]bool{}
237
238         for _, test := range tests {
239                 name := strings.Title(strings.ToLower(test.description))
240                 name = re.ReplaceAllLiteralString(name, "")
241                 if name == "" {
242                         name = "Unnamed"
243                 }
244                 if usedNames[name] {
245                         for i := 2; ; i++ {
246                                 newName := fmt.Sprintf("%v_%v", name, i)
247                                 if !usedNames[newName] {
248                                         name = newName
249                                         break
250                                 }
251                         }
252                 }
253                 usedNames[name] = true
254
255                 fmt.Println(testToGoTest(test, name))
256         }
257 }