/
canimerge.go
230 lines (205 loc) · 4.9 KB
/
canimerge.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
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
package main
import (
"encoding/json"
"flag"
"fmt"
"io/ioutil"
"log"
"net/http"
"os/exec"
"strings"
)
var detail bool
var branch string
var debug bool
func init() {
flag.BoolVar(&detail, "detail", false, "Print detailed job status.")
flag.BoolVar(&debug, "debug", false, "Print retreived json (verbose)")
}
/*
* Top level branch view structures
*/
// Job is one test suite or configuration contained within a view
type Job struct {
Name string
Url string
Color string
}
// View is the top-level branch view containing a set of Jobs.
// A-master, for example, is a View
type View struct {
/* Description string */
Jobs []Job
Url string
}
/*
* Detailed test result structures
*/
// TestReportCase is one junit test in a suite
type TestReportCase struct {
ClassName string
Duration float64
Name string
Status string
}
// TestReportSuite is testReport/childReports/result/suites
type TestReportSuite struct {
Cases []TestReportCase
Name string
}
// TestChildReportsResult is testReport/childReports/result {}
type TestChildReportsResult struct {
Duration float64
FailCount int
PassCount int
SkipCount int
Suites []TestReportSuite
}
// TestChildReports testReport/childReports {}
type TestChildReports struct {
Result TestChildReportsResult
}
// TestReport is root of http://ci/.../testReport
type TestReport struct {
FailCount int
SkipCount int
TotalCount int
// some jobs contain sub-view childreports
ChildReports []TestChildReports
// others directly contain suites
Suites []TestReportSuite
}
/*
* The munging...
*/
func main() {
flag.Parse()
// If no branch name given, default to the current branch
if flag.NArg() == 0 {
branch = resolveCurrentGitBranch()
} else {
branch = flag.Arg(0)
}
if len(branch) == 0 {
log.Print("\nUsage: canimerge [--debug] [--detail] <branchname>\n")
flag.PrintDefaults()
return
}
checkBranch("A-master", "master")
if detail {
fmt.Printf("\n")
}
if "master" != branch {
checkBranch("branch-"+branch, branch)
if detail {
fmt.Printf("\n")
}
}
}
func resolveCurrentGitBranch() string {
out, err := exec.Command("git", "rev-parse", "--symbolic-full-name",
"--abbrev-ref", "HEAD").Output()
if err != nil {
log.Fatal("Can not resolve branch name. ", err)
}
return strings.TrimSpace(string(out))
}
func checkBranch(branch, display string) {
url := "http://ci/view/" + branch + "/api/json?pretty=true"
view := decodeView(getJSON(url))
if isViewBlue(view, branch) {
fmt.Printf(">> PASS: " + display + ".\n")
} else {
fmt.Printf(">> FAIL: " + display + ".\n")
}
}
// getJSON retrieves json from url via HTTP GET
func getJSON(url string) []byte {
res, err := http.Get(url)
defer res.Body.Close()
if err != nil {
log.Fatal("Error retrieving data from "+url+". ", err)
}
body, err := ioutil.ReadAll(res.Body)
if err != nil {
log.Fatal("Error reading HTTP response from "+url+". ", err)
}
if debug {
fmt.Printf("URL: %s BODY: %s", url, body)
}
return body
}
func decodeView(body []byte) View {
var view View
if err := json.Unmarshal(body, &view); err != nil {
log.Fatal("Error unmarshalling json. ", err)
}
return view
}
func isViewBlue(view View, branch string) (retval bool) {
retval = true
for _, job := range view.Jobs {
if job.Color == "blue" {
if detail {
fmt.Printf("PASS: %s\n", job.Name)
}
} else if job.Color == "blue_anime" {
if detail {
fmt.Printf("PASS (in progress): %s\n", job.Name)
}
} else if strings.Contains(job.Color, "aborted") {
if detail {
fmt.Printf("ABORTED: %s\n", job.Name)
printBranchFailureDetails(branch, job.Name)
}
retval = false
} else if job.Color == "red_anime" {
if detail {
fmt.Printf("FAIL (in progress): %s\n", job.Name)
printBranchFailureDetails(branch, job.Name)
}
retval = false
} else {
if detail {
fmt.Printf("FAIL: %s\n", job.Name)
printBranchFailureDetails(branch, job.Name)
}
retval = false
}
}
return
}
func printBranchFailureDetails(branch string, job string) {
url := "http://ci/view/" + branch + "/job/" +
job + "/lastCompletedBuild/testReport/api/json?pretty=true"
body := getJSON(url)
var testReport TestReport
if err := json.Unmarshal(body, &testReport); err != nil {
fmt.Printf("\tNo detail results available for " + job + ".\n")
return
}
var printed bool = false
// Print results for sub-views.
for _, childReports := range testReport.ChildReports {
for _, suite := range childReports.Result.Suites {
for _, test := range suite.Cases {
if test.Status == "FAILED" {
printed = true
fmt.Printf("\t%s %s failed\n", suite.Name, test.Name)
}
}
}
}
// Print results for directly contained suites
for _, suite := range testReport.Suites {
for _, test := range suite.Cases {
if test.Status == "FAILED" {
printed = true
fmt.Printf("\t%s %s failed\n", suite.Name, test.Name)
}
}
}
if debug && !printed {
fmt.Printf("DEBUG BODY:\n%s", body)
}
}