// 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 OpenTap.Addin; using OpenTap.Addin.Annotation; using OpenTap.Addin.Listener; using OpenTap.Addin.Reader; using OpenTap.Addin.Util; using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.ObjectModel; using System.ComponentModel; using System.Diagnostics; using System.IO; using System.Linq; using System.Text; using System.Xml.Linq; using System.Xml.Serialization; namespace OpenTap.Plugins.BasicSteps { [Display("Sequence Call", Description: "µ÷ÓÃ×ÓÐòÁÐ")] public class SequenceCallStep : TestStep { protected override void OnRootChanged(TestPlan old, TestPlan plan) { LoadPlan(); } //public List<> ITestStepParent parent; // The PlanDir of 'this' should be ignored when calculating Filepath, so the MacroString context is set to the parent. [XmlIgnore] public override ITestStepParent Parent { get => parent; set { parent = value; } } string currentlyLoaded; public bool CanOpenFile => File.Exists(Filepath); private string filePath; [Display("Îļþ", Order: 0, Description: "A file path pointing to a test plan which will be loaded as read-only test steps.")] [Browsable(true)] [FilePath(FilePathAttribute.BehaviorChoice.Open, "TapPlan | *.TapPlan", 1)] [DeserializeOrder(1.0)] [Unsweepable] public string Filepath { get => filePath; set { filePath = value; OnPropertyChanged(nameof(Filepath)); try { LoadPlan(); } catch { } } } [Obsolete] public bool ShareGlobals { get; set; } private string realPath; private bool currentFile; [Display("µ±Ç°Îļþ")] public bool CurrentFile { get => currentFile; set { currentFile = value; OnPropertyChanged(nameof(CurrentFile)); try { LoadPlan(); } catch { } } } private string sequenceName; [Display("ÐòÁÐÃû")] [DynamicSelect("SequenceNames")] public string SequenceName { get => sequenceName; set { sequenceName = value; var seq = Sequences?.FirstOrDefault(e => e.SequenceName == value); if (seq != null) { var list = new ObservableCollection(); foreach (var tv in seq.Parameters) { var newone = new TestVariable { Name = tv.Name, IsArray = tv.IsArray, Type = tv.Type }; var old = Parameters.FirstOrDefault(e => e.Name == tv.Name); if (old != null) { newone.Value = old.Value; } list.Add(newone); } Parameters = list;// seq.Parameters; } OnPropertyChanged(nameof(SequenceName)); } } [XmlIgnore] public List SequenceNames { get => plan?.Sequences.Select(s => s.SequenceName).ToList(); } [XmlIgnore] public ObservableCollection Sequences { get => plan?.Sequences; } [Browsable(false)] public string Hash { get; set; } [AnnotationIgnore] public string Path => Filepath; private ObservableCollection parameters = new ObservableCollection(); [Display("²ÎÊý")] [BindingList(nameof(Parameters), false)] //[TreeData] public ObservableCollection Parameters { get => parameters; set { parameters = value; OnPropertyChanged(nameof(Parameters)); } } public SequenceCallStep() { } private void RefreshPlan(string path) { if (CurrentFile) { plan = this.Root; } else { var newSerializer = new TapSerializer(); var timer = Stopwatch.StartNew(); var doc = ReadCachedXmlFile(path); Log.Debug(timer, $"Read Sequence Xml {path}"); //plan = (TestPlan)newSerializer.Deserialize(doc, TypeData.FromType(typeof(TestPlan)), true, path); plan = TestPlanReader.ReadTestPlanFromDocument(doc, path); OnPropertyChanged(nameof(SequenceNames)); using (var algo = System.Security.Cryptography.SHA1.Create()) { using (var ms = new MemoryStream()) { doc.Save(ms, SaveOptions.DisableFormatting); Hash = BitConverter.ToString(algo.ComputeHash(ms.ToArray()), 0, 8) .Replace("-", string.Empty); } } } } private string GetPlanPath() { if (CurrentFile) { return this.plan?.VisualPath; } if (PlanRun == null) { // ¼æÈÝδÔËÐеÄÇé¿ö£¬ÕâÀﻹҪÓÅ»¯ realPath = VariableResolverFactory.DEFAULT.Resolve(null, this, Filepath); } else { realPath = PlanRun.Resolve(this, Filepath); } if (!string.IsNullOrEmpty(realPath)) { var refPlanPath = realPath; return refPlanPath.Replace('\\', '/'); } return realPath; } private void LoadPlan() { if (this.Root == null) { return; } if (CurrentFile) { plan = this.Root; OnPropertyChanged(nameof(SequenceNames)); } else { string path = GetPlanPath(); if (string.IsNullOrEmpty(path)) { return; } string p1 = System.IO.Path.GetFullPath(path); string p2 = System.IO.Path.GetFullPath(this.Root.VisualPath); if (string.Equals(p1, p2, StringComparison.OrdinalIgnoreCase)) { plan = this.Root; OnPropertyChanged(nameof(SequenceNames)); return; } if (!File.Exists(path)) { Log.Warning("File does not exist: \"{0}\"", path); return; } try { RefreshPlan(path); } catch (Exception ex) { Log.Error("Unable to read '{0}'.", realPath); Log.Error(ex); } } } public override void PrePlanRun() { string path = GetPlanPath(); if (string.IsNullOrEmpty(path)) { throw new ArgumentException("Path Can't be empty!"); } if (!File.Exists(path)) { throw new ArgumentException($"File does not exist: \"{path}\""); } RefreshPlan(path); var xml = plan.SerializeToString(); using (var reader = new MemoryStream(Encoding.UTF8.GetBytes(xml))) { runningPlan = TestPlanReader.ReadTestPlanFromStream(reader, plan.Path); } //runningPlan = Utils.DeserializeFromString(xml); runningPlan.PrintTestPlanRunSummary = false; runningPlan.VisualPath = plan.VisualPath; } private RuntimeVariable Convert(TestVariable tv) { if (tv.Type == TestVariableType.String) { return new RuntimeVariable(PlanRun, tv.Name, PlanRun.Resolve(this, tv.Value)); } if (tv.Type == TestVariableType.Int) { return new RuntimeVariable(PlanRun, tv.Name, PlanRun.Resolve(this, tv.Value)); } if (tv.Type == TestVariableType.Double) { return new RuntimeVariable(PlanRun, tv.Name, PlanRun.Resolve(this, tv.Value)); } if (tv.Type == TestVariableType.Bool) { return new RuntimeVariable(PlanRun, tv.Name, PlanRun.Resolve(this, tv.Value)); } return new RuntimeVariable(PlanRun, tv.Name, PlanRun.Resolve(this, tv.Value)); } private RuntimeVariable GetMergeContainer() { ConcurrentDictionary pool = new ConcurrentDictionary(); foreach (var tv in Parameters) { pool[tv.Name] = Convert(tv); } return new RuntimeVariable(PlanRun, ConstNames.ParametersName, pool); } protected override void RunDetail() { try { var loglisteners = OpenTap.Log.GetListeners(); var resultSetting = ResultSettings.Current; var fileGlobalsContext = PlanRun.fileGlobalsContext; List childrenListeners = new List(); using (Session.Create()) { var sb = ResultSettings.Current; foreach (var listener in loglisteners) { OpenTap.Log.AddListener(listener); } // Ìí¼Ó×Ó±¨¸æ foreach (var listener in resultSetting) { if (listener is RootListener rl) { var child = rl.CreateChild(); childrenListeners.Add(child); ResultSettings.Current.Add(child); } } TestPlanRun subRun; subRun = runningPlan.Execute(SequenceName, sb, null, null, new VariableContainer(GetMergeContainer(), fileGlobalsContext, this.PlanRun.StationGlobalsRuntime)); UpgradeVerdict(subRun.Verdict); foreach (var listener in loglisteners) { OpenTap.Log.RemoveListener(listener); } foreach (var listener in childrenListeners) { ResultSettings.Current.Remove(listener); } } } catch (Exception ex) { Log.Error("Sequence Call Error. {0}", ex); this.Verdict = Verdict.Error; } } static readonly Memorizer dict = new Memorizer(p => { return XDocument.Load(p, LoadOptions.SetLineInfo); }) { // Validator is to reload the file if it has been changed. // Assuming it is much faster to check file write time than to read and parse it. Testing has verified this. Validator = str => { var file = new FileInfo(str); return $"{file.LastWriteTime} {file.Length}"; }, MaxNumberOfElements = 100 }; static XDocument ReadCachedXmlFile(string path) { var cached = dict.Invoke(path); // The deserializer may modify the XDocument class so it must be cloned by constructing a new XDocument (this causes a deep clone to be made). return new XDocument(cached); } [XmlIgnore] internal TestPlan plan; [XmlIgnore] public TestPlan runningPlan { get; private set; } string loadedPlanPath; void UpdateStep() { if (CurrentFile) { plan = this.Root; OnPropertyChanged(nameof(SequenceNames)); // Sequences = plan?.Sequences; } else { object testplandir = null; var currentSerializer = TapSerializer.GetObjectDeserializer(this); if (currentSerializer != null && currentSerializer.ReadPath != null) testplandir = System.IO.Path.GetDirectoryName(currentSerializer.ReadPath); var refPlanPath = realPath; refPlanPath = refPlanPath.Replace('\\', '/'); if (!File.Exists(refPlanPath)) { Log.Warning("File does not exist: \"{0}\"", refPlanPath); return; } try { try { var newSerializer = new TapSerializer(); if (currentSerializer != null) newSerializer.GetSerializer().PreloadedValues.MergeInto(currentSerializer.GetSerializer().PreloadedValues); var ext = newSerializer.GetSerializer(); loadedPlanPath = realPath; var doc = ReadCachedXmlFile(refPlanPath); TestPlan tp = (TestPlan)newSerializer.Deserialize(doc, TypeData.FromType(typeof(TestPlan)), true, refPlanPath); plan = tp; OnPropertyChanged(nameof(SequenceNames)); //Sequences = tp.Sequences; using (var algo = System.Security.Cryptography.SHA1.Create()) { using (var ms = new MemoryStream()) { doc.Save(ms, SaveOptions.DisableFormatting); Hash = BitConverter.ToString(algo.ComputeHash(ms.ToArray()), 0, 8) .Replace("-", string.Empty); } } if (currentSerializer == null) { // if currentSerializer is set, it means that we are loading a previously saved test plan reference. // Hence these things will be automatically set up. // otherwise we need to trasfer mixins and dynamic member values. // transfer mixins var thisType = TypeData.GetTypeData(this); var s = TypeData.GetTypeData(tp).GetMembers(); foreach (var member in TypeData.GetTypeData(tp).GetMembers()) { if (member is MixinMemberData mixinMember) { if (thisType.GetMember(member.Name) != null) continue; var mixin = mixinMember.Source; var mem = mixin.ToDynamicMember(thisType); if (mem == null) { if (mixin is IValidatingObject validating && validating.Error is string err && string.IsNullOrEmpty(err) == false) { Log.Error($"Unable to load mixin: {err}"); } else { Log.Error($"Unable to load mixin: {TypeData.GetTypeData(mixin)?.GetDisplayAttribute()?.Name ?? mixin.ToString()}"); } continue; } // transfer the value from the test plan instance to this instance. var value = member.GetValue(tp); DynamicMember.AddDynamicMember(this, mem); mem.SetValue(this, value); } } // transfer dynamic member values. foreach (var member in TypeData.GetTypeData(tp).GetMembers().Where(mem => mem is DynamicMember)) { if (member.HasAttribute()) continue; member.SetValue(this, member.GetValue(tp)); } } } finally { } } catch (Exception ex) { Log.Error("Unable to read '{0}'.", realPath); Log.Error(ex); } } } /// Used to determine if a step ID has changed from the time it was loaded to the time it is saved. Dictionary StepIdMapping { get; set; } [Browsable(true)] //[Display("Load Test Plan", Order: 1, Description: "Load the selected test plan.")] [EnabledIf(nameof(CanOpenFile))] public void LoadTestPlan() => loadTestPlan(); public void loadTestPlan() { if (!CurrentFile && string.IsNullOrWhiteSpace(realPath)) { return; } UpdateStep(); } string GetPath() { object testplandir = null; var currentSerializer = TapSerializer.GetObjectDeserializer(this); if (currentSerializer != null && currentSerializer.ReadPath != null) testplandir = System.IO.Path.GetDirectoryName(currentSerializer.ReadPath); var refPlanPath = realPath; refPlanPath = refPlanPath.Replace('\\', '/'); return refPlanPath; } public bool anyStepsLoaded => ChildTestSteps.Any() && GetPath() is string path && File.Exists(path); } }