diff --git a/src/Microsoft.Extensions.Testing.Abstractions/FullPdbReader.cs b/src/Microsoft.Extensions.Testing.Abstractions/FullPdbReader.cs
index b4c08e599..d5339096b 100644
--- a/src/Microsoft.Extensions.Testing.Abstractions/FullPdbReader.cs
+++ b/src/Microsoft.Extensions.Testing.Abstractions/FullPdbReader.cs
@@ -51,25 +51,32 @@ namespace Microsoft.Extensions.Testing.Abstractions
private static ISymUnmanagedReader3 CreateNativeSymReader(Stream pdbStream)
{
- object symReader = null;
- var guid = default(Guid);
- if (IntPtr.Size == 4)
+ try
{
- CreateSymReader32(ref guid, out symReader);
+ object symReader = null;
+ var guid = default(Guid);
+ if (IntPtr.Size == 4)
+ {
+ CreateSymReader32(ref guid, out symReader);
+ }
+ else
+ {
+ CreateSymReader64(ref guid, out symReader);
+ }
+ var reader = (ISymUnmanagedReader3)symReader;
+ var hr = reader.Initialize(new DummyMetadataImport(), null, null, new ComStreamWrapper(pdbStream));
+ SymUnmanagedReaderExtensions.ThrowExceptionForHR(hr);
+ return reader;
}
- else
+ catch (Exception e)
{
- CreateSymReader64(ref guid, out symReader);
+ throw new IOException(e.Message, e);
}
- var reader = (ISymUnmanagedReader3)symReader;
- var hr = reader.Initialize(new DummyMetadataImport(), null, null, new ComStreamWrapper(pdbStream));
- SymUnmanagedReaderExtensions.ThrowExceptionForHR(hr);
- return reader;
}
public void Dispose()
{
- ((ISymUnmanagedDispose) _symReader).Destroy();
+ ((ISymUnmanagedDispose)_symReader).Destroy();
}
}
diff --git a/src/Microsoft.Extensions.Testing.Abstractions/IPdbReaderFactory.cs b/src/Microsoft.Extensions.Testing.Abstractions/IPdbReaderFactory.cs
index e3ee8b53f..d6f7a23e3 100644
--- a/src/Microsoft.Extensions.Testing.Abstractions/IPdbReaderFactory.cs
+++ b/src/Microsoft.Extensions.Testing.Abstractions/IPdbReaderFactory.cs
@@ -1,12 +1,21 @@
// Copyright (c) .NET Foundation and contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+using System;
using System.IO;
namespace Microsoft.Extensions.Testing.Abstractions
{
public interface IPdbReaderFactory
{
+ ///
+ /// Creates for given file.
+ ///
+ ///
+ /// Path to the .pdb file or a PE file that refers to the .pdb file in its Debug Directory Table.
+ ///
+ /// File does not exist or can't be read.
+ /// is null.
IPdbReader Create(string pdbPath);
}
}
diff --git a/src/Microsoft.Extensions.Testing.Abstractions/MissingPdbReader.cs b/src/Microsoft.Extensions.Testing.Abstractions/MissingPdbReader.cs
new file mode 100644
index 000000000..c4b860670
--- /dev/null
+++ b/src/Microsoft.Extensions.Testing.Abstractions/MissingPdbReader.cs
@@ -0,0 +1,12 @@
+using System.Reflection;
+
+namespace Microsoft.Extensions.Testing.Abstractions
+{
+ internal sealed class MissingPdbReader : IPdbReader
+ {
+ public static readonly MissingPdbReader Instance = new MissingPdbReader();
+
+ public SourceInformation GetSourceInformation(MethodInfo methodInfo) => null;
+ public void Dispose() { }
+ }
+}
diff --git a/src/Microsoft.Extensions.Testing.Abstractions/PdbReaderFactory.cs b/src/Microsoft.Extensions.Testing.Abstractions/PdbReaderFactory.cs
index 24e3eb119..8c05227fe 100644
--- a/src/Microsoft.Extensions.Testing.Abstractions/PdbReaderFactory.cs
+++ b/src/Microsoft.Extensions.Testing.Abstractions/PdbReaderFactory.cs
@@ -3,31 +3,103 @@
using System;
using System.IO;
+using System.Reflection.Metadata;
+using System.Reflection.PortableExecutable;
namespace Microsoft.Extensions.Testing.Abstractions
{
public class PdbReaderFactory : IPdbReaderFactory
{
+ ///
+ /// Creates for given file.
+ ///
+ ///
+ /// Path to the .pdb file or a PE file that refers to the .pdb file in its Debug Directory Table.
+ ///
+ /// File does not exist or can't be read.
+ /// is null.
public IPdbReader Create(string pdbPath)
{
- var pdbStream = new FileStream(pdbPath, FileMode.Open, FileAccess.Read);
-
- if (IsPortable(pdbStream))
+ if (pdbPath == null)
{
- return new PortablePdbReader(pdbStream);
+ throw new ArgumentNullException(nameof(pdbPath));
}
- else
+
+ Stream stream = OpenRead(pdbPath);
+
+ if (IsPE(stream))
{
- return new FullPdbReader(pdbStream);
+ return CreateFromPortableExecutable(stream, pdbPath);
+ }
+
+ if (IsPortable(stream))
+ {
+ return new PortablePdbReader(stream);
+ }
+
+ return new FullPdbReader(stream);
+ }
+
+ private static bool IsPortable(Stream stream)
+ {
+ bool result = stream.ReadByte() == 'B' && stream.ReadByte() == 'S' && stream.ReadByte() == 'J' && stream.ReadByte() == 'B';
+ stream.Position = 0;
+ return result;
+ }
+
+ private static bool IsPE(Stream stream)
+ {
+ bool result = stream.ReadByte() == 'M' && stream.ReadByte() == 'Z';
+ stream.Position = 0;
+ return result;
+ }
+
+ private IPdbReader CreateFromPortableExecutable(Stream peStream, string pePath)
+ {
+ using (var peReader = new PEReader(peStream))
+ {
+ MetadataReaderProvider pdbProvider;
+ string pdbPath;
+ if (peReader.TryOpenAssociatedPortablePdb(pePath, TryOpenRead, out pdbProvider, out pdbPath))
+ {
+ return new PortablePdbReader(pdbProvider);
+ }
+
+ return MissingPdbReader.Instance;
}
}
- private static bool IsPortable(Stream pdbStream)
+ private static Stream OpenRead(string path)
{
- return pdbStream.ReadByte() == 'B' &&
- pdbStream.ReadByte() == 'S' &&
- pdbStream.ReadByte() == 'J' &&
- pdbStream.ReadByte() == 'B';
+ try
+ {
+ return File.OpenRead(path);
+ }
+ catch (Exception e) when (!(e is IOException))
+ {
+ throw new IOException(e.Message, e);
+ }
+ }
+
+ private static Stream TryOpenRead(string path)
+ {
+ if (!File.Exists(path))
+ {
+ return null;
+ }
+
+ try
+ {
+ return File.OpenRead(path);
+ }
+ catch (FileNotFoundException)
+ {
+ return null;
+ }
+ catch (Exception e) when (!(e is IOException))
+ {
+ throw new IOException(e.Message, e);
+ }
}
}
}
diff --git a/src/Microsoft.Extensions.Testing.Abstractions/PortablePdbReader.cs b/src/Microsoft.Extensions.Testing.Abstractions/PortablePdbReader.cs
index 80ea95cf2..7b6d11220 100644
--- a/src/Microsoft.Extensions.Testing.Abstractions/PortablePdbReader.cs
+++ b/src/Microsoft.Extensions.Testing.Abstractions/PortablePdbReader.cs
@@ -6,27 +6,23 @@ using System.IO;
using System.Linq;
using System.Reflection;
using System.Reflection.Metadata;
-using System.Runtime.InteropServices;
namespace Microsoft.Extensions.Testing.Abstractions
{
public class PortablePdbReader : IPdbReader
{
private MetadataReader _reader;
- private GCHandle _gcHandle;
+ private MetadataReaderProvider _provider;
- public PortablePdbReader(Stream pdbStream)
+ public PortablePdbReader(Stream stream)
+ : this(MetadataReaderProvider.FromPortablePdbStream(stream))
{
- pdbStream.Position = 0;
- var buffer = new byte[pdbStream.Length];
- pdbStream.Read(buffer, 0, buffer.Length);
-
- _gcHandle = GCHandle.Alloc(buffer, GCHandleType.Pinned);
- unsafe
- {
- var address = _gcHandle.AddrOfPinnedObject();
- _reader = new MetadataReader((byte*)address.ToPointer(), buffer.Length);
- }
+ }
+
+ internal PortablePdbReader(MetadataReaderProvider provider)
+ {
+ _provider = provider;
+ _reader = provider.GetMetadataReader();
}
public SourceInformation GetSourceInformation(MethodInfo methodInfo)
@@ -43,6 +39,11 @@ namespace Microsoft.Extensions.Testing.Abstractions
private SourceInformation GetSourceInformation(MethodDebugInformationHandle handle)
{
+ if (_reader == null)
+ {
+ throw new ObjectDisposedException(nameof(PortablePdbReader));
+ }
+
SourceInformation sourceInformation = null;
try
{
@@ -81,7 +82,9 @@ namespace Microsoft.Extensions.Testing.Abstractions
public void Dispose()
{
- _gcHandle.Free();
+ _provider?.Dispose();
+ _provider = null;
+ _reader = null;
}
}
}
diff --git a/src/Microsoft.Extensions.Testing.Abstractions/SourceInformationProvider.cs b/src/Microsoft.Extensions.Testing.Abstractions/SourceInformationProvider.cs
index 1e018cc0e..eadfaa743 100644
--- a/src/Microsoft.Extensions.Testing.Abstractions/SourceInformationProvider.cs
+++ b/src/Microsoft.Extensions.Testing.Abstractions/SourceInformationProvider.cs
@@ -10,23 +10,44 @@ namespace Microsoft.Extensions.Testing.Abstractions
{
public class SourceInformationProvider : ISourceInformationProvider
{
- private readonly string _pdbPath;
+ private readonly string _filePath;
private readonly IPdbReader _pdbReader;
+ ///
+ /// Creates a source info provide from a specified file path.
+ ///
+ ///
+ /// Path to the .pdb file or a PE file that refers to the .pdb file in its Debug Directory Table.
+ ///
public SourceInformationProvider(string pdbPath) :
this(pdbPath, new PdbReaderFactory())
{
}
+ ///
+ /// Creates a source info provide from a specified file path.
+ ///
+ ///
+ /// Path to the .pdb file or a PE file that refers to the .pdb file in its Debug Directory Table.
+ ///
+ ///
+ /// Factory that creates instance used to read the PDB.
+ ///
+ /// File does not exist or can't be read.
+ /// or is null.
public SourceInformationProvider(string pdbPath, IPdbReaderFactory pdbReaderFactory)
{
- if (string.IsNullOrWhiteSpace(pdbPath) || !File.Exists(pdbPath))
+ if (pdbPath == null)
{
- throw new ArgumentException($"The file '{pdbPath}' does not exist.", nameof(pdbPath));
+ throw new ArgumentNullException(nameof(pdbPath));
}
- _pdbPath = pdbPath;
+ if (pdbReaderFactory == null)
+ {
+ throw new ArgumentNullException(nameof(pdbReaderFactory));
+ }
+ _filePath = pdbPath;
_pdbReader = pdbReaderFactory.Create(pdbPath);
}
diff --git a/src/Microsoft.Extensions.Testing.Abstractions/project.json b/src/Microsoft.Extensions.Testing.Abstractions/project.json
index 5f1ccc646..649f67315 100644
--- a/src/Microsoft.Extensions.Testing.Abstractions/project.json
+++ b/src/Microsoft.Extensions.Testing.Abstractions/project.json
@@ -13,7 +13,7 @@
},
"Microsoft.DiaSymReader": "1.0.8",
"Microsoft.DiaSymReader.Native": "1.4.0-rc2",
- "System.Reflection.Metadata": "1.4.1-beta-24410-02"
+ "System.Reflection.Metadata": "1.4.1-beta-24426-02"
},
"frameworks": {
"net451": {},