// 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<TestVariable>();
|
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<string> SequenceNames
|
{
|
get => plan?.Sequences.Select(s => s.SequenceName).ToList();
|
}
|
|
|
[XmlIgnore]
|
public ObservableCollection<TestStepList> Sequences
|
{
|
get => plan?.Sequences;
|
}
|
|
[Browsable(false)]
|
public string Hash { get; set; }
|
|
[AnnotationIgnore]
|
public string Path => Filepath;
|
|
private ObservableCollection<TestVariable> parameters = new ObservableCollection<TestVariable>();
|
[Display("²ÎÊý")]
|
[BindingList(nameof(Parameters), false)]
|
//[TreeData]
|
public ObservableCollection<TestVariable> 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<string>(null, this, Filepath);
|
}
|
else
|
{
|
realPath = PlanRun.Resolve<string>(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<TestPlan>(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<string>(this, tv.Value));
|
}
|
|
if (tv.Type == TestVariableType.Int)
|
{
|
return new RuntimeVariable(PlanRun, tv.Name, PlanRun.Resolve<int>(this, tv.Value));
|
}
|
|
if (tv.Type == TestVariableType.Double)
|
{
|
return new RuntimeVariable(PlanRun, tv.Name, PlanRun.Resolve<double>(this, tv.Value));
|
}
|
|
if (tv.Type == TestVariableType.Bool)
|
{
|
return new RuntimeVariable(PlanRun, tv.Name, PlanRun.Resolve<bool>(this, tv.Value));
|
}
|
return new RuntimeVariable(PlanRun, tv.Name, PlanRun.Resolve(this, tv.Value));
|
}
|
|
private RuntimeVariable GetMergeContainer()
|
{
|
ConcurrentDictionary<string, RuntimeVariable> pool = new ConcurrentDictionary<string, RuntimeVariable>();
|
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<object> childrenListeners = new List<object>();
|
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<string, XDocument> dict = new Memorizer<string, XDocument>(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<ExternalParameterSerializer>().PreloadedValues.MergeInto(currentSerializer.GetSerializer<ExternalParameterSerializer>().PreloadedValues);
|
var ext = newSerializer.GetSerializer<ExternalParameterSerializer>();
|
|
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<XmlIgnoreAttribute>())
|
continue;
|
member.SetValue(this, member.GetValue(tp));
|
}
|
}
|
|
|
}
|
finally
|
{
|
|
}
|
}
|
catch (Exception ex)
|
{
|
Log.Error("Unable to read '{0}'.", realPath);
|
Log.Error(ex);
|
}
|
}
|
}
|
|
/// <summary> Used to determine if a step ID has changed from the time it was loaded to the time it is saved. </summary>
|
Dictionary<ITestStep, Guid> 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);
|
|
|
|
|
}
|
}
|