post migration cleanup (#4449)

* Create tests

* Basic scenario working & tested

* Feature Complete

* prevent build of intentionally broken test asset

* Update migrate command backup

* PR Feedback

* Move negative test to negative test directory

* Fix tests

* make test output directories unique

* Merge Conflict

* make backup the default behavior

* Pass2Fail

* Remove tests' PJ dependency
This commit is contained in:
Piotr Puszkiewicz 2016-10-29 01:58:37 -07:00 committed by GitHub
parent a4776a2b2c
commit 5ede3b6367
14 changed files with 376 additions and 19 deletions

View file

@ -0,0 +1,17 @@
// Copyright (c) .NET Foundation and contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System;
using System.Diagnostics;
namespace TestApp
{
public class Program
{
public static int Main(string[] args)
{
Console.WriteLine(TestLibrary.Helper.GetMessage());
return 100;
}
}
}

View file

@ -0,0 +1,30 @@
{
"version": "1.0.0-*",
"buildOptions": {
"emitEntryPoint": true,
"preserveCompilationContext": true
},
"dependencies": {
"TestLibrary": {
"target": "project",
"version": "1.0.0-*"
},
"Microsoft.NETCore.App": "1.0.1"
},
"frameworks": {
"netcoreapp1.0": {}
},
"runtimes": {
"win7-x64": {},
"win7-x86": {},
"osx.10.10-x64": {},
"osx.10.11-x64": {},
"ubuntu.14.04-x64": {},
"ubuntu.16.04-x64": {},
"centos.7-x64": {},
"rhel.7.2-x64": {},
"debian.8-x64": {},
"fedora.23-x64": {},
"opensuse.13.2-x64": {}
}
}

View file

@ -0,0 +1,24 @@
// Copyright (c) .NET Foundation and contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System;
namespace TestLibrary
{
public static class Helper
{
/// <summary>
/// Gets the message from the helper. This comment is here to help test XML documentation file generation, please do not remove it.
/// </summary>
/// <returns>A message</returns>
public static string GetMessage()
{
return "This string came from the test library!";
}
public static void SayHi()
{
Console.WriteLine("Hello there!");
}
}
}

View file

@ -0,0 +1,22 @@
{
"version": "1.0.0-*",
"buildOptions": {
"nowarn": [
"CS1591"
],
"xmlDoc": true,
"additionalArguments": [
"-highentropyva+"
]
},
"dependencies": {
"MissingP2PDependency": {
"target": "project",
"version": "1.0.0-*"
},
"NETStandard.Library": "1.6.0"
},
"frameworks": {
"netstandard1.5": {}
}
}

View file

@ -0,0 +1,3 @@
{
"projects": [ "."]
}

View file

@ -11,12 +11,15 @@ using Microsoft.Build.Evaluation;
using Microsoft.DotNet.Cli.Utils; using Microsoft.DotNet.Cli.Utils;
using Microsoft.DotNet.ProjectJsonMigration; using Microsoft.DotNet.ProjectJsonMigration;
using Microsoft.DotNet.Internal.ProjectModel; using Microsoft.DotNet.Internal.ProjectModel;
using ProjectModel = Microsoft.DotNet.Internal.ProjectModel.Project; using Project = Microsoft.DotNet.Internal.ProjectModel.Project;
using Microsoft.DotNet.Tools.Common;
namespace Microsoft.DotNet.Tools.Migrate namespace Microsoft.DotNet.Tools.Migrate
{ {
public partial class MigrateCommand public partial class MigrateCommand
{ {
private readonly DirectoryInfo _workspaceDirectory;
private readonly DirectoryInfo _backupDirectory;
private readonly string _templateFile; private readonly string _templateFile;
private readonly string _projectArg; private readonly string _projectArg;
private readonly string _sdkVersion; private readonly string _sdkVersion;
@ -24,6 +27,7 @@ namespace Microsoft.DotNet.Tools.Migrate
private readonly bool _skipProjectReferences; private readonly bool _skipProjectReferences;
private readonly string _reportFile; private readonly string _reportFile;
private readonly bool _reportFormatJson; private readonly bool _reportFormatJson;
private readonly bool _skipBackup;
public MigrateCommand( public MigrateCommand(
string templateFile, string templateFile,
@ -32,15 +36,21 @@ namespace Microsoft.DotNet.Tools.Migrate
string xprojFilePath, string xprojFilePath,
string reportFile, string reportFile,
bool skipProjectReferences, bool skipProjectReferences,
bool reportFormatJson) bool reportFormatJson,
{ bool skipBackup)
{
_templateFile = templateFile; _templateFile = templateFile;
_projectArg = projectArg ?? Directory.GetCurrentDirectory(); _projectArg = projectArg ?? Directory.GetCurrentDirectory();
_workspaceDirectory = File.Exists(_projectArg)
? new FileInfo(_projectArg).Directory
: new DirectoryInfo(_projectArg);
_backupDirectory = new DirectoryInfo(Path.Combine(_workspaceDirectory.FullName, "backup"));
_sdkVersion = sdkVersion; _sdkVersion = sdkVersion;
_xprojFilePath = xprojFilePath; _xprojFilePath = xprojFilePath;
_skipProjectReferences = skipProjectReferences; _skipProjectReferences = skipProjectReferences;
_reportFile = reportFile; _reportFile = reportFile;
_reportFormatJson = reportFormatJson; _reportFormatJson = reportFormatJson;
_skipBackup = skipBackup;
} }
public int Execute() public int Execute()
@ -82,12 +92,73 @@ namespace Microsoft.DotNet.Tools.Migrate
temporaryDotnetNewProject.Clean(); temporaryDotnetNewProject.Clean();
MoveProjectJsonArtifactsToBackup(migrationReport);
return migrationReport.FailedProjectsCount; return migrationReport.FailedProjectsCount;
} }
private void MoveProjectJsonArtifactsToBackup(MigrationReport migrationReport)
{
if (_skipBackup)
{
return;
}
if (migrationReport.FailedProjectsCount > 0)
{
return;
}
BackupGlobalJson();
BackupProjects(migrationReport);
}
private void BackupGlobalJson()
{
_backupDirectory.Create();
var globalJson = Path.Combine(_workspaceDirectory.FullName, GlobalSettings.FileName);
if (File.Exists(globalJson))
{
File.Move(globalJson, Path.Combine(_backupDirectory.FullName, GlobalSettings.FileName));
}
}
private void BackupProjects(MigrationReport migrationReport)
{
foreach (var report in migrationReport.ProjectMigrationReports)
{
MigrateProject(report);
}
}
private void MigrateProject(ProjectMigrationReport report)
{
var projectDirectory = PathUtility.EnsureTrailingSlash(report.ProjectDirectory);
var relativeDirectory = PathUtility.GetRelativePath(PathUtility.EnsureTrailingSlash(_workspaceDirectory.FullName), projectDirectory);
var targetDirectory = String.IsNullOrEmpty(relativeDirectory)
? _backupDirectory.FullName
: Path.Combine(_backupDirectory.FullName, relativeDirectory);
PathUtility.EnsureDirectory(PathUtility.EnsureTrailingSlash(targetDirectory));
var movableFiles = new DirectoryInfo(projectDirectory)
.EnumerateFiles()
.Where(f => f.Name == Project.FileName || f.Extension == ".xproj");
foreach (var movableFile in movableFiles)
{
movableFile.MoveTo(Path.Combine(targetDirectory, movableFile.Name));
}
}
private void WriteReport(MigrationReport migrationReport) private void WriteReport(MigrationReport migrationReport)
{ {
if (!string.IsNullOrEmpty(_reportFile)) if (!string.IsNullOrEmpty(_reportFile))
{ {
using (var outputTextWriter = GetReportFileOutputTextWriter()) using (var outputTextWriter = GetReportFileOutputTextWriter())
@ -194,13 +265,14 @@ namespace Microsoft.DotNet.Tools.Migrate
{ {
IEnumerable<string> projects = null; IEnumerable<string> projects = null;
if (projectArg.EndsWith(ProjectModel.FileName, StringComparison.OrdinalIgnoreCase)) if (projectArg.EndsWith(Project.FileName, StringComparison.OrdinalIgnoreCase))
{ {
projects = Enumerable.Repeat(projectArg, 1); projects = Enumerable.Repeat(projectArg, 1);
} }
else if (projectArg.EndsWith(GlobalSettings.FileName, StringComparison.OrdinalIgnoreCase)) else if (projectArg.EndsWith(GlobalSettings.FileName, StringComparison.OrdinalIgnoreCase))
{ {
projects = GetProjectsFromGlobalJson(projectArg); projects = GetProjectsFromGlobalJson(projectArg);
if (!projects.Any()) if (!projects.Any())
{ {
throw new Exception("Unable to find any projects in global.json"); throw new Exception("Unable to find any projects in global.json");
@ -208,8 +280,8 @@ namespace Microsoft.DotNet.Tools.Migrate
} }
else if (Directory.Exists(projectArg)) else if (Directory.Exists(projectArg))
{ {
projects = projects = Directory.EnumerateFiles(projectArg, Project.FileName, SearchOption.AllDirectories);
Directory.EnumerateFiles(projectArg, ProjectModel.FileName, SearchOption.AllDirectories);
if (!projects.Any()) if (!projects.Any())
{ {
throw new Exception($"No project.json file found in '{projectArg}'"); throw new Exception($"No project.json file found in '{projectArg}'");

View file

@ -42,7 +42,8 @@ namespace Microsoft.DotNet.Tools.Migrate
CommandOption skipProjectReferences = app.Option("-s|--skip-project-references", "Skip migrating project references. By default project references are migrated recursively", CommandOptionType.BoolValue); CommandOption skipProjectReferences = app.Option("-s|--skip-project-references", "Skip migrating project references. By default project references are migrated recursively", CommandOptionType.BoolValue);
CommandOption reportFile = app.Option("-r|--report-file", "Output migration report to a file in addition to the console.", CommandOptionType.SingleValue); CommandOption reportFile = app.Option("-r|--report-file", "Output migration report to a file in addition to the console.", CommandOptionType.SingleValue);
CommandOption structuredReportOutput = app.Option("--format-report-file-json", "Output migration report file as json rather than user messages", CommandOptionType.BoolValue); CommandOption structuredReportOutput = app.Option("--format-report-file-json", "Output migration report file as json rather than user messages", CommandOptionType.BoolValue);
CommandOption skipBackup = app.Option("--skip-backup", "Skip moving project.json, global.json, and *.xproj to a `backup` directory after successful migration.", CommandOptionType.BoolValue);
app.OnExecute(() => app.OnExecute(() =>
{ {
@ -53,7 +54,8 @@ namespace Microsoft.DotNet.Tools.Migrate
xprojFile.Value(), xprojFile.Value(),
reportFile.Value(), reportFile.Value(),
skipProjectReferences.BoolValue.HasValue ? skipProjectReferences.BoolValue.Value : false, skipProjectReferences.BoolValue.HasValue ? skipProjectReferences.BoolValue.Value : false,
structuredReportOutput.BoolValue.HasValue ? structuredReportOutput.BoolValue.Value : false); structuredReportOutput.BoolValue.HasValue ? structuredReportOutput.BoolValue.Value : false,
skipBackup.BoolValue.HasValue ? skipBackup.BoolValue.Value : false);
return migrateCommand.Execute(); return migrateCommand.Execute();
}); });

View file

@ -21,7 +21,7 @@ namespace Microsoft.DotNet.ProjectJsonMigration.Tests
public GivenThatIWantToMigrateAssemblyInfo() public GivenThatIWantToMigrateAssemblyInfo()
{ {
var projectDirectory = var projectDirectory =
TestAssetsManager.CreateTestInstance("AppWithAssemblyInfo", callingMethod: "i").Path; TestAssetsManager.CreateTestInstance("AppWithAssemblyInfo").Path;
var projectContext = var projectContext =
ProjectContext.Create(projectDirectory, FrameworkConstants.CommonFrameworks.NetCoreApp10); ProjectContext.Create(projectDirectory, FrameworkConstants.CommonFrameworks.NetCoreApp10);
_mockProject = ProjectRootElement.Create(); _mockProject = ProjectRootElement.Create();

View file

@ -30,11 +30,31 @@ namespace Microsoft.DotNet.Tools.Test.Utilities
return new AndConstraint<DirectoryInfoAssertions>(this); return new AndConstraint<DirectoryInfoAssertions>(this);
} }
public AndConstraint<DirectoryInfoAssertions> HaveFile(string expectedFile) public AndConstraint<DirectoryInfoAssertions> HaveFile(string expectedFile, string because = "", params object[] reasonArgs)
{ {
var file = _dirInfo.EnumerateFiles(expectedFile, SearchOption.TopDirectoryOnly).SingleOrDefault(); var file = _dirInfo.EnumerateFiles(expectedFile, SearchOption.TopDirectoryOnly).SingleOrDefault();
Execute.Assertion.ForCondition(file != null)
.FailWith("Expected File {0} cannot be found in directory {1}.", expectedFile, _dirInfo.FullName); Execute.Assertion
.ForCondition(file != null)
.BecauseOf(because, reasonArgs)
.FailWith($"Expected File {expectedFile} cannot be found in directory {_dirInfo.FullName}.");
return new AndConstraint<DirectoryInfoAssertions>(this);
}
public AndConstraint<DirectoryInfoAssertions> HaveTextFile(string expectedFile, string expectedContents, string because = "", params object[] reasonArgs)
{
this.HaveFile(expectedFile, because, reasonArgs);
var file = _dirInfo.EnumerateFiles(expectedFile, SearchOption.TopDirectoryOnly).SingleOrDefault();
var contents = File.ReadAllText(file.FullName);
Execute.Assertion
.ForCondition(contents.Equals(expectedContents))
.BecauseOf(because, reasonArgs)
.FailWith($"Expected file {expectedFile} to contain \n\n{expectedContents}\n\nbut it contains\n\n{contents}\n");
return new AndConstraint<DirectoryInfoAssertions>(this); return new AndConstraint<DirectoryInfoAssertions>(this);
} }
@ -56,12 +76,26 @@ namespace Microsoft.DotNet.Tools.Test.Utilities
return new AndConstraint<DirectoryInfoAssertions>(this); return new AndConstraint<DirectoryInfoAssertions>(this);
} }
public AndConstraint<DirectoryInfoAssertions> HaveFilesMatching(string expectedFilesSearchPattern, SearchOption searchOption) public AndConstraint<DirectoryInfoAssertions> HaveTextFiles(IDictionary<string, string> expectedFiles, string because = "", params object[] reasonArgs)
{
foreach (var expectedFile in expectedFiles)
{
HaveTextFile(expectedFile.Key, expectedFile.Value, because, reasonArgs);
}
return new AndConstraint<DirectoryInfoAssertions>(this);
}
public AndConstraint<DirectoryInfoAssertions> HaveFilesMatching(string expectedFilesSearchPattern, SearchOption searchOption, string because = "", params object[] reasonArgs)
{ {
var matchingFileExists = _dirInfo.EnumerateFiles(expectedFilesSearchPattern, searchOption).Any(); var matchingFileExists = _dirInfo.EnumerateFiles(expectedFilesSearchPattern, searchOption).Any();
Execute.Assertion.ForCondition(matchingFileExists == true)
Execute.Assertion
.ForCondition(matchingFileExists == true)
.BecauseOf(because, reasonArgs)
.FailWith("Expected directory {0} to contain files matching {1}, but no matching file exists.", .FailWith("Expected directory {0} to contain files matching {1}, but no matching file exists.",
_dirInfo.FullName, expectedFilesSearchPattern); _dirInfo.FullName, expectedFilesSearchPattern);
return new AndConstraint<DirectoryInfoAssertions>(this); return new AndConstraint<DirectoryInfoAssertions>(this);
} }
@ -108,5 +142,15 @@ namespace Microsoft.DotNet.Tools.Test.Utilities
return new AndConstraint<DirectoryInfoAssertions>(this); return new AndConstraint<DirectoryInfoAssertions>(this);
} }
public AndConstraint<DirectoryInfoAssertions> NotExist(string because = "", params object[] reasonArgs)
{
Execute.Assertion
.ForCondition(_dirInfo.Exists == false)
.BecauseOf(because, reasonArgs)
.FailWith($"Expected directory {_dirInfo.FullName} to not exist, but it does.");
return new AndConstraint<DirectoryInfoAssertions>(this);
}
} }
} }

View file

@ -0,0 +1,29 @@
// Copyright (c) .NET Foundation and contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using Microsoft.DotNet.Cli.Utils;
namespace Microsoft.DotNet.Tools.Test.Utilities
{
public sealed class MigrateCommand : TestCommand
{
public MigrateCommand()
: base("dotnet")
{
}
public override CommandResult Execute(string args="")
{
args = $"migrate {args}";
return base.Execute(args);
}
public override CommandResult ExecuteWithCapturedOutput(string args = "")
{
args = $"migrate {args}";
return base.ExecuteWithCapturedOutput(args);
}
}
}

View file

@ -0,0 +1,111 @@
using FluentAssertions;
using Microsoft.DotNet.TestFramework;
using Microsoft.DotNet.Tools.Common;
using Microsoft.DotNet.Tools.Test.Utilities;
using System;
using System.Collections.Generic;
using System.IO;
using Xunit;
namespace Microsoft.DotNet.Migration.Tests
{
public class GivenThatAnAppWasMigrated : TestBase
{
[Theory]
[InlineData("PJTestAppSimple")]
[InlineData("TestAppWithLibrary")]
public void When_migration_succeeds_Then_project_json_artifacts_get_moved_to_backup(string testProjectName)
{
var testRoot = TestAssetsManager
.CreateTestInstance(testProjectName, identifier: testProjectName)
.Path;
var backupRoot = Path.Combine(testRoot, "backup");
var migratableArtifacts = GetProjectJsonArtifacts(testRoot);
new MigrateCommand()
.WithWorkingDirectory(testRoot)
.Execute()
.Should().Pass();
var backupArtifacts = GetProjectJsonArtifacts(backupRoot);
backupArtifacts.Should().Equal(migratableArtifacts, "Because all of and only these artifacts should have been moved");
new DirectoryInfo(testRoot).Should().NotHaveFiles(backupArtifacts.Keys);
new DirectoryInfo(backupRoot).Should().HaveTextFiles(backupArtifacts);
}
[Theory]
[InlineData("TestAppWithLibraryAndMissingP2P")]
public void When_migration_fails_Then_project_json_artifacts_do_not_get_moved_to_backup(string testProjectName)
{
var testRoot = new TestAssetsManager(Path.Combine(RepoRoot, "TestAssets", "NonRestoredTestProjects"))
.CreateTestInstance(testProjectName, identifier: testProjectName)
.Path;
var backupRoot = Path.Combine(testRoot, "backup");
var migratableArtifacts = GetProjectJsonArtifacts(testRoot);
new MigrateCommand()
.WithWorkingDirectory(testRoot)
.Execute()
.Should().Fail();
new DirectoryInfo(backupRoot).Should().NotExist("Because migration failed and therefore no backup is needed.");
new DirectoryInfo(testRoot).Should().HaveTextFiles(migratableArtifacts, "Because migration failed so nothing was moved to backup.");
}
[Theory]
[InlineData("PJTestAppSimple")]
public void When_skipbackup_specified_Then_project_json_artifacts_do_not_get_moved_to_backup(string testProjectName)
{
var testRoot = TestAssetsManager.CreateTestInstance(testProjectName, identifier: testProjectName).Path;
var backupRoot = Path.Combine(testRoot, "backup");
var migratableArtifacts = GetProjectJsonArtifacts(testRoot);
new MigrateCommand()
.WithWorkingDirectory(testRoot)
.Execute("--skip-backup")
.Should().Pass();
new DirectoryInfo(backupRoot).Should().NotExist("Because --skip-backup was specified.");
new DirectoryInfo(testRoot).Should().HaveTextFiles(migratableArtifacts, "Because --skip-backup was specified.");
}
private Dictionary<string, string> GetProjectJsonArtifacts(string rootPath)
{
var catalog = new Dictionary<string, string>();
var patterns = new [] { "global.json", "project.json", "*.xproj" };
foreach (var pattern in patterns)
{
AddArtifactsToCatalog(catalog, rootPath, pattern);
}
return catalog;
}
private void AddArtifactsToCatalog(Dictionary<string, string> catalog, string basePath, string pattern)
{
basePath = PathUtility.EnsureTrailingSlash(basePath);
var baseDirectory = new DirectoryInfo(basePath);
var files = baseDirectory.GetFiles(pattern, SearchOption.AllDirectories);
foreach (var file in files)
{
catalog.Add(PathUtility.GetRelativePath(basePath, file.FullName), File.ReadAllText(file.FullName));
}
}
}
}

View file

@ -12,6 +12,8 @@ using BuildCommand = Microsoft.DotNet.Tools.Test.Utilities.BuildCommand;
using System.Runtime.Loader; using System.Runtime.Loader;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using MigrateCommand = Microsoft.DotNet.Tools.Migrate.MigrateCommand;
namespace Microsoft.DotNet.Migration.Tests namespace Microsoft.DotNet.Migration.Tests
{ {
public class GivenThatIWantToMigrateTestApps : TestBase public class GivenThatIWantToMigrateTestApps : TestBase
@ -354,9 +356,10 @@ namespace Microsoft.DotNet.Migration.Tests
public void It_migrates_and_publishes_projects_with_runtimes() public void It_migrates_and_publishes_projects_with_runtimes()
{ {
var projectName = "PJTestAppSimple"; var projectName = "PJTestAppSimple";
var projectDirectory = TestAssetsManager.CreateTestInstance(projectName, callingMethod: "i") var projectDirectory = TestAssetsManager
.WithLockFiles() .CreateTestInstance(projectName)
.Path; .WithLockFiles()
.Path;
CleanBinObj(projectDirectory); CleanBinObj(projectDirectory);
BuildProjectJsonMigrateBuildMSBuild(projectDirectory, projectName); BuildProjectJsonMigrateBuildMSBuild(projectDirectory, projectName);
@ -440,7 +443,7 @@ namespace Microsoft.DotNet.Migration.Tests
private void VerifyMigration(IEnumerable<string> expectedProjects, string rootDir) private void VerifyMigration(IEnumerable<string> expectedProjects, string rootDir)
{ {
var migratedProjects = Directory.EnumerateFiles(rootDir, "project.json", SearchOption.AllDirectories) var migratedProjects = Directory.EnumerateFiles(rootDir, "*.csproj", SearchOption.AllDirectories)
.Where(s => Directory.EnumerateFiles(Path.GetDirectoryName(s), "*.csproj").Count() == 1) .Where(s => Directory.EnumerateFiles(Path.GetDirectoryName(s), "*.csproj").Count() == 1)
.Where(s => Path.GetFileName(Path.GetDirectoryName(s)).Contains("Project")) .Where(s => Path.GetFileName(Path.GetDirectoryName(s)).Contains("Project"))
.Select(s => Path.GetFileName(Path.GetDirectoryName(s))); .Select(s => Path.GetFileName(Path.GetDirectoryName(s)));