Skip to content

Commit

Permalink
Merge pull request dotnet#34 from kevinwkt/TypeWrapperAssemblyMetadat…
Browse files Browse the repository at this point in the history
…aFix

Add typewrapper tests after fixing metadataloadcontext issue
  • Loading branch information
kevinwkt authored Aug 7, 2020
2 parents 7d31d9c + d0625ff commit efc134e
Show file tree
Hide file tree
Showing 9 changed files with 247 additions and 34 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,22 @@

using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Reflection;
using System.Text.Json.Serialization;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Xunit;

namespace System.Text.Json.SourceGeneration.UnitTests
{
public class CompilationHelper
{
public static Compilation CreateCompilation(string source)
public static Compilation CreateCompilation(string source, MetadataReference[] additionalReferences = null)
{
// Bypass System.Runtime error.
Assembly systemRuntimeAssembly = Assembly.Load("System.Runtime, Version=5.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a");
string systemRuntimeAssemblyPath = systemRuntimeAssembly.Location;

MetadataReference[] references = new MetadataReference[] {
List<MetadataReference> references = new List<MetadataReference> {
MetadataReference.CreateFromFile(typeof(object).Assembly.Location),
MetadataReference.CreateFromFile(typeof(Attribute).Assembly.Location),
MetadataReference.CreateFromFile(typeof(JsonSerializableAttribute).Assembly.Location),
Expand All @@ -31,10 +29,19 @@ public static Compilation CreateCompilation(string source)
MetadataReference.CreateFromFile(systemRuntimeAssemblyPath),
};

// Add additional references as needed.
if (additionalReferences != null)
{
foreach (MetadataReference reference in additionalReferences)
{
references.Add(reference);
}
}

return CSharpCompilation.Create(
"TestAssembly",
syntaxTrees: new[] { CSharpSyntaxTree.ParseText(source) },
references: references,
references: references.ToArray(),
options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,15 +48,16 @@ public void UsePrivates()

JsonSerializerSourceGenerator generator = new JsonSerializerSourceGenerator();

Compilation newCompilation = CompilationHelper.RunGenerators(compilation, out var generatorDiags, generator);
Compilation newCompilation = CompilationHelper.RunGenerators(compilation, out ImmutableArray<Diagnostic> generatorDiags, generator);

// Make sure compilation was successful.
Assert.Empty(generatorDiags);
Assert.Empty(newCompilation.GetDiagnostics());

// Check base functionality of found types.
Assert.Equal(1, generator.foundTypes.Count);
Assert.Equal("HelloWorld.MyType", generator.foundTypes["MyType"].FullName);
Assert.Equal(1, generator.FoundTypes.Count);
Type myType = generator.FoundTypes["MyType"];
Assert.Equal("HelloWorld.MyType", myType.FullName);

// Check for received fields, properties and methods in created type.
string[] expectedPropertyNames = { "PrivatePropertyInt", "PrivatePropertyString", "PublicPropertyInt", "PublicPropertyString",};
Expand Down Expand Up @@ -111,10 +112,12 @@ public class NotMyType { }
Assert.Empty(newCompilation.GetDiagnostics());

// Check base functionality of found types.
Assert.Equal(2, generator.foundTypes.Count);
Assert.Equal(2, generator.FoundTypes.Count);
Type myType = generator.FoundTypes["MyType"];
Type notMyType = generator.FoundTypes["NotMyType"];

// Check for MyType.
Assert.Equal("HelloWorld.MyType", generator.foundTypes["MyType"].FullName);
Assert.Equal("HelloWorld.MyType", myType.FullName);

// Check for received fields, properties and methods for MyType.
string[] expectedFieldNamesMyType = { "PrivateChar", "PrivateDouble", "PublicChar", "PublicDouble" };
Expand All @@ -123,7 +126,7 @@ public class NotMyType { }
CheckFieldsPropertiesMethods("MyType", ref generator, expectedFieldNamesMyType, expectedPropertyNamesMyType, expectedMethodNamesMyType);

// Check for NotMyType.
Assert.Equal("System.Text.Json.Serialization.JsonConverterAttribute", generator.foundTypes["NotMyType"].FullName);
Assert.Equal("System.Text.Json.Serialization.JsonConverterAttribute", generator.FoundTypes["NotMyType"].FullName);

// Check for received fields, properties and methods for NotMyType.
string[] expectedFieldNamesNotMyType = { };
Expand Down Expand Up @@ -184,10 +187,10 @@ public class ShouldNotFind { }
Assert.Empty(newCompilation.GetDiagnostics());

// Check base functionality of found types.
Assert.Equal(2, generator.foundTypes.Count);
Assert.Equal(2, generator.FoundTypes.Count);

// Check for MyType.
Assert.Equal("HelloWorld.MyType", generator.foundTypes["MyType"].FullName);
Assert.Equal("HelloWorld.MyType", generator.FoundTypes["MyType"].FullName);

// Check for received fields, properties and methods for MyType.
string[] expectedFieldNamesMyType = { "PrivateChar", "PrivateDouble", "PublicChar", "PublicDouble" };
Expand All @@ -196,7 +199,7 @@ public class ShouldNotFind { }
CheckFieldsPropertiesMethods("MyType", ref generator, expectedFieldNamesMyType, expectedPropertyNamesMyType, expectedMethodNamesMyType);

// Check for NotMyType.
Assert.Equal("System.Text.Json.Serialization.JsonConverterAttribute", generator.foundTypes["NotMyType"].FullName);
Assert.Equal("System.Text.Json.Serialization.JsonConverterAttribute", generator.FoundTypes["NotMyType"].FullName);

// Check for received fields, properties and methods for NotMyType.
string[] expectedFieldNamesNotMyType = { };
Expand All @@ -207,9 +210,9 @@ public class ShouldNotFind { }

private void CheckFieldsPropertiesMethods(string typeName, ref JsonSerializerSourceGenerator generator, string[] expectedFields, string[] expectedProperties, string[] expectedMethods)
{
string[] receivedFields = generator.foundTypes[typeName].GetFields().Select(field => field.Name).OrderBy(s => s).ToArray();
string[] receivedProperties = generator.foundTypes[typeName].GetProperties().Select(property => property.Name).OrderBy(s => s).ToArray();
string[] receivedMethods = generator.foundTypes[typeName].GetMethods().Select(method => method.Name).OrderBy(s => s).ToArray();
string[] receivedFields = generator.FoundTypes[typeName].GetFields().Select(field => field.Name).OrderBy(s => s).ToArray();
string[] receivedProperties = generator.FoundTypes[typeName].GetProperties().Select(property => property.Name).OrderBy(s => s).ToArray();
string[] receivedMethods = generator.FoundTypes[typeName].GetMethods().Select(method => method.Name).OrderBy(s => s).ToArray();

Assert.Equal(expectedFields, receivedFields);
Assert.Equal(expectedProperties, receivedProperties);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@
</ItemGroup>

<ItemGroup>
<Compile Include="JsonSourceGeneratorTests.cs" />
<Compile Include="CompilationHelper.cs" />
<Compile Include="JsonSourceGeneratorTests.cs" />
<Compile Include="TypeWrapperTests.cs" />
</ItemGroup>

<Target Name="FixIncrementalCoreCompileWithAnalyzers" BeforeTargets="CoreCompile">
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System.Collections.Immutable;
using System.IO;
using System.Linq;
using System.Reflection;
using Microsoft.CodeAnalysis;
using Xunit;

namespace System.Text.Json.SourceGeneration.UnitTests
{
public class TypeWrapperTests
{
[Fact]
public void MetadataLoadFilePathHandle()
{
// Create a MetadataReference from new code.
string referencedSource = @"
namespace ReferencedAssembly
{
public class ReferencedType {
public int ReferencedPublicInt;
public double ReferencedPublicDouble;
}
}";

// Compile the referenced assembly first.
Compilation referencedCompilation = CompilationHelper.CreateCompilation(referencedSource);

// Emit the image of the referenced assembly.
byte[] referencedImage;
using (MemoryStream ms = new MemoryStream())
{
var emitResult = referencedCompilation.Emit(ms);
if (!emitResult.Success)
{
throw new InvalidOperationException();
}
referencedImage = ms.ToArray();
}

string source = @"
using System.Text.Json.Serialization;
using ReferencedAssembly;
namespace HelloWorld
{
[JsonSerializable]
public class MyType {
public void MyMethod() { }
public void MySecondMethod() { }
}
[JsonSerializable(typeof(ReferencedType))]
public static partial class ExternType { }
}";

MetadataReference[] additionalReferences = { MetadataReference.CreateFromImage(referencedImage) };

// Compilation using the referenced image should fail if out MetadataLoadContext does not handle.
Compilation compilation = CompilationHelper.CreateCompilation(source, additionalReferences);

JsonSerializerSourceGenerator generator = new JsonSerializerSourceGenerator();

Compilation newCompilation = CompilationHelper.RunGenerators(compilation, out ImmutableArray<Diagnostic> generatorDiags, generator);

// Make sure compilation was successful.
Assert.Empty(generatorDiags);
Assert.Empty(newCompilation.GetDiagnostics());

// Should find both types since compilation above was successful.
Assert.Equal(2, generator.FoundTypes.Count);
}

[Fact]
public void CanGetAttributes()
{
string source = @"
using System;
using System.Text.Json.Serialization;
namespace HelloWorld
{
[JsonSerializable]
public class MyType {
[JsonInclude]
public double PublicDouble;
[JsonPropertyName(""PPublicDouble"")]
public char PublicChar;
[JsonIgnore]
private double PrivateDouble;
private char PrivateChar;
public MyType() {{ }}
[JsonConstructor]
public MyType(double d) {{ PrivateDouble = d; }}
[JsonPropertyName(""TestName"")]
public int PublicPropertyInt { get; set; }
[JsonExtensionData]
public string PublicPropertyString { get; set; }
[JsonIgnore]
private int PrivatePropertyInt { get; set; }
private string PrivatePropertyString { get; set; }
[Obsolete(""Testing"", true)]
public void MyMethod() { }
public void MySecondMethod() { }
}
}";

Compilation compilation = CompilationHelper.CreateCompilation(source);

JsonSerializerSourceGenerator generator = new JsonSerializerSourceGenerator();

Compilation outCompilation = CompilationHelper.RunGenerators(compilation, out ImmutableArray<Diagnostic> generatorDiags, generator);

// Check base functionality of found types.
Assert.Equal(1, generator.FoundTypes.Count);
Type foundType = generator.FoundTypes.First().Value;

Assert.Equal("HelloWorld.MyType", foundType.FullName);

// Check for ConstructorInfoWrapper attribute usage.
(string, string[])[] receivedCtorsWithAttributeNames = foundType.GetConstructors().Select(ctor => (ctor.DeclaringType.FullName, ctor.GetCustomAttributesData().Cast<CustomAttributeData>().Select(attributeData => attributeData.AttributeType.Name).ToArray())).ToArray();
Assert.Equal(
receivedCtorsWithAttributeNames,
new (string, string[])[] {
("HelloWorld.MyType", new string[] { }),
("HelloWorld.MyType", new string[] { "JsonConstructorAttribute" })
});

// Check for MethodInfoWrapper attribute usage.
(string, string[])[] receivedMethodsWithAttributeNames = foundType.GetMethods().Select(method => (method.Name, method.GetCustomAttributesData().Cast<CustomAttributeData>().Select(attributeData => attributeData.AttributeType.Name).ToArray())).Where(x => x.Item2.Any()).ToArray();
Assert.Equal(
receivedMethodsWithAttributeNames,
new (string, string[])[] { ("MyMethod", new string[] { "ObsoleteAttribute" }) });

// Check for FieldInfoWrapper attribute usage.
(string, string[])[] receivedFieldsWithAttributeNames = foundType.GetFields().Select(field => (field.Name, field.GetCustomAttributesData().Cast<CustomAttributeData>().Select(attributeData => attributeData.AttributeType.Name).ToArray())).Where(x => x.Item2.Any()).ToArray();
Assert.Equal(
receivedFieldsWithAttributeNames,
new (string, string[])[] {
("PublicDouble", new string[] { "JsonIncludeAttribute" }),
("PublicChar", new string[] { "JsonPropertyNameAttribute" }),
("PrivateDouble", new string[] { "JsonIgnoreAttribute" } )
});

// Check for PropertyInfoWrapper attribute usage.
(string, string[])[] receivedPropertyWithAttributeNames = foundType.GetProperties().Select(property => (property.Name, property.GetCustomAttributesData().Cast<CustomAttributeData>().Select(attributeData => attributeData.AttributeType.Name).ToArray())).Where(x => x.Item2.Any()).ToArray();
Assert.Equal(
receivedPropertyWithAttributeNames,
new (string, string[])[] {
("PublicPropertyInt", new string[] { "JsonPropertyNameAttribute" }),
("PublicPropertyString", new string[] { "JsonExtensionDataAttribute" }),
("PrivatePropertyInt", new string[] { "JsonIgnoreAttribute" } )
});

// Check for MemberInfoWrapper attribute usage.
(string, string[])[] receivedMembersWithAttributeNames = foundType.GetMembers().Select(member => (member.Name, member.GetCustomAttributesData().Cast<CustomAttributeData>().Select(attributeData => attributeData.AttributeType.Name).ToArray())).Where(x => x.Item2.Any()).ToArray();
Assert.Equal(
receivedMembersWithAttributeNames,
new (string, string[])[] {
("PublicDouble", new string[] { "JsonIncludeAttribute" }),
("PublicChar", new string[] { "JsonPropertyNameAttribute" }),
("PrivateDouble", new string[] { "JsonIgnoreAttribute" } ),
(".ctor", new string[] { "JsonConstructorAttribute" }),
("PublicPropertyInt", new string[] { "JsonPropertyNameAttribute" }),
("PublicPropertyString", new string[] { "JsonExtensionDataAttribute" }),
("PrivatePropertyInt", new string[] { "JsonIgnoreAttribute" } ),
("MyMethod", new string[] { "ObsoleteAttribute" }),
});
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ namespace System.Text.Json.SourceGeneration
[Generator]
public class JsonSerializerSourceGenerator : ISourceGenerator
{
public Dictionary<string, Type> foundTypes = new Dictionary<string, Type>();
public Dictionary<string, Type> FoundTypes = new Dictionary<string, Type>();

public void Execute(SourceGeneratorContext context)
{
Expand Down Expand Up @@ -59,12 +59,12 @@ public void Execute(SourceGeneratorContext context)

// Get non-user owned typeSymbol from IdentifierNameSyntax and add to found types.
ITypeSymbol externalTypeSymbol = model.GetTypeInfo(externalTypeNode).ConvertedType;
foundTypes[typeDeclarationNode.Identifier.Text] = new TypeWrapper(externalTypeSymbol, metadataLoadContext);
FoundTypes[typeDeclarationNode.Identifier.Text] = new TypeWrapper(externalTypeSymbol, metadataLoadContext);
}
else
{
// Add user owned type into found types.
foundTypes[typeDeclarationNode.Identifier.Text] = new TypeWrapper(typeSymbol, metadataLoadContext);
FoundTypes[typeDeclarationNode.Identifier.Text] = new TypeWrapper(typeSymbol, metadataLoadContext);
}
}
}
Expand All @@ -73,40 +73,40 @@ public void Execute(SourceGeneratorContext context)
StringBuilder member = new StringBuilder();
string foundMethods, foundFields, foundProperties, foundCtorParams, foundCtors;

foreach (KeyValuePair<string, Type> entry in foundTypes)
foreach (KeyValuePair<string, Type> entry in FoundTypes)
{
foreach(MethodInfo method in entry.Value.GetMethods())
foreach (MethodInfo method in entry.Value.GetMethods())
{
member.Append(@$"""{method.Name}"", ");
}
foundMethods = member.ToString();
member.Clear();

foreach(FieldInfo field in entry.Value.GetFields())
foreach (FieldInfo field in entry.Value.GetFields())
{
member.Append(@$"{{""{field.Name}"", ""{field.FieldType.Name}""}}, ");
}
foundFields = member.ToString();
member.Clear();

foreach(PropertyInfo property in entry.Value.GetProperties())
foreach (PropertyInfo property in entry.Value.GetProperties())
{
member.Append(@$"{{""{property.Name}"", ""{property.PropertyType.Name}""}}, ");
}
foundProperties = member.ToString();
member.Clear();

foreach(ConstructorInfo ctor in entry.Value.GetConstructors())
foreach (ConstructorInfo ctor in entry.Value.GetConstructors())
{
foreach(ParameterInfo param in ctor.GetParameters())
foreach (ParameterInfo param in ctor.GetParameters())
{
member.Append(@$"{{""{param.Name}"", ""{param.ParameterType.Name}""}}, ");
}
}
foundCtorParams = member.ToString();
member.Clear();

foreach(ConstructorInfo ctor in entry.Value.GetConstructors())
foreach (ConstructorInfo ctor in entry.Value.GetConstructors())
{
member.Append($@"""{ctor.Name}"", ");
}
Expand Down
Loading

0 comments on commit efc134e

Please sign in to comment.