1 // Licensed to the .NET Foundation under one or more agreements.
2 // The .NET Foundation licenses this file to you under the MIT license.
3 // See the LICENSE file in the project root for more information.
4 
5 using System;
6 using System.Collections.Generic;
7 using System.Diagnostics;
8 using System.IO.Ports;
9 using System.Linq;
10 using System.Text;
11 using System.Threading;
12 using System.Threading.Tasks;
13 using Xunit;
14 
15 namespace Legacy.Support
16 {
17     public static class TCSupport
18     {
19         public enum SerialPortRequirements { None, OneSerialPort, TwoSerialPorts, NullModem, Loopback, LoopbackOrNullModem };
20 
21         private static LocalMachineSerialInfo s_localMachineSerialInfo;
22         private static SerialPortRequirements s_localMachineSerialPortRequirements;
23 
24         // Set this true to display port info to the console rather than Debug.WriteLine
25         private static bool s_displayPortInfoOnConsole = true;
26 
TCSupport()27         static TCSupport()
28         {
29             Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
30             InitializeSerialInfo();
31         }
32 
InitializeSerialInfo()33         private static void InitializeSerialInfo()
34         {
35             GenerateSerialInfo();
36 
37             if (s_localMachineSerialInfo.LoopbackPortName != null)
38                 s_localMachineSerialPortRequirements = SerialPortRequirements.Loopback;
39             else if (s_localMachineSerialInfo.NullModemPresent)
40                 s_localMachineSerialPortRequirements = SerialPortRequirements.NullModem;
41             else if (!string.IsNullOrEmpty(s_localMachineSerialInfo.SecondAvailablePortName))
42                 s_localMachineSerialPortRequirements = SerialPortRequirements.TwoSerialPorts;
43             else if (!string.IsNullOrEmpty(s_localMachineSerialInfo.FirstAvailablePortName))
44                 s_localMachineSerialPortRequirements = SerialPortRequirements.OneSerialPort;
45             else
46                 s_localMachineSerialPortRequirements = SerialPortRequirements.None;
47         }
48 
GenerateSerialInfo()49         private static void GenerateSerialInfo()
50         {
51             string[] installedPortNames = PortHelper.GetPorts();
52             bool nullModemPresent = false;
53             string portName1 = null, portName2 = null, loopbackPortName = null;
54 
55             Array.Sort(installedPortNames);
56             PrintInfo("Installed ports : " + string.Join(",", installedPortNames));
57 
58             IList<string> openablePortNames = CheckPortsCanBeOpened(installedPortNames);
59 
60             PrintInfo("Openable ports  : " + string.Join(",", openablePortNames));
61 
62             // Find any pair of ports which are null-modem connected
63             // If there is a pair like this, then they take precedence over any other way of identifying two available ports
64             for (int firstIndex = 0; firstIndex < openablePortNames.Count && !nullModemPresent; firstIndex++)
65             {
66                 for (int secondIndex = firstIndex + 1; secondIndex < openablePortNames.Count && !nullModemPresent; secondIndex++)
67                 {
68                     string firstPortName = openablePortNames[firstIndex];
69                     string secondPortName = openablePortNames[secondIndex];
70 
71                     if (SerialPortConnection.VerifyConnection(firstPortName, secondPortName))
72                     {
73                         // We have a null modem port
74                         portName1 = firstPortName;
75                         portName2 = secondPortName;
76                         nullModemPresent = true;
77 
78                         PrintInfo("Null-modem connection from {0} to {1}", firstPortName, secondPortName);
79                     }
80                 }
81             }
82 
83             if (!nullModemPresent)
84             {
85                 // If we don't have a null-modem connection - check for a loopback connection
86                 foreach (string portName in openablePortNames)
87                 {
88                     if (SerialPortConnection.VerifyLoopback(portName))
89                     {
90                         portName1 = loopbackPortName = portName;
91                         break;
92                     }
93                 }
94 
95                 if (portName1 == null)
96                 {
97                     portName1 = openablePortNames.FirstOrDefault();
98                 }
99 
100                 portName2 = openablePortNames.FirstOrDefault(name => name != portName1);
101             }
102 
103             // See Github issues #15961, #16033, #20764 - hardware tests are currently insufficiently stable on master CI
104             if (loopbackPortName == null && !nullModemPresent)
105             {
106                 // We don't have any supporting hardware - disable all the tests which would use just an open port
107                 PrintInfo("No support hardware - not using serial ports");
108                 portName1 = portName2 = null;
109             }
110 
111             PrintInfo("First available port name  : " + portName1);
112             PrintInfo("Second available port name : " + portName2);
113             PrintInfo("Loopback port name         : " + loopbackPortName);
114             PrintInfo("NullModem present          : " + nullModemPresent);
115 
116             s_localMachineSerialInfo = new LocalMachineSerialInfo(portName1, portName2, loopbackPortName, nullModemPresent);
117 
118             if (portName1 != null)
119             {
120                 // Measure how big a packet we need to write to be sure to see blocking behaviour at a port
121                 s_flowControlCapabilities = SerialPortConnection.MeasureFlowControlCapabilities(portName1);
122 
123                 PrintInfo("{0}: Flow capabilities {1}", portName1, s_flowControlCapabilities);
124             }
125         }
126 
PrintInfo(string format, params object[] args)127         private static void PrintInfo(string format, params object[] args)
128         {
129             if (s_displayPortInfoOnConsole)
130             {
131                 Console.WriteLine(format, args);
132             }
133             else
134             {
135                 Debug.WriteLine(format, args);
136             }
137         }
138 
CheckPortsCanBeOpened(IEnumerable<string> installedPortNames)139         private static IList<string> CheckPortsCanBeOpened(IEnumerable<string> installedPortNames)
140         {
141             List<string> openablePortNames = new List<string>();
142             foreach (string portName in installedPortNames)
143             {
144                 using (SerialPort com = new SerialPort(portName))
145                 {
146                     try
147                     {
148                         com.Open();
149                         com.Close();
150 
151                         openablePortNames.Add(portName);
152                     }
153                     catch (Exception e)
154                     {
155                         PrintInfo("Exception opening port {0}: {1}", portName, e);
156                     }
157                 }
158             }
159             return openablePortNames;
160         }
161 
SufficientHardwareRequirements(SerialPortRequirements serialPortRequirements)162         public static bool SufficientHardwareRequirements(SerialPortRequirements serialPortRequirements)
163         {
164             switch (serialPortRequirements)
165             {
166                 case SerialPortRequirements.None:
167                     return true;
168 
169                 case SerialPortRequirements.OneSerialPort:
170                     return s_localMachineSerialPortRequirements == SerialPortRequirements.OneSerialPort ||
171                         s_localMachineSerialPortRequirements == SerialPortRequirements.TwoSerialPorts ||
172                         s_localMachineSerialPortRequirements == SerialPortRequirements.Loopback ||
173                         s_localMachineSerialPortRequirements == SerialPortRequirements.NullModem;
174                 case SerialPortRequirements.TwoSerialPorts:
175                     return s_localMachineSerialPortRequirements == SerialPortRequirements.TwoSerialPorts ||
176                         s_localMachineSerialPortRequirements == SerialPortRequirements.NullModem;
177                 case SerialPortRequirements.NullModem:
178                     return s_localMachineSerialPortRequirements == SerialPortRequirements.NullModem;
179                 case SerialPortRequirements.Loopback:
180                     return s_localMachineSerialPortRequirements == SerialPortRequirements.Loopback;
181                 case SerialPortRequirements.LoopbackOrNullModem:
182                     return s_localMachineSerialPortRequirements == SerialPortRequirements.Loopback ||
183                         s_localMachineSerialPortRequirements == SerialPortRequirements.NullModem;
184             }
185 
186             return false;
187         }
188 
InitFirstSerialPort()189         public static SerialPort InitFirstSerialPort()
190         {
191             if (LocalMachineSerialInfo.NullModemPresent)
192             {
193                 return new SerialPort(LocalMachineSerialInfo.FirstAvailablePortName);
194             }
195             else if (null != LocalMachineSerialInfo.LoopbackPortName)
196             {
197                 return new SerialPort(LocalMachineSerialInfo.LoopbackPortName);
198             }
199             else if (null != LocalMachineSerialInfo.FirstAvailablePortName)
200             {
201                 return new SerialPort(LocalMachineSerialInfo.FirstAvailablePortName);
202             }
203 
204             return null;
205         }
206 
InitSecondSerialPort(SerialPort com)207         public static SerialPort InitSecondSerialPort(SerialPort com)
208         {
209             if (LocalMachineSerialInfo.NullModemPresent)
210             {
211                 return new SerialPort(LocalMachineSerialInfo.SecondAvailablePortName);
212             }
213             else if (null != LocalMachineSerialInfo.LoopbackPortName)
214             {
215                 return com;
216             }
217             else if (null != LocalMachineSerialInfo.SecondAvailablePortName)
218             {
219                 return new SerialPort(LocalMachineSerialInfo.SecondAvailablePortName);
220             }
221 
222             return null;
223         }
224 
225         public static LocalMachineSerialInfo LocalMachineSerialInfo => s_localMachineSerialInfo;
226 
227         /// <summary>
228         /// Set this true to shorten the very long-running stress tests
229         /// </summary>
230         public static bool RunShortStressTests { get; set; } = true;
231 
232         public static int MinimumBlockingByteCount => s_flowControlCapabilities.MinimumBlockingByteCount;
233         public static int HardwareTransmitBufferSize => s_flowControlCapabilities.HardwareTransmitBufferSize;
234         public static bool HardwareWriteBlockingAvailable => s_flowControlCapabilities.HardwareWriteBlockingAvailable;
235 
Predicate()236         public delegate bool Predicate();
237 
ValueGenerator()238         public delegate T ValueGenerator<T>();
239 
WaitForPredicate(Predicate predicate, int maxWait, string errorMessageFormat, params object[] formatArgs)240         public static void WaitForPredicate(Predicate predicate, int maxWait, string errorMessageFormat, params object[] formatArgs)
241         {
242             WaitForPredicate(predicate, maxWait, string.Format(errorMessageFormat, formatArgs));
243         }
WaitForPredicate(Predicate predicate, int maxWait, string errorMessage)244         public static void WaitForPredicate(Predicate predicate, int maxWait, string errorMessage)
245         {
246             Stopwatch stopWatch = Stopwatch.StartNew();
247             bool predicateValue = false;
248 
249             while (!predicateValue && stopWatch.ElapsedMilliseconds < maxWait)
250             {
251                 predicateValue = predicate();
252                 Thread.Sleep(10);
253             }
254 
255             Assert.True(predicateValue, errorMessage);
256         }
257 
WaitForExpected(ValueGenerator<T> actualValueGenerator, T expectedValue, int maxWait, string errorMessage)258         public static void WaitForExpected<T>(ValueGenerator<T> actualValueGenerator, T expectedValue, int maxWait,
259             string errorMessage)
260         {
261             Stopwatch stopWatch = new Stopwatch();
262             bool result;
263             T actualValue;
264             int iterationWaitTime = 0;
265 
266             stopWatch.Start();
267 
268             do
269             {
270                 actualValue = actualValueGenerator();
271                 result = actualValue == null ? null == expectedValue : actualValue.Equals(expectedValue);
272 
273                 Thread.Sleep(iterationWaitTime);
274                 iterationWaitTime = 10; //This is just to ensure there is no delay the first time we check
275             } while (!result && stopWatch.ElapsedMilliseconds < maxWait);
276 
277             Assert.True(result, errorMessage +
278                                 " Expected:" + (null == expectedValue ? "<null>" : expectedValue.ToString()) +
279                                 " Actual:" + (null == actualValue ? "<null>" : actualValue.ToString()));
280         }
281 
282         private const int MIN_RANDOM_CHAR = 0;
283 
284         private const int MIN_HIGH_SURROGATE = 0xD800;
285         private const int MAX_HIGH_SURROGATE = 0xDBFF;
286 
287         private const int MIN_LOW_SURROGATE = 0xDC00;
288         private const int MAX_LOW_SURROGATE = 0xDFFF;
289 
290         private const int MIN_RANDOM_ASCII_CHAR = 0;
291         private const int MAX_RANDOM_ASCII_CHAR = 127;
292 
293         private static readonly Random s_random = new Random(-55);
294         private static FlowControlCapabilities s_flowControlCapabilities = new FlowControlCapabilities(0, 0, false);
295 
296         [Flags]
297         public enum CharacterOptions { None, Surrogates, ASCII };
298 
GetRandomChars(int count, bool withSurrogates)299         public static char[] GetRandomChars(int count, bool withSurrogates)
300         {
301             if (withSurrogates)
302                 return GetRandomCharsWithSurrogates(count);
303             else
304                 return GetRandomCharsWithoutSurrogates(count);
305         }
306 
GetRandomChars(int count, CharacterOptions options)307         public static char[] GetRandomChars(int count, CharacterOptions options)
308         {
309             if (0 != (options & CharacterOptions.Surrogates))
310                 return GetRandomCharsWithSurrogates(count);
311             if (0 != (options & CharacterOptions.ASCII))
312                 return GetRandomASCIIChars(count);
313             else
314                 return GetRandomCharsWithoutSurrogates(count);
315         }
316 
GetRandomChars(char[] chars, int index, int count, CharacterOptions options)317         public static void GetRandomChars(char[] chars, int index, int count, CharacterOptions options)
318         {
319             if (0 != (options & CharacterOptions.Surrogates))
320                 GetRandomCharsWithSurrogates(chars, index, count);
321             if (0 != (options & CharacterOptions.ASCII))
322                 GetRandomASCIIChars(chars, index, count);
323             else
324                 GetRandomCharsWithoutSurrogates(chars, index, count);
325         }
326 
GetRandomString(int count, CharacterOptions options)327         public static string GetRandomString(int count, CharacterOptions options)
328         {
329             return new string(GetRandomChars(count, options));
330         }
331 
GetRandomStringBuilder(int count, CharacterOptions options)332         public static StringBuilder GetRandomStringBuilder(int count, CharacterOptions options)
333         {
334             StringBuilder sb = new StringBuilder(count);
335             sb.Append(GetRandomChars(count, options));
336             return sb;
337         }
338 
GetRandomASCIIChars(int count)339         public static char[] GetRandomASCIIChars(int count)
340         {
341             char[] chars = new char[count];
342 
343             GetRandomASCIIChars(chars, 0, count);
344 
345             return chars;
346         }
347 
GetRandomASCIIChars(char[] chars, int index, int count)348         public static void GetRandomASCIIChars(char[] chars, int index, int count)
349         {
350             for (int i = 0; i < count; ++i)
351             {
352                 chars[i] = GenerateRandomASCII();
353             }
354         }
355 
GetRandomCharsWithoutSurrogates(int count)356         public static char[] GetRandomCharsWithoutSurrogates(int count)
357         {
358             char[] chars = new char[count];
359 
360             GetRandomCharsWithoutSurrogates(chars, 0, count);
361 
362             return chars;
363         }
364 
GetRandomCharsWithoutSurrogates(char[] chars, int index, int count)365         public static void GetRandomCharsWithoutSurrogates(char[] chars, int index, int count)
366         {
367             for (int i = 0; i < count; ++i)
368             {
369                 chars[i] = GenerateRandomCharNonSurrogate();
370             }
371         }
372 
GetRandomCharsWithSurrogates(int count)373         public static char[] GetRandomCharsWithSurrogates(int count)
374         {
375             char[] chars = new char[count];
376 
377             GetRandomCharsWithSurrogates(chars, 0, count);
378 
379             return chars;
380         }
381 
GetRandomCharsWithSurrogates(char[] chars, int index, int count)382         public static void GetRandomCharsWithSurrogates(char[] chars, int index, int count)
383         {
384             for (int i = 0; i < count; ++i)
385             {
386                 int randomChar = GenerateRandomCharWithHighSurrogate();
387 
388                 if (MIN_HIGH_SURROGATE <= randomChar)
389                 {
390                     if (i < (count - 1))
391                     {
392                         chars[i] = (char)randomChar;
393 
394                         ++i;
395                         chars[i] = GenerateRandomLowSurrogate();
396                     }
397                     else
398                     {
399                         chars[i] = GenerateRandomCharNonSurrogate();
400                     }
401                 }
402                 else
403                 {
404                     chars[i] = (char)randomChar;
405                 }
406             }
407         }
408 
GenerateRandomASCII()409         public static char GenerateRandomASCII()
410         {
411             return (char)s_random.Next(MIN_RANDOM_ASCII_CHAR, MAX_RANDOM_ASCII_CHAR + 1);
412         }
413 
GenerateRandomHighSurrogate()414         public static char GenerateRandomHighSurrogate()
415         {
416             return (char)s_random.Next(MIN_HIGH_SURROGATE, MAX_HIGH_SURROGATE + 1);
417         }
418 
GenerateRandomLowSurrogate()419         public static char GenerateRandomLowSurrogate()
420         {
421             return (char)s_random.Next(MIN_LOW_SURROGATE, MAX_LOW_SURROGATE + 1);
422         }
423 
GenerateRandomCharWithHighSurrogate()424         public static char GenerateRandomCharWithHighSurrogate()
425         {
426             return (char)s_random.Next(MIN_RANDOM_CHAR, MAX_HIGH_SURROGATE + 1);
427         }
428 
GenerateRandomCharNonSurrogate()429         public static char GenerateRandomCharNonSurrogate()
430         {
431             return (char)s_random.Next(MIN_RANDOM_CHAR, MIN_HIGH_SURROGATE);
432         }
433 
GetRandomBytes(int count)434         public static byte[] GetRandomBytes(int count)
435         {
436             byte[] bytes = new byte[count];
437 
438             GetRandomBytes(bytes, 0, count);
439 
440             return bytes;
441         }
442 
443         /// <summary>
444         /// Returns a random char that is not c
445         /// </summary>
GetRandomOtherChar(char c, CharacterOptions options)446         public static char GetRandomOtherChar(char c, CharacterOptions options)
447         {
448             switch (options)
449             {
450                 case CharacterOptions.ASCII:
451                     return GetRandomOtherASCIIChar(c);
452                 default:
453                     return GetRandomOtherUnicodeChar(c);
454             }
455         }
456 
GetRandomOtherUnicodeChar(char c)457         public static char GetRandomOtherUnicodeChar(char c)
458         {
459             char newChar;
460 
461             do
462             {
463                 newChar = GenerateRandomCharNonSurrogate();
464             } while (newChar == c);
465 
466             return newChar;
467         }
468 
GetRandomOtherASCIIChar(char c)469         public static char GetRandomOtherASCIIChar(char c)
470         {
471             char newChar;
472 
473             do
474             {
475                 newChar = GenerateRandomASCII();
476             } while (newChar == c);
477 
478             return newChar;
479         }
480 
GetRandomBytes(byte[] bytes, int index, int count)481         public static void GetRandomBytes(byte[] bytes, int index, int count)
482         {
483             for (int i = 0; i < count; ++i)
484             {
485                 bytes[i + index] = (byte)s_random.Next(0, 256);
486             }
487         }
488 
IsSurrogate(char c)489         public static bool IsSurrogate(char c)
490         {
491             return IsHighSurrogate(c) || IsLowSurrogate(c);
492         }
493 
IsHighSurrogate(char c)494         public static bool IsHighSurrogate(char c)
495         {
496             return MIN_HIGH_SURROGATE <= c && c <= MAX_HIGH_SURROGATE;
497         }
498 
IsLowSurrogate(char c)499         public static bool IsLowSurrogate(char c)
500         {
501             return MIN_LOW_SURROGATE <= c && c <= MAX_LOW_SURROGATE;
502         }
503 
OrdinalIndexOf(string input, string search)504         public static int OrdinalIndexOf(string input, string search)
505         {
506             return OrdinalIndexOf(input, 0, input.Length, search);
507         }
508 
OrdinalIndexOf(string input, int startIndex, string search)509         public static int OrdinalIndexOf(string input, int startIndex, string search)
510         {
511             return OrdinalIndexOf(input, startIndex, input.Length - startIndex, search);
512         }
513 
OrdinalIndexOf(string input, int startIndex, int count, string search)514         private static int OrdinalIndexOf(string input, int startIndex, int count, string search)
515         {
516             int lastSearchIndex = (count + startIndex) - search.Length;
517             if (lastSearchIndex >= input.Length)
518             {
519                 throw new ArgumentOutOfRangeException(nameof(input), "Searching will result in accessing element past the end of the array");
520             }
521 
522             for (int i = startIndex; i <= lastSearchIndex; ++i)
523             {
524                 if (input[i] == search[0])
525                 {
526                     bool match = true;
527                     for (int searchIndex = 1, inputIndex = i + 1; searchIndex < search.Length; ++searchIndex, ++inputIndex)
528                     {
529                         match = input[inputIndex] == search[searchIndex];
530 
531                         if (!match)
532                         {
533                             break;
534                         }
535                     }
536 
537                     if (match)
538                     {
539                         return i;
540                     }
541                 }
542             }
543 
544             return -1;
545         }
546 
PrintChars(char[] chars)547         public static void PrintChars(char[] chars)
548         {
549             foreach (char chr in chars)
550             {
551                 Debug.WriteLine("(char){0}, //Char={1}, {0:X}", (int)chr, chr);
552             }
553         }
554 
PrintBytes(byte[] bytes)555         public static void PrintBytes(byte[] bytes)
556         {
557             for (int i = 0; i < bytes.Length; i++)
558             {
559                 Debug.WriteLine("{0}, //{0:X} Index: {1}", (int)bytes[i], i);
560             }
561         }
562 
563         /// <summary>
564         /// Verifies the contents of the array.
565         /// </summary>
566         /// <typeparam name="T"></typeparam>
567         /// <param name="expectedArray">The expected items in the array.</param>
568         /// <param name="actualArray">The actual array.</param>
569         /// <returns>true if expectedArray and actualArray have the same contents.</returns>
VerifyArray(T[] expectedArray, T[] actualArray)570         public static void VerifyArray<T>(T[] expectedArray, T[] actualArray)
571         {
572             Assert.Equal(expectedArray.Length, actualArray.Length);
573             VerifyArray(expectedArray, actualArray, 0, expectedArray.Length);
574         }
575 
576         /// <summary>
577         /// Verifies the contents of the array.
578         /// </summary>
579         /// <typeparam name="T"></typeparam>
580         /// <param name="expectedArray">The expected items in the array.</param>
581         /// <param name="actualArray">The actual array.</param>
582         /// <param name="index">The index to start verifying the items at.</param>
583         /// <param name="length">The number of item to verify</param>
584         /// <returns>true if expectedArray and actualArray have the same contents.</returns>
VerifyArray(T[] expectedArray, T[] actualArray, int index, int length)585         public static void VerifyArray<T>(T[] expectedArray, T[] actualArray, int index, int length)
586         {
587             int tempLength = length + index;
588             for (int i = index; i < tempLength; ++i)
589             {
590                 bool result = expectedArray[i] == null ? null != actualArray[i] : expectedArray[i].Equals(actualArray[i]);
591 
592                 Assert.True(result, string.Format("Err_55808aoped Items differ at {0} expected {1} actual {2}", i, expectedArray[i], actualArray[i]));
593             }
594         }
595 
596         /// <summary>
597         /// Set both ports to 115200 baud to speed test performance
598         /// </summary>
SetHighSpeed(SerialPort com1, SerialPort com2)599         public static void SetHighSpeed(SerialPort com1, SerialPort com2)
600         {
601             if (com1 != null)
602             {
603                 com1.BaudRate = 115200;
604             }
605             if (com2 != null && com2 != com1)
606             {
607                 com2.BaudRate = 115200;
608             }
609         }
610 
611 
612         /// <summary>
613         /// Wait for write data to be written into a blocked (by adverse flow control) port
614         /// </summary>
WaitForWriteBufferToLoad(SerialPort com, int bufferLength)615         public static void WaitForWriteBufferToLoad(SerialPort com, int bufferLength)
616         {
617             Stopwatch sw = Stopwatch.StartNew();
618             while (com.BytesToWrite + HardwareTransmitBufferSize < bufferLength)
619             {
620                 Thread.Sleep(50);
621                 Assert.True(sw.ElapsedMilliseconds < 3000, $"Timeout while waiting for data to be written to port (wrote {bufferLength}, queued {com.BytesToWrite}, bufSize {HardwareTransmitBufferSize})");
622             }
623         }
624 
625         /// <summary>
626         /// Wait for write data to be written into a blocked (by adverse flow control) port,
627         /// then check that exactly the expected quantity is present
628         /// </summary>
WaitForExactWriteBufferLoad(SerialPort com, int bufferLength)629         public static void WaitForExactWriteBufferLoad(SerialPort com, int bufferLength)
630         {
631             WaitForWriteBufferToLoad(com, bufferLength);
632             Assert.Equal(bufferLength, com.BytesToWrite + HardwareTransmitBufferSize);
633         }
634 
635         /// <summary>
636         /// Wait for the data to arrive into the read buffer
637         /// </summary>
WaitForReadBufferToLoad(SerialPort com, int bufferLength)638         public static void WaitForReadBufferToLoad(SerialPort com, int bufferLength)
639         {
640             Stopwatch sw = Stopwatch.StartNew();
641             while (com.BytesToRead < bufferLength)
642             {
643                 Thread.Sleep(50);
644                 Assert.True(sw.ElapsedMilliseconds < 3000, $"Timeout while waiting for data to be arrive at port (expected {bufferLength}, available {com.BytesToRead})");
645             }
646         }
647 
648 
WaitForTaskToStart(Task task)649         public static void WaitForTaskToStart(Task task)
650         {
651             Stopwatch sw = Stopwatch.StartNew();
652             while (task.Status < TaskStatus.Running)
653             {
654                 // Wait for the thread to start
655                 Thread.Sleep(50);
656                 Assert.True(sw.ElapsedMilliseconds < 2000, "Timeout waiting for task to start");
657             }
658         }
659 
WaitForTaskCompletion(Task task)660         public static void WaitForTaskCompletion(Task task)
661         {
662             Assert.True(task.Wait(5000), "Timeout waiting for task completion");
663         }
664     }
665 }
666