Skip to content

Commit

Permalink
Add tests for source generators
Browse files Browse the repository at this point in the history
- Bootstrap xUnit project to test source generators
- Implement source generator tests
- Better tests structure (put test data in cs files)
- Enable `ScriptSerializationGeneratorTests`
- Enable `ScriptPathAttributeGeneratorTests`
- Fix `NesterClass` -> `NestedClass`
- Use `Path.Combine` when dealing with paths
- Copy test data to the output directory
  • Loading branch information
paulloz committed Dec 19, 2023
1 parent a574c02 commit b352bdc
Show file tree
Hide file tree
Showing 42 changed files with 3,411 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Testing;
using Microsoft.CodeAnalysis.Testing;
using Microsoft.CodeAnalysis.Testing.Verifiers;
using Microsoft.CodeAnalysis.Text;

namespace Godot.SourceGenerators.Tests;

public static class CSharpSourceGeneratorVerifier<TSourceGenerator>
where TSourceGenerator : ISourceGenerator, new()
{
public class Test : CSharpSourceGeneratorTest<TSourceGenerator, XUnitVerifier>
{
public Test()
{
ReferenceAssemblies = ReferenceAssemblies.Net.Net60;

SolutionTransforms.Add((Solution solution, ProjectId projectId) =>
{
Project project = solution.GetProject(projectId)!
.AddMetadataReference(Constants.GodotSharpAssembly.CreateMetadataReference());

return project.Solution;
});
}
}

public static Task Verify(string source, params string[] generatedSources)
{
return Verify(new string[] { source }, generatedSources);
}

public static Task VerifyNoCompilerDiagnostics(string source, params string[] generatedSources)
{
return VerifyNoCompilerDiagnostics(new string[] { source }, generatedSources);
}

public static Task Verify(ICollection<string> sources, params string[] generatedSources)
{
return MakeVerifier(sources, generatedSources).RunAsync();
}

public static Task VerifyNoCompilerDiagnostics(ICollection<string> sources, params string[] generatedSources)
{
var verifier = MakeVerifier(sources, generatedSources);
verifier.CompilerDiagnostics = CompilerDiagnostics.None;
return verifier.RunAsync();
}

public static Test MakeVerifier(ICollection<string> sources, ICollection<string> generatedSources)
{
var verifier = new Test();

verifier.TestState.AnalyzerConfigFiles.Add(("/.globalconfig", $"""
is_global = true
build_property.GodotProjectDir = {Constants.ExecutingAssemblyPath}
"""));

verifier.TestState.Sources.AddRange(sources.Select(source =>
{
return (source, SourceText.From(File.ReadAllText(Path.Combine(Constants.SourceFolderPath, source))));
}));

verifier.TestState.GeneratedSources.AddRange(generatedSources.Select(generatedSource =>
{
return (FullGeneratedSourceName(generatedSource), SourceText.From(File.ReadAllText(Path.Combine(Constants.GeneratedSourceFolderPath, generatedSource)), Encoding.UTF8));
}));

return verifier;
}

private static string FullGeneratedSourceName(string name)
{
var generatorType = typeof(TSourceGenerator);
return Path.Combine(generatorType.Namespace!, generatorType.FullName!, name);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
using System.IO;
using System.Reflection;

namespace Godot.SourceGenerators.Tests;

public static class Constants
{
public static Assembly GodotSharpAssembly => typeof(GodotObject).Assembly;

public static string ExecutingAssemblyPath { get; }
public static string SourceFolderPath { get; }
public static string GeneratedSourceFolderPath { get; }

static Constants()
{
ExecutingAssemblyPath = Path.GetFullPath(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location!)!);

var testDataPath = Path.Combine(ExecutingAssemblyPath, "TestData");

SourceFolderPath = Path.Combine(testDataPath, "Sources");
GeneratedSourceFolderPath = Path.Combine(testDataPath, "GeneratedSources");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using System.Reflection;
using Microsoft.CodeAnalysis;

namespace Godot.SourceGenerators.Tests;

public static class Extensions
{
public static MetadataReference CreateMetadataReference(this Assembly assembly)
{
return MetadataReference.CreateFromFile(assembly.Location);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>

<LangVersion>11</LangVersion>

<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
</PropertyGroup>

<PropertyGroup>
<DefaultItemExcludesInProjectFolder>$(DefaultItemExcludesInProjectFolder);TestData\**</DefaultItemExcludesInProjectFolder>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.SourceGenerators.Testing.XUnit" Version="1.1.1" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.7.1" />
<PackageReference Include="xunit" Version="2.4.2" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="coverlet.collector" Version="3.2.0">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\..\..\glue\GodotSharp\GodotSharp\GodotSharp.csproj" />
<ProjectReference Include="..\Godot.SourceGenerators\Godot.SourceGenerators.csproj" />
</ItemGroup>

<ItemGroup>
<None Include="TestData\**\*.cs" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
using Xunit;

namespace Godot.SourceGenerators.Tests;

public class ScriptMethodsGeneratorTests
{
[Fact]
public async void Methods()
{
await CSharpSourceGeneratorVerifier<ScriptMethodsGenerator>.Verify(
"Methods.cs",
"Methods_ScriptMethods.generated.cs"
);
}

[Fact]
public async void ScriptBoilerplate()
{
await CSharpSourceGeneratorVerifier<ScriptMethodsGenerator>.Verify(
"ScriptBoilerplate.cs",
"ScriptBoilerplate_ScriptMethods.generated.cs", "OuterClass.NestedClass_ScriptMethods.generated.cs"
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using Microsoft.CodeAnalysis.Text;
using Xunit;

namespace Godot.SourceGenerators.Tests;

public class ScriptPathAttributeGeneratorTests
{
private static (string, SourceText) MakeAssemblyScriptTypesGeneratedSource(ICollection<string> types)
{
return (
Path.Combine("Godot.SourceGenerators", "Godot.SourceGenerators.ScriptPathAttributeGenerator", "AssemblyScriptTypes.generated.cs"),
SourceText.From($$"""
[assembly:Godot.AssemblyHasScriptsAttribute(new System.Type[] {{{string.Join(", ", types.Select(type => $"typeof({type})"))}}})]
""", Encoding.UTF8)
);
}

[Fact]
public async void ScriptBoilerplate()
{
var verifier = CSharpSourceGeneratorVerifier<ScriptPathAttributeGenerator>.MakeVerifier(
new string[] { "ScriptBoilerplate.cs" },
new string[] { "ScriptBoilerplate_ScriptPath.generated.cs" }
);
verifier.TestState.GeneratedSources.Add(MakeAssemblyScriptTypesGeneratedSource(new string[] { "global::ScriptBoilerplate" }));
await verifier.RunAsync();
}

[Fact]
public async void FooBar()
{
var verifier = CSharpSourceGeneratorVerifier<ScriptPathAttributeGenerator>.MakeVerifier(
new string[] { "Foo.cs", "Bar.cs" },
new string[] { "Foo_ScriptPath.generated.cs", "Bar_ScriptPath.generated.cs" }
);
verifier.TestState.GeneratedSources.Add(MakeAssemblyScriptTypesGeneratedSource(new string[] { "global::Foo", "global::Bar" }));
await verifier.RunAsync();
}

[Fact]
public async void Generic()
{
var verifier = CSharpSourceGeneratorVerifier<ScriptPathAttributeGenerator>.MakeVerifier(
new string[] { "Generic.cs" },
new string[] { "Generic_ScriptPath.generated.cs" }
);
verifier.TestState.GeneratedSources.Add(MakeAssemblyScriptTypesGeneratedSource(new string[] { "global::Generic" }));
await verifier.RunAsync();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
using Xunit;

namespace Godot.SourceGenerators.Tests;

public class ScriptPropertiesGeneratorTests
{
[Fact]
public async void ExportedFields()
{
await CSharpSourceGeneratorVerifier<ScriptPropertiesGenerator>.Verify(
new string[] { "ExportedFields.cs", "MoreExportedFields.cs" },
new string[] { "ExportedFields_ScriptProperties.generated.cs" }
);
}

[Fact]
public async void ExportedProperties()
{
await CSharpSourceGeneratorVerifier<ScriptPropertiesGenerator>.Verify(
"ExportedProperties.cs",
"ExportedProperties_ScriptProperties.generated.cs"
);
}

[Fact]
public async void OneWayPropertiesAllReadOnly()
{
await CSharpSourceGeneratorVerifier<ScriptPropertiesGenerator>.Verify(
"AllReadOnly.cs",
"AllReadOnly_ScriptProperties.generated.cs"
);
}

[Fact]
public async void OneWayPropertiesAllWriteOnly()
{
await CSharpSourceGeneratorVerifier<ScriptPropertiesGenerator>.Verify(
"AllWriteOnly.cs",
"AllWriteOnly_ScriptProperties.generated.cs"
);
}

[Fact]
public async void OneWayPropertiesMixedReadonlyWriteOnly()
{
await CSharpSourceGeneratorVerifier<ScriptPropertiesGenerator>.Verify(
"MixedReadOnlyWriteOnly.cs",
"MixedReadOnlyWriteOnly_ScriptProperties.generated.cs"
);
}

[Fact]
public async void ScriptBoilerplate()
{
await CSharpSourceGeneratorVerifier<ScriptPropertiesGenerator>.Verify(
"ScriptBoilerplate.cs",
"ScriptBoilerplate_ScriptProperties.generated.cs", "OuterClass.NestedClass_ScriptProperties.generated.cs"
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
using Xunit;

namespace Godot.SourceGenerators.Tests;

public class ScriptPropertyDefValGeneratorTests
{
[Fact]
public async void ExportedFields()
{
await CSharpSourceGeneratorVerifier<ScriptPropertyDefValGenerator>.Verify(
new string[] { "ExportedFields.cs", "MoreExportedFields.cs" },
new string[] { "ExportedFields_ScriptPropertyDefVal.generated.cs" }
);
}

[Fact]
public async void ExportedProperties()
{
await CSharpSourceGeneratorVerifier<ScriptPropertyDefValGenerator>.Verify(
"ExportedProperties.cs",
"ExportedProperties_ScriptPropertyDefVal.generated.cs"
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using Xunit;

namespace Godot.SourceGenerators.Tests;

public class ScriptSerializationGeneratorTests
{
[Fact]
public async void ScriptBoilerplate()
{
await CSharpSourceGeneratorVerifier<ScriptSerializationGenerator>.VerifyNoCompilerDiagnostics(
"ScriptBoilerplate.cs",
"ScriptBoilerplate_ScriptSerialization.generated.cs", "OuterClass.NestedClass_ScriptSerialization.generated.cs"
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using Xunit;

namespace Godot.SourceGenerators.Tests;

public class ScriptSignalsGeneratorTests
{
[Fact]
public async void EventSignals()
{
await CSharpSourceGeneratorVerifier<ScriptSignalsGenerator>.Verify(
"EventSignals.cs",
"EventSignals_ScriptSignals.generated.cs"
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
root = true

[*.cs]
exclude = true
generated_code = true
Loading

0 comments on commit b352bdc

Please sign in to comment.