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