Skip to content

Commit 4630583

Browse files
authored
GD-266: GdUnitBuild tool fails to generate c# tests if source has no namespace defined (#267)
- fix GdUnitBuild to use localized path to generate the new test suite in the configured test folder - added json error response - fix the C# TestSuiteBuilder to handle optional namespaces - bump to version v2.2.3 (needs to be synchron with the vsc extension)
1 parent 9fe5e9d commit 4630583

File tree

6 files changed

+83
-38
lines changed

6 files changed

+83
-38
lines changed

addons/gdUnit3/bin/GdUnitBuildTool.gd

+10-2
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ func _init():
3030
for cmd in cmd_options:
3131
if cmd.name() == '-scp':
3232
_source_file = cmd.arguments()[0]
33+
_source_file = ProjectSettings.localize_path(ProjectSettings.localize_path(_source_file))
3334
if cmd.name() == '-scl':
3435
_source_line = int(cmd.arguments()[0])
3536
# verify required arguments
@@ -49,26 +50,33 @@ func _idle(_delta):
4950

5051
var result := GdUnitTestSuiteBuilder.new().create(script, _source_line)
5152
if result.is_error():
53+
print_json_error(result.error_message())
5254
exit(RETURN_ERROR, result.error_message())
55+
return
5356

5457
_console.prints_color("Added testcase: %s" % result.value(), Color.cornflower)
5558
print_json_result(result.value())
5659
exit(RETURN_SUCCESS)
5760

5861
func exit(code :int, message :String = "") -> void:
62+
_status = EXIT
5963
if code == RETURN_ERROR:
6064
if not message.empty():
6165
_console.prints_error(message)
6266
_console.prints_error("Abnormal exit with %d" % code)
6367
else:
6468
_console.prints_color("Exit code: %d" % RETURN_SUCCESS, Color.darksalmon)
6569
quit(code)
66-
_status = EXIT
6770

6871
func print_json_result(result :Dictionary) -> void:
69-
var json = 'JSON_RESULT:{"TestCases" : [{"line":%d, "path": "%s"}]}' % [result["line"], result["path"]]
72+
# convert back to system path
73+
var path = ProjectSettings.globalize_path(result["path"]);
74+
var json = 'JSON_RESULT:{"TestCases" : [{"line":%d, "path": "%s"}]}' % [result["line"], path]
7075
prints(json)
7176

77+
func print_json_error(error :String) -> void:
78+
prints('JSON_RESULT:{"Error" : "%s"}' % error)
79+
7280
func show_options(show_advanced :bool = false) -> void:
7381
_console.prints_color(" Usage:", Color.darksalmon)
7482
_console.prints_color(" build -scp <source_path> -scl <line_number>", Color.darksalmon)

addons/gdUnit3/package.json

-11
This file was deleted.

addons/gdUnit3/plugin.cfg

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,5 @@
33
name="gdUnit3"
44
description="Unit Testing Framework for Godot Scripts"
55
author="Mike Schulze"
6-
version="2.2.0"
6+
version="2.2.3"
77
script="plugin.gd"

addons/gdUnit3/src/core/GdUnitTestSuiteBuilder.cs

+19-24
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,10 @@ internal static Dictionary<string, object> Build(string sourcePath, int lineNumb
7676
internal static Type? ParseType(String classPath)
7777
{
7878
if (String.IsNullOrEmpty(classPath) || !new FileInfo(classPath).Exists)
79+
{
80+
Console.Error.WriteLine($"Class `{classPath}` not exists .");
7981
return null;
82+
}
8083
try
8184
{
8285
var root = CSharpSyntaxTree.ParseText(File.ReadAllText(classPath)).GetCompilationUnitRoot();
@@ -116,36 +119,36 @@ private static string LoadTestSuiteTemplate()
116119

117120
private static string FillFromTemplate(string template, Type type, string classPath) =>
118121
template
119-
.Replace(TAG_TEST_SUITE_NAMESPACE, type.Namespace)
122+
.Replace(TAG_TEST_SUITE_NAMESPACE, String.IsNullOrEmpty(type.Namespace) ? "GdUnitDefaultTestNamespace" : type.Namespace)
120123
.Replace(TAG_TEST_SUITE_CLASS, type.Name + "Test")
121124
.Replace(TAG_SOURCE_RESOURCE_PATH, classPath)
122125
.Replace(TAG_SOURCE_CLASS_NAME, type.Name)
123126
.Replace(TAG_SOURCE_CLASS_VARNAME, type.Name);
124127

128+
internal static ClassDeclarationSyntax ClassDeclaration(CompilationUnitSyntax root)
129+
{
130+
NamespaceDeclarationSyntax namespaceSyntax = root.Members.OfType<NamespaceDeclarationSyntax>().FirstOrDefault();
131+
return namespaceSyntax == null
132+
? root.Members.OfType<ClassDeclarationSyntax>().First()
133+
: namespaceSyntax.Members.OfType<ClassDeclarationSyntax>().First();
134+
}
135+
125136
internal static int TestCaseLineNumber(CompilationUnitSyntax root, string testCaseName)
126137
{
127-
NamespaceDeclarationSyntax namespaceSyntax = root.Members.OfType<NamespaceDeclarationSyntax>().First();
128-
ClassDeclarationSyntax? programClassSyntax = namespaceSyntax?.Members.OfType<ClassDeclarationSyntax>().First();
129138
// lookup on test cases
130-
return programClassSyntax?.Members.OfType<MethodDeclarationSyntax>()
139+
return ClassDeclaration(root).Members.OfType<MethodDeclarationSyntax>()
131140
.FirstOrDefault(method => method.Identifier.Text.Equals(testCaseName))
132141
.Body?.GetLocation().GetLineSpan().StartLinePosition.Line ?? -1;
133142
}
134143

135-
internal static bool TestCaseExists(CompilationUnitSyntax root, string testCaseName)
136-
{
137-
NamespaceDeclarationSyntax namespaceSyntax = root.Members.OfType<NamespaceDeclarationSyntax>().First();
138-
ClassDeclarationSyntax? programClassSyntax = namespaceSyntax?.Members.OfType<ClassDeclarationSyntax>().First();
139-
return programClassSyntax?.Members.OfType<MethodDeclarationSyntax>()
140-
.Any(method => method.Identifier.Text.Equals(testCaseName)) ?? false;
141-
}
144+
internal static bool TestCaseExists(CompilationUnitSyntax root, string testCaseName) =>
145+
ClassDeclaration(root).Members.OfType<MethodDeclarationSyntax>().Any(method => method.Identifier.Text.Equals(testCaseName));
142146

143147
internal static CompilationUnitSyntax AddTestCase(SyntaxTree syntaxTree, string testCaseName)
144148
{
145149
var root = syntaxTree.GetCompilationUnitRoot();
146-
NamespaceDeclarationSyntax? namespaceSyntax = root.Members.OfType<NamespaceDeclarationSyntax>().First();
147-
ClassDeclarationSyntax? programClassSyntax = namespaceSyntax?.Members.OfType<ClassDeclarationSyntax>().First();
148-
SyntaxNode insertAt = programClassSyntax?.ChildNodes().Last()!;
150+
ClassDeclarationSyntax programClassSyntax = ClassDeclaration(root);
151+
SyntaxNode insertAt = programClassSyntax.ChildNodes().Last()!;
149152

150153
AttributeSyntax testCaseAttribute = SyntaxFactory.Attribute(SyntaxFactory.IdentifierName("TestCase"));
151154
AttributeListSyntax attributes = SyntaxFactory.AttributeList(SyntaxFactory.SingletonSeparatedList<AttributeSyntax>(testCaseAttribute));
@@ -171,22 +174,14 @@ internal static CompilationUnitSyntax AddTestCase(SyntaxTree syntaxTree, string
171174
internal static string? FindMethod(string sourcePath, int lineNumber)
172175
{
173176
SyntaxTree syntaxTree = CSharpSyntaxTree.ParseText(File.ReadAllText(sourcePath));
174-
var root = syntaxTree.GetCompilationUnitRoot();
175-
var spanToFind = syntaxTree.GetText().Lines[lineNumber - 1].Span;
176-
177-
NamespaceDeclarationSyntax? namespaceSyntax = root.Members.OfType<NamespaceDeclarationSyntax>().First();
178-
if (namespaceSyntax == null)
179-
{
180-
Console.Error.WriteLine($"Can't parse method name from {sourcePath}:{lineNumber}. Error: no namespace found.");
181-
return null;
182-
}
183-
ClassDeclarationSyntax? programClassSyntax = namespaceSyntax.Members.OfType<ClassDeclarationSyntax>().First();
177+
ClassDeclarationSyntax programClassSyntax = ClassDeclaration(syntaxTree.GetCompilationUnitRoot());
184178
if (programClassSyntax == null)
185179
{
186180
Console.Error.WriteLine($"Can't parse method name from {sourcePath}:{lineNumber}. Error: no class declararion found.");
187181
return null;
188182
}
189183

184+
var spanToFind = syntaxTree.GetText().Lines[lineNumber - 1].Span;
190185
// lookup on properties
191186
foreach (PropertyDeclarationSyntax m in programClassSyntax.Members.OfType<PropertyDeclarationSyntax>())
192187
{

addons/gdUnit3/test/core/GdUnitTestSuiteBuilderTest.cs

+28
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,34 @@ public void CreateTestSuite_NoMethodFound()
9292
.EndsWith("TestPerson.cs:4.");
9393
}
9494

95+
[TestCase]
96+
public void CreateTestSuite_NoNamespace()
97+
{
98+
var tmp = CreateTempDir("build-test-suite-test");
99+
string sourceClass = Path.Combine(tmp, "TestPerson2.cs");
100+
File.Copy(Path.GetFullPath(Godot.ProjectSettings.GlobalizePath("res://addons/gdUnit3/test/core/resources/sources/TestPerson2.cs")), sourceClass);
101+
102+
// use of a line number for which no method is defined in the source class
103+
string testSuite = Path.Combine(tmp, "TestPerson2Test.cs");
104+
Dictionary<string, object> dictionary = GdUnitTestSuiteBuilder.Build(sourceClass, 12, testSuite);
105+
AssertThat(dictionary["path"]).IsEqual(testSuite);
106+
AssertThat((int)dictionary["line"]).IsEqual(16);
107+
}
108+
109+
[TestCase]
110+
public void CreateTestSuite_WithNamespace()
111+
{
112+
var tmp = CreateTempDir("build-test-suite-test");
113+
string sourceClass = Path.Combine(tmp, "TestPerson.cs");
114+
File.Copy(Path.GetFullPath(Godot.ProjectSettings.GlobalizePath("res://addons/gdUnit3/test/core/resources/sources/TestPerson.cs")), sourceClass);
115+
116+
// use of a line number for which no method is defined in the source class
117+
string testSuite = Path.Combine(tmp, "TestPersonTest.cs");
118+
Dictionary<string, object> dictionary = GdUnitTestSuiteBuilder.Build(sourceClass, 14, testSuite);
119+
AssertThat(dictionary["path"]).IsEqual(testSuite);
120+
AssertThat((int)dictionary["line"]).IsEqual(16);
121+
}
122+
95123
[TestCase]
96124
public void CreateTestSuite_TestCaseAlreadyExists()
97125
{
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
using Godot;
2+
3+
public class TestPerson2
4+
{
5+
6+
public TestPerson2(string firstName, string lastName)
7+
{
8+
FirstName = firstName;
9+
LastName = lastName;
10+
}
11+
12+
public string FirstName { get; }
13+
14+
public string LastName { get; }
15+
16+
public string FullName => FirstName + " " + LastName;
17+
18+
public string FullName2() => FirstName + " " + LastName;
19+
20+
public string FullName3()
21+
{
22+
return FirstName + " " + LastName;
23+
}
24+
25+
}

0 commit comments

Comments
 (0)