diff --git a/src/Kestrel.Core/Properties/AssemblyInfo.cs b/src/Kestrel.Core/Properties/AssemblyInfo.cs index 9e4800deed..c9518b4b60 100644 --- a/src/Kestrel.Core/Properties/AssemblyInfo.cs +++ b/src/Kestrel.Core/Properties/AssemblyInfo.cs @@ -9,7 +9,7 @@ using System.Runtime.CompilerServices; [assembly: InternalsVisibleTo("InMemory.FunctionalTests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] [assembly: InternalsVisibleTo("Sockets.BindTests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] [assembly: InternalsVisibleTo("Libuv.BindTests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] -[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Server.Kestrel.Core.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] +[assembly: InternalsVisibleTo("Core.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] [assembly: InternalsVisibleTo("Kestrel.Performance, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] [assembly: InternalsVisibleTo("Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] [assembly: InternalsVisibleTo("PlatformBenchmarks, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] diff --git a/src/Kestrel.Transport.Abstractions/Properties/AssemblyInfo.cs b/src/Kestrel.Transport.Abstractions/Properties/AssemblyInfo.cs index 7056daa197..76b33e146d 100644 --- a/src/Kestrel.Transport.Abstractions/Properties/AssemblyInfo.cs +++ b/src/Kestrel.Transport.Abstractions/Properties/AssemblyInfo.cs @@ -3,7 +3,9 @@ using System.Runtime.CompilerServices; -[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Server.Kestrel.Core.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] +[assembly: InternalsVisibleTo("Core.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] +[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Server.Kestrel.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] +[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] [assembly: InternalsVisibleTo("Sockets.FunctionalTests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] [assembly: InternalsVisibleTo("Libuv.FunctionalTests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] [assembly: InternalsVisibleTo("InMemory.FunctionalTests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] diff --git a/test/Kestrel.Core.Tests/Kestrel.Core.Tests.csproj b/test/Kestrel.Core.Tests/Kestrel.Core.Tests.csproj index a8e44fa8c8..f6144e34fc 100644 --- a/test/Kestrel.Core.Tests/Kestrel.Core.Tests.csproj +++ b/test/Kestrel.Core.Tests/Kestrel.Core.Tests.csproj @@ -1,7 +1,7 @@  - Microsoft.AspNetCore.Server.Kestrel.Core.Tests + Core.Tests Microsoft.AspNetCore.Server.Kestrel.Core.Tests $(StandardTestTfms) true @@ -18,10 +18,11 @@ + + - - + diff --git a/test/Kestrel.Core.Tests/Properties/AssemblyInfo.cs b/test/Kestrel.Core.Tests/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..678468c757 --- /dev/null +++ b/test/Kestrel.Core.Tests/Properties/AssemblyInfo.cs @@ -0,0 +1,6 @@ +// 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 Microsoft.Extensions.Logging.Testing; + +[assembly: ShortClassName] diff --git a/test/Kestrel.Core.Tests/TestInput.cs b/test/Kestrel.Core.Tests/TestHelpers/TestInput.cs similarity index 100% rename from test/Kestrel.Core.Tests/TestInput.cs rename to test/Kestrel.Core.Tests/TestHelpers/TestInput.cs diff --git a/test/Kestrel.InMemory.FunctionalTests/BadHttpRequestTests.cs b/test/Kestrel.InMemory.FunctionalTests/BadHttpRequestTests.cs index d58615c8a7..b566c5617f 100644 --- a/test/Kestrel.InMemory.FunctionalTests/BadHttpRequestTests.cs +++ b/test/Kestrel.InMemory.FunctionalTests/BadHttpRequestTests.cs @@ -5,7 +5,7 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Server.Kestrel.Core; -using Microsoft.AspNetCore.Server.Kestrel.Core.Internal; +using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; using Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests.TestTransport; using Microsoft.AspNetCore.Testing; using Microsoft.Extensions.Logging; @@ -191,7 +191,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests private async Task TestBadRequest(string request, string expectedResponseStatusCode, string expectedExceptionMessage, string expectedAllowHeader = null) { BadHttpRequestException loggedException = null; - var mockKestrelTrace = new Mock(Logger) { CallBase = true }; + var mockKestrelTrace = new Mock(); mockKestrelTrace .Setup(trace => trace.IsEnabled(LogLevel.Information)) .Returns(true); diff --git a/test/Kestrel.Core.Tests/Http2ConnectionTests.cs b/test/Kestrel.InMemory.FunctionalTests/Http2/Http2ConnectionTests.cs similarity index 82% rename from test/Kestrel.Core.Tests/Http2ConnectionTests.cs rename to test/Kestrel.InMemory.FunctionalTests/Http2/Http2ConnectionTests.cs index 182b7418d9..b1b77db419 100644 --- a/test/Kestrel.Core.Tests/Http2ConnectionTests.cs +++ b/test/Kestrel.InMemory.FunctionalTests/Http2/Http2ConnectionTests.cs @@ -3,35 +3,25 @@ using System; using System.Buffers; -using System.Collections.Concurrent; using System.Collections.Generic; using System.IO; -using System.IO.Pipelines; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Connections; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Server.Kestrel.Core.Features; -using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.HPack; -using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; -using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal; using Microsoft.AspNetCore.Testing; using Microsoft.Extensions.Logging; using Microsoft.Net.Http.Headers; -using Moq; using Xunit; namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests { - public class Http2ConnectionTests : IDisposable, IHttpHeadersHandler + public class Http2ConnectionTests : Http2TestBase { - private static readonly string _largeHeaderValue = new string('a', HPackDecoder.MaxStringOctets); - private static readonly IEnumerable> _postRequestHeaders = new[] { new KeyValuePair(HeaderNames.Method, "POST"), @@ -49,19 +39,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests new KeyValuePair("expect", "100-continue"), }; - private static readonly IEnumerable> _browserRequestHeaders = new[] - { - new KeyValuePair(HeaderNames.Method, "GET"), - new KeyValuePair(HeaderNames.Path, "/"), - new KeyValuePair(HeaderNames.Scheme, "http"), - new KeyValuePair(HeaderNames.Authority, "localhost:80"), - new KeyValuePair("user-agent", "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:54.0) Gecko/20100101 Firefox/54.0"), - new KeyValuePair("accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"), - new KeyValuePair("accept-language", "en-US,en;q=0.5"), - new KeyValuePair("accept-encoding", "gzip, deflate, br"), - new KeyValuePair("upgrade-insecure-requests", "1"), - }; - private static readonly IEnumerable> _requestTrailers = new[] { new KeyValuePair("trailer-one", "1"), @@ -102,253 +79,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests private static readonly byte[] _noData = new byte[0]; private static readonly byte[] _maxData = Encoding.ASCII.GetBytes(new string('a', Http2Frame.MinAllowedMaxFrameSize)); - private readonly TestApplicationErrorLogger _logger; - private readonly Http2PeerSettings _clientSettings = new Http2PeerSettings(); - private readonly HPackEncoder _hpackEncoder = new HPackEncoder(); - private readonly HPackDecoder _hpackDecoder; - - private readonly ConcurrentDictionary> _runningStreams = new ConcurrentDictionary>(); - private readonly Dictionary _receivedHeaders = new Dictionary(StringComparer.OrdinalIgnoreCase); - private readonly Dictionary _decodedHeaders = new Dictionary(StringComparer.OrdinalIgnoreCase); - private readonly HashSet _abortedStreamIds = new HashSet(); - private readonly object _abortedStreamIdsLock = new object(); - private readonly TaskCompletionSource _closingStateReached = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - private readonly TaskCompletionSource _closedStateReached = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - - private readonly RequestDelegate _noopApplication; - private readonly RequestDelegate _readHeadersApplication; - private readonly RequestDelegate _readTrailersApplication; - private readonly RequestDelegate _bufferingApplication; - private readonly RequestDelegate _echoApplication; - private readonly RequestDelegate _echoWaitForAbortApplication; - private readonly RequestDelegate _largeHeadersApplication; - private readonly RequestDelegate _waitForAbortApplication; - private readonly RequestDelegate _waitForAbortFlushingApplication; - private readonly RequestDelegate _waitForAbortWithDataApplication; - - private MemoryPool _memoryPool; - private DuplexPipe.DuplexPipePair _pair; - private Http2ConnectionContext _connectionContext; - private Http2Connection _connection; - - private Task _connectionTask; - - public Http2ConnectionTests() - { - _noopApplication = context => Task.CompletedTask; - - _readHeadersApplication = context => - { - foreach (var header in context.Request.Headers) - { - _receivedHeaders[header.Key] = header.Value.ToString(); - } - - return Task.CompletedTask; - }; - - _readTrailersApplication = async context => - { - using (var ms = new MemoryStream()) - { - // Consuming the entire request body guarantees trailers will be available - await context.Request.Body.CopyToAsync(ms); - } - - foreach (var header in context.Request.Headers) - { - _receivedHeaders[header.Key] = header.Value.ToString(); - } - }; - - _bufferingApplication = async context => - { - var data = new List(); - var buffer = new byte[1024]; - var received = 0; - - while ((received = await context.Request.Body.ReadAsync(buffer, 0, buffer.Length)) > 0) - { - data.AddRange(new ArraySegment(buffer, 0, received)); - } - - await context.Response.Body.WriteAsync(data.ToArray(), 0, data.Count); - }; - - _echoApplication = async context => - { - var buffer = new byte[Http2Frame.MinAllowedMaxFrameSize]; - var received = 0; - - while ((received = await context.Request.Body.ReadAsync(buffer, 0, buffer.Length)) > 0) - { - await context.Response.Body.WriteAsync(buffer, 0, received); - } - }; - - _echoWaitForAbortApplication = async context => - { - var buffer = new byte[Http2Frame.MinAllowedMaxFrameSize]; - var received = 0; - - while ((received = await context.Request.Body.ReadAsync(buffer, 0, buffer.Length)) > 0) - { - await context.Response.Body.WriteAsync(buffer, 0, received); - } - - var sem = new SemaphoreSlim(0); - - context.RequestAborted.Register(() => - { - sem.Release(); - }); - - await sem.WaitAsync().DefaultTimeout(); - }; - - _largeHeadersApplication = context => - { - foreach (var name in new[] { "a", "b", "c", "d", "e", "f", "g", "h" }) - { - context.Response.Headers[name] = _largeHeaderValue; - } - - return Task.CompletedTask; - }; - - _waitForAbortApplication = async context => - { - var streamIdFeature = context.Features.Get(); - var sem = new SemaphoreSlim(0); - - context.RequestAborted.Register(() => - { - lock (_abortedStreamIdsLock) - { - _abortedStreamIds.Add(streamIdFeature.StreamId); - } - - sem.Release(); - }); - - await sem.WaitAsync().DefaultTimeout(); - - _runningStreams[streamIdFeature.StreamId].TrySetResult(null); - }; - - _waitForAbortFlushingApplication = async context => - { - var streamIdFeature = context.Features.Get(); - var sem = new SemaphoreSlim(0); - - context.RequestAborted.Register(() => - { - lock (_abortedStreamIdsLock) - { - _abortedStreamIds.Add(streamIdFeature.StreamId); - } - - sem.Release(); - }); - - await sem.WaitAsync().DefaultTimeout(); - - await context.Response.Body.FlushAsync(); - - _runningStreams[streamIdFeature.StreamId].TrySetResult(null); - }; - - _waitForAbortWithDataApplication = async context => - { - var streamIdFeature = context.Features.Get(); - var sem = new SemaphoreSlim(0); - - context.RequestAborted.Register(() => - { - lock (_abortedStreamIdsLock) - { - _abortedStreamIds.Add(streamIdFeature.StreamId); - } - - sem.Release(); - }); - - await sem.WaitAsync().DefaultTimeout(); - - await context.Response.Body.WriteAsync(new byte[10], 0, 10); - - _runningStreams[streamIdFeature.StreamId].TrySetResult(null); - }; - - _hpackDecoder = new HPackDecoder((int)_clientSettings.HeaderTableSize); - - _logger = new TestApplicationErrorLogger(); - - InitializeConnectionFields(KestrelMemoryPool.Create()); - } - - private void InitializeConnectionFields(MemoryPool memoryPool) - { - _memoryPool = memoryPool; - - // Always dispatch test code back to the ThreadPool. This prevents deadlocks caused by continuing - // Http2Connection.ProcessRequestsAsync() loop with writer locks acquired. Run product code inline to make - // it easier to verify request frames are processed correctly immediately after sending the them. - var inputPipeOptions = new PipeOptions( - pool: _memoryPool, - readerScheduler: PipeScheduler.Inline, - writerScheduler: PipeScheduler.ThreadPool, - useSynchronizationContext: false - ); - var outputPipeOptions = new PipeOptions( - pool: _memoryPool, - readerScheduler: PipeScheduler.ThreadPool, - writerScheduler: PipeScheduler.Inline, - useSynchronizationContext: false - ); - - _pair = DuplexPipe.CreateConnectionPair(inputPipeOptions, outputPipeOptions); - - var mockKestrelTrace = new Mock(_logger) - { - CallBase = true - }; - mockKestrelTrace - .Setup(m => m.Http2ConnectionClosing(It.IsAny())) - .Callback(() => _closingStateReached.SetResult(null)); - mockKestrelTrace - .Setup(m => m.Http2ConnectionClosed(It.IsAny(), It.IsAny())) - .Callback(() => _closedStateReached.SetResult(null)); - - _connectionContext = new Http2ConnectionContext - { - ConnectionFeatures = new FeatureCollection(), - ServiceContext = new TestServiceContext() - { - Log = mockKestrelTrace.Object - }, - MemoryPool = _memoryPool, - Application = _pair.Application, - Transport = _pair.Transport - }; - - _connection = new Http2Connection(_connectionContext); - } - - public void Dispose() - { - _pair.Application.Input.Complete(); - _pair.Application.Output.Complete(); - _pair.Transport.Input.Complete(); - _pair.Transport.Output.Complete(); - _memoryPool.Dispose(); - } - - void IHttpHeadersHandler.OnHeader(Span name, Span value) - { - _decodedHeaders[name.GetAsciiStringNonNullCharacters()] = value.GetAsciiStringNonNullCharacters(); - } - [Fact] public async Task Frame_Received_OverMaxSize_FrameError() { @@ -1025,7 +755,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests var closedMessage = CoreStrings.FormatHttp2ErrorStreamClosed(Http2FrameType.DATA, streamId: 1); var halfClosedMessage = CoreStrings.FormatHttp2ErrorStreamHalfClosedRemote(Http2FrameType.DATA, streamId: 1); - var message = Assert.Single(_logger.Messages, m => m.Exception is Http2ConnectionErrorException); + var message = Assert.Single(TestApplicationErrorLogger.Messages, m => m.Exception is Http2ConnectionErrorException); Assert.True(message.Exception.Message.IndexOf(closedMessage) >= 0 || message.Exception.Message.IndexOf(halfClosedMessage) >= 0); } @@ -1524,7 +1254,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests var closedMessage = CoreStrings.FormatHttp2ErrorStreamClosed(Http2FrameType.HEADERS, streamId: 1); var halfClosedMessage = CoreStrings.FormatHttp2ErrorStreamHalfClosedRemote(Http2FrameType.HEADERS, streamId: 1); - var message = Assert.Single(_logger.Messages, m => m.Exception is Http2ConnectionErrorException); + var message = Assert.Single(TestApplicationErrorLogger.Messages, m => m.Exception is Http2ConnectionErrorException); Assert.True(message.Exception.Message.IndexOf(closedMessage) >= 0 || message.Exception.Message.IndexOf(halfClosedMessage) >= 0); } @@ -3363,7 +3093,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests _pair.Application.Output.Complete(new ConnectionResetException(string.Empty)); await StopConnectionAsync(1, ignoreNonGoAwayFrames: false); - Assert.Single(_logger.Messages, m => m.Exception is ConnectionResetException); + Assert.Single(TestApplicationErrorLogger.Messages, m => m.Exception is ConnectionResetException); } [Fact] @@ -3375,7 +3105,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests var result = await _pair.Application.Input.ReadAsync(); Assert.True(result.IsCompleted); - Assert.DoesNotContain(_logger.Messages, m => m.Exception is ConnectionResetException); + Assert.DoesNotContain(TestApplicationErrorLogger.Messages, m => m.Exception is ConnectionResetException); } [Fact] @@ -3557,7 +3287,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests Assert.Equal(TaskStatus.RanToCompletion, _connection.ProcessRequestsAsync(new DummyApplication(_noopApplication)).Status); - var logMessage = _logger.Messages.Single(m => m.LogLevel >= LogLevel.Information); + var logMessage = TestApplicationErrorLogger.Messages.Single(m => m.LogLevel >= LogLevel.Information); Assert.Equal(LogLevel.Information, logMessage.LogLevel); Assert.Equal("Connection id \"(null)\" request processing ended abnormally.", logMessage.Message); @@ -3572,572 +3302,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests Assert.Equal(TaskStatus.RanToCompletion, _connection.ProcessRequestsAsync(new DummyApplication(_noopApplication)).Status); - var logMessage = _logger.Messages.Single(m => m.LogLevel >= LogLevel.Information); + var logMessage = TestApplicationErrorLogger.Messages.Single(m => m.LogLevel >= LogLevel.Information); Assert.Equal(LogLevel.Warning, logMessage.LogLevel); Assert.Equal(CoreStrings.RequestProcessingEndError, logMessage.Message); Assert.Same(exception, logMessage.Exception); } - private async Task InitializeConnectionAsync(RequestDelegate application) - { - _connectionTask = _connection.ProcessRequestsAsync(new DummyApplication(application)); - - await SendPreambleAsync().ConfigureAwait(false); - await SendSettingsAsync(); - - await ExpectAsync(Http2FrameType.SETTINGS, - withLength: 6, - withFlags: 0, - withStreamId: 0); - - await ExpectAsync(Http2FrameType.SETTINGS, - withLength: 0, - withFlags: (byte)Http2SettingsFrameFlags.ACK, - withStreamId: 0); - } - - private async Task StartStreamAsync(int streamId, IEnumerable> headers, bool endStream) - { - var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - _runningStreams[streamId] = tcs; - - var frame = new Http2Frame(); - frame.PrepareHeaders(Http2HeadersFrameFlags.NONE, streamId); - var done = _hpackEncoder.BeginEncode(headers, frame.HeadersPayload, out var length); - frame.Length = length; - - if (done) - { - frame.HeadersFlags = Http2HeadersFrameFlags.END_HEADERS; - } - - if (endStream) - { - frame.HeadersFlags |= Http2HeadersFrameFlags.END_STREAM; - } - - await SendAsync(frame.Raw); - - while (!done) - { - frame.PrepareContinuation(Http2ContinuationFrameFlags.NONE, streamId); - done = _hpackEncoder.Encode(frame.HeadersPayload, out length); - frame.Length = length; - - if (done) - { - frame.ContinuationFlags = Http2ContinuationFrameFlags.END_HEADERS; - } - - await SendAsync(frame.Raw); - } - } - - private async Task SendHeadersWithPaddingAsync(int streamId, IEnumerable> headers, byte padLength, bool endStream) - { - var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - _runningStreams[streamId] = tcs; - - var frame = new Http2Frame(); - - frame.PrepareHeaders(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.PADDED, streamId); - frame.HeadersPadLength = padLength; - - _hpackEncoder.BeginEncode(headers, frame.HeadersPayload, out var length); - - frame.Length = 1 + length + padLength; - frame.Payload.Slice(1 + length).Fill(0); - - if (endStream) - { - frame.HeadersFlags |= Http2HeadersFrameFlags.END_STREAM; - } - - await SendAsync(frame.Raw); - } - - private async Task SendHeadersWithPriorityAsync(int streamId, IEnumerable> headers, byte priority, int streamDependency, bool endStream) - { - var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - _runningStreams[streamId] = tcs; - - var frame = new Http2Frame(); - frame.PrepareHeaders(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.PRIORITY, streamId); - frame.HeadersPriority = priority; - frame.HeadersStreamDependency = streamDependency; - - _hpackEncoder.BeginEncode(headers, frame.HeadersPayload, out var length); - - frame.Length = 5 + length; - - if (endStream) - { - frame.HeadersFlags |= Http2HeadersFrameFlags.END_STREAM; - } - - await SendAsync(frame.Raw); - } - - private async Task SendHeadersWithPaddingAndPriorityAsync(int streamId, IEnumerable> headers, byte padLength, byte priority, int streamDependency, bool endStream) - { - var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - _runningStreams[streamId] = tcs; - - var frame = new Http2Frame(); - frame.PrepareHeaders(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.PADDED | Http2HeadersFrameFlags.PRIORITY, streamId); - frame.HeadersPadLength = padLength; - frame.HeadersPriority = priority; - frame.HeadersStreamDependency = streamDependency; - - _hpackEncoder.BeginEncode(headers, frame.HeadersPayload, out var length); - - frame.Length = 6 + length + padLength; - frame.Payload.Slice(6 + length).Fill(0); - - if (endStream) - { - frame.HeadersFlags |= Http2HeadersFrameFlags.END_STREAM; - } - - await SendAsync(frame.Raw); - } - - private Task WaitForAllStreamsAsync() - { - return Task.WhenAll(_runningStreams.Values.Select(tcs => tcs.Task)).DefaultTimeout(); - } - - private Task SendAsync(ReadOnlySpan span) - { - var writableBuffer = _pair.Application.Output; - writableBuffer.Write(span); - return FlushAsync(writableBuffer); - } - - private static async Task FlushAsync(PipeWriter writableBuffer) - { - await writableBuffer.FlushAsync(); - } - - private Task SendPreambleAsync() => SendAsync(new ArraySegment(Http2Connection.ClientPreface)); - - private Task SendSettingsAsync() - { - var frame = new Http2Frame(); - frame.PrepareSettings(Http2SettingsFrameFlags.NONE, _clientSettings.GetNonProtocolDefaults()); - return SendAsync(frame.Raw); - } - - private Task SendSettingsAckWithInvalidLengthAsync(int length) - { - var frame = new Http2Frame(); - frame.PrepareSettings(Http2SettingsFrameFlags.ACK); - frame.Length = length; - return SendAsync(frame.Raw); - } - - private Task SendSettingsWithInvalidStreamIdAsync(int streamId) - { - var frame = new Http2Frame(); - frame.PrepareSettings(Http2SettingsFrameFlags.NONE, _clientSettings.GetNonProtocolDefaults()); - frame.StreamId = streamId; - return SendAsync(frame.Raw); - } - - private Task SendSettingsWithInvalidLengthAsync(int length) - { - var frame = new Http2Frame(); - frame.PrepareSettings(Http2SettingsFrameFlags.NONE, _clientSettings.GetNonProtocolDefaults()); - frame.Length = length; - return SendAsync(frame.Raw); - } - - private Task SendSettingsWithInvalidParameterValueAsync(Http2SettingsParameter parameter, uint value) - { - var frame = new Http2Frame(); - frame.PrepareSettings(Http2SettingsFrameFlags.NONE); - frame.Length = 6; - - frame.Payload[0] = (byte)((ushort)parameter >> 8); - frame.Payload[1] = (byte)(ushort)parameter; - frame.Payload[2] = (byte)(value >> 24); - frame.Payload[3] = (byte)(value >> 16); - frame.Payload[4] = (byte)(value >> 8); - frame.Payload[5] = (byte)value; - - return SendAsync(frame.Raw); - } - - private Task SendPushPromiseFrameAsync() - { - var frame = new Http2Frame(); - frame.Length = 0; - frame.Type = Http2FrameType.PUSH_PROMISE; - frame.StreamId = 1; - return SendAsync(frame.Raw); - } - - private async Task SendHeadersAsync(int streamId, Http2HeadersFrameFlags flags, IEnumerable> headers) - { - var frame = new Http2Frame(); - - frame.PrepareHeaders(flags, streamId); - var done = _hpackEncoder.BeginEncode(headers, frame.Payload, out var length); - frame.Length = length; - - await SendAsync(frame.Raw); - - return done; - } - - private Task SendHeadersAsync(int streamId, Http2HeadersFrameFlags flags, byte[] headerBlock) - { - var frame = new Http2Frame(); - - frame.PrepareHeaders(flags, streamId); - frame.Length = headerBlock.Length; - headerBlock.CopyTo(frame.HeadersPayload); - - return SendAsync(frame.Raw); - } - - private Task SendInvalidHeadersFrameAsync(int streamId, int frameLength, byte padLength) - { - Assert.True(padLength >= frameLength, $"{nameof(padLength)} must be greater than or equal to {nameof(frameLength)} to create an invalid frame."); - - var frame = new Http2Frame(); - - frame.PrepareHeaders(Http2HeadersFrameFlags.PADDED, streamId); - frame.Payload[0] = padLength; - - // Set length last so .Payload can be written to - frame.Length = frameLength; - - return SendAsync(frame.Raw); - } - - private Task SendIncompleteHeadersFrameAsync(int streamId) - { - var frame = new Http2Frame(); - - frame.PrepareHeaders(Http2HeadersFrameFlags.END_HEADERS, streamId); - frame.Length = 3; - - // Set up an incomplete Literal Header Field w/ Incremental Indexing frame, - // with an incomplete new name - frame.Payload[0] = 0; - frame.Payload[1] = 2; - frame.Payload[2] = (byte)'a'; - - return SendAsync(frame.Raw); - } - - private async Task SendContinuationAsync(int streamId, Http2ContinuationFrameFlags flags) - { - var frame = new Http2Frame(); - - frame.PrepareContinuation(flags, streamId); - var done = _hpackEncoder.Encode(frame.Payload, out var length); - frame.Length = length; - - await SendAsync(frame.Raw); - - return done; - } - - private async Task SendContinuationAsync(int streamId, Http2ContinuationFrameFlags flags, byte[] payload) - { - var frame = new Http2Frame(); - - frame.PrepareContinuation(flags, streamId); - frame.Length = payload.Length; - payload.CopyTo(frame.Payload); - - await SendAsync(frame.Raw); - } - - private Task SendEmptyContinuationFrameAsync(int streamId, Http2ContinuationFrameFlags flags) - { - var frame = new Http2Frame(); - - frame.PrepareContinuation(flags, streamId); - frame.Length = 0; - - return SendAsync(frame.Raw); - } - - private Task SendIncompleteContinuationFrameAsync(int streamId) - { - var frame = new Http2Frame(); - - frame.PrepareContinuation(Http2ContinuationFrameFlags.END_HEADERS, streamId); - frame.Length = 3; - - // Set up an incomplete Literal Header Field w/ Incremental Indexing frame, - // with an incomplete new name - frame.Payload[0] = 0; - frame.Payload[1] = 2; - frame.Payload[2] = (byte)'a'; - - return SendAsync(frame.Raw); - } - - private Task SendDataAsync(int streamId, Span data, bool endStream) - { - var frame = new Http2Frame(); - - frame.PrepareData(streamId); - frame.Length = data.Length; - frame.DataFlags = endStream ? Http2DataFrameFlags.END_STREAM : Http2DataFrameFlags.NONE; - data.CopyTo(frame.DataPayload); - - return SendAsync(frame.Raw); - } - - private Task SendDataWithPaddingAsync(int streamId, Span data, byte padLength, bool endStream) - { - var frame = new Http2Frame(); - - frame.PrepareData(streamId, padLength); - frame.Length = data.Length + 1 + padLength; - data.CopyTo(frame.DataPayload); - - if (endStream) - { - frame.DataFlags |= Http2DataFrameFlags.END_STREAM; - } - - return SendAsync(frame.Raw); - } - - private Task SendInvalidDataFrameAsync(int streamId, int frameLength, byte padLength) - { - Assert.True(padLength >= frameLength, $"{nameof(padLength)} must be greater than or equal to {nameof(frameLength)} to create an invalid frame."); - - var frame = new Http2Frame(); - - frame.PrepareData(streamId); - frame.DataFlags = Http2DataFrameFlags.PADDED; - frame.Payload[0] = padLength; - - // Set length last so .Payload can be written to - frame.Length = frameLength; - - return SendAsync(frame.Raw); - } - - private Task SendPingAsync(Http2PingFrameFlags flags) - { - var pingFrame = new Http2Frame(); - pingFrame.PreparePing(flags); - return SendAsync(pingFrame.Raw); - } - - private Task SendPingWithInvalidLengthAsync(int length) - { - var pingFrame = new Http2Frame(); - pingFrame.PreparePing(Http2PingFrameFlags.NONE); - pingFrame.Length = length; - return SendAsync(pingFrame.Raw); - } - - private Task SendPingWithInvalidStreamIdAsync(int streamId) - { - Assert.NotEqual(0, streamId); - - var pingFrame = new Http2Frame(); - pingFrame.PreparePing(Http2PingFrameFlags.NONE); - pingFrame.StreamId = streamId; - return SendAsync(pingFrame.Raw); - } - - private Task SendPriorityAsync(int streamId, int streamDependency = 0) - { - var priorityFrame = new Http2Frame(); - priorityFrame.PreparePriority(streamId, streamDependency: streamDependency, exclusive: false, weight: 0); - return SendAsync(priorityFrame.Raw); - } - - private Task SendInvalidPriorityFrameAsync(int streamId, int length) - { - var priorityFrame = new Http2Frame(); - priorityFrame.PreparePriority(streamId, streamDependency: 0, exclusive: false, weight: 0); - priorityFrame.Length = length; - return SendAsync(priorityFrame.Raw); - } - - private Task SendRstStreamAsync(int streamId) - { - var rstStreamFrame = new Http2Frame(); - rstStreamFrame.PrepareRstStream(streamId, Http2ErrorCode.CANCEL); - return SendAsync(rstStreamFrame.Raw); - } - - private Task SendInvalidRstStreamFrameAsync(int streamId, int length) - { - var frame = new Http2Frame(); - frame.PrepareRstStream(streamId, Http2ErrorCode.CANCEL); - frame.Length = length; - return SendAsync(frame.Raw); - } - - private Task SendGoAwayAsync() - { - var frame = new Http2Frame(); - frame.PrepareGoAway(0, Http2ErrorCode.NO_ERROR); - return SendAsync(frame.Raw); - } - - private Task SendInvalidGoAwayFrameAsync() - { - var frame = new Http2Frame(); - frame.PrepareGoAway(0, Http2ErrorCode.NO_ERROR); - frame.StreamId = 1; - return SendAsync(frame.Raw); - } - - private Task SendWindowUpdateAsync(int streamId, int sizeIncrement) - { - var frame = new Http2Frame(); - frame.PrepareWindowUpdate(streamId, sizeIncrement); - return SendAsync(frame.Raw); - } - - private Task SendInvalidWindowUpdateAsync(int streamId, int sizeIncrement, int length) - { - var frame = new Http2Frame(); - frame.PrepareWindowUpdate(streamId, sizeIncrement); - frame.Length = length; - return SendAsync(frame.Raw); - } - - private Task SendUnknownFrameTypeAsync(int streamId, int frameType) - { - var frame = new Http2Frame(); - frame.StreamId = streamId; - frame.Type = (Http2FrameType)frameType; - frame.Length = 0; - return SendAsync(frame.Raw); - } - - private async Task ReceiveFrameAsync() - { - var frame = new Http2Frame(); - - while (true) - { - var result = await _pair.Application.Input.ReadAsync(); - var buffer = result.Buffer; - var consumed = buffer.Start; - var examined = buffer.End; - - try - { - Assert.True(buffer.Length > 0); - - if (Http2FrameReader.ReadFrame(buffer, frame, 16_384, out consumed, out examined)) - { - return frame; - } - - if (result.IsCompleted) - { - throw new IOException("The reader completed without returning a frame."); - } - } - finally - { - _pair.Application.Input.AdvanceTo(consumed, examined); - } - } - } - - private async Task ExpectAsync(Http2FrameType type, int withLength, byte withFlags, int withStreamId) - { - var frame = await ReceiveFrameAsync(); - - Assert.Equal(type, frame.Type); - Assert.Equal(withLength, frame.Length); - Assert.Equal(withFlags, frame.Flags); - Assert.Equal(withStreamId, frame.StreamId); - - return frame; - } - - private Task StopConnectionAsync(int expectedLastStreamId, bool ignoreNonGoAwayFrames) - { - _pair.Application.Output.Complete(); - - return WaitForConnectionStopAsync(expectedLastStreamId, ignoreNonGoAwayFrames); - } - - private Task WaitForConnectionStopAsync(int expectedLastStreamId, bool ignoreNonGoAwayFrames) - { - return WaitForConnectionErrorAsync(ignoreNonGoAwayFrames, expectedLastStreamId, Http2ErrorCode.NO_ERROR, expectedErrorMessage: null); - } - - private void VerifyGoAway(Http2Frame frame, int expectedLastStreamId, Http2ErrorCode expectedErrorCode) - { - Assert.Equal(Http2FrameType.GOAWAY, frame.Type); - Assert.Equal(8, frame.Length); - Assert.Equal(0, frame.Flags); - Assert.Equal(0, frame.StreamId); - Assert.Equal(expectedLastStreamId, frame.GoAwayLastStreamId); - Assert.Equal(expectedErrorCode, frame.GoAwayErrorCode); - } - - private async Task WaitForConnectionErrorAsync(bool ignoreNonGoAwayFrames, int expectedLastStreamId, Http2ErrorCode expectedErrorCode, string expectedErrorMessage) - where TException : Exception - { - var frame = await ReceiveFrameAsync(); - - if (ignoreNonGoAwayFrames) - { - while (frame.Type != Http2FrameType.GOAWAY) - { - frame = await ReceiveFrameAsync(); - } - } - - VerifyGoAway(frame, expectedLastStreamId, expectedErrorCode); - - if (expectedErrorMessage != null) - { - var message = Assert.Single(_logger.Messages, m => m.Exception is TException); - Assert.Contains(expectedErrorMessage, message.Exception.Message); - } - - await _connectionTask; - _pair.Application.Output.Complete(); - } - - private async Task WaitForStreamErrorAsync(int expectedStreamId, Http2ErrorCode expectedErrorCode, string expectedErrorMessage) - { - var frame = await ReceiveFrameAsync(); - - Assert.Equal(Http2FrameType.RST_STREAM, frame.Type); - Assert.Equal(4, frame.Length); - Assert.Equal(0, frame.Flags); - Assert.Equal(expectedStreamId, frame.StreamId); - Assert.Equal(expectedErrorCode, frame.RstStreamErrorCode); - - if (expectedErrorMessage != null) - { - var message = Assert.Single(_logger.Messages, m => m.Exception is Http2StreamErrorException); - Assert.Contains(expectedErrorMessage, message.Exception.Message); - } - } - - private void VerifyDecodedRequestHeaders(IEnumerable> expectedHeaders) - { - foreach (var header in expectedHeaders) - { - Assert.True(_receivedHeaders.TryGetValue(header.Key, out var value), header.Key); - Assert.Equal(header.Value, value, ignoreCase: true); - } - } - public static TheoryData UpperCaseHeaderNameData { get diff --git a/test/Kestrel.Core.Tests/Http2StreamTests.cs b/test/Kestrel.InMemory.FunctionalTests/Http2/Http2StreamTests.cs similarity index 82% rename from test/Kestrel.Core.Tests/Http2StreamTests.cs rename to test/Kestrel.InMemory.FunctionalTests/Http2/Http2StreamTests.cs index ead884a765..4a82ff4d01 100644 --- a/test/Kestrel.Core.Tests/Http2StreamTests.cs +++ b/test/Kestrel.InMemory.FunctionalTests/Http2/Http2StreamTests.cs @@ -2,11 +2,8 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using System.Buffers; -using System.Collections.Concurrent; using System.Collections.Generic; using System.IO; -using System.IO.Pipelines; using System.Linq; using System.Runtime.ExceptionServices; using System.Threading; @@ -15,11 +12,7 @@ using Microsoft.AspNetCore.Connections; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Server.Kestrel.Core.Features; -using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2; -using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.HPack; -using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; -using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal; using Microsoft.AspNetCore.Testing; using Microsoft.Extensions.Logging; using Microsoft.Net.Http.Headers; @@ -27,195 +20,8 @@ using Xunit; namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests { - public class Http2StreamTests : IDisposable, IHttpHeadersHandler + public class Http2StreamTests : Http2TestBase { - private static readonly string _largeHeaderValue = new string('a', HPackDecoder.MaxStringOctets); - - private static readonly IEnumerable> _browserRequestHeaders = new[] - { - new KeyValuePair(HeaderNames.Method, "GET"), - new KeyValuePair(HeaderNames.Path, "/"), - new KeyValuePair(HeaderNames.Scheme, "http"), - new KeyValuePair(HeaderNames.Authority, "localhost:80"), - new KeyValuePair("user-agent", "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:54.0) Gecko/20100101 Firefox/54.0"), - new KeyValuePair("accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"), - new KeyValuePair("accept-language", "en-US,en;q=0.5"), - new KeyValuePair("accept-encoding", "gzip, deflate, br"), - new KeyValuePair("upgrade-insecure-requests", "1"), - }; - - private MemoryPool _memoryPool = KestrelMemoryPool.Create(); - private DuplexPipe.DuplexPipePair _pair; - private readonly TestApplicationErrorLogger _logger; - private Http2ConnectionContext _connectionContext; - private Http2Connection _connection; - private readonly Http2PeerSettings _clientSettings = new Http2PeerSettings(); - private readonly HPackEncoder _hpackEncoder = new HPackEncoder(); - private readonly HPackDecoder _hpackDecoder; - - private readonly ConcurrentDictionary> _runningStreams = new ConcurrentDictionary>(); - private readonly Dictionary _decodedHeaders = new Dictionary(StringComparer.OrdinalIgnoreCase); - private readonly HashSet _abortedStreamIds = new HashSet(); - private readonly object _abortedStreamIdsLock = new object(); - - private readonly RequestDelegate _noopApplication; - private readonly RequestDelegate _echoMethod; - private readonly RequestDelegate _echoHost; - private readonly RequestDelegate _echoPath; - private readonly RequestDelegate _waitForAbortApplication; - private readonly RequestDelegate _waitForAbortFlushingApplication; - private readonly RequestDelegate _waitForAbortWithDataApplication; - - private Task _connectionTask; - - public Http2StreamTests() - { - _noopApplication = context => Task.CompletedTask; - - _echoMethod = context => - { - context.Response.Headers["Method"] = context.Request.Method; - - return Task.CompletedTask; - }; - - _echoHost = context => - { - context.Response.Headers[HeaderNames.Host] = context.Request.Headers[HeaderNames.Host]; - - return Task.CompletedTask; - }; - - _echoPath = context => - { - context.Response.Headers["path"] = context.Request.Path.ToString(); - context.Response.Headers["rawtarget"] = context.Features.Get().RawTarget; - - return Task.CompletedTask; - }; - - _waitForAbortApplication = async context => - { - var streamIdFeature = context.Features.Get(); - var sem = new SemaphoreSlim(0); - - context.RequestAborted.Register(() => - { - lock (_abortedStreamIdsLock) - { - _abortedStreamIds.Add(streamIdFeature.StreamId); - } - - sem.Release(); - }); - - await sem.WaitAsync().DefaultTimeout(); - - _runningStreams[streamIdFeature.StreamId].TrySetResult(null); - }; - - _waitForAbortFlushingApplication = async context => - { - var streamIdFeature = context.Features.Get(); - var sem = new SemaphoreSlim(0); - - context.RequestAborted.Register(() => - { - lock (_abortedStreamIdsLock) - { - _abortedStreamIds.Add(streamIdFeature.StreamId); - } - - sem.Release(); - }); - - await sem.WaitAsync().DefaultTimeout(); - - await context.Response.Body.FlushAsync(); - - _runningStreams[streamIdFeature.StreamId].TrySetResult(null); - }; - - _waitForAbortWithDataApplication = async context => - { - var streamIdFeature = context.Features.Get(); - var sem = new SemaphoreSlim(0); - - context.RequestAborted.Register(() => - { - lock (_abortedStreamIdsLock) - { - _abortedStreamIds.Add(streamIdFeature.StreamId); - } - - sem.Release(); - }); - - await sem.WaitAsync().DefaultTimeout(); - - await context.Response.Body.WriteAsync(new byte[10], 0, 10); - - _runningStreams[streamIdFeature.StreamId].TrySetResult(null); - }; - - _hpackDecoder = new HPackDecoder((int)_clientSettings.HeaderTableSize); - - _logger = new TestApplicationErrorLogger(); - - InitializeConnectionFields(KestrelMemoryPool.Create()); - } - - private void InitializeConnectionFields(MemoryPool memoryPool) - { - _memoryPool = memoryPool; - - // Always dispatch test code back to the ThreadPool. This prevents deadlocks caused by continuing - // Http2Connection.ProcessRequestsAsync() loop with writer locks acquired. Run product code inline to make - // it easier to verify request frames are processed correctly immediately after sending the them. - var inputPipeOptions = new PipeOptions( - pool: _memoryPool, - readerScheduler: PipeScheduler.Inline, - writerScheduler: PipeScheduler.ThreadPool, - useSynchronizationContext: false - ); - var outputPipeOptions = new PipeOptions( - pool: _memoryPool, - readerScheduler: PipeScheduler.ThreadPool, - writerScheduler: PipeScheduler.Inline, - useSynchronizationContext: false - ); - - _pair = DuplexPipe.CreateConnectionPair(inputPipeOptions, outputPipeOptions); - - _connectionContext = new Http2ConnectionContext - { - ConnectionFeatures = new FeatureCollection(), - ServiceContext = new TestServiceContext() - { - Log = new TestKestrelTrace(_logger), - }, - MemoryPool = _memoryPool, - Application = _pair.Application, - Transport = _pair.Transport - }; - - _connection = new Http2Connection(_connectionContext); - } - - public void Dispose() - { - _pair.Application.Input.Complete(); - _pair.Application.Output.Complete(); - _pair.Transport.Input.Complete(); - _pair.Transport.Output.Complete(); - _memoryPool.Dispose(); - } - - void IHttpHeadersHandler.OnHeader(Span name, Span value) - { - _decodedHeaders[name.GetAsciiStringNonNullCharacters()] = value.GetAsciiStringNonNullCharacters(); - } - [Fact] public async Task HEADERS_Received_EmptyMethod_Reset() { @@ -1105,7 +911,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests withFlags: (byte)Http2DataFrameFlags.END_STREAM, withStreamId: 1); - Assert.Contains(_logger.Messages, m => m.Exception?.Message.Contains("Response Content-Length mismatch: too many bytes written (12 of 11).") ?? false); + Assert.Contains(TestApplicationErrorLogger.Messages, m => m.Exception?.Message.Contains("Response Content-Length mismatch: too many bytes written (12 of 11).") ?? false); await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); @@ -1182,7 +988,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests withFlags: (byte)Http2DataFrameFlags.END_STREAM, withStreamId: 1); - Assert.Contains(_logger.Messages, m => m.Exception?.Message.Contains("Response Content-Length mismatch: too few bytes written (0 of 11).") ?? false); + Assert.Contains(TestApplicationErrorLogger.Messages, m => m.Exception?.Message.Contains("Response Content-Length mismatch: too few bytes written (0 of 11).") ?? false); await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); @@ -1538,7 +1344,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests withFlags: (byte)Http2DataFrameFlags.END_STREAM, withStreamId: 1); - Assert.Contains(_logger.Messages, m => (m.Exception?.Message.Contains("App Faulted") ?? false) && m.LogLevel == LogLevel.Error); + Assert.Contains(TestApplicationErrorLogger.Messages, m => (m.Exception?.Message.Contains("App Faulted") ?? false) && m.LogLevel == LogLevel.Error); await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); @@ -1859,220 +1665,5 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests Assert.IsType(thrownEx.InnerException); Assert.Equal(CoreStrings.ConnectionAbortedByApplication, thrownEx.InnerException.Message); } - - private async Task InitializeConnectionAsync(RequestDelegate application) - { - _connectionTask = _connection.ProcessRequestsAsync(new DummyApplication(application)); - - await SendPreambleAsync().ConfigureAwait(false); - await SendSettingsAsync(); - - await ExpectAsync(Http2FrameType.SETTINGS, - withLength: 6, - withFlags: 0, - withStreamId: 0); - - await ExpectAsync(Http2FrameType.SETTINGS, - withLength: 0, - withFlags: (byte)Http2SettingsFrameFlags.ACK, - withStreamId: 0); - } - - private async Task StartStreamAsync(int streamId, IEnumerable> headers, bool endStream) - { - var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - _runningStreams[streamId] = tcs; - - var frame = new Http2Frame(); - frame.PrepareHeaders(Http2HeadersFrameFlags.NONE, streamId); - var done = _hpackEncoder.BeginEncode(headers, frame.HeadersPayload, out var length); - frame.Length = length; - - if (done) - { - frame.HeadersFlags = Http2HeadersFrameFlags.END_HEADERS; - } - - if (endStream) - { - frame.HeadersFlags |= Http2HeadersFrameFlags.END_STREAM; - } - - await SendAsync(frame.Raw); - - while (!done) - { - frame.PrepareContinuation(Http2ContinuationFrameFlags.NONE, streamId); - done = _hpackEncoder.Encode(frame.HeadersPayload, out length); - frame.Length = length; - - if (done) - { - frame.ContinuationFlags = Http2ContinuationFrameFlags.END_HEADERS; - } - - await SendAsync(frame.Raw); - } - } - - private Task WaitForAllStreamsAsync() - { - return Task.WhenAll(_runningStreams.Values.Select(tcs => tcs.Task)).DefaultTimeout(); - } - - private Task SendAsync(ReadOnlySpan span) - { - var writableBuffer = _pair.Application.Output; - writableBuffer.Write(span); - return FlushAsync(writableBuffer); - } - - private static async Task FlushAsync(PipeWriter writableBuffer) - { - await writableBuffer.FlushAsync(); - } - - private Task SendPreambleAsync() => SendAsync(new ArraySegment(Http2Connection.ClientPreface)); - - private Task SendSettingsAsync() - { - var frame = new Http2Frame(); - frame.PrepareSettings(Http2SettingsFrameFlags.NONE, _clientSettings.GetNonProtocolDefaults()); - return SendAsync(frame.Raw); - } - - private async Task SendHeadersAsync(int streamId, Http2HeadersFrameFlags flags, IEnumerable> headers) - { - var frame = new Http2Frame(); - - frame.PrepareHeaders(flags, streamId); - var done = _hpackEncoder.BeginEncode(headers, frame.Payload, out var length); - frame.Length = length; - - await SendAsync(frame.Raw); - - return done; - } - - private Task SendDataAsync(int streamId, Span data, bool endStream) - { - var frame = new Http2Frame(); - - frame.PrepareData(streamId); - frame.Length = data.Length; - frame.DataFlags = endStream ? Http2DataFrameFlags.END_STREAM : Http2DataFrameFlags.NONE; - data.CopyTo(frame.DataPayload); - - return SendAsync(frame.Raw); - } - - private Task SendRstStreamAsync(int streamId) - { - var rstStreamFrame = new Http2Frame(); - rstStreamFrame.PrepareRstStream(streamId, Http2ErrorCode.CANCEL); - return SendAsync(rstStreamFrame.Raw); - } - - private async Task ReceiveFrameAsync() - { - var frame = new Http2Frame(); - - while (true) - { - var result = await _pair.Application.Input.ReadAsync(); - var buffer = result.Buffer; - var consumed = buffer.Start; - var examined = buffer.End; - - try - { - Assert.True(buffer.Length > 0); - - if (Http2FrameReader.ReadFrame(buffer, frame, 16_384, out consumed, out examined)) - { - return frame; - } - - if (result.IsCompleted) - { - throw new IOException("The reader completed without returning a frame."); - } - } - finally - { - _pair.Application.Input.AdvanceTo(consumed, examined); - } - } - } - - private async Task ExpectAsync(Http2FrameType type, int withLength, byte withFlags, int withStreamId) - { - var frame = await ReceiveFrameAsync().DefaultTimeout(); - - Assert.Equal(type, frame.Type); - Assert.Equal(withLength, frame.Length); - Assert.Equal(withFlags, frame.Flags); - Assert.Equal(withStreamId, frame.StreamId); - - return frame; - } - - private Task StopConnectionAsync(int expectedLastStreamId, bool ignoreNonGoAwayFrames) - { - _pair.Application.Output.Complete(); - - return WaitForConnectionStopAsync(expectedLastStreamId, ignoreNonGoAwayFrames); - } - - private Task WaitForConnectionStopAsync(int expectedLastStreamId, bool ignoreNonGoAwayFrames) - { - return WaitForConnectionErrorAsync(ignoreNonGoAwayFrames, expectedLastStreamId, Http2ErrorCode.NO_ERROR, expectedErrorMessage: null); - } - - private async Task WaitForConnectionErrorAsync(bool ignoreNonGoAwayFrames, int expectedLastStreamId, Http2ErrorCode expectedErrorCode, string expectedErrorMessage) - where TException : Exception - { - var frame = await ReceiveFrameAsync(); - - if (ignoreNonGoAwayFrames) - { - while (frame.Type != Http2FrameType.GOAWAY) - { - frame = await ReceiveFrameAsync(); - } - } - - Assert.Equal(Http2FrameType.GOAWAY, frame.Type); - Assert.Equal(8, frame.Length); - Assert.Equal(0, frame.Flags); - Assert.Equal(0, frame.StreamId); - Assert.Equal(expectedLastStreamId, frame.GoAwayLastStreamId); - Assert.Equal(expectedErrorCode, frame.GoAwayErrorCode); - - if (expectedErrorMessage != null) - { - var message = Assert.Single(_logger.Messages, m => m.Exception is TException); - Assert.Contains(expectedErrorMessage, message.Exception.Message); - } - - await _connectionTask; - _pair.Application.Output.Complete(); - } - - private async Task WaitForStreamErrorAsync(int expectedStreamId, Http2ErrorCode expectedErrorCode, string expectedErrorMessage) - { - var frame = await ReceiveFrameAsync(); - - Assert.Equal(Http2FrameType.RST_STREAM, frame.Type); - Assert.Equal(4, frame.Length); - Assert.Equal(0, frame.Flags); - Assert.Equal(expectedStreamId, frame.StreamId); - Assert.Equal(expectedErrorCode, frame.RstStreamErrorCode); - - if (expectedErrorMessage != null) - { - Assert.Contains(_logger.Messages, m => m.Exception?.Message.Contains(expectedErrorMessage) ?? false); - } - } } } \ No newline at end of file diff --git a/test/Kestrel.InMemory.FunctionalTests/Http2/Http2TestBase.cs b/test/Kestrel.InMemory.FunctionalTests/Http2/Http2TestBase.cs new file mode 100644 index 0000000000..bc9c2d7f27 --- /dev/null +++ b/test/Kestrel.InMemory.FunctionalTests/Http2/Http2TestBase.cs @@ -0,0 +1,867 @@ +// 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; +using System.Buffers; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.IO; +using System.IO.Pipelines; +using System.Linq; +using System.Reflection; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.Features; +using Microsoft.AspNetCore.Server.Kestrel.Core.Features; +using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http; +using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2; +using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.HPack; +using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; +using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal; +using Microsoft.AspNetCore.Testing; +using Microsoft.Net.Http.Headers; +using Moq; +using Xunit; +using Xunit.Abstractions; + +namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests +{ + public class Http2TestBase : TestApplicationErrorLoggerLoggedTest, IDisposable, IHttpHeadersHandler + { + protected static readonly string _largeHeaderValue = new string('a', HPackDecoder.MaxStringOctets); + + protected static readonly IEnumerable> _browserRequestHeaders = new[] + { + new KeyValuePair(HeaderNames.Method, "GET"), + new KeyValuePair(HeaderNames.Path, "/"), + new KeyValuePair(HeaderNames.Scheme, "http"), + new KeyValuePair(HeaderNames.Authority, "localhost:80"), + new KeyValuePair("user-agent", "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:54.0) Gecko/20100101 Firefox/54.0"), + new KeyValuePair("accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"), + new KeyValuePair("accept-language", "en-US,en;q=0.5"), + new KeyValuePair("accept-encoding", "gzip, deflate, br"), + new KeyValuePair("upgrade-insecure-requests", "1"), + }; + + private readonly MemoryPool _memoryPool = KestrelMemoryPool.Create(); + internal readonly DuplexPipe.DuplexPipePair _pair; + + protected readonly Http2PeerSettings _clientSettings = new Http2PeerSettings(); + protected readonly HPackEncoder _hpackEncoder = new HPackEncoder(); + protected readonly HPackDecoder _hpackDecoder; + + protected readonly ConcurrentDictionary> _runningStreams = new ConcurrentDictionary>(); + protected readonly Dictionary _receivedHeaders = new Dictionary(StringComparer.OrdinalIgnoreCase); + protected readonly Dictionary _decodedHeaders = new Dictionary(StringComparer.OrdinalIgnoreCase); + protected readonly HashSet _abortedStreamIds = new HashSet(); + protected readonly object _abortedStreamIdsLock = new object(); + protected readonly TaskCompletionSource _closingStateReached = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + protected readonly TaskCompletionSource _closedStateReached = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + + protected readonly RequestDelegate _noopApplication; + protected readonly RequestDelegate _readHeadersApplication; + protected readonly RequestDelegate _readTrailersApplication; + protected readonly RequestDelegate _bufferingApplication; + protected readonly RequestDelegate _echoApplication; + protected readonly RequestDelegate _echoWaitForAbortApplication; + protected readonly RequestDelegate _largeHeadersApplication; + protected readonly RequestDelegate _waitForAbortApplication; + protected readonly RequestDelegate _waitForAbortFlushingApplication; + protected readonly RequestDelegate _waitForAbortWithDataApplication; + protected readonly RequestDelegate _echoMethod; + protected readonly RequestDelegate _echoHost; + protected readonly RequestDelegate _echoPath; + + protected Http2ConnectionContext _connectionContext; + protected Http2Connection _connection; + protected Task _connectionTask; + + public Http2TestBase() + { + // Always dispatch test code back to the ThreadPool. This prevents deadlocks caused by continuing + // Http2Connection.ProcessRequestsAsync() loop with writer locks acquired. Run product code inline to make + // it easier to verify request frames are processed correctly immediately after sending the them. + var inputPipeOptions = new PipeOptions( + pool: _memoryPool, + readerScheduler: PipeScheduler.Inline, + writerScheduler: PipeScheduler.ThreadPool, + useSynchronizationContext: false + ); + var outputPipeOptions = new PipeOptions( + pool: _memoryPool, + readerScheduler: PipeScheduler.ThreadPool, + writerScheduler: PipeScheduler.Inline, + useSynchronizationContext: false + ); + + _pair = DuplexPipe.CreateConnectionPair(inputPipeOptions, outputPipeOptions); + _hpackDecoder = new HPackDecoder((int)_clientSettings.HeaderTableSize); + + _noopApplication = context => Task.CompletedTask; + + _readHeadersApplication = context => + { + foreach (var header in context.Request.Headers) + { + _receivedHeaders[header.Key] = header.Value.ToString(); + } + + return Task.CompletedTask; + }; + + _readTrailersApplication = async context => + { + using (var ms = new MemoryStream()) + { + // Consuming the entire request body guarantees trailers will be available + await context.Request.Body.CopyToAsync(ms); + } + + foreach (var header in context.Request.Headers) + { + _receivedHeaders[header.Key] = header.Value.ToString(); + } + }; + + _bufferingApplication = async context => + { + var data = new List(); + var buffer = new byte[1024]; + var received = 0; + + while ((received = await context.Request.Body.ReadAsync(buffer, 0, buffer.Length)) > 0) + { + data.AddRange(new ArraySegment(buffer, 0, received)); + } + + await context.Response.Body.WriteAsync(data.ToArray(), 0, data.Count); + }; + + _echoApplication = async context => + { + var buffer = new byte[Http2Frame.MinAllowedMaxFrameSize]; + var received = 0; + + while ((received = await context.Request.Body.ReadAsync(buffer, 0, buffer.Length)) > 0) + { + await context.Response.Body.WriteAsync(buffer, 0, received); + } + }; + + _echoWaitForAbortApplication = async context => + { + var buffer = new byte[Http2Frame.MinAllowedMaxFrameSize]; + var received = 0; + + while ((received = await context.Request.Body.ReadAsync(buffer, 0, buffer.Length)) > 0) + { + await context.Response.Body.WriteAsync(buffer, 0, received); + } + + var sem = new SemaphoreSlim(0); + + context.RequestAborted.Register(() => + { + sem.Release(); + }); + + await sem.WaitAsync().DefaultTimeout(); + }; + + _largeHeadersApplication = context => + { + foreach (var name in new[] { "a", "b", "c", "d", "e", "f", "g", "h" }) + { + context.Response.Headers[name] = _largeHeaderValue; + } + + return Task.CompletedTask; + }; + + _waitForAbortApplication = async context => + { + var streamIdFeature = context.Features.Get(); + var sem = new SemaphoreSlim(0); + + context.RequestAborted.Register(() => + { + lock (_abortedStreamIdsLock) + { + _abortedStreamIds.Add(streamIdFeature.StreamId); + } + + sem.Release(); + }); + + await sem.WaitAsync().DefaultTimeout(); + + _runningStreams[streamIdFeature.StreamId].TrySetResult(null); + }; + + _waitForAbortFlushingApplication = async context => + { + var streamIdFeature = context.Features.Get(); + var sem = new SemaphoreSlim(0); + + context.RequestAborted.Register(() => + { + lock (_abortedStreamIdsLock) + { + _abortedStreamIds.Add(streamIdFeature.StreamId); + } + + sem.Release(); + }); + + await sem.WaitAsync().DefaultTimeout(); + + await context.Response.Body.FlushAsync(); + + _runningStreams[streamIdFeature.StreamId].TrySetResult(null); + }; + + _waitForAbortWithDataApplication = async context => + { + var streamIdFeature = context.Features.Get(); + var sem = new SemaphoreSlim(0); + + context.RequestAborted.Register(() => + { + lock (_abortedStreamIdsLock) + { + _abortedStreamIds.Add(streamIdFeature.StreamId); + } + + sem.Release(); + }); + + await sem.WaitAsync().DefaultTimeout(); + + await context.Response.Body.WriteAsync(new byte[10], 0, 10); + + _runningStreams[streamIdFeature.StreamId].TrySetResult(null); + }; + + _echoMethod = context => + { + context.Response.Headers["Method"] = context.Request.Method; + + return Task.CompletedTask; + }; + + _echoHost = context => + { + context.Response.Headers[HeaderNames.Host] = context.Request.Headers[HeaderNames.Host]; + + return Task.CompletedTask; + }; + + _echoPath = context => + { + context.Response.Headers["path"] = context.Request.Path.ToString(); + context.Response.Headers["rawtarget"] = context.Features.Get().RawTarget; + + return Task.CompletedTask; + }; + } + + public override void Initialize(MethodInfo methodInfo, object[] testMethodArguments, ITestOutputHelper testOutputHelper) + { + base.Initialize(methodInfo, testMethodArguments, testOutputHelper); + + var mockKestrelTrace = new Mock(); + mockKestrelTrace + .Setup(m => m.Http2ConnectionClosing(It.IsAny())) + .Callback(() => _closingStateReached.SetResult(null)); + mockKestrelTrace + .Setup(m => m.Http2ConnectionClosed(It.IsAny(), It.IsAny())) + .Callback(() => _closedStateReached.SetResult(null)); + + _connectionContext = new Http2ConnectionContext + { + ConnectionFeatures = new FeatureCollection(), + ServiceContext = new TestServiceContext(LoggerFactory, mockKestrelTrace.Object), + MemoryPool = _memoryPool, + Application = _pair.Application, + Transport = _pair.Transport + }; + + _connection = new Http2Connection(_connectionContext); + } + + public override void Dispose() + { + _pair.Application.Input.Complete(); + _pair.Application.Output.Complete(); + _pair.Transport.Input.Complete(); + _pair.Transport.Output.Complete(); + _memoryPool.Dispose(); + + base.Dispose(); + } + + void IHttpHeadersHandler.OnHeader(Span name, Span value) + { + _decodedHeaders[name.GetAsciiStringNonNullCharacters()] = value.GetAsciiStringNonNullCharacters(); + } + + protected async Task InitializeConnectionAsync(RequestDelegate application) + { + _connectionTask = _connection.ProcessRequestsAsync(new DummyApplication(application)); + + await SendPreambleAsync().ConfigureAwait(false); + await SendSettingsAsync(); + + await ExpectAsync(Http2FrameType.SETTINGS, + withLength: 6, + withFlags: 0, + withStreamId: 0); + + await ExpectAsync(Http2FrameType.SETTINGS, + withLength: 0, + withFlags: (byte)Http2SettingsFrameFlags.ACK, + withStreamId: 0); + } + + protected async Task StartStreamAsync(int streamId, IEnumerable> headers, bool endStream) + { + var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + _runningStreams[streamId] = tcs; + + var frame = new Http2Frame(); + frame.PrepareHeaders(Http2HeadersFrameFlags.NONE, streamId); + var done = _hpackEncoder.BeginEncode(headers, frame.HeadersPayload, out var length); + frame.Length = length; + + if (done) + { + frame.HeadersFlags = Http2HeadersFrameFlags.END_HEADERS; + } + + if (endStream) + { + frame.HeadersFlags |= Http2HeadersFrameFlags.END_STREAM; + } + + await SendAsync(frame.Raw); + + while (!done) + { + frame.PrepareContinuation(Http2ContinuationFrameFlags.NONE, streamId); + done = _hpackEncoder.Encode(frame.HeadersPayload, out length); + frame.Length = length; + + if (done) + { + frame.ContinuationFlags = Http2ContinuationFrameFlags.END_HEADERS; + } + + await SendAsync(frame.Raw); + } + } + + protected async Task SendHeadersWithPaddingAsync(int streamId, IEnumerable> headers, byte padLength, bool endStream) + { + var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + _runningStreams[streamId] = tcs; + + var frame = new Http2Frame(); + + frame.PrepareHeaders(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.PADDED, streamId); + frame.HeadersPadLength = padLength; + + _hpackEncoder.BeginEncode(headers, frame.HeadersPayload, out var length); + + frame.Length = 1 + length + padLength; + frame.Payload.Slice(1 + length).Fill(0); + + if (endStream) + { + frame.HeadersFlags |= Http2HeadersFrameFlags.END_STREAM; + } + + await SendAsync(frame.Raw); + } + + protected async Task SendHeadersWithPriorityAsync(int streamId, IEnumerable> headers, byte priority, int streamDependency, bool endStream) + { + var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + _runningStreams[streamId] = tcs; + + var frame = new Http2Frame(); + frame.PrepareHeaders(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.PRIORITY, streamId); + frame.HeadersPriority = priority; + frame.HeadersStreamDependency = streamDependency; + + _hpackEncoder.BeginEncode(headers, frame.HeadersPayload, out var length); + + frame.Length = 5 + length; + + if (endStream) + { + frame.HeadersFlags |= Http2HeadersFrameFlags.END_STREAM; + } + + await SendAsync(frame.Raw); + } + + protected async Task SendHeadersWithPaddingAndPriorityAsync(int streamId, IEnumerable> headers, byte padLength, byte priority, int streamDependency, bool endStream) + { + var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + _runningStreams[streamId] = tcs; + + var frame = new Http2Frame(); + frame.PrepareHeaders(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.PADDED | Http2HeadersFrameFlags.PRIORITY, streamId); + frame.HeadersPadLength = padLength; + frame.HeadersPriority = priority; + frame.HeadersStreamDependency = streamDependency; + + _hpackEncoder.BeginEncode(headers, frame.HeadersPayload, out var length); + + frame.Length = 6 + length + padLength; + frame.Payload.Slice(6 + length).Fill(0); + + if (endStream) + { + frame.HeadersFlags |= Http2HeadersFrameFlags.END_STREAM; + } + + await SendAsync(frame.Raw); + } + + protected Task WaitForAllStreamsAsync() + { + return Task.WhenAll(_runningStreams.Values.Select(tcs => tcs.Task)).DefaultTimeout(); + } + + protected Task SendAsync(ReadOnlySpan span) + { + var writableBuffer = _pair.Application.Output; + writableBuffer.Write(span); + return FlushAsync(writableBuffer); + } + + protected static async Task FlushAsync(PipeWriter writableBuffer) + { + await writableBuffer.FlushAsync(); + } + + protected Task SendPreambleAsync() => SendAsync(new ArraySegment(Http2Connection.ClientPreface)); + + protected Task SendSettingsAsync() + { + var frame = new Http2Frame(); + frame.PrepareSettings(Http2SettingsFrameFlags.NONE, _clientSettings.GetNonProtocolDefaults()); + return SendAsync(frame.Raw); + } + + protected Task SendSettingsAckWithInvalidLengthAsync(int length) + { + var frame = new Http2Frame(); + frame.PrepareSettings(Http2SettingsFrameFlags.ACK); + frame.Length = length; + return SendAsync(frame.Raw); + } + + protected Task SendSettingsWithInvalidStreamIdAsync(int streamId) + { + var frame = new Http2Frame(); + frame.PrepareSettings(Http2SettingsFrameFlags.NONE, _clientSettings.GetNonProtocolDefaults()); + frame.StreamId = streamId; + return SendAsync(frame.Raw); + } + + protected Task SendSettingsWithInvalidLengthAsync(int length) + { + var frame = new Http2Frame(); + frame.PrepareSettings(Http2SettingsFrameFlags.NONE, _clientSettings.GetNonProtocolDefaults()); + frame.Length = length; + return SendAsync(frame.Raw); + } + + protected Task SendSettingsWithInvalidParameterValueAsync(Http2SettingsParameter parameter, uint value) + { + var frame = new Http2Frame(); + frame.PrepareSettings(Http2SettingsFrameFlags.NONE); + frame.Length = 6; + + frame.Payload[0] = (byte)((ushort)parameter >> 8); + frame.Payload[1] = (byte)(ushort)parameter; + frame.Payload[2] = (byte)(value >> 24); + frame.Payload[3] = (byte)(value >> 16); + frame.Payload[4] = (byte)(value >> 8); + frame.Payload[5] = (byte)value; + + return SendAsync(frame.Raw); + } + + protected Task SendPushPromiseFrameAsync() + { + var frame = new Http2Frame(); + frame.Length = 0; + frame.Type = Http2FrameType.PUSH_PROMISE; + frame.StreamId = 1; + return SendAsync(frame.Raw); + } + + protected async Task SendHeadersAsync(int streamId, Http2HeadersFrameFlags flags, IEnumerable> headers) + { + var frame = new Http2Frame(); + + frame.PrepareHeaders(flags, streamId); + var done = _hpackEncoder.BeginEncode(headers, frame.Payload, out var length); + frame.Length = length; + + await SendAsync(frame.Raw); + + return done; + } + + protected Task SendHeadersAsync(int streamId, Http2HeadersFrameFlags flags, byte[] headerBlock) + { + var frame = new Http2Frame(); + + frame.PrepareHeaders(flags, streamId); + frame.Length = headerBlock.Length; + headerBlock.CopyTo(frame.HeadersPayload); + + return SendAsync(frame.Raw); + } + + protected Task SendInvalidHeadersFrameAsync(int streamId, int frameLength, byte padLength) + { + Assert.True(padLength >= frameLength, $"{nameof(padLength)} must be greater than or equal to {nameof(frameLength)} to create an invalid frame."); + + var frame = new Http2Frame(); + + frame.PrepareHeaders(Http2HeadersFrameFlags.PADDED, streamId); + frame.Payload[0] = padLength; + + // Set length last so .Payload can be written to + frame.Length = frameLength; + + return SendAsync(frame.Raw); + } + + protected Task SendIncompleteHeadersFrameAsync(int streamId) + { + var frame = new Http2Frame(); + + frame.PrepareHeaders(Http2HeadersFrameFlags.END_HEADERS, streamId); + frame.Length = 3; + + // Set up an incomplete Literal Header Field w/ Incremental Indexing frame, + // with an incomplete new name + frame.Payload[0] = 0; + frame.Payload[1] = 2; + frame.Payload[2] = (byte)'a'; + + return SendAsync(frame.Raw); + } + + protected async Task SendContinuationAsync(int streamId, Http2ContinuationFrameFlags flags) + { + var frame = new Http2Frame(); + + frame.PrepareContinuation(flags, streamId); + var done = _hpackEncoder.Encode(frame.Payload, out var length); + frame.Length = length; + + await SendAsync(frame.Raw); + + return done; + } + + protected async Task SendContinuationAsync(int streamId, Http2ContinuationFrameFlags flags, byte[] payload) + { + var frame = new Http2Frame(); + + frame.PrepareContinuation(flags, streamId); + frame.Length = payload.Length; + payload.CopyTo(frame.Payload); + + await SendAsync(frame.Raw); + } + + protected Task SendEmptyContinuationFrameAsync(int streamId, Http2ContinuationFrameFlags flags) + { + var frame = new Http2Frame(); + + frame.PrepareContinuation(flags, streamId); + frame.Length = 0; + + return SendAsync(frame.Raw); + } + + protected Task SendIncompleteContinuationFrameAsync(int streamId) + { + var frame = new Http2Frame(); + + frame.PrepareContinuation(Http2ContinuationFrameFlags.END_HEADERS, streamId); + frame.Length = 3; + + // Set up an incomplete Literal Header Field w/ Incremental Indexing frame, + // with an incomplete new name + frame.Payload[0] = 0; + frame.Payload[1] = 2; + frame.Payload[2] = (byte)'a'; + + return SendAsync(frame.Raw); + } + + protected Task SendDataAsync(int streamId, Span data, bool endStream) + { + var frame = new Http2Frame(); + + frame.PrepareData(streamId); + frame.Length = data.Length; + frame.DataFlags = endStream ? Http2DataFrameFlags.END_STREAM : Http2DataFrameFlags.NONE; + data.CopyTo(frame.DataPayload); + + return SendAsync(frame.Raw); + } + + protected Task SendDataWithPaddingAsync(int streamId, Span data, byte padLength, bool endStream) + { + var frame = new Http2Frame(); + + frame.PrepareData(streamId, padLength); + frame.Length = data.Length + 1 + padLength; + data.CopyTo(frame.DataPayload); + + if (endStream) + { + frame.DataFlags |= Http2DataFrameFlags.END_STREAM; + } + + return SendAsync(frame.Raw); + } + + protected Task SendInvalidDataFrameAsync(int streamId, int frameLength, byte padLength) + { + Assert.True(padLength >= frameLength, $"{nameof(padLength)} must be greater than or equal to {nameof(frameLength)} to create an invalid frame."); + + var frame = new Http2Frame(); + + frame.PrepareData(streamId); + frame.DataFlags = Http2DataFrameFlags.PADDED; + frame.Payload[0] = padLength; + + // Set length last so .Payload can be written to + frame.Length = frameLength; + + return SendAsync(frame.Raw); + } + + protected Task SendPingAsync(Http2PingFrameFlags flags) + { + var pingFrame = new Http2Frame(); + pingFrame.PreparePing(flags); + return SendAsync(pingFrame.Raw); + } + + protected Task SendPingWithInvalidLengthAsync(int length) + { + var pingFrame = new Http2Frame(); + pingFrame.PreparePing(Http2PingFrameFlags.NONE); + pingFrame.Length = length; + return SendAsync(pingFrame.Raw); + } + + protected Task SendPingWithInvalidStreamIdAsync(int streamId) + { + Assert.NotEqual(0, streamId); + + var pingFrame = new Http2Frame(); + pingFrame.PreparePing(Http2PingFrameFlags.NONE); + pingFrame.StreamId = streamId; + return SendAsync(pingFrame.Raw); + } + + protected Task SendPriorityAsync(int streamId, int streamDependency = 0) + { + var priorityFrame = new Http2Frame(); + priorityFrame.PreparePriority(streamId, streamDependency: streamDependency, exclusive: false, weight: 0); + return SendAsync(priorityFrame.Raw); + } + + protected Task SendInvalidPriorityFrameAsync(int streamId, int length) + { + var priorityFrame = new Http2Frame(); + priorityFrame.PreparePriority(streamId, streamDependency: 0, exclusive: false, weight: 0); + priorityFrame.Length = length; + return SendAsync(priorityFrame.Raw); + } + + protected Task SendRstStreamAsync(int streamId) + { + var rstStreamFrame = new Http2Frame(); + rstStreamFrame.PrepareRstStream(streamId, Http2ErrorCode.CANCEL); + return SendAsync(rstStreamFrame.Raw); + } + + protected Task SendInvalidRstStreamFrameAsync(int streamId, int length) + { + var frame = new Http2Frame(); + frame.PrepareRstStream(streamId, Http2ErrorCode.CANCEL); + frame.Length = length; + return SendAsync(frame.Raw); + } + + protected Task SendGoAwayAsync() + { + var frame = new Http2Frame(); + frame.PrepareGoAway(0, Http2ErrorCode.NO_ERROR); + return SendAsync(frame.Raw); + } + + protected Task SendInvalidGoAwayFrameAsync() + { + var frame = new Http2Frame(); + frame.PrepareGoAway(0, Http2ErrorCode.NO_ERROR); + frame.StreamId = 1; + return SendAsync(frame.Raw); + } + + protected Task SendWindowUpdateAsync(int streamId, int sizeIncrement) + { + var frame = new Http2Frame(); + frame.PrepareWindowUpdate(streamId, sizeIncrement); + return SendAsync(frame.Raw); + } + + protected Task SendInvalidWindowUpdateAsync(int streamId, int sizeIncrement, int length) + { + var frame = new Http2Frame(); + frame.PrepareWindowUpdate(streamId, sizeIncrement); + frame.Length = length; + return SendAsync(frame.Raw); + } + + protected Task SendUnknownFrameTypeAsync(int streamId, int frameType) + { + var frame = new Http2Frame(); + frame.StreamId = streamId; + frame.Type = (Http2FrameType)frameType; + frame.Length = 0; + return SendAsync(frame.Raw); + } + + protected async Task ReceiveFrameAsync() + { + var frame = new Http2Frame(); + + while (true) + { + var result = await _pair.Application.Input.ReadAsync().AsTask().DefaultTimeout(); + var buffer = result.Buffer; + var consumed = buffer.Start; + var examined = buffer.End; + + try + { + Assert.True(buffer.Length > 0); + + if (Http2FrameReader.ReadFrame(buffer, frame, 16_384, out consumed, out examined)) + { + return frame; + } + + if (result.IsCompleted) + { + throw new IOException("The reader completed without returning a frame."); + } + } + finally + { + _pair.Application.Input.AdvanceTo(consumed, examined); + } + } + } + + protected async Task ExpectAsync(Http2FrameType type, int withLength, byte withFlags, int withStreamId) + { + var frame = await ReceiveFrameAsync(); + + Assert.Equal(type, frame.Type); + Assert.Equal(withLength, frame.Length); + Assert.Equal(withFlags, frame.Flags); + Assert.Equal(withStreamId, frame.StreamId); + + return frame; + } + + protected Task StopConnectionAsync(int expectedLastStreamId, bool ignoreNonGoAwayFrames) + { + _pair.Application.Output.Complete(); + + return WaitForConnectionStopAsync(expectedLastStreamId, ignoreNonGoAwayFrames); + } + + protected Task WaitForConnectionStopAsync(int expectedLastStreamId, bool ignoreNonGoAwayFrames) + { + return WaitForConnectionErrorAsync(ignoreNonGoAwayFrames, expectedLastStreamId, Http2ErrorCode.NO_ERROR, expectedErrorMessage: null); + } + + protected void VerifyGoAway(Http2Frame frame, int expectedLastStreamId, Http2ErrorCode expectedErrorCode) + { + Assert.Equal(Http2FrameType.GOAWAY, frame.Type); + Assert.Equal(8, frame.Length); + Assert.Equal(0, frame.Flags); + Assert.Equal(0, frame.StreamId); + Assert.Equal(expectedLastStreamId, frame.GoAwayLastStreamId); + Assert.Equal(expectedErrorCode, frame.GoAwayErrorCode); + } + + protected async Task WaitForConnectionErrorAsync(bool ignoreNonGoAwayFrames, int expectedLastStreamId, Http2ErrorCode expectedErrorCode, string expectedErrorMessage) + where TException : Exception + { + var frame = await ReceiveFrameAsync(); + + if (ignoreNonGoAwayFrames) + { + while (frame.Type != Http2FrameType.GOAWAY) + { + frame = await ReceiveFrameAsync(); + } + } + + VerifyGoAway(frame, expectedLastStreamId, expectedErrorCode); + + if (expectedErrorMessage != null) + { + var message = Assert.Single(TestApplicationErrorLogger.Messages, m => m.Exception is TException); + Assert.Contains(expectedErrorMessage, message.Exception.Message); + } + + await _connectionTask; + _pair.Application.Output.Complete(); + } + + protected async Task WaitForStreamErrorAsync(int expectedStreamId, Http2ErrorCode expectedErrorCode, string expectedErrorMessage) + { + var frame = await ReceiveFrameAsync(); + + Assert.Equal(Http2FrameType.RST_STREAM, frame.Type); + Assert.Equal(4, frame.Length); + Assert.Equal(0, frame.Flags); + Assert.Equal(expectedStreamId, frame.StreamId); + Assert.Equal(expectedErrorCode, frame.RstStreamErrorCode); + + if (expectedErrorMessage != null) + { + Assert.Contains(TestApplicationErrorLogger.Messages, m => m.Exception?.Message.Contains(expectedErrorMessage) ?? false); + } + } + + protected void VerifyDecodedRequestHeaders(IEnumerable> expectedHeaders) + { + foreach (var header in expectedHeaders) + { + Assert.True(_receivedHeaders.TryGetValue(header.Key, out var value), header.Key); + Assert.Equal(header.Value, value, ignoreCase: true); + } + } + } +} diff --git a/test/Kestrel.InMemory.FunctionalTests/Kestrel.InMemory.FunctionalTests.csproj b/test/Kestrel.InMemory.FunctionalTests/Kestrel.InMemory.FunctionalTests.csproj index 6d4915600c..54d56c1077 100644 --- a/test/Kestrel.InMemory.FunctionalTests/Kestrel.InMemory.FunctionalTests.csproj +++ b/test/Kestrel.InMemory.FunctionalTests/Kestrel.InMemory.FunctionalTests.csproj @@ -9,7 +9,6 @@ - diff --git a/test/Kestrel.InMemory.FunctionalTests/ResponseTests.cs b/test/Kestrel.InMemory.FunctionalTests/ResponseTests.cs index 23d6e390a7..e0e63fd0d5 100644 --- a/test/Kestrel.InMemory.FunctionalTests/ResponseTests.cs +++ b/test/Kestrel.InMemory.FunctionalTests/ResponseTests.cs @@ -11,8 +11,8 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Server.Kestrel.Core; -using Microsoft.AspNetCore.Server.Kestrel.Core.Internal; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http; +using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; using Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests.TestTransport; using Microsoft.AspNetCore.Testing; using Microsoft.Extensions.DependencyInjection; @@ -421,7 +421,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests const string response = "hello, world"; var logTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - var mockKestrelTrace = new Mock(Logger) { CallBase = true }; + var mockKestrelTrace = new Mock(); mockKestrelTrace .Setup(trace => trace.ConnectionHeadResponseBodyWrite(It.IsAny(), response.Length)) .Callback((connectionId, count) => logTcs.SetResult(null)); @@ -610,7 +610,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests public async Task WhenAppWritesLessThanContentLengthErrorLogged() { var logTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - var mockTrace = new Mock(Logger) { CallBase = true }; + var mockTrace = new Mock(); mockTrace .Setup(trace => trace.ApplicationError(It.IsAny(), It.IsAny(), It.IsAny())) .Callback((connectionId, requestId, ex) => @@ -663,7 +663,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests public async Task WhenAppWritesLessThanContentLengthButRequestIsAbortedErrorNotLogged() { var requestAborted = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - var mockTrace = new Mock(Logger) { CallBase = true }; + var mockTrace = new Mock(); using (var server = new TestServer(async httpContext => { diff --git a/test/Kestrel.Tests/Kestrel.Tests.csproj b/test/Kestrel.Tests/Kestrel.Tests.csproj index ec0ae9149a..ffeff4838b 100644 --- a/test/Kestrel.Tests/Kestrel.Tests.csproj +++ b/test/Kestrel.Tests/Kestrel.Tests.csproj @@ -18,7 +18,11 @@ + + + + diff --git a/test/Kestrel.Transport.FunctionalTests/RequestTests.cs b/test/Kestrel.Transport.FunctionalTests/RequestTests.cs index 81ef08d614..fc5a5d382b 100644 --- a/test/Kestrel.Transport.FunctionalTests/RequestTests.cs +++ b/test/Kestrel.Transport.FunctionalTests/RequestTests.cs @@ -19,7 +19,7 @@ using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Server.Kestrel.Core; -using Microsoft.AspNetCore.Server.Kestrel.Core.Internal; +using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; using Microsoft.AspNetCore.Testing; using Microsoft.AspNetCore.Testing.xunit; using Microsoft.Extensions.Logging; @@ -318,8 +318,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests .Callback>((logLevel, eventId, state, exception, formatter) => { Logger.Log(logLevel, eventId, state, exception, formatter); - var log = $"Log {logLevel}[{eventId}]: {formatter(state, exception)} {exception}"; - TestOutputHelper.WriteLine(log); if (eventId.Id == _connectionResetEventId) { @@ -707,10 +705,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests "Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets"))) .Returns(mockLogger.Object); - var mockKestrelTrace = new Mock(Logger) { CallBase = true }; - var testContext = new TestServiceContext(mockLoggerFactory.Object) + var mockKestrelTrace = new Mock(); + var testContext = new TestServiceContext(mockLoggerFactory.Object, mockKestrelTrace.Object) { - Log = mockKestrelTrace.Object, ServerOptions = { Limits = @@ -765,11 +762,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests var readTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); var appStartedTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - var mockKestrelTrace = new Mock(Logger) { CallBase = true }; - var testContext = new TestServiceContext() - { - Log = mockKestrelTrace.Object, - }; + var mockKestrelTrace = new Mock(); + var testContext = new TestServiceContext(LoggerFactory, mockKestrelTrace.Object); var scratchBuffer = new byte[4096]; diff --git a/test/Kestrel.Transport.FunctionalTests/ResponseTests.cs b/test/Kestrel.Transport.FunctionalTests/ResponseTests.cs index 88d54f6d22..bc61229b9c 100644 --- a/test/Kestrel.Transport.FunctionalTests/ResponseTests.cs +++ b/test/Kestrel.Transport.FunctionalTests/ResponseTests.cs @@ -18,7 +18,6 @@ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Server.Kestrel.Core; using Microsoft.AspNetCore.Server.Kestrel.Core.Adapter.Internal; -using Microsoft.AspNetCore.Server.Kestrel.Core.Internal; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; using Microsoft.AspNetCore.Server.Kestrel.Https; using Microsoft.AspNetCore.Server.Kestrel.Https.Internal; @@ -250,7 +249,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests var clientClosedConnection = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); var writeTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - var mockKestrelTrace = new Mock(Logger) { CallBase = true }; + var mockKestrelTrace = new Mock(); var mockLogger = new Mock(); mockLogger .Setup(logger => logger.IsEnabled(It.IsAny())) @@ -276,9 +275,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests "Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets"))) .Returns(mockLogger.Object); - var testContext = new TestServiceContext(mockLoggerFactory.Object) + var testContext = new TestServiceContext(mockLoggerFactory.Object, mockKestrelTrace.Object) { - Log = mockKestrelTrace.Object, ServerOptions = { Limits = @@ -470,7 +468,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests var requestAborted = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); var appFuncCompleted = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - var mockKestrelTrace = new Mock(Logger) { CallBase = true }; + var mockKestrelTrace = new Mock(); mockKestrelTrace .Setup(trace => trace.ResponseMininumDataRateNotSatisfied(It.IsAny(), It.IsAny())) .Callback(() => responseRateTimeoutMessageLogged.SetResult(null)); @@ -478,10 +476,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests .Setup(trace => trace.ConnectionStop(It.IsAny())) .Callback(() => connectionStopMessageLogged.SetResult(null)); - var testContext = new TestServiceContext + var testContext = new TestServiceContext(loggerFactory, mockKestrelTrace.Object) { - LoggerFactory = loggerFactory, - Log = mockKestrelTrace.Object, ServerOptions = { Limits = @@ -539,7 +535,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests await responseRateTimeoutMessageLogged.Task.DefaultTimeout(); await connectionStopMessageLogged.Task.DefaultTimeout(); await appFuncCompleted.Task.DefaultTimeout(); - await AssertStreamAborted(connection.Reader.BaseStream, chunkSize * chunks); + await AssertStreamAborted(connection.Stream, chunkSize * chunks); sw.Stop(); logger.LogInformation("Connection was aborted after {totalMilliseconds}ms.", sw.ElapsedMilliseconds); @@ -562,7 +558,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests var aborted = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); var appFuncCompleted = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - var mockKestrelTrace = new Mock(Logger) { CallBase = true }; + var mockKestrelTrace = new Mock(); mockKestrelTrace .Setup(trace => trace.ResponseMininumDataRateNotSatisfied(It.IsAny(), It.IsAny())) .Callback(() => responseRateTimeoutMessageLogged.SetResult(null)); @@ -616,7 +612,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests { using (var connection = server.CreateConnection()) { - using (var sslStream = new SslStream(connection.Reader.BaseStream, false, (sender, cert, chain, errors) => true, null)) + using (var sslStream = new SslStream(connection.Stream, false, (sender, cert, chain, errors) => true, null)) { await sslStream.AuthenticateAsClientAsync("localhost", new X509CertificateCollection(), SslProtocols.Tls12 | SslProtocols.Tls11, false); @@ -628,7 +624,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests await connectionStopMessageLogged.Task.DefaultTimeout(); await appFuncCompleted.Task.DefaultTimeout(); - await AssertStreamAborted(connection.Reader.BaseStream, chunkSize * chunks); + await AssertStreamAborted(connection.Stream, chunkSize * chunks); } } } @@ -647,7 +643,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests var requestAborted = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); var copyToAsyncCts = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - var mockKestrelTrace = new Mock(Logger) { CallBase = true }; + var mockKestrelTrace = new Mock(); mockKestrelTrace .Setup(trace => trace.ResponseMininumDataRateNotSatisfied(It.IsAny(), It.IsAny())) .Callback(() => responseRateTimeoutMessageLogged.SetResult(null)); @@ -655,10 +651,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests .Setup(trace => trace.ConnectionStop(It.IsAny())) .Callback(() => connectionStopMessageLogged.SetResult(null)); - var testContext = new TestServiceContext + var testContext = new TestServiceContext(LoggerFactory, mockKestrelTrace.Object) { - LoggerFactory = LoggerFactory, - Log = mockKestrelTrace.Object, ServerOptions = { Limits = @@ -734,11 +728,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests var requestAborted = false; var appFuncCompleted = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - var mockKestrelTrace = new Mock(Logger) { CallBase = true }; + var mockKestrelTrace = new Mock(); - var testContext = new TestServiceContext + var testContext = new TestServiceContext(LoggerFactory, mockKestrelTrace.Object) { - Log = mockKestrelTrace.Object, ServerOptions = { Limits = @@ -783,7 +776,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests // Make sure consuming a single chunk exceeds the 2 second timeout. var targetBytesPerSecond = chunkSize / 4; - await AssertStreamCompleted(connection.Reader.BaseStream, minTotalOutputSize, targetBytesPerSecond); + await AssertStreamCompleted(connection.Stream, minTotalOutputSize, targetBytesPerSecond); await appFuncCompleted.Task.DefaultTimeout(); mockKestrelTrace.Verify(t => t.ResponseMininumDataRateNotSatisfied(It.IsAny(), It.IsAny()), Times.Never()); @@ -803,11 +796,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests var headerStringValues = new StringValues(Enumerable.Repeat(headerValue, headerCount).ToArray()); var requestAborted = false; - var mockKestrelTrace = new Mock(Logger) { CallBase = true }; + var mockKestrelTrace = new Mock(); - var testContext = new TestServiceContext + var testContext = new TestServiceContext(LoggerFactory, mockKestrelTrace.Object) { - Log = mockKestrelTrace.Object, ServerOptions = { Limits = @@ -860,7 +852,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests // Make sure consuming a single set of response headers exceeds the 2 second timeout. var targetBytesPerSecond = responseSize / 4; - await AssertStreamCompleted(connection.Reader.BaseStream, minTotalOutputSize, targetBytesPerSecond); + await AssertStreamCompleted(connection.Stream, minTotalOutputSize, targetBytesPerSecond); mockKestrelTrace.Verify(t => t.ResponseMininumDataRateNotSatisfied(It.IsAny(), It.IsAny()), Times.Never()); mockKestrelTrace.Verify(t => t.ConnectionStop(It.IsAny()), Times.Once()); diff --git a/test/Kestrel.Transport.Libuv.BindTests/Kestrel.Transport.Libuv.BindTests.csproj b/test/Kestrel.Transport.Libuv.BindTests/Kestrel.Transport.Libuv.BindTests.csproj index edf6bd9a96..2e1b2bbe54 100644 --- a/test/Kestrel.Transport.Libuv.BindTests/Kestrel.Transport.Libuv.BindTests.csproj +++ b/test/Kestrel.Transport.Libuv.BindTests/Kestrel.Transport.Libuv.BindTests.csproj @@ -12,7 +12,6 @@ - diff --git a/test/Kestrel.Transport.Libuv.FunctionalTests/Kestrel.Transport.Libuv.FunctionalTests.csproj b/test/Kestrel.Transport.Libuv.FunctionalTests/Kestrel.Transport.Libuv.FunctionalTests.csproj index b2e93c9777..7c9eb1fa1d 100644 --- a/test/Kestrel.Transport.Libuv.FunctionalTests/Kestrel.Transport.Libuv.FunctionalTests.csproj +++ b/test/Kestrel.Transport.Libuv.FunctionalTests/Kestrel.Transport.Libuv.FunctionalTests.csproj @@ -13,7 +13,6 @@ - diff --git a/test/Kestrel.Transport.Libuv.Tests/Kestrel.Transport.Libuv.Tests.csproj b/test/Kestrel.Transport.Libuv.Tests/Kestrel.Transport.Libuv.Tests.csproj index 0ad5d2346d..dbdec3565c 100644 --- a/test/Kestrel.Transport.Libuv.Tests/Kestrel.Transport.Libuv.Tests.csproj +++ b/test/Kestrel.Transport.Libuv.Tests/Kestrel.Transport.Libuv.Tests.csproj @@ -18,9 +18,10 @@ - + + diff --git a/test/Kestrel.Transport.Sockets.BindTests/Kestrel.Transport.Sockets.BindTests.csproj b/test/Kestrel.Transport.Sockets.BindTests/Kestrel.Transport.Sockets.BindTests.csproj index 4aa0791ed7..6393beddf0 100644 --- a/test/Kestrel.Transport.Sockets.BindTests/Kestrel.Transport.Sockets.BindTests.csproj +++ b/test/Kestrel.Transport.Sockets.BindTests/Kestrel.Transport.Sockets.BindTests.csproj @@ -12,7 +12,6 @@ - diff --git a/test/Kestrel.Transport.Sockets.FunctionalTests/Kestrel.Transport.Sockets.FunctionalTests.csproj b/test/Kestrel.Transport.Sockets.FunctionalTests/Kestrel.Transport.Sockets.FunctionalTests.csproj index e717642f93..35be01d361 100644 --- a/test/Kestrel.Transport.Sockets.FunctionalTests/Kestrel.Transport.Sockets.FunctionalTests.csproj +++ b/test/Kestrel.Transport.Sockets.FunctionalTests/Kestrel.Transport.Sockets.FunctionalTests.csproj @@ -12,7 +12,6 @@ - diff --git a/test/shared/FunctionalTestHelpers/DiagnosticMemoryPoolFactory.cs b/test/shared/DiagnosticMemoryPoolFactory.cs similarity index 100% rename from test/shared/FunctionalTestHelpers/DiagnosticMemoryPoolFactory.cs rename to test/shared/DiagnosticMemoryPoolFactory.cs diff --git a/test/shared/FunctionalTestHelpers/TestApplicationErrorLoggerLoggedTest.cs b/test/shared/TestApplicationErrorLoggerLoggedTest.cs similarity index 100% rename from test/shared/FunctionalTestHelpers/TestApplicationErrorLoggerLoggedTest.cs rename to test/shared/TestApplicationErrorLoggerLoggedTest.cs index 312c64660c..c149111fdb 100644 --- a/test/shared/FunctionalTestHelpers/TestApplicationErrorLoggerLoggedTest.cs +++ b/test/shared/TestApplicationErrorLoggerLoggedTest.cs @@ -1,8 +1,8 @@ // 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 Microsoft.Extensions.Logging.Testing; using System.Reflection; +using Microsoft.Extensions.Logging.Testing; using Xunit.Abstractions; namespace Microsoft.AspNetCore.Testing diff --git a/test/shared/TransportTestHelpers/TestServer.cs b/test/shared/TransportTestHelpers/TestServer.cs index e5b9376289..df73888129 100644 --- a/test/shared/TransportTestHelpers/TestServer.cs +++ b/test/shared/TransportTestHelpers/TestServer.cs @@ -16,6 +16,7 @@ using Microsoft.AspNetCore.Server.Kestrel.Core; using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal; using Microsoft.AspNetCore.Testing; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests @@ -85,11 +86,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests .Build(); _host.Start(); + + Context.Log.LogDebug($"TestServer is listening on port {Port}"); } - public IPEndPoint EndPoint => _listenOptions.IPEndPoint; - public int Port => _listenOptions.IPEndPoint.Port; - public AddressFamily AddressFamily => _listenOptions.IPEndPoint.AddressFamily; + // Avoid NullReferenceException in the CanListenToOpenTcpSocketHandle test + public int Port => _listenOptions.IPEndPoint?.Port ?? 0; public TestServiceContext Context { get; } @@ -107,7 +109,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests public TestConnection CreateConnection() { - return new TestConnection(Port, AddressFamily); + return new TestConnection(Port, _listenOptions.IPEndPoint.AddressFamily); } public Task StopAsync(CancellationToken token = default)