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 Read_char_int_int_Generic : PortsTest
17     {
18         //Set bounds fore random timeout values.
19         //If the min is to low read 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 read method and the testcase fails.
28         private const double maxPercentageDifference = .15;
29 
30         //The number of random bytes to receive for parity testing
31         private const int numRndBytesPairty = 8;
32 
33         //The number of characters to read at a time for parity testing
34         private const int numBytesReadPairty = 2;
35 
36         //The number of random bytes to receive for BytesToRead testing
37         private const int numRndBytesToRead = 16;
38 
39         //When we test Read and do not care about actually reading anything we must still
40         //create an byte array to pass into the method the following is the size of the
41         //byte array used in this situation
42         private const int defaultCharArraySize = 1;
43         private const int NUM_TRYS = 5;
44 
45         #region Test Cases
46 
47         [Fact]
ReadWithoutOpen()48         public void ReadWithoutOpen()
49         {
50             using (SerialPort com = new SerialPort())
51             {
52                 Debug.WriteLine("Verifying read method throws exception without a call to Open()");
53 
54                 VerifyReadException(com, typeof(InvalidOperationException));
55             }
56         }
57 
58         [ConditionalFact(nameof(HasOneSerialPort))]
ReadAfterFailedOpen()59         public void ReadAfterFailedOpen()
60         {
61             using (SerialPort com = new SerialPort("BAD_PORT_NAME"))
62             {
63                 Debug.WriteLine("Verifying read method throws exception with a failed call to Open()");
64 
65                 //Since the PortName is set to a bad port name Open will thrown an exception
66                 //however we don't care what it is since we are verifying a read method
67                 Assert.ThrowsAny<Exception>(() => com.Open());
68 
69                 VerifyReadException(com, typeof(InvalidOperationException));
70             }
71         }
72 
73 
74         [ConditionalFact(nameof(HasOneSerialPort))]
ReadAfterClose()75         public void ReadAfterClose()
76         {
77             using (SerialPort com = new SerialPort(TCSupport.LocalMachineSerialInfo.FirstAvailablePortName))
78             {
79                 Debug.WriteLine("Verifying read method throws exception after a call to Cloes()");
80                 com.Open();
81                 com.Close();
82 
83                 VerifyReadException(com, typeof(InvalidOperationException));
84             }
85         }
86 
87         [Trait(XunitConstants.Category, XunitConstants.IgnoreForCI)]  // Timing-sensitive
88         [ConditionalFact(nameof(HasOneSerialPort))]
Timeout()89         public void Timeout()
90         {
91             using (SerialPort com = new SerialPort(TCSupport.LocalMachineSerialInfo.FirstAvailablePortName))
92             {
93                 Random rndGen = new Random(-55);
94 
95                 com.ReadTimeout = rndGen.Next(minRandomTimeout, maxRandomTimeout);
96                 Debug.WriteLine("Verifying ReadTimeout={0}", com.ReadTimeout);
97                 com.Open();
98 
99                 VerifyTimeout(com);
100             }
101         }
102 
103         [Trait(XunitConstants.Category, XunitConstants.IgnoreForCI)]  // Timing-sensitive
104         [ConditionalFact(nameof(HasOneSerialPort))]
SuccessiveReadTimeoutNoData()105         public void SuccessiveReadTimeoutNoData()
106         {
107             using (SerialPort com = new SerialPort(TCSupport.LocalMachineSerialInfo.FirstAvailablePortName))
108             {
109                 Random rndGen = new Random(-55);
110 
111 
112                 com.ReadTimeout = rndGen.Next(minRandomTimeout, maxRandomTimeout);
113                 //		com.Encoding = new System.Text.UTF7Encoding();
114                 com.Encoding = Encoding.Unicode;
115 
116                 Debug.WriteLine("Verifying ReadTimeout={0} with successive call to read method and no data", com.ReadTimeout);
117                 com.Open();
118 
119                 Assert.Throws<TimeoutException>(() => com.Read(new char[defaultCharArraySize], 0, defaultCharArraySize));
120 
121                 VerifyTimeout(com);
122             }
123         }
124 
125         [ConditionalFact(nameof(HasNullModem))]
SuccessiveReadTimeoutSomeData()126         public void SuccessiveReadTimeoutSomeData()
127         {
128             using (SerialPort com1 = new SerialPort(TCSupport.LocalMachineSerialInfo.FirstAvailablePortName))
129             {
130                 Random rndGen = new Random(-55);
131                 var t = new Task(WriteToCom1);
132 
133                 com1.ReadTimeout = rndGen.Next(minRandomTimeout, maxRandomTimeout);
134                 com1.Encoding = new UTF8Encoding();
135 
136                 Debug.WriteLine("Verifying ReadTimeout={0} with successive call to read method and some data being received in the first call", com1.ReadTimeout);
137                 com1.Open();
138 
139                 //Call WriteToCom1 asynchronously this will write to com1 some time before the following call
140                 //to a read method times out
141                 t.Start();
142 
143                 try
144                 {
145                     com1.Read(new char[defaultCharArraySize], 0, defaultCharArraySize);
146                 }
147                 catch (TimeoutException)
148                 {
149                 }
150 
151                 TCSupport.WaitForTaskCompletion(t);
152 
153                 //Make sure there is no bytes in the buffer so the next call to read will timeout
154                 com1.DiscardInBuffer();
155                 VerifyTimeout(com1);
156             }
157         }
158 
WriteToCom1()159         private void WriteToCom1()
160         {
161             using (SerialPort com2 = new SerialPort(TCSupport.LocalMachineSerialInfo.SecondAvailablePortName))
162             {
163                 Random rndGen = new Random(-55);
164                 byte[] xmitBuffer = new byte[1];
165                 int sleepPeriod = rndGen.Next(minRandomTimeout, maxRandomTimeout / 2);
166 
167                 //Sleep some random period with of a maximum duration of half the largest possible timeout value for a read method on COM1
168                 Thread.Sleep(sleepPeriod);
169                 com2.Open();
170                 com2.Write(xmitBuffer, 0, xmitBuffer.Length);
171             }
172         }
173 
174         [ConditionalFact(nameof(HasNullModem))]
DefaultParityReplaceByte()175         public void DefaultParityReplaceByte()
176         {
177             VerifyParityReplaceByte(-1, numRndBytesPairty - 2);
178         }
179 
180         [ConditionalFact(nameof(HasNullModem))]
NoParityReplaceByte()181         public void NoParityReplaceByte()
182         {
183             Random rndGen = new Random(-55);
184 
185             VerifyParityReplaceByte('\0', rndGen.Next(0, numRndBytesPairty - 1));
186         }
187 
188         [ConditionalFact(nameof(HasNullModem))]
RNDParityReplaceByte()189         public void RNDParityReplaceByte()
190         {
191             Random rndGen = new Random(-55);
192 
193             VerifyParityReplaceByte(rndGen.Next(0, 128), 0);
194         }
195 
196         [ConditionalFact(nameof(HasNullModem))]
ParityErrorOnLastByte()197         public void ParityErrorOnLastByte()
198         {
199             using (SerialPort com1 = new SerialPort(TCSupport.LocalMachineSerialInfo.FirstAvailablePortName))
200             using (SerialPort com2 = new SerialPort(TCSupport.LocalMachineSerialInfo.SecondAvailablePortName))
201             {
202                 Random rndGen = new Random(15);
203                 byte[] bytesToWrite = new byte[numRndBytesPairty];
204                 char[] expectedChars = new char[numRndBytesPairty];
205                 char[] actualChars = new char[numRndBytesPairty + 1];
206 
207                 /* 1 Additional character gets added to the input buffer when the parity error occurs on the last byte of a stream
208                  We are verifying that besides this everything gets read in correctly. See NDP Whidbey: 24216 for more info on this */
209                 Debug.WriteLine("Verifying default ParityReplace byte with a parity errro on the last byte");
210 
211                 //Genrate random characters without an parity error
212                 for (int i = 0; i < bytesToWrite.Length; i++)
213                 {
214                     byte randByte = (byte)rndGen.Next(0, 128);
215 
216                     bytesToWrite[i] = randByte;
217                     expectedChars[i] = (char)randByte;
218                 }
219 
220                 bytesToWrite[bytesToWrite.Length - 1] = (byte)(bytesToWrite[bytesToWrite.Length - 1] | 0x80);
221                 //Create a parity error on the last byte
222                 expectedChars[expectedChars.Length - 1] = (char)com1.ParityReplace;
223                 // Set the last expected char to be the ParityReplace Byte
224 
225                 com1.Parity = Parity.Space;
226                 com1.DataBits = 7;
227                 com1.ReadTimeout = 250;
228                 com1.Open();
229 
230                 com2.Open();
231                 com2.Write(bytesToWrite, 0, bytesToWrite.Length);
232 
233                 TCSupport.WaitForReadBufferToLoad(com1, bytesToWrite.Length + 1);
234 
235                 com1.Read(actualChars, 0, actualChars.Length);
236 
237                 //Compare the chars that were written with the ones we expected to read
238                 for (int i = 0; i < expectedChars.Length; i++)
239                 {
240                     if (expectedChars[i] != actualChars[i])
241                     {
242                         Fail("ERROR!!!: Expected to read {0}  actual read  {1}", (int)expectedChars[i], (int)actualChars[i]);
243                     }
244                 }
245 
246                 if (1 < com1.BytesToRead)
247                 {
248                     Debug.WriteLine("ByteRead={0}, {1}", com1.ReadByte(), bytesToWrite[bytesToWrite.Length - 1]);
249                     Fail("ERROR!!!: Expected BytesToRead=0 actual={0}", com1.BytesToRead);
250                 }
251 
252                 bytesToWrite[bytesToWrite.Length - 1] = (byte)(bytesToWrite[bytesToWrite.Length - 1] & 0x7F);
253                 //Clear the parity error on the last byte
254                 expectedChars[expectedChars.Length - 1] = (char)bytesToWrite[bytesToWrite.Length - 1];
255                 VerifyRead(com1, com2, bytesToWrite, expectedChars, expectedChars.Length / 2);
256             }
257         }
258 
259         [ConditionalFact(nameof(HasNullModem))]
BytesToRead_RND_Buffer_Size()260         public void BytesToRead_RND_Buffer_Size()
261         {
262             Random rndGen = new Random(-55);
263 
264             VerifyBytesToRead(rndGen.Next(1, 2 * numRndBytesToRead));
265         }
266 
267         [ConditionalFact(nameof(HasNullModem))]
BytesToRead_1_Buffer_Size()268         public void BytesToRead_1_Buffer_Size()
269         {
270             VerifyBytesToRead(1);
271         }
272 
273         [ConditionalFact(nameof(HasNullModem))]
BytesToRead_Equal_Buffer_Size()274         public void BytesToRead_Equal_Buffer_Size()
275         {
276             Random rndGen = new Random(-55);
277 
278             VerifyBytesToRead(numRndBytesToRead);
279         }
280         #endregion
281 
282         #region Verification for Test Cases
VerifyTimeout(SerialPort com)283         private void VerifyTimeout(SerialPort com)
284         {
285             Stopwatch timer = new Stopwatch();
286             int expectedTime = com.ReadTimeout;
287             int actualTime = 0;
288             double percentageDifference;
289 
290             //Warm up read method
291             Assert.Throws<TimeoutException>(() => com.Read(new char[defaultCharArraySize], 0, defaultCharArraySize));
292 
293             Thread.CurrentThread.Priority = ThreadPriority.Highest;
294             for (int i = 0; i < NUM_TRYS; i++)
295             {
296                 timer.Start();
297 
298                 Assert.Throws<TimeoutException>(() => com.Read(new char[defaultCharArraySize], 0, defaultCharArraySize));
299 
300                 timer.Stop();
301 
302                 actualTime += (int)timer.ElapsedMilliseconds;
303                 timer.Reset();
304             }
305 
306             Thread.CurrentThread.Priority = ThreadPriority.Normal;
307             actualTime /= NUM_TRYS;
308             percentageDifference = Math.Abs((expectedTime - actualTime) / (double)expectedTime);
309 
310             //Verify that the percentage difference between the expected and actual timeout is less then maxPercentageDifference
311             if (maxPercentageDifference < percentageDifference)
312             {
313                 Fail("ERROR!!!: The read method timed-out in {0} expected {1} percentage difference: {2}", actualTime, expectedTime, percentageDifference);
314             }
315         }
316 
VerifyReadException(SerialPort com, Type expectedException)317         private void VerifyReadException(SerialPort com, Type expectedException)
318         {
319             Assert.Throws(expectedException, () => com.Read(new char[defaultCharArraySize], 0, defaultCharArraySize));
320         }
321 
VerifyParityReplaceByte(int parityReplace, int parityErrorIndex)322         private void VerifyParityReplaceByte(int parityReplace, int parityErrorIndex)
323         {
324             using (SerialPort com1 = new SerialPort(TCSupport.LocalMachineSerialInfo.FirstAvailablePortName))
325             using (SerialPort com2 = new SerialPort(TCSupport.LocalMachineSerialInfo.SecondAvailablePortName))
326             {
327                 Random rndGen = new Random(-55);
328                 byte[] bytesToWrite = new byte[numRndBytesPairty];
329                 char[] expectedChars = new char[numRndBytesPairty];
330                 byte expectedByte;
331 
332                 //Genrate random characters without an parity error
333                 for (int i = 0; i < bytesToWrite.Length; i++)
334                 {
335                     byte randByte = (byte)rndGen.Next(0, 128);
336 
337                     bytesToWrite[i] = randByte;
338                     expectedChars[i] = (char)randByte;
339                 }
340 
341                 if (-1 == parityReplace)
342                 {
343                     //If parityReplace is -1 and we should just use the default value
344                     expectedByte = com1.ParityReplace;
345                 }
346                 else if ('\0' == parityReplace)
347                 {
348                     //If parityReplace is the null charachater and parity replacement should not occur
349                     com1.ParityReplace = (byte)parityReplace;
350                     expectedByte = bytesToWrite[parityErrorIndex];
351                 }
352                 else
353                 {
354                     //Else parityReplace was set to a value and we should expect this value to be returned on a parity error
355                     com1.ParityReplace = (byte)parityReplace;
356                     expectedByte = (byte)parityReplace;
357                 }
358 
359                 //Create an parity error by setting the highest order bit to true
360                 bytesToWrite[parityErrorIndex] = (byte)(bytesToWrite[parityErrorIndex] | 0x80);
361                 expectedChars[parityErrorIndex] = (char)expectedByte;
362 
363                 Debug.WriteLine("Verifying ParityReplace={0} with an ParityError at: {1} ", com1.ParityReplace,
364                     parityErrorIndex);
365 
366                 com1.Parity = Parity.Space;
367                 com1.DataBits = 7;
368                 com1.Open();
369                 com2.Open();
370 
371                 VerifyRead(com1, com2, bytesToWrite, expectedChars, numBytesReadPairty);
372             }
373         }
374 
VerifyBytesToRead(int numBytesRead)375         private void VerifyBytesToRead(int numBytesRead)
376         {
377             using (SerialPort com1 = new SerialPort(TCSupport.LocalMachineSerialInfo.FirstAvailablePortName))
378             using (SerialPort com2 = new SerialPort(TCSupport.LocalMachineSerialInfo.SecondAvailablePortName))
379             {
380                 Random rndGen = new Random(-55);
381                 byte[] bytesToWrite = new byte[numRndBytesToRead];
382                 char[] expectedChars;
383                 ASCIIEncoding encoding = new ASCIIEncoding();
384 
385                 //Genrate random characters
386                 for (int i = 0; i < bytesToWrite.Length; i++)
387                 {
388                     byte randByte = (byte)rndGen.Next(0, 256);
389 
390                     bytesToWrite[i] = randByte;
391                 }
392 
393                 expectedChars = encoding.GetChars(bytesToWrite, 0, bytesToWrite.Length);
394 
395                 Debug.WriteLine("Verifying BytesToRead with a buffer of: {0} ", numBytesRead);
396                 com1.Open();
397                 com2.Open();
398 
399                 VerifyRead(com1, com2, bytesToWrite, expectedChars, numBytesRead);
400             }
401         }
402 
403 
VerifyRead(SerialPort com1, SerialPort com2, byte[] bytesToWrite, char[] expectedChars, int rcvBufferSize)404         private void VerifyRead(SerialPort com1, SerialPort com2, byte[] bytesToWrite, char[] expectedChars, int rcvBufferSize)
405         {
406             char[] rcvBuffer = new char[rcvBufferSize];
407             char[] buffer = new char[expectedChars.Length];
408             int totalBytesRead;
409             int totalCharsRead;
410             int bytesToRead;
411 
412             com2.Write(bytesToWrite, 0, bytesToWrite.Length);
413             com1.ReadTimeout = 250;
414 
415             TCSupport.WaitForReadBufferToLoad(com1, bytesToWrite.Length);
416 
417             totalBytesRead = 0;
418             totalCharsRead = 0;
419 
420             bytesToRead = com1.BytesToRead;
421 
422             while (true)
423             {
424                 int charsRead;
425                 try
426                 {
427                     charsRead = com1.Read(rcvBuffer, 0, rcvBufferSize);
428                 }
429                 catch (TimeoutException)
430                 {
431                     break;
432                 }
433 
434                 //While their are more characters to be read
435                 int bytesRead = com1.Encoding.GetByteCount(rcvBuffer, 0, charsRead);
436 
437                 if ((bytesToRead > bytesRead && rcvBufferSize != bytesRead) ||
438                     (bytesToRead <= bytesRead && bytesRead != bytesToRead))
439                 {
440                     //If we have not read all of the characters that we should have
441                     Fail("ERROR!!!: Read did not return all of the characters that were in SerialPort buffer");
442                 }
443 
444                 if (expectedChars.Length < totalCharsRead + charsRead)
445                 {
446                     //If we have read in more characters then we expect
447                     Fail("ERROR!!!: We have received more characters then were sent");
448                 }
449 
450                 Array.Copy(rcvBuffer, 0, buffer, totalCharsRead, charsRead);
451                 totalBytesRead += bytesRead;
452                 totalCharsRead += charsRead;
453 
454                 if (bytesToWrite.Length - totalBytesRead != com1.BytesToRead)
455                 {
456                     Fail("ERROR!!!: Expected BytesToRead={0} actual={1}", bytesToWrite.Length - totalBytesRead,
457                         com1.BytesToRead);
458                 }
459 
460                 bytesToRead = com1.BytesToRead;
461             }
462 
463             //Compare the chars that were written with the ones we expected to read
464             for (int i = 0; i < expectedChars.Length; i++)
465             {
466                 if (expectedChars[i] != buffer[i])
467                 {
468                     Fail("ERROR!!!: Expected to read {0}  actual read  {1}", (int)expectedChars[i], (int)buffer[i]);
469                 }
470             }
471         }
472 
473         #endregion
474     }
475 }
476