// Copyright Keysight Technologies 2012-2019
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, you can obtain one at http://mozilla.org/MPL/2.0/.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Threading;
namespace OpenTap
{
///
/// Object used to indicate that a TestStep is interested in results from another TestStep.
/// A public property of this type should exist on the interested TestStep.
/// Methods defined in this interface are called on all instances found as properties on TestSteps in the TestPlan.
///
public interface IResultSink
{
///
/// Called when a TestStep publishes results. This is happening in a background thread.
///
void OnResultPublished(TestStepRun run, ResultTable table);
///
/// Called when a TestStep is starting. This is happening in a background thread.
///
void OnTestStepRunStart(TestStepRun stepRun);
///
/// Called when a TestStep is completed. This is happening in a background thread. ResultPublished will not be called anymore with this TestStepRun after this.
///
void OnTestStepRunCompleted(TestStepRun stepRun);
///
/// Called when a TestPlan starts running. This is happening in a background thread.
///
void OnTestPlanRunStart(TestPlanRun stepRun);
///
/// Called when a TestPlan run is completed. This is happening in a background thread.
///
void OnTestPlanRunCompleted(TestPlanRun stepRun);
}
[Browsable(false)]
internal class ResultSinkListener : ResultListener
{
private IEnumerable currentSinks;
Dictionary currentStepRuns = new Dictionary();
public ResultSinkListener(IEnumerable sinks)
{
currentSinks = sinks;
}
public override void OnTestPlanRunStart(TestPlanRun planRun)
{
foreach (IResultSink sink in currentSinks)
{
try
{
sink.OnTestPlanRunStart(planRun);
}
catch(Exception ex)
{
Log.Error($"{TypeData.GetTypeData(sink).Name} caused an error.");
Log.Debug(ex);
}
}
}
public override void OnTestPlanRunCompleted(TestPlanRun planRun, Stream log)
{
foreach (IResultSink sink in currentSinks)
{
try
{
sink.OnTestPlanRunCompleted(planRun);
}
catch (Exception ex)
{
Log.Error($"{TypeData.GetTypeData(sink).Name} caused an error.");
Log.Debug(ex);
}
}
}
public override void OnTestStepRunStart(TestStepRun stepRun)
{
currentStepRuns.Add(stepRun.Id, stepRun);
foreach (IResultSink sink in currentSinks)
{
try
{
sink.OnTestStepRunStart(stepRun);
}
catch (Exception ex)
{
Log.Error($"{TypeData.GetTypeData(sink).Name} caused an error.");
Log.Debug(ex);
}
}
}
public override void OnTestStepRunCompleted(TestStepRun stepRun)
{
currentStepRuns.Remove(stepRun.Id);
foreach (IResultSink sink in currentSinks)
{
try
{
sink.OnTestStepRunCompleted(stepRun);
}
catch (Exception ex)
{
Log.Error($"{TypeData.GetTypeData(sink).Name} caused an error.");
Log.Debug(ex);
}
}
}
public override void OnResultPublished(Guid stepRunId, ResultTable result)
{
foreach (IResultSink sink in currentSinks)
{
try
{
sink.OnResultPublished(currentStepRuns[stepRunId], result);
}
catch (Exception ex)
{
Log.Error($"{TypeData.GetTypeData(sink).Name} caused an error.");
Log.Debug(ex);
}
}
}
}
///
/// ResultSink that will provide the first result from a given result column published by a given TestStep.
/// When SourceTestStep is inside a loop step, only results from the last iteration of SourceTestStep is accessible.
///
public class ScalarResultSink : IResultSink where T : IConvertible
{
private Queue Result;
private ManualResetEvent ItemsInQueue;
///
/// The ID of the source TestStep that we are interested in results from.
///
public ITestStep SourceTestStep { get; set; }
///
/// The name of the result column to get the result from.
///
public string ResultColumnName { get; set; }
private ITestStep ListeningStep;
///
/// Creates an instance. This should probably be called from the constructor of the TestStep.
///
///
///
///public class ListeningStepExample : TestStep
///{
/// public ITestStep SourceStep { get => Sink.SourceTestStep; set => Sink.SourceTestStep = value; }
/// public ScalarResultSink<double> Sink { get; set; }
///
/// public ListeningStepExample()
/// {
/// Sink = new ScalarResultSink<double>(this);
/// }
///
/// public override void Run()
/// {
/// log.Debug("Result was: {0}", Sink.GetResult(TapThread.Current.AbortToken));
/// }
///}
///
///
///
public ScalarResultSink(ITestStep listeningStep)
{
Result = new Queue();
ListeningStep = listeningStep;
}
///
/// Called by TestSteps when the result is needed. Blocks until a result is available.
///
public T GetResult(CancellationToken ct)
{
WaitHandle.WaitAny(new [] { ItemsInQueue, ct.WaitHandle });
ct.ThrowIfCancellationRequested();
lock (Result)
{
var res = Result.Dequeue();
if (!Result.Any())
ItemsInQueue.Reset();
return res;
}
}
///
/// Called by TestSteps when the result is needed. Returns true if a result is available.
///
public bool TryGetResult(out T result)
{
lock (Result)
{
if (Result.Any())
{
result = Result.Dequeue();
if (!Result.Any())
ItemsInQueue.Reset();
return true;
}
}
result = default(T);
return false;
}
///
/// Resets result collection when a new run of the is started.
///
public void OnTestStepRunStart(TestStepRun run)
{
if (run.TestStepId == SourceTestStep?.Id)
{
lock (Result)
{
ItemsInQueue.Reset();
Result.Clear();
}
}
}
///
/// Called when a TestStep is completed.
///
public void OnTestStepRunCompleted(TestStepRun run) { }
///
/// Called by OpenTAP when the source TestStep publishes results. This is happening in a background thread.
///
public void OnResultPublished(TestStepRun stepRun, ResultTable result)
{
if (stepRun.TestStepId == SourceTestStep?.Id)
{
ResultColumn column = result.Columns.FirstOrDefault(col => col.Name == ResultColumnName);
if (column != null)
{
lock (Result)
{
Result.Enqueue(column.GetValue(0));
ItemsInQueue.Set();
}
}
}
}
/// Initializes this instance.
public void OnTestPlanRunStart(TestPlanRun run)
{
ItemsInQueue = new ManualResetEvent(false);
}
/// Cleans up after this instance
public void OnTestPlanRunCompleted(TestPlanRun run)
{
ItemsInQueue.Dispose();
}
}
}