github.com/getgauge/gauge@v1.6.9/execution/rerun/rerun.go (about)

     1  /*----------------------------------------------------------------
     2   *  Copyright (c) ThoughtWorks, Inc.
     3   *  Licensed under the Apache License, Version 2.0
     4   *  See LICENSE in the project root for license information.
     5   *----------------------------------------------------------------*/
     6  
     7  package rerun
     8  
     9  import (
    10  	"encoding/json"
    11  	"errors"
    12  	"fmt"
    13  	"os"
    14  	"path/filepath"
    15  	"sync"
    16  
    17  	"github.com/getgauge/common"
    18  	"github.com/getgauge/gauge-proto/go/gauge_messages"
    19  	"github.com/getgauge/gauge/config"
    20  	"github.com/getgauge/gauge/execution/event"
    21  	"github.com/getgauge/gauge/execution/result"
    22  	"github.com/getgauge/gauge/gauge"
    23  	"github.com/getgauge/gauge/logger"
    24  	"github.com/getgauge/gauge/util"
    25  )
    26  
    27  const (
    28  	failedFile         = "failures.json"
    29  	lastRunCmdFileName = "lastRunCmd.json"
    30  )
    31  
    32  var failedMeta *failedMetadata
    33  
    34  func init() {
    35  	failedMeta = newFailedMetaData()
    36  }
    37  
    38  type failedMetadata struct {
    39  	Args           []string
    40  	failedItemsMap map[string]map[string]bool
    41  	FailedItems    []string
    42  }
    43  
    44  func (m *failedMetadata) args() []string {
    45  	return append(m.Args, m.FailedItems...)
    46  }
    47  
    48  func (m *failedMetadata) getFailedItems() []string {
    49  	failedItems := []string{}
    50  	for _, v := range m.failedItemsMap {
    51  		for k := range v {
    52  			failedItems = append(failedItems, k)
    53  		}
    54  	}
    55  	return failedItems
    56  }
    57  
    58  func (m *failedMetadata) aggregateFailedItems() {
    59  	m.FailedItems = m.getFailedItems()
    60  }
    61  
    62  func newFailedMetaData() *failedMetadata {
    63  	return &failedMetadata{Args: make([]string, 0), failedItemsMap: make(map[string]map[string]bool), FailedItems: []string{}}
    64  }
    65  
    66  func (m *failedMetadata) addFailedItem(itemName string, item string) {
    67  	if _, ok := m.failedItemsMap[itemName]; !ok {
    68  		m.failedItemsMap[itemName] = make(map[string]bool)
    69  	}
    70  	m.failedItemsMap[itemName][item] = true
    71  }
    72  
    73  // ListenFailedScenarios listens to execution events and writes the failed scenarios to JSON file
    74  func ListenFailedScenarios(wg *sync.WaitGroup, specDirs []string) {
    75  	ch := make(chan event.ExecutionEvent)
    76  	event.Register(ch, event.ScenarioEnd)
    77  	event.Register(ch, event.SpecEnd)
    78  	event.Register(ch, event.SuiteEnd)
    79  	wg.Add(1)
    80  
    81  	go func() {
    82  		for {
    83  			e := <-ch
    84  			switch e.Topic {
    85  			case event.ScenarioEnd:
    86  				prepareScenarioFailedMetadata(e.Result.(*result.ScenarioResult), e.Item.(*gauge.Scenario), e.ExecutionInfo)
    87  			case event.SpecEnd:
    88  				addFailedMetadata(e.Result, specDirs, addSpecFailedMetadata)
    89  			case event.SuiteEnd:
    90  				addFailedMetadata(e.Result, specDirs, addSuiteFailedMetadata)
    91  				failedMeta.aggregateFailedItems()
    92  				writeFailedMeta(getJSON(failedMeta))
    93  				wg.Done()
    94  			}
    95  		}
    96  	}()
    97  }
    98  
    99  func prepareScenarioFailedMetadata(res *result.ScenarioResult, sce *gauge.Scenario, executionInfo *gauge_messages.ExecutionInfo) {
   100  	if res.GetFailed() {
   101  		specPath := executionInfo.GetCurrentSpec().GetFileName()
   102  		failedScenario := util.RelPathToProjectRoot(specPath)
   103  		failedMeta.addFailedItem(specPath, fmt.Sprintf("%s:%v", failedScenario, sce.Span.Start))
   104  	}
   105  }
   106  
   107  func addSpecFailedMetadata(res result.Result, args []string) {
   108  	fileName := util.RelPathToProjectRoot(res.(*result.SpecResult).ProtoSpec.GetFileName())
   109  	delete(failedMeta.failedItemsMap, fileName)
   110  	failedMeta.addFailedItem(fileName, fileName)
   111  }
   112  
   113  func addSuiteFailedMetadata(res result.Result, args []string) {
   114  	failedMeta.failedItemsMap = make(map[string]map[string]bool)
   115  	for _, arg := range args {
   116  		path, err := filepath.Abs(arg)
   117  		path = util.RelPathToProjectRoot(path)
   118  		if err == nil {
   119  			failedMeta.addFailedItem(path, path)
   120  		}
   121  	}
   122  }
   123  
   124  func addFailedMetadata(res result.Result, args []string, add func(res result.Result, args []string)) {
   125  	if len(res.GetPostHook()) > 0 || len(res.GetPreHook()) > 0 {
   126  		add(res, args)
   127  	}
   128  }
   129  
   130  func writeFailedMeta(contents string) {
   131  	failuresFile := filepath.Join(config.ProjectRoot, common.DotGauge, failedFile)
   132  	dotGaugeDir := filepath.Join(config.ProjectRoot, common.DotGauge)
   133  	if err := os.MkdirAll(dotGaugeDir, common.NewDirectoryPermissions); err != nil {
   134  		logger.Fatalf(true, "Failed to create directory in %s. Reason: %s", dotGaugeDir, err.Error())
   135  	}
   136  	err := os.WriteFile(failuresFile, []byte(contents), common.NewFilePermissions)
   137  	if err != nil {
   138  		logger.Fatalf(true, "Failed to write to %s. Reason: %s", failuresFile, err.Error())
   139  	}
   140  }
   141  
   142  func getJSON(failedMeta *failedMetadata) string {
   143  	j, err := json.MarshalIndent(failedMeta, "", "\t")
   144  	if err != nil {
   145  		logger.Warningf(true, "Failed to save run info. Reason: %s", err.Error())
   146  	}
   147  	return string(j)
   148  }
   149  
   150  var GetLastFailedState = func() ([]string, error) {
   151  	meta := readLastFailedState()
   152  	util.SetWorkingDir(config.ProjectRoot)
   153  	if len(meta.FailedItems) == 0 {
   154  		return nil, errors.New("No failed tests found.")
   155  	}
   156  	return meta.args(), nil
   157  }
   158  
   159  func SaveState(args []string, specs []string) {
   160  	isPresent := func(values []string, value string) bool {
   161  		for _, v := range values {
   162  			if v == value {
   163  				return true
   164  			}
   165  		}
   166  		return false
   167  	}
   168  	for _, a := range args {
   169  		if !isPresent(specs, a) {
   170  			failedMeta.Args = append(failedMeta.Args, a)
   171  		}
   172  	}
   173  }
   174  
   175  func readLastFailedState() *failedMetadata {
   176  	contents, err := common.ReadFileContents(filepath.Join(config.ProjectRoot, common.DotGauge, failedFile))
   177  	if err != nil {
   178  		logger.Fatalf(true, "Failed to read last run information. Reason: %s", err.Error())
   179  	}
   180  	meta := newFailedMetaData()
   181  	if err = json.Unmarshal([]byte(contents), meta); err != nil {
   182  		logger.Fatalf(true, "Invalid last run information. Reason: %s", err.Error())
   183  	}
   184  	return meta
   185  }
   186  
   187  var ReadPrevArgs = func() []string {
   188  	contents, err := common.ReadFileContents(filepath.Join(config.ProjectRoot, common.DotGauge, lastRunCmdFileName))
   189  	if err != nil {
   190  		logger.Fatalf(true, "Failed to read previous command information. Reason: %s", err.Error())
   191  		return nil
   192  	}
   193  	var args []string
   194  	if err = json.Unmarshal([]byte(contents), &args); err != nil {
   195  		logger.Fatalf(true, "Invalid previous command information. Reason: %s", err.Error())
   196  		return nil
   197  	}
   198  	return args
   199  }
   200  
   201  var WritePrevArgs = func(cmdArgs []string) {
   202  	b, err := json.MarshalIndent(cmdArgs, "", "\t")
   203  	if err != nil {
   204  		logger.Fatalf(true, "Unable to parse last run command. Error : %v", err.Error())
   205  	}
   206  	prevCmdFile := filepath.Join(config.ProjectRoot, common.DotGauge, lastRunCmdFileName)
   207  	dotGaugeDir := filepath.Join(config.ProjectRoot, common.DotGauge)
   208  	if err = os.MkdirAll(dotGaugeDir, common.NewDirectoryPermissions); err != nil {
   209  		logger.Fatalf(true, "Failed to create directory in %s. Reason: %s", dotGaugeDir, err.Error())
   210  	}
   211  	err = os.WriteFile(prevCmdFile, b, common.NewFilePermissions)
   212  	if err != nil {
   213  		logger.Fatalf(true, "Failed to write to %s. Reason: %s", prevCmdFile, err.Error())
   214  	}
   215  }