github.com/getgauge/gauge@v1.6.9/reporter/jsonConsole.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 reporter 8 9 import ( 10 "encoding/json" 11 "fmt" 12 "io" 13 "strconv" 14 "strings" 15 "sync" 16 17 gm "github.com/getgauge/gauge-proto/go/gauge_messages" 18 "github.com/getgauge/gauge/execution/result" 19 "github.com/getgauge/gauge/formatter" 20 "github.com/getgauge/gauge/gauge" 21 "github.com/getgauge/gauge/logger" 22 "github.com/getgauge/gauge/util" 23 ) 24 25 type eventType string 26 type status string 27 28 const ( 29 suiteStart eventType = "suiteStart" 30 specStart eventType = "specStart" 31 scenarioStart eventType = "scenarioStart" 32 scenarioEnd eventType = "scenarioEnd" 33 specEnd eventType = "specEnd" 34 suiteEnd eventType = "suiteEnd" 35 pass status = "pass" 36 fail status = "fail" 37 skip status = "skip" 38 ) 39 40 type jsonConsole struct { 41 *sync.Mutex 42 writer io.Writer 43 isParallel bool 44 stream int 45 stepCache map[*gm.ScenarioInfo][]*stepInfo 46 } 47 48 type stepInfo struct { 49 step *gauge.Step 50 protoStep *gm.ProtoStep 51 } 52 53 type executionEvent struct { 54 EventType eventType `json:"type"` 55 ID string `json:"id,omitempty"` 56 ParentID string `json:"parentId,omitempty"` 57 Name string `json:"name,omitempty"` 58 Filename string `json:"filename,omitempty"` 59 Line int `json:"line,omitempty"` 60 Stream int `json:"stream,omitempty"` 61 Res *executionResult `json:"result,omitempty"` 62 } 63 64 type executionResult struct { 65 Status status `json:"status,omitempty"` 66 Time int64 `json:"time"` 67 Stdout string `json:"out,omitempty"` 68 Errors []executionError `json:"errors,omitempty"` 69 BeforeHookFailure *executionError `json:"beforeHookFailure,omitempty"` 70 AfterHookFailure *executionError `json:"afterHookFailure,omitempty"` 71 Table *tableInfo `json:"table,omitempty"` 72 } 73 74 type tableInfo struct { 75 Text string `json:"text"` 76 Row int `json:"rowIndex"` 77 } 78 79 type executionError struct { 80 Text string `json:"text"` 81 Filename string `json:"filename"` 82 Message string `json:"message"` 83 LineNo string `json:"lineNo"` 84 StackTrace string `json:"stackTrace"` 85 } 86 87 func newJSONConsole(out io.Writer, isParallel bool, stream int) *jsonConsole { 88 return &jsonConsole{Mutex: &sync.Mutex{}, writer: out, isParallel: isParallel, stream: stream, stepCache: make(map[*gm.ScenarioInfo][]*stepInfo)} 89 } 90 91 func (c *jsonConsole) SuiteStart() { 92 c.Lock() 93 defer c.Unlock() 94 c.write(executionEvent{EventType: suiteStart, Stream: c.stream}) 95 } 96 97 func (c *jsonConsole) SuiteEnd(res result.Result) { 98 c.Lock() 99 defer c.Unlock() 100 sRes := res.(*result.SuiteResult) 101 c.write(executionEvent{ 102 EventType: suiteEnd, 103 Stream: c.stream, 104 Res: &executionResult{ 105 Status: getStatus(sRes.IsFailed, false), 106 BeforeHookFailure: getHookFailure(res.GetPreHook(), "Before Suite"), 107 AfterHookFailure: getHookFailure(res.GetPostHook(), "After Suite"), 108 }, 109 }) 110 } 111 112 func (c *jsonConsole) SpecStart(spec *gauge.Specification, res result.Result) { 113 c.Lock() 114 defer c.Unlock() 115 addRow := c.isParallel && spec.DataTable.IsInitialized() 116 c.write(executionEvent{ 117 EventType: specStart, 118 ID: getIDWithRow(spec.FileName, spec.Scenarios, addRow), 119 Name: spec.Heading.Value, 120 Filename: spec.FileName, 121 Line: spec.Heading.LineNo, 122 Stream: c.stream, 123 }) 124 } 125 126 func (c *jsonConsole) SpecEnd(spec *gauge.Specification, res result.Result) { 127 c.Lock() 128 defer c.Unlock() 129 protoSpec := res.Item().(*gm.ProtoSpec) 130 sRes := res.(*result.SpecResult) 131 addRow := c.isParallel && spec.DataTable.IsInitialized() 132 e := executionEvent{ 133 EventType: specEnd, 134 ID: getIDWithRow(spec.FileName, spec.Scenarios, addRow), 135 Name: protoSpec.GetSpecHeading(), 136 Filename: spec.FileName, 137 Line: spec.Heading.LineNo, 138 Stream: c.stream, 139 Res: &executionResult{ 140 Status: getStatus(sRes.GetFailed(), sRes.Skipped), 141 BeforeHookFailure: getHookFailure(res.GetPreHook(), "Before Specification"), 142 AfterHookFailure: getHookFailure(res.GetPostHook(), "After Specification"), 143 }, 144 } 145 c.write(e) 146 } 147 148 func (c *jsonConsole) ScenarioStart(scenario *gauge.Scenario, i *gm.ExecutionInfo, res result.Result) { 149 c.Lock() 150 defer c.Unlock() 151 addRow := c.isParallel && scenario.SpecDataTableRow.IsInitialized() 152 parentID := getIDWithRow(i.CurrentSpec.FileName, []*gauge.Scenario{scenario}, addRow) 153 e := executionEvent{ 154 EventType: scenarioStart, 155 ID: parentID + ":" + strconv.Itoa(scenario.Span.Start), 156 ParentID: parentID, 157 Filename: i.CurrentSpec.FileName, 158 Line: scenario.Heading.LineNo, 159 Name: scenario.Heading.Value, 160 Stream: c.stream, 161 Res: &executionResult{Table: getTable(scenario)}, 162 } 163 c.write(e) 164 } 165 166 func (c *jsonConsole) ScenarioEnd(scenario *gauge.Scenario, res result.Result, i *gm.ExecutionInfo) { 167 c.Lock() 168 defer c.Unlock() 169 addRow := c.isParallel && scenario.SpecDataTableRow.IsInitialized() 170 parentID := getIDWithRow(i.CurrentSpec.FileName, []*gauge.Scenario{scenario}, addRow) 171 e := executionEvent{ 172 EventType: scenarioEnd, 173 ID: parentID + ":" + strconv.Itoa(scenario.Span.Start), 174 ParentID: parentID, 175 Filename: i.CurrentSpec.FileName, 176 Line: scenario.Heading.LineNo, 177 Name: scenario.Heading.Value, 178 Stream: c.stream, 179 Res: &executionResult{ 180 Status: getScenarioStatus(res.(*result.ScenarioResult)), 181 Time: res.ExecTime(), 182 Errors: getErrors(c.stepCache, getAllStepsFromScenario(res.(*result.ScenarioResult).ProtoScenario), i.CurrentSpec.FileName, i), 183 BeforeHookFailure: getHookFailure(res.GetPreHook(), "Before Scenario"), 184 AfterHookFailure: getHookFailure(res.GetPostHook(), "After Scenario"), 185 Table: getTable(scenario), 186 }, 187 } 188 c.write(e) 189 } 190 191 func getAllStepsFromScenario(scenario *gm.ProtoScenario) []*gm.ProtoItem { 192 return append(scenario.GetContexts(), append(scenario.GetScenarioItems(), scenario.GetTearDownSteps()...)...) 193 } 194 195 func (c *jsonConsole) StepStart(stepText string) { 196 } 197 198 func (c *jsonConsole) StepEnd(step gauge.Step, res result.Result, execInfo *gm.ExecutionInfo) { 199 si := &stepInfo{step: &step, protoStep: res.(*result.StepResult).Item().(*gm.ProtoStep)} 200 c.stepCache[execInfo.CurrentScenario] = append(c.stepCache[execInfo.CurrentScenario], si) 201 } 202 203 func (c *jsonConsole) ConceptStart(conceptHeading string) { 204 } 205 206 func (c *jsonConsole) ConceptEnd(res result.Result) { 207 } 208 209 func (c *jsonConsole) DataTable(table string) { 210 } 211 212 func (c *jsonConsole) Errorf(err string, args ...interface{}) { 213 c.Lock() 214 defer c.Unlock() 215 } 216 217 func (c *jsonConsole) Write(b []byte) (int, error) { 218 c.Lock() 219 defer c.Unlock() 220 s := strings.Split(string(b), "\n") 221 for _, m := range s { 222 outMessage := &logger.OutMessage{MessageType: "out", Message: strings.Trim(m, "\n ")} 223 t, err := outMessage.ToJSON() 224 if err != nil { 225 return 0, err 226 } 227 fmt.Fprintf(c.writer, "%s\n", string(t)) 228 } 229 return len(b), nil 230 } 231 232 func (c *jsonConsole) write(e executionEvent) { 233 b, _ := json.Marshal(e) 234 fmt.Fprint(c.writer, string(b)+newline) 235 } 236 237 func getIDWithRow(name string, scenarios []*gauge.Scenario, isDataTable bool) string { 238 if !isDataTable || len(scenarios) < 1 { 239 return name 240 } 241 return name + ":" + strconv.Itoa(scenarios[0].SpecDataTableRowIndex) 242 } 243 244 func getScenarioStatus(result *result.ScenarioResult) status { 245 return getStatus(result.ProtoScenario.GetExecutionStatus() == gm.ExecutionStatus_FAILED, 246 result.ProtoScenario.GetExecutionStatus() == gm.ExecutionStatus_SKIPPED) 247 } 248 249 func getStatus(failed, skipped bool) status { 250 if failed { 251 return fail 252 } 253 if skipped { 254 return skip 255 } 256 return pass 257 } 258 259 func getErrors(stepCache map[*gm.ScenarioInfo][]*stepInfo, items []*gm.ProtoItem, id string, execInfo *gm.ExecutionInfo) (errors []executionError) { 260 for _, item := range items { 261 executionResult := item.GetStep().GetStepExecutionResult() 262 res := executionResult.GetExecutionResult() 263 switch item.GetItemType() { 264 case gm.ProtoItem_Step: 265 filename := util.RelPathToProjectRoot(id) 266 if err := executionResult.GetPreHookFailure(); err != nil { 267 errors = append(errors, *getHookFailure([]*gm.ProtoHookFailure{err}, "BeforeStep hook for step: "+item.Step.ActualText)) 268 } else { 269 if executionResult.GetSkipped() { 270 errors = append(errors, executionError{ 271 Text: item.Step.ActualText, 272 Filename: getFileName(filename, stepCache, item.GetStep(), execInfo), 273 Message: executionResult.SkippedReason, 274 }) 275 } else if res.GetFailed() { 276 errors = append(errors, executionError{ 277 Text: item.Step.ActualText, 278 Filename: getFileName(filename, stepCache, item.GetStep(), execInfo), 279 LineNo: getLineNo(stepCache, item.GetStep(), execInfo), 280 StackTrace: res.StackTrace, 281 Message: res.ErrorMessage, 282 }) 283 } 284 } 285 if err := executionResult.GetPostHookFailure(); err != nil { 286 errors = append(errors, *getHookFailure([]*gm.ProtoHookFailure{err}, "AfterStep hook for step: "+item.Step.ActualText)) 287 } 288 case gm.ProtoItem_Concept: 289 errors = append(errors, getErrors(stepCache, item.GetConcept().GetSteps(), id, execInfo)...) 290 } 291 } 292 return 293 } 294 295 func getFileName(file string, stepCache map[*gm.ScenarioInfo][]*stepInfo, step *gm.ProtoStep, info *gm.ExecutionInfo) string { 296 for _, si := range stepCache[info.CurrentScenario] { 297 if si.protoStep == step { 298 return si.step.FileName 299 } 300 } 301 return file 302 } 303 304 func getLineNo(stepCache map[*gm.ScenarioInfo][]*stepInfo, step *gm.ProtoStep, info *gm.ExecutionInfo) string { 305 for _, si := range stepCache[info.CurrentScenario] { 306 if si.protoStep == step { 307 return strconv.Itoa(si.step.LineNo) 308 } 309 } 310 return "" 311 } 312 313 func getTable(scenario *gauge.Scenario) *tableInfo { 314 if scenario.SpecDataTableRow.IsInitialized() { 315 return &tableInfo{ 316 Text: formatter.FormatTable(&scenario.SpecDataTableRow), 317 Row: scenario.SpecDataTableRowIndex, 318 } 319 } 320 return nil 321 } 322 323 func getHookFailure(hookFailure []*gm.ProtoHookFailure, text string) *executionError { 324 if len(hookFailure) > 0 { 325 return &executionError{ 326 Text: text, 327 Message: hookFailure[0].ErrorMessage, 328 StackTrace: hookFailure[0].StackTrace, 329 } 330 } 331 return nil 332 }