/
BreakSerializerExample.cs
129 lines (110 loc) · 6.05 KB
/
BreakSerializerExample.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
// Sometimes you need to introduce a breaking change in a plugin library.
// Other times you find that two steps does the same, and you want to consolidate the two.
// In any case, you might prefer that the old test plans are still able to load.
// this example shows how to use a ITapSerializerPlugin to migrate and load old test plans in with new plugins.
using System;
using System.Xml.Linq;
using OpenTap;
namespace PluginDevelopment.Advanced_Examples
{
[Display("Old Delay Step", "Demonstrates an old version of test step which is wanted to migrate to a new version.",
Groups: new[] { "Examples", "Plugin Development", "Advanced Examples" })]
public class OldDelayStep : TestStep
{
public int DelayMs { get; set; }
public override void Run()
{
TapThread.Sleep(DelayMs);
}
}
[Display("New Delay Step", "Demonstrates an new version of test step which is wanted to migrate." +
" To test this, try creating an 'Old Delay Step', save and load. The New Delay Step should have been created.",
Groups: new[] { "Examples", "Plugin Development", "Advanced Examples" })]
public class NewDelayStep : TestStep
{
public double DelaySec { get; set; }
public override void Run()
{
TapThread.Sleep(TimeSpan.FromSeconds(DelaySec));
}
}
public class BreakingChangeFixupSerializer : ITapSerializerPlugin
{
static XName steps = "Steps";
static XName typeattr = "type";
static XName parameterattr = "Parameter";
static XName scopeAttr = "Scope";
static XName delayMsElem = "DelayMs";
static XName delaySecElem = "DelaySec";
static XName postScaleAttr = "post-scale";
// the type we want to replace.
static string searchType = "PluginDevelopment.Advanced_Examples.OldDelayStep";
static string replaceType = "PluginDevelopment.Advanced_Examples.NewDelayStep";
static readonly TraceSource log = Log.CreateSource("Fix Serializer");
void iterateAndReplaceTypesInXml(XElement node, XElement testPlanNode)
{
if (node.Attribute(typeattr) is XAttribute typeAttribute && typeAttribute.Value == searchType)
{
// we discovered the type
typeAttribute.Value = replaceType;
var valueElem = node.Element(delayMsElem);
valueElem.Name = delaySecElem; // change the name of the node to DelaySec
valueElem.Add(new XAttribute(postScaleAttr, 0.001));
if (valueElem.Attribute(parameterattr) is XAttribute parameterAttribute &&
valueElem.Attribute(scopeAttr) == null)
{
// the property is a test plan parameter. This will give issues with pre-scaling.
// in this case it could simply be removed from the test plan node.
// but it will give issues if other test steps has properties that are merged with the same parameter.
testPlanNode.Element(parameterAttribute.Value)?.Remove();
// but also print a warning.
log.Warning("fixing member that is being used in the test plan parameter '{0}'. Please verify the value of this parameter.", parameterAttribute.Value);
}
}
foreach(var subnode in node.Elements())
iterateAndReplaceTypesInXml(subnode, testPlanNode);
}
public bool Deserialize(XElement node, ITypeData t, Action<object> setter)
{
// 1. 'Iterate and Replace' if the node is the test plan node, we want to find all test step sub-nodes and replace the types.
if(t.DescendsTo(typeof(TestPlan)))
iterateAndReplaceTypesInXml(node, node);
else if (node.Attribute(postScaleAttr) is XAttribute attribute)
{
// 2. do post-scaling.
// when the old step was detected the property was marked with this post-scale attribute to show that it should scale it after deserialization.
// this is because the old step used milliseconds, but the new one uses seconds.
attribute.Remove(); // remove the attribute to avoid hitting this again.
// this new setter applies the post-scaling that was added to the attributes during 'iterate and replace'.
Action<object> newSetter = x => setter(double.Parse(x.ToString()) * double.Parse(attribute.Value));
// call the deserializer to actually deserialize the property, but direct it to use the new setter instead of the original.
return TapSerializer.GetCurrentSerializer().Deserialize(node, newSetter, t);
}
return false;
}
// Do nothing during serialization.
public bool Serialize(XElement node, object obj, ITypeData expectedType) => false;
// The right order can be hard to determine, but many different values works!.
// the best is probably to run some code to check which other serializers is already installed (see below).
// this specific serializer should be run early in the chain.
// if a serializer has high order it will get called early.
public double Order => 5;
// consider calling this code during debugging to list the existing serializers.
void printExistingSerializerPlugins()
{
log.Info("Deserializers:");
foreach (var type in TypeData.GetDerivedTypes<ITapSerializerPlugin>())
{
if (type.CanCreateInstance == false) continue;
try
{
var plugin = (ITapSerializerPlugin)type.CreateInstance(Array.Empty<object>());
log.Info("{0} Order: {1}", type.Name, plugin.Order);
}
catch
{
}
}
}
}
}