Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
// <auto-generated/>
#pragma warning disable

#nullable enable
namespace TUnit.Generated;
[global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverageAttribute]
[global::System.CodeDom.Compiler.GeneratedCode("TUnit", "VERSION_SCRUBBED")]
internal static class TUnit_TestProject_CustomAttributeInheritanceTests__TestSource
{
private static readonly global::TUnit.Core.ClassMetadata __classMetadata = global::TUnit.Core.ClassMetadata.GetOrAdd("TestsBase`1:global::TUnit.TestProject.CustomAttributeInheritanceTests", new global::TUnit.Core.ClassMetadata
{
Type = typeof(global::TUnit.TestProject.CustomAttributeInheritanceTests),
TypeInfo = new global::TUnit.Core.ConcreteType(typeof(global::TUnit.TestProject.CustomAttributeInheritanceTests)),
Name = "CustomAttributeInheritanceTests",
Namespace = "TUnit.TestProject",
Assembly = global::TUnit.Core.AssemblyMetadata.GetOrAdd("TestsBase`1", "TestsBase`1"),
Parameters = global::System.Array.Empty<global::TUnit.Core.ParameterMetadata>(),
Properties = global::System.Array.Empty<global::TUnit.Core.PropertyMetadata>(),
Parent = null
});
private static readonly global::System.Type __classType = typeof(global::TUnit.TestProject.CustomAttributeInheritanceTests);
private static readonly global::TUnit.Core.MethodMetadata __mm_0 = global::TUnit.Core.MethodMetadataFactory.Create("Test", __classType, typeof(void), __classMetadata);
private static global::TUnit.TestProject.CustomAttributeInheritanceTests __CreateInstance(global::System.Type[] typeArgs, object?[] args)
{
return new global::TUnit.TestProject.CustomAttributeInheritanceTests();
}
private static global::System.Threading.Tasks.ValueTask __Invoke(global::TUnit.TestProject.CustomAttributeInheritanceTests instance, int methodIndex, object?[] args, global::System.Threading.CancellationToken cancellationToken)
{
switch (methodIndex)
{
case 0:
{
try
{
instance.Test();
return default(global::System.Threading.Tasks.ValueTask);
}
catch (global::System.Exception ex)
{
return new global::System.Threading.Tasks.ValueTask(global::System.Threading.Tasks.Task.FromException(ex));
}
}
default:
throw new global::System.ArgumentOutOfRangeException(nameof(methodIndex));
}
}
private static global::System.Attribute[] __Attributes(int groupIndex)
{
switch (groupIndex)
{
case 0:
{
return
[
new global::TUnit.Core.TestAttribute(),
new global::TUnit.TestProject.Attributes.EngineTest(global::TUnit.TestProject.Attributes.ExpectedResult.Pass),
new global::TUnit.TestProject.Attributes.SkipNetFrameworkAttribute("Not supported on .NET Framework")
];
}
default:
throw new global::System.ArgumentOutOfRangeException(nameof(groupIndex));
}
}
public static readonly global::TUnit.Core.TestEntry<global::TUnit.TestProject.CustomAttributeInheritanceTests>[] Entries = new global::TUnit.Core.TestEntry<global::TUnit.TestProject.CustomAttributeInheritanceTests>[]
{
new global::TUnit.Core.TestEntry<global::TUnit.TestProject.CustomAttributeInheritanceTests>
{
MethodName = "Test",
FullyQualifiedName = "TUnit.TestProject.CustomAttributeInheritanceTests.Test",
FilePath = @"",
LineNumber = 9,
Categories = global::System.Array.Empty<string>(),
Properties = global::System.Array.Empty<string>(),
HasDataSource = false,
RepeatCount = 0,
DependsOn = global::System.Array.Empty<string>(),
MethodMetadata = __mm_0,
CreateInstance = __CreateInstance,
InvokeBody = __Invoke,
MethodIndex = 0,
CreateAttributes = __Attributes,
AttributeGroupIndex = 0,
},
};
}
internal static partial class TUnit_TestRegistration
{
static readonly int _r_TUnit_TestProject_CustomAttributeInheritanceTests__TestSource = global::TUnit.Core.SourceRegistrar.RegisterEntries<global::TUnit.TestProject.CustomAttributeInheritanceTests>(static () => TUnit_TestProject_CustomAttributeInheritanceTests__TestSource.Entries);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using TUnit.Core.SourceGenerator.Tests.Options;

namespace TUnit.Core.SourceGenerator.Tests;

internal class CustomAttributeInheritanceTests : TestsBase
{
[Test]
public Task Test() => RunTest(Path.Combine(Git.RootDirectory.FullName,
"TUnit.TestProject",
"CustomAttributeInheritanceTests.cs"),
new RunTestOptions(),
async generatedFiles =>
{
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,7 @@ global::TUnit.Core.ParameterMetadataFactory.Create(typeof(global::System.Threadi
return
[
new global::TUnit.Core.TestAttribute(),
new global::TUnit.TestProject.Attributes.EngineTest(global::TUnit.TestProject.Attributes.ExpectedResult.Pass),
new global::System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessageAttribute("Usage", "TUnit0046:Return a `Func<T>` rather than a `<T>`")
new global::TUnit.TestProject.Attributes.EngineTest(global::TUnit.TestProject.Attributes.ExpectedResult.Pass)
];
}
default:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -223,8 +223,7 @@ internal static class TUnit_TestProject_STAThreadTests__TestSource
new global::TUnit.Core.Executors.STAThreadExecutorAttribute(),
new global::TUnit.TestProject.Attributes.EngineTest(global::TUnit.TestProject.Attributes.ExpectedResult.Pass),
new global::TUnit.Core.RunOnAttribute(global::TUnit.Core.Enums.OS.Windows),
new global::TUnit.Core.RepeatAttribute(100),
new global::System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessageAttribute("Interoperability", "CA1416:Validate platform compatibility")
new global::TUnit.Core.RepeatAttribute(100)
];
}
case 1:
Expand All @@ -234,8 +233,7 @@ internal static class TUnit_TestProject_STAThreadTests__TestSource
new global::TUnit.Core.TestAttribute(),
new global::TUnit.TestProject.Attributes.EngineTest(global::TUnit.TestProject.Attributes.ExpectedResult.Pass),
new global::TUnit.Core.RunOnAttribute(global::TUnit.Core.Enums.OS.Windows),
new global::TUnit.Core.RepeatAttribute(100),
new global::System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessageAttribute("Interoperability", "CA1416:Validate platform compatibility")
new global::TUnit.Core.RepeatAttribute(100)
];
}
default:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -223,8 +223,7 @@ internal static class TUnit_TestProject_STAThreadTests__TestSource
new global::TUnit.Core.Executors.STAThreadExecutorAttribute(),
new global::TUnit.TestProject.Attributes.EngineTest(global::TUnit.TestProject.Attributes.ExpectedResult.Pass),
new global::TUnit.Core.RunOnAttribute(global::TUnit.Core.Enums.OS.Windows),
new global::TUnit.Core.RepeatAttribute(100),
new global::System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessageAttribute("Interoperability", "CA1416:Validate platform compatibility")
new global::TUnit.Core.RepeatAttribute(100)
];
}
case 1:
Expand All @@ -234,8 +233,7 @@ internal static class TUnit_TestProject_STAThreadTests__TestSource
new global::TUnit.Core.TestAttribute(),
new global::TUnit.TestProject.Attributes.EngineTest(global::TUnit.TestProject.Attributes.ExpectedResult.Pass),
new global::TUnit.Core.RunOnAttribute(global::TUnit.Core.Enums.OS.Windows),
new global::TUnit.Core.RepeatAttribute(100),
new global::System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessageAttribute("Interoperability", "CA1416:Validate platform compatibility")
new global::TUnit.Core.RepeatAttribute(100)
];
}
default:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -223,8 +223,7 @@ internal static class TUnit_TestProject_STAThreadTests__TestSource
new global::TUnit.Core.Executors.STAThreadExecutorAttribute(),
new global::TUnit.TestProject.Attributes.EngineTest(global::TUnit.TestProject.Attributes.ExpectedResult.Pass),
new global::TUnit.Core.RunOnAttribute(global::TUnit.Core.Enums.OS.Windows),
new global::TUnit.Core.RepeatAttribute(100),
new global::System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessageAttribute("Interoperability", "CA1416:Validate platform compatibility")
new global::TUnit.Core.RepeatAttribute(100)
];
}
case 1:
Expand All @@ -234,8 +233,7 @@ internal static class TUnit_TestProject_STAThreadTests__TestSource
new global::TUnit.Core.TestAttribute(),
new global::TUnit.TestProject.Attributes.EngineTest(global::TUnit.TestProject.Attributes.ExpectedResult.Pass),
new global::TUnit.Core.RunOnAttribute(global::TUnit.Core.Enums.OS.Windows),
new global::TUnit.Core.RepeatAttribute(100),
new global::System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessageAttribute("Interoperability", "CA1416:Validate platform compatibility")
new global::TUnit.Core.RepeatAttribute(100)
];
}
default:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -223,8 +223,7 @@ internal static class TUnit_TestProject_STAThreadTests__TestSource
new global::TUnit.Core.Executors.STAThreadExecutorAttribute(),
new global::TUnit.TestProject.Attributes.EngineTest(global::TUnit.TestProject.Attributes.ExpectedResult.Pass),
new global::TUnit.Core.RunOnAttribute(global::TUnit.Core.Enums.OS.Windows),
new global::TUnit.Core.RepeatAttribute(100),
new global::System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessageAttribute("Interoperability", "CA1416:Validate platform compatibility")
new global::TUnit.Core.RepeatAttribute(100)
];
}
case 1:
Expand All @@ -234,8 +233,7 @@ internal static class TUnit_TestProject_STAThreadTests__TestSource
new global::TUnit.Core.TestAttribute(),
new global::TUnit.TestProject.Attributes.EngineTest(global::TUnit.TestProject.Attributes.ExpectedResult.Pass),
new global::TUnit.Core.RunOnAttribute(global::TUnit.Core.Enums.OS.Windows),
new global::TUnit.Core.RepeatAttribute(100),
new global::System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessageAttribute("Interoperability", "CA1416:Validate platform compatibility")
new global::TUnit.Core.RepeatAttribute(100)
];
}
default:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,12 @@

namespace TUnit.Core.SourceGenerator.CodeGenerators.Writers;

public class AttributeWriter(Compilation compilation, TUnit.Core.SourceGenerator.Helpers.WellKnownTypes wellKnownTypes)
public class AttributeWriter(Compilation compilation)
{
private const string TUnitRootNamespace = "TUnit";

private readonly Dictionary<AttributeData, string> _attributeObjectInitializerCache = new();
private readonly Dictionary<INamedTypeSymbol, bool> _tunitRelatedCache = new(SymbolEqualityComparer.Default);

public void WriteAttributes(ICodeWriter sourceCodeWriter,
IEnumerable<AttributeData> attributeDatas)
Expand All @@ -17,22 +20,13 @@ public void WriteAttributes(ICodeWriter sourceCodeWriter,
// Filter out attributes that we can write
foreach (var attributeData in attributeDatas)
{
// Include attributes with syntax reference (from current compilation)
// Include attributes without syntax reference (from other assemblies) as long as they have an AttributeClass
if (attributeData.ApplicationSyntaxReference is null && attributeData.AttributeClass is null)
{
continue;
}

// Skip compiler-internal and assembly-level attributes
if (ShouldSkipCompilerInternalAttribute(attributeData))
if (attributeData.AttributeClass is null)
{
continue;
}

// Skip framework-specific attributes when targeting older frameworks
// We determine this by checking if we can compile the attribute
if (ShouldSkipFrameworkSpecificAttribute(attributeData))
// Only include attributes that are, inherit from, or implement a TUnit type
if (!IsTUnitRelatedAttribute(attributeData.AttributeClass))
{
continue;
}
Expand Down Expand Up @@ -185,80 +179,32 @@ public static void WriteAttributeWithoutSyntax(ICodeWriter sourceCodeWriter, Att
}


private bool ShouldSkipFrameworkSpecificAttribute(AttributeData attributeData)
private bool IsTUnitRelatedAttribute(INamedTypeSymbol attributeClass)
{
if (attributeData.AttributeClass == null)
if (_tunitRelatedCache.TryGetValue(attributeClass, out var cached))
{
return false;
return cached;
}

// Generic approach: Check if the attribute type is actually available in the target compilation
// This works by seeing if we can resolve the type from the compilation's references
var fullyQualifiedName = wellKnownTypes.GetDisplayString(attributeData.AttributeClass);

// Check if this is a system/runtime attribute that might not exist on all frameworks
if (fullyQualifiedName.StartsWith("System.") || fullyQualifiedName.StartsWith("Microsoft."))
{
// Try to get the type from the compilation
// If it doesn't exist in the compilation's references, we should skip it
var typeSymbol = wellKnownTypes.TryGet(fullyQualifiedName);

// If the type doesn't exist in the compilation, skip it
if (typeSymbol == null)
{
return true;
}
var result = attributeClass.GetSelfAndBaseTypes().Any(IsInTUnitNamespace)
|| attributeClass.AllInterfaces.Any(IsInTUnitNamespace);

// Special handling for attributes that exist but may not be usable
// For example, nullable attributes exist in the reference assemblies but not at runtime for .NET Framework
if (IsNullableAttribute(fullyQualifiedName))
{
// Check if we're targeting .NET Framework by looking at references
var isNetFramework = compilation.References.Any(r =>
r.Display?.Contains("mscorlib") == true &&
!r.Display.Contains("System.Runtime"));

if (isNetFramework)
{
return true; // Skip nullable attributes on .NET Framework
}
}
}

return false;
}

private static bool IsNullableAttribute(string fullyQualifiedName)
{
return fullyQualifiedName.Contains("NullableAttribute") ||
fullyQualifiedName.Contains("NullableContextAttribute") ||
fullyQualifiedName.Contains("NullablePublicOnlyAttribute");
_tunitRelatedCache[attributeClass] = result;
return result;
}

private bool ShouldSkipCompilerInternalAttribute(AttributeData attributeData)
private static bool IsInTUnitNamespace(INamedTypeSymbol type)
{
if (attributeData.AttributeClass == null)
{
return false;
}

var fullyQualifiedName = wellKnownTypes.GetDisplayString(attributeData.AttributeClass);

// Skip compiler-internal attributes that should never be re-emitted
// System.Runtime.CompilerServices contains compiler-generated and structural metadata attributes
if (fullyQualifiedName.StartsWith("System.Runtime.CompilerServices."))
{
return true;
}
var ns = type.ContainingNamespace;
INamespaceSymbol? outermost = null;

// Skip debugger attributes (compiler-generated for debugging support)
if (fullyQualifiedName.StartsWith("System.Diagnostics.Debugger"))
while (ns is { IsGlobalNamespace: false })
{
return true;
outermost = ns;
ns = ns.ContainingNamespace;
}

// Skip ParamArrayAttribute (compiler-generated for params keyword)
return fullyQualifiedName == "System.ParamArrayAttribute";
return outermost?.Name == TUnitRootNamespace;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
var wellKnownTypes = new WellKnownTypes(c);
return new CompilationContext(
(CSharpCompilation)c,
new AttributeWriter(c, wellKnownTypes),
new AttributeWriter(c),
wellKnownTypes
);
});
Expand Down
14 changes: 14 additions & 0 deletions TUnit.TestProject/CustomAttributeInheritanceTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using TUnit.Core;
using TUnit.TestProject.Attributes;

namespace TUnit.TestProject;

[SkipNetFramework("Not supported on .NET Framework")]
public class CustomAttributeInheritanceTests
{
[Test]
[EngineTest(ExpectedResult.Pass)]
public void Test()
{
}
}
Loading