github.com/getgauge/gauge@v1.6.9/reporter/reporter.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  	"fmt"
    11  	"io"
    12  	"os"
    13  	"runtime/debug"
    14  
    15  	"sync"
    16  
    17  	"github.com/getgauge/gauge-proto/go/gauge_messages"
    18  	"github.com/getgauge/gauge/execution/event"
    19  	"github.com/getgauge/gauge/execution/result"
    20  	"github.com/getgauge/gauge/formatter"
    21  	"github.com/getgauge/gauge/gauge"
    22  	"github.com/getgauge/gauge/logger"
    23  )
    24  
    25  // IsParallel represents console reporting format based on simple/parallel execution
    26  var IsParallel bool
    27  
    28  // NumberOfExecutionStreams indicates the total number of parallel execution streams
    29  var NumberOfExecutionStreams int
    30  
    31  // SimpleConsoleOutput represents if coloring should be removed from the Console output
    32  var SimpleConsoleOutput bool
    33  
    34  // Verbose represents level of console Reporting. If true its at step level, else at scenario level.
    35  var Verbose bool
    36  
    37  // MachineReadable represents if output should be in JSON format.
    38  var MachineReadable bool
    39  
    40  const newline = "\n"
    41  
    42  // Reporter reports the progress of spec execution. It reports
    43  // 1. Which spec / scenarion / step (if verbose) is currently executing.
    44  // 2. Status (pass/fail) of the spec / scenario / step (if verbose) once its executed.
    45  type Reporter interface {
    46  	SuiteStart()
    47  	SpecStart(*gauge.Specification, result.Result)
    48  	SpecEnd(*gauge.Specification, result.Result)
    49  	ScenarioStart(*gauge.Scenario, *gauge_messages.ExecutionInfo, result.Result)
    50  	ScenarioEnd(*gauge.Scenario, result.Result, *gauge_messages.ExecutionInfo)
    51  	StepStart(string)
    52  	StepEnd(gauge.Step, result.Result, *gauge_messages.ExecutionInfo)
    53  	ConceptStart(string)
    54  	ConceptEnd(result.Result)
    55  	DataTable(string)
    56  	SuiteEnd(result.Result)
    57  
    58  	Errorf(string, ...interface{})
    59  
    60  	io.Writer
    61  }
    62  
    63  var currentReporter Reporter
    64  
    65  func reporter(e event.ExecutionEvent) Reporter {
    66  	if IsParallel {
    67  		return ParallelReporter(e.Stream)
    68  	}
    69  	return Current()
    70  }
    71  
    72  // Current returns the current instance of Reporter, if present. Else, it returns a new Reporter.
    73  func Current() Reporter {
    74  	if currentReporter == nil {
    75  		if MachineReadable {
    76  			currentReporter = newJSONConsole(os.Stdout, IsParallel, 0)
    77  		} else if SimpleConsoleOutput {
    78  			currentReporter = newSimpleConsole(os.Stdout)
    79  		} else if Verbose {
    80  			currentReporter = newVerboseColoredConsole(os.Stdout)
    81  		} else {
    82  			currentReporter = newColoredConsole(os.Stdout)
    83  		}
    84  	}
    85  	return currentReporter
    86  }
    87  
    88  type parallelReportWriter struct {
    89  	nRunner int
    90  }
    91  
    92  func (p *parallelReportWriter) Write(b []byte) (int, error) {
    93  	return fmt.Printf("[runner: %d] %s", p.nRunner, string(b))
    94  }
    95  
    96  // ParallelReporter returns the instance of parallel console reporter
    97  func ParallelReporter(n int) Reporter {
    98  	if r, ok := parallelReporters[n]; ok {
    99  		return r
   100  	}
   101  	return Current()
   102  }
   103  
   104  var parallelReporters map[int]Reporter
   105  
   106  func initParallelReporters() {
   107  	parallelReporters = make(map[int]Reporter, NumberOfExecutionStreams)
   108  	for i := 1; i <= NumberOfExecutionStreams; i++ {
   109  		if MachineReadable {
   110  			parallelReporters[i] = newJSONConsole(os.Stdout, true, i)
   111  		} else {
   112  			writer := &parallelReportWriter{nRunner: i}
   113  			parallelReporters[i] = newSimpleConsole(writer)
   114  		}
   115  	}
   116  }
   117  
   118  // ListenExecutionEvents listens to all execution events for reporting on console
   119  func ListenExecutionEvents(wg *sync.WaitGroup) {
   120  	ch := make(chan event.ExecutionEvent)
   121  	initParallelReporters()
   122  	event.Register(ch, event.SuiteStart, event.SpecStart, event.SpecEnd, event.ScenarioStart, event.ScenarioEnd, event.StepStart, event.StepEnd, event.ConceptStart, event.ConceptEnd, event.SuiteEnd)
   123  	var r Reporter
   124  	wg.Add(1)
   125  
   126  	go func() {
   127  		defer recoverPanic()
   128  		for {
   129  			e := <-ch
   130  			r = reporter(e)
   131  			switch e.Topic {
   132  			case event.SuiteStart:
   133  				r.SuiteStart()
   134  			case event.SpecStart:
   135  				r.SpecStart(e.Item.(*gauge.Specification), e.Result)
   136  			case event.ScenarioStart:
   137  				skipped := e.Result.(*result.ScenarioResult).ProtoScenario.GetExecutionStatus() == gauge_messages.ExecutionStatus_SKIPPED
   138  				sce := e.Item.(*gauge.Scenario)
   139  				// if it is datatable driven execution
   140  				if !skipped {
   141  					if sce.SpecDataTableRow.GetRowCount() != 0 {
   142  						r.DataTable(formatter.FormatTable(&sce.SpecDataTableRow))
   143  					}
   144  					if sce.ScenarioDataTableRow.GetRowCount() != 0 {
   145  						r.DataTable(formatter.FormatTable(&sce.ScenarioDataTableRow))
   146  					}
   147  				}
   148  				r.ScenarioStart(sce, e.ExecutionInfo, e.Result)
   149  			case event.ConceptStart:
   150  				r.ConceptStart(formatter.FormatStep(e.Item.(*gauge.Step)))
   151  			case event.StepStart:
   152  				r.StepStart(formatter.FormatStepWithResolvedArgs(e.Item.(*gauge.Step)))
   153  			case event.StepEnd:
   154  				r.StepEnd(e.Item.(gauge.Step), e.Result, e.ExecutionInfo)
   155  			case event.ConceptEnd:
   156  				r.ConceptEnd(e.Result)
   157  			case event.ScenarioEnd:
   158  				r.ScenarioEnd(e.Item.(*gauge.Scenario), e.Result, e.ExecutionInfo)
   159  			case event.SpecEnd:
   160  				r.SpecEnd(e.Item.(*gauge.Specification), e.Result)
   161  			case event.SuiteEnd:
   162  				r.SuiteEnd(e.Result)
   163  				wg.Done()
   164  			}
   165  		}
   166  	}()
   167  }
   168  
   169  func recoverPanic() {
   170  	if r := recover(); r != nil {
   171  		logger.Fatalf(true, "%v\n%s", r, string(debug.Stack()))
   172  	}
   173  }