Skip to content

Commit

Permalink
Unit testing phase3 (#198)
Browse files Browse the repository at this point in the history
* Fixes for unit testing phase 3 and flow debugger
  • Loading branch information
abhide-tibco authored Aug 9, 2024
1 parent e82eb38 commit 8ea4c88
Show file tree
Hide file tree
Showing 4 changed files with 153 additions and 2 deletions.
31 changes: 29 additions & 2 deletions action.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"errors"
"fmt"
"github.com/project-flogo/core/data/expression/script/gocc/ast"
"strings"
"time"

Expand Down Expand Up @@ -397,13 +398,20 @@ func (fa *FlowAction) Run(ctx context.Context, inputs map[string]interface{}, ha
inst.SetValue(k, v)
}

fa.applyAssertionInterceptor(inst)
fa.applyAssertionInterceptor(inst, flowsupport.AssertionActivity)

handler.HandleResult(returnData, err)
} else if inst.Status() == model.FlowStatusFailed {
hasFlowExceptionAssert := fa.applyAssertionInterceptor(inst, flowsupport.AssertionException)

if inst.TracingContext() != nil {
_ = trace.GetTracer().FinishTrace(inst.TracingContext(), inst.GetError())
}
// If we are in unit testing mode and flow has assertion exception then we dont make the flow fail
// The testcase will be passed or failed based on the assertions executed.
if hasFlowExceptionAssert {
handler.HandleResult(nil, nil)
}
handler.HandleResult(nil, inst.GetError())
}

Expand All @@ -423,14 +431,22 @@ func (fa *FlowAction) Run(ctx context.Context, inputs map[string]interface{}, ha
return nil
}

func (fa *FlowAction) applyAssertionInterceptor(inst *instance.IndependentInstance) {
func (fa *FlowAction) applyAssertionInterceptor(inst *instance.IndependentInstance, assertType int) bool {

if !inst.HasInterceptor() {
return false
}
hasFlowExceptionAssertion := false
if inst.GetInterceptor() != nil {
interceptor := inst.GetInterceptor().GetTaskInterceptor(inst.Instance.Name() + "-_flowOutput")
if interceptor != nil {
interceptor.Result = flowsupport.Pass
ef := expression.NewFactory(definition.GetDataResolver())
for id, assertion := range interceptor.Assertions {
if interceptor.Type != assertType {
interceptor.Assertions[id].Result = flowsupport.AssertionNotExecuted
continue
}
if assertion.Expression == "" {
interceptor.Assertions[id].Message = "Empty expression"
interceptor.Assertions[id].Result = flowsupport.NotExecuted
Expand All @@ -448,6 +464,11 @@ func (fa *FlowAction) applyAssertionInterceptor(inst *instance.IndependentInstan
interceptor.Assertions[id].Result = flowsupport.Fail
interceptor.Assertions[id].Message = "Failed to evaluate expression"
} else {
exp, ok := expr.(ast.ExprEvalResult)
var resultData ast.ExprEvalData
if ok {
resultData = exp.Detail()
}
res, _ := coerce.ToBool(result)
if res {
interceptor.Assertions[id].Result = flowsupport.Pass
Expand All @@ -456,10 +477,16 @@ func (fa *FlowAction) applyAssertionInterceptor(inst *instance.IndependentInstan
interceptor.Assertions[id].Result = flowsupport.Fail
interceptor.Assertions[id].Message = "Comparison failure"
}
interceptor.Assertions[id].EvalResult = resultData
if assertType == flowsupport.AssertionException {
hasFlowExceptionAssertion = true
}

}

}
}
}
return hasFlowExceptionAssertion

}
53 changes: 53 additions & 0 deletions instance/ind_instance.go
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,10 @@ func (inst *IndependentInstance) ApplyInterceptor(interceptor *flowsupport.Inter
}
}

func (inst *IndependentInstance) HasInterceptor() bool {
return inst.interceptor != nil
}

func (inst *IndependentInstance) GetInterceptor() *flowsupport.Interceptor {
return inst.interceptor
}
Expand Down Expand Up @@ -415,6 +419,7 @@ func (inst *IndependentInstance) handleTaskDone(taskBehavior model.TaskBehavior,
if taskInst.traceContext != nil {
_ = trace.GetTracer().FinishTrace(taskInst.traceContext, nil)
}
inst.addActivityToCoverage(taskInst, nil)
}

if err != nil {
Expand Down Expand Up @@ -530,6 +535,7 @@ func (inst *IndependentInstance) handleTaskError(taskBehavior model.TaskBehavior
// Set task status to failed for subflow activity
taskInst.SetStatus(model.TaskStatusFailed)

inst.addActivityToCoverage(taskInst, err)
handled, taskEntries := taskBehavior.Error(taskInst, err)

containerInst := taskInst.flowInst
Expand Down Expand Up @@ -673,6 +679,53 @@ func (inst *IndependentInstance) enterTasks(activeInst *Instance, taskEntries []
return nil
}

func (inst *IndependentInstance) addActivityToCoverage(taskInst *TaskInst, err error) {

if !inst.HasInterceptor() {
return
}
var errorObj map[string]interface{}
if err != nil {
errorObj = taskInst.getErrorObject(err)
}

var coverage flowsupport.ActivityCoverage
if inst.GetInterceptor().CollectIO {
outputs := taskInst.outputs
if outputs == nil {
if inst.returnData != nil {
outputs = inst.returnData
}
}
coverage = flowsupport.ActivityCoverage{
ActivityName: taskInst.taskID,
LinkFrom: inst.getLinks(taskInst.GetFromLinkInstances()),
LinkTo: inst.getLinks(taskInst.GetToLinkInstances()),
Inputs: taskInst.inputs,
Outputs: outputs,
Error: errorObj,
FlowName: taskInst.flowInst.Name(),
IsMainFlow: !inst.isHandlingError,
}
} else {
coverage = flowsupport.ActivityCoverage{
ActivityName: taskInst.taskID,
FlowName: taskInst.flowInst.Name(),
IsMainFlow: !inst.isHandlingError,
}
}

inst.interceptor.AddToActivityCoverage(coverage)
}

func (inst *IndependentInstance) getLinks(instances []model.LinkInstance) []string {
var names []string
for _, linkInst := range instances {
names = append(names, linkInst.Link().Label())
}
return names
}

//////////////////////////////////////////////////////////////////

// WorkItem describes an item of work (event for a Task) that should be executed on Step
Expand Down
35 changes: 35 additions & 0 deletions instance/linkinst.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package instance
import (
"github.com/project-flogo/flow/definition"
"github.com/project-flogo/flow/model"
"github.com/project-flogo/flow/support"
)

// LinkInst represents data associated with an instance of a Link
Expand Down Expand Up @@ -34,10 +35,44 @@ func (li *LinkInst) Status() model.LinkStatus {
// SetStatus sets the current state indicator for the LinkInst
func (li *LinkInst) SetStatus(status model.LinkStatus) {
li.status = status
if status == model.LinkStatusTrue {
li.addLinkToCoverage()
}
li.flowInst.master.changeTracker.LinkUpdated(li)
//ld.flowInst.master.ChangeTracker.trackLinkData(ld.flowInst.subFlowId, &LinkInstChange{ChgType: CtUpd, ID: ld.link.ID(), LinkInst: ld})
}

func (li *LinkInst) addLinkToCoverage() {

if !li.flowInst.master.HasInterceptor() {
return
}

t := li.Link().Type()
linkType := ""
switch t {
case definition.LtExpression:
linkType = "Expression"
case definition.LtLabel:
linkType = "Label"
case definition.LtError:
linkType = "Error"
case definition.LtExprOtherwise:
linkType = "Otherwise"
}
coverage := support.TransitionCoverage{

TransitionName: li.Link().Label(),
TransitionType: linkType,
TransitionExpression: li.Link().Value(),
TransitionFrom: li.Link().FromTask().ID(),
TransitionTo: li.Link().ToTask().ID(),
FlowName: li.flowInst.Name(),
IsMainFlow: !li.flowInst.isHandlingError,
}
li.flowInst.master.interceptor.AddToLinkCoverage(coverage)
}

// Link returns the Link associated with ld context
func (li *LinkInst) Link() *definition.Link {
return li.link
Expand Down
36 changes: 36 additions & 0 deletions support/interceptor.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ type Interceptor struct {
TaskInterceptors []*TaskInterceptor `json:"tasks"`

taskInterceptorMap map[string]*TaskInterceptor
Coverage *Coverage `json:"coverage"`
CollectIO bool
}

// Init initializes the FlowInterceptor, usually called after deserialization
Expand All @@ -52,6 +54,14 @@ func (pi *Interceptor) GetTaskInterceptor(taskID string) *TaskInterceptor {
return pi.taskInterceptorMap[taskID]
}

func (pi *Interceptor) AddToActivityCoverage(coverage ActivityCoverage) {
pi.Coverage.ActivityCoverage = append(pi.Coverage.ActivityCoverage, &coverage)
}

func (pi *Interceptor) AddToLinkCoverage(coverage TransitionCoverage) {
pi.Coverage.TransitionCoverage = append(pi.Coverage.TransitionCoverage, &coverage)
}

// TaskInterceptor contains instance override information for a Task, such has attributes.
// Also, a 'Skip' flag can be enabled to inform the runtime that the task should not
// execute.
Expand All @@ -76,3 +86,29 @@ type Assertion struct {
Message string
EvalResult ast.ExprEvalData
}

type Coverage struct {
ActivityCoverage []*ActivityCoverage `json:"activityCoverage,omitempty"`
TransitionCoverage []*TransitionCoverage `json:"transitionCoverage,omitempty"`
}

type ActivityCoverage struct {
ActivityName string
LinkFrom []string
LinkTo []string
Inputs map[string]interface{} `json:"inputs,omitempty"`
Outputs map[string]interface{} `json:"outputs,omitempty"`
Error map[string]interface{} `json:"errors,omitempty"`
FlowName string `json:"flowName"`
IsMainFlow bool `json:"scope"`
}

type TransitionCoverage struct {
TransitionName string `json:"transitionName"`
TransitionType string `json:"transitionType"`
TransitionFrom string `json:"transitionFrom"`
TransitionTo string `json:"transitionTo"`
TransitionExpression string `json:"transitionExpression"`
FlowName string `json:"flowName"`
IsMainFlow bool `json:"scope"`
}

0 comments on commit 8ea4c88

Please sign in to comment.