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.Diagnostics;
6 using System.IO.PortsTests;
7 using System.Text;
8 using System.Threading;
9 using System.Threading.Tasks;
10 using Legacy.Support;
11 using Xunit;
12 using Xunit.NetCore.Extensions;
13 
14 namespace System.IO.Ports.Tests
15 {
16     public class Write_char_int_int_generic : PortsTest
17     {
18         //Set bounds fore random timeout values.
19         //If the min is to low write will not timeout accurately and the testcase will fail
20         private const int minRandomTimeout = 250;
21 
22         //If the max is to large then the testcase will take forever to run
23         private const int maxRandomTimeout = 2000;
24 
25         //If the percentage difference between the expected timeout and the actual timeout
26         //found through Stopwatch is greater then 10% then the timeout value was not correctly
27         //to the write method and the testcase fails.
28         private static double s_maxPercentageDifference = .15;
29 
30         //The char size used when veryifying exceptions that write will throw
31         private const int CHAR_SIZE_EXCEPTION = 4;
32 
33         //The char size used when veryifying timeout
34         private const int CHAR_SIZE_TIMEOUT = 4;
35 
36         //The char size used when veryifying BytesToWrite
37         private const int CHAR_SIZE_BYTES_TO_WRITE = 4;
38 
39         //The char size used when veryifying Handshake
40         private const int CHAR_SIZE_HANDSHAKE = 8;
41         private const int NUM_TRYS = 5;
42 
43         #region Test Cases
44 
45         [Fact]
WriteWithoutOpen()46         public void WriteWithoutOpen()
47         {
48             using (SerialPort com = new SerialPort())
49             {
50                 Debug.WriteLine("Verifying write method throws exception without a call to Open()");
51                 VerifyWriteException(com, typeof(InvalidOperationException));
52             }
53         }
54 
55         [ConditionalFact(nameof(HasOneSerialPort))]
WriteAfterFailedOpen()56         public void WriteAfterFailedOpen()
57         {
58             using (SerialPort com = new SerialPort("BAD_PORT_NAME"))
59             {
60                 Debug.WriteLine("Verifying write method throws exception with a failed call to Open()");
61 
62                 //Since the PortName is set to a bad port name Open will thrown an exception
63                 //however we don't care what it is since we are verifying a write method
64                 Assert.ThrowsAny<Exception>(() => com.Open());
65 
66                 VerifyWriteException(com, typeof(InvalidOperationException));
67             }
68         }
69 
70 
71         [ConditionalFact(nameof(HasOneSerialPort))]
WriteAfterClose()72         public void WriteAfterClose()
73         {
74             using (SerialPort com = new SerialPort(TCSupport.LocalMachineSerialInfo.FirstAvailablePortName))
75             {
76                 Debug.WriteLine("Verifying write method throws exception after a call to Cloes()");
77                 com.Open();
78                 com.Close();
79 
80                 VerifyWriteException(com, typeof(InvalidOperationException));
81             }
82         }
83 
84         [ConditionalFact(nameof(HasNullModem))]
Timeout()85         public void Timeout()
86         {
87             using (SerialPort com1 = new SerialPort(TCSupport.LocalMachineSerialInfo.FirstAvailablePortName))
88             using (SerialPort com2 = new SerialPort(TCSupport.LocalMachineSerialInfo.SecondAvailablePortName))
89             {
90                 Random rndGen = new Random(-55);
91                 byte[] XOffBuffer = new byte[1];
92 
93                 XOffBuffer[0] = 19;
94 
95                 com1.WriteTimeout = rndGen.Next(minRandomTimeout, maxRandomTimeout);
96                 com1.Handshake = Handshake.XOnXOff;
97 
98                 Debug.WriteLine("Verifying WriteTimeout={0}", com1.WriteTimeout);
99 
100                 com1.Open();
101                 com2.Open();
102 
103                 com2.Write(XOffBuffer, 0, 1);
104                 Thread.Sleep(250);
105 
106                 com2.Close();
107 
108                 VerifyTimeout(com1);
109             }
110         }
111 
112         [Trait(XunitConstants.Category, XunitConstants.IgnoreForCI)]  // Timing-sensitive
113         [ConditionalFact(nameof(HasOneSerialPort), nameof(HasHardwareFlowControl))]
SuccessiveReadTimeout()114         public void SuccessiveReadTimeout()
115         {
116             using (SerialPort com = new SerialPort(TCSupport.LocalMachineSerialInfo.FirstAvailablePortName))
117             {
118                 Random rndGen = new Random(-55);
119 
120 
121                 com.WriteTimeout = rndGen.Next(minRandomTimeout, maxRandomTimeout);
122                 com.Handshake = Handshake.RequestToSendXOnXOff;
123                 //		com.Encoding = new System.Text.UTF7Encoding();
124                 com.Encoding = Encoding.Unicode;
125 
126                 Debug.WriteLine("Verifying WriteTimeout={0} with successive call to write method", com.WriteTimeout);
127 
128                 com.Open();
129 
130                 try
131                 {
132                     com.Write(new char[CHAR_SIZE_TIMEOUT], 0, CHAR_SIZE_TIMEOUT);
133                 }
134                 catch (TimeoutException)
135                 {
136                 }
137 
138                 VerifyTimeout(com);
139             }
140         }
141 
142         [ConditionalFact(nameof(HasNullModem), nameof(HasHardwareFlowControl))]
SuccessiveReadTimeoutWithWriteSucceeding()143         public void SuccessiveReadTimeoutWithWriteSucceeding()
144         {
145             using (SerialPort com1 = new SerialPort(TCSupport.LocalMachineSerialInfo.FirstAvailablePortName))
146             {
147                 Random rndGen = new Random(-55);
148                 AsyncEnableRts asyncEnableRts = new AsyncEnableRts();
149                 var t = new Task(asyncEnableRts.EnableRTS);
150 
151                 com1.WriteTimeout = rndGen.Next(minRandomTimeout, maxRandomTimeout);
152                 com1.Handshake = Handshake.RequestToSend;
153                 com1.Encoding = new UTF8Encoding();
154 
155                 Debug.WriteLine("Verifying WriteTimeout={0} with successive call to write method with the write succeeding sometime before its timeout", com1.WriteTimeout);
156                 com1.Open();
157 
158                 //Call EnableRTS asynchronously this will enable RTS in the middle of the following write call allowing it to succeed
159                 //before the timeout is reached
160                 t.Start();
161                 TCSupport.WaitForTaskToStart(t);
162 
163                 try
164                 {
165                     com1.Write(new char[CHAR_SIZE_TIMEOUT], 0, CHAR_SIZE_TIMEOUT);
166                 }
167                 catch (TimeoutException)
168                 {
169                 }
170 
171                 asyncEnableRts.Stop();
172 
173                 TCSupport.WaitForTaskCompletion(t);
174 
175                 VerifyTimeout(com1);
176             }
177         }
178 
179         [ConditionalFact(nameof(HasNullModem), nameof(HasHardwareFlowControl))]
BytesToWrite()180         public void BytesToWrite()
181         {
182             using (SerialPort com = new SerialPort(TCSupport.LocalMachineSerialInfo.FirstAvailablePortName))
183             {
184                 AsyncWriteRndCharArray asyncWriteRndCharArray = new AsyncWriteRndCharArray(com, CHAR_SIZE_BYTES_TO_WRITE);
185                 var t = new Task(asyncWriteRndCharArray.WriteRndCharArray);
186 
187                 Debug.WriteLine("Verifying BytesToWrite with one call to Write");
188 
189                 com.Handshake = Handshake.RequestToSend;
190                 com.Open();
191                 com.WriteTimeout = 500;
192 
193                 //Write a random char[] asynchronously so we can verify some things while the write call is blocking
194                 t.Start();
195                 TCSupport.WaitForTaskToStart(t);
196                 TCSupport.WaitForExactWriteBufferLoad(com, CHAR_SIZE_BYTES_TO_WRITE);
197                 TCSupport.WaitForTaskCompletion(t);
198             }
199         }
200 
201         [ConditionalFact(nameof(HasNullModem), nameof(HasHardwareFlowControl))]
BytesToWriteSuccessive()202         public void BytesToWriteSuccessive()
203         {
204             using (SerialPort com = new SerialPort(TCSupport.LocalMachineSerialInfo.FirstAvailablePortName))
205             {
206                 AsyncWriteRndCharArray asyncWriteRndCharArray = new AsyncWriteRndCharArray(com, CHAR_SIZE_BYTES_TO_WRITE);
207                 var t1 = new Task(asyncWriteRndCharArray.WriteRndCharArray);
208                 var t2 = new Task(asyncWriteRndCharArray.WriteRndCharArray);
209 
210                 Debug.WriteLine("Verifying BytesToWrite with successive calls to Write");
211 
212                 com.Handshake = Handshake.RequestToSend;
213                 com.Open();
214                 com.WriteTimeout = 4000;
215 
216                 //Write a random char[] asynchronously so we can verify some things while the write call is blocking
217                 t1.Start();
218                 TCSupport.WaitForTaskToStart(t1);
219                 TCSupport.WaitForExactWriteBufferLoad(com, CHAR_SIZE_BYTES_TO_WRITE);
220 
221                 //Write a random char[] asynchronously so we can verify some things while the write call is blocking
222                 t2.Start();
223                 TCSupport.WaitForTaskToStart(t2);
224                 TCSupport.WaitForExactWriteBufferLoad(com, CHAR_SIZE_BYTES_TO_WRITE * 2);
225 
226                 //Wait for both write methods to timeout
227                 TCSupport.WaitForTaskCompletion(t1);
228                 var aggregatedException = Assert.Throws<AggregateException>(() => TCSupport.WaitForTaskCompletion(t2));
229                 Assert.IsType<IOException>(aggregatedException.InnerException);
230             }
231         }
232 
233         [ConditionalFact(nameof(HasNullModem))]
Handshake_None()234         public void Handshake_None()
235         {
236             using (SerialPort com = new SerialPort(TCSupport.LocalMachineSerialInfo.FirstAvailablePortName))
237             {
238                 AsyncWriteRndCharArray asyncWriteRndCharArray = new AsyncWriteRndCharArray(com, CHAR_SIZE_HANDSHAKE);
239                 var t = new Task(asyncWriteRndCharArray.WriteRndCharArray);
240 
241                 //Write a random char[] asynchronously so we can verify some things while the write call is blocking
242                 Debug.WriteLine("Verifying Handshake=None");
243 
244                 com.Open();
245 
246                 t.Start();
247                 TCSupport.WaitForTaskCompletion(t);
248 
249                 Assert.Equal(0, com.BytesToWrite);
250             }
251         }
252 
253         [ConditionalFact(nameof(HasNullModem))]
Handshake_RequestToSend()254         public void Handshake_RequestToSend()
255         {
256             Verify_Handshake(Handshake.RequestToSend);
257         }
258 
259         [ConditionalFact(nameof(HasNullModem))]
Handshake_XOnXOff()260         public void Handshake_XOnXOff()
261         {
262             Verify_Handshake(Handshake.XOnXOff);
263         }
264 
265         [ConditionalFact(nameof(HasNullModem))]
Handshake_RequestToSendXOnXOff()266         public void Handshake_RequestToSendXOnXOff()
267         {
268             Verify_Handshake(Handshake.RequestToSendXOnXOff);
269         }
270 
271         public class AsyncEnableRts
272         {
273             private bool _stop;
274 
EnableRTS()275             public void EnableRTS()
276             {
277                 lock (this)
278                 {
279                     using (SerialPort com2 = new SerialPort(TCSupport.LocalMachineSerialInfo.SecondAvailablePortName))
280                     {
281                         Random rndGen = new Random(-55);
282                         int sleepPeriod = rndGen.Next(minRandomTimeout, maxRandomTimeout / 2);
283 
284                         //Sleep some random period with of a maximum duration of half the largest possible timeout value for a write method on COM1
285                         Thread.Sleep(sleepPeriod);
286 
287                         com2.Open();
288                         com2.RtsEnable = true;
289 
290                         while (!_stop)
291                             Monitor.Wait(this);
292 
293                         com2.RtsEnable = false;
294                     }
295                 }
296             }
297 
298 
Stop()299             public void Stop()
300             {
301                 lock (this)
302                 {
303                     _stop = true;
304                     Monitor.Pulse(this);
305                 }
306             }
307         }
308 
309 
310 
311         public class AsyncWriteRndCharArray
312         {
313             private readonly SerialPort _com;
314             private readonly int _charLength;
315 
316 
AsyncWriteRndCharArray(SerialPort com, int charLength)317             public AsyncWriteRndCharArray(SerialPort com, int charLength)
318             {
319                 _com = com;
320                 _charLength = charLength;
321             }
322 
323 
WriteRndCharArray()324             public void WriteRndCharArray()
325             {
326                 char[] buffer = TCSupport.GetRandomChars(_charLength, TCSupport.CharacterOptions.Surrogates);
327 
328                 try
329                 {
330                     _com.Write(buffer, 0, buffer.Length);
331                 }
332                 catch (TimeoutException)
333                 {
334                 }
335             }
336         }
337         #endregion
338 
339         #region Verification for Test Cases
VerifyWriteException(SerialPort com, Type expectedException)340         public static void VerifyWriteException(SerialPort com, Type expectedException)
341         {
342             Assert.Throws(expectedException, () => com.Write(new char[CHAR_SIZE_EXCEPTION], 0, CHAR_SIZE_EXCEPTION));
343         }
344 
VerifyTimeout(SerialPort com)345         private void VerifyTimeout(SerialPort com)
346         {
347             Stopwatch timer = new Stopwatch();
348             int expectedTime = com.WriteTimeout;
349             int actualTime = 0;
350             double percentageDifference;
351 
352             try
353             {
354                 com.Write(new char[CHAR_SIZE_TIMEOUT], 0, CHAR_SIZE_TIMEOUT); //Warm up write method
355             }
356             catch (TimeoutException) { }
357 
358             Thread.CurrentThread.Priority = ThreadPriority.Highest;
359 
360             for (int i = 0; i < NUM_TRYS; i++)
361             {
362                 timer.Start();
363 
364                 try
365                 {
366                     com.Write(new char[CHAR_SIZE_TIMEOUT], 0, CHAR_SIZE_TIMEOUT);
367                 }
368                 catch (TimeoutException) { }
369 
370                 timer.Stop();
371                 actualTime += (int)timer.ElapsedMilliseconds;
372                 timer.Reset();
373             }
374 
375             Thread.CurrentThread.Priority = ThreadPriority.Normal;
376             actualTime /= NUM_TRYS;
377             percentageDifference = Math.Abs((expectedTime - actualTime) / (double)expectedTime);
378 
379             //Verify that the percentage difference between the expected and actual timeout is less then maxPercentageDifference
380             if (s_maxPercentageDifference < percentageDifference)
381             {
382                 Fail("ERROR!!!: The write method timedout in {0} expected {1} percentage difference: {2}", actualTime, expectedTime, percentageDifference);
383             }
384         }
385 
Verify_Handshake(Handshake handshake)386         private void Verify_Handshake(Handshake handshake)
387         {
388             using (SerialPort com1 = new SerialPort(TCSupport.LocalMachineSerialInfo.FirstAvailablePortName))
389             using (SerialPort com2 = new SerialPort(TCSupport.LocalMachineSerialInfo.SecondAvailablePortName))
390             {
391                 AsyncWriteRndCharArray asyncWriteRndCharArray = new AsyncWriteRndCharArray(com1, CHAR_SIZE_HANDSHAKE);
392                 var t = new Task(asyncWriteRndCharArray.WriteRndCharArray);
393 
394                 byte[] XOffBuffer = new byte[1];
395                 byte[] XOnBuffer = new byte[1];
396 
397                 XOffBuffer[0] = 19;
398                 XOnBuffer[0] = 17;
399 
400                 Debug.WriteLine("Verifying Handshake={0}", handshake);
401 
402                 com1.Handshake = handshake;
403                 com1.Open();
404                 com2.Open();
405 
406                 //Setup to ensure write will bock with type of handshake method being used
407                 if (Handshake.RequestToSend == handshake || Handshake.RequestToSendXOnXOff == handshake)
408                 {
409                     com2.RtsEnable = false;
410                 }
411 
412                 if (Handshake.XOnXOff == handshake || Handshake.RequestToSendXOnXOff == handshake)
413                 {
414                     com2.Write(XOffBuffer, 0, 1);
415                     Thread.Sleep(250);
416                 }
417 
418                 //Write a random char array asynchronously so we can verify some things while the write call is blocking
419                 t.Start();
420                 TCSupport.WaitForTaskToStart(t);
421                 TCSupport.WaitForExactWriteBufferLoad(com1, CHAR_SIZE_HANDSHAKE);
422 
423                 //Verify that CtsHolding is false if the RequestToSend or RequestToSendXOnXOff handshake method is used
424                 if ((Handshake.RequestToSend == handshake || Handshake.RequestToSendXOnXOff == handshake) && com1.CtsHolding)
425                 {
426                     Fail("ERROR!!! Expcted CtsHolding={0} actual {1}", false, com1.CtsHolding);
427                 }
428 
429                 //Setup to ensure write will succeed
430                 if (Handshake.RequestToSend == handshake || Handshake.RequestToSendXOnXOff == handshake)
431                 {
432                     com2.RtsEnable = true;
433                 }
434 
435                 if (Handshake.XOnXOff == handshake || Handshake.RequestToSendXOnXOff == handshake)
436                 {
437                     com2.Write(XOnBuffer, 0, 1);
438                 }
439 
440                 TCSupport.WaitForTaskCompletion(t);
441 
442                 //Verify that the correct number of bytes are in the buffer
443                 Assert.Equal(0, com1.BytesToWrite);
444 
445                 //Verify that CtsHolding is true if the RequestToSend or RequestToSendXOnXOff handshake method is used
446                 if ((Handshake.RequestToSend == handshake || Handshake.RequestToSendXOnXOff == handshake) &&
447                     !com1.CtsHolding)
448                 {
449                     Fail("ERROR!!! Expcted CtsHolding={0} actual {1}", true, com1.CtsHolding);
450                 }
451             }
452         }
453 
454         #endregion
455     }
456 }
457