chr
2026-04-08 53e656200368a983e563550e2cc1acbc6d86b729
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
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
//            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.ComponentModel;
using System.Reflection;
using System.Xml.Serialization;
using System.Text;
using System.Xml.Linq;
using System.Threading;
 
namespace OpenTap.Plugins.BasicSteps
{
    /// <summary> Sweep behavior for linear sweeps. </summary>
    public enum SweepBehavior
    {
        /// <summary> Linear growth function. </summary>
        [Display("Linear", Description: "Linear growth function.")]
        Linear = 0,
        /// <summary> Exponential growth function.</summary>
        [Display("Exponential", Description: "Exponential growth function.")]
        Exponential = 1
    }
 
    [Browsable(false)]
    [Display("Sweep Loop (Range)", Groups: new [] { "Flow Control", "Legacy" }, Description: "Loops all of its child steps while sweeping a specified parameter/setting over a range.", Collapsed:true)]
    [AllowAnyChild]
    public class SweepLoopRange : LoopTestStep
    {
 
        [Display("Sweep Parameters", Order: -4, Description: "Test step parameters that should be swept. The variable must be a numeric type.")]
        [XmlIgnore]
        [Browsable(true)]
        [Unsweepable]
        public List<IMemberData> SweepProperties { get; set; }
 
        [Display("Start", Order: -2, Description: "The parameter value where the sweep will start.")]
        public decimal SweepStart { get; set; }
 
        [Display("Stop", Order: -1, Description: "The parameter value where the sweep will stop.")]
        public decimal SweepStop { get; set; }
 
        [Browsable(false)]
        public decimal SweepEnd { get { return SweepStop; } set { SweepStop = value; } }
 
 
        [Display("Step Size", Order: 1, Description: "The value to be increased or decreased between every iteration of the sweep.")]
        [EnabledIf("SweepBehavior", SweepBehavior.Linear, HideIfDisabled = true)]
        [XmlIgnore] // this is inferred from the other properties and should not be set by the serializer
        [Browsable(true)]
        public decimal SweepStep
        {
            get
            {
                if (SweepPoints == 0) return 0;
                if (SweepPoints == 1) return 0;
                return (SweepStop - SweepStart) / (SweepPoints - 1);
            }
            set
            {
                if (decimal.Zero == value) return;
                var newv = (uint)Math.Round((SweepStop - SweepStart) / value) + 1;
                SweepPoints = newv;
            }
        }
 
        [Display("Points", Order: 1, Description: "The number of points to sweep.")]
        public uint SweepPoints { get; set; }
        
        [Display("Behavior", Order: -3, Description: "Linear or exponential growth.")]
        public SweepBehavior SweepBehavior { get; set; }
 
        [Output]
        [Display("Current Value", "Shows the current value of the loop.", Order: 4)]
        public decimal Current { get; private set; }
 
        [Browsable(false)]
        [DeserializeOrder(1.0)]
        public string SweepPropertyName
        {
            get => propertyInfosToString(SweepProperties);
            set => SweepProperties = parseInfosFromString(value).ToList();
        }
 
        static string propertyInfosToString(IEnumerable<IMemberData> infos)
        {
            return string.Join("|", infos.Select(x => String.Format("{0};{1}", x.Name, x.DeclaringType.Name)));
        }
 
        List<IMemberData> parseInfosFromString(string str)
        {
            var cs = this.RecursivelyGetChildSteps(TestStepSearch.All).Select( x => TypeData.GetTypeData(x)).ToArray();
            var allMembers = cs.SelectMany(x => x.GetMembers().ToArray());
            Dictionary<string, IMemberData> dict = new Dictionary<string, IMemberData>();
            foreach(var x in allMembers)
            {
                var key = String.Format("{0};{1}", x.Name, x.DeclaringType.Name);
                dict[key] = x;
            }
            var items = str.Split('|');
            List<IMemberData> outlist = new List<IMemberData>();
            foreach (var item in items)
            {
                if (dict.TryGetValue(item, out IMemberData member))
                    outlist.Add(member);
            }
            return outlist;
        }
 
        public override void PrePlanRun()
        {
            if (SweepProperties.Count == 0)
                throw new InvalidOperationException("No parameters selected to sweep");
            validateSweepMutex.WaitOne();
            validateSweepMutex.ReleaseMutex();
        }
 
        ITypeData SweepType => SweepProperties.FirstOrDefault()?.TypeDescriptor;
 
        // Check if the test plan is running before validating sweeps.
        // the validateSweep might have been started before the plan started.
        // hence we use the validateSweeepMutex to ensure that validation is done before 
        // the plan starts.
        bool isRunning => GetParent<TestPlan>()?.IsRunning ?? false;
        Mutex validateSweepMutex = new Mutex();
        string validateSweep(decimal Value)
        {   // Mostly copied from Run
            if (SweepProperties.Count == 0) return "";
            if (isRunning) return ""; // Avoid changing the value during run when the gui asks for validation errors.
            if (!validateSweepMutex.WaitOne(0)) return "";
            StringBuilder sb = new StringBuilder();
 
            var sets = GetPropertySets(ChildTestSteps).ToList();
            var originalValues = sets.Select(set => set.GetValue()).ToArray();
            try
            {
                foreach (var set in sets)
                    set.SetValue(Value, false);
                bool first = true;
                foreach (var step in sets)
                {
                    if (string.IsNullOrWhiteSpace(step.Obj.Error))
                        continue;
                    if (first)
                        first = false;
                    else
                        sb.AppendLine();
                    ITestStep step2 = step.Obj as ITestStep;
                    if (step2 != null)
                        sb.AppendFormat("Step '{0}' : {1}", step2.Name, step2.Error);
                    else
                        sb.AppendFormat("Object '{0}' : {1}", step.Obj, step.Obj.Error);
                }
                return sb.ToString();
            }
            catch (TargetInvocationException e)
            {
                return e.InnerException.Message;
            }
            finally
            {
                for (int i = 0; i < sets.Count; i++)
                    sets[i].SetValue(originalValues[i], false);
                validateSweepMutex.ReleaseMutex();
            }
        }
 
        public SweepLoopRange()
        {
            SweepProperties = new List<IMemberData>();
 
            SweepStart = 1;
            SweepStop = 100;
            SweepPoints = 100;
 
            Rules.Add(() => SweepProperties.Count != 0, "No parameters selected to sweep", nameof(SweepProperties));
            Rules.Add(() => string.IsNullOrWhiteSpace(validateSweep(SweepStart)), () => validateSweep(SweepStart), "SweepStart");
            Rules.Add(() => string.IsNullOrWhiteSpace(validateSweep(SweepStop)), () => validateSweep(SweepStop), "SweepEnd");
 
            Rules.Add(() => ((SweepBehavior != SweepBehavior.Exponential)) || (SweepStart != 0), "Sweep start value must be non-zero.", "SweepStart");
            Rules.Add(() => ((SweepBehavior != SweepBehavior.Exponential)) || (SweepStop != 0), "Sweep end value must be non-zero.", "SweepEnd");
            Rules.Add(() => SweepPoints > 1, "Sweep points must be bigger than 1.", "SweepPoints");
            
            Rules.Add(() => ((SweepBehavior != SweepBehavior.Exponential)) || (Math.Sign(SweepStop) == Math.Sign(SweepStart)), "Sweep start and end value must have the same sign.", "SweepEnd", "SweepStart");
 
            ChildTestSteps.ChildStepsChanged += childStepsChanged;
            PropertyChanged += SweepLoopRange_PropertyChanged;
        }
 
        void SweepLoopRange_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            if (e.PropertyName == nameof(ChildTestSteps))
                ChildTestSteps.ChildStepsChanged += childStepsChanged;
        }
 
        readonly Dictionary<IMemberData, object> membersCache = new Dictionary<IMemberData, object>();
        void childStepsChanged(TestStepList sender, TestStepList.ChildStepsChangedAction Action, ITestStep Object, int Index)
        {
            // Make a copy to stop collection modified exception in TapSerializer.
            membersCache.Clear();
            getPropertiesForItems(this, membersCache);
            SweepProperties.RemoveIf<IMemberData>(x => membersCache.ContainsKey(x) == false);
 
        }
        static bool memberCanSweep(IMemberData mem) => false == mem.HasAttribute<UnsweepableAttribute>();
        static void getPropertiesForItems(ITestStep step, Dictionary<IMemberData, object> members)
        {
            foreach (ITestStep cs in step.ChildTestSteps)
            {
                foreach (var member in TypeData.GetTypeData(cs).GetMembers())
                {
                    if (member.TypeDescriptor is TypeData t)
                    {
                        if (t.Type.IsNumeric() && members.ContainsKey(member) == false && memberCanSweep(member))
                        {
                            members.Add(member, member.GetValue(cs));
                        }
                    }
                }
                getPropertiesForItems(cs, members);
            }
        }
 
        internal static Dictionary<IMemberData, object> GetPropertiesForItems(ITestStep baseStep)
        {
            Dictionary<IMemberData, object> members = new Dictionary<IMemberData, object>();
            getPropertiesForItems(baseStep, members);
            return members;
        } 
 
        /// <summary> Obj/Property mapping </summary>
        struct ObjPropSet
        {
            public IValidatingObject Obj;
            public IMemberData Prop;
            public void SetValue(object value, bool invokePropertyChanged = true)
            {
 
                if (Prop.TypeDescriptor.DescendsTo(value.GetType()))
                {
                    Prop.SetValue(Obj, value);
                }
                else if(Prop.TypeDescriptor is TypeData cst)
                {
                    Prop.SetValue(Obj, Convert.ChangeType(value, cst.Type));
                }
                else
                {
                    return;
                }
                if (invokePropertyChanged)
                    Obj.OnPropertyChanged(Prop.Name);
            }
            public object GetValue()
            {
                return Prop.GetValue(Obj);
            }
        }
 
        IEnumerable<ObjPropSet> GetPropertySets(TestStepList childTestStep)
        {
            foreach (var step in childTestStep)
            {
                foreach (var prop in SweepProperties)
                {
                    IMemberData paramProp = TypeData.GetTypeData(step).GetMember(prop.Name);
                    if (paramProp != null && (paramProp.TypeDescriptor == prop.TypeDescriptor && paramProp.GetDisplayAttribute().GetFullName() == prop.GetDisplayAttribute().GetFullName()))
                        yield return new ObjPropSet { Obj = step, Prop = paramProp };
                    if (step is TestPlanReference) continue; //TODO: keep this hack?
                    foreach (var set in GetPropertySets(step.ChildTestSteps))
                    {
                        yield return set;
                    }
                }
            }
        }
 
 
        public static IEnumerable<decimal> LinearRange(decimal start,  decimal end, int points)
        {
            if (points == 0) yield break;
            decimal Value = start;
            for(int i = 0; i < points - 1; i++)
            {
                yield return (start * (points - 1- i) + end * (i)) / (points - 1);
            }
            yield return end;
        }
 
        public static IEnumerable<decimal> ExponentialRange(decimal start, decimal end, int points)
        {
            if (start == 0)
                throw new ArgumentException("Start value must be different than zero.");
            if (end == 0)
                throw new ArgumentException("End value must be different than zero.");
            if (Math.Sign(start) != Math.Sign(end))
                throw new ArgumentException("Start and end value must have the same sign.");
            
            var logs = Math.Log10((double) start);
            var loge = Math.Log10((double) end);
            return LinearRange((decimal)logs, (decimal)loge, points).Select(x => (decimal)Math.Pow(10, (double)x));
        }
        
        
        public override void Run()
        {
            base.Run();
 
            var sets = GetPropertySets(ChildTestSteps).ToList();
            var originalValues = sets.Select(set => set.GetValue()).ToArray();
 
 
            IEnumerable<decimal> range = LinearRange(SweepStart, SweepStop, (int)SweepPoints);
 
            if (SweepBehavior == SweepBehavior.Exponential)
                range = ExponentialRange(SweepStart, SweepStop, (int)SweepPoints);
 
            var disps = SweepProperties.Select(x => x.GetDisplayAttribute()).ToList();
            string names = string.Join(", ", disps.Select(x => x.Name));
            
            if (disps.Count > 1)
                names = string.Format("{{{0}}}", names);
 
            foreach (var Value in range)
            {
                Current = Value;
                OnPropertyChanged("Current");
                foreach (var set in sets)
                {
                    try
                    {
                        set.SetValue(Value);
                    }
                    catch (TargetInvocationException ex)
                    {
                        Log.Error("Unable to set '{0}' to value '{2}': {1}", set.Prop.GetDisplayAttribute().Name, ex.InnerException.Message, Value);
                        Log.Debug(ex.InnerException);
                    }
                }
 
                var AdditionalParams = new ResultParameters();
                
                foreach (var disp in disps)
                    AdditionalParams.Add(new ResultParameter(disp.Group.FirstOrDefault() ?? "", disp.Name, Value));
 
                Log.Info("Running child steps with {0} = {1} ", names, Value);
 
                var runs = RunChildSteps(AdditionalParams, BreakLoopRequested, throwOnBreak: false).ToArray();
                if (BreakLoopRequested.IsCancellationRequested) break;
                runs.ForEach(r => r.WaitForCompletion());
                if (runs.LastOrDefault()?.BreakConditionsSatisfied() == true)
                    break;
            }
            for (int i = 0; i < sets.Count; i++)
                sets[i].SetValue(originalValues[i]);
        }
    }
 
    public class LegacySweepLoader : TapSerializerPlugin
    {
        public override double Order { get { return 4; } }
 
        HashSet<XElement> proccessingNodes = new HashSet<XElement>();
 
        static readonly XName Type = "type";
        
        public override bool Deserialize(XElement node, ITypeData t, Action<object> setter)
        {
            var typeAttribute = node.Attribute(Type);
            if(typeAttribute != null && (typeAttribute.Value == "OpenTap.Plugins.BasicSteps.LinearSweepLoop" || typeAttribute.Value == "OpenTap.Plugins.BasicSteps.SweepLoopRange") )
            {
                if (!proccessingNodes.Add(node))
                    return false;
                try
                {
                    typeAttribute.SetValue(typeof(SweepLoopRange).FullName);
                    //Rename old SweepEnd setting to its new (8x) name "SweepStop"
                    var newN = node.Element("SweepStop");
                    var oldN = node.Element("SweepEnd");
                    if (oldN != null)
                    {
                        oldN.Name = "SweepStop";
 
                        if (newN != null)
                        {
                            if (oldN.Attribute("external") != null && newN.Attribute("external") == null)
                                newN.SetAttributeValue("external", oldN.Attribute("external").Value); // move the attribute from the old to the new
 
                            oldN.Remove();
                        }
                    }
                    return Serializer.Deserialize(node, setter, typeof(SweepLoopRange));
                }
                finally
                {
                    proccessingNodes.Remove(node);
                }
            }
            return false;
        }
 
        public override bool Serialize(XElement node, object obj, ITypeData expectedType)
        {
            return false;
        }
    }
}