diff --git a/.azure/pipelines/richnav.yml b/.azure/pipelines/richnav.yml
index 46d8b9e3cb75c9f74b4e8b705ae762f45c5b6972..e5fe8756d315e0991920164f4b10f28d975d5632 100644
--- a/.azure/pipelines/richnav.yml
+++ b/.azure/pipelines/richnav.yml
@@ -6,59 +6,69 @@
 trigger:
   branches:
     include:
-    - blazor-wasm
     - main
     - release/*
-    - internal/release/*
+
+# Do not run this pipeline for PR validation.
+pr: none
 
 variables:
 - name: _BuildArgs
   value: '/p:SkipTestBuild=true'
-- name: Windows86LogArgs
+- name: WindowsNonX64LogArgs
   value: -ExcludeCIBinaryLog
 
 stages:
 - stage: build
   displayName: Build
   jobs:
-  # Build Windows (x64/x86)
+  # Build Windows (x64/x86/arm64)
   - template: jobs/default-build.yml
     parameters:
       codeSign: false
       jobName: Windows_build
-      jobDisplayName: "Build: Windows x64/x86"
+      jobDisplayName: "Build: Windows x64/x86/arm64"
       enableRichCodeNavigation: true
       agentOs: Windows
       steps:
-      - script: ./build.cmd
+      - script: ./eng/build.cmd
                 -ci
-                -all
                 -arch x64
-                /p:EnableRichCodeNavigation=true
+                -buildNative
+                /p:EnableRichCodeNavigation=false
+                $(_BuildArgs)
+        displayName: Build x64 native assets
+
+      - script: ./eng/build.cmd
+                -ci
+                -arch x64
+                -all
+                -noBuildNative
+                -noBuildRepoTasks
                 $(_BuildArgs)
         displayName: Build x64
 
       # Build the x86 shared framework
       # This is going to actually build x86 native assets.
-      - script: ./build.cmd
+      - script: ./eng/build.cmd
                 -ci
-                -noBuildRepoTasks
                 -arch x86
                 -all
                 -noBuildJava
                 -noBuildNative
-                /p:EnableRichCodeNavigation=true
+                -noBuildRepoTasks
                 $(_BuildArgs)
-                $(Windows86LogArgs)
+                $(WindowsNonX64LogArgs)
         displayName: Build x86
 
-      # Windows installers bundle both x86 and x64 assets
-      - script: ./build.cmd
+      # Build the arm64 shared framework
+      - script: ./eng/build.cmd
                 -ci
-                -noBuildRepoTasks
-                -buildInstallers
+                -arch arm64
+                -noBuildJava
                 -noBuildNative
-                /p:AssetManifestFileName=aspnetcore-win-x64-x86.xml
-                /p:EnableRichCodeNavigation=true
+                -noBuildRepoTasks
                 $(_BuildArgs)
-        displayName: Build Installers
+                $(WindowsNonX64LogArgs)
+        displayName: Build ARM64
+
diff --git a/Directory.Build.props b/Directory.Build.props
index 8f0ac5942284cb62724017bdaa2c5700e4b1ce37..721f099a7734bd344434f3eeb2100fba83bd62b3 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -30,6 +30,9 @@
           $(MSBuildProjectName.EndsWith('.Test')) OR
           $(MSBuildProjectName.EndsWith('.FunctionalTest')) ) ">true</IsUnitTestProject>
     <IsTestAssetProject Condition=" $(RepoRelativeProjectDir.Contains('testassets')) OR $(MSBuildProjectName.Contains('TestCommon'))">true</IsTestAssetProject>
+    <IsProjectTemplateProject Condition=" ($(RepoRelativeProjectDir.Contains('ProjectTemplates')) OR $(MSBuildProjectName.Contains('ProjectTemplates')) ) AND
+        '$(IsUnitTestProject)' != 'true' AND
+        '$(IsTestAssetProject)' != 'true' ">true</IsProjectTemplateProject>
     <IsSampleProject Condition=" $(RepoRelativeProjectDir.ToUpperInvariant().Contains('SAMPLE')) ">true</IsSampleProject>
     <IsAnalyzersProject Condition="$(MSBuildProjectName.EndsWith('.Analyzers'))">true</IsAnalyzersProject>
     <IsShipping Condition=" '$(IsSampleProject)' == 'true' OR
diff --git a/Directory.Build.targets b/Directory.Build.targets
index a195c0fb2ea0e22d72f7972c6d6938784a3942c6..fd388a1176d2762dbe99ba7519ca2f8f03391f4c 100644
--- a/Directory.Build.targets
+++ b/Directory.Build.targets
@@ -1,10 +1,15 @@
 <Project>
 
   <PropertyGroup>
-    <!-- Only build Microsoft.AspNetCore.App, Microsoft.AspNetCore.App.Ref, and ref/ assemblies in source build. -->
+    <!-- Only build Microsoft.AspNetCore.App, Microsoft.AspNetCore.App.Ref, ref/ assemblies, and ProjectTemplates in source build. -->
     <!-- Analyzer package are needed in source build for WebSDK -->
     <ExcludeFromSourceBuild
-        Condition="'$(ExcludeFromSourceBuild)' == '' and '$(DotNetBuildFromSource)' == 'true' and '$(IsAspNetCoreApp)' != 'true' and '$(MSBuildProjectName)' != '$(TargetingPackName)' and '$(IsAnalyzersProject)' != 'true'">true</ExcludeFromSourceBuild>
+        Condition="'$(ExcludeFromSourceBuild)' == '' and 
+            '$(DotNetBuildFromSource)' == 'true' and 
+            '$(IsAspNetCoreApp)' != 'true' and 
+            '$(MSBuildProjectName)' != '$(TargetingPackName)' and 
+            '$(IsAnalyzersProject)' != 'true' and 
+            '$(IsProjectTemplateProject)' != 'true'">true</ExcludeFromSourceBuild>
 
     <!-- If the user has specified that they want to skip building any test related projects with SkipTestBuild,
      suppress all targets for TestProjects using ExcludeFromBuild. -->
diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml
index a15e7e7e18252fba5d8178387ea8ae52d37b3bcb..640bbfa0defb6cf2765556fb7c3a88a080ff690a 100644
--- a/eng/Version.Details.xml
+++ b/eng/Version.Details.xml
@@ -280,22 +280,22 @@
       <Uri>https://dev.azure.com/dnceng/internal/_git/dotnet-runtime</Uri>
       <Sha>be98e88c760526452df94ef452fff4602fb5bded</Sha>
     </Dependency>
-    <Dependency Name="Microsoft.DotNet.Arcade.Sdk" Version="6.0.0-beta.22161.1">
+    <Dependency Name="Microsoft.DotNet.Arcade.Sdk" Version="6.0.0-beta.22178.5">
       <Uri>https://github.com/dotnet/arcade</Uri>
-      <Sha>879df783283dfb44c7653493fdf7fd7b07ba6b01</Sha>
+      <Sha>f8c0d51185208227e582f76ac3c5003db237b689</Sha>
       <SourceBuild RepoName="arcade" ManagedOnly="true" />
     </Dependency>
-    <Dependency Name="Microsoft.DotNet.Build.Tasks.Installers" Version="6.0.0-beta.22161.1">
+    <Dependency Name="Microsoft.DotNet.Build.Tasks.Installers" Version="6.0.0-beta.22178.5">
       <Uri>https://github.com/dotnet/arcade</Uri>
-      <Sha>879df783283dfb44c7653493fdf7fd7b07ba6b01</Sha>
+      <Sha>f8c0d51185208227e582f76ac3c5003db237b689</Sha>
     </Dependency>
-    <Dependency Name="Microsoft.DotNet.Build.Tasks.Templating" Version="6.0.0-beta.22161.1">
+    <Dependency Name="Microsoft.DotNet.Build.Tasks.Templating" Version="6.0.0-beta.22178.5">
       <Uri>https://github.com/dotnet/arcade</Uri>
-      <Sha>879df783283dfb44c7653493fdf7fd7b07ba6b01</Sha>
+      <Sha>f8c0d51185208227e582f76ac3c5003db237b689</Sha>
     </Dependency>
-    <Dependency Name="Microsoft.DotNet.Helix.Sdk" Version="6.0.0-beta.22161.1">
+    <Dependency Name="Microsoft.DotNet.Helix.Sdk" Version="6.0.0-beta.22178.5">
       <Uri>https://github.com/dotnet/arcade</Uri>
-      <Sha>879df783283dfb44c7653493fdf7fd7b07ba6b01</Sha>
+      <Sha>f8c0d51185208227e582f76ac3c5003db237b689</Sha>
     </Dependency>
   </ToolsetDependencies>
 </Dependencies>
diff --git a/eng/Versions.props b/eng/Versions.props
index 2d3e47d26e4157f7296c91910bd40ca534570899..72ca8c5f00473f37d36adbfa780b9ea33bdf85d5 100644
--- a/eng/Versions.props
+++ b/eng/Versions.props
@@ -8,8 +8,8 @@
   <PropertyGroup Label="Version settings">
     <AspNetCoreMajorVersion>6</AspNetCoreMajorVersion>
     <AspNetCoreMinorVersion>0</AspNetCoreMinorVersion>
-    <AspNetCorePatchVersion>4</AspNetCorePatchVersion>
-    <ValidateBaseline>true</ValidateBaseline>
+    <AspNetCorePatchVersion>5</AspNetCorePatchVersion>
+    <ValidateBaseline>false</ValidateBaseline>
     <!--
         When StabilizePackageVersion is set to 'true', this branch will produce stable outputs for 'Shipping' packages
     -->
@@ -131,8 +131,8 @@
     <MicrosoftEntityFrameworkCoreVersion>6.0.4</MicrosoftEntityFrameworkCoreVersion>
     <MicrosoftEntityFrameworkCoreDesignVersion>6.0.4</MicrosoftEntityFrameworkCoreDesignVersion>
     <!-- Packages from dotnet/arcade -->
-    <MicrosoftDotNetBuildTasksInstallersVersion>6.0.0-beta.22161.1</MicrosoftDotNetBuildTasksInstallersVersion>
-    <MicrosoftDotNetBuildTasksTemplatingVersion>6.0.0-beta.22161.1</MicrosoftDotNetBuildTasksTemplatingVersion>
+    <MicrosoftDotNetBuildTasksInstallersVersion>6.0.0-beta.22178.5</MicrosoftDotNetBuildTasksInstallersVersion>
+    <MicrosoftDotNetBuildTasksTemplatingVersion>6.0.0-beta.22178.5</MicrosoftDotNetBuildTasksTemplatingVersion>
   </PropertyGroup>
   <!--
 
diff --git a/eng/common/templates/steps/source-build.yml b/eng/common/templates/steps/source-build.yml
index ba40dc82f1411b9fb54db50a4c315d07a27aa305..abb1b2bcda42b84352ff6ead9d449f959e27dfc8 100644
--- a/eng/common/templates/steps/source-build.yml
+++ b/eng/common/templates/steps/source-build.yml
@@ -43,8 +43,8 @@ steps:
     # In that case, add variables to allow the download of internal runtimes if the specified versions are not found
     # in the default public locations.
     internalRuntimeDownloadArgs=
-    if [ '$(dotnetclimsrc-read-sas-token-base64)' != '$''(dotnetclimsrc-read-sas-token-base64)' ]; then
-      internalRuntimeDownloadArgs='/p:DotNetRuntimeSourceFeed=https://dotnetclimsrc.blob.core.windows.net/dotnet /p:DotNetRuntimeSourceFeedKey=$(dotnetclimsrc-read-sas-token-base64) --runtimesourcefeed https://dotnetclimsrc.blob.core.windows.net/dotnet --runtimesourcefeedkey $(dotnetclimsrc-read-sas-token-base64)'
+    if [ '$(dotnetbuilds-internal-container-read-token-base64)' != '$''(dotnetbuilds-internal-container-read-token-base64)' ]; then
+      internalRuntimeDownloadArgs='/p:DotNetRuntimeSourceFeed=https://dotnetbuilds.blob.core.windows.net/internal /p:DotNetRuntimeSourceFeedKey=$(dotnetbuilds-internal-container-read-token-base64) --runtimesourcefeed https://dotnetbuilds.blob.core.windows.net/internal --runtimesourcefeedkey $(dotnetbuilds-internal-container-read-token-base64)'
     fi
 
     buildConfig=Release
diff --git a/global.json b/global.json
index 05ed9cd7faca029f874fb92c428b51d5f4bd4c55..ecf50a40254a6674389e826d308e3b0537d9907d 100644
--- a/global.json
+++ b/global.json
@@ -29,7 +29,7 @@
   },
   "msbuild-sdks": {
     "Yarn.MSBuild": "1.22.10",
-    "Microsoft.DotNet.Arcade.Sdk": "6.0.0-beta.22161.1",
-    "Microsoft.DotNet.Helix.Sdk": "6.0.0-beta.22161.1"
+    "Microsoft.DotNet.Arcade.Sdk": "6.0.0-beta.22178.5",
+    "Microsoft.DotNet.Helix.Sdk": "6.0.0-beta.22178.5"
   }
 }
diff --git a/src/Components/WebAssembly/Authentication.Msal/src/Microsoft.Authentication.WebAssembly.Msal.csproj b/src/Components/WebAssembly/Authentication.Msal/src/Microsoft.Authentication.WebAssembly.Msal.csproj
index 8ae5d1c43d7b6df28fc0c7c7f649590f499fca71..1db0368b882c85dbbe4973be06857ee348f9d3f2 100644
--- a/src/Components/WebAssembly/Authentication.Msal/src/Microsoft.Authentication.WebAssembly.Msal.csproj
+++ b/src/Components/WebAssembly/Authentication.Msal/src/Microsoft.Authentication.WebAssembly.Msal.csproj
@@ -1,6 +1,6 @@
 <Project Sdk="Microsoft.NET.Sdk.Razor">
 
-  <Sdk Name="Yarn.MSBuild" />
+  <Import Project="Sdk.props" Sdk="Yarn.MSBuild" Condition=" '$(DotNetBuildFromSource)' != 'true'" />
 
   <PropertyGroup>
     <TargetFramework>$(DefaultNetCoreTargetFramework)</TargetFramework>
@@ -25,6 +25,7 @@
   <PropertyGroup>
     <YarnWorkingDir>$(MSBuildThisFileDirectory)Interop\</YarnWorkingDir>
     <ResolveStaticWebAssetsInputsDependsOn>
+      CheckForSourceBuild;
       CompileInterop;
       IncludeCompileInteropOutput;
       $(ResolveStaticWebAssetsInputsDependsOn)
@@ -91,5 +92,11 @@
       <FileWrites Include="$(_InteropBuildOutput)" />
     </ItemGroup>
   </Target>
+   
+  <Target Name="CheckForSourceBuild" Condition=" '$(DotNetBuildFromSource)' == 'true'">
+    <Error Text="The Yarn.Msbuild SDK is currently excluded from SourceBuild. If you are enabling this project for SourceBuild, remove the condition on the Yarn.Msbuild SDK above." />
+  </Target>
 
+  <Import Project="Sdk.targets" Sdk="Yarn.MSBuild" Condition=" '$(DotNetBuildFromSource)' != 'true'" />
+   
 </Project>
diff --git a/src/Components/WebAssembly/WebAssembly.Authentication/src/Microsoft.AspNetCore.Components.WebAssembly.Authentication.csproj b/src/Components/WebAssembly/WebAssembly.Authentication/src/Microsoft.AspNetCore.Components.WebAssembly.Authentication.csproj
index 8d6a000d74f0d646a51119c6c53c8137169c30df..35c79a73eb8b10ac60c15ef1dd85ef1e155bb500 100644
--- a/src/Components/WebAssembly/WebAssembly.Authentication/src/Microsoft.AspNetCore.Components.WebAssembly.Authentication.csproj
+++ b/src/Components/WebAssembly/WebAssembly.Authentication/src/Microsoft.AspNetCore.Components.WebAssembly.Authentication.csproj
@@ -1,6 +1,6 @@
 <Project Sdk="Microsoft.NET.Sdk.Razor">
 
-  <Sdk Name="Yarn.MSBuild" />
+  <Import Project="Sdk.props" Sdk="Yarn.MSBuild" Condition=" '$(DotNetBuildFromSource)' != 'true'" />
 
   <PropertyGroup>
     <TargetFramework>$(DefaultNetCoreTargetFramework)</TargetFramework>
@@ -26,6 +26,7 @@
   <PropertyGroup>
     <YarnWorkingDir>$(MSBuildThisFileDirectory)Interop\</YarnWorkingDir>
     <ResolveStaticWebAssetsInputsDependsOn>
+      CheckForSourceBuild;
       CompileInterop;
       IncludeCompileInteropOutput;
       $(ResolveStaticWebAssetsInputsDependsOn)
@@ -93,4 +94,10 @@
     </ItemGroup>
   </Target>
 
+  <Target Name="CheckForSourceBuild" Condition=" '$(DotNetBuildFromSource)' == 'true'">
+    <Error Text="The Yarn.Msbuild SDK is currently excluded from SourceBuild. If you are enabling this project for SourceBuild, remove the condition on the Yarn.Msbuild SDK above." />
+  </Target>
+
+  <Import Project="Sdk.targets" Sdk="Yarn.MSBuild" Condition=" '$(DotNetBuildFromSource)' != 'true'" />
+
 </Project>
diff --git a/src/Mvc/test/Mvc.FunctionalTests/ErrorPageTests.cs b/src/Mvc/test/Mvc.FunctionalTests/ErrorPageTests.cs
index 11ff6b17d8999af93de231af5bbb07344b285f70..ccfb60a40bb4aa962c6f58f89b087b8c0e899295 100644
--- a/src/Mvc/test/Mvc.FunctionalTests/ErrorPageTests.cs
+++ b/src/Mvc/test/Mvc.FunctionalTests/ErrorPageTests.cs
@@ -1,14 +1,10 @@
 // Licensed to the .NET Foundation under one or more agreements.
 // The .NET Foundation licenses this file to you under the MIT license.
 
-using System;
-using System.IO;
-using System.Linq;
 using System.Net;
 using System.Net.Http;
 using System.Net.Http.Headers;
 using System.Text.Encodings.Web;
-using System.Threading.Tasks;
 using Microsoft.AspNetCore.Hosting;
 using Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation;
 using Microsoft.AspNetCore.TestHost;
@@ -16,7 +12,6 @@ using Microsoft.AspNetCore.Testing;
 using Microsoft.Extensions.DependencyInjection;
 using Microsoft.Extensions.Logging;
 using Microsoft.Extensions.Logging.Testing;
-using Xunit;
 using Xunit.Abstractions;
 
 namespace Microsoft.AspNetCore.Mvc.FunctionalTests
@@ -24,7 +19,7 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
     /// <summary>
     /// Functional test to verify the error reporting of Razor compilation by diagnostic middleware.
     /// </summary>
-    public class ErrorPageTests : IClassFixture<MvcTestFixture<ErrorPageMiddlewareWebSite.Startup>>, IDisposable
+    public class ErrorPageTests : IClassFixture<MvcTestFixture<ErrorPageMiddlewareWebSite.Startup>>
     {
         private static readonly string PreserveCompilationContextMessage = HtmlEncoder.Default.Encode(
             "One or more compilation references may be missing. " +
@@ -189,10 +184,5 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
             Assert.Contains(nullReferenceException, content);
             Assert.Contains(indexOutOfRangeException, content);
         }
-
-        public void Dispose()
-        {
-            _assemblyTestLog.Dispose();
-        }
     }
 }
diff --git a/src/Servers/HttpSys/HttpSysServer.slnf b/src/Servers/HttpSys/HttpSysServer.slnf
index 3990e33925cbff07c901e51ce3ccde3c33a99007..c546d5797a4acf062a54e3b020589b01bc241768 100644
--- a/src/Servers/HttpSys/HttpSysServer.slnf
+++ b/src/Servers/HttpSys/HttpSysServer.slnf
@@ -4,9 +4,11 @@
     "projects": [
       "src\\DefaultBuilder\\src\\Microsoft.AspNetCore.csproj",
       "src\\Extensions\\Features\\src\\Microsoft.Extensions.Features.csproj",
+      "src\\FileProviders\\Embedded\\src\\Microsoft.Extensions.FileProviders.Embedded.csproj",
       "src\\Hosting\\Abstractions\\src\\Microsoft.AspNetCore.Hosting.Abstractions.csproj",
       "src\\Hosting\\Hosting\\src\\Microsoft.AspNetCore.Hosting.csproj",
       "src\\Hosting\\Server.Abstractions\\src\\Microsoft.AspNetCore.Hosting.Server.Abstractions.csproj",
+      "src\\Hosting\\Server.IntegrationTesting\\src\\Microsoft.AspNetCore.Server.IntegrationTesting.csproj",
       "src\\Http\\Authentication.Abstractions\\src\\Microsoft.AspNetCore.Authentication.Abstractions.csproj",
       "src\\Http\\Authentication.Core\\src\\Microsoft.AspNetCore.Authentication.Core.csproj",
       "src\\Http\\Headers\\src\\Microsoft.Net.Http.Headers.csproj",
diff --git a/src/Servers/HttpSys/src/DelegationRule.cs b/src/Servers/HttpSys/src/DelegationRule.cs
index 1f57f82985584022be8ac93664b00f9420d85fbf..c454952eea9e3bc280e7f08f7ea56335bd9d6b63 100644
--- a/src/Servers/HttpSys/src/DelegationRule.cs
+++ b/src/Servers/HttpSys/src/DelegationRule.cs
@@ -13,17 +13,19 @@ namespace Microsoft.AspNetCore.Server.HttpSys
     public class DelegationRule : IDisposable
     {
         private readonly ILogger _logger;
-        private readonly UrlGroup _urlGroup;
         private readonly UrlGroup _sourceQueueUrlGroup;
         private bool _disposed;
+
         /// <summary>
         /// The name of the Http.Sys request queue
         /// </summary>
         public string QueueName { get; }
+
         /// <summary>
         /// The URL of the Http.Sys Url Prefix
         /// </summary>
         public string UrlPrefix { get; }
+
         internal RequestQueue Queue { get; }
 
         internal DelegationRule(UrlGroup sourceQueueUrlGroup, string queueName, string urlPrefix, ILogger logger)
@@ -32,8 +34,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys
             _logger = logger;
             QueueName = queueName;
             UrlPrefix = urlPrefix;
-            Queue = new RequestQueue(queueName, UrlPrefix, _logger, receiver: true);
-            _urlGroup = Queue.UrlGroup;
+            Queue = new RequestQueue(queueName, _logger);
         }
 
         /// <inheritdoc />
@@ -51,7 +52,6 @@ namespace Microsoft.AspNetCore.Server.HttpSys
                 _sourceQueueUrlGroup.UnSetDelegationProperty(Queue, throwOnError: false);
             }
             catch (ObjectDisposedException) { /* Server may have been shutdown */ }
-            _urlGroup.Dispose();
             Queue.Dispose();
         }
     }
diff --git a/src/Servers/HttpSys/src/NativeInterop/RequestQueue.cs b/src/Servers/HttpSys/src/NativeInterop/RequestQueue.cs
index 8e25a8c21c37cc558db42be59130e70d1aa19ef1..89638a222575ee9de312ddcb681a493770efaed2 100644
--- a/src/Servers/HttpSys/src/NativeInterop/RequestQueue.cs
+++ b/src/Servers/HttpSys/src/NativeInterop/RequestQueue.cs
@@ -19,25 +19,16 @@ namespace Microsoft.AspNetCore.Server.HttpSys
         private readonly ILogger _logger;
         private bool _disposed;
 
-        internal RequestQueue(string requestQueueName, string urlPrefix, ILogger logger, bool receiver)
-            : this(urlGroup: null!, requestQueueName, RequestQueueMode.Attach, logger, receiver)
+        internal RequestQueue(string requestQueueName, ILogger logger)
+            : this(urlGroup: null, requestQueueName, RequestQueueMode.Attach, logger, receiver: true)
         {
-            try
-            {
-                UrlGroup = new UrlGroup(this, UrlPrefix.Create(urlPrefix), logger);
-            }
-            catch
-            {
-                Dispose();
-                throw;
-            }
         }
 
         internal RequestQueue(UrlGroup urlGroup, string? requestQueueName, RequestQueueMode mode, ILogger logger)
             : this(urlGroup, requestQueueName, mode, logger, false)
         { }
 
-        private RequestQueue(UrlGroup urlGroup, string? requestQueueName, RequestQueueMode mode, ILogger logger, bool receiver)
+        private RequestQueue(UrlGroup? urlGroup, string? requestQueueName, RequestQueueMode mode, ILogger logger, bool receiver)
         {
             _mode = mode;
             UrlGroup = urlGroup;
@@ -117,10 +108,15 @@ namespace Microsoft.AspNetCore.Server.HttpSys
         internal SafeHandle Handle { get; }
         internal ThreadPoolBoundHandle BoundHandle { get; }
 
-        internal UrlGroup UrlGroup { get; }
+        internal UrlGroup? UrlGroup { get; }
 
         internal unsafe void AttachToUrlGroup()
         {
+            if (UrlGroup == null)
+            {
+                throw new NotSupportedException("Can't attach when UrlGroup is null");
+            }
+
             Debug.Assert(Created);
             CheckDisposed();
             // Set the association between request queue and url group. After this, requests for registered urls will
@@ -138,6 +134,11 @@ namespace Microsoft.AspNetCore.Server.HttpSys
 
         internal unsafe void DetachFromUrlGroup()
         {
+            if (UrlGroup == null)
+            {
+                throw new NotSupportedException("Can't detach when UrlGroup is null");
+            }
+
             Debug.Assert(Created);
             CheckDisposed();
             // Break the association between request queue and url group. After this, requests for registered urls
diff --git a/src/Servers/HttpSys/src/NativeInterop/UrlGroup.cs b/src/Servers/HttpSys/src/NativeInterop/UrlGroup.cs
index d13264889dddebec2b00d3ae938c8d5269f5aa6d..59a67ca43195617b26954ede805d3d429216ac63 100644
--- a/src/Servers/HttpSys/src/NativeInterop/UrlGroup.cs
+++ b/src/Servers/HttpSys/src/NativeInterop/UrlGroup.cs
@@ -41,24 +41,6 @@ namespace Microsoft.AspNetCore.Server.HttpSys
             Id = urlGroupId;
         }
 
-        internal unsafe UrlGroup(RequestQueue requestQueue, UrlPrefix url, ILogger logger)
-        {
-            _logger = logger;
-
-            ulong urlGroupId = 0;
-            _created = false;
-            var statusCode = HttpApi.HttpFindUrlGroupId(
-                url.FullPrefix, requestQueue.Handle, &urlGroupId);
-
-            if (statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS)
-            {
-                throw new HttpSysException((int)statusCode);
-            }
-
-            Debug.Assert(urlGroupId != 0, "Invalid id returned by HttpCreateUrlGroup");
-            Id = urlGroupId;
-        }
-
         internal ulong Id { get; private set; }
 
         internal unsafe void SetMaxConnections(long maxConnections)
diff --git a/src/Servers/HttpSys/src/RequestProcessing/Request.cs b/src/Servers/HttpSys/src/RequestProcessing/Request.cs
index ffffc050672cce7df8cba9bd1482b7ca86069981..880beebf562d686115a681dea15d3b860f4ef3c5 100644
--- a/src/Servers/HttpSys/src/RequestProcessing/Request.cs
+++ b/src/Servers/HttpSys/src/RequestProcessing/Request.cs
@@ -120,7 +120,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys
         internal ulong RawConnectionId { get; }
 
         // No ulongs in public APIs...
-        public long ConnectionId => (long)RawConnectionId;
+        public long ConnectionId => RawConnectionId != 0 ? (long)RawConnectionId : (long)UConnectionId;
 
         internal ulong RequestId { get; }
 
diff --git a/src/Servers/HttpSys/src/RequestProcessing/RequestContext.cs b/src/Servers/HttpSys/src/RequestProcessing/RequestContext.cs
index fabae04d35e597fd9dc28333870ad1c27c3f495f..9c45a262dac8fd981d3ccab5360f2e1937afdaea 100644
--- a/src/Servers/HttpSys/src/RequestProcessing/RequestContext.cs
+++ b/src/Servers/HttpSys/src/RequestProcessing/RequestContext.cs
@@ -289,10 +289,13 @@ namespace Microsoft.AspNetCore.Server.HttpSys
                     PropertyInfoLength = (uint)System.Text.Encoding.Unicode.GetByteCount(destination.UrlPrefix)
                 };
 
+                // Passing 0 for delegateUrlGroupId allows http.sys to find the right group for the
+                // URL passed in via the property above. If we passed in the receiver's URL group id
+                // instead of 0, then delegation would fail if the receiver restarted.
                 statusCode = HttpApi.HttpDelegateRequestEx(source.Handle,
                                                                destination.Queue.Handle,
                                                                Request.RequestId,
-                                                               destination.Queue.UrlGroup.Id,
+                                                               delegateUrlGroupId: 0,
                                                                propertyInfoSetSize: 1,
                                                                &property);
             }
diff --git a/src/Servers/HttpSys/src/ServerDelegationPropertyFeature.cs b/src/Servers/HttpSys/src/ServerDelegationPropertyFeature.cs
index 14aee50615c90497b6137037d19650cccfbbe5c6..a97520a837d796d01b94a1d7cb51bf3d8de6eefe 100644
--- a/src/Servers/HttpSys/src/ServerDelegationPropertyFeature.cs
+++ b/src/Servers/HttpSys/src/ServerDelegationPropertyFeature.cs
@@ -14,18 +14,23 @@ namespace Microsoft.AspNetCore.Server.HttpSys
     internal class ServerDelegationPropertyFeature : IServerDelegationFeature
     {
         private readonly ILogger _logger;
-        private readonly RequestQueue _queue;
+        private readonly UrlGroup _urlGroup;
 
         public ServerDelegationPropertyFeature(RequestQueue queue, ILogger logger)
         {
-            _queue = queue;
+            if (queue.UrlGroup == null)
+            {
+                throw new ArgumentException($"{nameof(queue)}.UrlGroup can't be null");
+            }
+
+            _urlGroup = queue.UrlGroup;
             _logger = logger;
         }
 
         public DelegationRule CreateDelegationRule(string queueName, string uri)
         {
-            var rule = new DelegationRule(_queue.UrlGroup, queueName, uri, _logger);
-            _queue.UrlGroup.SetDelegationProperty(rule.Queue);
+            var rule = new DelegationRule(_urlGroup, queueName, uri, _logger);
+            _urlGroup.SetDelegationProperty(rule.Queue);
             return rule;
         }
     }
diff --git a/src/Servers/HttpSys/test/FunctionalTests/DelegateTests.cs b/src/Servers/HttpSys/test/FunctionalTests/DelegateTests.cs
index ca9dcf3a0736e18915f26d539f73b745b02cda30..9e38c7da8b0651c7c27e65fec2fb50260d231351 100644
--- a/src/Servers/HttpSys/test/FunctionalTests/DelegateTests.cs
+++ b/src/Servers/HttpSys/test/FunctionalTests/DelegateTests.cs
@@ -1,13 +1,11 @@
 // Licensed to the .NET Foundation under one or more agreements.
 // The .NET Foundation licenses this file to you under the MIT license.
 
-using System;
-using System.IO;
 using System.Net.Http;
-using System.Threading.Tasks;
+using System.Runtime.InteropServices;
 using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.HttpSys.Internal;
 using Microsoft.AspNetCore.Testing;
-using Xunit;
 
 namespace Microsoft.AspNetCore.Server.HttpSys.FunctionalTests
 {
@@ -198,6 +196,72 @@ namespace Microsoft.AspNetCore.Server.HttpSys.FunctionalTests
             destination?.Dispose();
         }
 
+        [ConditionalFact]
+        [DelegateSupportedCondition(true)]
+        public async Task DelegateAfterReceiverRestart()
+        {
+            var queueName = Guid.NewGuid().ToString();
+            using var receiver = Utilities.CreateHttpServer(out var receiverAddress, async httpContext =>
+            {
+                await httpContext.Response.WriteAsync(_expectedResponseString);
+            },
+            options =>
+            {
+                options.RequestQueueName = queueName;
+            });
+
+            DelegationRule destination = default;
+            using var delegator = Utilities.CreateHttpServer(out var delegatorAddress, httpContext =>
+            {
+                var delegateFeature = httpContext.Features.Get<IHttpSysRequestDelegationFeature>();
+                delegateFeature.DelegateRequest(destination);
+                return Task.CompletedTask;
+            });
+
+            var delegationProperty = delegator.Features.Get<IServerDelegationFeature>();
+            destination = delegationProperty.CreateDelegationRule(queueName, receiverAddress);
+
+            var responseString = await SendRequestAsync(delegatorAddress);
+            Assert.Equal(_expectedResponseString, responseString);
+
+            // Stop the receiver
+            receiver?.Dispose();
+
+            // Start the receiver again but this time we need to attach to the existing queue.
+            // Due to https://github.com/dotnet/aspnetcore/issues/40359, we have to manually
+            // register URL prefixes and attach the server's queue to them.
+            using var receiverRestarted = (MessagePump)Utilities.CreateHttpServer(out receiverAddress, async httpContext =>
+            {
+                await httpContext.Response.WriteAsync(_expectedResponseString);
+            },
+            options =>
+            {
+                options.RequestQueueName = queueName;
+                options.RequestQueueMode = RequestQueueMode.Attach;
+                options.UrlPrefixes.Clear();
+                options.UrlPrefixes.Add(receiverAddress);
+            });
+            AttachToUrlGroup(receiverRestarted.Listener.RequestQueue);
+            receiverRestarted.Listener.Options.UrlPrefixes.RegisterAllPrefixes(receiverRestarted.Listener.UrlGroup);
+
+            responseString = await SendRequestAsync(delegatorAddress);
+            Assert.Equal(_expectedResponseString, responseString);
+
+            destination?.Dispose();
+        }
+
+        private unsafe void AttachToUrlGroup(RequestQueue requestQueue)
+        {
+            var info = new HttpApiTypes.HTTP_BINDING_INFO();
+            info.Flags = HttpApiTypes.HTTP_FLAGS.HTTP_PROPERTY_FLAG_PRESENT;
+            info.RequestQueueHandle = requestQueue.Handle.DangerousGetHandle();
+
+            var infoptr = new IntPtr(&info);
+
+            requestQueue.UrlGroup.SetProperty(HttpApiTypes.HTTP_SERVER_PROPERTY.HttpServerBindingProperty,
+                infoptr, (uint)Marshal.SizeOf<HttpApiTypes.HTTP_BINDING_INFO>());
+        }
+
         private async Task<string> SendRequestAsync(string uri)
         {
             using var client = new HttpClient();
diff --git a/src/Servers/IIS/IIS/test/Common.FunctionalTests/StartupTests.cs b/src/Servers/IIS/IIS/test/Common.FunctionalTests/StartupTests.cs
index e0b0ab828131bc6129d9ffba2b94de3b54462822..e6acee1a6d43e27d664889ed26d86e55a3162832 100644
--- a/src/Servers/IIS/IIS/test/Common.FunctionalTests/StartupTests.cs
+++ b/src/Servers/IIS/IIS/test/Common.FunctionalTests/StartupTests.cs
@@ -468,7 +468,6 @@ namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests
 
         [ConditionalFact]
         [RequiresNewHandler]
-        [QuarantinedTest("https://github.com/dotnet/aspnetcore/issues/40036")]
         public async Task StartupTimeoutIsApplied()
         {
             // From what we can tell, this failure is due to ungraceful shutdown.
diff --git a/src/Servers/Kestrel/Core/src/Internal/Http/HttpParser.cs b/src/Servers/Kestrel/Core/src/Internal/Http/HttpParser.cs
index 064da12cb6346c87d4eba523da33829f1375e762..3c66964cafd472a866c44ff948c33e8be632d0a6 100644
--- a/src/Servers/Kestrel/Core/src/Internal/Http/HttpParser.cs
+++ b/src/Servers/Kestrel/Core/src/Internal/Http/HttpParser.cs
@@ -38,6 +38,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
 
         public bool ParseRequestLine(TRequestHandler handler, ref SequenceReader<byte> reader)
         {
+            // Skip any leading \r or \n on the request line. This is not technically allowed,
+            // but apparently there are enough clients relying on this that it's worth allowing.
+            // Peek first as a minor performance optimization; it's a quick inlined check.
+            if (reader.TryPeek(out byte b) && (b == ByteCR || b == ByteLF))
+            {
+                reader.AdvancePastAny(ByteCR, ByteLF);
+            }
+
             if (reader.TryReadTo(out ReadOnlySpan<byte> requestLine, ByteLF, advancePastDelimiter: true))
             {
                 ParseRequestLine(handler, requestLine);
diff --git a/src/Servers/Kestrel/Core/test/StartLineTests.cs b/src/Servers/Kestrel/Core/test/StartLineTests.cs
index 4c65611e95e4b16b71d0580940211050277d0052..59fdf6dd0cc1bca027df08e013ba396eb06988f9 100644
--- a/src/Servers/Kestrel/Core/test/StartLineTests.cs
+++ b/src/Servers/Kestrel/Core/test/StartLineTests.cs
@@ -6,6 +6,7 @@ using System.Buffers;
 using System.IO.Pipelines;
 using System.Text;
 using Microsoft.AspNetCore.Connections;
+using Microsoft.AspNetCore.Http;
 using Microsoft.AspNetCore.Http.Features;
 using Microsoft.AspNetCore.Server.Kestrel.Core;
 using Microsoft.AspNetCore.Server.Kestrel.Core.Internal;
@@ -516,6 +517,55 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
             DifferentFormsWorkTogether();
         }
 
+        public static IEnumerable<object[]> GetCrLfAndMethodCombinations()
+        {
+            // HTTP methods to test
+            var methods = new string[] {
+                HttpMethods.Connect,
+                HttpMethods.Delete,
+                HttpMethods.Get,
+                HttpMethods.Head,
+                HttpMethods.Options,
+                HttpMethods.Patch,
+                HttpMethods.Post,
+                HttpMethods.Put,
+                HttpMethods.Trace
+            };
+
+            // Prefixes to test
+            var crLfPrefixes = new string[] {
+                "\r",
+                "\n",
+                "\r\r\r\r\r",
+                "\r\n",
+                "\n\r"
+            };
+
+            foreach (var method in methods)
+            {
+                foreach (var prefix in crLfPrefixes)
+                {
+                    yield return new object[] { prefix, method };
+                }
+            }
+        }
+
+        [Theory]
+        [MemberData(nameof(GetCrLfAndMethodCombinations))]
+        public void LeadingCrLfAreAllowed(string startOfRequestLine, string httpMethod)
+        {
+            var rawTarget = "http://localhost/path1?q=123&w=xyzw";
+            Http1Connection.Reset();
+            // RawTarget, Path, QueryString are null after reset
+            Assert.Null(Http1Connection.RawTarget);
+            Assert.Null(Http1Connection.Path);
+            Assert.Null(Http1Connection.QueryString);
+
+            var ros = new ReadOnlySequence<byte>(Encoding.ASCII.GetBytes($"{startOfRequestLine}{httpMethod} {rawTarget} HTTP/1.1\r\n"));
+            var reader = new SequenceReader<byte>(ros);
+            Assert.True(Parser.ParseRequestLine(ParsingHandler, ref reader));
+        }
+
         public StartLineTests()
         {
             MemoryPool = PinnedBlockMemoryPoolFactory.Create();
diff --git a/src/Testing/src/AssemblyTestLog.cs b/src/Testing/src/AssemblyTestLog.cs
index 0e3d06f2eb3ead47233c139a87c758fe846744ae..4e03f7ca303527b584463a8a6cd36e7b73e6fad5 100644
--- a/src/Testing/src/AssemblyTestLog.cs
+++ b/src/Testing/src/AssemblyTestLog.cs
@@ -7,10 +7,8 @@ using System.Diagnostics;
 using System.Diagnostics.CodeAnalysis;
 using System.Globalization;
 using System.IO;
-using System.Linq;
 using System.Reflection;
 using System.Runtime.CompilerServices;
-using System.Text;
 using Microsoft.Extensions.DependencyInjection;
 using Microsoft.Extensions.Logging;
 using Serilog;
@@ -22,20 +20,21 @@ using ILogger = Microsoft.Extensions.Logging.ILogger;
 
 namespace Microsoft.AspNetCore.Testing
 {
-    public class AssemblyTestLog : IDisposable
+    public class AssemblyTestLog : IAcceptFailureReports, IDisposable
     {
         private const string MaxPathLengthEnvironmentVariableName = "ASPNETCORE_TEST_LOG_MAXPATH";
         private const string LogFileExtension = ".log";
         private static readonly int MaxPathLength = GetMaxPathLength();
 
-        private static readonly object _lock = new object();
-        private static readonly Dictionary<Assembly, AssemblyTestLog> _logs = new Dictionary<Assembly, AssemblyTestLog>();
+        private static readonly object _lock = new();
+        private static readonly Dictionary<Assembly, AssemblyTestLog> _logs = new();
 
         private readonly ILoggerFactory _globalLoggerFactory;
         private readonly ILogger _globalLogger;
         private readonly string _baseDirectory;
         private readonly Assembly _assembly;
         private readonly IServiceProvider _serviceProvider;
+        private bool _testFailureReported;
 
         private static int GetMaxPathLength()
         {
@@ -53,6 +52,9 @@ namespace Microsoft.AspNetCore.Testing
             _serviceProvider = serviceProvider;
         }
 
+        // internal for testing
+        internal bool OnCI { get; set; } = SkipOnCIAttribute.OnCI();
+
         [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")]
         public IDisposable StartTestLog(ITestOutputHelper output, string className, out ILoggerFactory loggerFactory, [CallerMemberName] string testName = null) =>
             StartTestLog(output, className, out loggerFactory, LogLevel.Debug, testName);
@@ -178,11 +180,8 @@ namespace Microsoft.AspNetCore.Testing
             return serviceCollection.BuildServiceProvider();
         }
 
-        // For back compat
-        public static AssemblyTestLog Create(string assemblyName, string baseDirectory)
-            => Create(Assembly.Load(new AssemblyName(assemblyName)), baseDirectory);
-
-        public static AssemblyTestLog Create(Assembly assembly, string baseDirectory)
+        // internal for testing. Expectation is AspNetTestAssembly runner calls ForAssembly() first for every Assembly.
+        internal static AssemblyTestLog Create(Assembly assembly, string baseDirectory)
         {
             var logStart = DateTimeOffset.UtcNow;
             SerilogLoggerProvider serilogLoggerProvider = null;
@@ -224,26 +223,46 @@ namespace Microsoft.AspNetCore.Testing
             {
                 if (!_logs.TryGetValue(assembly, out var log))
                 {
-                    var baseDirectory = TestFileOutputContext.GetOutputDirectory(assembly);
+                    var stackTrace = Environment.StackTrace;
+                    if (!stackTrace.Contains(
+                        "Microsoft.AspNetCore.Testing"
+#if NETCOREAPP
+                        , StringComparison.Ordinal
+#endif
+                        ))
+                    {
+                        throw new InvalidOperationException($"Unexpected initial {nameof(ForAssembly)} caller.");
+                    }
 
-                    log = Create(assembly, baseDirectory);
-                    _logs[assembly] = log;
+                    var baseDirectory = TestFileOutputContext.GetOutputDirectory(assembly);
 
-                    // Try to clear previous logs, continue if it fails.
+                    // Try to clear previous logs, continue if it fails. Do this before creating new global logger.
                     var assemblyBaseDirectory = TestFileOutputContext.GetAssemblyBaseDirectory(assembly);
-                    if (!string.IsNullOrEmpty(assemblyBaseDirectory) && !TestFileOutputContext.GetPreserveExistingLogsInOutput(assembly))
+                    if (!string.IsNullOrEmpty(assemblyBaseDirectory) &&
+                        !TestFileOutputContext.GetPreserveExistingLogsInOutput(assembly))
                     {
                         try
                         {
                             Directory.Delete(assemblyBaseDirectory, recursive: true);
                         }
-                        catch { }
+                        catch
+                        {
+                        }
                     }
+
+                    log = Create(assembly, baseDirectory);
+                    _logs[assembly] = log;
                 }
+
                 return log;
             }
         }
 
+        public void ReportTestFailure()
+        {
+            _testFailureReported = true;
+        }
+
         private static TestFrameworkFileLoggerAttribute GetFileLoggerAttribute(Assembly assembly)
             => assembly.GetCustomAttribute<TestFrameworkFileLoggerAttribute>()
                 ?? throw new InvalidOperationException($"No {nameof(TestFrameworkFileLoggerAttribute)} found on the assembly {assembly.GetName().Name}. "
@@ -269,13 +288,32 @@ namespace Microsoft.AspNetCore.Testing
                 .MinimumLevel.Verbose()
                 .WriteTo.File(fileName, outputTemplate: "[{TimestampOffset}] [{SourceContext}] [{Level}] {Message:l}{NewLine}{Exception}", flushToDiskInterval: TimeSpan.FromSeconds(1), shared: true)
                 .CreateLogger();
+
             return new SerilogLoggerProvider(serilogger, dispose: true);
         }
 
-        public void Dispose()
+        void IDisposable.Dispose()
         {
             (_serviceProvider as IDisposable)?.Dispose();
             _globalLoggerFactory.Dispose();
+
+            // Clean up if no tests failed and we're not running local tests. (Ignoring tests of this class, OnCI is
+            // true on both build and Helix agents.) In particular, remove the directory containing the global.log
+            // file. All test class log files for this assembly are in subdirectories of this location.
+            if (!_testFailureReported &&
+                OnCI &&
+                _baseDirectory is not null &&
+                Directory.Exists(_baseDirectory))
+            {
+                try
+                {
+                    Directory.Delete(_baseDirectory, recursive: true);
+                }
+                catch
+                {
+                    // Best effort. Ignore problems deleting locked logged files.
+                }
+            }
         }
 
         private class AssemblyLogTimestampOffsetEnricher : ILogEventEnricher
diff --git a/src/Testing/src/AssemblyTestLogFixtureAttribute.cs b/src/Testing/src/AssemblyTestLogFixtureAttribute.cs
new file mode 100644
index 0000000000000000000000000000000000000000..e4a4452cd458ba50c83db4e0a37b37180d8b1f18
--- /dev/null
+++ b/src/Testing/src/AssemblyTestLogFixtureAttribute.cs
@@ -0,0 +1,11 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+namespace Microsoft.AspNetCore.Testing;
+
+public class AssemblyTestLogFixtureAttribute : AssemblyFixtureAttribute
+{
+    public AssemblyTestLogFixtureAttribute() : base(typeof(AssemblyTestLog))
+    {
+    }
+}
diff --git a/src/Testing/src/build/Microsoft.AspNetCore.Testing.props b/src/Testing/src/build/Microsoft.AspNetCore.Testing.props
index 063e9094d172ed930ec3ca4a721ef3a50724be3e..47d06dfef7a7ce53d212a11993fe368e95343227 100644
--- a/src/Testing/src/build/Microsoft.AspNetCore.Testing.props
+++ b/src/Testing/src/build/Microsoft.AspNetCore.Testing.props
@@ -11,8 +11,8 @@
   </PropertyGroup>
 
   <Target Name="SetLoggingTestingAssemblyAttributes"
-    BeforeTargets="GetAssemblyAttributes"
-    Condition="'$(GenerateLoggingTestingAssemblyAttributes)' != 'false'">
+      BeforeTargets="GetAssemblyAttributes"
+      Condition="'$(GenerateLoggingTestingAssemblyAttributes)' != 'false'">
     <PropertyGroup>
       <PreserveExistingLogsInOutput Condition="'$(PreserveExistingLogsInOutput)' == '' AND '$(ContinuousIntegrationBuild)' == 'true'">true</PreserveExistingLogsInOutput>
       <PreserveExistingLogsInOutput Condition="'$(PreserveExistingLogsInOutput)' == ''">false</PreserveExistingLogsInOutput>
@@ -24,6 +24,7 @@
         <_Parameter2>Microsoft.AspNetCore.Testing</_Parameter2>
       </AssemblyAttribute>
 
+      <AssemblyAttribute Include="Microsoft.AspNetCore.Testing.AssemblyTestLogFixtureAttribute" />
       <AssemblyAttribute Include="Microsoft.AspNetCore.Testing.TestFrameworkFileLoggerAttribute">
         <_Parameter1>$(PreserveExistingLogsInOutput)</_Parameter1>
         <_Parameter2>$(TargetFramework)</_Parameter2>
diff --git a/src/Testing/src/xunit/AspNetTestAssemblyRunner.cs b/src/Testing/src/xunit/AspNetTestAssemblyRunner.cs
index a83446375ddd8cb8374b05abf4d35d5ab97fdc03..1d71bdf939bed7beff854f73b73dc2d9ce2feaaf 100644
--- a/src/Testing/src/xunit/AspNetTestAssemblyRunner.cs
+++ b/src/Testing/src/xunit/AspNetTestAssemblyRunner.cs
@@ -4,6 +4,7 @@
 using System;
 using System.Collections.Generic;
 using System.Linq;
+using System.Reflection;
 using System.Threading;
 using System.Threading.Tasks;
 using Xunit;
@@ -14,7 +15,7 @@ namespace Microsoft.AspNetCore.Testing
 {
     public class AspNetTestAssemblyRunner : XunitTestAssemblyRunner
     {
-        private readonly Dictionary<Type, object> _assemblyFixtureMappings = new Dictionary<Type, object>();
+        private readonly Dictionary<Type, object> _assemblyFixtureMappings = new();
 
         public AspNetTestAssemblyRunner(
             ITestAssembly testAssembly,
@@ -26,6 +27,9 @@ namespace Microsoft.AspNetCore.Testing
         {
         }
 
+        // internal for testing
+        internal IEnumerable<object> Fixtures => _assemblyFixtureMappings.Values;
+
         protected override async Task AfterTestAssemblyStartingAsync()
         {
             await base.AfterTestAssemblyStartingAsync();
@@ -33,8 +37,8 @@ namespace Microsoft.AspNetCore.Testing
             // Find all the AssemblyFixtureAttributes on the test assembly
             await Aggregator.RunAsync(async () =>
             {
-                var fixturesAttributes = ((IReflectionAssemblyInfo)TestAssembly.Assembly)
-                    .Assembly
+                var assembly = ((IReflectionAssemblyInfo)TestAssembly.Assembly).Assembly;
+                var fixturesAttributes = assembly
                     .GetCustomAttributes(typeof(AssemblyFixtureAttribute), false)
                     .Cast<AssemblyFixtureAttribute>()
                     .ToList();
@@ -42,15 +46,30 @@ namespace Microsoft.AspNetCore.Testing
                 // Instantiate all the fixtures
                 foreach (var fixtureAttribute in fixturesAttributes)
                 {
-                    var ctorWithDiagnostics = fixtureAttribute.FixtureType.GetConstructor(new[] { typeof(IMessageSink) });
                     object instance = null;
-                    if (ctorWithDiagnostics != null)
+                    var staticCreator = fixtureAttribute.FixtureType.GetMethod(
+                        name: "ForAssembly",
+                        bindingAttr: BindingFlags.Public | BindingFlags.Static,
+                        binder: null,
+                        types: new[] { typeof(Assembly) },
+                        modifiers: null);
+                    if (staticCreator is null)
                     {
-                        instance = Activator.CreateInstance(fixtureAttribute.FixtureType, DiagnosticMessageSink);
+                        var ctorWithDiagnostics = fixtureAttribute
+                            .FixtureType
+                            .GetConstructor(new[] { typeof(IMessageSink) });
+                        if (ctorWithDiagnostics is null)
+                        {
+                            instance = Activator.CreateInstance(fixtureAttribute.FixtureType);
+                        }
+                        else
+                        {
+                            instance = Activator.CreateInstance(fixtureAttribute.FixtureType, DiagnosticMessageSink);
+                        }
                     }
                     else
                     {
-                        instance = Activator.CreateInstance(fixtureAttribute.FixtureType);
+                        instance = staticCreator.Invoke(obj: null, parameters: new[] { assembly });
                     }
 
                     _assemblyFixtureMappings[fixtureAttribute.FixtureType] = instance;
@@ -66,12 +85,12 @@ namespace Microsoft.AspNetCore.Testing
         protected override async Task BeforeTestAssemblyFinishedAsync()
         {
             // Dispose fixtures
-            foreach (var disposable in _assemblyFixtureMappings.Values.OfType<IDisposable>())
+            foreach (var disposable in Fixtures.OfType<IDisposable>())
             {
                 Aggregator.Run(disposable.Dispose);
             }
 
-            foreach (var disposable in _assemblyFixtureMappings.Values.OfType<IAsyncLifetime>())
+            foreach (var disposable in Fixtures.OfType<IAsyncLifetime>())
             {
                 await Aggregator.RunAsync(disposable.DisposeAsync);
             }
@@ -79,12 +98,13 @@ namespace Microsoft.AspNetCore.Testing
             await base.BeforeTestAssemblyFinishedAsync();
         }
 
-        protected override Task<RunSummary> RunTestCollectionAsync(
+        protected override async Task<RunSummary> RunTestCollectionAsync(
             IMessageBus messageBus,
             ITestCollection testCollection,
             IEnumerable<IXunitTestCase> testCases,
             CancellationTokenSource cancellationTokenSource)
-            => new AspNetTestCollectionRunner(
+        {
+            var runSummary = await new AspNetTestCollectionRunner(
                 _assemblyFixtureMappings,
                 testCollection,
                 testCases,
@@ -92,6 +112,17 @@ namespace Microsoft.AspNetCore.Testing
                 messageBus,
                 TestCaseOrderer,
                 new ExceptionAggregator(Aggregator),
-                cancellationTokenSource).RunAsync();
+                cancellationTokenSource)
+                .RunAsync();
+            if (runSummary.Failed != 0)
+            {
+                foreach (var fixture in Fixtures.OfType<IAcceptFailureReports>())
+                {
+                    fixture.ReportTestFailure();
+                }
+            }
+
+            return runSummary;
+        }
     }
 }
diff --git a/src/Testing/src/xunit/IAcceptFailureReports.cs b/src/Testing/src/xunit/IAcceptFailureReports.cs
new file mode 100644
index 0000000000000000000000000000000000000000..30ca366b3f1bf10783b4a81d382bc4d23792a79d
--- /dev/null
+++ b/src/Testing/src/xunit/IAcceptFailureReports.cs
@@ -0,0 +1,9 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+namespace Microsoft.AspNetCore.Testing;
+
+internal interface IAcceptFailureReports
+{
+    void ReportTestFailure();
+}
diff --git a/src/Testing/test/AspNetTestAssemblyRunnerTest.cs b/src/Testing/test/AspNetTestAssemblyRunnerTest.cs
new file mode 100644
index 0000000000000000000000000000000000000000..dbce4c69bb82af78017083f9c83e04b62b10b816
--- /dev/null
+++ b/src/Testing/test/AspNetTestAssemblyRunnerTest.cs
@@ -0,0 +1,219 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+using System.Reflection;
+using System.Threading.Tasks;
+using Xunit;
+using Xunit.Abstractions;
+
+namespace Microsoft.AspNetCore.Testing;
+
+public class AspNetTestAssemblyRunnerTest
+{
+    private const int NotCalled = -1;
+
+    [Fact]
+    public async Task ForAssemblyHasHigherPriorityThanConstructors()
+    {
+        var runner = TestableAspNetTestAssemblyRunner.Create(typeof(TestAssemblyFixtureWithAll));
+
+        await runner.AfterTestAssemblyStartingAsync_Public();
+
+        Assert.NotNull(runner.Fixtures);
+        var fixtureObject = Assert.Single(runner.Fixtures);
+        var fixture = Assert.IsType<TestAssemblyFixtureWithAll>(fixtureObject);
+        Assert.False(fixture.ConstructorWithMessageSinkCalled);
+        Assert.True(fixture.ForAssemblyCalled);
+        Assert.False(fixture.ParameterlessConstructorCalled);
+    }
+
+    [Fact]
+    public async Task ConstructorWithMessageSinkHasHigherPriorityThanParameterlessConstructor()
+    {
+        var runner = TestableAspNetTestAssemblyRunner.Create(typeof(TestAssemblyFixtureWithMessageSink));
+
+        await runner.AfterTestAssemblyStartingAsync_Public();
+
+        Assert.NotNull(runner.Fixtures);
+        var fixtureObject = Assert.Single(runner.Fixtures);
+        var fixture = Assert.IsType<TestAssemblyFixtureWithMessageSink>(fixtureObject);
+        Assert.True(fixture.ConstructorWithMessageSinkCalled);
+        Assert.False(fixture.ParameterlessConstructorCalled);
+    }
+
+    [Fact]
+    public async Task CalledInExpectedOrder_SuccessWithDispose()
+    {
+        var runner = TestableAspNetTestAssemblyRunner.Create(typeof(TextAssemblyFixtureWithDispose));
+
+        var runSummary = await runner.RunAsync();
+
+        Assert.NotNull(runSummary);
+        Assert.Equal(0, runSummary.Failed);
+        Assert.Equal(0, runSummary.Skipped);
+        Assert.Equal(1, runSummary.Total);
+
+        Assert.NotNull(runner.Fixtures);
+        var fixtureObject = Assert.Single(runner.Fixtures);
+        var fixture = Assert.IsType<TextAssemblyFixtureWithDispose>(fixtureObject);
+        Assert.Equal(NotCalled, fixture.ReportTestFailureCalledAt);
+        Assert.Equal(0, fixture.DisposeCalledAt);
+    }
+
+    [Fact]
+    public async Task CalledInExpectedOrder_FailedWithDispose()
+    {
+        var runner = TestableAspNetTestAssemblyRunner.Create(
+            typeof(TextAssemblyFixtureWithDispose),
+            failTestCase: true);
+
+        var runSummary = await runner.RunAsync();
+
+        Assert.NotNull(runSummary);
+        Assert.Equal(1, runSummary.Failed);
+        Assert.Equal(0, runSummary.Skipped);
+        Assert.Equal(1, runSummary.Total);
+
+        Assert.NotNull(runner.Fixtures);
+        var fixtureObject = Assert.Single(runner.Fixtures);
+        var fixture = Assert.IsType<TextAssemblyFixtureWithDispose>(fixtureObject);
+        Assert.Equal(0, fixture.ReportTestFailureCalledAt);
+        Assert.Equal(1, fixture.DisposeCalledAt);
+    }
+
+    [Fact]
+    public async Task CalledInExpectedOrder_SuccessWithAsyncDispose()
+    {
+        var runner = TestableAspNetTestAssemblyRunner.Create(typeof(TestAssemblyFixtureWithAsyncDispose));
+
+        var runSummary = await runner.RunAsync();
+
+        Assert.NotNull(runSummary);
+        Assert.Equal(0, runSummary.Failed);
+        Assert.Equal(0, runSummary.Skipped);
+        Assert.Equal(1, runSummary.Total);
+
+        Assert.NotNull(runner.Fixtures);
+        var fixtureObject = Assert.Single(runner.Fixtures);
+        var fixture = Assert.IsType<TestAssemblyFixtureWithAsyncDispose>(fixtureObject);
+        Assert.Equal(0, fixture.InitializeAsyncCalledAt);
+        Assert.Equal(NotCalled, fixture.ReportTestFailureCalledAt);
+        Assert.Equal(1, fixture.AsyncDisposeCalledAt);
+    }
+
+    [Fact]
+    public async Task CalledInExpectedOrder_FailedWithAsyncDispose()
+    {
+        var runner = TestableAspNetTestAssemblyRunner.Create(
+            typeof(TestAssemblyFixtureWithAsyncDispose),
+            failTestCase: true);
+
+        var runSummary = await runner.RunAsync();
+
+        Assert.NotNull(runSummary);
+        Assert.Equal(1, runSummary.Failed);
+        Assert.Equal(0, runSummary.Skipped);
+        Assert.Equal(1, runSummary.Total);
+
+        Assert.NotNull(runner.Fixtures);
+        var fixtureObject = Assert.Single(runner.Fixtures);
+        var fixture = Assert.IsType<TestAssemblyFixtureWithAsyncDispose>(fixtureObject);
+        Assert.Equal(0, fixture.InitializeAsyncCalledAt);
+        Assert.Equal(1, fixture.ReportTestFailureCalledAt);
+        Assert.Equal(2, fixture.AsyncDisposeCalledAt);
+    }
+
+    private class TestAssemblyFixtureWithAll
+    {
+        private TestAssemblyFixtureWithAll(bool forAssemblyCalled)
+        {
+            ForAssemblyCalled = forAssemblyCalled;
+        }
+
+        public TestAssemblyFixtureWithAll()
+        {
+            ParameterlessConstructorCalled = true;
+        }
+
+        public TestAssemblyFixtureWithAll(IMessageSink messageSink)
+        {
+            ConstructorWithMessageSinkCalled = true;
+        }
+
+        public static TestAssemblyFixtureWithAll ForAssembly(Assembly assembly)
+        {
+            return new TestAssemblyFixtureWithAll(forAssemblyCalled: true);
+        }
+
+        public bool ParameterlessConstructorCalled { get; }
+
+        public bool ConstructorWithMessageSinkCalled { get; }
+
+        public bool ForAssemblyCalled { get; }
+    }
+
+    private class TestAssemblyFixtureWithMessageSink
+    {
+        public TestAssemblyFixtureWithMessageSink()
+        {
+            ParameterlessConstructorCalled = true;
+        }
+
+        public TestAssemblyFixtureWithMessageSink(IMessageSink messageSink)
+        {
+            ConstructorWithMessageSinkCalled = true;
+        }
+
+        public bool ParameterlessConstructorCalled { get; }
+
+        public bool ConstructorWithMessageSinkCalled { get; }
+    }
+
+    private class TextAssemblyFixtureWithDispose : IAcceptFailureReports, IDisposable
+    {
+        private int _position;
+
+        public int ReportTestFailureCalledAt { get; private set; } = NotCalled;
+
+        public int DisposeCalledAt { get; private set; } = NotCalled;
+
+        void IAcceptFailureReports.ReportTestFailure()
+        {
+            ReportTestFailureCalledAt = _position++;
+        }
+
+        void IDisposable.Dispose()
+        {
+            DisposeCalledAt = _position++;
+        }
+    }
+
+    private class TestAssemblyFixtureWithAsyncDispose : IAcceptFailureReports, IAsyncLifetime
+    {
+        private int _position;
+
+        public int InitializeAsyncCalledAt { get; private set; } = NotCalled;
+
+        public int ReportTestFailureCalledAt { get; private set; } = NotCalled;
+
+        public int AsyncDisposeCalledAt { get; private set; } = NotCalled;
+
+        Task IAsyncLifetime.InitializeAsync()
+        {
+            InitializeAsyncCalledAt = _position++;
+            return Task.CompletedTask;
+        }
+
+        void IAcceptFailureReports.ReportTestFailure()
+        {
+            ReportTestFailureCalledAt = _position++;
+        }
+
+        Task IAsyncLifetime.DisposeAsync()
+        {
+            AsyncDisposeCalledAt = _position++;
+            return Task.CompletedTask;
+        }
+    }
+}
diff --git a/src/Testing/test/AssemblyTestLogTests.cs b/src/Testing/test/AssemblyTestLogTests.cs
index 57c3b87b16819360220011b7a1c9a345e56bda3c..bd3bbb1b8e2905f168b92bedee8b1d9bfb943029 100644
--- a/src/Testing/test/AssemblyTestLogTests.cs
+++ b/src/Testing/test/AssemblyTestLogTests.cs
@@ -4,21 +4,17 @@
 using System;
 using System.IO;
 using System.Linq;
-using System.Reflection;
 using System.Runtime.CompilerServices;
 using System.Text.RegularExpressions;
 using System.Threading.Tasks;
-using Microsoft.AspNetCore.Testing;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Logging.Testing.Tests;
 using Xunit;
 
-namespace Microsoft.Extensions.Logging.Testing.Tests
+namespace Microsoft.AspNetCore.Testing
 {
     public class AssemblyTestLogTests : LoggedTest
     {
-        private static readonly Assembly ThisAssembly = typeof(AssemblyTestLogTests).GetTypeInfo().Assembly;
-        private static readonly string ThisAssemblyName = ThisAssembly.GetName().Name;
-        private static readonly string TFM = ThisAssembly.GetCustomAttributes().OfType<TestOutputDirectoryAttribute>().FirstOrDefault().TargetFramework;
-
         [Fact]
         public void FunctionalLogs_LogsPreservedFromNonQuarantinedTest()
         {
@@ -35,15 +31,52 @@ namespace Microsoft.Extensions.Logging.Testing.Tests
         public void ForAssembly_ReturnsSameInstanceForSameAssembly()
         {
             Assert.Same(
-                AssemblyTestLog.ForAssembly(ThisAssembly),
-                AssemblyTestLog.ForAssembly(ThisAssembly));
+                AssemblyTestLog.ForAssembly(TestableAssembly.ThisAssembly),
+                AssemblyTestLog.ForAssembly(TestableAssembly.ThisAssembly));
         }
 
+        [Fact]
+        public Task ForAssemblyWritesToAssemblyBaseDirectory() =>
+            RunTestLogFunctionalTest((tempDir) =>
+            {
+                var logger = LoggerFactory.CreateLogger("Test");
+
+                var assembly = TestableAssembly.Create(typeof(AssemblyTestLog), logDirectory: tempDir);
+                var assemblyName = assembly.GetName().Name;
+                var testName = $"{TestableAssembly.TestClassName}.{TestableAssembly.TestMethodName}";
+
+                var tfmPath = Path.Combine(tempDir, assemblyName, TestableAssembly.TFM);
+                var globalLogPath = Path.Combine(tfmPath, "global.log");
+                var testLog = Path.Combine(tfmPath, TestableAssembly.TestClassName, $"{testName}.log");
+
+                using var testAssemblyLog = AssemblyTestLog.ForAssembly(assembly);
+                testAssemblyLog.OnCI = true;
+                logger.LogInformation("Created test log in {baseDirectory}", tempDir);
+
+                using (testAssemblyLog.StartTestLog(
+                    output: null,
+                    className: $"{assemblyName}.{TestableAssembly.TestClassName}",
+                    loggerFactory: out var testLoggerFactory,
+                    minLogLevel: LogLevel.Trace,
+                    testName: testName))
+                {
+                    var testLogger = testLoggerFactory.CreateLogger("TestLogger");
+                    testLogger.LogInformation("Information!");
+                    testLogger.LogTrace("Trace!");
+                }
+
+                Assert.True(File.Exists(globalLogPath), $"Expected global log file {globalLogPath} to exist.");
+                Assert.True(File.Exists(testLog), $"Expected test log file {testLog} to exist.");
+
+                logger.LogInformation("Finished test log in {baseDirectory}", tempDir);
+            });
+
         [Fact]
         public void TestLogWritesToITestOutputHelper()
         {
             var output = new TestTestOutputHelper();
-            var assemblyLog = AssemblyTestLog.Create(ThisAssemblyName, baseDirectory: null);
+
+            using var assemblyLog = AssemblyTestLog.Create(TestableAssembly.ThisAssembly, baseDirectory: null);
 
             using (assemblyLog.StartTestLog(output, "NonExistant.Test.Class", out var loggerFactory))
             {
@@ -69,11 +102,19 @@ namespace Microsoft.Extensions.Logging.Testing.Tests
             {
                 var illegalTestName = "T:e/s//t";
                 var escapedTestName = "T_e_s_t";
-                using (var testAssemblyLog = AssemblyTestLog.Create(ThisAssemblyName, baseDirectory: tempDir))
-                using (testAssemblyLog.StartTestLog(output: null, className: "FakeTestAssembly.FakeTestClass", loggerFactory: out var testLoggerFactory, minLogLevel: LogLevel.Trace, resolvedTestName: out var resolvedTestname, out var _, testName: illegalTestName))
-                {
-                    Assert.Equal(escapedTestName, resolvedTestname);
-                }
+
+                using var testAssemblyLog = AssemblyTestLog.Create(
+                    TestableAssembly.ThisAssembly,
+                    baseDirectory: tempDir);
+                using var disposable = testAssemblyLog.StartTestLog(
+                    output: null,
+                    className: "FakeTestAssembly.FakeTestClass",
+                    loggerFactory: out var testLoggerFactory,
+                    minLogLevel: LogLevel.Trace,
+                    resolvedTestName: out var resolvedTestname,
+                    out var _,
+                    testName: illegalTestName);
+                Assert.Equal(escapedTestName, resolvedTestname);
             });
 
         [Fact]
@@ -84,11 +125,19 @@ namespace Microsoft.Extensions.Logging.Testing.Tests
                 // but it's also testing the test logging facility. So this is pretty meta ;)
                 var logger = LoggerFactory.CreateLogger("Test");
 
-                using (var testAssemblyLog = AssemblyTestLog.Create(ThisAssemblyName, tempDir))
+                using (var testAssemblyLog = AssemblyTestLog.Create(
+                    TestableAssembly.ThisAssembly,
+                    baseDirectory: tempDir))
                 {
+                    testAssemblyLog.OnCI = false;
                     logger.LogInformation("Created test log in {baseDirectory}", tempDir);
 
-                    using (testAssemblyLog.StartTestLog(output: null, className: $"{ThisAssemblyName}.FakeTestClass", loggerFactory: out var testLoggerFactory, minLogLevel: LogLevel.Trace, testName: "FakeTestName"))
+                    using (testAssemblyLog.StartTestLog(
+                        output: null,
+                        className: $"{TestableAssembly.ThisAssemblyName}.FakeTestClass",
+                        loggerFactory: out var testLoggerFactory,
+                        minLogLevel: LogLevel.Trace,
+                        testName: "FakeTestName"))
                     {
                         var testLogger = testLoggerFactory.CreateLogger("TestLogger");
                         testLogger.LogInformation("Information!");
@@ -98,8 +147,17 @@ namespace Microsoft.Extensions.Logging.Testing.Tests
 
                 logger.LogInformation("Finished test log in {baseDirectory}", tempDir);
 
-                var globalLogPath = Path.Combine(tempDir, ThisAssemblyName, TFM, "global.log");
-                var testLog = Path.Combine(tempDir, ThisAssemblyName, TFM, "FakeTestClass", "FakeTestName.log");
+                var globalLogPath = Path.Combine(
+                    tempDir,
+                    TestableAssembly.ThisAssemblyName,
+                    TestableAssembly.TFM,
+                    "global.log");
+                var testLog = Path.Combine(
+                    tempDir,
+                    TestableAssembly.ThisAssemblyName,
+                    TestableAssembly.TFM,
+                    "FakeTestClass",
+                    "FakeTestName.log");
 
                 Assert.True(File.Exists(globalLogPath), $"Expected global log file {globalLogPath} to exist");
                 Assert.True(File.Exists(testLog), $"Expected test log file {testLog} to exist");
@@ -120,31 +178,139 @@ namespace Microsoft.Extensions.Logging.Testing.Tests
 ", testLogContent, ignoreLineEndingDifferences: true);
             });
 
+        [Fact]
+        public Task TestLogCleansLogFiles_AfterSuccessfulRun() =>
+            RunTestLogFunctionalTest((tempDir) =>
+            {
+                var logger = LoggerFactory.CreateLogger("Test");
+                var globalLogPath = Path.Combine(
+                    tempDir,
+                    TestableAssembly.ThisAssemblyName,
+                    TestableAssembly.TFM,
+                    "global.log");
+                var testLog = Path.Combine(
+                    tempDir,
+                    TestableAssembly.ThisAssemblyName,
+                    TestableAssembly.TFM,
+                    "FakeTestClass",
+                    "FakeTestName.log");
+
+                using (var testAssemblyLog = AssemblyTestLog.Create(
+                    TestableAssembly.ThisAssembly,
+                    baseDirectory: tempDir))
+                {
+                    testAssemblyLog.OnCI = true;
+                    logger.LogInformation("Created test log in {baseDirectory}", tempDir);
+
+                    using (testAssemblyLog.StartTestLog(
+                        output: null,
+                        className: $"{TestableAssembly.ThisAssemblyName}.FakeTestClass",
+                        loggerFactory: out var testLoggerFactory,
+                        minLogLevel: LogLevel.Trace,
+                        testName: "FakeTestName"))
+                    {
+                        var testLogger = testLoggerFactory.CreateLogger("TestLogger");
+                        testLogger.LogInformation("Information!");
+                        testLogger.LogTrace("Trace!");
+                    }
+
+                    Assert.True(File.Exists(globalLogPath), $"Expected global log file {globalLogPath} to exist.");
+                    Assert.True(File.Exists(testLog), $"Expected test log file {testLog} to exist.");
+                }
+
+                logger.LogInformation("Finished test log in {baseDirectory}", tempDir);
+
+                Assert.True(!File.Exists(globalLogPath), $"Expected no global log file {globalLogPath} to exist.");
+                Assert.True(!File.Exists(testLog), $"Expected no test log file {testLog} to exist.");
+            });
+
+        [Fact]
+        public Task TestLogDoesNotCleanLogFiles_AfterFailedRun() =>
+            RunTestLogFunctionalTest((tempDir) =>
+            {
+                var logger = LoggerFactory.CreateLogger("Test");
+                var globalLogPath = Path.Combine(
+                    tempDir,
+                    TestableAssembly.ThisAssemblyName,
+                    TestableAssembly.TFM,
+                    "global.log");
+                var testLog = Path.Combine(
+                    tempDir,
+                    TestableAssembly.ThisAssemblyName,
+                    TestableAssembly.TFM,
+                    "FakeTestClass",
+                    "FakeTestName.log");
+
+                using (var testAssemblyLog = AssemblyTestLog.Create(
+                    TestableAssembly.ThisAssembly,
+                    baseDirectory: tempDir))
+                {
+                    testAssemblyLog.OnCI = true;
+                    logger.LogInformation("Created test log in {baseDirectory}", tempDir);
+
+                    using (testAssemblyLog.StartTestLog(
+                        output: null,
+                        className: $"{TestableAssembly.ThisAssemblyName}.FakeTestClass",
+                        loggerFactory: out var testLoggerFactory,
+                        minLogLevel: LogLevel.Trace,
+                        testName: "FakeTestName"))
+                    {
+                        var testLogger = testLoggerFactory.CreateLogger("TestLogger");
+                        testLogger.LogInformation("Information!");
+                        testLogger.LogTrace("Trace!");
+                    }
+
+                    testAssemblyLog.ReportTestFailure();
+                }
+
+                logger.LogInformation("Finished test log in {baseDirectory}", tempDir);
+
+                Assert.True(File.Exists(globalLogPath), $"Expected global log file {globalLogPath} to exist.");
+                Assert.True(File.Exists(testLog), $"Expected test log file {testLog} to exist.");
+            });
+
         [Fact]
         public Task TestLogTruncatesTestNameToAvoidLongPaths() =>
             RunTestLogFunctionalTest((tempDir) =>
             {
-                var longTestName = new string('0', 50) + new string('1', 50) + new string('2', 50) + new string('3', 50) + new string('4', 50);
+                var longTestName = new string('0', 50) + new string('1', 50) + new string('2', 50) +
+                    new string('3', 50) + new string('4', 50);
                 var logger = LoggerFactory.CreateLogger("Test");
-                using (var testAssemblyLog = AssemblyTestLog.Create(ThisAssemblyName, tempDir))
+                using (var testAssemblyLog = AssemblyTestLog.Create(
+                    TestableAssembly.ThisAssembly,
+                    baseDirectory: tempDir))
                 {
+                    testAssemblyLog.OnCI = false;
                     logger.LogInformation("Created test log in {baseDirectory}", tempDir);
 
-                    using (testAssemblyLog.StartTestLog(output: null, className: $"{ThisAssemblyName}.FakeTestClass", loggerFactory: out var testLoggerFactory, minLogLevel: LogLevel.Trace, testName: longTestName))
+                    using (testAssemblyLog.StartTestLog(
+                        output: null,
+                        className: $"{TestableAssembly.ThisAssemblyName}.FakeTestClass",
+                        loggerFactory: out var testLoggerFactory,
+                        minLogLevel: LogLevel.Trace,
+                        testName: longTestName))
                     {
                         testLoggerFactory.CreateLogger("TestLogger").LogInformation("Information!");
                     }
                 }
+
                 logger.LogInformation("Finished test log in {baseDirectory}", tempDir);
 
-                var testLogFiles = new DirectoryInfo(Path.Combine(tempDir, ThisAssemblyName, TFM, "FakeTestClass")).EnumerateFiles();
+                var testLogFiles = new DirectoryInfo(
+                    Path.Combine(tempDir, TestableAssembly.ThisAssemblyName, TestableAssembly.TFM, "FakeTestClass"))
+                    .EnumerateFiles();
                 var testLog = Assert.Single(testLogFiles);
                 var testFileName = Path.GetFileNameWithoutExtension(testLog.Name);
 
                 // The first half of the file comes from the beginning of the test name passed to the logger
-                Assert.Equal(longTestName.Substring(0, testFileName.Length / 2), testFileName.Substring(0, testFileName.Length / 2));
+                Assert.Equal(
+                    longTestName.Substring(0, testFileName.Length / 2),
+                    testFileName.Substring(0, testFileName.Length / 2));
+
                 // The last half of the file comes from the ending of the test name passed to the logger
-                Assert.Equal(longTestName.Substring(longTestName.Length - testFileName.Length / 2, testFileName.Length / 2), testFileName.Substring(testFileName.Length - testFileName.Length / 2, testFileName.Length / 2));
+                Assert.Equal(
+                    longTestName.Substring(longTestName.Length - testFileName.Length / 2, testFileName.Length / 2),
+                    testFileName.Substring(testFileName.Length - testFileName.Length / 2, testFileName.Length / 2));
             });
 
         [Fact]
@@ -152,27 +318,46 @@ namespace Microsoft.Extensions.Logging.Testing.Tests
             RunTestLogFunctionalTest((tempDir) =>
             {
                 var logger = LoggerFactory.CreateLogger("Test");
-                using (var testAssemblyLog = AssemblyTestLog.Create(ThisAssemblyName, tempDir))
+                using (var testAssemblyLog = AssemblyTestLog.Create(
+                    TestableAssembly.ThisAssembly,
+                    baseDirectory: tempDir))
                 {
+                    testAssemblyLog.OnCI = false;
                     logger.LogInformation("Created test log in {baseDirectory}", tempDir);
 
                     for (var i = 0; i < 10; i++)
                     {
-                        using (testAssemblyLog.StartTestLog(output: null, className: $"{ThisAssemblyName}.FakeTestClass", loggerFactory: out var testLoggerFactory, minLogLevel: LogLevel.Trace, testName: "FakeTestName"))
+                        using (testAssemblyLog.StartTestLog(
+                            output: null,
+                            className: $"{TestableAssembly.ThisAssemblyName}.FakeTestClass",
+                            loggerFactory: out var testLoggerFactory,
+                            minLogLevel: LogLevel.Trace,
+                            testName: "FakeTestName"))
                         {
                             testLoggerFactory.CreateLogger("TestLogger").LogInformation("Information!");
                         }
                     }
                 }
+
                 logger.LogInformation("Finished test log in {baseDirectory}", tempDir);
 
                 // The first log file exists
-                Assert.True(File.Exists(Path.Combine(tempDir, ThisAssemblyName, TFM, "FakeTestClass", "FakeTestName.log")));
+                Assert.True(File.Exists(Path.Combine(
+                    tempDir,
+                    TestableAssembly.ThisAssemblyName,
+                    TestableAssembly.TFM,
+                    "FakeTestClass",
+                    "FakeTestName.log")));
 
                 // Subsequent files exist
                 for (var i = 0; i < 9; i++)
                 {
-                    Assert.True(File.Exists(Path.Combine(tempDir, ThisAssemblyName, TFM, "FakeTestClass", $"FakeTestName.{i}.log")));
+                    Assert.True(File.Exists(Path.Combine(
+                        tempDir,
+                        TestableAssembly.ThisAssemblyName,
+                        TestableAssembly.TFM,
+                        "FakeTestClass",
+                        $"FakeTestName.{i}.log")));
                 }
             });
 
diff --git a/src/Testing/test/TestableAspNetTestAssemblyRunner.cs b/src/Testing/test/TestableAspNetTestAssemblyRunner.cs
new file mode 100644
index 0000000000000000000000000000000000000000..17f9373b34c2886b94d5e1bd740ea95bbebb92d5
--- /dev/null
+++ b/src/Testing/test/TestableAspNetTestAssemblyRunner.cs
@@ -0,0 +1,105 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Reflection;
+using System.Threading.Tasks;
+using Moq;
+using Xunit.Abstractions;
+using Xunit.Sdk;
+
+namespace Microsoft.AspNetCore.Testing;
+
+public class TestableAspNetTestAssemblyRunner : AspNetTestAssemblyRunner
+{
+    private TestableAspNetTestAssemblyRunner(
+        ITestAssembly testAssembly,
+        IEnumerable<IXunitTestCase> testCases,
+        IMessageSink diagnosticMessageSink,
+        IMessageSink executionMessageSink,
+        ITestFrameworkExecutionOptions executionOptions) : base(
+            testAssembly,
+            testCases,
+            diagnosticMessageSink,
+            executionMessageSink,
+            executionOptions)
+    {
+    }
+
+    public static TestableAspNetTestAssemblyRunner Create(Type fixtureType, bool failTestCase = false)
+    {
+        var assembly = TestableAssembly.Create(fixtureType, failTestCase: failTestCase);
+        var testAssembly = GetTestAssembly(assembly);
+        var testCase = GetTestCase(assembly, testAssembly);
+
+        return new TestableAspNetTestAssemblyRunner(
+            testAssembly,
+            new[] { testCase },
+            diagnosticMessageSink: new NullMessageSink(),
+            executionMessageSink: new NullMessageSink(),
+            executionOptions: Mock.Of<ITestFrameworkExecutionOptions>());
+
+        // Do not call Xunit.Sdk.Reflector.Wrap(assembly) because it uses GetTypes() and that method
+        // throws NotSupportedException for a dynamic assembly.
+        IAssemblyInfo GetAssemblyInfo(Assembly assembly)
+        {
+            var testAssemblyName = assembly.GetName().Name;
+            var assemblyInfo = new Mock<IReflectionAssemblyInfo>();
+            assemblyInfo.SetupGet(r => r.Assembly).Returns(assembly);
+            assemblyInfo.SetupGet(r => r.Name).Returns(testAssemblyName);
+            assemblyInfo
+                .SetupGet(r => r.AssemblyPath)
+                .Returns(Path.Combine(Directory.GetCurrentDirectory(), $"{testAssemblyName}.dll"));
+
+            foreach (var attribute in CustomAttributeData.GetCustomAttributes(assembly))
+            {
+                var attributeInfo = Reflector.Wrap(attribute);
+                var attributeName = attribute.AttributeType.AssemblyQualifiedName;
+                assemblyInfo
+                    .Setup(r => r.GetCustomAttributes(attributeName))
+                    .Returns(new[] { attributeInfo });
+            }
+
+            var typeInfo = Reflector.Wrap(assembly.GetType(TestableAssembly.TestClassName));
+            assemblyInfo.Setup(r => r.GetType(TestableAssembly.TestClassName)).Returns(typeInfo);
+            assemblyInfo.Setup(r => r.GetTypes(It.IsAny<bool>())).Returns(new[] { typeInfo });
+
+            return assemblyInfo.Object;
+        }
+
+        ITestAssembly GetTestAssembly(Assembly assembly)
+        {
+            var assemblyInfo = GetAssemblyInfo(assembly);
+
+            return new TestAssembly(assemblyInfo);
+        }
+
+        IXunitTestCase GetTestCase(Assembly assembly, ITestAssembly testAssembly)
+        {
+            var testAssemblyName = assembly.GetName().Name;
+            var testCollection = new TestCollection(
+                testAssembly,
+                collectionDefinition: null,
+                displayName: $"Mock collection for '{testAssemblyName}'.");
+
+            var type = assembly.GetType(TestableAssembly.TestClassName);
+            var testClass = new TestClass(testCollection, Reflector.Wrap(type));
+            var method = type.GetMethod(TestableAssembly.TestMethodName);
+            var methodInfo = Reflector.Wrap(method);
+            var testMethod = new TestMethod(testClass, methodInfo);
+
+            return new XunitTestCase(
+                diagnosticMessageSink: new NullMessageSink(),
+                defaultMethodDisplay: TestMethodDisplay.ClassAndMethod,
+                defaultMethodDisplayOptions: TestMethodDisplayOptions.None,
+                testMethod: testMethod);
+        }
+    }
+
+    public Task AfterTestAssemblyStartingAsync_Public()
+    {
+        return base.AfterTestAssemblyStartingAsync();
+    }
+}
diff --git a/src/Testing/test/TestableAssembly.cs b/src/Testing/test/TestableAssembly.cs
new file mode 100644
index 0000000000000000000000000000000000000000..fd4b557cca12506fe4adae6e7436adc60c4c9f38
--- /dev/null
+++ b/src/Testing/test/TestableAssembly.cs
@@ -0,0 +1,95 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+using System.Linq;
+using System.Reflection;
+using System.Reflection.Emit;
+using Xunit;
+
+namespace Microsoft.AspNetCore.Testing;
+
+/* Creates a very simple dynamic assembly containing
+ *
+ * [Assembly: TestFramework(
+ *     typeName: "Microsoft.AspNetCore.Testing.AspNetTestFramework",
+ *     assemblyName: "Microsoft.AspNetCore.Testing")]
+ * [assembly: AssemblyFixture(typeof({fixtureType}))]
+ * [assembly: TestOutputDirectory(
+ *     preserveExistingLogsInOutput: "false",
+ *     targetFramework: TFM,
+ *     baseDirectory: logDirectory)] // logdirectory is passed into Create(...).
+ *
+ * public class MyTestClass
+ * {
+ *     public MyTestClass() { }
+ *
+ *     [Fact]
+ *     public MyTestMethod()
+ *     {
+ *         if (failTestCase) // Not exactly; condition checked during generation.
+ *         {
+ *             Assert.True(condition: false);
+ *         }
+ *     }
+ * }
+ */
+public static class TestableAssembly
+{
+    public static readonly Assembly ThisAssembly = typeof(TestableAssembly).GetTypeInfo().Assembly;
+    public static readonly string ThisAssemblyName = ThisAssembly.GetName().Name;
+
+    private static readonly TestOutputDirectoryAttribute ThisOutputDirectoryAttribute =
+        ThisAssembly.GetCustomAttributes().OfType<TestOutputDirectoryAttribute>().FirstOrDefault();
+    public static readonly string BaseDirectory = ThisOutputDirectoryAttribute.BaseDirectory;
+    public static readonly string TFM = ThisOutputDirectoryAttribute.TargetFramework;
+
+    public const string TestClassName = "MyTestClass";
+    public const string TestMethodName = "MyTestMethod";
+
+    public static Assembly Create(Type fixtureType, string logDirectory = null, bool failTestCase = false)
+    {
+        var frameworkConstructor = typeof(TestFrameworkAttribute)
+            .GetConstructor(new[] { typeof(string), typeof(string) });
+        var frameworkBuilder = new CustomAttributeBuilder(
+            frameworkConstructor,
+            new[] { "Microsoft.AspNetCore.Testing.AspNetTestFramework", "Microsoft.AspNetCore.Testing" });
+
+        var fixtureConstructor = typeof(AssemblyFixtureAttribute).GetConstructor(new[] { typeof(Type) });
+        var fixtureBuilder = new CustomAttributeBuilder(fixtureConstructor, new[] { fixtureType });
+
+        var outputConstructor = typeof(TestOutputDirectoryAttribute).GetConstructor(
+            new[] { typeof(string), typeof(string), typeof(string) });
+        var outputBuilder = new CustomAttributeBuilder(outputConstructor, new[] { "false", TFM, logDirectory });
+
+        var testAssemblyName = $"Test{Guid.NewGuid():n}";
+        var assemblyName = new AssemblyName(testAssemblyName);
+        var assembly = AssemblyBuilder.DefineDynamicAssembly(
+            assemblyName,
+            AssemblyBuilderAccess.Run,
+            new[] { frameworkBuilder, fixtureBuilder, outputBuilder });
+
+        var module = assembly.DefineDynamicModule(testAssemblyName);
+        var type = module.DefineType(TestClassName, TypeAttributes.Public);
+        type.DefineDefaultConstructor(MethodAttributes.Public);
+
+        var method = type.DefineMethod(TestMethodName, MethodAttributes.Public);
+        var factConstructor = typeof(FactAttribute).GetConstructor(Array.Empty<Type>());
+        var factBuilder = new CustomAttributeBuilder(factConstructor, Array.Empty<object>());
+        method.SetCustomAttribute(factBuilder);
+
+        var generator = method.GetILGenerator();
+        if (failTestCase)
+        {
+            // Assert.True(condition: false);
+            generator.Emit(OpCodes.Ldc_I4_0);
+            var trueInfo = typeof(Assert).GetMethod("True", new[] { typeof(bool) });
+            generator.EmitCall(OpCodes.Call, trueInfo, optionalParameterTypes: null);
+        }
+
+        generator.Emit(OpCodes.Ret);
+        type.CreateType();
+
+        return assembly;
+    }
+}
diff --git a/src/submodules/googletest b/src/submodules/googletest
index c9461a9b55ba954df0489bab6420eb297bed846b..af29db7ec28d6df1c7f0f745186884091e602e07 160000
--- a/src/submodules/googletest
+++ b/src/submodules/googletest
@@ -1 +1 @@
-Subproject commit c9461a9b55ba954df0489bab6420eb297bed846b
+Subproject commit af29db7ec28d6df1c7f0f745186884091e602e07