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