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 }