Skip to content

Commit 02c3e95

Browse files
Fix StreamReader EOF handling and improve perf (#69888)
* Fix StreamReader EOF handling and improve perf * Address PR feedback * Remove erroneous asserts Co-authored-by: Stephen Toub <[email protected]>
1 parent 9df6ea2 commit 02c3e95

File tree

2 files changed

+363
-153
lines changed

2 files changed

+363
-153
lines changed

src/libraries/System.IO/tests/StreamReader/StreamReaderTests.cs

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -676,5 +676,158 @@ public void StreamReader_WithOptionalArguments()
676676
Assert.False(tempStream.CanRead);
677677
}
678678
}
679+
680+
[Fact]
681+
public void Read_ShortStream_PerformsFinalFlushCorrectly()
682+
{
683+
MemoryStream memStream = new MemoryStream(new byte[] { 0x61 /* 'a' */, 0xF0 });
684+
685+
// First, use ReadToEnd API.
686+
687+
memStream.Position = 0;
688+
StreamReader reader = new StreamReader(memStream, Encoding.UTF8);
689+
Assert.Equal("a\uFFFD", reader.ReadToEnd());
690+
691+
// Next, use Read() API.
692+
693+
memStream.Position = 0;
694+
reader = new StreamReader(memStream, Encoding.UTF8);
695+
Assert.Equal('a', reader.Read());
696+
Assert.Equal('\uFFFD', reader.Read());
697+
Assert.Equal(-1, reader.Read());
698+
699+
// Next, use Read(Span<char>) API.
700+
701+
StringBuilder builder = new StringBuilder();
702+
Span<char> destBuffer = new char[1024];
703+
memStream.Position = 0;
704+
reader = new StreamReader(memStream, Encoding.UTF8);
705+
int charsRead;
706+
while ((charsRead = reader.Read(destBuffer)) > 0)
707+
{
708+
builder.Append(destBuffer.Slice(0, charsRead));
709+
}
710+
Assert.Equal("a\uFFFD", builder.ToString());
711+
712+
// Finally, use ReadLine API.
713+
714+
memStream.Position = 0;
715+
reader = new StreamReader(memStream, Encoding.UTF8);
716+
Assert.Equal("a\uFFFD", reader.ReadLine());
717+
Assert.Null(reader.ReadLine());
718+
}
719+
720+
[Fact]
721+
public void Read_LongStreamIntoShortBuffer_PerformsFinalFlushCorrectly()
722+
{
723+
MemoryStream memStream = new MemoryStream();
724+
memStream.Write(Enumerable.Repeat((byte)'a', 32 * 1024).ToArray());
725+
memStream.WriteByte(0xF0);
726+
string expected = new string('a', 32 * 1024) + "\uFFFD";
727+
728+
// First, use ReadToEnd API.
729+
730+
memStream.Position = 0;
731+
StreamReader reader = new StreamReader(memStream, encoding: Encoding.UTF8, bufferSize: 32);
732+
Assert.Equal(expected, reader.ReadToEnd());
733+
734+
// Next, use Read() API.
735+
736+
memStream.Position = 0;
737+
reader = new StreamReader(memStream, encoding: Encoding.UTF8, bufferSize: 32);
738+
for (int i = 0; i < 32 * 1024; i++)
739+
{
740+
Assert.Equal('a', reader.Read());
741+
}
742+
Assert.Equal('\uFFFD', reader.Read());
743+
Assert.Equal(-1, reader.Read());
744+
745+
// Next, use Read(Span<char>) API.
746+
747+
StringBuilder builder = new StringBuilder();
748+
Span<char> destBuffer = new char[47]; // prime number, because why not
749+
memStream.Position = 0;
750+
reader = new StreamReader(memStream, encoding: Encoding.UTF8, bufferSize: 32);
751+
int charsRead;
752+
while ((charsRead = reader.Read(destBuffer)) > 0)
753+
{
754+
builder.Append(destBuffer.Slice(0, charsRead));
755+
}
756+
Assert.Equal(expected, builder.ToString());
757+
758+
// Finally, use ReadLine API.
759+
760+
memStream.Position = 0;
761+
reader = new StreamReader(memStream, encoding: Encoding.UTF8, bufferSize: 32);
762+
Assert.Equal(expected, reader.ReadLine());
763+
Assert.Null(reader.ReadLine());
764+
}
765+
766+
[Fact]
767+
public async Task ReadAsync_ShortStream_PerformsFinalFlushCorrectly()
768+
{
769+
MemoryStream memStream = new MemoryStream(new byte[] { 0x61 /* 'a' */, 0xF0 });
770+
771+
// First, use ReadToEndAsync API.
772+
773+
memStream.Position = 0;
774+
StreamReader reader = new StreamReader(memStream, Encoding.UTF8);
775+
Assert.Equal("a\uFFFD", await reader.ReadToEndAsync());
776+
777+
// Next, use ReadAsync(Memory<char>) API.
778+
779+
StringBuilder builder = new StringBuilder();
780+
Memory<char> destBuffer = new char[1024];
781+
memStream.Position = 0;
782+
reader = new StreamReader(memStream, Encoding.UTF8);
783+
int charsRead;
784+
while ((charsRead = await reader.ReadAsync(destBuffer)) > 0)
785+
{
786+
builder.Append(destBuffer.Slice(0, charsRead));
787+
}
788+
Assert.Equal("a\uFFFD", builder.ToString());
789+
790+
// Finally, use ReadLineAsync API.
791+
792+
memStream.Position = 0;
793+
reader = new StreamReader(memStream, Encoding.UTF8);
794+
Assert.Equal("a\uFFFD", await reader.ReadLineAsync());
795+
Assert.Null(await reader.ReadLineAsync());
796+
}
797+
798+
[Fact]
799+
public async Task ReadAsync_LongStreamIntoShortBuffer_PerformsFinalFlushCorrectly()
800+
{
801+
MemoryStream memStream = new MemoryStream();
802+
memStream.Write(Enumerable.Repeat((byte)'a', 32 * 1024).ToArray());
803+
memStream.WriteByte(0xF0);
804+
string expected = new string('a', 32 * 1024) + "\uFFFD";
805+
806+
// First, use ReadToEndAsync API.
807+
808+
memStream.Position = 0;
809+
StreamReader reader = new StreamReader(memStream, encoding: Encoding.UTF8, bufferSize: 32);
810+
Assert.Equal(expected, await reader.ReadToEndAsync());
811+
812+
// Next, use Read(Memory<char>) API.
813+
814+
StringBuilder builder = new StringBuilder();
815+
Memory<char> destBuffer = new char[47]; // prime number, because why not
816+
memStream.Position = 0;
817+
reader = new StreamReader(memStream, encoding: Encoding.UTF8, bufferSize: 32);
818+
int charsRead;
819+
while ((charsRead = await reader.ReadAsync(destBuffer)) > 0)
820+
{
821+
builder.Append(destBuffer.Slice(0, charsRead));
822+
}
823+
Assert.Equal(expected, builder.ToString());
824+
825+
// Finally, use ReadLineAsync API.
826+
827+
memStream.Position = 0;
828+
reader = new StreamReader(memStream, encoding: Encoding.UTF8, bufferSize: 32);
829+
Assert.Equal(expected, await reader.ReadLineAsync());
830+
Assert.Null(await reader.ReadLineAsync());
831+
}
679832
}
680833
}

0 commit comments

Comments
 (0)