diff --git a/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj b/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj index d342695c0..2e50ffcf1 100644 --- a/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj +++ b/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj @@ -312,6 +312,7 @@ + diff --git a/ICSharpCode.Decompiler.Tests/PdbGenerationTestRunner.cs b/ICSharpCode.Decompiler.Tests/PdbGenerationTestRunner.cs index 174711c8b..c2a58c3d8 100644 --- a/ICSharpCode.Decompiler.Tests/PdbGenerationTestRunner.cs +++ b/ICSharpCode.Decompiler.Tests/PdbGenerationTestRunner.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection.Metadata; @@ -9,13 +8,10 @@ using System.Text; using System.Xml.Linq; using ICSharpCode.Decompiler.CSharp; -using ICSharpCode.Decompiler.CSharp.OutputVisitor; using ICSharpCode.Decompiler.DebugInfo; using ICSharpCode.Decompiler.Metadata; using ICSharpCode.Decompiler.Tests.Helpers; -using ICSharpCode.Decompiler.TypeSystem; -using Microsoft.CodeAnalysis.CSharp; using Microsoft.DiaSymReader.Tools; using NUnit.Framework; @@ -72,6 +68,60 @@ namespace ICSharpCode.Decompiler.Tests } } + [Test] + public void ProgressReporting() + { + // Generate a PDB for an assembly and validate that the progress reporter is called with reasonable values + (string peFileName, string pdbFileName) = CompileTestCase(nameof(ProgressReporting)); + + var moduleDefinition = new PEFile(peFileName); + var resolver = new UniversalAssemblyResolver(peFileName, false, moduleDefinition.Metadata.DetectTargetFrameworkId(), null, PEStreamOptions.PrefetchEntireImage); + var decompiler = new CSharpDecompiler(moduleDefinition, resolver, new DecompilerSettings()); + + var lastFilesWritten = 0; + var totalFiles = -1; + + Action reportFunc = progress => { + if (totalFiles == -1) + { + // Initialize value on first call + totalFiles = progress.TotalFiles; + } + + Assert.AreEqual(progress.TotalFiles, totalFiles); + Assert.AreEqual(progress.FilesWritten, lastFilesWritten + 1); + + lastFilesWritten = progress.FilesWritten; + }; + + using (FileStream pdbStream = File.Open(Path.Combine(TestCasePath, nameof(ProgressReporting) + ".pdb"), FileMode.OpenOrCreate, FileAccess.ReadWrite)) + { + pdbStream.SetLength(0); + PortablePdbWriter.WritePdb(moduleDefinition, decompiler, new DecompilerSettings(), pdbStream, noLogo: true, progress: new TestProgressReporter(reportFunc)); + + pdbStream.Position = 0; + var metadataReader = MetadataReaderProvider.FromPortablePdbStream(pdbStream).GetMetadataReader(); + var generatedPdbId = new BlobContentId(metadataReader.DebugMetadataHeader.Id); + } + + Assert.AreEqual(totalFiles, lastFilesWritten); + } + + private class TestProgressReporter : IProgress + { + private Action reportFunc; + + public TestProgressReporter(Action reportFunc) + { + this.reportFunc = reportFunc; + } + + public void Report(WritePortablePdbProgress value) + { + reportFunc(value); + } + } + private void TestGeneratePdb([CallerMemberName] string testName = null) { const PdbToXmlOptions options = PdbToXmlOptions.IncludeEmbeddedSources | PdbToXmlOptions.ThrowOnError | PdbToXmlOptions.IncludeTokens | PdbToXmlOptions.ResolveTokens | PdbToXmlOptions.IncludeMethodSpans; diff --git a/ICSharpCode.Decompiler.Tests/TestCases/PdbGen/CustomPdbId.xml b/ICSharpCode.Decompiler.Tests/TestCases/PdbGen/CustomPdbId.xml index e0708da65..e5d691c3d 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/PdbGen/CustomPdbId.xml +++ b/ICSharpCode.Decompiler.Tests/TestCases/PdbGen/CustomPdbId.xml @@ -1,11 +1,11 @@ - + + + + + + \ No newline at end of file diff --git a/ICSharpCode.Decompiler/DebugInfo/PortablePdbWriter.cs b/ICSharpCode.Decompiler/DebugInfo/PortablePdbWriter.cs index cc8e342bd..3be497b77 100644 --- a/ICSharpCode.Decompiler/DebugInfo/PortablePdbWriter.cs +++ b/ICSharpCode.Decompiler/DebugInfo/PortablePdbWriter.cs @@ -28,6 +28,7 @@ using System.Reflection.Metadata.Ecma335; using System.Reflection.PortableExecutable; using System.Security.Cryptography; using System.Text; +using System.Threading; using ICSharpCode.Decompiler.CSharp; using ICSharpCode.Decompiler.CSharp.OutputVisitor; @@ -40,6 +41,12 @@ using ICSharpCode.Decompiler.Util; namespace ICSharpCode.Decompiler.DebugInfo { + public struct WritePortablePdbProgress + { + public int TotalFiles { get; internal set; } + public int FilesWritten { get; internal set; } + } + public class PortablePdbWriter { static readonly FileVersionInfo decompilerVersion = FileVersionInfo.GetVersionInfo(typeof(CSharpDecompiler).Assembly.Location); @@ -49,7 +56,14 @@ namespace ICSharpCode.Decompiler.DebugInfo return file.Reader.ReadDebugDirectory().Any(entry => entry.Type == DebugDirectoryEntryType.CodeView); } - public static void WritePdb(PEFile file, CSharpDecompiler decompiler, DecompilerSettings settings, Stream targetStream, bool noLogo = false, BlobContentId? pdbId = null) + public static void WritePdb( + PEFile file, + CSharpDecompiler decompiler, + DecompilerSettings settings, + Stream targetStream, + bool noLogo = false, + BlobContentId? pdbId = null, + IProgress progress = null) { MetadataBuilder metadata = new MetadataBuilder(); MetadataReader reader = file.Metadata; @@ -72,10 +86,23 @@ namespace ICSharpCode.Decompiler.DebugInfo return Path.Combine(ns, WholeProjectDecompiler.CleanUpFileName(typeName.Name) + ".cs"); } - foreach (var sourceFile in reader.GetTopLevelTypeDefinitions().GroupBy(BuildFileNameFromTypeName)) + var sourceFiles = reader.GetTopLevelTypeDefinitions().GroupBy(BuildFileNameFromTypeName).ToList(); + WritePortablePdbProgress currentProgress = new WritePortablePdbProgress() { + TotalFiles = sourceFiles.Count, + FilesWritten = 0 + }; + + foreach (var sourceFile in sourceFiles) { // Generate syntax tree var syntaxTree = decompiler.DecompileTypes(sourceFile); + + if (progress != null) + { + currentProgress.FilesWritten++; + progress.Report(currentProgress); + } + if (!syntaxTree.HasChildren) continue;