Skip to content

Commit

Permalink
[xharness] Use Cecil to inspect assemblies, instead of Reflection. (#…
Browse files Browse the repository at this point in the history
…9082)

This is slightly faster - ~0.95s vs ~1.4s - (probably because reflection tries
to load a lot of other referenced assemblies, which may or may not exist,
causing exceptions (if they don't exist) or spend time loading them (which
Cecil won't)).

It also avoids a lot of exception details showing up when tracing xharness
execution.
  • Loading branch information
rolfbjarne authored and mandel-macaque committed Oct 5, 2020
1 parent 1117df7 commit 5b87778
Show file tree
Hide file tree
Showing 3 changed files with 42 additions and 22 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,7 @@
<EmbeddedResource Include="TestImporter\Templates\Managed\Resources\**\*" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="Mono.Cecil" Version="0.11.2" />
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
using System.Reflection;
using System.Collections.Generic;

using Mono.Cecil;

namespace Microsoft.DotNet.XHarness.iOS.Shared.TestImporter {
/// <summary>
/// Class that defines a bcl test project. A bcl test project by definition is the combination of the name
Expand All @@ -24,6 +26,16 @@ public bool IsXUnit {
}
}

Dictionary<string, AssemblyDefinition> assemblies = new Dictionary<string, AssemblyDefinition> ();
AssemblyDefinition LoadAssembly (string path)
{
lock (assemblies) {
if (!assemblies.TryGetValue (path, out var ad))
assemblies [path] = ad = AssemblyDefinition.ReadAssembly (path, new ReaderParameters (ReadingMode.Deferred));
return ad;
}
}

public ProjectDefinition (string name, IAssemblyLocator locator, ITestAssemblyDefinitionFactory factory, string [] assemblies, string extraArgs)
{
if (assemblies.Length == 0)
Expand Down Expand Up @@ -51,12 +63,12 @@ public ProjectDefinition (string name, IAssemblyLocator locator, ITestAssemblyDe
ExtraArgs = extraArgs;
}

static (string FailureMessage, IEnumerable<string> References) GetAssemblyReferences (string assemblyPath)
(string FailureMessage, IEnumerable<string> References) GetAssemblyReferences (string assemblyPath)
{
if (!File.Exists (assemblyPath))
return ($"The file {assemblyPath} does not exist.", null);
var a = Assembly.LoadFile (assemblyPath);
return (null, a.GetReferencedAssemblies ().Select ((arg) => arg.Name));
var ad = LoadAssembly (assemblyPath);
return (null, ad.MainModule.AssemblyReferences.Select ((arg) => arg.Name));
}

/// <summary>
Expand Down Expand Up @@ -97,32 +109,35 @@ public bool Validate ()
return (failureMessage, set);
}

public (string FailureMessage, Dictionary<string, Type> Types) GetTypeForAssemblies (string monoRootPath, Platform platform)
public (string FailureMessage, Dictionary<string, TypeDefinition> Types) GetTypeForAssemblies (string monoRootPath, Platform platform)
{
if (monoRootPath == null)
throw new ArgumentNullException (nameof (monoRootPath));
var dict = new Dictionary<string, Type> ();
var dict = new Dictionary<string, TypeDefinition> ();
// loop over the paths, grab the assembly, find a type and then add it
foreach (var definition in TestAssemblies) {
var path = definition.GetPath (platform);
if (!File.Exists (path))
return ($"The assembly {path} does not exist. Please make sure it exists, then re-generate the project files by executing 'git clean -xfd && make' in the tests/ directory.", null);
var a = Assembly.LoadFile (path);
try {
var types = a.ExportedTypes;
if (!types.Any ()) {
continue;
}
dict [Path.GetFileName (path)] = types.First (t => !t.IsGenericType && (t.FullName.EndsWith ("Test") || t.FullName.EndsWith ("Tests")) && t.Namespace != null);
} catch (ReflectionTypeLoadException e) { // ReflectionTypeLoadException
// we did get an exception, possible reason, the type comes from an assebly not loaded, but
// nevertheless we can do something about it, get all the not null types in the exception
// and use one of them
var types = e.Types.Where (t => t != null).Where (t => !t.IsGenericType && (t.FullName.EndsWith ("Test") || t.FullName.EndsWith ("Tests")) && t.Namespace != null);
if (types.Any ()) {
dict [Path.GetFileName (path)] = types.First ();
}
}
var ad = LoadAssembly (path);
var accessibleType = ad.MainModule.Types.FirstOrDefault ((t) => {
if (!t.IsPublic)
return false;

if (t.HasGenericParameters)
return false;

if (t.Namespace == null)
return false;

if (!t.FullName.EndsWith ("Test", StringComparison.OrdinalIgnoreCase) && !t.FullName.EndsWith ("Tests", StringComparison.OrdinalIgnoreCase))
return false;

return true;
});
if (accessibleType == null)
continue;
dict [Path.GetFileName (path)] = accessibleType;
}
return (null, dict);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,16 @@
using System.Threading.Tasks;
using System.Collections.Generic;

using Mono.Cecil;

namespace Microsoft.DotNet.XHarness.iOS.Shared.TestImporter.Templates.Managed {
public static class RegisterTypeGenerator {

static readonly string UsingReplacement = "%USING%";
static readonly string KeysReplacement = "%KEY VALUES%";
static readonly string IsxUnitReplacement = "%IS XUNIT%";

public static async Task<string> GenerateCodeAsync ((string FailureMessage, Dictionary<string, Type> Types) typeRegistration, bool isXunit,
public static async Task<string> GenerateCodeAsync ((string FailureMessage, Dictionary<string, TypeDefinition> Types) typeRegistration, bool isXunit,
Stream template)
{
var importStringBuilder = new StringBuilder ();
Expand Down

0 comments on commit 5b87778

Please sign in to comment.