github.com/markfisherdeloitte/godog@v0.7.9/fmt_events.go (about)

     1  package godog
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"io"
     7  
     8  	"github.com/DATA-DOG/godog/gherkin"
     9  )
    10  
    11  const nanoSec = 1000000
    12  const spec = "0.1.0"
    13  
    14  func init() {
    15  	Format("events", fmt.Sprintf("Produces JSON event stream, based on spec: %s.", spec), eventsFunc)
    16  }
    17  
    18  func eventsFunc(suite string, out io.Writer) Formatter {
    19  	formatter := &events{
    20  		basefmt: basefmt{
    21  			started: timeNowFunc(),
    22  			indent:  2,
    23  			out:     out,
    24  		},
    25  	}
    26  
    27  	formatter.event(&struct {
    28  		Event     string `json:"event"`
    29  		Version   string `json:"version"`
    30  		Timestamp int64  `json:"timestamp"`
    31  		Suite     string `json:"suite"`
    32  	}{
    33  		"TestRunStarted",
    34  		spec,
    35  		timeNowFunc().UnixNano() / nanoSec,
    36  		suite,
    37  	})
    38  
    39  	return formatter
    40  }
    41  
    42  type events struct {
    43  	basefmt
    44  
    45  	// currently running feature path, to be part of id.
    46  	// this is sadly not passed by gherkin nodes.
    47  	// it restricts this formatter to run only in synchronous single
    48  	// threaded execution. Unless running a copy of formatter for each feature
    49  	path         string
    50  	stat         stepType // last step status, before skipped
    51  	outlineSteps int      // number of current outline scenario steps
    52  }
    53  
    54  func (f *events) event(ev interface{}) {
    55  	data, err := json.Marshal(ev)
    56  	if err != nil {
    57  		panic(fmt.Sprintf("failed to marshal stream event: %+v - %v", ev, err))
    58  	}
    59  	fmt.Fprintln(f.out, string(data))
    60  }
    61  
    62  func (f *events) Node(n interface{}) {
    63  	f.basefmt.Node(n)
    64  
    65  	var id string
    66  	var undefined bool
    67  	switch t := n.(type) {
    68  	case *gherkin.Scenario:
    69  		id = fmt.Sprintf("%s:%d", f.path, t.Location.Line)
    70  		undefined = len(t.Steps) == 0
    71  	case *gherkin.TableRow:
    72  		id = fmt.Sprintf("%s:%d", f.path, t.Location.Line)
    73  		undefined = f.outlineSteps == 0
    74  	case *gherkin.ScenarioOutline:
    75  		f.outlineSteps = len(t.Steps)
    76  	}
    77  
    78  	if len(id) == 0 {
    79  		return
    80  	}
    81  
    82  	f.event(&struct {
    83  		Event     string `json:"event"`
    84  		Location  string `json:"location"`
    85  		Timestamp int64  `json:"timestamp"`
    86  	}{
    87  		"TestCaseStarted",
    88  		id,
    89  		timeNowFunc().UnixNano() / nanoSec,
    90  	})
    91  
    92  	if undefined {
    93  		// @TODO: is status undefined or passed? when there are no steps
    94  		// for this scenario
    95  		f.event(&struct {
    96  			Event     string `json:"event"`
    97  			Location  string `json:"location"`
    98  			Timestamp int64  `json:"timestamp"`
    99  			Status    string `json:"status"`
   100  		}{
   101  			"TestCaseFinished",
   102  			id,
   103  			timeNowFunc().UnixNano() / nanoSec,
   104  			"undefined",
   105  		})
   106  	}
   107  }
   108  
   109  func (f *events) Feature(ft *gherkin.Feature, p string, c []byte) {
   110  	f.basefmt.Feature(ft, p, c)
   111  	f.path = p
   112  	f.event(&struct {
   113  		Event    string `json:"event"`
   114  		Location string `json:"location"`
   115  		Source   string `json:"source"`
   116  	}{
   117  		"TestSource",
   118  		fmt.Sprintf("%s:%d", p, ft.Location.Line),
   119  		string(c),
   120  	})
   121  }
   122  
   123  func (f *events) Summary() {
   124  	// @TODO: determine status
   125  	status := passed
   126  	if len(f.failed) > 0 {
   127  		status = failed
   128  	} else if len(f.passed) == 0 {
   129  		if len(f.undefined) > len(f.pending) {
   130  			status = undefined
   131  		} else {
   132  			status = pending
   133  		}
   134  	}
   135  
   136  	snips := f.snippets()
   137  	if len(snips) > 0 {
   138  		snips = "You can implement step definitions for undefined steps with these snippets:\n" + snips
   139  	}
   140  
   141  	f.event(&struct {
   142  		Event     string `json:"event"`
   143  		Status    string `json:"status"`
   144  		Timestamp int64  `json:"timestamp"`
   145  		Snippets  string `json:"snippets"`
   146  		Memory    string `json:"memory"`
   147  	}{
   148  		"TestRunFinished",
   149  		status.String(),
   150  		timeNowFunc().UnixNano() / nanoSec,
   151  		snips,
   152  		"", // @TODO not sure that could be correctly implemented
   153  	})
   154  }
   155  
   156  func (f *events) step(res *stepResult) {
   157  	var errMsg string
   158  	if res.err != nil {
   159  		errMsg = res.err.Error()
   160  	}
   161  	f.event(&struct {
   162  		Event     string `json:"event"`
   163  		Location  string `json:"location"`
   164  		Timestamp int64  `json:"timestamp"`
   165  		Status    string `json:"status"`
   166  		Summary   string `json:"summary,omitempty"`
   167  	}{
   168  		"TestStepFinished",
   169  		fmt.Sprintf("%s:%d", f.path, res.step.Location.Line),
   170  		timeNowFunc().UnixNano() / nanoSec,
   171  		res.typ.String(),
   172  		errMsg,
   173  	})
   174  
   175  	// determine if test case has finished
   176  	var finished bool
   177  	var line int
   178  	switch t := f.owner.(type) {
   179  	case *gherkin.TableRow:
   180  		line = t.Location.Line
   181  		finished = f.isLastStep(res.step)
   182  	case *gherkin.Scenario:
   183  		line = t.Location.Line
   184  		finished = f.isLastStep(res.step)
   185  	}
   186  
   187  	if finished {
   188  		f.event(&struct {
   189  			Event     string `json:"event"`
   190  			Location  string `json:"location"`
   191  			Timestamp int64  `json:"timestamp"`
   192  			Status    string `json:"status"`
   193  		}{
   194  			"TestCaseFinished",
   195  			fmt.Sprintf("%s:%d", f.path, line),
   196  			timeNowFunc().UnixNano() / nanoSec,
   197  			f.stat.String(),
   198  		})
   199  	}
   200  }
   201  
   202  func (f *events) Defined(step *gherkin.Step, def *StepDef) {
   203  	if def != nil {
   204  		m := def.Expr.FindStringSubmatchIndex(step.Text)[2:]
   205  		var args [][2]int
   206  		for i := 0; i < len(m)/2; i++ {
   207  			pair := m[i : i*2+2]
   208  			var idxs [2]int
   209  			idxs[0] = pair[0]
   210  			idxs[1] = pair[1]
   211  			args = append(args, idxs)
   212  		}
   213  
   214  		if len(args) == 0 {
   215  			args = make([][2]int, 0)
   216  		}
   217  
   218  		f.event(&struct {
   219  			Event    string   `json:"event"`
   220  			Location string   `json:"location"`
   221  			DefID    string   `json:"definition_id"`
   222  			Args     [][2]int `json:"arguments"`
   223  		}{
   224  			"StepDefinitionFound",
   225  			fmt.Sprintf("%s:%d", f.path, step.Location.Line),
   226  			def.definitionID(),
   227  			args,
   228  		})
   229  	}
   230  
   231  	f.event(&struct {
   232  		Event     string `json:"event"`
   233  		Location  string `json:"location"`
   234  		Timestamp int64  `json:"timestamp"`
   235  	}{
   236  		"TestStepStarted",
   237  		fmt.Sprintf("%s:%d", f.path, step.Location.Line),
   238  		timeNowFunc().UnixNano() / nanoSec,
   239  	})
   240  }
   241  
   242  func (f *events) Passed(step *gherkin.Step, match *StepDef) {
   243  	f.basefmt.Passed(step, match)
   244  	f.stat = passed
   245  	f.step(f.passed[len(f.passed)-1])
   246  }
   247  
   248  func (f *events) Skipped(step *gherkin.Step, match *StepDef) {
   249  	f.basefmt.Skipped(step, match)
   250  	f.step(f.skipped[len(f.skipped)-1])
   251  }
   252  
   253  func (f *events) Undefined(step *gherkin.Step, match *StepDef) {
   254  	f.basefmt.Undefined(step, match)
   255  	f.stat = undefined
   256  	f.step(f.undefined[len(f.undefined)-1])
   257  }
   258  
   259  func (f *events) Failed(step *gherkin.Step, match *StepDef, err error) {
   260  	f.basefmt.Failed(step, match, err)
   261  	f.stat = failed
   262  	f.step(f.failed[len(f.failed)-1])
   263  }
   264  
   265  func (f *events) Pending(step *gherkin.Step, match *StepDef) {
   266  	f.stat = pending
   267  	f.basefmt.Pending(step, match)
   268  	f.step(f.pending[len(f.pending)-1])
   269  }