Console.Unix: fix OpenStandardInput Stream sometimes throwing for Reads. (#62153)
authorTom Deseyn <tom.deseyn@gmail.com>
Wed, 1 Dec 2021 08:35:36 +0000 (09:35 +0100)
committerGitHub <noreply@github.com>
Wed, 1 Dec 2021 08:35:36 +0000 (09:35 +0100)
* Console.Unix: fix OpenStandardInput Stream sometimes throwing for Reads.

When connected to a terminal reads happen line-by-line.

StdInReader caches the line in a StringBuilder.

Instead of returning when the destination buffer is full, an attempt
was made to decode the next chunk from the StringBuilder (if there is
one). This causes the encoder to throw for encoding into an empty buffer.

src/libraries/System.Console/src/System/IO/StdInReader.cs
src/libraries/System.Console/tests/ManualTests/ManualTests.cs

index 93c6595..fe0ab79 100644 (file)
@@ -116,12 +116,14 @@ namespace System.IO
             int charsUsedTotal = 0;
             foreach (ReadOnlyMemory<char> chunk in _readLineSB.GetChunks())
             {
+                Debug.Assert(!buffer.IsEmpty);
+
                 encoder.Convert(chunk.Span, buffer, flush: false, out int charsUsed, out int bytesUsed, out bool completed);
                 buffer = buffer.Slice(bytesUsed);
                 bytesUsedTotal += bytesUsed;
                 charsUsedTotal += charsUsed;
 
-                if (charsUsed == 0)
+                if (!completed || buffer.IsEmpty)
                 {
                     break;
                 }
index 70482d3..322322d 100644 (file)
@@ -5,6 +5,7 @@ using System.Collections.Generic;
 using System.Diagnostics;
 using System.Threading.Tasks;
 using System.IO;
+using System.Text;
 using Xunit;
 
 namespace System
@@ -46,6 +47,23 @@ namespace System
         }
 
         [ConditionalFact(nameof(ManualTestsEnabled))]
+        public static void ReadFromOpenStandardInput()
+        {
+            // The implementation in StdInReader uses a StringBuilder for caching. We want this builder to use
+            // multiple chunks. So the expectedLine is longer than 16 characters (StringBuilder.DefaultCapacity).
+            string expectedLine = $"This is a test for ReadFromOpenStandardInput.";
+            Assert.True(expectedLine.Length > new StringBuilder().Capacity);
+            Console.WriteLine($"Please type the sentence (without the quotes): \"{expectedLine}\"");
+            using Stream inputStream = Console.OpenStandardInput();
+            for (int i = 0; i < expectedLine.Length; i++)
+            {
+                Assert.Equal((byte)expectedLine[i], inputStream.ReadByte());
+            }
+            Assert.Equal((byte)'\n', inputStream.ReadByte());
+            AssertUserExpectedResults("the characters you typed properly echoed as you typed");
+        }
+
+        [ConditionalFact(nameof(ManualTestsEnabled))]
         public static void ConsoleReadSupportsBackspace()
         {
             const string expectedLine = "aab\r";