using System; using System.Collections.Generic; using System.ComponentModel; using System.Linq; using System.Xml.Linq; using NUnit.Framework; using OpenTap.EngineUnitTestUtils; using OpenTap.Package; using OpenTap.Plugins.BasicSteps; using OpenTap.UnitTests; namespace OpenTap.Engine.UnitTests { [TestFixture] public class SerializerTests { public class TestPlanWithMetaData : TestPlan { [MetaData(Name= "X Setting")] public string X { get; set; } [MetaData] public string Y { get; set; } } [Test] public void TestSerializeTestPlanMetaData() { var plan = new TestPlanWithMetaData(); var xml = plan.SerializeToString(); var xdoc = XDocument.Parse(xml); Assert.AreEqual("X Setting", xdoc.Root.Element("X").Attribute("Metadata").Value); Assert.AreEqual("Y", xdoc.Root.Element("Y").Attribute("Metadata").Value); } public class PackageVersionTestStep : TestStep { public PackageVersion[] PackageVersion { get; set; } public override void Run() { } } [Test] [TestCase("No Trailing Space")] [TestCase("Yes Trailing Space ")] public void TestSerializeInstrumentsWithTrailingSpaceInName(string name) { using var session = Session.Create(SessionOptions.OverlayComponentSettings); var ins = new ScpiInstrument() { Name = name }; var scpiStep1 = new ScpiTestStep() { Instrument = ins }; InstrumentSettings.Current.Add(ins); var plan = new TestPlan() { ChildTestSteps = { scpiStep1 } }; { // Verify deserialization completed without errors var str = plan.SerializeToString(); var ser = new TapSerializer(); var plan2 = ser.DeserializeFromString(str) as TestPlan; var scpiStep2 = plan2.ChildTestSteps[0] as ScpiTestStep; Assert.AreSame(scpiStep1.Instrument, scpiStep2.Instrument); Assert.That(ser.Errors.Count(), Is.EqualTo(0)); } } [Test] public void TestPackageVersionLicenseSerializer() { var packageVersion = new PackageVersion("pkg", SemanticVersion.Parse("1.0.0"), "Linux", CpuArchitecture.AnyCPU, DateTime.Now, new List() { "License 1", "License 2" }); var ser = new TapSerializer(); var str = ser.SerializeToString(packageVersion); var des = ser.DeserializeFromString(str); Assert.AreEqual(packageVersion, des); CollectionAssert.AreEqual(packageVersion.Licenses, ((des as PackageVersion)!).Licenses); } [Test] public void SerializeInputInTestPlanReferenceTest() { using (Session.Create(SessionOptions.OverlayComponentSettings)) { TestTraceListener tapTraceListener = new TestTraceListener(); Log.AddListener(tapTraceListener); const string PlanName = "ParameterizedIfStep.TapPlan"; { // Create child plan var childPlan = new TestPlan(); var ifstep = new IfStep(); childPlan.ChildTestSteps.Add(ifstep); var a = AnnotationCollection.Annotate(ifstep); var m = a.GetMember(nameof(ifstep.InputVerdict)); var items = m.Get().MenuItems.ToArray(); var icons = items.ToLookup(item => item.Get()?.IconName ?? ""); var parameterizeOnTestPlan = icons[IconNames.ParameterizeOnTestPlan].First(); var method = parameterizeOnTestPlan.Get(); method.Invoke(); childPlan.Save(PlanName); } tapTraceListener.Flush(); Assert.IsEmpty(tapTraceListener.ErrorMessage); // Try to load the child plan { var plan = new TestPlan() { ChildTestSteps = { new TestPlanReference() { Filepath = new MacroString() { Text = PlanName } } } }; tapTraceListener.Flush(); Assert.IsEmpty(tapTraceListener.ErrorMessage); var a = AnnotationCollection.Annotate(plan.ChildTestSteps[0]); var m = a.Get().Members.First(m => m.Name == "Load Test Plan"); var load = m.Get(); load.Invoke(); tapTraceListener.Flush(); Assert.IsEmpty(tapTraceListener.ErrorMessage); } } } [Test] public void TestPackageDependencySerializer() { var plan = new TestPlan() { ChildTestSteps = { new DelayStep() } }; var packageVersion = new PackageVersion("pkg", SemanticVersion.Parse("1.0.0"), "Linux", CpuArchitecture.AnyCPU, DateTime.Now, new List()); { // verify that a serialized plan has package dependencies var ser = new TapSerializer(); var str = ser.SerializeToString(plan); CollectionAssert.IsEmpty(ser.Errors); var elem = XElement.Parse(str); Assert.AreEqual(1, elem.Elements("Package.Dependencies").Count()); } { // verify that a serialized collection of plans has package dependencies var ser = new TapSerializer(); var plans = new TestPlan[] { plan, plan }; var str = ser.SerializeToString(plans); CollectionAssert.IsEmpty(ser.Errors); var elem = XElement.Parse(str); Assert.AreEqual(1, elem.Elements("Package.Dependencies").Count()); } { // verify that a serialized package version does not have package dependencies var ser = new TapSerializer(); var str = ser.SerializeToString(packageVersion); CollectionAssert.IsEmpty(ser.Errors); var elem = XElement.Parse(str); Assert.AreEqual(0, elem.Elements("Package.Dependencies").Count()); var deserialized = ser.DeserializeFromString(str); Assert.AreEqual(packageVersion, deserialized); } { // verify that a serialized list of package versions does not have package dependencies var ser = new TapSerializer(); var versions = new PackageVersion[] { packageVersion }; var str = ser.SerializeToString(versions); CollectionAssert.IsEmpty(ser.Errors); var elem = XElement.Parse(str); Assert.AreEqual(0, elem.Elements("Package.Dependencies").Count()); var deserialized = ser.DeserializeFromString(str); if (deserialized is PackageVersion[] versions2) { Assert.AreEqual(1, versions2.Count()); CollectionAssert.AreEqual(versions, versions2, "Deserialized versions were different from the serialized versions."); } else { Assert.Fail($"Failed to deserialize serialized version array."); } } { // Verify that a test plan still has package dependencies when it contains a PackageVersion property var ser = new TapSerializer(); var plan2 = new TestPlan() { ChildTestSteps = { new PackageVersionTestStep() { PackageVersion = new[] { packageVersion } } } }; var str = ser.SerializeToString(plan2); CollectionAssert.IsEmpty(ser.Errors); var elem = XElement.Parse(str); Assert.AreEqual(1, elem.Elements("Package.Dependencies").Count()); } } public interface IPSU : IInstrument { } public class InstrA : Instrument, IPSU { } public class InstrB : Instrument, IPSU { } public class StepA : TestStep { public IPSU Instr { get; set; } public List Instrs { get; set; } = new List(); public override void Run() { } } [Test] public void TestSerializingResourceReferences() { using (Session.Create()) { InstrumentSettings.Current.Clear(); var instr = new InstrA {Name = "AABBCC"}; var instr2 = new InstrB {Name = "AABBCC"}; var instr3 = new InstrB(); var instr4 = new InstrB(); InstrumentSettings.Current.Add(instr); var step = new StepA() { Instr = instr }; step.Instrs.Add(instr); var plan = new TestPlan(); plan.ChildTestSteps.Add(step); var x = plan.SerializeToString(); InstrumentSettings.Current.Remove(instr); InstrumentSettings.Current.AddRange([instr3, instr2, instr4]); var serializer = new TapSerializer(); var plan2 = (TestPlan)serializer.DeserializeFromString(x); var step2 = (StepA)plan2.ChildTestSteps[0]; Assert.AreEqual(instr2, step2.Instr); Assert.AreEqual(instr2, step2.Instrs[0]); var msg = serializer.XmlMessages.FirstOrDefault(); StringAssert.Contains("Selected resource 'AABBCC' of type InstrB instead of declared type InstrA.", msg.ToString()); } } public class StepWithLicenseException : TestStep { public static bool ThrowLicense; public StepWithLicenseException() { if (ThrowLicense) throw new LicenseException(GetType()); } public override void Run() { } } [Test] public void DeserializeLicensedTest() { TestTraceListener tapTraceListener = new TestTraceListener(); using (Session.Create(SessionOptions.RedirectLogging)) { Log.AddListener(tapTraceListener); var step = new StepWithLicenseException(); var plan = new TestPlan(); plan.Steps.Add(step); var str = plan.SerializeToString(); try { StepWithLicenseException.ThrowLicense = true; new TapSerializer().DeserializeFromString(str); } finally { StepWithLicenseException.ThrowLicense = false; } } // there should only be one error message (the license error) Assert.AreEqual(1, tapTraceListener.ErrorMessage.Count); Assert.AreEqual(1, tapTraceListener.ErrorMessage.Count(x => x.Contains("Unable to read StepWithLicenseException. A valid license cannot be granted"))); } [AllowAnyChild] public class TestSerializerChildSteps : TestStep { public TestSerializerChildSteps() { ChildTestSteps.Add(new DelayStep()); ChildTestSteps.Add(new DelayStep()); ChildTestSteps.Add(new DelayStep()); ChildTestSteps.Add(new DelayStep()); } public override void Run() { } } [Test] public void TestDeserializeStep() { { // round one var s = new TestSerializerChildSteps(); s.ChildTestSteps.Clear(); var xml = new TapSerializer().SerializeToString(s); var s2 = new TapSerializer().DeserializeFromString(xml) as TestSerializerChildSteps; Assert.AreEqual(s.ChildTestSteps.Count, s2.ChildTestSteps.Count); } { // round two var chld = new TestSerializerChildSteps(); var plan = new TestPlan() { ChildTestSteps = { chld } }; var expected = chld.ChildTestSteps.Count; Assert.AreEqual(expected, plan.ChildTestSteps[0].ChildTestSteps.Count); Reload(); Assert.AreEqual(expected, plan.ChildTestSteps[0].ChildTestSteps.Count); while (plan.ChildTestSteps[0].ChildTestSteps.Count > 0) { expected = plan.ChildTestSteps[0].ChildTestSteps.Count - 1; plan.ChildTestSteps[0].ChildTestSteps.RemoveAt(0); Reload(); Assert.AreEqual(expected, plan.ChildTestSteps[0].ChildTestSteps.Count); } void Reload() { var planXml = new TapSerializer().SerializeToString(plan); plan = new TapSerializer().DeserializeFromString(planXml) as TestPlan; } } } public class DynamicDependencySerializerPlugin : TapSerializerPlugin, ITapSerializerPluginDependencyMarker { public override double Order => 1000; HashSet visitedNodes = new HashSet(); static XName testPlanName = "TestPlan"; public override bool Deserialize(XElement node, ITypeData t, Action setter) { if (node.Name == testPlanName) { if (visitedNodes.Add(node)) return Serializer.Deserialize(node, setter, t); } return false; } public override bool Serialize(XElement node, object obj, ITypeData expectedType) { if (visitedNodes.Add(node) == false) return false; return Serializer.Serialize(node, obj, expectedType, true); } public bool NeededForDeserialization { get; set; } } public class NestedObject2 { public double X { get; set; } public double Y { get; set; } } public class NestedObject { public NestedObject2 A { get; set; } = new NestedObject2() {X = 1, Y = 2}; public NestedObject2 B { get; set; } = new NestedObject2() {X = 4, Y = 10}; public List Objects { get; set; }= new List(); public double C { get; set; } = 5.0; } [Test] public void TestSerializeNestedObject() { var obj0 = new NestedObject(); obj0.C = 10; obj0.A = new NestedObject2 {X = 3, Y = 3}; obj0.B = new NestedObject2 {X = 10, Y = 11}; obj0.Objects.Add(new NestedObject2 {X = 1, Y = -1}); var serializer = new TapSerializer(); var xml = serializer.SerializeToString(obj0); var obj1 = (NestedObject) serializer.DeserializeFromString(xml); Assert.AreEqual(10.0, obj1.C); Assert.AreEqual(3.0, obj1.A.X); Assert.AreEqual(3.0, obj1.A.Y); Assert.AreEqual(10.0, obj1.B.X); Assert.AreEqual(11.0, obj1.B.Y); Assert.AreEqual(1, obj1.Objects.Count); Assert.AreEqual(1, obj1.Objects[0].X); Assert.AreEqual(-1, obj1.Objects[0].Y); } [TestCase(true)] [TestCase(false)] public void SerializerMaybeUsedType(bool addSerializerPluginDependency) { var serializer = new TapSerializer(); var obj = new TestPlan(); serializer.GetSerializer().NeededForDeserialization = addSerializerPluginDependency; serializer.SerializeToString(obj); var usedTypes = serializer.GetUsedTypes(); var wasUsed = usedTypes.Contains(TypeData.FromType(typeof(DynamicDependencySerializerPlugin))); if (addSerializerPluginDependency) { Assert.IsTrue(wasUsed); } else { Assert.IsFalse(wasUsed); } } public class ObjectWithNullable { // if the value is 0x1337 means it has not been set. If its deliberately set to null // we should not treat it as default. public double? NullableDouble { get; set; } = 0x1337; } [Test] public void TestSerializerDeferOrder() { var ser = new TapSerializer(); int count = 0; Action Assertion(int expected) { return () => { count++; Assert.That(expected, Is.EqualTo(count)); }; } // Even though this defer is inserted first, it should run last ser.DeferLoad(Assertion(7), TapSerializer.DeferredLoadOrder.ExternalParameter); // This defer should run after all normal Defers ser.DeferLoad(Assertion(5), TapSerializer.DeferredLoadOrder.ParameterMemberDataSetter); // These defers should run first although they are inserted late ser.DeferLoad(Assertion(1), TapSerializer.DeferredLoadOrder.Normal); ser.DeferLoad(Assertion(2), TapSerializer.DeferredLoadOrder.Normal); ser.DeferLoad(Assertion(3), TapSerializer.DeferredLoadOrder.Normal); ser.DeferLoad(() => { // The defer inserted in this defer should run before parameter defers ser.DeferLoad(Assertion(4), TapSerializer.DeferredLoadOrder.Normal); }, TapSerializer.DeferredLoadOrder.Normal); ser.DeferLoad(() => { // This defer is inserted during the parameter defer phase, and is expected to run before the external parameter defers ser.DeferLoad(Assertion(6), TapSerializer.DeferredLoadOrder.Normal); }, TapSerializer.DeferredLoadOrder.ParameterMemberDataSetter); Assert.That(count, Is.EqualTo(0)); ser.Flush(); Assert.That(count, Is.EqualTo(7)); } [Test] public void SerializeDeserializePropertyWithNullable() { var a = new ObjectWithNullable { NullableDouble = 5 }; var b = new ObjectWithNullable() { NullableDouble = null }; // The problem only occurs if the type is not explicit. Note in the future we will probably // not require explicit type since it can be inferred through reflection, so this should work without. var aXml = new TapSerializer().SerializeToString(a).Replace("type=\"System.Double\"", ""); var bXml = new TapSerializer().SerializeToString(b).Replace("type=\"System.Double\"", ""); var a2 = (ObjectWithNullable)new TapSerializer().DeserializeFromString(aXml); var b2 = (ObjectWithNullable)new TapSerializer().DeserializeFromString(bXml); Assert.AreEqual(a.NullableDouble, a2.NullableDouble); Assert.AreEqual(b.NullableDouble, b2.NullableDouble); } } }