From 460b9bd7bdf23984963cbc8b1ae77e546b3d947d Mon Sep 17 00:00:00 2001 From: Mike Schulze Date: Sun, 29 Aug 2021 17:52:56 +0200 Subject: [PATCH] GD-129: Spike to enable CSharp test support --- .github/workflows/selftest-3.3.x-mono.yml | 39 ++++ .github/workflows/selftest-3.3.x.yml | 9 +- addons/gdUnit3/bin/GdUnitCmdTool.gd | 13 +- addons/gdUnit3/plugin.gd | 2 +- addons/gdUnit3/src/GdUnitStringAssert.gd | 2 +- addons/gdUnit3/src/GdUnitTestSuite.cs | 151 ++++++++++++ addons/gdUnit3/src/IGdUnitAssert.cs | 48 ++++ addons/gdUnit3/src/IGdUnitBoolAssert.cs | 18 ++ addons/gdUnit3/src/IGdUnitFloatAssert.cs | 9 + addons/gdUnit3/src/IGdUnitIntAssert.cs | 9 + addons/gdUnit3/src/IGdUnitNumberAssert.cs | 62 +++++ addons/gdUnit3/src/IGdUnitObjectAssert.cs | 23 ++ addons/gdUnit3/src/IGdUnitStringAssert.cs | 55 +++++ .../gdUnit3/src/asserts/GdUnitAssertBase.cs | 66 ++++++ .../gdUnit3/src/asserts/GdUnitAssertImpl.gd | 1 + .../src/asserts/GdUnitBoolAssertImpl.cs | 29 +++ .../src/asserts/GdUnitFloatAssertImpl.cs | 14 ++ .../src/asserts/GdUnitIntAssertImpl.cs | 15 ++ .../src/asserts/GdUnitNumberAssertImpl.cs | 101 ++++++++ .../src/asserts/GdUnitObjectAssertImpl.cs | 81 +++++++ .../src/asserts/GdUnitStringAssertImpl.cs | 82 +++++++ addons/gdUnit3/src/core/CsTools.cs | 41 ++++ addons/gdUnit3/src/core/GdObjects.gd | 32 ++- addons/gdUnit3/src/core/GdUnitExecutor.gd | 46 ++-- addons/gdUnit3/src/core/GdUnitRunner.gd | 18 +- addons/gdUnit3/src/core/GdUnitScriptType.gd | 21 ++ addons/gdUnit3/src/core/GdUnitSingleton.gd | 5 +- .../src/core/GdUnitTestSuiteDelegator.gd | 126 ++++++++++ addons/gdUnit3/src/core/GdUnitTools.gd | 6 + addons/gdUnit3/src/core/_TestSuiteScanner.gd | 91 +++++--- .../src/network/rpc/RPCGdUnitTestSuite.gd | 2 +- .../src/network/rpc/dtos/GdUnitResourceDto.gd | 2 +- .../src/network/rpc/dtos/GdUnitTestCaseDto.gd | 2 +- .../network/rpc/dtos/GdUnitTestSuiteDto.gd | 2 +- addons/gdUnit3/src/report/GdUnitHtmlReport.gd | 12 +- .../gdUnit3/src/report/GdUnitReportSummary.gd | 3 + .../src/report/GdUnitTestCaseReport.gd | 3 +- addons/gdUnit3/src/ui/GdUnitInspector.gd | 2 +- addons/gdUnit3/test/GdUnitScriptTypeTest.gd | 16 ++ .../gdUnit3/test/GdUnitTestResourceLoader.gd | 55 ++++- .../test/asserts/GdUnitBoolAssertImplTest.cs | 77 ++++++ .../test/asserts/GdUnitIntAssertImplTest.cs | 19 ++ .../asserts/GdUnitObjectAssertImplTest.cs | 142 +++++++++++ .../asserts/GdUnitObjectAssertImplTest.gd | 10 +- .../asserts/GdUnitStringAssertImplTest.cs | 221 ++++++++++++++++++ addons/gdUnit3/test/core/ExampleTest.cs | 42 ++++ addons/gdUnit3/test/core/GdObjectsTest.gd | 11 + .../gdUnit3/test/core/GdUnitExecutorTest.gd | 28 +-- .../test/core/_TestSuiteScannerTest.gd | 23 +- .../testsuites/mono/NotATestSuite.cs | 21 ++ .../ui/parts/InspectorTreeMainPanelTest.gd | 2 +- gdUnit3.csproj | 6 + gdUnit3.sln | 19 ++ project.godot | 84 +++++++ runtest.cmd | 9 + 55 files changed, 1892 insertions(+), 136 deletions(-) create mode 100644 .github/workflows/selftest-3.3.x-mono.yml create mode 100644 addons/gdUnit3/src/GdUnitTestSuite.cs create mode 100644 addons/gdUnit3/src/IGdUnitAssert.cs create mode 100644 addons/gdUnit3/src/IGdUnitBoolAssert.cs create mode 100644 addons/gdUnit3/src/IGdUnitFloatAssert.cs create mode 100644 addons/gdUnit3/src/IGdUnitIntAssert.cs create mode 100644 addons/gdUnit3/src/IGdUnitNumberAssert.cs create mode 100644 addons/gdUnit3/src/IGdUnitObjectAssert.cs create mode 100644 addons/gdUnit3/src/IGdUnitStringAssert.cs create mode 100644 addons/gdUnit3/src/asserts/GdUnitAssertBase.cs create mode 100644 addons/gdUnit3/src/asserts/GdUnitBoolAssertImpl.cs create mode 100644 addons/gdUnit3/src/asserts/GdUnitFloatAssertImpl.cs create mode 100644 addons/gdUnit3/src/asserts/GdUnitIntAssertImpl.cs create mode 100644 addons/gdUnit3/src/asserts/GdUnitNumberAssertImpl.cs create mode 100644 addons/gdUnit3/src/asserts/GdUnitObjectAssertImpl.cs create mode 100644 addons/gdUnit3/src/asserts/GdUnitStringAssertImpl.cs create mode 100644 addons/gdUnit3/src/core/CsTools.cs create mode 100644 addons/gdUnit3/src/core/GdUnitScriptType.gd create mode 100644 addons/gdUnit3/src/core/GdUnitTestSuiteDelegator.gd create mode 100644 addons/gdUnit3/test/GdUnitScriptTypeTest.gd create mode 100644 addons/gdUnit3/test/asserts/GdUnitBoolAssertImplTest.cs create mode 100644 addons/gdUnit3/test/asserts/GdUnitIntAssertImplTest.cs create mode 100644 addons/gdUnit3/test/asserts/GdUnitObjectAssertImplTest.cs create mode 100644 addons/gdUnit3/test/asserts/GdUnitStringAssertImplTest.cs create mode 100644 addons/gdUnit3/test/core/ExampleTest.cs create mode 100644 addons/gdUnit3/test/core/resources/testsuites/mono/NotATestSuite.cs create mode 100644 gdUnit3.csproj create mode 100644 gdUnit3.sln diff --git a/.github/workflows/selftest-3.3.x-mono.yml b/.github/workflows/selftest-3.3.x-mono.yml new file mode 100644 index 00000000..6aa79982 --- /dev/null +++ b/.github/workflows/selftest-3.3.x-mono.yml @@ -0,0 +1,39 @@ +name: Run selftest Godot 3.3.x - Mono +on: [push] + +jobs: + testing: + strategy: + matrix: + godot: [mono-3.3.1, mono-3.3.2, mono-3.3.3] + + name: GdUnit3 Selftest on Godot ${{ matrix.godot }} + runs-on: ubuntu-latest + container: + image: barichello/godot-ci:${{ matrix.godot }} + + steps: + - name: Checkout + uses: actions/checkout@v2 + with: + lfs: true + + - name: Compile + run: | + nuget restore + mkdir -p .mono/assemblies/Debug + cp /usr/local/bin/GodotSharp/Api/Release/* .mono/assemblies/Debug + msbuild + + - name: Run Selftes + timeout-minutes: 5 + env: + GODOT_BIN: "/usr/local/bin/godot" + shell: bash + run: ./runtest.sh --selftest + + - name: Collect Test Reports + uses: actions/upload-artifact@v2 + with: + name: Report_${{ matrix.godot }} + path: reports/** diff --git a/.github/workflows/selftest-3.3.x.yml b/.github/workflows/selftest-3.3.x.yml index 950797d4..14e1d858 100644 --- a/.github/workflows/selftest-3.3.x.yml +++ b/.github/workflows/selftest-3.3.x.yml @@ -5,8 +5,8 @@ jobs: testing: strategy: matrix: - godot: [3.3, 3.3.1, 3.3.2, 3.3.3, mono-3.3.3] - + godot: [3.3, 3.3.1, 3.3.2, 3.3.3] + name: GdUnit3 Selftest on Godot ${{ matrix.godot }} runs-on: ubuntu-latest container: @@ -20,15 +20,14 @@ jobs: - name: Setup shell: bash run: echo "GODOT_BIN=/usr/local/bin/godot" >> $GITHUB_ENV - + - name: Run Selftes shell: bash run: ./runtest.sh --selftest - + - name: Collect Test Report if: always() uses: actions/upload-artifact@v2 with: name: Report_${{ matrix.godot }} path: reports/** - diff --git a/addons/gdUnit3/bin/GdUnitCmdTool.gd b/addons/gdUnit3/bin/GdUnitCmdTool.gd index 31a625a9..8496b0d2 100644 --- a/addons/gdUnit3/bin/GdUnitCmdTool.gd +++ b/addons/gdUnit3/bin/GdUnitCmdTool.gd @@ -67,7 +67,7 @@ class CLIRunner extends Node: _state = STOP else: # process next test suite - var test_suite := _test_suites_to_process.pop_front() as GdUnitTestSuite + var test_suite := _test_suites_to_process.pop_front() as GdUnitTestSuiteDelegator var fs = _executor.execute(test_suite) if fs is GDScriptFunctionState: yield(fs, "completed") @@ -201,7 +201,7 @@ class CLIRunner extends Node: for test_suite in test_suites: skip_suite(test_suite, skipped) - func skip_suite(test_suite :GdUnitTestSuite, skipped :Dictionary) -> void: + func skip_suite(test_suite :GdUnitTestSuiteDelegator, skipped :Dictionary) -> void: var skipped_suites := skipped.keys() if skipped_suites.empty(): return @@ -217,7 +217,7 @@ class CLIRunner extends Node: else: # skip tests for test_to_skip in skipped_tests: - var test_case :_TestCase = test_suite.find_node(test_to_skip, true, false) + var test_case :_TestCase = test_suite.get_test_case_by_name(test_to_skip) if test_case: test_case.skip(true) else: @@ -244,13 +244,14 @@ class CLIRunner extends Node: _report.add_testsuite_report(GdUnitTestSuiteReport.new(event.resource_path(), event.suite_name())) GdUnitEvent.TESTSUITE_AFTER: - _report.update_test_suite_report(event.suite_name(), event.skipped_count(), event.orphan_nodes(), event.elapsed_time()) + _report.update_test_suite_report(event.resource_path(), event.skipped_count(), event.orphan_nodes(), event.elapsed_time()) GdUnitEvent.TESTCASE_BEFORE: - _report.add_testcase_report(event.suite_name(), GdUnitTestCaseReport.new(event.test_name())) + _report.add_testcase_report(event.resource_path(), GdUnitTestCaseReport.new(event.resource_path(), event.test_name())) GdUnitEvent.TESTCASE_AFTER: var test_report := GdUnitTestCaseReport.new( + event.resource_path(), event.test_name(), event.is_error(), event.is_failed(), @@ -258,7 +259,7 @@ class CLIRunner extends Node: event.skipped_count(), event.reports(), event.elapsed_time()) - _report.update_testcase_report(event.suite_name(), test_report) + _report.update_testcase_report(event.resource_path(), test_report) print_status(event) func report_exit_code(report :GdUnitHtmlReport) -> int: diff --git a/addons/gdUnit3/plugin.gd b/addons/gdUnit3/plugin.gd index 322884eb..6e5bda4e 100644 --- a/addons/gdUnit3/plugin.gd +++ b/addons/gdUnit3/plugin.gd @@ -13,7 +13,7 @@ func _enter_tree(): # show possible update notification when is enabled if GdUnitSettings.is_update_notification_enabled(): _update_tool = load("res://addons/gdUnit3/src/update/GdUnitUpdate.tscn").instance() - get_parent().add_child(_update_tool) + add_child(_update_tool) # install SignalHandler singleton GdUnitSingleton.add_singleton(SignalHandler.SINGLETON_NAME, "res://addons/gdUnit3/src/core/event/SignalHandler.gd") diff --git a/addons/gdUnit3/src/GdUnitStringAssert.gd b/addons/gdUnit3/src/GdUnitStringAssert.gd index 6c7e01f9..ce5fecc3 100644 --- a/addons/gdUnit3/src/GdUnitStringAssert.gd +++ b/addons/gdUnit3/src/GdUnitStringAssert.gd @@ -51,5 +51,5 @@ func ends_with(expected: String) -> GdUnitStringAssert: return self # Verifies that the current String has the expected length by used comparator. -func has_length(lenght: int, comparator: int = Comparator.EXACTLY) -> GdUnitStringAssert: +func has_length(lenght: int, comparator: int = Comparator.EQUAL) -> GdUnitStringAssert: return self diff --git a/addons/gdUnit3/src/GdUnitTestSuite.cs b/addons/gdUnit3/src/GdUnitTestSuite.cs new file mode 100644 index 00000000..25c684fd --- /dev/null +++ b/addons/gdUnit3/src/GdUnitTestSuite.cs @@ -0,0 +1,151 @@ +using Godot; +using System; + +namespace GdUnit3 +{ + + /** + This class is the main class to implement your unit tests
+ You have to extend and implement your test cases as described
+ e.g
+
+ For detailed instructions see HERE
+ + For example: + + + public class MyExampleTest : GdUnit3.GdUnitTestSuite + { + public void test_testCaseA() + { + assertThat("value").isEqual("value"); + } + } + + +
*/ + public abstract class GdUnitTestSuite : Node + { + + + [AttributeUsage(AttributeTargets.Class)] + public class TestSuiteAttribute : Attribute + { + } + + [AttributeUsage(AttributeTargets.Method)] + public class TestCaseAttribute : Attribute + { + public readonly int Timeout = -1; + public readonly int Line; + + public TestCaseAttribute([System.Runtime.CompilerServices.CallerLineNumber] int line = 0) + { + Line = line; + } + } + + + private bool _scipped = false; + private String _active_test_case; + + private static Godot.Resource GdUnitTools = (Resource)GD.Load("res://addons/gdUnit3/src/core/GdUnitTools.gd").New(); + + + + ~GdUnitTestSuite() + { + } + + /// + /// This function is called before a test suite starts + /// You can overwrite to prepare test data or initalizize necessary variables + /// + public virtual void Before() { } + + // This function is called at least when a test suite is finished + // You can overwrite to cleanup data created during test running + public virtual void After() { } + + // This function is called before a test case starts + // You can overwrite to prepare test case specific data + public virtual void BeforeTest() { } + + // This function is called after the test case is finished + // You can overwrite to cleanup your test case specific data + public virtual void AfterTest() { } + + // Skip the test-suite from execution, it will be ignored + public void skip(bool skipped) => _scipped = skipped; + + public bool is_skipped => _scipped; + + public void set_active_test_case(String test_case) => _active_test_case = test_case; + + // === Tools ==================================================================== + // Mapps Godot error number to a readable error message. See at ERROR + // https://docs.godotengine.org/de/stable/classes/class_@globalscope.html#enum-globalscope-error + public String error_as_string(int error_number) + { + return (String)GdUnitTools.Call("error_as_string", error_number); + } + + // A litle helper to auto freeing your created objects after test execution + public T auto_free(T obj) + { + GdUnitTools.Call("register_auto_free", obj, GetMeta("MEMORY_POOL")); + return obj; + } + + // Discard the error message triggered by a timeout (interruption). + // By default, an interrupted test is reported as an error. + // This function allows you to change the message to Success when an interrupted error is reported. + public void discard_error_interupted_by_timeout() + { + //GdUnitTools.register_expect_interupted_by_timeout(self, __active_test_case) + } + + // Creates a new directory under the temporary directory *user://tmp* + // Useful for storing data during test execution. + // The directory is automatically deleted after test suite execution + public String create_temp_dir(String relative_path) + { + //return GdUnitTools.create_temp_dir(relative_path) + return ""; + } + + // Deletes the temporary base directory + // Is called automatically after each execution of the test suite + public void clean_temp_dir() + { + //GdUnitTools.clear_tmp() + } + + // === Asserts ================================================================== + public IGdUnitBoolAssert AssertBool(bool current, IGdUnitAssert.EXPECT expectResult = IGdUnitAssert.EXPECT.SUCCESS) + { + return new GdUnitBoolAssertWrapper(this, current, expectResult); + } + + public IGdUnitStringAssert AssertString(string current, IGdUnitAssert.EXPECT expectResult = IGdUnitAssert.EXPECT.SUCCESS) + { + return new GdUnitStringAssertWrapper(this, current, expectResult); + } + + public IGdUnitIntAssert AssertInt(int current, IGdUnitAssert.EXPECT expectResult = IGdUnitAssert.EXPECT.SUCCESS) + { + return new GdUnitIntAssertWrapper(this, current, expectResult); + } + + public IGdUnitFloatAssert AssertFloat(double current, IGdUnitAssert.EXPECT expectResult = IGdUnitAssert.EXPECT.SUCCESS) + { + return new GdUnitFloatAssertWrapper(this, current, expectResult); + } + + public IGdUnitObjectAssert AssertObject(object current, IGdUnitAssert.EXPECT expectResult = IGdUnitAssert.EXPECT.SUCCESS) + { + return new GdUnitObjectAssertWrapper(this, current, expectResult); + } + } + +} diff --git a/addons/gdUnit3/src/IGdUnitAssert.cs b/addons/gdUnit3/src/IGdUnitAssert.cs new file mode 100644 index 00000000..8fec5457 --- /dev/null +++ b/addons/gdUnit3/src/IGdUnitAssert.cs @@ -0,0 +1,48 @@ +using System; +using System.ComponentModel; + +namespace GdUnit3 +{ + + /// Main interface of all GdUnit asserts + public interface IGdUnitAssert + { + + enum EXPECT : int + { + [Description("assert expects ends with success")] + SUCCESS = 0, + [Description("assert expects ends with errors")] + FAIL = 1 + } + } + + /// Base interface of all GdUnit asserts + public interface IGdUnitAssertBase : IGdUnitAssert + { + + /// Verifies that the current value is null. + IGdUnitAssertBase IsNull(); + + /// Verifies that the current value is not null. + IGdUnitAssertBase IsNotNull(); + + /// Verifies that the current value is equal to expected one. + IGdUnitAssertBase IsEqual(V expected); + + /// Verifies that the current value is not equal to expected one. + IGdUnitAssertBase IsNotEqual(V expected); + + /// + IGdUnitAssertBase TestFail(); + + /// Verifies the failure message is equal to expected one. + IGdUnitAssertBase HasFailureMessage(string expected); + + /// Verifies that the failure starts with the given value. + IGdUnitAssertBase StartsWithFailureMessage(string value); + + /// Overrides the default failure message by given custom message. + IGdUnitAssertBase OverrideFailureMessage(string message); + } +} diff --git a/addons/gdUnit3/src/IGdUnitBoolAssert.cs b/addons/gdUnit3/src/IGdUnitBoolAssert.cs new file mode 100644 index 00000000..5e016da8 --- /dev/null +++ b/addons/gdUnit3/src/IGdUnitBoolAssert.cs @@ -0,0 +1,18 @@ +using Godot; +using System; + +namespace GdUnit3 +{ + + /// An Assertion Tool to verify boolean values + public interface IGdUnitBoolAssert : IGdUnitAssertBase + { + + /// Verifies that the current value is true. + IGdUnitBoolAssert IsTrue(); + + /// Verifies that the current value is false. + IGdUnitBoolAssert IsFalse(); + + } +} \ No newline at end of file diff --git a/addons/gdUnit3/src/IGdUnitFloatAssert.cs b/addons/gdUnit3/src/IGdUnitFloatAssert.cs new file mode 100644 index 00000000..a170f3fa --- /dev/null +++ b/addons/gdUnit3/src/IGdUnitFloatAssert.cs @@ -0,0 +1,9 @@ +namespace GdUnit3 +{ + + /// Base interface for integer assertions. + public interface IGdUnitFloatAssert : IGdUnitNumberAssert + { + + } +} diff --git a/addons/gdUnit3/src/IGdUnitIntAssert.cs b/addons/gdUnit3/src/IGdUnitIntAssert.cs new file mode 100644 index 00000000..d28e5706 --- /dev/null +++ b/addons/gdUnit3/src/IGdUnitIntAssert.cs @@ -0,0 +1,9 @@ +namespace GdUnit3 +{ + + /// Base interface for integer assertions. + public interface IGdUnitIntAssert : IGdUnitNumberAssert + { + + } +} diff --git a/addons/gdUnit3/src/IGdUnitNumberAssert.cs b/addons/gdUnit3/src/IGdUnitNumberAssert.cs new file mode 100644 index 00000000..9a15983a --- /dev/null +++ b/addons/gdUnit3/src/IGdUnitNumberAssert.cs @@ -0,0 +1,62 @@ +using System; + +namespace GdUnit3 +{ + + /// Base interface for number assertions. + public interface IGdUnitNumberAssert : IGdUnitAssertBase + { + + /// Verifies that the current value is less than the given one. + public IGdUnitNumberAssert IsLess(V expected); + + + /// Verifies that the current value is less than or equal the given one. + public IGdUnitNumberAssert IsLessEqual(V expected); + + + /// Verifies that the current value is greater than the given one. + public IGdUnitNumberAssert IsGreater(V expected); + + + /// Verifies that the current value is greater than or equal the given one. + public IGdUnitNumberAssert IsGreaterEqual(V expected); + + + /// Verifies that the current value is even. + public IGdUnitNumberAssert IsEven(); + + + /// Verifies that the current value is odd. + public IGdUnitNumberAssert IsOdd(); + + + /// Verifies that the current value is negative. + public IGdUnitNumberAssert IsNegative(); + + + /// Verifies that the current value is not negative. + public IGdUnitNumberAssert IsNotNegative(); + + + /// Verifies that the current value is equal to zero. + public IGdUnitNumberAssert IsZero(); + + + /// Verifies that the current value is not equal to zero. + public IGdUnitNumberAssert IsNotZero(); + + + /// Verifies that the current value is in the given set of values. + public IGdUnitNumberAssert IsIn(Array expected); + + + /// Verifies that the current value is not in the given set of values. + public IGdUnitNumberAssert IsNotIn(Array expected); + + + /// Verifies that the current value is between the given boundaries (inclusive). + public IGdUnitNumberAssert IsBetween(V from, V to); + + } +} diff --git a/addons/gdUnit3/src/IGdUnitObjectAssert.cs b/addons/gdUnit3/src/IGdUnitObjectAssert.cs new file mode 100644 index 00000000..06b32f04 --- /dev/null +++ b/addons/gdUnit3/src/IGdUnitObjectAssert.cs @@ -0,0 +1,23 @@ +using Godot; +using System; + +namespace GdUnit3 +{ + + /// An Assertion Tool to verify object values + public interface IGdUnitObjectAssert : IGdUnitAssertBase + { + // Verifies that the current value is the same as the given one. + public IGdUnitObjectAssert IsSame(object expected); + + // Verifies that the current value is not the same as the given one. + public IGdUnitObjectAssert IsNotSame(object expected); + + // Verifies that the current value is an instance of the given type. + public IGdUnitObjectAssert IsInstanceof(); + + // Verifies that the current value is not an instance of the given type. + public IGdUnitObjectAssert IsNotInstanceof(); + + } +} diff --git a/addons/gdUnit3/src/IGdUnitStringAssert.cs b/addons/gdUnit3/src/IGdUnitStringAssert.cs new file mode 100644 index 00000000..244884d0 --- /dev/null +++ b/addons/gdUnit3/src/IGdUnitStringAssert.cs @@ -0,0 +1,55 @@ +using Godot; +using System; + +namespace GdUnit3 +{ + + /// An Assertion Tool to verify string values + public interface IGdUnitStringAssert : IGdUnitAssertBase + { + enum Compare + { + EQUAL, + LESS_THAN, + LESS_EQUAL, + GREATER_THAN, + GREATER_EQUAL, + BETWEEN_EQUAL, + NOT_BETWEEN_EQUAL, + } + + /// Verifies that the current String is equal to the given one, ignoring case considerations. + public IGdUnitStringAssert IsEqualIgnoringCase(string expected); + + /// Verifies that the current String is not equal to the given one, ignoring case considerations. + public IGdUnitStringAssert IsNotEqualIgnoringCase(string expected); + + /// Verifies that the current String is empty, it has a length of 0. + public IGdUnitStringAssert IsEmpty(); + + /// Verifies that the current String is not empty, it has a length of minimum 1. + public IGdUnitStringAssert IsNotEmpty(); + + /// Verifies that the current String contains the given String. + public IGdUnitStringAssert Contains(string expected); + + /// Verifies that the current String does not contain the given String. + public IGdUnitStringAssert NotContains(string expected); + + /// Verifies that the current String does not contain the given String, ignoring case considerations. + public IGdUnitStringAssert ContainsIgnoringCase(string expected); + + /// Verifies that the current String does not contain the given String, ignoring case considerations. + public IGdUnitStringAssert NotContainsIgnoringCase(string expected); + + /// Verifies that the current String starts with the given prefix. + public IGdUnitStringAssert StartsWith(string expected); + + /// Verifies that the current String ends with the given suffix. + public IGdUnitStringAssert EndsWith(string expected); + + /// Verifies that the current String has the expected length by used comparator. + public IGdUnitStringAssert HasLength(int lenght, Compare comparator = Compare.EQUAL); + + } +} diff --git a/addons/gdUnit3/src/asserts/GdUnitAssertBase.cs b/addons/gdUnit3/src/asserts/GdUnitAssertBase.cs new file mode 100644 index 00000000..fd51a3e0 --- /dev/null +++ b/addons/gdUnit3/src/asserts/GdUnitAssertBase.cs @@ -0,0 +1,66 @@ +using Godot; +using System; + +namespace GdUnit3 +{ + public abstract class GdUnitAssertBase : IGdUnitAssertBase + { + + protected readonly Godot.Reference _delegator; + protected readonly object _current; + + protected GdUnitAssertBase(Godot.Reference delegator, object current = null) + { + _delegator = delegator; + _current = current; + } + + public IGdUnitAssertBase HasFailureMessage(string expected) + { + _delegator.Call("has_failure_message", expected); + return this; + } + + public IGdUnitAssertBase IsEqual(V expected) + { + _delegator.Call("is_equal", expected); + return this; + } + + public IGdUnitAssertBase IsNotEqual(V expected) + { + _delegator.Call("is_not_equal", expected); + return this; + } + + public IGdUnitAssertBase IsNotNull() + { + _delegator.Call("is_not_null"); + return this; + } + + public IGdUnitAssertBase IsNull() + { + _delegator.Call("is_null"); + return this; + } + + public IGdUnitAssertBase OverrideFailureMessage(string message) + { + _delegator.Call("override_failure_message", message); + return this; + } + + public IGdUnitAssertBase StartsWithFailureMessage(string value) + { + _delegator.Call("starts_with_failure_message"); + return this; + } + + public IGdUnitAssertBase TestFail() + { + _delegator.Call("test_fail"); + return this; + } + } +} diff --git a/addons/gdUnit3/src/asserts/GdUnitAssertImpl.gd b/addons/gdUnit3/src/asserts/GdUnitAssertImpl.gd index ea2c9544..868705f2 100644 --- a/addons/gdUnit3/src/asserts/GdUnitAssertImpl.gd +++ b/addons/gdUnit3/src/asserts/GdUnitAssertImpl.gd @@ -37,6 +37,7 @@ static func _get_line_number() -> int: func _init(caller :Object, current, expect_result :int = EXPECT_SUCCESS): assert(caller != null, "missing argument caller!") assert(caller.has_meta(GdUnitReportConsumer.META_PARAM), "caller must register a report consumer!") + _report_consumer = weakref(caller.get_meta(GdUnitReportConsumer.META_PARAM)) _current = current # we expect the test will fail diff --git a/addons/gdUnit3/src/asserts/GdUnitBoolAssertImpl.cs b/addons/gdUnit3/src/asserts/GdUnitBoolAssertImpl.cs new file mode 100644 index 00000000..bbd5e092 --- /dev/null +++ b/addons/gdUnit3/src/asserts/GdUnitBoolAssertImpl.cs @@ -0,0 +1,29 @@ +using Godot; +using System; + +namespace GdUnit3 +{ + public sealed class GdUnitBoolAssertWrapper : GdUnitAssertBase, IGdUnitBoolAssert + { + private static Godot.GDScript GdUnitBoolAssertImpl = GD.Load("res://addons/gdUnit3/src/asserts/GdUnitBoolAssertImpl.gd"); + + public GdUnitBoolAssertWrapper(object caller, object current, IGdUnitAssert.EXPECT expectResult) + : base((Godot.Reference)GdUnitBoolAssertImpl.New(caller, current, expectResult)) + { + + } + + public IGdUnitBoolAssert IsFalse() + { + _delegator.Call("is_false"); + return this; + } + + public IGdUnitBoolAssert IsTrue() + { + _delegator.Call("is_true"); + return this; + } + + } +} diff --git a/addons/gdUnit3/src/asserts/GdUnitFloatAssertImpl.cs b/addons/gdUnit3/src/asserts/GdUnitFloatAssertImpl.cs new file mode 100644 index 00000000..ea8afa7a --- /dev/null +++ b/addons/gdUnit3/src/asserts/GdUnitFloatAssertImpl.cs @@ -0,0 +1,14 @@ +using Godot; +using System; + +namespace GdUnit3 +{ + public sealed class GdUnitFloatAssertWrapper : GdUnitNumberAssertWrapper, IGdUnitFloatAssert + { + private static Godot.GDScript AssertImpl = GD.Load("res://addons/gdUnit3/src/asserts/GdUnitFloatAssertImpl.gd"); + public GdUnitFloatAssertWrapper(object caller, object current, IGdUnitAssert.EXPECT expectResult) + : base((Godot.Reference)AssertImpl.New(caller, current, expectResult), current) + { + } + } +} diff --git a/addons/gdUnit3/src/asserts/GdUnitIntAssertImpl.cs b/addons/gdUnit3/src/asserts/GdUnitIntAssertImpl.cs new file mode 100644 index 00000000..0b9c1650 --- /dev/null +++ b/addons/gdUnit3/src/asserts/GdUnitIntAssertImpl.cs @@ -0,0 +1,15 @@ +using Godot; +using System; + +namespace GdUnit3 +{ + public sealed class GdUnitIntAssertWrapper : GdUnitNumberAssertWrapper, IGdUnitIntAssert + { + private static Godot.GDScript AssertImpl = GD.Load("res://addons/gdUnit3/src/asserts/GdUnitIntAssertImpl.gd"); + + public GdUnitIntAssertWrapper(object caller, object current, IGdUnitAssert.EXPECT expectResult) + : base((Godot.Reference)AssertImpl.New(caller, current, expectResult), current) + { + } + } +} diff --git a/addons/gdUnit3/src/asserts/GdUnitNumberAssertImpl.cs b/addons/gdUnit3/src/asserts/GdUnitNumberAssertImpl.cs new file mode 100644 index 00000000..791d3c11 --- /dev/null +++ b/addons/gdUnit3/src/asserts/GdUnitNumberAssertImpl.cs @@ -0,0 +1,101 @@ +using Godot; +using System; + +namespace GdUnit3 +{ + public class GdUnitNumberAssertWrapper : GdUnitAssertBase, IGdUnitNumberAssert + { + public GdUnitNumberAssertWrapper(Godot.Reference delegator, object current) + : base(delegator, current) + { + } + + public IGdUnitNumberAssert IsBetween(V from, V to) + { + _delegator.Call("is_between", from, to); + return this; + } + + public IGdUnitNumberAssert IsEven() + { + _delegator.Call("is_even"); + return this; + } + + public IGdUnitNumberAssert IsGreater(V expected) + { + _delegator.Call("is_greater"); + return this; + } + + public IGdUnitNumberAssert IsGreaterEqual(V expected) + { + _delegator.Call("is_greater_equal"); + return this; + + } + + public IGdUnitNumberAssert IsIn(Array expected) + { + _delegator.Call("is_in", expected); + return this; + + } + + public IGdUnitNumberAssert IsLess(V expected) + { + _delegator.Call("is_less", expected); + return this; + + } + + public IGdUnitNumberAssert IsLessEqual(V expected) + { + _delegator.Call("is_less_equal", expected); + return this; + + } + + public IGdUnitNumberAssert IsNegative() + { + _delegator.Call("is_negative"); + return this; + + } + + public IGdUnitNumberAssert IsNotIn(Array expected) + { + _delegator.Call("is_not_in", expected); + return this; + + } + + public IGdUnitNumberAssert IsNotNegative() + { + _delegator.Call("is_not_negative"); + return this; + + } + + public IGdUnitNumberAssert IsNotZero() + { + _delegator.Call("is_not_zero"); + return this; + + } + + public IGdUnitNumberAssert IsOdd() + { + _delegator.Call("is_odd"); + return this; + + } + + public IGdUnitNumberAssert IsZero() + { + _delegator.Call("is_zero"); + return this; + + } + } +} diff --git a/addons/gdUnit3/src/asserts/GdUnitObjectAssertImpl.cs b/addons/gdUnit3/src/asserts/GdUnitObjectAssertImpl.cs new file mode 100644 index 00000000..b2184be9 --- /dev/null +++ b/addons/gdUnit3/src/asserts/GdUnitObjectAssertImpl.cs @@ -0,0 +1,81 @@ +using Godot; +using System; + +namespace GdUnit3 +{ + public sealed class GdUnitObjectAssertWrapper : GdUnitAssertBase, IGdUnitObjectAssert + { + private static Godot.GDScript AssertImpl = GD.Load("res://addons/gdUnit3/src/asserts/GdUnitObjectAssertImpl.gd"); + + private static Godot.GDScript GdAssertMessages = GD.Load("res://addons/gdUnit3/src/asserts/GdAssertMessages.gd"); + + private readonly Godot.Reference _messageBuilder; + + public GdUnitObjectAssertWrapper(object caller, object current, IGdUnitAssert.EXPECT expectResult) + : base((Godot.Reference)AssertImpl.New(caller, current, expectResult), current) + { + _messageBuilder = GdAssertMessages.New() as Godot.Reference; + } + + + public IGdUnitObjectAssert IsNotInstanceof() + { + if (_current is ExpectedType) + { + var message = String.Format("Expected not be a instance of <{0}>", typeof(ExpectedType)); + _delegator.Call("report_error", message); + return this; + } + _delegator.Call("report_success"); + return this; + } + + public IGdUnitObjectAssert IsNotSame(object expected) + { + _delegator.Call("is_not_same", expected); + return this; + } + + public IGdUnitObjectAssert IsSame(object expected) + { + _delegator.Call("is_same", expected); + return this; + } + + + public IGdUnitObjectAssert IsInstanceof() + { + if (!(_current is ExpectedType)) + { + var message = error_is_instanceof(_current != null ? _current.GetType() : null, typeof(ExpectedType)); + _delegator.Call("report_error", message); + return this; + } + _delegator.Call("report_success"); + return this; + } + + private String format_expected(string value) + { + return _messageBuilder.Call("_expected", value) as string; + } + + private String format_current(string value) + { + return _messageBuilder.Call("_current", value) as string; + } + + private String format_error(string value) + { + return _messageBuilder.Call("_error", value) as string; + } + + private string error_is_instanceof(Type current, Type expected) + { + return String.Format("{0}\n {1}\n But it was {2}", + format_error("Expected instance of:"), + format_expected(expected.ToString()), + format_current(current != null ? current.ToString() : "Null")); + } + } +} diff --git a/addons/gdUnit3/src/asserts/GdUnitStringAssertImpl.cs b/addons/gdUnit3/src/asserts/GdUnitStringAssertImpl.cs new file mode 100644 index 00000000..7f02ac59 --- /dev/null +++ b/addons/gdUnit3/src/asserts/GdUnitStringAssertImpl.cs @@ -0,0 +1,82 @@ +using Godot; +using System; + +namespace GdUnit3 +{ + public sealed class GdUnitStringAssertWrapper : GdUnitAssertBase, IGdUnitStringAssert + { + private static Godot.GDScript AssertImpl = GD.Load("res://addons/gdUnit3/src/asserts/GdUnitStringAssertImpl.gd"); + + public GdUnitStringAssertWrapper(object caller, object current, IGdUnitAssert.EXPECT expectResult) + : base((Godot.Reference)AssertImpl.New(caller, current, expectResult)) + { + } + + public IGdUnitStringAssert Contains(string expected) + { + _delegator.Call("contains", expected); + return this; + } + + public IGdUnitStringAssert ContainsIgnoringCase(string expected) + { + _delegator.Call("contains_ignoring_case", expected); + return this; + } + + public IGdUnitStringAssert EndsWith(string expected) + { + _delegator.Call("ends_with", expected); + return this; + } + + public IGdUnitStringAssert HasLength(int lenght, IGdUnitStringAssert.Compare comparator = IGdUnitStringAssert.Compare.EQUAL) + { + _delegator.Call("has_length", lenght, comparator); + return this; + } + + public IGdUnitStringAssert IsEmpty() + { + _delegator.Call("is_empty"); + return this; + } + + public IGdUnitStringAssert IsEqualIgnoringCase(string expected) + { + _delegator.Call("is_equal_ignoring_case", expected); + return this; + } + + public IGdUnitStringAssert IsNotEmpty() + { + _delegator.Call("is_not_empty"); + return this; + } + + public IGdUnitStringAssert IsNotEqualIgnoringCase(string expected) + { + _delegator.Call("is_not_equal_ignoring_case", expected); + return this; + } + + public IGdUnitStringAssert NotContains(string expected) + { + _delegator.Call("not_contains", expected); + return this; + } + + public IGdUnitStringAssert NotContainsIgnoringCase(string expected) + { + _delegator.Call("not_contains_ignoring_case", expected); + return this; + } + + public IGdUnitStringAssert StartsWith(string expected) + { + _delegator.Call("starts_with", expected); + return this; + } + + } +} diff --git a/addons/gdUnit3/src/core/CsTools.cs b/addons/gdUnit3/src/core/CsTools.cs new file mode 100644 index 00000000..64552a32 --- /dev/null +++ b/addons/gdUnit3/src/core/CsTools.cs @@ -0,0 +1,41 @@ +using Godot; +using Godot.Collections; +using Array = Godot.Collections.Array; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; + +namespace GdUnit3 +{ + public class CsTools : Reference + { + public Array GetTestCases(String className) + { + System.Type type = System.Type.GetType(className); + Array methods = new Array(); + List methodInfos = new List(type.GetMethods().Where(m => m.IsDefined(typeof(GdUnitTestSuite.TestCaseAttribute))).ToList()); + foreach (var methodInfo in methodInfos) + { + var attributes = methodInfo.GetCustomAttribute(); + + methods.Add(new Dictionary { + { "name", methodInfo.Name }, + { "line_number", attributes.Line } + }); + } + return methods; + } + + public bool IsTestSuite(String className) + { + System.Type type = System.Type.GetType(className); + if (type == null) + { + return false; + } + return Attribute.GetCustomAttribute(type, typeof(GdUnitTestSuite.TestSuiteAttribute)) != null; + } + + } +} \ No newline at end of file diff --git a/addons/gdUnit3/src/core/GdObjects.gd b/addons/gdUnit3/src/core/GdObjects.gd index d5051def..d2f101a9 100644 --- a/addons/gdUnit3/src/core/GdObjects.gd +++ b/addons/gdUnit3/src/core/GdObjects.gd @@ -300,9 +300,14 @@ static func is_object(value) -> bool: static func is_script(value) -> bool: return is_object(value) and value is Script -static func is_testsuite(script :GDScript) -> bool: - if not script: - return false +static func is_test_suite(script :Script) -> bool: + if is_gd_script(script): + return _is_extends_test_suite(script) + if is_cs_script(script): + return _is_annotated_test_suite(script) + return false + +static func _is_extends_test_suite(script :Script) -> bool: var stack := [script] while not stack.empty(): var current := stack.pop_front() as Script @@ -313,6 +318,10 @@ static func is_testsuite(script :GDScript) -> bool: stack.push_back(base) return false +static func _is_annotated_test_suite(script :Script) -> bool: + var csTools = GdUnitSingleton.get_or_create_singleton("CsTools", "res://addons/gdUnit3/src/core/CsTools.cs") + return csTools.IsTestSuite(script.resource_path.get_file().replace(".cs", "")) + static func is_native_class(value) -> bool: return is_object(value) and value.to_string() != null and value.to_string().find("GDScriptNativeClass") != -1 @@ -322,6 +331,19 @@ static func is_scene(value) -> bool: static func is_scene_resource_path(value) -> bool: return value is String and value.ends_with(".tscn") +static func is_cs_script(script :Script) -> bool: + # we need to check by stringify name because on non mono Godot the class CSharpScript is not available + return str(script).find("CSharpScript") != -1 + +static func is_vs_script(script :Script) -> bool: + return script is VisualScript + +static func is_gd_script(script :Script) -> bool: + return script is GDScript + +static func is_native_script(script :Script) -> bool: + return script is NativeScript + static func is_instance(value) -> bool: if not is_object(value) or is_native_class(value): return false @@ -515,8 +537,8 @@ static func array_to_string(elements, delimiter := "\n") -> String: if formatted.length() > 0 : formatted += delimiter formatted += str(element) - if formatted.length() > 64: - return formatted + delimiter + "..." + #if formatted.length() > 64: + # return formatted + delimiter + "..." return formatted # Filters an array by given value diff --git a/addons/gdUnit3/src/core/GdUnitExecutor.gd b/addons/gdUnit3/src/core/GdUnitExecutor.gd index 2f695f78..48d76196 100644 --- a/addons/gdUnit3/src/core/GdUnitExecutor.gd +++ b/addons/gdUnit3/src/core/GdUnitExecutor.gd @@ -57,7 +57,7 @@ func fire_event(event :GdUnitEvent) -> void: else: emit_signal("send_event", event) -func suite_before(test_suite :GdUnitTestSuite, total_count :int) -> GDScriptFunctionState: +func suite_before(test_suite :GdUnitTestSuiteDelegator, total_count :int) -> GDScriptFunctionState: set_stage(STAGE_TEST_SUITE_BEFORE) fire_event(GdUnitEvent.new()\ .suite_before(test_suite.get_script().resource_path, test_suite.get_name(), total_count)) @@ -74,7 +74,7 @@ func suite_before(test_suite :GdUnitTestSuite, total_count :int) -> GDScriptFunc GdUnitTools.run_auto_close() return null -func suite_after(test_suite :GdUnitTestSuite) -> GDScriptFunctionState: +func suite_after(test_suite :GdUnitTestSuiteDelegator) -> GDScriptFunctionState: set_stage(STAGE_TEST_SUITE_AFTER) GdUnitTools.clear_tmp() @@ -117,7 +117,7 @@ func suite_after(test_suite :GdUnitTestSuite) -> GDScriptFunctionState: _report_collector.clear_reports(STAGE_TEST_SUITE_BEFORE|STAGE_TEST_SUITE_AFTER) return null -func test_before(test_suite :GdUnitTestSuite, test_case :_TestCase) -> GDScriptFunctionState: +func test_before(test_suite :GdUnitTestSuiteDelegator, test_case :_TestCase) -> GDScriptFunctionState: set_stage(STAGE_TEST_CASE_BEFORE) _memory_pool.set_pool(test_suite, GdUnitMemoryPool.TEST_SETUP, true) @@ -135,7 +135,7 @@ func test_before(test_suite :GdUnitTestSuite, test_case :_TestCase) -> GDScriptF GdUnitTools.run_auto_close() return null -func test_after(test_suite :GdUnitTestSuite, test_case :_TestCase) -> GDScriptFunctionState: +func test_after(test_suite :GdUnitTestSuiteDelegator, test_case :_TestCase) -> GDScriptFunctionState: set_stage(STAGE_TEST_CASE_AFTER) _memory_pool.set_pool(test_suite, GdUnitMemoryPool.TEST_SETUP) @@ -177,7 +177,7 @@ func test_after(test_suite :GdUnitTestSuite, test_case :_TestCase) -> GDScriptFu _report_collector.clear_reports(STAGE_TEST_CASE_BEFORE|STAGE_TEST_CASE_EXECUTE|STAGE_TEST_CASE_AFTER) return null -func execute_test_case(test_suite :GdUnitTestSuite, test_case :_TestCase) -> GDScriptFunctionState: +func execute_test_case(test_suite :GdUnitTestSuiteDelegator, test_case :_TestCase) -> GDScriptFunctionState: _test_run_state = test_before(test_suite, test_case) if GdUnitTools.is_yielded(_test_run_state): yield(_test_run_state, "completed") @@ -235,7 +235,7 @@ func execute_test_case(test_suite :GdUnitTestSuite, test_case :_TestCase) -> GDS _test_run_state = null return _test_run_state -func execute(test_suite :GdUnitTestSuite) -> GDScriptFunctionState: +func execute(test_suite :GdUnitTestSuiteDelegator) -> GDScriptFunctionState: # stop on first error if fail fast enabled if _fail_fast and _total_test_failed > 0: test_suite.free() @@ -243,13 +243,14 @@ func execute(test_suite :GdUnitTestSuite) -> GDScriptFunctionState: _report_collector.register_report_provider(test_suite) add_child(test_suite) - var fs = suite_before(test_suite, test_suite.get_child_count()) + + var fs = suite_before(test_suite, test_suite.get_test_cases_count()) if GdUnitTools.is_yielded(fs): yield(fs, "completed") if not test_suite.is_skipped(): - for test_case_index in test_suite.get_child_count(): - var test_case = test_suite.get_child(test_case_index) + for test_case_index in test_suite.get_test_cases_count(): + var test_case = test_suite.get_test_case(test_case_index) # only iterate over test case, we need to filter because of possible adding other child types on before() or before_test() if not test_case is _TestCase: continue @@ -265,7 +266,7 @@ func execute(test_suite :GdUnitTestSuite) -> GDScriptFunctionState: # it needs to go this hard way to kill the outstanding yields of a test case when the test timed out # we delete the current test suite where is execute the current test case to kill the function state # and replace it by a clone without function state - test_suite = clone_test_suite(test_suite) + test_suite = test_suite.clone() fs = suite_after(test_suite) if GdUnitTools.is_yielded(fs): @@ -274,30 +275,9 @@ func execute(test_suite :GdUnitTestSuite) -> GDScriptFunctionState: test_suite.free() return null -# clones a test suite and moves the test cases to new instance -func clone_test_suite(test_suite :GdUnitTestSuite) -> GdUnitTestSuite: - var _test_suite = test_suite.duplicate() - # copy all property values - for property in test_suite.get_property_list(): - var property_name = property["name"] - _test_suite.set(property_name, test_suite.get(property_name)) - - # remove incomplete duplicated childs - for child in _test_suite.get_children(): - _test_suite.remove_child(child) - child.free() - assert(_test_suite.get_child_count() == 0) - # now move original test cases to duplicated test suite - for child in test_suite.get_children(): - child.get_parent().remove_child(child) - _test_suite.add_child(child) - # finally free current test suite instance - remove_child(test_suite) - test_suite.free() - add_child(_test_suite) - return _test_suite -static func create_fuzzers(test_suite :GdUnitTestSuite, test_case :_TestCase) -> Array: + +static func create_fuzzers(test_suite :GdUnitTestSuiteDelegator, test_case :_TestCase) -> Array: if not test_case.has_fuzzer(): return Array() var fuzzers := Array() diff --git a/addons/gdUnit3/src/core/GdUnitRunner.gd b/addons/gdUnit3/src/core/GdUnitRunner.gd index 7fd93f80..ff84ac19 100644 --- a/addons/gdUnit3/src/core/GdUnitRunner.gd +++ b/addons/gdUnit3/src/core/GdUnitRunner.gd @@ -58,7 +58,7 @@ func _process(delta): _state = STOP else: # process next test suite - var test_suite := _test_suites_to_process.pop_front() as GdUnitTestSuite + var test_suite := _test_suites_to_process.pop_front() as GdUnitTestSuiteDelegator var fs = _executor.execute(test_suite) # is yielded than wait for completed if GdUnitTools.is_yielded(fs): @@ -99,30 +99,26 @@ func gdUnitInit() -> void: send_message("Scaned %d test suites" % _test_suites_to_process.size()) var total_count = _collect_test_case_count(_test_suites_to_process) _on_Executor_send_event(GdUnitInit.new(_test_suites_to_process.size(), total_count)) - for t in _test_suites_to_process: - var test_suite := t as GdUnitTestSuite + for test_suite in _test_suites_to_process: send_test_suite(test_suite) -func _filter_test_case(test_suites :Array, test_case_names :Array) -> void: - if test_case_names.empty(): +func _filter_test_case(test_suites :Array, includes_tests :Array) -> void: + if includes_tests.empty(): return for test_suite in test_suites: - for test_case in test_suite.get_children(): - if not test_case_names.has(test_case.get_name()): - test_suite.remove_child(test_case) - test_case.free() + test_suite.filter_tests(includes_tests) func _collect_test_case_count(testSuites :Array) -> int: var total :int = 0 for test_suite in testSuites: - total += (test_suite as Node).get_child_count() + total += (test_suite as GdUnitTestSuiteDelegator).get_test_cases_count() return total # RPC send functions func send_message(message :String): _client.rpc_send(RPCMessage.of(message)) -func send_test_suite(test_suite :GdUnitTestSuite): +func send_test_suite(test_suite :GdUnitTestSuiteDelegator): _client.rpc_send(RPCGdUnitTestSuite.of(test_suite)) func _on_Executor_send_event(event :GdUnitEvent): diff --git a/addons/gdUnit3/src/core/GdUnitScriptType.gd b/addons/gdUnit3/src/core/GdUnitScriptType.gd new file mode 100644 index 00000000..0a352a0a --- /dev/null +++ b/addons/gdUnit3/src/core/GdUnitScriptType.gd @@ -0,0 +1,21 @@ +class_name GdUnitScriptType +extends Reference + +const UNKNOWN := "" +const CS := "cs" +const GD := "gd" +const NATIVE := "gdns" +const VS := "vs" + +static func type_of(script :Script) -> String: + if script == null: + return UNKNOWN + if GdObjects.is_gd_script(script): + return GD + if GdObjects.is_vs_script(script): + return VS + if GdObjects.is_native_script(script): + return NATIVE + if GdObjects.is_cs_script(script): + return CS + return UNKNOWN diff --git a/addons/gdUnit3/src/core/GdUnitSingleton.gd b/addons/gdUnit3/src/core/GdUnitSingleton.gd index 9e4e7c3a..be8aa146 100644 --- a/addons/gdUnit3/src/core/GdUnitSingleton.gd +++ b/addons/gdUnit3/src/core/GdUnitSingleton.gd @@ -17,9 +17,10 @@ static func get_singleton(name: String) -> Object: static func add_singleton(name: String, path: String) -> Object: var singleton:Object = load(path).new() - singleton.set_name(name) + if singleton.has_method("set_name"): + singleton.set_name(name) _singletons[name] = singleton - #print_debug("Added singleton", name, singleton) + #print_debug("Added singleton ", name, " ",singleton) return singleton static func get_or_create_singleton(name: String, path: String) -> Object: diff --git a/addons/gdUnit3/src/core/GdUnitTestSuiteDelegator.gd b/addons/gdUnit3/src/core/GdUnitTestSuiteDelegator.gd new file mode 100644 index 00000000..300b1fee --- /dev/null +++ b/addons/gdUnit3/src/core/GdUnitTestSuiteDelegator.gd @@ -0,0 +1,126 @@ +class_name GdUnitTestSuiteDelegator +extends Node + +enum { + TYPE_CS, + TYPE_GD +} + + +var _test_suite :Node +var _script_type :String +var _skipped :bool +var _active_test_case :String + +func _init(test_suite :Node): + _test_suite = test_suite + add_child(_test_suite) + _script_type = GdUnitScriptType.type_of(test_suite.get_script()) + +func set_meta(name: String, value) -> void: + _test_suite.set_meta(name, value) + +func get_script() -> Script: + return _test_suite.get_script() + +func get_name() -> String: + return _test_suite.get_name() + +# This function is called before a test suite starts +# You can overwrite to prepare test data or initalizize necessary variables +func before() -> void: + match _script_type: + GdUnitScriptType.GD: + _test_suite.before() + GdUnitScriptType.CS: + _test_suite.Before() + + +# This function is called at least when a test suite is finished +# You can overwrite to cleanup data created during test running +func after() -> void: + match _script_type: + GdUnitScriptType.GD: + _test_suite.after() + GdUnitScriptType.CS: + _test_suite.After() + +# This function is called before a test case starts +# You can overwrite to prepare test case specific data +func before_test() -> void: + match _script_type: + GdUnitScriptType.GD: + _test_suite.before_test() + GdUnitScriptType.CS: + _test_suite.BeforeTest() + +# This function is called after the test case is finished +# You can overwrite to cleanup your test case specific data +func after_test() -> void: + match _script_type: + GdUnitScriptType.GD: + _test_suite.after_test() + GdUnitScriptType.CS: + _test_suite.AfterTest() + +func get_test_cases_count() -> int: + return _test_suite.get_child_count() + +func get_test_case(index :int) -> Node: + return _test_suite.get_child(index) + +func get_test_case_by_name(name :String) -> Node: + return _test_suite.find_node(name, false, false) + +func get_test_cases() -> Array: + return _test_suite.get_children() + +func delete_test_case(name :String) -> void: + var test_case = _test_suite.find_node(name, true, false) + _test_suite.remove_child(test_case) + test_case.free() + +# Skip the test-suite from execution, it will be ignored +func skip(skipped :bool) -> void: + _skipped = skipped + +# filters by given test names +func filter_tests(test_case_names :Array) -> void: + for test_case in get_test_cases(): + if test_case.get_name() in test_case_names: + continue + _test_suite.remove_child(test_case) + test_case.free() + #test_case.skip(true) + +func is_skipped() -> bool: + return _skipped + +func set_active_test_case(test_case :String) -> void: + _active_test_case = test_case + _test_suite.set_active_test_case(test_case) + + +# clones a test suite and moves the test cases to new instance +func clone() -> GdUnitTestSuiteDelegator: + var test_suite = _test_suite.duplicate() + # copy all property values + for property in _test_suite.get_property_list(): + var property_name = property["name"] + test_suite.set(property_name, _test_suite.get(property_name)) + + # remove incomplete duplicated childs + for child in test_suite.get_children(): + test_suite.remove_child(child) + child.free() + assert(test_suite.get_child_count() == 0) + # now move original test cases to duplicated test suite + for child in _test_suite.get_children(): + child.get_parent().remove_child(child) + test_suite.add_child(child) + # finally free current test suite instance + remove_child(_test_suite) + _test_suite.free() + _test_suite = test_suite + add_child(_test_suite) + return self diff --git a/addons/gdUnit3/src/core/GdUnitTools.gd b/addons/gdUnit3/src/core/GdUnitTools.gd index 840054d1..a22ad563 100644 --- a/addons/gdUnit3/src/core/GdUnitTools.gd +++ b/addons/gdUnit3/src/core/GdUnitTools.gd @@ -345,6 +345,10 @@ static func is_auto_free_registered(obj, pool :int) -> bool: static func is_yielded(obj) -> bool: return obj is GDScriptFunctionState and obj.is_valid() +# test is Godot mono running +static func is_mono_supported() -> bool: + return ClassDB.class_exists("CSharpScript") + # runs over all registered files and closes it static func run_auto_close(): while not _files_to_close.empty(): @@ -376,6 +380,8 @@ static func clear_push_errors() -> void: runner.clear_push_errors() static func register_expect_interupted_by_timeout(test_suite :Node, test_case_name :String) -> void: + prints(test_suite.get_children()) + var test_case = test_suite.find_node(test_case_name, false, false) test_case.expect_to_interupt() diff --git a/addons/gdUnit3/src/core/_TestSuiteScanner.gd b/addons/gdUnit3/src/core/_TestSuiteScanner.gd index 3467ff9a..a83d0707 100644 --- a/addons/gdUnit3/src/core/_TestSuiteScanner.gd +++ b/addons/gdUnit3/src/core/_TestSuiteScanner.gd @@ -5,7 +5,6 @@ extends Node var _script_parser := GdScriptParser.new() var _extends_test_suite_classes := Array() - func scan_testsuite_classes() -> void: # scan and cache extends GdUnitTestSuite by class name an resource paths _extends_test_suite_classes.append("GdUnitTestSuite") @@ -17,11 +16,13 @@ func scan_testsuite_classes() -> void: _extends_test_suite_classes.append(script_meta["class"]) func scan(resource_path :String) -> Array: + scan_testsuite_classes() var base_dir := Directory.new() # if single testsuite requested if base_dir.file_exists(resource_path): - if resource_path.ends_with(".gd") and _is_test_suite(resource_path): - return [_parse_test_suite(resource_path)] + var test_suite := _parse_is_test_suite(resource_path) + if test_suite: + return [test_suite] if base_dir.open(resource_path) != OK: prints("Given directory or file does not exists:", resource_path) @@ -33,45 +34,79 @@ func _scan_test_suites(dir :Directory, collected_suites :Array) -> Array: dir.list_dir_begin(true, true) var file_name := dir.get_next() while file_name != "": - var current = dir.get_current_dir() + "/" + file_name + var resource_path = _file(dir, file_name) if dir.current_is_dir(): var sub_dir := Directory.new() - if sub_dir.open(current) == OK: + if sub_dir.open(resource_path) == OK: _scan_test_suites(sub_dir, collected_suites) else: - if _is_test_suite(current): - collected_suites.append(_parse_test_suite(current)) + var test_suite := _parse_is_test_suite(resource_path) + if test_suite: + collected_suites.append(test_suite) file_name = dir.get_next() return collected_suites -func _is_test_suite(file_name :String) -> bool: - # only scan on gd scrip files - if not file_name.ends_with(".gd"): - return false +static func _file(dir :Directory, file_name :String) -> String: + var current_dir := dir.get_current_dir() + if current_dir.ends_with("/"): + return current_dir + file_name + return current_dir + "/" + file_name + +func _parse_is_test_suite(resource_path :String) -> GdUnitTestSuiteDelegator: + if not _is_script_format_supported(resource_path): + return null # exclude non test directories - if file_name.find("/test") == -1: - return false - return GdObjects.is_testsuite(ResourceLoader.load(file_name)) + if resource_path.find("/test") == -1: + return null + var script :Script = ResourceLoader.load(resource_path) + if not GdObjects.is_test_suite(script): + return null + if GdObjects.is_gd_script(script): + return GdUnitTestSuiteDelegator.new(_parse_test_suite(script)) + if GdObjects.is_cs_script(script): + return GdUnitTestSuiteDelegator.new(_parse_cs_test_suite(script)) + return null + +static func _is_script_format_supported(resource_path :String) -> bool: + var ext := resource_path.get_extension() + if ext == "gd": + return true + if ext == "cs" and GdUnitTools.is_mono_supported(): + return true + return false + +func _parse_cs_test_suite(script :Script) -> Node: + var test_suite = script.new() + test_suite.set_name(parse_test_suite_name(script)) + var csTools = GdUnitSingleton.get_or_create_singleton("CsTools", "res://addons/gdUnit3/src/core/CsTools.cs") + var cs_test_cases = csTools.GetTestCases(script.resource_path.get_file().replace(".cs", "")) + for test_case in cs_test_cases: + var meta :Dictionary = test_case + var test := _TestCase.new() + test.configure(meta.get("name"), meta.get("line_number"), script.resource_path) + test_suite.add_child(test) + return test_suite + -func _parse_test_suite(resource_path :String) -> GdUnitTestSuite: - var test_suite := load(resource_path).new() as GdUnitTestSuite - test_suite.set_name(parse_test_suite_name(resource_path)) +func _parse_test_suite(script :GDScript) -> GdUnitTestSuite: + var test_suite = script.new() + test_suite.set_name(parse_test_suite_name(script)) # find all test cases as array of names - var test_case_names := _extract_test_case_names(test_suite) + var test_case_names := _extract_test_case_names(script) # add test cases to test suite and parse test case line nummber - _parse_and_add_test_cases(test_suite, resource_path, test_case_names) + _parse_and_add_test_cases(test_suite, script, test_case_names) # not all test case parsed? # we have to scan the base class to if not test_case_names.empty(): var base_script :GDScript = test_suite.get_script().get_base_script() while base_script is GDScript: - _parse_and_add_test_cases(test_suite, base_script.resource_path, test_case_names) + _parse_and_add_test_cases(test_suite, base_script, test_case_names) base_script = base_script.get_base_script() return test_suite -func _extract_test_case_names(test_suite :GdUnitTestSuite) -> PoolStringArray: +func _extract_test_case_names(script :GDScript) -> PoolStringArray: var names := PoolStringArray() - for method in test_suite.get_script().get_script_method_list(): + for method in script.get_script_method_list(): #prints(method["flags"], method["name"] ) var flags :int = method["flags"] var funcName :String = method["name"] @@ -79,15 +114,13 @@ func _extract_test_case_names(test_suite :GdUnitTestSuite) -> PoolStringArray: names.append(funcName) return names -static func parse_test_suite_name(resource_path :String) -> String: - var start := resource_path.find_last("/") - var end := resource_path.find_last(".gd") - return resource_path.substr(start, end-start) +static func parse_test_suite_name(script :Script) -> String: + return script.resource_path.get_file().replace(".gd", "").replace(".cs", "") -func _parse_and_add_test_cases(test_suite :GdUnitTestSuite, resource_path :String, test_case_names :PoolStringArray): +func _parse_and_add_test_cases(test_suite, script :GDScript, test_case_names :PoolStringArray): var test_cases_to_find = Array(test_case_names) var file := File.new() - file.open(resource_path, File.READ) + file.open(script.resource_path, File.READ) var line_number:int = 0 file.seek(0) @@ -108,7 +141,7 @@ func _parse_and_add_test_cases(test_suite :GdUnitTestSuite, resource_path :Strin var iterations = _script_parser.parse_argument(row, Fuzzer.ARGUMENT_ITERATIONS, Fuzzer.ITERATION_DEFAULT_COUNT) var seed_value = _script_parser.parse_argument(row, Fuzzer.ARGUMENT_SEED, -1) var fuzzers := _script_parser.parse_fuzzers(row) - test_suite.add_child(_TestCase.new().configure(func_name, line_number, resource_path, timeout, fuzzers, iterations, seed_value)) + test_suite.add_child(_TestCase.new().configure(func_name, line_number, script.resource_path, timeout, fuzzers, iterations, seed_value)) file.close() diff --git a/addons/gdUnit3/src/network/rpc/RPCGdUnitTestSuite.gd b/addons/gdUnit3/src/network/rpc/RPCGdUnitTestSuite.gd index 0c517848..79270526 100644 --- a/addons/gdUnit3/src/network/rpc/RPCGdUnitTestSuite.gd +++ b/addons/gdUnit3/src/network/rpc/RPCGdUnitTestSuite.gd @@ -3,7 +3,7 @@ extends RPC var _data :Dictionary -static func of(test_suite :GdUnitTestSuite) -> RPCGdUnitTestSuite: +static func of(test_suite :GdUnitTestSuiteDelegator) -> RPCGdUnitTestSuite: var rpc = load("res://addons/gdUnit3/src/network/rpc/RPCGdUnitTestSuite.gd").new() rpc._data = GdUnitTestSuiteDto.new().serialize(test_suite) return rpc diff --git a/addons/gdUnit3/src/network/rpc/dtos/GdUnitResourceDto.gd b/addons/gdUnit3/src/network/rpc/dtos/GdUnitResourceDto.gd index 54590d96..6fb9164f 100644 --- a/addons/gdUnit3/src/network/rpc/dtos/GdUnitResourceDto.gd +++ b/addons/gdUnit3/src/network/rpc/dtos/GdUnitResourceDto.gd @@ -4,7 +4,7 @@ extends Resource var _name :String var _path :String -func serialize(resource :Object) -> Dictionary: +func serialize(resource) -> Dictionary: var serialized := Dictionary() serialized["name"] = resource.get_name() var script = resource.get_script() diff --git a/addons/gdUnit3/src/network/rpc/dtos/GdUnitTestCaseDto.gd b/addons/gdUnit3/src/network/rpc/dtos/GdUnitTestCaseDto.gd index 3557920c..e11f8c36 100644 --- a/addons/gdUnit3/src/network/rpc/dtos/GdUnitTestCaseDto.gd +++ b/addons/gdUnit3/src/network/rpc/dtos/GdUnitTestCaseDto.gd @@ -3,7 +3,7 @@ extends GdUnitResourceDto var _line_number :int = -1 -func serialize(test_case :Object) -> Dictionary: +func serialize(test_case) -> Dictionary: var serialized := .serialize(test_case) serialized["line_number"] = test_case.line_number() return serialized diff --git a/addons/gdUnit3/src/network/rpc/dtos/GdUnitTestSuiteDto.gd b/addons/gdUnit3/src/network/rpc/dtos/GdUnitTestSuiteDto.gd index ab581fb7..0f912edb 100644 --- a/addons/gdUnit3/src/network/rpc/dtos/GdUnitTestSuiteDto.gd +++ b/addons/gdUnit3/src/network/rpc/dtos/GdUnitTestSuiteDto.gd @@ -7,7 +7,7 @@ func serialize(test_suite :Object) -> Dictionary: var serialized := .serialize(test_suite) var test_cases := Array() serialized["test_cases"] = test_cases - for test_case in test_suite.get_children(): + for test_case in test_suite.get_test_cases(): test_cases.append(GdUnitTestCaseDto.new().serialize(test_case)) return serialized diff --git a/addons/gdUnit3/src/report/GdUnitHtmlReport.gd b/addons/gdUnit3/src/report/GdUnitHtmlReport.gd index ed9cbc3b..60bd4d2b 100644 --- a/addons/gdUnit3/src/report/GdUnitHtmlReport.gd +++ b/addons/gdUnit3/src/report/GdUnitHtmlReport.gd @@ -13,21 +13,21 @@ func _init(path :String): func add_testsuite_report(suite_report :GdUnitTestSuiteReport): _reports.append(suite_report) -func add_testcase_report(suite_name :String, suite_report :GdUnitTestCaseReport) -> void: +func add_testcase_report(resource_path :String, suite_report :GdUnitTestCaseReport) -> void: for report in _reports: - if report.name() == suite_name: + if report.resource_path() == resource_path: report.add_report(suite_report) -func update_test_suite_report(suite_name :String, skipped :int, orphans :int, duration :int) -> void: +func update_test_suite_report(resource_path :String, skipped :int, orphans :int, duration :int) -> void: for report in _reports: - if report.name() == suite_name: + if report.resource_path() == resource_path: report.set_duration(duration) report.set_skipped(skipped) report.set_orphans(orphans) -func update_testcase_report(suite_name :String, test_report :GdUnitTestCaseReport): +func update_testcase_report(resource_path :String, test_report :GdUnitTestCaseReport): for report in _reports: - if report.name() == suite_name: + if report.resource_path() == resource_path: report.update(test_report) func write() -> String: diff --git a/addons/gdUnit3/src/report/GdUnitReportSummary.gd b/addons/gdUnit3/src/report/GdUnitReportSummary.gd index 58a5be48..6b0213d8 100644 --- a/addons/gdUnit3/src/report/GdUnitReportSummary.gd +++ b/addons/gdUnit3/src/report/GdUnitReportSummary.gd @@ -18,6 +18,9 @@ func name() -> String: func path() -> String: return _resource_path.get_base_dir().replace("res://", "") +func resource_path() -> String: + return _resource_path + func suite_count() -> int: return _reports.size() diff --git a/addons/gdUnit3/src/report/GdUnitTestCaseReport.gd b/addons/gdUnit3/src/report/GdUnitTestCaseReport.gd index 0a623b3d..8b0bc97c 100644 --- a/addons/gdUnit3/src/report/GdUnitTestCaseReport.gd +++ b/addons/gdUnit3/src/report/GdUnitTestCaseReport.gd @@ -3,7 +3,8 @@ extends GdUnitReportSummary var _failure_reports :Array -func _init(test_name :String, is_error :bool = false, is_failed :bool = false, orphans :int = 0, skipped :int = 0, failure_reports :Array = [], duration :int = 0): +func _init(resource_path :String, test_name :String, is_error :bool = false, is_failed :bool = false, orphans :int = 0, skipped :int = 0, failure_reports :Array = [], duration :int = 0): + _resource_path = resource_path _name = test_name _test_count = 1 _error_count = is_error diff --git a/addons/gdUnit3/src/ui/GdUnitInspector.gd b/addons/gdUnit3/src/ui/GdUnitInspector.gd index 68da7a0a..d53057a8 100644 --- a/addons/gdUnit3/src/ui/GdUnitInspector.gd +++ b/addons/gdUnit3/src/ui/GdUnitInspector.gd @@ -180,7 +180,7 @@ func extend_script_editor_popup(tab_container :Control) -> void: func _on_script_editor_context_menu_show(context_menu :PopupMenu): var current_script := _editor_interface.get_script_editor().get_current_script() - if GdObjects.is_testsuite(current_script): + if GdObjects.is_test_suite(current_script): context_menu.add_separator() # save menu entry index var current_index := context_menu.get_item_count() diff --git a/addons/gdUnit3/test/GdUnitScriptTypeTest.gd b/addons/gdUnit3/test/GdUnitScriptTypeTest.gd new file mode 100644 index 00000000..625e6ec6 --- /dev/null +++ b/addons/gdUnit3/test/GdUnitScriptTypeTest.gd @@ -0,0 +1,16 @@ +# GdUnit generated TestSuite +#warning-ignore-all:unused_argument +#warning-ignore-all:return_value_discarded +class_name GdUnitScriptTypeTest +extends GdUnitTestSuite + +# TestSuite generated from +const __source = 'res://addons/gdUnit3/src/core/GdUnitScriptType.gd' + +func test_type_of() -> void: + assert_str(GdUnitScriptType.type_of(null)).is_equal(GdUnitScriptType.UNKNOWN) + assert_str(GdUnitScriptType.type_of(ClassDB.instance("GDScript"))).is_equal(GdUnitScriptType.GD) + if GdUnitTools.is_mono_supported(): + assert_str(GdUnitScriptType.type_of(ClassDB.instance("CSharpScript"))).is_equal(GdUnitScriptType.CS) + assert_str(GdUnitScriptType.type_of(ClassDB.instance("VisualScript"))).is_equal(GdUnitScriptType.VS) + assert_str(GdUnitScriptType.type_of(ClassDB.instance("NativeScript"))).is_equal(GdUnitScriptType.NATIVE) diff --git a/addons/gdUnit3/test/GdUnitTestResourceLoader.gd b/addons/gdUnit3/test/GdUnitTestResourceLoader.gd index 9bd809e2..0f3bfc8c 100644 --- a/addons/gdUnit3/test/GdUnitTestResourceLoader.gd +++ b/addons/gdUnit3/test/GdUnitTestResourceLoader.gd @@ -1,18 +1,63 @@ class_name GdUnitTestResourceLoader extends Reference -static func load_test_suite(resource_path :String) -> GdUnitTestSuite: +enum { + GD_SUITE, + CS_SUITE +} + + +static func load_test_suite(resource_path :String, script_type = GD_SUITE) -> GdUnitTestSuiteDelegator: + match script_type: + GD_SUITE: + return load_test_suite_gd(resource_path) + CS_SUITE: + return load_test_suite_cs(resource_path) + assert("type '%s' is not impleented" % script_type) + return null + +static func load_test_suite_gd(resource_path :String) -> GdUnitTestSuiteDelegator: var script := GDScript.new() script.source_code = GdUnitTools.resource_as_string(resource_path) script.resource_path = resource_path script.reload() var test_suite :GdUnitTestSuite = GdUnitTestSuite.new() test_suite.set_script(script) - test_suite.set_name(_TestSuiteScanner.parse_test_suite_name(resource_path.replace(".resource", ".gd"))) + test_suite.set_name(resource_path.get_file().replace(".resource", "")) # complete test suite wiht parsed test cases var suite_parser := _TestSuiteScanner.new() - var test_case_names := suite_parser._extract_test_case_names(test_suite) + var test_case_names := suite_parser._extract_test_case_names(script) # add test cases to test suite and parse test case line nummber - suite_parser._parse_and_add_test_cases(test_suite, resource_path, test_case_names) + suite_parser._parse_and_add_test_cases(test_suite, script, test_case_names) suite_parser.free() - return test_suite + return GdUnitTestSuiteDelegator.new(test_suite) + + +static func load_test_suite_cs(resource_path :String) -> GdUnitTestSuiteDelegator: + if not GdUnitTools.is_mono_supported(): + return null + var script = ClassDB.instance("CSharpScript") + script.source_code = GdUnitTools.resource_as_string(resource_path) + script.resource_path = resource_path + script.reload() + + return null + +static func load_cs_script(resource_path :String) -> Script: + if not GdUnitTools.is_mono_supported(): + return null + var script = ClassDB.instance("CSharpScript") + script.source_code = GdUnitTools.resource_as_string(resource_path) + script.resource_path = GdUnitTools.create_temp_dir("test") + "/%s" % resource_path.get_file().replace(".resource", ".cs") + Directory.new().remove(script.resource_path) + ResourceSaver.save(script.resource_path, script) + script.reload() + return script + +static func load_gd_script(resource_path :String) -> GDScript: + var script := GDScript.new() + script.source_code = GdUnitTools.resource_as_string(resource_path) + script.resource_path = resource_path.replace(".resource", ".gd") + script.reload() + return script + diff --git a/addons/gdUnit3/test/asserts/GdUnitBoolAssertImplTest.cs b/addons/gdUnit3/test/asserts/GdUnitBoolAssertImplTest.cs new file mode 100644 index 00000000..6c59e175 --- /dev/null +++ b/addons/gdUnit3/test/asserts/GdUnitBoolAssertImplTest.cs @@ -0,0 +1,77 @@ +using GdUnit3; + +[TestSuite] +public class GdUnitBoolAssertImplTest : GdUnitTestSuite +{ + [TestCase] + public void IsTrue() + { + AssertBool(true).IsTrue(); + AssertBool(false, IGdUnitAssert.EXPECT.FAIL).IsTrue() + .HasFailureMessage("Expecting: 'True' but is 'False'"); + } + + [TestCase] + public void IsFalse() + { + AssertBool(false).IsFalse(); + AssertBool(true, IGdUnitAssert.EXPECT.FAIL).IsFalse() + .HasFailureMessage("Expecting: 'False' but is 'True'"); + } + + [TestCase] + public void IsNull() + { + AssertBool(true, IGdUnitAssert.EXPECT.FAIL) + .IsNull() + .StartsWithFailureMessage("Expecting: 'Null' but was 'True'"); + AssertBool(false, IGdUnitAssert.EXPECT.FAIL) + .IsNull() + .StartsWithFailureMessage("Expecting: 'Null' but was 'False'"); + } + + [TestCase] + public void IsNotNull() + { + AssertBool(true).IsNotNull(); + AssertBool(false).IsNotNull(); + } + + [TestCase] + public void IsEqual() + { + AssertBool(true).IsEqual(true); + AssertBool(false).IsEqual(false); + AssertBool(true, IGdUnitAssert.EXPECT.FAIL) + .IsEqual(false) + .HasFailureMessage("Expecting:\n 'False'\n but was\n 'True'"); + } + + [TestCase] + public void IsNotEqual() + { + AssertBool(true).IsNotEqual(false); + AssertBool(false).IsNotEqual(true); + AssertBool(true, IGdUnitAssert.EXPECT.FAIL) + .IsNotEqual(true) + .HasFailureMessage("Expecting:\n 'True'\n not equal to\n 'True'"); + } + + [TestCase] + public void Fluent() + { + AssertBool(true).IsTrue() + .IsEqual(true) + .IsNotEqual(false) + .IsNotNull(); + } + + [TestCase] + public void OverrideFailureMessage() + { + AssertBool(true, IGdUnitAssert.EXPECT.FAIL) + .OverrideFailureMessage("Custom failure message") + .IsNull() + .HasFailureMessage("Custom failure message"); + } +} diff --git a/addons/gdUnit3/test/asserts/GdUnitIntAssertImplTest.cs b/addons/gdUnit3/test/asserts/GdUnitIntAssertImplTest.cs new file mode 100644 index 00000000..3f6dd243 --- /dev/null +++ b/addons/gdUnit3/test/asserts/GdUnitIntAssertImplTest.cs @@ -0,0 +1,19 @@ +using Godot; +using GdUnit3; +using static GdUnit3.IGdUnitAssert.EXPECT; +using static GdUnit3.IGdUnitStringAssert.Compare; + +[TestSuite] +public class GdUnitIntAssertImplTest : GdUnitTestSuite +{ + + [TestCase] + public void IsEqual() + { + //AssertInt(1).IsLess(2); + //AssertInt(1).IsEqual(1); + AssertInt(1).IsEqual(1); + + } + +} diff --git a/addons/gdUnit3/test/asserts/GdUnitObjectAssertImplTest.cs b/addons/gdUnit3/test/asserts/GdUnitObjectAssertImplTest.cs new file mode 100644 index 00000000..da3cd641 --- /dev/null +++ b/addons/gdUnit3/test/asserts/GdUnitObjectAssertImplTest.cs @@ -0,0 +1,142 @@ +using Godot; +using GdUnit3; +using static GdUnit3.IGdUnitAssert.EXPECT; +using static GdUnit3.IGdUnitStringAssert.Compare; + +[TestSuite] +public class GdUnitObjectAssertImplTest : GdUnitTestSuite +{ + + class CustomClass + { + public class InnerClassA : Node { } + + public class InnerClassB : InnerClassA { } + + public class InnerClassC : Node { } + } + + [TestCase] + public void IsEqual() + { + AssertObject(new CubeMesh()).IsEqual(new CubeMesh()); + // should fail because the current is an CubeMesh and we expect equal to a Skin + AssertObject(new CubeMesh(), FAIL) + .IsEqual(new Skin()); + } + + [TestCase] + public void IsNotEqual() + { + AssertObject(new CubeMesh()).IsNotEqual(new Skin()); + // should fail because the current is an CubeMesh and we expect not equal to a CubeMesh + AssertObject(new CubeMesh(), FAIL) + .IsNotEqual(new CubeMesh()); + } + + [TestCase] + public void IsInstanceof() + { + // engine class test + AssertObject(auto_free(new Path())).IsInstanceof(); + AssertObject(auto_free(new Camera())).IsInstanceof(); + // script class test + // inner class test + AssertObject(auto_free(new CustomClass.InnerClassA())).IsInstanceof(); + AssertObject(auto_free(new CustomClass.InnerClassB())).IsInstanceof(); + + // should fail because the current is not a instance of `Tree` + AssertObject(auto_free(new Path()), FAIL) + .IsInstanceof() + .HasFailureMessage("Expected instance of:\n 'Godot.Tree'\n But it was 'Godot.Path'"); + AssertObject(null, FAIL) + .IsInstanceof() + .HasFailureMessage("Expected instance of:\n 'Godot.Tree'\n But it was 'Null'"); + } + + [TestCase] + public void IsNotInstanceof() + { + AssertObject(null).IsNotInstanceof(); + // engine class test + AssertObject(auto_free(new Path())).IsNotInstanceof(); + // inner class test + AssertObject(auto_free(new CustomClass.InnerClassA())).IsNotInstanceof(); + AssertObject(auto_free(new CustomClass.InnerClassB())).IsNotInstanceof(); + + // should fail because the current is not a instance of `Tree` + AssertObject(auto_free(new Path()), FAIL) + .IsNotInstanceof() + .HasFailureMessage("Expected not be a instance of "); + } + + [TestCase] + public void IsNull() + { + AssertObject(null).IsNull(); + // should fail because the current is not null + AssertObject(auto_free(new Node()), FAIL) + .IsNull() + .StartsWithFailureMessage("Expecting: 'Null' but was "); + } + + [TestCase] + public void IsNotNull() + { + AssertObject(auto_free(new Node())).IsNotNull(); + // should fail because the current is null + AssertObject(null, FAIL) + .IsNotNull() + .HasFailureMessage("Expecting: not to be 'Null'"); + } + + [TestCase] + public void IsSame() + { + var obj1 = auto_free(new Node()); + var obj2 = obj1; + var obj3 = auto_free(obj1.Duplicate()); + AssertObject(obj1).IsSame(obj1); + AssertObject(obj1).IsSame(obj2); + AssertObject(obj2).IsSame(obj1); + AssertObject(null, FAIL).IsSame(obj1); + AssertObject(obj1, FAIL).IsSame(obj3); + AssertObject(obj3, FAIL).IsSame(obj1); + AssertObject(obj3, FAIL).IsSame(obj2); + } + + [TestCase] + public void IsNotSame() + { + var obj1 = auto_free(new Node()); + var obj2 = obj1; + var obj3 = auto_free(obj1.Duplicate()); + AssertObject(null).IsNotSame(obj1); + AssertObject(obj1).IsNotSame(obj3); + AssertObject(obj3).IsNotSame(obj1); + AssertObject(obj3).IsNotSame(obj2); + + AssertObject(obj1, FAIL).IsNotSame(obj1); + AssertObject(obj1, FAIL).IsNotSame(obj2); + AssertObject(obj2, FAIL).IsNotSame(obj1); + } + + [TestCase] + public void must_fail_has_invlalid_type() + { + AssertObject(1, FAIL).HasFailureMessage("GdUnitObjectAssert inital error, unexpected type "); + AssertObject(1.3, FAIL).HasFailureMessage("GdUnitObjectAssert inital error, unexpected type "); + AssertObject(true, FAIL).HasFailureMessage("GdUnitObjectAssert inital error, unexpected type "); + AssertObject("foo", FAIL).HasFailureMessage("GdUnitObjectAssert inital error, unexpected type "); + } + + [TestCase] + public void OverrideFailureMessage() + { + AssertObject(auto_free(new Node()), FAIL) + .OverrideFailureMessage("Custom failure message") + .IsNull() + .HasFailureMessage("Custom failure message"); + } + +} diff --git a/addons/gdUnit3/test/asserts/GdUnitObjectAssertImplTest.gd b/addons/gdUnit3/test/asserts/GdUnitObjectAssertImplTest.gd index 14dedaa9..e454e69b 100644 --- a/addons/gdUnit3/test/asserts/GdUnitObjectAssertImplTest.gd +++ b/addons/gdUnit3/test/asserts/GdUnitObjectAssertImplTest.gd @@ -31,8 +31,12 @@ func test_is_instanceof(): assert_object(auto_free(Path.new()), GdUnitAssert.EXPECT_FAIL)\ .is_instanceof(Tree)\ .has_failure_message("Expected instance of:\n 'Tree'\n But it was 'Path'") + assert_object(null, GdUnitAssert.EXPECT_FAIL)\ + .is_instanceof(Tree)\ + .has_failure_message("Expected instance of:\n 'Tree'\n But it was 'Null'") func test_is_not_instanceof(): + assert_object(null).is_not_instanceof(Node) # engine class test assert_object(auto_free(Path.new())).is_not_instanceof(Tree) # script class test @@ -46,12 +50,6 @@ func test_is_not_instanceof(): .is_not_instanceof(Node)\ .has_failure_message("Expected not be a instance of ") -func test_is_not_instanceof_on_null_value(): - assert_object(null, GdUnitAssert.EXPECT_FAIL)\ - .is_not_null()\ - .is_instanceof(Node)\ - .has_failure_message("Expected instance of:\n 'Node'\n But it was 'Null'") - func test_is_null(): assert_object(null).is_null() # should fail because the current is not null diff --git a/addons/gdUnit3/test/asserts/GdUnitStringAssertImplTest.cs b/addons/gdUnit3/test/asserts/GdUnitStringAssertImplTest.cs new file mode 100644 index 00000000..423d53e8 --- /dev/null +++ b/addons/gdUnit3/test/asserts/GdUnitStringAssertImplTest.cs @@ -0,0 +1,221 @@ + +using GdUnit3; +using static GdUnit3.IGdUnitAssert.EXPECT; +using static GdUnit3.IGdUnitStringAssert.Compare; + +[TestSuite] +public class GdUnitStringAssertImplTest : GdUnitTestSuite +{ + [TestCase] + public void IsNull() + { + AssertString(null).IsNull(); + // should fail because the current is not null + AssertString("abc", FAIL) + .IsNull() + .StartsWithFailureMessage("Expecting: 'Null' but was 'abc'"); + } + + [TestCase] + public void IsNotNull() + { + AssertString("abc").IsNotNull(); + // should fail because the current is null + AssertString(null, FAIL) + .IsNotNull() + .HasFailureMessage("Expecting: not to be 'Null'"); + } + + [TestCase] + public void IsEqual() + { + AssertString("This is a test message").IsEqual("This is a test message"); + AssertString("This is a test message", FAIL) + .IsEqual("This is a test Message") + .HasFailureMessage("Expecting:\n 'This is a test Message'\n but was\n 'This is a test Mmessage'"); + } + + [TestCase] + public void IsEqualIgnoringCase() + { + AssertString("This is a test message").IsEqualIgnoringCase("This is a test Message"); + AssertString("This is a test message", FAIL) + .IsEqualIgnoringCase("This is a Message") + .HasFailureMessage("Expecting:\n 'This is a Message'\n but was\n 'This is a test Mmessage' (ignoring case)"); + } + + [TestCase] + public void IsNotEqual() + { + AssertString("This is a test message").IsNotEqual("This is a test Message"); + AssertString("This is a test message", FAIL) + .IsNotEqual("This is a test message") + .HasFailureMessage("Expecting:\n 'This is a test message'\n not equal to\n 'This is a test message'"); + } + + [TestCase] + public void IsNotEqualIgnoringCase() + { + AssertString("This is a test message").IsNotEqualIgnoringCase("This is a Message"); + AssertString("This is a test message", FAIL) + .IsNotEqualIgnoringCase("This is a test Message") + .HasFailureMessage("Expecting:\n 'This is a test Message'\n not equal to\n 'This is a test message'"); + } + + [TestCase] + public void IsEmpty() + { + AssertString("").IsEmpty(); + // should fail because the current value is not empty it contains a space + AssertString(" ", FAIL) + .IsEmpty() + .HasFailureMessage("Expecting:\n must be empty but was\n ' '"); + AssertString("abc", FAIL) + .IsEmpty() + .HasFailureMessage("Expecting:\n must be empty but was\n 'abc'"); + } + + [TestCase] + public void IsNotEmpty() + { + AssertString(" ").IsNotEmpty(); + AssertString(" ").IsNotEmpty(); + AssertString("abc").IsNotEmpty(); + // should fail because current is empty + AssertString("", FAIL) + .IsNotEmpty() + .HasFailureMessage("Expecting:\n must not be empty"); + } + + [TestCase] + public void Contains() + { + AssertString("This is a test message").Contains("a test"); + // must fail because of camel case difference + AssertString("This is a test message", FAIL) + .Contains("a Test") + .HasFailureMessage("Expecting:\n 'This is a test message'\n do contains\n 'a Test'"); + } + + [TestCase] + public void notContains() + { + AssertString("This is a test message").NotContains("a tezt"); + AssertString("This is a test message", FAIL) + .NotContains("a test") + .HasFailureMessage("Expecting:\n 'This is a test message'\n not do contain\n 'a test'"); + } + + [TestCase] + public void ContainsIgnoringCase() + { + AssertString("This is a test message").ContainsIgnoringCase("a Test"); + AssertString("This is a test message", FAIL) + .ContainsIgnoringCase("a Tesd") + .HasFailureMessage("Expecting:\n 'This is a test message'\n contains\n 'a Tesd'\n (ignoring case)"); + } + + [TestCase] + public void NotContainsIgnoringCase() + { + AssertString("This is a test message").NotContainsIgnoringCase("a Tezt"); + AssertString("This is a test message", FAIL) + .NotContainsIgnoringCase("a Test") + .HasFailureMessage("Expecting:\n 'This is a test message'\n not do contains\n 'a Test'\n (ignoring case)"); + } + + [TestCase] + public void StartsWith() + { + AssertString("This is a test message").StartsWith("This is"); + AssertString("This is a test message", FAIL) + .StartsWith("This iss") + .HasFailureMessage("Expecting:\n 'This is a test message'\n to start with\n 'This iss'"); + AssertString("This is a test message", FAIL) + .StartsWith("this is") + .HasFailureMessage("Expecting:\n 'This is a test message'\n to start with\n 'this is'"); + AssertString("This is a test message", FAIL) + .StartsWith("test") + .HasFailureMessage("Expecting:\n 'This is a test message'\n to start with\n 'test'"); + } + + [TestCase] + public void EndsWith() + { + AssertString("This is a test message").EndsWith("test message"); + AssertString("This is a test message", FAIL) + .EndsWith("tes message") + .HasFailureMessage("Expecting:\n 'This is a test message'\n to end with\n 'tes message'"); + AssertString("This is a test message", FAIL) + .EndsWith("a test") + .HasFailureMessage("Expecting:\n 'This is a test message'\n to end with\n 'a test'"); + } + + [TestCase] + public void HasLenght() + { + AssertString("This is a test message").HasLength(22); + AssertString("").HasLength(0); + AssertString("This is a test message", FAIL) + .HasLength(23) + .HasFailureMessage("Expecting size:\n '23' but was '22' in\n 'This is a test message'"); + } + + [TestCase] + public void HasLenghtLessThan() + { + AssertString("This is a test message").HasLength(23, LESS_THAN); + AssertString("This is a test message").HasLength(42, LESS_THAN); + AssertString("This is a test message", FAIL) + .HasLength(22, LESS_THAN) + .HasFailureMessage("Expecting size to be less than:\n '22' but was '22' in\n 'This is a test message'"); + } + + [TestCase] + public void HasLenghtLessEqual() + { + AssertString("This is a test message").HasLength(22, LESS_EQUAL); + AssertString("This is a test message").HasLength(23, LESS_EQUAL); + AssertString("This is a test message", FAIL) + .HasLength(21, LESS_EQUAL) + .HasFailureMessage("Expecting size to be less than or equal:\n '21' but was '22' in\n 'This is a test message'"); + } + + [TestCase] + public void HasLenghtGreaterThan() + { + AssertString("This is a test message").HasLength(21, GREATER_THAN); + AssertString("This is a test message", FAIL) + .HasLength(22, GREATER_THAN) + .HasFailureMessage("Expecting size to be greater than:\n '22' but was '22' in\n 'This is a test message'"); + } + + [TestCase] + public void HasLenghtGreaterEqual() + { + AssertString("This is a test message").HasLength(21, GREATER_EQUAL); + AssertString("This is a test message").HasLength(22, GREATER_EQUAL); + AssertString("This is a test message", FAIL) + .HasLength(23, GREATER_EQUAL) + .HasFailureMessage("Expecting size to be greater than or equal:\n '23' but was '22' in\n 'This is a test message'"); + } + + [TestCase] + public void Fluent() + { + AssertString("value a").HasLength(7) + .IsNotEqual("a") + .IsEqual("value a") + .IsNotNull(); + } + + [TestCase] + public void OverrideFailureMessage() + { + AssertString("", FAIL) + .OverrideFailureMessage("Custom failure message") + .IsNull() + .HasFailureMessage("Custom failure message"); + } + +} diff --git a/addons/gdUnit3/test/core/ExampleTest.cs b/addons/gdUnit3/test/core/ExampleTest.cs new file mode 100644 index 00000000..594c295d --- /dev/null +++ b/addons/gdUnit3/test/core/ExampleTest.cs @@ -0,0 +1,42 @@ +using Godot; +using System; +using GdUnit3; + + + +[TestSuite] +public class ExampleTest : GdUnitTestSuite +{ + public override void Before() + { + GD.PrintS("calling Before"); + } + + public override void After() + { + GD.PrintS("calling After"); + } + + public override void BeforeTest() + { + GD.PrintS("calling BeforeTest"); + } + + public override void AfterTest() + { + GD.PrintS("calling AfterTest"); + } + + [TestCase] + public void TestFoo() + { + AssertBool(true).IsEqual(true); + } + + [TestCase] + public void TestBar() + { + AssertBool(true).IsEqual(true); + } + +} diff --git a/addons/gdUnit3/test/core/GdObjectsTest.gd b/addons/gdUnit3/test/core/GdObjectsTest.gd index d4ffb949..4ab91abb 100644 --- a/addons/gdUnit3/test/core/GdObjectsTest.gd +++ b/addons/gdUnit3/test/core/GdObjectsTest.gd @@ -551,3 +551,14 @@ func test_all_types() -> void: GdObjects.TYPE_VOID, GdObjects.TYPE_VARARG, ]) + +func test_is_test_suite() -> void: + assert_bool(GdObjects.is_test_suite(GdUnitTestResourceLoader.load_gd_script("res://addons/gdUnit3/test/core/ResultTest.gd"))).is_true() + if GdUnitTools.is_mono_supported(): + assert_bool(GdObjects.is_test_suite(GdUnitTestResourceLoader.load_cs_script("res://addons/gdUnit3/test/core/ExampleTest.cs"))).is_true() + assert_bool(GdObjects.is_test_suite(GdUnitTestResourceLoader.load_cs_script("res://addons/gdUnit3/test/core/resources/testsuites/mono/NotATestSuite.cs"))).is_false() + # currently not supported + assert_bool(GdObjects.is_test_suite(NativeScript.new())).is_false() + assert_bool(GdObjects.is_test_suite(PluginScript.new())).is_false() + assert_bool(GdObjects.is_test_suite(VisualScript.new())).is_false() + diff --git a/addons/gdUnit3/test/core/GdUnitExecutorTest.gd b/addons/gdUnit3/test/core/GdUnitExecutorTest.gd index 0ce528d4..3807d741 100644 --- a/addons/gdUnit3/test/core/GdUnitExecutorTest.gd +++ b/addons/gdUnit3/test/core/GdUnitExecutorTest.gd @@ -13,13 +13,13 @@ func before(): Engine.get_main_loop().root.add_child(_executor) _executor.connect("send_event_debug", self, "_on_executor_event") -func resource(resource_path :String) -> GdUnitTestSuite: +func resource(resource_path :String) -> GdUnitTestSuiteDelegator: return GdUnitTestResourceLoader.load_test_suite(resource_path) func _on_executor_event(event :GdUnitEvent) -> void: _events.append(event) -func execute(test_suite :GdUnitTestSuite, enable_orphan_detection := true): +func execute(test_suite :GdUnitTestSuiteDelegator, enable_orphan_detection := true): yield(get_tree(), "idle_frame") _events.clear() _executor._memory_pool.configure(enable_orphan_detection) @@ -82,7 +82,7 @@ func assert_event_states(events :Array) -> GdUnitArrayAssert: func test_execute_success() -> void: var test_suite := resource("res://addons/gdUnit3/test/core/resources/testsuites/TestSuiteAllStagesSuccess.resource") # verify all test cases loaded - assert_array(test_suite.get_children()).extract("get_name").contains_exactly(["test_case1", "test_case2"]) + assert_array(test_suite.get_test_cases()).extract("get_name").contains_exactly(["test_case1", "test_case2"]) # simulate test suite execution var events = yield(execute(test_suite), "completed" ) # verify basis infos @@ -110,7 +110,7 @@ func test_execute_success() -> void: func test_execute_failure_on_stage_before() -> void: var test_suite := resource("res://addons/gdUnit3/test/core/resources/testsuites/TestSuiteFailOnStageBefore.resource") # verify all test cases loaded - assert_array(test_suite.get_children()).extract("get_name").contains_exactly(["test_case1", "test_case2"]) + assert_array(test_suite.get_test_cases()).extract("get_name").contains_exactly(["test_case1", "test_case2"]) # simulate test suite execution var events = yield(execute(test_suite), "completed" ) # verify basis infos @@ -147,7 +147,7 @@ func test_execute_failure_on_stage_before() -> void: func test_execute_failure_on_stage_after() -> void: var test_suite := resource("res://addons/gdUnit3/test/core/resources/testsuites/TestSuiteFailOnStageAfter.resource") # verify all test cases loaded - assert_array(test_suite.get_children()).extract("get_name").contains_exactly(["test_case1", "test_case2"]) + assert_array(test_suite.get_test_cases()).extract("get_name").contains_exactly(["test_case1", "test_case2"]) # simulate test suite execution var events = yield(execute(test_suite), "completed" ) # verify basis infos @@ -184,7 +184,7 @@ func test_execute_failure_on_stage_after() -> void: func test_execute_failure_on_stage_before_test() -> void: var test_suite := resource("res://addons/gdUnit3/test/core/resources/testsuites/TestSuiteFailOnStageBeforeTest.resource") # verify all test cases loaded - assert_array(test_suite.get_children()).extract("get_name").contains_exactly(["test_case1", "test_case2"]) + assert_array(test_suite.get_test_cases()).extract("get_name").contains_exactly(["test_case1", "test_case2"]) # simulate test suite execution var events = yield(execute(test_suite), "completed" ) # verify basis infos @@ -224,7 +224,7 @@ func test_execute_failure_on_stage_before_test() -> void: func test_execute_failure_on_stage_after_test() -> void: var test_suite := resource("res://addons/gdUnit3/test/core/resources/testsuites/TestSuiteFailOnStageAfterTest.resource") # verify all test cases loaded - assert_array(test_suite.get_children()).extract("get_name").contains_exactly(["test_case1", "test_case2"]) + assert_array(test_suite.get_test_cases()).extract("get_name").contains_exactly(["test_case1", "test_case2"]) # simulate test suite execution var events = yield(execute(test_suite), "completed" ) # verify basis infos @@ -264,7 +264,7 @@ func test_execute_failure_on_stage_after_test() -> void: func test_execute_failure_on_stage_test_case1() -> void: var test_suite := resource("res://addons/gdUnit3/test/core/resources/testsuites/TestSuiteFailOnStageTestCase1.resource") # verify all test cases loaded - assert_array(test_suite.get_children()).extract("get_name").contains_exactly(["test_case1", "test_case2"]) + assert_array(test_suite.get_test_cases()).extract("get_name").contains_exactly(["test_case1", "test_case2"]) # simulate test suite execution var events = yield(execute(test_suite), "completed" ) # verify basis infos @@ -302,7 +302,7 @@ func test_execute_failure_on_muliple_stages() -> void: # this is a more complex failure state, we expect to find multipe failures on different stages var test_suite := resource("res://addons/gdUnit3/test/core/resources/testsuites/TestSuiteFailOnMultipeStages.resource") # verify all test cases loaded - assert_array(test_suite.get_children()).extract("get_name").contains_exactly(["test_case1", "test_case2"]) + assert_array(test_suite.get_test_cases()).extract("get_name").contains_exactly(["test_case1", "test_case2"]) # simulate test suite execution var events = yield(execute(test_suite), "completed" ) # verify basis infos @@ -345,7 +345,7 @@ func test_execute_failure_and_orphans() -> void: # this is a more complex failure state, we expect to find multipe orphans on different stages var test_suite := resource("res://addons/gdUnit3/test/core/resources/testsuites/TestSuiteFailAndOrpahnsDetected.resource") # verify all test cases loaded - assert_array(test_suite.get_children()).extract("get_name").contains_exactly(["test_case1", "test_case2"]) + assert_array(test_suite.get_test_cases()).extract("get_name").contains_exactly(["test_case1", "test_case2"]) # simulate test suite execution var events = yield(execute(test_suite), "completed") # verify basis infos @@ -396,7 +396,7 @@ func test_execute_failure_and_orphans_report_orphan_disabled() -> void: # this is a more complex failure state, we expect to find multipe orphans on different stages var test_suite := resource("res://addons/gdUnit3/test/core/resources/testsuites/TestSuiteFailAndOrpahnsDetected.resource") # verify all test cases loaded - assert_array(test_suite.get_children()).extract("get_name").contains_exactly(["test_case1", "test_case2"]) + assert_array(test_suite.get_test_cases()).extract("get_name").contains_exactly(["test_case1", "test_case2"]) # simulate test suite execution whit disabled orphan detection var events = yield(execute(test_suite, false), "completed") # verify basis infos @@ -438,7 +438,7 @@ func test_execute_error_on_test_timeout() -> void: # this tests a timeout on a test case reported as error var test_suite := resource("res://addons/gdUnit3/test/core/resources/testsuites/TestSuiteErrorOnTestTimeout.resource") # verify all test cases loaded - assert_array(test_suite.get_children()).extract("get_name").contains_exactly(["test_case1", "test_case2"]) + assert_array(test_suite.get_test_cases()).extract("get_name").contains_exactly(["test_case1", "test_case2"]) # simulate test suite execution var events = yield(execute(test_suite), "completed" ) # verify basis infos @@ -480,7 +480,7 @@ func test_execute_failure_fuzzer_iteration() -> void: var test_suite := resource("res://addons/gdUnit3/test/core/resources/testsuites/GdUnitFuzzerTest.resource") # verify all test cases loaded var expected_test_cases := ["test_multi_yielding_with_fuzzer", "test_multi_yielding_with_fuzzer_fail_after_3_iterations"] - assert_array(test_suite.get_children()).extract("get_name").contains_exactly(expected_test_cases) + assert_array(test_suite.get_test_cases()).extract("get_name").contains_exactly(expected_test_cases) # simulate test suite execution var events = yield(execute(test_suite), "completed" ) @@ -518,7 +518,7 @@ func test_execute_failure_fuzzer_iteration() -> void: func test_execute_add_child_on_before_GD_106() -> void: var test_suite := resource("res://addons/gdUnit3/test/core/resources/testsuites/TestSuiteFailAddChildStageBefore.resource") # verify all test cases loaded - assert_array(test_suite.get_children()).extract("get_name").contains_exactly(["test_case1", "test_case2"]) + assert_array(test_suite.get_test_cases()).extract("get_name").contains_exactly(["test_case1", "test_case2"]) # simulate test suite execution var events = yield(execute(test_suite), "completed" ) # verify basis infos diff --git a/addons/gdUnit3/test/core/_TestSuiteScannerTest.gd b/addons/gdUnit3/test/core/_TestSuiteScannerTest.gd index 04e2d142..4fda3a09 100644 --- a/addons/gdUnit3/test/core/_TestSuiteScannerTest.gd +++ b/addons/gdUnit3/test/core/_TestSuiteScannerTest.gd @@ -125,11 +125,14 @@ func test_build_test_suite_path() -> void: func test_parse_and_add_test_cases() -> void: var default_time := GdUnitSettings.test_timeout() - var scanner = auto_free(_TestSuiteScanner.new()) + var scanner :_TestSuiteScanner = auto_free(_TestSuiteScanner.new()) + # fake a test suite var test_suite :GdUnitTestSuite = auto_free(GdUnitTestSuite.new()) - var script_path := "res://addons/gdUnit3/test/core/resources/test_script_with_arguments.gd" + var script := GDScript.new() + script.resource_path = "res://addons/gdUnit3/test/core/resources/test_script_with_arguments.gd" + test_suite.set_script(script) var test_case_names := PoolStringArray(["test_no_args", "test_with_timeout", "test_with_fuzzer", "test_with_fuzzer_iterations", "test_with_multible_fuzzers"]) - scanner._parse_and_add_test_cases(test_suite, script_path, test_case_names) + scanner._parse_and_add_test_cases(test_suite, test_suite.get_script(), test_case_names) assert_array(test_suite.get_children())\ .extractv(extr("get_name"), extr("timeout"), extr("fuzzers"), extr("iterations"))\ .contains_exactly([ @@ -143,7 +146,7 @@ func test_scan_by_inheritance_class_name() -> void: var scanner :_TestSuiteScanner = auto_free(_TestSuiteScanner.new()) var test_suites := scanner.scan("res://addons/gdUnit3/test/core/resources/scan_testsuite_inheritance/by_class_name/") - assert_array(test_suites).extractv(extr("get_name"), extr("get_script.get_path"), extr("get_children.get_name"))\ + assert_array(test_suites).extractv(extr("get_name"), extr("get_script.get_path"), extr("get_test_cases.get_name"))\ .contains_exactly_in_any_order([ tuple("BaseTest", "res://addons/gdUnit3/test/core/resources/scan_testsuite_inheritance/by_class_name/BaseTest.gd", ["test_foo1"]), tuple("ExtendedTest","res://addons/gdUnit3/test/core/resources/scan_testsuite_inheritance/by_class_name/ExtendedTest.gd", ["test_foo2", "test_foo1"]), @@ -157,7 +160,7 @@ func test_scan_by_inheritance_class_path() -> void: var scanner :_TestSuiteScanner = auto_free(_TestSuiteScanner.new()) var test_suites := scanner.scan("res://addons/gdUnit3/test/core/resources/scan_testsuite_inheritance/by_class_path/") - assert_array(test_suites).extractv(extr("get_name"), extr("get_script.get_path"), extr("get_children.get_name"))\ + assert_array(test_suites).extractv(extr("get_name"), extr("get_script.get_path"), extr("get_test_cases.get_name"))\ .contains_exactly_in_any_order([ tuple("BaseTest", "res://addons/gdUnit3/test/core/resources/scan_testsuite_inheritance/by_class_path/BaseTest.gd", ["test_foo1"]), tuple("ExtendedTest","res://addons/gdUnit3/test/core/resources/scan_testsuite_inheritance/by_class_path/ExtendedTest.gd", ["test_foo2", "test_foo1"]), @@ -166,3 +169,13 @@ func test_scan_by_inheritance_class_path() -> void: # finally free all scaned test suites for ts in test_suites: ts.free() + +func test_is_script_format_supported() -> void: + assert_bool(_TestSuiteScanner._is_script_format_supported("res://exampe.gd")).is_true() + if GdUnitTools.is_mono_supported(): + assert_bool(_TestSuiteScanner._is_script_format_supported("res://exampe.cs")).is_true() + else: + assert_bool(_TestSuiteScanner._is_script_format_supported("res://exampe.cs")).is_false() + assert_bool(_TestSuiteScanner._is_script_format_supported("res://exampe.gdns")).is_false() + assert_bool(_TestSuiteScanner._is_script_format_supported("res://exampe.vs")).is_false() + assert_bool(_TestSuiteScanner._is_script_format_supported("res://exampe.tres")).is_false() diff --git a/addons/gdUnit3/test/core/resources/testsuites/mono/NotATestSuite.cs b/addons/gdUnit3/test/core/resources/testsuites/mono/NotATestSuite.cs new file mode 100644 index 00000000..6ac562bb --- /dev/null +++ b/addons/gdUnit3/test/core/resources/testsuites/mono/NotATestSuite.cs @@ -0,0 +1,21 @@ +using Godot; +using System; +using GdUnit3; + + +// will be ignored becaus of missing `[TestSuite]` animation +public class NotATestSuite : GdUnitTestSuite +{ + + public override void Before() + { + GD.PrintS("calling Before"); + } + + [TestCase] + public void TestFoo() + { + AssertBool(true).IsEqual(false); + } + +} diff --git a/addons/gdUnit3/test/ui/parts/InspectorTreeMainPanelTest.gd b/addons/gdUnit3/test/ui/parts/InspectorTreeMainPanelTest.gd index 0576d637..3724fae1 100644 --- a/addons/gdUnit3/test/ui/parts/InspectorTreeMainPanelTest.gd +++ b/addons/gdUnit3/test/ui/parts/InspectorTreeMainPanelTest.gd @@ -30,7 +30,7 @@ func after_test(): _inspector.free() -static func toDto(test_suite :GdUnitTestSuite) -> GdUnitTestSuiteDto: +static func toDto(test_suite :GdUnitTestSuiteDelegator) -> GdUnitTestSuiteDto: var dto := GdUnitTestSuiteDto.new() return dto.deserialize(dto.serialize(test_suite)) as GdUnitTestSuiteDto diff --git a/gdUnit3.csproj b/gdUnit3.csproj new file mode 100644 index 00000000..af6af65f --- /dev/null +++ b/gdUnit3.csproj @@ -0,0 +1,6 @@ + + + net472 + preview + + diff --git a/gdUnit3.sln b/gdUnit3.sln new file mode 100644 index 00000000..993157f3 --- /dev/null +++ b/gdUnit3.sln @@ -0,0 +1,19 @@ +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 2012 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "gdUnit3", "gdUnit3.csproj", "{1F7492EE-4E0C-47ED-8D6F-FFF9FC4DCDB6}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + ExportDebug|Any CPU = ExportDebug|Any CPU + ExportRelease|Any CPU = ExportRelease|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {1F7492EE-4E0C-47ED-8D6F-FFF9FC4DCDB6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1F7492EE-4E0C-47ED-8D6F-FFF9FC4DCDB6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1F7492EE-4E0C-47ED-8D6F-FFF9FC4DCDB6}.ExportDebug|Any CPU.ActiveCfg = ExportDebug|Any CPU + {1F7492EE-4E0C-47ED-8D6F-FFF9FC4DCDB6}.ExportDebug|Any CPU.Build.0 = ExportDebug|Any CPU + {1F7492EE-4E0C-47ED-8D6F-FFF9FC4DCDB6}.ExportRelease|Any CPU.ActiveCfg = ExportRelease|Any CPU + {1F7492EE-4E0C-47ED-8D6F-FFF9FC4DCDB6}.ExportRelease|Any CPU.Build.0 = ExportRelease|Any CPU + EndGlobalSection +EndGlobal diff --git a/project.godot b/project.godot index 03df93b7..cd5f2997 100644 --- a/project.godot +++ b/project.godot @@ -194,11 +194,31 @@ _global_script_classes=[ { "language": "GDScript", "path": "res://addons/gdUnit3/test/mocker/resources/DeepStubTestClass.gd" }, { +"base": "Spatial", +"class": "Door", +"language": "GDScript", +"path": "res://gdUnit3-examples/RoomDemo3D/src/rooms/components/door.gd" +}, { "base": "GdUnitArgumentMatcher", "class": "EqualsArgumentMatcher", "language": "GDScript", "path": "res://addons/gdUnit3/src/matchers/EqualsArgumentMatcher.gd" }, { +"base": "GdUnitTestSuite", +"class": "ExampleMockWithSignalTest", +"language": "GDScript", +"path": "res://gdUnit3-examples/SignalsTestExamples/test/ExampleMockWithSignalTest.gd" +}, { +"base": "GdUnitTestSuite", +"class": "ExampleSpyWithSignalTest", +"language": "GDScript", +"path": "res://gdUnit3-examples/SignalsTestExamples/test/ExampleSpyWithSignalTest.gd" +}, { +"base": "Reference", +"class": "ExampleWithSignal", +"language": "GDScript", +"path": "res://gdUnit3-examples/SignalsTestExamples/src/ExampleWithSignal.gd" +}, { "base": "BaseTest", "class": "ExtendedTest", "language": "GDScript", @@ -219,6 +239,21 @@ _global_script_classes=[ { "language": "GDScript", "path": "res://addons/gdUnit3/src/Fuzzers.gd" }, { +"base": "GdUnitTestSuite", +"class": "GameMockTest", +"language": "GDScript", +"path": "res://gdUnit3-examples/MenuDemo2D/test/GameMockTest.gd" +}, { +"base": "Reference", +"class": "GameRepository", +"language": "GDScript", +"path": "res://gdUnit3-examples/MenuDemo2D/src/GameRepository.gd" +}, { +"base": "GdUnitTestSuite", +"class": "GameSpyTest", +"language": "GDScript", +"path": "res://gdUnit3-examples/MenuDemo2D/test/GameSpyTest.gd" +}, { "base": "Resource", "class": "GdAssertMessages", "language": "GDScript", @@ -635,6 +670,16 @@ _global_script_classes=[ { "path": "res://addons/gdUnit3/test/core/GdUnitSceneRunnerTest.gd" }, { "base": "Reference", +"class": "GdUnitScriptType", +"language": "GDScript", +"path": "res://addons/gdUnit3/src/core/GdUnitScriptType.gd" +}, { +"base": "GdUnitTestSuite", +"class": "GdUnitScriptTypeTest", +"language": "GDScript", +"path": "res://addons/gdUnit3/test/GdUnitScriptTypeTest.gd" +}, { +"base": "Reference", "class": "GdUnitSettings", "language": "GDScript", "path": "res://addons/gdUnit3/src/core/GdUnitSettings.gd" @@ -724,6 +769,11 @@ _global_script_classes=[ { "language": "GDScript", "path": "res://addons/gdUnit3/src/GdUnitTestSuite.gd" }, { +"base": "Node", +"class": "GdUnitTestSuiteDelegator", +"language": "GDScript", +"path": "res://addons/gdUnit3/src/core/GdUnitTestSuiteDelegator.gd" +}, { "base": "GdUnitResourceDto", "class": "GdUnitTestSuiteDto", "language": "GDScript", @@ -834,6 +884,11 @@ _global_script_classes=[ { "language": "GDScript", "path": "res://addons/gdUnit3/test/core/LocalTimeTest.gd" }, { +"base": "Control", +"class": "MenuDialog", +"language": "GDScript", +"path": "res://gdUnit3-examples/MenuDemo2D/src/menu/MenuDialog.gd" +}, { "base": "GdUnitTestSuite", "class": "NetworkServerTest", "language": "GDScript", @@ -849,6 +904,11 @@ _global_script_classes=[ { "language": "GDScript", "path": "res://addons/gdUnit3/test/resources/core/Person.gd" }, { +"base": "KinematicBody", +"class": "Player", +"language": "GDScript", +"path": "res://gdUnit3-examples/RoomDemo3D/src/Player.gd" +}, { "base": "GdUnitMonitor", "class": "PushErrorMonitor", "language": "GDScript", @@ -904,6 +964,16 @@ _global_script_classes=[ { "language": "GDScript", "path": "res://addons/gdUnit3/test/core/ResultTest.gd" }, { +"base": "Spatial", +"class": "RoomWithDoor", +"language": "GDScript", +"path": "res://gdUnit3-examples/RoomDemo3D/src/rooms/RoomWithDoor.gd" +}, { +"base": "GdUnitTestSuite", +"class": "RoomWithDoorTest", +"language": "GDScript", +"path": "res://gdUnit3-examples/RoomDemo3D/test/rooms/RoomWithDoorTest.gd" +}, { "base": "Resource", "class": "SignalHandler", "language": "GDScript", @@ -1012,11 +1082,18 @@ _global_script_class_icons={ "CustomNodeTestClass": "", "CustomResourceTestClass": "", "DeepStubTestClass": "", +"Door": "", "EqualsArgumentMatcher": "", +"ExampleMockWithSignalTest": "", +"ExampleSpyWithSignalTest": "", +"ExampleWithSignal": "", "ExtendedTest": "", "Fuzzer": "", "FuzzerTool": "", "Fuzzers": "", +"GameMockTest": "", +"GameRepository": "", +"GameSpyTest": "", "GdAssertMessages": "", "GdAssertReports": "", "GdClassDescriptor": "", @@ -1100,6 +1177,8 @@ _global_script_class_icons={ "GdUnitRunnerConfigTest": "", "GdUnitSceneRunner": "", "GdUnitSceneRunnerTest": "", +"GdUnitScriptType": "", +"GdUnitScriptTypeTest": "", "GdUnitSettings": "", "GdUnitSettingsTest": "", "GdUnitSingleton": "", @@ -1118,6 +1197,7 @@ _global_script_class_icons={ "GdUnitTestCaseReport": "", "GdUnitTestResourceLoader": "", "GdUnitTestSuite": "", +"GdUnitTestSuiteDelegator": "", "GdUnitTestSuiteDto": "", "GdUnitTestSuiteReport": "", "GdUnitTestSuiteTest": "", @@ -1140,9 +1220,11 @@ _global_script_class_icons={ "IntFuzzer": "", "LocalTime": "", "LocalTimeTest": "", +"MenuDialog": "", "NetworkServerTest": "", "OverridenGetClassTestClass": "", "Person": "", +"Player": "", "PushErrorMonitor": "", "PushErrorMonitorTest": "", "RPC": "", @@ -1154,6 +1236,8 @@ _global_script_class_icons={ "RPCMessage": "", "Result": "", "ResultTest": "", +"RoomWithDoor": "", +"RoomWithDoorTest": "", "SignalHandler": "", "Spell": "", "StringFuzzer": "", diff --git a/runtest.cmd b/runtest.cmd index 785accc0..0d071946 100644 --- a/runtest.cmd +++ b/runtest.cmd @@ -7,6 +7,15 @@ IF NOT DEFINED GODOT_BIN ( EXIT /b -1 ) +REM scan if Godot mono used and compile c# classes +for /f "tokens=5 delims=. " %%i in ('%GODOT_BIN% --version') do set GODOT_TYPE=%%i +IF "%GODOT_TYPE%" == "mono" ( + ECHO "Godot mono detected" + ECHO Compiling c# classes ... Please Wait + %GODOT_BIN% --build-solutions --no-window -q --quiet + ECHO done +) + %GODOT_BIN% --no-window -s -d .\addons\gdUnit3\bin\GdUnitCmdTool.gd %* SET exit_code=%errorlevel% %GODOT_BIN% --no-window --quiet -s -d .\addons\gdUnit3\bin\GdUnitCopyLog.gd %*