diff --git a/ArchUnitNET/Domain/Extensions/TypeExtensions.cs b/ArchUnitNET/Domain/Extensions/TypeExtensions.cs index c542a2dd1..9cfb4260b 100644 --- a/ArchUnitNET/Domain/Extensions/TypeExtensions.cs +++ b/ArchUnitNET/Domain/Extensions/TypeExtensions.cs @@ -241,22 +241,22 @@ public static Attribute GetAttributeOfType(this IType type, Class attributeClass public static bool ResidesInNamespace(this IType e, string fullName) { - return e.Namespace.FullNameEquals(fullName); + return e.Namespace != null && e.Namespace.FullNameEquals(fullName); } public static bool ResidesInNamespaceMatching(this IType e, string pattern) { - return e.Namespace.FullNameMatches(pattern); + return e.Namespace != null && e.Namespace.FullNameMatches(pattern); } public static bool ResidesInAssembly(this IType e, string fullName) { - return e.Assembly.FullNameEquals(fullName); + return e.Assembly != null && e.Assembly.FullNameEquals(fullName); } public static bool ResidesInAssemblyMatching(this IType e, string pattern) { - return e.Assembly.FullNameMatches(pattern); + return e.Assembly != null && e.Assembly.FullNameMatches(pattern); } public static bool IsDeclaredAsFieldIn(this IType type, string fullName) diff --git a/ArchUnitNET/Fluent/Syntax/Elements/Types/TypeConditionsDefinition.cs b/ArchUnitNET/Fluent/Syntax/Elements/Types/TypeConditionsDefinition.cs index 49e046242..86fce0305 100644 --- a/ArchUnitNET/Fluent/Syntax/Elements/Types/TypeConditionsDefinition.cs +++ b/ArchUnitNET/Fluent/Syntax/Elements/Types/TypeConditionsDefinition.cs @@ -296,11 +296,25 @@ Architecture architecture return new OrderedArchitectureCondition(Condition, description); } + private static string DescribeNamespaceOf(IType type) + { + return type.Namespace == null + ? "does not reside in a namespace" + : "does reside in " + type.Namespace.FullName; + } + + private static string DescribeAssemblyOf(IType type) + { + return type.Assembly == null + ? "does not reside in an assembly" + : "does reside in " + type.Assembly.FullName; + } + public static IOrderedCondition ResideInNamespace(string fullName) { return new SimpleCondition( type => type.ResidesInNamespace(fullName), - obj => "does reside in " + obj.Namespace.FullName, + obj => DescribeNamespaceOf(obj), "reside in namespace with full name \"" + fullName + "\"" ); } @@ -309,7 +323,7 @@ public static IOrderedCondition ResideInNamespaceMatching(string patt { return new SimpleCondition( type => type.ResidesInNamespaceMatching(pattern), - obj => "does reside in " + obj.Namespace.FullName, + obj => DescribeNamespaceOf(obj), "reside in namespace with full name matching \"" + pattern + "\"" ); } @@ -318,7 +332,7 @@ public static IOrderedCondition ResideInAssembly(string fullName) { return new SimpleCondition( type => type.ResidesInAssembly(fullName), - obj => "does reside in " + obj.Assembly.FullName, + obj => DescribeAssemblyOf(obj), "reside in assembly with full name \"" + fullName + "\"" ); } @@ -327,7 +341,7 @@ public static IOrderedCondition ResideInAssemblyMatching(string patte { return new SimpleCondition( type => type.ResidesInAssemblyMatching(pattern), - obj => "does reside in " + obj.Assembly.FullName, + obj => DescribeAssemblyOf(obj), "reside in assembly with full name matching \"" + pattern + "\"" ); } @@ -339,9 +353,9 @@ params System.Reflection.Assembly[] moreAssemblies { bool Condition(TRuleType ruleType, Architecture architecture) { - return ruleType.Assembly.Equals(architecture.GetAssemblyOfAssembly(assembly)) + return Equals(ruleType.Assembly, architecture.GetAssemblyOfAssembly(assembly)) || moreAssemblies.Any(asm => - ruleType.Assembly.Equals(architecture.GetAssemblyOfAssembly(asm)) + Equals(ruleType.Assembly, architecture.GetAssemblyOfAssembly(asm)) ); } @@ -352,7 +366,7 @@ bool Condition(TRuleType ruleType, Architecture architecture) return new OrderedArchitectureCondition( Condition, - (type, architecture) => "does reside in " + type.Assembly.FullName, + (type, architecture) => DescribeAssemblyOf(type), description ); } @@ -375,7 +389,7 @@ bool Condition(TRuleType ruleType) return new SimpleCondition( Condition, - type => "does reside in " + type.Assembly.FullName, + type => DescribeAssemblyOf(type), description ); } @@ -706,7 +720,7 @@ public static IOrderedCondition NotResideInNamespace(string fullName) { return new SimpleCondition( type => !type.ResidesInNamespace(fullName), - obj => "does reside in " + obj.Namespace.FullName, + obj => DescribeNamespaceOf(obj), "not reside in namespace with full name \"" + fullName + "\"" ); } @@ -715,7 +729,7 @@ public static IOrderedCondition NotResideInNamespaceMatching(string p { return new SimpleCondition( type => !type.ResidesInNamespaceMatching(pattern), - obj => "does reside in " + obj.Namespace.FullName, + obj => DescribeNamespaceOf(obj), "not reside in namespace with full name matching \"" + pattern + "\"" ); } @@ -724,7 +738,7 @@ public static IOrderedCondition NotResideInAssembly(string fullName) { return new SimpleCondition( type => !type.ResidesInAssembly(fullName), - obj => "does reside in " + obj.Assembly.FullName, + obj => DescribeAssemblyOf(obj), "not reside in assembly with full name \"" + fullName + "\"" ); } @@ -733,7 +747,7 @@ public static IOrderedCondition NotResideInAssemblyMatching(string pa { return new SimpleCondition( type => !type.ResidesInAssemblyMatching(pattern), - obj => "does reside in " + obj.Assembly.FullName, + obj => DescribeAssemblyOf(obj), "not reside in assembly with full name matching \"" + pattern + "\"" ); } @@ -745,9 +759,9 @@ params System.Reflection.Assembly[] moreAssemblies { bool Condition(TRuleType ruleType, Architecture architecture) { - return !ruleType.Assembly.Equals(architecture.GetAssemblyOfAssembly(assembly)) + return !Equals(ruleType.Assembly, architecture.GetAssemblyOfAssembly(assembly)) && !moreAssemblies.Any(asm => - ruleType.Assembly.Equals(architecture.GetAssemblyOfAssembly(asm)) + Equals(ruleType.Assembly, architecture.GetAssemblyOfAssembly(asm)) ); } @@ -758,7 +772,7 @@ bool Condition(TRuleType ruleType, Architecture architecture) return new OrderedArchitectureCondition( Condition, - (type, architecture) => "does reside in " + type.Assembly.FullName, + (type, architecture) => DescribeAssemblyOf(type), description ); } @@ -781,7 +795,7 @@ bool Condition(TRuleType ruleType) return new SimpleCondition( Condition, - type => "does reside in " + type.Assembly.FullName, + type => DescribeAssemblyOf(type), description ); } diff --git a/ArchUnitNETTests/Domain/FunctionPointerTests.cs b/ArchUnitNETTests/Domain/FunctionPointerTests.cs new file mode 100644 index 000000000..8bd4918ce --- /dev/null +++ b/ArchUnitNETTests/Domain/FunctionPointerTests.cs @@ -0,0 +1,155 @@ +using System.Linq; +using ArchUnitNET.Domain; +using ArchUnitNET.Domain.Extensions; +using ArchUnitNET.Fluent.Extensions; +using Xunit; +using static ArchUnitNET.Fluent.ArchRuleDefinition; + +namespace ArchUnitNETTests.Domain +{ + public class FunctionPointerTests + { + private static readonly Architecture Architecture = + StaticTestArchitectures.ArchUnitNETTestArchitecture; + + private readonly IType _functionPointer; + + public FunctionPointerTests() + { + _functionPointer = Architecture + .GetClassOfType(typeof(ClassWithFunctionPointerField)) + .GetFieldMembers() + .Single() + .Type; + } + + [Fact] + public void FunctionPointerHasNoNamespaceOrAssembly() + { + Assert.IsType(_functionPointer); + Assert.Contains(_functionPointer, Architecture.ReferencedTypes); + Assert.Null(_functionPointer.Namespace); + Assert.Null(_functionPointer.Assembly); + } + + [Fact] + public void NamespaceExtensionsReturnFalseForTypeWithoutNamespace() + { + Assert.False(_functionPointer.ResidesInNamespace("System")); + Assert.False(_functionPointer.ResidesInNamespaceMatching(".*")); + } + + [Fact] + public void AssemblyExtensionsReturnFalseForTypeWithoutAssembly() + { + Assert.False(_functionPointer.ResidesInAssembly("System")); + Assert.False(_functionPointer.ResidesInAssemblyMatching(".*")); + } + + [Fact] + public void NamespacePredicatesHandleTypesWithoutNamespace() + { + var namespaceName = typeof(ClassWithFunctionPointerField).Namespace; + Assert.DoesNotContain( + _functionPointer, + Types(true).That().ResideInNamespace(namespaceName).GetObjects(Architecture) + ); + Assert.DoesNotContain( + _functionPointer, + Types(true).That().ResideInNamespaceMatching(".*").GetObjects(Architecture) + ); + Assert.Contains( + _functionPointer, + Types(true).That().DoNotResideInNamespace(namespaceName).GetObjects(Architecture) + ); + Assert.Contains( + _functionPointer, + Types(true).That().DoNotResideInNamespaceMatching(".*").GetObjects(Architecture) + ); + } + + [Fact] + public void AssemblyPredicatesHandleTypesWithoutAssembly() + { + var assembly = typeof(ClassWithFunctionPointerField).Assembly; + Assert.DoesNotContain( + _functionPointer, + Types(true).That().ResideInAssembly(assembly.FullName).GetObjects(Architecture) + ); + Assert.DoesNotContain( + _functionPointer, + Types(true).That().ResideInAssemblyMatching(".*").GetObjects(Architecture) + ); + Assert.Contains( + _functionPointer, + Types(true).That().DoNotResideInAssembly(assembly.FullName).GetObjects(Architecture) + ); + } + + [Fact] + public void NamespaceConditionsReportTypesWithoutNamespace() + { + var functionPointers = Types(true).That().Are(_functionPointer); + AssertViolation( + functionPointers.Should().ResideInNamespace("System"), + "does not reside in a namespace" + ); + AssertViolation( + functionPointers.Should().ResideInNamespaceMatching(".*"), + "does not reside in a namespace" + ); + Assert.True( + functionPointers + .Should() + .NotResideInNamespace("System") + .HasNoViolations(Architecture) + ); + } + + [Fact] + public void AssemblyConditionsReportTypesWithoutAssembly() + { + var assembly = typeof(ClassWithFunctionPointerField).Assembly; + var functionPointers = Types(true).That().Are(_functionPointer); + AssertViolation( + functionPointers.Should().ResideInAssembly(assembly.FullName), + "does not reside in an assembly" + ); + AssertViolation( + functionPointers.Should().ResideInAssemblyMatching(".*"), + "does not reside in an assembly" + ); + AssertViolation( + functionPointers.Should().ResideInAssembly(assembly), + "does not reside in an assembly" + ); + AssertViolation( + functionPointers + .Should() + .ResideInAssembly(Architecture.GetAssemblyOfAssembly(assembly)), + "does not reside in an assembly" + ); + Assert.True( + functionPointers + .Should() + .NotResideInAssembly(assembly) + .HasNoViolations(Architecture) + ); + } + + private static void AssertViolation( + ArchUnitNET.Fluent.IArchRule rule, + string expectedDescription + ) + { + var result = rule.Evaluate(Architecture).Single(); + Assert.False(result.Passed); + Assert.Contains(expectedDescription, result.Description); + } + } + + public unsafe class ClassWithFunctionPointerField + { + public delegate* FunctionPointerField; + } +} diff --git a/ArchUnitNETTests/Loader/ArchLoaderTests.cs b/ArchUnitNETTests/Loader/ArchLoaderTests.cs index a5129e411..b91ba48fc 100644 --- a/ArchUnitNETTests/Loader/ArchLoaderTests.cs +++ b/ArchUnitNETTests/Loader/ArchLoaderTests.cs @@ -223,7 +223,7 @@ public void SameFullNameInMultipleAssemblies() types = ArchUnitNETTestArchitecture .ReferencedTypes.Where(type => - type.Namespace.FullName == "DuplicateClassAcrossAssemblies" + type.Namespace?.FullName == "DuplicateClassAcrossAssemblies" ) .ToList(); Assert.Equal(2, types.Count);