From 967c1a50b8f6156a7df2310434d0ba54a09b9188 Mon Sep 17 00:00:00 2001 From: Steve Sanderson Date: Thu, 7 Sep 2017 10:04:33 +0100 Subject: [PATCH] Use Xunit's ITestOutputHelper throughout. Other minor tidy-ups. --- test/Templates.Test/EmptyWebTemplateTest.cs | 5 +++ test/Templates.Test/Helpers/AspNetProcess.cs | 22 +++++++---- test/Templates.Test/Helpers/ProcessEx.cs | 38 ++++++++++++++----- .../Helpers/TemplatePackageInstaller.cs | 27 +++++++++++-- .../Helpers/TemplateTestBase.cs | 31 +++++++-------- test/Templates.Test/MvcTemplateTest.cs | 5 +++ test/Templates.Test/RazorPagesTemplateTest.cs | 5 +++ test/Templates.Test/SpaTemplateTest.cs | 5 +++ test/Templates.Test/WebApiTemplateTest.cs | 5 +++ 9 files changed, 108 insertions(+), 35 deletions(-) diff --git a/test/Templates.Test/EmptyWebTemplateTest.cs b/test/Templates.Test/EmptyWebTemplateTest.cs index de7f327df0..528975424d 100644 --- a/test/Templates.Test/EmptyWebTemplateTest.cs +++ b/test/Templates.Test/EmptyWebTemplateTest.cs @@ -1,9 +1,14 @@ using Xunit; +using Xunit.Abstractions; namespace Templates.Test { public class EmptyWebTemplateTest : TemplateTestBase { + public EmptyWebTemplateTest(ITestOutputHelper output) : base(output) + { + } + [Theory] [InlineData(null)] [InlineData("net461")] diff --git a/test/Templates.Test/Helpers/AspNetProcess.cs b/test/Templates.Test/Helpers/AspNetProcess.cs index 80396f4f93..78b2bbf33a 100644 --- a/test/Templates.Test/Helpers/AspNetProcess.cs +++ b/test/Templates.Test/Helpers/AspNetProcess.cs @@ -6,6 +6,7 @@ using System.Linq; using System.Net; using System.Net.Http; using Xunit; +using Xunit.Abstractions; namespace Templates.Test.Helpers { @@ -17,23 +18,27 @@ namespace Templates.Test.Helpers private readonly ProcessEx _process; private readonly Uri _listeningUri; private readonly HttpClient _httpClient; + private readonly ITestOutputHelper _output; - public AspNetProcess(string workingDirectory, string projectName, string targetFrameworkOverride, bool publish) + public AspNetProcess(ITestOutputHelper output, string workingDirectory, string projectName, string targetFrameworkOverride, bool publish) { + _output = output; _httpClient = new HttpClient(); var framework = string.IsNullOrEmpty(targetFrameworkOverride) ? DefaultFramework : targetFrameworkOverride; if (publish) { + output.WriteLine("Publishing ASP.NET application..."); ProcessEx - .Run(workingDirectory, "dotnet", "publish -c Release") + .Run(output, workingDirectory, "dotnet", "publish -c Release") .WaitForExit(assertSuccess: true); workingDirectory = Path.Combine(workingDirectory, "bin", "Release", framework, "publish"); } else { + output.WriteLine("Building ASP.NET application..."); ProcessEx - .Run(workingDirectory, "dotnet", "build --no-restore -c Debug") + .Run(output, workingDirectory, "dotnet", "build --no-restore -c Debug") .WaitForExit(assertSuccess: true); } @@ -47,20 +52,22 @@ namespace Templates.Test.Helpers envVars["ASPNETCORE_ENVIRONMENT"] = "Development"; } + output.WriteLine("Running ASP.NET application..."); if (framework.StartsWith("netcore")) { var dllPath = publish ? $"{projectName}.dll" : $"bin/Debug/{framework}/{projectName}.dll"; - _process = ProcessEx.Run(workingDirectory, "dotnet", $"exec {dllPath}", envVars: envVars); + _process = ProcessEx.Run(output, workingDirectory, "dotnet", $"exec {dllPath}", envVars: envVars); } else { var exeFullPath = publish ? Path.Combine(workingDirectory, $"{projectName}.exe") : Path.Combine(workingDirectory, "bin", "Debug", framework, $"{projectName}.exe"); - _process = ProcessEx.Run(workingDirectory, exeFullPath, envVars: envVars); + _process = ProcessEx.Run(output, workingDirectory, exeFullPath, envVars: envVars); } - + // Wait until the app is accepting HTTP requests + output.WriteLine("Waiting until ASP.NET application is accepting connections..."); var listeningMessage = _process .OutputLinesAsEnumerable .Where(line => line != null) @@ -70,6 +77,7 @@ namespace Templates.Test.Helpers // Verify we have a valid URL to make requests to var listeningUrlString = listeningMessage.Substring(ListeningMessagePrefix.Length); _listeningUri = new Uri(listeningUrlString, UriKind.Absolute); + output.WriteLine($"Detected that ASP.NET application is accepting connections on {listeningUrlString}"); } public void AssertOk(string requestUrl) @@ -90,7 +98,7 @@ namespace Templates.Test.Helpers public IWebDriver VisitInBrowser() { - Console.WriteLine($"Opening browser at {_listeningUri}..."); + _output.WriteLine($"Opening browser at {_listeningUri}..."); var driver = WebDriverFactory.CreateWebDriver(); driver.Navigate().GoToUrl(_listeningUri); return driver; diff --git a/test/Templates.Test/Helpers/ProcessEx.cs b/test/Templates.Test/Helpers/ProcessEx.cs index 50437e9744..91bf36b3a8 100644 --- a/test/Templates.Test/Helpers/ProcessEx.cs +++ b/test/Templates.Test/Helpers/ProcessEx.cs @@ -1,21 +1,22 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; -using System.Collections.Specialized; using System.Diagnostics; using System.Text; -using Xunit; +using Xunit.Abstractions; namespace Templates.Test.Helpers { internal class ProcessEx : IDisposable { + private readonly ITestOutputHelper _output; private readonly Process _process; private readonly StringBuilder _stderrCapture; private readonly StringBuilder _stdoutCapture; + private readonly object _pipeCaptureLock = new object(); private BlockingCollection _stdoutLines; - public static ProcessEx Run(string workingDirectory, string command, string args = null, IDictionary envVars = null) + public static ProcessEx Run(ITestOutputHelper output, string workingDirectory, string command, string args = null, IDictionary envVars = null) { var startInfo = new ProcessStartInfo(command, args) { @@ -36,11 +37,12 @@ namespace Templates.Test.Helpers var proc = Process.Start(startInfo); - return new ProcessEx(proc); + return new ProcessEx(output, proc); } - public ProcessEx(Process proc) + public ProcessEx(ITestOutputHelper output, Process proc) { + _output = output; _stdoutCapture = new StringBuilder(); _stderrCapture = new StringBuilder(); _stdoutLines = new BlockingCollection(); @@ -62,14 +64,32 @@ namespace Templates.Test.Helpers private void OnErrorData(object sender, DataReceivedEventArgs e) { - _stderrCapture.AppendLine(e.Data); - Console.Error.WriteLine(e.Data); + if (e.Data == null) + { + return; + } + + lock (_pipeCaptureLock) + { + _stderrCapture.AppendLine(e.Data); + } + + _output.WriteLine("[ERROR] " + e.Data); } private void OnOutputData(object sender, DataReceivedEventArgs e) { - _stdoutCapture.AppendLine(e.Data); - Console.WriteLine(e.Data); + if (e.Data == null) + { + return; + } + + lock (_pipeCaptureLock) + { + _stdoutCapture.AppendLine(e.Data); + } + + _output.WriteLine(e.Data); if (_stdoutLines != null) { diff --git a/test/Templates.Test/Helpers/TemplatePackageInstaller.cs b/test/Templates.Test/Helpers/TemplatePackageInstaller.cs index 33e2e32e26..329390000a 100644 --- a/test/Templates.Test/Helpers/TemplatePackageInstaller.cs +++ b/test/Templates.Test/Helpers/TemplatePackageInstaller.cs @@ -1,11 +1,15 @@ using System; using System.IO; using System.Linq; +using Xunit.Abstractions; namespace Templates.Test.Helpers { internal static class TemplatePackageInstaller { + private static object _templatePackagesReinstallationLock = new object(); + private static bool _haveReinstalledTemplatePackages; + private static readonly string[] _templatePackages = new[] { "Microsoft.DotNet.Web.ItemTemplates", @@ -14,19 +18,32 @@ namespace Templates.Test.Helpers "Microsoft.AspNetCore.SpaTemplates", }; - public static void ReinstallTemplatePackages() + public static void EnsureTemplatePackagesWereReinstalled(ITestOutputHelper output) + { + lock (_templatePackagesReinstallationLock) + { + if (!_haveReinstalledTemplatePackages) + { + ReinstallTemplatePackages(output); + _haveReinstalledTemplatePackages = true; + } + } + } + + private static void ReinstallTemplatePackages(ITestOutputHelper output) { // Remove any previous or prebundled version of the template packages foreach (var packageName in _templatePackages) { var proc = ProcessEx.Run( + output, Directory.GetCurrentDirectory(), "dotnet", $"new --uninstall {packageName}"); proc.WaitForExit(assertSuccess: true); } - VerifyCannotFindTemplate("ASP.NET Core Empty"); + VerifyCannotFindTemplate(output, "ASP.NET Core Empty"); // Locate the artifacts directory containing the built template packages var solutionDir = FindAncestorDirectoryContaining("Templating.sln"); @@ -36,8 +53,9 @@ namespace Templates.Test.Helpers { if (_templatePackages.Any(name => Path.GetFileName(packagePath).StartsWith(name, StringComparison.OrdinalIgnoreCase))) { - Console.WriteLine($"Installing templates package {packagePath}..."); + output.WriteLine($"Installing templates package {packagePath}..."); var proc = ProcessEx.Run( + output, Directory.GetCurrentDirectory(), "dotnet", $"new --install \"{packagePath}\""); @@ -46,7 +64,7 @@ namespace Templates.Test.Helpers } } - private static void VerifyCannotFindTemplate(string templateName) + private static void VerifyCannotFindTemplate(ITestOutputHelper output, string templateName) { // Verify we really did remove the previous templates var tempDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString("D")); @@ -54,6 +72,7 @@ namespace Templates.Test.Helpers try { var proc = ProcessEx.Run( + output, tempDir, "dotnet", $"new \"{templateName}\""); diff --git a/test/Templates.Test/Helpers/TemplateTestBase.cs b/test/Templates.Test/Helpers/TemplateTestBase.cs index 47db2292ca..8b28d81f71 100644 --- a/test/Templates.Test/Helpers/TemplateTestBase.cs +++ b/test/Templates.Test/Helpers/TemplateTestBase.cs @@ -4,6 +4,7 @@ using System.Reflection; using System.Threading; using Templates.Test.Helpers; using Xunit; +using Xunit.Abstractions; namespace Templates.Test { @@ -11,14 +12,13 @@ namespace Templates.Test { protected string ProjectName { get; set; } protected string TemplateOutputDir { get; private set; } + protected ITestOutputHelper Output { get; private set; } - static TemplateTestBase() + public TemplateTestBase(ITestOutputHelper output) { - TemplatePackageInstaller.ReinstallTemplatePackages(); - } + TemplatePackageInstaller.EnsureTemplatePackagesWereReinstalled(output); - public TemplateTestBase() - { + Output = output; ProjectName = Guid.NewGuid().ToString().Replace("-", ""); var assemblyPath = GetType().GetTypeInfo().Assembly.CodeBase; @@ -58,7 +58,7 @@ namespace Templates.Test args += $" -lang {language}"; } - ProcessEx.Run(TemplateOutputDir, "dotnet", args).WaitForExit(assertSuccess: true); + ProcessEx.Run(Output, TemplateOutputDir, "dotnet", args).WaitForExit(assertSuccess: true); } protected void RunNpmInstall() @@ -66,7 +66,7 @@ namespace Templates.Test // The first time this runs on any given CI agent it may take several minutes. // If the agent has NPM 5+ installed, it should be quite a lot quicker on // subsequent runs because of package caching. - ProcessEx.Run(TemplateOutputDir, "cmd", "/c \"npm install\"").WaitForExit(assertSuccess: true); + ProcessEx.Run(Output, TemplateOutputDir, "cmd", "/c \"npm install\"").WaitForExit(assertSuccess: true); } protected void AssertDirectoryExists(string path, bool shouldExist) @@ -107,7 +107,7 @@ namespace Templates.Test protected AspNetProcess StartAspNetProcess(string targetFrameworkOverride, bool publish = false) { - return new AspNetProcess(TemplateOutputDir, ProjectName, targetFrameworkOverride, publish); + return new AspNetProcess(Output, TemplateOutputDir, ProjectName, targetFrameworkOverride, publish); } public void Dispose() @@ -117,24 +117,25 @@ namespace Templates.Test private void DeleteOutputDirectory() { - var numAttempts = 5; - while (true) + const int NumAttempts = 10; + + for (var numAttemptsRemaining = NumAttempts; numAttemptsRemaining > 0; numAttemptsRemaining--) { try { Directory.Delete(TemplateOutputDir, true); return; } - catch (IOException) + catch (Exception ex) { - numAttempts--; - if (numAttempts > 0) + if (numAttemptsRemaining > 1) { - Thread.Sleep(2000); + Output.WriteLine($"Failed to delete directory {TemplateOutputDir} because of error {ex.Message}. Will try again {numAttemptsRemaining - 1} more time(s)."); + Thread.Sleep(3000); } else { - throw; + Output.WriteLine($"Giving up trying to delete directory {TemplateOutputDir} after {NumAttempts} attempts. Most recent error was: {ex.StackTrace}"); } } } diff --git a/test/Templates.Test/MvcTemplateTest.cs b/test/Templates.Test/MvcTemplateTest.cs index 1aa8967ae6..b662a6b1af 100644 --- a/test/Templates.Test/MvcTemplateTest.cs +++ b/test/Templates.Test/MvcTemplateTest.cs @@ -1,9 +1,14 @@ using Xunit; +using Xunit.Abstractions; namespace Templates.Test { public class MvcTemplateTest : TemplateTestBase { + public MvcTemplateTest(ITestOutputHelper output) : base(output) + { + } + [Theory] [InlineData(/* netcoreapp */ null, /* C# */ null)] [InlineData("net461", /* C# */ null)] diff --git a/test/Templates.Test/RazorPagesTemplateTest.cs b/test/Templates.Test/RazorPagesTemplateTest.cs index a57c049bad..48e88b4142 100644 --- a/test/Templates.Test/RazorPagesTemplateTest.cs +++ b/test/Templates.Test/RazorPagesTemplateTest.cs @@ -1,9 +1,14 @@ using Xunit; +using Xunit.Abstractions; namespace Templates.Test { public class RazorPagesTemplateTest : TemplateTestBase { + public RazorPagesTemplateTest(ITestOutputHelper output) : base(output) + { + } + [Theory] [InlineData(null)] [InlineData("net461")] diff --git a/test/Templates.Test/SpaTemplateTest.cs b/test/Templates.Test/SpaTemplateTest.cs index 2cbac089bf..8095c76899 100644 --- a/test/Templates.Test/SpaTemplateTest.cs +++ b/test/Templates.Test/SpaTemplateTest.cs @@ -1,11 +1,16 @@ using OpenQA.Selenium; using Templates.Test.Helpers; using Xunit; +using Xunit.Abstractions; namespace Templates.Test { public class SpaTemplateTest : TemplateTestBase { + public SpaTemplateTest(ITestOutputHelper output) : base(output) + { + } + [Theory] [InlineData(null, "angular")] [InlineData(null, "react")] diff --git a/test/Templates.Test/WebApiTemplateTest.cs b/test/Templates.Test/WebApiTemplateTest.cs index 6da33297ef..977a968a09 100644 --- a/test/Templates.Test/WebApiTemplateTest.cs +++ b/test/Templates.Test/WebApiTemplateTest.cs @@ -1,9 +1,14 @@ using Xunit; +using Xunit.Abstractions; namespace Templates.Test { public class WebApiTemplateTest : TemplateTestBase { + public WebApiTemplateTest(ITestOutputHelper output) : base(output) + { + } + [Theory] [InlineData(null)] [InlineData("net461")]