github.com/billybanfield/evergreen@v0.0.0-20170525200750-eeee692790f7/plugin/builtin/attach/xunit_results_command.go (about) 1 package attach 2 3 import ( 4 "os" 5 "path/filepath" 6 7 "github.com/evergreen-ci/evergreen/model" 8 "github.com/evergreen-ci/evergreen/model/task" 9 "github.com/evergreen-ci/evergreen/plugin" 10 "github.com/evergreen-ci/evergreen/plugin/builtin/attach/xunit" 11 "github.com/mitchellh/mapstructure" 12 "github.com/mongodb/grip" 13 "github.com/mongodb/grip/slogger" 14 "github.com/pkg/errors" 15 ) 16 17 // AttachXUnitResultsCommand reads in an xml file of xunit 18 // type results and converts them to a format MCI can use 19 type AttachXUnitResultsCommand struct { 20 // File describes the relative path of the file to be sent. Supports globbing. 21 // Note that this can also be described via expansions. 22 File string `mapstructure:"file" plugin:"expand"` 23 Files []string `mapstructure:"files" plugin:"expand"` 24 } 25 26 func (c *AttachXUnitResultsCommand) Name() string { 27 return AttachXunitResultsCmd 28 } 29 30 func (c *AttachXUnitResultsCommand) Plugin() string { 31 return AttachPluginName 32 } 33 34 // ParseParams reads and validates the command parameters. This is required 35 // to satisfy the 'Command' interface 36 func (c *AttachXUnitResultsCommand) ParseParams( 37 params map[string]interface{}) error { 38 if err := mapstructure.Decode(params, c); err != nil { 39 return errors.Wrapf(err, "error decoding '%s' params", c.Name()) 40 } 41 42 if err := c.validateParams(); err != nil { 43 return errors.Wrapf(err, "error validating '%s' params", c.Name()) 44 } 45 46 return nil 47 } 48 49 // validateParams is a helper function that ensures all 50 // the fields necessary for attaching an xunit results are present 51 func (c *AttachXUnitResultsCommand) validateParams() (err error) { 52 if c.File == "" && len(c.Files) == 0 { 53 return errors.New("must specify at least one file") 54 } 55 return nil 56 } 57 58 // Expand the parameter appropriately 59 func (c *AttachXUnitResultsCommand) expandParams(conf *model.TaskConfig) error { 60 if c.File != "" { 61 c.Files = append(c.Files, c.File) 62 } 63 64 var err error 65 catcher := grip.NewCatcher() 66 67 for idx, f := range c.Files { 68 c.Files[idx], err = conf.Expansions.ExpandString(f) 69 catcher.Add(err) 70 } 71 72 return errors.Wrapf(catcher.Resolve(), "problem expanding paths") 73 } 74 75 // Execute carries out the AttachResultsCommand command - this is required 76 // to satisfy the 'Command' interface 77 func (c *AttachXUnitResultsCommand) Execute(pluginLogger plugin.Logger, 78 pluginCom plugin.PluginCommunicator, 79 taskConfig *model.TaskConfig, 80 stop chan bool) error { 81 82 if err := c.expandParams(taskConfig); err != nil { 83 return err 84 } 85 86 errChan := make(chan error) 87 go func() { 88 errChan <- c.parseAndUploadResults(taskConfig, pluginLogger, pluginCom) 89 }() 90 91 select { 92 case err := <-errChan: 93 return err 94 case <-stop: 95 pluginLogger.LogExecution(slogger.INFO, "Received signal to terminate"+ 96 " execution of attach xunit results command") 97 return nil 98 } 99 } 100 101 // getFilePaths is a helper function that returns a slice of all absolute paths 102 // which match the given file path parameters. 103 func getFilePaths(workDir string, files []string) ([]string, error) { 104 catcher := grip.NewCatcher() 105 out := []string{} 106 107 for _, fileSpec := range files { 108 paths, err := filepath.Glob(filepath.Join(workDir, fileSpec)) 109 catcher.Add(err) 110 out = append(out, paths...) 111 } 112 113 return out, errors.Wrapf(catcher.Resolve(), "%d incorrect file specifications", catcher.Len()) 114 } 115 116 func (c *AttachXUnitResultsCommand) parseAndUploadResults( 117 taskConfig *model.TaskConfig, pluginLogger plugin.Logger, 118 pluginCom plugin.PluginCommunicator) error { 119 tests := []task.TestResult{} 120 logs := []*model.TestLog{} 121 logIdxToTestIdx := []int{} 122 123 reportFilePaths, err := getFilePaths(taskConfig.WorkDir, c.Files) 124 if err != nil { 125 return err 126 } 127 128 for _, reportFileLoc := range reportFilePaths { 129 file, err := os.Open(reportFileLoc) 130 if err != nil { 131 return errors.Wrap(err, "couldn't open xunit file") 132 } 133 134 testSuites, err := xunit.ParseXMLResults(file) 135 if err != nil { 136 return errors.Wrap(err, "error parsing xunit file") 137 } 138 139 if err = file.Close(); err != nil { 140 return errors.Wrap(err, "error closing xunit file") 141 } 142 143 // go through all the tests 144 for _, suite := range testSuites { 145 for _, tc := range suite.TestCases { 146 // logs are only created when a test case does not succeed 147 test, log := tc.ToModelTestResultAndLog(taskConfig.Task) 148 if log != nil { 149 logs = append(logs, log) 150 logIdxToTestIdx = append(logIdxToTestIdx, len(tests)) 151 } 152 tests = append(tests, test) 153 } 154 } 155 } 156 157 for i, log := range logs { 158 logId, err := SendJSONLogs(pluginLogger, pluginCom, log) 159 if err != nil { 160 pluginLogger.LogTask(slogger.WARN, "Error uploading logs for %v", log.Name) 161 continue 162 } 163 tests[logIdxToTestIdx[i]].LogId = logId 164 tests[logIdxToTestIdx[i]].LineNum = 1 165 } 166 167 return SendJSONResults(taskConfig, pluginLogger, pluginCom, &task.TestResults{tests}) 168 }