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 Legacy.Support; 10 using Xunit; 11 12 namespace System.IO.Ports.Tests 13 { 14 public class SerialStream_BeginWrite : PortsTest 15 { 16 // The string size used for large byte array testing 17 private const int LARGE_BUFFER_SIZE = 2048; 18 19 // When we test Write and do not care about actually writing anything we must still 20 // create an byte array to pass into the method the following is the size of the 21 // byte array used in this situation 22 private const int DEFAULT_BUFFER_SIZE = 1; 23 private const int DEFAULT_BUFFER_OFFSET = 0; 24 private const int DEFAULT_BUFFER_COUNT = 1; 25 26 // The maximum buffer size when an exception occurs 27 private const int MAX_BUFFER_SIZE_FOR_EXCEPTION = 255; 28 29 // The maximum buffer size when an exception is not expected 30 private const int MAX_BUFFER_SIZE = 8; 31 32 // The default number of times the write method is called when verifying write 33 private const int DEFAULT_NUM_WRITES = 3; 34 35 // The default number of bytes to write 36 private const int DEFAULT_NUM_BYTES_TO_WRITE = 128; 37 38 // Maximum time to wait for processing the read command to complete 39 private const int MAX_WAIT_WRITE_COMPLETE = 1000; 40 41 #region Test Cases 42 [ConditionalFact(nameof(HasOneSerialPort))] Buffer_Null()43 public void Buffer_Null() 44 { 45 VerifyWriteException(null, 0, 1, typeof(ArgumentNullException)); 46 } 47 48 [ConditionalFact(nameof(HasOneSerialPort))] Offset_NEG1()49 public void Offset_NEG1() 50 { 51 VerifyWriteException(new byte[DEFAULT_BUFFER_SIZE], -1, DEFAULT_BUFFER_COUNT, typeof(ArgumentOutOfRangeException)); 52 } 53 54 [ConditionalFact(nameof(HasOneSerialPort))] Offset_NEGRND()55 public void Offset_NEGRND() 56 { 57 Random rndGen = new Random(-55); 58 59 VerifyWriteException(new byte[DEFAULT_BUFFER_SIZE], rndGen.Next(int.MinValue, 0), DEFAULT_BUFFER_COUNT, typeof(ArgumentOutOfRangeException)); 60 } 61 62 [ConditionalFact(nameof(HasOneSerialPort))] Offset_MinInt()63 public void Offset_MinInt() 64 { 65 VerifyWriteException(new byte[DEFAULT_BUFFER_SIZE], int.MinValue, DEFAULT_BUFFER_COUNT, typeof(ArgumentOutOfRangeException)); 66 } 67 68 [ConditionalFact(nameof(HasOneSerialPort))] Count_NEG1()69 public void Count_NEG1() 70 { 71 VerifyWriteException(new byte[DEFAULT_BUFFER_SIZE], DEFAULT_BUFFER_OFFSET, -1, typeof(ArgumentOutOfRangeException)); 72 } 73 74 [ConditionalFact(nameof(HasOneSerialPort))] Count_NEGRND()75 public void Count_NEGRND() 76 { 77 Random rndGen = new Random(-55); 78 79 VerifyWriteException(new byte[DEFAULT_BUFFER_SIZE], DEFAULT_BUFFER_OFFSET, rndGen.Next(int.MinValue, 0), typeof(ArgumentOutOfRangeException)); 80 } 81 82 [ConditionalFact(nameof(HasOneSerialPort))] Count_MinInt()83 public void Count_MinInt() 84 { 85 VerifyWriteException(new byte[DEFAULT_BUFFER_SIZE], DEFAULT_BUFFER_OFFSET, int.MinValue, typeof(ArgumentOutOfRangeException)); 86 } 87 88 [ConditionalFact(nameof(HasOneSerialPort))] OffsetCount_EQ_Length_Plus_1()89 public void OffsetCount_EQ_Length_Plus_1() 90 { 91 Random rndGen = new Random(-55); 92 int bufferLength = rndGen.Next(1, MAX_BUFFER_SIZE_FOR_EXCEPTION); 93 int offset = rndGen.Next(0, bufferLength); 94 int count = bufferLength + 1 - offset; 95 Type expectedException = typeof(ArgumentException); 96 97 VerifyWriteException(new byte[bufferLength], offset, count, expectedException); 98 } 99 100 [ConditionalFact(nameof(HasOneSerialPort))] OffsetCount_GT_Length()101 public void OffsetCount_GT_Length() 102 { 103 Random rndGen = new Random(-55); 104 int bufferLength = rndGen.Next(1, MAX_BUFFER_SIZE_FOR_EXCEPTION); 105 int offset = rndGen.Next(0, bufferLength); 106 int count = rndGen.Next(bufferLength + 1 - offset, int.MaxValue); 107 Type expectedException = typeof(ArgumentException); 108 109 VerifyWriteException(new byte[bufferLength], offset, count, expectedException); 110 } 111 112 [ConditionalFact(nameof(HasOneSerialPort))] Offset_GT_Length()113 public void Offset_GT_Length() 114 { 115 Random rndGen = new Random(-55); 116 int bufferLength = rndGen.Next(1, MAX_BUFFER_SIZE_FOR_EXCEPTION); 117 int offset = rndGen.Next(bufferLength, int.MaxValue); 118 int count = DEFAULT_BUFFER_COUNT; 119 Type expectedException = typeof(ArgumentException); 120 121 VerifyWriteException(new byte[bufferLength], offset, count, expectedException); 122 } 123 124 [ConditionalFact(nameof(HasOneSerialPort))] Count_GT_Length()125 public void Count_GT_Length() 126 { 127 Random rndGen = new Random(-55); 128 int bufferLength = rndGen.Next(1, MAX_BUFFER_SIZE_FOR_EXCEPTION); 129 int offset = DEFAULT_BUFFER_OFFSET; 130 int count = rndGen.Next(bufferLength + 1, int.MaxValue); 131 Type expectedException = typeof(ArgumentException); 132 133 VerifyWriteException(new byte[bufferLength], offset, count, expectedException); 134 } 135 136 [ConditionalFact(nameof(HasNullModem))] OffsetCount_EQ_Length()137 public void OffsetCount_EQ_Length() 138 { 139 Random rndGen = new Random(-55); 140 int bufferLength = rndGen.Next(1, MAX_BUFFER_SIZE); 141 int offset = rndGen.Next(0, bufferLength - 1); 142 int count = bufferLength - offset; 143 144 VerifyWrite(new byte[bufferLength], offset, count); 145 } 146 147 [ConditionalFact(nameof(HasNullModem))] Offset_EQ_Length_Minus_1()148 public void Offset_EQ_Length_Minus_1() 149 { 150 Random rndGen = new Random(-55); 151 int bufferLength = rndGen.Next(1, MAX_BUFFER_SIZE); 152 int offset = bufferLength - 1; 153 int count = 1; 154 155 VerifyWrite(new byte[bufferLength], offset, count); 156 } 157 158 [ConditionalFact(nameof(HasNullModem))] Count_EQ_Length()159 public void Count_EQ_Length() 160 { 161 Random rndGen = new Random(-55); 162 int bufferLength = rndGen.Next(1, MAX_BUFFER_SIZE); 163 int offset = 0; 164 int count = bufferLength; 165 166 VerifyWrite(new byte[bufferLength], offset, count); 167 } 168 169 [ConditionalFact(nameof(HasNullModem))] ASCIIEncoding()170 public void ASCIIEncoding() 171 { 172 Random rndGen = new Random(-55); 173 int bufferLength = rndGen.Next(1, MAX_BUFFER_SIZE); 174 int offset = rndGen.Next(0, bufferLength - 1); 175 int count = rndGen.Next(1, bufferLength - offset); 176 177 VerifyWrite(new byte[bufferLength], offset, count, new ASCIIEncoding()); 178 } 179 180 [ConditionalFact(nameof(HasNullModem))] UTF8Encoding()181 public void UTF8Encoding() 182 { 183 Random rndGen = new Random(-55); 184 int bufferLength = rndGen.Next(1, MAX_BUFFER_SIZE); 185 int offset = rndGen.Next(0, bufferLength - 1); 186 int count = rndGen.Next(1, bufferLength - offset); 187 188 VerifyWrite(new byte[bufferLength], offset, count, new UTF8Encoding()); 189 } 190 191 [ConditionalFact(nameof(HasNullModem))] UTF32Encoding()192 public void UTF32Encoding() 193 { 194 Random rndGen = new Random(-55); 195 int bufferLength = rndGen.Next(1, MAX_BUFFER_SIZE); 196 int offset = rndGen.Next(0, bufferLength - 1); 197 int count = rndGen.Next(1, bufferLength - offset); 198 199 VerifyWrite(new byte[bufferLength], offset, count, new UTF32Encoding()); 200 } 201 202 [ConditionalFact(nameof(HasNullModem))] UnicodeEncoding()203 public void UnicodeEncoding() 204 { 205 Random rndGen = new Random(-55); 206 int bufferLength = rndGen.Next(1, MAX_BUFFER_SIZE); 207 int offset = rndGen.Next(0, bufferLength - 1); 208 int count = rndGen.Next(1, bufferLength - offset); 209 210 VerifyWrite(new byte[bufferLength], offset, count, new UnicodeEncoding()); 211 } 212 213 [ConditionalFact(nameof(HasNullModem), nameof(HasHardwareFlowControl))] LargeBuffer()214 public void LargeBuffer() 215 { 216 int bufferLength = LARGE_BUFFER_SIZE; 217 int offset = 0; 218 int count = bufferLength; 219 220 VerifyWrite(new byte[bufferLength], offset, count, 1); 221 } 222 223 [ConditionalFact(nameof(HasNullModem))] Callback()224 public void Callback() 225 { 226 using (SerialPort com1 = new SerialPort(TCSupport.LocalMachineSerialInfo.FirstAvailablePortName)) 227 using (SerialPort com2 = new SerialPort(TCSupport.LocalMachineSerialInfo.SecondAvailablePortName)) 228 { 229 CallbackHandler callbackHandler = new CallbackHandler(); 230 231 Debug.WriteLine("Verifying BeginWrite with a callback specified"); 232 233 com1.Open(); 234 com2.Open(); 235 236 IAsyncResult writeAsyncResult = com1.BaseStream.BeginWrite(new byte[DEFAULT_NUM_BYTES_TO_WRITE], 0, 237 DEFAULT_NUM_BYTES_TO_WRITE, callbackHandler.Callback, this); 238 callbackHandler.BeginWriteAysncResult = writeAsyncResult; 239 240 Assert.Equal(this, writeAsyncResult.AsyncState); 241 Assert.False(writeAsyncResult.CompletedSynchronously, "Should not have completed sync"); 242 Assert.False(writeAsyncResult.IsCompleted, "Should not have completed yet"); 243 244 com2.RtsEnable = true; 245 246 // callbackHandler.WriteAysncResult guarantees that the callback has been called however it does not gauarentee that 247 // the code calling the callback has finished it's processing 248 IAsyncResult callbackWriteAsyncResult = callbackHandler.WriteAysncResult; 249 250 // No we have to wait for the callbackHandler to complete 251 int elapsedTime = 0; 252 while (!callbackWriteAsyncResult.IsCompleted && elapsedTime < MAX_WAIT_WRITE_COMPLETE) 253 { 254 Thread.Sleep(10); 255 elapsedTime += 10; 256 } 257 258 Assert.Equal(this, callbackWriteAsyncResult.AsyncState); 259 Assert.False(callbackWriteAsyncResult.CompletedSynchronously, "Should not have completed sync (cback)"); 260 Assert.True(callbackWriteAsyncResult.IsCompleted, "Should have completed (cback)"); 261 Assert.Equal(this, writeAsyncResult.AsyncState); 262 Assert.False(writeAsyncResult.CompletedSynchronously, "Should not have completed sync (write)"); 263 Assert.True(writeAsyncResult.IsCompleted, "Should have completed (write)"); 264 } 265 } 266 267 [ConditionalFact(nameof(HasNullModem), nameof(HasHardwareFlowControl))] Callback_State()268 public void Callback_State() 269 { 270 using (SerialPort com1 = new SerialPort(TCSupport.LocalMachineSerialInfo.FirstAvailablePortName)) 271 using (SerialPort com2 = new SerialPort(TCSupport.LocalMachineSerialInfo.SecondAvailablePortName)) 272 { 273 CallbackHandler callbackHandler = new CallbackHandler(); 274 275 Debug.WriteLine("Verifying BeginWrite with a callback and state specified"); 276 com1.Handshake = Handshake.RequestToSend; 277 com1.Open(); 278 com2.Open(); 279 280 IAsyncResult writeAsyncResult = com1.BaseStream.BeginWrite(new byte[DEFAULT_NUM_BYTES_TO_WRITE], 0, DEFAULT_NUM_BYTES_TO_WRITE, callbackHandler.Callback, this); 281 callbackHandler.BeginWriteAysncResult = writeAsyncResult; 282 283 Assert.Equal(this, writeAsyncResult.AsyncState); 284 Assert.False(writeAsyncResult.CompletedSynchronously, "Should not have completed sync"); 285 Assert.False(writeAsyncResult.IsCompleted, "Should not have completed yet"); 286 287 com2.RtsEnable = true; 288 289 // callbackHandler.WriteAysncResult guarantees that the callback has been called however it does not gauarentee that 290 // the code calling the callback has finished it's processing 291 IAsyncResult callbackWriteAsyncResult = callbackHandler.WriteAysncResult; 292 293 // No we have to wait for the callbackHandler to complete 294 int elapsedTime = 0; 295 while (!callbackWriteAsyncResult.IsCompleted && elapsedTime < MAX_WAIT_WRITE_COMPLETE) 296 { 297 Thread.Sleep(10); 298 elapsedTime += 10; 299 } 300 301 Assert.Equal(this, callbackWriteAsyncResult.AsyncState); 302 Assert.False(writeAsyncResult.CompletedSynchronously, "Should not have completed sync (cback)"); 303 Assert.True(callbackWriteAsyncResult.IsCompleted, "Should have completed (cback)"); 304 Assert.Equal(this, writeAsyncResult.AsyncState); 305 Assert.False(writeAsyncResult.CompletedSynchronously, "Should not have completed sync (write)"); 306 Assert.True(writeAsyncResult.IsCompleted, "Should have completed (write)"); 307 } 308 } 309 310 [ConditionalFact(nameof(HasOneSerialPort))] InBreak()311 public void InBreak() 312 { 313 using (SerialPort com1 = new SerialPort(TCSupport.LocalMachineSerialInfo.FirstAvailablePortName)) 314 { 315 Debug.WriteLine("Verifying BeginWrite throws InvalidOperationException while in a Break"); 316 com1.Open(); 317 com1.BreakState = true; 318 319 Assert.Throws<InvalidOperationException>(() => com1.BaseStream.BeginWrite(new byte[8], 0, 8, null, null)); 320 } 321 } 322 #endregion 323 324 #region Verification for Test Cases 325 VerifyWriteException(byte[] buffer, int offset, int count, Type expectedException)326 private void VerifyWriteException(byte[] buffer, int offset, int count, Type expectedException) 327 { 328 using (SerialPort com = new SerialPort(TCSupport.LocalMachineSerialInfo.FirstAvailablePortName)) 329 { 330 int bufferLength = null == buffer ? 0 : buffer.Length; 331 332 Debug.WriteLine("Verifying write method throws {0} buffer.Lenght={1}, offset={2}, count={3}", 333 expectedException, bufferLength, offset, count); 334 com.Open(); 335 336 Assert.Throws(expectedException, () => com.BaseStream.BeginWrite(buffer, offset, count, null, null)); 337 } 338 } 339 VerifyWrite(byte[] buffer, int offset, int count)340 private void VerifyWrite(byte[] buffer, int offset, int count) 341 { 342 VerifyWrite(buffer, offset, count, new ASCIIEncoding()); 343 } 344 VerifyWrite(byte[] buffer, int offset, int count, int numWrites)345 private void VerifyWrite(byte[] buffer, int offset, int count, int numWrites) 346 { 347 VerifyWrite(buffer, offset, count, new ASCIIEncoding(), numWrites); 348 } 349 VerifyWrite(byte[] buffer, int offset, int count, Encoding encoding)350 private void VerifyWrite(byte[] buffer, int offset, int count, Encoding encoding) 351 { 352 VerifyWrite(buffer, offset, count, encoding, DEFAULT_NUM_WRITES); 353 } 354 VerifyWrite(byte[] buffer, int offset, int count, Encoding encoding, int numWrites)355 private void VerifyWrite(byte[] buffer, int offset, int count, Encoding encoding, int numWrites) 356 { 357 using (SerialPort com1 = new SerialPort(TCSupport.LocalMachineSerialInfo.FirstAvailablePortName)) 358 using (SerialPort com2 = new SerialPort(TCSupport.LocalMachineSerialInfo.SecondAvailablePortName)) 359 { 360 Random rndGen = new Random(-55); 361 362 Debug.WriteLine("Verifying write method buffer.Lenght={0}, offset={1}, count={2}, endocing={3}", 363 buffer.Length, offset, count, encoding.EncodingName); 364 365 com1.Encoding = encoding; 366 com2.Encoding = encoding; 367 368 com1.Open(); 369 com2.Open(); 370 371 for (int i = 0; i < buffer.Length; i++) 372 { 373 buffer[i] = (byte)rndGen.Next(0, 256); 374 } 375 376 VerifyWriteByteArray(buffer, offset, count, com1, com2, numWrites); 377 } 378 } 379 VerifyWriteByteArray(byte[] buffer, int offset, int count, SerialPort com1, SerialPort com2, int numWrites)380 private void VerifyWriteByteArray(byte[] buffer, int offset, int count, SerialPort com1, SerialPort com2, int numWrites) 381 { 382 int index = 0; 383 CallbackHandler callbackHandler = new CallbackHandler(); 384 385 var oldBuffer = (byte[])buffer.Clone(); 386 var expectedBytes = new byte[count]; 387 var actualBytes = new byte[expectedBytes.Length * numWrites]; 388 389 for (int i = 0; i < count; i++) 390 { 391 expectedBytes[i] = buffer[i + offset]; 392 } 393 394 for (int i = 0; i < numWrites; i++) 395 { 396 IAsyncResult writeAsyncResult = com1.BaseStream.BeginWrite(buffer, offset, count, callbackHandler.Callback, this); 397 writeAsyncResult.AsyncWaitHandle.WaitOne(); 398 callbackHandler.BeginWriteAysncResult = writeAsyncResult; 399 400 com1.BaseStream.EndWrite(writeAsyncResult); 401 402 IAsyncResult callbackWriteAsyncResult = callbackHandler.WriteAysncResult; 403 Assert.Equal(this, callbackWriteAsyncResult.AsyncState); 404 Assert.False(callbackWriteAsyncResult.CompletedSynchronously, "Should not have completed sync (cback)"); 405 Assert.True(callbackWriteAsyncResult.IsCompleted, "Should have completed (cback)"); 406 Assert.Equal(this, writeAsyncResult.AsyncState); 407 Assert.False(writeAsyncResult.CompletedSynchronously, "Should not have completed sync (write)"); 408 Assert.True(writeAsyncResult.IsCompleted, "Should have completed (write)"); 409 } 410 411 com2.ReadTimeout = 500; 412 Thread.Sleep((int)(((expectedBytes.Length * numWrites * 10.0) / com1.BaudRate) * 1000) + 250); 413 414 // Make sure buffer was not altered during the write call 415 for (int i = 0; i < buffer.Length; i++) 416 { 417 if (buffer[i] != oldBuffer[i]) 418 { 419 Fail("ERROR!!!: The contents of the buffer were changed from {0} to {1} at {2}", oldBuffer[i], buffer[i], i); 420 } 421 } 422 423 while (true) 424 { 425 int byteRead; 426 try 427 { 428 byteRead = com2.ReadByte(); 429 } 430 catch (TimeoutException) 431 { 432 break; 433 } 434 435 if (actualBytes.Length <= index) 436 { 437 // If we have read in more bytes then we expect 438 Fail("ERROR!!!: We have received more bytes then were sent"); 439 break; 440 } 441 442 actualBytes[index] = (byte)byteRead; 443 index++; 444 if (actualBytes.Length - index != com2.BytesToRead) 445 { 446 Fail("ERROR!!!: Expected BytesToRead={0} actual={1}", actualBytes.Length - index, com2.BytesToRead); 447 } 448 } 449 450 // Compare the bytes that were read with the ones we expected to read 451 for (int j = 0; j < numWrites; j++) 452 { 453 for (int i = 0; i < expectedBytes.Length; i++) 454 { 455 if (expectedBytes[i] != actualBytes[i + expectedBytes.Length * j]) 456 { 457 Fail("ERROR!!!: Expected to read byte {0} actual read {1} at {2}", (int)expectedBytes[i], (int)actualBytes[i + expectedBytes.Length * j], i); 458 } 459 } 460 } 461 } 462 463 private class CallbackHandler 464 { 465 private IAsyncResult _writeAysncResult; 466 private IAsyncResult _beginWriteAysncResult; 467 private readonly SerialPort _com; 468 CallbackHandler()469 public CallbackHandler() : this(null) { } 470 CallbackHandler(SerialPort com)471 private CallbackHandler(SerialPort com) 472 { 473 _com = com; 474 } 475 Callback(IAsyncResult writeAysncResult)476 public void Callback(IAsyncResult writeAysncResult) 477 { 478 Debug.WriteLine("About to enter callback lock (already entered {0})", Monitor.IsEntered(this)); 479 lock (this) 480 { 481 Debug.WriteLine("Inside callback lock"); 482 _writeAysncResult = writeAysncResult; 483 484 if (!writeAysncResult.IsCompleted) 485 { 486 throw new Exception("Err_23984afaea Expected IAsyncResult passed into callback to not be completed"); 487 } 488 489 while (null == _beginWriteAysncResult) 490 { 491 Assert.True(Monitor.Wait(this, 5000), "Monitor.Wait in Callback"); 492 } 493 494 if (null != _beginWriteAysncResult && !_beginWriteAysncResult.IsCompleted) 495 { 496 throw new Exception("Err_7907azpu Expected IAsyncResult returned from begin write to not be completed"); 497 } 498 499 if (null != _com) 500 { 501 _com.BaseStream.EndWrite(_beginWriteAysncResult); 502 if (!_beginWriteAysncResult.IsCompleted) 503 { 504 throw new Exception("Err_6498afead Expected IAsyncResult returned from begin write to not be completed"); 505 } 506 507 if (!writeAysncResult.IsCompleted) 508 { 509 throw new Exception("Err_1398ehpo Expected IAsyncResult passed into callback to not be completed"); 510 } 511 } 512 513 Monitor.Pulse(this); 514 } 515 } 516 517 518 public IAsyncResult WriteAysncResult 519 { 520 get 521 { 522 lock (this) 523 { 524 while (null == _writeAysncResult) 525 { 526 Monitor.Wait(this); 527 } 528 529 return _writeAysncResult; 530 } 531 } 532 } 533 534 public IAsyncResult BeginWriteAysncResult 535 { 536 get 537 { 538 return _beginWriteAysncResult; 539 } 540 set 541 { 542 lock (this) 543 { 544 _beginWriteAysncResult = value; 545 Monitor.Pulse(this); 546 } 547 } 548 } 549 } 550 551 #endregion 552 } 553 } 554