* Fix #402

Adds an argument to limit the number of matches a given tag will produce.
This commit is contained in:
Gabe Stocco 2021-12-09 17:47:20 -08:00 коммит произвёл GitHub
Родитель a4993261d3
Коммит 860d9009b4
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
5 изменённых файлов: 125 добавлений и 10 удалений

Просмотреть файл

@ -74,6 +74,9 @@ namespace Microsoft.ApplicationInspector.CLI
[Option('A', "allow-all-tags-in-build-files", Required = false, HelpText = "Allow all tags (not just Metadata tags) in files of type Build.")]
public bool AllowAllTagsInBuildFiles { get; internal set; }
[Option('M', "max-num-matches-per-tag", Required = false, HelpText = "If non-zero, and TagsOnly is not set, will ignore rules based on if all of their tags have been found the set value number of times.")]
public int MaxNumMatchesPerTag { get; set; } = 0;
}
[Verb("tagdiff", HelpText = "Compares unique tag values between two source paths")]

Просмотреть файл

@ -262,7 +262,8 @@ namespace Microsoft.ApplicationInspector.CLI
ScanUnknownTypes = cliOptions.ScanUnknownTypes,
TagsOnly = cliOptions.TagsOnly,
NoFileMetadata = cliOptions.NoFileMetadata,
AllowAllTagsInBuildFiles = cliOptions.AllowAllTagsInBuildFiles
AllowAllTagsInBuildFiles = cliOptions.AllowAllTagsInBuildFiles,
MaxNumMatchesPerTag = cliOptions.MaxNumMatchesPerTag
});
if (!cliOptions.NoShowProgressBar)

Просмотреть файл

@ -44,6 +44,10 @@ namespace Microsoft.ApplicationInspector.Commands
public int ContextLines { get; set; } = 3;
public bool ScanUnknownTypes { get; set; }
public bool NoFileMetadata { get; set; }
/// <summary>
/// If non-zero, and <see cref="TagsOnly"/> is not set, will ignore rules based on if all of their tags have been found the set value number of times.
/// </summary>
public int MaxNumMatchesPerTag { get; set; } = 0;
}
/// <summary>
@ -294,6 +298,7 @@ namespace Microsoft.ApplicationInspector.Commands
/// <param name="cancellationToken"></param>
/// <param name="opts"></param>
/// <param name="populatedEntries"></param>
[Obsolete("Instead PopulateRecords with no options argument and set the options when creating the AnalyzeCommand.")]
public AnalyzeResult.ExitCode PopulateRecords(CancellationToken cancellationToken, AnalyzeOptions opts, IEnumerable<FileEntry> populatedEntries)
{
WriteOnce.SafeLog("AnalyzeCommand::PopulateRecords", LogLevel.Trace);
@ -372,7 +377,7 @@ namespace Microsoft.ApplicationInspector.Commands
{
_metaDataHelper.AddLanguage("Unknown");
languageInfo = new LanguageInfo() { Extensions = new string[] { Path.GetExtension(file.FullPath) }, Name = "Unknown" };
if (!_options.ScanUnknownTypes)
if (!opts.ScanUnknownTypes)
{
fileRecord.Status = ScanState.Skipped;
}
@ -391,6 +396,10 @@ namespace Microsoft.ApplicationInspector.Commands
{
results = _rulesProcessor.AnalyzeFile(file, languageInfo, _metaDataHelper.UniqueTags.Keys, -1);
}
else if (opts.MaxNumMatchesPerTag > 0)
{
results = _rulesProcessor.AnalyzeFile(file, languageInfo, _metaDataHelper.UniqueTags.Where(x => x.Value < opts.MaxNumMatchesPerTag).Select(x => x.Key), opts.ContextLines);
}
else
{
results = _rulesProcessor.AnalyzeFile(file, languageInfo, null, opts.ContextLines);
@ -421,6 +430,10 @@ namespace Microsoft.ApplicationInspector.Commands
{
results = _rulesProcessor.AnalyzeFile(file, languageInfo, _metaDataHelper.UniqueTags.Keys, -1);
}
else if (opts.MaxNumMatchesPerTag > 0)
{
results = _rulesProcessor.AnalyzeFile(file, languageInfo, _metaDataHelper.UniqueTags.Where(x => x.Value < opts.MaxNumMatchesPerTag).Select(x => x.Key), opts.ContextLines);
}
else
{
results = _rulesProcessor.AnalyzeFile(file, languageInfo, null, opts.ContextLines);
@ -439,6 +452,13 @@ namespace Microsoft.ApplicationInspector.Commands
{
_metaDataHelper.AddTagsFromMatchRecord(matchRecord);
}
else if (opts.MaxNumMatchesPerTag > 0)
{
if (matchRecord.Tags?.Any(x => _metaDataHelper.UniqueTags.TryGetValue(x, out int value) is bool foundValue && (!foundValue || foundValue && value < opts.MaxNumMatchesPerTag)) ?? true)
{
_metaDataHelper.AddMatchRecord(matchRecord);
}
}
else
{
_metaDataHelper.AddMatchRecord(matchRecord);
@ -459,6 +479,14 @@ namespace Microsoft.ApplicationInspector.Commands
}
}
/// <summary>
/// Populate the MetaDataHelper with the data from the FileEntries
/// </summary>
/// <param name="cancellationToken"></param>
/// <param name="populatedEntries"></param>
/// <returns></returns>
public AnalyzeResult.ExitCode PopulateRecords(CancellationToken cancellationToken, IEnumerable<FileEntry> populatedEntries) => PopulateRecords(cancellationToken, _options, populatedEntries);
/// <summary>
/// Populate the records in the metadata asynchronously.
/// </summary>
@ -525,9 +553,9 @@ namespace Microsoft.ApplicationInspector.Commands
if (fileRecord.Status != ScanState.Skipped)
{
var results = _options.TagsOnly ?
await _rulesProcessor.AnalyzeFileAsync(file, languageInfo, cancellationToken, _metaDataHelper.UniqueTags.Keys, -1) :
await _rulesProcessor.AnalyzeFileAsync(file, languageInfo, cancellationToken, null, _options.ContextLines);
var contextLines = _options.TagsOnly ? -1 : _options.ContextLines;
var ignoredTags = _options.TagsOnly ? _metaDataHelper.UniqueTags.Keys : _options.MaxNumMatchesPerTag > 0 ? _metaDataHelper.UniqueTags.Where(x => x.Value < _options.MaxNumMatchesPerTag).Select(x => x.Key) : null;
var results = await _rulesProcessor.AnalyzeFileAsync(file, languageInfo, cancellationToken, ignoredTags, contextLines);
fileRecord.Status = ScanState.Analyzed;
if (results.Any())
@ -541,6 +569,13 @@ namespace Microsoft.ApplicationInspector.Commands
{
_metaDataHelper.AddTagsFromMatchRecord(matchRecord);
}
else if (_options.MaxNumMatchesPerTag > 0)
{
if (matchRecord.Tags?.Any(x => _metaDataHelper.UniqueTags.TryGetValue(x, out int value) is bool foundValue && (!foundValue || foundValue && value < _options.MaxNumMatchesPerTag)) ?? true)
{
_metaDataHelper.AddMatchRecord(matchRecord);
}
}
else
{
_metaDataHelper.AddMatchRecord(matchRecord);
@ -827,7 +862,7 @@ namespace Microsoft.ApplicationInspector.Commands
if (_options.ProcessingTimeOut > 0)
{
using var cts = new CancellationTokenSource();
var t = Task.Run(() => PopulateRecords(cts.Token, _options, fileEntries), cts.Token);
var t = Task.Run(() => PopulateRecords(cts.Token, fileEntries), cts.Token);
if (!t.Wait(new TimeSpan(0, 0, 0, 0, _options.ProcessingTimeOut)))
{
timedOut = true;
@ -845,7 +880,7 @@ namespace Microsoft.ApplicationInspector.Commands
}
else
{
PopulateRecords(new CancellationToken(), _options, fileEntries);
PopulateRecords(new CancellationToken(), fileEntries);
}
}
}

Просмотреть файл

@ -23,7 +23,7 @@ namespace Microsoft.ApplicationInspector.Commands
internal ConcurrentDictionary<string, byte> UniqueDependencies { get; set; } = new ConcurrentDictionary<string, byte>();
private ConcurrentDictionary<string, byte> AppTypes { get; set; } = new ConcurrentDictionary<string, byte>();
internal ConcurrentDictionary<string, byte> UniqueTags { get; set; } = new ConcurrentDictionary<string, byte>();
internal ConcurrentDictionary<string, int> UniqueTags { get; set; } = new ConcurrentDictionary<string, int>();
private ConcurrentDictionary<string, byte> Outputs { get; set; } = new ConcurrentDictionary<string, byte>();
private ConcurrentDictionary<string, byte> Targets { get; set; } = new ConcurrentDictionary<string, byte>();
private ConcurrentDictionary<string, byte> CPUTargets { get; set; } = new ConcurrentDictionary<string, byte>();
@ -122,7 +122,10 @@ namespace Microsoft.ApplicationInspector.Commands
//update list of unique tags as we go
foreach (string tag in matchRecord.Tags ?? Array.Empty<string>())
{
UniqueTags.TryAdd(tag, 0);
if (!UniqueTags.TryAdd(tag, 1))
{
UniqueTags[tag]++;
}
}
}
}
@ -196,7 +199,10 @@ namespace Microsoft.ApplicationInspector.Commands
//update list of unique tags as we go
foreach (string tag in nonCounters)
{
UniqueTags.TryAdd(tag, 0);
if (!UniqueTags.TryAdd(tag, 1))
{
UniqueTags[tag]++;
}
}
Matches.Add(matchRecord);

Просмотреть файл

@ -66,6 +66,76 @@ namespace ApplicationInspector.Unitprocess.Commands
Assert.IsTrue(exitCode == AnalyzeResult.ExitCode.CriticalError);//test fails even when values match unless this case run individually -mstest bug?
}
[TestMethod]
public void MaxNumMatches_Pass()
{
AnalyzeOptions options = new AnalyzeOptions()
{
SourcePath = new string[1] { Path.Combine(Helper.GetPath(Helper.AppPath.testSource), @"unzipped\simple\main.cpp") },
FilePathExclusions = Array.Empty<string>(), //allow source under unittest path,
MaxNumMatchesPerTag = 1
};
AnalyzeResult.ExitCode exitCode = AnalyzeResult.ExitCode.CriticalError;
AnalyzeCommand command = new AnalyzeCommand(options);
AnalyzeResult result = command.GetResult();
Assert.AreEqual(1, result.Metadata.Matches.Count(x => x.Tags.Contains("Platform.OS.Microsoft.WindowsStandard")));
exitCode = result.ResultCode;
Assert.IsTrue(exitCode == AnalyzeResult.ExitCode.Success);
}
[TestMethod]
public void MaxNumMatchesDisabled_Pass()
{
AnalyzeOptions options = new AnalyzeOptions()
{
SourcePath = new string[1] { Path.Combine(Helper.GetPath(Helper.AppPath.testSource), @"unzipped\simple\main.cpp") },
FilePathExclusions = Array.Empty<string>(), //allow source under unittest path,
};
AnalyzeResult.ExitCode exitCode = AnalyzeResult.ExitCode.CriticalError;
AnalyzeCommand command = new AnalyzeCommand(options);
AnalyzeResult result = command.GetResult();
Assert.AreEqual(3, result.Metadata.Matches.Count(x => x.Tags.Contains("Platform.OS.Microsoft.WindowsStandard")));
exitCode = result.ResultCode;
Assert.IsTrue(exitCode == AnalyzeResult.ExitCode.Success);
}
[TestMethod]
public async Task MaxNumMatchesAsync_Pass()
{
AnalyzeOptions options = new AnalyzeOptions()
{
SourcePath = new string[1] { Path.Combine(Helper.GetPath(Helper.AppPath.testSource), @"unzipped\simple\main.cpp") },
FilePathExclusions = Array.Empty<string>(), //allow source under unittest path,
MaxNumMatchesPerTag = 1
};
AnalyzeResult.ExitCode exitCode = AnalyzeResult.ExitCode.CriticalError;
AnalyzeCommand command = new AnalyzeCommand(options);
AnalyzeResult result = await command.GetResultAsync(new CancellationTokenSource().Token);
Assert.AreEqual(1, result.Metadata.Matches.Count(x => x.Tags.Contains("Platform.OS.Microsoft.WindowsStandard")));
exitCode = result.ResultCode;
Assert.IsTrue(exitCode == AnalyzeResult.ExitCode.Success);
}
[TestMethod]
public async Task MaxNumMatchesAsyncDisabled_Pass()
{
AnalyzeOptions options = new AnalyzeOptions()
{
SourcePath = new string[1] { Path.Combine(Helper.GetPath(Helper.AppPath.testSource), @"unzipped\simple\main.cpp") },
FilePathExclusions = Array.Empty<string>(), //allow source under unittest path,
};
AnalyzeResult.ExitCode exitCode = AnalyzeResult.ExitCode.CriticalError;
AnalyzeCommand command = new AnalyzeCommand(options);
AnalyzeResult result = await command.GetResultAsync(new CancellationTokenSource().Token);
Assert.AreEqual(3, result.Metadata.Matches.Count(x => x.Tags.Contains("Platform.OS.Microsoft.WindowsStandard")));
exitCode = result.ResultCode;
Assert.IsTrue(exitCode == AnalyzeResult.ExitCode.Success);
}
[TestMethod]
public void BasicAnalyze_Pass()
{