From 624f9fad0c53a77e3b861b66ae461e25815c7405 Mon Sep 17 00:00:00 2001 From: Alan West <3676547+alanwest@users.noreply.github.com> Date: Tue, 24 Oct 2023 13:46:28 -0700 Subject: [PATCH] Generate readme after running test --- .../AspNetCoreDiagnosticObserver.cs | 7 +- .../RouteTests/README.md | 16 ++- .../RouteTests/ReadmeGenerator.cs | 78 ------------ .../RouteTests/ReadmeGenerator.csproj | 31 ----- .../RouteTests/RouteInfoMiddleware.cs | 55 ++------ .../RouteTests/RouteTestData.cs | 2 +- .../RouteTests/RoutingTestFixture.cs | 120 ++++++++++++++++++ .../RouteTests/RoutingTests.cs | 51 +++----- .../RouteTests/TestApplicationFactory.cs | 12 +- 9 files changed, 172 insertions(+), 200 deletions(-) delete mode 100644 test/OpenTelemetry.Instrumentation.AspNetCore.Tests/RouteTests/ReadmeGenerator.cs delete mode 100644 test/OpenTelemetry.Instrumentation.AspNetCore.Tests/RouteTests/ReadmeGenerator.csproj create mode 100644 test/OpenTelemetry.Instrumentation.AspNetCore.Tests/RouteTests/RoutingTestFixture.cs diff --git a/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/RouteTests/AspNetCoreDiagnosticObserver.cs b/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/RouteTests/AspNetCoreDiagnosticObserver.cs index de560214dff..08dc0069ef3 100644 --- a/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/RouteTests/AspNetCoreDiagnosticObserver.cs +++ b/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/RouteTests/AspNetCoreDiagnosticObserver.cs @@ -75,7 +75,12 @@ public void OnNext(KeyValuePair value) info.SetValues(actionMethodEventData.ActionDescriptor); break; case OnStopEvent: - // Can't update RouteInfo here because the response is already written. + context = value.Value as HttpContext; + Debug.Assert(context != null, "HttpContext was null"); + info = context.Items["RouteInfo"] as RouteInfo; + Debug.Assert(info != null, "RouteInfo object not present in context.Items"); + info.SetValues(context); + RouteInfoMiddleware.RouteInfos.Add(info); break; default: break; diff --git a/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/RouteTests/README.md b/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/RouteTests/README.md index b06ff12a4c4..334b030faec 100644 --- a/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/RouteTests/README.md +++ b/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/RouteTests/README.md @@ -507,12 +507,12 @@ { "HttpMethod": "GET", "Path": "/MinimalApi", - "HttpRouteByRawText": null, + "HttpRouteByRawText": "/MinimalApi/", "HttpRouteByControllerActionAndParameters": "", "HttpRouteByActionDescriptor": "", "DebugInfo": { - "RawText": null, - "RouteDiagnosticMetadata": null, + "RawText": "/MinimalApi/", + "RouteDiagnosticMetadata": "/MinimalApi/", "RouteData": {}, "AttributeRouteInfo": null, "ActionParameters": null, @@ -530,13 +530,15 @@ { "HttpMethod": "GET", "Path": "/MinimalApi/123", - "HttpRouteByRawText": null, + "HttpRouteByRawText": "/MinimalApi/{id}", "HttpRouteByControllerActionAndParameters": "", "HttpRouteByActionDescriptor": "", "DebugInfo": { - "RawText": null, - "RouteDiagnosticMetadata": null, - "RouteData": {}, + "RawText": "/MinimalApi/{id}", + "RouteDiagnosticMetadata": "/MinimalApi/{id}", + "RouteData": { + "id": "123" + }, "AttributeRouteInfo": null, "ActionParameters": null, "PageActionDescriptorRelativePath": null, diff --git a/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/RouteTests/ReadmeGenerator.cs b/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/RouteTests/ReadmeGenerator.cs deleted file mode 100644 index beefcaca3d6..00000000000 --- a/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/RouteTests/ReadmeGenerator.cs +++ /dev/null @@ -1,78 +0,0 @@ -// -// Copyright The OpenTelemetry Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -#nullable disable - -using System.Text; -using RouteTests; - -public class ReadmeGenerator -{ - public static void Main() - { - var sb = new StringBuilder(); - - var testCases = RoutingTests.TestData; - - var results = new List(); - - foreach (var item in testCases) - { - using var tests = new RoutingTests(); - var testCase = item[0] as RouteTestData.RouteTestCase; - var result = tests.TestRoutes(testCase!).GetAwaiter().GetResult(); - results.Add(result); - } - - sb.AppendLine("| | | display name | expected name (w/o http.method) | routing type | request |"); - sb.AppendLine("| - | - | - | - | - | - |"); - - for (var i = 0; i < results.Count; ++i) - { - var result = results[i]; - var emoji = result.ActivityDisplayName.Equals(result.TestCase.ExpectedHttpRoute, StringComparison.InvariantCulture) - ? ":green_heart:" - : ":broken_heart:"; - sb.Append($"| {emoji} | [{i + 1}](#{i + 1}) "); - sb.AppendLine(FormatTestResult(results[i])); - } - - for (var i = 0; i < results.Count; ++i) - { - sb.AppendLine(); - sb.AppendLine($"#### {i + 1}"); - sb.AppendLine(); - sb.AppendLine("```json"); - sb.AppendLine(results[i].RouteInfo.ToString()); - sb.AppendLine("```"); - } - - File.WriteAllText("README.md", sb.ToString()); - - string FormatTestResult(TestResult result) - { - var testCase = result.TestCase!; - - return $"| {string.Join( - " | ", - result.ActivityDisplayName, // TODO: should be result.HttpRoute, but http.route is not currently added to Activity - testCase.ExpectedHttpRoute, - testCase.TestApplicationScenario, - $"{testCase.HttpMethod} {testCase.Path}", - result.ActivityDisplayName)} |"; - } - } -} diff --git a/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/RouteTests/ReadmeGenerator.csproj b/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/RouteTests/ReadmeGenerator.csproj deleted file mode 100644 index 2546cd772db..00000000000 --- a/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/RouteTests/ReadmeGenerator.csproj +++ /dev/null @@ -1,31 +0,0 @@ - - - - net8.0 - ReadmeGenerator - - - - - - - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - - - - - - - - - - RouteTests.testcases.json - Always - - - - diff --git a/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/RouteTests/RouteInfoMiddleware.cs b/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/RouteTests/RouteInfoMiddleware.cs index 0623884e74d..b4426a9d6e8 100644 --- a/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/RouteTests/RouteInfoMiddleware.cs +++ b/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/RouteTests/RouteInfoMiddleware.cs @@ -18,14 +18,13 @@ using System.Diagnostics; using System.Text.Json; -using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; -using static System.Net.Mime.MediaTypeNames; namespace RouteTests; public class RouteInfoMiddleware { + public static readonly List RouteInfos = new(); private readonly RequestDelegate next; public RouteInfoMiddleware(RequestDelegate next) @@ -33,51 +32,21 @@ public RouteInfoMiddleware(RequestDelegate next) this.next = next; } - public static void ConfigureExceptionHandler(IApplicationBuilder builder) + public async Task InvokeAsync(HttpContext context) { - builder.Run(async context => + if (context.Request.Path.ToString().Contains("GetLastRouteInfo")) { - context.Response.Body = (context.Items["originBody"] as Stream)!; - - context.Response.ContentType = Application.Json; - - var info = context.Items["RouteInfo"] as RouteInfo; + var response = context.Response; + var info = RouteInfos.Last(); Debug.Assert(info != null, "RouteInfo object not present in context.Items"); var jsonOptions = new JsonSerializerOptions { WriteIndented = true }; string modifiedResponse = JsonSerializer.Serialize(info, jsonOptions); - await context.Response.WriteAsync(modifiedResponse); - }); - } - - public async Task InvokeAsync(HttpContext context) - { - var response = context.Response; - - var originBody = response.Body; - context.Items["originBody"] = originBody; - using var newBody = new MemoryStream(); - response.Body = newBody; - - await this.next(context); - - var stream = response.Body; - using var reader = new StreamReader(stream, leaveOpen: true); - var originalResponse = await reader.ReadToEndAsync(); - - var info = context.Items["RouteInfo"] as RouteInfo; - Debug.Assert(info != null, "RouteInfo object not present in context.Items"); - var jsonOptions = new JsonSerializerOptions { WriteIndented = true }; - string modifiedResponse = JsonSerializer.Serialize(info, jsonOptions); - - stream.SetLength(0); - using var writer = new StreamWriter(stream, leaveOpen: true); - await writer.WriteAsync(modifiedResponse); - await writer.FlushAsync(); - response.ContentLength = stream.Length; - response.ContentType = "application/json"; - - newBody.Seek(0, SeekOrigin.Begin); - await newBody.CopyToAsync(originBody); - response.Body = originBody; + response.ContentType = "application/json"; + await response.WriteAsync(modifiedResponse); + } + else + { + await this.next(context); + } } } diff --git a/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/RouteTests/RouteTestData.cs b/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/RouteTests/RouteTestData.cs index f4f5b5c65d0..fc62fb12299 100644 --- a/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/RouteTests/RouteTestData.cs +++ b/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/RouteTests/RouteTestData.cs @@ -73,7 +73,7 @@ public class RouteTestCase public string? HttpMethod { get; set; } - public string? Path { get; set; } + public string Path { get; set; } = string.Empty; public int ExpectedStatusCode { get; set; } diff --git a/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/RouteTests/RoutingTestFixture.cs b/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/RouteTests/RoutingTestFixture.cs new file mode 100644 index 00000000000..fd2b794bf15 --- /dev/null +++ b/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/RouteTests/RoutingTestFixture.cs @@ -0,0 +1,120 @@ +// +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#nullable enable + +using System.Text; +using System.Text.Json; +using Microsoft.AspNetCore.Builder; + +namespace RouteTests; + +public class RoutingTestFixture : IDisposable +{ + private readonly Dictionary apps = new(); + private readonly HttpClient client = new(); + private readonly AspNetCoreDiagnosticObserver diagnostics = new(); + private readonly List testResults = new(); + + public RoutingTestFixture() + { + foreach (var scenario in Enum.GetValues()) + { + this.apps.Add(scenario, TestApplicationFactory.CreateApplication(scenario)); + } + + foreach (var app in this.apps) + { + app.Value.RunAsync(); + } + } + + public async Task MakeRequest(TestApplicationScenario scenario, string path) + { + var app = this.apps[scenario]; + var baseUrl = app.Urls.First(); + var url = $"{baseUrl}{path}"; + await this.client.GetAsync(url).ConfigureAwait(false); + } + + public void AddTestResult(TestResult testResult) + { + var app = this.apps[testResult.TestCase.TestApplicationScenario]; + var baseUrl = app.Urls.First(); + var url = $"{baseUrl}/GetLastRouteInfo"; + var responseMessage = this.client.GetAsync(url).ConfigureAwait(false).GetAwaiter().GetResult(); + var response = responseMessage.Content.ReadAsStringAsync().GetAwaiter().GetResult(); + var info = JsonSerializer.Deserialize(response); + testResult.RouteInfo = info; + this.testResults.Add(testResult); + } + + public void Dispose() + { + foreach (var app in this.apps) + { + app.Value.DisposeAsync().GetAwaiter().GetResult(); + } + + this.client.Dispose(); + this.diagnostics.Dispose(); + + this.GenerateReadme(); + } + + private void GenerateReadme() + { + var sb = new StringBuilder(); + sb.AppendLine("| | | display name | expected name (w/o http.method) | routing type | request |"); + sb.AppendLine("| - | - | - | - | - | - |"); + + for (var i = 0; i < this.testResults.Count; ++i) + { + var result = this.testResults[i]; + var emoji = result.ActivityDisplayName.Equals(result.TestCase.ExpectedHttpRoute, StringComparison.InvariantCulture) + ? ":green_heart:" + : ":broken_heart:"; + sb.Append($"| {emoji} | [{i + 1}](#{i + 1}) "); + sb.AppendLine(FormatTestResult(result)); + } + + for (var i = 0; i < this.testResults.Count; ++i) + { + var result = this.testResults[i]; + sb.AppendLine(); + sb.AppendLine($"#### {i + 1}"); + sb.AppendLine(); + sb.AppendLine("```json"); + sb.AppendLine(result.RouteInfo.ToString()); + sb.AppendLine("```"); + } + + File.WriteAllText(Path.Combine("..", "..", "..", "RouteTests", "README.md"), sb.ToString()); + + string FormatTestResult(TestResult result) + { + var testCase = result.TestCase!; + + return $"| {string.Join( + " | ", + result.ActivityDisplayName, // TODO: should be result.HttpRoute, but http.route is not currently added to Activity + testCase.ExpectedHttpRoute, + testCase.TestApplicationScenario, + $"{testCase.HttpMethod} {testCase.Path}", + result.ActivityDisplayName)} |"; + } + } +} diff --git a/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/RouteTests/RoutingTests.cs b/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/RouteTests/RoutingTests.cs index d77cf674fff..b38b7cc0079 100644 --- a/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/RouteTests/RoutingTests.cs +++ b/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/RouteTests/RoutingTests.cs @@ -17,8 +17,6 @@ #nullable enable using System.Diagnostics; -using System.Text.Json; -using Microsoft.AspNetCore.Builder; using OpenTelemetry; using OpenTelemetry.Metrics; using OpenTelemetry.Trace; @@ -26,27 +24,22 @@ namespace RouteTests; -public class RoutingTests : IDisposable +public class RoutingTests : IClassFixture, IDisposable { + // TODO: This test currently uses the old conventions. Update it to use the new conventions. private const string HttpStatusCode = "http.status_code"; private const string HttpMethod = "http.method"; private const string HttpRoute = "http.route"; - private TracerProvider tracerProvider; - private MeterProvider meterProvider; - private WebApplication? app; - private HttpClient client; - private List exportedActivities; - private List exportedMetrics; - private AspNetCoreDiagnosticObserver diagnostics; + private readonly RoutingTestFixture fixture; + private readonly TracerProvider tracerProvider; + private readonly MeterProvider meterProvider; + private readonly List exportedActivities = new(); + private readonly List exportedMetrics = new(); - public RoutingTests() + public RoutingTests(RoutingTestFixture fixture) { - this.diagnostics = new AspNetCoreDiagnosticObserver(); - this.client = new HttpClient { BaseAddress = new Uri("http://localhost:5000") }; - - this.exportedActivities = new List(); - this.exportedMetrics = new List(); + this.fixture = fixture; this.tracerProvider = Sdk.CreateTracerProviderBuilder() .AddAspNetCoreInstrumentation() @@ -61,18 +54,11 @@ public RoutingTests() public static IEnumerable TestData => RouteTestData.GetTestCases(); -#pragma warning disable xUnit1028 [Theory] [MemberData(nameof(TestData))] - public async Task TestRoutes(RouteTestData.RouteTestCase testCase, bool skipAsserts = true) + public async Task TestRoutes(RouteTestData.RouteTestCase testCase, bool skipAsserts = true) { - this.app = TestApplicationFactory.CreateApplication(testCase.TestApplicationScenario); - var appTask = this.app.RunAsync(); - - var responseMessage = await this.client.GetAsync(testCase.Path).ConfigureAwait(false); - var response = await responseMessage.Content.ReadAsStringAsync().ConfigureAwait(false); - - var info = JsonSerializer.Deserialize(response); + await this.fixture.MakeRequest(testCase.TestApplicationScenario, testCase.Path); for (var i = 0; i < 10; i++) { @@ -119,28 +105,23 @@ public async Task TestRoutes(RouteTestData.RouteTestCase testCase, b Assert.Equal(expectedActivityDisplayName, activity.DisplayName); } - return new TestResult + var testResult = new TestResult { ActivityDisplayName = activity.DisplayName, HttpStatusCode = activityHttpStatusCode, HttpMethod = activityHttpMethod, HttpRoute = activityHttpRoute, - RouteInfo = info!, + RouteInfo = null, TestCase = testCase, }; + + this.fixture.AddTestResult(testResult); } -#pragma warning restore xUnit1028 - public async void Dispose() + public void Dispose() { this.tracerProvider.Dispose(); this.meterProvider.Dispose(); - this.diagnostics.Dispose(); - this.client.Dispose(); - if (this.app != null) - { - await this.app.DisposeAsync().ConfigureAwait(false); - } } private void GetTagsFromActivity(Activity activity, out int httpStatusCode, out string httpMethod, out string? httpRoute) diff --git a/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/RouteTests/TestApplicationFactory.cs b/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/RouteTests/TestApplicationFactory.cs index bac22256c21..7760906a615 100644 --- a/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/RouteTests/TestApplicationFactory.cs +++ b/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/RouteTests/TestApplicationFactory.cs @@ -82,7 +82,8 @@ private static WebApplication CreateConventionalRoutingApplication() .AddApplicationPart(typeof(RoutingTests).Assembly); var app = builder.Build(); - app.UseExceptionHandler(RouteInfoMiddleware.ConfigureExceptionHandler); + app.Urls.Clear(); + app.Urls.Add("http://[::1]:0"); app.UseMiddleware(); app.UseStaticFiles(); app.UseRouting(); @@ -116,7 +117,8 @@ private static WebApplication CreateAttributeRoutingApplication() .AddApplicationPart(typeof(RoutingTests).Assembly); var app = builder.Build(); - app.UseExceptionHandler(RouteInfoMiddleware.ConfigureExceptionHandler); + app.Urls.Clear(); + app.Urls.Add("http://[::1]:0"); app.UseMiddleware(); app.MapControllers(); @@ -128,7 +130,8 @@ private static WebApplication CreateMinimalApiApplication() var builder = WebApplication.CreateBuilder(); // WebApplication.CreateSlimBuilder(); var app = builder.Build(); - app.UseExceptionHandler(RouteInfoMiddleware.ConfigureExceptionHandler); + app.Urls.Clear(); + app.Urls.Add("http://[::1]:0"); app.UseMiddleware(); #if NET7_0_OR_GREATER @@ -152,7 +155,8 @@ private static WebApplication CreateRazorPagesApplication() .AddApplicationPart(typeof(RoutingTests).Assembly); var app = builder.Build(); - app.UseExceptionHandler(RouteInfoMiddleware.ConfigureExceptionHandler); + app.Urls.Clear(); + app.Urls.Add("http://[::1]:0"); app.UseMiddleware(); app.UseStaticFiles(); app.UseRouting();