diff --git a/src/ReleaseHistory.md b/src/ReleaseHistory.md index 4e0b2a1e6..82c399ee6 100644 --- a/src/ReleaseHistory.md +++ b/src/ReleaseHistory.md @@ -2,6 +2,7 @@ ## Unreleased +* BREAKING: `AnalyzeCommandBase` previously persisted all scan target artifacts to SARIF logs rather than only persisting artifacts referenced by an analysis result, when an option to persist hashes, text file or binary information was set. `MultithreadedAnalyzeCommandBase` previously persisted all scan targets artifacts to SARIF logs in cases when hash insertion was eenabled rather than only persisting artifacts referenced by an analysis result. [#2433](https://github.com/microsoft/sarif-sdk/pull/2433) * BUGFIX: Adjust Json Serialization field order for ReportingDescriptor and skip emit empty AutomationDetails node. [#2420](https://github.com/microsoft/sarif-sdk/pull/2420) * BREAKING: Fix `InvalidOperationException` when using PropertiesDictionary in a multithreaded application, and remove `[Serializable]` from it. Now use of BinaryFormatter on it will result in `SerializationException`: Type `PropertiesDictionary` is not marked as serializable. [#2415](https://github.com/microsoft/sarif-sdk/pull/2415) diff --git a/src/Sarif.Driver/Sdk/AnalyzeCommandBase.cs b/src/Sarif.Driver/Sdk/AnalyzeCommandBase.cs index 5b96d6720..94bd4973b 100644 --- a/src/Sarif.Driver/Sdk/AnalyzeCommandBase.cs +++ b/src/Sarif.Driver/Sdk/AnalyzeCommandBase.cs @@ -169,7 +169,7 @@ private void Analyze(TOptions options, AggregatingLogger logger) targets = ValidateTargetsExist(_rootContext, targets); // 5. Initialize report file, if configured. - InitializeOutputFile(options, _rootContext, targets); + InitializeOutputFile(options, _rootContext); // 6. Instantiate skimmers. ISet> skimmers = CreateSkimmers(options, _rootContext); @@ -294,6 +294,7 @@ protected virtual TContext CreateContext( else { context.Hashes = HashUtilities.ComputeHashes(filePath); + _pathToHashDataMap?.Add(filePath, context.Hashes); } } } @@ -352,7 +353,7 @@ protected virtual void InitializeConfiguration(TOptions options, TContext contex } } - private void InitializeOutputFile(TOptions analyzeOptions, TContext context, ISet targets) + private void InitializeOutputFile(TOptions analyzeOptions, TContext context) { string filePath = analyzeOptions.OutputFilePath; AggregatingLogger aggregatingLogger = (AggregatingLogger)context.Logger; @@ -388,7 +389,7 @@ private void InitializeOutputFile(TOptions analyzeOptions, TContext context, ISe dataToRemove, tool: _tool, run: _run, - analysisTargets: targets, + analysisTargets: null, quiet: analyzeOptions.Quiet, invocationTokensToRedact: GenerateSensitiveTokensList(), invocationPropertiesToLog: analyzeOptions.InvocationPropertiesToLog, @@ -404,7 +405,7 @@ private void InitializeOutputFile(TOptions analyzeOptions, TContext context, ISe dataToRemove, tool: _tool, run: _run, - analysisTargets: targets, + analysisTargets: null, invocationTokensToRedact: GenerateSensitiveTokensList(), invocationPropertiesToLog: analyzeOptions.InvocationPropertiesToLog, levels: analyzeOptions.Level, diff --git a/src/Sarif.Driver/Sdk/MultithreadedAnalyzeCommandBase.cs b/src/Sarif.Driver/Sdk/MultithreadedAnalyzeCommandBase.cs index 744645ae1..60aa1554a 100644 --- a/src/Sarif.Driver/Sdk/MultithreadedAnalyzeCommandBase.cs +++ b/src/Sarif.Driver/Sdk/MultithreadedAnalyzeCommandBase.cs @@ -40,6 +40,7 @@ public abstract class MultithreadedAnalyzeCommandBase : Plug private Channel _resultsWritingChannel; private Channel _fileEnumerationChannel; private Dictionary> _hashToFilesMap; + private IDictionary _pathToHashDataMap; private ConcurrentDictionary _fileContexts; public Exception ExecutionException { get; set; } @@ -485,12 +486,9 @@ private async Task HashAsync() _hashToFilesMap[hashData.Sha256] = paths; } - _run?.GetFileIndex(new ArtifactLocation { Uri = context.TargetUri }, - dataToInsert: _dataToInsert, - hashData: hashData); - paths.Add(localPath); context.Hashes = hashData; + _pathToHashDataMap?.Add(localPath, hashData); } await _fileEnumerationChannel.Writer.WriteAsync(index); @@ -705,6 +703,7 @@ private void InitializeOutputFile(TOptions analyzeOptions, TContext context) kinds: analyzeOptions.Kind, insertProperties: analyzeOptions.InsertProperties); } + _pathToHashDataMap = sarifLogger.AnalysisTargetToHashDataMap; sarifLogger.AnalysisStarted(); aggregatingLogger.Loggers.Add(sarifLogger); }, diff --git a/src/Sarif/Autogenerated/ReportingDescriptor.cs b/src/Sarif/Autogenerated/ReportingDescriptor.cs index 662e79dc2..84e489e38 100644 --- a/src/Sarif/Autogenerated/ReportingDescriptor.cs +++ b/src/Sarif/Autogenerated/ReportingDescriptor.cs @@ -49,31 +49,31 @@ public SarifNodeKind SarifNodeKind /// /// An array of stable, opaque identifiers by which this report was known in some previous version of the analysis tool. /// - [DataMember(Name = "deprecatedIds", IsRequired = false, EmitDefaultValue = false, Order = 8)] + [DataMember(Name = "deprecatedIds", IsRequired = false, EmitDefaultValue = false, Order = 9)] public virtual IList DeprecatedIds { get; set; } /// /// A unique identifier for the reporting descriptor in the form of a GUID. /// - [DataMember(Name = "guid", IsRequired = false, EmitDefaultValue = false, Order = 9)] + [DataMember(Name = "guid", IsRequired = false, EmitDefaultValue = false, Order = 10)] public virtual string Guid { get; set; } /// /// An array of unique identifies in the form of a GUID by which this report was known in some previous version of the analysis tool. /// - [DataMember(Name = "deprecatedGuids", IsRequired = false, EmitDefaultValue = false, Order = 10)] + [DataMember(Name = "deprecatedGuids", IsRequired = false, EmitDefaultValue = false, Order = 11)] public virtual IList DeprecatedGuids { get; set; } /// /// A report identifier that is understandable to an end user. /// - [DataMember(Name = "name", IsRequired = false, EmitDefaultValue = false, Order = 7)] + [DataMember(Name = "name", IsRequired = false, EmitDefaultValue = false, Order = 2)] public virtual string Name { get; set; } /// /// An array of readable identifiers by which this report was known in some previous version of the analysis tool. /// - [DataMember(Name = "deprecatedNames", IsRequired = false, EmitDefaultValue = false, Order = 11)] + [DataMember(Name = "deprecatedNames", IsRequired = false, EmitDefaultValue = false, Order = 3)] public virtual IList DeprecatedNames { get; set; } /// @@ -85,46 +85,46 @@ public SarifNodeKind SarifNodeKind /// /// A description of the report. Should, as far as possible, provide details sufficient to enable resolution of any problem indicated by the result. /// - [DataMember(Name = "fullDescription", IsRequired = false, EmitDefaultValue = false, Order = 2)] + [DataMember(Name = "fullDescription", IsRequired = false, EmitDefaultValue = false, Order = 4)] public virtual MultiformatMessageString FullDescription { get; set; } /// /// A set of name/value pairs with arbitrary names. Each value is a multiformatMessageString object, which holds message strings in plain text and (optionally) Markdown format. The strings can include placeholders, which can be used to construct a message in combination with an arbitrary number of additional string arguments. /// - [DataMember(Name = "messageStrings", IsRequired = false, EmitDefaultValue = false, Order = 5)] + [DataMember(Name = "messageStrings", IsRequired = false, EmitDefaultValue = false, Order = 6)] public virtual IDictionary MessageStrings { get; set; } /// /// Default reporting configuration information. /// - [DataMember(Name = "defaultConfiguration", IsRequired = false, EmitDefaultValue = false, Order = 12)] + [DataMember(Name = "defaultConfiguration", IsRequired = false, EmitDefaultValue = false, Order = 13)] public virtual ReportingConfiguration DefaultConfiguration { get; set; } /// /// A URI where the primary documentation for the report can be found. /// - [DataMember(Name = "helpUri", IsRequired = false, EmitDefaultValue = false, Order = 3)] + [DataMember(Name = "helpUri", IsRequired = false, EmitDefaultValue = false, Order = 14)] [JsonConverter(typeof(Microsoft.CodeAnalysis.Sarif.Readers.UriConverter))] public virtual Uri HelpUri { get; set; } /// /// Provides the primary documentation for the report, useful when there is no online documentation. /// - [DataMember(Name = "help", IsRequired = false, EmitDefaultValue = false, Order = 4)] + [DataMember(Name = "help", IsRequired = false, EmitDefaultValue = false, Order = 5)] public virtual MultiformatMessageString Help { get; set; } /// /// An array of objects that describe relationships between this reporting descriptor and others. /// - [DataMember(Name = "relationships", IsRequired = false, EmitDefaultValue = false, Order = 13)] - [JsonProperty(DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate, Order = 13)] + [DataMember(Name = "relationships", IsRequired = false, EmitDefaultValue = false, Order = 15)] + [JsonProperty(DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate, Order = 15)] public virtual IList Relationships { get; set; } /// /// Key/value pairs that provide additional information about the report. /// [JsonProperty(Order = 14)] - [DataMember(Name = "properties", IsRequired = false, EmitDefaultValue = false, Order = 14)] + [DataMember(Name = "properties", IsRequired = false, EmitDefaultValue = false, Order = 16)] internal override IDictionary Properties { get; set; } /// diff --git a/src/Sarif/NotYetAutoGenerated/ReportingDescriptor.cs b/src/Sarif/NotYetAutoGenerated/ReportingDescriptor.cs index 662e79dc2..84e489e38 100644 --- a/src/Sarif/NotYetAutoGenerated/ReportingDescriptor.cs +++ b/src/Sarif/NotYetAutoGenerated/ReportingDescriptor.cs @@ -49,31 +49,31 @@ public SarifNodeKind SarifNodeKind /// /// An array of stable, opaque identifiers by which this report was known in some previous version of the analysis tool. /// - [DataMember(Name = "deprecatedIds", IsRequired = false, EmitDefaultValue = false, Order = 8)] + [DataMember(Name = "deprecatedIds", IsRequired = false, EmitDefaultValue = false, Order = 9)] public virtual IList DeprecatedIds { get; set; } /// /// A unique identifier for the reporting descriptor in the form of a GUID. /// - [DataMember(Name = "guid", IsRequired = false, EmitDefaultValue = false, Order = 9)] + [DataMember(Name = "guid", IsRequired = false, EmitDefaultValue = false, Order = 10)] public virtual string Guid { get; set; } /// /// An array of unique identifies in the form of a GUID by which this report was known in some previous version of the analysis tool. /// - [DataMember(Name = "deprecatedGuids", IsRequired = false, EmitDefaultValue = false, Order = 10)] + [DataMember(Name = "deprecatedGuids", IsRequired = false, EmitDefaultValue = false, Order = 11)] public virtual IList DeprecatedGuids { get; set; } /// /// A report identifier that is understandable to an end user. /// - [DataMember(Name = "name", IsRequired = false, EmitDefaultValue = false, Order = 7)] + [DataMember(Name = "name", IsRequired = false, EmitDefaultValue = false, Order = 2)] public virtual string Name { get; set; } /// /// An array of readable identifiers by which this report was known in some previous version of the analysis tool. /// - [DataMember(Name = "deprecatedNames", IsRequired = false, EmitDefaultValue = false, Order = 11)] + [DataMember(Name = "deprecatedNames", IsRequired = false, EmitDefaultValue = false, Order = 3)] public virtual IList DeprecatedNames { get; set; } /// @@ -85,46 +85,46 @@ public SarifNodeKind SarifNodeKind /// /// A description of the report. Should, as far as possible, provide details sufficient to enable resolution of any problem indicated by the result. /// - [DataMember(Name = "fullDescription", IsRequired = false, EmitDefaultValue = false, Order = 2)] + [DataMember(Name = "fullDescription", IsRequired = false, EmitDefaultValue = false, Order = 4)] public virtual MultiformatMessageString FullDescription { get; set; } /// /// A set of name/value pairs with arbitrary names. Each value is a multiformatMessageString object, which holds message strings in plain text and (optionally) Markdown format. The strings can include placeholders, which can be used to construct a message in combination with an arbitrary number of additional string arguments. /// - [DataMember(Name = "messageStrings", IsRequired = false, EmitDefaultValue = false, Order = 5)] + [DataMember(Name = "messageStrings", IsRequired = false, EmitDefaultValue = false, Order = 6)] public virtual IDictionary MessageStrings { get; set; } /// /// Default reporting configuration information. /// - [DataMember(Name = "defaultConfiguration", IsRequired = false, EmitDefaultValue = false, Order = 12)] + [DataMember(Name = "defaultConfiguration", IsRequired = false, EmitDefaultValue = false, Order = 13)] public virtual ReportingConfiguration DefaultConfiguration { get; set; } /// /// A URI where the primary documentation for the report can be found. /// - [DataMember(Name = "helpUri", IsRequired = false, EmitDefaultValue = false, Order = 3)] + [DataMember(Name = "helpUri", IsRequired = false, EmitDefaultValue = false, Order = 14)] [JsonConverter(typeof(Microsoft.CodeAnalysis.Sarif.Readers.UriConverter))] public virtual Uri HelpUri { get; set; } /// /// Provides the primary documentation for the report, useful when there is no online documentation. /// - [DataMember(Name = "help", IsRequired = false, EmitDefaultValue = false, Order = 4)] + [DataMember(Name = "help", IsRequired = false, EmitDefaultValue = false, Order = 5)] public virtual MultiformatMessageString Help { get; set; } /// /// An array of objects that describe relationships between this reporting descriptor and others. /// - [DataMember(Name = "relationships", IsRequired = false, EmitDefaultValue = false, Order = 13)] - [JsonProperty(DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate, Order = 13)] + [DataMember(Name = "relationships", IsRequired = false, EmitDefaultValue = false, Order = 15)] + [JsonProperty(DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate, Order = 15)] public virtual IList Relationships { get; set; } /// /// Key/value pairs that provide additional information about the report. /// [JsonProperty(Order = 14)] - [DataMember(Name = "properties", IsRequired = false, EmitDefaultValue = false, Order = 14)] + [DataMember(Name = "properties", IsRequired = false, EmitDefaultValue = false, Order = 16)] internal override IDictionary Properties { get; set; } /// diff --git a/src/Sarif/Writers/SarifLogger.cs b/src/Sarif/Writers/SarifLogger.cs index 04604881c..849a52314 100644 --- a/src/Sarif/Writers/SarifLogger.cs +++ b/src/Sarif/Writers/SarifLogger.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Globalization; using System.IO; @@ -19,7 +20,6 @@ public class SarifLogger : BaseLogger, IDisposable, IAnalysisLogger { private readonly Run _run; private readonly TextWriter _textWriter; - private readonly bool _persistArtifacts; private readonly bool _closeWriterOnDispose; private readonly LogFilePersistenceOptions _logFilePersistenceOptions; private readonly JsonTextWriter _jsonTextWriter; @@ -30,21 +30,20 @@ public class SarifLogger : BaseLogger, IDisposable, IAnalysisLogger protected const LogFilePersistenceOptions DefaultLogFilePersistenceOptions = LogFilePersistenceOptions.PrettyPrint; - public SarifLogger( - string outputFilePath, - LogFilePersistenceOptions logFilePersistenceOptions = DefaultLogFilePersistenceOptions, - OptionallyEmittedData dataToInsert = OptionallyEmittedData.None, - OptionallyEmittedData dataToRemove = OptionallyEmittedData.None, - Tool tool = null, - Run run = null, - IEnumerable analysisTargets = null, - IEnumerable invocationTokensToRedact = null, - IEnumerable invocationPropertiesToLog = null, - string defaultFileEncoding = null, - bool quiet = false, - IEnumerable levels = null, - IEnumerable kinds = null, - IEnumerable insertProperties = null) + public SarifLogger(string outputFilePath, + LogFilePersistenceOptions logFilePersistenceOptions = DefaultLogFilePersistenceOptions, + OptionallyEmittedData dataToInsert = OptionallyEmittedData.None, + OptionallyEmittedData dataToRemove = OptionallyEmittedData.None, + Tool tool = null, + Run run = null, + IEnumerable analysisTargets = null, + IEnumerable invocationTokensToRedact = null, + IEnumerable invocationPropertiesToLog = null, + string defaultFileEncoding = null, + bool quiet = false, + IEnumerable levels = null, + IEnumerable kinds = null, + IEnumerable insertProperties = null) : this(new StreamWriter(new FileStream(outputFilePath, FileMode.Create, FileAccess.Write, FileShare.None)), logFilePersistenceOptions: logFilePersistenceOptions, dataToInsert: dataToInsert, @@ -62,26 +61,25 @@ public SarifLogger( { } - public SarifLogger( - TextWriter textWriter, - LogFilePersistenceOptions logFilePersistenceOptions = DefaultLogFilePersistenceOptions, - OptionallyEmittedData dataToInsert = OptionallyEmittedData.None, - OptionallyEmittedData dataToRemove = OptionallyEmittedData.None, - Tool tool = null, - Run run = null, - IEnumerable analysisTargets = null, - IEnumerable invocationTokensToRedact = null, - IEnumerable invocationPropertiesToLog = null, - string defaultFileEncoding = null, - bool closeWriterOnDispose = true, - bool quiet = false, - IEnumerable levels = null, - IEnumerable kinds = null, - IEnumerable insertProperties = null) : this(textWriter, logFilePersistenceOptions, closeWriterOnDispose, levels, kinds) + public SarifLogger(TextWriter textWriter, + LogFilePersistenceOptions logFilePersistenceOptions = DefaultLogFilePersistenceOptions, + OptionallyEmittedData dataToInsert = OptionallyEmittedData.None, + OptionallyEmittedData dataToRemove = OptionallyEmittedData.None, + Tool tool = null, + Run run = null, + IEnumerable analysisTargets = null, + IEnumerable invocationTokensToRedact = null, + IEnumerable invocationPropertiesToLog = null, + string defaultFileEncoding = null, + bool closeWriterOnDispose = true, + bool quiet = false, + IEnumerable levels = null, + IEnumerable kinds = null, + IEnumerable insertProperties = null) : this(textWriter, logFilePersistenceOptions, closeWriterOnDispose, levels, kinds) { if (dataToInsert.HasFlag(OptionallyEmittedData.Hashes)) { - AnalysisTargetToHashDataMap = HashUtilities.MultithreadedComputeTargetFileHashes(analysisTargets, quiet); + AnalysisTargetToHashDataMap = HashUtilities.MultithreadedComputeTargetFileHashes(analysisTargets, quiet) ?? new ConcurrentDictionary(); } _run = run ?? new Run(); @@ -92,14 +90,13 @@ public SarifLogger( _insertOptionalDataVisitor = new InsertOptionalDataVisitor(dataToInsert, _run, insertProperties); } - EnhanceRun( - analysisTargets, - dataToInsert, - dataToRemove, - invocationTokensToRedact, - invocationPropertiesToLog, - defaultFileEncoding, - AnalysisTargetToHashDataMap); + EnhanceRun(analysisTargets, + dataToInsert, + dataToRemove, + invocationTokensToRedact, + invocationPropertiesToLog, + defaultFileEncoding, + AnalysisTargetToHashDataMap); tool = tool ?? Tool.CreateFromAssemblyData(); @@ -116,19 +113,13 @@ public SarifLogger( RuleToIndexMap[_run.Tool.Driver.Rules[i]] = i; } } - - _persistArtifacts = - (_dataToInsert & OptionallyEmittedData.Hashes) != 0 || - (_dataToInsert & OptionallyEmittedData.TextFiles) != 0 || - (_dataToInsert & OptionallyEmittedData.BinaryFiles) != 0; } - private SarifLogger( - TextWriter textWriter, - LogFilePersistenceOptions logFilePersistenceOptions, - bool closeWriterOnDipose, - IEnumerable levels, - IEnumerable kinds) : base(failureLevels: levels, resultKinds: kinds) + private SarifLogger(TextWriter textWriter, + LogFilePersistenceOptions logFilePersistenceOptions, + bool closeWriterOnDipose, + IEnumerable levels, + IEnumerable kinds) : base(failureLevels: levels, resultKinds: kinds) { _textWriter = textWriter; _closeWriterOnDispose = closeWriterOnDipose; @@ -149,14 +140,13 @@ private SarifLogger( RuleToIndexMap = new Dictionary(ReportingDescriptor.ValueComparer); } - private void EnhanceRun( - IEnumerable analysisTargets, - OptionallyEmittedData dataToInsert, - OptionallyEmittedData dataToRemove, - IEnumerable invocationTokensToRedact, - IEnumerable invocationPropertiesToLog, - string defaultFileEncoding = null, - IDictionary filePathToHashDataMap = null) + private void EnhanceRun(IEnumerable analysisTargets, + OptionallyEmittedData dataToInsert, + OptionallyEmittedData dataToRemove, + IEnumerable invocationTokensToRedact, + IEnumerable invocationPropertiesToLog, + string defaultFileEncoding = null, + IDictionary filePathToHashDataMap = null) { _run.Invocations ??= new List(); if (defaultFileEncoding != null) @@ -447,12 +437,15 @@ private void CaptureArtifact(ArtifactLocation fileLocation) catch (ArgumentException) { } // Unrecognized encoding name } + HashData hashData = null; + AnalysisTargetToHashDataMap?.TryGetValue(fileLocation.Uri.OriginalString, out hashData); + // Ensure Artifact is in Run.Artifacts and ArtifactLocation.Index is set to point to it - int index = _run.GetFileIndex( - fileLocation, - addToFilesTableIfNotPresent: _persistArtifacts, - _dataToInsert, - encoding); + int index = _run.GetFileIndex(fileLocation, + addToFilesTableIfNotPresent: true, + _dataToInsert, + encoding, + hashData); // Remove redundant Uri and UriBaseId once index has been set if (index > -1 && this.Optimize) diff --git a/src/Test.FunctionalTests.Sarif/TestData/Multitool/ValidateCommand/ExpectedOutputs/SARIF2012.ProvideRuleProperties_Invalid.sarif b/src/Test.FunctionalTests.Sarif/TestData/Multitool/ValidateCommand/ExpectedOutputs/SARIF2012.ProvideRuleProperties_Invalid.sarif index dcae9d60f..8a42d72bd 100644 --- a/src/Test.FunctionalTests.Sarif/TestData/Multitool/ValidateCommand/ExpectedOutputs/SARIF2012.ProvideRuleProperties_Invalid.sarif +++ b/src/Test.FunctionalTests.Sarif/TestData/Multitool/ValidateCommand/ExpectedOutputs/SARIF2012.ProvideRuleProperties_Invalid.sarif @@ -120,7 +120,7 @@ "index": 0 }, "region": { - "startLine": 22, + "startLine": 21, "startColumn": 46 } } diff --git a/src/Test.FunctionalTests.Sarif/TestData/Multitool/ValidateCommand/ExpectedOutputs/SARIF2014.ProvideDynamicMessageContent_Invalid.sarif b/src/Test.FunctionalTests.Sarif/TestData/Multitool/ValidateCommand/ExpectedOutputs/SARIF2014.ProvideDynamicMessageContent_Invalid.sarif index 1b22d4ad9..522ae4812 100644 --- a/src/Test.FunctionalTests.Sarif/TestData/Multitool/ValidateCommand/ExpectedOutputs/SARIF2014.ProvideDynamicMessageContent_Invalid.sarif +++ b/src/Test.FunctionalTests.Sarif/TestData/Multitool/ValidateCommand/ExpectedOutputs/SARIF2014.ProvideDynamicMessageContent_Invalid.sarif @@ -60,7 +60,7 @@ "index": 0 }, "region": { - "startLine": 19, + "startLine": 18, "startColumn": 74 } } diff --git a/src/Test.FunctionalTests.Sarif/TestData/Multitool/ValidateCommand/ExpectedOutputs/SARIF2015.EnquoteDynamicMessageContent_Invalid.sarif b/src/Test.FunctionalTests.Sarif/TestData/Multitool/ValidateCommand/ExpectedOutputs/SARIF2015.EnquoteDynamicMessageContent_Invalid.sarif index 35a7e14c7..cde713017 100644 --- a/src/Test.FunctionalTests.Sarif/TestData/Multitool/ValidateCommand/ExpectedOutputs/SARIF2015.EnquoteDynamicMessageContent_Invalid.sarif +++ b/src/Test.FunctionalTests.Sarif/TestData/Multitool/ValidateCommand/ExpectedOutputs/SARIF2015.EnquoteDynamicMessageContent_Invalid.sarif @@ -59,7 +59,7 @@ "index": 0 }, "region": { - "startLine": 19, + "startLine": 18, "startColumn": 91 } } diff --git a/src/Test.UnitTests.Sarif.Driver/Sdk/AnalyzeCommandBaseTests.cs b/src/Test.UnitTests.Sarif.Driver/Sdk/AnalyzeCommandBaseTests.cs index 834dfa9d2..fea6a8a50 100644 --- a/src/Test.UnitTests.Sarif.Driver/Sdk/AnalyzeCommandBaseTests.cs +++ b/src/Test.UnitTests.Sarif.Driver/Sdk/AnalyzeCommandBaseTests.cs @@ -1166,7 +1166,7 @@ public void AnalyzeCommandBase_AutomationDetailsTests() } [Fact(Timeout = 5000)] - public void AnalyzeCommandBase_ShouldGenerateSameResultsWhenRunningSingleAndMultiThread_CoyoteTest() + public void AnalyzeCommandBase_ShouldGenerateSameResultsWhenRunningSingleAndMultithreaded_CoyoteTest() { Configuration config = Configuration.Create().WithTestingIterations(100).WithConcurrencyFuzzingEnabled(); var engine = TestingEngine.Create(config, AnalyzeCommandBase_ShouldGenerateSameResultsWhenRunningSingleAndMultiThread_CoyoteHelper); @@ -1185,12 +1185,72 @@ public void AnalyzeCommandBase_ShouldGenerateSameResultsWhenRunningSingleAndMult } [Fact] - public void AnalyzeCommandBase_ShouldGenerateSameResultsWhenRunningSingleAndMultiThread() + public void AnalyzeCommandBase_ShouldGenerateSameResultsWhenRunningSingleAndMultithreaded() { int[] scenarios = SetupScenarios(); AnalyzeScenarios(scenarios); } + [Fact] + public void AnalyzeCommandBase_ShouldOnlyLogArtifactsWhenResultsAreFound() + { + const int expectedNumberOfArtifacts = 2; + const int expectedNumberOfResultsWithErrors = 1; + const int expectedNumberOfResultsWithWarnings = 1; + var files = new List + { + $@"{Environment.CurrentDirectory}\Error.dll", + $@"{Environment.CurrentDirectory}\Warning.dll", + $@"{Environment.CurrentDirectory}\Note.dll", + $@"{Environment.CurrentDirectory}\Pass.dll", + $@"{Environment.CurrentDirectory}\NotApplicable.exe", + $@"{Environment.CurrentDirectory}\Informational.sys", + $@"{Environment.CurrentDirectory}\Open.cab", + $@"{Environment.CurrentDirectory}\Review.dll", + $@"{Environment.CurrentDirectory}\NoIssues.dll", + }; + + var testCases = new[] + { + new + { + IsMultithreaded = false + }, + new + { + IsMultithreaded = true + } + }; + + foreach (var testCase in testCases) + { + var resultsCachingTestCase = new ResultsCachingTestCase + { + Files = files, + PersistLogFileToDisk = true, + FileSystem = CreateDefaultFileSystemForResultsCaching(files, generateSameInput: false) + }; + + var options = new TestAnalyzeOptions + { + TestRuleBehaviors = resultsCachingTestCase.TestRuleBehaviors, + OutputFilePath = resultsCachingTestCase.PersistLogFileToDisk ? Guid.NewGuid().ToString() : null, + TargetFileSpecifiers = new string[] { Guid.NewGuid().ToString() }, + Kind = new List { ResultKind.Fail }, + Level = new List { FailureLevel.Warning, FailureLevel.Error }, + DataToInsert = new OptionallyEmittedData[] { OptionallyEmittedData.Hashes }, + }; + + Run run = RunAnalyzeCommand(options, resultsCachingTestCase, multithreaded: testCase.IsMultithreaded); + + // Hashes is enabled and we should expect to see two artifacts because we have: + // one result with Error level and one result with Warning level. + run.Artifacts.Should().HaveCount(expectedNumberOfArtifacts); + run.Results.Count(r => r.Level == FailureLevel.Error).Should().Be(expectedNumberOfResultsWithErrors); + run.Results.Count(r => r.Level == FailureLevel.Warning).Should().Be(expectedNumberOfResultsWithWarnings); + } + } + private void AnalyzeCommandBase_ShouldGenerateSameResultsWhenRunningSingleAndMultiThread_CoyoteHelper() { int[] scenarios = SetupScenarios(true); diff --git a/src/Test.UnitTests.Sarif.Multitool.Library/SuppressCommandTests.cs b/src/Test.UnitTests.Sarif.Multitool.Library/SuppressCommandTests.cs index 291176f2f..f95856d47 100644 --- a/src/Test.UnitTests.Sarif.Multitool.Library/SuppressCommandTests.cs +++ b/src/Test.UnitTests.Sarif.Multitool.Library/SuppressCommandTests.cs @@ -19,6 +19,8 @@ namespace Microsoft.CodeAnalysis.Sarif.Multitool { public class SuppressCommandTests { + private const int DateTimeAssertPrecision = 500; + [Fact] public void SuppressCommand_ShouldReturnFailure_WhenBadArgumentsAreSupplied() { @@ -165,12 +167,12 @@ private static void VerifySuppressCommand(SuppressOptions options) if (options.Timestamps && suppression.TryGetProperty("timeUtc", out DateTime timeUtc)) { - timeUtc.Should().BeCloseTo(DateTime.UtcNow, precision: 500); + timeUtc.Should().BeCloseTo(DateTime.UtcNow, DateTimeAssertPrecision); } if (options.ExpiryInDays > 0 && suppression.TryGetProperty("expiryUtc", out DateTime expiryUtc)) { - expiryUtc.Should().BeCloseTo(DateTime.UtcNow.AddDays(options.ExpiryInDays)); + expiryUtc.Should().BeCloseTo(DateTime.UtcNow.AddDays(options.ExpiryInDays), DateTimeAssertPrecision); } } }