using System.Globalization; using NUnit.Framework; using OpenTap.Plugins.BasicSteps; namespace OpenTap.UnitTests; [TestFixture] public class ParameterTests { [Test] public void TestMultiLevelParameters() { // copying multi-level parameters has been a problem because the parameter got removed // the moment it was no longer a part of the test plan, even if the parameter was inside the scopes. var seq3 = new SequenceStep(); var seq = new SequenceStep(); var seq2 = new SequenceStep(); var delay = new DelayStep(); seq.ChildTestSteps.Add(seq2); seq2.ChildTestSteps.Add(delay); seq3.ChildTestSteps.Add(seq); TypeData.GetTypeData(delay).GetMember(nameof(delay.DelaySecs)).Parameterize(seq, delay, "A"); Assert.IsTrue(TypeData.GetTypeData(seq).GetMember("A") != null); seq3.ChildTestSteps.RemoveItems([seq]); Assert.IsTrue(TypeData.GetTypeData(seq).GetMember("A") != null); } [Test] public void TestMemberDataCacheConsistency() { const string correct = "Correct"; const string garbage = "Garbage"; var step = new DelayStep { Name = correct }; // This is an extremely unlikely sequence of reads and writes, // but this is esentially what can sometimes happen when writing // different merged parameters which affect different subset of the same set of steps. // See the `TestOverlappingMergedParameterWriting` for a more likely scenario triggering this. var a = AnnotationCollection.Annotate(step); var mem = a.GetMember(nameof(step.Name)); var ov = mem.Get(); // 1. Verify the annotation accurately reflects the current value Assert.That(ov.Value, Is.EqualTo(correct)); // 2. Assign a value, but don't write it ov.Value = garbage; // 3. Read the annotation back a.Read(); // 4. Write the current annotation a.Write(); // 5. Verify the garbage value was not written. Assert.That(step.Name, Is.EqualTo(correct)); } // Exhaustive list of assignment order to assign the parameterized members [TestCase(0, 1, 2)] [TestCase(0, 2, 1)] [TestCase(1, 0, 2)] [TestCase(1, 2, 0)] [TestCase(2, 1, 0)] [TestCase(2, 0, 1)] public void TestOverlappingMergedParameterWriting(int x, int y, int z) { int[] assignmentOrder = [x, y, z]; var plan = new TestPlan(); var nameParameterName = "shared_name"; var delay1ParameterName = "delay_name1"; var delay2ParameterName = "delay_name2"; plan.ChildTestSteps.AddRange([ new DelayStep(), new DelayStep(), new DelayStep(), new DelayStep(), new DelayStep() ]); // Create a merged parameters spanning all 5 steps foreach (var p in plan.ChildTestSteps) { var mem = AnnotationCollection.Annotate(p).GetMember(nameof(p.Name)).Get().Member; mem.Parameterize(plan, p, nameParameterName); } // Create a merged parameter on the first three steps for (int i = 0; i < 3; i++) { var step = plan.ChildTestSteps[i]; var mem = AnnotationCollection.Annotate(step).GetMember(nameof(DelayStep.DelaySecs)) .Get().Member; mem.Parameterize(plan, step, delay1ParameterName); } // Create a merged parameter on the last two steps for (int i = 3; i < 5; i++) { var step = plan.ChildTestSteps[i]; var mem = AnnotationCollection.Annotate(step).GetMember(nameof(DelayStep.DelaySecs)) .Get().Member; mem.Parameterize(plan, step, delay2ParameterName); } var a = AnnotationCollection.Annotate(plan); var nameParameter = a.GetMember(nameParameterName); var delayParameter1 = a.GetMember(delay1ParameterName); var delayParameter2 = a.GetMember(delay2ParameterName); var name = "Step Name"; double delaySecs = 1; double delaySecs2 = 2; // parameter assignments. They will be assigned in the order defined by the input parameters, xyz (AnnotationCollection a, string val)[] pairs = [ (nameParameter, name), (delayParameter1, delaySecs.ToString(CultureInfo.InvariantCulture)), (delayParameter2, delaySecs2.ToString(CultureInfo.InvariantCulture)), ]; // Ensure the assignment works reliably by looping a few times // During debugging, I saw scenarios where the assignment worked every 2nd time because // a cache was invalidated after a write which was conditional on the cache being invalidated. for (int i = 0; i < 10; i++) { // 1. Assign the parameter values for (int j = 0; j < assignmentOrder.Length; j++) { var pair = pairs[assignmentOrder[j]]; pair.a.Read(); var sv = pair.a.Get(); sv.Value = pair.val; pair.a.Write(); } // 2. Verify the correct values were propagated to all steps { foreach (var step in plan.ChildTestSteps) { Assert.That(step.Name, Is.EqualTo(name)); } for (int k = 0; k < 3; k++) { Assert.That((plan.ChildTestSteps[k] as DelayStep).DelaySecs, Is.EqualTo(delaySecs)); } for (int k = 3; k < 5; k++) { Assert.That((plan.ChildTestSteps[k] as DelayStep).DelaySecs, Is.EqualTo(delaySecs2)); } } // reset the parameter values for the next loop iteration { foreach (var step in plan.ChildTestSteps) { step.Name = "doesn't matter"; (step as DelayStep).DelaySecs = 123; } } } } [Test] public void TestRemoveParameter() { var seq = new SequenceStep(); var delay = new DelayStep(); seq.ChildTestSteps.Add(delay); var plan = new TestPlan(); plan.ChildTestSteps.Add(seq); var td = TypeData.GetTypeData(delay); var mem = td.GetMember(nameof(delay.DelaySecs)); var aMember = mem.Parameterize(seq, delay, "A 1"); Assert.IsNotNull(TypeData.GetTypeData(seq).GetMember("A 1")); aMember.Remove(); Assert.IsNull(TypeData.GetTypeData(seq).GetMember("A 1")); } }