// 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; using System.Collections.Generic; using System.Linq; using System.Reflection; using System.ComponentModel; using System.IO; using System.Runtime.InteropServices; using System.Runtime.CompilerServices; using System.Runtime.ExceptionServices; using System.Text; using System.Threading; using OpenTap.Translation; //**** WARNING ****// // This file is used in many projects(link existing), but only with internal protection. // NEVER insert a public class here or things will break due to multiple definitions of the same class. // Bugs introduced here will cause bugs in other projects too, so be careful. // ** namespace OpenTap { /// /// Class to ease the use of reflection. /// internal static class ReflectionHelper { /// Returns true if 'type' and 'basetype' are equal. /// /// /// public static bool IsA(this ITypeData type, Type basetype) { if (type is TypeData cst) return cst.Type == basetype; return false; } public static T GetBaseType(this ITypeData type) where T: ITypeData { var typeIterator = type; while (typeIterator != null) { if (typeIterator is TypeData) break; if (typeIterator is T t2) return t2; typeIterator = typeIterator.BaseType; } return default; } /// Really fast direct descendant test. This checks for reference equality of the type or a base type, and 'baseType'. /// Given these constraints are met, this can be 6x faster than DescendsTo, but should only be used in special cases. public static bool DirectInheritsFrom(this ITypeData type, ITypeData baseType) { do { if(ReferenceEquals(type, baseType)) return true; type = type.BaseType; } while (type != null); return false; } public static TypeData AsTypeData(this ITypeData type) { do { if (type is TypeData td) return td; type = type?.BaseType; } while (type != null); return null; } static Dictionary displayLookup = new Dictionary(1024); static object displayLookupLock = new object(); public static DisplayAttribute GetDisplayAttribute(this MemberInfo type) { lock (displayLookupLock) { if (!displayLookup.ContainsKey(type)) { DisplayAttribute attr; try { attr = type.GetAttribute(); } catch { // This might happen for outdated plugins where an Attribute type ceased to exist. attr = null; } if (attr == null) { attr = new DisplayAttribute(type.Name, null, Order: -10000, Collapsed: false); } displayLookup[type] = attr; } return displayLookup[type]; } } /// /// Parses a DisplayName into a group:name pair. /// /// /// /// internal static string ParseDisplayname(string displayName, out string group) { group = ""; var parts = displayName.Trim().TrimStart('-').Split('\\'); if (parts.Length == 1) return displayName; else if (parts.Length >= 2) { group = parts[0].Trim(); return parts.Last().Trim(); } else return displayName.Trim(); } static object[] getAttributes(MemberInfo mem) { try { return mem.GetCustomAttributes(true); } catch { return Array.Empty(); } } static readonly ConditionalWeakTable attrslookup = new ConditionalWeakTable(); public static object[] GetAllCustomAttributes(this MemberInfo prop) { return attrslookup.GetValue(prop, getAttributes); } static object[] getAttributesNoInherit(MemberInfo mem) { try { return mem.GetCustomAttributes(false); } catch { return Array.Empty(); } } static readonly ConditionalWeakTable attrslookupNoInherit = new ConditionalWeakTable(); public static object[] GetAllCustomAttributes(this MemberInfo prop, bool inherit) { if(!inherit) return attrslookupNoInherit.GetValue(prop, getAttributesNoInherit); return GetAllCustomAttributes(prop); } /// /// Gets the custom attributes. Both type and property attributes. Also inherited attributes. /// /// /// /// public static T[] GetCustomAttributes(this MemberInfo prop) where T : Attribute { // This method impacts GUI, serialization and even test plan execution times. // it needs to be as fast as possible. // Avoid allocation when there is nothing of type T in the attributes var array = GetAllCustomAttributes(prop); int cnt = 0; foreach (var attr in array) { if (attr is T) cnt++; } if (cnt == 0) return Array.Empty(); // This avoids allocation of empty arrays. T[] result = new T[cnt]; cnt = 0; foreach (var attr in array) { if (attr is T a) { result[cnt] = a; cnt++; } } return result; } /// /// Gets the first or default of the custom attributes for this member. Both type and property attributes also inherited attributes. /// /// /// /// public static T GetFirstOrDefaultCustomAttribute(this MemberInfo prop) where T : Attribute { foreach (var attr in GetAllCustomAttributes(prop)) if (attr is T a) return a; return null; } /// /// Gets the first or default of the custom attributes for this property. Both type and property attributes also inherited attributes. /// /// /// /// public static T GetAttribute(this MemberInfo prop) where T : Attribute { return GetFirstOrDefaultCustomAttribute(prop); } /// /// return whether the property has a given attribute T. /// /// /// /// public static bool HasAttribute(this MemberInfo prop) where T : Attribute { return prop.IsDefined(typeof(T), true); } /// /// Return whether the attribute has the given attribute T. /// /// /// /// public static bool HasAttribute(this Type t) where T : Attribute { return t.IsDefined(typeof(T), true); } /// /// Returns true if a MemberInfo is Browsable. /// /// /// public static bool IsBrowsable(this MemberInfo m) { var b = m.GetAttribute(); if (b == null) return true; return b.Browsable; } /// /// Check whether a type 'descends' to otherType or "can be otherType". /// /// /// /// public static bool DescendsTo(this Type t, Type otherType) { if (t == otherType) return true; if (otherType.IsGenericTypeDefinition) { // In the case otherType is constructed from typeof(X<>), not typeof(X). if (otherType.IsInterface) { var interfaces = t.GetInterfaces(); foreach (var iface in interfaces) { if (iface.IsGenericType) { if (iface.GetGenericTypeDefinition() == otherType) return true; } } if (t.IsInterface) { if (t.IsGenericType) { if (t.GetGenericTypeDefinition() == otherType) return true; } } } else { Type super = t; while (super != typeof(object) && super != null /*if not a class*/) { if (super.IsGenericType && super.GetGenericTypeDefinition() == otherType) return true; super = super.BaseType; } } } return otherType.IsAssignableFrom(t); } /// /// returns whether t has a given interface T. /// /// /// /// public static bool HasInterface(this Type t) { return typeof(T).IsAssignableFrom(t); } /// /// Returns true if a type is numeric. /// public static bool IsNumeric(this Type t) { if (t.IsEnum) return false; switch (Type.GetTypeCode(t)) { case TypeCode.Byte: case TypeCode.SByte: case TypeCode.UInt16: case TypeCode.UInt32: case TypeCode.UInt64: case TypeCode.Int16: case TypeCode.Int32: case TypeCode.Int64: case TypeCode.Decimal: case TypeCode.Double: case TypeCode.Single: return true; default: return false; } } /// Returns true if a type is numeric. public static bool IsNumeric(this ITypeData t) { return t.AsTypeData()?.Type.IsNumeric() == true; } /// Returns true if a type is a C# primitive. public static bool IsPrimitive(this ITypeData t) { return t.AsTypeData()?.Type.IsPrimitive ?? false; } /// Creates an instance of t with no constructor arguments. public static object CreateInstance(this Type t, params object[] args) => Activator.CreateInstance(t, args); /// Creates an instance of type t. If an error occurs it returns null and prints an error message. public static object CreateInstanceSafe(this ITypeData t, params object[] args) { try { return t.CreateInstance(args); } catch(Exception e) { var log = Log.CreateSource("Reflection"); log.Error($"Cannot create instance of {t.Name}: '{e.Message}'"); log.Debug(e); } return null; } /// /// If Type is a collection of items, get the element type. /// /// /// static public Type GetEnumerableElementType(this Type enumType) { if (enumType.IsArray) return enumType.GetElementType(); if (enumType.IsGenericTypeDefinition) return null; try { // check if it *has* the interface var ienumInterface = enumType.GetInterface(typeof(IEnumerable<>).Name); if (ienumInterface != null) return ienumInterface.GetGenericArguments().FirstOrDefault(); } catch (AmbiguousMatchException) { // the type implements multiple different IEnumerable<> interfaces. // this is an odd case that is not commonly seen. } // check if it *is* the interface. if(enumType.IsInterface && enumType.IsGenericType && enumType.GetGenericTypeDefinition() == typeof(IEnumerable<>)) { return enumType.GetGenericArguments().FirstOrDefault(); } return null; } static readonly Dictionary propslookup = new Dictionary(1024); static PropertyInfo[] getPropertiesTap(Type t) { lock (propslookup) { if (propslookup.ContainsKey(t) == false) { try { propslookup[t] = t.GetProperties(BindingFlags.Public | BindingFlags.Instance); } catch { propslookup[t] = Array.Empty(); } } return propslookup[t]; } } public static bool ContainsMember(this IParameterMemberData p, (object Source, IMemberData Member) item) { return p.ParameterizedMembers.Contains(item); } /// Extracts properties from a Type that are public and not static. Default GetProperties() also returns static properties. public static PropertyInfo[] GetPropertiesTap(this Type type) { return getPropertiesTap(type); } static readonly Dictionary propslookup2 = new Dictionary(1024); static MethodInfo[] getMethodsTap(Type t) { lock (propslookup2) { if (propslookup2.ContainsKey(t) == false) { try { propslookup2[t] = t.GetMethods(); } catch { propslookup2[t] = Array.Empty(); } } return propslookup2[t]; } } /// Extracts properties from a Type that are public and not static. Default GetProperties() also returns static properties. public static MethodInfo[] GetMethodsTap(this Type type) { return getMethodsTap(type); } public static bool MethodOverridden(this Type t, Type baseType, string methodName) { var m1 = baseType.GetMethod(methodName).MethodHandle.Value; var m2 = t.GetMethod(methodName).MethodHandle.Value; return m1 != m2; } /// Get the base C# type of a given type. internal static T As(this ITypeData type) where T: ITypeData { for(;type != null; type = type.BaseType) if (type is T td) return td; return default; } public static void GetAttributes(this IReflectionData mem, System.Collections.IList outList) { foreach (var item in mem.Attributes) { if (item is T x) outList.Add(x); } } } internal class Memorizer { /// /// Enumerates how cyclic invokes can be handled. /// public enum CyclicInvokeMode { /// /// Specifies that an exception should be thrown. /// ThrowException, /// /// Specifies that default(ResultT) should be returned. /// ReturnDefaultValue } } internal interface IMemorizer { ResultT Invoke(ArgT arg); void InvalidateAll(); } /// /// Convenient when some memorizer optimizations can be done. /// Includes functionality for decay time and max number of elements. /// It assumes that the same ArgT will always result in the same ResultT. /// /// /// /// internal class Memorizer : Memorizer, IMemorizer { /// /// Used for locking the invokation of a specific MemorizerKey. /// This makes it possible to call Invoke in parallel and avoid recalculating the same value multiple times. /// class LockObject { public bool IsLocked; } /// If a certain time passes a result should be removed. By default, never. public TimeSpan SoftSizeDecayTime = TimeSpan.MaxValue; protected Func getKey; protected Func getData = argt => (ResultT)(object)argt; readonly Dictionary lastUse = new Dictionary(); readonly Dictionary memorizerTable = new Dictionary(); readonly Dictionary locks = new Dictionary(); /// Can be used to create a validation key for each key in the memorizer. /// Validation keys are used for checking if the memory is up to date or if it should be refreshed. public Func Validator { get; set; } = null; readonly Dictionary validatorData = new Dictionary(); /// /// Specifies how to handle situations where an Invoke(x) triggers another Invoke(x) in the same thread. /// Since this might cause infinite recursion, it is not allowed. By default an exception is thrown. /// public CyclicInvokeMode CylicInvokeResponse = CyclicInvokeMode.ThrowException; public Nullable MaxNumberOfElements { get; set; } public Memorizer(Func getKey = null, Func extractData = null) { if (extractData != null) getData = extractData; this.getKey = getKey; } /// /// Forces manual update of constraints. /// public void CheckConstraints() { while (checkSizeConstraints() == Status.Changed) { } } enum Status { Changed, Unchanged } Status checkSizeConstraints() { if (SoftSizeDecayTime < TimeSpan.MaxValue || MaxNumberOfElements.HasValue && (ulong) memorizerTable.Count > MaxNumberOfElements.Value) { lock (memorizerTable) { var removeKey = lastUse.Keys.FindMin(key2 => lastUse[key2]); if (removeKey != null) { if (SoftSizeDecayTime < DateTime.UtcNow - lastUse[removeKey]) { lastUse.Remove(removeKey); memorizerTable.Remove(removeKey); return Status.Changed; } else if (MaxNumberOfElements.HasValue && (ulong) memorizerTable.Count > MaxNumberOfElements.Value) { lastUse.Remove(removeKey); memorizerTable.Remove(removeKey); return Status.Changed; } } } } return Status.Unchanged; } MemorizerKey invokeGetKey(ArgT arg) { return getKey == null ? (MemorizerKey)(object)arg : getKey(arg); } public virtual ResultT OnCyclicCallDetected(ArgT key) { throw new Exception("Cyclic memorizer invoke detected."); } public ResultT this[ArgT arg] => Invoke(arg); public ResultT Invoke(ArgT arg) { var key = invokeGetKey(arg); if(Validator != null){ var obj = Validator(key); lock (memorizerTable) { if (validatorData.TryGetValue(key, out object value)) { if (false == Equals(value, obj)) { Invalidate(arg); validatorData[key] = obj; } }else { validatorData[key] = obj; } } } LockObject lockObj; lock (memorizerTable) { lastUse[key] = DateTime.UtcNow; if (!locks.TryGetValue(key, out lockObj)) { lockObj = new LockObject(); locks[key] = lockObj; } } lock (lockObj) { if (lockObj.IsLocked) { // Avoid running into a StackOverflowException. if (CylicInvokeResponse == CyclicInvokeMode.ThrowException) return OnCyclicCallDetected(arg); return default(ResultT); } try { lockObj.IsLocked = true; lock (memorizerTable) { if (memorizerTable.TryGetValue(key, out ResultT value)) return value; } ResultT o = getData(arg); lock (memorizerTable) { memorizerTable[key] = o; checkSizeConstraints(); } return o; } finally { lockObj.IsLocked = false; } } } public ResultT GetCached(ArgT arg) { ResultT o = default(ResultT); var key = invokeGetKey(arg); lock (memorizerTable) { if (!memorizerTable.TryGetValue(key, out o)) return default(ResultT); lastUse[key] = DateTime.UtcNow;; } return o; } public void Add(ArgT arg, ResultT value) { var key = invokeGetKey(arg); lock (memorizerTable) { lastUse[key] = DateTime.UtcNow; memorizerTable[key] = value; checkSizeConstraints(); } } public void Invalidate(ArgT value) { var key = invokeGetKey(value); lock (memorizerTable) { memorizerTable.Remove(key); lastUse.Remove(key); validatorData.Remove(key); } } /// /// Invalidate the keys where f returns true. This is being done while /// the memorizer is locked, so race conditions are avoided. /// /// public void InvalidateWhere(Func predicate) { lock (memorizerTable) { List keys = null; foreach(var item in memorizerTable) { if(predicate(item.Key, item.Value)) { if(keys == null) { keys = new List(); } keys.Add(item.Key); } } if(keys != null) { foreach(var k in keys) { memorizerTable.Remove(k); lastUse.Remove(k); } } } } public List GetResults() { lock (memorizerTable) { return memorizerTable.Values.ToList(); } } public void InvalidateAll() { lock (memorizerTable) { memorizerTable.Clear(); lastUse.Clear(); validatorData.Clear(); } } } internal class Memorizer : Memorizer { public Memorizer(Func func) : base(extractData: func) { } } static class Utils { static readonly char[] padding = { '=' }; public static string Base64UrlEncode(byte[] bytes) { return Convert.ToBase64String(bytes) .TrimEnd(padding).Replace('+', '-') .Replace('/', '_'); } public static IEnumerable<(int, T)> WithIndex(this IEnumerable collection) { return collection.Select((ele, index) => (index, ele)); } /// /// Thread-safe and lock free value exchange. valueGen depends on the current value. /// /// Where to place the value /// Function generating a value based on the current value. /// The type of object. public static void InterlockedSwap(ref T outPlace, Func valueGen) where T : class { while (true) { // safely add a new item to the list using the compare-and-swap atomic operation. var currentValue = outPlace; var nextValue = valueGen(); if (Interlocked.CompareExchange(ref outPlace, nextValue, currentValue) == currentValue) { break; } } } #if DEBUG public static readonly bool IsDebugBuild = true; #else public static readonly bool IsDebugBuild = false; #endif /// Swaps two variables public static void Swap(ref T a, ref T b) => (a, b) = (b, a); /// Do nothing. public static void Noop() { } /// /// Returns the element for which selector returns the max value. /// if IEnumerable is empty, it returns default(T) multiplier gives the direction to search. /// static T FindExtreme(this IEnumerable sequence, Func selector, int multiplier) where C : IComparable { var e = sequence.GetEnumerator(); if (!e.MoveNext()) return default(T); T selected = e.Current; C max = selector(selected); while (e.MoveNext()) { var obj = e.Current; C comparable = selector(obj); if (comparable.CompareTo(max) * multiplier > 0) { selected = obj; max = comparable; } } return selected; } /// Returns the element for which selector returns the max value. if IEnumerable is empty, it returns default(T). public static T FindMax(this IEnumerable ienumerable, Func selector) where C : IComparable { return FindExtreme(ienumerable, selector, 1); } /// /// Returns the element for which selector returns the minimum value. /// if IEnumerable is empty, it returns default(T). /// public static T FindMin(this IEnumerable ienumerable, Func selector) where C : IComparable { return FindExtreme(ienumerable, selector, -1); } /// /// Skips last N items. /// /// /// /// n last items to skip. /// public static IEnumerable SkipLastN(this IEnumerable source, int n) { var list = source.ToList(); if ((list.Count - n) > 0) return list.Take(list.Count - n); else return Enumerable.Empty(); } /// /// Removes items of source matching a given predicate. /// /// /// public static void RemoveIf(this IList source, Predicate pred) { if (source is List lst) { lst.RemoveAll(pred); return; } for (int i = source.Count - 1; i >= 0; i--) { if (pred(source[i])) { source.RemoveAt(i); } } } /// /// Removes items of source matching a given predicate. /// /// /// public static void RemoveIf(this IList source, Predicate pred) { for (int i = source.Count - 1; i >= 0; i--) { if (pred(source[i])) { source.RemoveAt(i); } } } private static void flattenHeirarchy(IEnumerable lst, Func> lookup, IList result, HashSet found) { foreach (var item in lst) { if (found != null) { if (found.Contains(item)) continue; found.Add(item); } result.Add(item); var sublist = lookup(item); if (sublist != null) flattenHeirarchy(sublist, lookup, result, found); } } /// /// Flattens a recursive IEnumerable. /// /// /// /// Returns a list of the next level of elements. The returned value is allowed to be null and will in this case be treated like an empty list. /// True if only one of each element should be inserted in the list. /// Buffer to use instead of creating a new list to store the values. This can be used to avoid allocation. /// public static List FlattenHeirarchy(IEnumerable lst, Func> lookup, bool distinct = false, List buffer = null) { if (buffer != null) buffer.Clear(); else buffer = new List(); flattenHeirarchy(lst, lookup, buffer, distinct ? new HashSet() : null); return buffer; } public static void FlattenHeirarchyInto(IEnumerable lst, Func> lookup, ISet set) { foreach (var item in lst) { if (set.Add(item)) { var sublist = lookup(item); if (sublist != null) FlattenHeirarchyInto(sublist, lookup, set); } } } public static void ForEach(this IEnumerable source, Action func) { foreach (var item in source) { func(item); } } /// /// Appends a range of elements to an IEnumerable. /// /// /// /// /// public static IEnumerable Append(this IEnumerable source, params T[] newObjects) { return source.Concat(newObjects); } /// First index where the result of predicate function is true. public static int IndexWhen(this IEnumerable source, Func pred) { int idx = 0; foreach (var item in source) { if (pred(item)) { return idx; } idx++; } return -1; } /// /// Returns true if the generic IEnumerable is empty /// /// /// public static bool IsEnumerableEmpty(this IEnumerable enu) { var e = enu.GetEnumerator(); bool empty = e.MoveNext() == false; (e as IDisposable)?.Dispose(); return empty; } /// /// Returns true if the source is longer than count elements. /// /// /// /// /// public static bool IsLongerThan(this IEnumerable source, long count) { foreach (var _ in source) if (--count < 0) return true; return false; } /// /// Creates a HashSet from an IEnumerable. /// public static HashSet ToHashSet(this IEnumerable source) { return new HashSet(source); } /// /// Creates a HashSet from an IEnumerable, with a specialized comparer. /// public static HashSet ToHashSet(this IEnumerable source, IEqualityComparer comparer) { return new HashSet(source, comparer); } /// /// The opposite of Where. /// /// /// /// /// public static IEnumerable Except(this IEnumerable source, Func selector) { foreach (var x in source) if (selector(x) == false) yield return x; } /// As 'Select' but skipping null values. /// Short hand for/more efficient version of 'Select(f).Where(x => x != null)' /// /// /// /// /// public static IEnumerable SelectValues(this IEnumerable source, Func f) { foreach (var x in source) { var value = f(x); if (value != null) yield return value; } } /// As 'Select and FirstOrDefault' but skipping null values. /// Short hand for/more efficient version of 'Select(f).Where(x => x != null).FirstOrDefault()' /// public static T2 FirstNonDefault(this IEnumerable source, Func selector) { foreach (var x in source) { var value = selector(x); if (Equals(value, default(T2)) == false) return value; } return default(T2); } /// /// Merged a dictionary into another, overwriting colliding keys. /// /// /// /// /// public static void MergeInto(this Dictionary srcDict, Dictionary dstDict) { foreach (var kv in srcDict) { dstDict[kv.Key] = kv.Value; } } struct OnceLogToken { public object Token; public TraceSource Log; } static HashSet logOnceTokens = new HashSet(); /// /// Avoids spamming the log with errors that /// should only be shown once by memorizing token and TraceSource. /// /// True if an error was logged. public static bool ErrorOnce(this TraceSource log, object token, string message, params object[] args) { lock (logOnceTokens) { var logtoken = new OnceLogToken { Token = token, Log = log }; if (!logOnceTokens.Contains(logtoken)) { log.Error(message, args); logOnceTokens.Add(logtoken); return true; } return false; } } public static string ConvertToUnsecureString(this System.Security.SecureString securePassword) { if (securePassword == null) throw new ArgumentNullException("securePassword"); IntPtr unmanagedString = IntPtr.Zero; try { unmanagedString = Marshal.SecureStringToGlobalAllocUnicode(securePassword); return Marshal.PtrToStringUni(unmanagedString); } finally { Marshal.ZeroFreeGlobalAllocUnicode(unmanagedString); } } public static System.Security.SecureString ToSecureString(this string str) { System.Security.SecureString result = new System.Security.SecureString(); foreach (var c in str) result.AppendChar(c); return result; } public static bool IsNumeric(object obj) { switch (obj) { case float _: return true; case double _: return true; case decimal _: return true; case byte _: return true; case char _: return true; case sbyte _: return true; case short _: return true; case ushort _: return true; case int _: return true; case uint _: return true; case long _: return true; case ulong _: return true; default: return false; } } public static bool Compatible(Version searched, Version referenced) { if (searched == null) return true; if (searched.Major != referenced.Major) return false; if (searched.Minor >= referenced.Minor) return true; return false; } /// /// /// /// /// /// /// /// public static T SetFlag(this T e, T flag, bool enabled) where T : struct { if (e is Enum == false) throw new InvalidOperationException("T must be an enum"); int _e = (int)Convert.ChangeType(e, typeof(int)); int _flag = (int)Convert.ChangeType(flag, typeof(int)); int r; if (enabled) r = (_e | _flag); else r = (_e & ~_flag); return (T)Enum.ToObject(typeof(T), r); } public static string EnumToReadableString(Enum value) { if (value == null) return null; var enumType = value.GetType(); var mem = enumType.GetMember(value.ToString()).FirstOrDefault(); if (mem != null) { return TranslationManager.TranslateEnum(value).Name; } if (false == enumType.HasAttribute()) return value.ToString(); var zeroValue = Enum.ToObject(enumType, 0); if (value.Equals(zeroValue)) return ""; // this does not happen if zeroValue is declared. var flags = Enum.GetValues(enumType).OfType(); var activeFlags = flags.Where(value.HasFlag).Except(f => f.Equals(zeroValue)); var result = string.Join(" | ", activeFlags.Select(EnumToReadableString)); if (string.IsNullOrEmpty(result) == false) return result; // last resort. var val = (long)Convert.ChangeType(value, TypeCode.Int64); return val.ToString(); } public static string EnumToDescription(Enum value) { if (value == null) return null; var enumType = value.GetType(); var mem = enumType.GetMember(value.ToString()).FirstOrDefault(); // if member is null, fall back to the readable enum string (or description is null) return mem?.GetDisplayAttribute().Description ?? EnumToReadableString(value); } public static string SerializeToString(this TestPlan plan, bool throwOnErrors = false) { using (var mem = new MemoryStream()) { var serializer = new TapSerializer(); plan.Save(mem, serializer); if (throwOnErrors && serializer.Errors.Any()) throw new Exception(string.Join("\n", serializer.Errors)); return Encoding.UTF8.GetString(mem.ToArray()); } } public static object DeserializeFromString(string str) { return new TapSerializer().DeserializeFromString(str); } public static T DeserializeFromString(string str) => (T)DeserializeFromString(str); class ActionDisposable : IDisposable { Action action; public ActionDisposable(Action action) => this.action = action; public void Dispose() { action(); action = null; } } public static IDisposable WithDisposable(Action action) { return new ActionDisposable(action); } /// Gets or creates a value based on the key. This is useful for caches. public static V GetOrCreateValue(this Dictionary dictionary, K key, Func createValue) { if (dictionary.TryGetValue(key, out V value)) return value; return dictionary[key] = createValue(key); } public static string BytesToReadable(long bytes) { if (bytes < 1000) return $"{bytes} B"; if (bytes < 1000000) return $"{bytes / 1000.0:0.00} kB"; if (bytes < 1000000000) return $"{bytes / 1000000.0:0.00} MB"; return $"{bytes / 1000000000.0:0.00} GB"; } /// Backoff strategy for Retry. static void Backoff(int retry, int sleepBaseMs = 100) { const double backoffFactor = 2.0; const double jitterFactor = 0.2; double baseSleep = Math.Pow(backoffFactor, retry) * sleepBaseMs; // add a bit of jitter to avoid "thundering herd" problems. Random random = new Random(); double jitterAmount = 1.0 + jitterFactor * (random.NextDouble() - 0.5); int totalSleep = (int)(baseSleep * jitterAmount); if (totalSleep > 0) TapThread.Sleep(totalSleep); } /// Retries calling a function a number of times if calling it fails. /// The function to call. /// an exception type to retry on. By default this is typeof(Exception) /// default 5 times /// more exceptions to retry on /// The time to sleep at first iteration. Subsequent sleeps with be longer public static void Retry(Action function, Type retryOn = null, int maxRetries = 5, Type[] moreExceptions = null, int sleepBaseMs = 100) { Retry(() => { function(); return 0; }, retryOn, maxRetries, moreExceptions, sleepBaseMs); } /// Retries calling a function a number of times if calling it fails. /// The function to call. /// an exception type to retry on. By default this is typeof(Exception) /// default 5 times /// more exceptions to retry on /// The time to sleep at first iteration. Subsequent sleeps with be longer /// the return type. /// The return value public static T Retry(Func function, Type retryOn = null, int maxRetries = 5, Type[] moreExceptions = null, int sleepBaseMs = 100) { if (retryOn == null) retryOn = typeof(Exception); var allExceptions = new[] { retryOn }.Concat(moreExceptions ?? Array.Empty()).ToArray(); for (int i = 0; i < maxRetries - 1; i++) { try { return function(); } catch (Exception ex) { while (ex is AggregateException a && a.InnerExceptions.Count == 1) { ex = a.InnerExceptions[0]; } var exType = ex.GetType(); if (allExceptions.Any(x => x.IsAssignableFrom(exType))) { // if one of the expected exceptions. Backoff(i, sleepBaseMs); continue; } ExceptionDispatchInfo.Capture(ex).Throw(); } } // on the last attempt just call the function directly return function(); } } static internal class Sequence { /// Turns item into a one element array, unless it is null. public static T[] AsSingle(this T item) => item == null ? Array.Empty() : new[] {item}; /// /// Like distinct but keeps the last item. Returns List because we need to iterate until last element anyway. /// /// /// /// public static List DistinctLast(this IEnumerable items) { Dictionary d = new Dictionary(); int i = 0; foreach (var item in items) { d[item] = i; i++; } return d.OrderBy(kv => kv.Value).Select(kv => kv.Key).ToList(); } internal static int ProcessPattern(IEnumerator objs, Action f1) { while (objs.MoveNext()) { switch (objs.Current) { case T1 t: f1(t); return 1; } } return 0; } internal static int ProcessPattern(IEnumerator objs, Action f1, Action f2 ) { while (objs.MoveNext()) { switch (objs.Current) { case T1 t: f1(t); return 1 + ProcessPattern(objs, f2); case T2 t: f2(t); return 1 + ProcessPattern(objs, f1); } } return 0; } internal static int ProcessPattern(IEnumerator objs, Action f1, Action f2, Action f3 ) { while (objs.MoveNext()) { switch (objs.Current) { case T1 t: f1(t); return 1 + ProcessPattern(objs, f2, f3); case T2 t: f2(t); return 1 + ProcessPattern(objs, f1, f3); case T3 t: f3(t); return 1 + ProcessPattern(objs, f1, f2); } } return 0; } /// Adds elements that arent null to the list. internal static void AddExceptNull(this ICollection list, T x) { if (x != null) list.Add(x); } internal static int ProcessPattern(IEnumerator objs, Action f1, Action f2, Action f3, Action f4 ) { while (objs.MoveNext()) { switch (objs.Current) { case T1 t: f1(t); return 1 + ProcessPattern(objs, f2, f3, f4); case T2 t: f2(t); return 1 + ProcessPattern(objs, f1, f3, f4); case T3 t: f3(t); return 1 + ProcessPattern(objs, f1, f2, f4); case T4 t: f4(t); return 1 + ProcessPattern(objs, f1, f2, f3); } } return 0; } public static int ProcessPattern(IEnumerator objs, Action f1, Action f2, Action f3, Action f4 , Action f5 ) { while (objs.MoveNext()) { switch (objs.Current) { case T1 t: f1(t); return 1 + ProcessPattern(objs, f2, f3, f4, f5); case T2 t: f2(t); return 1 + ProcessPattern(objs, f1, f3, f4, f5); case T3 t: f3(t); return 1 + ProcessPattern(objs, f1, f2, f4, f5); case T4 t: f4(t); return 1 + ProcessPattern(objs, f1, f2, f3, f5); case T5 t: f5(t); return 1 + ProcessPattern(objs, f1, f2, f3, f4); } } return 0; } public static int ProcessPattern(IEnumerator objs, Action f1, Action f2, Action f3, Action f4 , Action f5, Action f6 ) { while (objs.MoveNext()) { switch (objs.Current) { case T1 t: f1(t); return 1 + ProcessPattern(objs, f2, f3, f4, f5, f6); case T2 t: f2(t); return 1 + ProcessPattern(objs, f1, f3, f4, f5, f6); case T3 t: f3(t); return 1 + ProcessPattern(objs, f1, f2, f4, f5,f6); case T4 t: f4(t); return 1 + ProcessPattern(objs, f1, f2, f3, f5,f6); case T5 t: f5(t); return 1 + ProcessPattern(objs, f1, f2, f3, f4,f6); case T6 t: f6(t); return 1 + ProcessPattern(objs, f1, f2, f3, f4, f5); } } return 0; } public static int ProcessPattern(IEnumerable objs, Action f1, Action f2) { using (var e = objs.GetEnumerator()) return ProcessPattern(e, f1, f2); } public static int ProcessPattern(IEnumerable objs, Action f1, Action f2, Action f3) { using (var e = objs.GetEnumerator()) return ProcessPattern(e, f1, f2, f3); } public static int ProcessPattern(IEnumerable objs, Action f1, Action f2, Action f3, Action f4) { using (var e = objs.GetEnumerator()) return ProcessPattern(e, f1, f2, f3, f4); } public static int ProcessPattern(IEnumerable objs, Action f1, Action f2, Action f3, Action f4, Action f5) { using (var e = objs.GetEnumerator()) return ProcessPattern(e, f1, f2, f3, f4, f5); } public static int ProcessPattern(IEnumerable objs, Action f1, Action f2, Action f3, Action f4, Action f5, Action f6) { using (var e = objs.GetEnumerator()) return ProcessPattern(e, f1, f2, f3, f4, f5, f6); } /// /// Count the number of elements in an enumerable. /// public static int Count(this IEnumerable enumerable) { if (enumerable is ICollection col) return col.Count; int c = 0; foreach (var _ in enumerable) c++; return c; } /// /// iterates lists and generates pairs of each list. Once the end is reached for one of the lists, execution stops. /// public static IEnumerable<(T1, T2)> Pairwise(this IEnumerable a, IEnumerable b) { using(var ia = a.GetEnumerator()) using (var ib = b.GetEnumerator()) { while (ia.MoveNext() && ib.MoveNext()) { yield return (ia.Current, ib.Current); } } } /// /// process the enumerable source N objects at a time. /// public static IEnumerable Batch(this IEnumerable src, int N) { var buffer = new T[N]; int cnt = 0; foreach (var elem in src) { buffer[cnt] = elem; cnt += 1; if (cnt == N) { foreach (var elem2 in buffer) yield return elem2; cnt = 0; } } foreach (var elem2 in buffer.Take(cnt)) yield return elem2; } public static void Append(ref T[] array, params T[] appendage) { int preLen = array.Length; Array.Resize(ref array, array.Length + appendage.Length); Array.Copy(appendage, 0, array, preLen, appendage.Length); } public static IEnumerable TrySelect(this IEnumerable src, Func f, Action handler) => src.TrySelect(f, handler); public static IEnumerable TrySelect(this IEnumerable src, Func f, Action handler) => src.TrySelect(f, (e,v) => handler(e)); public static IEnumerable TrySelect(this IEnumerable src, Func f, Action handler) where T3: Exception { foreach (var x in src) { T2 y; try { y = f(x); } catch(T3 e) { handler(e, x); continue; } yield return y; } } } internal static class Time { /// /// A TimeSpan from seconds that does not round to nearest millisecond. (TimeSpan.FromSeconds does that). /// /// /// public static TimeSpan FromSeconds(double seconds) { try { checked { // if the multiplication here creates a number greater than // long.Max (or overflows negatively), we just return // TimeSpan.MaxValue/MinValue. long ticks = (long)(seconds * 1e7); return TimeSpan.FromTicks(ticks); } } catch (OverflowException) { if (double.IsNaN(seconds)) throw new ArithmeticException("Nan is not a supported time value."); if (seconds < 0) return TimeSpan.MinValue; return TimeSpan.MaxValue; } } } /// Invoke an action after a timeout, unless canceled. class TimeoutOperation : IDisposable { /// Estimate of how long it takes for the user to loose patience. static readonly TimeSpan DefaultTimeout = TimeSpan.FromSeconds(2); TimeoutOperation(TimeSpan timeout, Action action) { this.timeout = timeout; this.action = action; tokenSource = new CancellationTokenSource(timeout); } readonly Action action; readonly TimeSpan timeout; readonly CancellationTokenSource tokenSource; bool isCompleted; void wait() { try { var token = tokenSource.Token; if (!token.IsCancellationRequested && WaitHandle.WaitTimeout == WaitHandle.WaitAny(new [] { token.WaitHandle, TapThread.Current.AbortToken.WaitHandle }, timeout)) action(); } finally { lock (tokenSource) { tokenSource.Dispose(); isCompleted = true; } } } /// Creates a new TimeoutOperation with a specific timeout. /// /// /// public static TimeoutOperation Create(TimeSpan timeout, Action actionOnTimeout) { TimeoutOperation operation = new TimeoutOperation(timeout, actionOnTimeout); TapThread.Start(operation.wait, "Timeout"); return operation; } /// Creates a timeout operation with the default timeout. /// /// public static TimeoutOperation Create(Action actionOnTimeout) { return Create(DefaultTimeout, actionOnTimeout); } /// /// Cancel invoking the action after the timeout. /// public void Cancel() { try { lock (tokenSource) { if (!isCompleted) tokenSource.Cancel(); } } catch (ObjectDisposedException) { } } public void Dispose() { Cancel(); } } }