| 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() |
| { |
| |
| m_CoverageOptionsArg = m_CoverageOptionsArg.TrimEnd('"'); |
|
|
| |
| |
| 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 (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 (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(); |
| } |
| } |
| } |
|
|