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