-
Notifications
You must be signed in to change notification settings - Fork 655
/
Copy pathmain.go
195 lines (168 loc) · 5.51 KB
/
main.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
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
package main
import (
"encoding/json"
"errors"
"fmt"
"go/ast"
"go/parser"
"go/token"
"io/fs"
"os"
"path/filepath"
"slices"
"sort"
"strings"
)
const (
testNamePrefix = "Test"
testFileNameSuffix = "_test.go"
e2eTestDirectory = "e2e"
// testEntryPointEnv specifies a single test function to run if provided.
testEntryPointEnv = "TEST_ENTRYPOINT"
// testExclusionsEnv is a comma separated list of test function names that will not be included
// in the results of this script.
testExclusionsEnv = "TEST_EXCLUSIONS"
// testNameEnv if provided returns a single test entry so that only one test is actually run.
testNameEnv = "TEST_NAME"
)
// GithubActionTestMatrix represents
type GithubActionTestMatrix struct {
Include []TestSuitePair `json:"include"`
}
type TestSuitePair struct {
Test string `json:"test"`
EntryPoint string `json:"entrypoint"`
}
func main() {
githubActionMatrix, err := getGithubActionMatrixForTests(e2eTestDirectory, getTestToRun(), getTestEntrypointToRun(), getExcludedTestFunctions())
if err != nil {
fmt.Printf("error generating github action json: %s", err)
os.Exit(1)
}
ghBytes, err := json.Marshal(githubActionMatrix)
if err != nil {
fmt.Printf("error marshalling github action json: %s", err)
os.Exit(1)
}
fmt.Println(string(ghBytes))
}
// getTestEntrypointToRun returns the specified test function to run if present, otherwise
// it returns an empty string which will result in running all test suites.
func getTestEntrypointToRun() string {
testSuite, ok := os.LookupEnv(testEntryPointEnv)
if !ok {
return ""
}
return testSuite
}
// getTestToRun returns the specified test function to run if present.
// If specified, only this test will be run.
func getTestToRun() string {
testName, ok := os.LookupEnv(testNameEnv)
if !ok {
return ""
}
return testName
}
// getExcludedTestFunctions returns a list of test functions that we don't want to run.
func getExcludedTestFunctions() []string {
exclusions, ok := os.LookupEnv(testExclusionsEnv)
if !ok {
return nil
}
return strings.Split(exclusions, ",")
}
// getGithubActionMatrixForTests returns a json string representing the contents that should go in the matrix
// field in a github action workflow. This string can be used with `fromJSON(str)` to dynamically build
// the workflow matrix to include all E2E tests under the e2eRootDirectory directory.
func getGithubActionMatrixForTests(e2eRootDirectory, testName string, suite string, excludedItems []string) (GithubActionTestMatrix, error) {
testSuiteMapping := map[string][]string{}
fset := token.NewFileSet()
err := filepath.Walk(e2eRootDirectory, func(path string, info fs.FileInfo, err error) error {
if err != nil {
return fmt.Errorf("error walking e2e directory: %s", err)
}
// only look at test files
if !strings.HasSuffix(path, testFileNameSuffix) {
return nil
}
f, err := parser.ParseFile(fset, path, nil, 0)
if err != nil {
return fmt.Errorf("failed parsing file: %s", err)
}
suiteNameForFile, testCases, err := extractSuiteAndTestNames(f)
if err != nil {
return nil
}
if testName != "" && slices.Contains(testCases, testName) {
testCases = []string{testName}
}
if slices.Contains(excludedItems, suiteNameForFile) {
return nil
}
if suite == "" || suiteNameForFile == suite {
testSuiteMapping[suiteNameForFile] = testCases
}
return nil
})
if err != nil {
return GithubActionTestMatrix{}, err
}
gh := GithubActionTestMatrix{
Include: []TestSuitePair{},
}
for testSuiteName, testCases := range testSuiteMapping {
for _, testCaseName := range testCases {
gh.Include = append(gh.Include, TestSuitePair{
Test: testCaseName,
EntryPoint: testSuiteName,
})
}
}
if len(gh.Include) == 0 {
return GithubActionTestMatrix{}, errors.New("no test cases found")
}
// Sort the test cases by name so that the order is consistent.
sort.SliceStable(gh.Include, func(i, j int) bool {
return gh.Include[i].Test < gh.Include[j].Test
})
if testName != "" && len(gh.Include) != 1 {
return GithubActionTestMatrix{}, fmt.Errorf("expected exactly 1 test in the output matrix but got %d", len(gh.Include))
}
return gh, nil
}
// extractSuiteAndTestNames extracts the name of the test suite function as well
// as all tests associated with it in the same file.
func extractSuiteAndTestNames(file *ast.File) (string, []string, error) {
var suiteNameForFile string
var testCases []string
for _, d := range file.Decls {
if f, ok := d.(*ast.FuncDecl); ok {
functionName := f.Name.Name
if isTestSuiteMethod(f) {
if suiteNameForFile != "" {
return "", nil, fmt.Errorf("found a second test function: %s when %s was already found", f.Name.Name, suiteNameForFile)
}
suiteNameForFile = functionName
continue
}
if isTestFunction(f) {
testCases = append(testCases, functionName)
}
}
}
if suiteNameForFile == "" {
return "", nil, fmt.Errorf("file %s had no test suite test case", file.Name.Name)
}
return suiteNameForFile, testCases, nil
}
// isTestSuiteMethod returns true if the function is a test suite function.
// e.g. func TestFeeMiddlewareTestSuite(t *testing.T) { ... }
func isTestSuiteMethod(f *ast.FuncDecl) bool {
return strings.HasPrefix(f.Name.Name, testNamePrefix) && len(f.Type.Params.List) == 1
}
// isTestFunction returns true if the function name starts with "Test" and has no parameters.
// as test suite functions do not accept a *testing.T.
func isTestFunction(f *ast.FuncDecl) bool {
return strings.HasPrefix(f.Name.Name, testNamePrefix) && len(f.Type.Params.List) == 0
}