// 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.Xml.Linq; namespace OpenTap.Plugins { /// Serializer implementation for parameters. [Display("Parameter Serializer", "Works with parameters in the test plan.")] public class ExternalParameterSerializer : TapSerializerPlugin { /// /// Structure for holding data about /// public struct ExternalParamData { /// /// The object /// public ITestStep Object; /// /// The external param property. /// public IMemberData Property; /// /// The name of the external test plan parameter. /// public string Name; } /// The order of this serializer. public override double Order => 50; List currentNode = new List(); /// /// Stores the data if a test plan was not serialized but the external keyword was used. /// public readonly List UnusedExternalParamData = new List(); /// /// Pre-Loaded external parameter Name/Value sets. /// public readonly Dictionary PreloadedValues = new Dictionary(); static readonly XName External = "external"; static readonly XName Scope = "Scope"; static readonly XName Parameter = "Parameter"; bool loadScopeParameter(Guid scope, ITestStep step, IMemberData member, string parameter) { ITestStepParent parent; if (scope == Guid.Empty) { parent = step.GetParent(); } else { ITestStep subparent = step.Parent as ITestStep; while (subparent != null) { if (subparent.Id == scope) break; subparent = subparent.Parent as ITestStep; } parent = subparent; } if (parent == null) return false; var newParameter = member.Parameterize(parent, step, parameter); if (newParameter.ParameterizedMembers.Skip(1).Any()) { // merge occured. See similar code in ExternalParameters. if (ParameterManager.UnmergableListType(newParameter)) { member.Unparameterize(newParameter, step); Log.Warning("Unable merge parameters {0}, since their types do not support it.", parameter); } } return true; } void loadPreloadedvalues(TestPlan plan) { foreach (var value in PreloadedValues) { var ext = plan.ExternalParameters.Get(value.Key); if (ext == null) continue; try { ext.Value = value.Value; } catch { } } // Update all external parameter values. // This is generally redundant, since they were all serialized with the same values. // but just to be sure. foreach (var extParam in plan.ExternalParameters.Entries) extParam.Value = extParam.Value; } XElement rootNode; /// Deserialization implementation. public override bool Deserialize(XElement elem, ITypeData t, Action setter) { if (rootNode == null && t.DescendsTo(typeof(TestPlan))) { rootNode = elem; TestPlan _plan = null; bool ok = Serializer.Deserialize(elem, x=> { _plan = (TestPlan)x; setter(_plan); }, t); // preloaded values should be handled as the absolute last thing. // This should be ensured by DeferredLoadOrder.Last, so we should make sure not to use this priority for other things. Serializer.DeferLoad(() => { loadPreloadedvalues(_plan); }, TapSerializer.DeferredLoadOrder.ExternalParameter); return ok; } if (elem.HasAttributes == false || currentNode.Contains(elem)) return false; var parameter = elem.Attribute(External)?.Value ?? elem.Attribute(Parameter)?.Value; if (string.IsNullOrWhiteSpace(parameter)) return false; var stepSerializer = Serializer.SerializerStack.OfType().FirstOrDefault(); var step = stepSerializer?.Object as ITestStep; if (step == null) return false; var member = stepSerializer.CurrentMember; Guid.TryParse(elem.Attribute(Scope)?.Value, out Guid scope); if (!loadScopeParameter(scope, step, member, parameter)) Serializer.DeferLoad(() => loadScopeParameter(scope, step, member, parameter)); if (scope != Guid.Empty) return false; var plan = Serializer.SerializerStack.OfType().FirstOrDefault()?.Plan; if (plan == null) { currentNode.Add(elem); try { UnusedExternalParamData.Add(new ExternalParamData { Object = (ITestStep) stepSerializer.Object, Property = stepSerializer.CurrentMember, Name = parameter }); return Serializer.Deserialize(elem, setter, t); } finally { currentNode.Remove(elem); } } currentNode.Add(elem); try { bool ok = Serializer.Deserialize(elem, setter, t); var ext = plan.ExternalParameters.Get(parameter); Serializer.DeferLoad(() => { var ext2 = plan.ExternalParameters.Get(parameter); if (ext2 == null) return; if (PreloadedValues.ContainsKey(ext2.Name)) // If there is a preloaded value, use that. ext2.Value = PreloadedValues[ext2.Name]; }); if (ext != null && PreloadedValues.ContainsKey(ext.Name)) // If there is a preloaded value, use that. ext.Value = PreloadedValues[ext.Name]; return ok; } finally { currentNode.Remove(elem); } } /// /// This dictionary is used to cache which parent steps point to which child test steps via a parameter. /// Hence it maps (step,member) to (parent, member). /// Dictionary<(object step, IMemberData member), (object parent, ParameterMemberData parentMember)> parameterReverseLookup = new Dictionary<(object step, IMemberData member), (object parent, ParameterMemberData parentMember)>(); HashSet reversedParents = new HashSet(); /// Serialization implementation. public override bool Serialize( XElement elem, object obj, ITypeData expectedType) { if (currentNode.Contains(elem)) return false; var objSerializer = Serializer.SerializerStack.OfType().FirstOrDefault(); if (objSerializer?.CurrentMember == null || false == objSerializer.Object is ITestStep) return false; ITestStep step = (ITestStep)objSerializer.Object; var member = objSerializer.CurrentMember; // here I need to check if any of its parent steps are forwarding // its member data. { // update reverse lookup. ITestStepParent parameterParent = step.Parent; while (parameterParent != null) { if (reversedParents.Add(parameterParent)) { var parameters = TypeData.GetTypeData(parameterParent) .GetMembers() .OfType(); foreach (var parameter in parameters) { foreach (var set in parameter.ParameterizedMembers) parameterReverseLookup.Add(set, (parameterParent, parameter)); } parameterParent = parameterParent.Parent; } else { break; } } } if (parameterReverseLookup.TryGetValue(((object)step, member), out (object parameterParent, ParameterMemberData parameterMember) parentMember) == false) return false; elem.SetAttributeValue(Parameter, parentMember.parameterMember.Name); if (parentMember.parameterParent is ITestStep parentStep) elem.SetAttributeValue(Scope, parentStep.Id.ToString()); // skip try { currentNode.Add(elem); return Serializer.Serialize(elem, obj, expectedType); } finally { currentNode.Remove(elem); } } } }