Skip to content

Commit c66e4c9

Browse files
committed
0.12.0 Fix several reasons for class mock generation failures
1 parent 62a6bfe commit c66e4c9

15 files changed

+739
-34
lines changed

CHANGELOG.md

+6
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# Changelog
22

3+
## [0.12.0] - 2021-11-13
4+
5+
### Fixed
6+
- Fix class mock generation failure when base class has any fields
7+
- Fix class mock generation failure when base class has internal members but no InternalsVisibleTo
8+
39
## [0.11.0] - 2021-10-21
410

511
### Fixed

Generators.Tests/Expected/BlobContainerClientMock.cs

+342
Large diffs are not rendered by default.

Generators.Tests/Generators.Tests.csproj

+1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
</PropertyGroup>
88

99
<ItemGroup>
10+
<PackageReference Include="Azure.Storage.Blobs" Version="12.10.0" />
1011
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="5.0.0" />
1112
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.5.0" />
1213
<PackageReference Include="xunit" Version="2.4.0" />

Generators.Tests/MockGeneratorTests.cs

+2
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using System.IO;
44
using System.Linq;
55
using System.Reflection;
6+
using Azure.Storage.Blobs;
67
using Microsoft.CodeAnalysis;
78
using Microsoft.CodeAnalysis.CSharp;
89
using Microsoft.Extensions.Logging;
@@ -19,6 +20,7 @@ public MockGeneratorTests(ITestOutputHelper output) {
1920

2021
[Theory]
2122
[InlineData(typeof(ILogger<>), "LoggerMock.cs")]
23+
[InlineData(typeof(BlobContainerClient), "BlobContainerClientMock.cs")]
2224
public void Generator_GeneratesExpectedMock(Type targetType, string expectedTextFileName) {
2325
// Arrange
2426
var targetTypeFullName = targetType.IsGenericType

Generators/Internal/MockClassGenerator.cs

+4-3
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ public MockClassGenerator(MockTargetModelFactory modelFactory) {
1717
}
1818

1919
[PerformanceSensitive("")]
20-
public string Generate(MockTarget target) {
20+
public string Generate(MockTarget target, IAssemblySymbol currentAssembly) {
2121
var targetTypeNamespace = target.Type.ContainingNamespace.ToDisplayString(TargetTypeNamespaceDisplayFormat);
2222
var mockBaseName = GenerateMockBaseName(target.Type.Name);
2323
var typeParameters = GenerateTypeParametersAsString(target);
@@ -58,7 +58,7 @@ public string Generate(MockTarget target) {
5858
.WriteLine(Indents.Type, "internal interface ", callsInterfaceName, " {");
5959

6060
#pragma warning disable HAA0401 // Possible allocation of reference type enumerator - TODO
61-
foreach (var member in _modelFactory.GetMockTargetMembers(target, customDelegatesClassName)) {
61+
foreach (var member in _modelFactory.GetMockTargetMembers(target, customDelegatesClassName, currentAssembly)) {
6262
#pragma warning restore HAA0401
6363
mainWriter.WriteLine();
6464
_mockMemberGenerator.WriteMemberMocks(
@@ -68,7 +68,8 @@ public string Generate(MockTarget target) {
6868
setupInterfaceName,
6969
callsInterfaceWriter,
7070
callsInterfaceName,
71-
member
71+
member,
72+
currentAssembly
7273
).WriteLine();
7374
}
7475

Generators/Internal/MockMemberGenerator.cs

+8-6
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,15 @@ public CodeWriter WriteMemberMocks(
1616
string setupInterfaceName,
1717
CodeWriter callsInterfaceWriter,
1818
string callsInterfaceName,
19-
in MockTargetMember member
19+
in MockTargetMember member,
20+
IAssemblySymbol currentAssembly
2021
) {
2122
WriteHandlerField(mockWriter, member).WriteLine();
2223
if (member.MethodRunDelegateType is { IsCustom: true } runDelegate)
2324
WriteCustomRunDelegate(customDelegatesClassWriter, member, runDelegate).WriteLine();
2425
WriteSetupInterfaceMember(setupInterfaceWriter, member).WriteLine();
2526
WriteSetupMemberImplementation(mockWriter, setupInterfaceName, member).WriteLine();
26-
WriteMemberImplementation(mockWriter, member).WriteLine();
27+
WriteMemberImplementation(mockWriter, member, currentAssembly).WriteLine();
2728
WriteCallsInterfaceMember(callsInterfaceWriter, member).WriteLine();
2829
WriteCallsMemberImplementation(mockWriter, callsInterfaceName, member);
2930
return mockWriter;
@@ -115,13 +116,14 @@ private CodeWriter WriteSetupMemberType(CodeWriter writer, in MockTargetMember m
115116
}
116117

117118
[PerformanceSensitive("")]
118-
private CodeWriter WriteMemberImplementation(CodeWriter writer, in MockTargetMember member) {
119+
private CodeWriter WriteMemberImplementation(CodeWriter writer, in MockTargetMember member, IAssemblySymbol currentAssembly) {
119120
writer.Write(Indents.Member);
120121
if (member.Symbol.ContainingType.TypeKind == TypeKind.Interface) {
121122
writer.Write("public ");
122123
}
123124
else {
124-
writer.Write(GetAccessibility(member.Symbol.DeclaredAccessibility), " override ");
125+
var currentAssemblyHasInternalAccess = member.Symbol.ContainingAssembly.GivesAccessTo(currentAssembly);
126+
writer.Write(GetAccessibility(member.Symbol.DeclaredAccessibility, currentAssemblyHasInternalAccess), " override ");
125127
}
126128
writer.Write(member.TypeFullName, " ", member.Name);
127129

@@ -171,10 +173,10 @@ private CodeWriter WriteMemberImplementation(CodeWriter writer, in MockTargetMem
171173
}
172174

173175
[PerformanceSensitive("")]
174-
private string GetAccessibility(Accessibility accessibility) => accessibility switch {
176+
private string GetAccessibility(Accessibility accessibility, bool currentAssemblylHasInternalAccess) => accessibility switch {
175177
Accessibility.Public => "public",
176178
Accessibility.Protected => "protected",
177-
Accessibility.ProtectedOrInternal => "protected internal",
179+
Accessibility.ProtectedOrInternal => currentAssemblylHasInternalAccess ? "protected internal" : "protected",
178180
Accessibility.ProtectedAndInternal => "private protected",
179181
#pragma warning disable HAA0601 // Boxing -- OK in exceptional case
180182
_ => throw Exceptions.NotSupported($"Unexpected accessibility: {accessibility}")

Generators/Internal/MockTargetModelFactory.cs

+26-13
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ public MockTarget GetMockTarget(INamedTypeSymbol type) {
1616
}
1717

1818
[PerformanceSensitive("")]
19-
public IEnumerable<MockTargetMember> GetMockTargetMembers(MockTarget target, string customDelegatesClassName) {
19+
public IEnumerable<MockTargetMember> GetMockTargetMembers(MockTarget target, string customDelegatesClassName, IAssemblySymbol currentAssembly) {
2020
#pragma warning disable HAA0502 // Explicit allocation -- unavoidable for now, can be pooled later (or removed if we handle them differently)
2121
var lastOverloadIds = new Dictionary<string, int>();
2222
#pragma warning restore HAA0502
@@ -26,7 +26,7 @@ public IEnumerable<MockTargetMember> GetMockTargetMembers(MockTarget target, str
2626
lastOverloadId = 0;
2727

2828
var overloadId = lastOverloadId + 1;
29-
if (GetMockTargetMember(member, overloadId, customDelegatesClassName) is not {} discovered)
29+
if (GetMockTargetMember(member, overloadId, customDelegatesClassName, currentAssembly) is not {} discovered)
3030
continue;
3131

3232
lastOverloadIds[member.Name] = overloadId;
@@ -41,7 +41,7 @@ public IEnumerable<MockTargetMember> GetMockTargetMembers(MockTarget target, str
4141
if (lastOverloadIds.ContainsKey(member.Name))
4242
throw Exceptions.NotSupported($"Type member {@interface.Name}.{member.Name} is hidden or overloaded by another type member. This is not yet supported.");
4343

44-
if (GetMockTargetMember(member, 1, customDelegatesClassName) is not {} discovered)
44+
if (GetMockTargetMember(member, 1, customDelegatesClassName, currentAssembly) is not {} discovered)
4545
continue;
4646

4747
lastOverloadIds[member.Name] = 1;
@@ -51,20 +51,25 @@ public IEnumerable<MockTargetMember> GetMockTargetMembers(MockTarget target, str
5151
}
5252

5353
[PerformanceSensitive("")]
54-
private MockTargetMember? GetMockTargetMember(ISymbol member, int overloadId, string customDelegatesClassName) => member switch {
55-
IMethodSymbol method => GetMockTargetMethod(method, overloadId, customDelegatesClassName),
56-
IPropertySymbol property => GetMockTargetProperty(property, overloadId),
54+
private MockTargetMember? GetMockTargetMember(ISymbol member, int overloadId, string customDelegatesClassName, IAssemblySymbol currentAssembly) => member switch {
55+
IMethodSymbol method => GetMockTargetMethod(method, overloadId, customDelegatesClassName, currentAssembly),
56+
IPropertySymbol property => GetMockTargetProperty(property, overloadId, currentAssembly),
57+
IFieldSymbol => null,
58+
ITypeSymbol => null,
5759
_ => throw Exceptions.MemberNotSupported(member)
5860
};
5961

6062
[PerformanceSensitive("")]
61-
private MockTargetMember? GetMockTargetMethod(IMethodSymbol method, int overloadId, string customDelegatesClassName) {
63+
private MockTargetMember? GetMockTargetMethod(IMethodSymbol method, int overloadId, string customDelegatesClassName, IAssemblySymbol currentAssembly) {
6264
if (method.MethodKind != MethodKind.Ordinary)
6365
return null;
6466

6567
if (!IsVirtual(method))
6668
return null;
6769

70+
if (!IsVisible(method, currentAssembly))
71+
return null;
72+
6873
var parameters = ConvertParametersFromSymbols(method.Parameters);
6974
var returnTypeFullName = GetFullTypeName(method.ReturnType, method.ReturnNullableAnnotation);
7075

@@ -80,10 +85,13 @@ public IEnumerable<MockTargetMember> GetMockTargetMembers(MockTarget target, str
8085
}
8186

8287
[PerformanceSensitive("")]
83-
private MockTargetMember? GetMockTargetProperty(IPropertySymbol property, int overloadId) {
88+
private MockTargetMember? GetMockTargetProperty(IPropertySymbol property, int overloadId, IAssemblySymbol currentAssembly) {
8489
if (!IsVirtual(property))
8590
return null;
8691

92+
if (!IsVisible(property, currentAssembly))
93+
return null;
94+
8795
return new(
8896
property, property.Name, property.Type,
8997
GetFullTypeName(property.Type, property.NullableAnnotation),
@@ -98,11 +106,16 @@ public IEnumerable<MockTargetMember> GetMockTargetMembers(MockTarget target, str
98106
}
99107

100108
[PerformanceSensitive("")]
101-
private bool IsVirtual(ISymbol symbol) {
102-
return symbol.ContainingType.TypeKind == TypeKind.Interface
103-
|| symbol.IsAbstract
104-
|| symbol.IsVirtual;
105-
}
109+
private bool IsVirtual(ISymbol symbol)
110+
=> symbol.ContainingType.TypeKind == TypeKind.Interface
111+
|| symbol.IsAbstract
112+
|| symbol.IsVirtual;
113+
114+
[PerformanceSensitive("")]
115+
private bool IsVisible(ISymbol symbol, IAssemblySymbol currentAssembly)
116+
=> symbol.ContainingType.TypeKind == TypeKind.Interface
117+
|| symbol.DeclaredAccessibility is Accessibility.Public or Accessibility.ProtectedOrInternal
118+
|| symbol.ContainingAssembly.GivesAccessTo(currentAssembly) && (symbol.DeclaredAccessibility is Accessibility.Internal or Accessibility.ProtectedAndInternal);
106119

107120
[PerformanceSensitive("")]
108121
private string GetFullTypeName(ITypeSymbol type, NullableAnnotation nullableAnnotation) {

Generators/MockGenerator.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -199,7 +199,7 @@ in GeneratorExecutionContext context
199199
GeneratorLog.Log("Generating mock for type " + target.FullTypeName);
200200
string mockContent;
201201
try {
202-
mockContent = _classGenerator.Generate(target);
202+
mockContent = _classGenerator.Generate(target, context.Compilation.Assembly);
203203
}
204204
catch (Exception ex) {
205205
#pragma warning disable HAA0101 // Array allocation for params parameter

0 commit comments

Comments
 (0)