// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.IO; using System.Text; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNet.Testing; using Microsoft.Extensions.MemoryPool; using Xunit; namespace Microsoft.AspNet.Mvc { public class HttpResponseStreamWriterTest { [Fact] public async Task DoesNotWriteBOM() { // Arrange var memoryStream = new MemoryStream(); var encodingWithBOM = new UTF8Encoding(encoderShouldEmitUTF8Identifier: true); var writer = new HttpResponseStreamWriter(memoryStream, encodingWithBOM); var expectedData = new byte[] { 97, 98, 99, 100 }; // without BOM // Act using (writer) { await writer.WriteAsync("abcd"); } // Assert Assert.Equal(expectedData, memoryStream.ToArray()); } #if DNX451 [Fact] public async Task DoesNotFlush_UnderlyingStream_OnClosingWriter() { // Arrange var stream = new TestMemoryStream(); var writer = new HttpResponseStreamWriter(stream, Encoding.UTF8); // Act await writer.WriteAsync("Hello"); writer.Close(); // Assert Assert.Equal(0, stream.FlushCallCount); Assert.Equal(0, stream.FlushAsyncCallCount); } #endif [Fact] public async Task DoesNotFlush_UnderlyingStream_OnDisposingWriter() { // Arrange var stream = new TestMemoryStream(); var writer = new HttpResponseStreamWriter(stream, Encoding.UTF8); // Act await writer.WriteAsync("Hello"); writer.Dispose(); // Assert Assert.Equal(0, stream.FlushCallCount); Assert.Equal(0, stream.FlushAsyncCallCount); } #if DNX451 [Fact] public async Task DoesNotClose_UnderlyingStream_OnDisposingWriter() { // Arrange var stream = new TestMemoryStream(); var writer = new HttpResponseStreamWriter(stream, Encoding.UTF8); // Act await writer.WriteAsync("Hello"); writer.Close(); // Assert Assert.Equal(0, stream.CloseCallCount); } #endif [Fact] public async Task DoesNotDispose_UnderlyingStream_OnDisposingWriter() { // Arrange var stream = new TestMemoryStream(); var writer = new HttpResponseStreamWriter(stream, Encoding.UTF8); // Act await writer.WriteAsync("Hello world"); writer.Dispose(); // Assert Assert.Equal(0, stream.DisposeCallCount); } [Theory] [InlineData(1023)] [InlineData(1024)] [InlineData(1050)] [InlineData(2048)] public async Task FlushesBuffer_OnClose(int byteLength) { // Arrange var stream = new TestMemoryStream(); var writer = new HttpResponseStreamWriter(stream, Encoding.UTF8); await writer.WriteAsync(new string('a', byteLength)); // Act #if DNX451 writer.Close(); #else writer.Dispose(); #endif // Assert Assert.Equal(0, stream.FlushCallCount); Assert.Equal(0, stream.FlushAsyncCallCount); Assert.Equal(byteLength, stream.Length); } [Theory] [InlineData(1023)] [InlineData(1024)] [InlineData(1050)] [InlineData(2048)] public async Task FlushesBuffer_OnDispose(int byteLength) { // Arrange var stream = new TestMemoryStream(); var writer = new HttpResponseStreamWriter(stream, Encoding.UTF8); await writer.WriteAsync(new string('a', byteLength)); // Act writer.Dispose(); // Assert Assert.Equal(0, stream.FlushCallCount); Assert.Equal(0, stream.FlushAsyncCallCount); Assert.Equal(byteLength, stream.Length); } [Fact] public void NoDataWritten_Flush_DoesNotFlushUnderlyingStream() { // Arrange var stream = new TestMemoryStream(); var writer = new HttpResponseStreamWriter(stream, Encoding.UTF8); // Act writer.Flush(); // Assert Assert.Equal(0, stream.FlushCallCount); Assert.Equal(0, stream.Length); } [Theory] [InlineData(1023)] [InlineData(1024)] [InlineData(1050)] [InlineData(2048)] public void FlushesBuffer_OnFlush(int byteLength) { // Arrange var stream = new TestMemoryStream(); var writer = new HttpResponseStreamWriter(stream, Encoding.UTF8); writer.Write(new string('a', byteLength)); // Act writer.Flush(); // Assert Assert.Equal(1, stream.FlushCallCount); Assert.Equal(byteLength, stream.Length); } [Fact] public async Task NoDataWritten_FlushAsync_DoesNotFlushUnderlyingStream() { // Arrange var stream = new TestMemoryStream(); var writer = new HttpResponseStreamWriter(stream, Encoding.UTF8); // Act await writer.FlushAsync(); // Assert Assert.Equal(0, stream.FlushAsyncCallCount); Assert.Equal(0, stream.Length); } [Theory] [InlineData(1023)] [InlineData(1024)] [InlineData(1050)] [InlineData(2048)] public async Task FlushesBuffer_OnFlushAsync(int byteLength) { // Arrange var stream = new TestMemoryStream(); var writer = new HttpResponseStreamWriter(stream, Encoding.UTF8); await writer.WriteAsync(new string('a', byteLength)); // Act await writer.FlushAsync(); // Assert Assert.Equal(1, stream.FlushAsyncCallCount); Assert.Equal(byteLength, stream.Length); } [Theory] [InlineData(1023)] [InlineData(1024)] [InlineData(1050)] [InlineData(2048)] public void WriteChar_WritesToStream(int byteLength) { // Arrange var stream = new TestMemoryStream(); var writer = new HttpResponseStreamWriter(stream, Encoding.UTF8); // Act using (writer) { for (var i = 0; i < byteLength; i++) { writer.Write('a'); } } // Assert Assert.Equal(byteLength, stream.Length); } [Theory] [InlineData(1023)] [InlineData(1024)] [InlineData(1050)] [InlineData(2048)] public void WriteCharArray_WritesToStream(int byteLength) { // Arrange var stream = new TestMemoryStream(); var writer = new HttpResponseStreamWriter(stream, Encoding.UTF8); // Act using (writer) { writer.Write((new string('a', byteLength)).ToCharArray()); } // Assert Assert.Equal(byteLength, stream.Length); } [Theory] [InlineData(1023)] [InlineData(1024)] [InlineData(1050)] [InlineData(2048)] public async Task WriteCharAsync_WritesToStream(int byteLength) { // Arrange var stream = new TestMemoryStream(); var writer = new HttpResponseStreamWriter(stream, Encoding.UTF8); // Act using (writer) { for (var i = 0; i < byteLength; i++) { await writer.WriteAsync('a'); } } // Assert Assert.Equal(byteLength, stream.Length); } [Theory] [InlineData(1023)] [InlineData(1024)] [InlineData(1050)] [InlineData(2048)] public async Task WriteCharArrayAsync_WritesToStream(int byteLength) { // Arrange var stream = new TestMemoryStream(); var writer = new HttpResponseStreamWriter(stream, Encoding.UTF8); // Act using (writer) { await writer.WriteAsync((new string('a', byteLength)).ToCharArray()); } // Assert Assert.Equal(byteLength, stream.Length); } [Theory] [InlineData("你好世界", "utf-16")] #if !DNXCORE50 // CoreCLR does not like shift_jis as an encoding. [InlineData("こんにちは世界", "shift_jis")] #endif [InlineData("హలో ప్రపంచ", "iso-8859-1")] [InlineData("வணக்கம் உலக", "utf-32")] public async Task WritesData_InExpectedEncoding(string data, string encodingName) { // Arrange var encoding = Encoding.GetEncoding(encodingName); var expectedBytes = encoding.GetBytes(data); var stream = new MemoryStream(); var writer = new HttpResponseStreamWriter(stream, encoding); // Act using (writer) { await writer.WriteAsync(data); } // Assert Assert.Equal(expectedBytes, stream.ToArray()); } [Theory] [InlineData('ん', 1023, "utf-8")] [InlineData('ん', 1024, "utf-8")] [InlineData('ん', 1050, "utf-8")] [InlineData('你', 1023, "utf-16")] [InlineData('你', 1024, "utf-16")] [InlineData('你', 1050, "utf-16")] #if !DNXCORE50 // CoreCLR does not like shift_jis as an encoding. [InlineData('こ', 1023, "shift_jis")] [InlineData('こ', 1024, "shift_jis")] [InlineData('こ', 1050, "shift_jis")] #endif [InlineData('హ', 1023, "iso-8859-1")] [InlineData('హ', 1024, "iso-8859-1")] [InlineData('హ', 1050, "iso-8859-1")] [InlineData('வ', 1023, "utf-32")] [InlineData('வ', 1024, "utf-32")] [InlineData('வ', 1050, "utf-32")] public async Task WritesData_OfDifferentLength_InExpectedEncoding( char character, int charCount, string encodingName) { // Arrange var encoding = Encoding.GetEncoding(encodingName); string data = new string(character, charCount); var expectedBytes = encoding.GetBytes(data); var stream = new MemoryStream(); var writer = new HttpResponseStreamWriter(stream, encoding); // Act using (writer) { await writer.WriteAsync(data); } // Assert Assert.Equal(expectedBytes, stream.ToArray()); } // None of the code in HttpResponseStreamWriter differs significantly when using pooled buffers. // // This test effectively verifies that things are correctly constructed and disposed. Pooled buffers // throw on the finalizer thread if not disposed, so that's why it's complicated. [Fact] public void HttpResponseStreamWriter_UsingPooledBuffers() { // Arrange var encoding = Encoding.UTF8; var stream = new MemoryStream(); var expectedBytes = encoding.GetBytes("Hello, World!"); using (var bytePool = new DefaultArraySegmentPool()) { using (var charPool = new DefaultArraySegmentPool()) { LeasedArraySegment bytes = null; LeasedArraySegment chars = null; HttpResponseStreamWriter writer; try { bytes = bytePool.Lease(4096); chars = charPool.Lease(1024); writer = new HttpResponseStreamWriter(stream, encoding, 1024, bytes, chars); } catch { if (bytes != null) { bytes.Owner.Return(bytes); } if (chars != null) { chars.Owner.Return(chars); } throw; } // Act using (writer) { writer.Write("Hello, World!"); } } } // Assert Assert.Equal(expectedBytes, stream.ToArray()); } // This covers the error case where the byte buffer is too small. This is a safeguard, and shouldn't happen // if we're using the writer factory. [Fact] public void HttpResponseStreamWriter_UsingPooledBuffers_SmallByteBuffer() { // Arrange var encoding = Encoding.UTF8; var stream = new MemoryStream(); var message = "The byte buffer must have a length of at least '12291' to be used with a char buffer of " + "size '4096' and encoding 'Unicode (UTF-8)'. Use 'System.Text.Encoding.GetMaxByteCount' " + "to compute the correct size for the byte buffer."; using (var bytePool = new DefaultArraySegmentPool()) { using (var charPool = new DefaultArraySegmentPool()) { LeasedArraySegment bytes = null; LeasedArraySegment chars = null; HttpResponseStreamWriter writer = null; try { bytes = bytePool.Lease(1024); chars = charPool.Lease(4096); // Act & Assert ExceptionAssert.ThrowsArgument( () => writer = new HttpResponseStreamWriter(stream, encoding, chars.Data.Count, bytes, chars), "byteBuffer", message); writer.Dispose(); } catch { if (bytes != null) { bytes.Owner.Return(bytes); } if (chars != null) { chars.Owner.Return(chars); } } } } } private class TestMemoryStream : MemoryStream { private int _flushCallCount; private int _flushAsyncCallCount; private int _disposeCallCount; public int FlushCallCount { get { return _flushCallCount; } } public int FlushAsyncCallCount { get { return _flushAsyncCallCount; } } public int CloseCallCount { get; private set; } public int DisposeCallCount { get { return _disposeCallCount; } } public override void Flush() { _flushCallCount++; base.Flush(); } public override Task FlushAsync(CancellationToken cancellationToken) { _flushAsyncCallCount++; return base.FlushAsync(cancellationToken); } #if DNX451 public override void Close() { CloseCallCount++; base.Close(); } #endif protected override void Dispose(bool disposing) { _disposeCallCount++; base.Dispose(disposing); } } } }