diff --git a/AspNetCoreSdkTests/TemplateData.cs b/AspNetCoreSdkTests/TemplateData.cs new file mode 100644 index 0000000000..a24771f986 --- /dev/null +++ b/AspNetCoreSdkTests/TemplateData.cs @@ -0,0 +1,35 @@ +using AspNetCoreSdkTests.Templates; +using NUnit.Framework; +using System.Collections.Generic; +using System.Linq; + +namespace AspNetCoreSdkTests +{ + public static class TemplateData + { + public static IEnumerable Restore = new TestCaseData[] + { + new TestCaseData(Template.GetInstance(NuGetConfig.Empty)), + new TestCaseData(Template.GetInstance(NuGetConfig.Empty)), + + // Offline restore currently not supported for RazorClassLibrary template (https://github.com/aspnet/Universe/issues/1123) + new TestCaseData(Template.GetInstance(NuGetConfig.NuGetOrg)), + + new TestCaseData(Template.GetInstance(NuGetConfig.Empty)), + new TestCaseData(Template.GetInstance(NuGetConfig.Empty)), + new TestCaseData(Template.GetInstance(NuGetConfig.Empty)), + new TestCaseData(Template.GetInstance(NuGetConfig.Empty)), + new TestCaseData(Template.GetInstance(NuGetConfig.Empty)), + new TestCaseData(Template.GetInstance(NuGetConfig.Empty)), + new TestCaseData(Template.GetInstance(NuGetConfig.Empty)), + }; + + public static IEnumerable Build => Restore; + + public static IEnumerable Publish => Restore; + + public static IEnumerable Run = Restore.Where(d => ((Template)d.Arguments[0]).Type == TemplateType.WebApplication); + + public static IEnumerable Exec => Run; + } +} diff --git a/AspNetCoreSdkTests/TemplateTests.cs b/AspNetCoreSdkTests/TemplateTests.cs index 4b75454fc7..a34c87d78b 100644 --- a/AspNetCoreSdkTests/TemplateTests.cs +++ b/AspNetCoreSdkTests/TemplateTests.cs @@ -1,71 +1,47 @@ using AspNetCoreSdkTests.Templates; -using AspNetCoreSdkTests.Util; using NUnit.Framework; -using System; using System.Net; -using System.Net.Http; -using System.Threading; namespace AspNetCoreSdkTests { [TestFixture] public class TemplateTests { - private static readonly TimeSpan _sleepBetweenHttpRequests = TimeSpan.FromMilliseconds(100); - - private static readonly HttpClient _httpClient = new HttpClient(new HttpClientHandler() - { - // Allow self-signed certs - ServerCertificateCustomValidationCallback = (m, c, ch, p) => true - }); - [Test] - [TestCaseSource(typeof(TemplateData), nameof(TemplateData.Current))] - public void RestoreBuildRunPublish(Template template, NuGetConfig nuGetConfig) + [TestCaseSource(typeof(TemplateData), nameof(TemplateData.Restore))] + public void Restore(Template template) { - using (var context = new DotNetContext(template)) - { - context.New(); - - context.Restore(nuGetConfig); - CollectionAssert.AreEquivalent(template.ExpectedObjFilesAfterRestore, context.GetObjFiles()); - - context.Build(); - CollectionAssert.AreEquivalent(template.ExpectedObjFilesAfterBuild, context.GetObjFiles()); - CollectionAssert.AreEquivalent(template.ExpectedBinFilesAfterBuild, context.GetBinFiles()); - - if (template.Type == TemplateType.WebApplication) - { - var (httpUrl, httpsUrl) = context.Run(); - Assert.AreEqual(HttpStatusCode.OK, GetAsync(new Uri(new Uri(httpUrl), template.RelativeUrl)).StatusCode); - Assert.AreEqual(HttpStatusCode.OK, GetAsync(new Uri(new Uri(httpsUrl), template.RelativeUrl)).StatusCode); - } - - context.Publish(); - CollectionAssert.AreEquivalent(template.ExpectedFilesAfterPublish, context.GetPublishFiles()); - - if (template.Type == TemplateType.WebApplication) - { - var (httpUrl, httpsUrl) = context.Exec(); - Assert.AreEqual(HttpStatusCode.OK, GetAsync(new Uri(new Uri(httpUrl), template.RelativeUrl)).StatusCode); - Assert.AreEqual(HttpStatusCode.OK, GetAsync(new Uri(new Uri(httpsUrl), template.RelativeUrl)).StatusCode); - } - } + CollectionAssert.AreEquivalent(template.ExpectedObjFilesAfterRestore, template.ObjFilesAfterRestore); } - private HttpResponseMessage GetAsync(Uri requestUri) + [Test] + [TestCaseSource(typeof(TemplateData), nameof(TemplateData.Build))] + public void Build(Template template) { - while (true) - { - try - { - return _httpClient.GetAsync(requestUri).Result; - } - catch - { - Thread.Sleep(_sleepBetweenHttpRequests); - } - } + CollectionAssert.AreEquivalent(template.ExpectedObjFilesAfterBuild, template.ObjFilesAfterBuild); + } + + [Test] + [TestCaseSource(typeof(TemplateData), nameof(TemplateData.Publish))] + public void Publish(Template template) + { + CollectionAssert.AreEquivalent(template.ExpectedFilesAfterPublish, template.FilesAfterPublish); + } + + [Test] + [TestCaseSource(typeof(TemplateData), nameof(TemplateData.Run))] + public void Run(Template template) + { + Assert.AreEqual(HttpStatusCode.OK, template.HttpResponseAfterRun.StatusCode); + Assert.AreEqual(HttpStatusCode.OK, template.HttpsResponseAfterRun.StatusCode); + } + + [Test] + [TestCaseSource(typeof(TemplateData), nameof(TemplateData.Exec))] + public void Exec(Template template) + { + Assert.AreEqual(HttpStatusCode.OK, template.HttpResponseAfterExec.StatusCode); + Assert.AreEqual(HttpStatusCode.OK, template.HttpsResponseAfterExec.StatusCode); } } } diff --git a/AspNetCoreSdkTests/Templates/AngularTemplate.cs b/AspNetCoreSdkTests/Templates/AngularTemplate.cs index 9b03800cee..dfaf51ce10 100644 --- a/AspNetCoreSdkTests/Templates/AngularTemplate.cs +++ b/AspNetCoreSdkTests/Templates/AngularTemplate.cs @@ -6,9 +6,7 @@ namespace AspNetCoreSdkTests.Templates { public class AngularTemplate : SpaBaseTemplate { - public new static AngularTemplate Instance { get; } = new AngularTemplate(); - - protected AngularTemplate() { } + public AngularTemplate() { } public override string Name => "angular"; diff --git a/AspNetCoreSdkTests/Templates/ClassLibraryTemplate.cs b/AspNetCoreSdkTests/Templates/ClassLibraryTemplate.cs index e9c702b56a..197429d292 100644 --- a/AspNetCoreSdkTests/Templates/ClassLibraryTemplate.cs +++ b/AspNetCoreSdkTests/Templates/ClassLibraryTemplate.cs @@ -6,9 +6,7 @@ namespace AspNetCoreSdkTests.Templates { public class ClassLibraryTemplate : Template { - public static ClassLibraryTemplate Instance { get; } = new ClassLibraryTemplate(); - - protected ClassLibraryTemplate() { } + public ClassLibraryTemplate() { } public override string Name => "classlib"; diff --git a/AspNetCoreSdkTests/Templates/ConsoleApplicationTemplate.cs b/AspNetCoreSdkTests/Templates/ConsoleApplicationTemplate.cs index 682ede5d3b..bad0616c2d 100644 --- a/AspNetCoreSdkTests/Templates/ConsoleApplicationTemplate.cs +++ b/AspNetCoreSdkTests/Templates/ConsoleApplicationTemplate.cs @@ -7,9 +7,7 @@ namespace AspNetCoreSdkTests.Templates { public class ConsoleApplicationTemplate : ClassLibraryTemplate { - public new static ConsoleApplicationTemplate Instance { get; } = new ConsoleApplicationTemplate(); - - protected ConsoleApplicationTemplate() { } + public ConsoleApplicationTemplate() { } public override string Name => "console"; diff --git a/AspNetCoreSdkTests/Templates/MvcTemplate.cs b/AspNetCoreSdkTests/Templates/MvcTemplate.cs index 0ce15a0f13..d33a3ab626 100644 --- a/AspNetCoreSdkTests/Templates/MvcTemplate.cs +++ b/AspNetCoreSdkTests/Templates/MvcTemplate.cs @@ -6,9 +6,7 @@ namespace AspNetCoreSdkTests.Templates { public class MvcTemplate : RazorBootstrapJQueryTemplate { - public new static MvcTemplate Instance { get; } = new MvcTemplate(); - - protected MvcTemplate() { } + public MvcTemplate() { } public override string Name => "mvc"; diff --git a/AspNetCoreSdkTests/Templates/RazorClassLibraryTemplate.cs b/AspNetCoreSdkTests/Templates/RazorClassLibraryTemplate.cs index feef3c5bea..9f87192281 100644 --- a/AspNetCoreSdkTests/Templates/RazorClassLibraryTemplate.cs +++ b/AspNetCoreSdkTests/Templates/RazorClassLibraryTemplate.cs @@ -6,9 +6,7 @@ namespace AspNetCoreSdkTests.Templates { public class RazorClassLibraryTemplate : RazorBaseTemplate { - public new static RazorClassLibraryTemplate Instance { get; } = new RazorClassLibraryTemplate(); - - protected RazorClassLibraryTemplate() { } + public RazorClassLibraryTemplate() { } public override string Name => "razorclasslib"; diff --git a/AspNetCoreSdkTests/Templates/RazorTemplate.cs b/AspNetCoreSdkTests/Templates/RazorTemplate.cs index 497f560878..c5da18e38d 100644 --- a/AspNetCoreSdkTests/Templates/RazorTemplate.cs +++ b/AspNetCoreSdkTests/Templates/RazorTemplate.cs @@ -6,9 +6,7 @@ namespace AspNetCoreSdkTests.Templates { public class RazorTemplate : RazorBootstrapJQueryTemplate { - public new static RazorTemplate Instance { get; } = new RazorTemplate(); - - protected RazorTemplate() { } + public RazorTemplate() { } public override string Name => "razor"; diff --git a/AspNetCoreSdkTests/Templates/ReactReduxTemplate.cs b/AspNetCoreSdkTests/Templates/ReactReduxTemplate.cs index 4741a5449d..8e89ba0817 100644 --- a/AspNetCoreSdkTests/Templates/ReactReduxTemplate.cs +++ b/AspNetCoreSdkTests/Templates/ReactReduxTemplate.cs @@ -5,9 +5,7 @@ namespace AspNetCoreSdkTests.Templates { public class ReactReduxTemplate : ReactTemplate { - public new static ReactReduxTemplate Instance { get; } = new ReactReduxTemplate(); - - protected ReactReduxTemplate() { } + public ReactReduxTemplate() { } public override string Name => "reactredux"; diff --git a/AspNetCoreSdkTests/Templates/ReactTemplate.cs b/AspNetCoreSdkTests/Templates/ReactTemplate.cs index 79dd2b2180..e8b4f21b23 100644 --- a/AspNetCoreSdkTests/Templates/ReactTemplate.cs +++ b/AspNetCoreSdkTests/Templates/ReactTemplate.cs @@ -6,9 +6,7 @@ namespace AspNetCoreSdkTests.Templates { public class ReactTemplate : SpaBaseTemplate { - public new static ReactTemplate Instance { get; } = new ReactTemplate(); - - protected ReactTemplate() { } + public ReactTemplate() { } public override string Name => "react"; diff --git a/AspNetCoreSdkTests/Templates/Template.cs b/AspNetCoreSdkTests/Templates/Template.cs index cb255cdf17..0910768e80 100644 --- a/AspNetCoreSdkTests/Templates/Template.cs +++ b/AspNetCoreSdkTests/Templates/Template.cs @@ -1,13 +1,76 @@ -using System.Collections.Generic; +using AspNetCoreSdkTests.Util; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Net.Http; +using System.Text.RegularExpressions; +using System.Threading; namespace AspNetCoreSdkTests.Templates { public abstract class Template { + private static readonly TimeSpan _sleepBetweenHttpRequests = TimeSpan.FromMilliseconds(100); + private static readonly TimeSpan _sleepBetweenOutputContains = TimeSpan.FromMilliseconds(100); + + private static readonly HttpClient _httpClient = new HttpClient(new HttpClientHandler() + { + // Allow self-signed certs + ServerCertificateCustomValidationCallback = (m, c, ch, p) => true + }); + + private static ConcurrentDictionary<(Type, NuGetConfig), Template> _templates = new ConcurrentDictionary<(Type, NuGetConfig), Template>(); + + public static T GetInstance(NuGetConfig nuGetConfig) where T : Template, new() + { + return (T)_templates.GetOrAdd((typeof(T), nuGetConfig), (k) => new T() { NuGetConfig = nuGetConfig }); + } + + private Lazy> _objFilesAfterRestore; + private Lazy<(IEnumerable ObjFiles, IEnumerable BinFiles)> _filesAfterBuild; + private Lazy> _filesAfterPublish; + private Lazy<(HttpResponseMessage Http, HttpResponseMessage Https)> _httpResponsesAfterRun; + private Lazy<(HttpResponseMessage Http, HttpResponseMessage Https)> _httpResponsesAfterExec; + + public NuGetConfig NuGetConfig { get; private set; } + + protected Template() + { + _objFilesAfterRestore = new Lazy>( + GetObjFilesAfterRestore, LazyThreadSafetyMode.ExecutionAndPublication); + + _filesAfterBuild = new Lazy<(IEnumerable ObjFiles, IEnumerable BinFiles)>( + GetFilesAfterBuild, LazyThreadSafetyMode.ExecutionAndPublication); + + _filesAfterPublish = new Lazy>( + GetFilesAfterPublish, LazyThreadSafetyMode.ExecutionAndPublication); + + _httpResponsesAfterRun = new Lazy<(HttpResponseMessage Http, HttpResponseMessage Https)>( + GetHttpResponsesAfterRun, LazyThreadSafetyMode.ExecutionAndPublication); + + _httpResponsesAfterExec = new Lazy<(HttpResponseMessage Http, HttpResponseMessage Https)>( + GetHttpResponsesAfterExec, LazyThreadSafetyMode.ExecutionAndPublication); + } + + public override string ToString() => $"{Name},{NuGetConfig}"; + + private string TempDir => Path.Combine(AssemblySetUp.TempDir, Name, NuGetConfig.ToString()); + public abstract string Name { get; } public abstract TemplateType Type { get; } public virtual string RelativeUrl => string.Empty; + public IEnumerable ObjFilesAfterRestore => _objFilesAfterRestore.Value; + public IEnumerable ObjFilesAfterBuild => _filesAfterBuild.Value.ObjFiles; + public IEnumerable BinFilesAfterBuild => _filesAfterBuild.Value.BinFiles; + public IEnumerable FilesAfterPublish => _filesAfterPublish.Value; + public HttpResponseMessage HttpResponseAfterRun => _httpResponsesAfterRun.Value.Http; + public HttpResponseMessage HttpsResponseAfterRun => _httpResponsesAfterRun.Value.Https; + public HttpResponseMessage HttpResponseAfterExec => _httpResponsesAfterExec.Value.Http; + public HttpResponseMessage HttpsResponseAfterExec => _httpResponsesAfterExec.Value.Https; + public virtual IEnumerable ExpectedObjFilesAfterRestore => new[] { $"{Name}.csproj.nuget.cache", @@ -22,6 +85,101 @@ namespace AspNetCoreSdkTests.Templates public abstract IEnumerable ExpectedFilesAfterPublish { get; } - public override string ToString() => Name; + private IEnumerable GetObjFilesAfterRestore() + { + Directory.CreateDirectory(TempDir); + DotNetUtil.New(Name, TempDir); + DotNetUtil.Restore(TempDir, NuGetConfig); + return IOUtil.GetFiles(System.IO.Path.Combine(TempDir, "obj")); + } + + private (IEnumerable ObjFiles, IEnumerable BinFiles) GetFilesAfterBuild() + { + // Build depends on Restore + _ = ObjFilesAfterRestore; + + DotNetUtil.Build(TempDir); + return (IOUtil.GetFiles(System.IO.Path.Combine(TempDir, "obj")), IOUtil.GetFiles(System.IO.Path.Combine(TempDir, "bin"))); + } + + private IEnumerable GetFilesAfterPublish() + { + // Publish depends on Build + _ = BinFilesAfterBuild; + + DotNetUtil.Publish(TempDir); + return IOUtil.GetFiles(System.IO.Path.Combine(TempDir, DotNetUtil.PublishOutput)); + } + + private (HttpResponseMessage Http, HttpResponseMessage Https) GetHttpResponsesAfterRun() + { + // Run depends on Build + _ = BinFilesAfterBuild; + + return GetHttpResponses(DotNetUtil.Run(TempDir)); + } + + private (HttpResponseMessage Http, HttpResponseMessage Https) GetHttpResponsesAfterExec() + { + // Exec depends on Publish + _ = FilesAfterPublish; + + return GetHttpResponses(DotNetUtil.Exec(TempDir, Name)); + } + + private (HttpResponseMessage Http, HttpResponseMessage Https) GetHttpResponses( + (Process Process, ConcurrentStringBuilder OutputBuilder, ConcurrentStringBuilder ErrorBuilder) process) + { + try + { + var (httpUrl, httpsUrl) = ScrapeUrls(process); + return (GetAsync(new Uri(new Uri(httpUrl), RelativeUrl)), GetAsync(new Uri(new Uri(httpsUrl), RelativeUrl))); + } + finally + { + DotNetUtil.StopProcess(process, throwOnError: false); + } + } + + private (string HttpUrl, string HttpsUrl) ScrapeUrls( + (Process Process, ConcurrentStringBuilder OutputBuilder, ConcurrentStringBuilder ErrorBuilder) process) + { + // Extract URLs from output + while (true) + { + var output = process.OutputBuilder.ToString(); + if (output.Contains("Application started")) + { + var httpUrl = Regex.Match(output, @"Now listening on: (http:\S*)").Groups[1].Value; + var httpsUrl = Regex.Match(output, @"Now listening on: (https:\S*)").Groups[1].Value; + return (httpUrl, httpsUrl); + } + else if (process.Process.HasExited) + { + var startInfo = process.Process.StartInfo; + throw new InvalidOperationException( + $"Failed to start process '{startInfo.FileName} {startInfo.Arguments}'" + Environment.NewLine + output); + } + else + { + Thread.Sleep(_sleepBetweenOutputContains); + } + } + } + + private HttpResponseMessage GetAsync(Uri requestUri) + { + while (true) + { + try + { + return _httpClient.GetAsync(requestUri).Result; + } + catch + { + Thread.Sleep(_sleepBetweenHttpRequests); + } + } + } } } diff --git a/AspNetCoreSdkTests/Templates/TemplateData.cs b/AspNetCoreSdkTests/Templates/TemplateData.cs deleted file mode 100644 index d4ba3e1376..0000000000 --- a/AspNetCoreSdkTests/Templates/TemplateData.cs +++ /dev/null @@ -1,42 +0,0 @@ -using NUnit.Framework; -using System; -using System.Collections.Generic; -using System.Linq; - -namespace AspNetCoreSdkTests.Templates -{ - public static class TemplateData - { - private static IEnumerable