/// <summary> /// Validate actual trace angainst expected trace. /// Call is comming from the parent step /// </summary> /// <param name="actualTrace">actual trace</param> /// <param name="startIndex">index to start searches from</param> /// <returns>true if traces match</returns> private static bool ValidateFirst(TraceGroup currentTrace, int startIndex) { currentTrace.startIndex = startIndex; for (int i = 0; i < currentTrace.Steps.Count; i++) { currentTrace.endIndexes[i] = -1; } bool match = false; if (!currentTrace.Optional) { match = TraceValidator.Validate(currentTrace, 0, startIndex, DateTime.MinValue, DateTime.MinValue); } else { TraceValidator.SetRestorePoint(); match = TraceValidator.Validate(currentTrace, 0, startIndex, DateTime.MinValue, DateTime.MinValue); if (match) { TraceValidator.Commit(); } else { TraceValidator.Rollback(); // try to skip this entire step, since it's optional match = TraceValidator.ValidateNextSibling(currentTrace.parent, currentTrace.indexInParent, startIndex); } } return(match); }
/// <summary> /// Flattens the expected trace hierarchy as much as possible /// Intended to simplify validation function and make expected /// trace more human-readable /// </summary> internal static void NormalizeExpectedTrace(TraceGroup currentTrace) { int index = 0; while (index < currentTrace.Steps.Count) { // overwrite if a placeholder if (currentTrace.Steps[index] is IPlaceholderTraceProvider) { currentTrace.Steps[index] = ((IPlaceholderTraceProvider)currentTrace.Steps[index]).GetPlaceholderTrace(); } TraceGroup childStep = currentTrace.Steps[index] as TraceGroup; if (childStep != null) { TraceValidator.NormalizeExpectedTrace(childStep); if (childStep.Steps.Count == 0) { currentTrace.Steps.RemoveAt(index); // skip index++; continue; } if (childStep.ordered == currentTrace.ordered && !childStep.Optional && !childStep.Async) { //flatten the child step currentTrace.Steps.RemoveAt(index); currentTrace.Steps.InsertRange(index, childStep.Steps); // count n steps forward and skip index++; index = childStep.Steps.Count; continue; } } index++; } if ((currentTrace.Steps.Count == 1) && (currentTrace.Steps[0] is TraceGroup)) { // merge current step with the only child step, and their properties TraceGroup child = (TraceGroup)currentTrace.Steps[0]; currentTrace.ordered = child.ordered; currentTrace.Async = (currentTrace.Async || child.Async); currentTrace.Optional = (currentTrace.Optional || child.Optional); //flatten the child step currentTrace.Steps.Clear(); currentTrace.Steps.AddRange(child.Steps); } }
/// <summary> /// Validate actual trace angainst expected trace. /// This method signals parent step to continue validation /// with the next sibling of this step. /// Call is comming from the child step /// </summary> /// <param name="childIndex">index of child just executed</param> /// <param name="actualTrace">actual trace</param> /// <param name="endIndex">index of the step next to the last step validated by child</param> /// <returns>true if traces match</returns> private static bool ValidateNextSibling(TraceGroup currentTrace, int childIndex, int endIndex) { currentTrace.endIndexes[childIndex] = endIndex; if (currentTrace.ordered) { return(TraceValidator.Validate(currentTrace, childIndex + 1, endIndex, DateTime.MinValue, DateTime.MinValue)); } else { return(TraceValidator.Validate(currentTrace, childIndex + 1, currentTrace.startIndex, DateTime.MinValue, DateTime.MinValue)); } }
// <summary> // Prepare datastructure for validation // </summary> // <param name="expectedTrace">expected trace</param> private static void PrepareActualTrace() { // remove ignorable traces var ignorableSteps = (from step in TraceValidator.s_actualTrace.Steps where TraceValidator.s_expectedTrace.CanBeIgnored(step) select step).ToList(); foreach (IActualTraceStep step in ignorableSteps) { TraceValidator.s_actualTrace.Steps.Remove(step); } foreach (IActualTraceStep step in TraceValidator.s_actualTrace.Steps) { step.Validated = 0; TraceValidator.AddActualStepCount(step); } }
// <summary> // Remove traces we can ignore // </summary> private static void RemoveIgnorableSteps(TraceGroup trace) { var ignorableSteps = (from step in trace.Steps where step is IActualTraceStep && TraceValidator.s_expectedTrace.CanBeIgnored((IActualTraceStep)step) select step).ToList(); foreach (WorkflowTraceStep step in ignorableSteps) { trace.Steps.Remove(step); } foreach (WorkflowTraceStep step in trace.Steps) { if (step is TraceGroup) { TraceValidator.RemoveIgnorableSteps((TraceGroup)step); } } }
/// <summary> /// Constructor /// </summary> public static void Validate(ActualTrace actualTrace, ExpectedTrace expectedTrace, bool traceTracking) { TraceValidator.s_actualTrace = actualTrace; TraceValidator.s_expectedTrace = expectedTrace; TraceValidator.s_errorList = new List <string>(); TraceValidator.s_stepCounts = new Dictionary <string, StepCount>(); TestTraceManager.OptionalLogTrace("[TraceValidator]Unfiltered expected trace:\n{0}", expectedTrace.ToString()); TestTraceManager.OptionalLogTrace("[TraceValidator]Unfiltered actual trace:\n{0}", actualTrace.ToString()); TraceValidator.NormalizeExpectedTrace(expectedTrace.Trace); TraceValidator.RemoveIgnorableSteps(expectedTrace.Trace); TraceValidator.PrepareExpectedTrace(expectedTrace.Trace, false, null, -1); TraceValidator.PrepareActualTrace(); if (traceTracking) { //Log.TraceInternal("[TraceValidator]Filtered expected trace:\n{0}", expectedTrace.ToString()); //Log.TraceInternal("[TraceValidator]Filtered actual trace:\n{0}", actualTrace.ToString()); //Log.TraceInternal("[TraceValidator]Doing count validation..."); } TraceValidator.CheckStepCounts(); TraceValidator.CheckErrors(); if (traceTracking) { //Log.TraceInternal("[TraceValidator]Validating..."); } TraceValidator.ValidateFirst(expectedTrace.Trace, 0); TraceValidator.CheckErrors(); if (traceTracking) { //Log.TraceInternal("[TraceValidator]ExpectedTrace: Validation complete."); } }
// <summary> // Prepares composite steps for validation // </summary> // <param name="stepCounter">StepCounter object to count optional and required steps</param> // <param name="parentIsOptional">Indicates if parent step is optional</param> // <param name="parentStep">parent step object</param> // <param name="indexInParent">index of this step inside its parent</param> private static void PrepareExpectedTrace(TraceGroup traceStep, bool parentIsOptional, TraceGroup parentStep, int indexInParent) { // set parent step reference for this step traceStep.parent = parentStep; traceStep.indexInParent = indexInParent; bool optional = parentIsOptional || traceStep.Optional; // set parent step reference for children steps for (int i = 0; i < traceStep.Steps.Count; i++) { WorkflowTraceStep childStep = traceStep.Steps[i]; if (childStep is TraceGroup) { TraceValidator.PrepareExpectedTrace((TraceGroup)childStep, optional, traceStep, i); } else if (childStep is IActualTraceStep) { if (optional || childStep.Optional) { TraceValidator.AddExpectedOptStepCount((IActualTraceStep)childStep); } else { TraceValidator.AddExpectedReqStepCount((IActualTraceStep)childStep); } } } // declare & init endIndexes[] array traceStep.endIndexes = new int[traceStep.Steps.Count]; for (int i = 0; i < traceStep.Steps.Count; i++) { traceStep.endIndexes[i] = -1; } }
public void Validate(ExpectedTrace expectedTrace, bool logTraces) { lock (_steps) { if (expectedTrace.SortBeforeVerification) { // copy the expected trace, remove activity traces and verify workflow instnace traces ExpectedTrace etrace = new ExpectedTrace(expectedTrace); ActualTrace atrace = new ActualTrace(this); etrace.AddIgnoreTypes(typeof(ActivityTrace), typeof(UserTrace)); TraceValidator.Validate(atrace, etrace, logTraces); // now verify the activity traces, after they have been ordered expectedTrace.AddIgnoreTypes(typeof(WorkflowInstanceTrace)); expectedTrace.AddIgnoreTypes(typeof(UserTrace)); this.OrderTraces(); TraceValidator.Validate(this, expectedTrace); } else { TraceValidator.Validate(this, expectedTrace, logTraces); } } }
private static void AddExpectedOptStepCount(IActualTraceStep step) { TraceValidator.GetStepCount(step).expectedOptCount++; }
private static void AddActualStepCount(IActualTraceStep step) { TraceValidator.GetStepCount(step).actualTraceCount++; }
/// <summary> /// Validate actual trace angainst expected trace. /// This method validates next same level expected step /// </summary> /// <param name="curExpIndex">expected trace step index</param> /// <param name="actualTrace">actual trace</param> /// <param name="startIndex">index to start searches from</param> /// <param name="lastTime">time last step occured at</param> /// <param name="mustBeAfter">subsequent step must occur after time specified</param> /// <returns>true if traces match</returns> private static bool Validate(TraceGroup currentTrace, int curExpIndex, int startIndex, DateTime lastTime, DateTime mustBeAfter) { // is this a last step to check? if (curExpIndex >= currentTrace.Steps.Count) { bool match = false; if (null == currentTrace.parent) { //Oracle.LogDebugInfo("End of step list. Verifying completeness"); return(TraceValidator.VerifyAllStepsValidated()); } else { //Oracle.LogDebugInfo("Check next parent step"); if (currentTrace.Async) { match = TraceValidator.ValidateNextSibling(currentTrace.parent, currentTrace.indexInParent, currentTrace.startIndex); } else { if (currentTrace.ordered) { match = TraceValidator.ValidateNextSibling(currentTrace.parent, currentTrace.indexInParent, currentTrace.startIndex); } else { match = TraceValidator.ValidateNextSibling(currentTrace.parent, currentTrace.indexInParent, TraceValidator.GetMaxEndIndex(currentTrace)); } } } return(match); } WorkflowTraceStep step = currentTrace.Steps[curExpIndex]; if (step is TraceGroup) { // check inner composite step return(TraceValidator.ValidateFirst((TraceGroup)step, startIndex)); } else if (step is DelayTrace) { mustBeAfter = lastTime.Add(((DelayTrace)step).TimeSpan); return(TraceValidator.Validate(currentTrace, curExpIndex + 1, startIndex, lastTime, mustBeAfter)); } else if (step is IActualTraceStep) { int[] entryIndexes = TraceValidator.FindAllEntries((IActualTraceStep)step, startIndex, mustBeAfter); bool match = false; if (entryIndexes.Length == 0) { // step not found if (!step.Optional) { string msg = String.Format("Step '{0}' is not found. Start index {1}", step, startIndex); //Oracle.LogDebugInfo("Adding error: {0}", msg); TraceValidator.s_errorList.Add(msg); } } else if (entryIndexes.Length == 1 && !step.Optional) { // this branch can be commented out // it's an optimization for the most common case // only one option int index = entryIndexes[0]; TraceValidator.MarkAsFound(index, out lastTime); currentTrace.endIndexes[curExpIndex] = index; if (currentTrace.ordered && !step.Async) { match = TraceValidator.Validate(currentTrace, curExpIndex + 1, index + 1, lastTime, mustBeAfter); } else { match = TraceValidator.Validate(currentTrace, curExpIndex + 1, startIndex, lastTime, mustBeAfter); } } else { // many options. try each choice until succeed foreach (int index in entryIndexes) { TraceValidator.SetRestorePoint(); //this.Dump("After SetRestorePoint"); TraceValidator.MarkAsFound(index, out lastTime); currentTrace.endIndexes[curExpIndex] = index; //this.Dump("After mark as found"); if (currentTrace.ordered && !step.Async) { match = TraceValidator.Validate(currentTrace, curExpIndex + 1, entryIndexes[0] + 1, lastTime, mustBeAfter); } else { match = TraceValidator.Validate(currentTrace, curExpIndex + 1, startIndex, lastTime, mustBeAfter); } //this.Dump("After searched subsequent steps"); if (match) { TraceValidator.Commit(); //this.Dump("After Commit"); break; } else { TraceValidator.Rollback(); //this.Dump("After Rollback1"); } } } if (!match && step.Optional) { //Oracle.LogDebugInfo("Skipping optional step"); match = TraceValidator.Validate(currentTrace, curExpIndex + 1, startIndex, lastTime, mustBeAfter); } return(match); } else { throw new Exception(String.Format( "Internal validation error. Unknown step type found {0}", step.GetType().Name)); } }