main shinobi / tests / runner / suite.go
  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(&current, 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}