-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathtestrunner.go
207 lines (189 loc) · 5.02 KB
/
testrunner.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
196
197
198
199
200
201
202
203
204
205
206
207
package main
import (
"context"
"fmt"
"log"
"os"
"runtime"
"sort"
"strconv"
"strings"
)
var _ Task = (*GoTestRunner)(nil)
// Strategy interface defines provider of tests for the testrunner
type Strategy interface {
CoverageEnabled() bool
TestsToRun(context.Context) (runAll bool, tests, subTests []string, err error)
}
// GoTestRunner runs go tests
type GoTestRunner struct {
strategy Strategy
cmd CommandCreator
args string
log *log.Logger
}
// NewGoTestRunner creates test runner
// strategy to use
// cmd creator
// args to runner
// logger for runner
func NewGoTestRunner(
strategy Strategy,
cmd CommandCreator,
args string,
logger *log.Logger,
) *GoTestRunner {
return &GoTestRunner{
strategy: strategy,
cmd: cmd,
args: args,
log: logger,
}
}
// ID returns Task ID
func (tr *GoTestRunner) ID() string {
return "GoTestRunner"
}
// Run method implements Task interface
// runs go tests
func (tr *GoTestRunner) Run(ctx context.Context) (string, error) {
runAll, tests, subTests, err := tr.strategy.TestsToRun(ctx)
if err != nil {
if err == ErrBuildFailed {
return "Build Failed", nil
}
return "", fmt.Errorf("strategy error %v", err)
}
if len(tests) == 0 && len(subTests) == 0 {
return "No test found to run", nil
}
var listArg []string
pkgPaths := map[string][]string{}
for _, tname := range tests {
id := strings.LastIndexByte(tname, '.')
pkgPath := tname[:id]
if pkgPath == "" {
pkgPath = "."
}
pkgPaths[pkgPath] = append(pkgPaths[pkgPath], tname[id+1:])
}
// run tests
// do not wait process to finish
// in case of console blocking programs
// -vet=off to improve speed
// TODO handle run all
msg := ""
testParams := []string{"test", "-v", "-vet", "off", "-failfast",
"-cpu", strconv.Itoa(runtime.GOMAXPROCS(0))}
logStrList(tr.log, "Tests to run", tests, true)
if len(subTests) > 0 {
logStrList(tr.log, "Subtests to run", subTests, true)
}
var pkgList, testNames []string
for k, pkgtests := range pkgPaths {
pkgList = append(pkgList, k)
testNames = append(testNames, pkgtests...)
}
testsFormated := tr.joinTestAndSubtest(testNames, subTests)
var cmd CommandExecutor
// TODO refactor
if runAll {
if tr.strategy.CoverageEnabled() {
testParams = append(testParams, "-coverprofile")
testParams = append(testParams, "coverage_profile")
}
testParams = append(testParams, "-run")
testParams = append(testParams, testsFormated)
testParams = append(testParams, listArg...)
if len(tr.args) > 0 {
testParams = append(testParams, "-args")
testParams = append(testParams, tr.args)
}
cmd = tr.cmd(ctx, "go", testParams...)
tr.log.Println(">>", strings.Join(cmd.GetArgs(), " "))
cmd.SetStdout(os.Stdout)
cmd.SetStderr(os.Stderr)
cmd.SetEnv(os.Environ())
cmd.Run()
} else {
// run cmd for each test and skip subtests to have separation between tests
OUTER:
for pkg, pkgtests := range pkgPaths {
for _, tname := range pkgtests {
// run all tests
testParams := []string{"test", "-v", "-vet", "off",
"-cpu", strconv.Itoa(runtime.GOMAXPROCS(0))}
if tr.strategy.CoverageEnabled() {
testParams = append(testParams, "-coverprofile")
testParams = append(testParams, fmt.Sprintf(".gtr/%s.%s",
strings.ReplaceAll(pkg, "/", "_"),
tname))
}
testParams = append(testParams, "-run")
testParams = append(testParams, tname+"$") // test
if tr.strategy.CoverageEnabled() {
// for all packages
testParams = append(testParams, "./...")
} else {
// package
testParams = append(testParams, pkg)
}
if len(tr.args) > 0 {
testParams = append(testParams, "-args")
// test binary args
testParams = append(testParams, tr.args)
}
cmd = tr.cmd(ctx, "go", testParams...)
tr.log.Println(">>", strings.Join(cmd.GetArgs(), " "))
cmd.SetStdout(os.Stdout)
cmd.SetStderr(os.Stderr)
cmd.SetEnv(os.Environ())
cmd.Run()
if !cmd.Success() {
// stop on failed test
break OUTER
}
}
}
}
if cmd.Success() {
msg = "Tests PASS: " + testsFormated
tr.log.Println("\033[32mTests PASS\033[39m")
} else {
msg = "Tests FAIL: " + testsFormated
tr.log.Println("\033[31mTests FAIL\033[39m")
}
return msg, nil
}
// joinTestAndSubtest joins and format tests according to go test -run arg format
func (tr *GoTestRunner) joinTestAndSubtest(tests, subTests []string) string {
sort.Strings(tests)
sort.Strings(subTests)
out := strings.Join(tests, "$|")
if len(out) > 0 {
out += "$"
}
for i := range subTests {
subTests[i] = strings.ReplaceAll(subTests[i], " ", "_")
}
if len(subTests) != 0 {
out += "/(" + strings.Join(subTests, "|") + ")"
}
return out
}
func logStrList(log *log.Logger, title string, tests []string, toSort bool) {
var out []string
if toSort {
out = make([]string, len(tests))
copy(out[0:], tests)
sort.Strings(out)
} else {
out = tests
}
log.Println("=============") // output for debug
log.Println(title)
for i := range out {
log.Printf("-> %+v\n", out[i]) // output for debug
}
log.Println("=============")
}