File: Navigation\DiaSession.cs
Web Access
Project: src\src\vstest\src\Microsoft.TestPlatform.ObjectModel\Microsoft.TestPlatform.ObjectModel.csproj (Microsoft.VisualStudio.TestPlatform.ObjectModel)
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System;
using System.Diagnostics.CodeAnalysis;
using System.IO;

using Microsoft.VisualStudio.TestPlatform.ObjectModel.Navigation;
using Microsoft.VisualStudio.TestPlatform.Utilities.Helpers;

namespace Microsoft.VisualStudio.TestPlatform.ObjectModel;

/// <summary>
/// The class that enables us to get debug information from both managed and native binaries.
/// </summary>
[SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly",
     Justification = "Dia is a specific name.")]
public class DiaSession : INavigationSession
{
    /// <summary>
    /// Characters that should be stripped off the end of test names.
    /// </summary>
    private static readonly char[] TestNameStripChars = ['(', ')', ' '];

    /// <summary>
    /// The symbol reader.
    /// </summary>
    private readonly ISymbolReader _symbolReader;

    private bool _isDisposed;

    /// <summary>
    /// Initializes a new instance of the <see cref="DiaSession"/> class.
    /// </summary>
    /// <param name="binaryPath">
    /// The binary path.
    /// </param>
    public DiaSession(string binaryPath)
        : this(binaryPath, null)
    {
    }

    /// <summary>
    /// Initializes a new instance of the <see cref="DiaSession"/> class.
    /// </summary>
    /// <param name="binaryPath">
    /// The binary path is assembly path Ex: \path\to\bin\Debug\simpleproject.dll
    /// </param>
    /// <param name="searchPath">
    /// search path.
    /// </param>
    public DiaSession(string binaryPath, string? searchPath)
        : this(binaryPath, searchPath, GetSymbolReader(binaryPath))
    {
    }

    internal DiaSession(string binaryPath, string? searchPath, ISymbolReader symbolReader)
    {
        _symbolReader = symbolReader;
        ValidateArg.NotNullOrEmpty(binaryPath, nameof(binaryPath));
        _symbolReader.CacheSymbols(binaryPath, searchPath);
    }

    /// <summary>
    /// Dispose symbol reader
    /// </summary>
    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    protected virtual void Dispose(bool disposing)
    {
        if (_isDisposed)
            return;

        if (disposing)
        {
            _symbolReader.Dispose();
        }

        _isDisposed = true;
    }

    /// <summary>
    /// Gets the navigation data for a method declared in a type.
    /// </summary>
    /// <param name="declaringTypeName"> The declaring type name. </param>
    /// <param name="methodName"> The method name. </param>
    /// <returns> The <see cref="INavigationData" /> for that method. </returns>
    /// <remarks> Leaving this method in place to preserve back compatibility. </remarks>
    public DiaNavigationData? GetNavigationData(string declaringTypeName, string methodName)
    {
        return (DiaNavigationData?)GetNavigationDataForMethod(declaringTypeName, methodName);
    }

    /// <summary>
    /// Gets the navigation data for a method declared in a type.
    /// </summary>
    /// <param name="declaringTypeName"> The declaring type name. </param>
    /// <param name="methodName"> The method name. </param>
    /// <returns> The <see cref="INavigationData" /> for that method. </returns>
    public INavigationData? GetNavigationDataForMethod(string declaringTypeName, string methodName)
    {
        ValidateArg.NotNullOrEmpty(declaringTypeName, nameof(declaringTypeName));
        ValidateArg.NotNullOrEmpty(methodName, nameof(methodName));
        methodName = methodName.TrimEnd(TestNameStripChars);
        return _symbolReader.GetNavigationData(declaringTypeName, methodName);
    }

    private static ISymbolReader GetSymbolReader(string? binaryPath)
    {
        var pdbFilePath = Path.ChangeExtension(binaryPath, ".pdb");

        // For remote scenario, also look for pdb in current directory, (esp for UWP)
        // The alternate search path should be an input from Adapters, but since it is not so currently adding a HACK
        pdbFilePath = !File.Exists(pdbFilePath)
            ? Path.Combine(Directory.GetCurrentDirectory(), Path.GetFileName(pdbFilePath)!)
            : pdbFilePath;

        if (File.Exists(pdbFilePath))
        {
            using var stream = new FileHelper().GetStream(pdbFilePath!, FileMode.Open, FileAccess.Read);
            return PortablePdbReader.IsPortable(stream) ? new PortableSymbolReader() : new FullSymbolReader();
        }
        else
        {
            // If we cannot find the pdb file, it might be embedded in the dll.
            return new PortableSymbolReader();
        }
    }
}