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