forked from evergreen-ci/evergreen
-
Notifications
You must be signed in to change notification settings - Fork 0
/
results_xunit.go
187 lines (156 loc) · 5 KB
/
results_xunit.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
package command
import (
"context"
"fmt"
"os"
"path/filepath"
"github.com/evergreen-ci/evergreen/model"
"github.com/evergreen-ci/evergreen/model/task"
"github.com/evergreen-ci/evergreen/rest/client"
"github.com/mitchellh/mapstructure"
"github.com/mongodb/grip"
"github.com/pkg/errors"
)
// xunitResults reads in an xml file of xunit
// type results and converts them to a format MCI can use
type xunitResults struct {
// File describes the relative path of the file to be sent. Supports globbing.
// Note that this can also be described via expansions.
File string `mapstructure:"file" plugin:"expand"`
Files []string `mapstructure:"files" plugin:"expand"`
base
}
func xunitResultsFactory() Command { return &xunitResults{} }
func (c *xunitResults) Name() string { return "attach.xunit_results" }
// ParseParams reads and validates the command parameters. This is required
// to satisfy the 'Command' interface
func (c *xunitResults) ParseParams(params map[string]interface{}) error {
if err := mapstructure.Decode(params, c); err != nil {
return errors.Wrapf(err, "error decoding '%s' params", c.Name())
}
if c.File == "" && len(c.Files) == 0 {
return errors.New("must specify at least one file")
}
return nil
}
// Expand the parameter appropriately
func (c *xunitResults) expandParams(conf *model.TaskConfig) error {
if c.File != "" {
c.Files = append(c.Files, c.File)
}
catcher := grip.NewBasicCatcher()
var err error
for idx, f := range c.Files {
c.Files[idx], err = conf.Expansions.ExpandString(f)
catcher.Add(err)
}
return errors.Wrapf(catcher.Resolve(), "problem expanding paths")
}
// Execute carries out the AttachResultsCommand command - this is required
// to satisfy the 'Command' interface
func (c *xunitResults) Execute(ctx context.Context,
comm client.Communicator, logger client.LoggerProducer, conf *model.TaskConfig) error {
if err := c.expandParams(conf); err != nil {
return err
}
errChan := make(chan error)
go func() {
errChan <- c.parseAndUploadResults(ctx, conf, logger, comm)
}()
select {
case err := <-errChan:
return errors.WithStack(err)
case <-ctx.Done():
logger.Execution().Info("Received signal to terminate execution of attach xunit results command")
return nil
}
}
// getFilePaths is a helper function that returns a slice of all absolute paths
// which match the given file path parameters.
func getFilePaths(workDir string, files []string) ([]string, error) {
catcher := grip.NewBasicCatcher()
out := []string{}
for _, fileSpec := range files {
paths, err := filepath.Glob(filepath.Join(workDir, fileSpec))
catcher.Add(err)
out = append(out, paths...)
}
if catcher.HasErrors() {
return nil, errors.Wrapf(catcher.Resolve(), "%d incorrect file specifications", catcher.Len())
}
return out, nil
}
func (c *xunitResults) parseAndUploadResults(ctx context.Context, conf *model.TaskConfig,
logger client.LoggerProducer, comm client.Communicator) error {
tests := []task.TestResult{}
logs := []*model.TestLog{}
logIdxToTestIdx := []int{}
reportFilePaths, err := getFilePaths(conf.WorkDir, c.Files)
if err != nil {
return err
}
var (
file *os.File
testSuites []testSuite
)
for _, reportFileLoc := range reportFilePaths {
if ctx.Err() != nil {
return errors.New("operation canceled")
}
file, err = os.Open(reportFileLoc)
if err != nil {
return errors.Wrap(err, "couldn't open xunit file")
}
testSuites, err = parseXMLResults(file)
if err != nil {
return errors.Wrap(err, "error parsing xunit file")
}
if err = file.Close(); err != nil {
return errors.Wrap(err, "error closing xunit file")
}
// go through all the tests
for idx, suite := range testSuites {
if len(suite.TestCases) == 0 && suite.Error != nil {
// if no test cases but an error, generate a default test case
tc := testCase{
Name: suite.Name,
Time: suite.Time,
Error: suite.Error,
}
if tc.Name == "" {
tc.Name = fmt.Sprintf("Unamed Test-%d", idx)
}
suite.TestCases = append(suite.TestCases, tc)
}
for _, tc := range suite.TestCases {
// logs are only created when a test case does not succeed
test, log := tc.toModelTestResultAndLog(conf.Task)
if log != nil {
if suite.SysOut != "" {
log.Lines = append(log.Lines, "system-out:", suite.SysOut)
}
if suite.SysErr != "" {
log.Lines = append(log.Lines, "system-err:", suite.SysErr)
}
logs = append(logs, log)
logIdxToTestIdx = append(logIdxToTestIdx, len(tests))
}
tests = append(tests, test)
}
}
}
td := client.TaskData{ID: conf.Task.Id, Secret: conf.Task.Secret}
for i, log := range logs {
if ctx.Err() != nil {
return errors.New("operation canceled")
}
logId, err := sendJSONLogs(ctx, logger, comm, td, log)
if err != nil {
logger.Task().Warningf("problem uploading logs for %s", log.Name)
continue
}
tests[logIdxToTestIdx[i]].LogId = logId
tests[logIdxToTestIdx[i]].LineNum = 1
}
return sendJSONResults(ctx, conf, logger, comm, &task.LocalTestResults{tests})
}