/
alltest.go
204 lines (178 loc) · 5.13 KB
/
alltest.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
/*
Runs all tests in all subdirectories, showing the test stdout. If any of test fails, this
program will exit with a non-zero exit code and print a message.
*/
package main
import (
"flag"
"io/ioutil"
"log"
"os"
"os/exec"
"path"
"strings"
"github.com/araddon/gou"
)
func main() {
baseDir, err := os.Getwd()
quitIfErr(err)
skipDirFlag := flag.String("skip", "trash", "Comma-separated list of directories to skip")
buildOnlyFlag := flag.Bool("buildOnly", false, "Do \"go build\" instead of \"go test\"")
shortFlag := flag.Bool("short", false, `Run "go test" with "short" flag`)
colorFlag := flag.Bool("c", true, "Use colorized log output, colored by severity")
verboseFlag := flag.Bool("v", false, `Run "go test" with -v, also be more verbose elsewhere`)
veryVerbose := flag.Bool("vv", false, `Very Verbose, comine stdout AND stderr to display`)
raceFlag := flag.Bool("race", false, `Run "go test" with "race" flag`)
flag.Parse()
gou.SetLogger(log.New(os.Stderr, "", 0), "debug")
if *colorFlag {
gou.SetColorIfTerminal()
}
skipDirNames := strings.Split(*skipDirFlag, ",")
skipDirStats := make([]os.FileInfo, 0)
for _, skipDirName := range skipDirNames {
if skipDirName == "" {
continue
}
stat, err := os.Stat(skipDirName)
if skipDirName == "trash" && err != nil {
continue
}
if err != nil {
gou.Errorf("Couldn't stat directory to skip %s: %s\n", skipDirName, err)
}
skipDirStats = append(skipDirStats, stat)
}
conf := NewConf(skipDirStats, *buildOnlyFlag, *shortFlag, *raceFlag, *verboseFlag, *veryVerbose)
failedDirs := RunTestsRecursively(baseDir, baseDir, conf)
if len(failedDirs) > 0 {
gou.Error("\nFailed directories:")
for _, dir := range failedDirs {
gou.Errorf(" %s", dir)
}
os.Exit(1)
} else {
gou.Info("\nall tests/builds succeeded")
}
}
func RunTestsRecursively(rootDir, dirName string, conf *Conf) []string {
if strings.Contains(dirName, "trash") {
return nil
}
// Standard go tools skip files/dirs prefixed with _
if strings.HasPrefix(path.Base(dirName), "_") {
return nil
}
// Skip this directory if the user requested that we skip it
stat, err := os.Stat(dirName)
quitIfErr(err)
for _, skipDir := range conf.skipDirs {
if os.SameFile(stat, skipDir) {
gou.Debugf("skipping directory %s as requested", dirName)
return []string{}
}
}
// Skip this directory if the user entered a .alltestignore file
_, err = os.Stat(path.Join(dirName, ".alltestignore"))
if err == nil {
// If err == nil that means we found a file, thus should bail
gou.Debugf("skipping directory %s as requested due to ignore file", dirName)
return []string{}
}
infos, err := ioutil.ReadDir(dirName)
quitIfErr(err)
failures := []string{}
anyTestsInDir := false
anyGoSrcsInDir := false
for _, info := range infos {
if info.IsDir() {
// Recursively run the tests in each subdirectory
subDirName := path.Join(dirName, info.Name())
failedSubDirs := RunTestsRecursively(rootDir, subDirName, conf)
failures = append(failures, failedSubDirs...)
} else if isTestFile(info) {
anyTestsInDir = true
} else if isGoFile(info) {
anyGoSrcsInDir = true
}
}
goRunOpts := []string{"test"}
// Run "go test" in this directory if it has any tests
if anyTestsInDir && !conf.buildOnly {
if conf.short {
goRunOpts = append(goRunOpts, "-short")
}
if conf.race {
goRunOpts = append(goRunOpts, "-race")
}
if conf.veryVerbose {
goRunOpts = append(goRunOpts, "-v")
}
} else if anyGoSrcsInDir {
goRunOpts = []string{"build"}
} else {
return failures
}
err = os.Chdir(dirName)
quitIfErr(err)
var bytes []byte
if conf.veryVerbose {
bytes, err = exec.Command("go", goRunOpts...).CombinedOutput() // combined means stderr & stdout
} else {
bytes, err = exec.Command("go", goRunOpts...).Output()
}
if len(bytes) > 0 && bytes[len(bytes)-1] == '\n' {
// lets get rid of last new line at end of this
bytes = bytes[0 : len(bytes)-1]
}
thisDirPath := strings.Replace(dirName, rootDir, "", -1)
if err != nil {
if len(bytes) > 0 {
gou.Errorf(string(bytes))
}
gou.Errorf(`Failed in directory: "%s"`, thisDirPath)
failures = append(failures, thisDirPath)
} else {
if conf.verbose && len(bytes) > 0 {
gou.Debug(string(bytes))
//gou.Infof(`Success in directory: "%s"`, thisDirPath)
}
}
return failures
}
type Conf struct {
skipDirs []os.FileInfo
buildOnly bool
short bool
race bool
verbose bool
veryVerbose bool
}
func NewConf(skipDirs []os.FileInfo, buildOnly, short, race, verbose, veryVerbose bool) *Conf {
return &Conf{
skipDirs: skipDirs,
buildOnly: buildOnly,
short: short,
race: race,
verbose: verbose,
veryVerbose: veryVerbose,
}
}
func isNormalFile(stat os.FileInfo) bool {
if stat.Mode()&os.ModeType != 0 {
return false // Not a normal file (pipe, device, directory, etc.)
}
return true
}
func isTestFile(stat os.FileInfo) bool {
return isNormalFile(stat) && strings.HasSuffix(stat.Name(), "_test.go")
}
func isGoFile(stat os.FileInfo) bool {
return isNormalFile(stat) && strings.HasSuffix(stat.Name(), ".go")
}
func quitIfErr(err error) {
if err != nil {
gou.Errorf("Error: %s", err)
os.Exit(1)
}
}