From 0ccf8181737a3e3534189ca4dbaff7c1816993e2 Mon Sep 17 00:00:00 2001 From: Alexander Linne Date: Fri, 10 Jan 2025 17:08:59 +0100 Subject: [PATCH] fix: dont throw exception if a type can not be resolved Signed-off-by: Alexander Linne --- ArchUnit.sln | 7 ++ ArchUnitNET/Domain/UnavailableType.cs | 75 +++++++++++++++++++ ArchUnitNET/Loader/TypeFactory.cs | 24 +++++- ArchUnitNETTests/ArchUnitNETTests.csproj | 1 + ArchUnitNETTests/Loader/ArchLoaderTests.cs | 28 +++++++ .../AttributeAssembly.csproj | 1 + .../DependencyAssembly.csproj | 1 + .../Class1.cs | 13 ++++ ...FilteredDirectoryLoaderTestAssembly.csproj | 16 ++++ .../FilteredDirectoryLoaderTestAssembly.sln | 25 +++++++ .../LoaderTestAssembly.csproj | 1 + .../OtherLoaderTestAssembly.csproj | 1 + .../VisibilityAssembly.csproj | 1 + 13 files changed, 193 insertions(+), 1 deletion(-) create mode 100644 ArchUnitNET/Domain/UnavailableType.cs create mode 100644 TestAssemblies/FilteredDirectoryLoaderTestAssembly/Class1.cs create mode 100644 TestAssemblies/FilteredDirectoryLoaderTestAssembly/FilteredDirectoryLoaderTestAssembly.csproj create mode 100644 TestAssemblies/FilteredDirectoryLoaderTestAssembly/FilteredDirectoryLoaderTestAssembly.sln diff --git a/ArchUnit.sln b/ArchUnit.sln index ff234cf7..4bc5a6b9 100644 --- a/ArchUnit.sln +++ b/ArchUnit.sln @@ -32,6 +32,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LoaderTestAssembly", "TestA EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OtherLoaderTestAssembly", "TestAssemblies\OtherLoaderTestAssembly\OtherLoaderTestAssembly.csproj", "{5A24529B-1794-4080-ADCC-77440BA0A0B3}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FilteredDirectoryLoaderTestAssembly", "TestAssemblies\FilteredDirectoryLoaderTestAssembly\FilteredDirectoryLoaderTestAssembly.csproj", "{E6CB8C69-25F5-4C94-8EA3-D56E444EB46B}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -94,6 +96,10 @@ Global {5A24529B-1794-4080-ADCC-77440BA0A0B3}.Debug|Any CPU.Build.0 = Debug|Any CPU {5A24529B-1794-4080-ADCC-77440BA0A0B3}.Release|Any CPU.ActiveCfg = Release|Any CPU {5A24529B-1794-4080-ADCC-77440BA0A0B3}.Release|Any CPU.Build.0 = Release|Any CPU + {E6CB8C69-25F5-4C94-8EA3-D56E444EB46B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E6CB8C69-25F5-4C94-8EA3-D56E444EB46B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E6CB8C69-25F5-4C94-8EA3-D56E444EB46B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E6CB8C69-25F5-4C94-8EA3-D56E444EB46B}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -104,5 +110,6 @@ Global {FBCD91F2-4DB9-44AC-8214-6F2FFF9178D5} = {B1191F18-91CB-4387-B775-A5EB64D3AC30} {0243F2D4-AC89-4561-A936-D647B6BB821F} = {B1191F18-91CB-4387-B775-A5EB64D3AC30} {5A24529B-1794-4080-ADCC-77440BA0A0B3} = {B1191F18-91CB-4387-B775-A5EB64D3AC30} + {E6CB8C69-25F5-4C94-8EA3-D56E444EB46B} = {B1191F18-91CB-4387-B775-A5EB64D3AC30} EndGlobalSection EndGlobal diff --git a/ArchUnitNET/Domain/UnavailableType.cs b/ArchUnitNET/Domain/UnavailableType.cs new file mode 100644 index 00000000..89bf7a81 --- /dev/null +++ b/ArchUnitNET/Domain/UnavailableType.cs @@ -0,0 +1,75 @@ +// Copyright 2019 Florian Gather +// Copyright 2019 Fritz Brandhuber +// Copyright 2020 Pavel Fischer +// +// SPDX-License-Identifier: Apache-2.0 + +using System.Collections.Generic; +using System.Linq; +using ArchUnitNET.Domain.Dependencies; + +namespace ArchUnitNET.Domain +{ + public class UnavailableType : IType + { + public UnavailableType(IType type) + { + Type = type; + } + + private IType Type { get; } + public string Name => Type.Name; + public string FullName => Type.FullName; + + public Visibility Visibility => Type.Visibility; + public bool IsNested => Type.IsNested; + public bool IsGeneric => Type.IsGeneric; + public bool IsGenericParameter => Type.IsGenericParameter; + public bool IsStub => true; + public bool IsCompilerGenerated => Type.IsCompilerGenerated; + + public Namespace Namespace => Type.Namespace; + public Assembly Assembly => Type.Assembly; + + public IEnumerable Attributes => + AttributeInstances.Select(instance => instance.Type); + public List AttributeInstances => Type.AttributeInstances; + + public List Dependencies => Type.Dependencies; + public List BackwardsDependencies => Type.BackwardsDependencies; + public IEnumerable ImplementedInterfaces => Type.ImplementedInterfaces; + + public MemberList Members => Type.Members; + public List GenericParameters => Type.GenericParameters; + + public override string ToString() + { + return FullName; + } + + private bool Equals(Struct other) + { + return Equals(Type, other.Type); + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } + + if (ReferenceEquals(this, obj)) + { + return true; + } + + return obj.GetType() == GetType() && Equals((Struct)obj); + } + + public override int GetHashCode() + { + return Type != null ? Type.GetHashCode() : 0; + } + } +} diff --git a/ArchUnitNET/Loader/TypeFactory.cs b/ArchUnitNET/Loader/TypeFactory.cs index 892000a5..4c48dbfc 100644 --- a/ArchUnitNET/Loader/TypeFactory.cs +++ b/ArchUnitNET/Loader/TypeFactory.cs @@ -229,7 +229,29 @@ bool isStub } if (typeDefinition == null) { - throw new ArchLoaderException($"Could not resolve type {typeReference.FullName}"); + // When assemblies are loaded by path, there are cases where a dependent type cannot be resolved because + // the assembly dependency is not loaded in the current application domain. In this case, we create a + // stub type. + return new TypeInstance( + new UnavailableType( + new Type( + typeReference.BuildFullName(), + typeReference.Name, + _assemblyRegistry.GetOrCreateAssembly( + typeReference.Module.Assembly.Name.FullName, + typeReference.Module.Assembly.FullName, + true, + null + ), + _namespaceRegistry.GetOrCreateNamespace(typeReference.Namespace), + NotAccessible, + typeReference.IsNested, + typeReference.HasGenericParameters, + true, + typeReference.IsCompilerGenerated() + ) + ) + ); } var typeName = typeDefinition.BuildFullName(); diff --git a/ArchUnitNETTests/ArchUnitNETTests.csproj b/ArchUnitNETTests/ArchUnitNETTests.csproj index 4f4c6969..9ca9c16b 100644 --- a/ArchUnitNETTests/ArchUnitNETTests.csproj +++ b/ArchUnitNETTests/ArchUnitNETTests.csproj @@ -13,6 +13,7 @@ + diff --git a/ArchUnitNETTests/Loader/ArchLoaderTests.cs b/ArchUnitNETTests/Loader/ArchLoaderTests.cs index a18d12be..34b941c5 100644 --- a/ArchUnitNETTests/Loader/ArchLoaderTests.cs +++ b/ArchUnitNETTests/Loader/ArchLoaderTests.cs @@ -4,7 +4,10 @@ // // SPDX-License-Identifier: Apache-2.0 +using System; using System.Linq; +using ArchUnitNET.Domain; +using ArchUnitNET.Domain.Extensions; using ArchUnitNET.Loader; using ArchUnitNET.xUnit; using ArchUnitNETTests.Domain.Dependencies.Members; @@ -96,5 +99,30 @@ public void TypesAreAssignedToCorrectAssemblies() .ResideInAssembly(GetType().Assembly) .Check(architecture); } + + [Fact] + public void UnavailableTypeTest() + { + // When loading an assembly from a file, there are situations where the assemblies dependencies are not + // available in the current AppDomain. This test checks that the loader does not throw an exception in this + // case. + var assemblyPath = AppDomain.CurrentDomain.BaseDirectory[ + ..AppDomain.CurrentDomain.BaseDirectory.IndexOf( + @"ArchUnitNETTests", + StringComparison.InvariantCulture + ) + ]; + var architecture = new ArchLoader() + .LoadFilteredDirectory( + assemblyPath, + "FilteredDirectoryLoaderTestAssembly.dll", + System.IO.SearchOption.AllDirectories + ) + .Build(); + Assert.Single(architecture.Types); + var loggerType = architecture.ReferencedTypes.WhereFullNameIs("Serilog.ILogger"); + Assert.NotNull(loggerType); + Assert.True(loggerType is UnavailableType); + } } } diff --git a/TestAssemblies/AttributeAssembly/AttributeAssembly.csproj b/TestAssemblies/AttributeAssembly/AttributeAssembly.csproj index 06fd2690..3f9e9e0a 100644 --- a/TestAssemblies/AttributeAssembly/AttributeAssembly.csproj +++ b/TestAssemblies/AttributeAssembly/AttributeAssembly.csproj @@ -5,6 +5,7 @@ enable enable true + false diff --git a/TestAssemblies/DependencyAssembly/DependencyAssembly.csproj b/TestAssemblies/DependencyAssembly/DependencyAssembly.csproj index 06fd2690..3f9e9e0a 100644 --- a/TestAssemblies/DependencyAssembly/DependencyAssembly.csproj +++ b/TestAssemblies/DependencyAssembly/DependencyAssembly.csproj @@ -5,6 +5,7 @@ enable enable true + false diff --git a/TestAssemblies/FilteredDirectoryLoaderTestAssembly/Class1.cs b/TestAssemblies/FilteredDirectoryLoaderTestAssembly/Class1.cs new file mode 100644 index 00000000..ea4b8c90 --- /dev/null +++ b/TestAssemblies/FilteredDirectoryLoaderTestAssembly/Class1.cs @@ -0,0 +1,13 @@ +using Serilog; + +namespace FilteredDirectoryLoaderTestAssembly; + +public class Class1 +{ + public ILogger logger; + + public Class1() + { + this.logger = new LoggerConfiguration().CreateLogger(); + } +} diff --git a/TestAssemblies/FilteredDirectoryLoaderTestAssembly/FilteredDirectoryLoaderTestAssembly.csproj b/TestAssemblies/FilteredDirectoryLoaderTestAssembly/FilteredDirectoryLoaderTestAssembly.csproj new file mode 100644 index 00000000..6d0a2fdc --- /dev/null +++ b/TestAssemblies/FilteredDirectoryLoaderTestAssembly/FilteredDirectoryLoaderTestAssembly.csproj @@ -0,0 +1,16 @@ + + + + net8.0 + enable + enable + true + false + + + + + + + + diff --git a/TestAssemblies/FilteredDirectoryLoaderTestAssembly/FilteredDirectoryLoaderTestAssembly.sln b/TestAssemblies/FilteredDirectoryLoaderTestAssembly/FilteredDirectoryLoaderTestAssembly.sln new file mode 100644 index 00000000..d9a61479 --- /dev/null +++ b/TestAssemblies/FilteredDirectoryLoaderTestAssembly/FilteredDirectoryLoaderTestAssembly.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.5.002.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FilteredDirectoryLoaderTestAssembly", "FilteredDirectoryLoaderTestAssembly.csproj", "{8597C220-0EC2-42AF-9675-C56607614773}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {8597C220-0EC2-42AF-9675-C56607614773}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8597C220-0EC2-42AF-9675-C56607614773}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8597C220-0EC2-42AF-9675-C56607614773}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8597C220-0EC2-42AF-9675-C56607614773}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {B166203B-6B5B-4BE6-B945-CA9FD03A6054} + EndGlobalSection +EndGlobal diff --git a/TestAssemblies/LoaderTestAssembly/LoaderTestAssembly.csproj b/TestAssemblies/LoaderTestAssembly/LoaderTestAssembly.csproj index 06fd2690..3f9e9e0a 100644 --- a/TestAssemblies/LoaderTestAssembly/LoaderTestAssembly.csproj +++ b/TestAssemblies/LoaderTestAssembly/LoaderTestAssembly.csproj @@ -5,6 +5,7 @@ enable enable true + false diff --git a/TestAssemblies/OtherLoaderTestAssembly/OtherLoaderTestAssembly.csproj b/TestAssemblies/OtherLoaderTestAssembly/OtherLoaderTestAssembly.csproj index 06fd2690..3f9e9e0a 100644 --- a/TestAssemblies/OtherLoaderTestAssembly/OtherLoaderTestAssembly.csproj +++ b/TestAssemblies/OtherLoaderTestAssembly/OtherLoaderTestAssembly.csproj @@ -5,6 +5,7 @@ enable enable true + false diff --git a/TestAssemblies/VisibilityAssembly/VisibilityAssembly.csproj b/TestAssemblies/VisibilityAssembly/VisibilityAssembly.csproj index 06fd2690..3f9e9e0a 100644 --- a/TestAssemblies/VisibilityAssembly/VisibilityAssembly.csproj +++ b/TestAssemblies/VisibilityAssembly/VisibilityAssembly.csproj @@ -5,6 +5,7 @@ enable enable true + false