diff --git a/eng/test-configuration.json b/eng/test-configuration.json deleted file mode 100644 index adea26ddb23335..00000000000000 --- a/eng/test-configuration.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "version": 1, - "defaultOnFailure": "fail", - "localRerunCount": 2, - "retryOnRules": [ - { "testAssembly": { "wildcard": "System.Net.*" } }, - { "failureMessage": { "regex": ".*Timed out after .* waiting for the browser to be ready.*" } }, - { "failureMessage": { "regex": "System.IO.IOException : Process for .*chrome.*unexpectedly exited.* during startup" } } - ] -} diff --git a/src/libraries/Common/tests/System/Net/Http/DribbleStream.cs b/src/libraries/Common/tests/System/Net/Http/DribbleStream.cs index d2e12d8cb99f9a..0261b982073cfd 100644 --- a/src/libraries/Common/tests/System/Net/Http/DribbleStream.cs +++ b/src/libraries/Common/tests/System/Net/Http/DribbleStream.cs @@ -20,8 +20,8 @@ public override async Task WriteAsync(byte[] buffer, int offset, int count, Canc { for (int i = 0; i < count; i++) { - await _wrapped.WriteAsync(buffer, offset + i, 1); - await _wrapped.FlushAsync(); + await _wrapped.WriteAsync(buffer, offset + i, 1, cancellationToken); + await _wrapped.FlushAsync(cancellationToken); await Task.Yield(); // introduce short delays, enough to send packets individually but not so long as to extend test duration significantly } } diff --git a/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.AutoRedirect.cs b/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.AutoRedirect.cs index c9bbdbc0c13c6b..1fa1642bd4e89c 100644 --- a/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.AutoRedirect.cs +++ b/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.AutoRedirect.cs @@ -184,7 +184,7 @@ await LoopbackServer.CreateServerAsync(async (origServer, origUrl) => { await LoopbackServer.CreateServerAsync(async (redirectServer, redirectUrl) => { - Task getResponseTask = client.GetAsync(origUrl); + Task getResponseTask = client.GetAsync(TestAsync, origUrl); Task redirectTask = redirectServer.AcceptConnectionSendResponseAndCloseAsync(); diff --git a/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTestBase.cs b/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTestBase.cs index 798b23907cc9c0..6560bb6dc8848b 100644 --- a/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTestBase.cs +++ b/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTestBase.cs @@ -2,10 +2,9 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Generic; -#if !NETCOREAPP using System.Diagnostics; -#endif using System.IO; +using System.Net.Test.Common; using System.Threading; using System.Threading.Tasks; using Xunit.Abstractions; @@ -28,9 +27,14 @@ public abstract partial class HttpClientHandlerTestBase : FileCleanupTestBase public HttpClientHandlerTestBase(ITestOutputHelper output) { - _output = output; + if (output is not null) + { + _output = new TestOutputWithTimestampHelper(output); + } } + public void EnableDebugLogs() => HttpDebug.DebugThisTest(_output); + protected virtual HttpClient CreateHttpClient() => CreateHttpClient(CreateHttpClientHandler()); protected HttpClient CreateHttpClient(HttpMessageHandler handler) => @@ -126,6 +130,25 @@ protected override async Task SendAsync(HttpRequestMessage return response; } } + + private sealed class TestOutputWithTimestampHelper(ITestOutputHelper output) : ITestOutputHelper + { + private readonly Stopwatch _stopwatch = Stopwatch.StartNew(); + + private string TimestampPrefix + { + get + { + TimeSpan elapsed = _stopwatch.Elapsed; + + return $"[{(int)elapsed.TotalSeconds}.{elapsed.Milliseconds:D4}]"; + } + } + + public void WriteLine(string message) => output.WriteLine($"{TimestampPrefix} {message}"); + + public void WriteLine(string format, params object[] args) => output.WriteLine($"{TimestampPrefix} {format}", args); + } } public static class HttpClientExtensions @@ -238,5 +261,10 @@ public static Task GetByteArrayAsync(this HttpClient client, bool async, return client.GetByteArrayAsync(uri); #endif } + + public static Task GetAsync(this HttpClient client, bool async, Uri uri, HttpCompletionOption completionOption = default, CancellationToken cancellationToken = default) + { + return SendAsync(client, async, new HttpRequestMessage(HttpMethod.Get, uri), completionOption, cancellationToken); + } } } diff --git a/src/libraries/Common/tests/System/Net/Http/HttpDebug.cs b/src/libraries/Common/tests/System/Net/Http/HttpDebug.cs new file mode 100644 index 00000000000000..9dd6bf15242f91 --- /dev/null +++ b/src/libraries/Common/tests/System/Net/Http/HttpDebug.cs @@ -0,0 +1,47 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Runtime.CompilerServices; +using System.Threading; +using TestUtilities; +using Xunit.Abstractions; + +namespace System.Net.Test.Common +{ + public static class HttpDebug + { + private sealed class State(ITestOutputHelper output) + { + private int _logCounter; + + public void WriteLine(string message) + { + // Avoid overwhelming the logs on noisy tests + if (Interlocked.Increment(ref _logCounter) < 10_000) + { + output.WriteLine(message); + } + } + } + + private static readonly Lazy s_eventListener = new( + () => new TestEventListener(WriteLine, TestEventListener.NetworkingEvents), + LazyThreadSafetyMode.ExecutionAndPublication); + + private static readonly AsyncLocal s_scope = new(); + + private static State? Scope => s_scope.Value; + + public static void DebugThisTest(ITestOutputHelper output) + { + s_scope.Value = new State(output); + _ = s_eventListener.Value; + } + + public static void WriteLine(string message) => + Scope?.WriteLine(message); + + public static void Log(string message, [CallerMemberName] string? memberName = null, [CallerLineNumber] int lineNumber = 0) => + Scope?.WriteLine($"[{memberName} #{lineNumber}] {message}"); + } +} diff --git a/src/libraries/Common/tests/System/Net/Http/HttpProtocolTests.cs b/src/libraries/Common/tests/System/Net/Http/HttpProtocolTests.cs index b90cffafeab5da..fd4af4a55c4c03 100644 --- a/src/libraries/Common/tests/System/Net/Http/HttpProtocolTests.cs +++ b/src/libraries/Common/tests/System/Net/Http/HttpProtocolTests.cs @@ -266,7 +266,8 @@ await LoopbackServer.CreateServerAsync(async (server, url) => { using (HttpClient client = CreateHttpClient()) { - Task getResponseTask = client.GetAsync(url); + Task getResponseTask = client.GetAsync(TestAsync, url); + await TestHelper.WhenAllCompletedOrAnyFailed( getResponseTask, server.AcceptConnectionSendCustomResponseAndCloseAsync( @@ -356,34 +357,116 @@ await LoopbackServer.CreateServerAsync(async (server, url) => Task ignoredServerTask = server.AcceptConnectionSendCustomResponseAndCloseAsync( responseString + "\r\nConnection: close\r\nContent-Length: 0\r\n\r\n"); - await Assert.ThrowsAsync(() => client.GetAsync(url)); + await Assert.ThrowsAsync(() => client.GetAsync(TestAsync, url)); } }, new LoopbackServer.Options { StreamWrapper = GetStream }); } + [Theory] + [InlineData("\r\n")] + [InlineData("\n")] + public async Task GetAsync_ResponseHasNormalLineEndings_Success_1(string lineEnding) + { + for (int i = 0; i < 10; i++) await GetAsync_ResponseHasNormalLineEndings_Success(lineEnding); + } + + [Theory] + [InlineData("\r\n")] + [InlineData("\n")] + public async Task GetAsync_ResponseHasNormalLineEndings_Success_2(string lineEnding) + { + for (int i = 0; i < 10; i++) await GetAsync_ResponseHasNormalLineEndings_Success(lineEnding); + } + + [Theory] + [InlineData("\r\n")] + [InlineData("\n")] + public async Task GetAsync_ResponseHasNormalLineEndings_Success_3(string lineEnding) + { + for (int i = 0; i < 10; i++) await GetAsync_ResponseHasNormalLineEndings_Success(lineEnding); + } + + [Theory] + [InlineData("\r\n")] + [InlineData("\n")] + public async Task GetAsync_ResponseHasNormalLineEndings_Success_4(string lineEnding) + { + for (int i = 0; i < 10; i++) await GetAsync_ResponseHasNormalLineEndings_Success(lineEnding); + } + + [Theory] + [InlineData("\r\n")] + [InlineData("\n")] + public async Task GetAsync_ResponseHasNormalLineEndings_Success_5(string lineEnding) + { + for (int i = 0; i < 10; i++) await GetAsync_ResponseHasNormalLineEndings_Success(lineEnding); + } + + [Theory] + [InlineData("\r\n")] + [InlineData("\n")] + public async Task GetAsync_ResponseHasNormalLineEndings_Success_6(string lineEnding) + { + for (int i = 0; i < 10; i++) await GetAsync_ResponseHasNormalLineEndings_Success(lineEnding); + } + + [Theory] + [InlineData("\r\n")] + [InlineData("\n")] + public async Task GetAsync_ResponseHasNormalLineEndings_Success_7(string lineEnding) + { + for (int i = 0; i < 10; i++) await GetAsync_ResponseHasNormalLineEndings_Success(lineEnding); + } + + [Theory] + [InlineData("\r\n")] + [InlineData("\n")] + public async Task GetAsync_ResponseHasNormalLineEndings_Success_8(string lineEnding) + { + for (int i = 0; i < 10; i++) await GetAsync_ResponseHasNormalLineEndings_Success(lineEnding); + } + + [Theory] + [InlineData("\r\n")] + [InlineData("\n")] + public async Task GetAsync_ResponseHasNormalLineEndings_Success_9(string lineEnding) + { + for (int i = 0; i < 10; i++) await GetAsync_ResponseHasNormalLineEndings_Success(lineEnding); + } + + [Theory] + [InlineData("\r\n")] + [InlineData("\n")] + public async Task GetAsync_ResponseHasNormalLineEndings_Success_10(string lineEnding) + { + for (int i = 0; i < 10; i++) await GetAsync_ResponseHasNormalLineEndings_Success(lineEnding); + } + [Theory] [InlineData("\r\n")] [InlineData("\n")] public async Task GetAsync_ResponseHasNormalLineEndings_Success(string lineEnding) { - await LoopbackServer.CreateServerAsync(async (server, url) => + EnableDebugLogs(); + + await LoopbackServer.CreateClientAndServerAsync(async url => { - using (HttpClient client = CreateHttpClient()) - { - Task getResponseTask = client.GetAsync(url); - Task> serverTask = server.AcceptConnectionSendCustomResponseAndCloseAsync( - $"HTTP/1.1 200 OK{lineEnding}Connection: close\r\nDate: {DateTimeOffset.UtcNow:R}{lineEnding}Server: TestServer{lineEnding}Content-Length: 0{lineEnding}{lineEnding}"); + using HttpClient client = CreateHttpClient(); - await TestHelper.WhenAllCompletedOrAnyFailed(getResponseTask, serverTask); + using HttpResponseMessage response = await client.GetAsync(TestAsync, url) + .WaitAsync(TestHelper.PassingTestTimeout); - using (HttpResponseMessage response = await getResponseTask) - { - Assert.Equal(200, (int)response.StatusCode); - Assert.Equal("OK", response.ReasonPhrase); - Assert.Equal("TestServer", response.Headers.Server.ToString()); - } - } - }, new LoopbackServer.Options { StreamWrapper = GetStream }); + Assert.Equal(200, (int)response.StatusCode); + Assert.Equal("OK", response.ReasonPhrase); + Assert.Equal("TestServer", response.Headers.Server.ToString()); + }, + async server => + { + await server.AcceptConnectionSendCustomResponseAndCloseAsync( + $"HTTP/1.1 200 OK{lineEnding}Connection: close{lineEnding}Date: {DateTimeOffset.UtcNow:R}{lineEnding}Server: TestServer{lineEnding}Content-Length: 0{lineEnding}{lineEnding}") + .WaitAsync(TestHelper.PassingTestTimeout); + }, + new LoopbackServer.Options { StreamWrapper = GetStream }); } public static IEnumerable GetAsync_Chunked_VaryingSizeChunks_ReceivedCorrectly_MemberData() diff --git a/src/libraries/Common/tests/System/Net/Http/IdnaProtocolTests.cs b/src/libraries/Common/tests/System/Net/Http/IdnaProtocolTests.cs index d3889a769de003..ea913303550048 100644 --- a/src/libraries/Common/tests/System/Net/Http/IdnaProtocolTests.cs +++ b/src/libraries/Common/tests/System/Net/Http/IdnaProtocolTests.cs @@ -40,7 +40,7 @@ await LoopbackServer.CreateServerAsync(async (server, serverUrl) => using (HttpClient client = CreateHttpClient(handler)) { - Task getResponseTask = client.GetAsync(uri); + Task getResponseTask = client.GetAsync(TestAsync, uri); Task> serverTask = server.AcceptConnectionSendResponseAndCloseAsync(); await TestHelper.WhenAllCompletedOrAnyFailed(getResponseTask, serverTask); diff --git a/src/libraries/Common/tests/TestUtilities/TestEventListener.cs b/src/libraries/Common/tests/TestUtilities/TestEventListener.cs index bd7b633ba6f822..2ebab476c86f4e 100644 --- a/src/libraries/Common/tests/TestUtilities/TestEventListener.cs +++ b/src/libraries/Common/tests/TestUtilities/TestEventListener.cs @@ -88,18 +88,29 @@ protected override void OnEventSourceCreated(EventSource eventSource) protected override void OnEventWritten(EventWrittenEventArgs eventData) { - StringBuilder sb = new StringBuilder(). -#if NETCOREAPP2_2_OR_GREATER || NETSTANDARD2_1_OR_GREATER - Append($"{eventData.TimeStamp:HH:mm:ss.fffffff}[{eventData.EventName}] "); -#else - Append($"[{eventData.EventName}] "); -#endif + string sourceName = eventData.EventSource.Name + .Replace("Private.InternalDiagnostics.System.Net.", "") + .Replace("System.Net.", ""); + + StringBuilder sb = new StringBuilder() + .Append($"[{sourceName}/{eventData.EventName}] "); + for (int i = 0; i < eventData.Payload?.Count; i++) { if (i > 0) sb.Append(", "); - sb.Append(eventData.PayloadNames?[i]).Append(": ").Append(eventData.Payload[i]); + + string? payloadName = eventData.PayloadNames?[i]; + object payload = eventData.Payload[i]; + + if (payload is byte[] buffer) + { + payload = BitConverter.ToString(buffer).Replace("-", ""); + } + + sb.Append(payloadName).Append(": ").Append(payload); } + try { _writeFunc(sb.ToString()); diff --git a/src/libraries/System.Net.Http.WinHttpHandler/tests/FunctionalTests/System.Net.Http.WinHttpHandler.Functional.Tests.csproj b/src/libraries/System.Net.Http.WinHttpHandler/tests/FunctionalTests/System.Net.Http.WinHttpHandler.Functional.Tests.csproj index 607b3ea0389107..bdbfac96196a02 100644 --- a/src/libraries/System.Net.Http.WinHttpHandler/tests/FunctionalTests/System.Net.Http.WinHttpHandler.Functional.Tests.csproj +++ b/src/libraries/System.Net.Http.WinHttpHandler/tests/FunctionalTests/System.Net.Http.WinHttpHandler.Functional.Tests.csproj @@ -58,6 +58,8 @@ Link="Common\System\Net\Http\Http2Frames.cs" /> + SendAsync(HttpMessageInvoker invoker, HttpRequestMessage request, CancellationToken cancellationToken = default) => TestHttpMessageInvoker ? - invoker.SendAsync(request, cancellationToken) : + (TestAsync ? invoker.SendAsync(request, cancellationToken) : Task.Run(() => invoker.Send(request, cancellationToken))) : ((HttpClient)invoker).SendAsync(TestAsync, request, cancellationToken); protected HttpMessageInvoker CreateHttpMessageInvoker(HttpMessageHandler? handler = null) => diff --git a/src/libraries/System.Net.Http/tests/FunctionalTests/System.Net.Http.Functional.Tests.csproj b/src/libraries/System.Net.Http/tests/FunctionalTests/System.Net.Http.Functional.Tests.csproj index fdb94e9caff24d..44355883e7edfb 100644 --- a/src/libraries/System.Net.Http/tests/FunctionalTests/System.Net.Http.Functional.Tests.csproj +++ b/src/libraries/System.Net.Http/tests/FunctionalTests/System.Net.Http.Functional.Tests.csproj @@ -98,6 +98,8 @@ Link="Common\System\Net\TestWebProxies.cs" /> + +