using AddInPlugin.Entity; using OpenTap; using OpenTap.Addin.Annotation; using OpenTap.Addin.Util; using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.ObjectModel; using System.IO; using System.Linq; using System.Reflection; using System.Xml.Serialization; using UtilLib; namespace AddInPlugin { public class DllData { public DateTime lastUpdateTime { get; set; } public Assembly assembly { get; set; } public ObservableCollection types { get; set; } public DllData(DateTime lastUpdateTime, string[] types) { this.lastUpdateTime = lastUpdateTime; this.types = new ObservableCollection(types); } } public class DllLoader { private DllLoader() { } private static object lockObj = new object(); private ConcurrentDictionary loadedCache = new ConcurrentDictionary(); private ConcurrentDictionary> loadedMethod = new ConcurrentDictionary>(); private static DllLoader instance; public static DllLoader Instance { get { lock (lockObj) { if (instance == null) { instance = new DllLoader(); } return instance; } } } public Assembly LoadAssembly(string path, out ObservableCollection typeNames) { path = path?.ToLower(); typeNames = null; if (!File.Exists(path)) { return null; } DateTime newTime = File.GetLastWriteTime(path); if (!loadedCache.ContainsKey(path) || loadedCache[path].lastUpdateTime != newTime) { var assembly = Assembly.LoadFrom(path); var types = assembly.GetTypes(); var data = new DllData(newTime, types.Select(e => e.ToString()).ToArray()) { assembly = assembly, }; loadedCache[path] = data; typeNames = data.types; } typeNames = loadedCache[path].types; return loadedCache[path].assembly; } //public List LoadMethods(Type type) { // if (!loadedMethod.ContainsKey(type)) { // loadedMethod[type] = MethodInfoHelper.GetPublicMethodNames(type); // } //} } public enum VariableDirection { In = 0, Out = 1 } public class MethodVariable : TapNotifyBase { private string name; [DataGridCol("Ãû³Æ", "Auto", true)] public string Name { get => name; set { Set(ref name, value); } } [XmlIgnore] public Type Type { get; set; } [XmlIgnore] public object Value { get; set; } private VariableDirection direction; [DataGridCol("·½Ïò", "Auto", true)] public VariableDirection Direction { get => direction; set { Set(ref direction, value); } } private string typeName; [DataGridCol("ÀàÐÍ", "Auto", true)] public string TypeName { get => typeName; set { Set(ref typeName, value); } } private bool log; [DataGridCol("Log", "Auto", false)] public bool Log { get => log; set { Set(ref log, value); } } private string valueStr; [DataGridCol("Öµ", "*", false, true)] public string ValueStr { get => valueStr; set { Set(ref valueStr, value); } } } [Prototype] [Display("DotNetStep", Description: "ÔËÐÐ.Net")] public class DotNetStep : VariableTestStep { #region Settings private string dllPath; [Obsolete] //[Display("·¾¶", Order: 0.0, Description: "Dll¾ø¶Ô·¾¶.")] [FilePath(FilePathAttribute.BehaviorChoice.Open)] public string DllPath { get => null; set { if (dllPath != value) { Set(ref dllPath, value); // LoadDll(); } } } public bool ShouldSerializeDllPath() { return false; } private LocationString dllLocation = new LocationString(); [Display("·¾¶", Order: 0.0, Description: "Dll¾ø¶Ô·¾¶.")] [FilePath(FilePathAttribute.BehaviorChoice.Open, fileExtension: "dll|*.dll")] public LocationString DllLocation { get => dllLocation; set { Set(ref dllLocation, value); } } private Assembly assembly; private Type targetType; private ObservableCollection types; [XmlIgnore] public ObservableCollection Types { get => types; set { types = value; OnPropertyChanged(nameof(Types)); } } private string className; [Display("ÀàÃû", Order: 0.0, Description: "ÀàÃû.")] [DynamicSelect("Types")] public string ClassName { get => className; set { if (loadException == null) { Set(ref className, value); if (!string.IsNullOrEmpty(value)) { LoadType(); } } } } private ObservableCollection methods; [XmlIgnore] public ObservableCollection Methods { get => methods; set { Set(ref methods, value); } } private string methodName; [Display("·½·¨", Order: 0.0, Description: "·½·¨Ãû.")] //[DynamicSelect(nameof(MethodName), "ClassName", "RefreshMethodName")] [DynamicSelect("Methods")] public string MethodName { get => methodName; set { if (loadException == null) { Set(ref methodName, value); if (!string.IsNullOrEmpty(value)) { RefreshVariables(); } } } } private Dictionary methodCache = new Dictionary(); [XmlIgnore] private object instance; #endregion private Exception loadException; [XmlIgnore] public Exception LoadException { get => loadException; set { Set(ref loadException, value); } } public DotNetStep() { // ToDo: Set default values for properties / settings. //Rules.Add(() => !string.IsNullOrWhiteSpace(DllPath), "·¾¶²»ÄÜΪ¿Õ", DllPath); //Rules.Add(() => DllLocation.GetLocation(Root.Path, out _), "·¾¶²»´æÔÚ", nameof(DllLocation)); Rules.Add(() => DllLocation.GetLocation("C:\\Users\\chrry\\Desktop\\Release\\Api\\test.plan", out _), "·¾¶²»´æÔÚ", nameof(DllLocation)); Rules.Add(() => !string.IsNullOrWhiteSpace(ClassName), "ÀàÃû²»ÄÜΪ¿Õ", nameof(ClassName)); Rules.Add(() => LoadException == null, () => $"Dll¼ÓÔØÊ§°Ü:{LoadException}", nameof(DllLocation)); } private void DllLocation_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e) { LoadDll(); } protected override void OnRootChanged(TestPlan old, TestPlan plan) { try { if (!string.IsNullOrEmpty(dllPath)) { DllLocation.AbsoluteLocation = dllPath; dllPath = null; } DllLocation.Refresh(Root?.Path); DllLocation.PropertyChanged += DllLocation_PropertyChanged; if (!DllLocation.GetLocation(Root.Path, out string path)) { throw new Exception("Dll Exception"); } assembly = Assembly.LoadFrom(path); Types = new ObservableCollection(assembly.GetTypes().Select(e => e.ToString()).ToArray()); LoadType(); RefreshVariables(); LoadException = null; } catch (Exception ex) { LoadException = ex; //Log.Error("DotNetStep load dll error. {0}", ex); } } //private void LoadDll() //{ // try // { // if (string.IsNullOrEmpty(DllPath)) // { // return; // } // assembly = Assembly.LoadFrom(DllPath); // Types = new ObservableCollection(assembly.GetTypes().Select(e => e.ToString()).ToArray()); // LoadException = null; // } // catch (Exception ex) // { // LoadException = ex; // //Log.Error("DotNetStep load dll error. {0}", ex); // } //} private void LoadDll() { try { if (!DllLocation.GetLocation(Root?.Path, out string path)) { throw new Exception("Dll Exception"); } assembly = DllLoader.Instance.LoadAssembly(path, out var types); Types = types; LoadException = null; } catch (Exception ex) { LoadException = ex; //Log.Error("DotNetStep load dll error. {0}", ex); } } public void LoadType() { methodCache.Clear(); try { targetType = assembly?.GetType(ClassName); if (targetType != null) { var creators = targetType?.GetConstructors(); if (creators != null) { for (int i = 0; i < creators.Length; i++) { var creator = creators[i]; var ps = creator.GetParameters(); var displayName = $"{targetType.Name} ({string.Join(", ", ps.Select(p => $"{p.ParameterType.ToString()} {p.Name}").ToArray())})"; methodCache[displayName] = creator; } } var methods = MethodInfoHelper.GetPublicMethodNames(targetType); if (methods != null) { for (int i = 0; i < methods.Count; i++) { var method = methods[i]; var ps = method.GetParameters(); var displayName = $"{method.Name} ({string.Join(", ", ps.Select(p => $"{p.ParameterType.ToString()} {p.Name}").ToArray())})"; methodCache[displayName] = method; } } } Methods = new ObservableCollection(methodCache.Keys.ToArray()); } catch (Exception ex) { Log.Error("DotNetStep LoadTypes error. {0}", ex); } } private void ConcatVariable(MethodVariable mv, MethodVariable[] source) { MethodVariables.Add(mv); var preview = source?.FirstOrDefault(v => v.Name == mv.Name && !string.IsNullOrEmpty(v.ValueStr)); if (preview != null) { mv.ValueStr = preview.ValueStr; } } public void RefreshVariables() { if (MethodVariables == null) { MethodVariables = new ObservableCollection(); } var last = MethodVariables?.ToArray(); MethodVariables.Clear(); try { if (!methodCache.ContainsKey(MethodName)) { return; } var method = methodCache[MethodName]; var ps = method.GetParameters(); var IsStatic = method.IsStatic; var IsConstructorMethod = method.IsConstructor; if (IsConstructorMethod) { var newV = new MethodVariable { Name = "(return)", Type = targetType, TypeName = targetType?.ToString(), Direction = VariableDirection.Out, }; ConcatVariable(newV, last); } else if (!IsStatic) { var mi = (MethodInfo)method; var newV = new MethodVariable { Name = "(instance)", Type = targetType, TypeName = targetType?.ToString(), Direction = VariableDirection.In, }; ConcatVariable(newV, last); newV = new MethodVariable { Name = "(return)", Type = mi.ReturnType, TypeName = mi.ReturnType?.ToString(), Direction = VariableDirection.Out, }; ConcatVariable(newV, last); } else { var mi = (MethodInfo)method; ConcatVariable(new MethodVariable { Name = "(return)", Type = mi.ReturnType, TypeName = mi.ReturnType?.ToString(), Direction = VariableDirection.Out, }, last); } foreach (var p in ps) { ConcatVariable(new MethodVariable { Name = p.Name, Type = p.ParameterType, TypeName = p.ParameterType.ToString(), Direction = p.IsOut ? VariableDirection.Out : VariableDirection.In, }, last); } // MethodVariables = variables; } catch (Exception ex) { Log.Error("DotNetStep LoadVariables error. {0}", ex); } } public override void PrePlanRun() { // ToDo: Optionally add any setup code this step needs to run before the testplan starts base.PrePlanRun(); } /// /// ת»»²ÎÊý /// private void ResolveVariables() { var method = methodCache[methodName]; var IsStatic = method.IsStatic; var IsConstructorMethod = method.IsConstructor; if (IsConstructorMethod) { //methodParams = new object[MethodVariables.Count - 1]; for (int i = 1; i < MethodVariables.Count; i++) { MethodVariable mv = MethodVariables[i]; if (mv.Direction == VariableDirection.In) { mv.Value = PlanRun.Resolve(this, mv.ValueStr, mv.Type); // mv.GetValue(PlanRun); } //methodParams[i-1] = mv.GetValue(PlanRun); } } else if (IsStatic) { //methodParams = new object[MethodVariables.Count - 2]; for (int i = 1; i < MethodVariables.Count; i++) { MethodVariable mv = MethodVariables[i]; if (mv.Direction == VariableDirection.In) { mv.Value = PlanRun.Resolve(this, mv.ValueStr, mv.Type); //mv.GetValue(PlanRun); } //methodParams[i] = mv.GetValue(PlanRun); } } else { //methodParams = new object[MethodVariables.Count - 1]; for (int i = 1; i < MethodVariables.Count; i++) { var mv = MethodVariables[i]; if (mv.Direction == VariableDirection.In) { mv.Value = PlanRun.Resolve(this, mv.ValueStr, mv.Type); //mv.GetValue(PlanRun); } //methodParams[i] = mv.GetValue(PlanRun); } } } /// /// ÔËÐк¯Êý /// private void RunMethod() { ResolveVariables(); var method = methodCache[methodName]; var IsStatic = method.IsStatic; var IsConstructorMethod = method.IsConstructor; if (IsConstructorMethod) { var constructor = (ConstructorInfo)methodCache[methodName]; var methodParams = new object[MethodVariables.Count - 1]; for (int i = 1; i < MethodVariables.Count; i++) { var mv = MethodVariables[i]; methodParams[i - 1] = mv.Value; } instance = constructor.Invoke(methodParams); MethodVariables[0].Value = instance; } else if (IsStatic) { var methodParams = new object[MethodVariables.Count - 1]; for (int i = 1; i < MethodVariables.Count; i++) { var mv = MethodVariables[i]; methodParams[i - 1] = mv.Value; } MethodVariables[0].Value = method.Invoke(null, methodParams); for (int i = 0; i < methodParams.Length; i++) { MethodVariables[i + 1].Value = methodParams[i]; } } else { var methodParams = new object[MethodVariables.Count - 2]; for (int i = 2; i < MethodVariables.Count; i++) { var mv = MethodVariables[i]; methodParams[i - 2] = mv.Value; } instance = PlanRun.Resolve(this, MethodVariables[0].ValueStr); MethodVariables[1].Value = method.Invoke(instance, methodParams); for (int i = 0; i < methodParams.Length; i++) { MethodVariables[i + 2].Value = methodParams[i]; } } } protected override void RunDetail() { try { // ToDo: Add test case code here //RunChildSteps(); //If step has child steps. RunMethod(); UpdateVariableToRuntime(); this.Verdict = Verdict.Pass; } catch (Exception ex) { Log.Error("DotNetStep Run error. {0}", ex); this.Verdict = Verdict.Error; } } } }