Skip to content

Commit

Permalink
Fixed bugs in ManagedMethod parsing, and updated hierarchies. (micros…
Browse files Browse the repository at this point in the history
…oft#3704)

* Fixed bugs in ManagedMethod parsing, and updated hierarchies.
  • Loading branch information
Haplois committed Jul 19, 2022
1 parent 4e75ec7 commit b7fde53
Show file tree
Hide file tree
Showing 50 changed files with 773 additions and 150 deletions.
5 changes: 5 additions & 0 deletions CODEOWNERS
Validating CODEOWNERS rules …
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,8 @@ PublicAPI.Unshipped.txt @nohwnd @MarcoRossignoli
# something that should be treated with care and we should be transparent about it. It also
# requires making sure all telemetry collection systems in place will continue to function well.
TelemetryDataConstants.cs @cvpoienaru @nohwnd

# Changes here might break our contracts with other adapters, and possibly
# Visual Studio.
/src/Microsoft.TestPlatform.AdapterUtilities/ @haplois @Evangelink
/test/Microsoft.TestPlatform.AdapterUtilities.UnitTests/ @haplois @Evangelink
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TestPlatformRoot Condition="$(TestPlatformRoot) == ''">..\..\</TestPlatformRoot>
</PropertyGroup>
<Import Project="$(TestPlatformRoot)scripts/build/TestPlatform.Settings.targets" />

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\..\src\Microsoft.TestPlatform.AdapterUtilities\Microsoft.TestPlatform.AdapterUtilities.csproj" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="$(JsonNetVersion)" />
<PackageReference Include="Microsoft.CodeAnalysis" Version="$(MicrosoftCodeAnalysisVersion)" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="$(MicrosoftCodeAnalysisVersion)" />
</ItemGroup>

<ItemGroup>
<Compile Update="TestClasses.cs">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Compile>
</ItemGroup>

<Import Project="$(TestPlatformRoot)scripts\build\TestPlatform.targets" />
</Project>
101 changes: 101 additions & 0 deletions playground/AdapterUtilitiesPlayground/FindMethodExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
// 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.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;

using Microsoft.CodeAnalysis;

namespace Microsoft.TestPlatform.AdapterUtilities.ManagedNameUtilities.UnitTests;

[DebuggerStepThrough]
internal static class FindMethodExtensions
{
private const BindingFlags PrivateBindingFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance;

internal static MethodInfo? FindMethod(this Type type, string signature)
=> type.FindMembers(MemberTypes.Method, PrivateBindingFlags,
(mbr, sig) => mbr.ToString() == (string?)sig, signature).FirstOrDefault() as MethodInfo;

internal static IMethodSymbol FindMethod(
this INamedTypeSymbol type,
string methodName,
int methodGenericArity = -1,
params ITypeSymbol[] methodParameterTypes)
{
var candidates = GetCandidateMethods(type, methodName);
if (candidates.Any() && !candidates.Skip(1).Any())
{
return candidates.Single();
}

if (methodGenericArity != -1)
{
candidates = candidates.Where(m => m.Arity == methodGenericArity);
if (candidates.Any() && !candidates.Skip(1).Any())
{
return candidates.Single();
}
}

if (methodParameterTypes != null && methodParameterTypes.Length >= 0)
{
candidates = candidates.Where(m => m.Parameters.Length == methodParameterTypes.Length);
if (candidates.Any() && !candidates.Skip(1).Any())
{
return candidates.Single();
}

candidates = candidates.Where(m => m.Parameters.Select(p => p.Type).SequenceEqual(methodParameterTypes));
}

Debug.Assert(candidates.Any() && !candidates.Skip(1).Any());
return candidates.Single();
}

internal static IMethodSymbol FindMethod(
this INamedTypeSymbol type,
string methodName,
int methodGenericArity,
int methodParameterCount,
Func<IMethodSymbol, bool> selector)
{
var candidates = GetCandidateMethods(type, methodName);
if (candidates.Any() && !candidates.Skip(1).Any())
{
return candidates.Single();
}

candidates = candidates.Where(m => m.Arity == methodGenericArity);
if (candidates.Any() && !candidates.Skip(1).Any())
{
return candidates.Single();
}

candidates = candidates.Where(m => m.Parameters.Length == methodParameterCount);
if (candidates.Any() && !candidates.Skip(1).Any())
{
return candidates.Single();
}

candidates = candidates.Where(selector);

Debug.Assert(candidates.Any() && !candidates.Skip(1).Any());
return candidates.Single();
}

private static IEnumerable<IMethodSymbol> GetCandidateMethods(INamedTypeSymbol type, string methodName)
{
var candidates = type.GetMembers(methodName).OfType<IMethodSymbol>();

if (type.BaseType != null && type.BaseType.SpecialType != SpecialType.System_Object)
{
candidates = candidates.Union(GetCandidateMethods(type.BaseType, methodName));
}

return candidates;
}
}
55 changes: 55 additions & 0 deletions playground/AdapterUtilitiesPlayground/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// 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.Reflection;

using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.TestPlatform.AdapterUtilities.ManagedNameUtilities;
using Microsoft.TestPlatform.AdapterUtilities.ManagedNameUtilities.UnitTests;

namespace AdapterUtilitiesPlayground;

internal class Program
{
private const BindingFlags PrivateBindingFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance;
private static readonly Compilation _compilation = CSharpCompilation.Create(
"Test.dll",
new[] { CSharpSyntaxTree.ParseText(File.ReadAllText("TestClasses.cs")) },
new[] { MetadataReference.CreateFromFile(typeof(object).Assembly.Location) });

static void Main(string[] args)
{
var derivedClass = typeof(TestClasses.DerivedClass);
var baseClass = typeof(TestClasses.BaseClass);

var derivedMethods = derivedClass.GetMethods(PrivateBindingFlags).ToArray();
var baseMethods = baseClass.GetMethods(PrivateBindingFlags).ToArray();
var derivedMethod0 = derivedMethods.Single(i => i.Name == "Method0" && i.DeclaringType == derivedClass);
var derivedbaseMethod0 = derivedMethods.Single(i => i.Name == "Method0" && i.DeclaringType == baseClass);
var baseMethod0 = baseMethods.Single(i => i.Name == "Method0" && i.DeclaringType == baseClass);

// {
// ManagedNameHelper.GetManagedName(derivedMethod0, out var managedType, out var managedMethod, out var hierarchies);
// var methodBase = ManagedNameHelper.GetMethod(derivedClass.Assembly, managedType, managedMethod);
// }
//
// {
// ManagedNameHelper.GetManagedName(derivedbaseMethod0, out var managedType, out var managedMethod, out var hierarchies);
// var methodBase = ManagedNameHelper.GetMethod(derivedClass.Assembly, managedType, managedMethod);
// }

//{
// ManagedNameHelper.GetManagedName(baseMethod0, out var managedType, out var managedMethod, out var hierarchies);
// var methodBase = ManagedNameHelper.GetMethod(derivedClass.Assembly, managedType, managedMethod);
//}

{
var method = typeof(TestClasses.IImplementation<string>).GetMethods(PrivateBindingFlags).SingleOrDefault(i => i.Name == "ImplMethod2")!;
method = method.MakeGenericMethod(typeof(int));

ManagedNameHelper.GetManagedName(method, out var managedType, out var managedMethod, out var hierarchies);
var methodBase = ManagedNameHelper.GetMethod(derivedClass.Assembly, managedType, managedMethod);
}
}
}
117 changes: 117 additions & 0 deletions playground/AdapterUtilitiesPlayground/TestClasses.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
// 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.Collections.Generic;

namespace TestClasses;

#pragma warning disable IDE0060 // Remove unused parameter
#pragma warning disable CA1822 // Mark members as static

internal class DerivedClass : BaseClass
{
public new void Method0(int i) { }
}

internal class BaseClass
{
public void Method0(int i) { }
public void Method1(int i) { }
}

internal class Outer
{
public void Method0() { }
public void Method1(int i) { }
public void Method2(List<string> ls) { }
public void Method3(string p, int l) { }
internal class Inner
{
public void Method0() { }
public void Method1(int i) { }
public void Method2<U>(int i) { }
public void Method3<U, T>(int i) { }
}
}

internal class OuterPrime : Outer { }

internal class Outer<T>
{
public void Method0() { }
public void Method1(T t) { }
public void Method2<U>(U[] u) { }
public void Method3<U>(T t, U u) { }

internal class Inner<V>
{
public void Method0() { }
public void Method1(T t) { }
public void Method2(V v) { }
public void Method3<U>(T t, U u, V v) { }
public void Method4<U, X>(X x, U u) { }
public void Method5<U, X>(List<X> x, U u) { }

internal class MoreInner<I>
{
public void Method0<U>(T t, V v, I i, U u) { }
}
}
}

internal class OuterPrime<Z> : Outer<Z> { }

internal class OuterPrime<Y, Z> : Outer<Z> { }

internal class OuterString : Outer<string> { }

internal interface IImplementation
{
void ImplMethod0();
void ImplMethod1(int i);
}

internal class Impl : IImplementation
{
void IImplementation.ImplMethod0() { }
void IImplementation.ImplMethod1(int i) { }
}

internal interface IImplementation<T>
{
void ImplMethod0();
void ImplMethod1(T t);
void ImplMethod2<U>(T t, U u, string s);
}

internal class Impl<T> : IImplementation<T>
{
void IImplementation<T>.ImplMethod0() { }
void IImplementation<T>.ImplMethod1(T t) { }
void IImplementation<T>.ImplMethod2<U>(T t, U u, string s) { }
}

internal class Overloads
{
public void Overload0() { }
public void Overload0(int i) { }
public void Overload0(int i, Overloads c) { }
public unsafe void Overload0(int* p) { }
public void Overload0(dynamic d) { }
public void Overload0<U>(U u) { }
public void Overload0<U>() { }
public void Overload0<U, T>() { }
public void Overload0<U>(U[] u) { }
public void Overload0<U>(U[][] u) { }
public void Overload0<U>(U[,] u) { }
public void Overload0<U>(U[,,] u) { }
public void Overload0<U>(List<int> l) { }
public void Overload0<U>(List<U> l) { }
public void Overload0<U, V>(Tuple<U, V> t0, Tuple<V, U> t1) { }
public void Overload0(Tuple<Tuple<string[,], int>> t0) { }
public void Overload0(Tuple<Tuple<string>, Tuple<int>> t) { }
public void Overload0<U>(Tuple<Tuple<Outer<U>.Inner<U>>> t) { }
}
#pragma warning restore IDE0060 // Remove unused parameter
#pragma warning restore CA1822 // Mark members as static
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
// associated with an assembly.
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("Microsoft Corporation")]
[assembly: AssemblyCopyright("© Microsoft Corporation. All rights reserved.")]
[assembly: AssemblyCopyright("© Microsoft Corporation. All rights reserved.")]
[assembly: AssemblyProduct("Microsoft.TestPlatform.Extensions.EventLogCollector")]
[assembly: AssemblyTrademark("")]
[assembly: NeutralResourcesLanguage("en-US")]
Expand Down
1 change: 1 addition & 0 deletions src/Microsoft.TestPlatform.AdapterUtilities/Friends.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@

using System.Runtime.CompilerServices;

[assembly: System.Resources.NeutralResourcesLanguage("en-US")]
[assembly: InternalsVisibleTo("Microsoft.TestPlatform.AdapterUtilities.UnitTests, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")]
32 changes: 29 additions & 3 deletions src/Microsoft.TestPlatform.AdapterUtilities/HierarchyConstants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,16 +26,42 @@ public static class Levels
/// <summary>
/// Total length of Hierarchy array.
/// </summary>
public const int TotalLevelCount = 2;
/// <remarks>
/// Currently the order for this is usually:
/// <c>Assembly Display Name</c>, <c>Namespace</c>, <c>ClassName</c>, <c>Managed Method Name</c>.
/// </remarks>
public const int TotalLevelCount = 4;

/// <summary>
/// Index of the test container element of the array.
/// </summary>
/// <remarks>
/// This is usually test asssembly display name.
/// </remarks>
public const int ContainerIndex = 0;

/// <summary>
/// Index of the namespace element of the array.
/// </summary>
public const int NamespaceIndex = 0;
/// <remarks>
/// This is usually test namespace without class name.
/// </remarks>
public const int NamespaceIndex = 1;

/// <summary>
/// Index of the class element of the array.
/// </summary>
public const int ClassIndex = 1;
/// <remarks>
/// This is usually test class name without namespace.
/// </remarks>
public const int ClassIndex = 2;

/// <summary>
/// Index of the test group element of the array.
/// </summary>
/// <remarks>
/// This is usually test method name.
/// </remarks>
public const int TestGroupIndex = 3;
}
}
Loading

0 comments on commit b7fde53

Please sign in to comment.