// 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(); } } }