CLI Testing via msbuild (#3779)
* Rebase * Remove Multi-Project Validator * Remove projectmodelserver tests * Enable test package creation * Incremental test restore * WiP * Enable Test Asset Project restore * Build Test Assets & Restore Test Projects * Build Test projects * Enable Test Execution also moves Test Targets to a well-known CLI Version [Stage 2] * Pass throuh existing telemetry profile * 2-space tabs * Revert TestTargets.cs * WiP PR feedback * Refactoring * Fix naming of RestoreTestAssetPackages * DotNetTest task * Fix merge issue * ExecuteWithCapturedOutput MSBuild considers StdErr output to be failures. This causes output of any test command which is expected to produce an error to be swallowed in the test. * Workaround for always-on tracing functionality in dotnet-test * Fix Path Separator Windows/Unix * Seperate package build from pack * Windows Pathing issues * PR Feedback * Workaround for msbuild #773 https://github.com/Microsoft/msbuild/issues/773
This commit is contained in:
parent
9885196eef
commit
08e9bc903e
25 changed files with 466 additions and 576 deletions
|
@ -1,33 +0,0 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
namespace MultiProjectValidator
|
||||
{
|
||||
public class AnalysisResult
|
||||
{
|
||||
|
||||
private IEnumerable<string> _messages;
|
||||
private bool _passed;
|
||||
|
||||
public AnalysisResult(IEnumerable<string> messages, bool passed)
|
||||
{
|
||||
_messages = messages;
|
||||
_passed = passed;
|
||||
}
|
||||
|
||||
public IEnumerable<string> Messages
|
||||
{
|
||||
get
|
||||
{
|
||||
return _messages;
|
||||
}
|
||||
}
|
||||
|
||||
public bool Passed
|
||||
{
|
||||
get
|
||||
{
|
||||
return _passed;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,53 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace MultiProjectValidator.AnalysisRules.DependencyMismatch
|
||||
{
|
||||
internal class DependencyGroup
|
||||
{
|
||||
public static DependencyGroup CreateWithEntry(DependencyInfo dependencyInfo)
|
||||
{
|
||||
var dependencyGroup = new DependencyGroup
|
||||
{
|
||||
DependencyName = dependencyInfo.Name,
|
||||
VersionDependencyInfoMap = new Dictionary<string, List<DependencyInfo>>()
|
||||
};
|
||||
|
||||
dependencyGroup.AddEntry(dependencyInfo);
|
||||
|
||||
return dependencyGroup;
|
||||
}
|
||||
|
||||
public string DependencyName { get; private set; }
|
||||
public Dictionary<string, List<DependencyInfo>> VersionDependencyInfoMap { get; private set; }
|
||||
|
||||
public bool HasConflict
|
||||
{
|
||||
get
|
||||
{
|
||||
return VersionDependencyInfoMap.Count > 1;
|
||||
}
|
||||
}
|
||||
|
||||
public void AddEntry(DependencyInfo dependencyInfo)
|
||||
{
|
||||
if (!dependencyInfo.Name.Equals(DependencyName, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
throw new Exception("Added dependency does not match group");
|
||||
}
|
||||
|
||||
if (VersionDependencyInfoMap.ContainsKey(dependencyInfo.Version))
|
||||
{
|
||||
VersionDependencyInfoMap[dependencyInfo.Version].Add(dependencyInfo);
|
||||
}
|
||||
else
|
||||
{
|
||||
VersionDependencyInfoMap[dependencyInfo.Version] = new List<DependencyInfo>()
|
||||
{
|
||||
dependencyInfo
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -1,22 +0,0 @@
|
|||
using System;
|
||||
using Microsoft.DotNet.ProjectModel;
|
||||
|
||||
namespace MultiProjectValidator.AnalysisRules.DependencyMismatch
|
||||
{
|
||||
internal class DependencyInfo
|
||||
{
|
||||
public static DependencyInfo Create(ProjectContext context, LibraryDescription library)
|
||||
{
|
||||
return new DependencyInfo
|
||||
{
|
||||
ProjectPath = context.ProjectFile.ProjectFilePath,
|
||||
Version = library.Identity.Version?.ToString() ?? String.Empty,
|
||||
Name = library.Identity.Name
|
||||
};
|
||||
}
|
||||
|
||||
public string ProjectPath { get; private set; }
|
||||
public string Version { get; private set; }
|
||||
public string Name { get; private set; }
|
||||
}
|
||||
}
|
|
@ -1,141 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.DotNet.ProjectModel;
|
||||
using System.Text;
|
||||
using MultiProjectValidator.AnalysisRules.DependencyMismatch;
|
||||
|
||||
namespace MultiProjectValidator.AnalysisRules
|
||||
{
|
||||
public class DependencyMismatchRule : IAnalysisRule
|
||||
{
|
||||
public AnalysisResult Evaluate(IEnumerable<ProjectContext> projectContexts)
|
||||
{
|
||||
var filteredContexts = FilterContextList(projectContexts);
|
||||
var targetGroupedContexts = GroupContextsByTarget(filteredContexts);
|
||||
|
||||
var failureMessages = EvaluateProjectContextTargetGroups(targetGroupedContexts);
|
||||
var pass = failureMessages.Count() == 0;
|
||||
|
||||
var result = new AnalysisResult(failureMessages, pass);
|
||||
return result;
|
||||
}
|
||||
|
||||
private IEnumerable<ProjectContext> FilterContextList(IEnumerable<ProjectContext> projectContexts)
|
||||
{
|
||||
return projectContexts.Where(context=> !context.TargetIsDesktop());
|
||||
}
|
||||
|
||||
private IEnumerable<string> EvaluateProjectContextTargetGroups(Dictionary<string, List<ProjectContext>> targetGroupedProjectContexts)
|
||||
{
|
||||
var failureMessages = new List<string>();
|
||||
|
||||
foreach (var target in targetGroupedProjectContexts.Keys)
|
||||
{
|
||||
var targetProjectContextGroup = targetGroupedProjectContexts[target];
|
||||
|
||||
var groupFailureMessages = EvaluateProjectContextTargetGroup(targetProjectContextGroup);
|
||||
|
||||
if (groupFailureMessages.Count > 0)
|
||||
{
|
||||
string aggregateFailureMessage = $"Failures for Target {target} {Environment.NewLine}{Environment.NewLine}"
|
||||
+ string.Join("", groupFailureMessages);
|
||||
|
||||
failureMessages.Add(aggregateFailureMessage);
|
||||
}
|
||||
}
|
||||
|
||||
return failureMessages;
|
||||
}
|
||||
|
||||
private List<string> EvaluateProjectContextTargetGroup(IEnumerable<ProjectContext> targetProjectContextGroup)
|
||||
{
|
||||
var failedMessages = new List<string>();
|
||||
|
||||
var dependencyGroups = CreateDependencyGroups(targetProjectContextGroup);
|
||||
|
||||
foreach (var dependencyGroup in dependencyGroups)
|
||||
{
|
||||
if(dependencyGroup.HasConflict)
|
||||
{
|
||||
failedMessages.Add(GetDependencyGroupConflictMessage(dependencyGroup));
|
||||
}
|
||||
}
|
||||
|
||||
return failedMessages;
|
||||
}
|
||||
|
||||
private string GetDependencyGroupConflictMessage(DependencyGroup dependencyGroup)
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
sb.Append($"Conflict for {dependencyGroup.DependencyName} in projects:{Environment.NewLine}");
|
||||
|
||||
foreach (var version in dependencyGroup.VersionDependencyInfoMap.Keys)
|
||||
{
|
||||
var dependencyInfoList = dependencyGroup.VersionDependencyInfoMap[version];
|
||||
|
||||
foreach (var dependencyInfo in dependencyInfoList)
|
||||
{
|
||||
sb.Append($"Version: {dependencyInfo.Version} Path: {dependencyInfo.ProjectPath} {Environment.NewLine}");
|
||||
}
|
||||
}
|
||||
sb.Append(Environment.NewLine);
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
private Dictionary<string, List<ProjectContext>> GroupContextsByTarget(IEnumerable<ProjectContext> projectContexts)
|
||||
{
|
||||
var targetContextMap = new Dictionary<string, List<ProjectContext>>();
|
||||
foreach (var context in projectContexts)
|
||||
{
|
||||
var target = context.TargetFramework + context.RuntimeIdentifier;
|
||||
|
||||
if (targetContextMap.ContainsKey(target))
|
||||
{
|
||||
targetContextMap[target].Add(context);
|
||||
}
|
||||
else
|
||||
{
|
||||
targetContextMap[target] = new List<ProjectContext>()
|
||||
{
|
||||
context
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return targetContextMap;
|
||||
}
|
||||
|
||||
private List<DependencyGroup> CreateDependencyGroups(IEnumerable<ProjectContext> projectContexts)
|
||||
{
|
||||
var libraryNameDependencyGroupMap = new Dictionary<string, DependencyGroup>();
|
||||
|
||||
foreach (var projectContext in projectContexts)
|
||||
{
|
||||
var libraries = projectContext.LibraryManager.GetLibraries();
|
||||
|
||||
foreach (var library in libraries)
|
||||
{
|
||||
var dependencyInfo = DependencyInfo.Create(projectContext, library);
|
||||
|
||||
if (libraryNameDependencyGroupMap.ContainsKey(dependencyInfo.Name))
|
||||
{
|
||||
var dependencyGroup = libraryNameDependencyGroupMap[dependencyInfo.Name];
|
||||
|
||||
dependencyGroup.AddEntry(dependencyInfo);
|
||||
}
|
||||
else
|
||||
{
|
||||
var dependencyGroup = DependencyGroup.CreateWithEntry(dependencyInfo);
|
||||
|
||||
libraryNameDependencyGroupMap[dependencyInfo.Name] = dependencyGroup;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return libraryNameDependencyGroupMap.Values.ToList();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,10 +0,0 @@
|
|||
using System.Collections.Generic;
|
||||
using Microsoft.DotNet.ProjectModel;
|
||||
|
||||
namespace MultiProjectValidator
|
||||
{
|
||||
public interface IAnalysisRule
|
||||
{
|
||||
AnalysisResult Evaluate(IEnumerable<ProjectContext> projectContexts);
|
||||
}
|
||||
}
|
|
@ -1,18 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion>
|
||||
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VSToolsPath)\DNX\Microsoft.DNX.Props" Condition="'$(VSToolsPath)' != ''" />
|
||||
<PropertyGroup Label="Globals">
|
||||
<ProjectGuid>08a68c6a-86f6-4ed2-89a7-b166d33e9f85</ProjectGuid>
|
||||
<RootNamespace>ProjectSanity</RootNamespace>
|
||||
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">..\..\artifacts\obj\$(MSBuildProjectName)</BaseIntermediateOutputPath>
|
||||
<OutputPath Condition="'$(OutputPath)'=='' ">..\..\artifacts\bin</OutputPath>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<SchemaVersion>2.0</SchemaVersion>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VSToolsPath)\DNX\Microsoft.DNX.targets" Condition="'$(VSToolsPath)' != ''" />
|
||||
</Project>
|
|
@ -1,126 +0,0 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.DotNet.ProjectModel;
|
||||
using System.Linq;
|
||||
|
||||
namespace MultiProjectValidator
|
||||
{
|
||||
public class Program
|
||||
{
|
||||
public static int Main(string[] args)
|
||||
{
|
||||
|
||||
string rootPath = null;
|
||||
|
||||
try
|
||||
{
|
||||
rootPath = ParseAndValidateArgs(args);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
List<ProjectContext> projects = null;
|
||||
try
|
||||
{
|
||||
projects = ProjectLoader.Load(rootPath);
|
||||
}
|
||||
catch(Exception)
|
||||
{
|
||||
Console.WriteLine("Failed to load projects from path: " + rootPath);
|
||||
return 1;
|
||||
}
|
||||
|
||||
var analyzer = ProjectAnalyzer.Create(projects);
|
||||
var analysisResults = analyzer.DoAnalysis();
|
||||
var failed = analysisResults.Where((a) => !a.Passed).Any();
|
||||
|
||||
PrintAnalysisResults(analysisResults);
|
||||
|
||||
return failed ? 1 : 0;
|
||||
}
|
||||
|
||||
private static void PrintAnalysisResults(List<AnalysisResult> analysisResults)
|
||||
{
|
||||
Console.WriteLine("Project Validation Results");
|
||||
|
||||
var failedCount = analysisResults.Where((a) => !a.Passed).Count();
|
||||
var passedCount = analysisResults.Where((a) => a.Passed).Count();
|
||||
|
||||
Console.ForegroundColor = ConsoleColor.Green;
|
||||
Console.WriteLine($"{passedCount} Successful Analysis Rules");
|
||||
|
||||
Console.ForegroundColor = ConsoleColor.Yellow;
|
||||
Console.WriteLine($"{failedCount} Failed Analysis Rules");
|
||||
|
||||
if (failedCount != 0)
|
||||
{
|
||||
Console.WriteLine("Failure Messages");
|
||||
|
||||
foreach(var result in analysisResults)
|
||||
{
|
||||
if (!result.Passed)
|
||||
{
|
||||
foreach(var message in result.Messages)
|
||||
{
|
||||
Console.WriteLine(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Console.ForegroundColor = ConsoleColor.White;
|
||||
}
|
||||
|
||||
private static bool AnyAnalysisFailed(List<AnalysisResult> analysisResults)
|
||||
{
|
||||
foreach (var result in analysisResults)
|
||||
{
|
||||
if (!result.Passed)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static string ParseAndValidateArgs(string[] args)
|
||||
{
|
||||
if (args.Length != 1)
|
||||
{
|
||||
PrintHelp();
|
||||
throw new Exception();
|
||||
}
|
||||
|
||||
string rootPath = args[0];
|
||||
|
||||
if (!Directory.Exists(rootPath))
|
||||
{
|
||||
Console.WriteLine("Root Directory does not exist: " + rootPath);
|
||||
throw new Exception();
|
||||
}
|
||||
|
||||
return rootPath;
|
||||
}
|
||||
|
||||
private static void PrintHelp()
|
||||
{
|
||||
var help = @"
|
||||
Multi-Project Validator
|
||||
|
||||
Description:
|
||||
This tool recursively searches for project.json's from the given root path.
|
||||
It then applies a set of analysis rules, determines whether they pass/fail
|
||||
and then sets exit code to reflect.
|
||||
|
||||
Note:
|
||||
Ensure all analyzed project.json have been restored prior to running this tool.
|
||||
|
||||
Usage:
|
||||
pjvalidate [root path of recursive search]";
|
||||
|
||||
Console.WriteLine(help);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,43 +0,0 @@
|
|||
using System.Collections.Generic;
|
||||
using Microsoft.DotNet.ProjectModel;
|
||||
using MultiProjectValidator.AnalysisRules;
|
||||
|
||||
namespace MultiProjectValidator
|
||||
{
|
||||
public class ProjectAnalyzer
|
||||
{
|
||||
|
||||
public static ProjectAnalyzer Create(List<ProjectContext> projectContexts)
|
||||
{
|
||||
// Any Additional rules would be added here
|
||||
var rules = new List<IAnalysisRule>
|
||||
{
|
||||
new DependencyMismatchRule()
|
||||
};
|
||||
|
||||
return new ProjectAnalyzer(rules, projectContexts);
|
||||
}
|
||||
|
||||
private List<ProjectContext> _projectContexts;
|
||||
private List<IAnalysisRule> _rules;
|
||||
|
||||
private ProjectAnalyzer(List<IAnalysisRule> rules, List<ProjectContext> projectContexts)
|
||||
{
|
||||
_rules = rules;
|
||||
_projectContexts = projectContexts;
|
||||
}
|
||||
|
||||
public List<AnalysisResult> DoAnalysis()
|
||||
{
|
||||
var results = new List<AnalysisResult>();
|
||||
|
||||
foreach(var rule in _rules)
|
||||
{
|
||||
results.Add(rule.Evaluate(_projectContexts));
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -1,17 +0,0 @@
|
|||
using System.IO;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.DotNet.ProjectModel;
|
||||
|
||||
namespace MultiProjectValidator
|
||||
{
|
||||
public static class ProjectContextExtensions
|
||||
{
|
||||
private static readonly string s_desktopTfmPrefix = ".NETFramework";
|
||||
public static bool TargetIsDesktop(this ProjectContext context)
|
||||
{
|
||||
var targetFramework = context.TargetFramework.ToString();
|
||||
|
||||
return targetFramework.StartsWith(s_desktopTfmPrefix);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,38 +0,0 @@
|
|||
using System.IO;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.DotNet.ProjectModel;
|
||||
|
||||
namespace MultiProjectValidator
|
||||
{
|
||||
public class ProjectLoader
|
||||
{
|
||||
private static readonly string s_projectFileName = "project.json";
|
||||
|
||||
public static List<ProjectContext> Load(string rootPath, bool recursive=true)
|
||||
{
|
||||
var projectFiles = DiscoverProjectFiles(rootPath);
|
||||
var projectContextList = LoadProjectContexts(projectFiles);
|
||||
|
||||
return projectContextList;
|
||||
}
|
||||
|
||||
private static string[] DiscoverProjectFiles(string rootPath)
|
||||
{
|
||||
return Directory.GetFiles(rootPath, s_projectFileName, SearchOption.AllDirectories);
|
||||
}
|
||||
|
||||
private static List<ProjectContext> LoadProjectContexts(string[] projectFiles)
|
||||
{
|
||||
var projectContexts = new List<ProjectContext>();
|
||||
|
||||
foreach (var file in projectFiles)
|
||||
{
|
||||
var fileTargetContexts = ProjectContext.CreateContextForEachTarget(file);
|
||||
|
||||
projectContexts.AddRange(fileTargetContexts);
|
||||
}
|
||||
|
||||
return projectContexts;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,35 +0,0 @@
|
|||
{
|
||||
"version": "1.0.0-*",
|
||||
"name": "pjvalidate",
|
||||
"buildOptions": {
|
||||
"emitEntryPoint": true
|
||||
},
|
||||
"dependencies": {
|
||||
"Microsoft.NETCore.App": "1.0.0",
|
||||
"Microsoft.DotNet.ProjectModel": {
|
||||
"target": "project"
|
||||
},
|
||||
"Microsoft.DotNet.Cli.Utils": {
|
||||
"target": "project"
|
||||
}
|
||||
},
|
||||
"frameworks": {
|
||||
"netcoreapp1.0": {
|
||||
"imports": [
|
||||
"dnxcore50"
|
||||
]
|
||||
}
|
||||
},
|
||||
"runtimes": {
|
||||
"win7-x64": {},
|
||||
"win7-x86": {},
|
||||
"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": {}
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue