forked from kubernetes/kubernetes
-
Notifications
You must be signed in to change notification settings - Fork 0
/
testfile_rewriter.go
163 lines (139 loc) · 4.58 KB
/
testfile_rewriter.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
package convert
import (
"bytes"
"fmt"
"go/ast"
"go/format"
"go/parser"
"go/token"
"io/ioutil"
"os"
)
/*
* Given a file path, rewrites any tests in the Ginkgo format.
* First, we parse the AST, and update the imports declaration.
* Then, we walk the first child elements in the file, returning tests to rewrite.
* A top level init func is declared, with a single Describe func inside.
* Then the test functions to rewrite are inserted as It statements inside the Describe.
* Finally we walk the rest of the file, replacing other usages of *testing.T
* Once that is complete, we write the AST back out again to its file.
*/
func rewriteTestsInFile(pathToFile string) {
fileSet := token.NewFileSet()
rootNode, err := parser.ParseFile(fileSet, pathToFile, nil, 0)
if err != nil {
panic(fmt.Sprintf("Error parsing test file '%s':\n%s\n", pathToFile, err.Error()))
}
addGinkgoImports(rootNode)
removeTestingImport(rootNode)
varUnderscoreBlock := createVarUnderscoreBlock()
describeBlock := createDescribeBlock()
varUnderscoreBlock.Values = []ast.Expr{describeBlock}
for _, testFunc := range findTestFuncs(rootNode) {
rewriteTestFuncAsItStatement(testFunc, rootNode, describeBlock)
}
underscoreDecl := &ast.GenDecl{
Tok: 85, // gah, magick numbers are needed to make this work
TokPos: 14, // this tricks Go into writing "var _ = Describe"
Specs: []ast.Spec{varUnderscoreBlock},
}
imports := rootNode.Decls[0]
tail := rootNode.Decls[1:]
rootNode.Decls = append(append([]ast.Decl{imports}, underscoreDecl), tail...)
rewriteOtherFuncsToUseGinkgoT(rootNode.Decls)
walkNodesInRootNodeReplacingTestingT(rootNode)
var buffer bytes.Buffer
if err = format.Node(&buffer, fileSet, rootNode); err != nil {
panic(fmt.Sprintf("Error formatting ast node after rewriting tests.\n%s\n", err.Error()))
}
fileInfo, err := os.Stat(pathToFile)
if err != nil {
panic(fmt.Sprintf("Error stat'ing file: %s\n", pathToFile))
}
ioutil.WriteFile(pathToFile, buffer.Bytes(), fileInfo.Mode())
return
}
/*
* Given a test func named TestDoesSomethingNeat, rewrites it as
* It("does something neat", func() { __test_body_here__ }) and adds it
* to the Describe's list of statements
*/
func rewriteTestFuncAsItStatement(testFunc *ast.FuncDecl, rootNode *ast.File, describe *ast.CallExpr) {
var funcIndex int = -1
for index, child := range rootNode.Decls {
if child == testFunc {
funcIndex = index
break
}
}
if funcIndex < 0 {
panic(fmt.Sprintf("Assert failed: Error finding index for test node %s\n", testFunc.Name.Name))
}
var block *ast.BlockStmt = blockStatementFromDescribe(describe)
block.List = append(block.List, createItStatementForTestFunc(testFunc))
replaceTestingTsWithGinkgoT(block, namedTestingTArg(testFunc))
// remove the old test func from the root node's declarations
rootNode.Decls = append(rootNode.Decls[:funcIndex], rootNode.Decls[funcIndex+1:]...)
return
}
/*
* walks nodes inside of a test func's statements and replaces the usage of
* it's named *testing.T param with GinkgoT's
*/
func replaceTestingTsWithGinkgoT(statementsBlock *ast.BlockStmt, testingT string) {
ast.Inspect(statementsBlock, func(node ast.Node) bool {
if node == nil {
return false
}
keyValueExpr, ok := node.(*ast.KeyValueExpr)
if ok {
replaceNamedTestingTsInKeyValueExpression(keyValueExpr, testingT)
return true
}
funcLiteral, ok := node.(*ast.FuncLit)
if ok {
replaceTypeDeclTestingTsInFuncLiteral(funcLiteral)
return true
}
callExpr, ok := node.(*ast.CallExpr)
if !ok {
return true
}
replaceTestingTsInArgsLists(callExpr, testingT)
funCall, ok := callExpr.Fun.(*ast.SelectorExpr)
if ok {
replaceTestingTsMethodCalls(funCall, testingT)
}
return true
})
}
/*
* rewrite t.Fail() or any other *testing.T method by replacing with T().Fail()
* This function receives a selector expression (eg: t.Fail()) and
* the name of the *testing.T param from the function declaration. Rewrites the
* selector expression in place if the target was a *testing.T
*/
func replaceTestingTsMethodCalls(selectorExpr *ast.SelectorExpr, testingT string) {
ident, ok := selectorExpr.X.(*ast.Ident)
if !ok {
return
}
if ident.Name == testingT {
selectorExpr.X = newGinkgoTFromIdent(ident)
}
}
/*
* replaces usages of a named *testing.T param inside of a call expression
* with a new GinkgoT object
*/
func replaceTestingTsInArgsLists(callExpr *ast.CallExpr, testingT string) {
for index, arg := range callExpr.Args {
ident, ok := arg.(*ast.Ident)
if !ok {
continue
}
if ident.Name == testingT {
callExpr.Args[index] = newGinkgoTFromIdent(ident)
}
}
}