1package main
2
3import (
4 "fmt"
5 "sort"
6 "strings"
7 "sync"
8)
9
10func runsuite(cfg suiteconfig, cases []testcase, categories []string) []categorysummary {
11 jobs := make(chan testcase)
12 results := make(chan caseresult, len(cases))
13
14 var wg sync.WaitGroup
15 for i := 0; i < cfg.jobs; i++ {
16 wg.Add(1)
17 go func() {
18 defer wg.Done()
19 for tc := range jobs {
20 results <- runcase(cfg, tc)
21 }
22 }()
23 }
24
25 go func() {
26 for _, tc := range cases {
27 jobs <- tc
28 }
29 close(jobs)
30 wg.Wait()
31 close(results)
32 }()
33
34 pendingcounts := make(map[string]int, len(categories))
35 totalcounts := make(map[string]int, len(categories))
36 buffered := make(map[string][]caseresult, len(categories))
37 for _, tc := range cases {
38 name := categoryname(tc)
39 pendingcounts[name]++
40 totalcounts[name]++
41 }
42
43 var orderedsummaries []categorysummary
44 nextcategory := 0
45 current := startprogress(categories, nextcategory, totalcounts, buffered, cfg)
46
47 for result := range results {
48 category := categoryname(result.Test)
49 buffered[category] = append(buffered[category], result)
50 pendingcounts[category]--
51 if current.name == category {
52 updatecategoryprogress(¤t, len(buffered[category]))
53 }
54
55 for nextcategory < len(categories) && pendingcounts[categories[nextcategory]] == 0 {
56 name := categories[nextcategory]
57 summary := summarizecategory(name, buffered[name])
58 orderedsummaries = append(orderedsummaries, summary)
59 finishcategoryprogress(current, summary)
60 nextcategory++
61 current = startprogress(categories, nextcategory, totalcounts, buffered, cfg)
62 }
63 }
64
65 return orderedsummaries
66}
67
68func summarizecategory(name string, results []caseresult) categorysummary {
69 sort.Slice(results, func(i, j int) bool {
70 return results[i].Test.Meta.Case < results[j].Test.Meta.Case
71 })
72 summary := categorysummary{Name: name, Results: results}
73 for _, result := range results {
74 if result.Passed {
75 summary.PassedCases++
76 } else {
77 summary.FailedCases++
78 }
79 }
80 return summary
81}
82
83func categorystatus(summary categorysummary) string {
84 status := fmt.Sprintf("FAILED (%d/%d passed)", summary.PassedCases, len(summary.Results))
85 if summary.FailedCases == 0 {
86 status = fmt.Sprintf("ok :) (%d passed)", summary.PassedCases)
87 }
88 return status
89}
90
91func printcategorymetadata(summary categorysummary, cfg suiteconfig) {
92 if cfg.verbose && len(summary.Results) > 0 {
93 description := strings.TrimSpace(summary.Results[0].Test.Meta.Description)
94 if description != "" {
95 fmt.Printf("\n%s\n\n", description)
96 }
97 }
98 if cfg.detail && len(summary.Results) > 0 {
99 details := strings.TrimSpace(summary.Results[0].Test.Meta.Details)
100 if details != "" {
101 fmt.Printf("%s\n\n", details)
102 }
103 }
104}
105
106func startprogress(categories []string, nextcategory int, totals map[string]int, buffered map[string][]caseresult, cfg suiteconfig) progressline {
107 if nextcategory >= len(categories) {
108 return progressline{}
109 }
110 name := categories[nextcategory]
111 summary := summarizecategory(name, buffered[name])
112 printcategorymetadata(summary, cfg)
113 prefix := name + " "
114 fmt.Print(prefix)
115 progresscols := 58 - len(prefix)
116 if progresscols < 1 {
117 progresscols = 1
118 }
119 return progressline{
120 name: name,
121 totalcases: totals[name],
122 progresscols: progresscols,
123 }
124}
125
126func updatecategoryprogress(line *progressline, completed int) {
127 if line.name == "" || line.totalcases <= 0 {
128 return
129 }
130 target := completed * line.progresscols / line.totalcases
131 if completed >= line.totalcases {
132 target = line.progresscols
133 }
134 if target <= line.printedcols {
135 return
136 }
137 fmt.Print(strings.Repeat(".", target-line.printedcols))
138 line.printedcols = target
139}
140
141func finishcategoryprogress(line progressline, summary categorysummary) {
142 updatecategoryprogress(&line, line.totalcases)
143 fmt.Printf(" %s\n", categorystatus(summary))
144}
145
146func printsummary(summaries []categorysummary) {
147 persuite := make(map[string][]categorysummary)
148 var suiteorder []string
149 for _, summary := range summaries {
150 suite := summarysuite(summary)
151 if _, ok := persuite[suite]; !ok {
152 suiteorder = append(suiteorder, suite)
153 }
154 persuite[suite] = append(persuite[suite], summary)
155 }
156
157 fmt.Println()
158 for _, suite := range suiteorder {
159 printsummaryline(suite, persuite[suite])
160 }
161 printsummaryline("total", summaries)
162 fmt.Println()
163}
164
165func summarysuite(summary categorysummary) string {
166 if summary.Name == "" {
167 return ""
168 }
169 parts := strings.SplitN(summary.Name, "/", 2)
170 if len(parts) == 2 {
171 return parts[0]
172 }
173 return ""
174}
175
176func printsummaryline(label string, summaries []categorysummary) {
177 totalcases := 0
178 totalpassed := 0
179 passedcategories := 0
180 for _, summary := range summaries {
181 totalcases += len(summary.Results)
182 totalpassed += summary.PassedCases
183 if summary.FailedCases == 0 {
184 passedcategories++
185 }
186 }
187 failedcases := totalcases - totalpassed
188 failedcategories := len(summaries) - passedcategories
189 passrate := 0.0
190 if totalcases > 0 {
191 passrate = 100.0 * float64(totalpassed) / float64(totalcases)
192 }
193
194 fmt.Printf("%s: %d/%d tests passed", label, totalpassed, totalcases)
195 if failedcases > 0 {
196 fmt.Printf(" (%d failed, %d categor", failedcases, failedcategories)
197 // grammar is important, dude.
198 if failedcategories == 1 {
199 fmt.Print("y affected")
200 } else {
201 fmt.Print("ies affected")
202 }
203 fmt.Printf(", pass rate %.1f%%) :(\n", passrate)
204 return
205 }
206
207 fmt.Printf(" in %d categor", passedcategories)
208 if passedcategories == 1 {
209 fmt.Print("y")
210 } else {
211 fmt.Print("ies")
212 }
213 fmt.Println(" ... no failures :)")
214}