| File: test\ProjectTemplates\Infrastructure\TemplateExecutionTestClassFixtureBase.cs | Web Access |
| Project: src\test\ProjectTemplates\Microsoft.McpServer.ProjectTemplates.IntegrationTests\Microsoft.McpServer.ProjectTemplates.Tests.csproj (Microsoft.McpServer.ProjectTemplates.Tests) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System; using System.IO; using System.Threading.Tasks; using Xunit; using Xunit.Abstractions; namespace Microsoft.Shared.ProjectTemplates.Tests; /// <summary> /// Provides functionality scoped to the duration of all the tests in a single test class /// extending <see cref="TemplateExecutionTestBase{TConfiguration}"/>. /// </summary> public abstract class TemplateExecutionTestClassFixtureBase : IAsyncLifetime { private readonly string _templatePackageName; private readonly string _sandboxPackages; private readonly string _sandboxOutput; private readonly string _sandboxInstallPath; private readonly string _sandboxProjectsPath; private readonly MessageSinkTestOutputHelper _messageSinkTestOutputHelper; private ITestOutputHelper? _currentTestOutputHelper; /// <summary> /// Gets the current preferred output helper. /// If a test is underway, the output will be associated with that test. /// Otherwise, the output will appear as a diagnostic message via <see cref="IMessageSink"/>. /// </summary> private ITestOutputHelper OutputHelper => _currentTestOutputHelper ?? _messageSinkTestOutputHelper; protected TemplateExecutionTestClassFixtureBase(TemplateExecutionTestConfiguration configuration, IMessageSink messageSink) { _messageSinkTestOutputHelper = new(messageSink); _templatePackageName = configuration.TemplatePackageName; _sandboxPackages = configuration.TemplateSandboxPackages; _sandboxOutput = configuration.TemplateSandboxOutput; _sandboxInstallPath = Path.Combine(_sandboxOutput, "install"); _sandboxProjectsPath = Path.Combine(_sandboxOutput, "projects"); } public async Task InitializeAsync() { // Here, we clear execution test output from the previous test run, if it exists. // It's critical that this clearing happens *before* the tests start, *not* after they complete. // // This is because: // 1. This enables debugging the previous test run by building/running generated projects manually. // 2. The existence of a project.assets.json file on disk is what allows template content to get discovered // for component governance reporting. // Copy the template sandbox infrastructure to the output location for use during tests CopySandboxDirectory(WellKnownPaths.TemplateSandboxSource, _sandboxOutput); var installResult = await new DotNetNewCommand("install", _templatePackageName) .WithWorkingDirectory(_sandboxInstallPath) .WithEnvironmentVariable("LOCAL_SHIPPING_PATH", WellKnownPaths.LocalShippingPackagesPath) .WithEnvironmentVariable("NUGET_PACKAGES", _sandboxPackages) .WithCustomHive(_sandboxInstallPath) .ExecuteAsync(OutputHelper); installResult.AssertSucceeded($"dotnet new install {_templatePackageName}"); // Create the sub-directory for the generated projects Directory.CreateDirectory(_sandboxProjectsPath); } private static void CopySandboxDirectory(string sandboxSource, string testSandbox) { if (Directory.Exists(testSandbox)) { Directory.Delete(testSandbox, recursive: true); } Directory.CreateDirectory(testSandbox); var source = new DirectoryInfo(sandboxSource); foreach (FileInfo file in source.GetFiles()) { file.CopyTo(Path.Combine(testSandbox, file.Name)); } foreach (DirectoryInfo subDir in source.GetDirectories()) { CopySandboxDirectory(subDir.FullName, Path.Combine(testSandbox, subDir.Name)); } } public async Task<Project> CreateProjectAsync(string templateName, string projectName, string? startupProjectRelativePath, params string[] args) { var outputFolderPath = Path.Combine(_sandboxProjectsPath, projectName); ReadOnlySpan<string> dotNetNewCommandArgs = [ templateName, "-o", outputFolderPath, "-n", projectName, "--no-update-check", .. args ]; var testDescription = string.Join(' ', dotNetNewCommandArgs); var newProjectResult = await new DotNetNewCommand(dotNetNewCommandArgs) .WithWorkingDirectory(_sandboxProjectsPath) .WithCustomHive(_sandboxInstallPath) .ExecuteAsync(OutputHelper); newProjectResult.AssertSucceeded(testDescription); return new Project(outputFolderPath, projectName) { StartupProjectRelativePath = startupProjectRelativePath }; } public async Task RestoreProjectAsync(Project project) { var restoreResult = await new DotNetCommand("restore") .WithWorkingDirectory(project.StartupProjectFullPath) .WithEnvironmentVariable("LOCAL_SHIPPING_PATH", WellKnownPaths.LocalShippingPackagesPath) .WithEnvironmentVariable("NUGET_PACKAGES", _sandboxPackages) .ExecuteAsync(OutputHelper); restoreResult.AssertSucceeded($""" dotnet restore Working Directory: {project.StartupProjectFullPath} Local Shipping Path: {WellKnownPaths.LocalShippingPackagesPath} NuGet Packages Path: {_sandboxPackages} """); } public async Task BuildProjectAsync(Project project) { var buildResult = await new DotNetCommand("build", "--no-restore") .WithWorkingDirectory(project.StartupProjectFullPath) .ExecuteAsync(OutputHelper); buildResult.AssertSucceeded($""" dotnet build --no-restore Working Directory: {project.StartupProjectFullPath} """); } public void SetCurrentTestOutputHelper(ITestOutputHelper? outputHelper) { if (_currentTestOutputHelper is not null && outputHelper is not null) { throw new InvalidOperationException( "Cannot set the template execution test output helper when one is already present. " + "This might be a sign that template execution tests are running in parallel, " + "which is not currently supported."); } _currentTestOutputHelper = outputHelper; } public Task DisposeAsync() { // Only here to implement IAsyncLifetime. Not currently used. return Task.CompletedTask; } }