diff --git a/.gitignore b/.gitignore index 2cbc103..a76f38e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ +.godot *.user *.userosscache *.sln.docstates diff --git a/GodotXUnit.csproj b/GodotXUnit.csproj index 6e3b172..e040ff3 100644 --- a/GodotXUnit.csproj +++ b/GodotXUnit.csproj @@ -1,21 +1,19 @@ - + {B6A000AB-04AE-4D1E-A0D5-93911E363F6D} Library GodotXUnit GodotXUnit 1.0.0.0 - 8 - net472 + 10 + net6.0 false false + true false - - - @@ -43,7 +41,7 @@ - + @@ -53,4 +51,4 @@ GodotXUnitApi - \ No newline at end of file + diff --git a/README.md b/README.md index 34b9375..6cafaae 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,7 @@ timeouts. full example at ./tests/PhysicsCollisionTest.cs [GodotFact(Scene = "res://test_scenes/PhysicsCollisionTest.tscn")] public async void TestOhNoTooSlowOfFall() { - var ball = (AVerySpecialBall) GDU.CurrentScene.FindNode("AVerySpecialBall"); + var ball = (AVerySpecialBall) GDU.CurrentScene.FindChild("AVerySpecialBall"); Assert.NotNull(ball); // it will throw a TimeoutException here because the gravity value is too low diff --git a/addons/GodotXUnit/GodotTestRunner.cs b/addons/GodotXUnit/GodotTestRunner.cs index 05b4883..66f8e15 100644 --- a/addons/GodotXUnit/GodotTestRunner.cs +++ b/addons/GodotXUnit/GodotTestRunner.cs @@ -2,7 +2,7 @@ namespace GodotXUnit { - public class GodotTestRunner : GodotXUnitRunnerBase + public partial class GodotTestRunner : GodotXUnitRunnerBase { } diff --git a/addons/GodotXUnit/GodotXUnitApi/GDI.cs b/addons/GodotXUnit/GodotXUnitApi/GDI.cs index 9563779..69e948a 100644 --- a/addons/GodotXUnit/GodotXUnitApi/GDI.cs +++ b/addons/GodotXUnit/GodotXUnitApi/GDI.cs @@ -14,17 +14,17 @@ public static class GDI /// public static float PositionXByScreenPercent(float percent) { - return GDU.Viewport.Size.x * percent; + return GDU.ViewportSize.X * percent; } - + /// /// gets the y pixel position based on the percent of the screen /// public static float PositionYByScreenPercent(float percent) { - return GDU.Viewport.Size.y * percent; + return GDU.ViewportSize.Y * percent; } - + /// /// gets a vector2 representing the pixel positions based on the screen percents. /// @@ -44,34 +44,38 @@ public static Vector2 PositionBy2DWorldPos(Vector2 worldPos) var scene = GDU.CurrentScene as Node2D; if (scene == null) throw new Exception("scene root must be a Node2D to use this method"); - return (scene.GetViewportTransform() * scene.GetGlobalTransform()).Xform(worldPos); + return (scene.GetViewportTransform() * scene.GetGlobalTransform()) * worldPos; } - + + // There exists a mouse_button_to_mask in the C++ code, but it's not exposed. + private static MouseButtonMask MouseButtonToMask(MouseButton index) => + (MouseButtonMask)(1 << ((int)index) - 1); + /// /// sends an mouse down event into godot /// - public static void InputMouseDown(Vector2 screenPosition, ButtonList index = ButtonList.Left) + public static void InputMouseDown(Vector2 screenPosition, MouseButton index = MouseButton.Left) { var inputEvent = new InputEventMouseButton(); inputEvent.GlobalPosition = screenPosition; inputEvent.Position = screenPosition; inputEvent.Pressed = true; - inputEvent.ButtonIndex = (int) index; - inputEvent.ButtonMask = (int) index; + inputEvent.ButtonIndex = index; + inputEvent.ButtonMask = MouseButtonToMask(index); Input.ParseInputEvent(inputEvent); } /// /// sends an mouse up event into godot /// - public static void InputMouseUp(Vector2 screenPosition, ButtonList index = ButtonList.Left) + public static void InputMouseUp(Vector2 screenPosition, MouseButton index = MouseButton.Left) { var inputEvent = new InputEventMouseButton(); inputEvent.GlobalPosition = screenPosition; inputEvent.Position = screenPosition; inputEvent.Pressed = false; - inputEvent.ButtonIndex = (int) index; - inputEvent.ButtonMask = (int) index; + inputEvent.ButtonIndex = index; + inputEvent.ButtonMask = MouseButtonToMask(index); Input.ParseInputEvent(inputEvent); } @@ -99,7 +103,7 @@ public static void InputMouseMove(Vector2 screenPosition) /// same as PositionByScreenPercent /// the button index /// the task that will resolve when the simulation is finished - public static async Task SimulateMouseClick(float screenPercentX, float screenPercentY, ButtonList index = ButtonList.Left) + public static async Task SimulateMouseClick(float screenPercentX, float screenPercentY, MouseButton index = MouseButton.Left) { var position = PositionByScreenPercent(screenPercentX, screenPercentY); await SimulateMouseClick(position, index); @@ -117,7 +121,7 @@ public static async Task SimulateMouseClick(float screenPercentX, float screenPe /// same as PositionByScreenPercent /// same as PositionByScreenPercent /// the button index - public static void SimulateMouseClickNoWait(float screenPercentX, float screenPercentY, ButtonList index = ButtonList.Left) + public static void SimulateMouseClickNoWait(float screenPercentX, float screenPercentY, MouseButton index = MouseButton.Left) { #pragma warning disable 4014 SimulateMouseClick(screenPercentX, screenPercentY, index); @@ -138,7 +142,7 @@ public static void SimulateMouseClickNoWait(float screenPercentX, float screenPe /// the position of where to click /// the button index /// the task that will resolve when the simulation is finished - public static async Task SimulateMouseClick(Vector2 position, ButtonList index = ButtonList.Left) + public static async Task SimulateMouseClick(Vector2 position, MouseButton index = MouseButton.Left) { await GDU.OnProcessAwaiter; InputMouseMove(position); @@ -150,7 +154,7 @@ public static async Task SimulateMouseClick(Vector2 position, ButtonList index = await GDU.OnProcessAwaiter; await GDU.OnProcessAwaiter; } - + /// /// simulates a click with these steps: /// - send a mouse moved event to the requested position @@ -162,13 +166,13 @@ public static async Task SimulateMouseClick(Vector2 position, ButtonList index = /// /// the position of where to click /// the button index - public static void SimulateMouseClickNoWait(Vector2 position, ButtonList index = ButtonList.Left) + public static void SimulateMouseClickNoWait(Vector2 position, MouseButton index = MouseButton.Left) { #pragma warning disable 4014 SimulateMouseClick(position, index); #pragma warning restore 4014 } - + /// /// simulates a mouse drag with these steps: /// - move mouse to start position @@ -187,7 +191,7 @@ public static void SimulateMouseClickNoWait(Vector2 position, ButtonList index = /// the task that will resolve when the simulation is finished public static async Task SimulateMouseDrag(float startScreenPercentX, float startScreenPercentY, float endScreenPercentX, float endScreenPercentY, - ButtonList index = ButtonList.Left) + MouseButton index = MouseButton.Left) { var start = PositionByScreenPercent(startScreenPercentX, startScreenPercentY); var end = PositionByScreenPercent(endScreenPercentX, endScreenPercentY); @@ -211,7 +215,7 @@ public static async Task SimulateMouseDrag(float startScreenPercentX, float star /// the button index public static void SimulateMouseDragNoWait(float startScreenPercentX, float startScreenPercentY, float endScreenPercentX, float endScreenPercentY, - ButtonList index = ButtonList.Left) + MouseButton index = MouseButton.Left) { #pragma warning disable 4014 SimulateMouseDrag(startScreenPercentX, startScreenPercentY, endScreenPercentX, endScreenPercentY, index); @@ -232,7 +236,7 @@ public static void SimulateMouseDragNoWait(float startScreenPercentX, float star /// the position of where the drag ends /// the button index /// the task that will resolve when the simulation is finished - public static async Task SimulateMouseDrag(Vector2 start, Vector2 end, ButtonList index = ButtonList.Left) + public static async Task SimulateMouseDrag(Vector2 start, Vector2 end, MouseButton index = MouseButton.Left) { await GDU.OnProcessAwaiter; InputMouseMove(start); @@ -260,7 +264,7 @@ public static async Task SimulateMouseDrag(Vector2 start, Vector2 end, ButtonLis /// the position of where the drag starts /// the position of where the drag ends /// the button index - public static void SimulateMouseDragNoWait(Vector2 start, Vector2 end, ButtonList index = ButtonList.Left) + public static void SimulateMouseDragNoWait(Vector2 start, Vector2 end, MouseButton index = MouseButton.Left) { #pragma warning disable 4014 SimulateMouseDrag(start, end, index); diff --git a/addons/GodotXUnit/GodotXUnitApi/GDU.cs b/addons/GodotXUnit/GodotXUnitApi/GDU.cs index 06c189b..4b995e7 100644 --- a/addons/GodotXUnit/GodotXUnitApi/GDU.cs +++ b/addons/GodotXUnit/GodotXUnitApi/GDU.cs @@ -36,13 +36,19 @@ public static Node2D Instance public static SignalAwaiter OnPhysicsProcessAwaiter => Instance.ToSignal(Instance, "OnPhysicsProcess"); - public static SignalAwaiter OnIdleFrameAwaiter => - Instance.ToSignal(Instance.GetTree(), "idle_frame"); + public static SignalAwaiter OnProcessFrameAwaiter => + Instance.ToSignal(Instance.GetTree(), SceneTree.SignalName.ProcessFrame); public static SceneTree Tree => Instance.GetTree(); - public static Viewport Viewport => Instance.GetViewport(); - + public static Vector2I ViewportSize => Instance.GetViewport() switch + { + Window window => window.ContentScaleSize, + SubViewport subViewport => subViewport.Size, + var vp => throw new Exception($"Unexpected viewport type {vp.GetType().Name}") + }; + + public static Node CurrentScene => Instance.GetTree().CurrentScene; /// @@ -67,7 +73,7 @@ public static async Task WaitForFrames(int count) for (int i = 0; i < count; i++) await OnProcessAwaiter; } - + /// /// helper to wrap a SignalAwaiter to return the first element from a signal /// result into the desired type. @@ -75,11 +81,11 @@ public static async Task WaitForFrames(int count) /// the target signal to wrap /// the type to cast to /// the task that awaits and casts when resolved - public static async Task AwaitType(this SignalAwaiter awaiter) + public static async Task AwaitType<[MustBeVariant] T>(this SignalAwaiter awaiter) { - return (T) (await awaiter)[0]; + return (await awaiter)[0].As(); } - + /// /// creates a task for a godot signal with a timeout. /// @@ -89,15 +95,15 @@ public static async Task AwaitType(this SignalAwaiter awaiter) /// makes this task throw an exception on timeout. otherwise, just resolves /// the new task with the given timeout /// only throws if throwOnTimeout is true - public static async Task ToSignalWithTimeout( - this Godot.Object source, + public static async Task ToSignalWithTimeout( + this GodotObject source, string signal, int timeoutMillis, bool throwOnTimeout = true) { return await source.ToSignal(source, signal).AwaitWithTimeout(timeoutMillis, throwOnTimeout); } - + /// /// wraps the given SignalAwaiter in a task with a timeout. /// @@ -106,14 +112,14 @@ public static async Task ToSignalWithTimeout( /// makes this task throw an exception on timeout. otherwise, just resolves /// the new task with the given timeout /// only throws if throwOnTimeout is true - public static Task AwaitWithTimeout( + public static Task AwaitWithTimeout( this SignalAwaiter awaiter, int timeoutMillis, bool throwOnTimeout = true) { return Task.Run(async () => await awaiter).AwaitWithTimeout(timeoutMillis, throwOnTimeout); } - + /// /// wraps a task with a task that will resolve after the wrapped task /// or after the specified amount of time (either by exiting or by throwing @@ -132,14 +138,15 @@ public static async Task AwaitWithTimeout( var task = Task.Run(async () => await wrapping); using var token = new CancellationTokenSource(); var completedTask = await Task.WhenAny(task, Task.Delay(timeoutMillis, token.Token)); - if (completedTask == task) { + if (completedTask == task) + { token.Cancel(); await task; } if (throwOnTimeout) throw new TimeoutException($"signal {wrapping} timed out after {timeoutMillis}ms."); } - + /// /// wraps a task with a task that will resolve after the wrapped task /// or after the specified amount of time (either by exiting or by throwing @@ -154,12 +161,13 @@ public static async Task AwaitWithTimeout( public static async Task AwaitWithTimeout( this Task wrapping, int timeoutMillis, - bool throwOnTimeout = true) + bool throwOnTimeout = true) { var task = Task.Run(async () => await wrapping); using var token = new CancellationTokenSource(); var completedTask = await Task.WhenAny(task, Task.Delay(timeoutMillis, token.Token)); - if (completedTask == task) { + if (completedTask == task) + { token.Cancel(); return await task; } @@ -181,7 +189,7 @@ public static async Task RequestDrawing(int frames, Action drawer) { for (int i = 0; i < frames; i++) { - ((GodotXUnitRunnerBase) Instance).RequestDraw(drawer); + ((GodotXUnitRunnerBase)Instance).RequestDraw(drawer); await Instance.ToSignal(Instance, "OnDrawRequestDone"); } } diff --git a/addons/GodotXUnit/GodotXUnitApi/GodotFactAttribute.cs b/addons/GodotXUnit/GodotXUnitApi/GodotFactAttribute.cs index 03eed85..c0548f8 100644 --- a/addons/GodotXUnit/GodotXUnitApi/GodotFactAttribute.cs +++ b/addons/GodotXUnit/GodotXUnitApi/GodotFactAttribute.cs @@ -47,7 +47,7 @@ public void IsInPhysicsProcess() */ [XunitTestCaseDiscoverer("GodotXUnitApi.Internal.GodotFactDiscoverer", "GodotXUnitApi")] // ReSharper disable once ClassWithVirtualMembersNeverInherited.Global - public class GodotFactAttribute : FactAttribute + public partial class GodotFactAttribute : FactAttribute { /// /// loads the given scene before the test starts and loads an empty scene after diff --git a/addons/GodotXUnit/GodotXUnitApi/GodotXUnitApi.csproj b/addons/GodotXUnit/GodotXUnitApi/GodotXUnitApi.csproj index 80adf19..f4f5540 100644 --- a/addons/GodotXUnit/GodotXUnitApi/GodotXUnitApi.csproj +++ b/addons/GodotXUnit/GodotXUnitApi/GodotXUnitApi.csproj @@ -1,11 +1,11 @@ - + 1.0.0 rexfleischer - net472 + net6.0 GodotXUnitApi GodotXUnitApi - 8 + 10 GodotXUnitApi true diff --git a/addons/GodotXUnit/GodotXUnitApi/GodotXUnitEvents.cs b/addons/GodotXUnit/GodotXUnitApi/GodotXUnitEvents.cs index 6d7e52e..b91e03d 100644 --- a/addons/GodotXUnit/GodotXUnitApi/GodotXUnitEvents.cs +++ b/addons/GodotXUnit/GodotXUnitApi/GodotXUnitEvents.cs @@ -5,7 +5,7 @@ namespace GodotXUnitApi { [Serializable] - public class GodotXUnitSummary + public partial class GodotXUnitSummary { public int testsDiscovered; public int testsExpectedToRun; @@ -83,7 +83,7 @@ public GodotXUnitOtherDiagnostic AddDiagnostic(string message) } [Serializable] - public class GodotXUnitTestResult + public partial class GodotXUnitTestResult { public string testCaseClass; public string testCaseName; @@ -98,14 +98,14 @@ public class GodotXUnitTestResult } [Serializable] - public class GodotXUnitTestStart + public partial class GodotXUnitTestStart { public string testCaseClass; public string testCaseName; } [Serializable] - public class GodotXUnitOtherDiagnostic + public partial class GodotXUnitOtherDiagnostic { public string message; public string exceptionType; diff --git a/addons/GodotXUnit/GodotXUnitApi/Internal/Consts.cs b/addons/GodotXUnit/GodotXUnitApi/Internal/Consts.cs index a189795..f3bc319 100644 --- a/addons/GodotXUnit/GodotXUnitApi/Internal/Consts.cs +++ b/addons/GodotXUnit/GodotXUnitApi/Internal/Consts.cs @@ -10,18 +10,18 @@ public static class Consts public static readonly Dictionary SETTING_RESULT_SUMMARY_PROP = new Dictionary { ["name"] = SETTING_RESULTS_SUMMARY, - ["type"] = Variant.Type.String, - ["hint"] = PropertyHint.Dir, + ["type"] = (long)Variant.Type.String, + ["hint"] = (long)PropertyHint.Dir, ["hint_string"] = "set where the summary json is written to.", ["default"] = SETTING_RESULTS_SUMMARY_DEF }; - + public static readonly string SETTING_TARGET_ASSEMBLY = "GodotXUnit/target_assembly"; public static readonly Dictionary SETTING_TARGET_ASSEMBLY_PROP = new Dictionary { ["name"] = SETTING_TARGET_ASSEMBLY, - ["type"] = Variant.Type.String, - ["hint"] = PropertyHint.None, + ["type"] = (long)Variant.Type.String, + ["hint"] = (long)PropertyHint.None, ["hint_string"] = "set the name of the csproj to test, or empty string for main assembly (can be set through UI)", ["default"] = "" }; @@ -31,32 +31,32 @@ public static class Consts public static readonly Dictionary SETTING_TARGET_ASSEMBLY_CUSTOM_PROP = new Dictionary { ["name"] = SETTING_TARGET_ASSEMBLY_CUSTOM, - ["type"] = Variant.Type.String, - ["hint"] = PropertyHint.None, + ["type"] = (long)Variant.Type.String, + ["hint"] = (long)PropertyHint.None, ["hint_string"] = "set the name of the csproj to test, or empty string for main assembly (can be set through UI)", ["default"] = "" }; - + public static readonly string SETTING_TARGET_CLASS = "GodotXUnit/target_class"; public static readonly Dictionary SETTING_TARGET_CLASS_PROP = new Dictionary { ["name"] = SETTING_TARGET_CLASS, - ["type"] = Variant.Type.String, - ["hint"] = PropertyHint.None, + ["type"] = (long)Variant.Type.String, + ["hint"] = (long)PropertyHint.None, ["hint_string"] = "set the name of the class to test, or empty string for all (can be set through UI)", ["default"] = "" }; - + public static readonly string SETTING_TARGET_METHOD = "GodotXUnit/target_method"; public static readonly Dictionary SETTING_TARGET_METHOD_PROP = new Dictionary { ["name"] = SETTING_TARGET_METHOD, - ["type"] = Variant.Type.String, - ["hint"] = PropertyHint.None, + ["type"] = (long)Variant.Type.String, + ["hint"] = (long)PropertyHint.None, ["hint_string"] = "set the name of the method to test, or empty string for all in class (can be set through UI)", ["default"] = "" }; - + public const string RUNNER_SCENE_PATH = "res://addons/GodotXUnit/runner/GodotTestRunnerScene.tscn"; public const string EMPTY_SCENE_PATH = "res://addons/GodotXUnit/runner/EmptyScene.tscn"; public const string DOCK_SCENE_PATH = "res://addons/GodotXUnit/XUnitDock.tscn"; @@ -65,10 +65,10 @@ public static class Consts public const string ICON_WARN = "res://addons/GodotXUnit/assets/warn.png"; public const string ICON_CHECK = "res://addons/GodotXUnit/assets/check.png"; public const string ICON_ERROR = "res://addons/GodotXUnit/assets/error.png"; - - public static Texture IconRunning => GD.Load(ICON_RUNNING); - public static Texture IconWarn => GD.Load(ICON_WARN); - public static Texture IconCheck => GD.Load(ICON_CHECK); - public static Texture IconError => GD.Load(ICON_ERROR); + + public static Texture2D IconRunning => GD.Load(ICON_RUNNING); + public static Texture2D IconWarn => GD.Load(ICON_WARN); + public static Texture2D IconCheck => GD.Load(ICON_CHECK); + public static Texture2D IconError => GD.Load(ICON_ERROR); } } \ No newline at end of file diff --git a/addons/GodotXUnit/GodotXUnitApi/Internal/Extensions.cs b/addons/GodotXUnit/GodotXUnitApi/Internal/Extensions.cs index 730a3e4..db3a8b5 100644 --- a/addons/GodotXUnit/GodotXUnitApi/Internal/Extensions.cs +++ b/addons/GodotXUnit/GodotXUnitApi/Internal/Extensions.cs @@ -5,6 +5,24 @@ namespace GodotXUnitApi.Internal { public static class Extensions { + public static FileAccess ThrowIfNotOk(this FileAccess file) + { + if (file is null) + { + ThrowIfNotOk(FileAccess.GetOpenError()); + } + return file; + } + + public static DirAccess ThrowIfNotOk(this DirAccess dir) + { + if (dir is null) + { + ThrowIfNotOk(DirAccess.GetOpenError()); + } + return dir; + } + public static void ThrowIfNotOk(this Error check) { if (check == Error.Ok) return; diff --git a/addons/GodotXUnit/GodotXUnitApi/Internal/GodotFactDiscoverer.cs b/addons/GodotXUnit/GodotXUnitApi/Internal/GodotFactDiscoverer.cs index 44ae191..94bd1ee 100644 --- a/addons/GodotXUnit/GodotXUnitApi/Internal/GodotFactDiscoverer.cs +++ b/addons/GodotXUnit/GodotXUnitApi/Internal/GodotFactDiscoverer.cs @@ -4,7 +4,7 @@ namespace GodotXUnitApi.Internal { - public class GodotFactDiscoverer : IXunitTestCaseDiscoverer + public partial class GodotFactDiscoverer : IXunitTestCaseDiscoverer { private readonly IMessageSink diagnosticMessageSink; diff --git a/addons/GodotXUnit/GodotXUnitApi/Internal/GodotTestCase.cs b/addons/GodotXUnit/GodotXUnitApi/Internal/GodotTestCase.cs index d4b5cf8..5b28757 100644 --- a/addons/GodotXUnit/GodotXUnitApi/Internal/GodotTestCase.cs +++ b/addons/GodotXUnit/GodotXUnitApi/Internal/GodotTestCase.cs @@ -10,14 +10,14 @@ namespace GodotXUnitApi.Internal { - public class GodotTestCase : XunitTestCase + public partial class GodotTestCase : XunitTestCase { private IAttributeInfo attribute; - + [EditorBrowsable(EditorBrowsableState.Never)] [Obsolete("Called by the de-serializer; should only be called by deriving classes for de-serialization purposes")] - public GodotTestCase() {} - + public GodotTestCase() { } + public GodotTestCase(IAttributeInfo attribute, IMessageSink diagnosticMessageSink, ITestFrameworkDiscoveryOptions discoveryOptions, @@ -42,7 +42,7 @@ public override async Task RunAsync(IMessageSink diagnosticMessageSi this, DisplayName, SkipReason, - constructorArguments, + constructorArguments, TestMethodArguments, messageBus, aggregator, @@ -51,10 +51,10 @@ public override async Task RunAsync(IMessageSink diagnosticMessageSi } } - public class GodotTestCaseRunner : XunitTestCaseRunner + public partial class GodotTestCaseRunner : XunitTestCaseRunner { private IAttributeInfo attribute; - + public GodotTestCaseRunner(IAttributeInfo attribute, IXunitTestCase testCase, string displayName, @@ -90,7 +90,7 @@ protected override XunitTestRunner CreateTestRunner( { return new GodotTestRunner(attribute, test, - messageBus, + messageBus, testClass, constructorArguments, testMethod, @@ -102,7 +102,7 @@ protected override XunitTestRunner CreateTestRunner( } } - public class GodotTestRunner : XunitTestRunner + public partial class GodotTestRunner : XunitTestRunner { private IAttributeInfo attribute; @@ -130,17 +130,17 @@ public GodotTestRunner(IAttributeInfo attribute, { this.attribute = attribute; } - + protected override async Task> InvokeTestAsync(ExceptionAggregator aggregator) { - + // override the ITestOutputHelper from XunitTestClassRunner - TestOutputHelper helper = null; + TestOutputHelper helper = null; for (int i = 0; i < ConstructorArguments.Length; i++) { if (ConstructorArguments[i] is ITestOutputHelper) { - helper = (TestOutputHelper) ConstructorArguments[i]; + helper = (TestOutputHelper)ConstructorArguments[i]; break; } } @@ -166,10 +166,10 @@ protected override Task InvokeTestMethodAsync(ExceptionAggregator aggre } } - public class GodotTestInvoker : XunitTestInvoker + public partial class GodotTestInvoker : XunitTestInvoker { private IAttributeInfo attribute; - + private Node addingToTree; private bool loadEmptyScene; @@ -185,8 +185,8 @@ public GodotTestInvoker(IAttributeInfo attribute, ExceptionAggregator aggregator, CancellationTokenSource cancellationTokenSource) : base(test, - messageBus, - testClass, + messageBus, + testClass, constructorArguments, testMethod, testMethodArguments, @@ -202,79 +202,141 @@ protected override object CreateTestClass() var check = base.CreateTestClass(); if (check is Node node) addingToTree = node; - + return check; } + /// + /// Runs the given function in Godot's main thread. + /// + /// + private void CallInGodotMain(Func fn) + { + Exception caught = null; + var semaphore = new Godot.Semaphore(); + // Create a callable and use CallDeferred to add it to Godot's + // execution queue, which will run the function in the main thread. + // Callables do not (as of Godot 4.1) accept Task functions. + // Wrapping it in an action makes it fire-and-forget, which is + // fine; we're using a semaphore to signal completion anyway. + Callable.From(new Action(async () => + { + try + { + await fn(); + } + catch (AggregateException aggregate) + { + caught = aggregate.InnerException; + } + catch (Exception e) + { + caught = e; + } + finally + { + semaphore.Post(); + } + })).CallDeferred(); + // Note: We're blocking the thread here. Is that a bad thing? + // It's probably a XUnit worker thread, so maybe its fine, but + // if any deadlocks are discovered we might want to spawn a new + // thread for this whole operation. It might be nicer if this whole + // method was async anyway. + semaphore.Wait(); + if (caught is not null) + { + throw caught; + } + } + protected override async Task BeforeTestMethodInvokedAsync() { var sceneCheck = attribute.GetNamedArgument(nameof(GodotFactAttribute.Scene)); - if (!string.IsNullOrEmpty(sceneCheck)) + try { - // you must be in the process frame to - await GDU.OnProcessAwaiter; - if (GDU.Instance.GetTree().ChangeScene(sceneCheck) != Error.Ok) + CallInGodotMain(async () => { - Aggregator.Add(new Exception($"could not load scene: {sceneCheck}")); - return; - } - loadEmptyScene = true; - - // the scene should be loaded within two frames - await GDU.OnIdleFrameAwaiter; - await GDU.OnIdleFrameAwaiter; - await GDU.OnProcessAwaiter; + if (!string.IsNullOrEmpty(sceneCheck)) + { + // you must be in the process frame to + await GDU.OnProcessAwaiter; + + if (GDU.Instance.GetTree().ChangeSceneToFile(sceneCheck) != Error.Ok) + { + Aggregator.Add(new Exception($"could not load scene: {sceneCheck}")); + return; + } + loadEmptyScene = true; + + // the scene should be loaded within two frames + await GDU.OnProcessFrameAwaiter; + await GDU.OnProcessFrameAwaiter; + await GDU.OnProcessAwaiter; + } + + if (addingToTree != null) + { + await GDU.OnProcessAwaiter; + GDU.Instance.AddChild(addingToTree); + await GDU.OnProcessAwaiter; + } + }); + } - - if (addingToTree != null) + catch (Exception e) { - await GDU.OnProcessAwaiter; - GDU.Instance.AddChild(addingToTree); - await GDU.OnProcessAwaiter; + Aggregator.Add(e); } - await base.BeforeTestMethodInvokedAsync(); } protected override async Task InvokeTestMethodAsync(object testClassInstance) { - var sceneCheck = attribute.GetNamedArgument(nameof(GodotFactAttribute.Frame)); - switch (sceneCheck) + decimal result = default; + CallInGodotMain(async () => { - case GodotFactFrame.Default: - break; - case GodotFactFrame.Process: - await GDU.OnProcessAwaiter; - break; - case GodotFactFrame.PhysicsProcess: - await GDU.OnPhysicsProcessAwaiter; - break; - default: - Aggregator.Add(new Exception($"unknown GodotFactFrame: {sceneCheck.ToString()}")); - throw new ArgumentOutOfRangeException(); - } - return await base.InvokeTestMethodAsync(testClassInstance); + var sceneCheck = attribute.GetNamedArgument(nameof(GodotFactAttribute.Frame)); + switch (sceneCheck) + { + case GodotFactFrame.Default: + break; + case GodotFactFrame.Process: + await GDU.OnProcessAwaiter; + break; + case GodotFactFrame.PhysicsProcess: + await GDU.OnPhysicsProcessAwaiter; + break; + default: + Aggregator.Add(new Exception($"unknown GodotFactFrame: {sceneCheck.ToString()}")); + throw new ArgumentOutOfRangeException(); + } + result = await base.InvokeTestMethodAsync(testClassInstance); + }); + return result; } protected override async Task AfterTestMethodInvokedAsync() { await base.AfterTestMethodInvokedAsync(); - - if (addingToTree != null) + CallInGodotMain(async () => { - await GDU.OnProcessAwaiter; - GDU.Instance.RemoveChild(addingToTree); - await GDU.OnProcessAwaiter; - } + if (addingToTree != null) + { + await GDU.OnProcessAwaiter; + GDU.Instance.RemoveChild(addingToTree); + await GDU.OnProcessAwaiter; + } - if (loadEmptyScene) - { - // change scenes again and wait for godot to catch up - GDU.Instance.GetTree().ChangeScene(Consts.EMPTY_SCENE_PATH); - await GDU.OnIdleFrameAwaiter; - await GDU.OnIdleFrameAwaiter; - await GDU.OnProcessAwaiter; - } + if (loadEmptyScene) + { + // change scenes again and wait for godot to catch up + GDU.Instance.GetTree().ChangeSceneToFile(Consts.EMPTY_SCENE_PATH); + await GDU.OnProcessFrameAwaiter; + await GDU.OnProcessFrameAwaiter; + await GDU.OnProcessAwaiter; + } + }); } } } \ No newline at end of file diff --git a/addons/GodotXUnit/GodotXUnitApi/Internal/GodotTestOutputHelper.cs b/addons/GodotXUnit/GodotXUnitApi/Internal/GodotTestOutputHelper.cs index d5c7ca6..09a6301 100644 --- a/addons/GodotXUnit/GodotXUnitApi/Internal/GodotTestOutputHelper.cs +++ b/addons/GodotXUnit/GodotXUnitApi/Internal/GodotTestOutputHelper.cs @@ -20,7 +20,7 @@ namespace GodotXUnitApi.Internal /// handed in with our own because it breaks compatibility with /// IDE runners. /// - public class GodotTestOutputHelper : TextWriter + public partial class GodotTestOutputHelper : TextWriter { private TestOutputHelper wrapping; private TextWriter oldOutput; diff --git a/addons/GodotXUnit/GodotXUnitApi/Internal/GodotXUnitRunnerBase.cs b/addons/GodotXUnit/GodotXUnitApi/Internal/GodotXUnitRunnerBase.cs index 4237af4..7f3fc3a 100644 --- a/addons/GodotXUnit/GodotXUnitApi/Internal/GodotXUnitRunnerBase.cs +++ b/addons/GodotXUnit/GodotXUnitApi/Internal/GodotXUnitRunnerBase.cs @@ -1,29 +1,53 @@ using System; using System.Collections.Concurrent; +using System.Linq; using System.Reflection; using Godot; using Newtonsoft.Json; using Xunit.Runners; using Path = System.IO.Path; -using Directory = System.IO.Directory; namespace GodotXUnitApi.Internal { - public abstract class GodotXUnitRunnerBase : Node2D + public abstract partial class GodotXUnitRunnerBase : Node2D { - protected virtual Assembly GetTargetAssembly(GodotXUnitSummary summary) + /// + /// Get the assembly name from the project settings. + /// + /// The name of the target assembly or null if the default is requested + private String GetTargetAssemblyNameFromSettings() { - // check if we even have a project assembly set if (!ProjectSettings.HasSetting(Consts.SETTING_TARGET_ASSEMBLY)) - return Assembly.GetExecutingAssembly(); - - // get the project and if its the default (the godot project), return executing + { + return null; + } var targetProject = ProjectSettings.GetSetting(Consts.SETTING_TARGET_ASSEMBLY).ToString(); if (string.IsNullOrEmpty(targetProject) || targetProject.Equals(ProjectListing.GetDefaultProject())) - return Assembly.GetExecutingAssembly(); + { + return null; + } + return targetProject; + } + + private String GetAssemblyPath(String assemblyName) + { + var currentDir = System.IO.Directory.GetCurrentDirectory(); + return Path.Combine(currentDir, $".mono/build/bin/Debug/{assemblyName}.dll"); + } - // if its a custom project target, attempt to just load the assembly directly - if (targetProject.Equals(Consts.SETTING_TARGET_ASSEMBLY_CUSTOM_FLAG)) + private String GetDefaultTargetAssemblyPath() + { + return GetAssemblyPath(Assembly.GetExecutingAssembly().GetName().Name); + } + + private String GetTargetAssemblyPath(GodotXUnitSummary summary) + { + var assemblyName = GetTargetAssemblyNameFromSettings(); + if (assemblyName is null) + { + return GetDefaultTargetAssemblyPath(); + } + if (assemblyName.Equals(Consts.SETTING_TARGET_ASSEMBLY_CUSTOM_FLAG)) { var customDll = ProjectSettings.HasSetting(Consts.SETTING_TARGET_ASSEMBLY_CUSTOM) ? ProjectSettings.GetSetting(Consts.SETTING_TARGET_ASSEMBLY_CUSTOM).ToString() @@ -32,53 +56,51 @@ protected virtual Assembly GetTargetAssembly(GodotXUnitSummary summary) { summary.AddDiagnostic("no custom dll assembly configured."); GD.PrintErr("no custom dll assembly configured."); - return Assembly.GetExecutingAssembly(); + return GetDefaultTargetAssemblyPath(); } summary.AddDiagnostic($"attempting to load custom dll at: {customDll}"); - return AppDomain.CurrentDomain.Load(AssemblyName.GetAssemblyName(customDll)); + return customDll; } // find the project in the project list. if its not there, print error and leave var projectList = ProjectListing.GetProjectInfo(); - if (!projectList.ContainsKey(targetProject)) + if (!projectList.ContainsKey(assemblyName)) { GD.PrintErr( - $"unable to find project {targetProject}. expected values: {string.Join(", ", projectList.Keys)}"); - return Assembly.GetExecutingAssembly(); + $"unable to find project {assemblyName}. expected values: {string.Join(", ", projectList.Keys)}"); + return GetDefaultTargetAssemblyPath(); } // finally, attempt to load project.. - var currentDir = Directory.GetCurrentDirectory(); - var targetAssembly = Path.Combine(currentDir, $".mono/build/bin/Debug/{targetProject}.dll"); - var name = AssemblyName.GetAssemblyName(targetAssembly); - return AppDomain.CurrentDomain.Load(name); + var targetAssembly = GetAssemblyPath(assemblyName); + return targetAssembly; } protected virtual string GetTargetClass(GodotXUnitSummary summary) { return ProjectSettings.HasSetting(Consts.SETTING_TARGET_CLASS) - ? ProjectSettings.GetSetting(Consts.SETTING_TARGET_CLASS)?.ToString() + ? ProjectSettings.GetSetting(Consts.SETTING_TARGET_CLASS).AsString() : null; } protected virtual string GetTargetMethod(GodotXUnitSummary summary) { return ProjectSettings.HasSetting(Consts.SETTING_TARGET_METHOD) - ? ProjectSettings.GetSetting(Consts.SETTING_TARGET_METHOD)?.ToString() + ? ProjectSettings.GetSetting(Consts.SETTING_TARGET_METHOD).AsString() : null; } private ConcurrentQueue> drawRequests = new ConcurrentQueue>(); [Signal] - public delegate void OnProcess(); + public delegate void OnProcessEventHandler(); [Signal] - public delegate void OnPhysicsProcess(); + public delegate void OnPhysicsProcessEventHandler(); [Signal] - public delegate void OnDrawRequestDone(); + public delegate void OnDrawRequestDoneEventHandler(); public void RequestDraw(Action request) { @@ -91,6 +113,31 @@ public void RequestDraw(Action request) private MessageSender messages; + public Assembly AssemblyResolve(object sender, ResolveEventArgs args) + { + GD.Print($"Resolving {args.Name}."); + Assembly assembly = AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault(a => a.FullName == args.Name); + if (assembly is not null) + { + GD.Print($"Assembly resolution success, already loaded: {assembly.Location}"); + return assembly; + } + try + { + var shortName = args.Name.Split(",")[0]; + assembly = Assembly.LoadFile(GetAssemblyPath(shortName)); + GD.Print($"Assembly resolution success {args.Name} -> {assembly.Location}"); + return assembly; + } + catch (System.IO.FileNotFoundException e) + { + var msg = $"Assembly resolution failed for {args.Name}, requested by {args.RequestingAssembly?.FullName ?? "unknown assembly"}"; + GD.PrintErr(msg); + GD.PushError(msg); + return null; + } + } + public override void _Ready() { GDU.Instance = this; @@ -98,6 +145,7 @@ public override void _Ready() WorkFiles.CleanWorkDir(); summary = new GodotXUnitSummary(); messages = new MessageSender(); + AppDomain.CurrentDomain.AssemblyResolve += AssemblyResolve; CreateRunner(); if (runner == null) { @@ -166,7 +214,7 @@ public override void _Ready() GD.Print($"targeting method for discovery: {targetMethod}"); runner.TestCaseFilter = test => targetMethod.Equals(test.TestMethod.Method.Name); } - + // if its an empty string, then we need to set it to null because the runner only checks for null var targetClass = GetTargetClass(summary); if (string.IsNullOrEmpty(targetClass)) @@ -182,14 +230,14 @@ private void CreateRunner() { try { - var check = GetTargetAssembly(summary); - if (check == null) + var assemblyPath = GetTargetAssemblyPath(summary); + if (String.IsNullOrEmpty(assemblyPath)) { - GD.PrintErr("no assembly returned for tests"); summary.AddDiagnostic(new Exception("no assembly returned for tests")); return; } - runner = AssemblyRunner.WithoutAppDomain(check.Location); + summary.AddDiagnostic($"Loading assembly at {assemblyPath}"); + runner = AssemblyRunner.WithoutAppDomain(assemblyPath); } catch (Exception ex) { @@ -211,22 +259,26 @@ private void WriteSummary(GodotXUnitSummary testSummary) var location = ProjectSettings.HasSetting(Consts.SETTING_RESULTS_SUMMARY) ? ProjectSettings.GetSetting(Consts.SETTING_RESULTS_SUMMARY).ToString() : Consts.SETTING_RESULTS_SUMMARY_DEF; - var file = new Godot.File(); - var result = file.Open(location, Godot.File.ModeFlags.Write); - if (result == Error.Ok) + + try + { + var file = FileAccess.Open(location, FileAccess.ModeFlags.Write).ThrowIfNotOk(); file.StoreString(JsonConvert.SerializeObject(testSummary, Formatting.Indented, WorkFiles.jsonSettings)); - else - GD.Print($"error returned for writing message at {location}: {result}"); - file.Close(); + file.Close(); + } + catch (System.IO.IOException e) + { + GD.Print($"error returned for writing message at {location}: {e}"); + } } - public override void _Process(float delta) + public override void _Process(double delta) { EmitSignal(nameof(OnProcess)); - Update(); + QueueRedraw(); } - public override void _PhysicsProcess(float delta) + public override void _PhysicsProcess(double delta) { EmitSignal(nameof(OnPhysicsProcess)); } diff --git a/addons/GodotXUnit/GodotXUnitApi/Internal/MessagePassing.cs b/addons/GodotXUnit/GodotXUnitApi/Internal/MessagePassing.cs index da3ab00..dcb5978 100644 --- a/addons/GodotXUnit/GodotXUnitApi/Internal/MessagePassing.cs +++ b/addons/GodotXUnit/GodotXUnitApi/Internal/MessagePassing.cs @@ -2,7 +2,7 @@ namespace GodotXUnitApi.Internal { - public class MessageSender + public partial class MessageSender { public int idAt { get; private set; } @@ -17,14 +17,14 @@ public void SendMessage(object message, string type) } } - public class MessageWatcher + public partial class MessageWatcher { - private Directory directory = new Directory(); - public object Poll() { - directory.ChangeDir(WorkFiles.WorkDir).ThrowIfNotOk(); - directory.ListDirBegin(true, true).ThrowIfNotOk(); + var directory = DirAccess.Open(WorkFiles.WorkDir).ThrowIfNotOk(); + directory.IncludeHidden = false; + directory.IncludeNavigational = false; + directory.ListDirBegin().ThrowIfNotOk(); try { while (true) diff --git a/addons/GodotXUnit/GodotXUnitApi/Internal/ProjectListing.cs b/addons/GodotXUnit/GodotXUnitApi/Internal/ProjectListing.cs index 5090902..93a129b 100644 --- a/addons/GodotXUnit/GodotXUnitApi/Internal/ProjectListing.cs +++ b/addons/GodotXUnit/GodotXUnitApi/Internal/ProjectListing.cs @@ -8,9 +8,9 @@ namespace GodotXUnitApi.Internal public static class ProjectListing { public static readonly string sep = Path.DirectorySeparatorChar.ToString(); - + private static string _projectDir; - + public static string ProjectDir { get @@ -29,12 +29,12 @@ public static string ProjectDir } GodotGD.PrintErr("unable to find root of godot project"); throw new Exception("unable to find root dir"); - + // TODO: if this becomes a problem, we can do OS.Execute('pwd'....), but i don't // want to do that if we don't need to. } } - + public static List GetProjectList() { var result = new List(); @@ -46,7 +46,7 @@ public static List GetProjectList() } return result; } - + public static Dictionary GetProjectInfo() { var result = new Dictionary(); diff --git a/addons/GodotXUnit/GodotXUnitApi/Internal/WorkFiles.cs b/addons/GodotXUnit/GodotXUnitApi/Internal/WorkFiles.cs index 964d680..6b41df0 100644 --- a/addons/GodotXUnit/GodotXUnitApi/Internal/WorkFiles.cs +++ b/addons/GodotXUnit/GodotXUnitApi/Internal/WorkFiles.cs @@ -21,25 +21,31 @@ public static string PathForResFile(string filename) public static void CleanWorkDir() { - var directory = new Godot.Directory(); - directory.MakeDirRecursive(WorkDir).ThrowIfNotOk(); - directory.Open(WorkDir).ThrowIfNotOk(); - directory.ListDirBegin(true, true).ThrowIfNotOk(); - while (true) + DirAccess.MakeDirRecursiveAbsolute(WorkDir).ThrowIfNotOk(); + var directory = DirAccess.Open(WorkDir).ThrowIfNotOk(); + directory.IncludeHidden = false; + directory.IncludeNavigational = false; + try + { + directory.ListDirBegin().ThrowIfNotOk(); + while (true) + { + var next = directory.GetNext(); + if (string.IsNullOrEmpty(next)) + break; + directory.Remove(next).ThrowIfNotOk(); + } + } + finally { - var next = directory.GetNext(); - if (string.IsNullOrEmpty(next)) - break; - directory.Remove(next).ThrowIfNotOk(); + directory.ListDirEnd(); } - directory.ListDirEnd(); } public static void WriteFile(string filename, object contents) { var writing = JsonConvert.SerializeObject(contents, Formatting.Indented, jsonSettings); - var file = new Godot.File(); - file.Open(PathForResFile(filename), File.ModeFlags.WriteRead).ThrowIfNotOk(); + var file = FileAccess.Open(PathForResFile(filename), FileAccess.ModeFlags.WriteRead).ThrowIfNotOk(); try { file.StoreString(writing); @@ -52,8 +58,7 @@ public static void WriteFile(string filename, object contents) public static object ReadFile(string filename) { - var file = new Godot.File(); - file.Open(PathForResFile(filename), File.ModeFlags.Read).ThrowIfNotOk(); + var file = FileAccess.Open(PathForResFile(filename), FileAccess.ModeFlags.Read).ThrowIfNotOk(); try { return JsonConvert.DeserializeObject(file.GetAsText(), jsonSettings); diff --git a/addons/GodotXUnit/Plugin.cs b/addons/GodotXUnit/Plugin.cs index f2ae7fe..ba73ed2 100644 --- a/addons/GodotXUnit/Plugin.cs +++ b/addons/GodotXUnit/Plugin.cs @@ -6,7 +6,7 @@ namespace GodotXUnit { [Tool] - public class Plugin : EditorPlugin + public partial class Plugin : EditorPlugin { private static Plugin _instance; @@ -14,7 +14,7 @@ public class Plugin : EditorPlugin private XUnitDock dock; - public override string GetPluginName() + public override string _GetPluginName() { return nameof(GodotXUnit); } @@ -27,8 +27,8 @@ public override void _EnterTree() EnsureProjectSetting(Consts.SETTING_TARGET_ASSEMBLY_CUSTOM_PROP); EnsureProjectSetting(Consts.SETTING_TARGET_CLASS_PROP); EnsureProjectSetting(Consts.SETTING_TARGET_METHOD_PROP); - dock = (XUnitDock) GD.Load(Consts.DOCK_SCENE_PATH).Instance(); - AddControlToBottomPanel(dock, GetPluginName()); + dock = GD.Load(Consts.DOCK_SCENE_PATH).Instantiate(); + AddControlToBottomPanel(dock, _GetPluginName()); } public override void _ExitTree() @@ -44,7 +44,7 @@ public override void _ExitTree() private void EnsureProjectSetting(Dictionary prop) { - var name = prop["name"]?.ToString() ?? throw new Exception("no name in prop"); + var name = prop["name"].AsString() ?? throw new Exception("no name in prop"); if (!ProjectSettings.HasSetting(name)) { ProjectSettings.SetSetting(name, prop["default"]); diff --git a/addons/GodotXUnit/XUnitDock.cs b/addons/GodotXUnit/XUnitDock.cs index b00bf49..68a1e57 100644 --- a/addons/GodotXUnit/XUnitDock.cs +++ b/addons/GodotXUnit/XUnitDock.cs @@ -11,7 +11,7 @@ namespace GodotXUnit { [Tool] - public class XUnitDock : MarginContainer + public partial class XUnitDock : MarginContainer { private RichTextLabel resultDetails; private RichTextLabel resultDiagnostics; @@ -30,7 +30,7 @@ public class XUnitDock : MarginContainer private int runningPid = -1; private CheckBox verboseCheck; private TabContainer runTabContainer; - + // there are better ways to do this, but to try to limit the amount of user // setup required, we'll just do this the hacky way. private Label totalRanLabel; @@ -44,40 +44,40 @@ public class XUnitDock : MarginContainer public override void _Ready() { - totalRanLabel = (Label) FindNode("TotalRanLabel"); - passedLabel = (Label) FindNode("PassedLabel"); - failedLabel = (Label) FindNode("FailedLabel"); - timeLabel = (Label) FindNode("TimeLabel"); + totalRanLabel = (Label)FindChild("TotalRanLabel"); + passedLabel = (Label)FindChild("PassedLabel"); + failedLabel = (Label)FindChild("FailedLabel"); + timeLabel = (Label)FindChild("TimeLabel"); ResetLabels(); - - stopButton = (Button) FindNode("StopButton"); - stopButton.Connect("pressed", this, nameof(StopTests)); - runAllButton = (Button) FindNode("RunAllTestsButton"); - runAllButton.Connect("pressed", this, nameof(RunAllTests)); - reRunButton = (Button) FindNode("ReRunButton"); - reRunButton.Connect("pressed", this, nameof(ReRunTests)); - targetClassLabel = (LineEdit) FindNode("TargetClassLabel"); - targetMethodLabel = (LineEdit) FindNode("TargetMethodLabel"); - runSelectedButton = (Button) FindNode("RunSelectedButton"); - runSelectedButton.Connect("pressed", this, nameof(RunSelected)); + + stopButton = (Button)FindChild("StopButton"); + stopButton.Connect("pressed", new Callable(this, nameof(StopTests))); + runAllButton = (Button)FindChild("RunAllTestsButton"); + runAllButton.Connect("pressed", new Callable(this, nameof(RunAllTests))); + reRunButton = (Button)FindChild("ReRunButton"); + reRunButton.Connect("pressed", new Callable(this, nameof(ReRunTests))); + targetClassLabel = (LineEdit)FindChild("TargetClassLabel"); + targetMethodLabel = (LineEdit)FindChild("TargetMethodLabel"); + runSelectedButton = (Button)FindChild("RunSelectedButton"); + runSelectedButton.Connect("pressed", new Callable(this, nameof(RunSelected))); runSelectedButton.Disabled = true; - targetAssemblyOption = (OptionButton) FindNode("TargetAssemblyOption"); - targetAssemblyOption.Connect("pressed", this, nameof(TargetAssemblyOptionPressed)); - targetAssemblyOption.Connect("item_selected", this, nameof(TargetAssemblyOptionSelected)); - targetAssemblyLabel = (LineEdit) FindNode("TargetAssemblyLabel"); + targetAssemblyOption = (OptionButton)FindChild("TargetAssemblyOption"); + targetAssemblyOption.Connect("pressed", new Callable(this, nameof(TargetAssemblyOptionPressed))); + targetAssemblyOption.Connect("item_selected", new Callable(this, nameof(TargetAssemblyOptionSelected))); + targetAssemblyLabel = (LineEdit)FindChild("TargetAssemblyLabel"); targetAssemblyLabel.Text = ProjectSettings.HasSetting(Consts.SETTING_TARGET_ASSEMBLY_CUSTOM) ? ProjectSettings.GetSetting(Consts.SETTING_TARGET_ASSEMBLY_CUSTOM).ToString() : ""; - targetAssemblyLabel.Connect("text_changed", this, nameof(TargetAssemblyLabelChanged)); + targetAssemblyLabel.Connect("text_changed", new Callable(this, nameof(TargetAssemblyLabelChanged))); TargetAssemblyOptionPressed(); - resultsTree = (Tree) FindNode("ResultsTree"); + resultsTree = (Tree)FindChild("ResultsTree"); resultsTree.HideRoot = true; resultsTree.SelectMode = Tree.SelectModeEnum.Single; - resultsTree.Connect("cell_selected", this, nameof(OnCellSelected)); - resultDetails = (RichTextLabel) FindNode("ResultDetails"); - resultDiagnostics = (RichTextLabel) FindNode("Diagnostics"); - verboseCheck = (CheckBox) FindNode("VerboseCheckBox"); - runTabContainer = (TabContainer) FindNode("RunTabContainer"); + resultsTree.Connect("cell_selected", new Callable(this, nameof(OnCellSelected))); + resultDetails = (RichTextLabel)FindChild("ResultDetails"); + resultDiagnostics = (RichTextLabel)FindChild("Diagnostics"); + verboseCheck = (CheckBox)FindChild("VerboseCheckBox"); + runTabContainer = (TabContainer)FindChild("RunTabContainer"); runTabContainer.CurrentTab = 0; SetProcess(false); } @@ -124,12 +124,12 @@ public void RunSelected() } StartTests(); } - + public void StartTests() { if (IsRunningTests()) OS.Kill(runningPid); - + runAllButton.Disabled = true; reRunButton.Disabled = true; runSelectedButton.Disabled = true; @@ -140,18 +140,18 @@ public void StartTests() resultDiagnostics.Text = ""; resultDetails.Text = ""; watcher = new MessageWatcher(); - + // if things dont clean up correctly, the old messages can still // be on the file system. this will cause the XUnitDock process to // see stale messages and potentially stop picking up new messages. WorkFiles.CleanWorkDir(); - + var runArgs = new StringList(); runArgs.Add(Consts.RUNNER_SCENE_PATH); - if (verboseCheck.Pressed) + if (verboseCheck.ButtonPressed) runArgs.Add("--verbose"); - runningPid = OS.Execute(OS.GetExecutablePath(), runArgs.ToArray(), false); - + runningPid = OS.Execute(OS.GetExecutablePath(), runArgs.ToArray(), null, false); + SetProcess(true); } @@ -172,7 +172,7 @@ public bool IsRunningTests() private void TargetAssemblyOptionPressed() { - var items = new GodotArray(); + targetAssemblyOption.Clear(); var projectList = ProjectListing.GetProjectList(); var projectSelected = ProjectSettings.HasSetting(Consts.SETTING_TARGET_ASSEMBLY) ? ProjectSettings.GetSetting(Consts.SETTING_TARGET_ASSEMBLY).ToString() @@ -183,14 +183,13 @@ private void TargetAssemblyOptionPressed() var projectName = projectList[i]; if (i == 0) projectName = $"{projectName} (main)"; - AddProjectListing(items, projectName, i); + targetAssemblyOption.AddItem(projectName, i); if (projectName.Equals(projectSelected)) projectSelectedIndex = i; } - AddProjectListing(items, "Custom Location ", 1000); + targetAssemblyOption.AddItem("Custom Location ", 1000); if (projectSelected.Equals(Consts.SETTING_TARGET_ASSEMBLY_CUSTOM_FLAG)) projectSelectedIndex = projectList.Count; - targetAssemblyOption.Items = items; targetAssemblyOption.Selected = projectSelectedIndex; } @@ -214,15 +213,6 @@ private void TargetAssemblyOptionSelected(int index) ProjectSettings.Save(); } - private void AddProjectListing(GodotArray items, string text, int id) - { - items.Add(text); - items.Add(null); - items.Add(false); - items.Add(id); - items.Add(null); - } - private void TargetAssemblyLabelChanged(string new_text) { ProjectSettings.SetSetting(Consts.SETTING_TARGET_ASSEMBLY_CUSTOM, new_text); @@ -238,7 +228,7 @@ private void OnCellSelected() runTabContainer.CurrentTab = 0; } - public override void _Process(float delta) + public override void _Process(double delta) { while (watcher != null) { @@ -276,7 +266,7 @@ private void GoToPostState() var missed = watcher.Poll(); if (missed == null) break; GD.PrintErr($"missed message: {missed.GetType()}"); - } + } } watcher = null; runningPid = -1; @@ -290,7 +280,7 @@ private void HandleTestStart(GodotXUnitTestStart testStart) var testItem = EnsureTreeClassAndMethod(testStart.testCaseClass, testStart.testCaseName); if (testItem.GetIcon(0) == null) { - testItem.SetIcon(0, Consts.IconRunning); + testItem.SetIcon(0, Consts.IconRunning); } } @@ -330,9 +320,9 @@ private void HandleTestResult(GodotXUnitTestResult testResult) private void SetTestResultDetails(GodotXUnitTestResult testResult, TreeItem item) { // set the header to include the time it took - var millis = (int) (testResult.time * 1000f); + var millis = (int)(testResult.time * 1000f); item.SetText(0, $"{testResult.testCaseName} ({millis} ms)"); - + // create the test result details var details = new StringBuilder(); details.AppendLine(testResult.FullName); @@ -347,7 +337,7 @@ private void SetTestResultDetails(GodotXUnitTestResult testResult, TreeItem item } details.AppendLine(string.IsNullOrEmpty(testResult.output) ? "No Output" : testResult.output); testDetails[item] = details.ToString(); - + // add the target so the run selected button can query what to run var target = new Array(); target.Add(testResult.testCaseClass); @@ -400,12 +390,10 @@ private TreeItem EnsureTreeClass(string testClass) private TreeItem FindTreeChildOrCreate(TreeItem parent, string name) { - var child = parent.GetChildren(); - while (child != null) + foreach (var child in parent.GetChildren()) { var text = child.GetMeta("for"); if (text.Equals(name)) return child; - child = child.GetNext(); } var newClassItem = resultsTree.CreateItem(parent); newClassItem.SetMeta("for", name); @@ -452,7 +440,7 @@ private void IncTimeLabel(float time) { // why? timeValue += time; - timeLabel.Text = $"Time: {(int) (timeValue * 1000)} ms"; + timeLabel.Text = $"Time: {(int)(timeValue * 1000)} ms"; } } } diff --git a/addons/GodotXUnit/XUnitDock.tscn b/addons/GodotXUnit/XUnitDock.tscn index 052950a..82e6f5f 100644 --- a/addons/GodotXUnit/XUnitDock.tscn +++ b/addons/GodotXUnit/XUnitDock.tscn @@ -5,99 +5,99 @@ [node name="Control" type="MarginContainer"] anchor_right = 1.0 anchor_bottom = 1.0 -custom_constants/margin_right = 0 -custom_constants/margin_top = 0 -custom_constants/margin_left = 0 -custom_constants/margin_bottom = 0 +theme_override_constants/margin_right = 0 +theme_override_constants/margin_top = 0 +theme_override_constants/margin_left = 0 +theme_override_constants/margin_bottom = 0 script = ExtResource( 1 ) __meta__ = { "_edit_use_anchors_": false } [node name="Rows" type="VBoxContainer" parent="."] -margin_right = 1024.0 -margin_bottom = 600.0 +offset_right = 1024.0 +offset_bottom = 600.0 [node name="Buttons" type="MarginContainer" parent="Rows"] -margin_right = 1024.0 -margin_bottom = 44.0 -custom_constants/margin_right = 5 -custom_constants/margin_top = 5 -custom_constants/margin_left = 5 -custom_constants/margin_bottom = 5 +offset_right = 1024.0 +offset_bottom = 44.0 +theme_override_constants/margin_right = 5 +theme_override_constants/margin_top = 5 +theme_override_constants/margin_left = 5 +theme_override_constants/margin_bottom = 5 [node name="Panel" type="Panel" parent="Rows/Buttons"] -margin_left = 5.0 -margin_top = 5.0 -margin_right = 1019.0 -margin_bottom = 39.0 +offset_left = 5.0 +offset_top = 5.0 +offset_right = 1019.0 +offset_bottom = 39.0 __meta__ = { "_edit_use_anchors_": false } [node name="Margin" type="MarginContainer" parent="Rows/Buttons"] -margin_left = 5.0 -margin_top = 5.0 -margin_right = 1019.0 -margin_bottom = 39.0 -custom_constants/margin_right = 5 -custom_constants/margin_top = 5 -custom_constants/margin_left = 5 -custom_constants/margin_bottom = 5 +offset_left = 5.0 +offset_top = 5.0 +offset_right = 1019.0 +offset_bottom = 39.0 +theme_override_constants/margin_right = 5 +theme_override_constants/margin_top = 5 +theme_override_constants/margin_left = 5 +theme_override_constants/margin_bottom = 5 [node name="Buttons" type="HBoxContainer" parent="Rows/Buttons/Margin"] -margin_left = 5.0 -margin_top = 5.0 -margin_right = 1009.0 -margin_bottom = 29.0 +offset_left = 5.0 +offset_top = 5.0 +offset_right = 1009.0 +offset_bottom = 29.0 __meta__ = { "_edit_use_anchors_": false } [node name="StopButton" type="Button" parent="Rows/Buttons/Margin/Buttons"] -margin_right = 40.0 -margin_bottom = 24.0 +offset_right = 40.0 +offset_bottom = 24.0 text = "Stop" [node name="RunAllTestsButton" type="Button" parent="Rows/Buttons/Margin/Buttons"] -margin_left = 44.0 -margin_right = 139.0 -margin_bottom = 24.0 +offset_left = 44.0 +offset_right = 139.0 +offset_bottom = 24.0 text = "Run All Tests" [node name="ReRunButton" type="Button" parent="Rows/Buttons/Margin/Buttons"] -margin_left = 143.0 -margin_right = 195.0 -margin_bottom = 24.0 +offset_left = 143.0 +offset_right = 195.0 +offset_bottom = 24.0 text = "ReRun" [node name="TargetClassLabel" type="LineEdit" parent="Rows/Buttons/Margin/Buttons"] -margin_left = 199.0 -margin_right = 389.0 -margin_bottom = 24.0 +offset_left = 199.0 +offset_right = 389.0 +offset_bottom = 24.0 size_flags_horizontal = 3 placeholder_text = "Target Class" [node name="TargetMethodLabel" type="LineEdit" parent="Rows/Buttons/Margin/Buttons"] -margin_left = 393.0 -margin_right = 583.0 -margin_bottom = 24.0 +offset_left = 393.0 +offset_right = 583.0 +offset_bottom = 24.0 size_flags_horizontal = 3 placeholder_text = "Target Method" [node name="TargetAssemblyOption" type="OptionButton" parent="Rows/Buttons/Margin/Buttons"] -margin_left = 587.0 -margin_right = 808.0 -margin_bottom = 24.0 +offset_left = 587.0 +offset_right = 808.0 +offset_bottom = 24.0 size_flags_horizontal = 3 text = "SubProjectForIntegrationTests" items = [ "GodotXUnit (main)", null, false, 0, null, "SubProjectForUnitTests", null, false, 1, null, "SubProjectForIntegrationTests", null, false, 2, null, "Custom Location ", null, false, 1000, null ] selected = 2 [node name="TargetAssemblyLabel" type="LineEdit" parent="Rows/Buttons/Margin/Buttons"] -margin_left = 812.0 -margin_right = 1004.0 -margin_bottom = 24.0 +offset_left = 812.0 +offset_right = 1004.0 +offset_bottom = 24.0 size_flags_horizontal = 3 text = "not found" placeholder_text = "Custom Assembly Path (dll)" @@ -106,135 +106,135 @@ __meta__ = { } [node name="Summary" type="MarginContainer" parent="Rows"] -margin_top = 48.0 -margin_right = 1024.0 -margin_bottom = 92.0 -custom_constants/margin_right = 5 -custom_constants/margin_top = 5 -custom_constants/margin_left = 5 -custom_constants/margin_bottom = 5 +offset_top = 48.0 +offset_right = 1024.0 +offset_bottom = 92.0 +theme_override_constants/margin_right = 5 +theme_override_constants/margin_top = 5 +theme_override_constants/margin_left = 5 +theme_override_constants/margin_bottom = 5 [node name="Panel" type="Panel" parent="Rows/Summary"] -margin_left = 5.0 -margin_top = 5.0 -margin_right = 1019.0 -margin_bottom = 39.0 +offset_left = 5.0 +offset_top = 5.0 +offset_right = 1019.0 +offset_bottom = 39.0 __meta__ = { "_edit_use_anchors_": false } [node name="Margin" type="MarginContainer" parent="Rows/Summary"] -margin_left = 5.0 -margin_top = 5.0 -margin_right = 1019.0 -margin_bottom = 39.0 -custom_constants/margin_right = 5 -custom_constants/margin_top = 5 -custom_constants/margin_left = 5 -custom_constants/margin_bottom = 5 +offset_left = 5.0 +offset_top = 5.0 +offset_right = 1019.0 +offset_bottom = 39.0 +theme_override_constants/margin_right = 5 +theme_override_constants/margin_top = 5 +theme_override_constants/margin_left = 5 +theme_override_constants/margin_bottom = 5 [node name="Results" type="HBoxContainer" parent="Rows/Summary/Margin"] -margin_left = 5.0 -margin_top = 5.0 -margin_right = 1009.0 -margin_bottom = 29.0 +offset_left = 5.0 +offset_top = 5.0 +offset_right = 1009.0 +offset_bottom = 29.0 __meta__ = { "_edit_use_anchors_": false } [node name="TotalRanLabel" type="Label" parent="Rows/Summary/Margin/Results"] -margin_top = 5.0 -margin_right = 227.0 -margin_bottom = 19.0 +offset_top = 5.0 +offset_right = 227.0 +offset_bottom = 19.0 size_flags_horizontal = 3 text = "TotalRan: 0" [node name="PassedLabel" type="Label" parent="Rows/Summary/Margin/Results"] -margin_left = 231.0 -margin_top = 5.0 -margin_right = 458.0 -margin_bottom = 19.0 +offset_left = 231.0 +offset_top = 5.0 +offset_right = 458.0 +offset_bottom = 19.0 size_flags_horizontal = 3 text = "Passed: 0" [node name="FailedLabel" type="Label" parent="Rows/Summary/Margin/Results"] -margin_left = 462.0 -margin_right = 689.0 -margin_bottom = 24.0 +offset_left = 462.0 +offset_right = 689.0 +offset_bottom = 24.0 size_flags_horizontal = 3 size_flags_vertical = 1 text = "Failed: 0" [node name="TimeLabel" type="Label" parent="Rows/Summary/Margin/Results"] -margin_left = 693.0 -margin_top = 5.0 -margin_right = 920.0 -margin_bottom = 19.0 +offset_left = 693.0 +offset_top = 5.0 +offset_right = 920.0 +offset_bottom = 19.0 size_flags_horizontal = 3 text = "Time: 0 ms" [node name="VerboseCheckBox" type="CheckBox" parent="Rows/Summary/Margin/Results"] -margin_left = 924.0 -margin_right = 1004.0 -margin_bottom = 24.0 +offset_left = 924.0 +offset_right = 1004.0 +offset_bottom = 24.0 text = "Verbose" [node name="Details" type="MarginContainer" parent="Rows"] -margin_top = 96.0 -margin_right = 1024.0 -margin_bottom = 600.0 +offset_top = 96.0 +offset_right = 1024.0 +offset_bottom = 600.0 size_flags_horizontal = 3 size_flags_vertical = 3 -custom_constants/margin_right = 5 -custom_constants/margin_top = 5 -custom_constants/margin_left = 5 -custom_constants/margin_bottom = 5 +theme_override_constants/margin_right = 5 +theme_override_constants/margin_top = 5 +theme_override_constants/margin_left = 5 +theme_override_constants/margin_bottom = 5 [node name="HBoxContainer" type="HBoxContainer" parent="Rows/Details"] -margin_left = 5.0 -margin_top = 5.0 -margin_right = 1019.0 -margin_bottom = 499.0 +offset_left = 5.0 +offset_top = 5.0 +offset_right = 1019.0 +offset_bottom = 499.0 [node name="ResultsTree" type="Tree" parent="Rows/Details/HBoxContainer"] -margin_right = 505.0 -margin_bottom = 494.0 +offset_right = 505.0 +offset_bottom = 494.0 size_flags_horizontal = 3 size_flags_vertical = 3 hide_root = true [node name="VBoxContainer" type="VBoxContainer" parent="Rows/Details/HBoxContainer"] -margin_left = 509.0 -margin_right = 1014.0 -margin_bottom = 494.0 +offset_left = 509.0 +offset_right = 1014.0 +offset_bottom = 494.0 size_flags_horizontal = 3 size_flags_vertical = 3 [node name="HBoxContainer" type="HBoxContainer" parent="Rows/Details/HBoxContainer/VBoxContainer"] -margin_right = 505.0 -margin_bottom = 20.0 +offset_right = 505.0 +offset_bottom = 20.0 size_flags_horizontal = 3 [node name="RunSelectedButton" type="Button" parent="Rows/Details/HBoxContainer/VBoxContainer/HBoxContainer"] -margin_right = 95.0 -margin_bottom = 20.0 +offset_right = 95.0 +offset_bottom = 20.0 disabled = true text = "Run Selected" [node name="RunTabContainer" type="TabContainer" parent="Rows/Details/HBoxContainer/VBoxContainer"] -margin_top = 24.0 -margin_right = 505.0 -margin_bottom = 494.0 +offset_top = 24.0 +offset_right = 505.0 +offset_bottom = 494.0 size_flags_horizontal = 3 size_flags_vertical = 3 [node name="ResultDetails" type="RichTextLabel" parent="Rows/Details/HBoxContainer/VBoxContainer/RunTabContainer"] anchor_right = 1.0 anchor_bottom = 1.0 -margin_left = 4.0 -margin_top = 32.0 -margin_right = -4.0 -margin_bottom = -4.0 +offset_left = 4.0 +offset_top = 32.0 +offset_right = -4.0 +offset_bottom = -4.0 focus_mode = 2 size_flags_horizontal = 3 size_flags_vertical = 3 @@ -244,10 +244,10 @@ selection_enabled = true visible = false anchor_right = 1.0 anchor_bottom = 1.0 -margin_left = 4.0 -margin_top = 32.0 -margin_right = -4.0 -margin_bottom = -4.0 +offset_left = 4.0 +offset_top = 32.0 +offset_right = -4.0 +offset_bottom = -4.0 focus_mode = 2 size_flags_horizontal = 3 size_flags_vertical = 3 diff --git a/addons/GodotXUnit/assets/check.png.import b/addons/GodotXUnit/assets/check.png.import index 407fe3a..2240dfa 100644 --- a/addons/GodotXUnit/assets/check.png.import +++ b/addons/GodotXUnit/assets/check.png.import @@ -1,8 +1,9 @@ [remap] importer="texture" -type="StreamTexture" -path="res://.import/check.png-93c4fe9a01601b2e5a6e2306ca570d1e.stex" +type="CompressedTexture2D" +uid="uid://cccd5c8wn54vy" +path="res://.godot/imported/check.png-93c4fe9a01601b2e5a6e2306ca570d1e.ctex" metadata={ "vram_texture": false } @@ -10,25 +11,24 @@ metadata={ [deps] source_file="res://addons/GodotXUnit/assets/check.png" -dest_files=[ "res://.import/check.png-93c4fe9a01601b2e5a6e2306ca570d1e.stex" ] +dest_files=["res://.godot/imported/check.png-93c4fe9a01601b2e5a6e2306ca570d1e.ctex"] [params] compress/mode=0 +compress/high_quality=false compress/lossy_quality=0.7 -compress/hdr_mode=0 -compress/bptc_ldr=0 +compress/hdr_compression=1 compress/normal_map=0 -flags/repeat=0 -flags/filter=true -flags/mipmaps=false -flags/anisotropic=false -flags/srgb=2 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" process/fix_alpha_border=true process/premult_alpha=false -process/HDR_as_SRGB=false -process/invert_color=false -stream=false -size_limit=0 -detect_3d=true -svg/scale=1.0 +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/addons/GodotXUnit/assets/error.png.import b/addons/GodotXUnit/assets/error.png.import index b5fe338..67f3502 100644 --- a/addons/GodotXUnit/assets/error.png.import +++ b/addons/GodotXUnit/assets/error.png.import @@ -1,8 +1,9 @@ [remap] importer="texture" -type="StreamTexture" -path="res://.import/error.png-7d7dee34d347bdc60be578dd61003c69.stex" +type="CompressedTexture2D" +uid="uid://pt7u6b6txpf4" +path="res://.godot/imported/error.png-7d7dee34d347bdc60be578dd61003c69.ctex" metadata={ "vram_texture": false } @@ -10,25 +11,24 @@ metadata={ [deps] source_file="res://addons/GodotXUnit/assets/error.png" -dest_files=[ "res://.import/error.png-7d7dee34d347bdc60be578dd61003c69.stex" ] +dest_files=["res://.godot/imported/error.png-7d7dee34d347bdc60be578dd61003c69.ctex"] [params] compress/mode=0 +compress/high_quality=false compress/lossy_quality=0.7 -compress/hdr_mode=0 -compress/bptc_ldr=0 +compress/hdr_compression=1 compress/normal_map=0 -flags/repeat=0 -flags/filter=true -flags/mipmaps=false -flags/anisotropic=false -flags/srgb=2 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" process/fix_alpha_border=true process/premult_alpha=false -process/HDR_as_SRGB=false -process/invert_color=false -stream=false -size_limit=0 -detect_3d=true -svg/scale=1.0 +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/addons/GodotXUnit/assets/running.png.import b/addons/GodotXUnit/assets/running.png.import index eb787e1..fd1a506 100644 --- a/addons/GodotXUnit/assets/running.png.import +++ b/addons/GodotXUnit/assets/running.png.import @@ -1,8 +1,9 @@ [remap] importer="texture" -type="StreamTexture" -path="res://.import/running.png-f4bbeb95db62dba2dfecfd374db363b5.stex" +type="CompressedTexture2D" +uid="uid://dpjbiechcpl37" +path="res://.godot/imported/running.png-f4bbeb95db62dba2dfecfd374db363b5.ctex" metadata={ "vram_texture": false } @@ -10,25 +11,24 @@ metadata={ [deps] source_file="res://addons/GodotXUnit/assets/running.png" -dest_files=[ "res://.import/running.png-f4bbeb95db62dba2dfecfd374db363b5.stex" ] +dest_files=["res://.godot/imported/running.png-f4bbeb95db62dba2dfecfd374db363b5.ctex"] [params] compress/mode=0 +compress/high_quality=false compress/lossy_quality=0.7 -compress/hdr_mode=0 -compress/bptc_ldr=0 +compress/hdr_compression=1 compress/normal_map=0 -flags/repeat=0 -flags/filter=true -flags/mipmaps=false -flags/anisotropic=false -flags/srgb=2 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" process/fix_alpha_border=true process/premult_alpha=false -process/HDR_as_SRGB=false -process/invert_color=false -stream=false -size_limit=0 -detect_3d=true -svg/scale=1.0 +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/addons/GodotXUnit/assets/warn.png.import b/addons/GodotXUnit/assets/warn.png.import index 5342fab..0b2717f 100644 --- a/addons/GodotXUnit/assets/warn.png.import +++ b/addons/GodotXUnit/assets/warn.png.import @@ -1,8 +1,9 @@ [remap] importer="texture" -type="StreamTexture" -path="res://.import/warn.png-d4a9955bdfe89d37d1373fc2d882c282.stex" +type="CompressedTexture2D" +uid="uid://q0gw22cly0cq" +path="res://.godot/imported/warn.png-d4a9955bdfe89d37d1373fc2d882c282.ctex" metadata={ "vram_texture": false } @@ -10,25 +11,24 @@ metadata={ [deps] source_file="res://addons/GodotXUnit/assets/warn.png" -dest_files=[ "res://.import/warn.png-d4a9955bdfe89d37d1373fc2d882c282.stex" ] +dest_files=["res://.godot/imported/warn.png-d4a9955bdfe89d37d1373fc2d882c282.ctex"] [params] compress/mode=0 +compress/high_quality=false compress/lossy_quality=0.7 -compress/hdr_mode=0 -compress/bptc_ldr=0 +compress/hdr_compression=1 compress/normal_map=0 -flags/repeat=0 -flags/filter=true -flags/mipmaps=false -flags/anisotropic=false -flags/srgb=2 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" process/fix_alpha_border=true process/premult_alpha=false -process/HDR_as_SRGB=false -process/invert_color=false -stream=false -size_limit=0 -detect_3d=true -svg/scale=1.0 +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/addons/GodotXUnit/runner/GodotTestRunnerScene.tscn b/addons/GodotXUnit/runner/GodotTestRunnerScene.tscn index 02d806b..d625368 100644 --- a/addons/GodotXUnit/runner/GodotTestRunnerScene.tscn +++ b/addons/GodotXUnit/runner/GodotTestRunnerScene.tscn @@ -1,6 +1,6 @@ -[gd_scene load_steps=2 format=2] +[gd_scene load_steps=2 format=3 uid="uid://dpfjqgmj8q0ex"] -[ext_resource path="res://addons/GodotXUnit/runner/GodotTestEntry.gd" type="Script" id=1] +[ext_resource type="Script" path="res://addons/GodotXUnit/runner/GodotTestEntry.gd" id="1"] [node name="GodotTestRunnerScene" type="Node2D"] -script = ExtResource( 1 ) +script = ExtResource("1") diff --git a/addons/GodotXUnit/runner/RiderTestRunner/Runner.cs b/addons/GodotXUnit/runner/RiderTestRunner/Runner.cs index 26f1cb8..ba73245 100644 --- a/addons/GodotXUnit/runner/RiderTestRunner/Runner.cs +++ b/addons/GodotXUnit/runner/RiderTestRunner/Runner.cs @@ -10,7 +10,7 @@ namespace RiderTestRunner { // ReSharper disable once UnusedType.Global - public class Runner : GodotXUnitRunnerBase // for GodotXUnit use: public class Runner : GodotTestRunner. https://github.com/fledware/GodotXUnit/issues/8#issuecomment-929849478 + public partial class Runner : GodotXUnitRunnerBase // for GodotXUnit use: public partial class Runner : GodotTestRunner. https://github.com/fledware/GodotXUnit/issues/8#issuecomment-929849478 { public override void _Ready() { diff --git a/addons/GodotXUnit/runner/RiderTestRunner/Runner.tscn b/addons/GodotXUnit/runner/RiderTestRunner/Runner.tscn index 9d9835f..3a3506f 100644 --- a/addons/GodotXUnit/runner/RiderTestRunner/Runner.tscn +++ b/addons/GodotXUnit/runner/RiderTestRunner/Runner.tscn @@ -6,11 +6,11 @@ script = ExtResource( 1 ) [node name="RichTextLabel" type="RichTextLabel" parent="."] -margin_left = 10.0 -margin_top = 10.0 -margin_right = 500.0 -margin_bottom = 200.0 -rect_scale = Vector2( 2, 2 ) +offset_left = 10.0 +offset_top = 10.0 +offset_right = 500.0 +offset_bottom = 200.0 +scale = Vector2( 2, 2 ) text = "Running" __meta__ = { "_edit_use_anchors_": false diff --git a/default_env.tres b/default_env.tres index 20207a4..324f988 100644 --- a/default_env.tres +++ b/default_env.tres @@ -1,7 +1,7 @@ -[gd_resource type="Environment" load_steps=2 format=2] +[gd_resource type="Environment" load_steps=2 format=3 uid="uid://cweo0g4tp68x"] -[sub_resource type="ProceduralSky" id=1] +[sub_resource type="Sky" id="1"] [resource] background_mode = 2 -background_sky = SubResource( 1 ) +sky = SubResource("1") diff --git a/icon.png.import b/icon.png.import index 96cbf46..3c02652 100644 --- a/icon.png.import +++ b/icon.png.import @@ -1,8 +1,9 @@ [remap] importer="texture" -type="StreamTexture" -path="res://.import/icon.png-487276ed1e3a0c39cad0279d744ee560.stex" +type="CompressedTexture2D" +uid="uid://dtyr5hwjibqfx" +path="res://.godot/imported/icon.png-487276ed1e3a0c39cad0279d744ee560.ctex" metadata={ "vram_texture": false } @@ -10,25 +11,24 @@ metadata={ [deps] source_file="res://icon.png" -dest_files=[ "res://.import/icon.png-487276ed1e3a0c39cad0279d744ee560.stex" ] +dest_files=["res://.godot/imported/icon.png-487276ed1e3a0c39cad0279d744ee560.ctex"] [params] compress/mode=0 +compress/high_quality=false compress/lossy_quality=0.7 -compress/hdr_mode=0 -compress/bptc_ldr=0 +compress/hdr_compression=1 compress/normal_map=0 -flags/repeat=0 -flags/filter=true -flags/mipmaps=false -flags/anisotropic=false -flags/srgb=2 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" process/fix_alpha_border=true process/premult_alpha=false -process/HDR_as_SRGB=false -process/invert_color=false -stream=false -size_limit=0 -detect_3d=true -svg/scale=1.0 +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/project.godot b/project.godot index 3920268..6956f1c 100644 --- a/project.godot +++ b/project.godot @@ -6,11 +6,7 @@ ; [section] ; section goes between [] ; param=value ; assign values to parameters -config_version=4 - -_global_script_classes=[ ] -_global_script_class_icons={ -} +config_version=5 [GodotXUnit] @@ -22,11 +18,16 @@ results_summary="res://TestResults.json" config/name="GodotXUnit" run/main_scene="res://addons/GodotXUnit/runner/GodotTestRunnerScene.tscn" +config/features=PackedStringArray("4.1", "C#") config/icon="res://icon.png" +[dotnet] + +project/assembly_name="GodotXUnit" + [editor_plugins] -enabled=PoolStringArray( "GodotXUnit" ) +enabled=PackedStringArray("res://addons/GodotXUnit/plugin.cfg") [mono] @@ -34,4 +35,4 @@ export/aot/enabled=true [rendering] -environment/default_environment="res://default_env.tres" +environment/defaults/default_environment="res://default_env.tres" diff --git a/test_scenes/AVerySpecialBall.cs b/test_scenes/AVerySpecialBall.cs index a2a8eae..20a63a3 100644 --- a/test_scenes/AVerySpecialBall.cs +++ b/test_scenes/AVerySpecialBall.cs @@ -2,29 +2,27 @@ namespace GodotXUnitTest { - public class AVerySpecialBall : KinematicBody2D + public partial class AVerySpecialBall : CharacterBody2D { [Signal] - public delegate void WeCollidedd(); - + public delegate void WeCollidedEventHandler(); + // this ball doesnt really like to go anywhere public float gravity = 10f; - public Vector2 velocity = new Vector2(); - - public override void _Process(float delta) + public override void _Process(double delta) { - Update(); + QueueRedraw(); } - public override void _PhysicsProcess(float delta) + public override void _PhysicsProcess(double delta) { - velocity.y += gravity * delta; - velocity = MoveAndSlide(velocity, Vector2.Up); + Velocity += new Vector2(0, gravity * (float)delta); + MoveAndSlide(); if (IsOnFloor()) { GD.Print("yay"); - EmitSignal(nameof(WeCollidedd)); + EmitSignal(nameof(WeCollided)); } } diff --git a/test_scenes/ClickButtonThing.cs b/test_scenes/ClickButtonThing.cs index a236482..6c395bd 100644 --- a/test_scenes/ClickButtonThing.cs +++ b/test_scenes/ClickButtonThing.cs @@ -2,7 +2,7 @@ namespace GodotXUnitTest { - public class ClickButtonThing : CheckBox + public partial class ClickButtonThing : CheckBox { public override void _UnhandledInput(InputEvent inputEvent) { diff --git a/test_scenes/ClickTestScene.tscn b/test_scenes/ClickTestScene.tscn index 3f322c1..6fd1f0f 100644 --- a/test_scenes/ClickTestScene.tscn +++ b/test_scenes/ClickTestScene.tscn @@ -14,9 +14,9 @@ anchor_left = 0.5 anchor_top = 0.5 anchor_right = 0.5 anchor_bottom = 0.5 -margin_left = -58.5 -margin_top = -12.0 -margin_right = 58.5 -margin_bottom = 12.0 +offset_left = -58.5 +offset_top = -12.0 +offset_right = 58.5 +offset_bottom = 12.0 text = "ImAButton" script = ExtResource( 1 ) diff --git a/test_scenes/PhysicsCollisionTest.tscn b/test_scenes/PhysicsCollisionTest.tscn index c6fc2b7..b4e304c 100644 --- a/test_scenes/PhysicsCollisionTest.tscn +++ b/test_scenes/PhysicsCollisionTest.tscn @@ -1,10 +1,10 @@ [gd_scene load_steps=5 format=2] -[ext_resource path="res://icon.png" type="Texture" id=1] +[ext_resource path="res://icon.png" type="Texture2D" id=1] [ext_resource path="res://test_scenes/AVerySpecialBall.cs" type="Script" id=2] [sub_resource type="RectangleShape2D" id=1] -extents = Vector2( 48, 48 ) +size = Vector2( 48, 48 ) [sub_resource type="CircleShape2D" id=2] @@ -16,13 +16,13 @@ __meta__ = { "_edit_group_": true } -[node name="icon" type="Sprite" parent="StaticBody2D"] +[node name="icon" type="Sprite2D" parent="StaticBody2D"] texture = ExtResource( 1 ) [node name="CollisionShape2D" type="CollisionShape2D" parent="StaticBody2D"] shape = SubResource( 1 ) -[node name="AVerySpecialBall" type="KinematicBody2D" parent="."] +[node name="AVerySpecialBall" type="CharacterBody2D" parent="."] position = Vector2( 480, 120 ) script = ExtResource( 2 ) __meta__ = { diff --git a/test_scenes/SomeTestSceneRoot.cs b/test_scenes/SomeTestSceneRoot.cs index 6665dc7..cbb3677 100644 --- a/test_scenes/SomeTestSceneRoot.cs +++ b/test_scenes/SomeTestSceneRoot.cs @@ -2,7 +2,7 @@ namespace GodotXUnitTest { - public class SomeTestSceneRoot : Node2D + public partial class SomeTestSceneRoot : Node2D { } diff --git a/tests/ClickTest.cs b/tests/ClickTest.cs index 374d973..ef22fe3 100644 --- a/tests/ClickTest.cs +++ b/tests/ClickTest.cs @@ -4,23 +4,23 @@ namespace GodotXUnitTest.Tests { - public class ClickTest + public partial class ClickTest { [GodotFact(Scene = "res://test_scenes/ClickTestScene.tscn")] public async void TestButtonGetsClicked() { - var checkBox = (Button) GDU.CurrentScene.FindNode("ImAButton"); + var checkBox = (Button)GDU.CurrentScene.FindChild("ImAButton"); Assert.NotNull(checkBox); - Assert.False(checkBox.Pressed); - + Assert.False(checkBox.ButtonPressed); + await GDU.RequestDrawing(60, drawer => { var position = GDI.PositionByScreenPercent(0.5f, 0.5f); drawer.DrawCircle(position, 30f, Colors.Azure); }); - + await GDI.SimulateMouseClick(0.5f, 0.5f); - Assert.True(checkBox.Pressed); + Assert.True(checkBox.ButtonPressed); } } } \ No newline at end of file diff --git a/tests/DebugDrawingTest.cs b/tests/DebugDrawingTest.cs index cfa168d..2e1f62d 100644 --- a/tests/DebugDrawingTest.cs +++ b/tests/DebugDrawingTest.cs @@ -4,7 +4,7 @@ namespace GodotXUnitTest.Tests { - public class DebugDrawingTest + public partial class DebugDrawingTest { [Fact] public async void TestDrawingStuffs() diff --git a/tests/PhysicsCollisionTest.cs b/tests/PhysicsCollisionTest.cs index 5c23faa..e7cc674 100644 --- a/tests/PhysicsCollisionTest.cs +++ b/tests/PhysicsCollisionTest.cs @@ -5,46 +5,46 @@ namespace GodotXUnitTest.Tests { - public class PhysicsCollisionTest + public partial class PhysicsCollisionTest { [GodotFact(Scene = "res://test_scenes/PhysicsCollisionTest.tscn")] public async void TestOhNoTooSlowOfFall() { GDU.Print("this will fail"); - var ball = (AVerySpecialBall) GDU.CurrentScene.FindNode("AVerySpecialBall"); + var ball = (AVerySpecialBall)GDU.CurrentScene.FindChild("AVerySpecialBall"); Assert.NotNull(ball); await Assert.ThrowsAsync(async () => { // this will throw a TimeoutException - await ball.ToSignalWithTimeout(nameof(AVerySpecialBall.WeCollidedd), 1000); + await ball.ToSignalWithTimeout(nameof(AVerySpecialBall.WeCollided), 1000); }); } - + [GodotFact(Scene = "res://test_scenes/PhysicsCollisionTest.tscn")] public async void TestOhNoTooSlowOfFallButNoException() { - var ball = (AVerySpecialBall) GDU.CurrentScene.FindNode("AVerySpecialBall"); + var ball = (AVerySpecialBall)GDU.CurrentScene.FindChild("AVerySpecialBall"); Assert.NotNull(ball); // it will not throw here, it will just continue - await ball.ToSignalWithTimeout(nameof(AVerySpecialBall.WeCollidedd), 1000, throwOnTimeout: false); - + await ball.ToSignalWithTimeout(nameof(AVerySpecialBall.WeCollided), 1000, throwOnTimeout: false); + // it will get here, but it will fail this equals check - Assert.NotEqual(new Vector2(), ball.velocity); + Assert.NotEqual(new Vector2(), ball.Velocity); } - + // passing test [GodotFact(Scene = "res://test_scenes/PhysicsCollisionTest.tscn")] public async void TestThatItWorksBecauseWeSetGravitySuperHigh() { - var ball = (AVerySpecialBall) GDU.CurrentScene.FindNode("AVerySpecialBall"); + var ball = (AVerySpecialBall)GDU.CurrentScene.FindChild("AVerySpecialBall"); Assert.NotNull(ball); ball.gravity = 1500f; // it will pass here - await ball.ToSignalWithTimeout(nameof(AVerySpecialBall.WeCollidedd), 1000); - Assert.Equal(new Vector2(), ball.velocity); + await ball.ToSignalWithTimeout(nameof(AVerySpecialBall.WeCollided), 1000); + Assert.Equal(new Vector2(), ball.Velocity); } } } \ No newline at end of file diff --git a/tests/SomeBasicTests.cs b/tests/SomeBasicTests.cs index b23095b..a3e392a 100644 --- a/tests/SomeBasicTests.cs +++ b/tests/SomeBasicTests.cs @@ -2,7 +2,7 @@ namespace GodotXUnitTest.Tests { - public class SomeBasicTests + public partial class SomeBasicTests { [Fact] public void SanityCheckThatWeGotHere() diff --git a/tests/SomePrintStatementsTest.cs b/tests/SomePrintStatementsTest.cs index 8a94a1f..2988fc1 100644 --- a/tests/SomePrintStatementsTest.cs +++ b/tests/SomePrintStatementsTest.cs @@ -5,7 +5,7 @@ namespace GodotXUnitTest.Tests { - public class SomePrintStatementsTest + public partial class SomePrintStatementsTest { private readonly ITestOutputHelper output; diff --git a/tests/SomeTestSceneTest.cs b/tests/SomeTestSceneTest.cs index deccb3d..c905092 100644 --- a/tests/SomeTestSceneTest.cs +++ b/tests/SomeTestSceneTest.cs @@ -3,7 +3,7 @@ namespace GodotXUnitTest.Tests { - public class SomeTestSceneTest + public partial class SomeTestSceneTest { [GodotFact(Scene = "res://test_scenes/SomeTestScene.tscn")] public void IsOnCorrectScene() diff --git a/tests/SubProjectForIntegrationTests/RealFailingTests.cs b/tests/SubProjectForIntegrationTests/RealFailingTests.cs index 517dfb8..e5aeb12 100644 --- a/tests/SubProjectForIntegrationTests/RealFailingTests.cs +++ b/tests/SubProjectForIntegrationTests/RealFailingTests.cs @@ -10,7 +10,7 @@ namespace SubProjectForIntegrationTests /// - we can build a CI example of running tests from a subproject /// - we can see an example of what happens when it fails /// - public class RealFailingTests + public partial class RealFailingTests { [GodotFact(Scene = "res://SomeSceneNotFound.tscn")] public void SceneUnableToBeFound() diff --git a/tests/SubProjectForIntegrationTests/SomeIntegrationTest.cs b/tests/SubProjectForIntegrationTests/SomeIntegrationTest.cs index fae9bea..c5bc167 100644 --- a/tests/SubProjectForIntegrationTests/SomeIntegrationTest.cs +++ b/tests/SubProjectForIntegrationTests/SomeIntegrationTest.cs @@ -1,10 +1,10 @@ -using GodotXUnitApi; +using GodotXUnitApi; using GodotXUnitTest; using Xunit; namespace SubProjectForIntegrationTests { - public class SomeIntegrationTest + public partial class SomeIntegrationTest { [GodotFact(Scene = "res://test_scenes/SomeTestScene.tscn")] public void IsOnCorrectScene() diff --git a/tests/SubProjectForIntegrationTests/SubProjectForIntegrationTests.csproj b/tests/SubProjectForIntegrationTests/SubProjectForIntegrationTests.csproj index 50b0286..70a6cd9 100644 --- a/tests/SubProjectForIntegrationTests/SubProjectForIntegrationTests.csproj +++ b/tests/SubProjectForIntegrationTests/SubProjectForIntegrationTests.csproj @@ -1,13 +1,14 @@ - + 1.0.0 rexfleischer - net472 + net6.0 SubProjectForIntegrationTests SubProjectForIntegrationTests + true - 8 + 10 diff --git a/tests/SubProjectForUnitTests/SubProjectForUnitTests.csproj b/tests/SubProjectForUnitTests/SubProjectForUnitTests.csproj index a698c33..8e35f2f 100644 --- a/tests/SubProjectForUnitTests/SubProjectForUnitTests.csproj +++ b/tests/SubProjectForUnitTests/SubProjectForUnitTests.csproj @@ -1,13 +1,14 @@ - + 1.0.0 rexfleischer - net472 + net6.0 SubProjectForUnitTests SubProjectForUnitTests + true - 8 + 10 diff --git a/tests/SubProjectForUnitTests/UnitTestWithoutGodot.cs b/tests/SubProjectForUnitTests/UnitTestWithoutGodot.cs index eec1923..fc33f0a 100644 --- a/tests/SubProjectForUnitTests/UnitTestWithoutGodot.cs +++ b/tests/SubProjectForUnitTests/UnitTestWithoutGodot.cs @@ -1,10 +1,10 @@ -using System.Configuration; +using System.Configuration; using GodotXUnitApi; using Xunit; namespace SubProjectForUnitTests { - public class SomeClassTest + public partial class SomeClassTest { [GodotFact] public void SomePrettyCoolTest() diff --git a/tests/TestInGodotCycle.cs b/tests/TestInGodotCycle.cs index 198650a..91f5139 100644 --- a/tests/TestInGodotCycle.cs +++ b/tests/TestInGodotCycle.cs @@ -4,7 +4,7 @@ namespace GodotXUnitTest.Tests { - public class TestInGodotCycle + public partial class TestInGodotCycle { /// /// this test will run in the xunit thread. diff --git a/tests/TestInGodotTree.cs b/tests/TestInGodotTree.cs index 4c14d62..cfbb386 100644 --- a/tests/TestInGodotTree.cs +++ b/tests/TestInGodotTree.cs @@ -6,7 +6,7 @@ namespace GodotXUnitTest.Tests { // tests that extend Godot.Node will automatically // be added as a child to the runner. - public class TestInGodotTree : Node + public partial class TestInGodotTree : Node { [GodotFact] public void IsInTree() diff --git a/tests/TestWithSetup.cs b/tests/TestWithSetup.cs index 38aa827..ffae56a 100644 --- a/tests/TestWithSetup.cs +++ b/tests/TestWithSetup.cs @@ -3,7 +3,7 @@ namespace GodotXUnitTest.Tests { - public class TestWithSetup + public partial class TestWithSetup { public int sayWhat = 0;