diff --git a/src/Microsoft.DotNet.TestFramework/TestAssetInfo.cs b/src/Microsoft.DotNet.TestFramework/TestAssetInfo.cs index 760c33ec4..f5ca0aebe 100644 --- a/src/Microsoft.DotNet.TestFramework/TestAssetInfo.cs +++ b/src/Microsoft.DotNet.TestFramework/TestAssetInfo.cs @@ -6,41 +6,20 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Runtime.CompilerServices; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.DotNet.Cli.Utils; -using Microsoft.DotNet.PlatformAbstractions; -using NuGet.Common; namespace Microsoft.DotNet.TestFramework { public class TestAssetInfo { - private const string DataDirectoryName = ".tam"; - private readonly string [] FilesToExclude = { ".DS_Store", ".noautobuild" }; - private readonly DirectoryInfo [] _directoriesToExclude; + public string AssetName { get; private set; } - private readonly string _assetName; + public FileInfo DotnetExeFile { get; private set; } - private readonly DirectoryInfo _dataDirectory; + public string ProjectFilePattern { get; private set; } - private readonly DirectoryInfo _root; - - private readonly TestAssetInventoryFiles _inventoryFiles; - - private readonly FileInfo _dotnetExeFile; - - private readonly string _projectFilePattern; - - internal DirectoryInfo Root - { - get - { - return _root; - } - } + public DirectoryInfo Root { get; private set; } internal TestAssetInfo(DirectoryInfo root, string assetName, FileInfo dotnetExeFile, string projectFilePattern) { @@ -64,22 +43,13 @@ namespace Microsoft.DotNet.TestFramework throw new ArgumentException("Argument cannot be null or whitespace", nameof(projectFilePattern)); } - _root = root; + Root = root; - _assetName = assetName; + AssetName = assetName; - _dotnetExeFile = dotnetExeFile; + DotnetExeFile = dotnetExeFile; - _projectFilePattern = projectFilePattern; - - _dataDirectory = _root.GetDirectory(DataDirectoryName); - - _inventoryFiles = new TestAssetInventoryFiles(_dataDirectory); - - _directoriesToExclude = new [] - { - _dataDirectory - }; + ProjectFilePattern = projectFilePattern; } public TestAssetInstance CreateInstance([CallerMemberName] string callingMethod = "", string identifier = "") @@ -95,37 +65,8 @@ namespace Microsoft.DotNet.TestFramework { ThrowIfTestAssetDoesNotExist(); - ThrowIfAssetSourcesHaveChanged(); - - return GetInventory( - _inventoryFiles.Source, - null, - () => {}); - } - - internal IEnumerable GetRestoreFiles() - { - ThrowIfTestAssetDoesNotExist(); - - ThrowIfAssetSourcesHaveChanged(); - - return GetInventory( - _inventoryFiles.Restore, - GetSourceFiles, - DoRestore); - } - - internal IEnumerable GetBuildFiles() - { - ThrowIfTestAssetDoesNotExist(); - - ThrowIfAssetSourcesHaveChanged(); - - return GetInventory( - _inventoryFiles.Build, - () => GetRestoreFiles() - .Concat(GetSourceFiles()), - DoBuild); + return Root.GetFiles("*.*", SearchOption.AllDirectories) + .Where(f => !FilesToExclude.Contains(f.Name)); } private DirectoryInfo GetTestDestinationDirectory(string callingMethod, string identifier) @@ -135,239 +76,15 @@ namespace Microsoft.DotNet.TestFramework #else string baseDirectory = AppContext.BaseDirectory; #endif - return new DirectoryInfo(Path.Combine(baseDirectory, callingMethod + identifier, _assetName)); - } - - private IEnumerable GetFileList() - { - return _root.GetFiles("*.*", SearchOption.AllDirectories) - .Where(f => !_directoriesToExclude.Any(d => d.Contains(f))) - .Where(f => !FilesToExclude.Contains(f.Name)); - } - - private IEnumerable GetInventory( - FileInfo file, - Func> beforeAction, - Action action) - { - var inventory = Enumerable.Empty(); - - IEnumerable preInventory; - - if (beforeAction == null) - { - preInventory = new List(); - } - else - { - preInventory = beforeAction(); - } - - ExclusiveFolderAccess.Do(_dataDirectory, (folder) => { - file.Refresh(); - if (file.Exists) - { - inventory = folder.LoadInventory(file); - } - else - { - action(); - - inventory = GetFileList().Where(i => !preInventory.Select(p => p.FullName).Contains(i.FullName)); - - folder.SaveInventory(file, inventory); - } - }); - - return inventory; - } - - private void DoRestore() - { - Console.WriteLine($"TestAsset Restore '{_assetName}'"); - - var projFiles = _root.GetFiles(_projectFilePattern, SearchOption.AllDirectories); - - foreach (var projFile in projFiles) - { - var restoreArgs = new string[] { "restore", projFile.FullName }; - - var commandResult = Command.Create(_dotnetExeFile.FullName, restoreArgs) - .CaptureStdOut() - .CaptureStdErr() - .Execute(); - - int exitCode = commandResult.ExitCode; - - if (exitCode != 0) - { - Console.WriteLine(commandResult.StdOut); - - Console.WriteLine(commandResult.StdErr); - - string message = string.Format($"TestAsset Restore '{_assetName}'@'{projFile.FullName}' Failed with {exitCode}"); - - throw new Exception(message); - } - } - } - - private void DoBuild() - { - string[] args = new string[] { "build" }; - - Console.WriteLine($"TestAsset Build '{_assetName}'"); - - var commandResult = Command.Create(_dotnetExeFile.FullName, args) - .WorkingDirectory(_root.FullName) - .CaptureStdOut() - .CaptureStdErr() - .Execute(); - - int exitCode = commandResult.ExitCode; - - if (exitCode != 0) - { - Console.WriteLine(commandResult.StdOut); - - Console.WriteLine(commandResult.StdErr); - - string message = string.Format($"TestAsset Build '{_assetName}' Failed with {exitCode}"); - - throw new Exception(message); - } - } - - private void ThrowIfAssetSourcesHaveChanged() - { - if (!_dataDirectory.Exists) - { - return; - } - - var dataDirectoryFiles = _dataDirectory.GetFiles("*", SearchOption.AllDirectories); - - if (!dataDirectoryFiles.Any()) - { - return; - } - - IEnumerable trackedFiles = null; - ExclusiveFolderAccess.Do(_dataDirectory, (folder) => { - trackedFiles = _inventoryFiles.AllInventoryFiles.SelectMany(f => folder.LoadInventory(f)); - }); - - var assetFiles = GetFileList(); - - var untrackedFiles = assetFiles.Where(a => !trackedFiles.Any(t => t.FullName.Equals(a.FullName))); - - if (untrackedFiles.Any()) - { - var message = $"TestAsset {_assetName} has untracked files. " + - "Consider cleaning the asset and deleting its `.tam` directory to " + - "recreate tracking files.\n\n" + - $".tam directory: {_dataDirectory.FullName}\n" + - "Untracked Files: \n"; - - message += String.Join("\n", untrackedFiles.Select(f => $" - {f.FullName}\n")); - - throw new Exception(message); - } - - var earliestDataDirectoryTimestamp = - dataDirectoryFiles - .OrderBy(f => f.LastWriteTime) - .First() - .LastWriteTime; - - if (earliestDataDirectoryTimestamp == null) - { - return; - } - - var updatedSourceFiles = ExclusiveFolderAccess.Read(_inventoryFiles.Source) - .Where(f => f.LastWriteTime > earliestDataDirectoryTimestamp); - - if (updatedSourceFiles.Any()) - { - var message = $"TestAsset {_assetName} has updated files. " + - "Consider cleaning the asset and deleting its `.tam` directory to " + - "recreate tracking files.\n\n" + - $".tam directory: {_dataDirectory.FullName}\n" + - "Updated Files: \n"; - - message += String.Join("\n", updatedSourceFiles.Select(f => $" - {f.FullName}\n")); - - throw new GracefulException(message); - } + return new DirectoryInfo(Path.Combine(baseDirectory, callingMethod + identifier, AssetName)); } private void ThrowIfTestAssetDoesNotExist() { - if (!_root.Exists) + if (!Root.Exists) { - throw new DirectoryNotFoundException($"Directory not found at '{_root.FullName}'"); + throw new DirectoryNotFoundException($"Directory not found at '{Root.FullName}'"); } } - - private class ExclusiveFolderAccess - { - private DirectoryInfo _directory; - - private ExclusiveFolderAccess(DirectoryInfo directory) - { - _directory = directory; - } - - public static void Do(DirectoryInfo directory, Action action) - { - Task.Run(async () => await ConcurrencyUtilities.ExecuteWithFileLockedAsync( - directory.FullName, - lockedToken => - { - action(new ExclusiveFolderAccess(directory)); - return Task.FromResult(new Object()); - }, - CancellationToken.None)).Wait(); - } - - public static IEnumerable Read(FileInfo file) - { - IEnumerable ret = null; - Do(file.Directory, (folder) => { - ret = folder.LoadInventory(file); - }); - - return ret; - } - - public IEnumerable LoadInventory(FileInfo file) - { - file.Refresh(); - if (!file.Exists) - { - return Enumerable.Empty(); - } - - var inventory = new List(); - foreach (var p in File.ReadAllLines(file.FullName)) - { - inventory.Add(new FileInfo(p)); - } - - return inventory; - } - - public void SaveInventory(FileInfo file, IEnumerable inventory) - { - _directory.Refresh(); - if (!_directory.Exists) - { - _directory.Create(); - } - - File.WriteAllLines(file.FullName, inventory.Select((fi) => fi.FullName).ToList()); - } - } } } diff --git a/src/Microsoft.DotNet.TestFramework/TestAssetInstance.cs b/src/Microsoft.DotNet.TestFramework/TestAssetInstance.cs index 0f7101b94..1c3385297 100644 --- a/src/Microsoft.DotNet.TestFramework/TestAssetInstance.cs +++ b/src/Microsoft.DotNet.TestFramework/TestAssetInstance.cs @@ -17,13 +17,25 @@ namespace Microsoft.DotNet.TestFramework { public class TestAssetInstance { + public DirectoryInfo MigrationBackupRoot { get; } + + public DirectoryInfo Root { get; } + + public TestAssetInfo TestAssetInfo { get; } + + private bool _filesCopied = false; + + private bool _restored = false; + + private bool _built = false; + public TestAssetInstance(TestAssetInfo testAssetInfo, DirectoryInfo root) { if (testAssetInfo == null) { throw new ArgumentException(nameof(testAssetInfo)); } - + if (root == null) { throw new ArgumentException(nameof(root)); @@ -48,36 +60,42 @@ namespace Microsoft.DotNet.TestFramework } } - public DirectoryInfo MigrationBackupRoot { get; } - - public DirectoryInfo Root { get; } - - public TestAssetInfo TestAssetInfo { get; } - - public TestAssetInstance WithSourceFiles() { - var filesToCopy = TestAssetInfo.GetSourceFiles(); + if (!_filesCopied) + { + CopySourceFiles(); - CopyFiles(filesToCopy); + _filesCopied = true; + } return this; } public TestAssetInstance WithRestoreFiles() { - var filesToCopy = TestAssetInfo.GetRestoreFiles(); + if (!_restored) + { + WithSourceFiles(); - CopyFiles(filesToCopy); + RestoreAllProjects(); + + _restored = true; + } return this; } public TestAssetInstance WithBuildFiles() { - var filesToCopy = TestAssetInfo.GetBuildFiles(); + if (!_built) + { + WithRestoreFiles(); - CopyFiles(filesToCopy); + BuildRootProjectOrSolution(); + + _built = true; + } return this; } @@ -156,19 +174,124 @@ namespace Microsoft.DotNet.TestFramework }); } - private void CopyFiles(IEnumerable filesToCopy) + private static string RebasePath(string path, string oldBaseDirectory, string newBaseDirectory) { + path = Path.IsPathRooted(path) ? PathUtility.GetRelativePath(PathUtility.EnsureTrailingSlash(oldBaseDirectory), path) : path; + return Path.Combine(newBaseDirectory, path); + } + + private void CopySourceFiles() + { + var filesToCopy = TestAssetInfo.GetSourceFiles(); foreach (var file in filesToCopy) { - var relativePath = file.FullName.Substring(TestAssetInfo.Root.FullName.Length + 1); - - var newPath = Path.Combine(Root.FullName, relativePath); + var newPath = RebasePath(file.FullName, TestAssetInfo.Root.FullName, Root.FullName); var newFile = new FileInfo(newPath); PathUtility.EnsureDirectoryExists(newFile.Directory.FullName); - file.CopyTo(newPath); + CopyFileAdjustingPaths(file, newFile); + } + } + + private void CopyFileAdjustingPaths(FileInfo source, FileInfo destination) + { + if (string.Equals(source.Name, "nuget.config", StringComparison.OrdinalIgnoreCase)) + { + CopyNugetConfigAdjustingPath(source, destination); + } + else + { + source.CopyTo(destination.FullName); + } + } + + private void CopyNugetConfigAdjustingPath(FileInfo source, FileInfo destination) + { + var doc = XDocument.Load(source.FullName, LoadOptions.PreserveWhitespace); + foreach (var packageSource in doc.Root.Element("packageSources").Elements("add").Attributes("value")) + { + if (!Path.IsPathRooted(packageSource.Value)) + { + string fullPathAtSource = Path.GetFullPath(Path.Combine(source.Directory.FullName, packageSource.Value)); + if (!PathUtility.IsChildOfDirectory(TestAssetInfo.Root.FullName, fullPathAtSource)) + { + packageSource.Value = fullPathAtSource; + } + } + + using (var file = new FileStream( + destination.FullName, + FileMode.CreateNew, + FileAccess.ReadWrite)) + { + doc.Save(file, SaveOptions.None); + } + } + } + + private void BuildRootProjectOrSolution() + { + string[] args = new string[] { "build" }; + + Console.WriteLine($"TestAsset Build '{TestAssetInfo.AssetName}'"); + + var commandResult = Command.Create(TestAssetInfo.DotnetExeFile.FullName, args) + .WorkingDirectory(Root.FullName) + .CaptureStdOut() + .CaptureStdErr() + .Execute(); + + int exitCode = commandResult.ExitCode; + + if (exitCode != 0) + { + Console.WriteLine(commandResult.StdOut); + + Console.WriteLine(commandResult.StdErr); + + string message = string.Format($"TestAsset Build '{TestAssetInfo.AssetName}' Failed with {exitCode}"); + + throw new Exception(message); + } + } + + private IEnumerable GetProjectFiles() + { + return Root.GetFiles(TestAssetInfo.ProjectFilePattern, SearchOption.AllDirectories); + } + + private void Restore(FileInfo projectFile) + { + var restoreArgs = new string[] { "restore", projectFile.FullName }; + + var commandResult = Command.Create(TestAssetInfo.DotnetExeFile.FullName, restoreArgs) + .CaptureStdOut() + .CaptureStdErr() + .Execute(); + + int exitCode = commandResult.ExitCode; + + if (exitCode != 0) + { + Console.WriteLine(commandResult.StdOut); + + Console.WriteLine(commandResult.StdErr); + + string message = string.Format($"TestAsset Restore '{TestAssetInfo.AssetName}'@'{projectFile.FullName}' Failed with {exitCode}"); + + throw new Exception(message); + } + } + + private void RestoreAllProjects() + { + Console.WriteLine($"TestAsset Restore '{TestAssetInfo.AssetName}'"); + + foreach (var projFile in GetProjectFiles()) + { + Restore(projFile); } } }