Skip to content

Commit 9dd9da0

Browse files
committed
test: cover cross-assembly accessibility in the source generator
Generator tests so far compiled everything into a single "TestAssembly", so the cross-assembly branches of `Helpers.IsOverridableFrom` and `Helpers.ResolveOverrideVisibility` were never exercised. Add an `ExternalAssembly` test helper that compiles source into a separate in-memory assembly, plus `Generator.RunWithReferences`, which adds such references to the generated compilation. Use it to verify that - an `internal virtual` member from a referenced assembly is excluded, - it is included when `InternalsVisibleTo` is granted, - a `protected internal virtual` member is overridden as plain `protected` without `InternalsVisibleTo`, and as `protected internal` with it, - public members are mocked across the assembly boundary.
1 parent 46ac672 commit 9dd9da0

3 files changed

Lines changed: 198 additions & 2 deletions

File tree

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
using Microsoft.CodeAnalysis;
2+
3+
namespace Mockolate.SourceGenerators.Tests;
4+
5+
public sealed partial class MockTests
6+
{
7+
public sealed class CrossAssemblyTests
8+
{
9+
private const string CreateMockForMyBaseClass = """
10+
using Mockolate;
11+
12+
namespace MyCode;
13+
14+
public class Program
15+
{
16+
public static void Main(string[] args) => _ = Ext.MyBaseClass.CreateMock();
17+
}
18+
""";
19+
20+
[Fact]
21+
public async Task InternalVirtualMember_WithInternalsVisibleTo_ShouldBeOverridden()
22+
{
23+
MetadataReference external = CompileMyBaseClassAssembly(grantsInternalsVisibleTo: true);
24+
25+
GeneratorResult result = Generator.RunWithReferences(CreateMockForMyBaseClass, [external,]);
26+
27+
await That(result.Diagnostics).IsEmpty();
28+
await That(result.Sources).ContainsKey("Mock.MyBaseClass.g.cs");
29+
await That(result.Sources["Mock.MyBaseClass.g.cs"])
30+
.Contains("internal override void InternalVirtualMethod()").And
31+
.Contains("internal override int InternalVirtualProperty");
32+
}
33+
34+
[Fact]
35+
public async Task InternalVirtualMember_WithoutInternalsVisibleTo_ShouldNotBeOverridden()
36+
{
37+
MetadataReference external = CompileMyBaseClassAssembly(grantsInternalsVisibleTo: false);
38+
39+
GeneratorResult result = Generator.RunWithReferences(CreateMockForMyBaseClass, [external,]);
40+
41+
await That(result.Diagnostics).IsEmpty();
42+
await That(result.Sources).ContainsKey("Mock.MyBaseClass.g.cs");
43+
await That(result.Sources["Mock.MyBaseClass.g.cs"])
44+
.Contains("public override void PublicAbstractMethod()").And
45+
.DoesNotContain("InternalVirtualMethod").And
46+
.DoesNotContain("InternalVirtualProperty");
47+
}
48+
49+
[Fact]
50+
public async Task ProtectedInternalMember_WithInternalsVisibleTo_ShouldBeOverriddenAsProtectedInternal()
51+
{
52+
MetadataReference external = CompileMyBaseClassAssembly(grantsInternalsVisibleTo: true);
53+
54+
GeneratorResult result = Generator.RunWithReferences(CreateMockForMyBaseClass, [external,]);
55+
56+
await That(result.Diagnostics).IsEmpty();
57+
await That(result.Sources).ContainsKey("Mock.MyBaseClass.g.cs");
58+
await That(result.Sources["Mock.MyBaseClass.g.cs"])
59+
.Contains("protected internal override void ProtectedInternalMethod()").And
60+
.Contains("protected internal override int ProtectedInternalProperty").And
61+
.Contains("protected internal override event global::System.EventHandler? ProtectedInternalEvent").And
62+
.Contains("protected internal set");
63+
}
64+
65+
[Fact]
66+
public async Task ProtectedInternalMember_WithoutInternalsVisibleTo_ShouldBeOverriddenAsProtected()
67+
{
68+
MetadataReference external = CompileMyBaseClassAssembly(grantsInternalsVisibleTo: false);
69+
70+
GeneratorResult result = Generator.RunWithReferences(CreateMockForMyBaseClass, [external,]);
71+
72+
await That(result.Diagnostics).IsEmpty();
73+
await That(result.Sources).ContainsKey("Mock.MyBaseClass.g.cs");
74+
await That(result.Sources["Mock.MyBaseClass.g.cs"])
75+
.Contains("protected override void ProtectedInternalMethod()").And
76+
.Contains("protected override int ProtectedInternalProperty").And
77+
.Contains("protected override event global::System.EventHandler? ProtectedInternalEvent").And
78+
.Contains("protected set").And
79+
.DoesNotContain("protected internal override").And
80+
.DoesNotContain("protected internal set");
81+
}
82+
83+
[Fact]
84+
public async Task PublicMembers_ShouldBeMockedAcrossAssemblyBoundary()
85+
{
86+
MetadataReference external = ExternalAssembly.Compile("""
87+
namespace Ext;
88+
89+
public interface IMyService
90+
{
91+
int GetValue();
92+
}
93+
94+
public abstract class MyAbstractService
95+
{
96+
public abstract string Name { get; }
97+
public virtual int Compute() => 0;
98+
}
99+
""");
100+
101+
GeneratorResult result = Generator.RunWithReferences("""
102+
using Mockolate;
103+
104+
namespace MyCode;
105+
106+
public class Program
107+
{
108+
public static void Main(string[] args)
109+
{
110+
_ = Ext.IMyService.CreateMock();
111+
_ = Ext.MyAbstractService.CreateMock();
112+
}
113+
}
114+
""", [external,]);
115+
116+
await That(result.Diagnostics).IsEmpty();
117+
await That(result.Sources).ContainsKey("Mock.IMyService.g.cs").And
118+
.ContainsKey("Mock.MyAbstractService.g.cs");
119+
await That(result.Sources["Mock.IMyService.g.cs"])
120+
.Contains("public int GetValue()");
121+
await That(result.Sources["Mock.MyAbstractService.g.cs"])
122+
.Contains("public override string Name").And
123+
.Contains("public override int Compute()");
124+
}
125+
126+
private static MetadataReference CompileMyBaseClassAssembly(bool grantsInternalsVisibleTo)
127+
{
128+
string internalsVisibleTo = grantsInternalsVisibleTo
129+
? """[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("TestAssembly")]"""
130+
: "";
131+
return ExternalAssembly.Compile($$"""
132+
{{internalsVisibleTo}}
133+
namespace Ext;
134+
135+
public abstract class MyBaseClass
136+
{
137+
public abstract void PublicAbstractMethod();
138+
internal virtual void InternalVirtualMethod() { }
139+
internal virtual int InternalVirtualProperty { get; set; }
140+
protected internal virtual void ProtectedInternalMethod() { }
141+
protected internal virtual int ProtectedInternalProperty { get; set; }
142+
protected internal virtual event System.EventHandler? ProtectedInternalEvent;
143+
public virtual int MixedAccessorProperty { get; protected internal set; }
144+
}
145+
""");
146+
}
147+
}
148+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
using System.Diagnostics.CodeAnalysis;
2+
using System.IO;
3+
using System.Linq;
4+
using Microsoft.CodeAnalysis;
5+
using Microsoft.CodeAnalysis.CSharp;
6+
using Microsoft.CodeAnalysis.Emit;
7+
8+
namespace Mockolate.SourceGenerators.Tests.TestHelpers;
9+
10+
public static class ExternalAssembly
11+
{
12+
/// <summary>
13+
/// Compiles the <paramref name="source" /> into an in-memory library named
14+
/// <paramref name="assemblyName" /> and returns a reference to it.
15+
/// </summary>
16+
public static MetadataReference Compile([StringSyntax("c#-test")] string source,
17+
string assemblyName = "ExternalAssembly")
18+
{
19+
CSharpParseOptions parseOptions = new(LanguageVersion.Latest);
20+
CSharpCompilation compilation = CSharpCompilation.Create(
21+
assemblyName,
22+
[CSharpSyntaxTree.ParseText(source, parseOptions),],
23+
Generator.GetReferences([]),
24+
new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));
25+
26+
using MemoryStream stream = new();
27+
EmitResult emitResult = compilation.Emit(stream);
28+
if (!emitResult.Success)
29+
{
30+
throw new InvalidOperationException(
31+
$"Could not compile the external assembly '{assemblyName}':{Environment.NewLine}" +
32+
string.Join(Environment.NewLine, emitResult.Diagnostics
33+
.Where(diagnostic => diagnostic.Severity == DiagnosticSeverity.Error)
34+
.Select(diagnostic => diagnostic.ToString())));
35+
}
36+
37+
stream.Position = 0;
38+
return MetadataReference.CreateFromStream(stream);
39+
}
40+
}

Tests/Mockolate.SourceGenerators.Tests/TestHelpers/Generator.cs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,14 @@ public static GeneratorResult Run([StringSyntax("c#-test")] string source,
5454

5555
public static GeneratorResult Run(string[] sources, DocumentationMode documentationMode,
5656
string[] preprocessorSymbols, params Type[] assemblyTypes)
57+
=> RunCore(sources, documentationMode, preprocessorSymbols, [], assemblyTypes);
58+
59+
public static GeneratorResult RunWithReferences([StringSyntax("c#-test")] string source,
60+
MetadataReference[] externalReferences, params Type[] assemblyTypes)
61+
=> RunCore([source,], DocumentationMode.Parse, [], externalReferences, assemblyTypes);
62+
63+
private static GeneratorResult RunCore(string[] sources, DocumentationMode documentationMode,
64+
string[] preprocessorSymbols, MetadataReference[] externalReferences, Type[] assemblyTypes)
5765
{
5866
MockGenerator generator = new();
5967
CSharpParseOptions parseOptions = new CSharpParseOptions(LanguageVersion.Latest, documentationMode)
@@ -65,7 +73,7 @@ public static GeneratorResult Run(string[] sources, DocumentationMode documentat
6573
CSharpCompilation compilation = CSharpCompilation.Create(
6674
"TestAssembly",
6775
syntaxTrees,
68-
GetReferences(assemblyTypes),
76+
[..GetReferences(assemblyTypes), ..externalReferences,],
6977
new CSharpCompilationOptions(OutputKind.ConsoleApplication));
7078

7179
GeneratorDriver driver = CSharpGeneratorDriver.Create(
@@ -100,7 +108,7 @@ private static string ToDiagnosticString(Diagnostic d)
100108
return result;
101109
}
102110

103-
private static List<PortableExecutableReference> GetReferences(Type[] types) =>
111+
internal static List<PortableExecutableReference> GetReferences(Type[] types) =>
104112
AppDomain.CurrentDomain.GetAssemblies()
105113
.Where(x => !x.IsDynamic && !string.IsNullOrWhiteSpace(x.Location))
106114
.Select(x => x.Location)

0 commit comments

Comments
 (0)