Skip to content

Commit

Permalink
[tests] Improve perf in cecil-tests by only loading assemblies once. (x…
Browse files Browse the repository at this point in the history
…amarin#16997)

Improve perf in cecil-tests by caching loaded assemblies, and thus only
loading them once. The gain isn't all that much - it saves about 3s of ~2m on
my machine, so ~1.5% faster - but it'll be more and more important as we write
more tests. Also the code becomes slightly simpler too.
  • Loading branch information
rolfbjarne authored Dec 13, 2022
1 parent fd511c7 commit 081505b
Show file tree
Hide file tree
Showing 10 changed files with 152 additions and 126 deletions.
69 changes: 24 additions & 45 deletions tests/cecil-tests/ApiCapitalizationTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,26 +20,6 @@
namespace Cecil.Tests {
[TestFixture]
public class ApiCapitalizationTest {

Dictionary<string, AssemblyDefinition> assemblyCache = new Dictionary<string, AssemblyDefinition> ();


[OneTimeSetUp]
public void SetUp ()
{
foreach (string assemblyPath in Helper.NetPlatformAssemblies) {

var assembly = Helper.GetAssembly (assemblyPath);
assemblyCache.Add (assemblyPath, assembly);
}
}

[OneTimeTearDown]
public void TearDown ()
{
assemblyCache.Clear ();
}

bool IsMemberObsolete (ICustomAttributeProvider member)
{
if (member is null || !member.HasCustomAttributes)
Expand All @@ -51,7 +31,7 @@ bool IsMemberObsolete (ICustomAttributeProvider member)
m.AttributeType.Name == "ObsoletedOSPlatformAttribute");
}

bool IsUnique (string assemblyPath, MethodDefinition m)
bool IsUnique (MethodDefinition m)
{

return m is not null && (m.IsRemoveOn || m.IsAddOn || m.IsConstructor || m.IsSpecialName || IsMemberObsolete (m) || m.IsFamilyOrAssembly || m.IsPInvokeImpl);
Expand Down Expand Up @@ -131,73 +111,72 @@ public bool IsSkip (string type, string memberName, Dictionary<string, HashSet<s
return Char.IsUpper (memberName [0]);
}

[TestCaseSource (typeof (Helper), nameof (Helper.NetPlatformAssemblies))]
[TestCaseSource (typeof (Helper), nameof (Helper.NetPlatformAssemblyDefinitions))]
[Test]
public void PropertiesCapitalizationTest (string assemblyPath)
public void PropertiesCapitalizationTest (AssemblyInfo info)
{
var assembly = info.Assembly;
Func<TypeDefinition, IEnumerable<string>> selectLambda = (type) => {
var typeName = type.Name;
return type.Properties
.Where (p => p.GetMethod?.IsPublic == true || p.SetMethod?.IsPublic == true)
.Where (p => !IsSkip (type.Name, p.Name, allowedProperties) && !IsMemberObsolete (p) && !p.IsSpecialName)
.Select (p => p.Name);
};
CapitalizationTest (assemblyPath, selectLambda);
CapitalizationTest (assembly, selectLambda);
}

[TestCaseSource (typeof (Helper), nameof (Helper.NetPlatformAssemblies))]
[TestCaseSource (typeof (Helper), nameof (Helper.NetPlatformAssemblyDefinitions))]
[Test]
public void MethodsCapitalizationTest (string assemblyPath)
public void MethodsCapitalizationTest (AssemblyInfo info)
{
var assembly = info.Assembly;
Func<TypeDefinition, IEnumerable<string>> selectLambda = (type) => {
return from m in type.Methods
where m.IsPublic && !IsSkip (type.Name, m.Name, allowedMethods) && !IsUnique (assemblyPath, m)
where m.IsPublic && !IsSkip (type.Name, m.Name, allowedMethods) && !IsUnique (m)
select m.Name;
};
CapitalizationTest (assemblyPath, selectLambda);
CapitalizationTest (assembly, selectLambda);
}

[TestCaseSource (typeof (Helper), nameof (Helper.NetPlatformAssemblies))]
[TestCaseSource (typeof (Helper), nameof (Helper.NetPlatformAssemblyDefinitions))]
[Test]
public void EventsCapitalizationTest (string assemblyPath)
public void EventsCapitalizationTest (AssemblyInfo info)
{
var assembly = info.Assembly;
Func<TypeDefinition, IEnumerable<string>> selectLambda = (type) => {
return from e in type.Events
where !(Char.IsUpper (e.Name [0]))
select e.Name;
};
CapitalizationTest (assemblyPath, selectLambda);
CapitalizationTest (assembly, selectLambda);
}

[TestCaseSource (typeof (Helper), nameof (Helper.NetPlatformAssemblies))]
[TestCaseSource (typeof (Helper), nameof (Helper.NetPlatformAssemblyDefinitions))]
[Test]
public void FieldsCapitalizationTest (string assemblyPath)
public void FieldsCapitalizationTest (AssemblyInfo info)
{
var assembly = info.Assembly;
Func<TypeDefinition, IEnumerable<string>> selectLambda = (type) => {
return from f in type.Fields
where f.IsPublic && f.IsFamilyOrAssembly && !IsSkip (type.Name, f.Name, allowedFields)
select f.Name;
};
CapitalizationTest (assemblyPath, selectLambda);
CapitalizationTest (assembly, selectLambda);
}

public void CapitalizationTest (string assemblyPath, Func<TypeDefinition, IEnumerable<string>> selectLambda)
public void CapitalizationTest (AssemblyDefinition assembly, Func<TypeDefinition, IEnumerable<string>> selectLambda)
{

if (assemblyCache.TryGetValue (assemblyPath, out var cache)) {
var typeDict = new Dictionary<string, string> ();

var publicTypes = cache.MainModule.Types.Where ((t) => t.IsPublic && !IsMemberObsolete (t));
var typeDict = new Dictionary<string, string> ();

foreach (var type in publicTypes) {
TypeCheck (type, selectLambda, typeDict);
}
var publicTypes = assembly.MainModule.Types.Where ((t) => t.IsPublic && !IsMemberObsolete (t));

Assert.AreEqual (0, typeDict.Count (), $"Capitalization Issues Found: {string.Join (Environment.NewLine, typeDict)}");
} else {
Assert.Ignore ($"{assemblyPath} could not be found (might be disabled in build)");
foreach (var type in publicTypes) {
TypeCheck (type, selectLambda, typeDict);
}

Assert.AreEqual (0, typeDict.Count (), $"Capitalization Issues Found: {string.Join (Environment.NewLine, typeDict)}");
}

public void TypeCheck (TypeDefinition type, Func<TypeDefinition, IEnumerable<string>> selectLambda, Dictionary<string, string> typeDict)
Expand Down
40 changes: 15 additions & 25 deletions tests/cecil-tests/AttributeTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,10 @@ public class AttributeTest {
//
// This test should find Extension, note that it has an ios attribute,
// and insist that some maccatalyst must also be set.
[TestCaseSource (typeof (Helper), nameof (Helper.NetPlatformAssemblies))]
public void ChildElementsListAvailabilityForAllPlatformsOnParent (string assemblyPath)
[TestCaseSource (typeof (Helper), nameof (Helper.NetPlatformAssemblyDefinitions))]
public void ChildElementsListAvailabilityForAllPlatformsOnParent (AssemblyInfo info)
{
var assembly = Helper.GetAssembly (assemblyPath);
var assembly = info.Assembly;

HashSet<string> found = new HashSet<string> ();
foreach (var prop in Helper.FilterProperties (assembly, a => HasAnyAvailabilityAttribute (a))) {
Expand Down Expand Up @@ -82,15 +82,10 @@ bool IgnoreAllPlatformsOnParent (string fullName)
// Example #2:
// [Watch (5,0), NoTV, NoMac, iOS (12,0), NoTV]
// interface Type { }
[TestCaseSource (typeof (Helper), nameof (Helper.NetPlatformAssemblies))]
public void DoubleAttributedElements (string assemblyPath)
[TestCaseSource (typeof (Helper), nameof (Helper.NetPlatformAssemblyDefinitions))]
public void DoubleAttributedElements (AssemblyInfo info)
{
var assembly = Helper.GetAssembly (assemblyPath);
if (assembly is null) {
Assert.Ignore ("{assemblyPath} could not be found (might be disabled in build)");
return;
}

var assembly = info.Assembly;
var doubleAttributed = new List<string> ();
foreach (var type in Helper.FilterTypes (assembly, a => HasAnyAvailabilityAttribute (a))) {
var platformCount = new Dictionary<string, int> ();
Expand Down Expand Up @@ -177,14 +172,9 @@ public void FindSupportedOnElementsThatDoNotExistInThatAssembly ()
var harvestedInfo = new Dictionary<string, Dictionary<string, PlatformClaimInfo>> ();

// Load each platform assembly
foreach (string assemblyPath in Helper.NetPlatformAssemblies) {
var assembly = Helper.GetAssembly (assemblyPath);
if (assembly is null) {
Assert.Ignore ("{assemblyPath} could not be found (might be disabled in build)");
return;
}

string currentPlatform = AssemblyToAttributeName (assemblyPath);
foreach (var info in Helper.NetPlatformAssemblyDefinitions) {
var assembly = info.Assembly;
string currentPlatform = AssemblyToAttributeName (assembly);

// Walk every class/struct/enum/property/method/enum value/pinvoke/event
foreach (var module in assembly.Modules) {
Expand Down Expand Up @@ -513,12 +503,12 @@ IEnumerable<IMemberDefinition> GetAllTypeMembers (TypeDefinition type)
// }
//
// When run against mac, this fails as Extension does not include a mac supported of any kind attribute
[TestCaseSource (typeof (Helper), nameof (Helper.NetPlatformAssemblies))]
public void AllAttributedItemsMustIncludeCurrentPlatform (string assemblyPath)
[TestCaseSource (typeof (Helper), nameof (Helper.NetPlatformAssemblyDefinitions))]
public void AllAttributedItemsMustIncludeCurrentPlatform (AssemblyInfo info)
{
var assembly = Helper.GetAssembly (assemblyPath);
var assembly = info.Assembly;

string platformName = AssemblyToAttributeName (assemblyPath);
string platformName = AssemblyToAttributeName (assembly);

HashSet<string> found = new HashSet<string> ();
foreach (var type in Helper.FilterTypes (assembly, a => HasAnyAvailabilityAttribute (a))) {
Expand Down Expand Up @@ -567,9 +557,9 @@ bool IgnoreCurrentPlatform (string fullName)
}
}

string AssemblyToAttributeName (string assemblyPath)
string AssemblyToAttributeName (AssemblyDefinition assembly)
{
var baseName = Path.GetFileName (assemblyPath);
var baseName = assembly.Name.Name + ".dll";
if (Configuration.GetBaseLibraryName (TargetFramework.DotNet_iOS.Platform, true) == baseName)
return "ios";
if (Configuration.GetBaseLibraryName (TargetFramework.DotNet_tvOS.Platform, true) == baseName)
Expand Down
8 changes: 4 additions & 4 deletions tests/cecil-tests/BlittablePInvokes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,18 +42,18 @@ public BlitAndReason (bool isBlittable, string reason)
}

[Ignore ("work in progress - there are 100 failures, mostly due to strings")]
[TestCaseSource (typeof (Helper), nameof (Helper.NetPlatformImplementationAssemblies))]
public void CheckForNonBlittablePInvokes (string assemblyPath)
[TestCaseSource (typeof (Helper), nameof (Helper.NetPlatformImplementationAssemblyDefinitions))]
public void CheckForNonBlittablePInvokes (AssemblyInfo info)
{
var assembly = Helper.GetAssembly (assemblyPath, readSymbols: true);
var assembly = info.Assembly;
var pinvokes = AllPInvokes (assembly).Where (IsPInvokeOK);
Assert.IsTrue (pinvokes.Count () > 0);

var blitCache = new Dictionary<string, BlitAndReason> ();
var results = pinvokes.Select (pi => IsMethodBlittable (assembly, pi, blitCache)).Where (r => !r.IsBlittable);
if (results.Count () > 0) {
var failString = new StringBuilder ();
failString.Append ($"There is an issue with {results.Count ()} pinvokes in {assembly.Name} ({assemblyPath}):\n");
failString.Append ($"There is an issue with {results.Count ()} pinvokes in {assembly.Name} ({info.Path}):\n");
foreach (var sb in results.Select (r => r.Result)) {
failString.Append (sb.ToString ());
}
Expand Down
8 changes: 4 additions & 4 deletions tests/cecil-tests/EnumTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,12 @@ namespace Cecil.Tests {
[TestFixture]
public class EnumTest {

[TestCaseSource (typeof (Helper), nameof (Helper.PlatformAssemblies))]
[TestCaseSource (typeof (Helper), nameof (Helper.NetPlatformAssemblies))]
[TestCaseSource (typeof (Helper), nameof (Helper.PlatformAssemblyDefinitions))]
[TestCaseSource (typeof (Helper), nameof (Helper.NetPlatformAssemblyDefinitions))]
// https://github.com/xamarin/xamarin-macios/issues/9724
public void NoAvailabilityOnError (string assemblyPath)
public void NoAvailabilityOnError (AssemblyInfo info)
{
var assembly = Helper.GetAssembly (assemblyPath);
var assembly = info.Assembly;
HashSet<string> found = new HashSet<string> ();
foreach (var type in assembly.MainModule.Types)
NoAvailabilityOnError (type, found);
Expand Down
12 changes: 6 additions & 6 deletions tests/cecil-tests/GenericPInvokes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ struct MethodAndResult {

[TestFixture]
public class GenericPInvokesTest {
[TestCaseSource (typeof (Helper), nameof (Helper.NetPlatformImplementationAssemblies))]
public void CheckSetupBlockUnsafeUsage (string assemblyPath)
[TestCaseSource (typeof (Helper), nameof (Helper.NetPlatformImplementationAssemblyDefinitions))]
public void CheckSetupBlockUnsafeUsage (AssemblyInfo info)
{
// this scans the specified assmebly for all methods
// that call SetupBlockUnsafe and then scans the method
Expand Down Expand Up @@ -56,7 +56,7 @@ public void CheckSetupBlockUnsafeUsage (string assemblyPath)
// So there's a little juggling to make sure we don't
// look past the array of parameters.

var assembly = Helper.GetAssembly (assemblyPath, readSymbols: true);
var assembly = info.Assembly;
var callsToSetupBlock = AllSetupBlocks (assembly);
Assert.IsTrue (callsToSetupBlock.Count () > 0);
var results = callsToSetupBlock.Select (GenericCheckDelegateArgument);
Expand All @@ -80,10 +80,10 @@ static bool IsSetupBlockUnsafeOK (MethodDefinition method)
}


[TestCaseSource (typeof (Helper), nameof (Helper.NetPlatformImplementationAssemblies))]
public void CheckAllPInvokes (string assemblyPath)
[TestCaseSource (typeof (Helper), nameof (Helper.NetPlatformImplementationAssemblyDefinitions))]
public void CheckAllPInvokes (AssemblyInfo info)
{
var assembly = Helper.GetAssembly (assemblyPath, readSymbols: true);
var assembly = info.Assembly;
var pinvokes = AllPInvokes (assembly).Where (IsPInvokeOK);
Assert.IsTrue (pinvokes.Count () > 0);

Expand Down
Loading

0 comments on commit 081505b

Please sign in to comment.