public virtual void Dispose() { // Disposing the json writer closes the stream but the textwriter // still needs to be disposed or closed to write the results if (_issueLogJsonWriter != null) { _issueLogJsonWriter.CloseResults(); if (_run?.Invocations?.Count > 0 && _run.Invocations[0].StartTimeUtc != new DateTime() && !_dataToRemove.HasFlag(OptionallyEmittedData.NondeterministicProperties)) { _run.Invocations[0].EndTimeUtc = DateTime.UtcNow; } _issueLogJsonWriter.CompleteRun(); _issueLogJsonWriter.Dispose(); } if (_closeWriterOnDispose) { if (_textWriter != null) { _textWriter.Dispose(); } if (_jsonTextWriter == null) { _jsonTextWriter.Close(); } } GC.SuppressFinalize(this); }
public SarifLogger( TextWriter textWriter, LogFilePersistenceOptions logFilePersistenceOptions = DefaultLogFilePersistenceOptions, OptionallyEmittedData dataToInsert = OptionallyEmittedData.None, OptionallyEmittedData dataToRemove = OptionallyEmittedData.None, Tool tool = null, Run run = null, IEnumerable <string> analysisTargets = null, IEnumerable <string> invocationTokensToRedact = null, IEnumerable <string> invocationPropertiesToLog = null, string defaultFileEncoding = null, bool closeWriterOnDispose = true, IEnumerable <FailureLevel> levels = null, IEnumerable <ResultKind> kinds = null) : this(textWriter, logFilePersistenceOptions, closeWriterOnDispose, levels, kinds) { if (dataToInsert.HasFlag(OptionallyEmittedData.Hashes)) { AnalysisTargetToHashDataMap = HashUtilities.MultithreadedComputeTargetFileHashes(analysisTargets); } _run = run ?? new Run(); if (dataToInsert.HasFlag(OptionallyEmittedData.RegionSnippets) || dataToInsert.HasFlag(OptionallyEmittedData.ContextRegionSnippets)) { _insertOptionalDataVisitor = new InsertOptionalDataVisitor(dataToInsert, _run); } EnhanceRun( analysisTargets, dataToInsert, dataToRemove, invocationTokensToRedact, invocationPropertiesToLog, defaultFileEncoding, AnalysisTargetToHashDataMap); tool = tool ?? Tool.CreateFromAssemblyData(); _run.Tool = tool; _dataToInsert = dataToInsert; _dataToRemove = dataToRemove; _issueLogJsonWriter.Initialize(_run); // Map existing Rules to ensure duplicates aren't created if (_run.Tool.Driver?.Rules != null) { for (int i = 0; i < _run.Tool.Driver.Rules.Count; ++i) { RuleToIndexMap[_run.Tool.Driver.Rules[i]] = i; } } _persistArtifacts = (_dataToInsert & OptionallyEmittedData.Hashes) != 0 || (_dataToInsert & OptionallyEmittedData.TextFiles) != 0 || (_dataToInsert & OptionallyEmittedData.BinaryFiles) != 0; }
public override Invocation VisitInvocation(Invocation node) { if (_dataToRemove.HasFlag(OptionallyEmittedData.NondeterministicProperties)) { node.StartTimeUtc = new DateTime(); node.EndTimeUtc = new DateTime(); } return(base.VisitInvocation(node)); }
public SarifLogger( TextWriter textWriter, LoggingOptions loggingOptions = DefaultLoggingOptions, OptionallyEmittedData dataToInsert = OptionallyEmittedData.None, OptionallyEmittedData dataToRemove = OptionallyEmittedData.None, Tool tool = null, Run run = null, IEnumerable <string> analysisTargets = null, IEnumerable <string> invocationTokensToRedact = null, IEnumerable <string> invocationPropertiesToLog = null, string defaultFileEncoding = null) : this(textWriter, loggingOptions) { if (dataToInsert.HasFlag(OptionallyEmittedData.Hashes)) { AnalysisTargetToHashDataMap = HashUtilities.MultithreadedComputeTargetFileHashes(analysisTargets); } _run = run ?? CreateRun( analysisTargets, dataToInsert, dataToRemove, invocationTokensToRedact, invocationPropertiesToLog, defaultFileEncoding, AnalysisTargetToHashDataMap); tool = tool ?? Tool.CreateFromAssemblyData(); _run.Tool = tool; _dataToInsert = dataToInsert; _dataToRemove = dataToRemove; _issueLogJsonWriter.Initialize(_run); }
public override int Run(TOptions options) { // To correctly initialize the logger, we must first add Hashes to dataToInsert #pragma warning disable CS0618 // Type or member is obsolete if (options.ComputeFileHashes) #pragma warning restore CS0618 { OptionallyEmittedData dataToInsert = options.DataToInsert.ToFlags(); dataToInsert |= OptionallyEmittedData.Hashes; options.DataToInsert = Enum.GetValues(typeof(OptionallyEmittedData)).Cast <OptionallyEmittedData>() .Where(oed => dataToInsert.HasFlag(oed)).ToList(); } // 0. Initialize an common logger that drives all outputs. This // object drives logging for console, statistics, etc. using (AggregatingLogger logger = InitializeLogger(options)) { // Once the logger has been correctly initialized, we can raise a warning _rootContext = CreateContext(options, logger, RuntimeErrors); #pragma warning disable CS0618 // Type or member is obsolete if (options.ComputeFileHashes) #pragma warning restore CS0618 { Warnings.LogObsoleteOption(_rootContext, "--hashes", SdkResources.ComputeFileHashes_ReplaceInsertHashes); } try { Analyze(options, logger); } catch (ExitApplicationException <ExitReason> ex) { // These exceptions have already been logged ExecutionException = ex; return(FAILURE); } catch (Exception ex) { // These exceptions escaped our net and must be logged here RuntimeErrors |= Errors.LogUnhandledEngineException(_rootContext, ex); ExecutionException = ex; return(FAILURE); } finally { logger.AnalysisStopped(RuntimeErrors); } } bool succeeded = (RuntimeErrors & ~RuntimeConditions.Nonfatal) == RuntimeConditions.None; if (options.RichReturnCode) { return((int)RuntimeErrors); } return(succeeded ? SUCCESS : FAILURE); }
private static void Validate(Artifact fileData, OptionallyEmittedData dataToInsert) { if (dataToInsert.HasFlag(OptionallyEmittedData.TextFiles)) { fileData.Contents.Should().NotBeNull(); } else { fileData.Contents.Should().BeNull(); } }
public void Artifact_PersistBinaryAndTextFileContents( string fileExtension, OptionallyEmittedData dataToInsert, bool shouldBePersisted) { string filePath = Path.GetTempFileName() + fileExtension; string fileContents = Guid.NewGuid().ToString(); Uri uri = new Uri(filePath); try { File.WriteAllText(filePath, fileContents); Artifact fileData = Artifact.Create(uri, dataToInsert); fileData.Location.Should().BeNull(); if (dataToInsert.HasFlag(OptionallyEmittedData.Hashes)) { fileData.Hashes.Should().NotBeNull(); } else { fileData.Hashes.Should().BeNull(); } string encodedFileContents = Convert.ToBase64String(File.ReadAllBytes(filePath)); if (shouldBePersisted) { string mimeType = MimeType.DetermineFromFileExtension(uri); if (MimeType.IsBinaryMimeType(mimeType)) { fileData.Contents.Binary.Should().Be(encodedFileContents); fileData.Contents.Text.Should().BeNull(); } else { fileData.Contents.Binary.Should().BeNull(); fileData.Contents.Text.Should().Be(fileContents); } } else { fileData.Contents.Should().BeNull(); } } finally { if (File.Exists(filePath)) { File.Delete(filePath); } } }
public SarifLogger( TextWriter textWriter, LoggingOptions loggingOptions = DefaultLoggingOptions, OptionallyEmittedData dataToInsert = OptionallyEmittedData.None, OptionallyEmittedData dataToRemove = OptionallyEmittedData.None, Tool tool = null, Run run = null, IEnumerable <string> analysisTargets = null, IEnumerable <string> invocationTokensToRedact = null, IEnumerable <string> invocationPropertiesToLog = null, string defaultFileEncoding = null, bool closeWriterOnDispose = true) : this(textWriter, loggingOptions, closeWriterOnDispose) { if (dataToInsert.HasFlag(OptionallyEmittedData.Hashes)) { AnalysisTargetToHashDataMap = HashUtilities.MultithreadedComputeTargetFileHashes(analysisTargets); } _run = run ?? CreateRun( analysisTargets, dataToInsert, dataToRemove, invocationTokensToRedact, invocationPropertiesToLog, defaultFileEncoding, AnalysisTargetToHashDataMap); tool = tool ?? Tool.CreateFromAssemblyData(); _run.Tool = tool; _dataToInsert = dataToInsert; _dataToRemove = dataToRemove; _issueLogJsonWriter.Initialize(_run); // Map existing Rules to ensure duplicates aren't created if (_run.Tool.Driver?.Rules != null) { for (int i = 0; i < _run.Tool.Driver.Rules.Count; ++i) { RuleToIndexMap[_run.Tool.Driver.Rules[i]] = i; } } }
public override Run VisitRun(Run node) { _run = node; if (_originalUriBaseIds != null) { _run.OriginalUriBaseIds = _run.OriginalUriBaseIds ?? new Dictionary <string, ArtifactLocation>(); foreach (string key in _originalUriBaseIds.Keys) { _run.OriginalUriBaseIds[key] = _originalUriBaseIds[key]; } } if (node == null) { return(null); } bool scrapeFileReferences = _dataToInsert.HasFlag(OptionallyEmittedData.Hashes) || _dataToInsert.HasFlag(OptionallyEmittedData.TextFiles) || _dataToInsert.HasFlag(OptionallyEmittedData.BinaryFiles); if (scrapeFileReferences) { var visitor = new AddFileReferencesVisitor(); visitor.VisitRun(node); } Run visited = base.VisitRun(node); return(visited); }
private void EnhanceRun( IEnumerable <string> analysisTargets, OptionallyEmittedData dataToInsert, OptionallyEmittedData dataToRemove, IEnumerable <string> invocationTokensToRedact, IEnumerable <string> invocationPropertiesToLog, string defaultFileEncoding = null, IDictionary <string, HashData> filePathToHashDataMap = null) { _run.Invocations ??= new List <Invocation>(); if (defaultFileEncoding != null) { _run.DefaultEncoding = defaultFileEncoding; } Encoding encoding = SarifUtilities.GetEncodingFromName(_run.DefaultEncoding); if (analysisTargets != null) { _run.Artifacts ??= new List <Artifact>(); foreach (string target in analysisTargets) { Uri uri = new Uri(UriHelper.MakeValidUri(target), UriKind.RelativeOrAbsolute); HashData hashData = null; if (dataToInsert.HasFlag(OptionallyEmittedData.Hashes)) { filePathToHashDataMap?.TryGetValue(target, out hashData); } var artifact = Artifact.Create( new Uri(target, UriKind.RelativeOrAbsolute), dataToInsert, encoding, hashData: hashData); var fileLocation = new ArtifactLocation { Uri = uri }; artifact.Location = fileLocation; // This call will insert the file object into run.Files if not already present artifact.Location.Index = _run.GetFileIndex( artifact.Location, addToFilesTableIfNotPresent: true, dataToInsert: dataToInsert, encoding: encoding, hashData: hashData); } } var invocation = Invocation.Create( emitMachineEnvironment: dataToInsert.HasFlag(OptionallyEmittedData.EnvironmentVariables), emitTimestamps: !dataToRemove.HasFlag(OptionallyEmittedData.NondeterministicProperties), invocationPropertiesToLog); // TODO we should actually redact across the complete log file context // by a dedicated rewriting visitor or some other approach. if (invocationTokensToRedact != null) { invocation.CommandLine = Redact(invocation.CommandLine, invocationTokensToRedact); invocation.Machine = Redact(invocation.Machine, invocationTokensToRedact); invocation.Account = Redact(invocation.Account, invocationTokensToRedact); if (invocation.WorkingDirectory != null) { invocation.WorkingDirectory.Uri = Redact(invocation.WorkingDirectory.Uri, invocationTokensToRedact); } if (invocation.EnvironmentVariables != null) { string[] keys = invocation.EnvironmentVariables.Keys.ToArray(); foreach (string key in keys) { string value = invocation.EnvironmentVariables[key]; invocation.EnvironmentVariables[key] = Redact(value, invocationTokensToRedact); } } } _run.Invocations.Add(invocation); }
protected override string ConstructTestOutputFromInputResource(string inputResourceName, object parameter) { SarifLog actualLog = PrereleaseCompatibilityTransformer.UpdateToCurrentVersion( GetResourceText(inputResourceName), formatting: Formatting.Indented, updatedLog: out _); // Some of the tests operate on SARIF files that mention the absolute path of the file // that was "analyzed" (InsertOptionalDataVisitor.txt). That path depends on the repo // root, and so can vary depending on the machine where the tests are run. To avoid // this problem, both the input files and the expected output files contain a fixed // string "REPLACED_AT_TEST_RUNTIME" in place of the directory portion of the path. But some // of the tests must read the contents of the analyzed file (for instance, when the // test requires snippets or file hashes to be inserted). Those test require the actual // path. Therefore we replace the fixed string with the actual path, execute the // visitor, and then restore the fixed string so the actual output can be compared // to the expected output. string enlistmentRoot = GitHelper.Default.GetRepositoryRoot(Environment.CurrentDirectory, useCache: false); if (inputResourceName == "Inputs.CoreTests-Relative.sarif") { Uri originalUri = actualLog.Runs[0].OriginalUriBaseIds["TESTROOT"].Uri; string uriString = originalUri.ToString(); uriString = uriString.Replace(EnlistmentRoot, enlistmentRoot); actualLog.Runs[0].OriginalUriBaseIds["TESTROOT"] = new ArtifactLocation { Uri = new Uri(uriString, UriKind.Absolute) }; var visitor = new InsertOptionalDataVisitor(_currentOptionallyEmittedData); visitor.Visit(actualLog.Runs[0]); // Restore the remanufactured URI so that file diffing succeeds. actualLog.Runs[0].OriginalUriBaseIds["TESTROOT"] = new ArtifactLocation { Uri = originalUri }; // In some of the tests, the visitor added an originalUriBaseId for the repo root. // Adjust that one, too. string repoRootUriBaseId = InsertOptionalDataVisitor.GetUriBaseId(0); if (actualLog.Runs[0].OriginalUriBaseIds.TryGetValue(repoRootUriBaseId, out ArtifactLocation artifactLocation)) { Uri repoRootUri = artifactLocation.Uri; string repoRootString = repoRootUri.ToString(); repoRootString = repoRootString.Replace(enlistmentRoot.Replace(@"\", "/"), EnlistmentRoot); actualLog.Runs[0].OriginalUriBaseIds[repoRootUriBaseId] = new ArtifactLocation { Uri = new Uri(repoRootString, UriKind.Absolute) }; } } else if (inputResourceName == "Inputs.CoreTests-Absolute.sarif") { Uri originalUri = actualLog.Runs[0].Artifacts[0].Location.Uri; string uriString = originalUri.ToString(); uriString = uriString.Replace(EnlistmentRoot, enlistmentRoot); actualLog.Runs[0].Artifacts[0].Location = new ArtifactLocation { Uri = new Uri(uriString, UriKind.Absolute) }; var visitor = new InsertOptionalDataVisitor(_currentOptionallyEmittedData); visitor.Visit(actualLog.Runs[0]); // Restore the remanufactured URI so that file diffing matches actualLog.Runs[0].Artifacts[0].Location = new ArtifactLocation { Uri = originalUri }; } else { var visitor = new InsertOptionalDataVisitor(_currentOptionallyEmittedData); visitor.Visit(actualLog.Runs[0]); } // Verify and remove Guids, because they'll vary with every run and can't be compared to a fixed expected output. if (_currentOptionallyEmittedData.HasFlag(OptionallyEmittedData.Guids)) { for (int i = 0; i < actualLog.Runs[0].Results.Count; ++i) { Result result = actualLog.Runs[0].Results[i]; result.Guid.Should().NotBeNullOrEmpty(because: "OptionallyEmittedData.Guids flag was set"); result.Guid = null; } } if (_currentOptionallyEmittedData.HasFlag(OptionallyEmittedData.VersionControlDetails)) { VersionControlDetails versionControlDetails = actualLog.Runs[0].VersionControlProvenance[0]; // Verify and replace the mapped directory (enlistment root), because it varies // from machine to machine. var mappedUri = new Uri(enlistmentRoot + @"\", UriKind.Absolute); versionControlDetails.MappedTo.Uri.Should().Be(mappedUri); versionControlDetails.MappedTo.Uri = new Uri($"file:///{EnlistmentRoot}/"); // When OptionallyEmittedData includes any file-related content, the visitor inserts // an artifact that points to the enlistment root. So we have to verify and adjust // that as well. IList<Artifact> artifacts = actualLog.Runs[0].Artifacts; if (artifacts.Count >= 2) { artifacts[1].Location.Uri.Should().Be(enlistmentRoot); artifacts[1].Location.Uri = new Uri($"file:///{EnlistmentRoot}/"); } // Verify and replace the remote repo URI, because it would be different in a fork. var gitHelper = new GitHelper(); Uri remoteUri = gitHelper.GetRemoteUri(enlistmentRoot); versionControlDetails.RepositoryUri.Should().Be(remoteUri); versionControlDetails.RepositoryUri = new Uri("https://REMOTE_URI"); // Verify and remove branch and revision id, because they vary from run to run. versionControlDetails.Branch.Should().NotBeNullOrEmpty(because: "OptionallyEmittedData.VersionControlInformation flag was set"); versionControlDetails.Branch = null; versionControlDetails.RevisionId.Should().NotBeNullOrEmpty(because: "OptionallyEmittedData.VersionControlInformation flag was set"); versionControlDetails.RevisionId = null; } return JsonConvert.SerializeObject(actualLog, Formatting.Indented); }
public override Run VisitRun(Run node) { _run = node; _gitHelper = new GitHelper(_fileSystem, _processRunner); _repoRootUris = new HashSet <Uri>(); if (_originalUriBaseIds != null) { _run.OriginalUriBaseIds ??= new Dictionary <string, ArtifactLocation>(); foreach (string key in _originalUriBaseIds.Keys) { _run.OriginalUriBaseIds[key] = _originalUriBaseIds[key]; } } if (node == null) { return(null); } bool scrapeFileReferences = _dataToInsert.HasFlag(OptionallyEmittedData.Hashes) || _dataToInsert.HasFlag(OptionallyEmittedData.TextFiles) || _dataToInsert.HasFlag(OptionallyEmittedData.BinaryFiles); if (scrapeFileReferences) { var visitor = new AddFileReferencesVisitor(); visitor.VisitRun(node); } Run visited = base.VisitRun(node); // After all the ArtifactLocations have been visited, if (_run.VersionControlProvenance == null && _dataToInsert.HasFlag(OptionallyEmittedData.VersionControlDetails)) { _run.VersionControlProvenance = CreateVersionControlProvenance(); } return(visited); }
public static Artifact Create( Uri uri, OptionallyEmittedData dataToInsert = OptionallyEmittedData.None, string mimeType = null, Encoding encoding = null, IFileSystem fileSystem = null) { if (uri == null) { throw new ArgumentNullException(nameof(uri)); } mimeType = mimeType ?? SarifWriters.MimeType.DetermineFromFileExtension(uri); fileSystem = fileSystem ?? new FileSystem(); var artifact = new Artifact() { Encoding = encoding?.WebName, MimeType = mimeType }; // Attempt to persist file contents and/or compute file hash and persist // this information to the log file. In the event that there is some issue // accessing the file, for example, due to ACLs applied to a directory, // we currently swallow these exceptions without populating any requested // data or putting a notification in the log file that a problem // occurred. Something to discuss moving forward. try { bool workTodo = dataToInsert.HasFlag(OptionallyEmittedData.Hashes) || dataToInsert.HasFlag(OptionallyEmittedData.TextFiles) || dataToInsert.HasFlag(OptionallyEmittedData.BinaryFiles); if (!workTodo || !uri.IsAbsoluteUri || !uri.IsFile || !fileSystem.FileExists(uri.LocalPath)) { return(artifact); } string filePath = uri.LocalPath; if (dataToInsert.HasFlag(OptionallyEmittedData.BinaryFiles) && SarifWriters.MimeType.IsBinaryMimeType(mimeType)) { artifact.Contents = GetEncodedFileContents(fileSystem, filePath, mimeType, encoding); } if (dataToInsert.HasFlag(OptionallyEmittedData.TextFiles) && SarifWriters.MimeType.IsTextualMimeType(mimeType)) { artifact.Contents = GetEncodedFileContents(fileSystem, filePath, mimeType, encoding); } if (dataToInsert.HasFlag(OptionallyEmittedData.Hashes)) { HashData hashes = HashUtilities.ComputeHashes(filePath); artifact.Hashes = new Dictionary <string, string> { { "md5", hashes.MD5 }, { "sha-1", hashes.Sha1 }, { "sha-256", hashes.Sha256 }, }; } } catch (Exception e) when(e is IOException || e is UnauthorizedAccessException) { } return(artifact); }
private static Run CreateRun( IEnumerable <string> analysisTargets, OptionallyEmittedData dataToInsert, IEnumerable <string> invocationTokensToRedact, IEnumerable <string> invocationPropertiesToLog, string defaultFileEncoding = null) { var run = new Run { Invocations = new List <Invocation>(), DefaultEncoding = defaultFileEncoding }; if (analysisTargets != null) { run.Artifacts = new List <Artifact>(); foreach (string target in analysisTargets) { Uri uri = new Uri(UriHelper.MakeValidUri(target), UriKind.RelativeOrAbsolute); var fileData = Artifact.Create( new Uri(target, UriKind.RelativeOrAbsolute), dataToInsert); var fileLocation = new ArtifactLocation { Uri = uri }; fileData.Location = fileLocation; // This call will insert the file object into run.Files if not already present fileData.Location.Index = run.GetFileIndex(fileData.Location, addToFilesTableIfNotPresent: true, dataToInsert); } } var invocation = Invocation.Create(dataToInsert.HasFlag(OptionallyEmittedData.EnvironmentVariables), invocationPropertiesToLog); // TODO we should actually redact across the complete log file context // by a dedicated rewriting visitor or some other approach. if (invocationTokensToRedact != null) { invocation.CommandLine = Redact(invocation.CommandLine, invocationTokensToRedact); invocation.Machine = Redact(invocation.Machine, invocationTokensToRedact); invocation.Account = Redact(invocation.Account, invocationTokensToRedact); if (invocation.WorkingDirectory != null) { invocation.WorkingDirectory.Uri = Redact(invocation.WorkingDirectory.Uri, invocationTokensToRedact); } if (invocation.EnvironmentVariables != null) { string[] keys = invocation.EnvironmentVariables.Keys.ToArray(); foreach (string key in keys) { string value = invocation.EnvironmentVariables[key]; invocation.EnvironmentVariables[key] = Redact(value, invocationTokensToRedact); } } } run.Invocations.Add(invocation); return(run); }
protected override string ConstructTestOutputFromInputResource(string inputResourceName, object parameter) { PrereleaseCompatibilityTransformer.UpdateToCurrentVersion( GetResourceText(inputResourceName), formatting: Formatting.Indented, out string transformedLog); SarifLog actualLog = PrereleaseCompatibilityTransformer.UpdateToCurrentVersion(transformedLog, formatting: Formatting.None, out transformedLog); // For CoreTests only - this code rewrites the log persisted URI to match the test environment if (inputResourceName == "Inputs.CoreTests-Relative.sarif") { Uri originalUri = actualLog.Runs[0].OriginalUriBaseIds["TESTROOT"].Uri; string uriString = originalUri.ToString(); string currentDirectory = Environment.CurrentDirectory; currentDirectory = currentDirectory.Substring(0, currentDirectory.IndexOf(@"\bld\")); uriString = uriString.Replace("REPLACED_AT_TEST_RUNTIME", currentDirectory); actualLog.Runs[0].OriginalUriBaseIds["TESTROOT"] = new ArtifactLocation { Uri = new Uri(uriString, UriKind.Absolute) }; var visitor = new InsertOptionalDataVisitor(_currentOptionallyEmittedData); visitor.Visit(actualLog.Runs[0]); // Restore the remanufactured URI so that file diffing matches actualLog.Runs[0].OriginalUriBaseIds["TESTROOT"] = new ArtifactLocation { Uri = originalUri }; } else if (inputResourceName == "Inputs.CoreTests-Absolute.sarif") { Uri originalUri = actualLog.Runs[0].Artifacts[0].Location.Uri; string uriString = originalUri.ToString(); string currentDirectory = Environment.CurrentDirectory; currentDirectory = currentDirectory.Substring(0, currentDirectory.IndexOf(@"\bld\")); uriString = uriString.Replace("REPLACED_AT_TEST_RUNTIME", currentDirectory); actualLog.Runs[0].Artifacts[0].Location = new ArtifactLocation { Uri = new Uri(uriString, UriKind.Absolute) }; var visitor = new InsertOptionalDataVisitor(_currentOptionallyEmittedData); visitor.Visit(actualLog.Runs[0]); // Restore the remanufactured URI so that file diffing matches actualLog.Runs[0].Artifacts[0].Location = new ArtifactLocation { Uri = originalUri }; } else { var visitor = new InsertOptionalDataVisitor(_currentOptionallyEmittedData); visitor.Visit(actualLog.Runs[0]); } // Verify and remove Guids, because they'll vary with every run and can't be compared to a fixed expected output if (_currentOptionallyEmittedData.HasFlag(OptionallyEmittedData.Guids)) { for (int i = 0; i < actualLog.Runs[0].Results.Count; ++i) { Result result = actualLog.Runs[0].Results[i]; if (string.IsNullOrEmpty(result.Guid)) { Assert.True(false, $"Results[{i}] had no Guid assigned, but OptionallyEmittedData.Guids flag was set."); } result.Guid = null; } } return(JsonConvert.SerializeObject(actualLog, Formatting.Indented)); }