You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

685 lines
24 KiB
C#

1 year ago
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using UnityEditor.TestTools.CodeCoverage.CommandLineParser;
using UnityEditor.TestTools.CodeCoverage.Utils;
using UnityEngine;
namespace UnityEditor.TestTools.CodeCoverage
{
internal class CommandLineManager : CommandLineManagerImplementation
{
private static CommandLineManager s_Instance = null;
public static CommandLineManager instance
{
get
{
if (s_Instance == null)
s_Instance = new CommandLineManager();
return s_Instance;
}
}
protected CommandLineManager() : base(Environment.GetCommandLineArgs())
{
}
}
internal class CommandLineManagerImplementation
{
public bool runFromCommandLine
{
get;
private set;
}
public string coverageResultsPath
{
get;
private set;
}
public string coverageHistoryPath
{
get;
private set;
}
public bool generateAdditionalMetrics
{
get;
private set;
}
public bool generateTestReferences
{
get;
private set;
}
public bool generateHTMLReportHistory
{
get;
private set;
}
public bool generateHTMLReport
{
get;
private set;
}
public bool generateBadgeReport
{
get;
private set;
}
public bool generateAdditionalReports
{
get;
private set;
}
public bool useProjectSettings
{
get;
private set;
}
public bool generateRootEmptyReport
{
get;
private set;
}
public bool dontClear
{
get;
private set;
}
public bool verbosityLevelSpecified
{
get;
private set;
}
public bool assemblyFiltersSpecified
{
get;
private set;
}
public bool assemblyFiltersFromFileSpecified
{
get;
private set;
}
public bool pathFiltersSpecified
{
get;
private set;
}
public bool pathFiltersFromFileSpecified
{
get;
private set;
}
public bool pathReplacingSpecified
{
get;
private set;
}
public string sourcePaths
{
get;
private set;
}
public bool sourcePathsSpecified
{
get;
private set;
}
public AssemblyFiltering assemblyFiltering
{
get;
private set;
}
public PathFiltering pathFiltering
{
get;
private set;
}
public PathReplacing pathReplacing
{
get;
private set;
}
public bool runTests
{
get;
private set;
}
public bool batchmode
{
get;
private set;
}
public bool burstDisabled
{
get;
private set;
}
private string m_CoverageOptionsArg;
private string m_IncludeAssemblies;
private string m_ExcludeAssemblies;
private string m_IncludePaths;
private string m_ExcludePaths;
private string m_PathReplacePatterns;
public CommandLineManagerImplementation(string[] commandLineArgs)
{
runFromCommandLine = false;
coverageResultsPath = string.Empty;
coverageHistoryPath = string.Empty;
sourcePaths = string.Empty;
generateAdditionalMetrics = false;
generateTestReferences = false;
generateHTMLReportHistory = false;
generateHTMLReport = false;
generateBadgeReport = false;
generateAdditionalReports = false;
useProjectSettings = false;
generateRootEmptyReport = false;
dontClear = false;
verbosityLevelSpecified = false;
assemblyFiltersSpecified = false;
pathFiltersSpecified = false;
pathReplacingSpecified = false;
sourcePathsSpecified = false;
pathFiltersFromFileSpecified = false;
assemblyFiltering = new AssemblyFiltering();
pathFiltering = new PathFiltering();
pathReplacing = new PathReplacing();
runTests = false;
batchmode = false;
burstDisabled = false;
m_CoverageOptionsArg = string.Empty;
m_IncludeAssemblies = string.Empty;
m_ExcludeAssemblies = string.Empty;
m_IncludePaths = string.Empty;
m_ExcludePaths = string.Empty;
m_PathReplacePatterns = string.Empty;
CommandLineOptionSet optionSet = new CommandLineOptionSet(
new CommandLineOption("enableCodeCoverage", () => { runFromCommandLine = true; }),
new CommandLineOption("coverageResultsPath", filePathArg => { SetCoverageResultsPath(filePathArg); }),
new CommandLineOption("coverageHistoryPath", filePathArg => { SetCoverageHistoryPath(filePathArg); }),
new CommandLineOption("coverageOptions", optionsArg => { AddCoverageOptions(optionsArg); }),
new CommandLineOption("runTests", () => { runTests = true; }),
new CommandLineOption("batchmode", () => { batchmode = true; }),
new CommandLineOption("-burst-disable-compilation", () => { burstDisabled = true; })
);
optionSet.Parse(commandLineArgs);
ValidateCoverageResultsPath();
ValidateCoverageHistoryPath();
if (runFromCommandLine)
ParseCoverageOptions();
}
private void SetCoverageResultsPath(string filePathArg)
{
if (coverageResultsPath != string.Empty)
{
ResultsLogger.Log(ResultID.Warning_MultipleResultsPaths, coverageResultsPath);
}
else
{
if (filePathArg != null)
{
coverageResultsPath = CoverageUtils.NormaliseFolderSeparators(filePathArg);
}
}
}
private void ValidateCoverageResultsPath()
{
if (!CoverageUtils.EnsureFolderExists(coverageResultsPath))
coverageResultsPath = string.Empty;
}
private void SetCoverageHistoryPath(string filePathArg)
{
if (coverageHistoryPath != string.Empty)
{
ResultsLogger.Log(ResultID.Warning_MultipleHistoryPaths, coverageHistoryPath);
}
else
{
if (filePathArg != null)
{
coverageHistoryPath = CoverageUtils.NormaliseFolderSeparators(filePathArg);
}
}
}
private void ValidateCoverageHistoryPath()
{
if (!CoverageUtils.EnsureFolderExists(coverageHistoryPath))
coverageHistoryPath = string.Empty;
}
private void AddCoverageOptions(string coverageOptionsArg)
{
if (coverageOptionsArg != null)
{
coverageOptionsArg = coverageOptionsArg.Trim('\'');
if (coverageOptionsArg != string.Empty)
{
if (m_CoverageOptionsArg == string.Empty)
{
m_CoverageOptionsArg = coverageOptionsArg;
}
else
{
m_CoverageOptionsArg += ";";
m_CoverageOptionsArg += coverageOptionsArg;
}
}
}
}
private void ParseCoverageOptions()
{
// Make sure there is no trailing quotes at the end of the options
m_CoverageOptionsArg = m_CoverageOptionsArg.TrimEnd('"');
// 'sourcePaths' option is moved at the beginning to ensure it is handled first,
// since it may be needed for other options
var options = m_CoverageOptionsArg.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries).ToList();
if (options.Count > 1)
{
var sourcePath = options.FirstOrDefault(option => option.StartsWith("SOURCEPATHS:", StringComparison.InvariantCultureIgnoreCase));
if (sourcePath != null)
{
var sourcePathIndex = options.IndexOf(sourcePath);
var firstElement = options[0];
options[sourcePathIndex] = firstElement;
options[0] = sourcePath;
}
}
string[] coverageOptions = options.ToArray();
foreach (string optionArgsStr in coverageOptions)
{
if (optionArgsStr.Length == 0)
continue;
string optionName = optionArgsStr;
string optionArgs = string.Empty;
int indexOfColon = optionArgsStr.IndexOf(':');
if (indexOfColon > 0)
{
optionName = optionArgsStr.Substring(0, indexOfColon);
optionArgs = optionArgsStr.Substring(indexOfColon+1);
}
switch (optionName.ToUpperInvariant())
{
case "GENERATEADDITIONALMETRICS":
generateAdditionalMetrics = true;
break;
case "GENERATEHTMLREPORTHISTORY":
generateHTMLReportHistory = true;
break;
case "GENERATEHTMLREPORT":
generateHTMLReport = true;
break;
case "GENERATEBADGEREPORT":
generateBadgeReport = true;
break;
case "GENERATEADDITIONALREPORTS":
generateAdditionalReports = true;
break;
case "GENERATEROOTEMPTYREPORT":
generateRootEmptyReport = true;
break;
case "DONTCLEAR":
dontClear = true;
break;
case "GENERATETESTREFERENCES":
generateTestReferences = true;
break;
case "USEPROJECTSETTINGS":
if (batchmode)
useProjectSettings = true;
else
ResultsLogger.Log(ResultID.Warning_UseProjectSettingsNonBatchmode);
break;
case "VERBOSITY":
if (optionArgs.Length > 0)
{
verbosityLevelSpecified = true;
switch (optionArgs.ToUpperInvariant())
{
case "VERBOSE":
ResultsLogger.VerbosityLevel = LogVerbosityLevel.Verbose;
break;
case "INFO":
ResultsLogger.VerbosityLevel = LogVerbosityLevel.Info;
break;
case "WARNING":
ResultsLogger.VerbosityLevel = LogVerbosityLevel.Warning;
break;
case "ERROR":
ResultsLogger.VerbosityLevel = LogVerbosityLevel.Error;
break;
case "OFF":
ResultsLogger.VerbosityLevel = LogVerbosityLevel.Off;
break;
}
}
break;
case "ASSEMBLYFILTERS":
if (optionArgs.Length > 0)
{
assemblyFiltersSpecified = true;
string[] assemblyFilters = optionArgs.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
ParseAssemblyFilters(assemblyFilters);
}
break;
case "PATHFILTERSFROMFILE":
if (optionArgs.Length > 0)
{
pathFiltersFromFileSpecified = true;
ResultsLogger.Log(ResultID.Warning_PathFiltersFromFileDeprecation);
if (File.Exists(optionArgs))
{
try
{
ParsePathFilters( GetPathFiltersFromFile(optionArgs) );
}
catch (Exception e)
{
ResultsLogger.Log(ResultID.Warning_FailedToExtractPathFiltersFromFile, e.Message, optionArgs);
}
}
}
break;
case "PATHFILTERS":
if (optionArgs.Length > 0)
{
string[] pathFilters = optionArgs.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
ParsePathFilters(pathFilters);
}
break;
case "FILTERSFROMFILE":
if (optionArgs.Length > 0)
{
try
{
JsonFile jsonFile = GetFiltersFromFile(optionArgs);
if (jsonFile != null)
{
string[] pathFilters = ConvertToFilterArray(jsonFile.pathsInclude, jsonFile.pathsExclude);
if (pathFilters != null && pathFilters.Length > 0)
{
pathFiltersFromFileSpecified = true;
ParsePathFilters(pathFilters);
}
string[] assemblyFilters = ConvertToFilterArray(jsonFile.assembliesInclude, jsonFile.assembliesExclude);
if (assemblyFilters != null && assemblyFilters.Length > 0)
{
assemblyFiltersFromFileSpecified = true;
ParseAssemblyFilters(assemblyFilters);
}
}
}
catch (Exception e)
{
ResultsLogger.Log(ResultID.Warning_FailedToExtractFiltersFromFile, e.Message, optionArgs);
}
}
break;
case "PATHREPLACEPATTERNS":
if (optionArgs.Length > 0)
{
pathReplacingSpecified = true;
m_PathReplacePatterns = optionArgs;
}
break;
case "SOURCEPATHS":
if (optionArgs.Length > 0)
{
string[] rawSourcePaths = optionArgs.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
for (int i = 0; i < rawSourcePaths.Length; ++i)
{
if (sourcePaths.Length > 0)
sourcePaths += ",";
sourcePaths += CoverageUtils.NormaliseFolderSeparators(rawSourcePaths[i]);
}
if (sourcePaths.Length > 0)
sourcePathsSpecified = true;
}
break;
default:
ResultsLogger.Log(ResultID.Warning_UnknownCoverageOptionProvided, optionArgsStr);
break;
}
}
if (m_IncludeAssemblies.Length == 0)
{
// If there are no inlcudedAssemblies specified but there are includedPaths specified
// then include all project assemblies so path filtering can take precedence over assembly filtering,
// othewise if there are no includedPaths specified neither then inlcude just the user assemblies (found under the Assets folder)
if (m_IncludePaths.Length > 0)
m_IncludeAssemblies = AssemblyFiltering.GetAllProjectAssembliesString();
else
m_IncludeAssemblies = AssemblyFiltering.GetUserOnlyAssembliesString();
}
if (m_ExcludeAssemblies.Length > 0)
m_ExcludeAssemblies += ",";
m_ExcludeAssemblies += AssemblyFiltering.kDefaultExcludedAssemblies;
assemblyFiltering.Parse(m_IncludeAssemblies, m_ExcludeAssemblies);
pathFiltering.Parse(m_IncludePaths, m_ExcludePaths);
pathReplacing.Parse(m_PathReplacePatterns);
}
private void ParseAssemblyFilters(string[] assemblyFilters)
{
for (int i = 0; i < assemblyFilters.Length; ++i)
{
string filter = assemblyFilters[i];
string filterBody = filter.Length > 1 ? filter.Substring(1) : string.Empty;
if (filter.StartsWith("+", StringComparison.OrdinalIgnoreCase))
{
if (m_IncludeAssemblies.Length > 0)
m_IncludeAssemblies += ",";
if (filterBody.StartsWith("<", StringComparison.OrdinalIgnoreCase))
{
if (string.Equals(filterBody, AssemblyFiltering.kAssetsAlias, StringComparison.OrdinalIgnoreCase))
m_IncludeAssemblies += AssemblyFiltering.GetUserOnlyAssembliesString();
else if (string.Equals(filterBody, AssemblyFiltering.kAllAlias, StringComparison.OrdinalIgnoreCase))
m_IncludeAssemblies += AssemblyFiltering.GetAllProjectAssembliesString();
else if (string.Equals(filterBody, AssemblyFiltering.kPackagesAlias, StringComparison.OrdinalIgnoreCase))
m_IncludeAssemblies += AssemblyFiltering.GetPackagesOnlyAssembliesString();
else if (string.Equals(filterBody, AssemblyFiltering.kCoreAlias, StringComparison.OrdinalIgnoreCase))
m_IncludeAssemblies += AssemblyFiltering.kCoreAssemblies;
}
else
{
m_IncludeAssemblies += filterBody;
}
}
else if (filter.StartsWith("-", StringComparison.OrdinalIgnoreCase))
{
if (m_ExcludeAssemblies.Length > 0)
m_ExcludeAssemblies += ",";
if (filterBody.StartsWith("<", StringComparison.OrdinalIgnoreCase))
{
if (string.Equals(filterBody, AssemblyFiltering.kAssetsAlias, StringComparison.OrdinalIgnoreCase))
m_ExcludeAssemblies += AssemblyFiltering.GetUserOnlyAssembliesString();
else if (string.Equals(filterBody, AssemblyFiltering.kAllAlias, StringComparison.OrdinalIgnoreCase))
m_ExcludeAssemblies += AssemblyFiltering.GetAllProjectAssembliesString();
else if (string.Equals(filterBody, AssemblyFiltering.kPackagesAlias, StringComparison.OrdinalIgnoreCase))
m_ExcludeAssemblies += AssemblyFiltering.GetPackagesOnlyAssembliesString();
else if (string.Equals(filterBody, AssemblyFiltering.kCoreAlias, StringComparison.OrdinalIgnoreCase))
m_ExcludeAssemblies += AssemblyFiltering.kCoreAssemblies;
}
else
{
m_ExcludeAssemblies += filterBody;
}
}
else
{
ResultsLogger.Log(ResultID.Warning_AssemblyFiltersNotPrefixed, filter);
}
}
}
private void ParsePathFilters(string[] pathFilters)
{
var sources = new string[0];
if (sourcePathsSpecified)
sources = sourcePaths.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
for (int i = 0; i < pathFilters.Length; ++i)
{
string filter = pathFilters[i];
string filterBody = filter.Length > 1 ? filter.Substring(1) : string.Empty;
var isRelative = !filterBody.StartsWith("*") && !filterBody.StartsWith("?") && string.IsNullOrEmpty(Path.GetPathRoot(filterBody));
//If current path is relative - expand it to an absolute path using specified source paths
if (isRelative && sourcePathsSpecified)
{
string expandedPaths = string.Empty;
foreach (var source in sources)
{
if (expandedPaths.Length > 0)
expandedPaths += ",";
expandedPaths += CoverageUtils.NormaliseFolderSeparators(Path.Combine(source, filterBody));
}
filterBody = expandedPaths;
}
if (filter.StartsWith("+", StringComparison.OrdinalIgnoreCase))
{
if (m_IncludePaths.Length > 0)
m_IncludePaths += ",";
m_IncludePaths += filterBody;
}
else if (filter.StartsWith("-", StringComparison.OrdinalIgnoreCase))
{
if (m_ExcludePaths.Length > 0)
m_ExcludePaths += ",";
m_ExcludePaths += filterBody;
}
else
{
ResultsLogger.Log(ResultID.Warning_PathFiltersNotPrefixed, filter);
}
}
if (m_IncludePaths.Length > 0 || m_ExcludePaths.Length > 0)
pathFiltersSpecified = true;
}
internal string[] GetPathFiltersFromFile(string path)
{
var paths = new List<string>();
foreach (var line in File.ReadAllLines(path))
{
var entry = line.Trim();
paths.Add(CoverageUtils.NormaliseFolderSeparators(entry));
}
return paths.ToArray();
}
internal JsonFile GetFiltersFromFile(string path)
{
string jsonString = JsonUtils.CleanJsonString(File.ReadAllText(path));
JsonUtils.ValidateJsonKeys(jsonString);
JsonFile jsonFile = JsonUtility.FromJson<JsonFile>(jsonString);
return jsonFile;
}
internal string[] ConvertToFilterArray(string[] include, string[] exclude)
{
var filtersList = new List<string>();
if (include != null && include.Length > 0)
{
foreach (var filter in include)
{
filtersList.Add($"+{filter}");
}
}
if (exclude != null && exclude.Length > 0)
{
foreach (var filter in exclude)
{
filtersList.Add($"-{filter}");
}
}
return filtersList.ToArray();
}
}
}