-
Notifications
You must be signed in to change notification settings - Fork 115
/
results.go
168 lines (151 loc) · 4.8 KB
/
results.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
package runner
import (
"context"
"errors"
"fmt"
"io"
"log"
"path"
"path/filepath"
"strconv"
"strings"
"time"
junit "github.com/joshdk/go-junit"
"golang.org/x/net/html"
"k8s.io/apimachinery/pkg/util/wait"
restclient "k8s.io/client-go/rest"
)
var (
resultsPortStr = strconv.Itoa(resultsPort)
errNotRun = errors.New("suite has not run yet")
)
func ensurePassingXML(results map[string][]byte) (hadXML bool, err error) {
// ensure the junit xml indicates a passing job
var match bool
for filename, data := range results {
log.Println("checking", filename)
match, err = filepath.Match("junit*.xml", filename)
if err != nil {
err = fmt.Errorf("failed matching filename %s: %w", filename, err)
return
}
if match {
hadXML = true
// Use Ginkgo's JUnitTestSuite to unmarshal the JUnit XML file
suites, e := junit.Ingest(data)
if e != nil {
err = fmt.Errorf("failed parsing junit xml in %s: %w", filename, e)
return
}
for _, suite := range suites {
for _, testcase := range suite.Tests {
if (testcase.Error) != nil {
err = fmt.Errorf("at least one test failed (see junit xml for more): %s", testcase.Error)
return
}
}
}
}
}
return
}
// RetrieveResults gathers the results from the test Pod. Should only be called after tests are finished.
func (r *Runner) RetrieveResults() (map[string][]byte, error) {
results, err := r.retrieveResultsForDirectory("")
if err != nil {
return nil, fmt.Errorf("failed retrieving results: %w", err)
}
return results, err
}
// RetrieveTestResults gathers and validates the results from the test Pod. Should only be called after tests are finished. This method both fetches the results and ensures that they contain valid JUnit XML indicating that all tests passed.
func (r *Runner) RetrieveTestResults() (map[string][]byte, error) {
results, err := r.RetrieveResults()
if err != nil {
return nil, fmt.Errorf("failed retrieving results: %w", err)
}
hadXML, err := ensurePassingXML(results)
if err != nil {
return results, fmt.Errorf("failed checking results for Junit XML report: %w", err)
}
if !hadXML {
return results, fmt.Errorf("results did not contain Junit XML report")
}
return results, err
}
func (r *Runner) retrieveResultsForDirectory(directory string) (map[string][]byte, error) {
var rdr io.ReadCloser
var resp restclient.ResponseWrapper
var err error
if r.svc == nil {
return nil, errNotRun
}
// request result list
// sometimes it is possible for the service/endpoint to not be ready before the results are finished.
// we loop through here five times with a sleep statement to check.
wait.PollImmediate(5*time.Second, 1*time.Minute, func() (bool, error) {
resp = r.Kube.CoreV1().Services(r.Namespace).ProxyGet("http", r.svc.Name, resultsPortStr, directory, nil)
rdr, err = resp.Stream(context.TODO())
if err != nil {
return false, nil
}
return true, nil
})
if err != nil {
return nil, fmt.Errorf("could not retrieve result file listing: %v", err)
}
// parse list
n, err := html.Parse(rdr)
if err != nil {
return nil, fmt.Errorf("failed to parse result file listing: %v", err)
} else if err = rdr.Close(); err != nil {
return nil, err
}
// download each file
results := map[string][]byte{}
if err = r.downloadLinks(n, results, directory); err != nil {
return results, fmt.Errorf("encountered error downloading results: %v", err)
}
return results, nil
}
// downloadLinks, given an html page, will download all present links.
// This is useful when a pod is publishing an html list of artifacts.
func (r *Runner) downloadLinks(n *html.Node, results map[string][]byte, directory string) error {
if n.Type == html.ElementNode && n.Data == "a" {
for _, a := range n.Attr {
if a.Key == "href" {
if strings.HasSuffix(a.Val, "/") {
var newDirectory string
if directory != "" {
newDirectory = a.Val
} else {
newDirectory = path.Join(directory, a.Val)
}
log.Println("Downloading directory " + newDirectory)
directoryResults, err := r.retrieveResultsForDirectory(newDirectory)
if err != nil {
log.Printf("error while getting results for directory %s: %v", newDirectory, err)
continue
}
for k, v := range directoryResults {
results[k] = v
}
} else {
resp := r.Kube.CoreV1().Services(r.Namespace).ProxyGet("http", r.svc.Name, resultsPortStr, path.Join(directory, a.Val), nil)
data, err := resp.DoRaw(context.TODO())
if err != nil {
return err
}
filename := a.Val
log.Println("Downloading " + filename)
results[path.Join(directory, filename)] = data
}
}
}
}
for c := n.FirstChild; c != nil; c = c.NextSibling {
if err := r.downloadLinks(c, results, directory); err != nil {
log.Printf("error while getting results for %s: %v", directory, err)
}
}
return nil
}