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