diff --git a/.gitattributes b/.gitattributes index c2f0f84273..5e91d85057 100644 --- a/.gitattributes +++ b/.gitattributes @@ -13,7 +13,7 @@ *.png binary *.gif binary -*.cs text=auto diff=csharp +*.cs text=auto diff=csharp *.vb text=auto *.resx text=auto *.c text=auto diff --git a/KestrelHttpServer.sln b/KestrelHttpServer.sln index c9679e5676..baa0ee92f4 100644 --- a/KestrelHttpServer.sln +++ b/KestrelHttpServer.sln @@ -2,26 +2,6 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 15 VisualStudioVersion = 15.0.27130.2010 MinimumVisualStudioVersion = 15.0.26730.03 -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{7972A5D6-3385-4127-9277-428506DD44FF}" - ProjectSection(SolutionItems) = preProject - .appveyor.yml = .appveyor.yml - .gitattributes = .gitattributes - .gitignore = .gitignore - .travis.yml = .travis.yml - build.cmd = build.cmd - build.ps1 = build.ps1 - build.sh = build.sh - CONTRIBUTING.md = CONTRIBUTING.md - Directory.Build.props = Directory.Build.props - Directory.Build.targets = Directory.Build.targets - LICENSE.txt = LICENSE.txt - NuGet.Config = NuGet.Config - NuGetPackageVerifier.json = NuGetPackageVerifier.json - README.md = README.md - ToProjectReferences.ps1 = ToProjectReferences.ps1 - version.xml = version.xml - EndProjectSection -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{2D5D5227-4DBD-499A-96B1-76A36B03B750}" ProjectSection(SolutionItems) = preProject src\Directory.Build.props = src\Directory.Build.props @@ -136,6 +116,28 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Kestrel.Transport.Libuv.Bin EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Kestrel.H2Spec.FunctionalTests", "test\Kestrel.H2Spec.FunctionalTests\Kestrel.H2Spec.FunctionalTests.csproj", "{C4123E55-5760-4557-B89B-39E1258FD7F9}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "SolutionItems", "SolutionItems", "{B19F67B8-7635-42C3-B5BF-00D1CC47FA64}" + ProjectSection(SolutionItems) = preProject + .gitattributes = .gitattributes + .gitignore = .gitignore + build.cmd = build.cmd + build.sh = build.sh + CONTRIBUTING.md = CONTRIBUTING.md + Directory.Build.props = Directory.Build.props + Directory.Build.targets = Directory.Build.targets + korebuild-lock.txt = korebuild-lock.txt + korebuild.json = korebuild.json + LICENSE.txt = LICENSE.txt + NuGet.config = NuGet.config + NuGetPackageVerifier.json = NuGetPackageVerifier.json + README.md = README.md + run.cmd = run.cmd + run.ps1 = run.ps1 + run.sh = run.sh + ToProjectReferences.ps1 = ToProjectReferences.ps1 + version.props = version.props + EndProjectSection +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU diff --git a/test/Kestrel.H2Spec.FunctionalTests/H2SpecTests.cs b/test/Kestrel.H2Spec.FunctionalTests/H2SpecTests.cs index 45e66cbf47..c2560335d3 100644 --- a/test/Kestrel.H2Spec.FunctionalTests/H2SpecTests.cs +++ b/test/Kestrel.H2Spec.FunctionalTests/H2SpecTests.cs @@ -11,6 +11,7 @@ using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting.Server.Features; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Server.Kestrel.Core; +using Microsoft.AspNetCore.Testing; using Microsoft.AspNetCore.Testing.xunit; using Microsoft.Extensions.Logging.Testing; using Xunit; @@ -23,8 +24,6 @@ namespace H2Spec.FunctionalTests SkipReason = "Missing Windows ALPN support: https://en.wikipedia.org/wiki/Application-Layer_Protocol_Negotiation#Support")] public class H2SpecTests : LoggedTest { - private static readonly string _testCertPath = Path.Combine(Directory.GetCurrentDirectory(), "shared", "TestCertificates", "testCert.pfx"); - [ConditionalTheory] [MemberData(nameof(H2SpecTestCases))] public async Task RunIndividualTestCase(H2SpecTestCase testCase) @@ -37,7 +36,7 @@ namespace H2Spec.FunctionalTests listenOptions.Protocols = HttpProtocols.Http2; if (testCase.Https) { - listenOptions.UseHttps(_testCertPath, "testPassword"); + listenOptions.UseHttps(TestResources.GetTestCertificate()); } }); }) diff --git a/test/Kestrel.H2Spec.FunctionalTests/Kestrel.H2Spec.FunctionalTests.csproj b/test/Kestrel.H2Spec.FunctionalTests/Kestrel.H2Spec.FunctionalTests.csproj index 5b45607b50..06e4138c78 100644 --- a/test/Kestrel.H2Spec.FunctionalTests/Kestrel.H2Spec.FunctionalTests.csproj +++ b/test/Kestrel.H2Spec.FunctionalTests/Kestrel.H2Spec.FunctionalTests.csproj @@ -11,6 +11,7 @@ + diff --git a/test/Kestrel.InMemory.FunctionalTests/HttpsTests.cs b/test/Kestrel.InMemory.FunctionalTests/HttpsTests.cs index da2df11053..5928f5d661 100644 --- a/test/Kestrel.InMemory.FunctionalTests/HttpsTests.cs +++ b/test/Kestrel.InMemory.FunctionalTests/HttpsTests.cs @@ -40,7 +40,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests public void UseHttpsDefaultsToDefaultCert() { var serverOptions = CreateServerOptions(); - var defaultCert = new X509Certificate2(TestResources.TestCertificatePath, "testPassword"); + var defaultCert = TestResources.GetTestCertificate(); serverOptions.DefaultCertificate = defaultCert; serverOptions.ListenLocalhost(5000, options => @@ -65,7 +65,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests public void ConfigureHttpsDefaultsNeverLoadsDefaultCert() { var serverOptions = CreateServerOptions(); - var testCert = new X509Certificate2(TestResources.TestCertificatePath, "testPassword"); + var testCert = TestResources.GetTestCertificate(); serverOptions.ConfigureHttpsDefaults(options => { Assert.Null(options.ServerCertificate); @@ -89,7 +89,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests public void ConfigureCertSelectorNeverLoadsDefaultCert() { var serverOptions = CreateServerOptions(); - var testCert = new X509Certificate2(TestResources.TestCertificatePath, "testPassword"); + var testCert = TestResources.GetTestCertificate(); serverOptions.ConfigureHttpsDefaults(options => { Assert.Null(options.ServerCertificate); @@ -124,7 +124,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests new TestServiceContext(LoggerFactory), listenOptions => { - listenOptions.UseHttps(TestResources.TestCertificatePath, "testPassword"); + listenOptions.UseHttps(TestResources.GetTestCertificate()); })) { using (var connection = server.CreateConnection()) @@ -151,7 +151,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests new TestServiceContext(LoggerFactory), listenOptions => { - listenOptions.UseHttps(TestResources.TestCertificatePath, "testPassword"); + listenOptions.UseHttps(TestResources.GetTestCertificate()); })) { using (var connection = server.CreateConnection()) @@ -195,7 +195,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests new TestServiceContext(LoggerFactory), listenOptions => { - listenOptions.UseHttps(TestResources.TestCertificatePath, "testPassword"); + listenOptions.UseHttps(TestResources.GetTestCertificate()); })) { using (var connection = server.CreateConnection()) @@ -238,7 +238,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests new TestServiceContext(LoggerFactory), listenOptions => { - listenOptions.UseHttps(TestResources.TestCertificatePath, "testPassword"); + listenOptions.UseHttps(TestResources.GetTestCertificate()); })) { using (var connection = server.CreateConnection()) @@ -269,7 +269,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests new TestServiceContext(LoggerFactory), listenOptions => { - listenOptions.UseHttps(TestResources.TestCertificatePath, "testPassword"); + listenOptions.UseHttps(TestResources.GetTestCertificate()); })) { using (var connection = server.CreateConnection()) @@ -295,7 +295,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests new TestServiceContext(LoggerFactory), listenOptions => { - listenOptions.UseHttps(TestResources.TestCertificatePath, "testPassword"); + listenOptions.UseHttps(TestResources.GetTestCertificate()); })) { using (var connection = server.CreateConnection()) @@ -323,7 +323,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests { listenOptions.UseHttps(o => { - o.ServerCertificate = new X509Certificate2(TestResources.TestCertificatePath, "testPassword"); + o.ServerCertificate = new X509Certificate2(TestResources.GetTestCertificate()); o.OnHandshakeStarted = () => handshakeStartedTcs.SetResult(null); handshakeTimeout = o.HandshakeTimeout; @@ -359,7 +359,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests new TestServiceContext(LoggerFactory), listenOptions => { - listenOptions.UseHttps(TestResources.TestCertificatePath, "testPassword"); + listenOptions.UseHttps(TestResources.GetTestCertificate()); })) { using (var connection = server.CreateConnection()) diff --git a/test/Kestrel.InMemory.FunctionalTests/LoggingConnectionAdapterTests.cs b/test/Kestrel.InMemory.FunctionalTests/LoggingConnectionAdapterTests.cs index 5eca246b35..cba0db84bd 100644 --- a/test/Kestrel.InMemory.FunctionalTests/LoggingConnectionAdapterTests.cs +++ b/test/Kestrel.InMemory.FunctionalTests/LoggingConnectionAdapterTests.cs @@ -25,7 +25,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests listenOptions => { listenOptions.UseConnectionLogging(); - listenOptions.UseHttps(TestResources.TestCertificatePath, "testPassword"); + listenOptions.UseHttps(TestResources.GetTestCertificate()); listenOptions.UseConnectionLogging(); })) { diff --git a/test/Kestrel.Tests/KestrelConfigurationBuilderTests.cs b/test/Kestrel.Tests/KestrelConfigurationBuilderTests.cs index 96f6f8777f..2299d38f63 100644 --- a/test/Kestrel.Tests/KestrelConfigurationBuilderTests.cs +++ b/test/Kestrel.Tests/KestrelConfigurationBuilderTests.cs @@ -136,7 +136,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Tests serverOptions.ConfigureHttpsDefaults(opt => { - opt.ServerCertificate = new X509Certificate2(TestResources.TestCertificatePath, "testPassword"); + opt.ServerCertificate = TestResources.GetTestCertificate(); opt.ClientCertificateMode = ClientCertificateMode.RequireCertificate; }); @@ -179,7 +179,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Tests serverOptions.ConfigureEndpointDefaults(opt => { opt.NoDelay = false; - opt.UseHttps(new X509Certificate2(TestResources.TestCertificatePath, "testPassword")); + opt.UseHttps(TestResources.GetTestCertificate()); }); serverOptions.ConfigureHttpsDefaults(opt => @@ -335,7 +335,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Tests serverOptions.ConfigureHttpsDefaults(opt => { - opt.ServerCertificate = new X509Certificate2(TestResources.TestCertificatePath, "testPassword"); + opt.ServerCertificate = TestResources.GetTestCertificate(); opt.ClientCertificateMode = ClientCertificateMode.RequireCertificate; }); @@ -391,7 +391,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Tests serverOptions.ConfigureHttpsDefaults(opt => { - opt.ServerCertificate = new X509Certificate2(TestResources.TestCertificatePath, "testPassword"); + opt.ServerCertificate = TestResources.GetTestCertificate(); opt.ClientCertificateMode = ClientCertificateMode.RequireCertificate; }); diff --git a/test/Kestrel.Transport.BindTests/AddressRegistrationTests.cs b/test/Kestrel.Transport.BindTests/AddressRegistrationTests.cs index 98abbc3a5c..972941e72d 100644 --- a/test/Kestrel.Transport.BindTests/AddressRegistrationTests.cs +++ b/test/Kestrel.Transport.BindTests/AddressRegistrationTests.cs @@ -289,7 +289,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests { if (testUrl.StartsWith("https")) { - listenOptions.UseHttps(TestResources.TestCertificatePath, "testPassword"); + listenOptions.UseHttps(TestResources.GetTestCertificate()); } }); }) @@ -461,7 +461,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests { if (mockHttps) { - options.DefaultCertificate = new X509Certificate2(TestResources.TestCertificatePath, "testPassword"); + options.DefaultCertificate = TestResources.GetTestCertificate(); } }) .Configure(ConfigureEchoAddress); @@ -545,7 +545,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests { options.Listen(new IPEndPoint(IPAddress.Loopback, 0), listenOptions => { - listenOptions.UseHttps(TestResources.TestCertificatePath, "testPassword"); + listenOptions.UseHttps(TestResources.GetTestCertificate()); }); }) .UseUrls(useUrlsAddress) @@ -622,7 +622,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests { options.Listen(new IPEndPoint(IPAddress.Loopback, 0), listenOptions => { - listenOptions.UseHttps(TestResources.TestCertificatePath, "testPassword"); + listenOptions.UseHttps(TestResources.GetTestCertificate()); }); }) .PreferHostingUrls(true) diff --git a/test/Kestrel.Transport.FunctionalTests/ChromeConstants.cs b/test/Kestrel.Transport.FunctionalTests/ChromeConstants.cs new file mode 100644 index 0000000000..e6f19feb73 --- /dev/null +++ b/test/Kestrel.Transport.FunctionalTests/ChromeConstants.cs @@ -0,0 +1,28 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.IO; +using System.Runtime.InteropServices; + +namespace Interop.FunctionalTests +{ + public static class ChromeConstants + { + public static string ExecutablePath { get; } = ResolveChromeExecutablePath(); + + private static string ResolveChromeExecutablePath() + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86), "Google", "Chrome", "Application", "chrome.exe"); + } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + { + return Path.Combine("/usr", "bin", "chromium-browser"); + } + + throw new PlatformNotSupportedException(); + } + } +} diff --git a/test/Kestrel.Transport.FunctionalTests/ChromeTests.cs b/test/Kestrel.Transport.FunctionalTests/ChromeTests.cs new file mode 100644 index 0000000000..89b16a8077 --- /dev/null +++ b/test/Kestrel.Transport.FunctionalTests/ChromeTests.cs @@ -0,0 +1,139 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +#if NETCOREAPP2_2 + +using System; +using System.Diagnostics; +using System.IO; +using System.Net; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Server.Kestrel.Core; +using Microsoft.AspNetCore.Server.Kestrel.FunctionalTests; +using Microsoft.AspNetCore.Testing; +using Microsoft.AspNetCore.Testing.xunit; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Testing; +using Xunit; + +namespace Interop.FunctionalTests +{ + [SkipIfChromeUnavailable] + public class ChromeTests : LoggedTest + { + private static readonly string _postHtml = +@" + + + + + +
+
+ +"; + + private string NetLogPath { get; set; } + private string StartupLogPath { get; set; } + private string ShutdownLogPath { get; set; } + private string ChromeArgs { get; set; } + + private void InitializeArgs() + { + NetLogPath = Path.Combine(ResolvedLogOutputDirectory, $"{ResolvedTestMethodName}.nl.json"); + StartupLogPath = Path.Combine(ResolvedLogOutputDirectory, $"{ResolvedTestMethodName}.su.json"); + ShutdownLogPath = Path.Combine(ResolvedLogOutputDirectory, $"{ResolvedTestMethodName}.sd.json"); + + ChromeArgs = $"--headless " + + $"--disable-gpu " + + $"--allow-insecure-localhost " + + $"--enable-logging " + + $"--dump-dom " + + $"--virtual-time-budget=10000 " + + $"--log-net-log={NetLogPath} " + + $"--trace-startup --trace-startup-file={StartupLogPath} " + + $"--trace-shutdown --trace-shutdown-file={ShutdownLogPath}"; + } + + [ConditionalTheory] + [OSSkipCondition(OperatingSystems.MacOSX, SkipReason = "Missing SslStream ALPN support: https://github.com/dotnet/corefx/issues/30492")] + [MinimumOSVersion(OperatingSystems.Windows, WindowsVersions.Win81, SkipReason = "Missing Windows ALPN support: https://en.wikipedia.org/wiki/Application-Layer_Protocol_Negotiation#Support")] + [InlineData("", "Interop HTTP/2 GET")] + [InlineData("?TestMethod=POST", "Interop HTTP/2 POST")] + public async Task Http2(string requestSuffix, string expectedResponse) + { + InitializeArgs(); + + using (var server = new TestServer(async context => + { + if (string.Equals(context.Request.Query["TestMethod"], "POST", StringComparison.OrdinalIgnoreCase)) + { + await context.Response.WriteAsync(_postHtml); + } + else + { + await context.Response.WriteAsync($"Interop {context.Request.Protocol} {context.Request.Method}"); + } + }, + new TestServiceContext(LoggerFactory), + options => options.Listen(IPAddress.Loopback, 0, listenOptions => + { + listenOptions.Protocols = HttpProtocols.Http2; + listenOptions.UseHttps(TestResources.GetTestCertificate()); + }))) + { + var chromeOutput = await RunHeadlessChrome($"https://localhost:{server.Port}/{requestSuffix}"); + + AssertExpectedResponseOrShowDebugInstructions(expectedResponse, chromeOutput); + } + } + + private async Task RunHeadlessChrome(string testUrl) + { + var chromeArgs = $"{ChromeArgs} {testUrl}"; + var chromeStartInfo = new ProcessStartInfo + { + FileName = ChromeConstants.ExecutablePath, + Arguments = chromeArgs, + UseShellExecute = false, + CreateNoWindow = true, + RedirectStandardError = true, + RedirectStandardOutput = true + }; + + Logger.LogInformation($"Staring chrome: {ChromeConstants.ExecutablePath} {chromeArgs}"); + + var headlessChromeProcess = Process.Start(chromeStartInfo); + var chromeOutput = await headlessChromeProcess.StandardOutput.ReadToEndAsync(); + + headlessChromeProcess.WaitForExit(); + + return chromeOutput; + } + + private void AssertExpectedResponseOrShowDebugInstructions(string expectedResponse, string actualResponse) + { + try + { + Assert.Contains(expectedResponse, actualResponse); + } + catch + { + Logger.LogError("Chrome interop tests failed. Please consult the following logs:"); + Logger.LogError($"Network logs: {NetLogPath}"); + Logger.LogError($"Startup logs: {StartupLogPath}"); + Logger.LogError($"Shutdown logs: {ShutdownLogPath}"); + throw; + } + } + } +} + +#elif NET461 // No ALPN support +#else +#error TFMs need updating +#endif diff --git a/test/Kestrel.Transport.FunctionalTests/ResponseTests.cs b/test/Kestrel.Transport.FunctionalTests/ResponseTests.cs index 3b50a8071b..d9aa983cac 100644 --- a/test/Kestrel.Transport.FunctionalTests/ResponseTests.cs +++ b/test/Kestrel.Transport.FunctionalTests/ResponseTests.cs @@ -560,7 +560,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests const int chunks = 256 * 1024; var chunkData = new byte[chunkSize]; - var certificate = new X509Certificate2(TestResources.TestCertificatePath, "testPassword"); + var certificate = TestResources.GetTestCertificate(); var responseRateTimeoutMessageLogged = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); var connectionStopMessageLogged = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); diff --git a/test/Kestrel.Transport.FunctionalTests/SkipIfChromeUnavailableAttribute.cs b/test/Kestrel.Transport.FunctionalTests/SkipIfChromeUnavailableAttribute.cs new file mode 100644 index 0000000000..0820eab7d9 --- /dev/null +++ b/test/Kestrel.Transport.FunctionalTests/SkipIfChromeUnavailableAttribute.cs @@ -0,0 +1,17 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.IO; +using Microsoft.AspNetCore.Testing.xunit; + +namespace Interop.FunctionalTests +{ + [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = false)] + public class SkipIfChromeUnavailableAttribute : Attribute, ITestCondition + { + public bool IsMet => string.IsNullOrEmpty(Environment.GetEnvironmentVariable("JENKINS_HOME")) && (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("CI")) || File.Exists(ChromeConstants.ExecutablePath)); + + public string SkipReason => "This is running on Jenkins or Chrome/Chromium is not installed and this is a dev environment."; + } +} diff --git a/test/shared/TestCertificates/testCert.pfx b/test/shared/TestCertificates/testCert.pfx index 7118908c2d..888ccb032a 100644 Binary files a/test/shared/TestCertificates/testCert.pfx and b/test/shared/TestCertificates/testCert.pfx differ