diff --git a/Capability/GameFrameX.Capability.Hosting.Core/CapabilityAssembly.cs b/Capability/GameFrameX.Capability.Hosting.Core/CapabilityAssembly.cs index 0a412721e..92d5dac2b 100644 --- a/Capability/GameFrameX.Capability.Hosting.Core/CapabilityAssembly.cs +++ b/Capability/GameFrameX.Capability.Hosting.Core/CapabilityAssembly.cs @@ -47,7 +47,8 @@ public static class CapabilityAssembly /// 扫描指定程序集集合(缺省 = 当前 AppDomain 全部已加载程序集;同 Id 多实现 fail-fast)。 /// /// 程序集集合(null = 全部已加载)。 - /// 发现的能力模块(按程序集名 + 类型名稳定排序;同 Id 多实现抛 )。 + /// 发现的能力模块(按程序集名 + 类型名稳定排序;同 Id 多实现抛 ; + /// 程序集类型无法完整加载抛 )。 public static IReadOnlyList DiscoverModules(IEnumerable assemblies = null) { var targetAssemblies = assemblies ?? AppDomain.CurrentDomain.GetAssemblies(); @@ -144,7 +145,19 @@ private static void CollectModuleTypes(Assembly assembly, List moduleTypes catch (ReflectionTypeLoadException exception) { // 命中命名约定的能力包类型无法完整加载(如依赖缺失)= 部署残缺——带定位信息 fail-fast,不再静默跳过。 - throw new InvalidOperationException(string.Format(LocalizationService.GetString(Localization.Keys.Capability.Hosting.ModuleTypeScanFailed), assembly.GetName().Name, exception.LoaderExceptions == null ? 0 : exception.LoaderExceptions.Length)); + // 抛 CapabilityAssemblyException:组合入口统一经 RenderFailures 逐条打 Error,首条为程序集定位,其后逐条保留底层加载原因。 + var loaderExceptions = exception.LoaderExceptions ?? Array.Empty(); + var problems = new List(loaderExceptions.Length + 1); + problems.Add(string.Format(LocalizationService.GetString(Localization.Keys.Capability.Hosting.ModuleTypeScanFailed), assembly.GetName().Name, loaderExceptions.Length)); + foreach (var loaderException in loaderExceptions) + { + if (loaderException != null) + { + problems.Add(loaderException.Message); + } + } + + throw new CapabilityAssemblyException(problems, exception); } foreach (var type in types) diff --git a/Capability/GameFrameX.Capability.Hosting.Core/CapabilityAssemblyException.cs b/Capability/GameFrameX.Capability.Hosting.Core/CapabilityAssemblyException.cs index 7cbcd72a1..2c30b0395 100644 --- a/Capability/GameFrameX.Capability.Hosting.Core/CapabilityAssemblyException.cs +++ b/Capability/GameFrameX.Capability.Hosting.Core/CapabilityAssemblyException.cs @@ -50,6 +50,17 @@ public CapabilityAssemblyException(IReadOnlyList problems) Problems = problems; } + /// + /// 初始化 (保留触发问题的原始异常,供诊断堆栈与底层异常对象)。 + /// + /// 全部问题条目(已本地化;至少一条)。 + /// 触发问题的原始异常。 + public CapabilityAssemblyException(IReadOnlyList problems, Exception innerException) + : base(BuildMessage(problems), innerException) + { + Problems = problems; + } + /// /// 获取全部问题条目(已本地化)。 /// diff --git a/Tests/GameFrameX.Tests/Online/CapabilityDeploymentDiscoveryTests.cs b/Tests/GameFrameX.Tests/Online/CapabilityDeploymentDiscoveryTests.cs index 106e1424e..40ad77c9f 100644 --- a/Tests/GameFrameX.Tests/Online/CapabilityDeploymentDiscoveryTests.cs +++ b/Tests/GameFrameX.Tests/Online/CapabilityDeploymentDiscoveryTests.cs @@ -89,7 +89,8 @@ public void LoadDeploymentAssemblies_CorruptArtifact_FailsFastWithProblems() } /// - /// 类型无法完整加载的能力包(依赖缺失)不再静默跳过:带程序集名定位信息 fail-fast。 + /// 类型无法完整加载的能力包(依赖缺失)不再静默跳过:抛 , + /// 首条问题带程序集名定位,其后逐条保留底层类型加载原因(组合入口据此经 RenderFailures 一次报全)。 /// [Fact] public void DiscoverModules_UnloadableTypes_FailsFastWithAssemblyName() @@ -100,8 +101,11 @@ public void DiscoverModules_UnloadableTypes_FailsFastWithAssemblyName() { var assembly = Assembly.LoadFrom(path); - var exception = Assert.Throws(() => CapabilityAssembly.DiscoverModules(new[] { assembly, })); - Assert.Contains("CapabilityDeploymentScanProbe.Broken", exception.Message); + var exception = Assert.Throws(() => CapabilityAssembly.DiscoverModules(new[] { assembly, })); + Assert.Contains("CapabilityDeploymentScanProbe.Broken", exception.Problems[0]); + Assert.True(exception.Problems.Count > 1); + Assert.Contains(exception.Problems.Skip(1), problem => problem.Contains("CapabilityDeploymentScanProbe.Missing")); + Assert.IsType(exception.InnerException); } finally {