// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Threading;
using Microsoft.Extensions.CommandLineUtils;
using Templates.Test.Helpers;
using Xunit;
using Xunit.Abstractions;
namespace Templates.Test
{
public class TemplateTestBase : IDisposable
{
private static object DotNetNewLock = new object();
protected string ProjectName { get; set; }
protected string ProjectGuid { get; set; }
protected string TemplateOutputDir { get; set; }
protected ITestOutputHelper Output { get; private set; }
protected bool UseRazorSdkPackage { get; set; } = true;
public TemplateTestBase(ITestOutputHelper output)
{
TemplatePackageInstaller.EnsureTemplatingEngineInitialized(output);
Output = output;
ProjectGuid = Guid.NewGuid().ToString("N");
ProjectName = $"AspNet.Template.{ProjectGuid}";
var assemblyPath = GetType().GetTypeInfo().Assembly.CodeBase;
var assemblyUri = new Uri(assemblyPath, UriKind.Absolute);
var basePath = Path.GetDirectoryName(assemblyUri.LocalPath);
TemplateOutputDir = Path.Combine(basePath, "TestTemplates", ProjectName);
Directory.CreateDirectory(TemplateOutputDir);
// We don't want any of the host repo's build config interfering with
// how the test project is built, so disconnect it from the
// Directory.Build.props/targets context
var templatesTestsPropsFilePath = Path.Combine(basePath, "TemplateTests.props");
var directoryBuildPropsContent =
$@"
";
File.WriteAllText(Path.Combine(TemplateOutputDir, "Directory.Build.props"), directoryBuildPropsContent);
File.WriteAllText(Path.Combine(TemplateOutputDir, "Directory.Build.targets"), "");
}
protected void RunDotNetNew(string templateName, string targetFrameworkOverride, string auth = null, string language = null, bool useLocalDB = false, bool noHttps = false)
{
SetAfterDirectoryBuildPropsContents();
var args = $"new {templateName} --debug:custom-hive \"{TemplatePackageInstaller.CustomHivePath}\"";
if (!string.IsNullOrEmpty(targetFrameworkOverride))
{
args += $" --target-framework-override {targetFrameworkOverride}";
}
if (!string.IsNullOrEmpty(auth))
{
args += $" -au {auth}";
}
if (!string.IsNullOrEmpty(language))
{
args += $" -lang {language}";
}
if (useLocalDB)
{
args += $" -uld";
}
if (noHttps)
{
args += $" --no-https";
}
// Only run one instance of 'dotnet new' at once, as a workaround for
// https://github.com/aspnet/templating/issues/63
lock (DotNetNewLock)
{
ProcessEx.Run(Output, TemplateOutputDir, DotNetMuxer.MuxerPathOrDefault(), args).WaitForExit(assertSuccess: true);
}
}
protected void SetAfterDirectoryBuildPropsContents()
{
var content = GetAfterDirectoryBuildPropsContent();
if (!string.IsNullOrEmpty(content))
{
content = "" + Environment.NewLine + content + Environment.NewLine + "";
File.WriteAllText(Path.Combine(TemplateOutputDir, "Directory.Build.After.props"), content);
}
}
protected virtual string GetAfterDirectoryBuildPropsContent()
{
var content = string.Empty;
if (UseRazorSdkPackage)
{
content +=
@"
";
}
return content;
}
protected void RunDotNet(string arguments)
{
lock (DotNetNewLock)
{
ProcessEx.Run(Output, TemplateOutputDir, DotNetMuxer.MuxerPathOrDefault(), arguments).WaitForExit(assertSuccess: true);
}
}
protected void RunDotNetEfCreateMigration(string migrationName)
{
var assembly = typeof(TemplateTestBase).Assembly;
var dotNetEfFullPath = assembly.GetCustomAttributes()
.First(attribute => attribute.Key == "DotNetEfFullPath")
.Value;
var args = $"\"{dotNetEfFullPath}\" migrations add {migrationName}";
// Only run one instance of 'dotnet new' at once, as a workaround for
// https://github.com/aspnet/templating/issues/63
lock (DotNetNewLock)
{
ProcessEx.Run(Output, TemplateOutputDir, DotNetMuxer.MuxerPathOrDefault(), args).WaitForExit(assertSuccess: true);
}
}
protected void AssertDirectoryExists(string path, bool shouldExist)
{
var fullPath = Path.Combine(TemplateOutputDir, path);
var doesExist = Directory.Exists(fullPath);
if (shouldExist)
{
Assert.True(doesExist, "Expected directory to exist, but it doesn't: " + path);
}
else
{
Assert.False(doesExist, "Expected directory not to exist, but it does: " + path);
}
}
// If this fails, you should generate new migrations via migrations/updateMigrations.cmd
protected void AssertEmptyMigration(string migration)
{
var fullPath = Path.Combine(TemplateOutputDir, "Data/Migrations");
var file = Directory.EnumerateFiles(fullPath).Where(f => f.EndsWith($"{migration}.cs")).FirstOrDefault();
Assert.NotNull(file);
var contents = File.ReadAllText(file);
var emptyMigration = @"protected override void Up(MigrationBuilder migrationBuilder)
{
}
protected override void Down(MigrationBuilder migrationBuilder)
{
}";
Assert.Contains(emptyMigration, contents);
}
protected void AssertFileExists(string path, bool shouldExist)
{
var fullPath = Path.Combine(TemplateOutputDir, path);
var doesExist = File.Exists(fullPath);
if (shouldExist)
{
Assert.True(doesExist, "Expected file to exist, but it doesn't: " + path);
}
else
{
Assert.False(doesExist, "Expected file not to exist, but it does: " + path);
}
}
protected string ReadFile(string path)
{
AssertFileExists(path, shouldExist: true);
return File.ReadAllText(Path.Combine(TemplateOutputDir, path));
}
protected AspNetProcess StartAspNetProcess(string targetFrameworkOverride, bool publish = false)
{
return new AspNetProcess(Output, TemplateOutputDir, ProjectName, targetFrameworkOverride, publish);
}
public void Dispose()
{
DeleteOutputDirectory();
}
private void DeleteOutputDirectory()
{
const int NumAttempts = 10;
for (var numAttemptsRemaining = NumAttempts; numAttemptsRemaining > 0; numAttemptsRemaining--)
{
try
{
Directory.Delete(TemplateOutputDir, true);
return;
}
catch (Exception ex)
{
if (numAttemptsRemaining > 1)
{
Output.WriteLine($"Failed to delete directory {TemplateOutputDir} because of error {ex.Message}. Will try again {numAttemptsRemaining - 1} more time(s).");
Thread.Sleep(3000);
}
else
{
Output.WriteLine($"Giving up trying to delete directory {TemplateOutputDir} after {NumAttempts} attempts. Most recent error was: {ex.StackTrace}");
}
}
}
}
}
}