// 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.Linq;
using System.Diagnostics;
using System.Runtime.Serialization;
using System.Collections;
using System.Collections.Immutable;
using System.ComponentModel;
namespace OpenTap
{
///
/// Abstract class forming the basis for all ResultListeners.
///
public abstract class ResultListener : Resource, IResultListener, IEnabledResource
{
bool isEnabled = true;
/// Gets or sets if this resource is enabled.
[Browsable(false)]
public bool IsEnabled {
get => isEnabled;
set
{
var oldValue = isEnabled;
isEnabled = value;
onEnabledChanged(oldValue, value);
}
}
/// Called when IsEnabled is changed.
///
///
protected virtual void onEnabledChanged(bool oldValue, bool newValue)
{
}
///
/// Called when a test plan starts.
///
/// Test plan run parameters.
public virtual void OnTestPlanRunStart(TestPlanRun planRun)
{
}
///
/// Called when test plan finishes. At this point no more results will be sent to the result listener from the test plan run.
///
/// Test plan run parameters.
/// The log file from the test plan run as a stream.
public virtual void OnTestPlanRunCompleted(TestPlanRun planRun, System.IO.Stream logStream)
{
}
///
/// Called just before a test step is started.
///
///
public virtual void OnTestStepRunStart(TestStepRun stepRun)
{
}
///
/// Called when a test step run is completed.
/// Result might still be propagated to the result listener after this event.
///
/// Step run parameters.
public virtual void OnTestStepRunCompleted(TestStepRun stepRun)
{
}
///
/// Called when a result is received.
///
/// Step run ID.
/// Result structure.
public virtual void OnResultPublished(Guid stepRunId, ResultTable result)
{
}
// lookup keeping track of which result listeners implements OnResultPublished
static ImmutableDictionary implementsOnResultsPublished = ImmutableDictionary.Empty;
/// Returns true if the result listener has implemented OnResultPublished. This is used to optimize performance in situations where they don't.
internal static bool ImplementsOnResultsPublished(ResultListener resultListener)
{
var resultListenerType = resultListener.GetType();
if (!implementsOnResultsPublished.TryGetValue(resultListenerType, out bool doesImplement))
{
doesImplement = resultListenerType.MethodOverridden(typeof(ResultListener), nameof(OnResultPublished));
implementsOnResultsPublished = implementsOnResultsPublished.Add(resultListenerType, doesImplement);
}
return doesImplement;
}
}
///
/// Instructs the ResultListener not to save the
/// public property value as metadata for TestStep results.
///
[Obsolete("This attribute is no longer in use and will be removed in a later version.")]
[AttributeUsage(AttributeTargets.Property)]
public class ResultListenerIgnoreAttribute : Attribute
{
}
///
/// Represents a result parameter.
///
[DebuggerDisplay("{Group} {Name} = {Value}")]
[DataContract]
public class ResultParameter : IParameter
{
///
/// Name of parameter.
///
[DataMember]
public readonly string Name;
///
/// Group name of the parameter.
///
[DataMember]
public readonly string Group;
///
/// Value of the parameter. If null, the value is the string "NULL".
///
[DataMember]
public readonly IConvertible Value;
///
/// Indicates the parameter came from a test step in a parent level above the initial object.
///
[DataMember]
public readonly int ParentLevel;
internal (string, string) Key => (Name, Group);
IConvertible IParameter.Value => Value;
string IAttributedObject.Name => Name;
string IParameter.Group => Group;
string IAttributedObject.ObjectType => "Parameter";
/// Gets if this result is metadata.
public bool IsMetaData { get; }
/// null or the macro name representation of the ResultParameter. This will make it possible to insert the parameter value into a string.
public readonly string MacroName;
/// Creates a result parameter with default group.
public ResultParameter(string name, IConvertible value)
{
Name = name;
Value = value ?? "NULL";
Group = "";
MacroName = null;
}
///
/// Initializes a new instance of ResultParameter.
///
public ResultParameter(string group, string name, IConvertible value, MetaDataAttribute metadata = null, int parentLevel = 0)
{
Group = group ?? "";
Name = name;
Value = value ?? "NULL";
ParentLevel = parentLevel;
if (metadata != null)
{
MacroName = metadata.MacroName;
IsMetaData = true;
}
else
{
MacroName = null;
}
}
/// Creates a new ResultParameter.
///
///
///
///
public ResultParameter(string group, string name, IConvertible value, string macroName)
{
Group = group;
Name = name;
Value = value ?? "NULL";
if (macroName != null)
{
IsMetaData = true;
if (macroName.Length > 0)
MacroName = macroName;
else MacroName = name;
}
}
///
/// Determines whether the specified object is equal to the current object.
///
///
///
public override bool Equals(object obj)
{
var parameter = obj as ResultParameter;
return parameter != null &&
Name == parameter.Name &&
Group == parameter.Group &&
EqualityComparer.Default.Equals(Value, parameter.Value) &&
ParentLevel == parameter.ParentLevel &&
MacroName == parameter.MacroName;
}
///
/// Calculates a hash code for the current object.
///
///
public override int GetHashCode()
{
var hashCode = -1808396095;
hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(Name);
hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(Group);
hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(Value);
hashCode = hashCode * -1521134295 + ParentLevel.GetHashCode();
hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(MacroName);
return hashCode;
}
}
// this array can be indexed and resized at the same time.
class SafeArray : IEnumerable
{
T[][] arrays = Array.Empty();
int count;
public int Count => count;
public SafeArray(T[] init)
{
arrays = new[] {init};
count = init.Length;
}
public SafeArray(SafeArray original)
{
count = original.count;
arrays = new []{new T[count]};
int offset = 0;
foreach (var elems in original.arrays)
{
elems.CopyTo(arrays[0], offset);
offset += elems.Length;
}
}
public SafeArray()
{
}
readonly object resizeLock = new object();
public void Resize(int newSize)
{
if(newSize < count)
throw new InvalidOperationException();
if (newSize == count) return;
lock (resizeLock)
{
Array.Resize(ref arrays, arrays.Length + 1);
arrays[arrays.Length - 1] = new T[newSize - count];
count = newSize;
}
}
public ref T this[int index]
{
get
{
for(int i = 0; i < arrays.Length; i++)
{
var array = arrays[i];
if (index < array.Length)
return ref array[index];
index -= array.Length;
}
throw new IndexOutOfRangeException();
}
}
public IEnumerator GetEnumerator() => arrays.SelectMany(x => x).GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
///
/// A collection of parameters related to the results.
///
public class ResultParameters : IReadOnlyList
{
struct resultParameter
{
public string Name;
public IConvertible Value;
public string Group;
public string MacroName;
public ResultParameter ToResultParameter() => new ResultParameter(Group, Name, Value, MacroName);
public static implicit operator resultParameter(ResultParameter v)
{
return new resultParameter
{
Name = v.Name,
Value = v.Value,
Group = v.Group,
MacroName = v.MacroName ?? (v.IsMetaData ? "" : null)
};
}
}
SafeArray data = new SafeArray();
///
/// Gets the parameter with the given index.
///
public ResultParameter this[int index] => data[index].ToResultParameter();
/// Gets a ResultParameter by name.
public ResultParameter Find(string name) => Find(name, "");
/// Gets a ResultParameter by name.
public ResultParameter Find(string name, string group)
{
if (indexByName.TryGetValue((name, group), out var idx) == false)
return null;
return this[idx];
}
/// Gets a ResultParameter by name.
public ResultParameter Find((string name, string group) key)
{
if (indexByName.TryGetValue(key, out var idx) == false)
return null;
return this[idx];
}
int FindIndex((string name, string group) key)
{
if (indexByName.TryGetValue(key, out var idx))
return idx;
return -1;
}
/// Gets the parameter with the key name.
public IConvertible this[string name, string group]
{
get => Find(name, group)?.Value;
set
{
int index = -1;
SetIndexed((name, group), ref index, value);
}
}
/// Gets a named parameter specifying only name. This assumes that the empty group is being used. So it is the same as calling [name, ""]..
public IConvertible this[string name]
{
get => this[name, ""];
set => this[name, ""] = value;
}
static void getMetadataFromObject(object res, string nestedName, ICollection output)
{
GetPropertiesFromObject(res, output, nestedName, true);
}
///
/// Returns a list with one entry for every property on the inputted
/// object decorated with .
///
public static ResultParameters GetMetadataFromObject(object res)
{
if (res == null)
throw new ArgumentNullException(nameof(res));
var parameters = new List();
getMetadataFromObject(res, "", parameters);
return new ResultParameters(parameters);
}
///
/// Returns a list with one entry for every property on every
/// implementation decorated with .
///
public static ResultParameters GetComponentSettingsMetadata(bool expandComponentSettings = false)
{
var componentSettings = TypeData.FromType(typeof(ComponentSettings)) //get component settings instances (lazy)
.DerivedTypes
.Where(x => x.CanCreateInstance && ParameterCache.GetParametersMap(x, true).Any())
.Select(td => ComponentSettings.GetCurrent(td.Type))
.Where(o => o != null)
.Cast