diff --git a/src/coreclr/tools/aot/ILCompiler.Trimming.Tests/TestCasesRunner/AssemblyChecker.cs b/src/coreclr/tools/aot/ILCompiler.Trimming.Tests/TestCasesRunner/AssemblyChecker.cs index 41540f8b6da593..8dd1e4cb136732 100644 --- a/src/coreclr/tools/aot/ILCompiler.Trimming.Tests/TestCasesRunner/AssemblyChecker.cs +++ b/src/coreclr/tools/aot/ILCompiler.Trimming.Tests/TestCasesRunner/AssemblyChecker.cs @@ -80,6 +80,16 @@ public AssemblyChecker ( } public void Verify () + { + var errors = VerifyImpl().ToList(); + if (errors.Any()) + { + Assert.Fail(string.Join(Environment.NewLine, errors)); + } + + } + + IEnumerable VerifyImpl() { // There are no type forwarders left after compilation in Native AOT // VerifyExportedTypes (originalAssembly, linkedAssembly); @@ -114,7 +124,8 @@ public void Verify () token, out LinkedEntity? linkedMember); - VerifyTypeDefinition (td, linkedMember); + foreach(var err in VerifyTypeDefinition (td, linkedMember)) + yield return err; linkedMembers.Remove (token); continue; @@ -123,22 +134,22 @@ public void Verify () throw new NotImplementedException ($"Don't know how to check member of type {originalMember.GetType ()}"); } - // Verify anything not in the main assembly - VerifyLinkingOfOtherAssemblies(this.originalAssembly); + // Verify anything not in the main assembly + foreach(var err in VerifyLinkingOfOtherAssemblies(this.originalAssembly)) + yield return err; - // Filter out all members which are not from the main assembly - // The Kept attributes are "optional" for non-main assemblies - string mainModuleName = originalAssembly.Name.Name; + // Filter out all members which are not from the main assembly + // The Kept attributes are "optional" for non-main assemblies + string mainModuleName = originalAssembly.Name.Name; List externalMembers = linkedMembers.Where (m => GetModuleName (m.Value.Entity) != mainModuleName).Select (m => m.Key).ToList (); foreach (var externalMember in externalMembers) { linkedMembers.Remove (externalMember); } if (linkedMembers.Count != 0) - Assert.Fail( - "Linked output includes unexpected member:\n " + - string.Join ("\n ", linkedMembers.Values.Select (e => e.Entity.GetDisplayName ()))); - } + yield return "Linked output includes unexpected member:\n " + + string.Join ("\n ", linkedMembers.Values.Select (e => e.Entity.GetDisplayName ())); + } private void PopulateLinkedMembers () { @@ -278,32 +289,32 @@ static bool ShouldIncludeType (TypeDesc type) static bool ShouldIncludeMethod (MethodDesc method) => ShouldIncludeType (method.OwningType) && ShouldIncludeEntityByDisplayName (method); } - private static MetadataType? GetOwningType (TypeSystemEntity? entity) - { - return entity switch - { - MetadataType type => type.ContainingType as MetadataType, - MethodDesc method => method.OwningType as MetadataType, - PropertyPseudoDesc prop => prop.OwningType, - EventPseudoDesc e => e.OwningType, - _ => null - }; - } + private static MetadataType? GetOwningType (TypeSystemEntity? entity) + { + return entity switch + { + MetadataType type => type.ContainingType as MetadataType, + MethodDesc method => method.OwningType as MetadataType, + PropertyPseudoDesc prop => prop.OwningType, + EventPseudoDesc e => e.OwningType, + _ => null + }; + } private static string? GetModuleName (TypeSystemEntity entity) { return entity switch { MetadataType type => type.Module.ToString (), - _ => GetOwningType(entity)?.Module.ToString() + _ => GetOwningType(entity)?.Module.ToString() }; } - protected virtual void VerifyModule (ModuleDefinition original, ModuleDefinition? linked) + protected virtual IEnumerable VerifyModule (ModuleDefinition original, ModuleDefinition? linked) { // We never link away a module today so let's make sure the linked one isn't null if (linked == null) { - Assert.Fail($"Linked assembly `{original.Assembly.Name.Name}` is missing module `{original.Name}`"); - return; + yield return $"Linked assembly `{original.Assembly.Name.Name}` is missing module `{original.Name}`"; + yield break; } var expected = original.Assembly.MainModule.AllDefinedTypes () @@ -314,16 +325,18 @@ protected virtual void VerifyModule (ModuleDefinition original, ModuleDefinition .Select (name => name.Name) .ToArray (); - Assert.Equal (expected, actual); + if (!expected.Equals(actual)) + yield return "Module references do not match"; - VerifyCustomAttributes (original, linked); + foreach(var err in VerifyCustomAttributes (original, linked)) + yield return err; } - void VerifyTypeDefinition (TypeDefinition original, LinkedEntity? linkedEntity) + IEnumerable VerifyTypeDefinition (TypeDefinition original, LinkedEntity? linkedEntity) { TypeDesc? linked = linkedEntity?.Entity as TypeDesc; if (linked != null && NameUtils.GetActualOriginDisplayName (linked) is string linkedDisplayName && verifiedGeneratedTypes.Contains (linkedDisplayName)) - return; + yield break; EcmaModule? linkedModule = (linked as MetadataType)?.Module as EcmaModule; @@ -340,7 +353,7 @@ void VerifyTypeDefinition (TypeDefinition original, LinkedEntity? linkedEntity) if (!expectedKept) { if (linked == null) - return; + yield break; // Compiler generated members can't be annotated with `Kept` attributes directly // For some of them we have special attributes (backing fields for example), but it's impractical to define @@ -351,13 +364,14 @@ void VerifyTypeDefinition (TypeDefinition original, LinkedEntity? linkedEntity) // we do want to validate. There's no specific use case right now, but I can easily imagine one // for more detailed testing of for example custom attributes on local functions, or similar. if (!IsCompilerGeneratedMember (original)) - Assert.Fail($"Type `{original}' should have been removed"); + yield return $"Type `{original}' should have been removed"; } bool prev = checkNames; checkNames |= original.HasAttribute (nameof (VerifyMetadataNamesAttribute)); - VerifyTypeDefinitionKept (original, linked); + foreach(var err in VerifyTypeDefinitionKept (original, linked)) + yield return err; checkNames = prev; @@ -370,7 +384,7 @@ void VerifyTypeDefinition (TypeDefinition original, LinkedEntity? linkedEntity) var linkedMemberName = linkedMembers.Keys.FirstOrDefault (l => l.Contains (newName)); if (linkedMemberName == null) - Assert.Fail($"Newly created member '{newName}' was not found"); + yield return $"Newly created member '{newName}' was not found"; linkedMembers.Remove (linkedMemberName); } @@ -378,33 +392,43 @@ void VerifyTypeDefinition (TypeDefinition original, LinkedEntity? linkedEntity) } } - protected virtual void VerifyTypeDefinitionKept (TypeDefinition original, TypeDesc? linked) + protected virtual IEnumerable VerifyTypeDefinitionKept (TypeDefinition original, TypeDesc? linked) { // NativeAOT will not keep delegate backing field type information, it's compiled down to a set of static fields // this infra currently doesn't track fields in any way. // Same goes for private implementation detail type. if (IsDelegateBackingFieldsType (original) || IsPrivateImplementationDetailsType(original)) - return; + yield break; if (linked == null) { - Assert.Fail($"Type `{original}' should have been kept"); - return; + yield return $"Type `{original}' should have been kept"; + yield break; } #if false // Skip verification of type metadata for compiler generated types (we don't currently need it yet) if (!IsCompilerGeneratedMember (original)) { - VerifyKeptByAttributes (original, linked); + foreach(var err in VerifyKeptByAttributes (original, linked)) + yield return err; if (!original.IsInterface) - VerifyBaseType (original, linked); + { + foreach(var err in VerifyBaseType (original, linked)) + yield return err; + } - VerifyInterfaces (original, linked); - VerifyPseudoAttributes (original, linked); - VerifyGenericParameters (original, linked); - VerifyCustomAttributes (original, linked); - VerifySecurityAttributes (original, linked); + foreach(var err in VerifyInterfaces (original, linked)) + yield return err; + foreach(var err in VerifyPseudoAttributes (original, linked)) + yield return err; + foreach(var err in VerifyGenericParameters (original, linked)) + yield return err; + foreach(var err in VerifyCustomAttributes (original, linked)) + yield return err; + foreach(var err in VerifySecurityAttributes (original, linked)) + yield return err; - VerifyFixedBufferFields (original, linked); + foreach(var err in VerifyFixedBufferFields (original, linked)) + yield return err; } #endif @@ -414,7 +438,8 @@ protected virtual void VerifyTypeDefinitionKept (TypeDefinition original, TypeDe token, out LinkedEntity? linkedMember); - VerifyTypeDefinition (td, linkedMember); + foreach(var err in VerifyTypeDefinition (td, linkedMember)) + yield return err; linkedMembers.Remove (token); } @@ -425,7 +450,8 @@ protected virtual void VerifyTypeDefinitionKept (TypeDefinition original, TypeDe linkedMembers.TryGetValue ( token, out LinkedEntity? linkedMember); - VerifyProperty (p, linkedMember, linked); + foreach(var err in VerifyProperty (p, linkedMember, linked)) + yield return err; linkedMembers.Remove (token); } // Need to check events before fields so that the KeptBackingFieldAttribute is handled correctly @@ -435,7 +461,8 @@ protected virtual void VerifyTypeDefinitionKept (TypeDefinition original, TypeDe linkedMembers.TryGetValue ( token, out LinkedEntity? linkedMember); - VerifyEvent (e, linkedMember, linked); + foreach(var err in VerifyEvent (e, linkedMember, linked)) + yield return err; linkedMembers.Remove (token); } @@ -460,12 +487,13 @@ protected virtual void VerifyTypeDefinitionKept (TypeDefinition original, TypeDe token, out LinkedEntity? linkedMember); - VerifyMethod (m, linkedMember); + foreach(var err in VerifyMethod (m, linkedMember)) + yield return err; linkedMembers.Remove (token); } } - private void VerifyBaseType (TypeDefinition src, TypeDefinition linked) + private IEnumerable VerifyBaseType (TypeDefinition src, TypeDefinition linked) { string expectedBaseName; var expectedBaseGenericAttr = src.CustomAttributes.FirstOrDefault (w => w.AttributeType.Name == nameof (KeptBaseTypeAttribute) && w.ConstructorArguments.Count > 1); @@ -477,26 +505,28 @@ private void VerifyBaseType (TypeDefinition src, TypeDefinition linked) } if (expectedBaseName != linked.BaseType?.FullName) { - Assert.Fail($"Incorrect base type on : {linked.Name}. Expected {expectedBaseName}, actual {linked.BaseType?.FullName}"); + yield return $"Incorrect base type on : {linked.Name}. Expected {expectedBaseName}, actual {linked.BaseType?.FullName}"; } } - private void VerifyInterfaces (TypeDefinition src, TypeDefinition linked) + private IEnumerable VerifyInterfaces (TypeDefinition src, TypeDefinition linked) { var expectedInterfaces = new HashSet (src.CustomAttributes .Where (w => w.AttributeType.Name == nameof (KeptInterfaceAttribute)) .Select (FormatBaseOrInterfaceAttributeValue)); if (expectedInterfaces.Count == 0) { - Assert.False (linked.HasInterfaces, $"Type `{src}' has unexpected interfaces"); + if (linked.HasInterfaces) + yield return $"Type `{src}' has unexpected interfaces"; } else { foreach (var iface in linked.Interfaces) { if (!expectedInterfaces.Remove (iface.InterfaceType.FullName)) { - Assert.True (expectedInterfaces.Remove (iface.InterfaceType.Resolve ().FullName), $"Type `{src}' interface `{iface.InterfaceType.Resolve ().FullName}' should have been removed"); + if (true != expectedInterfaces.Remove (iface.InterfaceType.Resolve ().FullName)) + yield return $"Type `{src}' interface `{iface.InterfaceType.Resolve ().FullName}' should have been removed"; } } if (expectedInterfaces.Count != 0) - Assert.Fail($"Expected interfaces were not found on {src}"); + yield return $"Expected interfaces were not found on {src}"; } } @@ -523,26 +553,27 @@ private static string FormatBaseOrInterfaceAttributeValue (CustomAttribute attr) return builder.ToString (); } - private void VerifyField (FieldDefinition src, FieldDesc? linked) + private IEnumerable VerifyField (FieldDefinition src, FieldDesc? linked) { bool compilerGenerated = IsCompilerGeneratedMember (src); bool expectedKept = ShouldBeKept (src) | compilerGenerated; if (!expectedKept) { if (linked != null) - Assert.Fail($"Field `{src}' should have been removed"); + yield return $"Field `{src}' should have been removed"; - return; + yield break; } - VerifyFieldKept (src, linked, compilerGenerated); + foreach(var err in VerifyFieldKept (src, linked, compilerGenerated)) + yield return err; } - private static void VerifyFieldKept (FieldDefinition src, FieldDesc? linked, bool compilerGenerated) + private static IEnumerable VerifyFieldKept (FieldDefinition src, FieldDesc? linked, bool compilerGenerated) { if (linked == null) { - Assert.Fail($"Field `{src}' should have been kept"); - return; + yield return $"Field `{src}' should have been kept"; + yield break; } @@ -550,18 +581,20 @@ private static void VerifyFieldKept (FieldDefinition src, FieldDesc? linked, boo throw new NotImplementedException ("Constant value for a field is not yet supported by the test infra."); #if false if (!Equals (src.Constant, linked.Constant)) { - Assert.Fail($"Field '{src}' value doesn's match. Expected {src.Constant}, actual {linked.Constant}"); + yield return $"Field '{src}' value doesn's match. Expected {src.Constant}, actual {linked.Constant}"; } #endif #if false - VerifyPseudoAttributes (src, linked); + foreach(var err in VerifyPseudoAttributes (src, linked)) + yield return err; if (!compilerGenerated) - VerifyCustomAttributes (src, linked); + foreach(var err in VerifyCustomAttributes (src, linked)) + yield return err; #endif } - private void VerifyProperty (PropertyDefinition src, LinkedEntity? linkedEntity, TypeDesc linkedType) + private IEnumerable VerifyProperty (PropertyDefinition src, LinkedEntity? linkedEntity, TypeDesc linkedType) { PropertyPseudoDesc? linked = linkedEntity?.Entity as PropertyPseudoDesc; VerifyMemberBackingField (src, linkedType); @@ -571,55 +604,61 @@ private void VerifyProperty (PropertyDefinition src, LinkedEntity? linkedEntity, if (!expectedKept) { if (linked is not null) - Assert.Fail($"Property `{src}' should have been removed"); + yield return $"Property `{src}' should have been removed"; - return; + yield break; } if (linked is null) { - Assert.Fail($"Property `{src}' should have been kept"); - return; + yield return $"Property `{src}' should have been kept"; + yield break; } if (src.HasConstant) throw new NotSupportedException ("Constant value for a property is not yet supported by the test infra."); #if false if (src.Constant != linked.Constant) { - Assert.Fail($"Property '{src}' value doesn's match. Expected {src.Constant}, actual {linked.Constant}"); + yield return $"Property '{src}' value doesn's match. Expected {src.Constant}, actual {linked.Constant}"; } #endif #if false - VerifyPseudoAttributes (src, linked); + foreach(var err in VerifyPseudoAttributes (src, linked)) + yield return err; if (!compilerGenerated) - VerifyCustomAttributes (src, linked); + { + foreach(var err in VerifyCustomAttributes (src, linked)) + yield return err; + } #endif } - private void VerifyEvent (EventDefinition src, LinkedEntity? linkedEntity, TypeDesc linkedType) + private IEnumerable VerifyEvent (EventDefinition src, LinkedEntity? linkedEntity, TypeDesc linkedType) { EventPseudoDesc? linked = linkedEntity?.Entity as EventPseudoDesc; - VerifyMemberBackingField (src, linkedType); + foreach(var err in VerifyMemberBackingField (src, linkedType)) + yield return err; bool compilerGenerated = IsCompilerGeneratedMember (src); bool expectedKept = ShouldBeKept (src) | compilerGenerated; if (!expectedKept) { if (linked is not null) - Assert.Fail($"Event `{src}' should have been removed"); + yield return $"Event `{src}' should have been removed"; - return; + yield break; } if (linked is null) { - Assert.Fail($"Event `{src}' should have been kept"); - return; + yield return $"Event `{src}' should have been kept"; + yield break; } if (src.CustomAttributes.Any (attr => attr.AttributeType.Name == nameof (KeptEventAddMethodAttribute))) { // TODO: This is wrong - we can't validate that the method is present by looking at linked (as that is not actually linked) // we need to look into linkedMembers to see if the method was actually preserved by the compiler (and has an entry point) - VerifyMethodInternal (src.AddMethod, new LinkedMethodEntity(linked.AddMethod, false), true, compilerGenerated); + foreach(var err in VerifyMethodInternal (src.AddMethod, new LinkedMethodEntity(linked.AddMethod, false), true, compilerGenerated)) + yield return err; verifiedEventMethods.Add (src.AddMethod.FullName); linkedMembers.Remove (new AssemblyQualifiedToken (src.AddMethod)); } @@ -627,47 +666,54 @@ private void VerifyEvent (EventDefinition src, LinkedEntity? linkedEntity, TypeD if (src.CustomAttributes.Any (attr => attr.AttributeType.Name == nameof (KeptEventRemoveMethodAttribute))) { // TODO: This is wrong - we can't validate that the method is present by looking at linked (as that is not actually linked) // we need to look into linkedMembers to see if the method was actually preserved by the compiler (and has an entry point) - VerifyMethodInternal (src.RemoveMethod, new LinkedMethodEntity(linked.RemoveMethod, false), true, compilerGenerated); + foreach(var err in VerifyMethodInternal (src.RemoveMethod, new LinkedMethodEntity(linked.RemoveMethod, false), true, compilerGenerated)) + yield return err; verifiedEventMethods.Add (src.RemoveMethod.FullName); linkedMembers.Remove (new AssemblyQualifiedToken (src.RemoveMethod)); } #if false - VerifyPseudoAttributes (src, linked); + foreach(var err in VerifyPseudoAttributes (src, linked)) + yield return err; if (!compilerGenerated) - VerifyCustomAttributes (src, linked); + { + foreach(var err in VerifyCustomAttributes (src, linned)) + yield return err; + } #endif } - private void VerifyMethod (MethodDefinition src, LinkedEntity? linkedEntity) + private IEnumerable VerifyMethod (MethodDefinition src, LinkedEntity? linkedEntity) { LinkedMethodEntity? linked = linkedEntity as LinkedMethodEntity; bool compilerGenerated = IsCompilerGeneratedMember (src); bool expectedKept = ShouldMethodBeKept (src); - VerifyMethodInternal (src, linked, expectedKept, compilerGenerated); + foreach(var err in VerifyMethodInternal (src, linked, expectedKept, compilerGenerated)) + yield return err; } - private void VerifyMethodInternal (MethodDefinition src, LinkedMethodEntity? linked, bool expectedKept, bool compilerGenerated) + private IEnumerable VerifyMethodInternal (MethodDefinition src, LinkedMethodEntity? linked, bool expectedKept, bool compilerGenerated) { if (!expectedKept) { if (linked == null) - return; + yield break; // Similar to comment on types, compiler-generated methods can't be annotated with Kept attribute directly // so we're not going to validate kept/remove on them. Note that we're still going to go validate "into" them // to check for other properties (like parameter name presence/removal for example) if (!compilerGenerated) - Assert.Fail($"Method `{NameUtils.GetExpectedOriginDisplayName (src)}' should have been removed"); + yield return $"Method `{NameUtils.GetExpectedOriginDisplayName (src)}' should have been removed"; } - VerifyMethodKept (src, linked, compilerGenerated); + foreach(var err in VerifyMethodKept (src, linked, compilerGenerated)) + yield return err; } - private void VerifyMemberBackingField (IMemberDefinition src, TypeDesc? linkedType) + private IEnumerable VerifyMemberBackingField (IMemberDefinition src, TypeDesc? linkedType) { var keptBackingFieldAttribute = src.CustomAttributes.FirstOrDefault (attr => attr.AttributeType.Name == nameof (KeptBackingFieldAttribute)); if (keptBackingFieldAttribute == null) - return; + yield break; var backingFieldName = src.MetadataToken.TokenType == TokenType.Property ? $"<{src.Name}>k__BackingField" : src.Name; @@ -683,60 +729,75 @@ private void VerifyMemberBackingField (IMemberDefinition src, TypeDesc? linkedTy } if (srcField == null) { - Assert.Fail($"{src.MetadataToken.TokenType} `{src}', could not locate the expected backing field {backingFieldName}"); - return; + yield return $"{src.MetadataToken.TokenType} `{src}', could not locate the expected backing field {backingFieldName}"; + yield break; } - VerifyFieldKept (srcField, linkedType?.GetFields ()?.FirstOrDefault (l => srcField.Name == l.Name), compilerGenerated: true); + foreach(var err in VerifyFieldKept (srcField, linkedType?.GetFields ()?.FirstOrDefault (l => srcField.Name == l.Name), compilerGenerated: true)) + yield return err; verifiedGeneratedFields.Add (srcField.FullName); linkedMembers.Remove (new AssemblyQualifiedToken (srcField)); } - void VerifyMethodKept (MethodDefinition src, LinkedMethodEntity? linked, bool compilerGenerated) + IEnumerable VerifyMethodKept (MethodDefinition src, LinkedMethodEntity? linked, bool compilerGenerated) { if (linked == null) { - Assert.Fail($"Method `{NameUtils.GetExpectedOriginDisplayName (src)}' should have been kept"); - return; + yield return $"Method `{NameUtils.GetExpectedOriginDisplayName (src)}' should have been kept"; + yield break; } #if false - VerifyPseudoAttributes (src, linked); - VerifyGenericParameters (src, linked); + foreach(var err in VerifyPseudoAttributes (src, linked)) + yield return err; + foreach(var err in VerifyGenericParameters (src, linked)) + yield return err; if (!compilerGenerated) { - VerifyCustomAttributes (src, linked); - VerifyCustomAttributes (src.MethodReturnType, linked.MethodReturnType); + foreach(var err in VerifyCustomAttributes (src, linked)) + yield return err; + foreach(var err in VerifyCustomAttributes (src.MethodReturnType, linked.MethodReturnType)) + yield return err; } #endif - VerifyParameters (src, linked); + foreach(var err in VerifyParameters (src, linked)) + yield return err; #if false - VerifySecurityAttributes (src, linked); - VerifyArrayInitializers (src, linked); + foreach(var err in VerifySecurityAttributes (src, linked)) + yield return err; + foreach(var err in VerifyArrayInitializers (src, linked)) + yield return err; // Method bodies are not very different in Native AOT - VerifyMethodBody (src, linked); - VerifyKeptByAttributes (src, linked); + foreach(var err in VerifyMethodBody (src, linked)) + yield return err; + foreach(var err in VerifyKeptByAttributes (src, linked)) + yield return err; #endif } - protected virtual void VerifyMethodBody (MethodDefinition src, MethodDefinition linked) + protected virtual IEnumerable VerifyMethodBody (MethodDefinition src, MethodDefinition linked) { if (!src.HasBody) - return; + yield break; - VerifyInstructions (src, linked); - VerifyLocals (src, linked); + foreach(var err in VerifyInstructions (src, linked)) + yield return err; + foreach(var err in VerifyLocals (src, linked)) + yield return err; } - protected static void VerifyInstructions (MethodDefinition src, MethodDefinition linked) + protected static IEnumerable VerifyInstructions (MethodDefinition src, MethodDefinition linked) { - VerifyBodyProperties ( + foreach (var err in VerifyBodyProperties ( src, linked, nameof (ExpectedInstructionSequenceAttribute), nameof (ExpectBodyModifiedAttribute), "instructions", m => FormatMethodBody (m.Body), - attr => GetStringArrayAttributeValue (attr)!.ToArray ()); + attr => GetStringArrayAttributeValue (attr)!.ToArray ())) + { + yield return err; + } } public static string[] FormatMethodBody (MethodBody body) @@ -848,19 +909,22 @@ private static string FormatInstruction (Instruction instr) } } - private static void VerifyLocals (MethodDefinition src, MethodDefinition linked) + private static IEnumerable VerifyLocals (MethodDefinition src, MethodDefinition linked) { - VerifyBodyProperties ( + foreach(var err in VerifyBodyProperties ( src, linked, nameof (ExpectedLocalsSequenceAttribute), nameof (ExpectLocalsModifiedAttribute), "locals", m => m.Body.Variables.Select (v => v.VariableType.ToString ()).ToArray (), - attr => GetStringOrTypeArrayAttributeValue (attr).ToArray ()); + attr => GetStringOrTypeArrayAttributeValue (attr).ToArray ())) + { + yield return err; + } } - public static void VerifyBodyProperties (MethodDefinition src, MethodDefinition linked, string sequenceAttributeName, string expectModifiedAttributeName, + public static IEnumerable VerifyBodyProperties (MethodDefinition src, MethodDefinition linked, string sequenceAttributeName, string expectModifiedAttributeName, string propertyDescription, Func valueCollector, Func getExpectFromSequenceAttribute) @@ -870,16 +934,25 @@ public static void VerifyBodyProperties (MethodDefinition src, MethodDefinition var srcValues = valueCollector (src); if (src.CustomAttributes.Any (attr => attr.AttributeType.Name == expectModifiedAttributeName)) { - linkedValues.Should ().BeEquivalentTo (srcValues, $"Expected method `{src} to have {propertyDescription} modified, however, the {propertyDescription} were the same as the original\n{FormattingUtils.FormatSequenceCompareFailureMessage (linkedValues, srcValues)}"); + if (linkedValues.SequenceEqual(srcValues)) + { + yield return $"Expected method `{src} to have {propertyDescription} modified, however, the {propertyDescription} were the same as the original\n{FormattingUtils.FormatSequenceCompareFailureMessage (linkedValues, srcValues)}"; + } } else if (expectedSequenceAttribute != null) { var expected = getExpectFromSequenceAttribute (expectedSequenceAttribute).ToArray (); - linkedValues.Should ().BeEquivalentTo (expected, $"Expected method `{src} to have it's {propertyDescription} modified, however, the sequence of {propertyDescription} does not match the expected value\n{FormattingUtils.FormatSequenceCompareFailureMessage2 (linkedValues, expected, srcValues)}"); + if (!linkedValues.SequenceEqual(expected)) + { + yield return $"Expected method `{src} to have it's {propertyDescription} modified, however, the sequence of {propertyDescription} does not match the expected value\n{FormattingUtils.FormatSequenceCompareFailureMessage2 (linkedValues, expected, srcValues)}"; + } } else { - linkedValues.Should ().BeEquivalentTo (srcValues, $"Expected method `{src} to have it's {propertyDescription} unchanged, however, the {propertyDescription} differ from the original\n{FormattingUtils.FormatSequenceCompareFailureMessage (linkedValues, srcValues)}"); + if (!linkedValues.SequenceEqual(srcValues)) + { + yield return $"Expected method `{src} to have it's {propertyDescription} unchanged, however, the {propertyDescription} differ from the original\n{FormattingUtils.FormatSequenceCompareFailureMessage (linkedValues, srcValues)}"; + } } } - private void VerifyReferences (AssemblyDefinition original, AssemblyDefinition linked) + private IEnumerable VerifyReferences (AssemblyDefinition original, AssemblyDefinition linked) { var expected = original.MainModule.AllDefinedTypes () .SelectMany (t => GetCustomAttributeCtorValues (t, nameof (KeptReferenceAttribute))) @@ -896,13 +969,14 @@ private void VerifyReferences (AssemblyDefinition original, AssemblyDefinition l Once 1 kept reference attribute is used, the test will need to define all of of it's expected references */ if (expected.Length == 0) - return; + yield break; var actual = linked.MainModule.AssemblyReferences .Select (name => name.Name) .ToArray (); - actual.Should ().BeEquivalentTo (expected); + if (!actual.SequenceEqual(expected)) + yield return $"Expected references do not match actual references:\n\tExpected: {string.Join(", ", expected)}\n\tActual: {string.Join(", ", actual)}"; } private string? ReduceAssemblyFileNameOrNameToNameOnly (string? fileNameOrAssemblyName) @@ -917,75 +991,97 @@ private void VerifyReferences (AssemblyDefinition original, AssemblyDefinition l return fileNameOrAssemblyName; } - private void VerifyResources (AssemblyDefinition original, AssemblyDefinition linked) + private IEnumerable VerifyResources (AssemblyDefinition original, AssemblyDefinition linked) { - var expectedResourceNames = original.MainModule.AllDefinedTypes () + List expectedResourceNames = original.MainModule.AllDefinedTypes () .SelectMany (t => GetCustomAttributeCtorValues (t, nameof (KeptResourceAttribute))) .ToList (); foreach (var resource in linked.MainModule.Resources) { if (!expectedResourceNames.Remove (resource.Name)) - Assert.Fail($"Resource '{resource.Name}' should be removed."); + yield return $"Resource '{resource.Name}' should be removed."; EmbeddedResource embeddedResource = (EmbeddedResource) resource; var expectedResource = (EmbeddedResource) original.MainModule.Resources.First (r => r.Name == resource.Name); - embeddedResource.GetResourceData ().Should ().BeEquivalentTo (expectedResource.GetResourceData (), $"Resource '{resource.Name}' data doesn't match."); + if (!embeddedResource.GetResourceData().SequenceEqual(expectedResource.GetResourceData())) + yield return $"Resource '{resource.Name}' data doesn't match."; } if (expectedResourceNames.Count > 0) { - Assert.Fail($"Resource '{expectedResourceNames.First ()}' should be kept."); + yield return $"Resource '{expectedResourceNames.First ()}' should be kept."; } } - private void VerifyExportedTypes (AssemblyDefinition original, AssemblyDefinition linked) + private IEnumerable VerifyExportedTypes (AssemblyDefinition original, AssemblyDefinition linked) { var expectedTypes = original.MainModule.AllDefinedTypes () .SelectMany (t => GetCustomAttributeCtorValues (t, nameof (KeptExportedTypeAttribute)).Select (l => l?.FullName ?? "")).ToArray (); - linked.MainModule.ExportedTypes.Select (l => l.FullName).Should ().BeEquivalentTo (expectedTypes); + if (!linked.MainModule.ExportedTypes.Select (l => l.FullName).SequenceEqual(expectedTypes)) + yield return $"Exported types do not match expected values"; } - protected virtual void VerifyPseudoAttributes (MethodDefinition src, MethodDefinition linked) + protected virtual IEnumerable VerifyPseudoAttributes (MethodDefinition src, MethodDefinition linked) { var expected = (MethodAttributes) GetExpectedPseudoAttributeValue (src, (uint) src.Attributes); - linked.Attributes.Should ().Be (expected, $"Method `{src}' pseudo attributes did not match expected"); + if(!linked.Attributes.Equals(expected)) + { + yield return $"Method `{src}' pseudo attributes did not match expected"; + } } - protected virtual void VerifyPseudoAttributes (TypeDefinition src, TypeDefinition linked) + protected virtual IEnumerable VerifyPseudoAttributes (TypeDefinition src, TypeDefinition linked) { var expected = (TypeAttributes) GetExpectedPseudoAttributeValue (src, (uint) src.Attributes); - linked.Attributes.Should ().Be (expected, $"Type `{src}' pseudo attributes did not match expected"); + + if(!linked.Attributes.Equals(expected)) + { + yield return $"Type `{src}' pseudo attributes did not match expected"; + } } - protected virtual void VerifyPseudoAttributes (FieldDefinition src, FieldDefinition linked) + protected virtual IEnumerable VerifyPseudoAttributes (FieldDefinition src, FieldDefinition linked) { var expected = (FieldAttributes) GetExpectedPseudoAttributeValue (src, (uint) src.Attributes); - linked.Attributes.Should ().Be (expected, $"Field `{src}' pseudo attributes did not match expected"); + if(!linked.Attributes.Equals(expected)) + { + yield return $"Field `{src}' pseudo attributes did not match expected"; + } } - protected virtual void VerifyPseudoAttributes (PropertyDefinition src, PropertyDefinition linked) + protected virtual IEnumerable VerifyPseudoAttributes (PropertyDefinition src, PropertyDefinition linked) { var expected = (PropertyAttributes) GetExpectedPseudoAttributeValue (src, (uint) src.Attributes); - linked.Attributes.Should ().Be (expected, $"Property `{src}' pseudo attributes did not match expected"); + if(!linked.Attributes.Equals(expected)) + { + yield return $"Property `{src}' pseudo attributes did not match expected"; + } + } - protected virtual void VerifyPseudoAttributes (EventDefinition src, EventDefinition linked) + protected virtual IEnumerable VerifyPseudoAttributes (EventDefinition src, EventDefinition linked) { var expected = (EventAttributes) GetExpectedPseudoAttributeValue (src, (uint) src.Attributes); - linked.Attributes.Should ().Be (expected, $"Event `{src}' pseudo attributes did not match expected"); + if(!linked.Attributes.Equals(expected)) + { + yield return $"Event `{src}' pseudo attributes did not match expected"; + } } - protected virtual void VerifyCustomAttributes (ICustomAttributeProvider src, ICustomAttributeProvider linked) + protected virtual IEnumerable VerifyCustomAttributes (ICustomAttributeProvider src, ICustomAttributeProvider linked) { var expectedAttrs = GetExpectedAttributes (src).ToList (); var linkedAttrs = FilterLinkedAttributes (linked).ToList (); - linkedAttrs.Should ().BeEquivalentTo (expectedAttrs, $"Custom attributes on `{src}' are not matching"); + if(!linkedAttrs.SequenceEqual(expectedAttrs)) + { + yield return $"Custom attributes on `{src}' are not matching"; + } } - protected virtual void VerifySecurityAttributes (ICustomAttributeProvider src, ISecurityDeclarationProvider linked) + protected virtual IEnumerable VerifySecurityAttributes (ICustomAttributeProvider src, ISecurityDeclarationProvider linked) { var expectedAttrs = GetCustomAttributeCtorValues (src, nameof (KeptSecurityAttribute)) .Select (attr => attr?.ToString () ?? "") @@ -993,11 +1089,14 @@ protected virtual void VerifySecurityAttributes (ICustomAttributeProvider src, I var linkedAttrs = FilterLinkedSecurityAttributes (linked).ToList (); - linkedAttrs.Should ().BeEquivalentTo (expectedAttrs, $"Security attributes on `{src}' are not matching"); + if(!linkedAttrs.SequenceEqual(expectedAttrs)) + { + yield return $"Security attributes on `{src}' are not matching"; + } } #if false - protected virtual void VerifyArrayInitializers (MethodDefinition src, MethodDefinition linked) + protected virtual IEnumerable VerifyArrayInitializers (MethodDefinition src, MethodDefinition linked) { var expectedIndices = GetCustomAttributeCtorValues (src, nameof (KeptInitializerData)) .Cast () @@ -1009,19 +1108,19 @@ protected virtual void VerifyArrayInitializers (MethodDefinition src, MethodDefi return; if (!src.HasBody) - Assert.Fail($"`{nameof (KeptInitializerData)}` cannot be used on methods that don't have bodies"); + yield return $"`{nameof (KeptInitializerData)}` cannot be used on methods that don't have bodies"; var srcImplementationDetails = src.Module.Types.FirstOrDefault (t => string.IsNullOrEmpty (t.Namespace) && t.Name.StartsWith ("")); if (srcImplementationDetails == null) { - Assert.Fail("Could not locate in the original assembly. Does your test use initializers?"); + yield return "Could not locate in the original assembly. Does your test use initializers?"; return; } var linkedImplementationDetails = linked.Module.Types.FirstOrDefault (t => string.IsNullOrEmpty (t.Namespace) && t.Name.StartsWith ("")); if (linkedImplementationDetails == null) { - Assert.Fail("Could not locate in the linked assembly"); + yield return "Could not locate in the linked assembly"; return; } @@ -1031,32 +1130,36 @@ protected virtual void VerifyArrayInitializers (MethodDefinition src, MethodDefi .ToArray (); if (possibleInitializerFields.Length == 0) - Assert.Fail($"`{src}` does not make use of any initializers"); + yield return $"`{src}` does not make use of any initializers"; if (expectKeptAll) { foreach (var srcField in possibleInitializerFields) { var linkedField = linkedImplementationDetails.Fields.FirstOrDefault (f => f.InitialValue.SequenceEqual (srcField.InitialValue)); - VerifyInitializerField (srcField, linkedField); + foreach(var err in VerifyInitializerField (srcField, linkedField)) + yield return err; } } else { foreach (var index in expectedIndices) { if (index < 0 || index > possibleInitializerFields.Length) - Assert.Fail($"Invalid expected index `{index}` in {src}. Value must be between 0 and {expectedIndices.Length}"); + yield return $"Invalid expected index `{index}` in {src}. Value must be between 0 and {expectedIndices.Length}"; var srcField = possibleInitializerFields[index]; var linkedField = linkedImplementationDetails.Fields.FirstOrDefault (f => f.InitialValue.SequenceEqual (srcField.InitialValue)); - VerifyInitializerField (srcField, linkedField); + foreach(var err in VerifyInitializerField (srcField, linkedField)) + yield return err; } } } - private void VerifyInitializerField (FieldDefinition src, FieldDefinition? linked) + private IEnumerable VerifyInitializerField (FieldDefinition src, FieldDefinition? linked) { - VerifyFieldKept (src, linked); + foreach(var err in VerifyFieldKept (src, linked)) + yield return err; verifiedGeneratedFields.Add (linked!.FullName); linkedMembers.Remove (new (linked)); - //VerifyTypeDefinitionKept (src.FieldType.Resolve (), linked.FieldType.Resolve ()); + // foreach(var err in VerifyTypeDefinitionKept (src.FieldType.Resolve (), linked.FieldType.Resolve ())) + // yield return err; linkedMembers.Remove (new (linked.FieldType.Resolve ())); linkedMembers.Remove (new (linked.DeclaringType.Resolve ())); verifiedGeneratedTypes.Add (linked.DeclaringType.FullName); @@ -1131,7 +1234,7 @@ protected virtual IEnumerable FilterLinkedSecurityAttributes (ISecurityD } #if false - private void VerifyFixedBufferFields (TypeDefinition src, TypeDefinition linked) + private IEnumerable VerifyFixedBufferFields (TypeDefinition src, TypeDefinition linked) { var fields = src.Fields.Where (f => f.CustomAttributes.Any (attr => attr.AttributeType.Name == nameof (KeptFixedBufferAttribute))); @@ -1141,93 +1244,109 @@ private void VerifyFixedBufferFields (TypeDefinition src, TypeDefinition linked) // while mcs and other versions of csc name it `__FixedBuffer0` var originalCompilerGeneratedBufferType = src.NestedTypes.FirstOrDefault (t => t.FullName.Contains ($"<{field.Name}>") && t.FullName.Contains ("__FixedBuffer")); if (originalCompilerGeneratedBufferType == null) { - Assert.Fail($"Could not locate original compiler generated fixed buffer type for field {field}"); - return; + yield return $"Could not locate original compiler generated fixed buffer type for field {field}"; + yield break; } var linkedCompilerGeneratedBufferType = linked.NestedTypes.FirstOrDefault (t => t.Name == originalCompilerGeneratedBufferType.Name); if (linkedCompilerGeneratedBufferType == null) { - Assert.Fail($"Missing expected type {originalCompilerGeneratedBufferType}"); - return; + yield return $"Missing expected type {originalCompilerGeneratedBufferType}"; + yield break; } // Have to verify the field before the type var originalElementField = originalCompilerGeneratedBufferType.Fields.FirstOrDefault (); if (originalElementField == null) { - Assert.Fail($"Could not locate original compiler generated FixedElementField on {originalCompilerGeneratedBufferType}"); - return; + yield return $"Could not locate original compiler generated FixedElementField on {originalCompilerGeneratedBufferType}"; + yield break; } var linkedField = linkedCompilerGeneratedBufferType?.Fields.FirstOrDefault (); - VerifyFieldKept (originalElementField, linkedField); + foreach(var err in VerifyFieldKept (originalElementField, linkedField)) + yield return err; verifiedGeneratedFields.Add (originalElementField.FullName); linkedMembers.Remove (new (linkedField!)); - //VerifyTypeDefinitionKept (originalCompilerGeneratedBufferType, linkedCompilerGeneratedBufferType); + // foreach(var err in VerifyTypeDefinitionKept (originalCompilerGeneratedBufferType, linkedCompilerGeneratedBufferType)) + // yield return err; verifiedGeneratedTypes.Add (originalCompilerGeneratedBufferType.FullName); } } - private void VerifyDelegateBackingFields (TypeDefinition src, TypeDefinition linked) + private IEnumerable VerifyDelegateBackingFields (TypeDefinition src, TypeDefinition linked) { var expectedFieldNames = GetCustomAttributeCtorValues (src, nameof (KeptDelegateCacheFieldAttribute)) .Select (unique => $"<>f__mg$cache{unique}") .ToList (); if (expectedFieldNames.Count == 0) - return; + yield break; foreach (var srcField in src.Fields) { if (!expectedFieldNames.Contains (srcField.Name)) continue; var linkedField = linked?.Fields.FirstOrDefault (l => l.Name == srcField.Name); - VerifyFieldKept (srcField, linkedField); + foreach(var err in VerifyFieldKept (srcField, linkedField)) + yield return err; verifiedGeneratedFields.Add (srcField.FullName); linkedMembers.Remove (new (srcField)); } } #endif - private void VerifyGenericParameters (IGenericParameterProvider src, IGenericParameterProvider linked) + private IEnumerable VerifyGenericParameters (IGenericParameterProvider src, IGenericParameterProvider linked) { - Assert.Equal (src.HasGenericParameters, linked.HasGenericParameters); + if (src.HasGenericParameters != linked.HasGenericParameters) + yield return $"Mismatch in having generic paramters. Expected {src.HasGenericParameters}, actual {linked.HasGenericParameters}"; + if (src.HasGenericParameters) { for (int i = 0; i < src.GenericParameters.Count; ++i) { // TODO: Verify constraints var srcp = src.GenericParameters[i]; var lnkp = linked.GenericParameters[i]; - VerifyCustomAttributes (srcp, lnkp); + foreach(var err in VerifyCustomAttributes (srcp, lnkp)) + yield return err; if (checkNames) { if (srcp.CustomAttributes.Any (attr => attr.AttributeType.Name == nameof (RemovedNameValueAttribute))) { string name = (src.GenericParameterType == GenericParameterType.Method ? "!!" : "!") + srcp.Position; - lnkp.Name.Should ().Be (name, "Expected empty generic parameter name"); + if (lnkp.Name != name) + yield return "Expected empty generic parameter name"; } else { - lnkp.Name.Should ().Be (srcp.Name, "Mismatch in generic parameter name"); + if (lnkp.Name != srcp.Name) + yield return "Mismatch in generic parameter name"; } } } } } - private void VerifyParameters (IMethodSignature src, LinkedMethodEntity linked) + private IEnumerable VerifyParameters (IMethodSignature src, LinkedMethodEntity linked) { - Assert.Equal (src.HasParameters, linked.Method.Signature.Length > 0); + if (src.HasParameters != linked.Method.Signature.Length > 0) + yield return $"Mismatch in having parameters in {src as MethodDefinition}: Expected {src.HasParameters}, actual {linked.Method.Signature.Length > 0}"; if (src.HasParameters) { for (int i = 0; i < src.Parameters.Count; ++i) { var srcp = src.Parameters[i]; //var lnkp = linked.Parameters[i]; #if false - VerifyCustomAttributes (srcp, lnkp); + foreach(var err in VerifyCustomAttributes (srcp, lnkp)) + yield return err; #endif if (checkNames) { if (srcp.CustomAttributes.Any (attr => attr.AttributeType.Name == nameof (RemovedNameValueAttribute))) - linked.IsReflected.Should ().BeFalse ($"Expected no parameter name (non-reflectable). Parameter {i} of {(src as MethodDefinition)}"); + { + if (linked.IsReflected != false) + yield return $"Expected no parameter name (non-reflectable). Parameter {i} of {src as MethodDefinition}"; + } else - linked.IsReflected.Should ().BeTrue ($"Expected accessible parameter name (reflectable). Parameter {i} of {(src as MethodDefinition)}"); + { + if (linked.IsReflected != true) + yield return $"Expected accessible parameter name (reflectable). Parameter {i} of {(src as MethodDefinition)}"; + } } } } @@ -1323,38 +1442,39 @@ private static bool HasActiveKeptDerivedAttribute (ICustomAttributeProvider prov return GetActiveKeptDerivedAttributes (provider).Any (); } - internal void VerifyLinkingOfOtherAssemblies (AssemblyDefinition original) + internal IEnumerable VerifyLinkingOfOtherAssemblies (AssemblyDefinition original) { var checks = BuildOtherAssemblyCheckTable (original); + List errs = []; try { foreach (var assemblyName in checks.Keys) { var linkedMembersInAssembly = ResolveLinkedMembersForAssembly (assemblyName); - var originalTargetAssembly = ResolveOriginalsAssembly(assemblyName); + var originalTargetAssembly = ResolveOriginalsAssembly(assemblyName); foreach (var checkAttrInAssembly in checks[assemblyName]) { var attributeTypeName = checkAttrInAssembly.AttributeType.Name; switch (attributeTypeName) { case nameof (KeptAllTypesAndMembersInAssemblyAttribute): - VerifyKeptAllTypesAndMembersInAssembly (assemblyName, linkedMembersInAssembly); + errs.AddRange(VerifyKeptAllTypesAndMembersInAssembly (assemblyName, linkedMembersInAssembly)); continue; case nameof (KeptAttributeInAssemblyAttribute): - // VerifyKeptAttributeInAssembly (checkAttrInAssembly, linkedAssembly); + // errs.AddRange(VerifyKeptAttributeInAssembly (checkAttrInAssembly, linkedAssembly)) continue; case nameof (RemovedAttributeInAssembly): - // VerifyRemovedAttributeInAssembly (checkAttrInAssembly, linkedAssembly); + // errs.AddRange(VerifyRemovedAttributeInAssembly (checkAttrInAssembly, linkedAssembly)) continue; default: break; } - var expectedTypeName = checkAttrInAssembly.ConstructorArguments[1].Value.ToString ()!; - var expectedType = originalTargetAssembly.MainModule.GetType(expectedTypeName); - linkedMembersInAssembly.TryGetValue(new AssemblyQualifiedToken(expectedType), out LinkedEntity? linkedTypeEntity); - MetadataType? linkedType = linkedTypeEntity?.Entity as MetadataType; + var expectedTypeName = checkAttrInAssembly.ConstructorArguments[1].Value.ToString ()!; + var expectedType = originalTargetAssembly.MainModule.GetType(expectedTypeName); + linkedMembersInAssembly.TryGetValue(new AssemblyQualifiedToken(expectedType), out LinkedEntity? linkedTypeEntity); + MetadataType? linkedType = linkedTypeEntity?.Entity as MetadataType; #if false - if (linkedType == null && linkedAssembly.MainModule.HasExportedTypes) { + if (linkedType == null && linkedAssembly.MainModule.HasExportedTypes) { ExportedType? exportedType = linkedAssembly.MainModule.ExportedTypes .FirstOrDefault (exported => exported.FullName == expectedTypeName); @@ -1371,92 +1491,108 @@ internal void VerifyLinkingOfOtherAssemblies (AssemblyDefinition original) switch (attributeTypeName) { case nameof (RemovedTypeInAssemblyAttribute): if (linkedType != null) - Assert.Fail ($"Type `{expectedTypeName}' should have been removed from assembly {assemblyName}"); + errs.Add($"Type `{expectedTypeName}' should have been removed from assembly {assemblyName}"); GetOriginalTypeFromInAssemblyAttribute (checkAttrInAssembly); break; case nameof (KeptTypeInAssemblyAttribute): if (linkedType == null) - Assert.Fail ($"Type `{expectedTypeName}' should have been kept in assembly {assemblyName}"); + errs.Add($"Type `{expectedTypeName}' should have been kept in assembly {assemblyName}"); break; #if false case nameof (RemovedInterfaceOnTypeInAssemblyAttribute): if (linkedType == null) - Assert.Fail ($"Type `{expectedTypeName}' should have been kept in assembly {assemblyName}"); - VerifyRemovedInterfaceOnTypeInAssembly (checkAttrInAssembly, linkedType); + { + errs.Add($"Type `{expectedTypeName}' should have been kept in assembly {assemblyName}"); + break; + } + errs.AddRange(VerifyRemovedInterfaceOnTypeInAssembly (checkAttrInAssembly, linkedType)); break; case nameof (KeptInterfaceOnTypeInAssemblyAttribute): if (linkedType == null) - Assert.Fail ($"Type `{expectedTypeName}' should have been kept in assembly {assemblyName}"); - VerifyKeptInterfaceOnTypeInAssembly (checkAttrInAssembly, linkedType); + { + errs.Add($"Type `{expectedTypeName}' should have been kept in assembly {assemblyName}"); + break; + } + errs.AddRange(VerifyKeptInterfaceOnTypeInAssembly (checkAttrInAssembly, linkedType)); break; case nameof (RemovedMemberInAssemblyAttribute): if (linkedType == null) continue; - VerifyRemovedMemberInAssembly (checkAttrInAssembly, linkedType); + errs.AddRange(VerifyRemovedMemberInAssembly (checkAttrInAssembly, linkedType)); break; case nameof (KeptBaseOnTypeInAssemblyAttribute): if (linkedType == null) - Assert.Fail ($"Type `{expectedTypeName}' should have been kept in assembly {assemblyName}"); - VerifyKeptBaseOnTypeInAssembly (checkAttrInAssembly, linkedType); + { + errs.Add($"Type `{expectedTypeName}' should have been kept in assembly {assemblyName}"); + break; + } + errs.AddRange(VerifyKeptBaseOnTypeInAssembly (checkAttrInAssembly, linkedType)); break; case nameof (KeptMemberInAssemblyAttribute): if (linkedType == null) - Assert.Fail ($"Type `{expectedTypeName}' should have been kept in assembly {assemblyName}"); + { + errs.Add($"Type `{expectedTypeName}' should have been kept in assembly {assemblyName}"); + break; + } - VerifyKeptMemberInAssembly (checkAttrInAssembly, linkedType); + errs.AddRange(VerifyKeptMemberInAssembly (checkAttrInAssembly, linkedType)); break; case nameof (RemovedForwarderAttribute): if (linkedAssembly.MainModule.ExportedTypes.Any (l => l.Name == expectedTypeName)) - Assert.Fail ($"Forwarder `{expectedTypeName}' should have been removed from assembly {assemblyName}"); + errs.Add($"Forwarder `{expectedTypeName}' should have been removed from assembly {assemblyName}"); break; case nameof (RemovedAssemblyReferenceAttribute): - Assert.False (linkedAssembly.MainModule.AssemblyReferences.Any (l => l.Name == expectedTypeName), - $"AssemblyRef '{expectedTypeName}' should have been removed from assembly {assemblyName}"); + if (linkedAssembly.MainModule.AssemblyReferences.Any (l => l.Name == expectedTypeName) != false) + errs.Add($"AssemblyRef '{expectedTypeName}' should have been removed from assembly {assemblyName}"); break; case nameof (KeptResourceInAssemblyAttribute): - VerifyKeptResourceInAssembly (checkAttrInAssembly); + errs.AddRange(VerifyKeptResourceInAssembly (checkAttrInAssembly)); break; case nameof (RemovedResourceInAssemblyAttribute): - VerifyRemovedResourceInAssembly (checkAttrInAssembly); + errs.AddRange(VerifyRemovedResourceInAssembly (checkAttrInAssembly)); break; case nameof (KeptReferencesInAssemblyAttribute): - VerifyKeptReferencesInAssembly (checkAttrInAssembly); + errs.AddRange(VerifyKeptReferencesInAssembly (checkAttrInAssembly)) break; case nameof (ExpectedInstructionSequenceOnMemberInAssemblyAttribute): if (linkedType == null) - Assert.Fail ($"Type `{expectedTypeName}` should have been kept in assembly {assemblyName}"); - VerifyExpectedInstructionSequenceOnMemberInAssembly (checkAttrInAssembly, linkedType); + { + errs.Add($"Type `{expectedTypeName}` should have been kept in assembly {assemblyName}"); + break; + } + errs.AddRange(VerifyExpectedInstructionSequenceOnMemberInAssembly (checkAttrInAssembly, linkedType)); break; - default: + default: UnhandledOtherAssemblyAssertion (expectedTypeName, checkAttrInAssembly, linkedType); break; #else - default: - break; + default: + break; #endif - } - } + } + } } } catch (AssemblyResolutionException e) { - Assert.Fail ($"Failed to resolve linked assembly `{e.AssemblyReference.Name}`. It must not exist in the output."); + errs.Add($"Failed to resolve linked assembly `{e.AssemblyReference.Name}`. It must not exist in the output."); } + return errs; } - private void VerifyKeptAttributeInAssembly (CustomAttribute inAssemblyAttribute, AssemblyDefinition linkedAssembly) + private IEnumerable VerifyKeptAttributeInAssembly (CustomAttribute inAssemblyAttribute, AssemblyDefinition linkedAssembly) { - VerifyAttributeInAssembly (inAssemblyAttribute, linkedAssembly, VerifyCustomAttributeKept); + return VerifyAttributeInAssembly (inAssemblyAttribute, linkedAssembly, VerifyCustomAttributeKept); } - private void VerifyRemovedAttributeInAssembly (CustomAttribute inAssemblyAttribute, AssemblyDefinition linkedAssembly) + private IEnumerable VerifyRemovedAttributeInAssembly (CustomAttribute inAssemblyAttribute, AssemblyDefinition linkedAssembly) { - VerifyAttributeInAssembly (inAssemblyAttribute, linkedAssembly, VerifyCustomAttributeRemoved); + return VerifyAttributeInAssembly (inAssemblyAttribute, linkedAssembly, VerifyCustomAttributeRemoved); } - private void VerifyAttributeInAssembly (CustomAttribute inAssemblyAttribute, AssemblyDefinition linkedAssembly, Action assertExpectedAttribute) + private IEnumerable VerifyAttributeInAssembly (CustomAttribute inAssemblyAttribute, AssemblyDefinition linkedAssembly, Func> assertExpectedAttribute) { var assemblyName = (string) inAssemblyAttribute.ConstructorArguments[0].Value!; string expectedAttributeTypeName; @@ -1469,23 +1605,30 @@ private void VerifyAttributeInAssembly (CustomAttribute inAssemblyAttribute, Ass if (inAssemblyAttribute.ConstructorArguments.Count == 2) { // Assembly - assertExpectedAttribute (linkedAssembly, expectedAttributeTypeName); - return; + foreach(var err in assertExpectedAttribute (linkedAssembly, expectedAttributeTypeName)) + yield return err; + yield break; } // We are asserting on type or member var typeOrTypeName = inAssemblyAttribute.ConstructorArguments[2].Value; var originalType = GetOriginalTypeFromInAssemblyAttribute (inAssemblyAttribute.ConstructorArguments[0].Value.ToString ()!, typeOrTypeName); if (originalType == null) - Assert.Fail ($"Invalid test assertion. The original `{assemblyName}` does not contain a type `{typeOrTypeName}`"); + { + yield return $"Invalid test assertion. The original `{assemblyName}` does not contain a type `{typeOrTypeName}`"; + yield break; + } var linkedType = linkedAssembly.MainModule.GetType (originalType.FullName); if (linkedType == null) - Assert.Fail ($"Missing expected type `{typeOrTypeName}` in `{assemblyName}`"); + { + yield return $"Missing expected type `{typeOrTypeName}` in `{assemblyName}`"; + yield break; + } if (inAssemblyAttribute.ConstructorArguments.Count == 3) { assertExpectedAttribute (linkedType, expectedAttributeTypeName); - return; + yield break; } // we are asserting on a member @@ -1497,61 +1640,71 @@ private void VerifyAttributeInAssembly (CustomAttribute inAssemblyAttribute, Ass if (originalFieldMember != null) { var linkedField = linkedType.Fields.FirstOrDefault (m => m.Name == memberName); if (linkedField == null) - Assert.Fail ($"Field `{memberName}` on Type `{originalType}` should have been kept"); + { + yield return $"Field `{memberName}` on Type `{originalType}` should have been kept"; + yield break; + } assertExpectedAttribute (linkedField, expectedAttributeTypeName); - return; + yield break; } var originalPropertyMember = originalType.Properties.FirstOrDefault (m => m.Name == memberName); if (originalPropertyMember != null) { var linkedProperty = linkedType.Properties.FirstOrDefault (m => m.Name == memberName); if (linkedProperty == null) - Assert.Fail ($"Property `{memberName}` on Type `{originalType}` should have been kept"); + { + yield return $"Property `{memberName}` on Type `{originalType}` should have been kept"; + yield break; + } - assertExpectedAttribute (linkedProperty, expectedAttributeTypeName); - return; + foreach(var err in assertExpectedAttribute (linkedProperty, expectedAttributeTypeName)) + yield return err; + yield break; } var originalMethodMember = originalType.Methods.FirstOrDefault (m => m.GetSignature () == memberName); if (originalMethodMember != null) { var linkedMethod = linkedType.Methods.FirstOrDefault (m => m.GetSignature () == memberName); if (linkedMethod == null) - Assert.Fail ($"Method `{memberName}` on Type `{originalType}` should have been kept"); + { + yield return $"Method `{memberName}` on Type `{originalType}` should have been kept"; + yield break; + } assertExpectedAttribute (linkedMethod, expectedAttributeTypeName); - return; + yield break; } - Assert.Fail ($"Invalid test assertion. No member named `{memberName}` exists on the original type `{originalType}`"); + yield return $"Invalid test assertion. No member named `{memberName}` exists on the original type `{originalType}`"; } - private static void VerifyCopyAssemblyIsKeptUnmodified (NPath outputDirectory, string assemblyName) + private static IEnumerable VerifyCopyAssemblyIsKeptUnmodified (NPath outputDirectory, string assemblyName) { string inputAssemblyPath = Path.Combine (Directory.GetParent (outputDirectory)!.ToString (), "input", assemblyName); string outputAssemblyPath = Path.Combine (outputDirectory, assemblyName); - Assert.True (File.ReadAllBytes (inputAssemblyPath).SequenceEqual (File.ReadAllBytes (outputAssemblyPath)), - $"Expected assemblies\n" + - $"\t{inputAssemblyPath}\n" + - $"\t{outputAssemblyPath}\n" + - $"binaries to be equal, since the input assembly has copy action."); + if (true != File.ReadAllBytes (inputAssemblyPath).SequenceEqual (File.ReadAllBytes (outputAssemblyPath))) + yield return $"Expected assemblies\n" + + $"\t{inputAssemblyPath}\n" + + $"\t{outputAssemblyPath}\n" + + $"binaries to be equal, since the input assembly has copy action."; } - private void VerifyCustomAttributeKept (ICustomAttributeProvider provider, string expectedAttributeTypeName) + private IEnumerable VerifyCustomAttributeKept (ICustomAttributeProvider provider, string expectedAttributeTypeName) { var match = provider.CustomAttributes.FirstOrDefault (attr => attr.AttributeType.FullName == expectedAttributeTypeName); if (match == null) - Assert.Fail ($"Expected `{provider}` to have an attribute of type `{expectedAttributeTypeName}`"); + yield return $"Expected `{provider}` to have an attribute of type `{expectedAttributeTypeName}`"; } - private void VerifyCustomAttributeRemoved (ICustomAttributeProvider provider, string expectedAttributeTypeName) + private IEnumerable VerifyCustomAttributeRemoved (ICustomAttributeProvider provider, string expectedAttributeTypeName) { var match = provider.CustomAttributes.FirstOrDefault (attr => attr.AttributeType.FullName == expectedAttributeTypeName); if (match != null) - Assert.Fail ($"Expected `{provider}` to no longer have an attribute of type `{expectedAttributeTypeName}`"); + yield return $"Expected `{provider}` to no longer have an attribute of type `{expectedAttributeTypeName}`"; } - private void VerifyRemovedInterfaceOnTypeInAssembly (CustomAttribute inAssemblyAttribute, TypeDefinition linkedType) + private IEnumerable VerifyRemovedInterfaceOnTypeInAssembly (CustomAttribute inAssemblyAttribute, TypeDefinition linkedType) { var originalType = GetOriginalTypeFromInAssemblyAttribute (inAssemblyAttribute); @@ -1560,18 +1713,18 @@ private void VerifyRemovedInterfaceOnTypeInAssembly (CustomAttribute inAssemblyA var originalInterface = GetOriginalTypeFromInAssemblyAttribute (interfaceAssemblyName, interfaceType); if (!originalType.HasInterfaces) - Assert.Fail ("Invalid assertion. Original type does not have any interfaces"); + yield return "Invalid assertion. Original type does not have any interfaces"; var originalInterfaceImpl = GetMatchingInterfaceImplementationOnType (originalType, originalInterface.FullName); if (originalInterfaceImpl == null) - Assert.Fail ($"Invalid assertion. Original type never had an interface of type `{originalInterface}`"); + yield return $"Invalid assertion. Original type never had an interface of type `{originalInterface}`"; var linkedInterfaceImpl = GetMatchingInterfaceImplementationOnType (linkedType, originalInterface.FullName); if (linkedInterfaceImpl != null) - Assert.Fail ($"Expected `{linkedType}` to no longer have an interface of type {originalInterface.FullName}"); + yield return $"Expected `{linkedType}` to no longer have an interface of type {originalInterface.FullName}"; } - private void VerifyKeptInterfaceOnTypeInAssembly (CustomAttribute inAssemblyAttribute, TypeDefinition linkedType) + private IEnumerable VerifyKeptInterfaceOnTypeInAssembly (CustomAttribute inAssemblyAttribute, TypeDefinition linkedType) { var originalType = GetOriginalTypeFromInAssemblyAttribute (inAssemblyAttribute); @@ -1580,18 +1733,18 @@ private void VerifyKeptInterfaceOnTypeInAssembly (CustomAttribute inAssemblyAttr var originalInterface = GetOriginalTypeFromInAssemblyAttribute (interfaceAssemblyName, interfaceType); if (!originalType.HasInterfaces) - Assert.Fail ("Invalid assertion. Original type does not have any interfaces"); + yield return "Invalid assertion. Original type does not have any interfaces"; var originalInterfaceImpl = GetMatchingInterfaceImplementationOnType (originalType, originalInterface.FullName); if (originalInterfaceImpl == null) - Assert.Fail ($"Invalid assertion. Original type never had an interface of type `{originalInterface}`"); + yield return $"Invalid assertion. Original type never had an interface of type `{originalInterface}`"; var linkedInterfaceImpl = GetMatchingInterfaceImplementationOnType (linkedType, originalInterface.FullName); if (linkedInterfaceImpl == null) - Assert.Fail ($"Expected `{linkedType}` to have interface of type {originalInterface.FullName}"); + yield return $"Expected `{linkedType}` to have interface of type {originalInterface.FullName}"; } - private void VerifyKeptBaseOnTypeInAssembly (CustomAttribute inAssemblyAttribute, TypeDefinition linkedType) + private IEnumerable VerifyKeptBaseOnTypeInAssembly (CustomAttribute inAssemblyAttribute, TypeDefinition linkedType) { var originalType = GetOriginalTypeFromInAssemblyAttribute (inAssemblyAttribute); @@ -1600,10 +1753,10 @@ private void VerifyKeptBaseOnTypeInAssembly (CustomAttribute inAssemblyAttribute var originalBase = GetOriginalTypeFromInAssemblyAttribute (baseAssemblyName, baseType); if (originalType.BaseType.Resolve () != originalBase) - Assert.Fail ("Invalid assertion. Original type's base does not match the expected base"); + yield return "Invalid assertion. Original type's base does not match the expected base"; - Assert.True (originalBase.FullName == linkedType.BaseType.FullName, - $"Incorrect base on `{linkedType.FullName}`. Expected `{originalBase.FullName}` but was `{linkedType.BaseType.FullName}`"); + if (originalBase.FullName != linkedType.BaseType.FullName) + yield return $"Incorrect base on `{linkedType.FullName}`. Expected `{originalBase.FullName}` but was `{linkedType.BaseType.FullName}`"; } private static InterfaceImplementation? GetMatchingInterfaceImplementationOnType (TypeDefinition type, string expectedInterfaceTypeName) @@ -1618,7 +1771,7 @@ private void VerifyKeptBaseOnTypeInAssembly (CustomAttribute inAssemblyAttribute }); } - private void VerifyRemovedMemberInAssembly (CustomAttribute inAssemblyAttribute, TypeDefinition linkedType) + private IEnumerable VerifyRemovedMemberInAssembly (CustomAttribute inAssemblyAttribute, TypeDefinition linkedType) { var originalType = GetOriginalTypeFromInAssemblyAttribute (inAssemblyAttribute); foreach (var memberNameAttr in (CustomAttributeArgument[]) inAssemblyAttribute.ConstructorArguments[2].Value) { @@ -1630,7 +1783,7 @@ private void VerifyRemovedMemberInAssembly (CustomAttribute inAssemblyAttribute, if (originalFieldMember != null) { var linkedField = linkedType.Fields.FirstOrDefault (m => m.Name == memberName); if (linkedField != null) - Assert.Fail ($"Field `{memberName}` on Type `{originalType}` should have been removed"); + yield return $"Field `{memberName}` on Type `{originalType}` should have been removed"; continue; } @@ -1639,7 +1792,7 @@ private void VerifyRemovedMemberInAssembly (CustomAttribute inAssemblyAttribute, if (originalPropertyMember != null) { var linkedProperty = linkedType.Properties.FirstOrDefault (m => m.Name == memberName); if (linkedProperty != null) - Assert.Fail ($"Property `{memberName}` on Type `{originalType}` should have been removed"); + yield return $"Property `{memberName}` on Type `{originalType}` should have been removed"; continue; } @@ -1648,20 +1801,21 @@ private void VerifyRemovedMemberInAssembly (CustomAttribute inAssemblyAttribute, if (originalMethodMember != null) { var linkedMethod = linkedType.Methods.FirstOrDefault (m => m.GetSignature () == memberName); if (linkedMethod != null) - Assert.Fail ($"Method `{memberName}` on Type `{originalType}` should have been removed"); + yield return $"Method `{memberName}` on Type `{originalType}` should have been removed"; continue; } - Assert.Fail ($"Invalid test assertion. No member named `{memberName}` exists on the original type `{originalType}`"); + yield return $"Invalid test assertion. No member named `{memberName}` exists on the original type `{originalType}`"; } } - private void VerifyKeptMemberInAssembly (CustomAttribute inAssemblyAttribute, TypeDefinition linkedType) + private IEnumerable VerifyKeptMemberInAssembly (CustomAttribute inAssemblyAttribute, TypeDefinition linkedType) { var originalType = GetOriginalTypeFromInAssemblyAttribute (inAssemblyAttribute); var memberNames = (CustomAttributeArgument[]) inAssemblyAttribute.ConstructorArguments[2].Value; - Assert.True (memberNames.Length > 0, "Invalid KeptMemberInAssemblyAttribute. Expected member names."); + if (!(memberNames.Length > 0)) + yield return "Invalid KeptMemberInAssemblyAttribute. Expected member names."; foreach (var memberNameAttr in memberNames) { string memberName = (string) memberNameAttr.Value; @@ -1677,7 +1831,7 @@ private void VerifyKeptMemberInAssembly (CustomAttribute inAssemblyAttribute, Ty if (TryVerifyKeptMemberInAssemblyAsMethod (memberName, originalType, linkedType)) continue; - Assert.Fail ($"Invalid test assertion. No member named `{memberName}` exists on the original type `{originalType}`"); + yield return $"Invalid test assertion. No member named `{memberName}` exists on the original type `{originalType}`"; } } @@ -1729,10 +1883,10 @@ protected virtual bool TryVerifyKeptMemberInAssemblyAsMethod (string memberName, return false; } - private void VerifyKeptReferencesInAssembly (CustomAttribute inAssemblyAttribute) + private IEnumerable VerifyKeptReferencesInAssembly (CustomAttribute inAssemblyAttribute) { #if false - var assembly = ResolveLinkedAssembly (inAssemblyAttribute.ConstructorArguments[0].Value.ToString ()!); + var assembly = ResolveLinkedAssembly (inAssemblyAttribute.ConstructorArguments[0].Value.ToString ()!); var expectedReferenceNames = ((CustomAttributeArgument[]) inAssemblyAttribute.ConstructorArguments[1].Value).Select (attr => (string) attr.Value).ToList (); for (int i = 0; i < expectedReferenceNames.Count; i++) if (expectedReferenceNames[i].EndsWith (".dll")) @@ -1740,53 +1894,61 @@ private void VerifyKeptReferencesInAssembly (CustomAttribute inAssemblyAttribute Assert.Equal (assembly.MainModule.AssemblyReferences.Select (asm => asm.Name), expectedReferenceNames); #endif + yield break; } - private void VerifyKeptResourceInAssembly (CustomAttribute inAssemblyAttribute) + private IEnumerable VerifyKeptResourceInAssembly (CustomAttribute inAssemblyAttribute) { #if false - var assembly = ResolveLinkedAssembly (inAssemblyAttribute.ConstructorArguments[0].Value.ToString ()!); + var assembly = ResolveLinkedAssembly (inAssemblyAttribute.ConstructorArguments[0].Value.ToString ()!); var resourceName = inAssemblyAttribute.ConstructorArguments[1].Value.ToString (); Assert.Contains (resourceName, assembly.MainModule.Resources.Select (r => r.Name)); #endif + yield break; } - private void VerifyRemovedResourceInAssembly (CustomAttribute inAssemblyAttribute) + private IEnumerable VerifyRemovedResourceInAssembly (CustomAttribute inAssemblyAttribute) { #if false - var assembly = ResolveLinkedAssembly (inAssemblyAttribute.ConstructorArguments[0].Value.ToString ()!); + var assembly = ResolveLinkedAssembly (inAssemblyAttribute.ConstructorArguments[0].Value.ToString ()!); var resourceName = inAssemblyAttribute.ConstructorArguments[1].Value.ToString (); Assert.DoesNotContain (resourceName, assembly.MainModule.Resources.Select (r => r.Name)); #endif + yield break; } - private void VerifyKeptAllTypesAndMembersInAssembly (string assemblyName, Dictionary linkedMembers) + private IEnumerable VerifyKeptAllTypesAndMembersInAssembly (string assemblyName, Dictionary linkedMembers) { var original = ResolveOriginalsAssembly (assemblyName); if (original == null) - Assert.Fail ($"Failed to resolve original assembly {assemblyName}"); + { + yield return $"Failed to resolve original assembly {assemblyName}"; + yield break; + } var originalTypes = original.AllDefinedTypes ().ToDictionary (t => new AssemblyQualifiedToken(t)); - var linkedTypes = linkedMembers.Where(t => t.Value.Entity is TypeDesc).ToDictionary(); + var linkedTypes = linkedMembers.Where(t => t.Value.Entity is TypeDesc).ToDictionary(); var missingInLinked = originalTypes.Keys.Except (linkedTypes.Keys); - Assert.False (missingInLinked.Any (), $"Expected all types to exist in the linked assembly {assemblyName}, but one or more were missing"); + if (missingInLinked.Any ()) + yield return $"Expected all types to exist in the linked assembly {assemblyName}, but one or more were missing"; foreach (var originalKvp in originalTypes) { var linkedType = linkedTypes[originalKvp.Key]; - TypeDesc linkedTypeDesc = (TypeDesc)linkedType.Entity; + TypeDesc linkedTypeDesc = (TypeDesc)linkedType.Entity; - // NativeAOT field trimming is very different (it basically doesn't trim fields, not in the same way trimmer does) + // NativeAOT field trimming is very different (it basically doesn't trim fields, not in the same way trimmer does) var originalMembers = originalKvp.Value.AllMembers ().Where(m => m is not FieldDefinition).Select (m => new AssemblyQualifiedToken(m)); - var linkedMembersOnType = linkedMembers.Where(t => GetOwningType(t.Value.Entity) == linkedTypeDesc).Select(t => t.Key); + var linkedMembersOnType = linkedMembers.Where(t => GetOwningType(t.Value.Entity) == linkedTypeDesc).Select(t => t.Key); var missingMembersInLinked = originalMembers.Except (linkedMembersOnType); - Assert.False (missingMembersInLinked.Any (), $"Expected all members of `{linkedTypeDesc.GetDisplayName()}`to exist in the linked assembly, but one or more were missing"); + if (missingMembersInLinked.Any ()) + yield return $"Expected all members of `{linkedTypeDesc.GetDisplayName()}`to exist in the linked assembly, but one or more were missing"; } } @@ -1823,9 +1985,9 @@ private static Dictionary> BuildOtherAssemblyCheck foreach (var attr in typeWithRemoveInAssembly.CustomAttributes.Where (IsTypeInOtherAssemblyAssertion)) { var assemblyName = (string) attr.ConstructorArguments[0].Value; - Tool? toolTarget = (Tool?)(int?)attr.GetPropertyValue("Tool"); - if (toolTarget is not null && !toolTarget.Value.HasFlag(Tool.NativeAot)) - continue; + Tool? toolTarget = (Tool?)(int?)attr.GetPropertyValue("Tool"); + if (toolTarget is not null && !toolTarget.Value.HasFlag(Tool.NativeAot)) + continue; if (!checks.TryGetValue (assemblyName, out List? checksForAssembly)) checks[assemblyName] = checksForAssembly = new List (); @@ -1839,11 +2001,11 @@ private static Dictionary> BuildOtherAssemblyCheck private Dictionary ResolveLinkedMembersForAssembly (string assemblyName) { - var cleanAssemblyName = assemblyName; - if (assemblyName.EndsWith(".exe") || assemblyName.EndsWith(".dll")) - cleanAssemblyName = System.IO.Path.GetFileNameWithoutExtension(assemblyName); + var cleanAssemblyName = assemblyName; + if (assemblyName.EndsWith(".exe") || assemblyName.EndsWith(".dll")) + cleanAssemblyName = System.IO.Path.GetFileNameWithoutExtension(assemblyName); - return this.linkedMembers.Where(e => GetModuleName(e.Value.Entity) == cleanAssemblyName).ToDictionary(); + return this.linkedMembers.Where(e => GetModuleName(e.Value.Entity) == cleanAssemblyName).ToDictionary(); } protected AssemblyDefinition ResolveOriginalsAssembly (string assemblyName) @@ -1859,7 +2021,7 @@ private static bool IsTypeInOtherAssemblyAssertion (CustomAttribute attr) return attr.AttributeType.Resolve ()?.DerivesFrom (nameof (BaseInAssemblyAttribute)) ?? false; } - private void VerifyExpectedInstructionSequenceOnMemberInAssembly (CustomAttribute inAssemblyAttribute, TypeDefinition linkedType) + private IEnumerable VerifyExpectedInstructionSequenceOnMemberInAssembly (CustomAttribute inAssemblyAttribute, TypeDefinition linkedType) { var originalType = GetOriginalTypeFromInAssemblyAttribute (inAssemblyAttribute); var memberName = (string) inAssemblyAttribute.ConstructorArguments[2].Value; @@ -1870,14 +2032,13 @@ private void VerifyExpectedInstructionSequenceOnMemberInAssembly (CustomAttribut var srcValues = valueCollector (originalMethod!); var expected = ((CustomAttributeArgument[]) inAssemblyAttribute.ConstructorArguments[3].Value)?.Select (arg => arg.Value.ToString ()).ToArray (); - Assert.Equal ( - linkedValues, - expected); + if (!linkedValues.Equals(expected)) + yield return "Expected instruction sequence does not match"; - return; + yield break; } - Assert.Fail ($"Invalid test assertion. No method named `{memberName}` exists on the original type `{originalType}`"); + yield return $"Invalid test assertion. No method named `{memberName}` exists on the original type `{originalType}`"; } protected virtual void UnhandledOtherAssemblyAssertion (string expectedTypeName, CustomAttribute checkAttrInAssembly, TypeDefinition? linkedType) diff --git a/src/tools/illink/test/Mono.Linker.Tests/TestCasesRunner/AssemblyChecker.cs b/src/tools/illink/test/Mono.Linker.Tests/TestCasesRunner/AssemblyChecker.cs index 36031245d5191b..8ed15724cc1148 100644 --- a/src/tools/illink/test/Mono.Linker.Tests/TestCasesRunner/AssemblyChecker.cs +++ b/src/tools/illink/test/Mono.Linker.Tests/TestCasesRunner/AssemblyChecker.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Globalization; using System.Linq; +using System.Linq.Expressions; using System.Text; using Mono.Cecil; using Mono.Cecil.Cil; @@ -39,77 +40,95 @@ public AssemblyChecker (AssemblyDefinition original, AssemblyDefinition linked, public void Verify () { - VerifyExportedTypes (originalAssembly, linkedAssembly); - - VerifyCustomAttributes (originalAssembly, linkedAssembly); - VerifySecurityAttributes (originalAssembly, linkedAssembly); - - foreach (var originalModule in originalAssembly.Modules) - VerifyModule (originalModule, linkedAssembly.Modules.FirstOrDefault (m => m.Name == originalModule.Name)); - - VerifyResources (originalAssembly, linkedAssembly); - VerifyReferences (originalAssembly, linkedAssembly); - VerifyKeptByAttributes (originalAssembly, originalAssembly.FullName); - - linkedMembers = new HashSet (linkedAssembly.MainModule.AllMembers ().Select (s => { - return s.FullName; - }), StringComparer.Ordinal); - - // Workaround for compiler injected attribute to describe the language version - linkedMembers.Remove ("System.Void Microsoft.CodeAnalysis.EmbeddedAttribute::.ctor()"); - linkedMembers.Remove ("System.Int32 System.Runtime.CompilerServices.RefSafetyRulesAttribute::Version"); - linkedMembers.Remove ("System.Void System.Runtime.CompilerServices.RefSafetyRulesAttribute::.ctor(System.Int32)"); + var failures = GetFailures ().ToList (); + if (failures.Count > 0) + Assert.Fail (string.Join (Environment.NewLine, failures)); + + IEnumerable GetFailures () + { + foreach (var err in VerifyExportedTypes (originalAssembly, linkedAssembly)) + yield return err; + foreach (var err in VerifyCustomAttributes (originalAssembly, linkedAssembly)) + yield return err; + foreach (var err in VerifySecurityAttributes (originalAssembly, linkedAssembly)) + yield return err; + + foreach (var originalModule in originalAssembly.Modules) + foreach (var err in VerifyModule (originalModule, linkedAssembly.Modules.FirstOrDefault (m => m.Name == originalModule.Name))) + yield return err; + + foreach (var err in VerifyResources (originalAssembly, linkedAssembly)) + yield return err; + foreach (var err in VerifyReferences (originalAssembly, linkedAssembly)) + yield return err; + foreach (var err in VerifyKeptByAttributes (originalAssembly, originalAssembly.FullName)) + yield return err; + + linkedMembers = new HashSet (linkedAssembly.MainModule.AllMembers ().Select (s => { + return s.FullName; + }), StringComparer.Ordinal); + + // Workaround for compiler injected attribute to describe the language version + linkedMembers.Remove ("System.Void Microsoft.CodeAnalysis.EmbeddedAttribute::.ctor()"); + linkedMembers.Remove ("System.Int32 System.Runtime.CompilerServices.RefSafetyRulesAttribute::Version"); + linkedMembers.Remove ("System.Void System.Runtime.CompilerServices.RefSafetyRulesAttribute::.ctor(System.Int32)"); + + // Workaround for compiler injected attribute to describe the language version + verifiedGeneratedTypes.Add ("Microsoft.CodeAnalysis.EmbeddedAttribute"); + verifiedGeneratedTypes.Add ("System.Runtime.CompilerServices.RefSafetyRulesAttribute"); + + var membersToAssert = originalAssembly.MainModule.Types; + foreach (var originalMember in membersToAssert) { + if (originalMember is TypeDefinition td) { + if (td.Name == "") { + linkedMembers.Remove (td.Name); + continue; + } - // Workaround for compiler injected attribute to describe the language version - verifiedGeneratedTypes.Add ("Microsoft.CodeAnalysis.EmbeddedAttribute"); - verifiedGeneratedTypes.Add ("System.Runtime.CompilerServices.RefSafetyRulesAttribute"); + TypeDefinition linkedType = linkedAssembly.MainModule.GetType (originalMember.FullName); + foreach (var err in VerifyTypeDefinition (td, linkedType)) + yield return err; + linkedMembers.Remove (td.FullName); - var membersToAssert = originalAssembly.MainModule.Types; - foreach (var originalMember in membersToAssert) { - if (originalMember is TypeDefinition td) { - if (td.Name == "") { - linkedMembers.Remove (td.Name); continue; } - TypeDefinition linkedType = linkedAssembly.MainModule.GetType (originalMember.FullName); - VerifyTypeDefinition (td, linkedType); - linkedMembers.Remove (td.FullName); - - continue; + yield return $"Don't know how to check member of type {originalMember.GetType ()}"; } - throw new NotImplementedException ($"Don't know how to check member of type {originalMember.GetType ()}"); + if (linkedMembers.Any ()) + foreach (var err in linkedMembers.Select (m => $"Member `{m}' was not expected to be kept")) + yield return err; } - - Assert.IsEmpty (linkedMembers, "Linked output includes unexpected member"); } static bool IsBackingField (FieldDefinition field) => field.Name.StartsWith ("<") && field.Name.EndsWith (">k__BackingField"); - protected virtual void VerifyModule (ModuleDefinition original, ModuleDefinition linked) + protected virtual IEnumerable VerifyModule (ModuleDefinition original, ModuleDefinition linked) { // We never link away a module today so let's make sure the linked one isn't null if (linked == null) - Assert.Fail ($"Linked assembly `{original.Assembly.Name.Name}` is missing module `{original.Name}`"); + yield return $"Linked assembly `{original.Assembly.Name.Name}` is missing module `{original.Name}`"; var expected = original.Assembly.MainModule.AllDefinedTypes () .SelectMany (t => GetCustomAttributeCtorValues (t, nameof (KeptModuleReferenceAttribute))) - .ToArray (); + .ToHashSet (); var actual = linked.ModuleReferences .Select (name => name.Name) - .ToArray (); + .ToHashSet (); - Assert.That (actual, Is.EquivalentTo (expected)); + if (!expected.SetEquals (actual)) + yield return $"In module {original.FileName} Expected module references `{string.Join (", ", expected)}` but got `{string.Join (", ", actual)}`"; - VerifyCustomAttributes (original, linked); + foreach (var err in VerifyCustomAttributes (original, linked)) + yield return err; } - protected virtual void VerifyTypeDefinition (TypeDefinition original, TypeDefinition linked) + protected virtual IEnumerable VerifyTypeDefinition (TypeDefinition original, TypeDefinition linked) { if (linked != null && verifiedGeneratedTypes.Contains (linked.FullName)) - return; + yield break; ModuleDefinition linkedModule = linked?.Module; @@ -126,7 +145,7 @@ protected virtual void VerifyTypeDefinition (TypeDefinition original, TypeDefini if (!expectedKept) { if (linked == null) - return; + yield break; // Compiler generated members can't be annotated with `Kept` attributes directly // For some of them we have special attributes (backing fields for example), but it's impractical to define @@ -137,13 +156,14 @@ protected virtual void VerifyTypeDefinition (TypeDefinition original, TypeDefini // we do want to validate. There's no specific use case right now, but I can easily imagine one // for more detailed testing of for example custom attributes on local functions, or similar. if (!IsCompilerGeneratedMember (original)) - Assert.Fail ($"Type `{original}' should have been removed"); + yield return $"Type `{original}' should have been removed"; } bool prev = checkNames; checkNames |= original.HasAttribute (nameof (VerifyMetadataNamesAttribute)); - VerifyTypeDefinitionKept (original, linked); + foreach (var err in VerifyTypeDefinitionKept (original, linked)) + yield return err; checkNames = prev; @@ -151,7 +171,9 @@ protected virtual void VerifyTypeDefinition (TypeDefinition original, TypeDefini foreach (var attr in original.CustomAttributes.Where (l => l.AttributeType.Name == nameof (CreatedMemberAttribute))) { var newName = original.FullName + "::" + attr.ConstructorArguments[0].Value.ToString (); - Assert.AreEqual (1, linkedMembers.RemoveWhere (l => l.Contains (newName)), $"Newly created member '{newName}' was not found"); + if (1 != linkedMembers.RemoveWhere (l => l.Contains (newName))) { + yield return $"Newly created member '{newName}' was not found"; + } } } } @@ -159,23 +181,21 @@ protected virtual void VerifyTypeDefinition (TypeDefinition original, TypeDefini /// /// Validates that all instances on a member are valid (i.e. ILLink recorded a marked dependency described in the attribute) /// - void VerifyKeptByAttributes (IMemberDefinition src, IMemberDefinition linked) + IEnumerable VerifyKeptByAttributes (IMemberDefinition src, IMemberDefinition linked) { - foreach (var keptByAttribute in src.CustomAttributes.Where (ca => ca.AttributeType.IsTypeOf ())) - VerifyKeptByAttribute (linked.FullName, keptByAttribute); + return src.CustomAttributes.Where (ca => ca.AttributeType.IsTypeOf ()).SelectMany (keptByAttribute => VerifyKeptByAttribute (linked.FullName, keptByAttribute)); } /// /// Validates that all instances on an attribute provider are valid (i.e. ILLink recorded a marked dependency described in the attribute) /// is the attribute provider that may have a , and is the 'FullName' of . /// - void VerifyKeptByAttributes (ICustomAttributeProvider src, string attributeProviderFullName) + IEnumerable VerifyKeptByAttributes (ICustomAttributeProvider src, string attributeProviderFullName) { - foreach (var keptByAttribute in src.CustomAttributes.Where (ca => ca.AttributeType.IsTypeOf ())) - VerifyKeptByAttribute (attributeProviderFullName, keptByAttribute); + return src.CustomAttributes.Where (ca => ca.AttributeType.IsTypeOf ()).SelectMany (attr => VerifyKeptByAttribute (attributeProviderFullName, attr)); } - void VerifyKeptByAttribute (string keptAttributeProviderName, CustomAttribute attribute) + IEnumerable VerifyKeptByAttribute (string keptAttributeProviderName, CustomAttribute attribute) { // public KeptByAttribute (string dependencyProvider, string reason) { } // public KeptByAttribute (Type dependencyProvider, string reason) { } @@ -213,57 +233,72 @@ void VerifyKeptByAttribute (string keptAttributeProviderName, CustomAttribute at foreach (var dep in this.linkedTestCase.Customizations.DependencyRecorder.Dependencies) { if (dep == expectedDependency) { - return; + yield break; } } - string errorMessage = $"{keptAttributeProviderName} was expected to be kept by {expectedDependency.Source} with reason {expectedDependency.DependencyKind.ToString ()}."; - Assert.Fail (errorMessage); + yield return $"{keptAttributeProviderName} was expected to be kept by {expectedDependency.Source} with reason {expectedDependency.DependencyKind.ToString ()}."; } - protected virtual void VerifyTypeDefinitionKept (TypeDefinition original, TypeDefinition linked) + protected virtual IEnumerable VerifyTypeDefinitionKept (TypeDefinition original, TypeDefinition linked) { - if (linked == null) - Assert.Fail ($"Type `{original}' should have been kept"); + if (linked == null) { + yield return $"Type `{original}' should have been kept"; + yield break; + } // Skip verification of type metadata for compiler generated types (we don't currently need it yet) if (!IsCompilerGeneratedMember (original)) { - VerifyKeptByAttributes (original, linked); + foreach (var err in VerifyKeptByAttributes (original, linked)) + yield return err; if (!original.IsInterface) - VerifyBaseType (original, linked); - - VerifyInterfaces (original, linked); - VerifyPseudoAttributes (original, linked); - VerifyGenericParameters (original, linked, compilerGenerated: false); - VerifyCustomAttributes (original, linked); - VerifySecurityAttributes (original, linked); - - VerifyFixedBufferFields (original, linked); + foreach (var err in VerifyBaseType (original, linked)) + yield return err; + + foreach (var err in VerifyInterfaces (original, linked)) + yield return err; + foreach (var err in VerifyPseudoAttributes (original, linked)) + yield return err; + foreach (var err in VerifyGenericParameters (original, linked, compilerGenerated: false)) + yield return err; + foreach (var err in VerifyCustomAttributes (original, linked)) + yield return err; + foreach (var err in VerifySecurityAttributes (original, linked)) + yield return err; + + foreach (var err in VerifyFixedBufferFields (original, linked)) + yield return err; } // Need to check delegate cache fields before the normal field check - VerifyDelegateBackingFields (original, linked); - VerifyPrivateImplementationDetails (original, linked); + foreach (var err in VerifyDelegateBackingFields (original, linked)) + yield return err; + foreach (var err in VerifyPrivateImplementationDetails (original, linked)) + yield return err; foreach (var td in original.NestedTypes) { - VerifyTypeDefinition (td, linked?.NestedTypes.FirstOrDefault (l => td.FullName == l.FullName)); + foreach (var err in VerifyTypeDefinition (td, linked?.NestedTypes.FirstOrDefault (l => td.FullName == l.FullName))) + yield return err; linkedMembers.Remove (td.FullName); } // Need to check properties before fields so that the KeptBackingFieldAttribute is handled correctly foreach (var p in original.Properties) { - VerifyProperty (p, linked?.Properties.FirstOrDefault (l => p.Name == l.Name), linked); + foreach (var err in VerifyProperty (p, linked?.Properties.FirstOrDefault (l => p.Name == l.Name), linked)) + yield return err; linkedMembers.Remove (p.FullName); } // Need to check events before fields so that the KeptBackingFieldAttribute is handled correctly foreach (var e in original.Events) { - VerifyEvent (e, linked?.Events.FirstOrDefault (l => e.Name == l.Name), linked); + foreach (var err in VerifyEvent (e, linked?.Events.FirstOrDefault (l => e.Name == l.Name), linked)) + yield return err; linkedMembers.Remove (e.FullName); } foreach (var f in original.Fields) { if (verifiedGeneratedFields.Contains (f.FullName)) continue; - VerifyField (f, linked?.Fields.FirstOrDefault (l => f.Name == l.Name)); + foreach (var err in VerifyField (f, linked?.Fields.FirstOrDefault (l => f.Name == l.Name))) + yield return err; linkedMembers.Remove (f.FullName); } @@ -271,12 +306,13 @@ protected virtual void VerifyTypeDefinitionKept (TypeDefinition original, TypeDe if (verifiedEventMethods.Contains (m.FullName)) continue; var msign = m.GetSignature (); - VerifyMethod (m, linked?.Methods.FirstOrDefault (l => msign == l.GetSignature ())); + foreach (var err in VerifyMethod (m, linked?.Methods.FirstOrDefault (l => msign == l.GetSignature ()))) + yield return err; linkedMembers.Remove (m.FullName); } } - void VerifyBaseType (TypeDefinition src, TypeDefinition linked) + IEnumerable VerifyBaseType (TypeDefinition src, TypeDefinition linked) { string expectedBaseName; var expectedBaseGenericAttr = src.CustomAttributes.FirstOrDefault (w => w.AttributeType.Name == nameof (KeptBaseTypeAttribute) && w.ConstructorArguments.Count > 1); @@ -286,24 +322,31 @@ void VerifyBaseType (TypeDefinition src, TypeDefinition linked) var defaultBaseType = src.IsEnum ? "System.Enum" : src.IsValueType ? "System.ValueType" : "System.Object"; expectedBaseName = GetCustomAttributeCtorValues (src, nameof (KeptBaseTypeAttribute)).FirstOrDefault ()?.ToString () ?? defaultBaseType; } - Assert.AreEqual (expectedBaseName, linked.BaseType?.FullName, $"Incorrect base type on : {linked.Name}"); + if (expectedBaseName != linked.BaseType?.FullName) + yield return $"Incorrect base type on : {linked.Name}"; } - void VerifyInterfaces (TypeDefinition src, TypeDefinition linked) + IEnumerable VerifyInterfaces (TypeDefinition src, TypeDefinition linked) { var expectedInterfaces = new HashSet (src.CustomAttributes .Where (w => w.AttributeType.Name == nameof (KeptInterfaceAttribute)) .Select (FormatBaseOrInterfaceAttributeValue)); if (expectedInterfaces.Count == 0) { - Assert.IsFalse (linked.HasInterfaces, $"Type `{src}' has unexpected interfaces"); + if (linked.HasInterfaces) { + yield return $"Type `{src}' has unexpected interfaces"; + } } else { foreach (var iface in linked.Interfaces) { if (!expectedInterfaces.Remove (iface.InterfaceType.FullName)) { - Assert.IsTrue (expectedInterfaces.Remove (iface.InterfaceType.Resolve ().FullName), $"Type `{src}' interface `{iface.InterfaceType.Resolve ().FullName}' should have been removed"); + if (!expectedInterfaces.Remove (iface.InterfaceType.Resolve ().FullName)) { + yield return $"Type `{src}' interface `{iface.InterfaceType.FullName}' should have been removed"; + } } } - Assert.IsEmpty (expectedInterfaces, $"Expected interfaces were not found on {src}"); + if (expectedInterfaces.Any ()) { + yield return $"Expected interfaces were not found on {src}"; + } } } @@ -377,7 +420,7 @@ static string FormatBaseOrInterfaceAttributeValue (CustomAttribute attr) return builder.ToString (); } - void VerifyField (FieldDefinition src, FieldDefinition linked) + IEnumerable VerifyField (FieldDefinition src, FieldDefinition linked) { bool compilerGenerated = IsCompilerGeneratedMember (src); bool expectedKept = ShouldBeKept (src) || @@ -385,115 +428,138 @@ void VerifyField (FieldDefinition src, FieldDefinition linked) if (!expectedKept) { if (linked != null) - Assert.Fail ($"Field `{src}' should have been removed"); + yield return $"Field `{src}' should have been removed"; - return; + yield break; } - VerifyFieldKept (src, linked, compilerGenerated); + foreach (var err in VerifyFieldKept (src, linked, compilerGenerated)) + yield return err; } - void VerifyFieldKept (FieldDefinition src, FieldDefinition linked, bool compilerGenerated) + IEnumerable VerifyFieldKept (FieldDefinition src, FieldDefinition linked, bool compilerGenerated) { - if (linked == null) - Assert.Fail ($"Field `{src}' should have been kept"); + if (linked == null) { + yield return $"Field `{src}' should have been kept"; + yield break; + } - Assert.AreEqual (src?.Constant, linked?.Constant, $"Field `{src}' value"); + if (!src?.Constant?.Equals (linked?.Constant) == true) + yield return $"Field `{src}' value was expected to be {src?.Constant} but was {linked?.Constant}"; - VerifyKeptByAttributes (src, linked); + foreach (var err in VerifyKeptByAttributes (src, linked)) + yield return err; VerifyPseudoAttributes (src, linked); if (!compilerGenerated) - VerifyCustomAttributes (src, linked); + foreach (var err in VerifyCustomAttributes (src, linked)) + yield return err; } - void VerifyProperty (PropertyDefinition src, PropertyDefinition linked, TypeDefinition linkedType) + IEnumerable VerifyProperty (PropertyDefinition src, PropertyDefinition linked, TypeDefinition linkedType) { - VerifyMemberBackingField (src, linkedType); + foreach (var err in VerifyMemberBackingField (src, linkedType)) + yield return err; bool compilerGenerated = IsCompilerGeneratedMember (src); bool expectedKept = ShouldBeKept (src) || compilerGenerated; if (!expectedKept) { if (linked != null) - Assert.Fail ($"Property `{src}' should have been removed"); + yield return $"Property `{src}' should have been removed"; - return; + yield break; } - if (linked == null) - Assert.Fail ($"Property `{src}' should have been kept"); + if (linked == null) { + yield return $"Property `{src}' should have been kept"; + yield break; + } - Assert.AreEqual (src?.Constant, linked?.Constant, $"Property `{src}' value"); + if (src?.Constant != linked?.Constant) + yield return $"Property `{src}' value"; - VerifyKeptByAttributes (src, linked); - VerifyPseudoAttributes (src, linked); + foreach (var err in VerifyKeptByAttributes (src, linked)) + yield return err; + foreach (var err in VerifyPseudoAttributes (src, linked)) + yield return err; if (!compilerGenerated) - VerifyCustomAttributes (src, linked); + foreach (var err in VerifyCustomAttributes (src, linked)) + yield return err; } - void VerifyEvent (EventDefinition src, EventDefinition linked, TypeDefinition linkedType) + IEnumerable VerifyEvent (EventDefinition src, EventDefinition linked, TypeDefinition linkedType) { - VerifyMemberBackingField (src, linkedType); + foreach (var err in VerifyMemberBackingField (src, linkedType)) + yield return err; bool compilerGenerated = IsCompilerGeneratedMember (src); bool expectedKept = ShouldBeKept (src) || compilerGenerated; if (!expectedKept) { if (linked != null) - Assert.Fail ($"Event `{src}' should have been removed"); + yield return $"Event `{src}' should have been removed"; - return; + yield break; } - if (linked == null) - Assert.Fail ($"Event `{src}' should have been kept"); + if (linked == null) { + yield return $"Event `{src}' should have been kept"; + yield break; + } if (src.CustomAttributes.Any (attr => attr.AttributeType.Name == nameof (KeptEventAddMethodAttribute))) { - VerifyMethodInternal (src.AddMethod, linked.AddMethod, true, compilerGenerated); + foreach (var err in VerifyMethodInternal (src.AddMethod, linked.AddMethod, true, compilerGenerated)) + yield return err; verifiedEventMethods.Add (src.AddMethod.FullName); linkedMembers.Remove (src.AddMethod.FullName); } if (src.CustomAttributes.Any (attr => attr.AttributeType.Name == nameof (KeptEventRemoveMethodAttribute))) { - VerifyMethodInternal (src.RemoveMethod, linked.RemoveMethod, true, compilerGenerated); + foreach (var err in VerifyMethodInternal (src.RemoveMethod, linked.RemoveMethod, true, compilerGenerated)) + yield return err; verifiedEventMethods.Add (src.RemoveMethod.FullName); linkedMembers.Remove (src.RemoveMethod.FullName); } - VerifyKeptByAttributes (src, linked); - VerifyPseudoAttributes (src, linked); + foreach (var err in VerifyKeptByAttributes (src, linked)) + yield return err; + foreach (var err in VerifyPseudoAttributes (src, linked)) + yield return err; if (!compilerGenerated) - VerifyCustomAttributes (src, linked); + foreach (var err in VerifyCustomAttributes (src, linked)) + yield return err; } - void VerifyMethod (MethodDefinition src, MethodDefinition linked) + IEnumerable VerifyMethod (MethodDefinition src, MethodDefinition linked) { bool compilerGenerated = IsCompilerGeneratedMember (src); bool expectedKept = ShouldMethodBeKept (src); - VerifyMethodInternal (src, linked, expectedKept, compilerGenerated); + foreach (var err in VerifyMethodInternal (src, linked, expectedKept, compilerGenerated)) + yield return err; } - void VerifyMethodInternal (MethodDefinition src, MethodDefinition linked, bool expectedKept, bool compilerGenerated) + IEnumerable VerifyMethodInternal (MethodDefinition src, MethodDefinition linked, bool expectedKept, bool compilerGenerated) { if (!expectedKept) { if (linked == null) - return; + yield break; // Similar to comment on types, compiler-generated methods can't be annotated with Kept attribute directly // so we're not going to validate kept/remove on them. Note that we're still going to go validate "into" them // to check for other properties (like parameter name presence/removal for example) if (!compilerGenerated) - Assert.Fail ($"Method `{src.FullName}' should have been removed"); + yield return $"Method `{src.FullName}' should have been removed"; } - VerifyMethodKept (src, linked, compilerGenerated); + foreach (var err in VerifyMethodKept (src, linked, compilerGenerated)) + yield return err; } - void VerifyMemberBackingField (IMemberDefinition src, TypeDefinition linkedType) + IEnumerable VerifyMemberBackingField (IMemberDefinition src, TypeDefinition linkedType) { var keptBackingFieldAttribute = src.CustomAttributes.FirstOrDefault (attr => attr.AttributeType.Name == nameof (KeptBackingFieldAttribute)); if (keptBackingFieldAttribute == null) - return; + yield break; var backingFieldName = src.MetadataToken.TokenType == TokenType.Property ? $"<{src.Name}>k__BackingField" : src.Name; @@ -508,44 +574,61 @@ void VerifyMemberBackingField (IMemberDefinition src, TypeDefinition linkedType) srcField = src.DeclaringType.Fields.FirstOrDefault (f => f.Name == backingFieldName); } - if (srcField == null) - Assert.Fail ($"{src.MetadataToken.TokenType} `{src}', could not locate the expected backing field {backingFieldName}"); + if (srcField == null) { + yield return $"{src.MetadataToken.TokenType} `{src}', could not locate the expected backing field {backingFieldName}"; + yield break; + } - VerifyFieldKept (srcField, linkedType?.Fields.FirstOrDefault (l => srcField.Name == l.Name), compilerGenerated: true); + foreach (var err in VerifyFieldKept (srcField, linkedType?.Fields.FirstOrDefault (l => srcField.Name == l.Name), compilerGenerated: true)) + yield return err; verifiedGeneratedFields.Add (srcField.FullName); linkedMembers.Remove (srcField.FullName); } - protected virtual void VerifyMethodKept (MethodDefinition src, MethodDefinition linked, bool compilerGenerated) + protected virtual IEnumerable VerifyMethodKept (MethodDefinition src, MethodDefinition linked, bool compilerGenerated) { - if (linked == null) - Assert.Fail ($"Method `{src.FullName}' should have been kept"); + if (linked == null) { + yield return $"Method `{src.FullName}' should have been kept"; + yield break; + } - VerifyPseudoAttributes (src, linked); - VerifyGenericParameters (src, linked, compilerGenerated); + foreach (var err in VerifyPseudoAttributes (src, linked)) + yield return err; + foreach (var err in VerifyGenericParameters (src, linked, compilerGenerated)) + yield return err; if (!compilerGenerated) { - VerifyCustomAttributes (src, linked); - VerifyCustomAttributes (src.MethodReturnType, linked.MethodReturnType); + foreach (var err in VerifyCustomAttributes (src, linked)) + yield return err; + foreach (var err in VerifyCustomAttributes (src.MethodReturnType, linked.MethodReturnType)) + yield return err; + } - VerifyParameters (src, linked, compilerGenerated); - VerifySecurityAttributes (src, linked); - VerifyArrayInitializers (src, linked); - VerifyMethodBody (src, linked); - VerifyKeptByAttributes (src, linked); + foreach (var err in VerifyParameters (src, linked, compilerGenerated)) + yield return err; + foreach (var err in VerifySecurityAttributes (src, linked)) + yield return err; + foreach (var err in VerifyArrayInitializers (src, linked)) + yield return err; + foreach (var err in VerifyMethodBody (src, linked)) + yield return err; + foreach (var err in VerifyKeptByAttributes (src, linked)) + yield return err; } - protected virtual void VerifyMethodBody (MethodDefinition src, MethodDefinition linked) + protected virtual IEnumerable VerifyMethodBody (MethodDefinition src, MethodDefinition linked) { if (!src.HasBody) - return; + yield break; - VerifyInstructions (src, linked); - VerifyLocals (src, linked); + foreach (var err in VerifyInstructions (src, linked)) + yield return err; + foreach (var err in VerifyLocals (src, linked)) + yield return err; } - protected static void VerifyInstructions (MethodDefinition src, MethodDefinition linked) + protected static IEnumerable VerifyInstructions (MethodDefinition src, MethodDefinition linked) { - VerifyBodyProperties ( + var errs = VerifyBodyProperties ( src, linked, nameof (ExpectedInstructionSequenceAttribute), @@ -553,6 +636,8 @@ protected static void VerifyInstructions (MethodDefinition src, MethodDefinition "instructions", m => FormatMethodBody (m.Body), attr => GetStringArrayAttributeValue (attr).ToArray ()); + foreach (var err in errs) + yield return err; } public static string[] FormatMethodBody (MethodBody body) @@ -669,9 +754,9 @@ static string FormatInstruction (Instruction instr) } } - static void VerifyLocals (MethodDefinition src, MethodDefinition linked) + static IEnumerable VerifyLocals (MethodDefinition src, MethodDefinition linked) { - VerifyBodyProperties ( + var errs = VerifyBodyProperties ( src, linked, nameof (ExpectedLocalsSequenceAttribute), @@ -679,9 +764,11 @@ static void VerifyLocals (MethodDefinition src, MethodDefinition linked) "locals", m => m.Body.Variables.Select (v => v.VariableType.ToString ()).ToArray (), attr => GetStringOrTypeArrayAttributeValue (attr).ToArray ()); + foreach (var err in errs) + yield return err; } - public static void VerifyBodyProperties (MethodDefinition src, MethodDefinition linked, string sequenceAttributeName, string expectModifiedAttributeName, + public static IEnumerable VerifyBodyProperties (MethodDefinition src, MethodDefinition linked, string sequenceAttributeName, string expectModifiedAttributeName, string propertyDescription, Func valueCollector, Func getExpectFromSequenceAttribute) @@ -691,30 +778,27 @@ public static void VerifyBodyProperties (MethodDefinition src, MethodDefinition var srcValues = valueCollector (src); if (src.CustomAttributes.Any (attr => attr.AttributeType.Name == expectModifiedAttributeName)) { - Assert.That ( - linkedValues, - Is.Not.EqualTo (srcValues), - $"Expected method `{src} to have {propertyDescription} modified, however, the {propertyDescription} were the same as the original\n{FormattingUtils.FormatSequenceCompareFailureMessage (linkedValues, srcValues)}"); + if (linkedValues.ToHashSet ().SetEquals (srcValues.ToHashSet ())) { + yield return $"Expected method `{src} to have it's {propertyDescription} modified, however, the {propertyDescription} were the same as the original\n{FormattingUtils.FormatSequenceCompareFailureMessage (linkedValues, srcValues)}"; + } } else if (expectedSequenceAttribute != null) { var expected = getExpectFromSequenceAttribute (expectedSequenceAttribute).ToArray (); - Assert.That ( - linkedValues, - Is.EqualTo (expected), - $"Expected method `{src} to have it's {propertyDescription} modified, however, the sequence of {propertyDescription} does not match the expected value\n{FormattingUtils.FormatSequenceCompareFailureMessage2 (linkedValues, expected, srcValues)}"); + if (!linkedValues.ToHashSet ().SetEquals (expected.ToHashSet ())) { + yield return $"Expected method `{src} to have it's {propertyDescription} modified, however, the sequence of {propertyDescription} does not match the expected value\n{FormattingUtils.FormatSequenceCompareFailureMessage2 (linkedValues, expected, srcValues)}"; + } } else { - Assert.That ( - linkedValues, - Is.EqualTo (srcValues), - $"Expected method `{src} to have it's {propertyDescription} unchanged, however, the {propertyDescription} differ from the original\n{FormattingUtils.FormatSequenceCompareFailureMessage (linkedValues, srcValues)}"); + if (!linkedValues.ToHashSet ().SetEquals (srcValues.ToHashSet ())) { + yield return $"Expected method `{src} to have it's {propertyDescription} unchanged, however, the {propertyDescription} differ from the original\n{FormattingUtils.FormatSequenceCompareFailureMessage (linkedValues, srcValues)}"; + } } } - void VerifyReferences (AssemblyDefinition original, AssemblyDefinition linked) + IEnumerable VerifyReferences (AssemblyDefinition original, AssemblyDefinition linked) { var expected = original.MainModule.AllDefinedTypes () .SelectMany (t => GetCustomAttributeCtorValues (t, nameof (KeptReferenceAttribute))) .Select (ReduceAssemblyFileNameOrNameToNameOnly) - .ToArray (); + .ToHashSet (); /* - The test case will always need to have at least 1 reference. @@ -725,14 +809,15 @@ void VerifyReferences (AssemblyDefinition original, AssemblyDefinition linked) Once 1 kept reference attribute is used, the test will need to define all of of it's expected references */ - if (expected.Length == 0) - return; + if (expected.Count == 0) + yield break; var actual = linked.MainModule.AssemblyReferences .Select (name => name.Name) - .ToArray (); + .ToHashSet (); - Assert.That (actual, Is.EquivalentTo (expected)); + if (!expected.SetEquals (actual)) + yield return $"Expected references `{string.Join (", ", expected)}` do not match actual references `{string.Join (", ", actual)}`"; } string ReduceAssemblyFileNameOrNameToNameOnly (string fileNameOrAssemblyName) @@ -744,7 +829,7 @@ string ReduceAssemblyFileNameOrNameToNameOnly (string fileNameOrAssemblyName) return fileNameOrAssemblyName; } - void VerifyResources (AssemblyDefinition original, AssemblyDefinition linked) + IEnumerable VerifyResources (AssemblyDefinition original, AssemblyDefinition linked) { var expectedResourceNames = original.MainModule.AllDefinedTypes () .SelectMany (t => GetCustomAttributeCtorValues (t, nameof (KeptResourceAttribute))) @@ -752,111 +837,137 @@ void VerifyResources (AssemblyDefinition original, AssemblyDefinition linked) foreach (var resource in linked.MainModule.Resources) { if (!expectedResourceNames.Remove (resource.Name)) - Assert.Fail ($"Resource '{resource.Name}' should be removed."); + yield return $"Resource '{resource.Name}' should be removed."; EmbeddedResource embeddedResource = (EmbeddedResource) resource; var expectedResource = (EmbeddedResource) original.MainModule.Resources.First (r => r.Name == resource.Name); - Assert.That (embeddedResource.GetResourceData (), Is.EquivalentTo (expectedResource.GetResourceData ()), $"Resource '{resource.Name}' data doesn't match."); + if (!embeddedResource.GetResourceData ().SequenceEqual (expectedResource.GetResourceData ())) + yield return $"Resource '{resource.Name}' data doesn't match."; } - Assert.IsEmpty (expectedResourceNames, $"Resource '{expectedResourceNames.FirstOrDefault ()}' should be kept."); + if (expectedResourceNames.Any ()) + yield return $"Resource '{expectedResourceNames.FirstOrDefault ()}' should be kept."; } - void VerifyExportedTypes (AssemblyDefinition original, AssemblyDefinition linked) + IEnumerable VerifyExportedTypes (AssemblyDefinition original, AssemblyDefinition linked) { var expectedTypes = original.MainModule.AllDefinedTypes () - .SelectMany (t => GetCustomAttributeCtorValues (t, nameof (KeptExportedTypeAttribute)).Select (l => l.FullName)).ToArray (); + .SelectMany (t => GetCustomAttributeCtorValues (t, nameof (KeptExportedTypeAttribute)).Select (l => l.FullName)); - Assert.That (linked.MainModule.ExportedTypes.Select (l => l.FullName), Is.EquivalentTo (expectedTypes)); + if (!linked.MainModule.ExportedTypes.Select (l => l.FullName).ToHashSet ().SetEquals (expectedTypes.ToHashSet ())) + yield return $"Exported types do not match expected."; } - protected virtual void VerifyPseudoAttributes (MethodDefinition src, MethodDefinition linked) + protected virtual IEnumerable VerifyPseudoAttributes (MethodDefinition src, MethodDefinition linked) { var expected = (MethodAttributes) GetExpectedPseudoAttributeValue (src, (uint) src.Attributes); - Assert.AreEqual (expected, linked.Attributes, $"Method `{src}' pseudo attributes did not match expected"); + if (expected != linked.Attributes) + yield return $"Method `{src}' pseudo attributes did not match expected"; } - protected virtual void VerifyPseudoAttributes (TypeDefinition src, TypeDefinition linked) + protected virtual IEnumerable VerifyPseudoAttributes (TypeDefinition src, TypeDefinition linked) { var expected = (TypeAttributes) GetExpectedPseudoAttributeValue (src, (uint) src.Attributes); - Assert.AreEqual (expected, linked.Attributes, $"Type `{src}' pseudo attributes did not match expected"); + if (expected == linked.Attributes) + yield break; + + yield return $"Type `{src}' pseudo attributes did not match expected"; } - protected virtual void VerifyPseudoAttributes (FieldDefinition src, FieldDefinition linked) + protected virtual IEnumerable VerifyPseudoAttributes (FieldDefinition src, FieldDefinition linked) { var expected = (FieldAttributes) GetExpectedPseudoAttributeValue (src, (uint) src.Attributes); - Assert.AreEqual (expected, linked.Attributes, $"Field `{src}' pseudo attributes did not match expected"); + if (expected != linked.Attributes) + yield return $"Field `{src}' pseudo attributes did not match expected"; } - protected virtual void VerifyPseudoAttributes (PropertyDefinition src, PropertyDefinition linked) + protected virtual IEnumerable VerifyPseudoAttributes (PropertyDefinition src, PropertyDefinition linked) { var expected = (PropertyAttributes) GetExpectedPseudoAttributeValue (src, (uint) src.Attributes); - Assert.AreEqual (expected, linked.Attributes, $"Property `{src}' pseudo attributes did not match expected"); + if (expected != linked.Attributes) + yield return $"Property `{src}' pseudo attributes did not match expected"; } - protected virtual void VerifyPseudoAttributes (EventDefinition src, EventDefinition linked) + protected virtual IEnumerable VerifyPseudoAttributes (EventDefinition src, EventDefinition linked) { var expected = (EventAttributes) GetExpectedPseudoAttributeValue (src, (uint) src.Attributes); - Assert.AreEqual (expected, linked.Attributes, $"Event `{src}' pseudo attributes did not match expected"); + if (expected != linked.Attributes) + yield return $"Event `{src}' pseudo attributes did not match expected"; } - protected virtual void VerifyCustomAttributes (ICustomAttributeProvider src, ICustomAttributeProvider linked) + protected virtual IEnumerable VerifyCustomAttributes (ICustomAttributeProvider src, ICustomAttributeProvider linked) { - var expectedAttrs = GetExpectedAttributes (src).ToList (); - var linkedAttrs = FilterLinkedAttributes (linked).ToList (); + var expectedAttrs = GetExpectedAttributes (src).ToHashSet (); + var linkedAttrs = FilterLinkedAttributes (linked).ToHashSet (); + if (!linkedAttrs.SetEquals (expectedAttrs)) { + var missing = $"Missing: {string.Join (", ", expectedAttrs.Except (linkedAttrs))}"; + var extra = $"Extra: {string.Join (", ", linkedAttrs.Except (expectedAttrs))}"; - Assert.That (linkedAttrs, Is.EquivalentTo (expectedAttrs), $"Custom attributes on `{src}' are not matching"); + yield return string.Join (Environment.NewLine, $"Custom attributes on `{src}' are not matching:", missing, extra); + } } - protected virtual void VerifySecurityAttributes (ICustomAttributeProvider src, ISecurityDeclarationProvider linked) + protected virtual IEnumerable VerifySecurityAttributes (ICustomAttributeProvider src, ISecurityDeclarationProvider linked) { var expectedAttrs = GetCustomAttributeCtorValues (src, nameof (KeptSecurityAttribute)) .Select (attr => attr.ToString ()) - .ToList (); + .ToHashSet (); - var linkedAttrs = FilterLinkedSecurityAttributes (linked).ToList (); + var linkedAttrs = FilterLinkedSecurityAttributes (linked).ToHashSet (); - Assert.That (linkedAttrs, Is.EquivalentTo (expectedAttrs), $"Security attributes on `{src}' are not matching"); + if (!linkedAttrs.SetEquals (expectedAttrs)) { + var missing = $"Missing: {string.Join (", ", expectedAttrs.Except (linkedAttrs))}"; + var extra = $"Extra: {string.Join (", ", linkedAttrs.Except (expectedAttrs))}"; + yield return string.Join ($"Security attributes on `{src}' are not matching:", missing, extra); + } } - void VerifyPrivateImplementationDetails (TypeDefinition original, TypeDefinition linked) + IEnumerable VerifyPrivateImplementationDetails (TypeDefinition original, TypeDefinition linked) { var expectedImplementationDetailsMethods = GetCustomAttributeCtorValues (original, nameof (KeptPrivateImplementationDetailsAttribute)) .Select (attr => attr.ToString ()) .ToList (); if (expectedImplementationDetailsMethods.Count == 0) - return; - - VerifyPrivateImplementationDetailsType (original.Module, linked.Module, out TypeDefinition srcImplementationDetails, out TypeDefinition linkedImplementationDetails); + yield break; + + TypeDefinition srcImplementationDetails; + TypeDefinition linkedImplementationDetails; + if (VerifyPrivateImplementationDetailsType (original.Module, linked.Module, out srcImplementationDetails, out linkedImplementationDetails, out var errors)) + { + foreach (var err in errors) + yield return err; + } foreach (var methodName in expectedImplementationDetailsMethods) { var originalMethod = srcImplementationDetails.Methods.FirstOrDefault (m => m.Name == methodName); if (originalMethod == null) - Assert.Fail ($"Could not locate original private implementation details method {methodName}"); + yield return $"Could not locate original private implementation details method {methodName}"; var linkedMethod = linkedImplementationDetails.Methods.FirstOrDefault (m => m.Name == methodName); - VerifyMethodKept (originalMethod, linkedMethod, compilerGenerated: true); + foreach (var erro in VerifyMethodKept (originalMethod, linkedMethod, compilerGenerated: true)) + yield return erro; linkedMembers.Remove (linkedMethod.FullName); } verifiedGeneratedTypes.Add (srcImplementationDetails.FullName); } - static void VerifyPrivateImplementationDetailsType (ModuleDefinition src, ModuleDefinition linked, out TypeDefinition srcImplementationDetails, out TypeDefinition linkedImplementationDetails) + static bool VerifyPrivateImplementationDetailsType (ModuleDefinition src, ModuleDefinition linked, out TypeDefinition srcImplementationDetails, out TypeDefinition linkedImplementationDetails, out IEnumerable errs) { + errs = []; srcImplementationDetails = src.Types.FirstOrDefault (t => IsPrivateImplementationDetailsType (t)); - if (srcImplementationDetails == null) - Assert.Fail ("Could not locate in the original assembly. Does your test use initializers?"); + errs.Append("Could not locate in the original assembly. Does your test use initializers?"); linkedImplementationDetails = linked.Types.FirstOrDefault (t => IsPrivateImplementationDetailsType (t)); - if (linkedImplementationDetails == null) - Assert.Fail ("Could not locate in the linked assembly"); + errs.Append("Could not locate in the linked assembly"); + + return !errs.Any(); } - protected virtual void VerifyArrayInitializers (MethodDefinition src, MethodDefinition linked) + protected virtual IEnumerable VerifyArrayInitializers (MethodDefinition src, MethodDefinition linked) { var expectedIndices = GetCustomAttributeCtorValues (src, nameof (KeptInitializerData)) .Cast () @@ -865,12 +976,17 @@ protected virtual void VerifyArrayInitializers (MethodDefinition src, MethodDefi var expectKeptAll = src.CustomAttributes.Any (attr => attr.AttributeType.Name == nameof (KeptInitializerData) && !attr.HasConstructorArguments); if (expectedIndices.Length == 0 && !expectKeptAll) - return; + yield break; if (!src.HasBody) - Assert.Fail ($"`{nameof (KeptInitializerData)}` cannot be used on methods that don't have bodies"); - - VerifyPrivateImplementationDetailsType (src.Module, linked.Module, out TypeDefinition srcImplementationDetails, out TypeDefinition linkedImplementationDetails); + yield return $"`{nameof (KeptInitializerData)}` cannot be used on methods that don't have bodies"; + TypeDefinition srcImplementationDetails; + TypeDefinition linkedImplementationDetails; + if (VerifyPrivateImplementationDetailsType (src.Module, linked.Module, out srcImplementationDetails, out linkedImplementationDetails, out var errs)) + { + foreach(var err in errs) + yield return err; + } var possibleInitializerFields = src.Body.Instructions .Where (ins => IsLdtokenOnPrivateImplementationDetails (srcImplementationDetails, ins)) @@ -878,32 +994,36 @@ protected virtual void VerifyArrayInitializers (MethodDefinition src, MethodDefi .ToArray (); if (possibleInitializerFields.Length == 0) - Assert.Fail ($"`{src}` does not make use of any initializers"); + yield return $"`{src}` does not make use of any initializers"; if (expectKeptAll) { foreach (var srcField in possibleInitializerFields) { var linkedField = linkedImplementationDetails.Fields.FirstOrDefault (f => f.InitialValue.SequenceEqual (srcField.InitialValue)); - VerifyInitializerField (srcField, linkedField); + foreach (var err in VerifyInitializerField (srcField, linkedField)) + yield return err; } } else { foreach (var index in expectedIndices) { if (index < 0 || index > possibleInitializerFields.Length) - Assert.Fail ($"Invalid expected index `{index}` in {src}. Value must be between 0 and {expectedIndices.Length}"); + yield return $"Invalid expected index `{index}` in {src}. Value must be between 0 and {expectedIndices.Length}"; var srcField = possibleInitializerFields[index]; var linkedField = linkedImplementationDetails.Fields.FirstOrDefault (f => f.InitialValue.SequenceEqual (srcField.InitialValue)); - VerifyInitializerField (srcField, linkedField); + foreach (var err in VerifyInitializerField (srcField, linkedField)) + yield return err; } } } - void VerifyInitializerField (FieldDefinition src, FieldDefinition linked) + IEnumerable VerifyInitializerField (FieldDefinition src, FieldDefinition linked) { - VerifyFieldKept (src, linked, compilerGenerated: true); + foreach (var err in VerifyFieldKept (src, linked, compilerGenerated: true)) + yield return err; verifiedGeneratedFields.Add (linked.FullName); linkedMembers.Remove (linked.FullName); - VerifyTypeDefinitionKept (src.FieldType.Resolve (), linked.FieldType.Resolve ()); + foreach (var err in VerifyTypeDefinitionKept (src.FieldType.Resolve (), linked.FieldType.Resolve ())) + yield return err; linkedMembers.Remove (linked.FieldType.FullName); linkedMembers.Remove (linked.DeclaringType.FullName); verifiedGeneratedTypes.Add (linked.DeclaringType.FullName); @@ -977,7 +1097,7 @@ protected virtual IEnumerable FilterLinkedSecurityAttributes (ISecurityD .Select (attr => attr.AttributeType.ToString ()); } - void VerifyFixedBufferFields (TypeDefinition src, TypeDefinition linked) + IEnumerable VerifyFixedBufferFields (TypeDefinition src, TypeDefinition linked) { var fields = src.Fields.Where (f => f.CustomAttributes.Any (attr => attr.AttributeType.Name == nameof (KeptFixedBufferAttribute))); @@ -987,28 +1107,30 @@ void VerifyFixedBufferFields (TypeDefinition src, TypeDefinition linked) // while mcs and other versions of csc name it `__FixedBuffer0` var originalCompilerGeneratedBufferType = src.NestedTypes.FirstOrDefault (t => t.FullName.Contains ($"<{field.Name}>") && t.FullName.Contains ("__FixedBuffer")); if (originalCompilerGeneratedBufferType == null) - Assert.Fail ($"Could not locate original compiler generated fixed buffer type for field {field}"); + yield return $"Could not locate original compiler generated fixed buffer type for field {field}"; var linkedCompilerGeneratedBufferType = linked.NestedTypes.FirstOrDefault (t => t.Name == originalCompilerGeneratedBufferType.Name); if (linkedCompilerGeneratedBufferType == null) - Assert.Fail ($"Missing expected type {originalCompilerGeneratedBufferType}"); + yield return $"Missing expected type {originalCompilerGeneratedBufferType}"; // Have to verify the field before the type var originalElementField = originalCompilerGeneratedBufferType.Fields.FirstOrDefault (); if (originalElementField == null) - Assert.Fail ($"Could not locate original compiler generated FixedElementField on {originalCompilerGeneratedBufferType}"); + yield return $"Could not locate original compiler generated FixedElementField on {originalCompilerGeneratedBufferType}"; var linkedField = linkedCompilerGeneratedBufferType?.Fields.FirstOrDefault (); - VerifyFieldKept (originalElementField, linkedField, compilerGenerated: true); + foreach (var err in VerifyFieldKept (originalElementField, linkedField, compilerGenerated: true)) + yield return err; verifiedGeneratedFields.Add (originalElementField.FullName); linkedMembers.Remove (linkedField.FullName); - VerifyTypeDefinitionKept (originalCompilerGeneratedBufferType, linkedCompilerGeneratedBufferType); + foreach (var err in VerifyTypeDefinitionKept (originalCompilerGeneratedBufferType, linkedCompilerGeneratedBufferType)) + yield return err; verifiedGeneratedTypes.Add (originalCompilerGeneratedBufferType.FullName); } } - void VerifyDelegateBackingFields (TypeDefinition src, TypeDefinition linked) + IEnumerable VerifyDelegateBackingFields (TypeDefinition src, TypeDefinition linked) { var expectedFieldNames = src.CustomAttributes .Where (a => a.AttributeType.Name == nameof (KeptDelegateCacheFieldAttribute)) @@ -1017,7 +1139,7 @@ void VerifyDelegateBackingFields (TypeDefinition src, TypeDefinition linked) .ToList (); if (expectedFieldNames.Count == 0) - return; + yield break; foreach (var nestedType in src.NestedTypes) { if (!IsDelegateBackingFieldsType (nestedType)) @@ -1027,20 +1149,22 @@ void VerifyDelegateBackingFields (TypeDefinition src, TypeDefinition linked) foreach (var expectedFieldName in expectedFieldNames) { var originalField = nestedType.Fields.FirstOrDefault (f => f.Name == expectedFieldName); if (originalField is null) - Assert.Fail ($"Invalid expected delegate backing field {expectedFieldName} in {src}. This member was not in the unlinked assembly"); + yield return $"Invalid expected delegate backing field {expectedFieldName} in {src}. This member was not in the unlinked assembly"; var linkedField = linkedNestedType?.Fields.FirstOrDefault (f => f.Name == expectedFieldName); - VerifyFieldKept (originalField, linkedField, compilerGenerated: true); + foreach (var err in VerifyFieldKept (originalField, linkedField, compilerGenerated: true)) + yield return err; verifiedGeneratedFields.Add (linkedField.FullName); linkedMembers.Remove (linkedField.FullName); } - VerifyTypeDefinitionKept (nestedType, linkedNestedType); + foreach (var err in VerifyTypeDefinitionKept (nestedType, linkedNestedType)) + yield return err; verifiedGeneratedTypes.Add (linkedNestedType.FullName); } } - void VerifyGenericParameters (IGenericParameterProvider src, IGenericParameterProvider linked, bool compilerGenerated) + IEnumerable VerifyGenericParameters (IGenericParameterProvider src, IGenericParameterProvider linked, bool compilerGenerated) { Assert.AreEqual (src.HasGenericParameters, linked.HasGenericParameters); if (src.HasGenericParameters) { @@ -1050,38 +1174,48 @@ void VerifyGenericParameters (IGenericParameterProvider src, IGenericParameterPr var lnkp = linked.GenericParameters[i]; if (!compilerGenerated) { - VerifyCustomAttributes (srcp, lnkp); + foreach (var err in VerifyCustomAttributes (srcp, lnkp)) + yield return err; } if (checkNames) { if (srcp.CustomAttributes.Any (attr => attr.AttributeType.Name == nameof (RemovedNameValueAttribute))) { string name = (src.GenericParameterType == GenericParameterType.Method ? "!!" : "!") + srcp.Position; - Assert.AreEqual (name, lnkp.Name, "Expected empty generic parameter name"); + if (name != lnkp.Name) { + yield return $"Expected empty generic parameter name. Parameter {i} of {(src.ToString ())}"; + } } else { - Assert.AreEqual (srcp.Name, lnkp.Name, "Mismatch in generic parameter name"); + if (srcp.Name != lnkp.Name) { + yield return $"Mismatch in generic parameter name. Parameter {i} of {(src.ToString ())}"; + } } } } } } - void VerifyParameters (IMethodSignature src, IMethodSignature linked, bool compilerGenerated) + IEnumerable VerifyParameters (IMethodSignature src, IMethodSignature linked, bool compilerGenerated) { - Assert.AreEqual (src.HasParameters, linked.HasParameters); + if (src.HasParameters != linked.HasParameters) + yield return $"Mismatch in parameters. {src} has parameters: {src.HasParameters}, {linked} has parameters: {linked.HasParameters}"; if (src.HasParameters) { for (int i = 0; i < src.Parameters.Count; ++i) { var srcp = src.Parameters[i]; var lnkp = linked.Parameters[i]; if (!compilerGenerated) { - VerifyCustomAttributes (srcp, lnkp); + foreach (var err in VerifyCustomAttributes (srcp, lnkp)) + yield return err; } if (checkNames) { - if (srcp.CustomAttributes.Any (attr => attr.AttributeType.Name == nameof (RemovedNameValueAttribute))) - Assert.IsEmpty (lnkp.Name, $"Expected empty parameter name. Parameter {i} of {(src as MethodDefinition)}"); - else - Assert.AreEqual (srcp.Name, lnkp.Name, $"Mismatch in parameter name. Parameter {i} of {(src as MethodDefinition)}"); + if (srcp.CustomAttributes.Any (attr => attr.AttributeType.Name == nameof (RemovedNameValueAttribute))) { + if (lnkp.Name != string.Empty) + yield return $"Expected empty parameter name. Parameter {i} of {(src as MethodDefinition)}"; + } else { + if (srcp.Name != lnkp.Name) + yield return $"Mismatch in parameter name. Parameter {i} of {(src as MethodDefinition)}"; + } } } } @@ -1113,7 +1247,7 @@ protected virtual bool ShouldBeKept (T member, string signature = null) where private static IEnumerable GetActiveKeptAttributes (ICustomAttributeProvider provider, string attributeName) { - return provider.CustomAttributes.Where(ca => { + return provider.CustomAttributes.Where (ca => { if (ca.AttributeType.Name != attributeName) { return false; } @@ -1121,7 +1255,7 @@ private static IEnumerable GetActiveKeptAttributes (ICustomAttr object keptBy = ca.GetPropertyValue (nameof (KeptAttribute.By)); return keptBy is null ? true : ((Tool) keptBy).HasFlag (Tool.Trimmer); }); - } + } private static bool HasActiveKeptAttribute (ICustomAttributeProvider provider) {