diff options
| author | Karsten Heimrich <karsten.heimrich@qt.io> | 2025-10-20 16:56:34 +0200 |
|---|---|---|
| committer | Karsten Heimrich <karsten.heimrich@qt.io> | 2025-11-17 11:16:51 +0000 |
| commit | 72239132b6afb52f746cf62d4e01689748cb853d (patch) | |
| tree | 9379a7f5ceb5ad212be8362fb5dbdf69e9f0bf74 | |
| parent | a0088eaf4bb6f593db5a79eea48bc63d66e5c9f2 (diff) | |
Complement "Refactor source filtering attributes" with more auto-tests
Change-Id: Ief59de29dac764e31408b6712a4909a190aaf383
Reviewed-by: Miguel Costa <miguel.costa@qt.io>
| -rw-r--r-- | tests/Test_Qt.DotNet.Generator/Test_FilterAttributes.cs | 940 |
1 files changed, 940 insertions, 0 deletions
diff --git a/tests/Test_Qt.DotNet.Generator/Test_FilterAttributes.cs b/tests/Test_Qt.DotNet.Generator/Test_FilterAttributes.cs new file mode 100644 index 0000000..19a2154 --- /dev/null +++ b/tests/Test_Qt.DotNet.Generator/Test_FilterAttributes.cs @@ -0,0 +1,940 @@ +/*************************************************************************************************** + Copyright (C) 2025 The Qt Company Ltd. + SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +***************************************************************************************************/ + +using System; +using System.Linq; +using System.Reflection; +using System.Threading.Tasks; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Test_Qt.DotNet.Generator +{ + using Qt; + using Support; + + [TestClass] + public class Test_FilterAttributes + { + private static readonly Assembly AdapterAssembly = typeof(IncludeAttribute).Assembly; + + public TestContext TestContext { get; set; } + + [TestMethod] + public async Task Exclude_ExactType_RemovesTypeAndEdges() + { + const string src = """ + using Qt; + [assembly: Qt.IgnoreType(typeof(Test.B))] + namespace Test + { + public class A + { + public B DependsB { get; set; } + } + public class B + { + public int X { get; set; } + } + } + """; + + var res = await TestCodeGenerator.GenerateAsync([src], sourceRefs: [AdapterAssembly], + ct: TestContext.CancellationTokenSource.Token); + var graph = res.Graph; + + var typeA = graph.Keys.Single(t => t.FullName == "Test.A"); + Assert.IsTrue(graph.ContainsKey(typeA), "A should be present"); + + Assert.IsFalse(graph.Keys.Any(t => t.FullName == "Test.B"), + "B must be excluded by [assembly:Exclude(typeof(B))]"); + + Assert.IsFalse(graph.Connected(typeA).Any(t => t.FullName == "Test.B"), + "Edge A -> B must not exist when B is excluded"); + + Assert.IsFalse(graph[typeA].OfType<PropertyInfo>().Any(p => p.Name == "DependsB"), + "Property A.DependsB should not be added because its type is excluded"); + } + + [TestMethod] + public async Task Exclude_Inherited_ByType_RemovesDerivedTypes() + { + const string src = """ + using Qt; + [assembly: Qt.IgnoreType(typeof(Test.Base), Inherited = true)] + namespace Test + { + public class Base + {} + public class Derived + : Base + {} + public class Holder + { + public Derived Value { get; set; } + } + } + """; + + var res = await TestCodeGenerator.GenerateAsync([src], sourceRefs: [AdapterAssembly], + ct: TestContext.CancellationTokenSource.Token); + var graph = res.Graph; + + var holder = graph.Keys.Single(t => t.FullName == "Test.Holder"); + Assert.IsTrue(graph.ContainsKey(holder), "Holder should be present"); + + Assert.IsFalse(graph.Keys.Any(t => t.FullName == "Test.Derived"), + "Derived must be excluded because Base is excluded with Inherited = true"); + + Assert.IsFalse(graph[holder].OfType<PropertyInfo>().Any(p => p.Name == "Value"), + "Holder.Value property should be skipped because its type is excluded"); + } + + [TestMethod] + public async Task Exclude_Inherited_ByStringName_ShouldRemoveDerivedTypes() + { + const string src = """ + using Qt; + [assembly: Qt.IgnoreType("Test.Base", Inherited = true)] + namespace Test + { + public class Base + {} + public class Derived + : Base + {} + public class Holder + { + public Derived Value { get; set; } + } + } + """; + + var res = await TestCodeGenerator.GenerateAsync([src], sourceRefs: [AdapterAssembly], + ct: TestContext.CancellationTokenSource.Token); + var graph = res.Graph; + + var holder = graph.Keys.Single(t => t.FullName == "Test.Holder"); + Assert.IsTrue(graph.ContainsKey(holder), "Holder should be present"); + + Assert.IsFalse(graph.Keys.Any(t => t.FullName == "Test.Derived"), + "Derived must be excluded because Base is excluded with Inherited = true"); + + Assert.IsFalse(graph[holder].OfType<PropertyInfo>().Any(p => p.Name == "Value"), + "Holder.Value property should be skipped because its type is excluded"); + } + + [TestMethod] + public async Task Ignore_OnType_RemovesTypeEverywhere() + { + const string src = """ + using Qt; + namespace Test + { + [Qt.Ignore] + public class Ghost + { + public int X { get; set; } + } + public class UsesGhost + { + public Ghost G { get; set; } + } + } + """; + + var res = await TestCodeGenerator.GenerateAsync([src], sourceRefs: [AdapterAssembly], + ct: TestContext.CancellationTokenSource.Token); + var graph = res.Graph; + + Assert.IsFalse(graph.Keys.Any(t => t.FullName == "Test.Ghost"), + "[Ignore] on type should exclude it from the graph"); + + var usesGhost = graph.Keys.Single(t => t.FullName == "Test.UsesGhost"); + Assert.IsTrue(graph.ContainsKey(usesGhost), "UsesGhost should be present"); + + Assert.IsFalse(graph[usesGhost].OfType<PropertyInfo>().Any(p => p.Name == "G"), + "Property referencing an ignored type must be skipped"); + } + + [TestMethod] + public async Task Ignore_OnMember_RemovesOnlyThatMember() + { + const string src = """ + using Qt; + namespace Test + { + public class Data + { + [Qt.Ignore] + public int SkipMe { get; set; } + public int KeepMe { get; set; } + } + } + """; + + var res = await TestCodeGenerator.GenerateAsync([src], sourceRefs: [AdapterAssembly], + ct: TestContext.CancellationTokenSource.Token); + var graph = res.Graph; + + var data = graph.Keys.Single(t => t.FullName == "Test.Data"); + var members = graph[data].OfType<PropertyInfo>().ToArray(); + + Assert.IsFalse(members.Any(p => p.Name == "SkipMe"), "[Ignore] should drop the member"); + Assert.IsTrue(members.Any(p => p.Name == "KeepMe"), "Other members should remain"); + } + + [TestMethod] + public async Task Include_OnType_Overrides_AssemblyExclude() + { + const string src = """ + using Qt; + [assembly: Qt.IgnoreType(typeof(Test.X))] + namespace Test + { + [Qt.Include] + public class X + { + public int N { get; set; } + } + public class Holder + { + public X Value { get; set; } + } + } + """; + + var res = await TestCodeGenerator.GenerateAsync([src], sourceRefs: [AdapterAssembly], + ct: TestContext.CancellationTokenSource.Token); + var graph = res.Graph; + + var x = graph.Keys.Single(t => t.FullName == "Test.X"); + Assert.IsTrue(graph.ContainsKey(x), + "[Include] on X should override assembly-level exclude"); + + var h = graph.Keys.Single(t => t.FullName == "Test.Holder"); + Assert.IsTrue(graph[h].OfType<PropertyInfo>().Any(p => p.Name == "Value"), + "Holder.Value should be present since X is not excluded anymore"); + } + + [TestMethod] + public async Task Exclude_IEnumerable_Inherited_Excludes_Implementations_And_Collections() + { + const string src = """ + using Qt; + using System.Collections; + using System.Collections.Generic; + + [assembly: Qt.IgnoreType(typeof(IEnumerable<>), Inherited = true)] + namespace Test + { + public class ImplementsSeq : IEnumerable<int> + { + public IEnumerator<int> GetEnumerator() + => System.Linq.Enumerable.Empty<int>().GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + } + + public class Holder + { + public ImplementsSeq A { get; set; } // own implementation + public List<int> B { get; set; } // BCL-Collection + public int[] C { get; set; } // Array (impl. IEnumerable<int>) + } + } + """; + + var res = await TestCodeGenerator.GenerateAsync([src], + sourceRefs: [AdapterAssembly], + ct: TestContext.CancellationTokenSource.Token); + var graph = res.Graph; + + Assert.IsFalse(graph.Keys.Any(t => t.FullName == "Test.ImplementsSeq"), + "ImplementsSeq should be excluded by [Exclude(typeof(IEnumerable<>), Inherited=true)]."); + + var holder = graph.Keys.Single(t => t.FullName == "Test.Holder"); + Assert.IsTrue(graph.ContainsKey(holder), "Holder should be present"); + + var props = graph[holder].OfType<PropertyInfo>().Select(p => p.Name).ToArray(); + CollectionAssert.DoesNotContain(props, "A", + "Holder.A property must be excluded (user-provided IEnumerable implementation)."); + CollectionAssert.DoesNotContain(props, "B", + "Holder.B property (List<int>) must be excluded (implements IEnumerable<int>)."); + CollectionAssert.DoesNotContain(props, "C", + "Holder.C property (int[]) must be excluded (Array implements IEnumerable<int>)."); + } + + [TestMethod] + public async Task Exclude_IEnumerable_AssemblyQualifiedName_Excludes_Implementations() + { + var assemblyName = typeof(object).Assembly.GetName().Name; + var excludedTypeString = $"System.Collections.Generic.IEnumerable`1,{assemblyName}"; + + var src = $$""" + using Qt; + using System.Collections; + using System.Collections.Generic; + [assembly: Qt.IgnoreType("{{excludedTypeString}}", Inherited = true)] + namespace Test + { + public class ImplementsSeq : IEnumerable<int> + { + public IEnumerator<int> GetEnumerator() + => System.Linq.Enumerable.Empty<int>().GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + } + public class Holder + { + public ImplementsSeq A { get; set; } + public List<int> B { get; set; } + public int[] C { get; set; } + } + } + """; + + var res = await TestCodeGenerator.GenerateAsync([src], + sourceRefs: [AdapterAssembly], + ct: TestContext.CancellationTokenSource.Token); + var graph = res.Graph; + + Assert.IsFalse(graph.Keys.Any(t => t.FullName == "Test.ImplementsSeq"), + "Type 'Test.ImplementsSeq' should be excluded by [assembly: Qt.IgnoreType(\"System" + + $".Collections.Generic.IEnumerable`1,{assemblyName}\", Inherited = true)]."); + + var holder = graph.Keys.SingleOrDefault(t => t.FullName == "Test.Holder"); + Assert.IsNotNull(holder, "Type 'Test.Holder' must be present in the graph."); + + var props = graph[holder] + .OfType<PropertyInfo>() + .Select(p => p.Name) + .ToArray(); + + CollectionAssert.DoesNotContain(props, "A", + "Holder.A property must be excluded (user-provided IEnumerable implementation."); + CollectionAssert.DoesNotContain(props, "B", + "Holder.B property (List<int>) must be excluded (implements IEnumerable<int>)."); + CollectionAssert.DoesNotContain(props, "C", + "Holder.C property (int[]) must be excluded (Array implements IEnumerable<int>)."); + } + + [TestMethod] + public async Task Exclude_Interface_With_Inherited_Excludes_Implementors() + { + const string src = """ + using Qt; + [assembly: Qt.IgnoreType(typeof(Test.IThing), Inherited = true)] + namespace Test + { + public interface IThing + {} + public class Impl : IThing + {} + public class Holder + { + public Impl V { get; set; } + } + } + """; + + var res = await TestCodeGenerator.GenerateAsync([src], sourceRefs: [AdapterAssembly], + ct: TestContext.CancellationTokenSource.Token); + var graph = res.Graph; + + Assert.IsFalse(graph.Keys.Any(t => t.FullName == "Test.Impl")); + var h = graph.Keys.Single(t => t.FullName == "Test.Holder"); + Assert.IsFalse(graph[h].OfType<PropertyInfo>().Any(p => p.Name == "V")); + } + + [TestMethod] + + public async Task Exclude_OpenGenericBase_With_Inherited_Excludes_ClosedDerived() + { + const string src = """ + using Qt; + [assembly: Qt.IgnoreType(typeof(Test.Base<>), Inherited = true)] + namespace Test + { + public class Base<T> + {} + public class Derived + : Base<int> + {} + public class Holder + { + public Derived V { get; set; } + } + } + """; + + var res = await TestCodeGenerator.GenerateAsync([src], sourceRefs: [AdapterAssembly], + ct: TestContext.CancellationTokenSource.Token); + var graph = res.Graph; + + Assert.IsFalse(graph.Keys.Any(t => t.FullName == "Test.Derived")); + var h = graph.Keys.Single(t => t.FullName == "Test.Holder"); + Assert.IsFalse(graph[h].OfType<PropertyInfo>().Any(p => p.Name == "V")); + } + + [TestMethod] + public async Task Member_Ignore_Is_Not_Inherited_To_Overrides() + { + const string src = """ + using Qt; + namespace Test + { + public class B + { + [Qt.Ignore] + public virtual int P { get; set; } + } + public class D : B + { + public override int P { get; set; } + } + } + """; + + var res = await TestCodeGenerator.GenerateAsync([src], sourceRefs: [AdapterAssembly], + ct: TestContext.CancellationTokenSource.Token); + var graph = res.Graph; + + var d = graph.Keys.Single(t => t.FullName == "Test.D"); + // Expectation: Ignore is not [Inherited]; Override in D remains visible + Assert.IsTrue(graph[d].OfType<PropertyInfo>().Any(p => p.Name == "P")); + } + + [TestMethod] + public async Task BaseMethod_Ignored_DerivedOverride_Remains_Visible() + { + const string src = """ + using Qt; + namespace Test + { + public class B + { + [Qt.Ignore] + public virtual int M() => 0; + } + public class D : B + { + public override int M() => 1; + } + } + """; + + var res = await TestCodeGenerator.GenerateAsync([src], sourceRefs: [AdapterAssembly], + ct: TestContext.CancellationTokenSource.Token); + var graph = res.Graph; + + var d = graph.Keys.Single(t => t.FullName == "Test.D"); + Assert.IsTrue(graph[d].OfType<MethodInfo>().Any(m => m.Name == "M"), + "Override 'D.M' remains visible: Member-[Ignore] is not inheritable."); + } + + [TestMethod] + public async Task IncludeType_Overrides_AssemblyExclude() + { + const string src = """ + using Qt; + [assembly: Qt.IgnoreType(typeof(Test.K))] + namespace Test + { + [Qt.Include] + public class K + { + public int P { get; set; } + } + + public class Holder + { + public K A { get; set; } + } + } + """; + + var res = await TestCodeGenerator.GenerateAsync([src], sourceRefs: [AdapterAssembly], + ct: TestContext.CancellationTokenSource.Token); + var graph = res.Graph; + + Assert.IsTrue(graph.Keys.Any(t => t.FullName == "Test.K"), + "Type-level [Include] should restore K despite assembly-level exclude."); + var h = graph.Keys.Single(t => t.FullName == "Test.Holder"); + Assert.IsTrue(graph[h].OfType<PropertyInfo>().Any(p => p.Name == "A"), + "Holder.A must be present since K is included."); + } + + [TestMethod] + public async Task TypeExcluded_RemovesTypeAndReferencingMembers() + { + const string src = """ + using Qt; + [assembly: Qt.IgnoreType(typeof(Test.L))] + namespace Test + { + public class L + { + public int M { get; set; } + } + + public class Holder + { + public L B { get; set; } + } + } + """; + + var res = await TestCodeGenerator.GenerateAsync([src], sourceRefs: [AdapterAssembly], + ct: TestContext.CancellationTokenSource.Token); + var graph = res.Graph; + + Assert.IsFalse(graph.Keys.Any(t => t.FullName == "Test.L"), + "L must remain excluded by [assembly: IgnoreType]."); + var h = graph.Keys.Single(t => t.FullName == "Test.Holder"); + Assert.IsFalse(graph[h].OfType<PropertyInfo>().Any(p => p.Name == "B"), + "Holder.B must be excluded, since L is excluded."); + } + + [TestMethod] + public async Task MemberInclude_IsRejected_ByCompiler() + { + const string src = """ + using Qt; + [assembly: Qt.IgnoreType(typeof(Test.L))] + namespace Test + { + public class L + { + [Qt.Include] // not valid on members anymore + public int M { get; set; } + } + } + """; + + await Assert.ThrowsExactlyAsync<InvalidOperationException>(async () => + await TestCodeGenerator.GenerateAsync([src], + sourceRefs: [AdapterAssembly], + ct: TestContext.CancellationTokenSource.Token + ) + ); + } + + [TestMethod] + public async Task Delegate_Type_Can_Be_Excluded() + { + const string src = """ + using Qt; + [assembly: Qt.IgnoreType(typeof(Test.MyDel))] + namespace Test + { + public delegate void MyDel(int x); + public class Uses { public MyDel D; } + } + """; + + var res = await TestCodeGenerator.GenerateAsync([src], sourceRefs: [AdapterAssembly], + ct: TestContext.CancellationTokenSource.Token); + var graph = res.Graph; + + Assert.IsFalse(graph.Keys.Any(t => t.FullName == "Test.MyDel"), + "Test.MyDel should be excluded by [Exclude(typeof(Test.MyDel))]."); + + var uses = graph.Keys.Single(t => t.FullName == "Test.Uses"); + Assert.IsFalse(graph[uses].OfType<FieldInfo>().Any(f => f.Name == "D"), + "Field 'D' must be excluded when the delegate type is excluded."); + } + + [TestMethod] + public async Task Delegate_Param_And_Return_Removed_When_PayloadType_Excluded() + { + const string src = """ + using Qt; using System; + [assembly: Qt.IgnoreType(typeof(Test.Ex))] + namespace Test + { + public class Ex + {} + public class Svc + { + public void WithDelegateParam(Action<Ex> cb) + {} + public Func<Ex> Make() => null; + } + } + """; + + var res = await TestCodeGenerator.GenerateAsync([src], sourceRefs: [AdapterAssembly], + ct: TestContext.CancellationTokenSource.Token); + var graph = res.Graph; + + Assert.IsFalse(graph.Keys.Any(t => t.FullName == "Test.Ex"), + "Excluded type 'Test.Ex' must not be present in the graph."); + + var svc = graph.Keys.Single(t => t.FullName == "Test.Svc"); + + Assert.IsFalse(graph[svc].OfType<MethodInfo>().Any(m => m.Name == "WithDelegateParam"), + "Method 'Svc.WithDelegateParam(Action<Ex>)' must be removed because the delegate " + + "payload type is excluded."); + + Assert.IsFalse(graph[svc].OfType<MethodInfo>().Any(m => m.Name == "Make"), + "Method 'Svc.Make(): Func<Ex>' must be removed because the delegate payload type " + + "in the return is excluded."); + } + + [TestMethod] + public async Task Exclude_MultiArgs_Removes_Types_And_Edges() + { + const string src = """ + using Qt; + [assembly: Qt.IgnoreType(typeof(Test.A), typeof(Test.B))] + namespace Test + { + public class A + {} + public class B + {} + public class Holder + { + public A X { get; set; } + public B Y { get; set; } + } + } + """; + + var res = await TestCodeGenerator.GenerateAsync([src], sourceRefs: [AdapterAssembly], + ct: TestContext.CancellationTokenSource.Token); + var graph = res.Graph; + + Assert.IsFalse(graph.Keys.Any(t => t.FullName == "Test.A"), + "Type 'Test.A' must be excluded by multi-argument [assembly: Qt.IgnoreType]."); + Assert.IsFalse(graph.Keys.Any(t => t.FullName == "Test.B"), + "Type 'Test.B' must be excluded by multi-argument [assembly: Qt.IgnoreType]."); + + var h = graph.Keys.Single(t => t.FullName == "Test.Holder"); + var props = graph[h].OfType<PropertyInfo>().Select(p => p.Name).ToArray(); + CollectionAssert.DoesNotContain(props, "X", "Holder.X must be excluded (A excluded)."); + CollectionAssert.DoesNotContain(props, "Y", "Holder.Y must be excluded (B excluded)."); + + Assert.IsFalse(graph.Connected(h).Any(t => t.FullName is "Test.A" or "Test.B"), + "Edges from Holder to excluded types must be absent."); + } + + [TestMethod] + public async Task Exclude_Core_StringType_Removes_String_Property() + { + const string src = """ + using Qt; + [assembly: Qt.IgnoreType("System.String")] + namespace Test + { + public class Holder + { + public string S { get; set; } + } + } + """; + + var res = await TestCodeGenerator.GenerateAsync([src], sourceRefs: [AdapterAssembly], + ct: TestContext.CancellationTokenSource.Token); + var graph = res.Graph; + + var h = graph.Keys.Single(t => t.FullName == "Test.Holder"); + var props = graph[h].OfType<PropertyInfo>().Select(p => p.Name).ToArray(); + CollectionAssert.DoesNotContain(props, "S", + "Holder.S (string) must be excluded by [assembly: Qt.IgnoreType(\"System.String\")]."); + } + + [TestMethod] + public async Task Type_Include_Overrides_Type_Ignore() + { + const string src = """ + using Qt; + namespace Test + { + [Qt.Ignore] + [Qt.Include] + public class T + { + public int P { get; set; } + } + public class Holder + { + public T Val { get; set; } + } + } + """; + + var res = await TestCodeGenerator.GenerateAsync([src], sourceRefs: [AdapterAssembly], + ct: TestContext.CancellationTokenSource.Token); + var graph = res.Graph; + + Assert.IsTrue(graph.Keys.Any(t => t.FullName == "Test.T"), + "Type-level [Include] must override [Ignore] and keep 'Test.T'."); + + var h = graph.Keys.Single(t => t.FullName == "Test.Holder"); + var props = graph[h].OfType<PropertyInfo>().Select(p => p.Name).ToArray(); + CollectionAssert.Contains(props, "Val", + "Holder.Val must remain present since type-level Include wins."); + } + + [TestMethod] + public async Task ExcludedType_Removes_MethodParams_And_Events() + { + const string src = """ + using Qt; + using System; + [assembly: Qt.IgnoreType(typeof(Test.Ex))] + namespace Test + { + public class Ex + {} + + public class Svc + { + public void M(Ex x) {} + public event Action<Ex> Ev; + public Ex Returner() => null; + } + } + """; + + var res = await TestCodeGenerator.GenerateAsync([src], sourceRefs: [AdapterAssembly], + ct: TestContext.CancellationTokenSource.Token); + var graph = res.Graph; + + Assert.IsFalse(graph.Keys.Any(t => t.FullName == "Test.Ex"), + "Excluded type 'Test.Ex' must not be present in the graph."); + + var svc = graph.Keys.Single(t => t.FullName == "Test.Svc"); + Assert.IsFalse(graph[svc].OfType<MethodInfo>().Any(m => m.Name == "M"), + "Method 'Svc.M(Ex)' must be excluded because its parameter type is excluded."); + Assert.IsFalse(graph[svc].OfType<EventInfo>().Any(e => e.Name == "Ev"), + "Event 'Svc.Ev' must be excluded because its delegate generic argument is excluded."); + Assert.IsFalse(graph[svc].OfType<MethodInfo>().Any(m => m.Name == "Returner"), + "Method 'Svc.Returner' must be excluded because its return type is excluded."); + } + + [TestMethod] + public async Task Exclude_Interface_Inherited_Removes_MultiImplements() + { + const string src = """ + using Qt; + [assembly: Qt.IgnoreType(typeof(Test.I1), Inherited = true)] + namespace Test + { + public interface I1 + {} + public interface I2 + {} + public class D : I1, I2 + {} + public class Holder + { + public D V { get; set; } + } + } + """; + + var res = await TestCodeGenerator.GenerateAsync([src], sourceRefs: [AdapterAssembly], + ct: TestContext.CancellationTokenSource.Token); + var graph = res.Graph; + + Assert.IsFalse(graph.Keys.Any(t => t.FullName == "Test.D"), + "Implementer of excluded interface I1 must be removed."); + var h = graph.Keys.Single(t => t.FullName == "Test.Holder"); + Assert.IsFalse(graph[h].OfType<PropertyInfo>().Any(p => p.Name == "V"), + "Holder.V must be removed because its type implements excluded I1."); + } + + [TestMethod] + public async Task Exclude_BaseClass_Inherited_Removes_Derived_With_ExtraInterfaces() + { + const string src = """ + using Qt; + [assembly: Qt.IgnoreType(typeof(Test.Base), Inherited = true)] + namespace Test + { + public class Base + {} + public interface IMark + {} + public class D : Base, IMark + {} + public class Holder + { + public D V { get; set; } + } + } + """; + + var res = await TestCodeGenerator.GenerateAsync([src], sourceRefs: [AdapterAssembly], + ct: TestContext.CancellationTokenSource.Token); + var graph = res.Graph; + + Assert.IsFalse(graph.Keys.Any(t => t.FullName == "Test.D"), + "Derived class must be removed when base class is excluded with Inherited=true."); + var h = graph.Keys.Single(t => t.FullName == "Test.Holder"); + Assert.IsFalse(graph[h].OfType<PropertyInfo>().Any(p => p.Name == "V"), + "Holder.V must be removed because D is excluded."); + } + + [TestMethod] + public async Task Exclude_Interface_With_Default_Impl_Inherited_Removes_Implementer() + { + const string src = """ + using Qt; + [assembly: Qt.IgnoreType(typeof(Test.IDef), Inherited = true)] + namespace Test + { + public interface IDef + { + void M() { } // default implementation (C# 8+) + } + public class Impl : IDef + {} // relies on default M() + public class Holder + { + public Impl V { get; set; } + } + } + """; + + var res = await TestCodeGenerator.GenerateAsync([src], sourceRefs: [AdapterAssembly], + ct: TestContext.CancellationTokenSource.Token); + var graph = res.Graph; + + Assert.IsFalse(graph.Keys.Any(t => t.FullName == "Test.Impl"), + "Implementer must be removed even if interface has default methods."); + var h = graph.Keys.Single(t => t.FullName == "Test.Holder"); + Assert.IsFalse(graph[h].OfType<PropertyInfo>().Any(p => p.Name == "V"), + "Holder.V must be removed because Impl implements excluded IDef."); + } + + [TestMethod] + public async Task Exclude_Outer_Cascades_To_Inner() + { + const string src = """ + using Qt; + [assembly: Qt.IgnoreType(typeof(Test.Outer))] + namespace Test + { + public class Outer + { + public class Inner {} + } + public class Holder + { + public Outer.Inner X { get; set; } + } + } + """; + + var res = await TestCodeGenerator.GenerateAsync([src], sourceRefs: [AdapterAssembly], + ct: TestContext.CancellationTokenSource.Token); + var graph = res.Graph; + + Assert.IsFalse(graph.Keys.Any(t => + t.FullName == "Test.Outer"), "Outer must be excluded."); + Assert.IsFalse(graph.Keys.Any(t => + t.FullName == "Test.Outer+Inner"), "Inner must be excluded"); + + var holder = graph.Keys.Single(t => t.FullName == "Test.Holder"); + var props = graph[holder].OfType<PropertyInfo>().Select(p => p.Name).ToArray(); + + CollectionAssert.DoesNotContain(props, "X", + "With cascade policy, Holder.X must be removed because Outer.Inner is excluded."); + } + + [TestMethod] + public async Task Include_Inner_Overrides_Outer_Exclude() + { + const string src = """ + using Qt; + [assembly: Qt.IgnoreType(typeof(Test.Outer))] + namespace Test + { + public class Outer + { + [Qt.Include] + public class Inner {} + } + public class Holder + { + public Outer.Inner X { get; set; } + } + } + """; + + var res = await TestCodeGenerator.GenerateAsync([src], sourceRefs: [AdapterAssembly], + ct: TestContext.CancellationTokenSource.Token); + var g = res.Graph; + + Assert.IsFalse(g.Keys.Any(t => t.FullName == "Test.Outer"), + "Outer must be excluded by assembly-level rule."); + Assert.IsTrue(g.Keys.Any(t => t.FullName == "Test.Outer+Inner"), + "Inner must remain because it has a type-level [Include]."); + + var h = g.Keys.Single(t => t.FullName == "Test.Holder"); + Assert.IsTrue(g[h].OfType<PropertyInfo>().Any(p => p.Name == "X"), + "Holder.X must be present since Inner is included."); + } + + [TestMethod] + public async Task Exclude_NestedType_Removes_References() + { + const string src = """ + using Qt; + [assembly: Qt.IgnoreType(typeof(Test.Outer.Inner))] + namespace Test + { + public class Outer + { + public class Inner {} + } + public class Holder + { + public Outer.Inner X { get; set; } + } + } + """; + + var res = await TestCodeGenerator.GenerateAsync([src], sourceRefs: [AdapterAssembly], + ct: TestContext.CancellationTokenSource.Token); + var graph = res.Graph; + + Assert.IsFalse(graph.Keys.Any(t => t.FullName == "Test.Outer+Inner"), + "Explicit exclusion of the nested type must remove it."); + var h = graph.Keys.Single(t => t.FullName == "Test.Holder"); + Assert.IsFalse(graph[h].OfType<PropertyInfo>().Any(p => p.Name == "X"), + "Holder.X must be removed because Outer.Inner is excluded."); + } + + [TestMethod] + public async Task Exclude_OpenGeneric_List_Removes_Closed_Generic_Properties() + { + const string src = """ + using Qt; + using System.Collections.Generic; + [assembly: Qt.IgnoreType(typeof(List<>))] + namespace Test + { + public class Holder + { + public List<int> L { get; set; } + } + } + """; + + var res = await TestCodeGenerator.GenerateAsync([src], sourceRefs: [AdapterAssembly], + ct: TestContext.CancellationTokenSource.Token); + var graph = res.Graph; + + var h = graph.Keys.Single(t => t.FullName == "Test.Holder"); + var props = graph[h].OfType<PropertyInfo>().Select(p => p.Name).ToArray(); + CollectionAssert.DoesNotContain(props, "L", + "Holder.L (List<int>) must be removed when List<> is excluded."); + } + + + } +} |
