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  }