diff --git a/TUnit.Core.SourceGenerator.Tests/DuplicateTypeNameAcrossAssembliesTests.InfrastructureGenerator_WithDuplicateTypeNames_CompilesSuccessfully.DotNet10_0.verified.txt b/TUnit.Core.SourceGenerator.Tests/DuplicateTypeNameAcrossAssembliesTests.InfrastructureGenerator_WithDuplicateTypeNames_CompilesSuccessfully.DotNet10_0.verified.txt new file mode 100644 index 0000000000..d1438ad71e --- /dev/null +++ b/TUnit.Core.SourceGenerator.Tests/DuplicateTypeNameAcrossAssembliesTests.InfrastructureGenerator_WithDuplicateTypeNames_CompilesSuccessfully.DotNet10_0.verified.txt @@ -0,0 +1,108 @@ +// +#pragma warning disable + +using TUnit.Core.Logging; +[global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverageAttribute] +[global::System.CodeDom.Compiler.GeneratedCode("TUnit", "1.0.0.0")] +file static class TUnitInfrastructure +{ + [global::System.Runtime.CompilerServices.ModuleInitializer] + public static void Initialize() + { + try + { + global::TUnit.Core.SourceRegistrar.IsEnabled = true; + } + catch { /* TUnit.Core not available - skip source registrar */ } + try + { + global::TUnit.Core.GlobalContext.Current.GlobalLogger.LogDebug("[ModuleInitializer:TestsBase`1] TUnit infrastructure initializing..."); + } + catch { /* TUnit.Core not available - skip logging */ } + try + { + global::TUnit.Core.GlobalContext.Current.GlobalLogger.LogDebug("[ModuleInitializer:TestsBase`1] Loading 3 assembly reference(s)..."); + } + catch { /* TUnit.Core not available - skip logging */ } + try + { + try + { + global::TUnit.Core.GlobalContext.Current.GlobalLogger.LogDebug("[ModuleInitializer:TestsBase`1] Loading assembly containing: global::TUnit.TestProject.Library.AsyncBaseTests"); + } + catch { /* TUnit.Core not available - skip logging */ } + var type_0 = typeof(global::TUnit.TestProject.Library.AsyncBaseTests); + // Force module initializer to complete before proceeding + // RunClassConstructor triggers static constructor, which can only run AFTER module initializer completes + global::System.Runtime.CompilerServices.RuntimeHelpers.RunClassConstructor(type_0.TypeHandle); + try + { + global::TUnit.Core.GlobalContext.Current.GlobalLogger.LogDebug("[ModuleInitializer:TestsBase`1] Assembly initialized: global::TUnit.TestProject.Library.AsyncBaseTests"); + } + catch { /* TUnit.Core not available - skip logging */ } + } + catch (global::System.Exception ex) + { + try + { + global::TUnit.Core.GlobalContext.Current.GlobalLogger.LogDebug("[ModuleInitializer:TestsBase`1] Failed to load global::TUnit.TestProject.Library.AsyncBaseTests: " + ex.Message); + } + catch { /* TUnit.Core not available - skip logging */ } + } + try + { + try + { + global::TUnit.Core.GlobalContext.Current.GlobalLogger.LogDebug("[ModuleInitializer:TestsBase`1] Loading assembly containing: global::VerifyTUnit.DerivePathInfo"); + } + catch { /* TUnit.Core not available - skip logging */ } + var type_1 = typeof(global::VerifyTUnit.DerivePathInfo); + // Force module initializer to complete before proceeding + // RunClassConstructor triggers static constructor, which can only run AFTER module initializer completes + global::System.Runtime.CompilerServices.RuntimeHelpers.RunClassConstructor(type_1.TypeHandle); + try + { + global::TUnit.Core.GlobalContext.Current.GlobalLogger.LogDebug("[ModuleInitializer:TestsBase`1] Assembly initialized: global::VerifyTUnit.DerivePathInfo"); + } + catch { /* TUnit.Core not available - skip logging */ } + } + catch (global::System.Exception ex) + { + try + { + global::TUnit.Core.GlobalContext.Current.GlobalLogger.LogDebug("[ModuleInitializer:TestsBase`1] Failed to load global::VerifyTUnit.DerivePathInfo: " + ex.Message); + } + catch { /* TUnit.Core not available - skip logging */ } + } + try + { + try + { + global::TUnit.Core.GlobalContext.Current.GlobalLogger.LogDebug("[ModuleInitializer:TestsBase`1] Loading assembly containing: global::TUnit.Core.SourceGenerator.Tests.AotConverterGeneratorTests"); + } + catch { /* TUnit.Core not available - skip logging */ } + var type_2 = typeof(global::TUnit.Core.SourceGenerator.Tests.AotConverterGeneratorTests); + // Force module initializer to complete before proceeding + // RunClassConstructor triggers static constructor, which can only run AFTER module initializer completes + global::System.Runtime.CompilerServices.RuntimeHelpers.RunClassConstructor(type_2.TypeHandle); + try + { + global::TUnit.Core.GlobalContext.Current.GlobalLogger.LogDebug("[ModuleInitializer:TestsBase`1] Assembly initialized: global::TUnit.Core.SourceGenerator.Tests.AotConverterGeneratorTests"); + } + catch { /* TUnit.Core not available - skip logging */ } + } + catch (global::System.Exception ex) + { + try + { + global::TUnit.Core.GlobalContext.Current.GlobalLogger.LogDebug("[ModuleInitializer:TestsBase`1] Failed to load global::TUnit.Core.SourceGenerator.Tests.AotConverterGeneratorTests: " + ex.Message); + } + catch { /* TUnit.Core not available - skip logging */ } + } + try + { + global::TUnit.Core.GlobalContext.Current.GlobalLogger.LogDebug("[ModuleInitializer:TestsBase`1] TUnit infrastructure initialized"); + } + catch { /* TUnit.Core not available - skip logging */ } + } +} diff --git a/TUnit.Core.SourceGenerator.Tests/DuplicateTypeNameAcrossAssembliesTests.InfrastructureGenerator_WithDuplicateTypeNames_CompilesSuccessfully.DotNet8_0.verified.txt b/TUnit.Core.SourceGenerator.Tests/DuplicateTypeNameAcrossAssembliesTests.InfrastructureGenerator_WithDuplicateTypeNames_CompilesSuccessfully.DotNet8_0.verified.txt new file mode 100644 index 0000000000..d1438ad71e --- /dev/null +++ b/TUnit.Core.SourceGenerator.Tests/DuplicateTypeNameAcrossAssembliesTests.InfrastructureGenerator_WithDuplicateTypeNames_CompilesSuccessfully.DotNet8_0.verified.txt @@ -0,0 +1,108 @@ +// +#pragma warning disable + +using TUnit.Core.Logging; +[global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverageAttribute] +[global::System.CodeDom.Compiler.GeneratedCode("TUnit", "1.0.0.0")] +file static class TUnitInfrastructure +{ + [global::System.Runtime.CompilerServices.ModuleInitializer] + public static void Initialize() + { + try + { + global::TUnit.Core.SourceRegistrar.IsEnabled = true; + } + catch { /* TUnit.Core not available - skip source registrar */ } + try + { + global::TUnit.Core.GlobalContext.Current.GlobalLogger.LogDebug("[ModuleInitializer:TestsBase`1] TUnit infrastructure initializing..."); + } + catch { /* TUnit.Core not available - skip logging */ } + try + { + global::TUnit.Core.GlobalContext.Current.GlobalLogger.LogDebug("[ModuleInitializer:TestsBase`1] Loading 3 assembly reference(s)..."); + } + catch { /* TUnit.Core not available - skip logging */ } + try + { + try + { + global::TUnit.Core.GlobalContext.Current.GlobalLogger.LogDebug("[ModuleInitializer:TestsBase`1] Loading assembly containing: global::TUnit.TestProject.Library.AsyncBaseTests"); + } + catch { /* TUnit.Core not available - skip logging */ } + var type_0 = typeof(global::TUnit.TestProject.Library.AsyncBaseTests); + // Force module initializer to complete before proceeding + // RunClassConstructor triggers static constructor, which can only run AFTER module initializer completes + global::System.Runtime.CompilerServices.RuntimeHelpers.RunClassConstructor(type_0.TypeHandle); + try + { + global::TUnit.Core.GlobalContext.Current.GlobalLogger.LogDebug("[ModuleInitializer:TestsBase`1] Assembly initialized: global::TUnit.TestProject.Library.AsyncBaseTests"); + } + catch { /* TUnit.Core not available - skip logging */ } + } + catch (global::System.Exception ex) + { + try + { + global::TUnit.Core.GlobalContext.Current.GlobalLogger.LogDebug("[ModuleInitializer:TestsBase`1] Failed to load global::TUnit.TestProject.Library.AsyncBaseTests: " + ex.Message); + } + catch { /* TUnit.Core not available - skip logging */ } + } + try + { + try + { + global::TUnit.Core.GlobalContext.Current.GlobalLogger.LogDebug("[ModuleInitializer:TestsBase`1] Loading assembly containing: global::VerifyTUnit.DerivePathInfo"); + } + catch { /* TUnit.Core not available - skip logging */ } + var type_1 = typeof(global::VerifyTUnit.DerivePathInfo); + // Force module initializer to complete before proceeding + // RunClassConstructor triggers static constructor, which can only run AFTER module initializer completes + global::System.Runtime.CompilerServices.RuntimeHelpers.RunClassConstructor(type_1.TypeHandle); + try + { + global::TUnit.Core.GlobalContext.Current.GlobalLogger.LogDebug("[ModuleInitializer:TestsBase`1] Assembly initialized: global::VerifyTUnit.DerivePathInfo"); + } + catch { /* TUnit.Core not available - skip logging */ } + } + catch (global::System.Exception ex) + { + try + { + global::TUnit.Core.GlobalContext.Current.GlobalLogger.LogDebug("[ModuleInitializer:TestsBase`1] Failed to load global::VerifyTUnit.DerivePathInfo: " + ex.Message); + } + catch { /* TUnit.Core not available - skip logging */ } + } + try + { + try + { + global::TUnit.Core.GlobalContext.Current.GlobalLogger.LogDebug("[ModuleInitializer:TestsBase`1] Loading assembly containing: global::TUnit.Core.SourceGenerator.Tests.AotConverterGeneratorTests"); + } + catch { /* TUnit.Core not available - skip logging */ } + var type_2 = typeof(global::TUnit.Core.SourceGenerator.Tests.AotConverterGeneratorTests); + // Force module initializer to complete before proceeding + // RunClassConstructor triggers static constructor, which can only run AFTER module initializer completes + global::System.Runtime.CompilerServices.RuntimeHelpers.RunClassConstructor(type_2.TypeHandle); + try + { + global::TUnit.Core.GlobalContext.Current.GlobalLogger.LogDebug("[ModuleInitializer:TestsBase`1] Assembly initialized: global::TUnit.Core.SourceGenerator.Tests.AotConverterGeneratorTests"); + } + catch { /* TUnit.Core not available - skip logging */ } + } + catch (global::System.Exception ex) + { + try + { + global::TUnit.Core.GlobalContext.Current.GlobalLogger.LogDebug("[ModuleInitializer:TestsBase`1] Failed to load global::TUnit.Core.SourceGenerator.Tests.AotConverterGeneratorTests: " + ex.Message); + } + catch { /* TUnit.Core not available - skip logging */ } + } + try + { + global::TUnit.Core.GlobalContext.Current.GlobalLogger.LogDebug("[ModuleInitializer:TestsBase`1] TUnit infrastructure initialized"); + } + catch { /* TUnit.Core not available - skip logging */ } + } +} diff --git a/TUnit.Core.SourceGenerator.Tests/DuplicateTypeNameAcrossAssembliesTests.InfrastructureGenerator_WithDuplicateTypeNames_CompilesSuccessfully.DotNet9_0.verified.txt b/TUnit.Core.SourceGenerator.Tests/DuplicateTypeNameAcrossAssembliesTests.InfrastructureGenerator_WithDuplicateTypeNames_CompilesSuccessfully.DotNet9_0.verified.txt new file mode 100644 index 0000000000..d1438ad71e --- /dev/null +++ b/TUnit.Core.SourceGenerator.Tests/DuplicateTypeNameAcrossAssembliesTests.InfrastructureGenerator_WithDuplicateTypeNames_CompilesSuccessfully.DotNet9_0.verified.txt @@ -0,0 +1,108 @@ +// +#pragma warning disable + +using TUnit.Core.Logging; +[global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverageAttribute] +[global::System.CodeDom.Compiler.GeneratedCode("TUnit", "1.0.0.0")] +file static class TUnitInfrastructure +{ + [global::System.Runtime.CompilerServices.ModuleInitializer] + public static void Initialize() + { + try + { + global::TUnit.Core.SourceRegistrar.IsEnabled = true; + } + catch { /* TUnit.Core not available - skip source registrar */ } + try + { + global::TUnit.Core.GlobalContext.Current.GlobalLogger.LogDebug("[ModuleInitializer:TestsBase`1] TUnit infrastructure initializing..."); + } + catch { /* TUnit.Core not available - skip logging */ } + try + { + global::TUnit.Core.GlobalContext.Current.GlobalLogger.LogDebug("[ModuleInitializer:TestsBase`1] Loading 3 assembly reference(s)..."); + } + catch { /* TUnit.Core not available - skip logging */ } + try + { + try + { + global::TUnit.Core.GlobalContext.Current.GlobalLogger.LogDebug("[ModuleInitializer:TestsBase`1] Loading assembly containing: global::TUnit.TestProject.Library.AsyncBaseTests"); + } + catch { /* TUnit.Core not available - skip logging */ } + var type_0 = typeof(global::TUnit.TestProject.Library.AsyncBaseTests); + // Force module initializer to complete before proceeding + // RunClassConstructor triggers static constructor, which can only run AFTER module initializer completes + global::System.Runtime.CompilerServices.RuntimeHelpers.RunClassConstructor(type_0.TypeHandle); + try + { + global::TUnit.Core.GlobalContext.Current.GlobalLogger.LogDebug("[ModuleInitializer:TestsBase`1] Assembly initialized: global::TUnit.TestProject.Library.AsyncBaseTests"); + } + catch { /* TUnit.Core not available - skip logging */ } + } + catch (global::System.Exception ex) + { + try + { + global::TUnit.Core.GlobalContext.Current.GlobalLogger.LogDebug("[ModuleInitializer:TestsBase`1] Failed to load global::TUnit.TestProject.Library.AsyncBaseTests: " + ex.Message); + } + catch { /* TUnit.Core not available - skip logging */ } + } + try + { + try + { + global::TUnit.Core.GlobalContext.Current.GlobalLogger.LogDebug("[ModuleInitializer:TestsBase`1] Loading assembly containing: global::VerifyTUnit.DerivePathInfo"); + } + catch { /* TUnit.Core not available - skip logging */ } + var type_1 = typeof(global::VerifyTUnit.DerivePathInfo); + // Force module initializer to complete before proceeding + // RunClassConstructor triggers static constructor, which can only run AFTER module initializer completes + global::System.Runtime.CompilerServices.RuntimeHelpers.RunClassConstructor(type_1.TypeHandle); + try + { + global::TUnit.Core.GlobalContext.Current.GlobalLogger.LogDebug("[ModuleInitializer:TestsBase`1] Assembly initialized: global::VerifyTUnit.DerivePathInfo"); + } + catch { /* TUnit.Core not available - skip logging */ } + } + catch (global::System.Exception ex) + { + try + { + global::TUnit.Core.GlobalContext.Current.GlobalLogger.LogDebug("[ModuleInitializer:TestsBase`1] Failed to load global::VerifyTUnit.DerivePathInfo: " + ex.Message); + } + catch { /* TUnit.Core not available - skip logging */ } + } + try + { + try + { + global::TUnit.Core.GlobalContext.Current.GlobalLogger.LogDebug("[ModuleInitializer:TestsBase`1] Loading assembly containing: global::TUnit.Core.SourceGenerator.Tests.AotConverterGeneratorTests"); + } + catch { /* TUnit.Core not available - skip logging */ } + var type_2 = typeof(global::TUnit.Core.SourceGenerator.Tests.AotConverterGeneratorTests); + // Force module initializer to complete before proceeding + // RunClassConstructor triggers static constructor, which can only run AFTER module initializer completes + global::System.Runtime.CompilerServices.RuntimeHelpers.RunClassConstructor(type_2.TypeHandle); + try + { + global::TUnit.Core.GlobalContext.Current.GlobalLogger.LogDebug("[ModuleInitializer:TestsBase`1] Assembly initialized: global::TUnit.Core.SourceGenerator.Tests.AotConverterGeneratorTests"); + } + catch { /* TUnit.Core not available - skip logging */ } + } + catch (global::System.Exception ex) + { + try + { + global::TUnit.Core.GlobalContext.Current.GlobalLogger.LogDebug("[ModuleInitializer:TestsBase`1] Failed to load global::TUnit.Core.SourceGenerator.Tests.AotConverterGeneratorTests: " + ex.Message); + } + catch { /* TUnit.Core not available - skip logging */ } + } + try + { + global::TUnit.Core.GlobalContext.Current.GlobalLogger.LogDebug("[ModuleInitializer:TestsBase`1] TUnit infrastructure initialized"); + } + catch { /* TUnit.Core not available - skip logging */ } + } +} diff --git a/TUnit.Core.SourceGenerator.Tests/DuplicateTypeNameAcrossAssembliesTests.InfrastructureGenerator_WithDuplicateTypeNames_CompilesSuccessfully.Net4_7.verified.txt b/TUnit.Core.SourceGenerator.Tests/DuplicateTypeNameAcrossAssembliesTests.InfrastructureGenerator_WithDuplicateTypeNames_CompilesSuccessfully.Net4_7.verified.txt new file mode 100644 index 0000000000..41b4956293 --- /dev/null +++ b/TUnit.Core.SourceGenerator.Tests/DuplicateTypeNameAcrossAssembliesTests.InfrastructureGenerator_WithDuplicateTypeNames_CompilesSuccessfully.Net4_7.verified.txt @@ -0,0 +1,108 @@ +// +#pragma warning disable + +using TUnit.Core.Logging; +[global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverageAttribute] +[global::System.CodeDom.Compiler.GeneratedCode("TUnit", "1.0.0.0")] +file static class TUnitInfrastructure +{ + [global::System.Runtime.CompilerServices.ModuleInitializer] + public static void Initialize() + { + try + { + global::TUnit.Core.SourceRegistrar.IsEnabled = true; + } + catch { /* TUnit.Core not available - skip source registrar */ } + try + { + global::TUnit.Core.GlobalContext.Current.GlobalLogger.LogDebug("[ModuleInitializer:TestsBase`1] TUnit infrastructure initializing..."); + } + catch { /* TUnit.Core not available - skip logging */ } + try + { + global::TUnit.Core.GlobalContext.Current.GlobalLogger.LogDebug("[ModuleInitializer:TestsBase`1] Loading 3 assembly reference(s)..."); + } + catch { /* TUnit.Core not available - skip logging */ } + try + { + try + { + global::TUnit.Core.GlobalContext.Current.GlobalLogger.LogDebug("[ModuleInitializer:TestsBase`1] Loading assembly containing: global::TUnit.TestProject.Library.AsyncBaseTests"); + } + catch { /* TUnit.Core not available - skip logging */ } + var type_0 = typeof(global::TUnit.TestProject.Library.AsyncBaseTests); + // Force module initializer to complete before proceeding + // RunClassConstructor triggers static constructor, which can only run AFTER module initializer completes + global::System.Runtime.CompilerServices.RuntimeHelpers.RunClassConstructor(type_0.TypeHandle); + try + { + global::TUnit.Core.GlobalContext.Current.GlobalLogger.LogDebug("[ModuleInitializer:TestsBase`1] Assembly initialized: global::TUnit.TestProject.Library.AsyncBaseTests"); + } + catch { /* TUnit.Core not available - skip logging */ } + } + catch (global::System.Exception ex) + { + try + { + global::TUnit.Core.GlobalContext.Current.GlobalLogger.LogDebug("[ModuleInitializer:TestsBase`1] Failed to load global::TUnit.TestProject.Library.AsyncBaseTests: " + ex.Message); + } + catch { /* TUnit.Core not available - skip logging */ } + } + try + { + try + { + global::TUnit.Core.GlobalContext.Current.GlobalLogger.LogDebug("[ModuleInitializer:TestsBase`1] Loading assembly containing: global::VerifyTUnit.DerivePathInfo"); + } + catch { /* TUnit.Core not available - skip logging */ } + var type_1 = typeof(global::VerifyTUnit.DerivePathInfo); + // Force module initializer to complete before proceeding + // RunClassConstructor triggers static constructor, which can only run AFTER module initializer completes + global::System.Runtime.CompilerServices.RuntimeHelpers.RunClassConstructor(type_1.TypeHandle); + try + { + global::TUnit.Core.GlobalContext.Current.GlobalLogger.LogDebug("[ModuleInitializer:TestsBase`1] Assembly initialized: global::VerifyTUnit.DerivePathInfo"); + } + catch { /* TUnit.Core not available - skip logging */ } + } + catch (global::System.Exception ex) + { + try + { + global::TUnit.Core.GlobalContext.Current.GlobalLogger.LogDebug("[ModuleInitializer:TestsBase`1] Failed to load global::VerifyTUnit.DerivePathInfo: " + ex.Message); + } + catch { /* TUnit.Core not available - skip logging */ } + } + try + { + try + { + global::TUnit.Core.GlobalContext.Current.GlobalLogger.LogDebug("[ModuleInitializer:TestsBase`1] Loading assembly containing: global::TUnit.Core.SourceGenerator.Tests.AotConverterGeneratorTests"); + } + catch { /* TUnit.Core not available - skip logging */ } + var type_2 = typeof(global::TUnit.Core.SourceGenerator.Tests.AotConverterGeneratorTests); + // Force module initializer to complete before proceeding + // RunClassConstructor triggers static constructor, which can only run AFTER module initializer completes + global::System.Runtime.CompilerServices.RuntimeHelpers.RunClassConstructor(type_2.TypeHandle); + try + { + global::TUnit.Core.GlobalContext.Current.GlobalLogger.LogDebug("[ModuleInitializer:TestsBase`1] Assembly initialized: global::TUnit.Core.SourceGenerator.Tests.AotConverterGeneratorTests"); + } + catch { /* TUnit.Core not available - skip logging */ } + } + catch (global::System.Exception ex) + { + try + { + global::TUnit.Core.GlobalContext.Current.GlobalLogger.LogDebug("[ModuleInitializer:TestsBase`1] Failed to load global::TUnit.Core.SourceGenerator.Tests.AotConverterGeneratorTests: " + ex.Message); + } + catch { /* TUnit.Core not available - skip logging */ } + } + try + { + global::TUnit.Core.GlobalContext.Current.GlobalLogger.LogDebug("[ModuleInitializer:TestsBase`1] TUnit infrastructure initialized"); + } + catch { /* TUnit.Core not available - skip logging */ } + } +} diff --git a/TUnit.Core.SourceGenerator.Tests/DuplicateTypeNameAcrossAssembliesTests.cs b/TUnit.Core.SourceGenerator.Tests/DuplicateTypeNameAcrossAssembliesTests.cs new file mode 100644 index 0000000000..ba153ed294 --- /dev/null +++ b/TUnit.Core.SourceGenerator.Tests/DuplicateTypeNameAcrossAssembliesTests.cs @@ -0,0 +1,55 @@ +using TUnit.Core.SourceGenerator.Tests.Options; + +namespace TUnit.Core.SourceGenerator.Tests; + +/// +/// Tests to verify that generated code compiles correctly when multiple assemblies +/// define types with the same fully-qualified name. +/// See: https://github.com/thomhurst/TUnit/issues/4663 +/// +internal class DuplicateTypeNameAcrossAssembliesTests : TestsBase +{ + /// + /// This test verifies that the InfrastructureGenerator correctly handles the case + /// where a type name is ambiguous (exists in multiple places). + /// + /// We simulate this by adding a synthetic type with the same fully-qualified name as + /// a type from the VerifyTUnit package. The generator uses GetTypeByMetadataName() to + /// detect ambiguous types and skip them, finding a unique type instead. + /// + /// Without the fix: generator picks DanglingSnapshots, which is ambiguous + /// With the fix: generator detects ambiguity and picks DerivePathInfo instead + /// + [Test] + public Task InfrastructureGenerator_WithDuplicateTypeNames_CompilesSuccessfully() => InfrastructureGenerator.RunTest( + Path.Combine(Git.RootDirectory.FullName, "TUnit.TestProject", "BasicTests.cs"), + new RunTestOptions + { + AdditionalSyntaxes = + [ + // Create a type that conflicts with VerifyTUnit.DanglingSnapshots + // This simulates the scenario where two assemblies define the same type + """ + namespace VerifyTUnit + { + public class DanglingSnapshots { } + } + """ + ], + VerifyConfigurator = verify => verify.UniqueForTargetFrameworkAndVersion() + }, + async generatedFiles => + { + // If we get here without compilation errors, the test passes + // The fix ensures the generator picks unique types that don't conflict + await Assert.That(generatedFiles).IsNotEmpty(); + + // Verify that the generated code doesn't reference the conflicting type + var infrastructureFile = generatedFiles.FirstOrDefault(f => f.Contains("TUnitInfrastructure")); + await Assert.That(infrastructureFile).IsNotNull(); + + // The generator should have found a different type from VerifyTUnit assembly + // since DanglingSnapshots is now ambiguous + await Assert.That(infrastructureFile!).DoesNotContain("typeof(global::VerifyTUnit.DanglingSnapshots)"); + }); +} diff --git a/TUnit.Core.SourceGenerator/CodeGenerators/InfrastructureGenerator.cs b/TUnit.Core.SourceGenerator/CodeGenerators/InfrastructureGenerator.cs index 608dbbce69..8c86ff486b 100644 --- a/TUnit.Core.SourceGenerator/CodeGenerators/InfrastructureGenerator.cs +++ b/TUnit.Core.SourceGenerator/CodeGenerators/InfrastructureGenerator.cs @@ -106,7 +106,7 @@ private static AssemblyInfoModel ExtractAssemblyInfo(Compilation compilation) { if (ShouldLoadAssembly(assembly, compilation)) { - var publicType = GetFirstPublicType(assembly); + var publicType = GetFirstUniquePublicType(assembly, compilation); if (publicType != null) { assembliesToLoad.Add(publicType); @@ -256,39 +256,85 @@ private static bool IsLoadableAtRuntime(IAssemblySymbol assembly, Compilation co return correspondingReference != null; } - private static string? GetFirstPublicType(IAssemblySymbol assembly) + /// + /// Gets the first public type from an assembly that can be uniquely resolved by the compilation. + /// This avoids CS0433 errors when multiple assemblies define types with the same fully-qualified name. + /// + private static string? GetFirstUniquePublicType(IAssemblySymbol assembly, Compilation compilation) { - var publicTypes = GetPublicTypesRecursive(assembly.GlobalNamespace); + foreach (var type in GetPublicTypesRecursive(assembly.GlobalNamespace)) + { + // Skip generic types to avoid typeof() formatting complexity + if (type.IsGenericType) + { + continue; + } - // Prefer non-generic types to avoid typeof() formatting complexity - var publicType = publicTypes.FirstOrDefault(t => !t.IsGenericType) - ?? publicTypes.FirstOrDefault(); + var metadataName = GetFullMetadataName(type); + var resolvedType = compilation.GetTypeByMetadataName(metadataName); - if (publicType == null) - { - return null; - } + // If Roslyn resolves to the same type, it's unambiguous - use it + // GetTypeByMetadataName returns null when the type name is ambiguous + if (SymbolEqualityComparer.Default.Equals(resolvedType, type)) + { + return type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); + } - var typeName = publicType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); + // null or different type = ambiguous, try next type + } - // For generic types (fallback case), use open generic syntax for typeof() - // Example: global::Foo -> global::Foo<> - // Example: global::Foo -> global::Foo<,> - if (publicType.IsGenericType) + // Fallback: try generic types if no non-generic unique type was found + foreach (var type in GetPublicTypesRecursive(assembly.GlobalNamespace)) { - var openGenericSuffix = publicType.Arity == 1 - ? "<>" - : $"<{new string(',', publicType.Arity - 1)}>"; + if (!type.IsGenericType) + { + continue; + } - // Remove everything from < to the end and append the open generic suffix - var genericStart = typeName.LastIndexOf('<'); - if (genericStart > 0) + var metadataName = GetFullMetadataName(type); + var resolvedType = compilation.GetTypeByMetadataName(metadataName); + + if (SymbolEqualityComparer.Default.Equals(resolvedType, type)) { - typeName = typeName.Substring(0, genericStart) + openGenericSuffix; + var typeName = type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); + + // Use open generic syntax for typeof() + // Example: global::Foo -> global::Foo<> + // Example: global::Foo -> global::Foo<,> + var openGenericSuffix = type.Arity == 1 + ? "<>" + : $"<{new string(',', type.Arity - 1)}>"; + + var genericStart = typeName.LastIndexOf('<'); + if (genericStart > 0) + { + typeName = typeName.Substring(0, genericStart) + openGenericSuffix; + } + + return typeName; } } - return typeName; + return null; // No unique type found, skip this assembly + } + + /// + /// Gets the full metadata name for a type (e.g., "Namespace.OuterClass+NestedClass"). + /// This is the format expected by Compilation.GetTypeByMetadataName(). + /// + private static string GetFullMetadataName(INamedTypeSymbol type) + { + if (type.ContainingType != null) + { + return $"{GetFullMetadataName(type.ContainingType)}+{type.MetadataName}"; + } + + if (type.ContainingNamespace.IsGlobalNamespace) + { + return type.MetadataName; + } + + return $"{type.ContainingNamespace.ToDisplayString()}.{type.MetadataName}"; } private static IEnumerable GetPublicTypesRecursive(INamespaceSymbol namespaceSymbol)