diff --git a/src/ReleaseHistory.md b/src/ReleaseHistory.md index 077ed1ba4..4e0b2a1e6 100644 --- a/src/ReleaseHistory.md +++ b/src/ReleaseHistory.md @@ -2,6 +2,7 @@ ## Unreleased +* 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) ## **v2.4.12** [Sdk](https://www.nuget.org/packages/Sarif.Sdk/2.4.12) | [Driver](https://www.nuget.org/packages/Sarif.Driver/2.4.12) | [Converters](https://www.nuget.org/packages/Sarif.Converters/2.4.12) | [Multitool](https://www.nuget.org/packages/Sarif.Multitool/2.4.12) | [Multitool Library](https://www.nuget.org/packages/Sarif.Multitool.Library/2.4.12) diff --git a/src/Sarif.Driver/Sdk/MultithreadedAnalyzeCommandBase.cs b/src/Sarif.Driver/Sdk/MultithreadedAnalyzeCommandBase.cs index db8b0384d..db658d171 100644 --- a/src/Sarif.Driver/Sdk/MultithreadedAnalyzeCommandBase.cs +++ b/src/Sarif.Driver/Sdk/MultithreadedAnalyzeCommandBase.cs @@ -7,9 +7,7 @@ using System.Diagnostics; using System.IO; using System.Linq; -using System.Reflection; using System.Runtime.InteropServices; -using System.Security; using System.Threading.Channels; using System.Threading.Tasks; diff --git a/src/Sarif/Autogenerated/ReportingDescriptor.cs b/src/Sarif/Autogenerated/ReportingDescriptor.cs index 3957c2c93..286b5e050 100644 --- a/src/Sarif/Autogenerated/ReportingDescriptor.cs +++ b/src/Sarif/Autogenerated/ReportingDescriptor.cs @@ -37,91 +37,94 @@ public SarifNodeKind SarifNodeKind // NOTYETAUTOGENERATED: Jschema needs a mechanism to emit all public methods as virtual // https://github.com/Microsoft/jschema/issues/97 + // NOTYETAUTOGENERATED: Order attribute + // https://github.com/microsoft/jschema/issues/140 // /// /// A stable, opaque identifier for the report. /// - [DataMember(Name = "id", IsRequired = false, EmitDefaultValue = false)] + [DataMember(Name = "id", IsRequired = false, EmitDefaultValue = false, Order = 1)] public virtual string Id { get; set; } /// /// 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)] + [DataMember(Name = "deprecatedIds", IsRequired = false, EmitDefaultValue = false, Order = 8)] public virtual IList DeprecatedIds { get; set; } /// /// A unique identifer for the reporting descriptor in the form of a GUID. /// - [DataMember(Name = "guid", IsRequired = false, EmitDefaultValue = false)] + [DataMember(Name = "guid", IsRequired = false, EmitDefaultValue = false, Order = 9)] 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)] + [DataMember(Name = "deprecatedGuids", IsRequired = false, EmitDefaultValue = false, Order = 10)] public virtual IList DeprecatedGuids { get; set; } /// /// A report identifier that is understandable to an end user. /// - [DataMember(Name = "name", IsRequired = false, EmitDefaultValue = false)] + [DataMember(Name = "name", IsRequired = false, EmitDefaultValue = false, Order = 7)] 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)] + [DataMember(Name = "deprecatedNames", IsRequired = false, EmitDefaultValue = false, Order = 11)] public virtual IList DeprecatedNames { get; set; } /// /// A concise description of the report. Should be a single sentence that is understandable when visible space is limited to a single line of text. /// - [DataMember(Name = "shortDescription", IsRequired = false, EmitDefaultValue = false)] + [DataMember(Name = "shortDescription", IsRequired = false, EmitDefaultValue = false, Order = 6)] public virtual MultiformatMessageString ShortDescription { get; set; } /// /// 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)] + [DataMember(Name = "fullDescription", IsRequired = false, EmitDefaultValue = false, Order = 2)] 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)] + [DataMember(Name = "messageStrings", IsRequired = false, EmitDefaultValue = false, Order = 5)] public virtual IDictionary MessageStrings { get; set; } /// /// Default reporting configuration information. /// - [DataMember(Name = "defaultConfiguration", IsRequired = false, EmitDefaultValue = false)] + [DataMember(Name = "defaultConfiguration", IsRequired = false, EmitDefaultValue = false, Order = 12)] 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)] + [DataMember(Name = "helpUri", IsRequired = false, EmitDefaultValue = false, Order = 3)] [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)] + [DataMember(Name = "help", IsRequired = false, EmitDefaultValue = false, Order = 4)] 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)] - [JsonProperty(DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate)] + [DataMember(Name = "relationships", IsRequired = false, EmitDefaultValue = false, Order = 13)] + [JsonProperty(DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate, Order = 13)] public virtual IList Relationships { get; set; } /// /// Key/value pairs that provide additional information about the report. /// - [DataMember(Name = "properties", IsRequired = false, EmitDefaultValue = false)] + [JsonProperty(Order = 14)] + [DataMember(Name = "properties", IsRequired = false, EmitDefaultValue = false, Order = 14)] internal override IDictionary Properties { get; set; } /// diff --git a/src/Sarif/NotYetAutoGenerated/ReportingDescriptor.cs b/src/Sarif/NotYetAutoGenerated/ReportingDescriptor.cs index 3957c2c93..286b5e050 100644 --- a/src/Sarif/NotYetAutoGenerated/ReportingDescriptor.cs +++ b/src/Sarif/NotYetAutoGenerated/ReportingDescriptor.cs @@ -37,91 +37,94 @@ public SarifNodeKind SarifNodeKind // NOTYETAUTOGENERATED: Jschema needs a mechanism to emit all public methods as virtual // https://github.com/Microsoft/jschema/issues/97 + // NOTYETAUTOGENERATED: Order attribute + // https://github.com/microsoft/jschema/issues/140 // /// /// A stable, opaque identifier for the report. /// - [DataMember(Name = "id", IsRequired = false, EmitDefaultValue = false)] + [DataMember(Name = "id", IsRequired = false, EmitDefaultValue = false, Order = 1)] public virtual string Id { get; set; } /// /// 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)] + [DataMember(Name = "deprecatedIds", IsRequired = false, EmitDefaultValue = false, Order = 8)] public virtual IList DeprecatedIds { get; set; } /// /// A unique identifer for the reporting descriptor in the form of a GUID. /// - [DataMember(Name = "guid", IsRequired = false, EmitDefaultValue = false)] + [DataMember(Name = "guid", IsRequired = false, EmitDefaultValue = false, Order = 9)] 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)] + [DataMember(Name = "deprecatedGuids", IsRequired = false, EmitDefaultValue = false, Order = 10)] public virtual IList DeprecatedGuids { get; set; } /// /// A report identifier that is understandable to an end user. /// - [DataMember(Name = "name", IsRequired = false, EmitDefaultValue = false)] + [DataMember(Name = "name", IsRequired = false, EmitDefaultValue = false, Order = 7)] 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)] + [DataMember(Name = "deprecatedNames", IsRequired = false, EmitDefaultValue = false, Order = 11)] public virtual IList DeprecatedNames { get; set; } /// /// A concise description of the report. Should be a single sentence that is understandable when visible space is limited to a single line of text. /// - [DataMember(Name = "shortDescription", IsRequired = false, EmitDefaultValue = false)] + [DataMember(Name = "shortDescription", IsRequired = false, EmitDefaultValue = false, Order = 6)] public virtual MultiformatMessageString ShortDescription { get; set; } /// /// 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)] + [DataMember(Name = "fullDescription", IsRequired = false, EmitDefaultValue = false, Order = 2)] 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)] + [DataMember(Name = "messageStrings", IsRequired = false, EmitDefaultValue = false, Order = 5)] public virtual IDictionary MessageStrings { get; set; } /// /// Default reporting configuration information. /// - [DataMember(Name = "defaultConfiguration", IsRequired = false, EmitDefaultValue = false)] + [DataMember(Name = "defaultConfiguration", IsRequired = false, EmitDefaultValue = false, Order = 12)] 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)] + [DataMember(Name = "helpUri", IsRequired = false, EmitDefaultValue = false, Order = 3)] [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)] + [DataMember(Name = "help", IsRequired = false, EmitDefaultValue = false, Order = 4)] 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)] - [JsonProperty(DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate)] + [DataMember(Name = "relationships", IsRequired = false, EmitDefaultValue = false, Order = 13)] + [JsonProperty(DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate, Order = 13)] public virtual IList Relationships { get; set; } /// /// Key/value pairs that provide additional information about the report. /// - [DataMember(Name = "properties", IsRequired = false, EmitDefaultValue = false)] + [JsonProperty(Order = 14)] + [DataMember(Name = "properties", IsRequired = false, EmitDefaultValue = false, Order = 14)] internal override IDictionary Properties { get; set; } /// diff --git a/src/Sarif/Writers/ResultLogJsonWriter.cs b/src/Sarif/Writers/ResultLogJsonWriter.cs index 49c63bc3d..9e26fdac9 100644 --- a/src/Sarif/Writers/ResultLogJsonWriter.cs +++ b/src/Sarif/Writers/ResultLogJsonWriter.cs @@ -295,7 +295,7 @@ public void CompleteRun() } if ((_writeConditions & Conditions.InvocationsWritten) != Conditions.InvocationsWritten && - _run.Invocations?.Count > 0) + _run.ShouldSerializeInvocations()) { WriteInvocations(_run.Invocations); } @@ -307,29 +307,42 @@ public void CompleteRun() SerializeIfNotNull(_run.OriginalUriBaseIds, "originalUriBaseIds"); if ((_writeConditions & Conditions.FilesWritten) != Conditions.FilesWritten && - _run.Artifacts != null) + _run.ShouldSerializeArtifacts()) { WriteArtifacts(_run.Artifacts); } if ((_writeConditions & Conditions.LogicalLocationsWritten) != Conditions.LogicalLocationsWritten && - _run.LogicalLocations != null) + _run.ShouldSerializeLogicalLocations()) { WriteLogicalLocations(_run.LogicalLocations); } - SerializeIfNotNull(_run.Graphs, "graphs"); + // All ShouldSerialize() will not be triggered automatically when manually write node by node. + // To make sure the same logic applied we should call the same method here, if it is defined. + if (_run.ShouldSerializeGraphs()) + { + Serialize(_run.Graphs, "graphs"); + } // Results go here in schema order - - SerializeIfNotNull(_run.AutomationDetails, "automationDetails"); + if (_run.ShouldSerializeAutomationDetails()) + { + Serialize(_run.AutomationDetails, "automationDetails"); + } SerializeIfNotNull(_run.RunAggregates, "runAggregates"); SerializeIfNotNull(_run.BaselineGuid, "baselineGuid"); SerializeIfNotNull(_run.RedactionTokens, "redactionTokens"); SerializeIfNotNull(_run.DefaultEncoding, "defaultEncoding"); SerializeIfNotNull(_run.DefaultSourceLanguage, "defaultSourceLanguage"); - SerializeIfNotNull(_run.NewlineSequences, "newlineSequences"); - SerializeIfNotNull(_run.ColumnKind == ColumnKind.UnicodeCodePoints ? "unicodeCodePoints" : "utf16CodeUnits", "columnKind"); + if (_run.ShouldSerializeNewlineSequences()) + { + Serialize(_run.NewlineSequences, "newlineSequences"); + } + if (_run.ShouldSerializeColumnKind()) + { + Serialize(_run.ColumnKind == ColumnKind.UnicodeCodePoints ? "unicodeCodePoints" : "utf16CodeUnits", "columnKind"); + } SerializeIfNotNull(_run.ExternalPropertyFileReferences, "externalPropertyFileReferences"); SerializeIfNotNull(_run.ThreadFlowLocations, "threadFlowLocations"); SerializeIfNotNull(_run.Taxonomies, "taxonomies"); 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 8a42d72bd..dcae9d60f 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": 21, + "startLine": 22, "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 522ae4812..1b22d4ad9 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": 18, + "startLine": 19, "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 cde713017..35a7e14c7 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": 18, + "startLine": 19, "startColumn": 91 } } diff --git a/src/Test.FunctionalTests.Sarif/TestData/Multitool/ValidateCommand/Inputs/SARIF2012.ProvideRuleProperties_Invalid.sarif b/src/Test.FunctionalTests.Sarif/TestData/Multitool/ValidateCommand/Inputs/SARIF2012.ProvideRuleProperties_Invalid.sarif index 5eb41915b..5bceadf8c 100644 --- a/src/Test.FunctionalTests.Sarif/TestData/Multitool/ValidateCommand/Inputs/SARIF2012.ProvideRuleProperties_Invalid.sarif +++ b/src/Test.FunctionalTests.Sarif/TestData/Multitool/ValidateCommand/Inputs/SARIF2012.ProvideRuleProperties_Invalid.sarif @@ -18,8 +18,8 @@ }, { "id": "TEST0002", - "name": "This isn't pascal case", - "helpUri": "http://docs.oasis-open.org/sarif/sarif/v2.1.0/sarif-v2.1.0.html" + "helpUri": "http://docs.oasis-open.org/sarif/sarif/v2.1.0/sarif-v2.1.0.html", + "name": "This isn't pascal case" } ] } diff --git a/src/Test.FunctionalTests.Sarif/TestData/Multitool/ValidateCommand/Inputs/SARIF2014.ProvideDynamicMessageContent_Invalid.sarif b/src/Test.FunctionalTests.Sarif/TestData/Multitool/ValidateCommand/Inputs/SARIF2014.ProvideDynamicMessageContent_Invalid.sarif index 21fac9840..e00955600 100644 --- a/src/Test.FunctionalTests.Sarif/TestData/Multitool/ValidateCommand/Inputs/SARIF2014.ProvideDynamicMessageContent_Invalid.sarif +++ b/src/Test.FunctionalTests.Sarif/TestData/Multitool/ValidateCommand/Inputs/SARIF2014.ProvideDynamicMessageContent_Invalid.sarif @@ -13,12 +13,12 @@ "fullDescription": { "text": "This is a test." }, + "helpUri": "http://docs.oasis-open.org/sarif/sarif/v2.1.0/sarif-v2.1.0.html", "messageStrings": { "NoPlaceholders": { "text": "This message does not contain dynamic content." } - }, - "helpUri": "http://docs.oasis-open.org/sarif/sarif/v2.1.0/sarif-v2.1.0.html" + } } ] } diff --git a/src/Test.FunctionalTests.Sarif/TestData/Multitool/ValidateCommand/Inputs/SARIF2015.EnquoteDynamicMessageContent_Invalid.sarif b/src/Test.FunctionalTests.Sarif/TestData/Multitool/ValidateCommand/Inputs/SARIF2015.EnquoteDynamicMessageContent_Invalid.sarif index 2fa22b71b..41ed4712d 100644 --- a/src/Test.FunctionalTests.Sarif/TestData/Multitool/ValidateCommand/Inputs/SARIF2015.EnquoteDynamicMessageContent_Invalid.sarif +++ b/src/Test.FunctionalTests.Sarif/TestData/Multitool/ValidateCommand/Inputs/SARIF2015.EnquoteDynamicMessageContent_Invalid.sarif @@ -13,12 +13,12 @@ "fullDescription": { "text": "This is a test." }, + "helpUri": "http://docs.oasis-open.org/sarif/sarif/v2.1.0/sarif-v2.1.0.html", "messageStrings": { "NotEnquoted": { "text": "This message contains dynamic content {0} that is not enquoted." } - }, - "helpUri": "http://docs.oasis-open.org/sarif/sarif/v2.1.0/sarif-v2.1.0.html" + } } ] } diff --git a/src/Test.UnitTests.Sarif.Driver/Sdk/AnalyzeCommandBaseTests.cs b/src/Test.UnitTests.Sarif.Driver/Sdk/AnalyzeCommandBaseTests.cs index 5bd830a43..39b5fd0cb 100644 --- a/src/Test.UnitTests.Sarif.Driver/Sdk/AnalyzeCommandBaseTests.cs +++ b/src/Test.UnitTests.Sarif.Driver/Sdk/AnalyzeCommandBaseTests.cs @@ -1078,8 +1078,9 @@ public void AnalyzeCommandBase_CachesResultsWhenPersistingToLogFile() } [Fact] - public void AnalyzeCommandBase_ShouldEmitAutomationDetailsWhenIdOrGuidExists() + public void AnalyzeCommandBase_AutomationDetailsTests() { + const string whiteSpace = " "; const string automationId = "automation-id"; const string automationGuid = "automation-guid"; @@ -1097,7 +1098,43 @@ public void AnalyzeCommandBase_ShouldEmitAutomationDetailsWhenIdOrGuidExists() { AutomationId = automationId, AutomationGuid = automationGuid - } + }, + new TestAnalyzeOptions + { + }, + new TestAnalyzeOptions + { + AutomationId = string.Empty, + AutomationGuid = string.Empty + }, + new TestAnalyzeOptions + { + AutomationId = null, + AutomationGuid = null + }, + new TestAnalyzeOptions + { + AutomationId = whiteSpace, + AutomationGuid = whiteSpace + }, + new TestAnalyzeOptions + { + AutomationId = string.Empty, + AutomationGuid = null + }, + new TestAnalyzeOptions + { + AutomationId = null, + AutomationGuid = whiteSpace + }, + new TestAnalyzeOptions + { + AutomationGuid = string.Empty + }, + new TestAnalyzeOptions + { + AutomationId = null + }, }; foreach (TestAnalyzeOptions enhancedOption in enhancedOptions) @@ -1344,6 +1381,11 @@ public void AnalyzeCommandBase_MultithreadedShouldUseCacheIfFilesAreTheSame() { runWithCaching.Artifacts.Should().NotBeEmpty(); + if (string.IsNullOrWhiteSpace(options.AutomationId) && string.IsNullOrWhiteSpace(options.AutomationGuid)) + { + runWithCaching.AutomationDetails.Should().Be(null); + } + if (!string.IsNullOrWhiteSpace(options.AutomationId)) { runWithCaching.AutomationDetails.Id.Should().Be(options.AutomationId);