diff --git a/eng/tools/HelixTestRunner/ProcessUtil.cs b/eng/tools/HelixTestRunner/ProcessUtil.cs index 1dc275987c6e052d22bfbd0ee2bf5489e5143e06..067850c4468e00623f0947f95c310961090d06df 100644 --- a/eng/tools/HelixTestRunner/ProcessUtil.cs +++ b/eng/tools/HelixTestRunner/ProcessUtil.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.Globalization; using System.IO; using System.Runtime.InteropServices; using System.Text; @@ -79,7 +80,7 @@ public static class ProcessUtil Action<int>? onStart = null, CancellationToken cancellationToken = default) { - Console.WriteLine($"Running '{filename} {arguments}'"); + PrintMessage($"Running '{filename} {arguments}'"); using var process = new Process() { StartInfo = @@ -151,7 +152,7 @@ public static class ProcessUtil process.Exited += (_, e) => { - Console.WriteLine($"'{process.StartInfo.FileName} {process.StartInfo.Arguments}' completed with exit code '{process.ExitCode}'"); + PrintMessage($"'{process.StartInfo.FileName} {process.StartInfo.Arguments}' completed with exit code '{process.ExitCode}'"); if (throwOnError && process.ExitCode != 0) { processLifetimeTask.TrySetException(new InvalidOperationException($"Command {filename} {arguments} returned exit code {process.ExitCode} output: {outputBuilder.ToString()}")); @@ -206,4 +207,7 @@ public static class ProcessUtil return await processLifetimeTask.Task; } + + public static void PrintMessage(string message) => Console.WriteLine($"{DateTime.UtcNow.ToString("O", CultureInfo.InvariantCulture)} {message}"); + public static void PrintErrorMessage(string message) => Console.Error.WriteLine($"{DateTime.UtcNow.ToString("O", CultureInfo.InvariantCulture)} {message}"); } diff --git a/eng/tools/HelixTestRunner/Program.cs b/eng/tools/HelixTestRunner/Program.cs index f388f76851eeeae27f590f1067e1a0ce645c4923..e2afbb13956a4b0db5eee7413e98254b4cdb7ccf 100644 --- a/eng/tools/HelixTestRunner/Program.cs +++ b/eng/tools/HelixTestRunner/Program.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.Globalization; using System.Threading.Tasks; namespace HelixTestRunner; @@ -28,7 +29,7 @@ class Program } else { - Console.WriteLine("Playwright install skipped."); + ProcessUtil.PrintMessage("Playwright install skipped."); } } @@ -38,23 +39,29 @@ class Program { if (!await runner.CheckTestDiscoveryAsync()) { - Console.WriteLine("RunTest stopping due to test discovery failure."); + ProcessUtil.PrintMessage("RunTest stopping due to test discovery failure."); Environment.Exit(1); return; } + ProcessUtil.PrintMessage("Start running tests"); var exitCode = await runner.RunTestsAsync(); + ProcessUtil.PrintMessage("Running tests complete"); + + ProcessUtil.PrintMessage("Uploading test results"); runner.UploadResults(); - Console.WriteLine($"Completed Helix job with exit code '{exitCode}'"); + ProcessUtil.PrintMessage("Test results uploaded"); + + ProcessUtil.PrintMessage($"Completed Helix job with exit code '{exitCode}'"); Environment.Exit(exitCode); } - Console.WriteLine("Tests were not run due to previous failures. Exit code=1"); + ProcessUtil.PrintMessage("Tests were not run due to previous failures. Exit code=1"); Environment.Exit(1); } catch (Exception e) { - Console.WriteLine($"HelixTestRunner uncaught exception: {e.ToString()}"); + ProcessUtil.PrintMessage($"HelixTestRunner uncaught exception: {e.ToString()}"); Environment.Exit(1); } } diff --git a/eng/tools/HelixTestRunner/TestRunner.cs b/eng/tools/HelixTestRunner/TestRunner.cs index 17cbf5b0ebda38a41794975ab27f5aea69876042..30285e90ee1e305a0d28627d89e0c2f045941779 100644 --- a/eng/tools/HelixTestRunner/TestRunner.cs +++ b/eng/tools/HelixTestRunner/TestRunner.cs @@ -22,33 +22,34 @@ public class TestRunner EnvironmentVariables.Add("PATH", Options.Path); EnvironmentVariables.Add("helix", Options.HelixQueue); - Console.WriteLine($"Current Directory: {Options.HELIX_WORKITEM_ROOT}"); + ProcessUtil.PrintMessage($"Current Directory: {Options.HELIX_WORKITEM_ROOT}"); var helixDir = Options.HELIX_WORKITEM_ROOT; - Console.WriteLine($"Setting HELIX_DIR: {helixDir}"); + ProcessUtil.PrintMessage($"Setting HELIX_DIR: {helixDir}"); EnvironmentVariables.Add("HELIX_DIR", helixDir); EnvironmentVariables.Add("NUGET_FALLBACK_PACKAGES", helixDir); var nugetRestore = Path.Combine(helixDir, "nugetRestore"); EnvironmentVariables.Add("NUGET_RESTORE", nugetRestore); var dotnetEFFullPath = Path.Combine(nugetRestore, helixDir, "dotnet-ef.exe"); - Console.WriteLine($"Set DotNetEfFullPath: {dotnetEFFullPath}"); + ProcessUtil.PrintMessage($"Set DotNetEfFullPath: {dotnetEFFullPath}"); EnvironmentVariables.Add("DotNetEfFullPath", dotnetEFFullPath); var dumpPath = Environment.GetEnvironmentVariable("HELIX_DUMP_FOLDER"); - Console.WriteLine($"Set VSTEST_DUMP_PATH: {dumpPath}"); + ProcessUtil.PrintMessage($"Set VSTEST_DUMP_PATH: {dumpPath}"); EnvironmentVariables.Add("VSTEST_DUMP_PATH", dumpPath); + EnvironmentVariables.Add("DOTNET_CLI_VSTEST_TRACE", "1"); if (Options.InstallPlaywright) { // Playwright will download and look for browsers to this directory var playwrightBrowsers = Environment.GetEnvironmentVariable("PLAYWRIGHT_BROWSERS_PATH"); - Console.WriteLine($"Setting PLAYWRIGHT_BROWSERS_PATH: {playwrightBrowsers}"); + ProcessUtil.PrintMessage($"Setting PLAYWRIGHT_BROWSERS_PATH: {playwrightBrowsers}"); EnvironmentVariables.Add("PLAYWRIGHT_BROWSERS_PATH", playwrightBrowsers); } else { - Console.WriteLine($"Skipping setting PLAYWRIGHT_BROWSERS_PATH"); + ProcessUtil.PrintMessage($"Skipping setting PLAYWRIGHT_BROWSERS_PATH"); } - Console.WriteLine($"Creating nuget restore directory: {nugetRestore}"); + ProcessUtil.PrintMessage($"Creating nuget restore directory: {nugetRestore}"); Directory.CreateDirectory(nugetRestore); // Rename default.runner.json to xunit.runner.json if there is not a custom one from the project @@ -66,7 +67,7 @@ public class TestRunner } catch (Exception e) { - Console.WriteLine($"Exception in SetupEnvironment: {e}"); + ProcessUtil.PrintMessage($"Exception in SetupEnvironment: {e}"); return false; } } @@ -76,20 +77,20 @@ public class TestRunner try { Console.WriteLine(); - Console.WriteLine($"Displaying directory contents for {path}:"); + ProcessUtil.PrintMessage($"Displaying directory contents for {path}:"); foreach (var file in Directory.EnumerateFiles(path)) { - Console.WriteLine(Path.GetFileName(file)); + ProcessUtil.PrintMessage(Path.GetFileName(file)); } foreach (var file in Directory.EnumerateDirectories(path)) { - Console.WriteLine(Path.GetFileName(file)); + ProcessUtil.PrintMessage(Path.GetFileName(file)); } Console.WriteLine(); } catch (Exception e) { - Console.WriteLine($"Exception in DisplayContents: {e}"); + ProcessUtil.PrintMessage($"Exception in DisplayContents: {e}"); } } @@ -97,7 +98,7 @@ public class TestRunner { try { - Console.WriteLine($"Installing Playwright Browsers to {Environment.GetEnvironmentVariable("PLAYWRIGHT_BROWSERS_PATH")}"); + ProcessUtil.PrintMessage($"Installing Playwright Browsers to {Environment.GetEnvironmentVariable("PLAYWRIGHT_BROWSERS_PATH")}"); var exitCode = Microsoft.Playwright.Program.Main(new[] { "install" }); @@ -106,7 +107,7 @@ public class TestRunner } catch (Exception e) { - Console.WriteLine($"Exception installing playwright: {e}"); + ProcessUtil.PrintMessage($"Exception installing playwright: {e}"); return false; } } @@ -127,30 +128,30 @@ public class TestRunner await ProcessUtil.RunAsync($"{Options.DotnetRoot}/dotnet", $"tool install dotnet-dump --tool-path {Options.HELIX_WORKITEM_ROOT} --add-source {correlationPayload}", environmentVariables: EnvironmentVariables, - outputDataReceived: Console.WriteLine, - errorDataReceived: Console.Error.WriteLine, + outputDataReceived: ProcessUtil.PrintMessage, + errorDataReceived: ProcessUtil.PrintErrorMessage, throwOnError: false, cancellationToken: new CancellationTokenSource(TimeSpan.FromMinutes(2)).Token); await ProcessUtil.RunAsync($"{Options.DotnetRoot}/dotnet", $"tool install dotnet-ef --tool-path {Options.HELIX_WORKITEM_ROOT} --add-source {correlationPayload}", environmentVariables: EnvironmentVariables, - outputDataReceived: Console.WriteLine, - errorDataReceived: Console.Error.WriteLine, + outputDataReceived: ProcessUtil.PrintMessage, + errorDataReceived: ProcessUtil.PrintErrorMessage, throwOnError: false, cancellationToken: new CancellationTokenSource(TimeSpan.FromMinutes(2)).Token); await ProcessUtil.RunAsync($"{Options.DotnetRoot}/dotnet", $"tool install dotnet-serve --tool-path {Options.HELIX_WORKITEM_ROOT} --add-source {correlationPayload}", environmentVariables: EnvironmentVariables, - outputDataReceived: Console.WriteLine, - errorDataReceived: Console.Error.WriteLine, + outputDataReceived: ProcessUtil.PrintMessage, + errorDataReceived: ProcessUtil.PrintErrorMessage, throwOnError: false, cancellationToken: new CancellationTokenSource(TimeSpan.FromMinutes(2)).Token); } catch (Exception e) { - Console.WriteLine($"Exception in InstallDotnetTools: {e}"); + ProcessUtil.PrintMessage($"Exception in InstallDotnetTools: {e}"); return false; } finally @@ -160,13 +161,13 @@ public class TestRunner try { - Console.WriteLine($"Adding current directory to nuget sources: {Options.HELIX_WORKITEM_ROOT}"); + ProcessUtil.PrintMessage($"Adding current directory to nuget sources: {Options.HELIX_WORKITEM_ROOT}"); await ProcessUtil.RunAsync($"{Options.DotnetRoot}/dotnet", $"nuget add source {Options.HELIX_WORKITEM_ROOT} --configfile {filename}", environmentVariables: EnvironmentVariables, - outputDataReceived: Console.WriteLine, - errorDataReceived: Console.Error.WriteLine, + outputDataReceived: ProcessUtil.PrintMessage, + errorDataReceived: ProcessUtil.PrintErrorMessage, throwOnError: false, cancellationToken: new CancellationTokenSource(TimeSpan.FromMinutes(2)).Token); @@ -174,14 +175,14 @@ public class TestRunner await ProcessUtil.RunAsync($"{Options.DotnetRoot}/dotnet", "nuget list source", environmentVariables: EnvironmentVariables, - outputDataReceived: Console.WriteLine, - errorDataReceived: Console.Error.WriteLine, + outputDataReceived: ProcessUtil.PrintMessage, + errorDataReceived: ProcessUtil.PrintErrorMessage, throwOnError: false, cancellationToken: new CancellationTokenSource(TimeSpan.FromMinutes(2)).Token); } catch (Exception e) { - Console.WriteLine($"Exception in InstallDotnetTools: {e}"); + ProcessUtil.PrintMessage($"Exception in InstallDotnetTools: {e}"); return false; } @@ -200,15 +201,15 @@ public class TestRunner if (discoveryResult.StandardOutput.Contains("Exception thrown")) { - Console.WriteLine("Exception thrown during test discovery."); - Console.WriteLine(discoveryResult.StandardOutput); + ProcessUtil.PrintMessage("Exception thrown during test discovery."); + ProcessUtil.PrintMessage(discoveryResult.StandardOutput); return false; } return true; } catch (Exception e) { - Console.WriteLine($"Exception in CheckTestDiscovery: {e}"); + ProcessUtil.PrintMessage($"Exception in CheckTestDiscovery: {e}"); return false; } } @@ -219,50 +220,60 @@ public class TestRunner try { // Timeout test run 5 minutes before the Helix job would timeout - var cts = new CancellationTokenSource(Options.Timeout.Subtract(TimeSpan.FromMinutes(5))); + var testProcessTimeout = Options.Timeout.Subtract(TimeSpan.FromMinutes(5)); + var cts = new CancellationTokenSource(testProcessTimeout); var diagLog = Path.Combine(Environment.GetEnvironmentVariable("HELIX_WORKITEM_UPLOAD_ROOT"), "vstest.log"); - var commonTestArgs = $"test {Options.Target} --diag:{diagLog} --logger:xunit --logger:\"console;verbosity=normal\" --blame \"CollectHangDump;TestTimeout=15m\""; + var commonTestArgs = $"test {Options.Target} --diag:{diagLog} --logger xunit --logger \"console;verbosity=normal\" " + + "--blame-crash --blame-hang-timeout 15m"; if (Options.Quarantined) { - Console.WriteLine("Running quarantined tests."); + ProcessUtil.PrintMessage("Running quarantined tests."); // Filter syntax: https://github.com/Microsoft/vstest-docs/blob/master/docs/filter.md var result = await ProcessUtil.RunAsync($"{Options.DotnetRoot}/dotnet", - commonTestArgs + " --TestCaseFilter:\"Quarantined=true\"", + commonTestArgs + " --filter \"Quarantined=true\"", environmentVariables: EnvironmentVariables, - outputDataReceived: Console.WriteLine, - errorDataReceived: Console.Error.WriteLine, + outputDataReceived: ProcessUtil.PrintMessage, + errorDataReceived: ProcessUtil.PrintErrorMessage, throwOnError: false, cancellationToken: cts.Token); + if (cts.Token.IsCancellationRequested) + { + ProcessUtil.PrintMessage($"Quarantined tests exceeded configured timeout: {testProcessTimeout.TotalMinutes}m."); + } if (result.ExitCode != 0) { - Console.WriteLine($"Failure in quarantined tests. Exit code: {result.ExitCode}."); + ProcessUtil.PrintMessage($"Failure in quarantined tests. Exit code: {result.ExitCode}."); } } else { - Console.WriteLine("Running non-quarantined tests."); + ProcessUtil.PrintMessage("Running non-quarantined tests."); // Filter syntax: https://github.com/Microsoft/vstest-docs/blob/master/docs/filter.md var result = await ProcessUtil.RunAsync($"{Options.DotnetRoot}/dotnet", - commonTestArgs + " --TestCaseFilter:\"Quarantined!=true|Quarantined=false\"", + commonTestArgs + " --filter \"Quarantined!=true|Quarantined=false\"", environmentVariables: EnvironmentVariables, - outputDataReceived: Console.WriteLine, - errorDataReceived: Console.Error.WriteLine, + outputDataReceived: ProcessUtil.PrintMessage, + errorDataReceived: ProcessUtil.PrintErrorMessage, throwOnError: false, cancellationToken: cts.Token); + if (cts.Token.IsCancellationRequested) + { + ProcessUtil.PrintMessage($"Non-quarantined tests exceeded configured timeout: {testProcessTimeout.TotalMinutes}m."); + } if (result.ExitCode != 0) { - Console.WriteLine($"Failure in non-quarantined tests. Exit code: {result.ExitCode}."); + ProcessUtil.PrintMessage($"Failure in non-quarantined tests. Exit code: {result.ExitCode}."); exitCode = result.ExitCode; } } } catch (Exception e) { - Console.WriteLine($"Exception in HelixTestRunner: {e}"); + ProcessUtil.PrintMessage($"Exception in HelixTestRunner: {e}"); exitCode = 1; } return exitCode; @@ -271,24 +282,24 @@ public class TestRunner public void UploadResults() { // 'testResults.xml' is the file Helix looks for when processing test results - Console.WriteLine("Trying to upload results..."); + ProcessUtil.PrintMessage("Trying to upload results..."); if (File.Exists("TestResults/TestResults.xml")) { - Console.WriteLine("Copying TestResults/TestResults.xml to ./testResults.xml"); + ProcessUtil.PrintMessage("Copying TestResults/TestResults.xml to ./testResults.xml"); File.Copy("TestResults/TestResults.xml", "testResults.xml", overwrite: true); } else { - Console.WriteLine("No test results found."); + ProcessUtil.PrintMessage("No test results found."); } var HELIX_WORKITEM_UPLOAD_ROOT = Environment.GetEnvironmentVariable("HELIX_WORKITEM_UPLOAD_ROOT"); if (string.IsNullOrEmpty(HELIX_WORKITEM_UPLOAD_ROOT)) { - Console.WriteLine("No HELIX_WORKITEM_UPLOAD_ROOT specified, skipping log copy"); + ProcessUtil.PrintMessage("No HELIX_WORKITEM_UPLOAD_ROOT specified, skipping log copy"); return; } - Console.WriteLine($"Copying artifacts/log/ to {HELIX_WORKITEM_UPLOAD_ROOT}/"); + ProcessUtil.PrintMessage($"Copying artifacts/log/ to {HELIX_WORKITEM_UPLOAD_ROOT}/"); if (Directory.Exists("artifacts/log")) { foreach (var file in Directory.EnumerateFiles("artifacts/log", "*.log", SearchOption.AllDirectories)) @@ -296,27 +307,27 @@ public class TestRunner // Combine the directory name + log name for the copied log file name to avoid overwriting // duplicate test names in different test projects var logName = $"{Path.GetFileName(Path.GetDirectoryName(file))}_{Path.GetFileName(file)}"; - Console.WriteLine($"Copying: {file} to {Path.Combine(HELIX_WORKITEM_UPLOAD_ROOT, logName)}"); + ProcessUtil.PrintMessage($"Copying: {file} to {Path.Combine(HELIX_WORKITEM_UPLOAD_ROOT, logName)}"); File.Copy(file, Path.Combine(HELIX_WORKITEM_UPLOAD_ROOT, logName)); } } else { - Console.WriteLine("No logs found in artifacts/log"); + ProcessUtil.PrintMessage("No logs found in artifacts/log"); } - Console.WriteLine($"Copying TestResults/**/Sequence*.xml to {HELIX_WORKITEM_UPLOAD_ROOT}/"); + ProcessUtil.PrintMessage($"Copying TestResults/**/Sequence*.xml to {HELIX_WORKITEM_UPLOAD_ROOT}/"); if (Directory.Exists("TestResults")) { foreach (var file in Directory.EnumerateFiles("TestResults", "Sequence*.xml", SearchOption.AllDirectories)) { var fileName = Path.GetFileName(file); - Console.WriteLine($"Copying: {file} to {Path.Combine(HELIX_WORKITEM_UPLOAD_ROOT, fileName)}"); + ProcessUtil.PrintMessage($"Copying: {file} to {Path.Combine(HELIX_WORKITEM_UPLOAD_ROOT, fileName)}"); File.Copy(file, Path.Combine(HELIX_WORKITEM_UPLOAD_ROOT, fileName)); } } else { - Console.WriteLine("No TestResults directory found."); + ProcessUtil.PrintMessage("No TestResults directory found."); } } } diff --git a/src/ProjectTemplates/BlazorTemplates.Tests/BlazorServerTemplateTest.cs b/src/ProjectTemplates/BlazorTemplates.Tests/BlazorServerTemplateTest.cs index 1a04fd77aa2bf61eae7e1a86b3ccc7a96cf630d1..073eaad776b20f43818c1709dab4f966876b5c5f 100644 --- a/src/ProjectTemplates/BlazorTemplates.Tests/BlazorServerTemplateTest.cs +++ b/src/ProjectTemplates/BlazorTemplates.Tests/BlazorServerTemplateTest.cs @@ -29,7 +29,7 @@ public class BlazorServerTemplateTest : BlazorTemplateTest [InlineData(BrowserKind.Chromium)] public async Task BlazorServerTemplateWorks_NoAuth(BrowserKind browserKind) { - var project = await CreateBuildPublishAsync("blazorservernoauth" + browserKind); + var project = await CreateBuildPublishAsync(); await using var browser = BrowserManager.IsAvailable(browserKind) ? await BrowserManager.GetBrowserInstance(browserKind, BrowserContextInfo) : @@ -83,9 +83,9 @@ public class BlazorServerTemplateTest : BlazorTemplateTest [Theory(Skip = "https://github.com/dotnet/aspnetcore/issues/30882")] [MemberData(nameof(BlazorServerTemplateWorks_IndividualAuthData))] [SkipOnHelix("https://github.com/dotnet/aspnetcore/issues/30825", Queues = "All.OSX")] - public async Task BlazorServerTemplateWorks_IndividualAuth(BrowserKind browserKind, bool useLocalDB) + public async Task BlazorServerTemplateWorks_IndividualAuth(BrowserKind browserKind) { - var project = await CreateBuildPublishAsync("blazorserverindividual" + browserKind + (useLocalDB ? "uld" : "")); + var project = await CreateBuildPublishAsync(); var browser = !BrowserManager.IsAvailable(browserKind) ? null : @@ -187,5 +187,5 @@ public class BlazorServerTemplateTest : BlazorTemplateTest [InlineData("SingleOrg", new string[] { "--called-api-url \"https://graph.microsoft.com\"", "--called-api-scopes user.readwrite" })] [InlineData("SingleOrg", new string[] { "--calls-graph" })] public Task BlazorServerTemplate_IdentityWeb_BuildAndPublish(string auth, string[] args) - => CreateBuildPublishAsync("blazorserveridweb" + Guid.NewGuid().ToString().Substring(0, 10).ToLowerInvariant(), auth, args); + => CreateBuildPublishAsync(auth, args); } diff --git a/src/ProjectTemplates/BlazorTemplates.Tests/BlazorTemplateTest.cs b/src/ProjectTemplates/BlazorTemplates.Tests/BlazorTemplateTest.cs index ef2af1f0719f30920c49693db4aae140ebd6527b..8ea4077007e80fc7718de2865e8460457b767d70 100644 --- a/src/ProjectTemplates/BlazorTemplates.Tests/BlazorTemplateTest.cs +++ b/src/ProjectTemplates/BlazorTemplates.Tests/BlazorTemplateTest.cs @@ -24,12 +24,12 @@ public abstract class BlazorTemplateTest : BrowserTestBase public abstract string ProjectType { get; } - protected async Task<Project> CreateBuildPublishAsync(string projectName, string auth = null, string[] args = null, string targetFramework = null, bool serverProject = false, bool onlyCreate = false) + protected async Task<Project> CreateBuildPublishAsync(string auth = null, string[] args = null, string targetFramework = null, bool serverProject = false, bool onlyCreate = false) { // Additional arguments are needed. See: https://github.com/dotnet/aspnetcore/issues/24278 Environment.SetEnvironmentVariable("EnableDefaultScopedCssItems", "true"); - var project = await ProjectFactory.GetOrCreateProject(projectName, Output); + var project = await ProjectFactory.CreateProject(Output); if (targetFramework != null) { project.TargetFramework = targetFramework; diff --git a/src/ProjectTemplates/BlazorTemplates.Tests/BlazorWasmTemplateTest.cs b/src/ProjectTemplates/BlazorTemplates.Tests/BlazorWasmTemplateTest.cs index 75107526b6c810e841708e0ea2bd2709bfbb0bae..a19b6596704e1f364cea0bed3edeccc0eb3be939 100644 --- a/src/ProjectTemplates/BlazorTemplates.Tests/BlazorWasmTemplateTest.cs +++ b/src/ProjectTemplates/BlazorTemplates.Tests/BlazorWasmTemplateTest.cs @@ -27,7 +27,7 @@ public class BlazorWasmTemplateTest : BlazorTemplateTest [InlineData(BrowserKind.Chromium)] public async Task BlazorWasmStandaloneTemplate_Works(BrowserKind browserKind) { - var project = await CreateBuildPublishAsync("blazorstandalone" + browserKind); + var project = await CreateBuildPublishAsync(); // The service worker assets manifest isn't generated for non-PWA projects var publishDir = Path.Combine(project.TemplatePublishDir, "wwwroot"); @@ -63,7 +63,7 @@ public class BlazorWasmTemplateTest : BlazorTemplateTest [InlineData(BrowserKind.Chromium)] public async Task BlazorWasmHostedTemplate_Works(BrowserKind browserKind) { - var project = await CreateBuildPublishAsync("blazorhosted" + BrowserKind.Chromium, args: new[] { "--hosted" }, serverProject: true); + var project = await CreateBuildPublishAsync(args: new[] { "--hosted" }, serverProject: true); var serverProject = GetSubProject(project, "Server", $"{project.ProjectName}.Server"); @@ -111,7 +111,7 @@ public class BlazorWasmTemplateTest : BlazorTemplateTest [InlineData(BrowserKind.Chromium)] public async Task BlazorWasmStandalonePwaTemplate_Works(BrowserKind browserKind) { - var project = await CreateBuildPublishAsync("blazorstandalonepwa", args: new[] { "--pwa" }); + var project = await CreateBuildPublishAsync(args: new[] { "--pwa" }); await BuildAndRunTest(project.ProjectName, project, browserKind); @@ -146,7 +146,7 @@ public class BlazorWasmTemplateTest : BlazorTemplateTest [InlineData(BrowserKind.Chromium)] public async Task BlazorWasmHostedPwaTemplate_Works(BrowserKind browserKind) { - var project = await CreateBuildPublishAsync("blazorhostedpwa", args: new[] { "--hosted", "--pwa" }, serverProject: true); + var project = await CreateBuildPublishAsync(args: new[] { "--hosted", "--pwa" }, serverProject: true); var serverProject = GetSubProject(project, "Server", $"{project.ProjectName}.Server"); @@ -226,13 +226,12 @@ public class BlazorWasmTemplateTest : BlazorTemplateTest public Task BlazorWasmHostedTemplate_IndividualAuth_Works_WithOutLocalDB(BrowserKind browserKind) => BlazorWasmHostedTemplate_IndividualAuth_Works(browserKind, false); - private async Task<Project> CreateBuildPublishIndividualAuthProject(BrowserKind browserKind, bool useLocalDb) + private async Task<Project> CreateBuildPublishIndividualAuthProject(bool useLocalDb) { // Additional arguments are needed. See: https://github.com/dotnet/aspnetcore/issues/24278 Environment.SetEnvironmentVariable("EnableDefaultScopedCssItems", "true"); - var project = await CreateBuildPublishAsync("blazorhostedindividual" + browserKind + (useLocalDb ? "uld" : ""), - args: new[] { "--hosted", "-au", "Individual", useLocalDb ? "-uld" : "" }); + var project = await CreateBuildPublishAsync(args: new[] { "--hosted", "-au", "Individual", useLocalDb ? "-uld" : "" }); var serverProject = GetSubProject(project, "Server", $"{project.ProjectName}.Server"); @@ -274,7 +273,7 @@ public class BlazorWasmTemplateTest : BlazorTemplateTest private async Task BlazorWasmHostedTemplate_IndividualAuth_Works(BrowserKind browserKind, bool useLocalDb) { - var project = await CreateBuildPublishIndividualAuthProject(browserKind, useLocalDb: useLocalDb); + var project = await CreateBuildPublishIndividualAuthProject(useLocalDb: useLocalDb); var serverProject = GetSubProject(project, "Server", $"{project.ProjectName}.Server"); @@ -376,7 +375,7 @@ public class BlazorWasmTemplateTest : BlazorTemplateTest [Theory(Skip = "https://github.com/dotnet/aspnetcore/issues/37782")] [MemberData(nameof(TemplateData))] public Task BlazorWasmHostedTemplate_AzureActiveDirectoryTemplate_Works(TemplateInstance instance) - => CreateBuildPublishAsync(instance.Name, args: instance.Arguments, targetFramework: "netstandard2.1"); + => CreateBuildPublishAsync(args: instance.Arguments, targetFramework: "netstandard2.1"); protected async Task BuildAndRunTest(string appName, Project project, BrowserKind browserKind, bool usesAuth = false) { diff --git a/src/ProjectTemplates/README.md b/src/ProjectTemplates/README.md index dc1615b5cb16206751e8459e2ae64ce8d63f7f4b..acb3b240a18fb566ff7a6880679938654570d035 100644 --- a/src/ProjectTemplates/README.md +++ b/src/ProjectTemplates/README.md @@ -36,7 +36,7 @@ To build the ProjectTemplates, use one of: ### Test -#### Running ProjectTemplate tests: +#### Running ProjectTemplate tests To run ProjectTemplate tests, first ensure the ASP.NET localhost development certificate is installed and trusted. Otherwise, you'll get a test error "Certificate error: Navigation blocked". @@ -62,6 +62,101 @@ Then, use one of: previous step, it is NOT advised that you install templates created on your local machine using just `dotnet new -i [nupkgPath]`. +#### Conditional tests & skipping test platforms + +Individual test methods can be decorated with attributes to configure them to not run ("skip running") on certain platforms. The `[ConditionalFact]` and `[ConditionalTheory]` attributes must be used on tests using the skip attributes in order for them to actually be skipped: + +``` csharp +[ConditionalFact] +[OSSkipCondition(OperatingSystems.Linux | OperatingSystems.MacOSX)] +[SkipOnHelix("cert failure", Queues = "All.OSX;" + HelixConstants.Windows10Arm64)] +public async Task MvcTemplate_SingleFileExe() +{ +``` + +An entire test project can be configured to skip specific platforms using the `<SkipHelixQueues>` property in the project's .csproj file, e.g.: + +```xml +<SkipHelixQueues> + $(HelixQueueArmDebian11); +</SkipHelixQueues> +``` + +Tests that are skipped should have details, or better yet link to an issue, explaining why they're being skipped, either as a string argument to the attribute or a code comment. + +#### Test timeouts + +When tests are run as part of the CI infrastructure, a number of different timeouts can impact whether tests pass or not. + +##### Helix job timeout + +When queuing test jobs to the Helix infrastructure, a timeout value is passed that the entire Helix job must complete within, i.e. that job running on a single queue. This default value is set in [eng\targets\Helix.props](eng/targets/Helix.props): + +```xml +<HelixTimeout>00:45:00</HelixTimeout> +``` + +This value is printed by the Helix runner at the beginning of the console log, formatted in seconds, e.g.: + +```log +Console log: 'ProjectTemplates.Tests--net7.0' from job b2f6fbe0-4dbe-45fa-a123-9a8c876d5923 (ubuntu.1804.armarch.open) using docker image mcr.microsoft.com/dotnet-buildtools/prereqs:debian-11-helix-arm64v8-20211001171229-97d8652 on ddvsotx2l137 +running $HELIX_CORRELATION_PAYLOAD/scripts/71557bd7f20e49fbbaa81cc79bd57fd6/execute.sh in /home/helixbot/work/C08609D9/w/B3D709E1/e max 2700 seconds +``` + +Note that some test projects might override this value in their project file and that some Helix queues are slower than others, so the same test job might complete within the timeout on one queue but exceed the timeout on another (the ARM queues are particularly prone to being slower than their AMD/Intel counterparts). + +##### Helix runner timeout + +The [Helix test runner](eng/tools/HelixTestRunner) launches the actual process that runs tests within a Helix job and when doing so configures its own timeout that is 5 minutes less than the Helix job timeout, e.g. if the Helix job timeout is 45 minutes, the Helix test runner process timeout will be 40 minutes. + +If this timeout is exceeded, the Helix test runner will capture a dump of the test process before terminating it and printing a message in the console log, e.g.: + +```log +2022-05-12T00:27:28.8279660Z Non-quarantined tests exceeded configured timeout: 40m. +``` + +##### Helix runner `dotnet test` timeout + +When running in Helix, a test hang timeout, e.g. `dotnet test --blame-hang-timeout 15m` , is configured in [eng\tools\HelixTestRunner\TestRunner.cs](eng/tools/HelixTestRunner/TestRunner.cs) + +```csharp +public async Task<int> RunTestsAsync() +{ + ... + var commonTestArgs = $"test {Options.Target} --diag:{diagLog} --logger xunit --logger \"console;verbosity=normal\" " + + "--blame-crash --blame-hang-timeout 15m"; +``` + +This timeout applies to each individual `[Fact]` or `[Theory]`. Note that for `[Theory]` this timeout is **not** reset for each instance of the theory, i.e. the entire `[Theory]` must run within the specified timeout. + +If this timeout is triggered, a message will be printed to the `vstest.datacollector.[date-time-stamp].log` file for the test run, e.g.: + +``` +19:54:18.888, 4653892436493, datacollector.dll, The specified inactivity time of 15 minutes has elapsed. Collecting hang dumps from testhost and its child processes +``` + +**Note:** It's a good idea to spread the number of cases for `[Theory]` tests across different test methods if the test method takes more than a few seconds to complete as this will help to keep the total `[Theory]` runtime less than the configured timeout, e.g.: + +``` csharp +[ConditionalTheory] +[SkipOnHelix("https://github.com/dotnet/aspnetcore/issues/28090", Queues = HelixConstants.Windows10Arm64 + HelixConstants.DebianArm64)] +[InlineData("IndividualB2C", null)] +[InlineData("IndividualB2C", new[] { ArgConstants.UseProgramMain })] +[InlineData("IndividualB2C", new[] { ArgConstants.CalledApiUrlGraphMicrosoftCom, ArgConstants.CalledApiScopesUserReadWrite })] +[InlineData("IndividualB2C", new[] { ArgConstants.UseProgramMain, ArgConstants.CalledApiUrlGraphMicrosoftCom, ArgConstants.CalledApiScopesUserReadWrite })] +public Task MvcTemplate_IdentityWeb_IndividualB2C_BuildsAndPublishes(string auth, string[] args) => MvcTemplateBuildsAndPublishes(auth: auth, args: args); + +[ConditionalTheory] +[SkipOnHelix("https://github.com/dotnet/aspnetcore/issues/28090", Queues = HelixConstants.Windows10Arm64 + HelixConstants.DebianArm64)] +[InlineData("SingleOrg", null)] +[InlineData("SingleOrg", new[] { ArgConstants.UseProgramMain })] +[InlineData("SingleOrg", new[] { ArgConstants.CalledApiUrlGraphMicrosoftCom, ArgConstants.CalledApiScopesUserReadWrite })] +[InlineData("SingleOrg", new[] { ArgConstants.UseProgramMain, ArgConstants.CalledApiUrlGraphMicrosoftCom, ArgConstants.CalledApiScopesUserReadWrite })] +[InlineData("SingleOrg", new[] { ArgConstants.CallsGraph })] +[InlineData("SingleOrg", new[] { ArgConstants.UseProgramMain, ArgConstants.CallsGraph })] +public Task MvcTemplate_IdentityWeb_SingleOrg_BuildsAndPublishes(string auth, string[] args) => MvcTemplateBuildsAndPublishes(auth: auth, args: args); +``` + ## More Information For more information, see the [ASP.NET Core README](../../README.md). diff --git a/src/ProjectTemplates/Shared/Project.cs b/src/ProjectTemplates/Shared/Project.cs index 4cf73e7ff9ca8b878cfdecea90768adba49428d0..07f27f07d03836f144812e6d7120137d2919debe 100644 --- a/src/ProjectTemplates/Shared/Project.cs +++ b/src/ProjectTemplates/Shared/Project.cs @@ -108,38 +108,24 @@ public class Project : IDisposable argString += $" -o {TemplateOutputDir}"; - // Only run one instance of 'dotnet new' at once, as a workaround for - // https://github.com/aspnet/templating/issues/63 - - await DotNetNewLock.WaitAsync(); - try + if (Directory.Exists(TemplateOutputDir)) { - Output.WriteLine("Acquired DotNetNewLock"); - - if (Directory.Exists(TemplateOutputDir)) - { - Output.WriteLine($"Template directory already exists, deleting contents of {TemplateOutputDir}"); - Directory.Delete(TemplateOutputDir, recursive: true); - } - - using var execution = ProcessEx.Run(Output, AppContext.BaseDirectory, DotNetMuxer.MuxerPathOrDefault(), argString, environmentVariables); - await execution.Exited; + Output.WriteLine($"Template directory already exists, deleting contents of {TemplateOutputDir}"); + Directory.Delete(TemplateOutputDir, recursive: true); + } - var result = new ProcessResult(execution); + using var execution = ProcessEx.Run(Output, AppContext.BaseDirectory, DotNetMuxer.MuxerPathOrDefault(), argString, environmentVariables); + await execution.Exited; - // Because dotnet new automatically restores but silently ignores restore errors, need to handle restore errors explicitly - if (errorOnRestoreError && (execution.Output.Contains("Restore failed.") || execution.Error.Contains("Restore failed."))) - { - result.ExitCode = -1; - } + var result = new ProcessResult(execution); - return result; - } - finally + // Because dotnet new automatically restores but silently ignores restore errors, need to handle restore errors explicitly + if (errorOnRestoreError && (execution.Output.Contains("Restore failed.") || execution.Error.Contains("Restore failed."))) { - DotNetNewLock.Release(); - Output.WriteLine("Released DotNetNewLock"); + result.ExitCode = -1; } + + return result; } internal async Task<ProcessResult> RunDotNetPublishAsync(IDictionary<string, string> packageOptions = null, string additionalArgs = null, bool noRestore = true) @@ -223,62 +209,38 @@ public class Project : IDisposable { var args = $"--verbose --no-build migrations add {migrationName}"; - // Only run one instance of 'dotnet new' at once, as a workaround for - // https://github.com/aspnet/templating/issues/63 - await DotNetNewLock.WaitAsync(); - try + var command = DotNetMuxer.MuxerPathOrDefault(); + if (string.IsNullOrEmpty(Environment.GetEnvironmentVariable("DotNetEfFullPath"))) { - Output.WriteLine("Acquired DotNetNewLock"); - var command = DotNetMuxer.MuxerPathOrDefault(); - if (string.IsNullOrEmpty(Environment.GetEnvironmentVariable("DotNetEfFullPath"))) - { - args = $"\"{DotNetEfFullPath}\" " + args; - } - else - { - command = "dotnet-ef"; - } - - using var result = ProcessEx.Run(Output, TemplateOutputDir, command, args); - await result.Exited; - return new ProcessResult(result); + args = $"\"{DotNetEfFullPath}\" " + args; } - finally + else { - DotNetNewLock.Release(); - Output.WriteLine("Released DotNetNewLock"); + command = "dotnet-ef"; } + + using var result = ProcessEx.Run(Output, TemplateOutputDir, command, args); + await result.Exited; + return new ProcessResult(result); } internal async Task<ProcessResult> RunDotNetEfUpdateDatabaseAsync() { var args = "--verbose --no-build database update"; - // Only run one instance of 'dotnet new' at once, as a workaround for - // https://github.com/aspnet/templating/issues/63 - await DotNetNewLock.WaitAsync(); - try + var command = DotNetMuxer.MuxerPathOrDefault(); + if (string.IsNullOrEmpty(Environment.GetEnvironmentVariable("DotNetEfFullPath"))) { - Output.WriteLine("Acquired DotNetNewLock"); - var command = DotNetMuxer.MuxerPathOrDefault(); - if (string.IsNullOrEmpty(Environment.GetEnvironmentVariable("DotNetEfFullPath"))) - { - args = $"\"{DotNetEfFullPath}\" " + args; - } - else - { - command = "dotnet-ef"; - } - - using var result = ProcessEx.Run(Output, TemplateOutputDir, command, args); - await result.Exited; - return new ProcessResult(result); + args = $"\"{DotNetEfFullPath}\" " + args; } - finally + else { - DotNetNewLock.Release(); - Output.WriteLine("Released DotNetNewLock"); + command = "dotnet-ef"; } + + using var result = ProcessEx.Run(Output, TemplateOutputDir, command, args); + await result.Exited; + return new ProcessResult(result); } // If this fails, you should generate new migrations via migrations/updateMigrations.cmd @@ -334,25 +296,15 @@ public class Project : IDisposable internal async Task<ProcessEx> RunDotNetNewRawAsync(string arguments) { - await DotNetNewLock.WaitAsync(); - try - { - Output.WriteLine("Acquired DotNetNewLock"); - var result = ProcessEx.Run( - Output, - AppContext.BaseDirectory, - DotNetMuxer.MuxerPathOrDefault(), - arguments + - $" --debug:disable-sdk-templates --debug:custom-hive \"{TemplatePackageInstaller.CustomHivePath}\"" + - $" -o {TemplateOutputDir}"); - await result.Exited; - return result; - } - finally - { - DotNetNewLock.Release(); - Output.WriteLine("Released DotNetNewLock"); - } + var result = ProcessEx.Run( + Output, + AppContext.BaseDirectory, + DotNetMuxer.MuxerPathOrDefault(), + arguments + + $" --debug:disable-sdk-templates --debug:custom-hive \"{TemplatePackageInstaller.CustomHivePath}\"" + + $" -o {TemplateOutputDir}"); + await result.Exited; + return result; } public void Dispose() diff --git a/src/ProjectTemplates/Shared/ProjectFactoryFixture.cs b/src/ProjectTemplates/Shared/ProjectFactoryFixture.cs index f16ef4dbc61941b78065870572e8e4b7202feaaf..0de11acd780e134b7d2d599691964cd6e1281317 100644 --- a/src/ProjectTemplates/Shared/ProjectFactoryFixture.cs +++ b/src/ProjectTemplates/Shared/ProjectFactoryFixture.cs @@ -24,42 +24,38 @@ public class ProjectFactoryFixture : IDisposable DiagnosticsMessageSink = diagnosticsMessageSink; } - public async Task<Project> GetOrCreateProject(string projectKey, ITestOutputHelper output) + public async Task<Project> CreateProject(ITestOutputHelper output) { await TemplatePackageInstaller.EnsureTemplatingEngineInitializedAsync(output); - // Different tests may have different output helpers, so need to fix up the output to write to the correct log - if (_projects.TryGetValue(projectKey, out var project)) + + var project = CreateProjectImpl(output); + + var projectKey = Guid.NewGuid().ToString().Substring(0, 10).ToLowerInvariant(); + if (!_projects.TryAdd(projectKey, project)) { - project.Output = output; - return project; + throw new InvalidOperationException($"Project key collision in {nameof(ProjectFactoryFixture)}.{nameof(CreateProject)}!"); } - return _projects.GetOrAdd( - projectKey, - (key, outputHelper) => - { - var project = new Project - { - Output = outputHelper, - DiagnosticsMessageSink = DiagnosticsMessageSink, - ProjectGuid = Path.GetRandomFileName().Replace(".", string.Empty) - }; - // Replace first character with a random letter if it's a digit to avoid random insertions of '_' - // into template namespace declarations (i.e. make it more stable for testing) - var projectNameSuffix = !char.IsLetter(project.ProjectGuid[0]) - ? string.Create(project.ProjectGuid.Length, project.ProjectGuid, (suffix, guid) => - { - guid.AsSpan(1).CopyTo(suffix[1..]); - suffix[0] = GetRandomLetter(); - }) - : project.ProjectGuid; - project.ProjectName = $"AspNet.{projectNameSuffix}"; - - var assemblyPath = GetType().Assembly; - var basePath = GetTemplateFolderBasePath(assemblyPath); - project.TemplateOutputDir = Path.Combine(basePath, project.ProjectName); - return project; - }, - output); + + return project; + } + + private Project CreateProjectImpl(ITestOutputHelper output) + { + var project = new Project + { + Output = output, + DiagnosticsMessageSink = DiagnosticsMessageSink, + // Ensure first character is a letter to avoid random insertions of '_' into template namespace + // declarations (i.e. make it more stable for testing) + ProjectGuid = GetRandomLetter() + Path.GetRandomFileName().Replace(".", string.Empty) + }; + project.ProjectName = $"AspNet.{project.ProjectGuid}"; + + var assemblyPath = GetType().Assembly; + var basePath = GetTemplateFolderBasePath(assemblyPath); + project.TemplateOutputDir = Path.Combine(basePath, project.ProjectName); + + return project; } private static char GetRandomLetter() => LetterChars[Random.Shared.Next(LetterChars.Length - 1)]; diff --git a/src/ProjectTemplates/Shared/TemplatePackageInstaller.cs b/src/ProjectTemplates/Shared/TemplatePackageInstaller.cs index e8d5ef5c84c62e02f9c901bfcd83a9db362d7927..a23b4f0351e8506c46554b3eb5e8645e33f57b45 100644 --- a/src/ProjectTemplates/Shared/TemplatePackageInstaller.cs +++ b/src/ProjectTemplates/Shared/TemplatePackageInstaller.cs @@ -53,24 +53,14 @@ internal static class TemplatePackageInstaller public static async Task EnsureTemplatingEngineInitializedAsync(ITestOutputHelper output) { - await ProcessLock.DotNetNewLock.WaitAsync(); - try + if (!_haveReinstalledTemplatePackages) { - output.WriteLine("Acquired DotNetNewLock"); - if (!_haveReinstalledTemplatePackages) + if (Directory.Exists(CustomHivePath)) { - if (Directory.Exists(CustomHivePath)) - { - Directory.Delete(CustomHivePath, recursive: true); - } - await InstallTemplatePackages(output); - _haveReinstalledTemplatePackages = true; + Directory.Delete(CustomHivePath, recursive: true); } - } - finally - { - ProcessLock.DotNetNewLock.Release(); - output.WriteLine("Released DotNetNewLock"); + await InstallTemplatePackages(output); + _haveReinstalledTemplatePackages = true; } } diff --git a/src/ProjectTemplates/test/ArgConstants.cs b/src/ProjectTemplates/test/ArgConstants.cs index 530b9d220a0493db3bd03bacdeae4011530b08e7..94a3b2965cce7374808c3f3c34b4086e032bca5b 100644 --- a/src/ProjectTemplates/test/ArgConstants.cs +++ b/src/ProjectTemplates/test/ArgConstants.cs @@ -23,4 +23,5 @@ internal static class ArgConstants public const string AppIdClientId = "--api-client-id"; public const string TenantId = "--tenant-id"; public const string AadB2cInstance = "--aad-b2c-instance"; + public const string UseLocalDb = "-uld"; } diff --git a/src/ProjectTemplates/test/AssemblyInfo.AssemblyFixtures.cs b/src/ProjectTemplates/test/AssemblyInfo.AssemblyFixtures.cs index 7e531ec57ea636cc134924468b591369fccc9bc4..b89b6eee9054bf3adc36cd094f4488153260a7ba 100644 --- a/src/ProjectTemplates/test/AssemblyInfo.AssemblyFixtures.cs +++ b/src/ProjectTemplates/test/AssemblyInfo.AssemblyFixtures.cs @@ -3,5 +3,7 @@ using Microsoft.AspNetCore.Testing; using Templates.Test.Helpers; +using Xunit; [assembly: AssemblyFixture(typeof(ProjectFactoryFixture))] +[assembly: CollectionBehavior(DisableTestParallelization = true)] diff --git a/src/ProjectTemplates/test/BaselineTest.cs b/src/ProjectTemplates/test/BaselineTest.cs index 73a5867204852a6537f19a6e7ded917f167620a1..57e4c5186bb2a71134622554eec2883120081415 100644 --- a/src/ProjectTemplates/test/BaselineTest.cs +++ b/src/ProjectTemplates/test/BaselineTest.cs @@ -72,7 +72,7 @@ public class BaselineTest : LoggedTest [MemberData(nameof(TemplateBaselines))] public async Task Template_Produces_The_Right_Set_Of_FilesAsync(string arguments, string[] expectedFiles) { - Project = await ProjectFactory.GetOrCreateProject(CreateProjectKey(arguments), Output); + Project = await ProjectFactory.CreateProject(Output); var createResult = await Project.RunDotNetNewRawAsync(arguments); Assert.True(createResult.ExitCode == 0, createResult.GetFormattedOutput()); @@ -105,7 +105,7 @@ public class BaselineTest : LoggedTest if (relativePath.EndsWith(".cs", StringComparison.Ordinal)) { var namespaceDeclarationPrefix = "namespace "; - var namespaceDeclaration = File.ReadLines(Path.Combine(Project.TemplateOutputDir, relativePath)) + var namespaceDeclaration = File.ReadLines(file) .SingleOrDefault(line => line.StartsWith(namespaceDeclarationPrefix, StringComparison.Ordinal)) ?.Substring(namespaceDeclarationPrefix.Length); @@ -118,65 +118,6 @@ public class BaselineTest : LoggedTest } } - private static ConcurrentDictionary<string, object> _projectKeys = new(); - - private string CreateProjectKey(string arguments) - { - var text = "baseline"; - - // Turn string like "new templatename -minimal -au SingleOrg --another-option OptionValue" - // into array like [ "new templatename", "minimal", "au SingleOrg", "another-option OptionValue" ] - var argumentsArray = arguments - .Split(new[] { " --", " -" }, StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries) - .ToArray(); - - // Add template name, value has form of "new name" - text += argumentsArray[0].Substring("new ".Length); - - // Sort arguments to ensure definitions that differ only by arguments order are caught - Array.Sort(argumentsArray, StringComparer.Ordinal); - - foreach (var argValue in argumentsArray) - { - var argSegments = argValue.Split(' ', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries); - - if (argSegments.Length == 0) - { - continue; - } - else if (argSegments.Length == 1) - { - text += argSegments[0] switch - { - "ho" => "hosted", - "p" => "pwa", - _ => argSegments[0].Replace("-", "") - }; - } - else - { - text += argSegments[0] switch - { - "au" => argSegments[1], - "uld" => "uld", - "language" => argSegments[1].Replace("#", "Sharp"), - "support-pages-and-views" when argSegments[1] == "true" => "supportpagesandviewstrue", - _ => "" - }; - } - } - - if (!_projectKeys.TryAdd(text, null)) - { - throw new InvalidOperationException( - $"Project key for template with args '{arguments}' already exists. " + - $"Check that the metadata specified in {BaselineDefinitionFileResourceName} is correct and that " + - $"the {nameof(CreateProjectKey)} method is considering enough template arguments to ensure uniqueness."); - } - - return text; - } - private void AssertFileExists(string basePath, string path, bool shouldExist) { var fullPath = Path.Combine(basePath, path); diff --git a/src/ProjectTemplates/test/BlazorServerTemplateTest.cs b/src/ProjectTemplates/test/BlazorServerTemplateTest.cs index 71984addf0fffe0ff3c5a3e6b403599cadeffac8..e6dc0bf87a4836f1fb30f01419fdec974fd58cc7 100644 --- a/src/ProjectTemplates/test/BlazorServerTemplateTest.cs +++ b/src/ProjectTemplates/test/BlazorServerTemplateTest.cs @@ -24,31 +24,36 @@ public class BlazorServerTemplateTest : BlazorTemplateTest public override string ProjectType { get; } = "blazorserver"; [Fact] - public Task BlazorServerTemplateWorks_NoAuth() => CreateBuildPublishAsync("blazorservernoauth"); + public Task BlazorServerTemplateWorks_NoAuth() => CreateBuildPublishAsync(); [Fact] - public Task BlazorServerTemplateWorks_ProgamMainNoAuth() => CreateBuildPublishAsync("blazorservernoauth", args: new[] { ArgConstants.UseProgramMain }); + public Task BlazorServerTemplateWorks_ProgamMainNoAuth() => CreateBuildPublishAsync(args: new[] { ArgConstants.UseProgramMain }); - [Theory] - [InlineData(true, null)] - [InlineData(true, new string[] { ArgConstants.UseProgramMain })] - [InlineData(false, null)] - [InlineData(false, new string[] { ArgConstants.UseProgramMain })] + [ConditionalTheory] + [InlineData("Individual", null)] + [InlineData("Individual", new string[] { ArgConstants.UseProgramMain })] [SkipOnHelix("https://github.com/dotnet/aspnetcore/issues/30825", Queues = "All.OSX")] - public Task BlazorServerTemplateWorks_IndividualAuth(bool useLocalDB, string[] args) => CreateBuildPublishAsync("blazorserverindividual" + (useLocalDB ? "uld" : "", args: args)); + public Task BlazorServerTemplateWorks_IndividualAuth(string auth, string[] args) => CreateBuildPublishAsync(auth, args: args); + + [ConditionalTheory] + [InlineData("Individual", new string[] { ArgConstants.UseLocalDb })] + [InlineData("Individual", new string[] { ArgConstants.UseProgramMain, ArgConstants.UseLocalDb })] + [OSSkipCondition(OperatingSystems.Linux | OperatingSystems.MacOSX, SkipReason = "No LocalDb on non-Windows")] + public Task BlazorServerTemplateWorks_IndividualAuth_LocalDb(string auth, string[] args) => CreateBuildPublishAsync(auth, args: args); [Theory] [InlineData("IndividualB2C", null)] [InlineData("IndividualB2C", new[] { ArgConstants.UseProgramMain })] [InlineData("IndividualB2C", new[] { ArgConstants.CalledApiUrlGraphMicrosoftCom, ArgConstants.CalledApiScopesUserReadWrite })] [InlineData("IndividualB2C", new[] { ArgConstants.UseProgramMain, ArgConstants.CalledApiUrlGraphMicrosoftCom, ArgConstants.CalledApiScopesUserReadWrite })] + public Task BlazorServerTemplate_IdentityWeb_BuildAndPublish_IndividualB2C(string auth, string[] args) => CreateBuildPublishAsync(auth, args); + + [Theory] [InlineData("SingleOrg", null)] [InlineData("SingleOrg", new[] { ArgConstants.UseProgramMain })] [InlineData("SingleOrg", new[] { ArgConstants.CalledApiUrlGraphMicrosoftCom, ArgConstants.CalledApiScopesUserReadWrite })] [InlineData("SingleOrg", new[] { ArgConstants.UseProgramMain, ArgConstants.CalledApiUrlGraphMicrosoftCom, ArgConstants.CalledApiScopesUserReadWrite })] [InlineData("SingleOrg", new[] { ArgConstants.CallsGraph })] [InlineData("SingleOrg", new[] { ArgConstants.UseProgramMain, ArgConstants.CallsGraph })] - public Task BlazorServerTemplate_IdentityWeb_BuildAndPublish(string auth, string[] args) - => CreateBuildPublishAsync("blazorserveridweb" + Guid.NewGuid().ToString().Substring(0, 10).ToLowerInvariant(), auth, args); - + public Task BlazorServerTemplate_IdentityWeb_BuildAndPublish_SingleOrg(string auth, string[] args) => CreateBuildPublishAsync(auth, args); } diff --git a/src/ProjectTemplates/test/BlazorTemplateTest.cs b/src/ProjectTemplates/test/BlazorTemplateTest.cs index 246a50288387233f4e79fa6aa76dc07fd1ff92e8..d0cf3717d2182902effc0ce0ec167b55c819324f 100644 --- a/src/ProjectTemplates/test/BlazorTemplateTest.cs +++ b/src/ProjectTemplates/test/BlazorTemplateTest.cs @@ -41,12 +41,12 @@ public abstract class BlazorTemplateTest : LoggedTest public abstract string ProjectType { get; } - protected async Task<Project> CreateBuildPublishAsync(string projectName, string auth = null, string[] args = null, string targetFramework = null, bool serverProject = false, bool onlyCreate = false) + protected async Task<Project> CreateBuildPublishAsync(string auth = null, string[] args = null, string targetFramework = null, bool serverProject = false, bool onlyCreate = false) { // Additional arguments are needed. See: https://github.com/dotnet/aspnetcore/issues/24278 Environment.SetEnvironmentVariable("EnableDefaultScopedCssItems", "true"); - var project = await ProjectFactory.GetOrCreateProject(projectName, Output); + var project = await ProjectFactory.CreateProject(Output); if (targetFramework != null) { project.TargetFramework = targetFramework; diff --git a/src/ProjectTemplates/test/BlazorWasmTemplateTest.cs b/src/ProjectTemplates/test/BlazorWasmTemplateTest.cs index 2c24bd1e0d8597ed62c671030652431d942f8f3a..1edca07fd8b8dc76aa43370702958f56e4c67b37 100644 --- a/src/ProjectTemplates/test/BlazorWasmTemplateTest.cs +++ b/src/ProjectTemplates/test/BlazorWasmTemplateTest.cs @@ -27,7 +27,7 @@ public class BlazorWasmTemplateTest : BlazorTemplateTest [Fact] public async Task BlazorWasmStandaloneTemplateCanCreateBuildPublish() { - var project = await CreateBuildPublishAsync("blazorstandalone"); + var project = await CreateBuildPublishAsync(); // The service worker assets manifest isn't generated for non-PWA projects var publishDir = Path.Combine(project.TemplatePublishDir, "wwwroot"); @@ -35,18 +35,18 @@ public class BlazorWasmTemplateTest : BlazorTemplateTest } [Fact] - public Task BlazorWasmHostedTemplateCanCreateBuildPublish() => CreateBuildPublishAsync("blazorhosted", args: new[] { ArgConstants.Hosted }, serverProject: true); + public Task BlazorWasmHostedTemplateCanCreateBuildPublish() => CreateBuildPublishAsync(args: new[] { ArgConstants.Hosted }, serverProject: true); [Fact] - public Task BlazorWasmHostedTemplateWithProgamMainCanCreateBuildPublish() => CreateBuildPublishAsync("blazorhosted", args: new[] { ArgConstants.UseProgramMain, ArgConstants.Hosted }, serverProject: true); + public Task BlazorWasmHostedTemplateWithProgamMainCanCreateBuildPublish() => CreateBuildPublishAsync(args: new[] { ArgConstants.UseProgramMain, ArgConstants.Hosted }, serverProject: true); [Fact] - public Task BlazorWasmStandalonePwaTemplateCanCreateBuildPublish() => CreateBuildPublishAsync("blazorstandalonepwa", args: new[] { ArgConstants.Pwa }); + public Task BlazorWasmStandalonePwaTemplateCanCreateBuildPublish() => CreateBuildPublishAsync(args: new[] { ArgConstants.Pwa }); [Fact] public async Task BlazorWasmHostedPwaTemplateCanCreateBuildPublish() { - var project = await CreateBuildPublishAsync("blazorhostedpwa", args: new[] { ArgConstants.Hosted, ArgConstants.Pwa }, serverProject: true); + var project = await CreateBuildPublishAsync(args: new[] { ArgConstants.Hosted, ArgConstants.Pwa }, serverProject: true); var serverProject = GetSubProject(project, "Server", $"{project.ProjectName}.Server"); @@ -105,8 +105,7 @@ public class BlazorWasmTemplateTest : BlazorTemplateTest // Additional arguments are needed. See: https://github.com/dotnet/aspnetcore/issues/24278 Environment.SetEnvironmentVariable("EnableDefaultScopedCssItems", "true"); - var project = await CreateBuildPublishAsync("blazorhostedindividual" + (useLocalDb ? "uld" : ""), - args: new[] { ArgConstants.Hosted, ArgConstants.Auth, "Individual", useLocalDb ? "-uld" : "", useProgramMain ? ArgConstants.UseProgramMain : "" }); + var project = await CreateBuildPublishAsync("Individual", args: new[] { ArgConstants.Hosted, useLocalDb ? "-uld" : "", useProgramMain ? ArgConstants.UseProgramMain : "" }); var serverProject = GetSubProject(project, "Server", $"{project.ProjectName}.Server"); @@ -150,9 +149,7 @@ public class BlazorWasmTemplateTest : BlazorTemplateTest [Fact] public async Task BlazorWasmStandaloneTemplate_IndividualAuth_CreateBuildPublish() { - var project = await CreateBuildPublishAsync("blazorstandaloneindividual", args: new[] { - ArgConstants.Auth, - "Individual", + var project = await CreateBuildPublishAsync("Individual", args: new[] { "--authority", "https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration", ArgConstants.ClientId, @@ -160,7 +157,7 @@ public class BlazorWasmTemplateTest : BlazorTemplateTest }); } - public static TheoryData<TemplateInstance> TemplateData => new TheoryData<TemplateInstance> + public static TheoryData<TemplateInstance> TemplateDataIndividualB2C => new TheoryData<TemplateInstance> { new TemplateInstance( "blazorwasmhostedaadb2c", "-ho", @@ -183,6 +180,25 @@ public class BlazorWasmTemplateTest : BlazorTemplateTest ArgConstants.AppIdUri, "ApiUri", ArgConstants.AppIdClientId, "1234123413241324", ArgConstants.UseProgramMain), + new TemplateInstance( + "blazorwasmstandaloneaadb2c", + ArgConstants.Auth, "IndividualB2C", + ArgConstants.AadB2cInstance, "example.b2clogin.com", + "-ssp", "b2c_1_siupin", + ArgConstants.ClientId, "clientId", + ArgConstants.Domain, "my-domain"), + new TemplateInstance( + "blazorwasmstandaloneaadb2c_program_main", + ArgConstants.Auth, "IndividualB2C", + ArgConstants.AadB2cInstance, "example.b2clogin.com", + "-ssp", "b2c_1_siupin", + ArgConstants.ClientId, "clientId", + ArgConstants.Domain, "my-domain", + ArgConstants.UseProgramMain), + }; + + public static TheoryData<TemplateInstance> TemplateDataSingleOrg => new TheoryData<TemplateInstance> + { new TemplateInstance( "blazorwasmhostedaad", "-ho", ArgConstants.Auth, "SingleOrg", @@ -193,19 +209,20 @@ public class BlazorWasmTemplateTest : BlazorTemplateTest ArgConstants.AppIdUri, "ApiUri", ArgConstants.AppIdClientId, "1234123413241324"), new TemplateInstance( - "blazorwasmhostedaad_program_main", "-ho", + "blazorwasmhostedaadgraph", "-ho", ArgConstants.Auth, "SingleOrg", + ArgConstants.CallsGraph, ArgConstants.Domain, "my-domain", ArgConstants.TenantId, "tenantId", ArgConstants.ClientId, "clientId", ArgConstants.DefaultScope, "full", ArgConstants.AppIdUri, "ApiUri", - ArgConstants.AppIdClientId, "1234123413241324", - ArgConstants.UseProgramMain), + ArgConstants.AppIdClientId, "1234123413241324"), new TemplateInstance( - "blazorwasmhostedaadgraph", "-ho", + "blazorwasmhostedaadapi", "-ho", ArgConstants.Auth, "SingleOrg", - ArgConstants.CallsGraph, + ArgConstants.CalledApiUrl, "\"https://graph.microsoft.com\"", + ArgConstants.CalledApiScopes, "user.readwrite", ArgConstants.Domain, "my-domain", ArgConstants.TenantId, "tenantId", ArgConstants.ClientId, "clientId", @@ -213,9 +230,18 @@ public class BlazorWasmTemplateTest : BlazorTemplateTest ArgConstants.AppIdUri, "ApiUri", ArgConstants.AppIdClientId, "1234123413241324"), new TemplateInstance( - "blazorwasmhostedaadgraph_program_main", "-ho", + "blazorwasmstandaloneaad", + ArgConstants.Auth, "SingleOrg", + ArgConstants.Domain, "my-domain", + ArgConstants.TenantId, "tenantId", + ArgConstants.ClientId, "clientId"), + }; + + public static TheoryData<TemplateInstance> TemplateDataSingleOrgProgramMain => new TheoryData<TemplateInstance> + { + new TemplateInstance( + "blazorwasmhostedaad_program_main", "-ho", ArgConstants.Auth, "SingleOrg", - ArgConstants.CallsGraph, ArgConstants.Domain, "my-domain", ArgConstants.TenantId, "tenantId", ArgConstants.ClientId, "clientId", @@ -224,16 +250,16 @@ public class BlazorWasmTemplateTest : BlazorTemplateTest ArgConstants.AppIdClientId, "1234123413241324", ArgConstants.UseProgramMain), new TemplateInstance( - "blazorwasmhostedaadapi", "-ho", + "blazorwasmhostedaadgraph_program_main", "-ho", ArgConstants.Auth, "SingleOrg", - ArgConstants.CalledApiUrl, "\"https://graph.microsoft.com\"", - ArgConstants.CalledApiScopes, "user.readwrite", + ArgConstants.CallsGraph, ArgConstants.Domain, "my-domain", ArgConstants.TenantId, "tenantId", ArgConstants.ClientId, "clientId", ArgConstants.DefaultScope, "full", ArgConstants.AppIdUri, "ApiUri", - ArgConstants.AppIdClientId, "1234123413241324"), + ArgConstants.AppIdClientId, "1234123413241324", + ArgConstants.UseProgramMain), new TemplateInstance( "blazorwasmhostedaadapi_program_main", "-ho", ArgConstants.Auth, "SingleOrg", @@ -246,27 +272,6 @@ public class BlazorWasmTemplateTest : BlazorTemplateTest ArgConstants.AppIdUri, "ApiUri", ArgConstants.AppIdClientId, "1234123413241324", ArgConstants.UseProgramMain), - new TemplateInstance( - "blazorwasmstandaloneaadb2c", - ArgConstants.Auth, "IndividualB2C", - ArgConstants.AadB2cInstance, "example.b2clogin.com", - "-ssp", "b2c_1_siupin", - ArgConstants.ClientId, "clientId", - ArgConstants.Domain, "my-domain"), - new TemplateInstance( - "blazorwasmstandaloneaadb2c_program_main", - ArgConstants.Auth, "IndividualB2C", - ArgConstants.AadB2cInstance, "example.b2clogin.com", - "-ssp", "b2c_1_siupin", - ArgConstants.ClientId, "clientId", - ArgConstants.Domain, "my-domain", - ArgConstants.UseProgramMain), - new TemplateInstance( - "blazorwasmstandaloneaad", - ArgConstants.Auth, "SingleOrg", - ArgConstants.Domain, "my-domain", - ArgConstants.TenantId, "tenantId", - ArgConstants.ClientId, "clientId"), new TemplateInstance( "blazorwasmstandaloneaad_program_main", ArgConstants.Auth, "SingleOrg", @@ -288,10 +293,20 @@ public class BlazorWasmTemplateTest : BlazorTemplateTest public string[] Arguments { get; } } - [Theory] - [MemberData(nameof(TemplateData))] - public Task BlazorWasmHostedTemplate_AzureActiveDirectoryTemplate_Works(TemplateInstance instance) - => CreateBuildPublishAsync(instance.Name, args: instance.Arguments, targetFramework: "netstandard2.1"); + [ConditionalTheory] + [MemberData(nameof(TemplateDataIndividualB2C))] + public Task BlazorWasmHostedTemplate_AzureActiveDirectoryTemplate_IndividualB2C_Works(TemplateInstance instance) + => CreateBuildPublishAsync(args: instance.Arguments, targetFramework: "netstandard2.1"); + + [ConditionalTheory] + [MemberData(nameof(TemplateDataSingleOrg))] + public Task BlazorWasmHostedTemplate_AzureActiveDirectoryTemplate_SingleOrg_Works(TemplateInstance instance) + => CreateBuildPublishAsync(args: instance.Arguments, targetFramework: "netstandard2.1"); + + [ConditionalTheory] + [MemberData(nameof(TemplateDataSingleOrgProgramMain))] + public Task BlazorWasmHostedTemplate_AzureActiveDirectoryTemplate_SingleOrg_ProgramMain_Works(TemplateInstance instance) + => CreateBuildPublishAsync(args: instance.Arguments, targetFramework: "netstandard2.1"); private string ReadFile(string basePath, string path) { diff --git a/src/ProjectTemplates/test/EmptyWebTemplateTest.cs b/src/ProjectTemplates/test/EmptyWebTemplateTest.cs index 60cf26af50a31f0cb5b79d5935d1aba2a02d3ce0..7be864efcd1e6751a6f4139ca23bcd79c9d4ccaf 100644 --- a/src/ProjectTemplates/test/EmptyWebTemplateTest.cs +++ b/src/ProjectTemplates/test/EmptyWebTemplateTest.cs @@ -53,7 +53,7 @@ public class EmptyWebTemplateTest : LoggedTest private async Task EmtpyTemplateCore(string languageOverride, string[] args = null) { - var project = await ProjectFactory.GetOrCreateProject("empty" + (languageOverride == "F#" ? "fsharp" : "csharp"), Output); + var project = await ProjectFactory.CreateProject(Output); var createResult = await project.RunDotNetNewAsync("web", args: args, language: languageOverride); Assert.True(0 == createResult.ExitCode, ErrorMessages.GetFailedProcessMessage("create/restore", project, createResult)); diff --git a/src/ProjectTemplates/test/GrpcTemplateTest.cs b/src/ProjectTemplates/test/GrpcTemplateTest.cs index edb8aa9338c5bd5fdb0e634a59bb40e9c01076b1..5f86925cc5040b479ae5f608b13291568ffd8d10 100644 --- a/src/ProjectTemplates/test/GrpcTemplateTest.cs +++ b/src/ProjectTemplates/test/GrpcTemplateTest.cs @@ -41,7 +41,7 @@ public class GrpcTemplateTest : LoggedTest [InlineData(false)] public async Task GrpcTemplate(bool useProgramMain) { - var project = await ProjectFactory.GetOrCreateProject("grpc", Output); + var project = await ProjectFactory.CreateProject(Output); var args = useProgramMain ? new[] { ArgConstants.UseProgramMain } : null; var createResult = await project.RunDotNetNewAsync("grpc", args: args); diff --git a/src/ProjectTemplates/test/IdentityUIPackageTest.cs b/src/ProjectTemplates/test/IdentityUIPackageTest.cs index ed081c459a050a17b832887109e6a1051540ed22..ee4f7fac4583503b4b14ac38b4f14d956c3f1be0 100644 --- a/src/ProjectTemplates/test/IdentityUIPackageTest.cs +++ b/src/ProjectTemplates/test/IdentityUIPackageTest.cs @@ -102,7 +102,7 @@ public class IdentityUIPackageTest : LoggedTest public async Task IdentityUIPackage_WorksWithDifferentOptions() { var packageOptions = new Dictionary<string, string>(); - var project = await ProjectFactory.GetOrCreateProject("identityuipackage" + string.Concat(packageOptions.Values), Output); + var project = await ProjectFactory.CreateProject(Output); var useLocalDB = false; var createResult = await project.RunDotNetNewAsync("razor", auth: "Individual", useLocalDB: useLocalDB, environmentVariables: packageOptions); diff --git a/src/ProjectTemplates/test/ItemTemplateTests/BlazorServerTests.cs b/src/ProjectTemplates/test/ItemTemplateTests/BlazorServerTests.cs index b2728d7f960b87baef15af1a306fecd23d119244..29c3a3e2cd6e44fa9d2456494a9a344ff69cb54f 100644 --- a/src/ProjectTemplates/test/ItemTemplateTests/BlazorServerTests.cs +++ b/src/ProjectTemplates/test/ItemTemplateTests/BlazorServerTests.cs @@ -25,7 +25,7 @@ public class BlazorServerTest [Fact] public async Task BlazorServerItemTemplate() { - Project = await ProjectFactory.GetOrCreateProject("razorcomponentitem", Output); + Project = await ProjectFactory.CreateProject(Output); var createResult = await Project.RunDotNetNewAsync("razorcomponent --name Different"); Assert.True(0 == createResult.ExitCode, ErrorMessages.GetFailedProcessMessage("create", Project, createResult)); diff --git a/src/ProjectTemplates/test/MvcTemplateTest.cs b/src/ProjectTemplates/test/MvcTemplateTest.cs index 1d3d2876aad6903a0576ca7d8e07b6b3e0c9cbcc..6eb289af348733fdd27d39fbd21c80317ebb9ccf 100644 --- a/src/ProjectTemplates/test/MvcTemplateTest.cs +++ b/src/ProjectTemplates/test/MvcTemplateTest.cs @@ -42,7 +42,7 @@ public class MvcTemplateTest : LoggedTest private async Task MvcTemplateCore(string languageOverride, string[] args = null) { - var project = await ProjectFactory.GetOrCreateProject("mvcnoauth" + (languageOverride == "F#" ? "fsharp" : "csharp"), Output); + var project = await ProjectFactory.CreateProject(Output); var createResult = await project.RunDotNetNewAsync("mvc", language: languageOverride, args: args); Assert.True(0 == createResult.ExitCode, ErrorMessages.GetFailedProcessMessage("create/restore", project, createResult)); @@ -113,14 +113,21 @@ public class MvcTemplateTest : LoggedTest } [ConditionalTheory] - [InlineData(true, false)] - [InlineData(true, true)] - [InlineData(false, false)] - [InlineData(false, true)] + [InlineData(false)] + [InlineData(true)] [SkipOnHelix("Cert failure, https://github.com/dotnet/aspnetcore/issues/28090", Queues = "All.OSX;" + HelixConstants.Windows10Arm64 + HelixConstants.DebianArm64 + HelixConstants.DebianAmd64)] - public async Task MvcTemplate_IndividualAuth(bool useLocalDB, bool useProgramMain) + [OSSkipCondition(OperatingSystems.Linux | OperatingSystems.MacOSX, SkipReason = "No LocalDb on non-Windows")] + public Task MvcTemplate_IndividualAuth_LocalDb(bool useProgramMain) => MvcTemplate_IndividualAuth_Core(useLocalDB: true, useProgramMain); + + [ConditionalTheory] + [InlineData(false)] + [InlineData(true)] + [SkipOnHelix("Cert failure, https://github.com/dotnet/aspnetcore/issues/28090", Queues = "All.OSX;" + HelixConstants.Windows10Arm64 + HelixConstants.DebianArm64 + HelixConstants.DebianAmd64)] + public Task MvcTemplate_IndividualAuth(bool useProgramMain) => MvcTemplate_IndividualAuth_Core(useLocalDB: false, useProgramMain); + + private async Task MvcTemplate_IndividualAuth_Core(bool useLocalDB, bool useProgramMain) { - var project = await ProjectFactory.GetOrCreateProject("mvcindividual" + (useLocalDB ? "uld" : ""), Output); + var project = await ProjectFactory.CreateProject(Output); var args = useProgramMain ? new[] { ArgConstants.UseProgramMain } : null; var createResult = await project.RunDotNetNewAsync("mvc", auth: "Individual", useLocalDB: useLocalDB, args: args); @@ -242,7 +249,7 @@ public class MvcTemplateTest : LoggedTest // This test verifies publishing an MVC app as a single file exe works. We'll limit testing // this to a few operating systems to make our lives easier. var runtimeIdentifer = "win-x64"; - var project = await ProjectFactory.GetOrCreateProject("mvcsinglefileexe", Output); + var project = await ProjectFactory.CreateProject(Output); project.RuntimeIdentifier = runtimeIdentifer; var createResult = await project.RunDotNetNewAsync("mvc"); @@ -288,17 +295,21 @@ public class MvcTemplateTest : LoggedTest [InlineData("IndividualB2C", new[] { ArgConstants.UseProgramMain })] [InlineData("IndividualB2C", new[] { ArgConstants.CalledApiUrlGraphMicrosoftCom, ArgConstants.CalledApiScopesUserReadWrite })] [InlineData("IndividualB2C", new[] { ArgConstants.UseProgramMain, ArgConstants.CalledApiUrlGraphMicrosoftCom, ArgConstants.CalledApiScopesUserReadWrite })] + public Task MvcTemplate_IdentityWeb_IndividualB2C_BuildsAndPublishes(string auth, string[] args) => MvcTemplateBuildsAndPublishes(auth: auth, args: args); + + [ConditionalTheory] + [SkipOnHelix("https://github.com/dotnet/aspnetcore/issues/28090", Queues = HelixConstants.Windows10Arm64 + HelixConstants.DebianArm64)] [InlineData("SingleOrg", null)] [InlineData("SingleOrg", new[] { ArgConstants.UseProgramMain })] [InlineData("SingleOrg", new[] { ArgConstants.CalledApiUrlGraphMicrosoftCom, ArgConstants.CalledApiScopesUserReadWrite })] [InlineData("SingleOrg", new[] { ArgConstants.UseProgramMain, ArgConstants.CalledApiUrlGraphMicrosoftCom, ArgConstants.CalledApiScopesUserReadWrite })] [InlineData("SingleOrg", new[] { ArgConstants.CallsGraph })] [InlineData("SingleOrg", new[] { ArgConstants.UseProgramMain, ArgConstants.CallsGraph })] - public Task MvcTemplate_IdentityWeb_BuildsAndPublishes(string auth, string[] args) => MvcTemplateBuildsAndPublishes(auth: auth, args: args); + public Task MvcTemplate_IdentityWeb_SingleOrg_BuildsAndPublishes(string auth, string[] args) => MvcTemplateBuildsAndPublishes(auth: auth, args: args); private async Task<Project> MvcTemplateBuildsAndPublishes(string auth, string[] args) { - var project = await ProjectFactory.GetOrCreateProject("mvc" + Guid.NewGuid().ToString().Substring(0, 10).ToLowerInvariant(), Output); + var project = await ProjectFactory.CreateProject(Output); var createResult = await project.RunDotNetNewAsync("mvc", auth: auth, args: args); Assert.True(0 == createResult.ExitCode, ErrorMessages.GetFailedProcessMessage("create/restore", project, createResult)); diff --git a/src/ProjectTemplates/test/ProjectTemplates.Tests.csproj b/src/ProjectTemplates/test/ProjectTemplates.Tests.csproj index e2a0fad6061412af76ce662daaba2f55ffa7072d..9a477c21a35dbca2d09c6508f7c118e1e33d281a 100644 --- a/src/ProjectTemplates/test/ProjectTemplates.Tests.csproj +++ b/src/ProjectTemplates/test/ProjectTemplates.Tests.csproj @@ -7,7 +7,7 @@ <TestGroupName>ProjectTemplates.E2ETests</TestGroupName> <DefineConstants>$(DefineConstants);XPLAT</DefineConstants> - <RunTemplateTests Condition="'$(RunTemplateTests)' == ''" >true</RunTemplateTests> + <RunTemplateTests Condition="'$(RunTemplateTests)' == ''">true</RunTemplateTests> <SkipTests Condition="'$(RunTemplateTests)' != 'true'">true</SkipTests> <BaseOutputPath /> @@ -18,6 +18,9 @@ <TestTemplateCreationFolder>TestTemplates\</TestTemplateCreationFolder> <TestPackageRestorePath>$([MSBuild]::EnsureTrailingSlash('$(RepoRoot)'))obj\template-restore\</TestPackageRestorePath> <TestDependsOnAspNetPackages>true</TestDependsOnAspNetPackages> + <SkipHelixQueues> + $(HelixQueueArmDebian11); + </SkipHelixQueues> </PropertyGroup> <ItemGroup> diff --git a/src/ProjectTemplates/test/RazorClassLibraryTemplateTest.cs b/src/ProjectTemplates/test/RazorClassLibraryTemplateTest.cs index f3360f62e28d52ccc38f2c1fc94c5a1885f8c67d..7675499fd05582187ed7a6b25f8bdd3a46726a09 100644 --- a/src/ProjectTemplates/test/RazorClassLibraryTemplateTest.cs +++ b/src/ProjectTemplates/test/RazorClassLibraryTemplateTest.cs @@ -33,7 +33,7 @@ public class RazorClassLibraryTemplateTest : LoggedTest [Fact] public async Task RazorClassLibraryTemplate_WithViews_Async() { - var project = await ProjectFactory.GetOrCreateProject("razorclasslibwithviews", Output); + var project = await ProjectFactory.CreateProject(Output); var createResult = await project.RunDotNetNewAsync("razorclasslib", args: new[] { "--support-pages-and-views", "true" }); Assert.True(0 == createResult.ExitCode, ErrorMessages.GetFailedProcessMessage("create/restore", project, createResult)); @@ -53,7 +53,7 @@ public class RazorClassLibraryTemplateTest : LoggedTest [SkipOnHelix("https://github.com/dotnet/aspnetcore/issues/28090", Queues = HelixConstants.Windows10Arm64 + HelixConstants.DebianArm64)] public async Task RazorClassLibraryTemplateAsync() { - var project = await ProjectFactory.GetOrCreateProject("razorclasslib", Output); + var project = await ProjectFactory.CreateProject(Output); var createResult = await project.RunDotNetNewAsync("razorclasslib"); Assert.True(0 == createResult.ExitCode, ErrorMessages.GetFailedProcessMessage("create/restore", project, createResult)); diff --git a/src/ProjectTemplates/test/RazorPagesTemplateTest.cs b/src/ProjectTemplates/test/RazorPagesTemplateTest.cs index f6e2c61779294ae60d69cfb01cd4c5547a9841ae..8d1d258bc42adc826b792266c1ae84b94f94f6b0 100644 --- a/src/ProjectTemplates/test/RazorPagesTemplateTest.cs +++ b/src/ProjectTemplates/test/RazorPagesTemplateTest.cs @@ -40,7 +40,7 @@ public class RazorPagesTemplateTest : LoggedTest [InlineData(false)] public async Task RazorPagesTemplate_NoAuth(bool useProgramMain) { - var project = await ProjectFactory.GetOrCreateProject("razorpagesnoauth", Output); + var project = await ProjectFactory.CreateProject(Output); var args = useProgramMain ? new[] { ArgConstants.UseProgramMain } : null; var createResult = await project.RunDotNetNewAsync("razor", args: args); @@ -107,14 +107,21 @@ public class RazorPagesTemplateTest : LoggedTest } [ConditionalTheory] - [InlineData(false, false)] - [InlineData(false, true)] - [InlineData(true, false)] - [InlineData(true, true)] - [SkipOnHelix("Cert failure, https://github.com/dotnet/aspnetcore/issues/28090", Queues = "All.OSX;" + HelixConstants.Windows10Arm64 + HelixConstants.DebianArm64)] - public async Task RazorPagesTemplate_IndividualAuth(bool useLocalDB, bool useProgramMain) + [InlineData(false)] + [InlineData(true)] + [SkipOnHelix("Cert failure, https://github.com/dotnet/aspnetcore/issues/28090", Queues = "All.OSX;" + HelixConstants.Windows10Arm64 + HelixConstants.DebianArm64 + HelixConstants.DebianAmd64)] + [OSSkipCondition(OperatingSystems.Linux | OperatingSystems.MacOSX, SkipReason = "No LocalDb on non-Windows")] + public Task RazorPagesTemplate_IndividualAuth_LocalDb(bool useProgramMain) => RazorPagesTemplate_IndividualAuth_Core(useLocalDB: true, useProgramMain); + + [ConditionalTheory] + [InlineData(false)] + [InlineData(true)] + [SkipOnHelix("Cert failure, https://github.com/dotnet/aspnetcore/issues/28090", Queues = "All.OSX;" + HelixConstants.Windows10Arm64 + HelixConstants.DebianArm64 + HelixConstants.DebianAmd64)] + public Task RazorPagesTemplate_IndividualAuth(bool useProgramMain) => RazorPagesTemplate_IndividualAuth_Core(useLocalDB: false, useProgramMain); + + private async Task RazorPagesTemplate_IndividualAuth_Core(bool useLocalDB, bool useProgramMain) { - var project = await ProjectFactory.GetOrCreateProject("razorpagesindividual" + (useLocalDB ? "uld" : ""), Output); + var project = await ProjectFactory.CreateProject(Output); var args = useProgramMain ? new[] { ArgConstants.UseProgramMain } : null; var createResult = await project.RunDotNetNewAsync("razor", auth: "Individual", useLocalDB: useLocalDB, args: args); @@ -234,20 +241,24 @@ public class RazorPagesTemplateTest : LoggedTest [InlineData("IndividualB2C", new[] { ArgConstants.UseProgramMain })] [InlineData("IndividualB2C", new[] { ArgConstants.CalledApiUrlGraphMicrosoftCom, ArgConstants.CalledApiScopesUserReadWrite })] [InlineData("IndividualB2C", new[] { ArgConstants.UseProgramMain, ArgConstants.CalledApiUrlGraphMicrosoftCom, ArgConstants.CalledApiScopesUserReadWrite })] + public Task RazorPagesTemplate_IdentityWeb_IndividualB2C_BuildsAndPublishes(string auth, string[] args) => BuildAndPublishRazorPagesTemplate(auth: auth, args: args); + + [ConditionalTheory] + [SkipOnHelix("https://github.com/dotnet/aspnetcore/issues/28090", Queues = HelixConstants.Windows10Arm64 + HelixConstants.DebianArm64)] [InlineData("SingleOrg", null)] [InlineData("SingleOrg", new[] { ArgConstants.UseProgramMain })] [InlineData("SingleOrg", new[] { ArgConstants.CalledApiUrlGraphMicrosoftCom, ArgConstants.CalledApiScopesUserReadWrite })] [InlineData("SingleOrg", new[] { ArgConstants.UseProgramMain, ArgConstants.CalledApiUrlGraphMicrosoftCom, ArgConstants.CalledApiScopesUserReadWrite })] - public Task RazorPagesTemplate_IdentityWeb_BuildsAndPublishes(string auth, string[] args) => BuildAndPublishRazorPagesTemplate(auth: auth, args: args); + public Task RazorPagesTemplate_IdentityWeb_SingleOrg_BuildsAndPublishes(string auth, string[] args) => BuildAndPublishRazorPagesTemplate(auth: auth, args: args); [ConditionalTheory] [InlineData("SingleOrg", new[] { ArgConstants.CallsGraph })] [InlineData("SingleOrg", new[] { ArgConstants.UseProgramMain, ArgConstants.CallsGraph })] - public Task RazorPagesTemplate_IdentityWeb_BuildsAndPublishes_WithSingleOrg(string auth, string[] args) => BuildAndPublishRazorPagesTemplate(auth: auth, args: args); + public Task RazorPagesTemplate_IdentityWeb_SingleOrg_CallsGraph_BuildsAndPublishes(string auth, string[] args) => BuildAndPublishRazorPagesTemplate(auth: auth, args: args); private async Task<Project> BuildAndPublishRazorPagesTemplate(string auth, string[] args) { - var project = await ProjectFactory.GetOrCreateProject("razorpages" + Guid.NewGuid().ToString().Substring(0, 10).ToLowerInvariant(), Output); + var project = await ProjectFactory.CreateProject(Output); var createResult = await project.RunDotNetNewAsync("razor", auth: auth, args: args); Assert.True(0 == createResult.ExitCode, ErrorMessages.GetFailedProcessMessage("create/restore", project, createResult)); diff --git a/src/ProjectTemplates/test/SpaTemplatesTest.cs b/src/ProjectTemplates/test/SpaTemplatesTest.cs index 76c77ad07fe89430fc8d366e226ffc551e7ae46f..f067923b0376d395a040ea39a2515410d6e1db25 100644 --- a/src/ProjectTemplates/test/SpaTemplatesTest.cs +++ b/src/ProjectTemplates/test/SpaTemplatesTest.cs @@ -35,13 +35,13 @@ public class SpaTemplatesTest : LoggedTest } [Theory] - [InlineData("angularind", "angular", "Individual")] - [InlineData("reactind", "react", "Individual")] - [InlineData("angularnoauth", "angular", null)] - [InlineData("reactnoauth", "react", null)] - public async Task SpaTemplates_BuildAndPublish(string projectKey, string template, string auth) + [InlineData("angular", "Individual")] + [InlineData("react", "Individual")] + [InlineData("angular", null)] + [InlineData("react", null)] + public async Task SpaTemplates_BuildAndPublish(string template, string auth) { - var project = await ProjectFactory.GetOrCreateProject(projectKey, Output); + var project = await ProjectFactory.CreateProject(Output); var args = new[] { "--NoSpaFrontEnd", "true" }; var createResult = await project.RunDotNetNewAsync(template, auth: auth, args: args); Assert.True(0 == createResult.ExitCode, ErrorMessages.GetFailedProcessMessage(template, project, createResult)); diff --git a/src/ProjectTemplates/test/WebApiTemplateTest.cs b/src/ProjectTemplates/test/WebApiTemplateTest.cs index 4840e8cee0306b5443be038d8400543c85a65572..778e11d99ff5e284f241d009f198e146bfbd35e0 100644 --- a/src/ProjectTemplates/test/WebApiTemplateTest.cs +++ b/src/ProjectTemplates/test/WebApiTemplateTest.cs @@ -35,26 +35,40 @@ public class WebApiTemplateTest : LoggedTest [ConditionalTheory] [SkipOnHelix("https://github.com/dotnet/aspnetcore/issues/28090", Queues = HelixConstants.Windows10Arm64 + HelixConstants.DebianArm64)] [InlineData("IndividualB2C", null)] - [InlineData("IndividualB2C", new string[] { ArgConstants.UseProgramMain })] [InlineData("IndividualB2C", new string[] { ArgConstants.UseMinimalApis })] - [InlineData("IndividualB2C", new string[] { ArgConstants.UseProgramMain, ArgConstants.UseMinimalApis })] [InlineData("IndividualB2C", new string[] { ArgConstants.CalledApiUrlGraphMicrosoftCom, ArgConstants.CalledApiScopesUserReadWrite })] [InlineData("IndividualB2C", new string[] { ArgConstants.UseMinimalApis, ArgConstants.CalledApiUrlGraphMicrosoftCom, ArgConstants.CalledApiScopesUserReadWrite })] + public Task WebApiTemplateCSharp_IdentityWeb_IndividualB2C_BuildsAndPublishes(string auth, string[] args) => PublishAndBuildWebApiTemplate(languageOverride: null, auth: auth, args: args); + + [ConditionalTheory] + [SkipOnHelix("https://github.com/dotnet/aspnetcore/issues/28090", Queues = HelixConstants.Windows10Arm64 + HelixConstants.DebianArm64)] + [InlineData("IndividualB2C", null)] + [InlineData("IndividualB2C", new string[] { ArgConstants.UseProgramMain })] + [InlineData("IndividualB2C", new string[] { ArgConstants.UseProgramMain, ArgConstants.UseMinimalApis })] [InlineData("IndividualB2C", new string[] { ArgConstants.UseProgramMain, ArgConstants.CalledApiUrlGraphMicrosoftCom, ArgConstants.CalledApiScopesUserReadWrite })] [InlineData("IndividualB2C", new string[] { ArgConstants.UseProgramMain, ArgConstants.UseMinimalApis, ArgConstants.CalledApiUrlGraphMicrosoftCom, ArgConstants.CalledApiScopesUserReadWrite })] + public Task WebApiTemplateCSharp_IdentityWeb_IndividualB2C_ProgramMain_BuildsAndPublishes(string auth, string[] args) => PublishAndBuildWebApiTemplate(languageOverride: null, auth: auth, args: args); + + [ConditionalTheory] + [SkipOnHelix("https://github.com/dotnet/aspnetcore/issues/28090", Queues = HelixConstants.Windows10Arm64 + HelixConstants.DebianArm64)] [InlineData("SingleOrg", null)] - [InlineData("SingleOrg", new string[] { ArgConstants.UseProgramMain })] [InlineData("SingleOrg", new string[] { ArgConstants.UseMinimalApis })] - [InlineData("SingleOrg", new string[] { ArgConstants.UseProgramMain, ArgConstants.UseMinimalApis })] [InlineData("SingleOrg", new string[] { ArgConstants.CalledApiUrlGraphMicrosoftCom, ArgConstants.CalledApiScopesUserReadWrite })] [InlineData("SingleOrg", new string[] { ArgConstants.UseMinimalApis, ArgConstants.CalledApiUrlGraphMicrosoftCom, ArgConstants.CalledApiScopesUserReadWrite })] + [InlineData("SingleOrg", new string[] { ArgConstants.CallsGraph })] + [InlineData("SingleOrg", new string[] { ArgConstants.UseMinimalApis, ArgConstants.CallsGraph })] + public Task WebApiTemplateCSharp_IdentityWeb_SingleOrg_BuildsAndPublishes(string auth, string[] args) => PublishAndBuildWebApiTemplate(languageOverride: null, auth: auth, args: args); + + [ConditionalTheory] + [SkipOnHelix("https://github.com/dotnet/aspnetcore/issues/28090", Queues = HelixConstants.Windows10Arm64 + HelixConstants.DebianArm64)] + [InlineData("SingleOrg", null)] + [InlineData("SingleOrg", new string[] { ArgConstants.UseProgramMain })] + [InlineData("SingleOrg", new string[] { ArgConstants.UseProgramMain, ArgConstants.UseMinimalApis })] [InlineData("SingleOrg", new string[] { ArgConstants.UseProgramMain, ArgConstants.CalledApiUrlGraphMicrosoftCom, ArgConstants.CalledApiScopesUserReadWrite })] [InlineData("SingleOrg", new string[] { ArgConstants.UseProgramMain, ArgConstants.UseMinimalApis, ArgConstants.CalledApiUrlGraphMicrosoftCom, ArgConstants.CalledApiScopesUserReadWrite })] - [InlineData("SingleOrg", new string[] { ArgConstants.CallsGraph })] [InlineData("SingleOrg", new string[] { ArgConstants.UseProgramMain, ArgConstants.CallsGraph })] - [InlineData("SingleOrg", new string[] { ArgConstants.UseMinimalApis, ArgConstants.CallsGraph })] [InlineData("SingleOrg", new string[] { ArgConstants.UseProgramMain, ArgConstants.UseMinimalApis, ArgConstants.CallsGraph })] - public Task WebApiTemplateCSharp_IdentityWeb_BuildsAndPublishes(string auth, string[] args) => PublishAndBuildWebApiTemplate(languageOverride: null, auth: auth, args: args); + public Task WebApiTemplateCSharp_IdentityWeb_SingleOrg_ProgramMain_BuildsAndPublishes(string auth, string[] args) => PublishAndBuildWebApiTemplate(languageOverride: null, auth: auth, args: args); [Fact] public Task WebApiTemplateFSharp() => WebApiTemplateCore(languageOverride: "F#"); @@ -83,7 +97,7 @@ public class WebApiTemplateTest : LoggedTest [SkipOnHelix("Cert failure, https://github.com/dotnet/aspnetcore/issues/28090", Queues = "All.OSX;" + HelixConstants.Windows10Arm64 + HelixConstants.DebianArm64)] public async Task WebApiTemplateCSharp_WithoutOpenAPI(bool useProgramMain, bool useMinimalApis) { - var project = await FactoryFixture.GetOrCreateProject("webapinoopenapi", Output); + var project = await FactoryFixture.CreateProject(Output); var args = useProgramMain ? useMinimalApis @@ -108,7 +122,7 @@ public class WebApiTemplateTest : LoggedTest private async Task<Project> PublishAndBuildWebApiTemplate(string languageOverride, string auth, string[] args = null) { - var project = await FactoryFixture.GetOrCreateProject("webapi" + (languageOverride == "F#" ? "fsharp" : "csharp") + Guid.NewGuid().ToString().Substring(0, 10).ToLowerInvariant(), Output); + var project = await FactoryFixture.CreateProject(Output); var createResult = await project.RunDotNetNewAsync("webapi", language: languageOverride, auth: auth, args: args); Assert.True(0 == createResult.ExitCode, ErrorMessages.GetFailedProcessMessage("create/restore", project, createResult)); diff --git a/src/ProjectTemplates/test/WorkerTemplateTest.cs b/src/ProjectTemplates/test/WorkerTemplateTest.cs index 07b2b0476a6910eb85e57ea18132501aae4ab57e..5faa726172ae32939e0be416bfedf4352a12094e 100644 --- a/src/ProjectTemplates/test/WorkerTemplateTest.cs +++ b/src/ProjectTemplates/test/WorkerTemplateTest.cs @@ -38,9 +38,7 @@ public class WorkerTemplateTest : LoggedTest [QuarantinedTest("https://github.com/dotnet/aspnetcore/issues/25404")] public async Task WorkerTemplateAsync(string language, string[] args) { - var project = await ProjectFactory.GetOrCreateProject( - $"worker-{language.ToLowerInvariant()[0]}sharp", - Output); + var project = await ProjectFactory.CreateProject(Output); var createResult = await project.RunDotNetNewAsync("worker", language: language, args: args); Assert.True(0 == createResult.ExitCode, ErrorMessages.GetFailedProcessMessage("create/restore", project, createResult));