Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add unit tests for C# diagnostic analyzers #86528

Merged
merged 1 commit into from
Jan 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk.sln
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Godot.SourceGenerators", "G
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Godot.SourceGenerators.Sample", "Godot.SourceGenerators.Sample\Godot.SourceGenerators.Sample.csproj", "{7297A614-8DF5-43DE-9EAD-99671B26BD1F}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Godot.SourceGenerators.Tests", "Godot.SourceGenerators.Tests\Godot.SourceGenerators.Tests.csproj", "{07E6D201-35C9-4463-9B29-D16621EA733D}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GodotSharp", "..\..\glue\GodotSharp\GodotSharp\GodotSharp.csproj", "{AEBF0036-DA76-4341-B651-A3F2856AB2FA}"
EndProject
Global
Expand All @@ -26,6 +28,10 @@ Global
{7297A614-8DF5-43DE-9EAD-99671B26BD1F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7297A614-8DF5-43DE-9EAD-99671B26BD1F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7297A614-8DF5-43DE-9EAD-99671B26BD1F}.Release|Any CPU.Build.0 = Release|Any CPU
{07E6D201-35C9-4463-9B29-D16621EA733D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{07E6D201-35C9-4463-9B29-D16621EA733D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{07E6D201-35C9-4463-9B29-D16621EA733D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{07E6D201-35C9-4463-9B29-D16621EA733D}.Release|Any CPU.Build.0 = Release|Any CPU
{AEBF0036-DA76-4341-B651-A3F2856AB2FA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{AEBF0036-DA76-4341-B651-A3F2856AB2FA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{AEBF0036-DA76-4341-B651-A3F2856AB2FA}.Release|Any CPU.ActiveCfg = Release|Any CPU
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
namespace Godot.SourceGenerators.Sample;

[GlobalClass]
public partial class CustomGlobalClass : GodotObject
{
}

// This doesn't works because global classes can't have any generic type parameter.
/*
[GlobalClass]
public partial class CustomGlobalClass<T> : Node
{
}
*/
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

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

<PropertyGroup>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
using System;
using Godot.Collections;
using Array = Godot.Collections.Array;

namespace Godot.SourceGenerators.Sample;

public class MustBeVariantMethods
{
public void MustBeVariantMethodCalls()
{
Method<bool>();
Method<char>();
Method<sbyte>();
Method<byte>();
Method<short>();
Method<ushort>();
Method<int>();
Method<uint>();
Method<long>();
Method<ulong>();
Method<float>();
Method<double>();
Method<string>();
Method<Vector2>();
Method<Vector2I>();
Method<Rect2>();
Method<Rect2I>();
Method<Transform2D>();
Method<Vector3>();
Method<Vector3I>();
Method<Vector4>();
Method<Vector4I>();
Method<Basis>();
Method<Quaternion>();
Method<Transform3D>();
Method<Projection>();
Method<Aabb>();
Method<Color>();
Method<Plane>();
Method<Callable>();
Method<Signal>();
Method<GodotObject>();
Method<StringName>();
Method<NodePath>();
Method<Rid>();
Method<Dictionary>();
Method<Array>();
Method<byte[]>();
Method<int[]>();
Method<long[]>();
Method<float[]>();
Method<double[]>();
Method<string[]>();
Method<Vector2[]>();
Method<Vector3[]>();
Method<Color[]>();
Method<GodotObject[]>();
Method<StringName[]>();
Method<NodePath[]>();
Method<Rid[]>();

// This call fails because generic type is not Variant-compatible.
//Method<object>();
}

public void Method<[MustBeVariant] T>()
{
}

public void MustBeVariantClasses()
{
new ClassWithGenericVariant<bool>();
new ClassWithGenericVariant<char>();
new ClassWithGenericVariant<sbyte>();
new ClassWithGenericVariant<byte>();
new ClassWithGenericVariant<short>();
new ClassWithGenericVariant<ushort>();
new ClassWithGenericVariant<int>();
new ClassWithGenericVariant<uint>();
new ClassWithGenericVariant<long>();
new ClassWithGenericVariant<ulong>();
new ClassWithGenericVariant<float>();
new ClassWithGenericVariant<double>();
new ClassWithGenericVariant<string>();
new ClassWithGenericVariant<Vector2>();
new ClassWithGenericVariant<Vector2I>();
new ClassWithGenericVariant<Rect2>();
new ClassWithGenericVariant<Rect2I>();
new ClassWithGenericVariant<Transform2D>();
new ClassWithGenericVariant<Vector3>();
new ClassWithGenericVariant<Vector3I>();
new ClassWithGenericVariant<Vector4>();
new ClassWithGenericVariant<Vector4I>();
new ClassWithGenericVariant<Basis>();
new ClassWithGenericVariant<Quaternion>();
new ClassWithGenericVariant<Transform3D>();
new ClassWithGenericVariant<Projection>();
new ClassWithGenericVariant<Aabb>();
new ClassWithGenericVariant<Color>();
new ClassWithGenericVariant<Plane>();
new ClassWithGenericVariant<Callable>();
new ClassWithGenericVariant<Signal>();
new ClassWithGenericVariant<GodotObject>();
new ClassWithGenericVariant<StringName>();
new ClassWithGenericVariant<NodePath>();
new ClassWithGenericVariant<Rid>();
new ClassWithGenericVariant<Dictionary>();
new ClassWithGenericVariant<Array>();
new ClassWithGenericVariant<byte[]>();
new ClassWithGenericVariant<int[]>();
new ClassWithGenericVariant<long[]>();
new ClassWithGenericVariant<float[]>();
new ClassWithGenericVariant<double[]>();
new ClassWithGenericVariant<string[]>();
new ClassWithGenericVariant<Vector2[]>();
new ClassWithGenericVariant<Vector3[]>();
new ClassWithGenericVariant<Color[]>();
new ClassWithGenericVariant<GodotObject[]>();
new ClassWithGenericVariant<StringName[]>();
new ClassWithGenericVariant<NodePath[]>();
new ClassWithGenericVariant<Rid[]>();

// This class fails because generic type is not Variant-compatible.
//new ClassWithGenericVariant<object>();
}
}

public class ClassWithGenericVariant<[MustBeVariant] T>
{
}

public class MustBeVariantAnnotatedMethods
{
[GenericTypeAttribute<string>()]
public void MethodWithAttributeOk()
{
}

// This method definition fails because generic type is not Variant-compatible.
/*
[GenericTypeAttribute<object>()]
public void MethodWithWrongAttribute()
{
}
*/
}

[GenericTypeAttribute<string>()]
public class ClassVariantAnnotated
{
}

// This class definition fails because generic type is not Variant-compatible.
/*
[GenericTypeAttribute<object>()]
public class ClassNonVariantAnnotated
{
}
*/

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true)]
public class GenericTypeAttribute<[MustBeVariant] T> : Attribute
{
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Testing;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Testing;
using Microsoft.CodeAnalysis.Testing.Verifiers;
using Microsoft.CodeAnalysis.Text;

namespace Godot.SourceGenerators.Tests;

public static class CSharpAnalyzerVerifier<TAnalyzer>
where TAnalyzer : DiagnosticAnalyzer, new()
{
public class Test : CSharpAnalyzerTest<TAnalyzer, 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 sources, params DiagnosticResult[] expected)
{
return MakeVerifier(new string[] { sources }, expected).RunAsync();
}

public static Test MakeVerifier(ICollection<string> sources, params DiagnosticResult[] expected)
{
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.ExpectedDiagnostics.AddRange(expected);
return verifier;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using Xunit;

namespace Godot.SourceGenerators.Tests;

public class GlobalClassAnalyzerTests
{
[Fact]
public async void GlobalClassMustDeriveFromGodotObjectTest()
{
const string GlobalClassGD0401 = "GlobalClass.GD0401.cs";
await CSharpAnalyzerVerifier<GlobalClassAnalyzer>.Verify(GlobalClassGD0401);
}

[Fact]
public async void GlobalClassMustNotBeGenericTest()
{
const string GlobalClassGD0402 = "GlobalClass.GD0402.cs";
await CSharpAnalyzerVerifier<GlobalClassAnalyzer>.Verify(GlobalClassGD0402);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
<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="Microsoft.CodeAnalysis.Testing.Verifiers.XUnit" Version="1.1.1" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Analyzer.Testing.XUnit" Version="1.1.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>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using Xunit;

namespace Godot.SourceGenerators.Tests;

public class MustBeVariantAnalyzerTests
{
[Fact]
public async void GenericTypeArgumentMustBeVariantTest()
{
const string MustBeVariantGD0301 = "MustBeVariant.GD0301.cs";
await CSharpAnalyzerVerifier<MustBeVariantAnalyzer>.Verify(MustBeVariantGD0301);
}

[Fact]
public async void GenericTypeParameterMustBeVariantAnnotatedTest()
{
const string MustBeVariantGD0302 = "MustBeVariant.GD0302.cs";
await CSharpAnalyzerVerifier<MustBeVariantAnalyzer>.Verify(MustBeVariantGD0302);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using Godot;

// This works because it inherits from GodotObject.
[GlobalClass]
public partial class CustomGlobalClass1 : GodotObject
{

}

// This works because it inherits from an object that inherits from GodotObject
[GlobalClass]
public partial class CustomGlobalClass2 : Node
{

}

// This raises a GD0401 diagnostic error: global classes must inherit from GodotObject
{|GD0401:[GlobalClass]
public partial class CustomGlobalClass3
{

}|}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using Godot;

// This works because it inherits from GodotObject and it doesn't have any generic type parameter.
[GlobalClass]
public partial class CustomGlobalClass : GodotObject
{

}

// This raises a GD0402 diagnostic error: global classes can't have any generic type parameter
{|GD0402:[GlobalClass]
public partial class CustomGlobalClass<T> : GodotObject
{

}|}
Loading
Loading