diff --git a/.gitignore b/.gitignore
index b57d6c37..9aa07c02 100644
--- a/.gitignore
+++ b/.gitignore
@@ -23,3 +23,4 @@ mono_crash.*.json
.fake
.ionide
+gdUnit4Net.sln.DotSettings.user
diff --git a/PackageVersions.props b/PackageVersions.props
index f71c1845..782376a9 100644
--- a/PackageVersions.props
+++ b/PackageVersions.props
@@ -5,7 +5,7 @@
17.9.0
4.9.2
4.18.4
- 4.2.5
- 1.1.2
+ 4.3.0
+ 2.0.0
diff --git a/api/src/api/TestAdapterReporter.cs b/api/src/api/TestAdapterReporter.cs
index dcd7f1d8..f481103e 100644
--- a/api/src/api/TestAdapterReporter.cs
+++ b/api/src/api/TestAdapterReporter.cs
@@ -1,10 +1,43 @@
namespace GdUnit4.Api;
+
using System;
+using System.IO;
+using System.IO.Pipes;
+using System.Security.Principal;
using Newtonsoft.Json;
internal class TestAdapterReporter : ITestEventListener
{
+ public const string PipeName = "gdunit4-event-pipe";
+ private readonly NamedPipeClientStream client;
+ private readonly StreamWriter? writer;
+
+ public TestAdapterReporter()
+ {
+ client = new NamedPipeClientStream(".", PipeName, PipeDirection.Out, PipeOptions.Asynchronous, TokenImpersonationLevel.Impersonation);
+ if (!client.IsConnected)
+ try
+ {
+ Console.WriteLine("Try to connect to GdUnit4 test report server!");
+ client.Connect(TimeSpan.FromSeconds(5));
+ writer = new StreamWriter(client) { AutoFlush = true };
+ writer.WriteLine("TestAdapterReporter: Successfully connected to GdUnit4 test report server!");
+ }
+ catch (TimeoutException e)
+ {
+ Console.Error.WriteLine(e);
+ throw;
+ }
+ }
+
+ public void Dispose()
+ {
+ writer?.WriteLine("TestAdapterReporter: Disconnecting from GdUnit4 test report server.");
+ writer?.Dispose();
+ client.Dispose();
+ }
+
public bool IsFailed { get; set; }
public void PublishEvent(TestEvent e)
@@ -12,6 +45,6 @@ public void PublishEvent(TestEvent e)
if (e.IsFailed || e.IsError)
IsFailed = true;
var json = JsonConvert.SerializeObject(e);
- Console.WriteLine($"GdUnitTestEvent:{json}");
+ writer!.WriteLine($"GdUnitTestEvent:{json}");
}
}
diff --git a/api/src/api/TestReporter.cs b/api/src/api/TestReporter.cs
index 11c6639f..52b2ebce 100644
--- a/api/src/api/TestReporter.cs
+++ b/api/src/api/TestReporter.cs
@@ -1,19 +1,21 @@
namespace GdUnit4.Api;
+
using System;
-using GdUnit4.Core;
+using Core;
internal class TestReporter : ITestEventListener
{
- public bool IsFailed { get; set; }
-
private static readonly GdUnitConsole Console = new();
- public TestReporter()
- { }
+ public bool IsFailed { get; set; }
public void PublishEvent(TestEvent testEvent) => PrintStatus(testEvent);
+ public void Dispose()
+ {
+ }
+
private void PrintStatus(TestEvent testEvent)
{
switch (testEvent.Type)
@@ -74,9 +76,6 @@ void WriteStatus(TestEvent testEvent)
private static void WriteFailureReport(TestEvent testEvent)
{
- foreach (var report in testEvent.Reports)
- {
- Console.Println(report.ToString().RichTextNormalize().Indentation(2), ConsoleColor.DarkCyan);
- }
+ foreach (var report in testEvent.Reports) Console.Println(report.ToString().RichTextNormalize().Indentation(2), ConsoleColor.DarkCyan);
}
}
diff --git a/api/src/api/TestRunner.cs b/api/src/api/TestRunner.cs
index e758216a..834808ef 100644
--- a/api/src/api/TestRunner.cs
+++ b/api/src/api/TestRunner.cs
@@ -1,74 +1,68 @@
namespace GdUnit4.Api;
+
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
-using CommandLine;
-
-using GdUnit4.Executions;
-using GdUnit4.Core;
-using Newtonsoft.Json;
-
-public partial class TestRunner : Godot.Node
-{
+using CommandLine;
- public class Options
- {
- [Option(Required = false, HelpText = "If FailFast=true the test run will abort on first test failure.")]
- public bool FailFast { get; set; } = false;
+using Core;
- [Option(Required = false, HelpText = "Runs the Runner in test adapter mode.")]
- public bool TestAdapter { get; set; }
+using Executions;
- [Option(Required = false, HelpText = "The test runner config.")]
- public string ConfigFile { get; set; } = "";
+using Godot;
- [Option(Required = false, HelpText = "Adds the given test suite or directory to the execution pipeline.")]
- public string Add { get; set; } = "";
- }
+using Newtonsoft.Json;
+public partial class TestRunner : Node
+{
private bool FailFast { get; set; } = true;
public async Task RunTests()
{
- var cmdArgs = Godot.OS.GetCmdlineArgs();
+ var cmdArgs = OS.GetCmdlineArgs();
await new Parser(with =>
- {
- with.EnableDashDash = true;
- with.IgnoreUnknownArguments = true;
- })
- .ParseArguments(cmdArgs)
- .WithParsedAsync(async o =>
- {
- FailFast = o.FailFast;
- var exitCode = await (o.TestAdapter
- ? RunTests(LoadTestSuites(o.ConfigFile), new TestAdapterReporter())
- : RunTests(LoadTestSuites(new DirectoryInfo(o.Add)), new TestReporter()));
- Console.WriteLine($"Testrun ends with exit code: {exitCode}, FailFast:{FailFast}");
- GetTree().Quit(exitCode);
- });
+ {
+ with.EnableDashDash = true;
+ with.IgnoreUnknownArguments = true;
+ })
+ .ParseArguments(cmdArgs)
+ .WithParsedAsync(async o =>
+ {
+ FailFast = o.FailFast;
+ var exitCode = await (o.TestAdapter
+ ? RunTests(LoadTestSuites(o.ConfigFile), new TestAdapterReporter())
+ : RunTests(LoadTestSuites(new DirectoryInfo(o.Add)), new TestReporter()));
+ Console.WriteLine($"Testrun ends with exit code: {exitCode}, FailFast:{FailFast}");
+ GetTree().Quit(exitCode);
+ });
}
private async Task RunTests(IEnumerable testSuites, ITestEventListener listener)
{
- if (!testSuites.Any())
+ using (listener)
{
- Console.Error.WriteLine("No testsuite's specified!, Abort!");
- return -1;
- }
- using Executor executor = new();
- executor.AddTestEventListener(listener);
+ if (!testSuites.Any())
+ {
+ Console.Error.WriteLine("No testsuite's specified!, Abort!");
+ return -1;
+ }
- foreach (var testSuite in testSuites)
- {
- await executor.ExecuteInternally(testSuite!);
- if (listener.IsFailed && FailFast)
- break;
+ using Executor executor = new();
+ executor.AddTestEventListener(listener);
+
+ foreach (var testSuite in testSuites)
+ {
+ await executor.ExecuteInternally(testSuite!);
+ if (listener.IsFailed && FailFast)
+ break;
+ }
+
+ return listener.IsFailed ? 100 : 0;
}
- return listener.IsFailed ? 100 : 0;
}
private static TestSuite? TryCreateTestSuite(KeyValuePair> entry)
@@ -109,13 +103,26 @@ private static IEnumerable LoadTestSuites(DirectoryInfo rootDir, stri
Console.WriteLine($"Scanning for test suites in: {currentDir.FullName}");
foreach (var filePath in Directory.EnumerateFiles(currentDir.FullName, searchPattern))
- {
if (GdUnitTestSuiteBuilder.ParseType(filePath, true) != null)
yield return new TestSuite(filePath);
- }
foreach (var directory in currentDir.GetDirectories())
stack.Push(directory);
}
}
+
+ public class Options
+ {
+ [Option(Required = false, HelpText = "If FailFast=true the test run will abort on first test failure.")]
+ public bool FailFast { get; set; } = false;
+
+ [Option(Required = false, HelpText = "Runs the Runner in test adapter mode.")]
+ public bool TestAdapter { get; set; }
+
+ [Option(Required = false, HelpText = "The test runner config.")]
+ public string ConfigFile { get; set; } = "";
+
+ [Option(Required = false, HelpText = "Adds the given test suite or directory to the execution pipeline.")]
+ public string Add { get; set; } = "";
+ }
}
diff --git a/api/src/core/event/TestEventListener.cs b/api/src/core/event/TestEventListener.cs
index 355c7c7e..84800685 100644
--- a/api/src/core/event/TestEventListener.cs
+++ b/api/src/core/event/TestEventListener.cs
@@ -1,7 +1,8 @@
-
namespace GdUnit4;
-internal interface ITestEventListener
+using System;
+
+internal interface ITestEventListener : IDisposable
{
bool IsFailed { get; protected set; }
diff --git a/api/src/core/execution/Executor.cs b/api/src/core/execution/Executor.cs
index d2fb14e8..8d868323 100644
--- a/api/src/core/execution/Executor.cs
+++ b/api/src/core/execution/Executor.cs
@@ -2,101 +2,36 @@ namespace GdUnit4.Executions;
using System;
using System.Collections.Generic;
-using System.Threading.Tasks;
-using System.Linq;
using System.IO;
-using Newtonsoft.Json;
+using System.Linq;
+using System.Threading.Tasks;
+
+using Core;
-using GdUnit4.Core;
+using Godot;
+
+using Newtonsoft.Json;
-public sealed partial class Executor : Godot.RefCounted, IExecutor
+public sealed partial class Executor : RefCounted, IExecutor
{
- [Godot.Signal]
+ [Signal]
public delegate void ExecutionCompletedEventHandler();
private readonly List eventListeners = new();
- private class GdTestEventListenerDelegator : ITestEventListener
- {
- private readonly Godot.GodotObject listener;
-
- public bool IsFailed { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }
-
- public GdTestEventListenerDelegator(Godot.GodotObject listener)
- => this.listener = listener;
-
- public void PublishEvent(TestEvent testEvent)
- {
- Godot.Collections.Dictionary data = new()
- {
- { "type", testEvent.Type.ToVariant() },
- { "resource_path", testEvent.ResourcePath.ToVariant() },
- { "suite_name", testEvent.SuiteName.ToVariant() },
- { "test_name", testEvent.TestName.ToVariant() },
- { "total_count", testEvent.TotalCount.ToVariant() },
- { "statistics", ToGdUnitEventStatistics(testEvent.Statistics) }
- };
-
- if (testEvent.Reports.Any())
- {
- var serializedReports = testEvent.Reports.Select(report => report.Serialize()).ToGodotArray();
- data.Add("reports", serializedReports);
- }
- listener.Call("PublishEvent", data);
- }
-
- private Godot.Collections.Dictionary ToGdUnitEventStatistics(IDictionary statistics)
- {
- var converted = new Godot.Collections.Dictionary();
- foreach (var (key, value) in statistics)
- converted[key.ToString().ToLower().ToVariant()] = value.ToVariant();
- return converted;
- }
- }
+ private bool ReportOrphanNodesEnabled => GdUnit4Settings.IsVerboseOrphans();
- public IExecutor AddGdTestEventListener(Godot.GodotObject listener)
+ public IExecutor AddGdTestEventListener(GodotObject listener)
{
// I want to using anonyms implementation to remove the extra delegator class
eventListeners.Add(new GdTestEventListenerDelegator(listener));
return this;
}
- internal void AddTestEventListener(ITestEventListener listener)
- => eventListeners.Add(listener);
-
- private bool ReportOrphanNodesEnabled => GdUnit4Settings.IsVerboseOrphans();
-
- public bool IsExecutable(Godot.Node node) => node is CsNode;
-
- private class GdUnitRunnerConfig
- {
- public Dictionary> Included { get; set; } = new();
- }
-
- ///
- /// Loads the list of included tests from GdUnitRunner.cfg if exists
- ///
- ///
- ///
- private IEnumerable? LoadTestFilter(CsNode testSuite)
- {
- // try to load runner config written by gdunit4 plugin
- var configPath = Path.Combine(Directory.GetCurrentDirectory(), "addons/gdUnit4/GdUnitRunner.cfg");
- if (!File.Exists(configPath))
- return null;
-
- var testSuitePath = testSuite.ResourcePath();
- var json = File.ReadAllText(configPath);
- var runnerConfig = JsonConvert.DeserializeObject(json);
- // Filter by testSuitePath and add values from runnerConfig.Included to the list
- var filteredTests = runnerConfig?.Included
- .Where(entry => entry.Key.EndsWith(testSuitePath))
- .SelectMany(entry => entry.Value);
- return filteredTests?.Any() == true ? filteredTests : null;
- }
+ public bool IsExecutable(Node node) => node is CsNode;
///
- /// Execute a testsuite, is called externally from Godot test suite runner
+ /// Execute a testsuite, is called externally from Godot test suite runner
///
///
public void Execute(CsNode testSuite)
@@ -121,6 +56,31 @@ public void Execute(CsNode testSuite)
}
}
+ internal void AddTestEventListener(ITestEventListener listener)
+ => eventListeners.Add(listener);
+
+ ///
+ /// Loads the list of included tests from GdUnitRunner.cfg if exists
+ ///
+ ///
+ ///
+ private IEnumerable? LoadTestFilter(CsNode testSuite)
+ {
+ // try to load runner config written by gdunit4 plugin
+ var configPath = Path.Combine(Directory.GetCurrentDirectory(), "addons/gdUnit4/GdUnitRunner.cfg");
+ if (!File.Exists(configPath))
+ return null;
+
+ var testSuitePath = testSuite.ResourcePath();
+ var json = File.ReadAllText(configPath);
+ var runnerConfig = JsonConvert.DeserializeObject(json);
+ // Filter by testSuitePath and add values from runnerConfig.Included to the list
+ var filteredTests = runnerConfig?.Included
+ .Where(entry => entry.Key.EndsWith(testSuitePath))
+ .SelectMany(entry => entry.Value);
+ return filteredTests?.Any() == true ? filteredTests : null;
+ }
+
internal async Task ExecuteInternally(TestSuite testSuite)
{
try
@@ -141,4 +101,50 @@ internal async Task ExecuteInternally(TestSuite testSuite)
testSuite.Dispose();
}
}
+
+ private class GdTestEventListenerDelegator : ITestEventListener
+ {
+ private readonly GodotObject listener;
+
+ public GdTestEventListenerDelegator(GodotObject listener)
+ => this.listener = listener;
+
+ public bool IsFailed { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }
+
+ public void PublishEvent(TestEvent testEvent)
+ {
+ Godot.Collections.Dictionary data = new()
+ {
+ { "type", testEvent.Type.ToVariant() },
+ { "resource_path", testEvent.ResourcePath.ToVariant() },
+ { "suite_name", testEvent.SuiteName.ToVariant() },
+ { "test_name", testEvent.TestName.ToVariant() },
+ { "total_count", testEvent.TotalCount.ToVariant() },
+ { "statistics", ToGdUnitEventStatistics(testEvent.Statistics) }
+ };
+
+ if (testEvent.Reports.Any())
+ {
+ var serializedReports = testEvent.Reports.Select(report => report.Serialize()).ToGodotArray();
+ data.Add("reports", serializedReports);
+ }
+
+ listener.Call("PublishEvent", data);
+ }
+
+ public void Dispose() => listener.Dispose();
+
+ private Godot.Collections.Dictionary ToGdUnitEventStatistics(IDictionary statistics)
+ {
+ var converted = new Godot.Collections.Dictionary();
+ foreach (var (key, value) in statistics)
+ converted[key.ToString().ToLower().ToVariant()] = value.ToVariant();
+ return converted;
+ }
+ }
+
+ private class GdUnitRunnerConfig
+ {
+ public Dictionary> Included { get; } = new();
+ }
}
diff --git a/gdUnit4.sln b/gdUnit4Net.sln
similarity index 100%
rename from gdUnit4.sln
rename to gdUnit4Net.sln
diff --git a/gdUnit4Net.sln.DotSettings.user b/gdUnit4Net.sln.DotSettings.user
new file mode 100644
index 00000000..837f21cb
--- /dev/null
+++ b/gdUnit4Net.sln.DotSettings.user
@@ -0,0 +1,12 @@
+
+ True
+ <SessionState ContinuousTestingMode="0" IsActive="True" Name="BoolAssertTest" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session">
+ <TestAncestor>
+ <TestId>VsTest::B97C5043-B4BE-4156-BE0F-FDCBDECA61F6::net8.0::executor://gdunit4.testadapter/#GdUnit4.Tests.Asserts.BoolAssertTest</TestId>
+ </TestAncestor>
+</SessionState>
+
+
+
+ TRACE
+ D:\development\workspace\gdUnit4Net\test\.runsettings
\ No newline at end of file
diff --git a/icon.svg b/icon.svg
deleted file mode 100644
index adc26df6..00000000
--- a/icon.svg
+++ /dev/null
@@ -1 +0,0 @@
-
diff --git a/project.godot b/project.godot
deleted file mode 100644
index b1f4958b..00000000
--- a/project.godot
+++ /dev/null
@@ -1,19 +0,0 @@
-; Engine configuration file.
-; It's best edited using the editor UI and not directly,
-; since the parameters that go here are not all obvious.
-;
-; Format:
-; [section] ; section goes between []
-; param=value ; assign values to parameters
-
-config_version=5
-
-[application]
-
-config/name="gdUnit4"
-config/features=PackedStringArray("4.2.2", "C#", "Forward Plus")
-config/icon="res://icon.svg"
-
-[dotnet]
-
-project/assembly_name="gdUnit4"
diff --git a/test/src/GdUnit4NetAPITest.cs b/test/src/GdUnit4NetAPITest.cs
index adf4d0a0..c0c734e8 100644
--- a/test/src/GdUnit4NetAPITest.cs
+++ b/test/src/GdUnit4NetAPITest.cs
@@ -16,5 +16,5 @@ public void IsTestSuite()
[TestCase]
public void Version()
- => AssertThat(GdUnit4NetAPI.Version()).StartsWith("4.2");
+ => AssertThat(GdUnit4NetAPI.Version()).StartsWith("4.3");
}
diff --git a/test/src/core/ExecutorTest.cs b/test/src/core/ExecutorTest.cs
index f237db34..03670b68 100644
--- a/test/src/core/ExecutorTest.cs
+++ b/test/src/core/ExecutorTest.cs
@@ -4,12 +4,17 @@ namespace GdUnit4.Tests.Core;
using System.Collections.Generic;
using System.Threading.Tasks;
+using Executions;
+
using GdUnit4.Asserts;
using GdUnit4.Core;
-using GdUnit4.Executions;
+
+using Godot;
using static Assertions;
+
using static TestEvent.TYPE;
+
using static TestReport.ReportType;
[TestSuite]
@@ -17,16 +22,38 @@ namespace GdUnit4.Tests.Core;
public class ExecutorTest : ITestEventListener
#pragma warning restore CA1001 // Types that own disposable fields should be disposable
{
- private Executor executor = null!;
private readonly List events = new();
#pragma warning disable CS0649
// enable to verbose debug event
private readonly bool verbose;
#pragma warning restore CS0649
+ private Executor executor = null!;
public bool IsFailed { get; set; }
+
+ void IDisposable.Dispose()
+ {
+ executor?.Dispose();
+ GC.SuppressFinalize(this);
+ }
+
+ void ITestEventListener.PublishEvent(TestEvent e)
+ {
+ if (verbose)
+ {
+ Console.WriteLine("-------------------------------");
+ Console.WriteLine($"Event Type: {e.Type}, SuiteName: {e.SuiteName}, TestName: {e.TestName}, Statistics: {e.Statistics}");
+ Console.WriteLine($"ErrorCount: {e.ErrorCount}, FailedCount: {e.FailedCount}, OrphanCount: {e.OrphanCount}");
+ var reports = new List(e.Reports).ConvertAll(r => new TestReport(r.Type, r.LineNumber, r.Message.RichTextNormalize()));
+ if (verbose)
+ reports.ForEach(r => Console.WriteLine($"Reports -> {r}"));
+ }
+
+ events.Add(e);
+ }
+
[Before]
public void Before()
{
@@ -36,12 +63,11 @@ public void Before()
[BeforeTest]
public void InitTest()
- => Godot.ProjectSettings.SetSetting(GdUnit4Settings.REPORT_ORPHANS, true);
+ => ProjectSettings.SetSetting(GdUnit4Settings.REPORT_ORPHANS, true);
[AfterTest]
public void TeardownTest()
- => Godot.ProjectSettings.SetSetting(GdUnit4Settings.REPORT_ORPHANS, true);
-
+ => ProjectSettings.SetSetting(GdUnit4Settings.REPORT_ORPHANS, true);
private static TestSuite LoadTestSuite(string clazzPath)
{
@@ -52,19 +78,6 @@ private static TestSuite LoadTestSuite(string clazzPath)
};
return testSuite;
}
- void ITestEventListener.PublishEvent(TestEvent e)
- {
- if (verbose)
- {
- Console.WriteLine("-------------------------------");
- Console.WriteLine($"Event Type: {e.Type}, SuiteName: {e.SuiteName}, TestName: {e.TestName}, Statistics: {e.Statistics}");
- Console.WriteLine($"ErrorCount: {e.ErrorCount}, FailedCount: {e.FailedCount}, OrphanCount: {e.OrphanCount}");
- var reports = new List(e.Reports).ConvertAll(r => new TestReport(r.Type, r.LineNumber, r.Message.RichTextNormalize()));
- if (verbose)
- reports.ForEach(r => Console.WriteLine($"Reports -> {r}"));
- }
- events.Add(e);
- }
private async Task> ExecuteTestSuite(TestSuite testSuite)
{
@@ -81,15 +94,13 @@ private async Task> ExecuteTestSuite(TestSuite testSuite)
private List ExpectedEvents(string suiteName, params string[] testCaseNames)
{
- var expectedEvents = new List
- {
- Tuple(TESTSUITE_BEFORE, suiteName, "Before", testCaseNames.Length)
- };
+ var expectedEvents = new List { Tuple(TESTSUITE_BEFORE, suiteName, "Before", testCaseNames.Length) };
foreach (var testCase in testCaseNames)
{
expectedEvents.Add(Tuple(TESTCASE_BEFORE, suiteName, testCase, 0));
expectedEvents.Add(Tuple(TESTCASE_AFTER, suiteName, testCase, 0));
}
+
expectedEvents.Add(Tuple(TESTSUITE_AFTER, suiteName, "After", 0));
return expectedEvents;
}
@@ -101,30 +112,33 @@ private List ExpectedEvents(string suiteName, params string[] testCaseNa
AssertArray(events).ExtractV(Extr("Type"), Extr("TestName"), Extr("ErrorCount"), Extr("FailedCount"), Extr("OrphanCount"));
private IEnumerableAssert