2016-10-27 18:46:43 -07:00
|
|
|
|
// 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.Collections.Generic;
|
|
|
|
|
using System.IO;
|
|
|
|
|
using System.Linq;
|
|
|
|
|
using System.Runtime.CompilerServices;
|
2017-01-06 01:40:26 -08:00
|
|
|
|
using System.Threading;
|
2016-10-27 18:46:43 -07:00
|
|
|
|
using System.Threading.Tasks;
|
|
|
|
|
using Microsoft.DotNet.Cli.Utils;
|
|
|
|
|
using Microsoft.DotNet.PlatformAbstractions;
|
2016-11-18 19:28:38 -08:00
|
|
|
|
using NuGet.Common;
|
2016-10-27 18:46:43 -07:00
|
|
|
|
|
|
|
|
|
namespace Microsoft.DotNet.TestFramework
|
|
|
|
|
{
|
|
|
|
|
public class TestAssetInfo
|
|
|
|
|
{
|
|
|
|
|
private const string DataDirectoryName = ".tam";
|
|
|
|
|
|
|
|
|
|
private readonly string [] FilesToExclude = { ".DS_Store", ".noautobuild" };
|
|
|
|
|
|
|
|
|
|
private readonly DirectoryInfo [] _directoriesToExclude;
|
|
|
|
|
|
|
|
|
|
private readonly string _assetName;
|
|
|
|
|
|
|
|
|
|
private readonly DirectoryInfo _dataDirectory;
|
|
|
|
|
|
|
|
|
|
private readonly DirectoryInfo _root;
|
|
|
|
|
|
|
|
|
|
private readonly TestAssetInventoryFiles _inventoryFiles;
|
|
|
|
|
|
2017-01-06 01:40:26 -08:00
|
|
|
|
private readonly FileInfo _dotnetExeFile;
|
|
|
|
|
|
|
|
|
|
private readonly string _projectFilePattern;
|
|
|
|
|
|
2016-10-27 18:46:43 -07:00
|
|
|
|
internal DirectoryInfo Root
|
|
|
|
|
{
|
|
|
|
|
get
|
|
|
|
|
{
|
|
|
|
|
return _root;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-01-06 01:40:26 -08:00
|
|
|
|
internal TestAssetInfo(DirectoryInfo root, string assetName, FileInfo dotnetExeFile, string projectFilePattern)
|
2016-10-27 18:46:43 -07:00
|
|
|
|
{
|
2017-01-06 01:40:26 -08:00
|
|
|
|
if (root == null)
|
2016-10-27 18:46:43 -07:00
|
|
|
|
{
|
2017-01-06 01:40:26 -08:00
|
|
|
|
throw new ArgumentNullException(nameof(root));
|
2016-10-27 18:46:43 -07:00
|
|
|
|
}
|
|
|
|
|
|
2017-01-06 01:40:26 -08:00
|
|
|
|
if (string.IsNullOrWhiteSpace(assetName))
|
|
|
|
|
{
|
|
|
|
|
throw new ArgumentException("Argument cannot be null or whitespace", nameof(assetName));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (dotnetExeFile == null)
|
|
|
|
|
{
|
|
|
|
|
throw new ArgumentNullException(nameof(dotnetExeFile));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (string.IsNullOrWhiteSpace(projectFilePattern))
|
|
|
|
|
{
|
|
|
|
|
throw new ArgumentException("Argument cannot be null or whitespace", nameof(projectFilePattern));
|
|
|
|
|
}
|
2016-10-27 18:46:43 -07:00
|
|
|
|
|
|
|
|
|
_root = root;
|
|
|
|
|
|
2017-01-06 01:40:26 -08:00
|
|
|
|
_assetName = assetName;
|
|
|
|
|
|
|
|
|
|
_dotnetExeFile = dotnetExeFile;
|
|
|
|
|
|
|
|
|
|
_projectFilePattern = projectFilePattern;
|
|
|
|
|
|
|
|
|
|
_dataDirectory = _root.GetDirectory(DataDirectoryName);
|
2016-10-27 18:46:43 -07:00
|
|
|
|
|
|
|
|
|
_inventoryFiles = new TestAssetInventoryFiles(_dataDirectory);
|
|
|
|
|
|
|
|
|
|
_directoriesToExclude = new []
|
|
|
|
|
{
|
|
|
|
|
_dataDirectory
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public TestAssetInstance CreateInstance([CallerMemberName] string callingMethod = "", string identifier = "")
|
|
|
|
|
{
|
2017-01-06 01:40:26 -08:00
|
|
|
|
var instancePath = GetTestDestinationDirectory(callingMethod, identifier);
|
2016-10-27 18:46:43 -07:00
|
|
|
|
|
2017-01-06 01:40:26 -08:00
|
|
|
|
var testInstance = new TestAssetInstance(this, instancePath);
|
2016-10-27 18:46:43 -07:00
|
|
|
|
|
|
|
|
|
return testInstance;
|
|
|
|
|
}
|
|
|
|
|
|
2017-01-06 01:40:26 -08:00
|
|
|
|
internal IEnumerable<FileInfo> GetSourceFiles()
|
|
|
|
|
{
|
|
|
|
|
ThrowIfTestAssetDoesNotExist();
|
|
|
|
|
|
|
|
|
|
ThrowIfAssetSourcesHaveChanged();
|
|
|
|
|
|
|
|
|
|
return GetInventory(
|
|
|
|
|
_inventoryFiles.Source,
|
|
|
|
|
null,
|
|
|
|
|
() => {});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
internal IEnumerable<FileInfo> GetRestoreFiles()
|
|
|
|
|
{
|
|
|
|
|
ThrowIfTestAssetDoesNotExist();
|
|
|
|
|
|
|
|
|
|
ThrowIfAssetSourcesHaveChanged();
|
|
|
|
|
|
|
|
|
|
return GetInventory(
|
|
|
|
|
_inventoryFiles.Restore,
|
|
|
|
|
GetSourceFiles,
|
|
|
|
|
DoRestore);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
internal IEnumerable<FileInfo> GetBuildFiles()
|
|
|
|
|
{
|
|
|
|
|
ThrowIfTestAssetDoesNotExist();
|
|
|
|
|
|
|
|
|
|
ThrowIfAssetSourcesHaveChanged();
|
|
|
|
|
|
|
|
|
|
return GetInventory(
|
|
|
|
|
_inventoryFiles.Build,
|
|
|
|
|
() => GetRestoreFiles()
|
|
|
|
|
.Concat(GetSourceFiles()),
|
|
|
|
|
DoBuild);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private DirectoryInfo GetTestDestinationDirectory(string callingMethod, string identifier)
|
2016-10-27 18:46:43 -07:00
|
|
|
|
{
|
|
|
|
|
#if NET451
|
|
|
|
|
string baseDirectory = AppDomain.CurrentDomain.BaseDirectory;
|
|
|
|
|
#else
|
|
|
|
|
string baseDirectory = AppContext.BaseDirectory;
|
|
|
|
|
#endif
|
2017-01-06 01:40:26 -08:00
|
|
|
|
return new DirectoryInfo(Path.Combine(baseDirectory, callingMethod + identifier, _assetName));
|
2016-10-27 18:46:43 -07:00
|
|
|
|
}
|
|
|
|
|
|
2017-01-06 01:40:26 -08:00
|
|
|
|
private IEnumerable<FileInfo> LoadInventory(FileInfo file)
|
2016-10-27 18:46:43 -07:00
|
|
|
|
{
|
|
|
|
|
if (!file.Exists)
|
|
|
|
|
{
|
2017-01-06 01:40:26 -08:00
|
|
|
|
return Enumerable.Empty<FileInfo>();
|
2016-10-27 18:46:43 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var inventory = new List<FileInfo>();
|
|
|
|
|
|
|
|
|
|
var lines = file.OpenText();
|
|
|
|
|
|
|
|
|
|
while (lines.Peek() > 0)
|
|
|
|
|
{
|
|
|
|
|
inventory.Add(new FileInfo(lines.ReadLine()));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return inventory;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void SaveInventory(FileInfo file, IEnumerable<FileInfo> inventory)
|
|
|
|
|
{
|
2016-11-18 19:28:38 -08:00
|
|
|
|
FileUtility.ReplaceWithLock(
|
|
|
|
|
filePath =>
|
2016-10-27 18:46:43 -07:00
|
|
|
|
{
|
2017-01-06 01:40:26 -08:00
|
|
|
|
if (!_dataDirectory.Exists)
|
|
|
|
|
{
|
|
|
|
|
_dataDirectory.Create();
|
|
|
|
|
}
|
|
|
|
|
|
2016-11-18 19:28:38 -08:00
|
|
|
|
using (var stream =
|
|
|
|
|
new FileStream(filePath, FileMode.Create, FileAccess.Write, FileShare.None))
|
|
|
|
|
{
|
|
|
|
|
using (var writer = new StreamWriter(stream))
|
|
|
|
|
{
|
|
|
|
|
foreach (var path in inventory.Select(i => i.FullName))
|
|
|
|
|
{
|
|
|
|
|
writer.WriteLine(path);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
file.FullName);
|
2016-10-27 18:46:43 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private IEnumerable<FileInfo> GetFileList()
|
|
|
|
|
{
|
|
|
|
|
return _root.GetFiles("*.*", SearchOption.AllDirectories)
|
|
|
|
|
.Where(f => !_directoriesToExclude.Any(d => d.Contains(f)))
|
|
|
|
|
.Where(f => !FilesToExclude.Contains(f.Name));
|
|
|
|
|
}
|
|
|
|
|
|
2016-11-18 19:28:38 -08:00
|
|
|
|
private IEnumerable<FileInfo> GetInventory(
|
|
|
|
|
FileInfo file,
|
|
|
|
|
Func<IEnumerable<FileInfo>> beforeAction,
|
|
|
|
|
Action action)
|
2016-10-27 18:46:43 -07:00
|
|
|
|
{
|
2016-11-18 19:28:38 -08:00
|
|
|
|
var inventory = Enumerable.Empty<FileInfo>();
|
2016-10-27 18:46:43 -07:00
|
|
|
|
if (file.Exists)
|
|
|
|
|
{
|
2016-11-18 19:28:38 -08:00
|
|
|
|
inventory = LoadInventory(file);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if(inventory.Any())
|
|
|
|
|
{
|
|
|
|
|
return inventory;
|
2016-10-27 18:46:43 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
IEnumerable<FileInfo> preInventory;
|
|
|
|
|
|
|
|
|
|
if (beforeAction == null)
|
|
|
|
|
{
|
|
|
|
|
preInventory = new List<FileInfo>();
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2016-11-18 19:28:38 -08:00
|
|
|
|
preInventory = beforeAction();
|
2016-10-27 18:46:43 -07:00
|
|
|
|
}
|
|
|
|
|
|
2017-01-06 01:40:26 -08:00
|
|
|
|
Task.Run(async () => await ConcurrencyUtilities.ExecuteWithFileLockedAsync<object>(
|
|
|
|
|
_dataDirectory.FullName,
|
|
|
|
|
lockedToken =>
|
|
|
|
|
{
|
|
|
|
|
if (file.Exists)
|
|
|
|
|
{
|
|
|
|
|
inventory = LoadInventory(file);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
action();
|
|
|
|
|
|
|
|
|
|
inventory = GetFileList().Where(i => !preInventory.Select(p => p.FullName).Contains(i.FullName));
|
2016-10-27 18:46:43 -07:00
|
|
|
|
|
2017-01-06 01:40:26 -08:00
|
|
|
|
SaveInventory(file, inventory);
|
|
|
|
|
}
|
2016-10-27 18:46:43 -07:00
|
|
|
|
|
2017-01-06 01:40:26 -08:00
|
|
|
|
return Task.FromResult(new Object());
|
|
|
|
|
},
|
|
|
|
|
CancellationToken.None)).Wait();
|
2016-10-27 18:46:43 -07:00
|
|
|
|
|
|
|
|
|
return inventory;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void DoRestore()
|
|
|
|
|
{
|
|
|
|
|
Console.WriteLine($"TestAsset Restore '{_assetName}'");
|
|
|
|
|
|
2017-01-06 01:40:26 -08:00
|
|
|
|
var projFiles = _root.GetFiles(_projectFilePattern, SearchOption.AllDirectories);
|
2016-10-27 18:46:43 -07:00
|
|
|
|
|
|
|
|
|
foreach (var projFile in projFiles)
|
|
|
|
|
{
|
2017-01-06 01:40:26 -08:00
|
|
|
|
var restoreArgs = new string[] { "restore", projFile.FullName };
|
2016-10-27 18:46:43 -07:00
|
|
|
|
|
2017-01-06 01:40:26 -08:00
|
|
|
|
var commandResult = Command.Create(_dotnetExeFile.FullName, restoreArgs)
|
2017-01-24 01:14:35 -08:00
|
|
|
|
.CaptureStdOut()
|
|
|
|
|
.CaptureStdErr()
|
2017-01-06 01:40:26 -08:00
|
|
|
|
.Execute();
|
2016-10-27 18:46:43 -07:00
|
|
|
|
|
|
|
|
|
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}'");
|
|
|
|
|
|
2017-01-06 01:40:26 -08:00
|
|
|
|
var commandResult = Command.Create(_dotnetExeFile.FullName, args)
|
|
|
|
|
.WorkingDirectory(_root.FullName)
|
|
|
|
|
.CaptureStdOut()
|
|
|
|
|
.CaptureStdErr()
|
|
|
|
|
.Execute();
|
2016-10-27 18:46:43 -07:00
|
|
|
|
|
|
|
|
|
int exitCode = commandResult.ExitCode;
|
|
|
|
|
|
|
|
|
|
if (exitCode != 0)
|
|
|
|
|
{
|
|
|
|
|
Console.WriteLine(commandResult.StdOut);
|
2016-11-08 23:23:13 -08:00
|
|
|
|
|
2016-10-27 18:46:43 -07:00
|
|
|
|
Console.WriteLine(commandResult.StdErr);
|
2016-11-08 23:23:13 -08:00
|
|
|
|
|
2016-10-27 18:46:43 -07:00
|
|
|
|
string message = string.Format($"TestAsset Build '{_assetName}' Failed with {exitCode}");
|
2016-11-08 23:23:13 -08:00
|
|
|
|
|
2016-10-27 18:46:43 -07:00
|
|
|
|
throw new Exception(message);
|
|
|
|
|
}
|
|
|
|
|
}
|
2017-01-06 01:40:26 -08:00
|
|
|
|
|
|
|
|
|
private void ThrowIfAssetSourcesHaveChanged()
|
|
|
|
|
{
|
|
|
|
|
if (!_dataDirectory.Exists)
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var dataDirectoryFiles = _dataDirectory.GetFiles("*", SearchOption.AllDirectories);
|
|
|
|
|
|
|
|
|
|
if (!dataDirectoryFiles.Any())
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var trackedFiles = _inventoryFiles.AllInventoryFiles.SelectMany(f => 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 = LoadInventory(_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);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void ThrowIfTestAssetDoesNotExist()
|
|
|
|
|
{
|
|
|
|
|
if (!_root.Exists)
|
|
|
|
|
{
|
|
|
|
|
throw new DirectoryNotFoundException($"Directory not found at '{_root.FullName}'");
|
|
|
|
|
}
|
|
|
|
|
}
|
2016-10-27 18:46:43 -07:00
|
|
|
|
}
|
|
|
|
|
}
|