diff --git a/docs/multitool-usage.md b/docs/multitool-usage.md
index 3ab92cab9..151f880c2 100644
--- a/docs/multitool-usage.md
+++ b/docs/multitool-usage.md
@@ -11,10 +11,11 @@ Use the SARIF Multitool to rewrite, enrich, filter, result match, and do other c
| file-work-items | Send SARIF results to a work item tracking system such as GitHub or Azure DevOps |
| match-results-forward | Match Results run over run to identify New, Absent, and Unchanged Results |
| merge | Merge multiple SARIF files into one |
-| page | Extract a subset of results from a source SARIF file. |
-| query | Find the matching subset of a SARIF file and output it or log it. |
-| rebaseuri | Rebase the URIs in one or more sarif files. |
+| page | Extract a subset of results from a source SARIF file |
+| query | Find the matching subset of a SARIF file and output it or log it |
+| rebaseuri | Rebase the URIs in one or more sarif files |
| rewrite | Transform a SARIF file to a reformatted version |
+| suppress | Suppress results from a SARIF file |
| validate | Validate a SARIF File against the schema and against additional correctness rules. |
| help | See Usage |
| version | Display version information |
@@ -60,6 +61,9 @@ Sarif.Multitool merge C:\Input\*.sarif --recurse --output-directory=C:\Output\ -
: Extract new Results only from New Baseline
Sarif.Multitool query NewBaseline.sarif --expression "BaselineState == 'New'" --output Current.NewResults.sarif
+: Suppress Results
+Sarif.Multitool suppress current.sarif --justification "some justification" --alias "some alias" --guids --timestamps --expiryInDays 5 --status Accepted --output suppressed.sarif
+
: Validate a SARIF file conforms to the schema
Sarif.Multitool validate Other.sarif
```
diff --git a/src/ReleaseHistory.md b/src/ReleaseHistory.md
index e6c83b8c4..1f2fcb75b 100644
--- a/src/ReleaseHistory.md
+++ b/src/ReleaseHistory.md
@@ -1,5 +1,12 @@
# SARIF Package Release History (SDK, Driver, Converters, and Multitool)
+## Unreleased
+
+* FEATURE: `MultithreadCommandBase` will use cache when hashing is enabled. [#2388](https://github.com/microsoft/sarif-sdk/pull/2388)
+* FEATURE: Flow suppressions when baselining. [#2390](https://github.com/microsoft/sarif-sdk/pull/2390)
+* BUGFIX: Fix number of results when filing work item. [#2391](https://github.com/microsoft/sarif-sdk/pull/2391)
+* FEATURE: Add `suppress` command to multitool. [#2394](https://github.com/microsoft/sarif-sdk/pull/2394)
+
## **v2.4.11** [Sdk](https://www.nuget.org/packages/Sarif.Sdk/2.4.11) | [Driver](https://www.nuget.org/packages/Sarif.Driver/2.4.11) | [Converters](https://www.nuget.org/packages/Sarif.Converters/2.4.11) | [Multitool](https://www.nuget.org/packages/Sarif.Multitool/2.4.11) | [Multitool Library](https://www.nuget.org/packages/Sarif.Multitool.Library/2.4.11)
* BUGFIX: Fix partitioning visitor log duplication. [#2369](https://github.com/microsoft/sarif-sdk/pull/2369)
diff --git a/src/Sarif.Multitool.Library/OptionsInterpretter.cs b/src/Sarif.Multitool.Library/OptionsInterpretter.cs
index 1e1205edc..131518095 100644
--- a/src/Sarif.Multitool.Library/OptionsInterpretter.cs
+++ b/src/Sarif.Multitool.Library/OptionsInterpretter.cs
@@ -14,7 +14,6 @@ public class OptionsInterpretter
// Poor man's dependency injection
public OptionsInterpretter() : this(new EnvironmentVariableGetter())
{
-
}
public OptionsInterpretter(IEnvironmentVariableGetter environmentVariableGetter)
@@ -24,7 +23,7 @@ public OptionsInterpretter(IEnvironmentVariableGetter environmentVariableGetter)
private readonly IEnvironmentVariableGetter _environmentVariableGetter;
- // Protected methods for abstract classes and public methods for concrete classes to ensure proper roll up and no
+ // Protected methods for abstract classes and public methods for concrete classes to ensure proper roll up and no
// redundant execution. Only leaves of the class diagram should have public methods and be called outside this class
protected void ConsumeEnvVarsAndInterpretOptions(CommonOptionsBase commonOptionsBase)
{
@@ -38,17 +37,17 @@ protected void ConsumeEnvVarsAndInterpretOptions(CommonOptionsBase commonOptions
}
#pragma warning disable IDE0060 // Ignore unused parameter for now
+
protected void ConsumeEnvVarsAndInterpretOptions(ExportConfigurationOptions exportConfigurationOptions)
#pragma warning restore IDE0060
{
-
}
#pragma warning disable IDE0060 // Ignore unused parameter for now
+
protected void ConsumeEnvVarsAndInterpretOptions(ExportRulesMetadataOptions exportConfigurationOptions)
#pragma warning restore IDE0060
{
-
}
protected void ConsumeEnvVarsAndInterpretOptions(AnalyzeOptionsBase analyzeOptionsBase)
@@ -73,16 +72,23 @@ protected void ConsumeEnvVarsAndInterpretOptions(SingleFileOptionsBase singleFil
ConsumeEnvVarsAndInterpretOptions((CommonOptionsBase)singleFileOptionsBase);
}
+ public void ConsumeEnvVarsAndInterpretOptions(SuppressOptions options)
+ {
+ ConsumeEnvVarsAndInterpretOptions((SingleFileOptionsBase)options);
+ }
+
public void ConsumeEnvVarsAndInterpretOptions(AbsoluteUriOptions absoluteUriOptions)
{
ConsumeEnvVarsAndInterpretOptions((MultipleFilesOptionsBase)absoluteUriOptions);
}
#if DEBUG
+
public void ConsumeEnvVarsAndInterpretOptions(AnalyzeTestOptions analyzeTestOptions)
{
ConsumeEnvVarsAndInterpretOptions((AnalyzeOptionsBase)analyzeTestOptions);
}
+
#endif
public void ConsumeEnvVarsAndInterpretOptions(ApplyPolicyOptions applyPolicyOptions)
@@ -121,17 +127,17 @@ public void ConsumeEnvVarsAndInterpretOptions(MergeOptions mergeOptions)
}
#pragma warning disable IDE0060 // Ignore unused parameter for now
+
public void ConsumeEnvVarsAndInterpretOptions(PageOptions pageOptions)
#pragma warning restore IDE0060
{
-
}
#pragma warning disable IDE0060 // Ignore unused parameter for now
+
public void ConsumeEnvVarsAndInterpretOptions(QueryOptions queryOptions)
#pragma warning restore IDE0060
{
-
}
public void ConsumeEnvVarsAndInterpretOptions(RebaseUriOptions rebaseUriOptions)
diff --git a/src/Sarif.Multitool.Library/Sarif.Multitool.Library.csproj b/src/Sarif.Multitool.Library/Sarif.Multitool.Library.csproj
index e45912ffe..e0439a0f1 100644
--- a/src/Sarif.Multitool.Library/Sarif.Multitool.Library.csproj
+++ b/src/Sarif.Multitool.Library/Sarif.Multitool.Library.csproj
@@ -62,4 +62,4 @@
-
\ No newline at end of file
+
diff --git a/src/Sarif.Multitool.Library/SuppressCommand.cs b/src/Sarif.Multitool.Library/SuppressCommand.cs
new file mode 100644
index 000000000..5cf1813fa
--- /dev/null
+++ b/src/Sarif.Multitool.Library/SuppressCommand.cs
@@ -0,0 +1,82 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System;
+using System.Diagnostics;
+
+using Microsoft.CodeAnalysis.Sarif.Driver;
+using Microsoft.CodeAnalysis.Sarif.Readers;
+using Microsoft.CodeAnalysis.Sarif.Visitors;
+using Microsoft.CodeAnalysis.Sarif.Writers;
+
+namespace Microsoft.CodeAnalysis.Sarif.Multitool
+{
+ public class SuppressCommand : CommandBase
+ {
+ public SuppressCommand(IFileSystem fileSystem = null) : base(fileSystem)
+ {
+ }
+
+ public int Run(SuppressOptions options)
+ {
+ try
+ {
+ Console.WriteLine($"Suppress '{options.InputFilePath}' => '{options.OutputFilePath}'...");
+ var w = Stopwatch.StartNew();
+
+ bool valid = ValidateOptions(options);
+ if (!valid)
+ {
+ return FAILURE;
+ }
+
+ SarifLog currentSarifLog = PrereleaseCompatibilityTransformer.UpdateToCurrentVersion(FileSystem.FileReadAllText(options.InputFilePath),
+ options.Formatting,
+ out string _);
+
+ SarifLog reformattedLog = new SuppressVisitor(options.Justification,
+ options.Alias,
+ options.Guids,
+ options.Timestamps,
+ options.ExpiryInDays,
+ options.Status).VisitSarifLog(currentSarifLog);
+
+ string actualOutputPath = CommandUtilities.GetTransformedOutputFileName(options);
+ if (options.SarifOutputVersion == SarifVersion.OneZeroZero)
+ {
+ var visitor = new SarifCurrentToVersionOneVisitor();
+ visitor.VisitSarifLog(reformattedLog);
+
+ WriteSarifFile(FileSystem, visitor.SarifLogVersionOne, actualOutputPath, options.Formatting, SarifContractResolverVersionOne.Instance);
+ }
+ else
+ {
+ WriteSarifFile(FileSystem, reformattedLog, actualOutputPath, options.Formatting);
+ }
+
+ w.Stop();
+ Console.WriteLine($"Supress completed in {w.Elapsed}.");
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine(ex);
+ return FAILURE;
+ }
+
+ return SUCCESS;
+ }
+
+ private bool ValidateOptions(SuppressOptions options)
+ {
+ bool valid = true;
+
+ valid &= options.Validate();
+ valid &= options.ExpiryInDays >= 0;
+ valid &= !string.IsNullOrWhiteSpace(options.Justification);
+ valid &= (options.Status == SuppressionStatus.Accepted || options.Status == SuppressionStatus.UnderReview);
+ valid &= DriverUtilities.ReportWhetherOutputFileCanBeCreated(options.OutputFilePath, options.Force, FileSystem);
+
+ return valid;
+ }
+ }
+}
diff --git a/src/Sarif.Multitool.Library/SuppressOptions.cs b/src/Sarif.Multitool.Library/SuppressOptions.cs
new file mode 100644
index 000000000..ef2162763
--- /dev/null
+++ b/src/Sarif.Multitool.Library/SuppressOptions.cs
@@ -0,0 +1,44 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using CommandLine;
+
+using Microsoft.CodeAnalysis.Sarif.Driver;
+
+namespace Microsoft.CodeAnalysis.Sarif.Multitool
+{
+ [Verb("suppress", HelpText = "Enrich a SARIF file with additional data.")]
+ public class SuppressOptions : SingleFileOptionsBase
+ {
+ [Option(
+ "justification",
+ HelpText = "A string that provides the rationale for the suppressions",
+ Required = true)]
+ public string Justification { get; set; }
+
+ [Option(
+ "alias",
+ HelpText = "The account name associated with the suppression.")]
+ public string Alias { get; set; }
+
+ [Option(
+ "guids",
+ HelpText = "A UUID that will be associated with a suppression.")]
+ public bool Guids { get; set; }
+
+ [Option(
+ "timestamps",
+ HelpText = "The property 'timeUtc' that will be associated with a suppression.")]
+ public bool Timestamps { get; set; }
+
+ [Option(
+ "expiryInDays",
+ HelpText = "The property 'expiryUtc' that will be associated with a suppression from the 'timeUtc'.")]
+ public int ExpiryInDays { get; set; }
+
+ [Option(
+ "status",
+ HelpText = "The status that will be used in the suppression. Valid values include Accepted and UnderReview.")]
+ public SuppressionStatus Status { get; set; }
+ }
+}
diff --git a/src/Sarif.Multitool/Program.cs b/src/Sarif.Multitool/Program.cs
index 3bb28586b..c820295bd 100644
--- a/src/Sarif.Multitool/Program.cs
+++ b/src/Sarif.Multitool/Program.cs
@@ -37,6 +37,7 @@ public static int Main(string[] args)
QueryOptions,
RebaseUriOptions,
RewriteOptions,
+ SuppressOptions,
ValidateOptions>(args)
.WithParsed(x => { optionsInterpretter.ConsumeEnvVarsAndInterpretOptions(x); })
#if DEBUG
@@ -53,6 +54,7 @@ public static int Main(string[] args)
.WithParsed(x => { optionsInterpretter.ConsumeEnvVarsAndInterpretOptions(x); })
.WithParsed(x => { optionsInterpretter.ConsumeEnvVarsAndInterpretOptions(x); })
.WithParsed(x => { optionsInterpretter.ConsumeEnvVarsAndInterpretOptions(x); })
+ .WithParsed(x => { optionsInterpretter.ConsumeEnvVarsAndInterpretOptions(x); })
.WithParsed(x => { optionsInterpretter.ConsumeEnvVarsAndInterpretOptions(x); })
.MapResult(
(AbsoluteUriOptions absoluteUriOptions) => new AbsoluteUriCommand().Run(absoluteUriOptions),
@@ -71,6 +73,7 @@ public static int Main(string[] args)
(QueryOptions queryOptions) => new QueryCommand().Run(queryOptions),
(RebaseUriOptions rebaseOptions) => new RebaseUriCommand().Run(rebaseOptions),
(RewriteOptions rewriteOptions) => new RewriteCommand().Run(rewriteOptions),
+ (SuppressOptions options) => new SuppressCommand().Run(options),
(ValidateOptions validateOptions) => new ValidateCommand().Run(validateOptions),
_ => HandleParseError(args));
}
diff --git a/src/Sarif/Visitors/SuppressVisitor.cs b/src/Sarif/Visitors/SuppressVisitor.cs
new file mode 100644
index 000000000..1903e68f8
--- /dev/null
+++ b/src/Sarif/Visitors/SuppressVisitor.cs
@@ -0,0 +1,76 @@
+// Copyright (c) Microsoft. 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.Globalization;
+
+namespace Microsoft.CodeAnalysis.Sarif.Visitors
+{
+ public class SuppressVisitor : SarifRewritingVisitor
+ {
+ private readonly bool guids;
+ private readonly string alias;
+ private readonly bool timestamps;
+ private readonly DateTime timeUtc;
+ private readonly DateTime expiryUtc;
+ private readonly int expiryInDays;
+ private readonly string justification;
+ private readonly SuppressionStatus suppressionStatus;
+
+ public SuppressVisitor(string justification,
+ string alias,
+ bool guids,
+ bool timestamps,
+ int expiryInDays,
+ SuppressionStatus suppressionStatus)
+ {
+ this.alias = alias;
+ this.guids = guids;
+ this.timestamps = timestamps;
+ this.timeUtc = DateTime.UtcNow;
+ this.expiryInDays = expiryInDays;
+ this.justification = justification;
+ this.suppressionStatus = suppressionStatus;
+ this.expiryUtc = this.timeUtc.AddDays(expiryInDays);
+ }
+
+ public override Result VisitResult(Result node)
+ {
+ if (node.Suppressions == null)
+ {
+ node.Suppressions = new List();
+ }
+
+ var suppression = new Suppression
+ {
+ Status = suppressionStatus,
+ Justification = justification,
+ Kind = SuppressionKind.External
+ };
+
+ if (!string.IsNullOrWhiteSpace(alias))
+ {
+ suppression.SetProperty(nameof(alias), alias);
+ }
+
+ if (guids)
+ {
+ suppression.SetProperty("guid", Guid.NewGuid());
+ }
+
+ if (timestamps)
+ {
+ suppression.SetProperty(nameof(timeUtc), timeUtc);
+ }
+
+ if (expiryInDays > 0)
+ {
+ suppression.SetProperty(nameof(expiryUtc), expiryUtc);
+ }
+
+ node.Suppressions.Add(suppression);
+ return base.VisitResult(node);
+ }
+ }
+}
diff --git a/src/Test.UnitTests.Sarif.Multitool.Library/SuppressCommandTests.cs b/src/Test.UnitTests.Sarif.Multitool.Library/SuppressCommandTests.cs
new file mode 100644
index 000000000..1bd44ba1e
--- /dev/null
+++ b/src/Test.UnitTests.Sarif.Multitool.Library/SuppressCommandTests.cs
@@ -0,0 +1,177 @@
+// Copyright (c) Microsoft. 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.Text;
+
+using FluentAssertions;
+
+using Microsoft.CodeAnalysis.Sarif.Driver;
+
+using Moq;
+
+using Newtonsoft.Json;
+
+using Xunit;
+
+namespace Microsoft.CodeAnalysis.Sarif.Multitool
+{
+ public class SuppressCommandTests
+ {
+ [Fact]
+ public void SuppressCommand_ShouldReturnFailure_WhenBadArgumentsAreSupplied()
+ {
+ const string outputPath = @"c:\output.sarif";
+ var optionsTestCases = new SuppressOptions[]
+ {
+ new SuppressOptions
+ {
+ ExpiryInDays = -1
+ },
+ new SuppressOptions
+ {
+ ExpiryInDays = 1,
+ Justification = string.Empty
+ },
+ new SuppressOptions
+ {
+ ExpiryInDays = 1,
+ Justification = "some justification",
+ Status = SuppressionStatus.Rejected
+ },
+ new SuppressOptions
+ {
+ ExpiryInDays = 1,
+ Justification = "some justification",
+ Status = SuppressionStatus.Accepted,
+ SarifOutputVersion = SarifVersion.Unknown
+ },
+ new SuppressOptions
+ {
+ ExpiryInDays = -1,
+ Justification = "some justification",
+ OutputFilePath = outputPath,
+ Status = SuppressionStatus.Accepted
+ },
+ };
+
+ var mock = new Mock();
+ mock.Setup(f => f.FileExists(outputPath))
+ .Returns(false);
+
+ foreach (SuppressOptions options in optionsTestCases)
+ {
+ var command = new SuppressCommand();
+ command.Run(options).Should().Be(CommandBase.FAILURE);
+ }
+ }
+
+ [Fact]
+ public void SuppressCommand_ShouldReturnSuccess_WhenCorrectArgumentsAreSupplied()
+ {
+ var optionsTestCases = new SuppressOptions[]
+ {
+ new SuppressOptions
+ {
+ Alias = "some alias",
+ InputFilePath = @"C:\input.sarif",
+ OutputFilePath = @"C:\output.sarif",
+ Justification = "some justification",
+ Status = SuppressionStatus.Accepted
+ },
+ new SuppressOptions
+ {
+ InputFilePath = @"C:\input.sarif",
+ OutputFilePath = @"C:\output.sarif",
+ Justification = "some justification",
+ Status = SuppressionStatus.UnderReview
+ },
+ new SuppressOptions
+ {
+ Guids = true,
+ InputFilePath = @"C:\input.sarif",
+ OutputFilePath = @"C:\output.sarif",
+ Justification = "some justification",
+ Status = SuppressionStatus.Accepted
+ },
+ new SuppressOptions
+ {
+ Guids = true,
+ ExpiryInDays = 5,
+ Timestamps = true,
+ InputFilePath = @"C:\input.sarif",
+ OutputFilePath = @"C:\output.sarif",
+ Justification = "some justification",
+ Status = SuppressionStatus.Accepted
+ },
+ };
+
+ foreach (SuppressOptions options in optionsTestCases)
+ {
+ VerifySuppressCommand(options);
+ }
+ }
+
+ private static void VerifySuppressCommand(SuppressOptions options)
+ {
+ var current = new SarifLog
+ {
+ Runs = new List
+ {
+ new Run
+ {
+ Results = new List
+ {
+ new Result
+ {
+ RuleId = "Test0001"
+ }
+ }
+ }
+ }
+ };
+
+ var transformedContents = new StringBuilder();
+ var mockFileSystem = new Mock();
+ mockFileSystem
+ .Setup(x => x.FileReadAllText(options.InputFilePath))
+ .Returns(JsonConvert.SerializeObject(current));
+
+ mockFileSystem
+ .Setup(x => x.FileCreate(options.OutputFilePath))
+ .Returns(() => new MemoryStreamToStringBuilder(transformedContents));
+
+ var command = new SuppressCommand(mockFileSystem.Object);
+ command.Run(options).Should().Be(CommandBase.SUCCESS);
+
+ SarifLog suppressed = JsonConvert.DeserializeObject(transformedContents.ToString());
+ suppressed.Runs[0].Results[0].Suppressions.Should().NotBeNullOrEmpty();
+
+ Suppression suppression = suppressed.Runs[0].Results[0].Suppressions[0];
+ suppression.Status.Should().Be(options.Status);
+ suppression.Kind.Should().Be(SuppressionKind.External);
+ suppression.Justification.Should().Be(options.Justification);
+
+ if (!string.IsNullOrWhiteSpace(options.Alias))
+ {
+ suppression.GetProperty("alias").Should().Be(options.Alias);
+ }
+
+ if (options.Guids && suppression.TryGetProperty("guid", out Guid guid))
+ {
+ guid.Should().NotBeEmpty();
+ }
+
+ if (options.Timestamps && suppression.TryGetProperty("timeUtc", out DateTime timeUtc))
+ {
+ timeUtc.Should().BeCloseTo(DateTime.UtcNow);
+ }
+
+ if (options.ExpiryInDays > 0 && suppression.TryGetProperty("expiryUtc", out DateTime expiryUtc))
+ {
+ expiryUtc.Should().BeCloseTo(DateTime.UtcNow.AddDays(options.ExpiryInDays));
+ }
+ }
+ }
+}
diff --git a/src/Test.UnitTests.Sarif/Visitors/SuppressVisitorTests.cs b/src/Test.UnitTests.Sarif/Visitors/SuppressVisitorTests.cs
new file mode 100644
index 000000000..11228baaf
--- /dev/null
+++ b/src/Test.UnitTests.Sarif/Visitors/SuppressVisitorTests.cs
@@ -0,0 +1,138 @@
+// Copyright (c) Microsoft. 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 FluentAssertions;
+
+using Microsoft.CodeAnalysis.Sarif.Visitors;
+
+using Xunit;
+
+namespace Microsoft.CodeAnalysis.Sarif.UnitTests.Visitors
+{
+ public class SuppressVisitorTests
+ {
+ [Fact]
+ public void SuppressVisitor_ShouldFlowPropertiesCorrectly()
+ {
+ var testCases = new[]
+ {
+ new
+ {
+ Alias = string.Empty,
+ Justification = "some suppress justification",
+ Guids = false,
+ Timestamps = false,
+ ExpiryInDays = 0,
+ SuppressionStatus = SuppressionStatus.Accepted
+ },
+ new
+ {
+ Alias = "some alias",
+ Justification = "some suppress justification",
+ Guids = false,
+ Timestamps = false,
+ ExpiryInDays = 0,
+ SuppressionStatus = SuppressionStatus.Accepted
+ },
+ new
+ {
+ Alias = "some alias",
+ Justification = "some suppress justification",
+ Guids = true,
+ Timestamps = false,
+ ExpiryInDays = 0,
+ SuppressionStatus = SuppressionStatus.Accepted
+ },
+ new
+ {
+ Alias = "some alias",
+ Justification = "some suppress justification",
+ Guids = true,
+ Timestamps = true,
+ ExpiryInDays = 0,
+ SuppressionStatus = SuppressionStatus.Accepted
+ },
+ new
+ {
+ Alias = "some alias",
+ Justification = "some suppress justification",
+ Guids = true,
+ Timestamps = true,
+ ExpiryInDays = 1,
+ SuppressionStatus = SuppressionStatus.Accepted
+ },
+ new
+ {
+ Alias = "some alias",
+ Justification = "some suppress justification",
+ Guids = true,
+ Timestamps = true,
+ ExpiryInDays = 1,
+ SuppressionStatus = SuppressionStatus.UnderReview
+ },
+ };
+
+ foreach (var testCase in testCases)
+ {
+ VerifySuppressVisitor(testCase.Alias,
+ testCase.Justification,
+ testCase.Guids,
+ testCase.Timestamps,
+ testCase.ExpiryInDays,
+ testCase.SuppressionStatus);
+ }
+ }
+
+ private static void VerifySuppressVisitor(string alias,
+ string justification,
+ bool guids,
+ bool timestamps,
+ int expiryInDays,
+ SuppressionStatus suppressionStatus)
+ {
+ var visitor = new SuppressVisitor(justification,
+ alias,
+ guids,
+ timestamps,
+ expiryInDays,
+ suppressionStatus);
+
+ var random = new Random();
+ SarifLog current = RandomSarifLogGenerator.GenerateSarifLogWithRuns(random, runCount: 1, resultCount: 1);
+ SarifLog suppressed = visitor.VisitSarifLog(current);
+ IList results = suppressed.Runs[0].Results;
+ foreach (Result result in results)
+ {
+ result.Suppressions.Should().NotBeNullOrEmpty();
+
+ Suppression suppression = result.Suppressions[0];
+ suppression.Status.Should().Be(suppressionStatus);
+ suppression.Justification.Should().Be(justification);
+ suppression.Kind.Should().Be(SuppressionKind.External);
+
+ if (!string.IsNullOrWhiteSpace(alias))
+ {
+ suppression.GetProperty("alias").Should().Be(alias);
+ }
+
+ if (guids && suppression.TryGetProperty("guid", out Guid guid))
+ {
+ guid.Should().NotBeEmpty();
+ }
+
+ if (timestamps && suppression.TryGetProperty("timeUtc", out DateTime timeUtc))
+ {
+ timeUtc.Should().BeCloseTo(DateTime.UtcNow);
+ }
+
+ if (expiryInDays > 0 && suppression.TryGetProperty("expiryUtc", out DateTime expiryUtc))
+ {
+ expiryUtc.Should().BeCloseTo(DateTime.UtcNow.AddDays(expiryInDays));
+ }
+ }
+ }
+ }
+}