From f80784d57fc1e3a25bdbb89a56e0633b40c8e205 Mon Sep 17 00:00:00 2001 From: miguel Date: Sat, 22 Jun 2024 11:45:23 +0100 Subject: [PATCH] update to gdunit 4.3 (#342) --- .github/actions/godot-install/action.yml | 89 -- .../actions/publish-test-report/action.yml | 24 - .github/actions/unit-test/action.yml | 45 - .github/actions/upload-test-report/action.yml | 16 - .github/workflows/beehave-ci.yml | 53 +- .github/workflows/unit-tests.yml | 83 -- README.md | 5 +- addons/beehave/blackboard.gd | 16 +- addons/beehave/debug/debugger.gd | 7 +- addons/beehave/debug/debugger_messages.gd | 1 - addons/beehave/debug/frames.gd | 20 +- addons/beehave/debug/graph_edit.gd | 18 +- addons/beehave/debug/graph_node.gd | 42 +- addons/beehave/debug/tree_node.gd | 28 +- .../beehave/metrics/beehave_global_metrics.gd | 14 +- addons/beehave/nodes/composites/composite.gd | 4 +- .../nodes/composites/randomized_composite.gd | 65 +- .../nodes/composites/selector_random.gd | 3 +- .../nodes/composites/selector_reactive.gd | 1 + addons/beehave/nodes/composites/sequence.gd | 1 - .../nodes/composites/sequence_random.gd | 2 +- .../nodes/composites/sequence_reactive.gd | 9 +- .../beehave/nodes/composites/sequence_star.gd | 2 +- .../nodes/composites/simple_parallel.gd | 42 +- addons/beehave/nodes/decorators/cooldown.gd | 24 +- addons/beehave/nodes/decorators/delayer.gd | 24 +- addons/beehave/nodes/decorators/failer.gd | 1 + addons/beehave/nodes/decorators/inverter.gd | 1 + addons/beehave/nodes/decorators/limiter.gd | 17 +- addons/beehave/nodes/decorators/repeater.gd | 10 +- addons/beehave/nodes/decorators/succeeder.gd | 1 + .../beehave/nodes/decorators/time_limiter.gd | 10 +- addons/beehave/nodes/decorators/until_fail.gd | 12 +- addons/beehave/nodes/leaves/action.gd | 1 + .../nodes/leaves/blackboard_compare.gd | 32 +- .../beehave/nodes/leaves/blackboard_erase.gd | 6 +- addons/beehave/nodes/leaves/blackboard_has.gd | 4 +- addons/beehave/nodes/leaves/blackboard_set.gd | 11 +- addons/beehave/nodes/leaves/condition.gd | 1 + addons/beehave/nodes/leaves/leaf.gd | 16 +- addons/beehave/plugin.gd | 2 + addons/beehave/utils/utils.gd | 1 + addons/gdUnit4/bin/GdUnitBuildTool.gd | 36 +- addons/gdUnit4/bin/GdUnitCmdTool.gd | 624 ++++++++---- addons/gdUnit4/bin/GdUnitCopyLog.gd | 85 +- addons/gdUnit4/bin/ProjectScanner.gd | 133 +-- addons/gdUnit4/plugin.cfg | 2 +- addons/gdUnit4/plugin.gd | 53 +- addons/gdUnit4/runtest.cmd | 4 +- addons/gdUnit4/runtest.sh | 7 +- addons/gdUnit4/src/Comparator.gd | 2 +- addons/gdUnit4/src/Fuzzers.gd | 12 +- addons/gdUnit4/src/GdUnitArrayAssert.gd | 28 +- addons/gdUnit4/src/GdUnitAssert.gd | 32 +- addons/gdUnit4/src/GdUnitAwaiter.gd | 17 +- addons/gdUnit4/src/GdUnitBoolAssert.gd | 4 +- addons/gdUnit4/src/GdUnitConstants.gd | 2 + addons/gdUnit4/src/GdUnitDictionaryAssert.gd | 14 +- addons/gdUnit4/src/GdUnitFuncAssert.gd | 4 +- addons/gdUnit4/src/GdUnitObjectAssert.gd | 10 +- addons/gdUnit4/src/GdUnitResultAssert.gd | 4 +- addons/gdUnit4/src/GdUnitSceneRunner.gd | 104 +- addons/gdUnit4/src/GdUnitStringAssert.gd | 10 +- addons/gdUnit4/src/GdUnitTestSuite.gd | 125 +-- addons/gdUnit4/src/GdUnitTuple.gd | 6 +- addons/gdUnit4/src/GdUnitValueExtractor.gd | 2 +- .../src/asserts/CallBackValueProvider.gd | 6 +- .../src/asserts/DefaultValueProvider.gd | 12 +- .../gdUnit4/src/asserts/GdAssertMessages.gd | 277 +++--- addons/gdUnit4/src/asserts/GdAssertReports.gd | 12 +- .../src/asserts/GdUnitArrayAssertImpl.gd | 263 ++--- .../gdUnit4/src/asserts/GdUnitAssertImpl.gd | 29 +- .../gdUnit4/src/asserts/GdUnitAssertions.gd | 36 +- .../src/asserts/GdUnitBoolAssertImpl.gd | 27 +- .../src/asserts/GdUnitDictionaryAssertImpl.gd | 101 +- .../src/asserts/GdUnitFailureAssertImpl.gd | 25 +- .../src/asserts/GdUnitFileAssertImpl.gd | 43 +- .../src/asserts/GdUnitFloatAssertImpl.gd | 37 +- .../src/asserts/GdUnitFuncAssertImpl.gd | 50 +- .../src/asserts/GdUnitGodotErrorAssertImpl.gd | 20 +- .../src/asserts/GdUnitIntAssertImpl.gd | 41 +- .../src/asserts/GdUnitObjectAssertImpl.gd | 41 +- .../src/asserts/GdUnitResultAssertImpl.gd | 36 +- .../src/asserts/GdUnitSignalAssertImpl.gd | 22 +- .../src/asserts/GdUnitStringAssertImpl.gd | 63 +- .../src/asserts/GdUnitVectorAssertImpl.gd | 42 +- addons/gdUnit4/src/asserts/ValueProvider.gd | 4 +- addons/gdUnit4/src/cmd/CmdArgumentParser.gd | 12 +- addons/gdUnit4/src/cmd/CmdCommand.gd | 6 +- addons/gdUnit4/src/cmd/CmdCommandHandler.gd | 50 +- addons/gdUnit4/src/cmd/CmdConsole.gd | 45 +- addons/gdUnit4/src/cmd/CmdOption.gd | 4 +- addons/gdUnit4/src/cmd/CmdOptions.gd | 14 +- addons/gdUnit4/src/core/GdArrayTools.gd | 18 +- addons/gdUnit4/src/core/GdDiffTool.gd | 44 +- addons/gdUnit4/src/core/GdFunctionDoubler.gd | 70 +- addons/gdUnit4/src/core/GdObjects.gd | 147 +-- addons/gdUnit4/src/core/GdUnit4Version.gd | 19 +- addons/gdUnit4/src/core/GdUnitClassDoubler.gd | 12 +- addons/gdUnit4/src/core/GdUnitFileAccess.gd | 211 ++++ .../src/core/GdUnitObjectInteractions.gd | 54 +- .../core/GdUnitObjectInteractionsTemplate.gd | 95 +- addons/gdUnit4/src/core/GdUnitProperty.gd | 11 +- addons/gdUnit4/src/core/GdUnitResult.gd | 4 +- addons/gdUnit4/src/core/GdUnitRunner.gd | 48 +- addons/gdUnit4/src/core/GdUnitRunnerConfig.gd | 21 +- .../gdUnit4/src/core/GdUnitSceneRunnerImpl.gd | 255 +++-- addons/gdUnit4/src/core/GdUnitSettings.gd | 66 +- .../gdUnit4/src/core/GdUnitSignalAwaiter.gd | 38 +- .../gdUnit4/src/core/GdUnitSignalCollector.gd | 51 +- addons/gdUnit4/src/core/GdUnitSignals.gd | 12 +- addons/gdUnit4/src/core/GdUnitSingleton.gd | 24 +- .../src/core/GdUnitTestSuiteBuilder.gd | 4 +- .../src/core/GdUnitTestSuiteScanner.gd | 127 ++- addons/gdUnit4/src/core/GdUnitTools.gd | 266 +----- .../gdUnit4/src/core/GodotVersionFixures.gd | 20 +- addons/gdUnit4/src/core/LocalTime.gd | 4 +- addons/gdUnit4/src/core/_TestCase.gd | 105 +- .../gdUnit4/src/core/command/GdUnitCommand.gd | 2 +- .../src/core/command/GdUnitCommandHandler.gd | 77 +- .../src/core/command/GdUnitShortcutAction.gd | 2 +- .../core/discovery/GdUnitTestDiscoverGuard.gd | 86 ++ .../core/discovery/GdUnitTestDiscoverer.gd | 37 + addons/gdUnit4/src/core/event/GdUnitEvent.gd | 33 +- .../gdUnit4/src/core/event/GdUnitEventInit.gd | 3 +- .../gdUnit4/src/core/event/GdUnitEventStop.gd | 3 +- .../core/event/GdUnitEventTestDiscoverEnd.gd | 19 + .../event/GdUnitEventTestDiscoverStart.gd | 6 + .../event/GdUnitEventTestDiscoverTestAdded.gd | 17 + .../GdUnitEventTestDiscoverTestRemoved.gd | 9 + .../GdUnitEventTestDiscoverTestSuiteAdded.gd | 16 + .../core/execution/GdUnitExecutionContext.gd | 45 +- .../core/execution/GdUnitMemoryObserver.gd | 19 +- .../execution/GdUnitTestReportCollector.gd | 2 +- .../core/execution/GdUnitTestSuiteExecutor.gd | 6 +- .../stages/GdUnitTestCaseAfterStage.gd | 15 +- .../stages/GdUnitTestCaseBeforeStage.gd | 9 +- .../stages/GdUnitTestCaseExecutionStage.gd | 2 +- .../stages/GdUnitTestSuiteAfterStage.gd | 8 +- .../stages/GdUnitTestSuiteBeforeStage.gd | 4 +- .../stages/GdUnitTestSuiteExecutionStage.gd | 12 +- .../GdUnitTestCaseFuzzedExecutionStage.gd | 2 +- .../fuzzed/GdUnitTestCaseFuzzedTestStage.gd | 6 +- ...UnitTestCaseParameterizedExecutionStage.gd | 2 +- .../GdUnitTestCaseParameterizedTestStage.gd | 58 +- .../GdUnitTestCaseSingleExecutionStage.gd | 2 +- .../src/core/parse/GdClassDescriptor.gd | 10 +- .../src/core/parse/GdDefaultValueDecoder.gd | 38 +- .../src/core/parse/GdFunctionArgument.gd | 67 +- .../src/core/parse/GdFunctionDescriptor.gd | 4 +- .../gdUnit4/src/core/parse/GdScriptParser.gd | 294 +++--- .../src/core/parse/GdTestParameterSet.gd | 70 -- .../parse/GdUnitTestParameterSetResolver.gd | 193 ++++ .../gdUnit4/src/core/report/GdUnitReport.gd | 6 +- .../GdUnitTestSuiteDefaultTemplate.gd | 10 +- .../test_suite/GdUnitTestSuiteTemplate.gd | 17 +- .../src/core/thread/GdUnitThreadContext.gd | 2 +- .../src/core/thread/GdUnitThreadManager.gd | 12 +- .../extractors/GdUnitFuncValueExtractor.gd | 18 +- addons/gdUnit4/src/fuzzers/FloatFuzzer.gd | 13 + addons/gdUnit4/src/fuzzers/Fuzzer.gd | 2 +- addons/gdUnit4/src/fuzzers/IntFuzzer.gd | 4 +- addons/gdUnit4/src/fuzzers/StringFuzzer.gd | 15 +- addons/gdUnit4/src/fuzzers/Vector2Fuzzer.gd | 12 +- addons/gdUnit4/src/fuzzers/Vector3Fuzzer.gd | 14 +- .../src/matchers/AnyArgumentMatcher.gd | 8 +- .../matchers/AnyBuildInTypeArgumentMatcher.gd | 44 +- .../src/matchers/AnyClazzArgumentMatcher.gd | 22 +- .../src/matchers/ChainedArgumentMatcher.gd | 19 +- .../src/matchers/EqualsArgumentMatcher.gd | 12 +- .../src/matchers/GdUnitArgumentMatcher.gd | 2 +- .../src/matchers/GdUnitArgumentMatchers.gd | 8 +- addons/gdUnit4/src/mocking/GdUnitMock.gd | 8 +- .../gdUnit4/src/mocking/GdUnitMockBuilder.gd | 32 +- .../src/mocking/GdUnitMockFunctionDoubler.gd | 14 +- addons/gdUnit4/src/mocking/GdUnitMockImpl.gd | 130 +-- addons/gdUnit4/src/monitor/ErrorLogEntry.gd | 8 +- addons/gdUnit4/src/monitor/GdUnitMonitor.gd | 6 +- .../src/monitor/GdUnitOrphanNodesMonitor.gd | 6 +- .../src/monitor/GodotGdErrorMonitor.gd | 59 +- addons/gdUnit4/src/mono/GdUnit4CSharpApi.cs | 50 + ...ApiLoader.gd => GdUnit4CSharpApiLoader.gd} | 26 +- addons/gdUnit4/src/mono/GdUnit4MonoApi.cs | 17 - addons/gdUnit4/src/network/GdUnitServer.gd | 10 +- addons/gdUnit4/src/network/GdUnitServer.tscn | 10 +- addons/gdUnit4/src/network/GdUnitTask.gd | 5 +- addons/gdUnit4/src/network/GdUnitTcpClient.gd | 45 +- addons/gdUnit4/src/network/GdUnitTcpServer.gd | 109 +-- addons/gdUnit4/src/network/rpc/RPC.gd | 6 +- .../src/network/rpc/RPCClientConnect.gd | 2 +- .../src/network/rpc/RPCClientDisconnect.gd | 2 +- addons/gdUnit4/src/network/rpc/RPCData.gd | 11 - .../gdUnit4/src/network/rpc/RPCGdUnitEvent.gd | 4 +- .../src/network/rpc/RPCGdUnitTestSuite.gd | 9 +- addons/gdUnit4/src/network/rpc/RPCMessage.gd | 7 +- .../src/network/rpc/dtos/GdUnitResourceDto.gd | 6 +- .../src/network/rpc/dtos/GdUnitTestCaseDto.gd | 2 +- .../network/rpc/dtos/GdUnitTestSuiteDto.gd | 19 +- .../gdUnit4/src/report/GdUnitByPathReport.gd | 24 +- addons/gdUnit4/src/report/GdUnitHtmlReport.gd | 72 +- .../gdUnit4/src/report/GdUnitReportSummary.gd | 16 +- .../src/report/GdUnitTestCaseReport.gd | 18 +- .../src/report/GdUnitTestSuiteReport.gd | 25 +- addons/gdUnit4/src/report/JUnitXmlReport.gd | 38 +- addons/gdUnit4/src/report/XmlElement.gd | 35 +- .../src/report/template/folder_report.html | 6 +- addons/gdUnit4/src/report/template/index.html | 8 +- .../src/report/template/suite_report.html | 4 +- addons/gdUnit4/src/spy/GdUnitSpyBuilder.gd | 26 +- .../src/spy/GdUnitSpyFunctionDoubler.gd | 20 +- addons/gdUnit4/src/spy/GdUnitSpyImpl.gd | 10 +- .../src/ui/EditorFileSystemControls.gd | 33 - addons/gdUnit4/src/ui/GdUnitConsole.gd | 87 +- addons/gdUnit4/src/ui/GdUnitFonts.gd | 17 +- addons/gdUnit4/src/ui/GdUnitInspector.gd | 62 +- addons/gdUnit4/src/ui/GdUnitInspector.tscn | 19 +- .../src/ui/GdUnitInspectorTreeConstants.gd | 18 + addons/gdUnit4/src/ui/GdUnitUiTools.gd | 148 +++ addons/gdUnit4/src/ui/ScriptEditorControls.gd | 59 +- addons/gdUnit4/src/ui/assets/PlayDebug.svg | 121 --- .../src/ui/assets/PlayDebug.svg.import | 38 - addons/gdUnit4/src/ui/assets/PlayOverall.svg | 61 -- .../src/ui/assets/PlayOverall.svg.import | 38 - addons/gdUnit4/src/ui/assets/TestCase.svg | 62 -- .../gdUnit4/src/ui/assets/TestCase.svg.import | 38 - .../gdUnit4/src/ui/assets/TestCaseError.svg | 65 -- .../src/ui/assets/TestCaseError.svg.import | 38 - .../gdUnit4/src/ui/assets/TestCaseFailed.svg | 66 -- .../src/ui/assets/TestCaseFailed.svg.import | 38 - .../gdUnit4/src/ui/assets/TestCaseSuccess.svg | 66 -- .../src/ui/assets/TestCaseSuccess.svg.import | 38 - .../src/ui/assets/TestCase_error_orphan.tres | 12 - .../src/ui/assets/TestCase_failed_orphan.tres | 12 - .../ui/assets/TestCase_success_orphan.tres | 12 - addons/gdUnit4/src/ui/assets/TestSuite.svg | 70 -- .../src/ui/assets/TestSuite.svg.import | 38 - addons/gdUnit4/src/ui/assets/clock.svg | 151 --- addons/gdUnit4/src/ui/assets/clock.svg.import | 38 - addons/gdUnit4/src/ui/assets/errors.svg | 57 -- .../gdUnit4/src/ui/assets/errors.svg.import | 38 - addons/gdUnit4/src/ui/assets/failures.svg | 56 -- .../gdUnit4/src/ui/assets/failures.svg.import | 38 - addons/gdUnit4/src/ui/assets/icon.png | Bin 13817 -> 0 bytes addons/gdUnit4/src/ui/assets/icon.png.import | 34 - .../src/ui/assets/orphan/TestCaseError1.svg | 69 -- .../assets/orphan/TestCaseError1.svg.import | 38 - .../src/ui/assets/orphan/TestCaseError2.svg | 166 ---- .../assets/orphan/TestCaseError2.svg.import | 38 - .../src/ui/assets/orphan/TestCaseFailed.svg | 66 -- .../assets/orphan/TestCaseFailed.svg.import | 38 - .../src/ui/assets/orphan/TestCaseFailed1.svg | 69 -- .../assets/orphan/TestCaseFailed1.svg.import | 38 - .../src/ui/assets/orphan/TestCaseFailed2.svg | 166 ---- .../assets/orphan/TestCaseFailed2.svg.import | 38 - .../src/ui/assets/orphan/TestCaseSuccess1.svg | 129 --- .../assets/orphan/TestCaseSuccess1.svg.import | 38 - .../src/ui/assets/orphan/TestCaseSuccess2.svg | 166 ---- .../assets/orphan/TestCaseSuccess2.svg.import | 38 - .../assets/orphan/orphan_animated_icon.tres | 32 - .../src/ui/assets/orphan/orphan_green.svg | 156 --- .../ui/assets/orphan/orphan_green.svg.import | 38 - .../src/ui/assets/orphan/orphan_red1.svg | 156 --- .../ui/assets/orphan/orphan_red1.svg.import | 38 - .../src/ui/assets/orphan/orphan_red2.svg | 156 --- .../ui/assets/orphan/orphan_red2.svg.import | 38 - .../src/ui/assets/orphan/orphan_red3.svg | 156 --- .../ui/assets/orphan/orphan_red3.svg.import | 38 - .../src/ui/assets/orphan/orphan_red4.svg | 156 --- .../ui/assets/orphan/orphan_red4.svg.import | 38 - .../src/ui/assets/orphan/orphan_red5.svg | 156 --- .../ui/assets/orphan/orphan_red5.svg.import | 38 - .../src/ui/assets/orphan/orphan_red6.svg | 156 --- .../ui/assets/orphan/orphan_red6.svg.import | 38 - .../src/ui/assets/orphan/orphan_red7.svg | 156 --- .../ui/assets/orphan/orphan_red7.svg.import | 38 - addons/gdUnit4/src/ui/assets/running.png | Bin 515 -> 0 bytes .../gdUnit4/src/ui/assets/running.png.import | 34 - addons/gdUnit4/src/ui/assets/spinner.tres | 30 - .../src/ui/assets/spinner/Progress1.svg | 1 - .../ui/assets/spinner/Progress1.svg.import | 38 - .../src/ui/assets/spinner/Progress2.svg | 1 - .../ui/assets/spinner/Progress2.svg.import | 38 - .../src/ui/assets/spinner/Progress3.svg | 1 - .../ui/assets/spinner/Progress3.svg.import | 38 - .../src/ui/assets/spinner/Progress4.svg | 1 - .../ui/assets/spinner/Progress4.svg.import | 38 - .../src/ui/assets/spinner/Progress5.svg | 1 - .../ui/assets/spinner/Progress5.svg.import | 38 - .../src/ui/assets/spinner/Progress6.svg | 1 - .../ui/assets/spinner/Progress6.svg.import | 38 - .../src/ui/assets/spinner/Progress7.svg | 1 - .../ui/assets/spinner/Progress7.svg.import | 38 - .../src/ui/assets/spinner/Progress8.svg | 1 - .../ui/assets/spinner/Progress8.svg.import | 38 - .../EditorFileSystemContextMenuHandler.gd | 59 +- .../src/ui/menu/GdUnitContextMenuItem.gd | 40 +- .../ui/menu/ScriptEditorContextMenuHandler.gd | 38 +- .../gdUnit4/src/ui/parts/InspectorMonitor.gd | 26 +- .../src/ui/parts/InspectorMonitor.tscn | 133 +-- .../src/ui/parts/InspectorProgressBar.gd | 23 +- .../src/ui/parts/InspectorProgressBar.tscn | 1 - .../src/ui/parts/InspectorStatusBar.gd | 150 ++- .../src/ui/parts/InspectorStatusBar.tscn | 306 ++++-- .../gdUnit4/src/ui/parts/InspectorToolBar.gd | 61 +- .../src/ui/parts/InspectorToolBar.tscn | 193 +++- .../src/ui/parts/InspectorTreeMainPanel.gd | 901 +++++++++++++----- .../src/ui/parts/InspectorTreePanel.tscn | 247 ++++- .../src/ui/settings/GdUnitInputCapture.gd | 27 +- .../src/ui/settings/GdUnitSettingsDialog.gd | 140 ++- .../src/ui/settings/GdUnitSettingsDialog.tscn | 418 +++++++- addons/gdUnit4/src/ui/settings/logo.png | Bin 0 -> 49775 bytes .../src/ui/templates/TestSuiteTemplate.gd | 31 +- addons/gdUnit4/src/update/GdMarkDownReader.gd | 76 +- addons/gdUnit4/src/update/GdUnitPatch.gd | 2 +- addons/gdUnit4/src/update/GdUnitPatcher.gd | 18 +- addons/gdUnit4/src/update/GdUnitUpdate.gd | 49 +- .../gdUnit4/src/update/GdUnitUpdateClient.gd | 37 +- .../gdUnit4/src/update/GdUnitUpdateNotify.gd | 66 +- test/actions/clear_count_action.gd | 1 + test/actions/count_up_action.gd | 1 + test/beehave_tree_test.gd | 8 +- test/before_after_run_test.gd | 14 +- test/blackboard/blackboard_register_action.gd | 2 - test/blackboard/blackboard_test.gd | 31 +- test/blackboard/shared_blackboard_scene.gd | 1 - test/conditions/value_reached_condition.gd | 1 + test/debug/debugger_test.gd | 5 +- test/debug/debugger_test_scene.gd | 3 +- test/e2e_test.gd | 3 +- test/nodes/composites/selector_random_test.gd | 4 +- .../composites/selector_reactive_test.gd | 43 +- test/nodes/composites/selector_test.gd | 22 +- test/nodes/composites/sequence_random_test.gd | 47 +- .../composites/sequence_reactive_test.gd | 40 +- test/nodes/composites/sequence_star_test.gd | 33 +- test/nodes/composites/sequence_test.gd | 15 +- test/nodes/composites/simple_parallel_test.gd | 43 +- test/nodes/decorators/cooldown_test.gd | 16 +- test/nodes/decorators/delayer_test.gd | 17 +- test/nodes/decorators/failer_test.gd | 6 +- test/nodes/decorators/inverter_test.gd | 7 +- test/nodes/decorators/limiter_test.gd | 8 +- test/nodes/decorators/repeater_test.gd | 13 +- test/nodes/decorators/succeeder_test.gd | 6 +- test/nodes/decorators/time_limiter_test.gd | 8 +- test/nodes/decorators/until_fail_test.gd | 21 +- .../leaves/actions/blackboard_erase_test.gd | 6 +- .../leaves/actions/blackboard_set_test.gd | 13 +- .../conditions/blackboard_compare_test.gd | 30 +- .../leaves/conditions/blackboard_has_test.gd | 17 +- .../RuntimeChangesTestScene.gd | 1 + .../runtime_changes/runtime_changes_test.gd | 106 +-- .../selector_random_weights.gd | 17 +- .../selector_random_weights_test.gd | 19 +- .../sequence_random_weights.gd | 2 +- .../sequence_random_weights_test.gd | 21 +- test/unit_test_scene.gd | 4 +- 357 files changed, 7084 insertions(+), 8789 deletions(-) delete mode 100644 .github/actions/godot-install/action.yml delete mode 100644 .github/actions/publish-test-report/action.yml delete mode 100644 .github/actions/unit-test/action.yml delete mode 100644 .github/actions/upload-test-report/action.yml delete mode 100644 .github/workflows/unit-tests.yml create mode 100644 addons/gdUnit4/src/core/GdUnitFileAccess.gd create mode 100644 addons/gdUnit4/src/core/discovery/GdUnitTestDiscoverGuard.gd create mode 100644 addons/gdUnit4/src/core/discovery/GdUnitTestDiscoverer.gd create mode 100644 addons/gdUnit4/src/core/event/GdUnitEventTestDiscoverEnd.gd create mode 100644 addons/gdUnit4/src/core/event/GdUnitEventTestDiscoverStart.gd create mode 100644 addons/gdUnit4/src/core/event/GdUnitEventTestDiscoverTestAdded.gd create mode 100644 addons/gdUnit4/src/core/event/GdUnitEventTestDiscoverTestRemoved.gd create mode 100644 addons/gdUnit4/src/core/event/GdUnitEventTestDiscoverTestSuiteAdded.gd delete mode 100644 addons/gdUnit4/src/core/parse/GdTestParameterSet.gd create mode 100644 addons/gdUnit4/src/core/parse/GdUnitTestParameterSetResolver.gd create mode 100644 addons/gdUnit4/src/fuzzers/FloatFuzzer.gd create mode 100644 addons/gdUnit4/src/mono/GdUnit4CSharpApi.cs rename addons/gdUnit4/src/mono/{GdUnit4MonoApiLoader.gd => GdUnit4CSharpApiLoader.gd} (67%) delete mode 100644 addons/gdUnit4/src/mono/GdUnit4MonoApi.cs delete mode 100644 addons/gdUnit4/src/network/rpc/RPCData.gd delete mode 100644 addons/gdUnit4/src/ui/EditorFileSystemControls.gd create mode 100644 addons/gdUnit4/src/ui/GdUnitInspectorTreeConstants.gd create mode 100644 addons/gdUnit4/src/ui/GdUnitUiTools.gd delete mode 100644 addons/gdUnit4/src/ui/assets/PlayDebug.svg delete mode 100644 addons/gdUnit4/src/ui/assets/PlayDebug.svg.import delete mode 100644 addons/gdUnit4/src/ui/assets/PlayOverall.svg delete mode 100644 addons/gdUnit4/src/ui/assets/PlayOverall.svg.import delete mode 100644 addons/gdUnit4/src/ui/assets/TestCase.svg delete mode 100644 addons/gdUnit4/src/ui/assets/TestCase.svg.import delete mode 100644 addons/gdUnit4/src/ui/assets/TestCaseError.svg delete mode 100644 addons/gdUnit4/src/ui/assets/TestCaseError.svg.import delete mode 100644 addons/gdUnit4/src/ui/assets/TestCaseFailed.svg delete mode 100644 addons/gdUnit4/src/ui/assets/TestCaseFailed.svg.import delete mode 100644 addons/gdUnit4/src/ui/assets/TestCaseSuccess.svg delete mode 100644 addons/gdUnit4/src/ui/assets/TestCaseSuccess.svg.import delete mode 100644 addons/gdUnit4/src/ui/assets/TestCase_error_orphan.tres delete mode 100644 addons/gdUnit4/src/ui/assets/TestCase_failed_orphan.tres delete mode 100644 addons/gdUnit4/src/ui/assets/TestCase_success_orphan.tres delete mode 100644 addons/gdUnit4/src/ui/assets/TestSuite.svg delete mode 100644 addons/gdUnit4/src/ui/assets/TestSuite.svg.import delete mode 100644 addons/gdUnit4/src/ui/assets/clock.svg delete mode 100644 addons/gdUnit4/src/ui/assets/clock.svg.import delete mode 100644 addons/gdUnit4/src/ui/assets/errors.svg delete mode 100644 addons/gdUnit4/src/ui/assets/errors.svg.import delete mode 100644 addons/gdUnit4/src/ui/assets/failures.svg delete mode 100644 addons/gdUnit4/src/ui/assets/failures.svg.import delete mode 100644 addons/gdUnit4/src/ui/assets/icon.png delete mode 100644 addons/gdUnit4/src/ui/assets/icon.png.import delete mode 100644 addons/gdUnit4/src/ui/assets/orphan/TestCaseError1.svg delete mode 100644 addons/gdUnit4/src/ui/assets/orphan/TestCaseError1.svg.import delete mode 100644 addons/gdUnit4/src/ui/assets/orphan/TestCaseError2.svg delete mode 100644 addons/gdUnit4/src/ui/assets/orphan/TestCaseError2.svg.import delete mode 100644 addons/gdUnit4/src/ui/assets/orphan/TestCaseFailed.svg delete mode 100644 addons/gdUnit4/src/ui/assets/orphan/TestCaseFailed.svg.import delete mode 100644 addons/gdUnit4/src/ui/assets/orphan/TestCaseFailed1.svg delete mode 100644 addons/gdUnit4/src/ui/assets/orphan/TestCaseFailed1.svg.import delete mode 100644 addons/gdUnit4/src/ui/assets/orphan/TestCaseFailed2.svg delete mode 100644 addons/gdUnit4/src/ui/assets/orphan/TestCaseFailed2.svg.import delete mode 100644 addons/gdUnit4/src/ui/assets/orphan/TestCaseSuccess1.svg delete mode 100644 addons/gdUnit4/src/ui/assets/orphan/TestCaseSuccess1.svg.import delete mode 100644 addons/gdUnit4/src/ui/assets/orphan/TestCaseSuccess2.svg delete mode 100644 addons/gdUnit4/src/ui/assets/orphan/TestCaseSuccess2.svg.import delete mode 100644 addons/gdUnit4/src/ui/assets/orphan/orphan_animated_icon.tres delete mode 100644 addons/gdUnit4/src/ui/assets/orphan/orphan_green.svg delete mode 100644 addons/gdUnit4/src/ui/assets/orphan/orphan_green.svg.import delete mode 100644 addons/gdUnit4/src/ui/assets/orphan/orphan_red1.svg delete mode 100644 addons/gdUnit4/src/ui/assets/orphan/orphan_red1.svg.import delete mode 100644 addons/gdUnit4/src/ui/assets/orphan/orphan_red2.svg delete mode 100644 addons/gdUnit4/src/ui/assets/orphan/orphan_red2.svg.import delete mode 100644 addons/gdUnit4/src/ui/assets/orphan/orphan_red3.svg delete mode 100644 addons/gdUnit4/src/ui/assets/orphan/orphan_red3.svg.import delete mode 100644 addons/gdUnit4/src/ui/assets/orphan/orphan_red4.svg delete mode 100644 addons/gdUnit4/src/ui/assets/orphan/orphan_red4.svg.import delete mode 100644 addons/gdUnit4/src/ui/assets/orphan/orphan_red5.svg delete mode 100644 addons/gdUnit4/src/ui/assets/orphan/orphan_red5.svg.import delete mode 100644 addons/gdUnit4/src/ui/assets/orphan/orphan_red6.svg delete mode 100644 addons/gdUnit4/src/ui/assets/orphan/orphan_red6.svg.import delete mode 100644 addons/gdUnit4/src/ui/assets/orphan/orphan_red7.svg delete mode 100644 addons/gdUnit4/src/ui/assets/orphan/orphan_red7.svg.import delete mode 100644 addons/gdUnit4/src/ui/assets/running.png delete mode 100644 addons/gdUnit4/src/ui/assets/running.png.import delete mode 100644 addons/gdUnit4/src/ui/assets/spinner.tres delete mode 100644 addons/gdUnit4/src/ui/assets/spinner/Progress1.svg delete mode 100644 addons/gdUnit4/src/ui/assets/spinner/Progress1.svg.import delete mode 100644 addons/gdUnit4/src/ui/assets/spinner/Progress2.svg delete mode 100644 addons/gdUnit4/src/ui/assets/spinner/Progress2.svg.import delete mode 100644 addons/gdUnit4/src/ui/assets/spinner/Progress3.svg delete mode 100644 addons/gdUnit4/src/ui/assets/spinner/Progress3.svg.import delete mode 100644 addons/gdUnit4/src/ui/assets/spinner/Progress4.svg delete mode 100644 addons/gdUnit4/src/ui/assets/spinner/Progress4.svg.import delete mode 100644 addons/gdUnit4/src/ui/assets/spinner/Progress5.svg delete mode 100644 addons/gdUnit4/src/ui/assets/spinner/Progress5.svg.import delete mode 100644 addons/gdUnit4/src/ui/assets/spinner/Progress6.svg delete mode 100644 addons/gdUnit4/src/ui/assets/spinner/Progress6.svg.import delete mode 100644 addons/gdUnit4/src/ui/assets/spinner/Progress7.svg delete mode 100644 addons/gdUnit4/src/ui/assets/spinner/Progress7.svg.import delete mode 100644 addons/gdUnit4/src/ui/assets/spinner/Progress8.svg delete mode 100644 addons/gdUnit4/src/ui/assets/spinner/Progress8.svg.import create mode 100644 addons/gdUnit4/src/ui/settings/logo.png diff --git a/.github/actions/godot-install/action.yml b/.github/actions/godot-install/action.yml deleted file mode 100644 index b517fefc..00000000 --- a/.github/actions/godot-install/action.yml +++ /dev/null @@ -1,89 +0,0 @@ -name: install-godot-binary -description: "Installs the Godot Runtime" - -inputs: - godot-version: - description: "The Godot engine version" - type: string - required: true - godot-status-version: - description: "The Godot engine status version" - type: string - required: true - godot-mono: - required: false - type: boolean - default: false - godot-bin-name: - type: string - required: true - godot-cache-path: - type: string - required: true - - -runs: - using: composite - steps: - - - name: "Set Cache Name" - shell: bash - run: | - if ${{inputs.godot-mono == 'true'}}; then - echo "CACHE_NAME=${{ runner.OS }}-Godot_v${{ inputs.godot-version }}-${{ inputs.godot-status-version }}_mono" >> "$GITHUB_ENV" - else - echo "CACHE_NAME=${{ runner.OS }}-Godot_v${{ inputs.godot-version }}-${{ inputs.godot-status-version }}" >> "$GITHUB_ENV" - fi - - - name: "Build Cache" - uses: actions/cache@v3 - id: godot-cache-binary - with: - path: ${{ inputs.godot-cache-path }} - key: ${{ env.CACHE_NAME }} - restore-keys: ${{ env.CACHE_NAME }} - - - name: "Download and Install Godot ${{ inputs.godot-version }}" - if: steps.godot-cache-binary.outputs.cache-hit != 'true' - continue-on-error: false - shell: bash - run: | - mkdir -p ${{ inputs.godot-cache-path }} - chmod 770 ${{ inputs.godot-cache-path }} - DIR="$HOME/.config/godot" - if [ ! -d "$DIR" ]; then - mkdir -p "$DIR" - chmod 770 "$DIR" - fi - - DOWNLOAD_URL=https://github.com/godotengine/godot/releases/download/${{ inputs.godot-version }}-${{ inputs.godot-status-version }} - GODOT_BIN=Godot_v${{ inputs.godot-version }}-${{ inputs.godot-status-version }}_${{ inputs.godot-bin-name }} - if ${{inputs.godot-mono == 'true'}}; then - GODOT_BIN=Godot_v${{ inputs.godot-version }}-${{ inputs.godot-status-version }}_mono_${{ inputs.godot-bin-name }} - DOWNLOAD_URL=$DOWNLOAD_URL/mono - fi - - GODOT_PACKAGE=$GODOT_BIN.zip - wget $DOWNLOAD_URL/$GODOT_PACKAGE -P ${{ inputs.godot-cache-path }} - unzip ${{ inputs.godot-cache-path }}/$GODOT_PACKAGE -d ${{ inputs.godot-cache-path }} - - if ${{runner.OS == 'Linux'}}; then - echo "Run linux part" - if ${{inputs.godot-mono == 'true'}}; then - mv ${{ inputs.godot-cache-path }}/$GODOT_BIN/* ${{ inputs.godot-cache-path }}/ - rm -rf ${{ inputs.godot-cache-path }}/$GODOT_BIN* - mv ${{ inputs.godot-cache-path }}/Godot_v${{ inputs.godot-version }}* ${{ inputs.godot-cache-path }}/godot - else - mv ${{ inputs.godot-cache-path }}/$GODOT_BIN ${{ inputs.godot-cache-path }}/godot - fi - - chmod u+x ${{ inputs.godot-cache-path }}/godot - echo "${{ inputs.godot-cache-path }}/godot" - else - echo "Run windows part" - pwd - mv ${{ inputs.godot-cache-path }}/$GODOT_BIN ${{ inputs.godot-cache-path }}/godot.exe - chmod u+x ${{ inputs.godot-cache-path }}/godot.exe - ${{ inputs.godot-cache-path }}/godot.exe --version - echo "${{ inputs.godot-cache-path }}/godot.exe" - fi diff --git a/.github/actions/publish-test-report/action.yml b/.github/actions/publish-test-report/action.yml deleted file mode 100644 index 034c2270..00000000 --- a/.github/actions/publish-test-report/action.yml +++ /dev/null @@ -1,24 +0,0 @@ -name: publish-test-report -description: "Publishes the GdUnit test results" - -inputs: - report-name: - description: "Name of the check run which will be created." - required: true - -runs: - using: composite - steps: - - - name: 'Fixing the dubious git 128 error' - shell: bash - run: | - git config --global --add safe.directory '*' - - - name: 'Publish Test Results' - uses: dorny/test-reporter@v1.6.0 - with: - name: test_report_${{ inputs.report-name }} - path: 'reports/**/results.xml' - reporter: java-junit - fail-on-error: 'false' \ No newline at end of file diff --git a/.github/actions/unit-test/action.yml b/.github/actions/unit-test/action.yml deleted file mode 100644 index eaf7b600..00000000 --- a/.github/actions/unit-test/action.yml +++ /dev/null @@ -1,45 +0,0 @@ -name: unit-test -description: "Runs the unit tests on GdUnit4 API" - -inputs: - test-includes: - description: "The path to include tests to be run" - required: true - godot-bin: - required: true - -runs: - using: composite - - steps: - - name: "Unit Test Linux" - if: ${{ runner.OS == 'Linux' }} - env: - GODOT_BIN: "/home/runner/godot-linux/godot" - shell: bash - run: | - chmod +x ./addons/gdUnit4/runtest.sh - xvfb-run --auto-servernum ./addons/gdUnit4/runtest.sh --add ${{ inputs.test-includes }} --audio-driver Dummy --display-driver x11 --rendering-driver opengl3 --screen 0 --continue --verbose - - -# not tested yet - - name: "Unit Test Windows cmd" - if: ${{ runner.OS == 'Windows' }} - env: - GODOT_BIN: "C:\\Users\\runneradmin/godot-win/godot.exe" - shell: cmd - run: | - echo "%HOMEPATH%" - set unix_path=${{ inputs.godot-bin }} - set win_path=%unix_path:~1% - set win_path=%HOMEPATH%%unix_path:~1% - set win_path=%win_path:/=\% - echo "%win_path%" - echo "%GODOT_BIN%" - - %GODOT_BIN% -s -d .\addons\gdUnit4\bin\GdUnitCmdTool.gd - - - # chmod +x ./runtest.cmd - # ./runtest.cmd --add ${{ inputs.test-includes }} --continue - # set GODOT_BIN=%win_path% \ No newline at end of file diff --git a/.github/actions/upload-test-report/action.yml b/.github/actions/upload-test-report/action.yml deleted file mode 100644 index 9f1c8646..00000000 --- a/.github/actions/upload-test-report/action.yml +++ /dev/null @@ -1,16 +0,0 @@ -name: upload-test-report -description: "Uploads the GdUnit test reports" - -inputs: - report-name: - description: "Name of the report to be upload." - required: true - -runs: - using: composite - steps: - - name: Collect Test Artifacts - uses: actions/upload-artifact@v3 - with: - name: test_report_${{ inputs.report-name }} - path: reports/** \ No newline at end of file diff --git a/.github/workflows/beehave-ci.yml b/.github/workflows/beehave-ci.yml index 610ae0ea..2c3f4004 100644 --- a/.github/workflows/beehave-ci.yml +++ b/.github/workflows/beehave-ci.yml @@ -5,18 +5,18 @@ on: branches: - "godot-4.x" paths-ignore: - - '**.jpg' - - '**.png' - - '**.svg' - - '**.md' - - '**plugin.cfg' + - "**.jpg" + - "**.png" + - "**.svg" + - "**.md" + - "**plugin.cfg" pull_request: paths-ignore: - - '**.jpg' - - '**.png' - - '**.svg' - - '**.md' - - '**plugin.cfg' + - "**.jpg" + - "**.png" + - "**.svg" + - "**.md" + - "**plugin.cfg" workflow_dispatch: concurrency: @@ -25,28 +25,45 @@ concurrency: jobs: unit-tests: - permissions: write-all + runs-on: ubuntu-latest + permissions: + actions: write + checks: write + contents: write + pull-requests: write + statuses: write strategy: fail-fast: false max-parallel: 10 matrix: - godot-version: ['4.0.4', '4.1.3', '4.2.1'] + godot-version: ["4.2.2"] name: "๐Ÿค– CI on Godot ${{ matrix.godot-version }}" - uses: ./.github/workflows/unit-tests.yml - with: - godot-version: ${{ matrix.godot-version }} + steps: + - name: Checkout repository + uses: actions/checkout@v2 + + - name: Run GDUnit4 tests + uses: MikeSchulze/gdUnit4-action@v1.1.1 + with: + godot-version: ${{ matrix.godot-version }} + godot-status: "stable" + timeout: 3 + paths: "res://test" + version: "installed" + publish-report: false + report-name: report_Godot${{ matrix.godot-version }}_unit-tests finalize: if: ${{ !cancelled() }} runs-on: ubuntu-latest - name: Final Results + name: "Final Results" needs: [unit-tests] steps: - run: exit 1 if: >- ${{ - contains(needs.*.result, 'failure') - || contains(needs.*.result, 'cancelled') + contains(needs.*.result, 'failure') + || contains(needs.*.result, 'cancelled') }} diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml deleted file mode 100644 index ecce7c76..00000000 --- a/.github/workflows/unit-tests.yml +++ /dev/null @@ -1,83 +0,0 @@ -name: unit-tests -run-name: ${{ github.head_ref || github.ref_name }}-unit-tests - -on: - workflow_call: - inputs: - os: - required: false - type: string - default: 'ubuntu-22.04' - godot-version: - required: true - type: string - - workflow_dispatch: - inputs: - os: - required: false - type: string - default: 'ubuntu-22.04' - godot-version: - required: true - type: string - -concurrency: - group: unit-tests-${{ github.head_ref || github.ref_name }}-${{ inputs.godot-version }} - cancel-in-progress: true - -jobs: - - unit-test: - name: "Unit Tests" - runs-on: ${{ inputs.os }} - timeout-minutes: 15 - - steps: - - name: "๐Ÿ“ฆ Checkout Beehave Repository" - uses: actions/checkout@v3 - with: - lfs: true - submodules: 'recursive' - - - name: "๐Ÿค– Install Godot ${{ inputs.godot-version }}" - uses: ./.github/actions/godot-install - with: - godot-version: ${{ inputs.godot-version }} - godot-mono: false - godot-status-version: 'stable' - godot-bin-name: 'linux.x86_64' - godot-cache-path: '~/godot-linux' - - - name: "โ™ป๏ธ Update Project" - if: ${{ !cancelled() }} - timeout-minutes: 1 - continue-on-error: true # we still ignore the timeout, the script is not quit and we run into a timeout - shell: bash - run: | - xvfb-run --auto-servernum ~/godot-linux/godot -e --path . -s res://addons/gdUnit4/bin/ProjectScanner.gd --headless --audio-driver Dummy - - - name: "๐Ÿงช Run Unit Tests" - if: ${{ !cancelled() }} - timeout-minutes: 4 - uses: ./.github/actions/unit-test - with: - godot-bin: ${{ env.GODOT_BIN }} - test-includes: "res://test" - - #- name: "โœ Set Report Name" - # if: ${{ always() }} - # shell: bash - # run: echo "REPORT_NAME=${{ inputs.os }}-${{ inputs.godot-version }}" >> "$GITHUB_ENV" - - #- name: "๐Ÿ“š Publish Unit Test Reports" - # if: ${{ !cancelled() }} - # uses: ./.github/actions/publish-test-report - # with: - # report-name: ${{ env.REPORT_NAME }} - - #- name: "๐ŸŽ‰ Upload Unit Test Reports" - # if: ${{ !cancelled() }} - # uses: ./.github/actions/upload-test-report - # with: - # report-name: ${{ env.REPORT_NAME }} diff --git a/README.md b/README.md index cd194caa..feae6176 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,7 @@ In order to avoid bugs creeping into the codebase, every feature is covered by u # ๐Ÿ“ฆ Installation 1. [Download Latest Release](https://github.com/bitbrain/beehave/releases/latest) - - (optional) access latest build for [Godot 3.x](https://github.com/bitbrain/beehave/archive/refs/heads/godot-3.x.zip), [Godot 4.x](https://github.com/bitbrain/beehave/archive/refs/heads/godot-4.x.zip) + - (optional) access latest build for [Godot 3.x](https://github.com/bitbrain/beehave/archive/refs/heads/godot-3.x.zip), [Godot 4.x](https://github.com/bitbrain/beehave/archive/refs/heads/godot-4.x.zip) 2. Unpack the `addons/beehave` folder into your `/addons` folder within the Godot project 3. Enable this addon within the Godot settings: `Project > Project Settings > Plugins` 4. Move `script_templates` into your project folder. @@ -49,6 +49,8 @@ To better understand what branch to choose from for which Godot version, please |---|---|--| |`3.x`|`3.x`|`1.x`| |`4.x`|`4.x`|`2.x`| +|`4.1.x`|`4.x`|`2.7.x`| +|`4.0.x`|`4.x`|`2.7.x`| Refer to [this guide](https://bitbra.in/2023/09/03/godot-addon-compatibility.html) for more details behind this structure. @@ -68,7 +70,6 @@ Behavior trees are a modular way to build AI logic for your game. For simple AI, [Liam Flannery](https://twitter.com/liamflannery56) wrote up a getting started [tutorial](https://medium.com/@liam.flannery56/easily-creating-behaviour-trees-in-godot-4-2-beehave-tutorial-ff8a911d43a0) that's up to date with Godot 4.2 - # ๐Ÿฅฐ Credits - logo designs by [@NathanHoad](https://twitter.com/nathanhoad) & [@StuartDeVille](https://twitter.com/StuartDeVille) diff --git a/addons/beehave/blackboard.gd b/addons/beehave/blackboard.gd index efc0da9a..39482121 100644 --- a/addons/beehave/blackboard.gd +++ b/addons/beehave/blackboard.gd @@ -1,10 +1,8 @@ @icon("icons/blackboard.svg") class_name Blackboard extends Node - const DEFAULT = "default" - ## The blackboard is an object that can be used to store and access data between ## multiple nodes of the behavior tree. @export var blackboard: Dictionary = {}: @@ -12,8 +10,7 @@ const DEFAULT = "default" blackboard = b _data[DEFAULT] = blackboard - -var _data:Dictionary = {} +var _data: Dictionary = {} func _ready(): @@ -25,6 +22,7 @@ func keys() -> Array[String]: keys.assign(_data.keys().duplicate()) return keys + func set_value(key: Variant, value: Variant, blackboard_name: String = DEFAULT) -> void: if not _data.has(blackboard_name): _data[blackboard_name] = {} @@ -32,14 +30,20 @@ func set_value(key: Variant, value: Variant, blackboard_name: String = DEFAULT) _data[blackboard_name][key] = value -func get_value(key: Variant, default_value: Variant = null, blackboard_name: String = DEFAULT) -> Variant: +func get_value( + key: Variant, default_value: Variant = null, blackboard_name: String = DEFAULT +) -> Variant: if has_value(key, blackboard_name): return _data[blackboard_name].get(key, default_value) return default_value func has_value(key: Variant, blackboard_name: String = DEFAULT) -> bool: - return _data.has(blackboard_name) and _data[blackboard_name].has(key) and _data[blackboard_name][key] != null + return ( + _data.has(blackboard_name) + and _data[blackboard_name].has(key) + and _data[blackboard_name][key] != null + ) func erase_value(key: Variant, blackboard_name: String = DEFAULT) -> void: diff --git a/addons/beehave/debug/debugger.gd b/addons/beehave/debug/debugger.gd index 246ecd2e..73bdc50f 100644 --- a/addons/beehave/debug/debugger.gd +++ b/addons/beehave/debug/debugger.gd @@ -17,7 +17,7 @@ func _capture(message: String, data: Array, session_id: int) -> bool: # in case the behavior tree has invalid setup this might be null if debugger_tab == null: return false - + if message == "beehave:register_tree": debugger_tab.register_tree(data[0]) return true @@ -63,7 +63,10 @@ func _on_make_floating() -> void: floating_window = Window.new() var panel := Panel.new() - panel.add_theme_stylebox_override("panel", editor_interface.get_base_control().get_theme_stylebox("PanelForeground", "EditorStyles")) + panel.add_theme_stylebox_override( + "panel", + editor_interface.get_base_control().get_theme_stylebox("PanelForeground", "EditorStyles") + ) panel.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT) floating_window.add_child(panel) diff --git a/addons/beehave/debug/debugger_messages.gd b/addons/beehave/debug/debugger_messages.gd index 3fb49fbb..6e0a0551 100644 --- a/addons/beehave/debug/debugger_messages.gd +++ b/addons/beehave/debug/debugger_messages.gd @@ -28,4 +28,3 @@ static func process_begin(instance_id: int) -> void: static func process_end(instance_id: int) -> void: if can_send_message(): EngineDebugger.send_message("beehave:process_end", [instance_id]) - diff --git a/addons/beehave/debug/frames.gd b/addons/beehave/debug/frames.gd index 6e310dbb..a9f6aa8f 100644 --- a/addons/beehave/debug/frames.gd +++ b/addons/beehave/debug/frames.gd @@ -1,10 +1,8 @@ @tool extends RefCounted - const BeehaveUtils := preload("res://addons/beehave/utils/utils.gd") - const SUCCESS_COLOR := Color("#009944c8") const NORMAL_COLOR := Color("#15181e") const FAILURE_COLOR := Color("#cf000f80") @@ -26,9 +24,21 @@ func _init() -> void: empty = StyleBoxEmpty.new() - normal = plugin.get_editor_interface().get_base_control().get_theme_stylebox(&"frame", &"GraphNode").duplicate() - - success = plugin.get_editor_interface().get_base_control().get_theme_stylebox(&"selected_frame", &"GraphNode").duplicate() + normal = ( + plugin + . get_editor_interface() + . get_base_control() + . get_theme_stylebox(&"frame", &"GraphNode") + . duplicate() + ) + + success = ( + plugin + . get_editor_interface() + . get_base_control() + . get_theme_stylebox(&"selected_frame", &"GraphNode") + . duplicate() + ) failure = success.duplicate() running = success.duplicate() diff --git a/addons/beehave/debug/graph_edit.gd b/addons/beehave/debug/graph_edit.gd index 709d46c9..98e5097e 100644 --- a/addons/beehave/debug/graph_edit.gd +++ b/addons/beehave/debug/graph_edit.gd @@ -11,7 +11,6 @@ const INACTIVE_COLOR: Color = Color("#898989aa") const ACTIVE_COLOR: Color = Color("#ffcc00c8") const SUCCESS_COLOR: Color = Color("#009944c8") - var updating_graph: bool = false var arraging_nodes: bool = false var beehave_tree: Dictionary: @@ -32,14 +31,13 @@ var horizontal_layout: bool = false: _update_layout_button() _update_graph() - -var frames:RefCounted +var frames: RefCounted var active_nodes: Array[String] var progress: int = 0 var layout_button: Button -func _init(frames:RefCounted) -> void: +func _init(frames: RefCounted) -> void: self.frames = frames @@ -271,11 +269,15 @@ func _draw() -> void: # Godot 4.0+ if from.has_method("get_connection_output_position"): - output_port_position = from.position + from.call("get_connection_output_position", c.from_port) + output_port_position = ( + from.position + from.call("get_connection_output_position", c.from_port) + ) input_port_position = to.position + to.call("get_connection_input_position", c.to_port) # Godot 4.2+ else: - output_port_position = from.position + from.call("get_output_port_position", c.from_port) + output_port_position = ( + from.position + from.call("get_output_port_position", c.from_port) + ) input_port_position = to.position + to.call("get_input_port_position", c.to_port) var line := _get_connection_line(output_port_position, input_port_position) @@ -302,4 +304,6 @@ func _draw() -> void: func _update_layout_button() -> void: layout_button.icon = VERTICAL_LAYOUT_ICON if horizontal_layout else HORIZONTAL_LAYOUT_ICON - layout_button.tooltip_text = "Switch to Vertical layout" if horizontal_layout else "Switch to Horizontal layout" + layout_button.tooltip_text = ( + "Switch to Vertical layout" if horizontal_layout else "Switch to Horizontal layout" + ) diff --git a/addons/beehave/debug/graph_node.gd b/addons/beehave/debug/graph_node.gd index 912fa781..a0a978b0 100644 --- a/addons/beehave/debug/graph_node.gd +++ b/addons/beehave/debug/graph_node.gd @@ -1,10 +1,8 @@ @tool extends GraphNode - const BeehaveUtils := preload("res://addons/beehave/utils/utils.gd") - const DEFAULT_COLOR := Color("#dad4cb") const PORT_TOP_ICON := preload("icons/port_top.svg") @@ -12,7 +10,6 @@ const PORT_BOTTOM_ICON := preload("icons/port_bottom.svg") const PORT_LEFT_ICON := preload("icons/port_left.svg") const PORT_RIGHT_ICON := preload("icons/port_right.svg") - @export var title_text: String: set(value): title_text = value @@ -35,7 +32,6 @@ var layout_size: float: get: return size.y if horizontal else size.x - var panel: PanelContainer var icon_rect: TextureRect var title_label: Label @@ -46,7 +42,7 @@ var frames: RefCounted var horizontal: bool = false -func _init(frames:RefCounted, horizontal: bool = false) -> void: +func _init(frames: RefCounted, horizontal: bool = false) -> void: self.frames = frames self.horizontal = horizontal @@ -73,7 +69,9 @@ func _ready() -> void: var title_size := 24 * BeehaveUtils.get_editor_scale() var margin_container := MarginContainer.new() - margin_container.add_theme_constant_override("margin_top", -title_size - 2 * BeehaveUtils.get_editor_scale()) + margin_container.add_theme_constant_override( + "margin_top", -title_size - 2 * BeehaveUtils.get_editor_scale() + ) margin_container.mouse_filter = Control.MOUSE_FILTER_PASS vbox_container.add_child(margin_container) @@ -119,18 +117,34 @@ func set_status(status: int) -> void: func _get_stylebox(status: int) -> StyleBox: match status: - 0: return frames.success - 1: return frames.failure - 2: return frames.running - _: return frames.normal + 0: + return frames.success + 1: + return frames.failure + 2: + return frames.running + _: + return frames.normal func set_slots(left_enabled: bool, right_enabled: bool) -> void: if horizontal: - set_slot(1, left_enabled, 0, Color.WHITE, right_enabled, 0, Color.WHITE, PORT_LEFT_ICON, PORT_RIGHT_ICON) + set_slot( + 1, + left_enabled, + 0, + Color.WHITE, + right_enabled, + 0, + Color.WHITE, + PORT_LEFT_ICON, + PORT_RIGHT_ICON + ) else: set_slot(0, left_enabled, 0, Color.WHITE, false, -2, Color.TRANSPARENT, PORT_TOP_ICON, null) - set_slot(2, false, -1, Color.TRANSPARENT, right_enabled, 0, Color.WHITE, null, PORT_BOTTOM_ICON) + set_slot( + 2, false, -1, Color.TRANSPARENT, right_enabled, 0, Color.WHITE, null, PORT_BOTTOM_ICON + ) func set_color(color: Color) -> void: @@ -147,4 +161,6 @@ func set_output_color(color: Color) -> void: func _on_size_changed(): - add_theme_constant_override("port_offset", 12 * BeehaveUtils.get_editor_scale() if horizontal else round(size.x / 2.0)) + add_theme_constant_override( + "port_offset", 12 * BeehaveUtils.get_editor_scale() if horizontal else round(size.x / 2.0) + ) diff --git a/addons/beehave/debug/tree_node.gd b/addons/beehave/debug/tree_node.gd index 81cab813..13779706 100644 --- a/addons/beehave/debug/tree_node.gd +++ b/addons/beehave/debug/tree_node.gd @@ -100,7 +100,11 @@ func _calculate_initial_x(node: TreeNode) -> void: _calculate_initial_x(child) if node.is_leaf(): if not node.is_most_left(): - node.x = node.get_previous_sibling().x + node.get_previous_sibling().item.layout_size + SIBLING_DISTANCE + node.x = ( + node.get_previous_sibling().x + + node.get_previous_sibling().item.layout_size + + SIBLING_DISTANCE + ) else: node.x = 0 else: @@ -111,12 +115,24 @@ func _calculate_initial_x(node: TreeNode) -> void: else: var left_child := node.get_most_left_child() var right_child := node.get_most_right_child() - mid = (left_child.x + right_child.x + right_child.item.layout_size - node.item.layout_size) / 2 + mid = ( + ( + left_child.x + + right_child.x + + right_child.item.layout_size + - node.item.layout_size + ) + / 2 + ) if node.is_most_left(): node.x = mid else: - node.x = node.get_previous_sibling().x + node.get_previous_sibling().item.layout_size + SIBLING_DISTANCE + node.x = ( + node.get_previous_sibling().x + + node.get_previous_sibling().item.layout_size + + SIBLING_DISTANCE + ) node.mod = node.x - mid if not node.is_leaf() and not node.is_most_left(): @@ -150,7 +166,7 @@ func _check_for_conflicts(node: TreeNode) -> void: var shift_value: float = 0 var shift_sibling: TreeNode = null - var node_contour: Dictionary = {}# { int, float } + var node_contour: Dictionary = {} # { int, float } _get_left_contour(node, 0, node_contour) var sibling := node.get_most_left_sibling() @@ -158,7 +174,9 @@ func _check_for_conflicts(node: TreeNode) -> void: var sibling_contour: Dictionary = {} _get_right_contour(sibling, 0, sibling_contour) - for level in range(node.y + 1, min(sibling_contour.keys().max(), node_contour.keys().max()) + 1): + for level in range( + node.y + 1, min(sibling_contour.keys().max(), node_contour.keys().max()) + 1 + ): var distance: float = node_contour[level] - sibling_contour[level] if distance + shift_value < min_distance: shift_value = min_distance - distance diff --git a/addons/beehave/metrics/beehave_global_metrics.gd b/addons/beehave/metrics/beehave_global_metrics.gd index 59720b0a..ef29db28 100644 --- a/addons/beehave/metrics/beehave_global_metrics.gd +++ b/addons/beehave/metrics/beehave_global_metrics.gd @@ -13,13 +13,13 @@ func _enter_tree() -> void: func register_tree(tree) -> void: if _registered_trees.has(tree): return - + _registered_trees.append(tree) _tree_count += 1 - + if tree.enabled: _active_tree_count += 1 - + tree.tree_enabled.connect(_on_tree_enabled) tree.tree_disabled.connect(_on_tree_disabled) @@ -27,13 +27,13 @@ func register_tree(tree) -> void: func unregister_tree(tree) -> void: if not _registered_trees.has(tree): return - + _registered_trees.erase(tree) _tree_count -= 1 - + if tree.enabled: _active_tree_count -= 1 - + tree.tree_enabled.disconnect(_on_tree_enabled) tree.tree_disabled.disconnect(_on_tree_disabled) @@ -41,7 +41,7 @@ func unregister_tree(tree) -> void: func _get_total_trees() -> int: return _tree_count - + func _get_total_enabled_trees() -> int: return _active_tree_count diff --git a/addons/beehave/nodes/composites/composite.gd b/addons/beehave/nodes/composites/composite.gd index 4a8fa5a5..53a8e901 100644 --- a/addons/beehave/nodes/composites/composite.gd +++ b/addons/beehave/nodes/composites/composite.gd @@ -11,7 +11,9 @@ func _get_configuration_warnings() -> PackedStringArray: var warnings: PackedStringArray = super._get_configuration_warnings() if get_children().filter(func(x): return x is BeehaveNode).size() < 2: - warnings.append("Any composite node should have at least two children. Otherwise it is not useful.") + warnings.append( + "Any composite node should have at least two children. Otherwise it is not useful." + ) return warnings diff --git a/addons/beehave/nodes/composites/randomized_composite.gd b/addons/beehave/nodes/composites/randomized_composite.gd index cc6dfe24..2afa5e9d 100644 --- a/addons/beehave/nodes/composites/randomized_composite.gd +++ b/addons/beehave/nodes/composites/randomized_composite.gd @@ -32,7 +32,7 @@ func _ready(): func _connect_children_changing_signals(): if not child_entered_tree.is_connected(_on_child_entered_tree): child_entered_tree.connect(_on_child_entered_tree) - + if not child_exiting_tree.is_connected(_on_child_exiting_tree): child_exiting_tree.connect(_on_child_exiting_tree) @@ -41,30 +41,35 @@ func get_shuffled_children() -> Array[Node]: var children_bag: Array[Node] = get_children().duplicate() if use_weights: var weights: Array[int] - weights.assign(children_bag.map(func (child): return _weights[child.name])) + weights.assign(children_bag.map(func(child): return _weights[child.name])) children_bag.assign(_weighted_shuffle(children_bag, weights)) else: children_bag.shuffle() return children_bag -## Returns a shuffled version of a given array using the supplied array of weights. +## Returns a shuffled version of a given array using the supplied array of weights. ## Think of weights as the chance of a given item being the first in the array. func _weighted_shuffle(items: Array, weights: Array[int]) -> Array: if len(items) != len(weights): - push_error("items and weights size mismatch: expected %d weights, got %d instead." % [len(items), len(weights)]) + push_error( + ( + "items and weights size mismatch: expected %d weights, got %d instead." + % [len(items), len(weights)] + ) + ) return items - - # This method is based on the weighted random sampling algorithm + + # This method is based on the weighted random sampling algorithm # by Efraimidis, Spirakis; 2005. This runs in O(n log(n)). - + # For each index, it will calculate random_value^(1/weight). var chance_calc = func(i): return [i, randf() ** (1.0 / weights[i])] var random_distribuition = range(len(items)).map(chance_calc) - + # Now we just have to order by the calculated value, descending. random_distribuition.sort_custom(func(a, b): return a[1] > b[1]) - + return random_distribuition.map(func(dist): return items[dist[0]]) @@ -73,23 +78,25 @@ func _get_property_list(): if use_weights: for key in _weights.keys(): - properties.append({ - "name": WEIGHTS_PREFIX + key, - "type": TYPE_INT, - "usage": PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_EDITOR, - "hint": PROPERTY_HINT_RANGE, - "hint_string": "1,100" - }) - + properties.append( + { + "name": WEIGHTS_PREFIX + key, + "type": TYPE_INT, + "usage": PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_EDITOR, + "hint": PROPERTY_HINT_RANGE, + "hint_string": "1,100" + } + ) + return properties - - + + func _set(property: StringName, value: Variant) -> bool: if property.begins_with(WEIGHTS_PREFIX): var weight_name = property.trim_prefix(WEIGHTS_PREFIX) _weights[weight_name] = value return true - + return false @@ -97,7 +104,7 @@ func _get(property: StringName): if property.begins_with(WEIGHTS_PREFIX): var weight_name = property.trim_prefix(WEIGHTS_PREFIX) return _weights[weight_name] - + return null @@ -126,7 +133,7 @@ func _on_child_entered_tree(node: Node): var renamed_callable = _on_child_renamed.bind(node.name, node) if not node.renamed.is_connected(renamed_callable): node.renamed.connect(renamed_callable) - + if not node.tree_exited.is_connected(_on_child_tree_exited): node.tree_exited.connect(_on_child_tree_exited.bind(node)) @@ -143,22 +150,20 @@ func _on_child_tree_exited(node: Node) -> void: var children = get_children() children.erase(node) _update_weights(children) - + if node.tree_exited.is_connected(_on_child_tree_exited): node.tree_exited.disconnect(_on_child_tree_exited) func _on_child_renamed(old_name: String, renamed_child: Node): if old_name == renamed_child.name: - return # No need to update the weights. - + return # No need to update the weights. + # Disconnect signal with old name... - renamed_child.renamed\ - .disconnect(_on_child_renamed.bind(old_name, renamed_child)) + renamed_child.renamed.disconnect(_on_child_renamed.bind(old_name, renamed_child)) # ...and connect with the new name. - renamed_child.renamed\ - .connect(_on_child_renamed.bind(renamed_child.name, renamed_child)) - + renamed_child.renamed.connect(_on_child_renamed.bind(renamed_child.name, renamed_child)) + var original_weight = _weights[old_name] _weights.erase(old_name) _weights[renamed_child.name] = original_weight diff --git a/addons/beehave/nodes/composites/selector_random.gd b/addons/beehave/nodes/composites/selector_random.gd index a85e83cb..7a423001 100644 --- a/addons/beehave/nodes/composites/selector_random.gd +++ b/addons/beehave/nodes/composites/selector_random.gd @@ -10,6 +10,7 @@ class_name SelectorRandomComposite extends RandomizedComposite var _children_bag: Array[Node] = [] var c: Node + func _ready() -> void: super() if random_seed == 0: @@ -72,7 +73,7 @@ func _get_reversed_indexes() -> Array[int]: func _reset() -> void: var new_order = get_shuffled_children() _children_bag = new_order.duplicate() - _children_bag.reverse() # It needs to run the children in reverse order. + _children_bag.reverse() # It needs to run the children in reverse order. func get_class_name() -> Array[StringName]: diff --git a/addons/beehave/nodes/composites/selector_reactive.gd b/addons/beehave/nodes/composites/selector_reactive.gd index b0d5d23d..fa164d05 100644 --- a/addons/beehave/nodes/composites/selector_reactive.gd +++ b/addons/beehave/nodes/composites/selector_reactive.gd @@ -7,6 +7,7 @@ class_name SelectorReactiveComposite extends Composite ## return `FAILURE`. ## If a child returns `RUNNING` it will restart. + func tick(actor: Node, blackboard: Blackboard) -> int: for c in get_children(): if c != running_child: diff --git a/addons/beehave/nodes/composites/sequence.gd b/addons/beehave/nodes/composites/sequence.gd index f5517654..d308e8d0 100644 --- a/addons/beehave/nodes/composites/sequence.gd +++ b/addons/beehave/nodes/composites/sequence.gd @@ -13,7 +13,6 @@ var successful_index: int = 0 func tick(actor: Node, blackboard: Blackboard) -> int: for c in get_children(): - if c.get_index() < successful_index: continue diff --git a/addons/beehave/nodes/composites/sequence_random.gd b/addons/beehave/nodes/composites/sequence_random.gd index a61bd929..a744f52f 100644 --- a/addons/beehave/nodes/composites/sequence_random.gd +++ b/addons/beehave/nodes/composites/sequence_random.gd @@ -86,7 +86,7 @@ func _get_reversed_indexes() -> Array[int]: func _reset() -> void: var new_order = get_shuffled_children() _children_bag = new_order.duplicate() - _children_bag.reverse() # It needs to run the children in reverse order. + _children_bag.reverse() # It needs to run the children in reverse order. reset.emit(new_order) diff --git a/addons/beehave/nodes/composites/sequence_reactive.gd b/addons/beehave/nodes/composites/sequence_reactive.gd index ade4b32a..f40d8ab2 100644 --- a/addons/beehave/nodes/composites/sequence_reactive.gd +++ b/addons/beehave/nodes/composites/sequence_reactive.gd @@ -15,10 +15,10 @@ func tick(actor: Node, blackboard: Blackboard) -> int: for c in get_children(): if c.get_index() < successful_index: continue - + if c != running_child: c.before_run(actor, blackboard) - + var response = c.tick(actor, blackboard) if can_send_message(blackboard): BeehaveDebuggerMessages.process_tick(c.get_instance_id(), response) @@ -26,7 +26,7 @@ func tick(actor: Node, blackboard: Blackboard) -> int: if c is ConditionLeaf: blackboard.set_value("last_condition", c, str(actor.get_instance_id())) blackboard.set_value("last_condition_status", response, str(actor.get_instance_id())) - + match response: SUCCESS: successful_index += 1 @@ -51,7 +51,8 @@ func tick(actor: Node, blackboard: Blackboard) -> int: func interrupt(actor: Node, blackboard: Blackboard) -> void: _reset() super(actor, blackboard) - + + func _reset() -> void: successful_index = 0 diff --git a/addons/beehave/nodes/composites/sequence_star.gd b/addons/beehave/nodes/composites/sequence_star.gd index a6400e53..36bb19ce 100644 --- a/addons/beehave/nodes/composites/sequence_star.gd +++ b/addons/beehave/nodes/composites/sequence_star.gd @@ -50,7 +50,7 @@ func interrupt(actor: Node, blackboard: Blackboard) -> void: _reset() super(actor, blackboard) - + func _reset() -> void: successful_index = 0 diff --git a/addons/beehave/nodes/composites/simple_parallel.gd b/addons/beehave/nodes/composites/simple_parallel.gd index 62974812..4133d600 100644 --- a/addons/beehave/nodes/composites/simple_parallel.gd +++ b/addons/beehave/nodes/composites/simple_parallel.gd @@ -2,11 +2,11 @@ @icon("../../icons/simple_parallel.svg") class_name SimpleParallelComposite extends Composite -## Simple Parallel nodes will attampt to execute all chidren at same time and -## can only have exactly two children. First child as primary node, second +## Simple Parallel nodes will attampt to execute all chidren at same time and +## can only have exactly two children. First child as primary node, second ## child as secondary node. -## This node will always report primary node's state, and continue tick while -## primary node return 'RUNNING'. The state of secondary node will be ignored +## This node will always report primary node's state, and continue tick while +## primary node return 'RUNNING'. The state of secondary node will be ignored ## and executed like a subtree. ## If primary node return 'SUCCESS' or 'FAILURE', this node will interrupt ## secondary node and return primary node's result. @@ -14,41 +14,43 @@ class_name SimpleParallelComposite extends Composite ## finish its action after primary node terminates. #how many times should secondary node repeat, zero means loop forever -@export var secondary_node_repeat_count:int = 0 +@export var secondary_node_repeat_count: int = 0 #wether to wait secondary node finish its current action after primary node finished -@export var delay_mode:bool = false +@export var delay_mode: bool = false var delayed_result := SUCCESS -var main_task_finished:bool = false -var secondary_node_running:bool = false -var secondary_node_repeat_left:int = 0 +var main_task_finished: bool = false +var secondary_node_running: bool = false +var secondary_node_repeat_left: int = 0 + func _get_configuration_warnings() -> PackedStringArray: var warnings: PackedStringArray = super._get_configuration_warnings() if get_child_count() != 2: warnings.append("SimpleParallel should have exactly two child nodes.") - + if not get_child(0) is ActionLeaf: warnings.append("SimpleParallel should have an action leaf node as first child node.") return warnings - + + func tick(actor, blackboard: Blackboard): for c in get_children(): var node_index = c.get_index() if node_index == 0 and not main_task_finished: if c != running_child: c.before_run(actor, blackboard) - + var response = c.tick(actor, blackboard) if can_send_message(blackboard): BeehaveDebuggerMessages.process_tick(c.get_instance_id(), response) - + delayed_result = response match response: - SUCCESS,FAILURE: + SUCCESS, FAILURE: _cleanup_running_task(c, actor, blackboard) c.after_run(actor, blackboard) main_task_finished = true @@ -76,14 +78,16 @@ func tick(actor, blackboard: Blackboard): elif secondary_node_repeat_left > 0: secondary_node_repeat_left -= 1 else: - secondary_node_running = true + secondary_node_running = true return RUNNING -func before_run(actor: Node, blackboard:Blackboard) -> void: + +func before_run(actor: Node, blackboard: Blackboard) -> void: secondary_node_repeat_left = secondary_node_repeat_count super(actor, blackboard) + func interrupt(actor: Node, blackboard: Blackboard) -> void: if not main_task_finished: get_child(0).interrupt(actor, blackboard) @@ -92,14 +96,17 @@ func interrupt(actor: Node, blackboard: Blackboard) -> void: _reset() super(actor, blackboard) + func after_run(actor: Node, blackboard: Blackboard) -> void: _reset() super(actor, blackboard) + func _reset() -> void: main_task_finished = false secondary_node_running = false + ## Changes `running_action` and `running_child` after the node finishes executing. func _cleanup_running_task(finished_action: Node, actor: Node, blackboard: Blackboard): var blackboard_name = str(actor.get_instance_id()) @@ -107,7 +114,8 @@ func _cleanup_running_task(finished_action: Node, actor: Node, blackboard: Black running_child = null if finished_action == blackboard.get_value("running_action", null, blackboard_name): blackboard.set_value("running_action", null, blackboard_name) - + + func get_class_name() -> Array[StringName]: var classes := super() classes.push_back(&"SimpleParallelComposite") diff --git a/addons/beehave/nodes/decorators/cooldown.gd b/addons/beehave/nodes/decorators/cooldown.gd index ee084f03..84c27b74 100644 --- a/addons/beehave/nodes/decorators/cooldown.gd +++ b/addons/beehave/nodes/decorators/cooldown.gd @@ -8,44 +8,42 @@ class_name CooldownDecorator ## The timer resets the next time its child is executed and it is not `RUNNING` ## The wait time in seconds -@export var wait_time: = 0.0 +@export var wait_time := 0.0 -@onready var cache_key = 'cooldown_%s' % self.get_instance_id() +@onready var cache_key = "cooldown_%s" % self.get_instance_id() func tick(actor: Node, blackboard: Blackboard) -> int: var c = get_child(0) var remaining_time = blackboard.get_value(cache_key, 0.0, str(actor.get_instance_id())) var response - + if c != running_child: c.before_run(actor, blackboard) - + if remaining_time > 0: response = FAILURE - + remaining_time -= get_physics_process_delta_time() blackboard.set_value(cache_key, remaining_time, str(actor.get_instance_id())) - + if can_send_message(blackboard): BeehaveDebuggerMessages.process_tick(self.get_instance_id(), response) else: response = c.tick(actor, blackboard) - + if can_send_message(blackboard): BeehaveDebuggerMessages.process_tick(c.get_instance_id(), response) - + if c is ConditionLeaf: blackboard.set_value("last_condition", c, str(actor.get_instance_id())) blackboard.set_value("last_condition_status", response, str(actor.get_instance_id())) - + if response == RUNNING and c is ActionLeaf: running_child = c blackboard.set_value("running_action", c, str(actor.get_instance_id())) - + if response != RUNNING: blackboard.set_value(cache_key, wait_time, str(actor.get_instance_id())) - - return response - + return response diff --git a/addons/beehave/nodes/decorators/delayer.gd b/addons/beehave/nodes/decorators/delayer.gd index 2fdac858..2b8b73cf 100644 --- a/addons/beehave/nodes/decorators/delayer.gd +++ b/addons/beehave/nodes/decorators/delayer.gd @@ -8,42 +8,42 @@ class_name DelayDecorator ## The timer resets when both it and its child are not `RUNNING` ## The wait time in seconds -@export var wait_time: = 0.0 +@export var wait_time := 0.0 + +@onready var cache_key = "time_limiter_%s" % self.get_instance_id() -@onready var cache_key = 'time_limiter_%s' % self.get_instance_id() func tick(actor: Node, blackboard: Blackboard) -> int: var c = get_child(0) var total_time = blackboard.get_value(cache_key, 0.0, str(actor.get_instance_id())) var response - + if c != running_child: c.before_run(actor, blackboard) - + if total_time < wait_time: response = RUNNING - + total_time += get_physics_process_delta_time() blackboard.set_value(cache_key, total_time, str(actor.get_instance_id())) - + if can_send_message(blackboard): BeehaveDebuggerMessages.process_tick(self.get_instance_id(), response) else: response = c.tick(actor, blackboard) - + if can_send_message(blackboard): BeehaveDebuggerMessages.process_tick(c.get_instance_id(), response) - + if c is ConditionLeaf: blackboard.set_value("last_condition", c, str(actor.get_instance_id())) blackboard.set_value("last_condition_status", response, str(actor.get_instance_id())) - + if response == RUNNING and c is ActionLeaf: running_child = c blackboard.set_value("running_action", c, str(actor.get_instance_id())) - + if response != RUNNING: blackboard.set_value(cache_key, 0.0, str(actor.get_instance_id())) - - return response + return response diff --git a/addons/beehave/nodes/decorators/failer.gd b/addons/beehave/nodes/decorators/failer.gd index d5e937c6..e47312fe 100644 --- a/addons/beehave/nodes/decorators/failer.gd +++ b/addons/beehave/nodes/decorators/failer.gd @@ -4,6 +4,7 @@ class_name AlwaysFailDecorator extends Decorator ## A Failer node will always return a `FAILURE` status code. + func tick(actor: Node, blackboard: Blackboard) -> int: var c = get_child(0) diff --git a/addons/beehave/nodes/decorators/inverter.gd b/addons/beehave/nodes/decorators/inverter.gd index 9f2d2389..25706774 100644 --- a/addons/beehave/nodes/decorators/inverter.gd +++ b/addons/beehave/nodes/decorators/inverter.gd @@ -5,6 +5,7 @@ class_name InverterDecorator extends Decorator ## An inverter will return `FAILURE` in case it's child returns a `SUCCESS` status ## code or `SUCCESS` in case its child returns a `FAILURE` status code. + func tick(actor: Node, blackboard: Blackboard) -> int: var c = get_child(0) diff --git a/addons/beehave/nodes/decorators/limiter.gd b/addons/beehave/nodes/decorators/limiter.gd index ae2695b2..a38d7f37 100644 --- a/addons/beehave/nodes/decorators/limiter.gd +++ b/addons/beehave/nodes/decorators/limiter.gd @@ -6,15 +6,16 @@ class_name LimiterDecorator extends Decorator ## maximum ticks is reached, it will return a `FAILURE` status code. ## The count resets the next time that a child is not `RUNNING` -@onready var cache_key = 'limiter_%s' % self.get_instance_id() +@onready var cache_key = "limiter_%s" % self.get_instance_id() + +@export var max_count: float = 0 -@export var max_count : float = 0 func tick(actor: Node, blackboard: Blackboard) -> int: if not get_child_count() == 1: return FAILURE - var child = get_child(0) + var child = get_child(0) var current_count = blackboard.get_value(cache_key, 0, str(actor.get_instance_id())) if current_count < max_count: @@ -30,17 +31,17 @@ func tick(actor: Node, blackboard: Blackboard) -> int: if child is ActionLeaf and response == RUNNING: running_child = child blackboard.set_value("running_action", child, str(actor.get_instance_id())) - + if response != RUNNING: child.after_run(actor, blackboard) - + return response else: interrupt(actor, blackboard) child.after_run(actor, blackboard) return FAILURE - - + + func before_run(actor: Node, blackboard: Blackboard) -> void: blackboard.set_value(cache_key, 0, str(actor.get_instance_id())) if get_child_count() > 0: @@ -51,7 +52,7 @@ func get_class_name() -> Array[StringName]: var classes := super() classes.push_back(&"LimiterDecorator") return classes - + func _get_configuration_warnings() -> PackedStringArray: if not get_child_count() == 1: diff --git a/addons/beehave/nodes/decorators/repeater.gd b/addons/beehave/nodes/decorators/repeater.gd index b271440e..647f7e0f 100644 --- a/addons/beehave/nodes/decorators/repeater.gd +++ b/addons/beehave/nodes/decorators/repeater.gd @@ -15,13 +15,13 @@ func before_run(actor: Node, blackboard: Blackboard): func tick(actor: Node, blackboard: Blackboard) -> int: var child = get_child(0) - + if current_count < repetitions: if running_child == null: child.before_run(actor, blackboard) var response = child.tick(actor, blackboard) - + if can_send_message(blackboard): BeehaveDebuggerMessages.process_tick(child.get_instance_id(), response) @@ -40,13 +40,13 @@ func tick(actor: Node, blackboard: Blackboard) -> int: if running_child != null: running_child = null - + if response == FAILURE: return FAILURE - + if current_count >= repetitions: return SUCCESS - + return RUNNING else: return SUCCESS diff --git a/addons/beehave/nodes/decorators/succeeder.gd b/addons/beehave/nodes/decorators/succeeder.gd index d6601deb..344b90f6 100644 --- a/addons/beehave/nodes/decorators/succeeder.gd +++ b/addons/beehave/nodes/decorators/succeeder.gd @@ -4,6 +4,7 @@ class_name AlwaysSucceedDecorator extends Decorator ## A succeeder node will always return a `SUCCESS` status code. + func tick(actor: Node, blackboard: Blackboard) -> int: var c = get_child(0) diff --git a/addons/beehave/nodes/decorators/time_limiter.gd b/addons/beehave/nodes/decorators/time_limiter.gd index b9fa93fd..d9638d73 100644 --- a/addons/beehave/nodes/decorators/time_limiter.gd +++ b/addons/beehave/nodes/decorators/time_limiter.gd @@ -3,12 +3,12 @@ class_name TimeLimiterDecorator extends Decorator ## The Time Limit Decorator will give its `RUNNING` child a set amount of time to finish -## before interrupting it and return a `FAILURE` status code. +## before interrupting it and return a `FAILURE` status code. ## The timer resets the next time that a child is not `RUNNING` -@export var wait_time: = 0.0 +@export var wait_time := 0.0 -@onready var cache_key = 'time_limiter_%s' % self.get_instance_id() +@onready var cache_key = "time_limiter_%s" % self.get_instance_id() func tick(actor: Node, blackboard: Blackboard) -> int: @@ -24,11 +24,11 @@ func tick(actor: Node, blackboard: Blackboard) -> int: var response = child.tick(actor, blackboard) if can_send_message(blackboard): BeehaveDebuggerMessages.process_tick(child.get_instance_id(), response) - + if child is ConditionLeaf: blackboard.set_value("last_condition", child, str(actor.get_instance_id())) blackboard.set_value("last_condition_status", response, str(actor.get_instance_id())) - + if response == RUNNING: running_child = child if child is ActionLeaf: diff --git a/addons/beehave/nodes/decorators/until_fail.gd b/addons/beehave/nodes/decorators/until_fail.gd index d8a97689..8f5ebbec 100644 --- a/addons/beehave/nodes/decorators/until_fail.gd +++ b/addons/beehave/nodes/decorators/until_fail.gd @@ -1,18 +1,19 @@ @tool @icon("../../icons/until_fail.svg") -class_name UntilFailDecorator +class_name UntilFailDecorator extends Decorator ## The UntilFail Decorator will return `RUNNING` if its child returns ## `SUCCESS` or `RUNNING` or it will return `SUCCESS` if its child returns ## `FAILURE` + func tick(actor: Node, blackboard: Blackboard) -> int: var c = get_child(0) - + if c != running_child: c.before_run(actor, blackboard) - + var response = c.tick(actor, blackboard) if can_send_message(blackboard): BeehaveDebuggerMessages.process_tick(c.get_instance_id(), response) @@ -20,7 +21,7 @@ func tick(actor: Node, blackboard: Blackboard) -> int: if c is ConditionLeaf: blackboard.set_value("last_condition", c, str(actor.get_instance_id())) blackboard.set_value("last_condition_status", response, str(actor.get_instance_id())) - + if response == RUNNING: running_child = c if c is ActionLeaf: @@ -28,6 +29,5 @@ func tick(actor: Node, blackboard: Blackboard) -> int: return RUNNING if response == SUCCESS: return RUNNING - - return SUCCESS + return SUCCESS diff --git a/addons/beehave/nodes/leaves/action.gd b/addons/beehave/nodes/leaves/action.gd index 7b0957dc..9074c078 100644 --- a/addons/beehave/nodes/leaves/action.gd +++ b/addons/beehave/nodes/leaves/action.gd @@ -7,6 +7,7 @@ class_name ActionLeaf extends Leaf ## frame executions. In this case, the node should return `RUNNING` until the ## action is completed. + func get_class_name() -> Array[StringName]: var classes := super() classes.push_back(&"ActionLeaf") diff --git a/addons/beehave/nodes/leaves/blackboard_compare.gd b/addons/beehave/nodes/leaves/blackboard_compare.gd index 3b29cd01..38a870dd 100644 --- a/addons/beehave/nodes/leaves/blackboard_compare.gd +++ b/addons/beehave/nodes/leaves/blackboard_compare.gd @@ -14,7 +14,6 @@ enum Operators { LESS_EQUAL, } - ## Expression represetning left operand. ## This value can be any valid GDScript expression. ## In order to use the existing blackboard keys for comparison, @@ -28,32 +27,37 @@ enum Operators { ## use get_value("key_name") e.g. get_value("direction").length() @export_placeholder(EXPRESSION_PLACEHOLDER) var right_operand: String = "" - @onready var _left_expression: Expression = _parse_expression(left_operand) @onready var _right_expression: Expression = _parse_expression(right_operand) func tick(actor: Node, blackboard: Blackboard) -> int: var left: Variant = _left_expression.execute([], blackboard) - + if _left_expression.has_execute_failed(): return FAILURE - + var right: Variant = _right_expression.execute([], blackboard) - + if _right_expression.has_execute_failed(): return FAILURE - + var result: bool = false - + match operator: - Operators.EQUAL: result = left == right - Operators.NOT_EQUAL: result = left != right - Operators.GREATER: result = left > right - Operators.LESS: result = left < right - Operators.GREATER_EQUAL: result = left >= right - Operators.LESS_EQUAL: result = left <= right - + Operators.EQUAL: + result = left == right + Operators.NOT_EQUAL: + result = left != right + Operators.GREATER: + result = left > right + Operators.LESS: + result = left < right + Operators.GREATER_EQUAL: + result = left >= right + Operators.LESS_EQUAL: + result = left <= right + return SUCCESS if result else FAILURE diff --git a/addons/beehave/nodes/leaves/blackboard_erase.gd b/addons/beehave/nodes/leaves/blackboard_erase.gd index 31e0a194..e5cc1d47 100644 --- a/addons/beehave/nodes/leaves/blackboard_erase.gd +++ b/addons/beehave/nodes/leaves/blackboard_erase.gd @@ -12,12 +12,12 @@ class_name BlackboardEraseAction extends ActionLeaf func tick(actor: Node, blackboard: Blackboard) -> int: var key_value: Variant = _key_expression.execute([], blackboard) - + if _key_expression.has_execute_failed(): return FAILURE - + blackboard.erase_value(key_value) - + return SUCCESS diff --git a/addons/beehave/nodes/leaves/blackboard_has.gd b/addons/beehave/nodes/leaves/blackboard_has.gd index ccd46d25..2ee5e92f 100644 --- a/addons/beehave/nodes/leaves/blackboard_has.gd +++ b/addons/beehave/nodes/leaves/blackboard_has.gd @@ -12,10 +12,10 @@ class_name BlackboardHasCondition extends ConditionLeaf func tick(actor: Node, blackboard: Blackboard) -> int: var key_value: Variant = _key_expression.execute([], blackboard) - + if _key_expression.has_execute_failed(): return FAILURE - + return SUCCESS if blackboard.has_value(key_value) else FAILURE diff --git a/addons/beehave/nodes/leaves/blackboard_set.gd b/addons/beehave/nodes/leaves/blackboard_set.gd index 144336d0..4f0ed9fc 100644 --- a/addons/beehave/nodes/leaves/blackboard_set.gd +++ b/addons/beehave/nodes/leaves/blackboard_set.gd @@ -9,24 +9,23 @@ class_name BlackboardSetAction extends ActionLeaf ## Expression representing a blackboard value to assign to the specified key. @export_placeholder(EXPRESSION_PLACEHOLDER) var value: String = "" - @onready var _key_expression: Expression = _parse_expression(key) @onready var _value_expression: Expression = _parse_expression(value) func tick(actor: Node, blackboard: Blackboard) -> int: var key_value: Variant = _key_expression.execute([], blackboard) - + if _key_expression.has_execute_failed(): return FAILURE - + var value_value: Variant = _value_expression.execute([], blackboard) - + if _value_expression.has_execute_failed(): return FAILURE - + blackboard.set_value(key_value, value_value) - + return SUCCESS diff --git a/addons/beehave/nodes/leaves/condition.gd b/addons/beehave/nodes/leaves/condition.gd index b3b5c4fe..f4610b4f 100644 --- a/addons/beehave/nodes/leaves/condition.gd +++ b/addons/beehave/nodes/leaves/condition.gd @@ -5,6 +5,7 @@ class_name ConditionLeaf extends Leaf ## Conditions are leaf nodes that either return SUCCESS or FAILURE depending on ## a single simple condition. They should never return `RUNNING`. + func get_class_name() -> Array[StringName]: var classes := super() classes.push_back(&"ConditionLeaf") diff --git a/addons/beehave/nodes/leaves/leaf.gd b/addons/beehave/nodes/leaves/leaf.gd index 6f485d72..4946c7d1 100644 --- a/addons/beehave/nodes/leaves/leaf.gd +++ b/addons/beehave/nodes/leaves/leaf.gd @@ -14,29 +14,31 @@ func _get_configuration_warnings() -> PackedStringArray: if children.any(func(x): return x is BeehaveNode): warnings.append("Leaf nodes should not have any child nodes. They won't be ticked.") - + for source in _get_expression_sources(): var error_text: String = _parse_expression(source).get_error_text() if not error_text.is_empty(): warnings.append("Expression `%s` is invalid! Error text: `%s`" % [source, error_text]) - + return warnings func _parse_expression(source: String) -> Expression: var result: Expression = Expression.new() var error: int = result.parse(source) - + if not Engine.is_editor_hint() and error != OK: push_error( - "[Leaf] Couldn't parse expression with source: `%s` Error text: `%s`" %\ - [source, result.get_error_text()] + ( + "[Leaf] Couldn't parse expression with source: `%s` Error text: `%s`" + % [source, result.get_error_text()] + ) ) - + return result -func _get_expression_sources() -> Array[String]: # virtual +func _get_expression_sources() -> Array[String]: # virtual return [] diff --git a/addons/beehave/plugin.gd b/addons/beehave/plugin.gd index 66601b2d..1e43ea7e 100644 --- a/addons/beehave/plugin.gd +++ b/addons/beehave/plugin.gd @@ -5,12 +5,14 @@ const BeehaveEditorDebugger := preload("debug/debugger.gd") var editor_debugger: BeehaveEditorDebugger var frames: RefCounted + func _init(): name = "BeehavePlugin" add_autoload_singleton("BeehaveGlobalMetrics", "metrics/beehave_global_metrics.gd") add_autoload_singleton("BeehaveGlobalDebugger", "debug/global_debugger.gd") print("Beehave initialized!") + func _enter_tree() -> void: editor_debugger = BeehaveEditorDebugger.new() frames = preload("debug/frames.gd").new() diff --git a/addons/beehave/utils/utils.gd b/addons/beehave/utils/utils.gd index 491f78b5..5f51ce78 100644 --- a/addons/beehave/utils/utils.gd +++ b/addons/beehave/utils/utils.gd @@ -1,5 +1,6 @@ @tool + static func get_plugin() -> EditorPlugin: var tree: SceneTree = Engine.get_main_loop() return tree.get_root().get_child(0).get_node_or_null("BeehavePlugin") diff --git a/addons/gdUnit4/bin/GdUnitBuildTool.gd b/addons/gdUnit4/bin/GdUnitBuildTool.gd index 21eeb5a1..c4ef66cc 100644 --- a/addons/gdUnit4/bin/GdUnitBuildTool.gd +++ b/addons/gdUnit4/bin/GdUnitBuildTool.gd @@ -2,7 +2,9 @@ extends SceneTree enum { - INIT, PROCESSING, EXIT + INIT, + PROCESSING, + EXIT } const RETURN_SUCCESS = 0 @@ -11,23 +13,34 @@ const RETURN_WARNING = 101 var _console := CmdConsole.new() var _cmd_options: = CmdOptions.new([ - CmdOption.new("-scp, --src_class_path", "-scp ", "The full class path of the source file.", TYPE_STRING), - CmdOption.new("-scl, --src_class_line", "-scl ", "The selected line number to generate test case.", TYPE_INT) - ]) + CmdOption.new( + "-scp, --src_class_path", + "-scp ", + "The full class path of the source file.", + TYPE_STRING + ), + CmdOption.new( + "-scl, --src_class_line", + "-scl ", + "The selected line number to generate test case.", + TYPE_INT + ) +]) var _status := INIT var _source_file :String = "" var _source_line :int = -1 -func _init(): +func _init() -> void: var cmd_parser := CmdArgumentParser.new(_cmd_options, "GdUnitBuildTool.gd") var result := cmd_parser.parse(OS.get_cmdline_args()) if result.is_error(): show_options() exit(RETURN_ERROR, result.error_message()); return - var cmd_options = result.value() + + var cmd_options :Array[CmdCommand] = result.value() for cmd in cmd_options: if cmd.name() == '-scp': _source_file = cmd.arguments()[0] @@ -44,18 +57,16 @@ func _init(): _status = PROCESSING -func _idle(_delta): +func _idle(_delta :float) -> void: if _status == PROCESSING: var script := ResourceLoader.load(_source_file) as Script if script == null: exit(RETURN_ERROR, "Can't load source file %s!" % _source_file) - var result := GdUnitTestSuiteBuilder.create(script, _source_line) if result.is_error(): print_json_error(result.error_message()) exit(RETURN_ERROR, result.error_message()) return - _console.prints_color("Added testcase: %s" % result.value(), Color.CORNFLOWER_BLUE) print_json_result(result.value()) exit(RETURN_SUCCESS) @@ -74,8 +85,8 @@ func exit(code :int, message :String = "") -> void: func print_json_result(result :Dictionary) -> void: # convert back to system path - var path = ProjectSettings.globalize_path(result["path"]); - var json = 'JSON_RESULT:{"TestCases" : [{"line":%d, "path": "%s"}]}' % [result["line"], path] + var path := ProjectSettings.globalize_path(result["path"]); + var json := 'JSON_RESULT:{"TestCases" : [{"line":%d, "path": "%s"}]}' % [result["line"], path] prints(json) @@ -86,7 +97,8 @@ func print_json_error(error :String) -> void: func show_options() -> void: _console.prints_color(" Usage:", Color.DARK_SALMON) _console.prints_color(" build -scp -scl ", Color.DARK_SALMON) - _console.prints_color("-- Options ---------------------------------------------------------------------------------------", Color.DARK_SALMON).new_line() + _console.prints_color("-- Options ---------------------------------------------------------------------------------------", + Color.DARK_SALMON).new_line() for option in _cmd_options.default_options(): descripe_option(option) diff --git a/addons/gdUnit4/bin/GdUnitCmdTool.gd b/addons/gdUnit4/bin/GdUnitCmdTool.gd index 6ab26f88..7e905afc 100644 --- a/addons/gdUnit4/bin/GdUnitCmdTool.gd +++ b/addons/gdUnit4/bin/GdUnitCmdTool.gd @@ -3,9 +3,11 @@ extends SceneTree const GdUnitTools := preload("res://addons/gdUnit4/src/core/GdUnitTools.gd") + #warning-ignore-all:return_value_discarded -class CLIRunner extends Node: - +class CLIRunner: + extends Node + enum { READY, INIT, @@ -13,61 +15,118 @@ class CLIRunner extends Node: STOP, EXIT } - + const DEFAULT_REPORT_COUNT = 20 - const RETURN_SUCCESS = 0 - const RETURN_ERROR = 100 - const RETURN_ERROR_HEADLESS_NOT_SUPPORTED = 103 - const RETURN_WARNING = 101 - - var _state = READY - var _test_suites_to_process :Array - var _executor - var _cs_executor - var _report :GdUnitHtmlReport + const RETURN_SUCCESS = 0 + const RETURN_ERROR = 100 + const RETURN_ERROR_HEADLESS_NOT_SUPPORTED = 103 + const RETURN_ERROR_GODOT_VERSION_NOT_SUPPORTED = 104 + const RETURN_WARNING = 101 + + var _state := READY + var _test_suites_to_process: Array + var _executor :Variant + var _cs_executor :Variant + var _report: GdUnitHtmlReport var _report_dir: String var _report_max: int = DEFAULT_REPORT_COUNT + var _headless_mode_ignore := false var _runner_config := GdUnitRunnerConfig.new() + var _runner_config_file := "" var _console := CmdConsole.new() - var _cmd_options: = CmdOptions.new([ - CmdOption.new("-a, --add", "-a ", "Adds the given test suite or directory to the execution pipeline.", TYPE_STRING), - CmdOption.new("-i, --ignore", "-i ", "Adds the given test suite or test case to the ignore list.", TYPE_STRING), - CmdOption.new("-c, --continue", "", "By default GdUnit will abort checked first test failure to be fail fast, instead of stop after first failure you can use this option to run the complete test set."), - CmdOption.new("-conf, --config", "-conf [testconfiguration.cfg]", "Run all tests by given test configuration. Default is 'GdUnitRunner.cfg'", TYPE_STRING, true), - CmdOption.new("-help", "", "Shows this help message."), - CmdOption.new("--help-advanced", "", "Shows advanced options.") - ], [ + var _cmd_options := CmdOptions.new([ + CmdOption.new( + "-a, --add", + "-a ", + "Adds the given test suite or directory to the execution pipeline.", + TYPE_STRING + ), + CmdOption.new( + "-i, --ignore", + "-i ", + "Adds the given test suite or test case to the ignore list.", + TYPE_STRING + ), + CmdOption.new( + "-c, --continue", + "", + """By default GdUnit will abort checked first test failure to be fail fast, + instead of stop after first failure you can use this option to run the complete test set.""".dedent() + ), + CmdOption.new( + "-conf, --config", + "-conf [testconfiguration.cfg]", + "Run all tests by given test configuration. Default is 'GdUnitRunner.cfg'", + TYPE_STRING, + true + ), + CmdOption.new( + "-help", "", + "Shows this help message." + ), + CmdOption.new("--help-advanced", "", + "Shows advanced options." + ) + ], + [ # advanced options - CmdOption.new("-rd, --report-directory", "-rd ", "Specifies the output directory in which the reports are to be written. The default is res://reports/.", TYPE_STRING, true), - CmdOption.new("-rc, --report-count", "-rc ", "Specifies how many reports are saved before they are deleted. The default is "+str(DEFAULT_REPORT_COUNT)+".", TYPE_INT, true), + CmdOption.new( + "-rd, --report-directory", + "-rd ", + "Specifies the output directory in which the reports are to be written. The default is res://reports/.", + TYPE_STRING, + true + ), + CmdOption.new( + "-rc, --report-count", + "-rc ", + "Specifies how many reports are saved before they are deleted. The default is %s." % str(DEFAULT_REPORT_COUNT), + TYPE_INT, + true + ), #CmdOption.new("--list-suites", "--list-suites [directory]", "Lists all test suites located in the given directory.", TYPE_STRING), #CmdOption.new("--describe-suite", "--describe-suite ", "Shows the description of selected test suite.", TYPE_STRING), - CmdOption.new("--info", "", "Shows the GdUnit version info"), - CmdOption.new("--selftest", "", "Runs the GdUnit self test"), + CmdOption.new( + "--info", "", + "Shows the GdUnit version info" + ), + CmdOption.new( + "--selftest", "", + "Runs the GdUnit self test" + ), + CmdOption.new( + "--ignoreHeadlessMode", + "--ignoreHeadlessMode", + "By default, running GdUnit4 in headless mode is not allowed. You can switch off the headless mode check by set this property." + ), ]) - - - func _ready(): + + + func _ready() -> void: _state = INIT - _report_dir = GdUnitTools.current_dir() + "reports" + _report_dir = GdUnitFileAccess.current_dir() + "reports" _executor = load("res://addons/gdUnit4/src/core/execution/GdUnitTestSuiteExecutor.gd").new() # stop checked first test failure to fail fast _executor.fail_fast(true) - - if GdUnit4MonoApiLoader.is_mono_supported(): - prints("GdUnit4Mono Version %s loaded." % GdUnit4MonoApiLoader.version()) - _cs_executor = GdUnit4MonoApiLoader.create_executor(self) - var err = GdUnitSignals.instance().gdunit_event.connect(Callable(self, "_on_gdunit_event")) + if GdUnit4CSharpApiLoader.is_mono_supported(): + prints("GdUnit4Net version '%s' loaded." % GdUnit4CSharpApiLoader.version()) + _cs_executor = GdUnit4CSharpApiLoader.create_executor(self) + var err := GdUnitSignals.instance().gdunit_event.connect(_on_gdunit_event) if err != OK: prints("gdUnitSignals failed") push_error("Error checked startup, can't connect executor for 'send_event'") quit(RETURN_ERROR) - - - func _process(_delta): + + + func _notification(what: int) -> void: + if what == NOTIFICATION_PREDELETE: + prints("Finallize .. done") + + + func _process(_delta :float) -> void: match _state: INIT: - gdUnitInit() + init_gd_unit() _state = RUN RUN: # all test suites executed @@ -87,98 +146,143 @@ class CLIRunner extends Node: _state = EXIT _on_gdunit_event(GdUnitStop.new()) quit(report_exit_code(_report)) - - - func quit(code :int) -> void: + + + func quit(code: int) -> void: + _cs_executor = null GdUnitTools.dispose_all() await GdUnitMemoryObserver.gc_on_guarded_instances() await get_tree().physics_frame get_tree().quit(code) - - - func set_report_dir(path :String) -> void: - _report_dir = ProjectSettings.globalize_path(GdUnitTools.make_qualified_path(path)) - _console.prints_color("Set write reports to %s" % _report_dir, Color.DEEP_SKY_BLUE) - - - func set_report_count(count :String) -> void: + + + func set_report_dir(path: String) -> void: + _report_dir = ProjectSettings.globalize_path(GdUnitFileAccess.make_qualified_path(path)) + _console.prints_color( + "Set write reports to %s" % _report_dir, + Color.DEEP_SKY_BLUE + ) + + + func set_report_count(count: String) -> void: var report_count := count.to_int() if report_count < 1: - _console.prints_error("Invalid report history count '%s' set back to default %d" % [count, DEFAULT_REPORT_COUNT]) + _console.prints_error( + "Invalid report history count '%s' set back to default %d" + % [count, DEFAULT_REPORT_COUNT] + ) _report_max = DEFAULT_REPORT_COUNT else: - _console.prints_color("Set report history count to %s" % count, Color.DEEP_SKY_BLUE) + _console.prints_color( + "Set report history count to %s" % count, + Color.DEEP_SKY_BLUE + ) _report_max = report_count - - + + func disable_fail_fast() -> void: - _console.prints_color("Disabled fail fast!", Color.DEEP_SKY_BLUE) + _console.prints_color( + "Disabled fail fast!", + Color.DEEP_SKY_BLUE + ) _executor.fail_fast(false) - - + + func run_self_test() -> void: - _console.prints_color("Run GdUnit4 self tests.", Color.DEEP_SKY_BLUE) + _console.prints_color( + "Run GdUnit4 self tests.", + Color.DEEP_SKY_BLUE + ) disable_fail_fast() _runner_config.self_test() - - + + func show_version() -> void: - _console.prints_color("Godot %s" % Engine.get_version_info().get("string"), Color.DARK_SALMON) - var config = ConfigFile.new() - config.load('addons/gdUnit4/plugin.cfg') - _console.prints_color("GdUnit4 %s" % config.get_value('plugin', 'version'), Color.DARK_SALMON) + _console.prints_color( + "Godot %s" % Engine.get_version_info().get("string"), + Color.DARK_SALMON + ) + var config := ConfigFile.new() + config.load("addons/gdUnit4/plugin.cfg") + _console.prints_color( + "GdUnit4 %s" % config.get_value("plugin", "version"), + Color.DARK_SALMON + ) quit(RETURN_SUCCESS) - - - func show_options(show_advanced :bool = false) -> void: - _console.prints_color(" Usage:", Color.DARK_SALMON) - _console.prints_color(" runtest -a ", Color.DARK_SALMON) - _console.prints_color(" runtest -a -i ", Color.DARK_SALMON).new_line() - _console.prints_color("-- Options ---------------------------------------------------------------------------------------", Color.DARK_SALMON).new_line() + + + func check_headless_mode() -> void: + _headless_mode_ignore = true + + + func show_options(show_advanced: bool = false) -> void: + _console.prints_color( + """ + Usage: + runtest -a + runtest -a -i + """.dedent(), + Color.DARK_SALMON + ).prints_color( + "-- Options ---------------------------------------------------------------------------------------", + Color.DARK_SALMON + ).new_line() for option in _cmd_options.default_options(): descripe_option(option) if show_advanced: - _console.prints_color("-- Advanced options --------------------------------------------------------------------------", Color.DARK_SALMON).new_line() + _console.prints_color( + "-- Advanced options --------------------------------------------------------------------------", + Color.DARK_SALMON + ).new_line() for option in _cmd_options.advanced_options(): descripe_option(option) - - - func descripe_option(cmd_option :CmdOption) -> void: - _console.print_color(" %-40s" % str(cmd_option.commands()), Color.CORNFLOWER_BLUE) - _console.prints_color(cmd_option.description(), Color.LIGHT_GREEN) + + + func descripe_option(cmd_option: CmdOption) -> void: + _console.print_color( + " %-40s" % str(cmd_option.commands()), + Color.CORNFLOWER_BLUE + ) + _console.prints_color( + cmd_option.description(), + Color.LIGHT_GREEN + ) if not cmd_option.help().is_empty(): - _console.prints_color("%-4s %s" % ["", cmd_option.help()], Color.DARK_TURQUOISE) + _console.prints_color( + "%-4s %s" % ["", cmd_option.help()], + Color.DARK_TURQUOISE + ) _console.new_line() - - + + func load_test_config(path := GdUnitRunnerConfig.CONFIG_FILE) -> void: - _console.print_color("Loading test configuration %s\n" % path, Color.CORNFLOWER_BLUE) + _console.print_color( + "Loading test configuration %s\n" % path, + Color.CORNFLOWER_BLUE + ) + _runner_config_file = path _runner_config.load_config(path) - - + + func show_help() -> void: show_options() quit(RETURN_SUCCESS) - - + + func show_advanced_help() -> void: show_options(true) quit(RETURN_SUCCESS) - - - func gdUnitInit() -> void: - _console.prints_color("----------------------------------------------------------------------------------------------", Color.DARK_SALMON) - _console.prints_color(" GdUnit4 Comandline Tool", Color.DARK_SALMON) - _console.new_line() - - if DisplayServer.get_name() == "headless": - _console.prints_error("Headless mode is not supported!").new_line() - _console.print_color("Tests that use UI interaction do not work in headless mode because 'InputEvents' are not transported by the Godot engine and thus have no effect!", Color.CORNFLOWER_BLUE)\ - .new_line().new_line() - _console.prints_error("Abnormal exit with %d" % RETURN_ERROR_HEADLESS_NOT_SUPPORTED) - quit(RETURN_ERROR_HEADLESS_NOT_SUPPORTED) - return - + + + func init_gd_unit() -> void: + _console.prints_color( + """ + -------------------------------------------------------------------------------------------------- + GdUnit4 Comandline Tool + --------------------------------------------------------------------------------------------------""".dedent(), + Color.DARK_SALMON + ).new_line() + var cmd_parser := CmdArgumentParser.new(_cmd_options, "GdUnitCmdTool.gd") var result := cmd_parser.parse(OS.get_cmdline_args()) if result.is_error(): @@ -188,57 +292,86 @@ class CLIRunner extends Node: _state = STOP quit(RETURN_ERROR) return - if result.is_empty(): show_help() return - # build runner config by given commands - result = CmdCommandHandler.new(_cmd_options)\ - .register_cb("-help", Callable(self, "show_help"))\ - .register_cb("--help-advanced", Callable(self, "show_advanced_help"))\ - .register_cb("-a", Callable(_runner_config, "add_test_suite"))\ - .register_cbv("-a", Callable(_runner_config, "add_test_suites"))\ - .register_cb("-i", Callable(_runner_config, "skip_test_suite"))\ - .register_cbv("-i", Callable(_runner_config, "skip_test_suites"))\ - .register_cb("-rd", Callable(self, "set_report_dir"))\ - .register_cb("-rc", Callable(self, "set_report_count"))\ - .register_cb("--selftest", Callable(self, "run_self_test"))\ - .register_cb("-c", Callable(self, "disable_fail_fast"))\ - .register_cb("-conf", Callable(self, "load_test_config"))\ - .register_cb("--info", Callable(self, "show_version"))\ - .execute(result.value()) + var commands :Array[CmdCommand] = [] + commands.append_array(result.value()) + result = ( + CmdCommandHandler.new(_cmd_options) + .register_cb("-help", Callable(self, "show_help")) + .register_cb("--help-advanced", Callable(self, "show_advanced_help")) + .register_cb("-a", Callable(_runner_config, "add_test_suite")) + .register_cbv("-a", Callable(_runner_config, "add_test_suites")) + .register_cb("-i", Callable(_runner_config, "skip_test_suite")) + .register_cbv("-i", Callable(_runner_config, "skip_test_suites")) + .register_cb("-rd", set_report_dir) + .register_cb("-rc", set_report_count) + .register_cb("--selftest", run_self_test) + .register_cb("-c", disable_fail_fast) + .register_cb("-conf", load_test_config) + .register_cb("--info", show_version) + .register_cb("--ignoreHeadlessMode", check_headless_mode) + .execute(commands) + ) if result.is_error(): _console.prints_error(result.error_message()) _state = STOP quit(RETURN_ERROR) - + + if DisplayServer.get_name() == "headless": + if _headless_mode_ignore: + _console.prints_warning(""" + Headless mode is ignored by option '--ignoreHeadlessMode'" + + Please note that tests that use UI interaction do not work correctly in headless mode. + Godot 'InputEvents' are not transported by the Godot engine in headless mode and therefore + have no effect in the test! + """.dedent() + ).new_line() + else: + _console.prints_error(""" + Headless mode is not supported! + + Please note that tests that use UI interaction do not work correctly in headless mode. + Godot 'InputEvents' are not transported by the Godot engine in headless mode and therefore + have no effect in the test! + + You can run with '--ignoreHeadlessMode' to swtich off this check. + """.dedent() + ).prints_error( + "Abnormal exit with %d" % RETURN_ERROR_HEADLESS_NOT_SUPPORTED + ) + quit(RETURN_ERROR_HEADLESS_NOT_SUPPORTED) + return + _test_suites_to_process = load_testsuites(_runner_config) if _test_suites_to_process.is_empty(): _console.prints_warning("No test suites found, abort test run!") - _console.prints_color("Exit code: %d" % RETURN_SUCCESS, Color.DARK_SALMON) + _console.prints_color("Exit code: %d" % RETURN_SUCCESS, Color.DARK_SALMON) _state = STOP quit(RETURN_SUCCESS) - - var total_test_count = _collect_test_case_count(_test_suites_to_process) + var total_test_count := _collect_test_case_count(_test_suites_to_process) _on_gdunit_event(GdUnitInit.new(_test_suites_to_process.size(), total_test_count)) - - - func load_testsuites(config :GdUnitRunnerConfig) -> Array[Node]: - var test_suites_to_process :Array[Node] = [] + + + func load_testsuites(config: GdUnitRunnerConfig) -> Array[Node]: + var test_suites_to_process: Array[Node] = [] + # Dictionary[String, Dictionary[String, PackedStringArray]] var to_execute := config.to_execute() # scan for the requested test suites - var _scanner := GdUnitTestSuiteScanner.new() - for resource_path_ in to_execute.keys(): - var selected_tests :PackedStringArray = to_execute.get(resource_path_) - var scaned_suites := _scanner.scan(resource_path_) + var ts_scanner := GdUnitTestSuiteScanner.new() + for as_resource_path in to_execute.keys() as Array[String]: + var selected_tests: PackedStringArray = to_execute.get(as_resource_path) + var scaned_suites := ts_scanner.scan(as_resource_path) skip_test_case(scaned_suites, selected_tests) test_suites_to_process.append_array(scaned_suites) skip_suites(test_suites_to_process, config) return test_suites_to_process - - - func skip_test_case(test_suites :Array, test_case_names :Array) -> void: + + + func skip_test_case(test_suites: Array[Node], test_case_names: Array[String]) -> void: if test_case_names.is_empty(): return for test_suite in test_suites: @@ -246,67 +379,94 @@ class CLIRunner extends Node: if not test_case_names.has(test_case.get_name()): test_suite.remove_child(test_case) test_case.free() - - - func skip_suites(test_suites :Array, config :GdUnitRunnerConfig) -> void: + + + func skip_suites(test_suites: Array[Node], config: GdUnitRunnerConfig) -> void: var skipped := config.skipped() + if skipped.is_empty(): + return + _console.prints_warning("Found excluded test suite's configured at '%s'" % _runner_config_file) for test_suite in test_suites: + # skipp c# testsuites for now + if test_suite.get_script() == null: + continue skip_suite(test_suite, skipped) - - - func skip_suite(test_suite :Node, skipped :Dictionary) -> void: - var skipped_suites := skipped.keys() - if skipped_suites.is_empty(): - return + + + # Dictionary[String, PackedStringArray] + func skip_suite(test_suite: Node, skipped: Dictionary) -> void: + var skipped_suites :Array[String] = skipped.keys() var suite_name := test_suite.get_name() - # skipp c# testsuites for now - if test_suite.get_script() == null: - return - var test_suite_path :String = test_suite.get_meta("ResourcePath") if test_suite.get_script() == null else test_suite.get_script().resource_path + var test_suite_path: String = ( + test_suite.get_meta("ResourcePath") if test_suite.get_script() == null + else test_suite.get_script().resource_path + ) for suite_to_skip in skipped_suites: # if suite skipped by path or name - if suite_to_skip == test_suite_path or (suite_to_skip.is_valid_filename() and suite_to_skip == suite_name): - var skipped_tests :Array = skipped.get(suite_to_skip) + if ( + suite_to_skip == test_suite_path + or (suite_to_skip.is_valid_filename() and suite_to_skip == suite_name) + ): + var skipped_tests: Array[String] = skipped.get(suite_to_skip) + var skip_reason := "Excluded by config '%s'" % _runner_config_file # if no tests skipped test the complete suite is skipped if skipped_tests.is_empty(): - _console.prints_warning("Skip test suite %s:%s" % suite_to_skip) - test_suite.skip(true) + _console.prints_warning("Mark test suite '%s' as skipped!" % suite_to_skip) + test_suite.__is_skipped = true + test_suite.__skip_reason = skip_reason else: # skip tests for test_to_skip in skipped_tests: - var test_case :_TestCase = test_suite.find_child(test_to_skip, true, false) + var test_case: _TestCase = test_suite.find_child(test_to_skip, true, false) if test_case: - test_case.skip(true) - _console.prints_warning("Skip test case %s:%s" % [suite_to_skip, test_to_skip]) + test_case.skip(true, skip_reason) + _console.prints_warning("Mark test case '%s':%s as skipped" % [suite_to_skip, test_to_skip]) else: - _console.prints_error("Can't skip test '%s' checked test suite '%s', no test with given name exists!" % [test_to_skip, suite_to_skip]) - - - 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() + _console.prints_error( + "Can't skip test '%s' checked test suite '%s', no test with given name exists!" + % [test_to_skip, suite_to_skip] + ) + + + func _collect_test_case_count(test_suites: Array[Node]) -> int: + var total: int = 0 + for test_suite in test_suites: + total += test_suite.get_child_count() return total - - - func PublishEvent(data :Dictionary) -> void: + + + # gdlint: disable=function-name + func PublishEvent(data: Dictionary) -> void: _on_gdunit_event(GdUnitEvent.new().deserialize(data)) - - - func _on_gdunit_event(event :GdUnitEvent): + + + func _on_gdunit_event(event: GdUnitEvent) -> void: match event.type(): GdUnitEvent.INIT: _report = GdUnitHtmlReport.new(_report_dir) GdUnitEvent.STOP: + if _report == null: + _report = GdUnitHtmlReport.new(_report_dir) var report_path := _report.write() _report.delete_history(_report_max) JUnitXmlReport.new(_report._report_path, _report.iteration()).write(_report) - _console.prints_color("Total test suites: %s" % _report.suite_count(), Color.DARK_SALMON) - _console.prints_color("Total test cases: %s" % _report.test_count(), Color.DARK_SALMON) - _console.prints_color("Total time: %s" % LocalTime.elapsed(_report.duration()), Color.DARK_SALMON) - _console.prints_color("Open Report at: file://%s" % report_path, Color.CORNFLOWER_BLUE) + _console.prints_color( + build_executed_test_suite_msg(_report.suite_executed_count(), _report.suite_count()), + Color.DARK_SALMON + ).prints_color( + build_executed_test_case_msg(_report.test_executed_count(), _report.test_count()), + Color.DARK_SALMON + ).prints_color( + "Total time: %s" % LocalTime.elapsed(_report.duration()), + Color.DARK_SALMON + ).prints_color( + "Open Report at: file://%s" % report_path, + Color.CORNFLOWER_BLUE + ) GdUnitEvent.TESTSUITE_BEFORE: - _report.add_testsuite_report(GdUnitTestSuiteReport.new(event.resource_path(), event.suite_name())) + _report.add_testsuite_report( + GdUnitTestSuiteReport.new(event.resource_path(), event.suite_name(), event.total_count()) + ) GdUnitEvent.TESTSUITE_AFTER: _report.update_test_suite_report( event.resource_path(), @@ -318,9 +478,17 @@ class CLIRunner extends Node: event.skipped_count(), event.failed_count(), event.orphan_nodes(), - event.reports()) + event.reports() + ) GdUnitEvent.TESTCASE_BEFORE: - _report.add_testcase_report(event.resource_path(), GdUnitTestCaseReport.new(event.resource_path(), event.suite_name(), event.test_name())) + _report.add_testcase_report( + event.resource_path(), + GdUnitTestCaseReport.new( + event.resource_path(), + event.suite_name(), + event.test_name() + ) + ) GdUnitEvent.TESTCASE_AFTER: var test_report := GdUnitTestCaseReport.new( event.resource_path(), @@ -332,72 +500,124 @@ class CLIRunner extends Node: event.orphan_nodes(), event.is_skipped(), event.reports(), - event.elapsed_time()) + event.elapsed_time() + ) _report.update_testcase_report(event.resource_path(), test_report) print_status(event) - - - func report_exit_code(report :GdUnitHtmlReport) -> int: + + + func build_executed_test_suite_msg(executed_count :int, total_count :int) -> String: + if executed_count == total_count: + return "Executed test suites: (%d/%d)" % [executed_count, total_count] + return "Executed test suites: (%d/%d), %d skipped" % [executed_count, total_count, (total_count - executed_count)] + + + func build_executed_test_case_msg(executed_count :int, total_count :int) -> String: + if executed_count == total_count: + return "Executed test cases: (%d/%d)" % [executed_count, total_count] + return "Executed test cases: (%d/%d), %d skipped" % [executed_count, total_count, (total_count - executed_count)] + + + func report_exit_code(report: GdUnitHtmlReport) -> int: if report.error_count() + report.failure_count() > 0: _console.prints_color("Exit code: %d" % RETURN_ERROR, Color.FIREBRICK) return RETURN_ERROR if report.orphan_count() > 0: _console.prints_color("Exit code: %d" % RETURN_WARNING, Color.GOLDENROD) return RETURN_WARNING - _console.prints_color("Exit code: %d" % RETURN_SUCCESS, Color.DARK_SALMON) + _console.prints_color("Exit code: %d" % RETURN_SUCCESS, Color.DARK_SALMON) return RETURN_SUCCESS - - - func print_status(event :GdUnitEvent) -> void: + + + func print_status(event: GdUnitEvent) -> void: match event.type(): GdUnitEvent.TESTSUITE_BEFORE: - _console.prints_color("Run Test Suite %s " % event.resource_path(), Color.ANTIQUE_WHITE) + _console.prints_color( + "Run Test Suite %s " % event.resource_path(), + Color.ANTIQUE_WHITE + ) GdUnitEvent.TESTCASE_BEFORE: - _console.print_color(" Run Test: %s > %s :" % [event.resource_path(), event.test_name()], Color.ANTIQUE_WHITE)\ - .prints_color("STARTED", Color.FOREST_GREEN) + _console.print_color( + " Run Test: %s > %s :" % [event.resource_path(), event.test_name()], + Color.ANTIQUE_WHITE + ).prints_color( + "STARTED", + Color.FOREST_GREEN + ).save_cursor() GdUnitEvent.TESTCASE_AFTER: - _console.print_color(" Run Test: %s > %s :" % [event.resource_path(), event.test_name()], Color.ANTIQUE_WHITE) + #_console.restore_cursor() + _console.print_color( + " Run Test: %s > %s :" % [event.resource_path(), event.test_name()], + Color.ANTIQUE_WHITE + ) _print_status(event) _print_failure_report(event.reports()) GdUnitEvent.TESTSUITE_AFTER: _print_failure_report(event.reports()) _print_status(event) - _console.prints_color(" | %d total | %d error | %d failed | %d skipped | %d orphans |\n" % [_report.test_count(), _report.error_count(), _report.failure_count(), _report.skipped_count(), _report.orphan_count()], Color.ANTIQUE_WHITE) - - - func _print_failure_report(reports :Array) -> void: + _console.prints_color( + "Statistics: | %d tests cases | %d error | %d failed | %d skipped | %d orphans |\n" + % [ + _report.test_count(), + _report.error_count(), + _report.failure_count(), + _report.skipped_count(), + _report.orphan_count() + ], + Color.ANTIQUE_WHITE + ) + + + func _print_failure_report(reports: Array[GdUnitReport]) -> void: for report in reports: - if report.is_failure() or report.is_error() or report.is_warning() or report.is_skipped(): - _console.prints_color(" Report:", Color.DARK_TURQUOISE, CmdConsole.BOLD|CmdConsole.UNDERLINE) - var text = GdUnitTools.richtext_normalize(report._to_string()) + if ( + report.is_failure() + or report.is_error() + or report.is_warning() + or report.is_skipped() + ): + _console.prints_color( + " Report:", + Color.DARK_TURQUOISE, CmdConsole.BOLD | CmdConsole.UNDERLINE + ) + var text := GdUnitTools.richtext_normalize(str(report)) for line in text.split("\n"): _console.prints_color(" %s" % line, Color.DARK_TURQUOISE) _console.new_line() - - - func _print_status(event :GdUnitEvent) -> void: + + + func _print_status(event: GdUnitEvent) -> void: if event.is_skipped(): - _console.print_color("SKIPPED", Color.GOLDENROD, CmdConsole.BOLD|CmdConsole.ITALIC) + _console.print_color("SKIPPED", Color.GOLDENROD, CmdConsole.BOLD | CmdConsole.ITALIC) elif event.is_failed() or event.is_error(): - _console.print_color("FAILED", Color.CRIMSON, CmdConsole.BOLD) + _console.print_color("FAILED", Color.FIREBRICK, CmdConsole.BOLD) elif event.orphan_nodes() > 0: - _console.print_color("PASSED", Color.GOLDENROD, CmdConsole.BOLD|CmdConsole.UNDERLINE) + _console.print_color("PASSED", Color.GOLDENROD, CmdConsole.BOLD | CmdConsole.UNDERLINE) else: _console.print_color("PASSED", Color.FOREST_GREEN, CmdConsole.BOLD) - _console.prints_color(" %s" % LocalTime.elapsed(event.elapsed_time()), Color.CORNFLOWER_BLUE) + _console.prints_color( + " %s" % LocalTime.elapsed(event.elapsed_time()), Color.CORNFLOWER_BLUE + ) var _cli_runner :CLIRunner -func _initialize(): +func _initialize() -> void: + if Engine.get_version_info().hex < 0x40200: + prints("GdUnit4 requires a minimum of Godot 4.2.x Version!") + quit(CLIRunner.RETURN_ERROR_GODOT_VERSION_NOT_SUPPORTED) + return DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_MINIMIZED) _cli_runner = CLIRunner.new() root.add_child(_cli_runner) -func _finalize(): - prints("Finallize ..") - prints("-Orphan nodes report-----------------------") - Window.print_orphan_nodes() - prints("Finallize .. done") +# do not use print statements on _finalize it results in random crashes +func _finalize() -> void: + queue_delete(_cli_runner) + if OS.is_stdout_verbose(): + prints("Finallize ..") + prints("-Orphan nodes report-----------------------") + Window.print_orphan_nodes() + prints("Finallize .. done") diff --git a/addons/gdUnit4/bin/GdUnitCopyLog.gd b/addons/gdUnit4/bin/GdUnitCopyLog.gd index 5f57806f..2e037a69 100644 --- a/addons/gdUnit4/bin/GdUnitCopyLog.gd +++ b/addons/gdUnit4/bin/GdUnitCopyLog.gd @@ -3,6 +3,7 @@ extends MainLoop const GdUnitTools := preload("res://addons/gdUnit4/src/core/GdUnitTools.gd") +# gdlint: disable=max-line-length const NO_LOG_TEMPLATE = """ @@ -23,96 +24,118 @@ const NO_LOG_TEMPLATE = """ """ #warning-ignore-all:return_value_discarded -var _cmd_options: = CmdOptions.new([ - CmdOption.new("-rd, --report-directory", "-rd ", "Specifies the output directory in which the reports are to be written. The default is res://reports/.", TYPE_STRING, true), -]) +var _cmd_options := CmdOptions.new([ + CmdOption.new( + "-rd, --report-directory", + "-rd ", + "Specifies the output directory in which the reports are to be written. The default is res://reports/.", + TYPE_STRING, + true + ) + ]) -var _report_root_path :String +var _report_root_path: String -func _init(): - _report_root_path = GdUnitTools.current_dir() + "reports" -func _process(_delta): +func _init() -> void: + _report_root_path = GdUnitFileAccess.current_dir() + "reports" + + +func _process(_delta :float) -> bool: # check if reports exists if not reports_available(): prints("no reports found") return true # scan for latest report path - var iteration = GdUnitTools.find_last_path_index(_report_root_path, GdUnitHtmlReport.REPORT_DIR_PREFIX) - var report_path = "%s/%s%d" % [_report_root_path, GdUnitHtmlReport.REPORT_DIR_PREFIX, iteration] - + var iteration := GdUnitFileAccess.find_last_path_index( + _report_root_path, GdUnitHtmlReport.REPORT_DIR_PREFIX + ) + var report_path := "%s/%s%d" % [_report_root_path, GdUnitHtmlReport.REPORT_DIR_PREFIX, iteration] # only process if godot logging is enabled if not GdUnitSettings.is_log_enabled(): _patch_report(report_path, "") return true - - # parse possible custom report path, + # parse possible custom report path, var cmd_parser := CmdArgumentParser.new(_cmd_options, "GdUnitCmdTool.gd") # ignore erros and exit quitly if cmd_parser.parse(OS.get_cmdline_args(), true).is_error(): return true - CmdCommandHandler.new(_cmd_options).register_cb("-rd", Callable(self, "set_report_directory")) - + CmdCommandHandler.new(_cmd_options).register_cb("-rd", set_report_directory) # scan for latest godot log and copy to report var godot_log := _scan_latest_godot_log() var result := _copy_and_pach(godot_log, report_path) if result.is_error(): push_error(result.error_message()) return true - _patch_report(report_path, godot_log) return true -func set_report_directory(path :String) -> void: + +func set_report_directory(path: String) -> void: _report_root_path = path + func _scan_latest_godot_log() -> String: var path := GdUnitSettings.get_log_path().get_base_dir() var files_sorted := Array() - for file in GdUnitTools.scan_dir(path): - var file_name := "%s/%s" % [path,file] + for file in GdUnitFileAccess.scan_dir(path): + var file_name := "%s/%s" % [path, file] files_sorted.append(file_name) # sort by name, the name contains the timestamp so we sort at the end by timestamp files_sorted.sort() return files_sorted[-1] -func _patch_report(report_path :String, godot_log :String) -> void: + +func _patch_report(report_path: String, godot_log: String) -> void: var index_file := FileAccess.open("%s/index.html" % report_path, FileAccess.READ_WRITE) if index_file == null: - push_error("Can't add log path to index.html. Error: %s" % GdUnitTools.error_as_string(FileAccess.get_open_error())) + push_error( + "Can't add log path to index.html. Error: %s" + % error_string(FileAccess.get_open_error()) + ) return # if no log file available than add a information howto enable it if godot_log.is_empty(): - FileAccess.open("%s/logging_not_available.html" % report_path, FileAccess.WRITE)\ - .store_string(NO_LOG_TEMPLATE) - var log_file = "logging_not_available.html" if godot_log.is_empty() else godot_log.get_file() + FileAccess.open( + "%s/logging_not_available.html" % report_path, + FileAccess.WRITE).store_string(NO_LOG_TEMPLATE) + var log_file := "logging_not_available.html" if godot_log.is_empty() else godot_log.get_file() var content := index_file.get_as_text().replace("${log_file}", log_file) # overide it index_file.seek(0) index_file.store_string(content) - + + func _copy_and_pach(from_file: String, to_dir: String) -> GdUnitResult: - var result := GdUnitTools.copy_file(from_file, to_dir) + var result := GdUnitFileAccess.copy_file(from_file, to_dir) if result.is_error(): return result var file := FileAccess.open(from_file, FileAccess.READ) if file == null: - return GdUnitResult.error("Can't find file '%s'. Error: %s" % [from_file, GdUnitTools.error_as_string(FileAccess.get_open_error())]) + return GdUnitResult.error( + "Can't find file '%s'. Error: %s" + % [from_file, error_string(FileAccess.get_open_error())] + ) var content := file.get_as_text() # patch out console format codes for color_index in range(0, 256): var to_replace := "[38;5;%dm" % color_index content = content.replace(to_replace, "") - content = content.replace("", "")\ - .replace(CmdConsole.__CSI_BOLD, "")\ - .replace(CmdConsole.__CSI_ITALIC, "")\ - .replace(CmdConsole.__CSI_UNDERLINE, "") + content = content\ + .replace("", "")\ + .replace(CmdConsole.CSI_BOLD, "")\ + .replace(CmdConsole.CSI_ITALIC, "")\ + .replace(CmdConsole.CSI_UNDERLINE, "") var to_file := to_dir + "/" + from_file.get_file() file = FileAccess.open(to_file, FileAccess.WRITE) if file == null: - return GdUnitResult.error("Can't open to write '%s'. Error: %s" % [to_file, GdUnitTools.error_as_string(FileAccess.get_open_error())]) + return GdUnitResult.error( + "Can't open to write '%s'. Error: %s" + % [to_file, error_string(FileAccess.get_open_error())] + ) file.store_string(content) return GdUnitResult.empty() + func reports_available() -> bool: return DirAccess.dir_exists_absolute(_report_root_path) diff --git a/addons/gdUnit4/bin/ProjectScanner.gd b/addons/gdUnit4/bin/ProjectScanner.gd index 74bbbcf4..93c40dec 100644 --- a/addons/gdUnit4/bin/ProjectScanner.gd +++ b/addons/gdUnit4/bin/ProjectScanner.gd @@ -4,87 +4,96 @@ extends SceneTree const CmdConsole = preload("res://addons/gdUnit4/src/cmd/CmdConsole.gd") -var scanner := SourceScanner.new() -func _initialize(): +func _initialize() -> void: set_auto_accept_quit(false) + var scanner := SourceScanner.new(self) root.add_child(scanner) -func _finalize(): - prints("__finalize") - - - +# gdlint: disable=trailing-whitespace class SourceScanner extends Node: - + enum { INIT, + STARTUP, SCAN, QUIT, DONE } - - - var _counter = 0 - var WAIT_TIME_IN_MS = 5.000 - var _state = INIT + + var _state := INIT var _console := CmdConsole.new() - - - func _init(): - _state = SCAN - - - func _process(delta): + var _elapsed_time := 0.0 + var _plugin: EditorPlugin + var _fs: EditorFileSystem + var _scene: SceneTree + + + func _init(scene :SceneTree) -> void: + _scene = scene + _console.prints_color(""" + ======================================================================== + Running project scan:""".dedent(), + Color.CORNFLOWER_BLUE + ) + _state = INIT + + + func _process(delta :float) -> void: + _elapsed_time += delta + set_process(false) + await_inital_scan() + await scan_project() + set_process(true) + + + # !! don't use any await in this phase otherwise the editor will be instable !! + func await_inital_scan() -> void: + if _state == INIT: + _console.prints_color("Wait initial scanning ...", Color.DARK_GREEN) + _plugin = EditorPlugin.new() + _fs = _plugin.get_editor_interface().get_resource_filesystem() + _plugin.get_editor_interface().set_plugin_enabled("gdUnit4", false) + _state = STARTUP + + if _state == STARTUP: + if _fs.is_scanning(): + _console.progressBar(_fs.get_scanning_progress() * 100 as int) + # we wait 10s in addition to be on the save site the scanning is done + if _elapsed_time > 10.0: + _console.progressBar(100) + _console.new_line() + _console.prints_color("initial scanning ... done", Color.DARK_GREEN) + _state = SCAN + + + func scan_project() -> void: if _state != SCAN: return - _counter += delta - if _state == SCAN: - set_process(false) - _console.prints_color("======================================", Color.CORNFLOWER_BLUE) - _console.prints_color("Running project scan:", Color.CORNFLOWER_BLUE) - await scan_project() - set_process(true) - _state = QUIT - if _state == QUIT or _counter >= WAIT_TIME_IN_MS: - _state = DONE - _console.prints_color("Scan project done.", Color.CORNFLOWER_BLUE) - _console.prints_color("======================================", Color.CORNFLOWER_BLUE) - _console.new_line() - await get_tree().process_frame - get_tree().quit(0) - - - func scan_project() -> void: - var plugin := EditorPlugin.new() - var fs := plugin.get_editor_interface().get_resource_filesystem() - - if fs.has_method("reimport_files--"): - _console.prints_color("Reimport images :", Color.SANDY_BROWN) - for source in ["res://addons/gdUnit4/src/ui/assets/orphan", "res://addons/gdUnit4/src/ui/assets/spinner", "res://addons/gdUnit4/src/ui/assets/"]: - var image_files := Array(DirAccess.get_files_at(source)) - #_console.prints_color("%s" % image_files, Color.SANDY_BROWN) - var files := image_files.map(func full_path(file_name): - return "%s/%s" % [source, file_name] )\ - .filter(func filter_import_files(path :String): - return path.get_extension() != "import") - prints(files) - fs.reimport_files(files) - - _console.prints_color("Scan sources: ", Color.SANDY_BROWN) - fs.scan_sources() - await get_tree().create_timer(5).timeout + _console.prints_color("Scan project: ", Color.SANDY_BROWN) await get_tree().process_frame - + _fs.scan_sources() + await get_tree().create_timer(5).timeout _console.prints_color("Scan: ", Color.SANDY_BROWN) - fs.scan() + _console.progressBar(0) await get_tree().process_frame - while fs.is_scanning(): + _fs.scan() + while _fs.is_scanning(): await get_tree().process_frame - _console.progressBar(fs.get_scanning_progress() * 100 as int) + _console.progressBar(_fs.get_scanning_progress() * 100 as int) + await get_tree().create_timer(10).timeout _console.progressBar(100) _console.new_line() + _plugin.free() + _console.prints_color(""" + Scan project done. + ========================================================================""".dedent(), + Color.CORNFLOWER_BLUE + ) await get_tree().process_frame - plugin.queue_free() - await get_tree().process_frame + await get_tree().physics_frame + queue_free() + # force quit editor + _state = DONE + _scene.quit(0) diff --git a/addons/gdUnit4/plugin.cfg b/addons/gdUnit4/plugin.cfg index 56c4c0f6..1bfdd62d 100644 --- a/addons/gdUnit4/plugin.cfg +++ b/addons/gdUnit4/plugin.cfg @@ -3,5 +3,5 @@ name="gdUnit4" description="Unit Testing Framework for Godot Scripts" author="Mike Schulze" -version="4.2.0" +version="4.3.2" script="plugin.gd" diff --git a/addons/gdUnit4/plugin.gd b/addons/gdUnit4/plugin.gd index 065e0318..37a2a696 100644 --- a/addons/gdUnit4/plugin.gd +++ b/addons/gdUnit4/plugin.gd @@ -1,15 +1,22 @@ @tool extends EditorPlugin -const GdUnitTools := preload("res://addons/gdUnit4/src/core/GdUnitTools.gd") +const GdUnitTools := preload ("res://addons/gdUnit4/src/core/GdUnitTools.gd") +const GdUnitTestDiscoverGuard := preload ("res://addons/gdUnit4/src/core/discovery/GdUnitTestDiscoverGuard.gd") + var _gd_inspector :Node -var _server_node var _gd_console :Node +var _guard: GdUnitTestDiscoverGuard -func _enter_tree(): - Engine.set_meta("GdUnitEditorPlugin", self) +func _enter_tree() -> void: + if check_running_in_test_env(): + CmdConsole.new().prints_warning("It was recognized that GdUnit4 is running in a test environment, therefore the GdUnit4 plugin will not be executed!") + return + if Engine.get_version_info().hex < 0x40200: + prints("GdUnit4 plugin requires a minimum of Godot 4.2.x Version!") + return GdUnitSettings.setup() # install the GdUnit inspector _gd_inspector = load("res://addons/gdUnit4/src/ui/GdUnitInspector.tscn").instantiate() @@ -17,28 +24,36 @@ func _enter_tree(): # install the GdUnit Console _gd_console = load("res://addons/gdUnit4/src/ui/GdUnitConsole.tscn").instantiate() add_control_to_bottom_panel(_gd_console, "gdUnitConsole") - _server_node = load("res://addons/gdUnit4/src/network/GdUnitServer.tscn").instantiate() - add_child(_server_node) prints("Loading GdUnit4 Plugin success") if GdUnitSettings.is_update_notification_enabled(): - var update_tool = load("res://addons/gdUnit4/src/update/GdUnitUpdateNotify.tscn").instantiate() - Engine.get_main_loop().root.call_deferred("add_child", update_tool) - if GdUnit4MonoApiLoader.is_mono_supported(): - prints("GdUnit4Mono Version %s loaded." % GdUnit4MonoApiLoader.version()) + var update_tool: Node = load("res://addons/gdUnit4/src/update/GdUnitUpdateNotify.tscn").instantiate() + Engine.get_main_loop().root.add_child.call_deferred(update_tool) + if GdUnit4CSharpApiLoader.is_mono_supported(): + prints("GdUnit4Net version '%s' loaded." % GdUnit4CSharpApiLoader.version()) + # connect to be notified for script changes to be able to discover new tests + _guard = GdUnitTestDiscoverGuard.new() + resource_saved.connect(_on_resource_saved) -func _exit_tree(): +func _exit_tree() -> void: + if check_running_in_test_env(): + return if is_instance_valid(_gd_inspector): remove_control_from_docks(_gd_inspector) _gd_inspector.free() if is_instance_valid(_gd_console): remove_control_from_bottom_panel(_gd_console) _gd_console.free() - if is_instance_valid(_server_node): - remove_child(_server_node) - _server_node.free() - GdUnitTools.dispose_all() - if Engine.has_meta("GdUnitEditorPlugin"): - Engine.remove_meta("GdUnitEditorPlugin") - if Engine.get_version_info().hex < 0x40100 or Engine.get_version_info().hex > 0x40101: - prints("Unload GdUnit4 Plugin success") + GdUnitTools.dispose_all(true) + prints("Unload GdUnit4 Plugin success") + + +func check_running_in_test_env() -> bool: + var args := OS.get_cmdline_args() + args.append_array(OS.get_cmdline_user_args()) + return DisplayServer.get_name() == "headless" or args.has("--selftest") or args.has("--add") or args.has("-a") or args.has("--quit-after") or args.has("--import") + + +func _on_resource_saved(resource: Resource) -> void: + if resource is Script: + _guard.discover(resource) diff --git a/addons/gdUnit4/runtest.cmd b/addons/gdUnit4/runtest.cmd index fbd410e1..0c8fbc5f 100644 --- a/addons/gdUnit4/runtest.cmd +++ b/addons/gdUnit4/runtest.cmd @@ -16,9 +16,9 @@ IF "%GODOT_TYPE%" == "mono" ( ECHO done %errorlevel% ) -%GODOT_BIN% -s -d .\addons\gdUnit4\bin\GdUnitCmdTool.gd %* +%GODOT_BIN% -s -d res://addons/gdUnit4/bin/GdUnitCmdTool.gd %* SET exit_code=%errorlevel% -%GODOT_BIN% --headless --quiet -s -d .\addons\gdUnit4\bin\GdUnitCopyLog.gd %* +%GODOT_BIN% --headless --quiet -s -d res://addons/gdUnit4/bin/GdUnitCopyLog.gd %* ECHO %exit_code% diff --git a/addons/gdUnit4/runtest.sh b/addons/gdUnit4/runtest.sh index c1e55bd1..17ecb0dd 100644 --- a/addons/gdUnit4/runtest.sh +++ b/addons/gdUnit4/runtest.sh @@ -6,7 +6,10 @@ if [ -z "$GODOT_BIN" ]; then exit 1 fi -$GODOT_BIN --path . -s -d ./addons/gdUnit4/bin/GdUnitCmdTool.gd $* +"$GODOT_BIN" --path . -s -d res://addons/gdUnit4/bin/GdUnitCmdTool.gd $* exit_code=$? -$GODOT_BIN --headless --path . --quiet -s -d ./addons/gdUnit4/bin/GdUnitCopyLog.gd $* > /dev/null +echo "Run tests ends with $exit_code" + +"$GODOT_BIN" --headless --path . --quiet -s -d res://addons/gdUnit4/bin/GdUnitCopyLog.gd $* > /dev/null +exit_code2=$? exit $exit_code diff --git a/addons/gdUnit4/src/Comparator.gd b/addons/gdUnit4/src/Comparator.gd index 12f7e820..096088a6 100644 --- a/addons/gdUnit4/src/Comparator.gd +++ b/addons/gdUnit4/src/Comparator.gd @@ -1,7 +1,7 @@ class_name Comparator extends Resource -enum { +enum { EQUAL, LESS_THAN, LESS_EQUAL, diff --git a/addons/gdUnit4/src/Fuzzers.gd b/addons/gdUnit4/src/Fuzzers.gd index cd53d20a..f61ba6e6 100644 --- a/addons/gdUnit4/src/Fuzzers.gd +++ b/addons/gdUnit4/src/Fuzzers.gd @@ -2,22 +2,26 @@ class_name Fuzzers extends Resource + ## Generates an random string with min/max length and given charset -static func rand_str(min_length :int, max_length, charset := StringFuzzer.DEFAULT_CHARSET) -> Fuzzer: +static func rand_str(min_length: int, max_length :int, charset := StringFuzzer.DEFAULT_CHARSET) -> Fuzzer: return StringFuzzer.new(min_length, max_length, charset) -## Generates an random integer in a range form to +## Generates an random integer in a range form to static func rangei(from: int, to: int) -> Fuzzer: return IntFuzzer.new(from, to) +## Generates a randon float within in a given range +static func rangef(from: float, to: float) -> Fuzzer: + return FloatFuzzer.new(from, to) -## Generates an random Vector2 in a range form to +## Generates an random Vector2 in a range form to static func rangev2(from: Vector2, to: Vector2) -> Fuzzer: return Vector2Fuzzer.new(from, to) -## Generates an random Vector3 in a range form to +## Generates an random Vector3 in a range form to static func rangev3(from: Vector3, to: Vector3) -> Fuzzer: return Vector3Fuzzer.new(from, to) diff --git a/addons/gdUnit4/src/GdUnitArrayAssert.gd b/addons/gdUnit4/src/GdUnitArrayAssert.gd index 31cb088b..3fdaacac 100644 --- a/addons/gdUnit4/src/GdUnitArrayAssert.gd +++ b/addons/gdUnit4/src/GdUnitArrayAssert.gd @@ -15,25 +15,25 @@ func is_not_null() -> GdUnitArrayAssert: ## Verifies that the current Array is equal to the given one. @warning_ignore("unused_parameter") -func is_equal(expected) -> GdUnitArrayAssert: +func is_equal(expected :Variant) -> GdUnitArrayAssert: return self ## Verifies that the current Array is equal to the given one, ignoring case considerations. @warning_ignore("unused_parameter") -func is_equal_ignoring_case(expected) -> GdUnitArrayAssert: +func is_equal_ignoring_case(expected :Variant) -> GdUnitArrayAssert: return self ## Verifies that the current Array is not equal to the given one. @warning_ignore("unused_parameter") -func is_not_equal(expected) -> GdUnitArrayAssert: +func is_not_equal(expected :Variant) -> GdUnitArrayAssert: return self ## Verifies that the current Array is not equal to the given one, ignoring case considerations. @warning_ignore("unused_parameter") -func is_not_equal_ignoring_case(expected) -> GdUnitArrayAssert: +func is_not_equal_ignoring_case(expected :Variant) -> GdUnitArrayAssert: return self @@ -49,14 +49,14 @@ func is_not_empty() -> GdUnitArrayAssert: ## Verifies that the current Array is the same. [br] ## Compares the current by object reference equals @warning_ignore("unused_parameter", "shadowed_global_identifier") -func is_same(expected) -> GdUnitArrayAssert: +func is_same(expected :Variant) -> GdUnitArrayAssert: return self ## Verifies that the current Array is NOT the same. [br] ## Compares the current by object reference equals @warning_ignore("unused_parameter", "shadowed_global_identifier") -func is_not_same(expected) -> GdUnitArrayAssert: +func is_not_same(expected :Variant) -> GdUnitArrayAssert: return self @@ -69,42 +69,42 @@ func has_size(expectd: int) -> GdUnitArrayAssert: ## Verifies that the current Array contains the given values, in any order.[br] ## The values are compared by deep parameter comparision, for object reference compare you have to use [method contains_same] @warning_ignore("unused_parameter") -func contains(expected) -> GdUnitArrayAssert: +func contains(expected :Variant) -> GdUnitArrayAssert: return self ## Verifies that the current Array contains exactly only the given values and nothing else, in same order.[br] ## The values are compared by deep parameter comparision, for object reference compare you have to use [method contains_same_exactly] @warning_ignore("unused_parameter") -func contains_exactly(expected) -> GdUnitArrayAssert: +func contains_exactly(expected :Variant) -> GdUnitArrayAssert: return self ## Verifies that the current Array contains exactly only the given values and nothing else, in any order.[br] ## The values are compared by deep parameter comparision, for object reference compare you have to use [method contains_same_exactly_in_any_order] @warning_ignore("unused_parameter") -func contains_exactly_in_any_order(expected) -> GdUnitArrayAssert: +func contains_exactly_in_any_order(expected :Variant) -> GdUnitArrayAssert: return self ## Verifies that the current Array contains the given values, in any order.[br] ## The values are compared by object reference, for deep parameter comparision use [method contains] @warning_ignore("unused_parameter") -func contains_same(expected) -> GdUnitArrayAssert: +func contains_same(expected :Variant) -> GdUnitArrayAssert: return self ## Verifies that the current Array contains exactly only the given values and nothing else, in same order.[br] ## The values are compared by object reference, for deep parameter comparision use [method contains_exactly] @warning_ignore("unused_parameter") -func contains_same_exactly(expected) -> GdUnitArrayAssert: +func contains_same_exactly(expected :Variant) -> GdUnitArrayAssert: return self ## Verifies that the current Array contains exactly only the given values and nothing else, in any order.[br] ## The values are compared by object reference, for deep parameter comparision use [method contains_exactly_in_any_order] @warning_ignore("unused_parameter") -func contains_same_exactly_in_any_order(expected) -> GdUnitArrayAssert: +func contains_same_exactly_in_any_order(expected :Variant) -> GdUnitArrayAssert: return self @@ -118,7 +118,7 @@ func contains_same_exactly_in_any_order(expected) -> GdUnitArrayAssert: ## assert_array([1, 2, 3, 4, 5]).not_contains([2, 6]) ## [/codeblock] @warning_ignore("unused_parameter") -func not_contains(expected) -> GdUnitArrayAssert: +func not_contains(expected :Variant) -> GdUnitArrayAssert: return self @@ -132,7 +132,7 @@ func not_contains(expected) -> GdUnitArrayAssert: ## assert_array([1, 2, 3, 4, 5]).not_contains([2, 6]) ## [/codeblock] @warning_ignore("unused_parameter") -func not_contains_same(expected) -> GdUnitArrayAssert: +func not_contains_same(expected :Variant) -> GdUnitArrayAssert: return self diff --git a/addons/gdUnit4/src/GdUnitAssert.gd b/addons/gdUnit4/src/GdUnitAssert.gd index 04ba5263..d2a5fd0b 100644 --- a/addons/gdUnit4/src/GdUnitAssert.gd +++ b/addons/gdUnit4/src/GdUnitAssert.gd @@ -3,59 +3,39 @@ class_name GdUnitAssert extends RefCounted -# Scans the current stack trace for the root cause to extract the line number -static func _get_line_number() -> int: - var stack_trace := get_stack() - if stack_trace == null or stack_trace.is_empty(): - return -1 - for stack_info in stack_trace: - var function :String = stack_info.get("function") - # we catch helper asserts to skip over to return the correct line number - if function.begins_with("assert_"): - continue - if function.begins_with("test_"): - return stack_info.get("line") - var source :String = stack_info.get("source") - if source.is_empty() \ - or source.begins_with("user://") \ - or source.ends_with("GdUnitAssert.gd") \ - or source.ends_with("AssertImpl.gd") \ - or source.ends_with("GdUnitTestSuite.gd") \ - or source.ends_with("GdUnitSceneRunnerImpl.gd") \ - or source.ends_with("GdUnitObjectInteractions.gd") \ - or source.ends_with("GdUnitAwaiter.gd"): - continue - return stack_info.get("line") - return -1 - - ## Verifies that the current value is null. +@warning_ignore("untyped_declaration") func is_null(): return self ## Verifies that the current value is not null. +@warning_ignore("untyped_declaration") func is_not_null(): return self ## Verifies that the current value is equal to expected one. @warning_ignore("unused_parameter") +@warning_ignore("untyped_declaration") func is_equal(expected): return self ## Verifies that the current value is not equal to expected one. @warning_ignore("unused_parameter") +@warning_ignore("untyped_declaration") func is_not_equal(expected): return self +@warning_ignore("untyped_declaration") func test_fail(): return self ## Overrides the default failure message by given custom message. @warning_ignore("unused_parameter") +@warning_ignore("untyped_declaration") func override_failure_message(message :String): return self diff --git a/addons/gdUnit4/src/GdUnitAwaiter.gd b/addons/gdUnit4/src/GdUnitAwaiter.gd index c1fc2ccf..fc2e487b 100644 --- a/addons/gdUnit4/src/GdUnitAwaiter.gd +++ b/addons/gdUnit4/src/GdUnitAwaiter.gd @@ -11,21 +11,20 @@ const GdUnitAssertImpl = preload("res://addons/gdUnit4/src/asserts/GdUnitAssertI # timeout: the timeout in ms, default is set to 2000ms func await_signal_on(source :Object, signal_name :String, args :Array = [], timeout_millis :int = 2000) -> Variant: # fail fast if the given source instance invalid - var line_number := GdUnitAssert._get_line_number() + var assert_that := GdUnitAssertImpl.new(signal_name) + var line_number := GdUnitAssertions.get_line_number() if not is_instance_valid(source): - GdUnitAssertImpl.new(signal_name)\ - .report_error(GdAssertMessages.error_await_signal_on_invalid_instance(source, signal_name, args), line_number) + assert_that.report_error(GdAssertMessages.error_await_signal_on_invalid_instance(source, signal_name, args), line_number) return await Engine.get_main_loop().process_frame # fail fast if the given source instance invalid if not is_instance_valid(source): - GdUnitAssertImpl.new(signal_name)\ - .report_error(GdAssertMessages.error_await_signal_on_invalid_instance(source, signal_name, args), line_number) + assert_that.report_error(GdAssertMessages.error_await_signal_on_invalid_instance(source, signal_name, args), line_number) return await await_idle_frame() var awaiter := GdUnitSignalAwaiter.new(timeout_millis) var value :Variant = await awaiter.on_signal(source, signal_name, args) if awaiter.is_interrupted(): - var failure = "await_signal_on(%s, %s) timed out after %sms" % [signal_name, args, timeout_millis] - GdUnitAssertImpl.new(signal_name).report_error(failure, line_number) + var failure := "await_signal_on(%s, %s) timed out after %sms" % [signal_name, args, timeout_millis] + assert_that.report_error(failure, line_number) return value @@ -35,7 +34,7 @@ func await_signal_on(source :Object, signal_name :String, args :Array = [], time # args: the expected signal arguments as an array # timeout: the timeout in ms, default is set to 2000ms func await_signal_idle_frames(source :Object, signal_name :String, args :Array = [], timeout_millis :int = 2000) -> Variant: - var line_number := GdUnitAssert._get_line_number() + var line_number := GdUnitAssertions.get_line_number() # fail fast if the given source instance invalid if not is_instance_valid(source): GdUnitAssertImpl.new(signal_name)\ @@ -44,7 +43,7 @@ func await_signal_idle_frames(source :Object, signal_name :String, args :Array = var awaiter := GdUnitSignalAwaiter.new(timeout_millis, true) var value :Variant = await awaiter.on_signal(source, signal_name, args) if awaiter.is_interrupted(): - var failure = "await_signal_idle_frames(%s, %s) timed out after %sms" % [signal_name, args, timeout_millis] + var failure := "await_signal_idle_frames(%s, %s) timed out after %sms" % [signal_name, args, timeout_millis] GdUnitAssertImpl.new(signal_name).report_error(failure, line_number) return value diff --git a/addons/gdUnit4/src/GdUnitBoolAssert.gd b/addons/gdUnit4/src/GdUnitBoolAssert.gd index 7e62dbe4..de67a941 100644 --- a/addons/gdUnit4/src/GdUnitBoolAssert.gd +++ b/addons/gdUnit4/src/GdUnitBoolAssert.gd @@ -15,13 +15,13 @@ func is_not_null() -> GdUnitBoolAssert: ## Verifies that the current value is equal to the given one. @warning_ignore("unused_parameter") -func is_equal(expected) -> GdUnitBoolAssert: +func is_equal(expected :Variant) -> GdUnitBoolAssert: return self ## Verifies that the current value is not equal to the given one. @warning_ignore("unused_parameter") -func is_not_equal(expected) -> GdUnitBoolAssert: +func is_not_equal(expected :Variant) -> GdUnitBoolAssert: return self diff --git a/addons/gdUnit4/src/GdUnitConstants.gd b/addons/gdUnit4/src/GdUnitConstants.gd index 578d0327..04458947 100644 --- a/addons/gdUnit4/src/GdUnitConstants.gd +++ b/addons/gdUnit4/src/GdUnitConstants.gd @@ -2,3 +2,5 @@ class_name GdUnitConstants extends RefCounted const NO_ARG :Variant = "<--null-->" + +const EXPECT_ASSERT_REPORT_FAILURES := "expect_assert_report_failures" diff --git a/addons/gdUnit4/src/GdUnitDictionaryAssert.gd b/addons/gdUnit4/src/GdUnitDictionaryAssert.gd index 092a7942..49dd3053 100644 --- a/addons/gdUnit4/src/GdUnitDictionaryAssert.gd +++ b/addons/gdUnit4/src/GdUnitDictionaryAssert.gd @@ -15,13 +15,13 @@ func is_not_null() -> GdUnitDictionaryAssert: ## Verifies that the current dictionary is equal to the given one, ignoring order. @warning_ignore("unused_parameter") -func is_equal(expected) -> GdUnitDictionaryAssert: +func is_equal(expected :Variant) -> GdUnitDictionaryAssert: return self ## Verifies that the current dictionary is not equal to the given one, ignoring order. @warning_ignore("unused_parameter") -func is_not_equal(expected) -> GdUnitDictionaryAssert: +func is_not_equal(expected :Variant) -> GdUnitDictionaryAssert: return self @@ -38,14 +38,14 @@ func is_not_empty() -> GdUnitDictionaryAssert: ## Verifies that the current dictionary is the same. [br] ## Compares the current by object reference equals @warning_ignore("unused_parameter", "shadowed_global_identifier") -func is_same(expected) -> GdUnitDictionaryAssert: +func is_same(expected :Variant) -> GdUnitDictionaryAssert: return self ## Verifies that the current dictionary is NOT the same. [br] ## Compares the current by object reference equals -@warning_ignore("unused_parameter", "shadowed_global_identifier") -func is_not_same(expected) -> GdUnitDictionaryAssert: +@warning_ignore("unused_parameter") +func is_not_same(expected :Variant) -> GdUnitDictionaryAssert: return self @@ -65,7 +65,7 @@ func contains_keys(expected :Array) -> GdUnitDictionaryAssert: ## Verifies that the current dictionary contains the given key and value.[br] ## The key and value are compared by deep parameter comparision, for object reference compare you have to use [method contains_same_key_value] @warning_ignore("unused_parameter") -func contains_key_value(key, value) -> GdUnitDictionaryAssert: +func contains_key_value(key :Variant, value :Variant) -> GdUnitDictionaryAssert: return self @@ -94,7 +94,7 @@ func contains_same_keys(expected :Array) -> GdUnitDictionaryAssert: ## Verifies that the current dictionary contains the given key and value.[br] ## The key and value are compared by object reference, for deep parameter comparision use [method contains_key_value] @warning_ignore("unused_parameter") -func contains_same_key_value(key, value) -> GdUnitDictionaryAssert: +func contains_same_key_value(key :Variant, value :Variant) -> GdUnitDictionaryAssert: return self diff --git a/addons/gdUnit4/src/GdUnitFuncAssert.gd b/addons/gdUnit4/src/GdUnitFuncAssert.gd index 77c46090..c5e0e5fc 100644 --- a/addons/gdUnit4/src/GdUnitFuncAssert.gd +++ b/addons/gdUnit4/src/GdUnitFuncAssert.gd @@ -17,14 +17,14 @@ func is_not_null() -> GdUnitFuncAssert: ## Verifies that the current value is equal to the given one. @warning_ignore("unused_parameter") -func is_equal(expected) -> GdUnitFuncAssert: +func is_equal(expected :Variant) -> GdUnitFuncAssert: await Engine.get_main_loop().process_frame return self ## Verifies that the current value is not equal to the given one. @warning_ignore("unused_parameter") -func is_not_equal(expected) -> GdUnitFuncAssert: +func is_not_equal(expected :Variant) -> GdUnitFuncAssert: await Engine.get_main_loop().process_frame return self diff --git a/addons/gdUnit4/src/GdUnitObjectAssert.gd b/addons/gdUnit4/src/GdUnitObjectAssert.gd index d73bfd5a..f2068e5a 100644 --- a/addons/gdUnit4/src/GdUnitObjectAssert.gd +++ b/addons/gdUnit4/src/GdUnitObjectAssert.gd @@ -5,13 +5,13 @@ extends GdUnitAssert ## Verifies that the current value is equal to expected one. @warning_ignore("unused_parameter") -func is_equal(expected) -> GdUnitObjectAssert: +func is_equal(expected :Variant) -> GdUnitObjectAssert: return self ## Verifies that the current value is not equal to expected one. @warning_ignore("unused_parameter") -func is_not_equal(expected) -> GdUnitObjectAssert: +func is_not_equal(expected :Variant) -> GdUnitObjectAssert: return self @@ -27,13 +27,13 @@ func is_not_null() -> GdUnitObjectAssert: ## Verifies that the current value is the same as the given one. @warning_ignore("unused_parameter", "shadowed_global_identifier") -func is_same(expected) -> GdUnitObjectAssert: +func is_same(expected :Variant) -> GdUnitObjectAssert: return self ## Verifies that the current value is not the same as the given one. @warning_ignore("unused_parameter") -func is_not_same(expected) -> GdUnitObjectAssert: +func is_not_same(expected :Variant) -> GdUnitObjectAssert: return self @@ -45,5 +45,5 @@ func is_instanceof(expected :Object) -> GdUnitObjectAssert: ## Verifies that the current value is not an instance of the given type. @warning_ignore("unused_parameter") -func is_not_instanceof(expected) -> GdUnitObjectAssert: +func is_not_instanceof(expected :Variant) -> GdUnitObjectAssert: return self diff --git a/addons/gdUnit4/src/GdUnitResultAssert.gd b/addons/gdUnit4/src/GdUnitResultAssert.gd index dc33d5f7..347e6374 100644 --- a/addons/gdUnit4/src/GdUnitResultAssert.gd +++ b/addons/gdUnit4/src/GdUnitResultAssert.gd @@ -13,7 +13,7 @@ func is_not_null() -> GdUnitResultAssert: return self -## Verifies that the result is ends up with empty +## Verifies that the result is ends up with empty func is_empty() -> GdUnitResultAssert: return self @@ -41,5 +41,5 @@ func contains_message(expected :String) -> GdUnitResultAssert: ## Verifies that the result contains the given value @warning_ignore("unused_parameter") -func is_value(expected) -> GdUnitResultAssert: +func is_value(expected :Variant) -> GdUnitResultAssert: return self diff --git a/addons/gdUnit4/src/GdUnitSceneRunner.gd b/addons/gdUnit4/src/GdUnitSceneRunner.gd index 62c2dc90..5707a6ae 100644 --- a/addons/gdUnit4/src/GdUnitSceneRunner.gd +++ b/addons/gdUnit4/src/GdUnitSceneRunner.gd @@ -21,6 +21,27 @@ func get_global_mouse_position() -> Vector2: return Vector2.ZERO +## Simulates that an action has been pressed.[br] +## [member action] : the action e.g. [code]"ui_up"[/code][br] +@warning_ignore("unused_parameter") +func simulate_action_pressed(action :String) -> GdUnitSceneRunner: + return self + + +## Simulates that an action is pressed.[br] +## [member action] : the action e.g. [code]"ui_up"[/code][br] +@warning_ignore("unused_parameter") +func simulate_action_press(action :String) -> GdUnitSceneRunner: + return self + + +## Simulates that an action has been released.[br] +## [member action] : the action e.g. [code]"ui_up"[/code][br] +@warning_ignore("unused_parameter") +func simulate_action_release(action :String) -> GdUnitSceneRunner: + return self + + ## Simulates that a key has been pressed.[br] ## [member key_code] : the key code e.g. [constant KEY_ENTER][br] ## [member shift_pressed] : false by default set to true if simmulate shift is press[br] @@ -56,10 +77,35 @@ func simulate_mouse_move(pos :Vector2) -> GdUnitSceneRunner: ## Simulates a mouse move to the relative coordinates (offset).[br] -## [member relative] : The relative position, e.g. the mouse position offset[br] -## [member speed] : The mouse speed in pixels per second.[br] +## [color=yellow]You must use [b]await[/b] to wait until the simulated mouse movement is complete.[/color][br] +## [br] +## [member relative] : The relative position, indicating the mouse position offset.[br] +## [member time] : The time to move the mouse by the relative position in seconds (default is 1 second).[br] +## [member trans_type] : Sets the type of transition used (default is TRANS_LINEAR).[br] +## [codeblock] +## func test_move_mouse(): +## var runner = scene_runner("res://scenes/simple_scene.tscn") +## await runner.simulate_mouse_move_relative(Vector2(100,100)) +## [/codeblock] +@warning_ignore("unused_parameter") +func simulate_mouse_move_relative(relative: Vector2, time: float = 1.0, trans_type: Tween.TransitionType = Tween.TRANS_LINEAR) -> GdUnitSceneRunner: + await Engine.get_main_loop().process_frame + return self + + +## Simulates a mouse move to the absolute coordinates.[br] +## [color=yellow]You must use [b]await[/b] to wait until the simulated mouse movement is complete.[/color][br] +## [br] +## [member position] : The final position of the mouse.[br] +## [member time] : The time to move the mouse to the final position in seconds (default is 1 second).[br] +## [member trans_type] : Sets the type of transition used (default is TRANS_LINEAR).[br] +## [codeblock] +## func test_move_mouse(): +## var runner = scene_runner("res://scenes/simple_scene.tscn") +## await runner.simulate_mouse_move_absolute(Vector2(100,100)) +## [/codeblock] @warning_ignore("unused_parameter") -func simulate_mouse_move_relative(relative :Vector2, speed :Vector2 = Vector2.ONE) -> GdUnitSceneRunner: +func simulate_mouse_move_absolute(position: Vector2, time: float = 1.0, trans_type: Tween.TransitionType = Tween.TRANS_LINEAR) -> GdUnitSceneRunner: await Engine.get_main_loop().process_frame return self @@ -106,7 +152,18 @@ func simulate_frames(frames: int, delta_milli :int = -1) -> GdUnitSceneRunner: ## [member signal_name] : the signal to stop the simulation[br] ## [member args] : optional signal arguments to be matched for stop[br] @warning_ignore("unused_parameter") -func simulate_until_signal(signal_name :String, arg0=NO_ARG, arg1=NO_ARG, arg2=NO_ARG, arg3=NO_ARG, arg4=NO_ARG, arg5=NO_ARG, arg6=NO_ARG, arg7=NO_ARG, arg8=NO_ARG, arg9=NO_ARG) -> GdUnitSceneRunner: +func simulate_until_signal( + signal_name :String, + arg0 :Variant = NO_ARG, + arg1 :Variant = NO_ARG, + arg2 :Variant = NO_ARG, + arg3 :Variant = NO_ARG, + arg4 :Variant = NO_ARG, + arg5 :Variant = NO_ARG, + arg6 :Variant = NO_ARG, + arg7 :Variant = NO_ARG, + arg8 :Variant = NO_ARG, + arg9 :Variant = NO_ARG) -> GdUnitSceneRunner: await Engine.get_main_loop().process_frame return self @@ -116,11 +173,29 @@ func simulate_until_signal(signal_name :String, arg0=NO_ARG, arg1=NO_ARG, arg2=N ## [member signal_name] : the signal to stop the simulation[br] ## [member args] : optional signal arguments to be matched for stop @warning_ignore("unused_parameter") -func simulate_until_object_signal(source :Object, signal_name :String, arg0=NO_ARG, arg1=NO_ARG, arg2=NO_ARG, arg3=NO_ARG, arg4=NO_ARG, arg5=NO_ARG, arg6=NO_ARG, arg7=NO_ARG, arg8=NO_ARG, arg9=NO_ARG) -> GdUnitSceneRunner: +func simulate_until_object_signal( + source :Object, + signal_name :String, + arg0 :Variant = NO_ARG, + arg1 :Variant = NO_ARG, + arg2 :Variant = NO_ARG, + arg3 :Variant = NO_ARG, + arg4 :Variant = NO_ARG, + arg5 :Variant = NO_ARG, + arg6 :Variant = NO_ARG, + arg7 :Variant = NO_ARG, + arg8 :Variant = NO_ARG, + arg9 :Variant = NO_ARG) -> GdUnitSceneRunner: await Engine.get_main_loop().process_frame return self +### Waits for all input events are processed +func await_input_processed() -> void: + await Engine.get_main_loop().process_frame + await Engine.get_main_loop().physics_frame + + ## Waits for the function return value until specified timeout or fails.[br] ## [member args] : optional function arguments @warning_ignore("unused_parameter") @@ -141,7 +216,7 @@ func await_func_on(source :Object, func_name :String, args := []) -> GdUnitFuncA ## [member args] : the expected signal arguments as an array[br] ## [member timeout] : the timeout in ms, default is set to 2000ms @warning_ignore("unused_parameter") -func await_signal(signal_name :String, args := [], timeout := 2000 ): +func await_signal(signal_name :String, args := [], timeout := 2000 ) -> void: await Engine.get_main_loop().process_frame pass @@ -152,7 +227,7 @@ func await_signal(signal_name :String, args := [], timeout := 2000 ): ## [member args] : the expected signal arguments as an array[br] ## [member timeout] : the timeout in ms, default is set to 2000ms @warning_ignore("unused_parameter") -func await_signal_on(source :Object, signal_name :String, args := [], timeout := 2000 ): +func await_signal_on(source :Object, signal_name :String, args := [], timeout := 2000 ) -> void: pass @@ -182,8 +257,19 @@ func set_property(name :String, value :Variant) -> bool: ## [member args] : optional function arguments[br] ## [member return] : the function result @warning_ignore("unused_parameter") -func invoke(name :String, arg0=NO_ARG, arg1=NO_ARG, arg2=NO_ARG, arg3=NO_ARG, arg4=NO_ARG, arg5=NO_ARG, arg6=NO_ARG, arg7=NO_ARG, arg8=NO_ARG, arg9=NO_ARG): - pass +func invoke( + name :String, + arg0 :Variant = NO_ARG, + arg1 :Variant = NO_ARG, + arg2 :Variant = NO_ARG, + arg3 :Variant = NO_ARG, + arg4 :Variant = NO_ARG, + arg5 :Variant = NO_ARG, + arg6 :Variant = NO_ARG, + arg7 :Variant = NO_ARG, + arg8 :Variant = NO_ARG, + arg9 :Variant = NO_ARG) -> Variant: + return null ## Searches for the specified node with the name in the current scene and returns it, otherwise null.[br] diff --git a/addons/gdUnit4/src/GdUnitStringAssert.gd b/addons/gdUnit4/src/GdUnitStringAssert.gd index 91e5bf0a..5b4a6a1e 100644 --- a/addons/gdUnit4/src/GdUnitStringAssert.gd +++ b/addons/gdUnit4/src/GdUnitStringAssert.gd @@ -5,25 +5,25 @@ extends GdUnitAssert ## Verifies that the current String is equal to the given one. @warning_ignore("unused_parameter") -func is_equal(expected) -> GdUnitStringAssert: +func is_equal(expected :Variant) -> GdUnitStringAssert: return self ## Verifies that the current String is equal to the given one, ignoring case considerations. @warning_ignore("unused_parameter") -func is_equal_ignoring_case(expected) -> GdUnitStringAssert: +func is_equal_ignoring_case(expected :Variant) -> GdUnitStringAssert: return self ## Verifies that the current String is not equal to the given one. @warning_ignore("unused_parameter") -func is_not_equal(expected) -> GdUnitStringAssert: +func is_not_equal(expected :Variant) -> GdUnitStringAssert: return self ## Verifies that the current String is not equal to the given one, ignoring case considerations. @warning_ignore("unused_parameter") -func is_not_equal_ignoring_case(expected) -> GdUnitStringAssert: +func is_not_equal_ignoring_case(expected :Variant) -> GdUnitStringAssert: return self @@ -75,5 +75,5 @@ func ends_with(expected: String) -> GdUnitStringAssert: ## Verifies that the current String has the expected length by used comparator. @warning_ignore("unused_parameter") -func has_length(lenght: int, comparator: int = Comparator.EQUAL) -> GdUnitStringAssert: +func has_length(length: int, comparator: int = Comparator.EQUAL) -> GdUnitStringAssert: return self diff --git a/addons/gdUnit4/src/GdUnitTestSuite.gd b/addons/gdUnit4/src/GdUnitTestSuite.gd index 30734918..f23cbd74 100644 --- a/addons/gdUnit4/src/GdUnitTestSuite.gd +++ b/addons/gdUnit4/src/GdUnitTestSuite.gd @@ -10,7 +10,7 @@ ## [/codeblock] ## @tutorial: https://mikeschulze.github.io/gdUnit4/faq/test-suite/ -@icon("res://addons/gdUnit4/src/ui/assets/TestSuite.svg") +@icon("res://addons/gdUnit4/src/ui/settings/logo.png") class_name GdUnitTestSuite extends Node @@ -24,7 +24,7 @@ var __skip_reason :String = "Unknow." var __active_test_case :String var __awaiter := __gdunit_awaiter() # holds the actual execution context -var __execution_context +var __execution_context :RefCounted ### We now load all used asserts and tool scripts into the cache according to the principle of "lazy loading" @@ -43,15 +43,19 @@ func __gdunit_tools() -> GDScript: return __lazy_load("res://addons/gdUnit4/src/core/GdUnitTools.gd") +func __gdunit_file_access() -> GDScript: + return __lazy_load("res://addons/gdUnit4/src/core/GdUnitFileAccess.gd") + + func __gdunit_awaiter() -> Object: return __lazy_load("res://addons/gdUnit4/src/GdUnitAwaiter.gd").new() -func __gdunit_argument_matchers(): +func __gdunit_argument_matchers() -> GDScript: return __lazy_load("res://addons/gdUnit4/src/matchers/GdUnitArgumentMatchers.gd") -func __gdunit_object_interactions(): +func __gdunit_object_interactions() -> GDScript: return __lazy_load("res://addons/gdUnit4/src/core/GdUnitObjectInteractions.gd") @@ -91,12 +95,17 @@ func set_active_test_case(test_case :String) -> void: # 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 func error_as_string(error_number :int) -> String: - return __gdunit_tools().error_as_string(error_number) + return error_string(error_number) ## A litle helper to auto freeing your created objects after test execution -func auto_free(obj) -> Variant: - return __execution_context.register_auto_free(obj) +func auto_free(obj :Variant) -> Variant: + if __execution_context != null: + return __execution_context.register_auto_free(obj) + else: + if is_instance_valid(obj): + obj.queue_free() + return obj @warning_ignore("native_method_override") @@ -117,35 +126,35 @@ func discard_error_interupted_by_timeout() -> void: ## Useful for storing data during test execution. [br] ## The directory is automatically deleted after test suite execution func create_temp_dir(relative_path :String) -> String: - return __gdunit_tools().create_temp_dir(relative_path) + return __gdunit_file_access().create_temp_dir(relative_path) ## Deletes the temporary base directory[br] ## Is called automatically after each execution of the test suite -func clean_temp_dir(): - __gdunit_tools().clear_tmp() +func clean_temp_dir() -> void: + __gdunit_file_access().clear_tmp() ## Creates a new file under the temporary directory *user://tmp* + [br] ## with given name and given file (default = File.WRITE)[br] ## If success the returned File is automatically closed after the execution of the test suite func create_temp_file(relative_path :String, file_name :String, mode := FileAccess.WRITE) -> FileAccess: - return __gdunit_tools().create_temp_file(relative_path, file_name, mode) + return __gdunit_file_access().create_temp_file(relative_path, file_name, mode) ## Reads a resource by given path into a PackedStringArray. func resource_as_array(resource_path :String) -> PackedStringArray: - return __gdunit_tools().resource_as_array(resource_path) + return __gdunit_file_access().resource_as_array(resource_path) ## Reads a resource by given path and returned the content as String. func resource_as_string(resource_path :String) -> String: - return __gdunit_tools().resource_as_string(resource_path) + return __gdunit_file_access().resource_as_string(resource_path) ## Reads a resource by given path and return Variand translated by str_to_var -func resource_as_var(resource_path :String): - return str_to_var(__gdunit_tools().resource_as_string(resource_path)) +func resource_as_var(resource_path :String) -> Variant: + return str_to_var(__gdunit_file_access().resource_as_string(resource_path)) ## clears the debuger error list[br] @@ -164,7 +173,7 @@ func await_signal_on(source :Object, signal_name :String, args :Array = [], time ## Waits until the next idle frame -func await_idle_frame(): +func await_idle_frame() -> void: await __awaiter.await_idle_frame() @@ -175,7 +184,7 @@ func await_idle_frame(): ## await await_millis(myNode, 100).completed ## [/codeblock][br] ## use this waiter and not `await get_tree().create_timer().timeout to prevent errors when a test case is timed out -func await_millis(timeout :int): +func await_millis(timeout :int) -> void: await __awaiter.await_millis(timeout) @@ -184,19 +193,19 @@ func await_millis(timeout :int): ## example:[br] ## [codeblock] ## # creates a runner by using a instanciated scene -## var scene = load("res://foo/my_scne.tscn").instantiate() +## var scene = load("res://foo/my_scne.tscn").instantiate() ## var runner := scene_runner(scene) ## ## # or simply creates a runner by using the scene resource path ## var runner := scene_runner("res://foo/my_scne.tscn") ## [/codeblock] -func scene_runner(scene, verbose := false) -> GdUnitSceneRunner: +func scene_runner(scene :Variant, verbose := false) -> GdUnitSceneRunner: return auto_free(__lazy_load("res://addons/gdUnit4/src/core/GdUnitSceneRunnerImpl.gd").new(scene, verbose)) # === Mocking & Spy =========================================================== -## do return a default value for primitive types or null +## do return a default value for primitive types or null const RETURN_DEFAULTS = GdUnitMock.RETURN_DEFAULTS ## do call the real implementation const CALL_REAL_FUNC = GdUnitMock.CALL_REAL_FUNC @@ -206,12 +215,12 @@ const RETURN_DEEP_STUB = GdUnitMock.RETURN_DEEP_STUB ## Creates a mock for given class name -func mock(clazz, mock_mode := RETURN_DEFAULTS) -> Object: +func mock(clazz :Variant, mock_mode := RETURN_DEFAULTS) -> Variant: return __lazy_load("res://addons/gdUnit4/src/mocking/GdUnitMockBuilder.gd").build(clazz, mock_mode) ## Creates a spy checked given object instance -func spy(instance) -> Object: +func spy(instance :Variant) -> Variant: return __lazy_load("res://addons/gdUnit4/src/spy/GdUnitSpyBuilder.gd").build(instance) @@ -221,27 +230,27 @@ func spy(instance) -> Object: ## # overrides the return value of myMock.is_selected() to false ## do_return(false).on(myMock).is_selected() ## [/codeblock] -func do_return(value) -> GdUnitMock: +func do_return(value :Variant) -> GdUnitMock: return GdUnitMock.new(value) ## Verifies certain behavior happened at least once or exact number of times -func verify(obj, times := 1): +func verify(obj :Variant, times := 1) -> Variant: return __gdunit_object_interactions().verify(obj, times) ## Verifies no interactions is happen checked this mock or spy -func verify_no_interactions(obj) -> GdUnitAssert: +func verify_no_interactions(obj :Variant) -> GdUnitAssert: return __gdunit_object_interactions().verify_no_interactions(obj) ## Verifies the given mock or spy has any unverified interaction. -func verify_no_more_interactions(obj) -> GdUnitAssert: +func verify_no_more_interactions(obj :Variant) -> GdUnitAssert: return __gdunit_object_interactions().verify_no_more_interactions(obj) ## Resets the saved function call counters checked a mock or spy -func reset(obj) -> void: +func reset(obj :Variant) -> void: __gdunit_object_interactions().reset(obj) @@ -363,16 +372,16 @@ func any_basis() -> GdUnitArgumentMatcher: return __gdunit_argument_matchers().by_type(TYPE_BASIS) -## Argument matcher to match any Transform3D value -func any_transform() -> GdUnitArgumentMatcher: - return __gdunit_argument_matchers().by_type(TYPE_TRANSFORM3D) - - ## Argument matcher to match any Transform2D value func any_transform_2d() -> GdUnitArgumentMatcher: return __gdunit_argument_matchers().by_type(TYPE_TRANSFORM2D) +## Argument matcher to match any Transform3D value +func any_transform_3d() -> GdUnitArgumentMatcher: + return __gdunit_argument_matchers().by_type(TYPE_TRANSFORM3D) + + ## Argument matcher to match any NodePath value func any_node_path() -> GdUnitArgumentMatcher: return __gdunit_argument_matchers().by_type(TYPE_NODE_PATH) @@ -399,37 +408,47 @@ func any_array() -> GdUnitArgumentMatcher: ## Argument matcher to match any PackedByteArray value -func any_pool_byte_array() -> GdUnitArgumentMatcher: +func any_packed_byte_array() -> GdUnitArgumentMatcher: return __gdunit_argument_matchers().by_type(TYPE_PACKED_BYTE_ARRAY) ## Argument matcher to match any PackedInt32Array value -func any_pool_int_array() -> GdUnitArgumentMatcher: +func any_packed_int32_array() -> GdUnitArgumentMatcher: return __gdunit_argument_matchers().by_type(TYPE_PACKED_INT32_ARRAY) +## Argument matcher to match any PackedInt64Array value +func any_packed_int64_array() -> GdUnitArgumentMatcher: + return __gdunit_argument_matchers().by_type(TYPE_PACKED_INT64_ARRAY) + + ## Argument matcher to match any PackedFloat32Array value -func any_pool_float_array() -> GdUnitArgumentMatcher: +func any_packed_float32_array() -> GdUnitArgumentMatcher: return __gdunit_argument_matchers().by_type(TYPE_PACKED_FLOAT32_ARRAY) +## Argument matcher to match any PackedFloat64Array value +func any_packed_float64_array() -> GdUnitArgumentMatcher: + return __gdunit_argument_matchers().by_type(TYPE_PACKED_FLOAT64_ARRAY) + + ## Argument matcher to match any PackedStringArray value -func any_pool_string_array() -> GdUnitArgumentMatcher: +func any_packed_string_array() -> GdUnitArgumentMatcher: return __gdunit_argument_matchers().by_type(TYPE_PACKED_STRING_ARRAY) ## Argument matcher to match any PackedVector2Array value -func any_pool_vector2_array() -> GdUnitArgumentMatcher: +func any_packed_vector2_array() -> GdUnitArgumentMatcher: return __gdunit_argument_matchers().by_type(TYPE_PACKED_VECTOR2_ARRAY) ## Argument matcher to match any PackedVector3Array value -func any_pool_vector3_array() -> GdUnitArgumentMatcher: +func any_packed_vector3_array() -> GdUnitArgumentMatcher: return __gdunit_argument_matchers().by_type(TYPE_PACKED_VECTOR3_ARRAY) ## Argument matcher to match any PackedColorArray value -func any_pool_color_array() -> GdUnitArgumentMatcher: +func any_packed_color_array() -> GdUnitArgumentMatcher: return __gdunit_argument_matchers().by_type(TYPE_PACKED_COLOR_ARRAY) @@ -462,7 +481,7 @@ func tuple(arg0 :Variant, ## The common assertion tool to verify values. ## It checks the given value by type to fit to the best assert -func assert_that(current) -> GdUnitAssert: +func assert_that(current :Variant) -> GdUnitAssert: match typeof(current): TYPE_BOOL: return assert_bool(current) @@ -487,22 +506,22 @@ func assert_that(current) -> GdUnitAssert: ## An assertion tool to verify boolean values. -func assert_bool(current) -> GdUnitBoolAssert: +func assert_bool(current :Variant) -> GdUnitBoolAssert: return __lazy_load("res://addons/gdUnit4/src/asserts/GdUnitBoolAssertImpl.gd").new(current) ## An assertion tool to verify String values. -func assert_str(current) -> GdUnitStringAssert: +func assert_str(current :Variant) -> GdUnitStringAssert: return __lazy_load("res://addons/gdUnit4/src/asserts/GdUnitStringAssertImpl.gd").new(current) ## An assertion tool to verify integer values. -func assert_int(current) -> GdUnitIntAssert: +func assert_int(current :Variant) -> GdUnitIntAssert: return __lazy_load("res://addons/gdUnit4/src/asserts/GdUnitIntAssertImpl.gd").new(current) ## An assertion tool to verify float values. -func assert_float(current) -> GdUnitFloatAssert: +func assert_float(current :Variant) -> GdUnitFloatAssert: return __lazy_load("res://addons/gdUnit4/src/asserts/GdUnitFloatAssertImpl.gd").new(current) @@ -512,31 +531,31 @@ func assert_float(current) -> GdUnitFloatAssert: ## [codeblock] ## assert_vector(Vector2(1.2, 1.000001)).is_equal(Vector2(1.2, 1.000001)) ## [/codeblock] -func assert_vector(current) -> GdUnitVectorAssert: +func assert_vector(current :Variant) -> GdUnitVectorAssert: return __lazy_load("res://addons/gdUnit4/src/asserts/GdUnitVectorAssertImpl.gd").new(current) ## An assertion tool to verify arrays. -func assert_array(current) -> GdUnitArrayAssert: +func assert_array(current :Variant) -> GdUnitArrayAssert: return __lazy_load("res://addons/gdUnit4/src/asserts/GdUnitArrayAssertImpl.gd").new(current) ## An assertion tool to verify dictionaries. -func assert_dict(current) -> GdUnitDictionaryAssert: +func assert_dict(current :Variant) -> GdUnitDictionaryAssert: return __lazy_load("res://addons/gdUnit4/src/asserts/GdUnitDictionaryAssertImpl.gd").new(current) ## An assertion tool to verify FileAccess. -func assert_file(current) -> GdUnitFileAssert: +func assert_file(current :Variant) -> GdUnitFileAssert: return __lazy_load("res://addons/gdUnit4/src/asserts/GdUnitFileAssertImpl.gd").new(current) ## An assertion tool to verify Objects. -func assert_object(current) -> GdUnitObjectAssert: +func assert_object(current :Variant) -> GdUnitObjectAssert: return __lazy_load("res://addons/gdUnit4/src/asserts/GdUnitObjectAssertImpl.gd").new(current) -func assert_result(current) -> GdUnitResultAssert: +func assert_result(current :Variant) -> GdUnitResultAssert: return __lazy_load("res://addons/gdUnit4/src/asserts/GdUnitResultAssertImpl.gd").new(current) @@ -579,7 +598,7 @@ func assert_failure_await(assertion :Callable) -> GdUnitFailureAssert: ## # tests no error was occured during execution the code ## await assert_error(func (): return 0 )\ ## .is_success() -## +## ## # tests an push_error('test error') was occured during execution the code ## await assert_error(func (): push_error('test error') )\ ## .is_push_error('test error') @@ -588,11 +607,11 @@ func assert_error(current :Callable) -> GdUnitGodotErrorAssert: return __lazy_load("res://addons/gdUnit4/src/asserts/GdUnitGodotErrorAssertImpl.gd").new(current) -func assert_not_yet_implemented(): +func assert_not_yet_implemented() -> void: __gdunit_assert().new(null).test_fail() -func fail(message :String): +func fail(message :String) -> void: __gdunit_assert().new(null).report_error(message) diff --git a/addons/gdUnit4/src/GdUnitTuple.gd b/addons/gdUnit4/src/GdUnitTuple.gd index a6e3c41b..6c910023 100644 --- a/addons/gdUnit4/src/GdUnitTuple.gd +++ b/addons/gdUnit4/src/GdUnitTuple.gd @@ -1,4 +1,4 @@ -## A tuple implementation to hold two or many values +## A tuple implementation to hold two or many values class_name GdUnitTuple extends RefCounted @@ -16,7 +16,7 @@ func _init(arg0:Variant, arg6 :Variant=NO_ARG, arg7 :Variant=NO_ARG, arg8 :Variant=NO_ARG, - arg9 :Variant=NO_ARG): + arg9 :Variant=NO_ARG) -> void: __values = GdArrayTools.filter_value([arg0,arg1,arg2,arg3,arg4,arg5,arg6,arg7,arg8,arg9], NO_ARG) @@ -24,5 +24,5 @@ func values() -> Array: return __values -func _to_string(): +func _to_string() -> String: return "tuple(%s)" % str(__values) diff --git a/addons/gdUnit4/src/GdUnitValueExtractor.gd b/addons/gdUnit4/src/GdUnitValueExtractor.gd index be702cf7..1a344454 100644 --- a/addons/gdUnit4/src/GdUnitValueExtractor.gd +++ b/addons/gdUnit4/src/GdUnitValueExtractor.gd @@ -4,6 +4,6 @@ extends RefCounted ## Extracts a value by given implementation -func extract_value(value): +func extract_value(value :Variant) -> Variant: push_error("Uninplemented func 'extract_value'") return value diff --git a/addons/gdUnit4/src/asserts/CallBackValueProvider.gd b/addons/gdUnit4/src/asserts/CallBackValueProvider.gd index 0b4a72cf..6be4b3ee 100644 --- a/addons/gdUnit4/src/asserts/CallBackValueProvider.gd +++ b/addons/gdUnit4/src/asserts/CallBackValueProvider.gd @@ -1,12 +1,12 @@ # a value provider unsing a callback to get `next` value from a certain function -class_name CallBackValueProvider +class_name CallBackValueProvider extends ValueProvider var _cb :Callable var _args :Array -func _init(instance :Object, func_name :String, args :Array = Array(), force_error := true): +func _init(instance :Object, func_name :String, args :Array = Array(), force_error := true) -> void: _cb = Callable(instance, func_name); _args = args if force_error and not _cb.is_valid(): @@ -21,5 +21,5 @@ func get_value() -> Variant: return await _cb.callv(_args) -func dispose(): +func dispose() -> void: _cb = Callable() diff --git a/addons/gdUnit4/src/asserts/DefaultValueProvider.gd b/addons/gdUnit4/src/asserts/DefaultValueProvider.gd index aa35d8f0..2f828fa2 100644 --- a/addons/gdUnit4/src/asserts/DefaultValueProvider.gd +++ b/addons/gdUnit4/src/asserts/DefaultValueProvider.gd @@ -1,11 +1,13 @@ # default value provider, simple returns the initial value -class_name DefaultValueProvider +class_name DefaultValueProvider extends ValueProvider -var _value +var _value: Variant -func _init(value): + +func _init(value: Variant) -> void: _value = value - -func get_value(): + + +func get_value() -> Variant: return _value diff --git a/addons/gdUnit4/src/asserts/GdAssertMessages.gd b/addons/gdUnit4/src/asserts/GdAssertMessages.gd index 42a8ceb5..01402ff3 100644 --- a/addons/gdUnit4/src/asserts/GdAssertMessages.gd +++ b/addons/gdUnit4/src/asserts/GdAssertMessages.gd @@ -8,7 +8,7 @@ const SUB_COLOR := Color(1, 0, 0, .3) const ADD_COLOR := Color(0, 1, 0, .3) -static func _format_dict(value :Dictionary) -> String: +static func format_dict(value :Dictionary) -> String: if value.is_empty(): return "{ }" var as_rows := var_to_str(value).split("\n") @@ -22,28 +22,30 @@ static func _format_dict(value :Dictionary) -> String: static func input_event_as_text(event :InputEvent) -> String: var text := "" if event is InputEventKey: - text += "InputEventKey : key='%s', pressed=%s, keycode=%d, physical_keycode=%s" % [event.as_text(), event.pressed, event.keycode, event.physical_keycode] + text += "InputEventKey : key='%s', pressed=%s, keycode=%d, physical_keycode=%s" % [ + event.as_text(), event.pressed, event.keycode, event.physical_keycode] else: text += event.as_text() if event is InputEventMouse: text += ", global_position %s" % event.global_position if event is InputEventWithModifiers: - text += ", shift=%s, alt=%s, control=%s, meta=%s, command=%s" % [event.shift_pressed, event.alt_pressed, event.ctrl_pressed, event.meta_pressed, event.command_or_control_autoremap] + text += ", shift=%s, alt=%s, control=%s, meta=%s, command=%s" % [ + event.shift_pressed, event.alt_pressed, event.ctrl_pressed, event.meta_pressed, event.command_or_control_autoremap] return text static func _colored_string_div(characters :String) -> String: - return _colored_array_div(characters.to_ascii_buffer()) + return colored_array_div(characters.to_utf8_buffer()) -static func _colored_array_div(characters :PackedByteArray) -> String: +static func colored_array_div(characters :PackedByteArray) -> String: if characters.is_empty(): return "" - var result = PackedByteArray() - var index = 0 + var result := PackedByteArray() + var index := 0 var missing_chars := PackedByteArray() var additional_chars := PackedByteArray() - + while index < characters.size(): var character := characters[index] match character: @@ -62,13 +64,13 @@ static func _colored_array_div(characters :PackedByteArray) -> String: additional_chars = PackedByteArray() result.append(character) index += 1 - + result.append_array(format_chars(missing_chars, SUB_COLOR)) result.append_array(format_chars(additional_chars, ADD_COLOR)) return result.get_string_from_utf8() -static func _typed_value(value) -> String: +static func _typed_value(value :Variant) -> String: return GdDefaultValueDecoder.decode(value) @@ -80,7 +82,7 @@ static func _error(error :String) -> String: return "[color=%s]%s[/color]" % [ERROR_COLOR, error] -static func _nerror(number) -> String: +static func _nerror(number :Variant) -> String: match typeof(number): TYPE_INT: return "[color=%s]%d[/color]" % [ERROR_COLOR, number] @@ -90,7 +92,7 @@ static func _nerror(number) -> String: return "[color=%s]%s[/color]" % [ERROR_COLOR, str(number)] -static func _colored_value(value, _delimiter ="\n") -> String: +static func _colored_value(value :Variant) -> String: match typeof(value): TYPE_STRING, TYPE_STRING_NAME: return "'[color=%s]%s[/color]'" % [VALUE_COLOR, _colored_string_div(value)] @@ -106,22 +108,23 @@ static func _colored_value(value, _delimiter ="\n") -> String: if value is InputEvent: return "[color=%s]<%s>[/color]" % [VALUE_COLOR, input_event_as_text(value)] if value.has_method("_to_string"): - return "[color=%s]<%s>[/color]" % [VALUE_COLOR, value._to_string()] + return "[color=%s]<%s>[/color]" % [VALUE_COLOR, str(value)] return "[color=%s]<%s>[/color]" % [VALUE_COLOR, value.get_class()] TYPE_DICTIONARY: - return "'[color=%s]%s[/color]'" % [VALUE_COLOR, _format_dict(value)] + return "'[color=%s]%s[/color]'" % [VALUE_COLOR, format_dict(value)] _: if GdArrayTools.is_array_type(value): return "'[color=%s]%s[/color]'" % [VALUE_COLOR, _typed_value(value)] return "'[color=%s]%s[/color]'" % [VALUE_COLOR, value] + static func _index_report_as_table(index_reports :Array) -> String: var table := "[table=3]$cells[/table]" var header := "[cell][right][b]$text[/b][/right]\t[/cell]" var cell := "[cell][right]$text[/right]\t[/cell]" var cells := header.replace("$text", "Index") + header.replace("$text", "Current") + header.replace("$text", "Expected") - for report in index_reports: + for report :Variant in index_reports: var index :String = str(report["index"]) var current :String = str(report["current"]) var expected :String = str(report["expected"]) @@ -129,26 +132,26 @@ static func _index_report_as_table(index_reports :Array) -> String: return table.replace("$cells", cells) -static func orphan_detected_on_suite_setup(count :int): +static func orphan_detected_on_suite_setup(count :int) -> String: return "%s\n Detected <%d> orphan nodes during test suite setup stage! [b]Check before() and after()![/b]" % [ _warning("WARNING:"), count] -static func orphan_detected_on_test_setup(count :int): +static func orphan_detected_on_test_setup(count :int) -> String: return "%s\n Detected <%d> orphan nodes during test setup! [b]Check before_test() and after_test()![/b]" % [ _warning("WARNING:"), count] -static func orphan_detected_on_test(count :int): +static func orphan_detected_on_test(count :int) -> String: return "%s\n Detected <%d> orphan nodes during test execution!" % [ _warning("WARNING:"), count] static func fuzzer_interuped(iterations: int, error: String) -> String: return "%s %s %s\n %s" % [ - _error("Found an error after"), - _colored_value(iterations + 1), - _error("test iterations"), + _error("Found an error after"), + _colored_value(iterations + 1), + _error("test iterations"), error] @@ -156,7 +159,8 @@ static func test_timeout(timeout :int) -> String: return "%s\n %s" % [_error("Timeout !"), _colored_value("Test timed out after %s" % LocalTime.elapsed(timeout))] -static func test_suite_skipped(hint :String, skip_count) -> String: +# gdlint:disable = mixed-tabs-and-spaces +static func test_suite_skipped(hint :String, skip_count :int) -> String: return """ %s Tests skipped: %s @@ -177,7 +181,7 @@ static func error_not_implemented() -> String: return _error("Test not implemented!") -static func error_is_null(current) -> String: +static func error_is_null(current :Variant) -> String: return "%s %s but was %s" % [_error("Expecting:"), _colored_value(null), _colored_value(current)] @@ -185,26 +189,27 @@ static func error_is_not_null() -> String: return "%s %s" % [_error("Expecting: not to be"), _colored_value(null)] -static func error_equal(current, expected, index_reports :Array = []) -> String: - var report = """ +static func error_equal(current :Variant, expected :Variant, index_reports :Array = []) -> String: + var report := """ %s %s but was - %s""".dedent().trim_prefix("\n") % [_error("Expecting:"), _colored_value(expected, true), _colored_value(current, true)] + %s""".dedent().trim_prefix("\n") % [_error("Expecting:"), _colored_value(expected), _colored_value(current)] if not index_reports.is_empty(): report += "\n\n%s\n%s" % [_error("Differences found:"), _index_report_as_table(index_reports)] return report -static func error_not_equal(current, expected) -> String: - return "%s\n %s\n not equal to\n %s" % [_error("Expecting:"), _colored_value(expected, true), _colored_value(current, true)] +static func error_not_equal(current :Variant, expected :Variant) -> String: + return "%s\n %s\n not equal to\n %s" % [_error("Expecting:"), _colored_value(expected), _colored_value(current)] -static func error_not_equal_case_insensetiv(current, expected) -> String: - return "%s\n %s\n not equal to (case insensitiv)\n %s" % [_error("Expecting:"), _colored_value(expected, true), _colored_value(current, true)] +static func error_not_equal_case_insensetiv(current :Variant, expected :Variant) -> String: + return "%s\n %s\n not equal to (case insensitiv)\n %s" % [ + _error("Expecting:"), _colored_value(expected), _colored_value(current)] -static func error_is_empty(current) -> String: +static func error_is_empty(current :Variant) -> String: return "%s\n must be empty but was\n %s" % [_error("Expecting:"), _colored_value(current)] @@ -212,16 +217,16 @@ static func error_is_not_empty() -> String: return "%s\n must not be empty" % [_error("Expecting:")] -static func error_is_same(current, expected) -> String: +static func error_is_same(current :Variant, expected :Variant) -> String: return "%s\n %s\n to refer to the same object\n %s" % [_error("Expecting:"), _colored_value(expected), _colored_value(current)] @warning_ignore("unused_parameter") -static func error_not_same(current, expected) -> String: +static func error_not_same(_current :Variant, expected :Variant) -> String: return "%s\n %s" % [_error("Expecting not same:"), _colored_value(expected)] -static func error_not_same_error(current, expected) -> String: +static func error_not_same_error(current :Variant, expected :Variant) -> String: return "%s\n %s\n but was\n %s" % [_error("Expecting error message:"), _colored_value(expected), _colored_value(current)] @@ -231,33 +236,33 @@ static func error_is_instanceof(current: GdUnitResult, expected :GdUnitResult) - # -- Boolean Assert specific messages ----------------------------------------------------- -static func error_is_true(current) -> String: +static func error_is_true(current :Variant) -> String: return "%s %s but is %s" % [_error("Expecting:"), _colored_value(true), _colored_value(current)] -static func error_is_false(current) -> String: +static func error_is_false(current :Variant) -> String: return "%s %s but is %s" % [_error("Expecting:"), _colored_value(false), _colored_value(current)] # - Integer/Float Assert specific messages ----------------------------------------------------- -static func error_is_even(current) -> String: +static func error_is_even(current :Variant) -> String: return "%s\n %s must be even" % [_error("Expecting:"), _colored_value(current)] -static func error_is_odd(current) -> String: +static func error_is_odd(current :Variant) -> String: return "%s\n %s must be odd" % [_error("Expecting:"), _colored_value(current)] -static func error_is_negative(current) -> String: +static func error_is_negative(current :Variant) -> String: return "%s\n %s be negative" % [_error("Expecting:"), _colored_value(current)] -static func error_is_not_negative(current) -> String: +static func error_is_not_negative(current :Variant) -> String: return "%s\n %s be not negative" % [_error("Expecting:"), _colored_value(current)] -static func error_is_zero(current) -> String: +static func error_is_zero(current :Variant) -> String: return "%s\n equal to 0 but is %s" % [_error("Expecting:"), _colored_value(current)] @@ -272,7 +277,7 @@ static func error_is_wrong_type(current_type :Variant.Type, expected_type :Varia _colored_value(GdObjects.type_as_string(expected_type))] -static func error_is_value(operation, current, expected, expected2=null) -> String: +static func error_is_value(operation :int, current :Variant, expected :Variant, expected2 :Variant = null) -> String: match operation: Comparator.EQUAL: return "%s\n %s but was '%s'" % [_error("Expecting:"), _colored_value(expected), _nerror(current)] @@ -285,126 +290,169 @@ static func error_is_value(operation, current, expected, expected2=null) -> Stri Comparator.GREATER_EQUAL: return "%s\n %s but was '%s'" % [_error("Expecting to be greater than or equal:"), _colored_value(expected), _nerror(current)] Comparator.BETWEEN_EQUAL: - return "%s\n %s\n in range between\n %s <> %s" % [_error("Expecting:"), _colored_value(current), _colored_value(expected), _colored_value(expected2)] + return "%s\n %s\n in range between\n %s <> %s" % [ + _error("Expecting:"), _colored_value(current), _colored_value(expected), _colored_value(expected2)] Comparator.NOT_BETWEEN_EQUAL: - return "%s\n %s\n not in range between\n %s <> %s" % [_error("Expecting:"), _colored_value(current), _colored_value(expected), _colored_value(expected2)] + return "%s\n %s\n not in range between\n %s <> %s" % [ + _error("Expecting:"), _colored_value(current), _colored_value(expected), _colored_value(expected2)] return "TODO create expected message" -static func error_is_in(current, expected :Array) -> String: +static func error_is_in(current :Variant, expected :Array) -> String: return "%s\n %s\n is in\n %s" % [_error("Expecting:"), _colored_value(current), _colored_value(str(expected))] -static func error_is_not_in(current, expected :Array) -> String: +static func error_is_not_in(current :Variant, expected :Array) -> String: return "%s\n %s\n is not in\n %s" % [_error("Expecting:"), _colored_value(current), _colored_value(str(expected))] # - StringAssert --------------------------------------------------------------------------------- -static func error_equal_ignoring_case(current, expected) -> String: +static func error_equal_ignoring_case(current :Variant, expected :Variant) -> String: return "%s\n %s\n but was\n %s (ignoring case)" % [_error("Expecting:"), _colored_value(expected), _colored_value(current)] -static func error_contains(current, expected) -> String: +static func error_contains(current :Variant, expected :Variant) -> String: return "%s\n %s\n do contains\n %s" % [_error("Expecting:"), _colored_value(current), _colored_value(expected)] -static func error_not_contains(current, expected) -> String: +static func error_not_contains(current :Variant, expected :Variant) -> String: return "%s\n %s\n not do contain\n %s" % [_error("Expecting:"), _colored_value(current), _colored_value(expected)] -static func error_contains_ignoring_case(current, expected) -> String: +static func error_contains_ignoring_case(current :Variant, expected :Variant) -> String: return "%s\n %s\n contains\n %s\n (ignoring case)" % [_error("Expecting:"), _colored_value(current), _colored_value(expected)] -static func error_not_contains_ignoring_case(current, expected) -> String: +static func error_not_contains_ignoring_case(current :Variant, expected :Variant) -> String: return "%s\n %s\n not do contains\n %s\n (ignoring case)" % [_error("Expecting:"), _colored_value(current), _colored_value(expected)] -static func error_starts_with(current, expected) -> String: +static func error_starts_with(current :Variant, expected :Variant) -> String: return "%s\n %s\n to start with\n %s" % [_error("Expecting:"), _colored_value(current), _colored_value(expected)] -static func error_ends_with(current, expected) -> String: +static func error_ends_with(current :Variant, expected :Variant) -> String: return "%s\n %s\n to end with\n %s" % [_error("Expecting:"), _colored_value(current), _colored_value(expected)] -static func error_has_length(current, expected: int, compare_operator) -> String: - var current_length = current.length() if current != null else null +static func error_has_length(current :Variant, expected: int, compare_operator :int) -> String: + var current_length :Variant = current.length() if current != null else null match compare_operator: Comparator.EQUAL: - return "%s\n %s but was '%s' in\n %s" % [_error("Expecting size:"), _colored_value(expected), _nerror(current_length), _colored_value(current)] + return "%s\n %s but was '%s' in\n %s" % [ + _error("Expecting size:"), _colored_value(expected), _nerror(current_length), _colored_value(current)] Comparator.LESS_THAN: - return "%s\n %s but was '%s' in\n %s" % [_error("Expecting size to be less than:"), _colored_value(expected), _nerror(current_length), _colored_value(current)] + return "%s\n %s but was '%s' in\n %s" % [ + _error("Expecting size to be less than:"), _colored_value(expected), _nerror(current_length), _colored_value(current)] Comparator.LESS_EQUAL: - return "%s\n %s but was '%s' in\n %s" % [_error("Expecting size to be less than or equal:"), _colored_value(expected), _nerror(current_length), _colored_value(current)] + return "%s\n %s but was '%s' in\n %s" % [ + _error("Expecting size to be less than or equal:"), _colored_value(expected), + _nerror(current_length), _colored_value(current)] Comparator.GREATER_THAN: - return "%s\n %s but was '%s' in\n %s" % [_error("Expecting size to be greater than:"), _colored_value(expected), _nerror(current_length), _colored_value(current)] + return "%s\n %s but was '%s' in\n %s" % [ + _error("Expecting size to be greater than:"), _colored_value(expected), + _nerror(current_length), _colored_value(current)] Comparator.GREATER_EQUAL: - return "%s\n %s but was '%s' in\n %s" % [_error("Expecting size to be greater than or equal:"), _colored_value(expected), _nerror(current_length), _colored_value(current)] + return "%s\n %s but was '%s' in\n %s" % [ + _error("Expecting size to be greater than or equal:"), _colored_value(expected), + _nerror(current_length), _colored_value(current)] return "TODO create expected message" # - ArrayAssert specific messgaes --------------------------------------------------- -static func error_arr_contains(current, expected :Array, not_expect :Array, not_found :Array, by_reference :bool) -> String: - var failure_message = "Expecting contains SAME elements:" if by_reference else "Expecting contains elements:" - var error := "%s\n %s\n do contains (in any order)\n %s" % [_error(failure_message), _colored_value(current, ", "), _colored_value(expected, ", ")] +static func error_arr_contains(current :Variant, expected :Array, not_expect :Array, not_found :Array, by_reference :bool) -> String: + var failure_message := "Expecting contains SAME elements:" if by_reference else "Expecting contains elements:" + var error := "%s\n %s\n do contains (in any order)\n %s" % [ + _error(failure_message), _colored_value(current), _colored_value(expected)] if not not_expect.is_empty(): - error += "\nbut some elements where not expected:\n %s" % _colored_value(not_expect, ", ") + error += "\nbut some elements where not expected:\n %s" % _colored_value(not_expect) if not not_found.is_empty(): - var prefix = "but" if not_expect.is_empty() else "and" - error += "\n%s could not find elements:\n %s" % [prefix, _colored_value(not_found, ", ")] + var prefix := "but" if not_expect.is_empty() else "and" + error += "\n%s could not find elements:\n %s" % [prefix, _colored_value(not_found)] return error -static func error_arr_contains_exactly(current, expected, not_expect, not_found, compare_mode :GdObjects.COMPARE_MODE) -> String: - var failure_message = "Expecting contains exactly elements:" if compare_mode == GdObjects.COMPARE_MODE.PARAMETER_DEEP_TEST else "Expecting contains SAME exactly elements:" +static func error_arr_contains_exactly( + current :Variant, + expected :Variant, + not_expect :Variant, + not_found :Variant, compare_mode :GdObjects.COMPARE_MODE) -> String: + var failure_message := ( + "Expecting contains exactly elements:" if compare_mode == GdObjects.COMPARE_MODE.PARAMETER_DEEP_TEST + else "Expecting contains SAME exactly elements:" + ) if not_expect.is_empty() and not_found.is_empty(): var diff := _find_first_diff(current, expected) - return "%s\n %s\n do contains (in same order)\n %s\n but has different order %s" % [_error(failure_message), _colored_value(current, ", "), _colored_value(expected, ", "), diff] - - var error := "%s\n %s\n do contains (in same order)\n %s" % [_error(failure_message), _colored_value(current, ", "), _colored_value(expected, ", ")] + return "%s\n %s\n do contains (in same order)\n %s\n but has different order %s" % [ + _error(failure_message), _colored_value(current), _colored_value(expected), diff] + + var error := "%s\n %s\n do contains (in same order)\n %s" % [ + _error(failure_message), _colored_value(current), _colored_value(expected)] if not not_expect.is_empty(): - error += "\nbut some elements where not expected:\n %s" % _colored_value(not_expect, ", ") + error += "\nbut some elements where not expected:\n %s" % _colored_value(not_expect) if not not_found.is_empty(): - var prefix = "but" if not_expect.is_empty() else "and" - error += "\n%s could not find elements:\n %s" % [prefix, _colored_value(not_found, ", ")] + var prefix := "but" if not_expect.is_empty() else "and" + error += "\n%s could not find elements:\n %s" % [prefix, _colored_value(not_found)] return error -static func error_arr_contains_exactly_in_any_order(current, expected :Array, not_expect :Array, not_found :Array, compare_mode :GdObjects.COMPARE_MODE) -> String: - var failure_message = "Expecting contains exactly elements:" if compare_mode == GdObjects.COMPARE_MODE.PARAMETER_DEEP_TEST else "Expecting contains SAME exactly elements:" - var error := "%s\n %s\n do contains exactly (in any order)\n %s" % [_error(failure_message), _colored_value(current, ", "), _colored_value(expected, ", ")] +static func error_arr_contains_exactly_in_any_order( + current :Variant, + expected :Array, + not_expect :Array, + not_found :Array, + compare_mode :GdObjects.COMPARE_MODE) -> String: + + var failure_message := ( + "Expecting contains exactly elements:" if compare_mode == GdObjects.COMPARE_MODE.PARAMETER_DEEP_TEST + else "Expecting contains SAME exactly elements:" + ) + var error := "%s\n %s\n do contains exactly (in any order)\n %s" % [ + _error(failure_message), _colored_value(current), _colored_value(expected)] if not not_expect.is_empty(): - error += "\nbut some elements where not expected:\n %s" % _colored_value(not_expect, ", ") + error += "\nbut some elements where not expected:\n %s" % _colored_value(not_expect) if not not_found.is_empty(): - var prefix = "but" if not_expect.is_empty() else "and" - error += "\n%s could not find elements:\n %s" % [prefix, _colored_value(not_found, ", ")] + var prefix := "but" if not_expect.is_empty() else "and" + error += "\n%s could not find elements:\n %s" % [prefix, _colored_value(not_found)] return error static func error_arr_not_contains(current :Array, expected :Array, found :Array, compare_mode :GdObjects.COMPARE_MODE) -> String: - var failure_message = "Expecting:" if compare_mode == GdObjects.COMPARE_MODE.PARAMETER_DEEP_TEST else "Expecting SAME:" - var error := "%s\n %s\n do not contains\n %s" % [_error(failure_message), _colored_value(current, ", "), _colored_value(expected, ", ")] + var failure_message := "Expecting:" if compare_mode == GdObjects.COMPARE_MODE.PARAMETER_DEEP_TEST else "Expecting SAME:" + var error := "%s\n %s\n do not contains\n %s" % [ + _error(failure_message), _colored_value(current), _colored_value(expected)] if not found.is_empty(): - error += "\n but found elements:\n %s" % _colored_value(found, ", ") + error += "\n but found elements:\n %s" % _colored_value(found) return error # - DictionaryAssert specific messages ---------------------------------------------- static func error_contains_keys(current :Array, expected :Array, keys_not_found :Array, compare_mode :GdObjects.COMPARE_MODE) -> String: - var failure := "Expecting contains keys:" if compare_mode == GdObjects.COMPARE_MODE.PARAMETER_DEEP_TEST else "Expecting contains SAME keys:" - return "%s\n %s\n to contains:\n %s\n but can't find key's:\n %s" % [_error(failure), _colored_value(current, ", "), _colored_value(expected, ", "), _colored_value(keys_not_found, ", ")] + var failure := ( + "Expecting contains keys:" if compare_mode == GdObjects.COMPARE_MODE.PARAMETER_DEEP_TEST + else "Expecting contains SAME keys:" + ) + return "%s\n %s\n to contains:\n %s\n but can't find key's:\n %s" % [ + _error(failure), _colored_value(current), _colored_value(expected), _colored_value(keys_not_found)] static func error_not_contains_keys(current :Array, expected :Array, keys_not_found :Array, compare_mode :GdObjects.COMPARE_MODE) -> String: - var failure := "Expecting NOT contains keys:" if compare_mode == GdObjects.COMPARE_MODE.PARAMETER_DEEP_TEST else "Expecting NOT contains SAME keys" - return "%s\n %s\n do not contains:\n %s\n but contains key's:\n %s" % [_error(failure), _colored_value(current, ", "), _colored_value(expected, ", "), _colored_value(keys_not_found, ", ")] + var failure := ( + "Expecting NOT contains keys:" if compare_mode == GdObjects.COMPARE_MODE.PARAMETER_DEEP_TEST + else "Expecting NOT contains SAME keys" + ) + return "%s\n %s\n do not contains:\n %s\n but contains key's:\n %s" % [ + _error(failure), _colored_value(current), _colored_value(expected), _colored_value(keys_not_found)] -static func error_contains_key_value(key, value, current_value, compare_mode :GdObjects.COMPARE_MODE) -> String: - var failure := "Expecting contains key and value:" if compare_mode == GdObjects.COMPARE_MODE.PARAMETER_DEEP_TEST else "Expecting contains SAME key and value:" - return "%s\n %s : %s\n but contains\n %s : %s" % [_error(failure), _colored_value(key), _colored_value(value), _colored_value(key), _colored_value(current_value)] +static func error_contains_key_value(key :Variant, value :Variant, current_value :Variant, compare_mode :GdObjects.COMPARE_MODE) -> String: + var failure := ( + "Expecting contains key and value:" if compare_mode == GdObjects.COMPARE_MODE.PARAMETER_DEEP_TEST + else "Expecting contains SAME key and value:" + ) + return "%s\n %s : %s\n but contains\n %s : %s" % [ + _error(failure), _colored_value(key), _colored_value(value), _colored_value(key), _colored_value(current_value)] # - ResultAssert specific errors ---------------------------------------------------- @@ -432,7 +480,7 @@ static func error_result_has_message_on_success(expected :String) -> String: return "%s\n %s\n but the GdUnitResult is a success." % [_error("Expecting:"), _colored_value(expected)] -static func error_result_is_value(current, expected) -> String: +static func error_result_is_value(current :Variant, expected :Variant) -> String: return "%s\n %s\n but was\n %s." % [_error("Expecting to contain same value:"), _colored_value(expected), _colored_value(current)] @@ -441,11 +489,11 @@ static func _result_error_message(current :GdUnitResult, expected_type :int) -> return _error("Expecting the result must be a %s but was ." % result_type(expected_type)) if current.is_success(): return _error("Expecting the result must be a %s but was SUCCESS." % result_type(expected_type)) - var error = "Expecting the result must be a %s but was %s:" % [result_type(expected_type), result_type(current._state)] + var error := "Expecting the result must be a %s but was %s:" % [result_type(expected_type), result_type(current._state)] return "%s\n %s" % [_error(error), _colored_value(result_message(current))] -static func error_interrupted(func_name :String, expected, elapsed :String) -> String: +static func error_interrupted(func_name :String, expected :Variant, elapsed :String) -> String: func_name = humanized(func_name) if expected == null: return "%s %s but timed out after %s" % [_error("Expected:"), func_name, elapsed] @@ -454,17 +502,24 @@ static func error_interrupted(func_name :String, expected, elapsed :String) -> S static func error_wait_signal(signal_name :String, args :Array, elapsed :String) -> String: if args.is_empty(): - return "%s %s but timed out after %s" % [_error("Expecting emit signal:"), _colored_value(signal_name + "()"), elapsed] - return "%s %s but timed out after %s" % [_error("Expecting emit signal:"), _colored_value(signal_name + "(" + str(args) + ")"), elapsed] + return "%s %s but timed out after %s" % [ + _error("Expecting emit signal:"), _colored_value(signal_name + "()"), elapsed] + return "%s %s but timed out after %s" % [ + _error("Expecting emit signal:"), _colored_value(signal_name + "(" + str(args) + ")"), elapsed] static func error_signal_emitted(signal_name :String, args :Array, elapsed :String) -> String: if args.is_empty(): - return "%s %s but is emitted after %s" % [_error("Expecting do not emit signal:"), _colored_value(signal_name + "()"), elapsed] - return "%s %s but is emitted after %s" % [_error("Expecting do not emit signal:"), _colored_value(signal_name + "(" + str(args) + ")"), elapsed] + return "%s %s but is emitted after %s" % [ + _error("Expecting do not emit signal:"), _colored_value(signal_name + "()"), elapsed] + return "%s %s but is emitted after %s" % [ + _error("Expecting do not emit signal:"), _colored_value(signal_name + "(" + str(args) + ")"), elapsed] + + +static func error_await_signal_on_invalid_instance(source :Variant, signal_name :String, args :Array) -> String: + return "%s\n await_signal_on(%s, %s, %s)" % [ + _error("Invalid source! Can't await on signal:"), _colored_value(source), signal_name, args] -static func error_await_signal_on_invalid_instance(source, signal_name :String, args :Array) -> String: - return "%s\n await_signal_on(%s, %s, %s)" % [_error("Invalid source! Can't await on signal:"), _colored_value(source), signal_name, args] static func result_type(type :int) -> String: match type: @@ -487,7 +542,7 @@ static func result_message(result :GdUnitResult) -> String: # - Spy|Mock specific errors ---------------------------------------------------- static func error_no_more_interactions(summary :Dictionary) -> String: var interactions := PackedStringArray() - for args in summary.keys(): + for args :Variant in summary.keys(): var times :int = summary[args] interactions.append(_format_arguments(args, times)) return "%s\n%s\n%s" % [_error("Expecting no more interactions!"), _error("But found interactions on:"), "\n".join(interactions)] @@ -495,11 +550,12 @@ static func error_no_more_interactions(summary :Dictionary) -> String: static func error_validate_interactions(current_interactions :Dictionary, expected_interactions :Dictionary) -> String: var interactions := PackedStringArray() - for args in current_interactions.keys(): + for args :Variant in current_interactions.keys(): var times :int = current_interactions[args] interactions.append(_format_arguments(args, times)) var expected_interaction := _format_arguments(expected_interactions.keys()[0], expected_interactions.values()[0]) - return "%s\n%s\n%s\n%s" % [_error("Expecting interaction on:"), expected_interaction, _error("But found interactions on:"), "\n".join(interactions)] + return "%s\n%s\n%s\n%s" % [ + _error("Expecting interaction on:"), expected_interaction, _error("But found interactions on:"), "\n".join(interactions)] static func _format_arguments(args :Array, times :int) -> String: @@ -512,28 +568,28 @@ static func _format_arguments(args :Array, times :int) -> String: static func _to_typed_args(args :Array) -> PackedStringArray: var typed := PackedStringArray() - for arg in args: + for arg :Variant in args: typed.append(_format_arg(arg) + " :" + GdObjects.type_as_string(typeof(arg))) return typed -static func _format_arg(arg) -> String: +static func _format_arg(arg :Variant) -> String: if arg is InputEvent: return input_event_as_text(arg) return str(arg) -static func _find_first_diff( left :Array, right :Array) -> String: +static func _find_first_diff(left :Array, right :Array) -> String: for index in left.size(): - var l = left[index] - var r = "" if index >= right.size() else right[index] + var l :Variant = left[index] + var r :Variant = "" if index >= right.size() else right[index] if not GdObjects.equals(l, r): return "at position %s\n '%s' vs '%s'" % [_colored_value(index), _typed_value(l), _typed_value(r)] return "" -static func error_has_size(current, expected: int) -> String: - var current_size = null if current == null else current.size() +static func error_has_size(current :Variant, expected: int) -> String: + var current_size :Variant = null if current == null else current.size() return "%s\n %s\n but was\n %s" % [_error("Expecting size:"), _colored_value(expected), _colored_value(current_size)] @@ -545,7 +601,8 @@ static func format_chars(characters :PackedByteArray, type :Color) -> PackedByte if characters.size() == 0:# or characters[0] == 10: return characters var result := PackedByteArray() - var message := "[bgcolor=#%s][color=with]%s[/color][/bgcolor]" % [type.to_html(), characters.get_string_from_utf8().replace("\n", "")] + var message := "[bgcolor=#%s][color=with]%s[/color][/bgcolor]" % [ + type.to_html(), characters.get_string_from_utf8().replace("\n", "")] result.append_array(message.to_utf8_buffer()) return result diff --git a/addons/gdUnit4/src/asserts/GdAssertReports.gd b/addons/gdUnit4/src/asserts/GdAssertReports.gd index dc427ed3..1ac9e04b 100644 --- a/addons/gdUnit4/src/asserts/GdAssertReports.gd +++ b/addons/gdUnit4/src/asserts/GdAssertReports.gd @@ -21,7 +21,7 @@ static func report_error(message:String, line_number :int) -> void: GdAssertReports.set_last_error_line_number(line_number) Engine.set_meta(LAST_ERROR, message) # if we expect to fail we handle as success test - if is_expect_fail(): + if _do_expect_assert_failing(): return send_report(GdUnitReport.new().create(GdUnitReport.FAILURE, line_number, message)) @@ -40,13 +40,9 @@ static func get_last_error_line_number() -> int: return -1 -static func expect_fail(enabled :bool = true): - Engine.set_meta("report_failures", enabled) - - -static func is_expect_fail() -> bool: - if Engine.has_meta("report_failures"): - return Engine.get_meta("report_failures") +static func _do_expect_assert_failing() -> bool: + if Engine.has_meta(GdUnitConstants.EXPECT_ASSERT_REPORT_FAILURES): + return Engine.get_meta(GdUnitConstants.EXPECT_ASSERT_REPORT_FAILURES) return false diff --git a/addons/gdUnit4/src/asserts/GdUnitArrayAssertImpl.gd b/addons/gdUnit4/src/asserts/GdUnitArrayAssertImpl.gd index 419f0172..8b711050 100644 --- a/addons/gdUnit4/src/asserts/GdUnitArrayAssertImpl.gd +++ b/addons/gdUnit4/src/asserts/GdUnitArrayAssertImpl.gd @@ -5,16 +5,17 @@ var _base :GdUnitAssert var _current_value_provider :ValueProvider -func _init(current): +func _init(current :Variant) -> void: _current_value_provider = DefaultValueProvider.new(current) - _base = ResourceLoader.load("res://addons/gdUnit4/src/asserts/GdUnitAssertImpl.gd", "GDScript", ResourceLoader.CACHE_MODE_REUSE).new(current) + _base = ResourceLoader.load("res://addons/gdUnit4/src/asserts/GdUnitAssertImpl.gd", "GDScript", + ResourceLoader.CACHE_MODE_REUSE).new(current) # save the actual assert instance on the current thread context GdUnitThreadManager.get_current_context().set_assert(self) - if not __validate_value_type(current): + if not _validate_value_type(current): report_error("GdUnitArrayAssert inital error, unexpected type <%s>" % GdObjects.typeof_as_string(current)) -func _notification(event): +func _notification(event :int) -> void: if event == NOTIFICATION_PREDELETE: if _base != null: _base.notification(event) @@ -30,7 +31,8 @@ func report_error(error :String) -> GdUnitArrayAssert: _base.report_error(error) return self -func _failure_message() -> String: + +func failure_message() -> String: return _base._current_error_message @@ -39,54 +41,51 @@ func override_failure_message(message :String) -> GdUnitArrayAssert: return self -func __validate_value_type(value) -> bool: - return ( - value == null - or GdArrayTools.is_array_type(value) - ) +func _validate_value_type(value :Variant) -> bool: + return value == null or GdArrayTools.is_array_type(value) -func __current() -> Variant: +func get_current_value() -> Variant: return _current_value_provider.get_value() -func max_length(left, right) -> int: - var ls = str(left).length() - var rs = str(right).length() +func max_length(left :Variant, right :Variant) -> int: + var ls := str(left).length() + var rs := str(right).length() return rs if ls < rs else ls -func _array_equals_div(current, expected, case_sensitive :bool = false) -> Array: - var current_ := PackedStringArray(Array(current)) - var expected_ := PackedStringArray(Array(expected)) - var index_report_ := Array() - for index in current_.size(): - var c := current_[index] - if index < expected_.size(): - var e := expected_[index] +func _array_equals_div(current :Array, expected :Array, case_sensitive :bool = false) -> Array: + var current_value := PackedStringArray(current) + var expected_value := PackedStringArray(expected) + var index_report := Array() + for index in current_value.size(): + var c := current_value[index] + if index < expected_value.size(): + var e := expected_value[index] if not GdObjects.equals(c, e, case_sensitive): var length := max_length(c, e) - current_[index] = GdAssertMessages.format_invalid(c.lpad(length)) - expected_[index] = e.lpad(length) - index_report_.push_back({"index" : index, "current" :c, "expected": e}) + current_value[index] = GdAssertMessages.format_invalid(c.lpad(length)) + expected_value[index] = e.lpad(length) + index_report.push_back({"index" : index, "current" :c, "expected": e}) else: - current_[index] = GdAssertMessages.format_invalid(c) - index_report_.push_back({"index" : index, "current" :c, "expected": ""}) - - for index in range(current.size(), expected_.size()): - var value := expected_[index] - expected_[index] = GdAssertMessages.format_invalid(value) - index_report_.push_back({"index" : index, "current" : "", "expected": value}) - return [current_, expected_, index_report_] + current_value[index] = GdAssertMessages.format_invalid(c) + index_report.push_back({"index" : index, "current" :c, "expected": ""}) + + for index in range(current.size(), expected_value.size()): + var value := expected_value[index] + expected_value[index] = GdAssertMessages.format_invalid(value) + index_report.push_back({"index" : index, "current" : "", "expected": value}) + return [current_value, expected_value, index_report] func _array_div(compare_mode :GdObjects.COMPARE_MODE, left :Array[Variant], right :Array[Variant], _same_order := false) -> Array[Variant]: var not_expect := left.duplicate(true) var not_found := right.duplicate(true) for index_c in left.size(): - var c = left[index_c] + var c :Variant = left[index_c] for index_e in right.size(): - var e = right[index_e] + var e :Variant = right[index_e] if GdObjects.equals(c, e, false, compare_mode): GdArrayTools.erase_value(not_expect, e) GdArrayTools.erase_value(not_found, c) @@ -94,68 +93,67 @@ func _array_div(compare_mode :GdObjects.COMPARE_MODE, left :Array[Variant], righ return [not_expect, not_found] - -func _contains(expected, compare_mode :GdObjects.COMPARE_MODE) -> GdUnitArrayAssert: - if not __validate_value_type(expected): +func _contains(expected :Variant, compare_mode :GdObjects.COMPARE_MODE) -> GdUnitArrayAssert: + if not _validate_value_type(expected): return report_error("ERROR: expected value: <%s>\n is not a Array Type!" % GdObjects.typeof_as_string(expected)) var by_reference := compare_mode == GdObjects.COMPARE_MODE.OBJECT_REFERENCE - var current_ = __current() - if current_ == null: - return report_error(GdAssertMessages.error_arr_contains(current_, expected, [], expected, by_reference)) - var diffs := _array_div(compare_mode, current_, expected) + var current_value :Variant = get_current_value() + if current_value == null: + return report_error(GdAssertMessages.error_arr_contains(current_value, expected, [], expected, by_reference)) + var diffs := _array_div(compare_mode, current_value, expected) #var not_expect := diffs[0] as Array var not_found := diffs[1] as Array if not not_found.is_empty(): - return report_error(GdAssertMessages.error_arr_contains(current_, expected, [], not_found, by_reference)) + return report_error(GdAssertMessages.error_arr_contains(current_value, expected, [], not_found, by_reference)) return report_success() -func _contains_exactly(expected, compare_mode :GdObjects.COMPARE_MODE) -> GdUnitArrayAssert: - if not __validate_value_type(expected): +func _contains_exactly(expected :Variant, compare_mode :GdObjects.COMPARE_MODE) -> GdUnitArrayAssert: + if not _validate_value_type(expected): return report_error("ERROR: expected value: <%s>\n is not a Array Type!" % GdObjects.typeof_as_string(expected)) - var current_ = __current() - if current_ == null: - return report_error(GdAssertMessages.error_arr_contains_exactly(current_, expected, [], expected, compare_mode)) + var current_value :Variant = get_current_value() + if current_value == null: + return report_error(GdAssertMessages.error_arr_contains_exactly(current_value, expected, [], expected, compare_mode)) # has same content in same order - if GdObjects.equals(Array(current_), Array(expected), false, compare_mode): + if GdObjects.equals(Array(current_value), Array(expected), false, compare_mode): return report_success() # check has same elements but in different order - if GdObjects.equals_sorted(Array(current_), Array(expected), false, compare_mode): - return report_error(GdAssertMessages.error_arr_contains_exactly(current_, expected, [], [], compare_mode)) + if GdObjects.equals_sorted(Array(current_value), Array(expected), false, compare_mode): + return report_error(GdAssertMessages.error_arr_contains_exactly(current_value, expected, [], [], compare_mode)) # find the difference - var diffs := _array_div(compare_mode, current_, expected, GdObjects.COMPARE_MODE.PARAMETER_DEEP_TEST) + var diffs := _array_div(compare_mode, current_value, expected, GdObjects.COMPARE_MODE.PARAMETER_DEEP_TEST) var not_expect := diffs[0] as Array[Variant] var not_found := diffs[1] as Array[Variant] - return report_error(GdAssertMessages.error_arr_contains_exactly(current_, expected, not_expect, not_found, compare_mode)) + return report_error(GdAssertMessages.error_arr_contains_exactly(current_value, expected, not_expect, not_found, compare_mode)) -func _contains_exactly_in_any_order(expected, compare_mode :GdObjects.COMPARE_MODE) -> GdUnitArrayAssert: - if not __validate_value_type(expected): +func _contains_exactly_in_any_order(expected :Variant, compare_mode :GdObjects.COMPARE_MODE) -> GdUnitArrayAssert: + if not _validate_value_type(expected): return report_error("ERROR: expected value: <%s>\n is not a Array Type!" % GdObjects.typeof_as_string(expected)) - var current_ = __current() - if current_ == null: - return report_error(GdAssertMessages.error_arr_contains_exactly_in_any_order(current_, expected, [], expected, compare_mode)) + var current_value :Variant = get_current_value() + if current_value == null: + return report_error(GdAssertMessages.error_arr_contains_exactly_in_any_order(current_value, expected, [], expected, compare_mode)) # find the difference - var diffs := _array_div(compare_mode, current_, expected, false) + var diffs := _array_div(compare_mode, current_value, expected, false) var not_expect := diffs[0] as Array var not_found := diffs[1] as Array if not_expect.is_empty() and not_found.is_empty(): return report_success() - return report_error(GdAssertMessages.error_arr_contains_exactly_in_any_order(current_, expected, not_expect, not_found, compare_mode)) + return report_error(GdAssertMessages.error_arr_contains_exactly_in_any_order(current_value, expected, not_expect, not_found, compare_mode)) -func _not_contains(expected, compare_mode :GdObjects.COMPARE_MODE) -> GdUnitArrayAssert: - if not __validate_value_type(expected): +func _not_contains(expected :Variant, compare_mode :GdObjects.COMPARE_MODE) -> GdUnitArrayAssert: + if not _validate_value_type(expected): return report_error("ERROR: expected value: <%s>\n is not a Array Type!" % GdObjects.typeof_as_string(expected)) - var current_ = __current() - if current_ == null: - return report_error(GdAssertMessages.error_arr_contains_exactly_in_any_order(current_, expected, [], expected, compare_mode)) - var diffs := _array_div(compare_mode, current_, expected) + var current_value :Variant = get_current_value() + if current_value == null: + return report_error(GdAssertMessages.error_arr_contains_exactly_in_any_order(current_value, expected, [], expected, compare_mode)) + var diffs := _array_div(compare_mode, current_value, expected) var found := diffs[0] as Array - if found.size() == current_.size(): + if found.size() == current_value.size(): return report_success() var diffs2 := _array_div(compare_mode, expected, diffs[1]) - return report_error(GdAssertMessages.error_arr_not_contains(current_, expected, diffs2[0], compare_mode)) + return report_error(GdAssertMessages.error_arr_not_contains(current_value, expected, diffs2[0], compare_mode)) func is_null() -> GdUnitArrayAssert: @@ -169,150 +167,151 @@ func is_not_null() -> GdUnitArrayAssert: # Verifies that the current String is equal to the given one. -func is_equal(expected) -> GdUnitArrayAssert: - if not __validate_value_type(expected): +func is_equal(expected :Variant) -> GdUnitArrayAssert: + if not _validate_value_type(expected): return report_error("ERROR: expected value: <%s>\n is not a Array Type!" % GdObjects.typeof_as_string(expected)) - var current_ = __current() - if current_ == null and expected != null: + var current_value :Variant = get_current_value() + if current_value == null and expected != null: return report_error(GdAssertMessages.error_equal(null, expected)) - if not GdObjects.equals(current_, expected): - var diff := _array_equals_div(current_, expected) - var expected_as_list = GdArrayTools.as_string(diff[0], false) - var current_as_list = GdArrayTools.as_string(diff[1], false) - var index_report = diff[2] + if not GdObjects.equals(current_value, expected): + var diff := _array_equals_div(current_value, expected) + var expected_as_list := GdArrayTools.as_string(diff[0], false) + var current_as_list := GdArrayTools.as_string(diff[1], false) + var index_report :Variant = diff[2] return report_error(GdAssertMessages.error_equal(expected_as_list, current_as_list, index_report)) return report_success() # Verifies that the current Array is equal to the given one, ignoring case considerations. -func is_equal_ignoring_case(expected) -> GdUnitArrayAssert: - if not __validate_value_type(expected): +func is_equal_ignoring_case(expected :Variant) -> GdUnitArrayAssert: + if not _validate_value_type(expected): return report_error("ERROR: expected value: <%s>\n is not a Array Type!" % GdObjects.typeof_as_string(expected)) - var current_ = __current() - if current_ == null and expected != null: + var current_value :Variant = get_current_value() + if current_value == null and expected != null: return report_error(GdAssertMessages.error_equal(null, GdArrayTools.as_string(expected))) - if not GdObjects.equals(current_, expected, true): - var diff := _array_equals_div(current_, expected, true) + if not GdObjects.equals(current_value, expected, true): + var diff := _array_equals_div(current_value, expected, true) var expected_as_list := GdArrayTools.as_string(diff[0]) var current_as_list := GdArrayTools.as_string(diff[1]) - var index_report = diff[2] + var index_report :Variant = diff[2] return report_error(GdAssertMessages.error_equal(expected_as_list, current_as_list, index_report)) return report_success() -func is_not_equal(expected) -> GdUnitArrayAssert: - if not __validate_value_type(expected): +func is_not_equal(expected :Variant) -> GdUnitArrayAssert: + if not _validate_value_type(expected): return report_error("ERROR: expected value: <%s>\n is not a Array Type!" % GdObjects.typeof_as_string(expected)) - var current_ = __current() - if GdObjects.equals(current_, expected): - return report_error(GdAssertMessages.error_not_equal(current_, expected)) + var current_value :Variant = get_current_value() + if GdObjects.equals(current_value, expected): + return report_error(GdAssertMessages.error_not_equal(current_value, expected)) return report_success() -func is_not_equal_ignoring_case(expected) -> GdUnitArrayAssert: - if not __validate_value_type(expected): +func is_not_equal_ignoring_case(expected :Variant) -> GdUnitArrayAssert: + if not _validate_value_type(expected): return report_error("ERROR: expected value: <%s>\n is not a Array Type!" % GdObjects.typeof_as_string(expected)) - var current_ = __current() - if GdObjects.equals(current_, expected, true): - var c := GdArrayTools.as_string(current_) + var current_value :Variant = get_current_value() + if GdObjects.equals(current_value, expected, true): + var c := GdArrayTools.as_string(current_value) var e := GdArrayTools.as_string(expected) return report_error(GdAssertMessages.error_not_equal_case_insensetiv(c, e)) return report_success() func is_empty() -> GdUnitArrayAssert: - var current_ = __current() - if current_ == null or current_.size() > 0: - return report_error(GdAssertMessages.error_is_empty(current_)) + var current_value :Variant = get_current_value() + if current_value == null or current_value.size() > 0: + return report_error(GdAssertMessages.error_is_empty(current_value)) return report_success() func is_not_empty() -> GdUnitArrayAssert: - var current_ = __current() - if current_ != null and current_.size() == 0: + var current_value :Variant = get_current_value() + if current_value != null and current_value.size() == 0: return report_error(GdAssertMessages.error_is_not_empty()) return report_success() @warning_ignore("unused_parameter", "shadowed_global_identifier") -func is_same(expected) -> GdUnitArrayAssert: - if not __validate_value_type(expected): +func is_same(expected :Variant) -> GdUnitArrayAssert: + if not _validate_value_type(expected): return report_error("ERROR: expected value: <%s>\n is not a Array Type!" % GdObjects.typeof_as_string(expected)) - var current = __current() + var current :Variant = get_current_value() if not is_same(current, expected): report_error(GdAssertMessages.error_is_same(current, expected)) return self -func is_not_same(expected) -> GdUnitArrayAssert: - if not __validate_value_type(expected): +func is_not_same(expected :Variant) -> GdUnitArrayAssert: + if not _validate_value_type(expected): return report_error("ERROR: expected value: <%s>\n is not a Array Type!" % GdObjects.typeof_as_string(expected)) - var current = __current() + var current :Variant = get_current_value() if is_same(current, expected): report_error(GdAssertMessages.error_not_same(current, expected)) return self func has_size(expected: int) -> GdUnitArrayAssert: - var current_ = __current() - if current_ == null or current_.size() != expected: - return report_error(GdAssertMessages.error_has_size(current_, expected)) + var current_value :Variant= get_current_value() + if current_value == null or current_value.size() != expected: + return report_error(GdAssertMessages.error_has_size(current_value, expected)) return report_success() -func contains(expected) -> GdUnitArrayAssert: +func contains(expected :Variant) -> GdUnitArrayAssert: return _contains(expected, GdObjects.COMPARE_MODE.PARAMETER_DEEP_TEST) -func contains_exactly(expected) -> GdUnitArrayAssert: +func contains_exactly(expected :Variant) -> GdUnitArrayAssert: return _contains_exactly(expected, GdObjects.COMPARE_MODE.PARAMETER_DEEP_TEST) -func contains_exactly_in_any_order(expected) -> GdUnitArrayAssert: +func contains_exactly_in_any_order(expected :Variant) -> GdUnitArrayAssert: return _contains_exactly_in_any_order(expected, GdObjects.COMPARE_MODE.PARAMETER_DEEP_TEST) -func contains_same(expected) -> GdUnitArrayAssert: +func contains_same(expected :Variant) -> GdUnitArrayAssert: return _contains(expected, GdObjects.COMPARE_MODE.OBJECT_REFERENCE) -func contains_same_exactly(expected) -> GdUnitArrayAssert: +func contains_same_exactly(expected :Variant) -> GdUnitArrayAssert: return _contains_exactly(expected, GdObjects.COMPARE_MODE.OBJECT_REFERENCE) -func contains_same_exactly_in_any_order(expected) -> GdUnitArrayAssert: +func contains_same_exactly_in_any_order(expected :Variant) -> GdUnitArrayAssert: return _contains_exactly_in_any_order(expected, GdObjects.COMPARE_MODE.OBJECT_REFERENCE) -func not_contains(expected) -> GdUnitArrayAssert: +func not_contains(expected :Variant) -> GdUnitArrayAssert: return _not_contains(expected, GdObjects.COMPARE_MODE.PARAMETER_DEEP_TEST) -func not_contains_same(expected) -> GdUnitArrayAssert: +func not_contains_same(expected :Variant) -> GdUnitArrayAssert: return _not_contains(expected, GdObjects.COMPARE_MODE.OBJECT_REFERENCE) -func is_instanceof(expected) -> GdUnitAssert: +func is_instanceof(expected :Variant) -> GdUnitAssert: _base.is_instanceof(expected) return self func extract(func_name :String, args := Array()) -> GdUnitArrayAssert: var extracted_elements := Array() - var extractor :GdUnitValueExtractor = ResourceLoader.load("res://addons/gdUnit4/src/extractors/GdUnitFuncValueExtractor.gd", "GDScript", ResourceLoader.CACHE_MODE_REUSE).new(func_name, args) - var current = __current() + var extractor :GdUnitValueExtractor = ResourceLoader.load("res://addons/gdUnit4/src/extractors/GdUnitFuncValueExtractor.gd", + "GDScript", ResourceLoader.CACHE_MODE_REUSE).new(func_name, args) + var current :Variant = get_current_value() if current == null: _current_value_provider = DefaultValueProvider.new(null) else: - for element in current: + for element :Variant in current: extracted_elements.append(extractor.extract_value(element)) _current_value_provider = DefaultValueProvider.new(extracted_elements) return self func extractv( - extr0 :GdUnitValueExtractor, - extr1 :GdUnitValueExtractor = null, + extr0 :GdUnitValueExtractor, + extr1 :GdUnitValueExtractor = null, extr2 :GdUnitValueExtractor = null, extr3 :GdUnitValueExtractor = null, extr4 :GdUnitValueExtractor = null, @@ -323,16 +322,26 @@ func extractv( extr9 :GdUnitValueExtractor = null) -> GdUnitArrayAssert: var extractors :Variant = GdArrayTools.filter_value([extr0, extr1, extr2, extr3, extr4, extr5, extr6, extr7, extr8, extr9], null) var extracted_elements := Array() - var current = __current() + var current :Variant = get_current_value() if current == null: _current_value_provider = DefaultValueProvider.new(null) else: - for element in __current(): - var ev :Array[Variant] = [GdUnitTuple.NO_ARG, GdUnitTuple.NO_ARG, GdUnitTuple.NO_ARG, GdUnitTuple.NO_ARG, GdUnitTuple.NO_ARG, GdUnitTuple.NO_ARG, GdUnitTuple.NO_ARG, GdUnitTuple.NO_ARG, GdUnitTuple.NO_ARG, GdUnitTuple.NO_ARG] - for index in extractors.size(): + for element: Variant in current: + var ev :Array[Variant] = [ + GdUnitTuple.NO_ARG, + GdUnitTuple.NO_ARG, + GdUnitTuple.NO_ARG, + GdUnitTuple.NO_ARG, + GdUnitTuple.NO_ARG, + GdUnitTuple.NO_ARG, + GdUnitTuple.NO_ARG, + GdUnitTuple.NO_ARG, + GdUnitTuple.NO_ARG, + GdUnitTuple.NO_ARG + ] + for index :int in extractors.size(): var extractor :GdUnitValueExtractor = extractors[index] ev[index] = extractor.extract_value(element) - if extractors.size() > 1: extracted_elements.append(GdUnitTuple.new(ev[0], ev[1], ev[2], ev[3], ev[4], ev[5], ev[6], ev[7], ev[8], ev[9])) else: diff --git a/addons/gdUnit4/src/asserts/GdUnitAssertImpl.gd b/addons/gdUnit4/src/asserts/GdUnitAssertImpl.gd index 6fc87b18..2755285c 100644 --- a/addons/gdUnit4/src/asserts/GdUnitAssertImpl.gd +++ b/addons/gdUnit4/src/asserts/GdUnitAssertImpl.gd @@ -6,70 +6,67 @@ var _current_error_message :String = "" var _custom_failure_message :String = "" -func _init(current :Variant): +func _init(current :Variant) -> void: _current = current # save the actual assert instance on the current thread context GdUnitThreadManager.get_current_context().set_assert(self) GdAssertReports.reset_last_error_line_number() -func _failure_message() -> String: +func failure_message() -> String: return _current_error_message -func __current() -> Variant: +func current_value() -> Variant: return _current -func __validate_value_type(value, type :Variant.Type) -> bool: - return value == null or typeof(value) == type - - func report_success() -> GdUnitAssert: GdAssertReports.report_success() return self func report_error(error_message :String, failure_line_number: int = -1) -> GdUnitAssert: - var line_number := failure_line_number if failure_line_number != -1 else GdUnitAssert._get_line_number() + var line_number := failure_line_number if failure_line_number != -1 else GdUnitAssertions.get_line_number() GdAssertReports.set_last_error_line_number(line_number) _current_error_message = error_message if _custom_failure_message.is_empty() else _custom_failure_message GdAssertReports.report_error(_current_error_message, line_number) + Engine.set_meta("GD_TEST_FAILURE", true) return self -func test_fail(): +func test_fail() -> GdUnitAssert: return report_error(GdAssertMessages.error_not_implemented()) -func override_failure_message(message :String): +func override_failure_message(message :String) -> GdUnitAssert: _custom_failure_message = message return self -func is_equal(expected) -> GdUnitAssert: - var current = __current() +func is_equal(expected :Variant) -> GdUnitAssert: + var current :Variant = current_value() if not GdObjects.equals(current, expected): return report_error(GdAssertMessages.error_equal(current, expected)) return report_success() -func is_not_equal(expected) -> GdUnitAssert: - var current = __current() +func is_not_equal(expected :Variant) -> GdUnitAssert: + var current :Variant = current_value() if GdObjects.equals(current, expected): return report_error(GdAssertMessages.error_not_equal(current, expected)) return report_success() func is_null() -> GdUnitAssert: - var current = __current() + var current :Variant = current_value() if current != null: return report_error(GdAssertMessages.error_is_null(current)) return report_success() func is_not_null() -> GdUnitAssert: - var current = __current() + var current :Variant = current_value() if current == null: return report_error(GdAssertMessages.error_is_not_null()) return report_success() diff --git a/addons/gdUnit4/src/asserts/GdUnitAssertions.gd b/addons/gdUnit4/src/asserts/GdUnitAssertions.gd index 0f7219b3..a1b12caf 100644 --- a/addons/gdUnit4/src/asserts/GdUnitAssertions.gd +++ b/addons/gdUnit4/src/asserts/GdUnitAssertions.gd @@ -3,8 +3,9 @@ class_name GdUnitAssertions extends RefCounted -func _init(): +func _init() -> void: # preload all gdunit assertions to speedup testsuite loading time + # gdlint:disable=private-method-call GdUnitAssertions.__lazy_load("res://addons/gdUnit4/src/asserts/GdUnitAssertImpl.gd") GdUnitAssertions.__lazy_load("res://addons/gdUnit4/src/asserts/GdUnitBoolAssertImpl.gd") GdUnitAssertions.__lazy_load("res://addons/gdUnit4/src/asserts/GdUnitStringAssertImpl.gd") @@ -26,5 +27,38 @@ func _init(): ### in order to noticeably reduce the loading time of the test suite. # We go this hard way to increase the loading performance to avoid reparsing all the used scripts # for more detailed info -> https://github.com/godotengine/godot/issues/67400 +# gdlint:disable=function-name static func __lazy_load(script_path :String) -> GDScript: return ResourceLoader.load(script_path, "GDScript", ResourceLoader.CACHE_MODE_REUSE) + + +static func validate_value_type(value :Variant, type :Variant.Type) -> bool: + return value == null or typeof(value) == type + + +# Scans the current stack trace for the root cause to extract the line number +static func get_line_number() -> int: + var stack_trace := get_stack() + if stack_trace == null or stack_trace.is_empty(): + return -1 + for index in stack_trace.size(): + var stack_info :Dictionary = stack_trace[index] + var function :String = stack_info.get("function") + # we catch helper asserts to skip over to return the correct line number + if function.begins_with("assert_"): + continue + if function.begins_with("test_"): + return stack_info.get("line") + var source :String = stack_info.get("source") + if source.is_empty() \ + or source.begins_with("user://") \ + or source.ends_with("GdUnitAssert.gd") \ + or source.ends_with("GdUnitAssertions.gd") \ + or source.ends_with("AssertImpl.gd") \ + or source.ends_with("GdUnitTestSuite.gd") \ + or source.ends_with("GdUnitSceneRunnerImpl.gd") \ + or source.ends_with("GdUnitObjectInteractions.gd") \ + or source.ends_with("GdUnitAwaiter.gd"): + continue + return stack_info.get("line") + return -1 diff --git a/addons/gdUnit4/src/asserts/GdUnitBoolAssertImpl.gd b/addons/gdUnit4/src/asserts/GdUnitBoolAssertImpl.gd index d838c86f..d25c6ebc 100644 --- a/addons/gdUnit4/src/asserts/GdUnitBoolAssertImpl.gd +++ b/addons/gdUnit4/src/asserts/GdUnitBoolAssertImpl.gd @@ -3,23 +3,24 @@ extends GdUnitBoolAssert var _base: GdUnitAssert -func _init(current): - _base = ResourceLoader.load("res://addons/gdUnit4/src/asserts/GdUnitAssertImpl.gd", "GDScript", ResourceLoader.CACHE_MODE_REUSE).new(current) +func _init(current :Variant) -> void: + _base = ResourceLoader.load("res://addons/gdUnit4/src/asserts/GdUnitAssertImpl.gd", "GDScript", + ResourceLoader.CACHE_MODE_REUSE).new(current) # save the actual assert instance on the current thread context GdUnitThreadManager.get_current_context().set_assert(self) - if not _base.__validate_value_type(current, TYPE_BOOL): + if not GdUnitAssertions.validate_value_type(current, TYPE_BOOL): report_error("GdUnitBoolAssert inital error, unexpected type <%s>" % GdObjects.typeof_as_string(current)) -func _notification(event): +func _notification(event :int) -> void: if event == NOTIFICATION_PREDELETE: if _base != null: _base.notification(event) _base = null -func __current(): - return _base.__current() +func current_value() -> Variant: + return _base.current_value() func report_success() -> GdUnitBoolAssert: @@ -32,7 +33,7 @@ func report_error(error :String) -> GdUnitBoolAssert: return self -func _failure_message() -> String: +func failure_message() -> String: return _base._current_error_message @@ -53,23 +54,23 @@ func is_not_null() -> GdUnitBoolAssert: return self -func is_equal(expected) -> GdUnitBoolAssert: +func is_equal(expected :Variant) -> GdUnitBoolAssert: _base.is_equal(expected) return self -func is_not_equal(expected) -> GdUnitBoolAssert: +func is_not_equal(expected :Variant) -> GdUnitBoolAssert: _base.is_not_equal(expected) return self func is_true() -> GdUnitBoolAssert: - if __current() != true: - return report_error(GdAssertMessages.error_is_true(__current())) + if current_value() != true: + return report_error(GdAssertMessages.error_is_true(current_value())) return report_success() func is_false() -> GdUnitBoolAssert: - if __current() == true || __current() == null: - return report_error(GdAssertMessages.error_is_false(__current())) + if current_value() == true || current_value() == null: + return report_error(GdAssertMessages.error_is_false(current_value())) return report_success() diff --git a/addons/gdUnit4/src/asserts/GdUnitDictionaryAssertImpl.gd b/addons/gdUnit4/src/asserts/GdUnitDictionaryAssertImpl.gd index 514894ba..87f77d18 100644 --- a/addons/gdUnit4/src/asserts/GdUnitDictionaryAssertImpl.gd +++ b/addons/gdUnit4/src/asserts/GdUnitDictionaryAssertImpl.gd @@ -3,15 +3,16 @@ extends GdUnitDictionaryAssert var _base :GdUnitAssert -func _init(current): - _base = ResourceLoader.load("res://addons/gdUnit4/src/asserts/GdUnitAssertImpl.gd", "GDScript", ResourceLoader.CACHE_MODE_REUSE).new(current) +func _init(current :Variant) -> void: + _base = ResourceLoader.load("res://addons/gdUnit4/src/asserts/GdUnitAssertImpl.gd", "GDScript", + ResourceLoader.CACHE_MODE_REUSE).new(current) # save the actual assert instance on the current thread context GdUnitThreadManager.get_current_context().set_assert(self) - if not _base.__validate_value_type(current, TYPE_DICTIONARY): + if not GdUnitAssertions.validate_value_type(current, TYPE_DICTIONARY): report_error("GdUnitDictionaryAssert inital error, unexpected type <%s>" % GdObjects.typeof_as_string(current)) -func _notification(event): +func _notification(event :int) -> void: if event == NOTIFICATION_PREDELETE: if _base != null: _base.notification(event) @@ -28,7 +29,7 @@ func report_error(error :String) -> GdUnitDictionaryAssert: return self -func _failure_message() -> String: +func failure_message() -> String: return _base._current_error_message @@ -37,8 +38,8 @@ func override_failure_message(message :String) -> GdUnitDictionaryAssert: return self -func __current() -> Variant: - return _base.__current() +func current_value() -> Variant: + return _base.current_value() func is_null() -> GdUnitDictionaryAssert: @@ -51,66 +52,64 @@ func is_not_null() -> GdUnitDictionaryAssert: return self -func is_equal(expected) -> GdUnitDictionaryAssert: - var current = __current() +func is_equal(expected :Variant) -> GdUnitDictionaryAssert: + var current :Variant = current_value() if current == null: - return report_error(GdAssertMessages.error_equal(null, GdAssertMessages._format_dict(expected))) + return report_error(GdAssertMessages.error_equal(null, GdAssertMessages.format_dict(expected))) if not GdObjects.equals(current, expected): - var c := GdAssertMessages._format_dict(current) - var e := GdAssertMessages._format_dict(expected) + var c := GdAssertMessages.format_dict(current) + var e := GdAssertMessages.format_dict(expected) var diff := GdDiffTool.string_diff(c, e) - var curent_ = GdAssertMessages._colored_array_div(diff[1]) - return report_error(GdAssertMessages.error_equal(curent_, e)) + var curent_diff := GdAssertMessages.colored_array_div(diff[1]) + return report_error(GdAssertMessages.error_equal(curent_diff, e)) return report_success() -func is_not_equal(expected) -> GdUnitDictionaryAssert: - var current = __current() +func is_not_equal(expected :Variant) -> GdUnitDictionaryAssert: + var current :Variant = current_value() if GdObjects.equals(current, expected): return report_error(GdAssertMessages.error_not_equal(current, expected)) return report_success() @warning_ignore("unused_parameter", "shadowed_global_identifier") -func is_same(expected) -> GdUnitDictionaryAssert: - var current = __current() +func is_same(expected :Variant) -> GdUnitDictionaryAssert: + var current :Variant = current_value() if current == null: - return report_error(GdAssertMessages.error_equal(null, GdAssertMessages._format_dict(expected))) + return report_error(GdAssertMessages.error_equal(null, GdAssertMessages.format_dict(expected))) if not is_same(current, expected): - var c := GdAssertMessages._format_dict(current) - var e := GdAssertMessages._format_dict(expected) + var c := GdAssertMessages.format_dict(current) + var e := GdAssertMessages.format_dict(expected) var diff := GdDiffTool.string_diff(c, e) - var curent_ = GdAssertMessages._colored_array_div(diff[1]) - return report_error(GdAssertMessages.error_is_same(curent_, e)) + var curent_diff := GdAssertMessages.colored_array_div(diff[1]) + return report_error(GdAssertMessages.error_is_same(curent_diff, e)) return report_success() - @warning_ignore("unused_parameter", "shadowed_global_identifier") -func is_not_same(expected) -> GdUnitDictionaryAssert: - var current = __current() +func is_not_same(expected :Variant) -> GdUnitDictionaryAssert: + var current :Variant = current_value() if is_same(current, expected): return report_error(GdAssertMessages.error_not_same(current, expected)) return report_success() - func is_empty() -> GdUnitDictionaryAssert: - var current = __current() + var current :Variant = current_value() if current == null or not current.is_empty(): return report_error(GdAssertMessages.error_is_empty(current)) return report_success() func is_not_empty() -> GdUnitDictionaryAssert: - var current = __current() + var current :Variant = current_value() if current == null or current.is_empty(): return report_error(GdAssertMessages.error_is_not_empty()) return report_success() func has_size(expected: int) -> GdUnitDictionaryAssert: - var current = __current() + var current :Variant = current_value() if current == null: return report_error(GdAssertMessages.error_is_not_null()) if current.size() != expected: @@ -119,49 +118,34 @@ func has_size(expected: int) -> GdUnitDictionaryAssert: func _contains_keys(expected :Array, compare_mode :GdObjects.COMPARE_MODE) -> GdUnitDictionaryAssert: - var current = __current() + var current :Variant = current_value() if current == null: return report_error(GdAssertMessages.error_is_not_null()) # find expected keys - var keys_not_found :Array = expected.filter(func(expected_key): - for current_key in current.keys(): - if GdObjects.equals(current_key, expected_key, false, compare_mode): - return false - return true - ) + var keys_not_found :Array = expected.filter(_filter_by_key.bind(current.keys(), compare_mode)) if not keys_not_found.is_empty(): return report_error(GdAssertMessages.error_contains_keys(current.keys(), expected, keys_not_found, compare_mode)) return report_success() -func _contains_key_value(key, value, compare_mode :GdObjects.COMPARE_MODE) -> GdUnitDictionaryAssert: - var current = __current() +func _contains_key_value(key :Variant, value :Variant, compare_mode :GdObjects.COMPARE_MODE) -> GdUnitDictionaryAssert: + var current :Variant = current_value() var expected := [key] if current == null: return report_error(GdAssertMessages.error_is_not_null()) - var keys_not_found :Array = expected.filter(func(expected_key): - for current_key in current.keys(): - if GdObjects.equals(current_key, expected_key, false, compare_mode): - return false - return true - ) + var keys_not_found :Array = expected.filter(_filter_by_key.bind(current.keys(), compare_mode)) if not keys_not_found.is_empty(): - return report_error(GdAssertMessages.error_contains_key_value(key, value, current.keys(), compare_mode)) + return report_error(GdAssertMessages.error_contains_keys(current.keys(), expected, keys_not_found, compare_mode)) if not GdObjects.equals(current[key], value, false, compare_mode): return report_error(GdAssertMessages.error_contains_key_value(key, value, current[key], compare_mode)) return report_success() func _not_contains_keys(expected :Array, compare_mode :GdObjects.COMPARE_MODE) -> GdUnitDictionaryAssert: - var current = __current() + var current :Variant = current_value() if current == null: return report_error(GdAssertMessages.error_is_not_null()) - var keys_found :Array = current.keys().filter(func(current_key): - for expected_key in expected: - if GdObjects.equals(current_key, expected_key, false, compare_mode): - return true - return false - ) + var keys_found :Array = current.keys().filter(_filter_by_key.bind(expected, compare_mode, true)) if not keys_found.is_empty(): return report_error(GdAssertMessages.error_not_contains_keys(current.keys(), expected, keys_found, compare_mode)) return report_success() @@ -171,7 +155,7 @@ func contains_keys(expected :Array) -> GdUnitDictionaryAssert: return _contains_keys(expected, GdObjects.COMPARE_MODE.PARAMETER_DEEP_TEST) -func contains_key_value(key, value) -> GdUnitDictionaryAssert: +func contains_key_value(key :Variant, value :Variant) -> GdUnitDictionaryAssert: return _contains_key_value(key, value, GdObjects.COMPARE_MODE.PARAMETER_DEEP_TEST) @@ -183,9 +167,16 @@ func contains_same_keys(expected :Array) -> GdUnitDictionaryAssert: return _contains_keys(expected, GdObjects.COMPARE_MODE.OBJECT_REFERENCE) -func contains_same_key_value(key, value) -> GdUnitDictionaryAssert: +func contains_same_key_value(key :Variant, value :Variant) -> GdUnitDictionaryAssert: return _contains_key_value(key, value, GdObjects.COMPARE_MODE.OBJECT_REFERENCE) func not_contains_same_keys(expected :Array) -> GdUnitDictionaryAssert: return _not_contains_keys(expected, GdObjects.COMPARE_MODE.OBJECT_REFERENCE) + + +func _filter_by_key(element :Variant, values :Array, compare_mode :GdObjects.COMPARE_MODE, is_not :bool = false) -> bool: + for key :Variant in values: + if GdObjects.equals(key, element, false, compare_mode): + return is_not + return !is_not diff --git a/addons/gdUnit4/src/asserts/GdUnitFailureAssertImpl.gd b/addons/gdUnit4/src/asserts/GdUnitFailureAssertImpl.gd index 8be056e6..88a30625 100644 --- a/addons/gdUnit4/src/asserts/GdUnitFailureAssertImpl.gd +++ b/addons/gdUnit4/src/asserts/GdUnitFailureAssertImpl.gd @@ -6,9 +6,13 @@ var _is_failed := false var _failure_message :String +func _set_do_expect_fail(enabled :bool = true) -> void: + Engine.set_meta(GdUnitConstants.EXPECT_ASSERT_REPORT_FAILURES, enabled) + + func execute_and_await(assertion :Callable, do_await := true) -> GdUnitFailureAssert: # do not report any failure from the original assertion we want to test - GdAssertReports.expect_fail(true) + _set_do_expect_fail(true) var thread_context := GdUnitThreadManager.get_current_context() thread_context.set_assert(null) GdUnitSignals.instance().gdunit_set_test_failed.connect(_on_test_failed) @@ -17,14 +21,14 @@ func execute_and_await(assertion :Callable, do_await := true) -> GdUnitFailureAs await assertion.call() else: assertion.call() - GdAssertReports.expect_fail(false) + _set_do_expect_fail(false) # get the assert instance from current tread context var current_assert := thread_context.get_assert() if not is_instance_of(current_assert, GdUnitAssert): _is_failed = true _failure_message = "Invalid Callable! It must be a callable of 'GdUnitAssert'" - return - _failure_message = current_assert._failure_message() + return self + _failure_message = current_assert.failure_message() return self @@ -38,12 +42,12 @@ func _on_test_failed(value :bool) -> void: @warning_ignore("unused_parameter") -func is_equal(expected :GdUnitAssert) -> GdUnitFailureAssert: +func is_equal(_expected :GdUnitAssert) -> GdUnitFailureAssert: return _report_error("Not implemented") @warning_ignore("unused_parameter") -func is_not_equal(expected :GdUnitAssert) -> GdUnitFailureAssert: +func is_not_equal(_expected :GdUnitAssert) -> GdUnitFailureAssert: return _report_error("Not implemented") @@ -75,11 +79,12 @@ func has_line(expected :int) -> GdUnitFailureAssert: func has_message(expected :String) -> GdUnitFailureAssert: - var expected_error := GdUnitTools.normalize_text(expected) + is_failed() + var expected_error := GdUnitTools.normalize_text(GdUnitTools.richtext_normalize(expected)) var current_error := GdUnitTools.normalize_text(GdUnitTools.richtext_normalize(_failure_message)) if current_error != expected_error: var diffs := GdDiffTool.string_diff(current_error, expected_error) - var current := GdAssertMessages._colored_array_div(diffs[1]) + var current := GdAssertMessages.colored_array_div(diffs[1]) _report_error(GdAssertMessages.error_not_same_error(current, expected_error)) return self @@ -89,13 +94,13 @@ func starts_with_message(expected :String) -> GdUnitFailureAssert: var current_error := GdUnitTools.normalize_text(GdUnitTools.richtext_normalize(_failure_message)) if current_error.find(expected_error) != 0: var diffs := GdDiffTool.string_diff(current_error, expected_error) - var current := GdAssertMessages._colored_array_div(diffs[1]) + var current := GdAssertMessages.colored_array_div(diffs[1]) _report_error(GdAssertMessages.error_not_same_error(current, expected_error)) return self func _report_error(error_message :String, failure_line_number: int = -1) -> GdUnitAssert: - var line_number := failure_line_number if failure_line_number != -1 else GdUnitAssert._get_line_number() + var line_number := failure_line_number if failure_line_number != -1 else GdUnitAssertions.get_line_number() GdAssertReports.report_error(error_message, line_number) return self diff --git a/addons/gdUnit4/src/asserts/GdUnitFileAssertImpl.gd b/addons/gdUnit4/src/asserts/GdUnitFileAssertImpl.gd index b4e07008..f5118799 100644 --- a/addons/gdUnit4/src/asserts/GdUnitFileAssertImpl.gd +++ b/addons/gdUnit4/src/asserts/GdUnitFileAssertImpl.gd @@ -5,23 +5,24 @@ const GdUnitTools := preload("res://addons/gdUnit4/src/core/GdUnitTools.gd") var _base: GdUnitAssert -func _init(current): - _base = ResourceLoader.load("res://addons/gdUnit4/src/asserts/GdUnitAssertImpl.gd", "GDScript", ResourceLoader.CACHE_MODE_REUSE).new(current) +func _init(current :Variant) -> void: + _base = ResourceLoader.load("res://addons/gdUnit4/src/asserts/GdUnitAssertImpl.gd", "GDScript", + ResourceLoader.CACHE_MODE_REUSE).new(current) # save the actual assert instance on the current thread context GdUnitThreadManager.get_current_context().set_assert(self) - if not _base.__validate_value_type(current, TYPE_STRING): + if not GdUnitAssertions.validate_value_type(current, TYPE_STRING): report_error("GdUnitFileAssert inital error, unexpected type <%s>" % GdObjects.typeof_as_string(current)) -func _notification(event): +func _notification(event :int) -> void: if event == NOTIFICATION_PREDELETE: if _base != null: _base.notification(event) _base = null -func __current() -> String: - return _base.__current() as String +func current_value() -> String: + return _base.current_value() as String func report_success() -> GdUnitFileAssert: @@ -34,7 +35,7 @@ func report_error(error :String) -> GdUnitFileAssert: return self -func _failure_message() -> String: +func failure_message() -> String: return _base._current_error_message @@ -43,52 +44,52 @@ func override_failure_message(message :String) -> GdUnitFileAssert: return self -func is_equal(expected) -> GdUnitFileAssert: +func is_equal(expected :Variant) -> GdUnitFileAssert: _base.is_equal(expected) return self -func is_not_equal(expected) -> GdUnitFileAssert: +func is_not_equal(expected :Variant) -> GdUnitFileAssert: _base.is_not_equal(expected) return self func is_file() -> GdUnitFileAssert: - var current := __current() + var current := current_value() if FileAccess.open(current, FileAccess.READ) == null: return report_error("Is not a file '%s', error code %s" % [current, FileAccess.get_open_error()]) return report_success() func exists() -> GdUnitFileAssert: - var current := __current() + var current := current_value() if not FileAccess.file_exists(current): return report_error("The file '%s' not exists" %current) return report_success() func is_script() -> GdUnitFileAssert: - var current := __current() + var current := current_value() if FileAccess.open(current, FileAccess.READ) == null: return report_error("Can't acces the file '%s'! Error code %s" % [current, FileAccess.get_open_error()]) - - var script = load(current) + + var script := load(current) if not script is GDScript: return report_error("The file '%s' is not a GdScript" % current) return report_success() func contains_exactly(expected_rows :Array) -> GdUnitFileAssert: - var current := __current() + var current := current_value() if FileAccess.open(current, FileAccess.READ) == null: return report_error("Can't acces the file '%s'! Error code %s" % [current, FileAccess.get_open_error()]) - - var script = load(current) + + var script := load(current) if script is GDScript: - var instance = script.new() - var source_code = GdScriptParser.to_unix_format(instance.get_script().source_code) + var instance :Variant = script.new() + var source_code := GdScriptParser.to_unix_format(instance.get_script().source_code) GdUnitTools.free_instance(instance) var rows := Array(source_code.split("\n")) - ResourceLoader.load("res://addons/gdUnit4/src/asserts/GdUnitArrayAssertImpl.gd", "GDScript", ResourceLoader.CACHE_MODE_REUSE).new(rows)\ - .contains_exactly(expected_rows) + ResourceLoader.load("res://addons/gdUnit4/src/asserts/GdUnitArrayAssertImpl.gd", "GDScript", + ResourceLoader.CACHE_MODE_REUSE).new(rows).contains_exactly(expected_rows) return self diff --git a/addons/gdUnit4/src/asserts/GdUnitFloatAssertImpl.gd b/addons/gdUnit4/src/asserts/GdUnitFloatAssertImpl.gd index d457b00a..5920d9dd 100644 --- a/addons/gdUnit4/src/asserts/GdUnitFloatAssertImpl.gd +++ b/addons/gdUnit4/src/asserts/GdUnitFloatAssertImpl.gd @@ -3,23 +3,24 @@ extends GdUnitFloatAssert var _base: GdUnitAssert -func _init(current): - _base = ResourceLoader.load("res://addons/gdUnit4/src/asserts/GdUnitAssertImpl.gd", "GDScript", ResourceLoader.CACHE_MODE_REUSE).new(current) +func _init(current :Variant) -> void: + _base = ResourceLoader.load("res://addons/gdUnit4/src/asserts/GdUnitAssertImpl.gd", "GDScript", + ResourceLoader.CACHE_MODE_REUSE).new(current) # save the actual assert instance on the current thread context GdUnitThreadManager.get_current_context().set_assert(self) - if not _base.__validate_value_type(current, TYPE_FLOAT): + if not GdUnitAssertions.validate_value_type(current, TYPE_FLOAT): report_error("GdUnitFloatAssert inital error, unexpected type <%s>" % GdObjects.typeof_as_string(current)) -func _notification(event): +func _notification(event :int) -> void: if event == NOTIFICATION_PREDELETE: if _base != null: _base.notification(event) _base = null -func __current(): - return _base.__current() +func current_value() -> Variant: + return _base.current_value() func report_success() -> GdUnitFloatAssert: @@ -32,7 +33,7 @@ func report_error(error :String) -> GdUnitFloatAssert: return self -func _failure_message() -> String: +func failure_message() -> String: return _base._current_error_message @@ -67,77 +68,77 @@ func is_equal_approx(expected :float, approx :float) -> GdUnitFloatAssert: func is_less(expected :float) -> GdUnitFloatAssert: - var current = __current() + var current :Variant = current_value() if current == null or current >= expected: return report_error(GdAssertMessages.error_is_value(Comparator.LESS_THAN, current, expected)) return report_success() func is_less_equal(expected :float) -> GdUnitFloatAssert: - var current = __current() + var current :Variant = current_value() if current == null or current > expected: return report_error(GdAssertMessages.error_is_value(Comparator.LESS_EQUAL, current, expected)) return report_success() func is_greater(expected :float) -> GdUnitFloatAssert: - var current = __current() + var current :Variant = current_value() if current == null or current <= expected: return report_error(GdAssertMessages.error_is_value(Comparator.GREATER_THAN, current, expected)) return report_success() func is_greater_equal(expected :float) -> GdUnitFloatAssert: - var current = __current() + var current :Variant = current_value() if current == null or current < expected: return report_error(GdAssertMessages.error_is_value(Comparator.GREATER_EQUAL, current, expected)) return report_success() func is_negative() -> GdUnitFloatAssert: - var current = __current() + var current :Variant = current_value() if current == null or current >= 0.0: return report_error(GdAssertMessages.error_is_negative(current)) return report_success() func is_not_negative() -> GdUnitFloatAssert: - var current = __current() + var current :Variant = current_value() if current == null or current < 0.0: return report_error(GdAssertMessages.error_is_not_negative(current)) return report_success() func is_zero() -> GdUnitFloatAssert: - var current = __current() + var current :Variant = current_value() if current == null or not is_equal_approx(0.00000000, current): return report_error(GdAssertMessages.error_is_zero(current)) return report_success() func is_not_zero() -> GdUnitFloatAssert: - var current = __current() + var current :Variant = current_value() if current == null or is_equal_approx(0.00000000, current): return report_error(GdAssertMessages.error_is_not_zero()) return report_success() func is_in(expected :Array) -> GdUnitFloatAssert: - var current = __current() + var current :Variant = current_value() if not expected.has(current): return report_error(GdAssertMessages.error_is_in(current, expected)) return report_success() func is_not_in(expected :Array) -> GdUnitFloatAssert: - var current = __current() + var current :Variant = current_value() if expected.has(current): return report_error(GdAssertMessages.error_is_not_in(current, expected)) return report_success() func is_between(from :float, to :float) -> GdUnitFloatAssert: - var current = __current() + var current :Variant = current_value() if current == null or current < from or current > to: return report_error(GdAssertMessages.error_is_value(Comparator.BETWEEN_EQUAL, current, from, to)) return report_success() diff --git a/addons/gdUnit4/src/asserts/GdUnitFuncAssertImpl.gd b/addons/gdUnit4/src/asserts/GdUnitFuncAssertImpl.gd index 0c9e4b9f..ccd2a116 100644 --- a/addons/gdUnit4/src/asserts/GdUnitFuncAssertImpl.gd +++ b/addons/gdUnit4/src/asserts/GdUnitFuncAssertImpl.gd @@ -14,8 +14,8 @@ var _interrupted := false var _sleep_timer :Timer = null -func _init(instance :Object, func_name :String, args := Array()): - _line_number = GdUnitAssert._get_line_number() +func _init(instance :Object, func_name :String, args := Array()) -> void: + _line_number = GdUnitAssertions.get_line_number() GdAssertReports.reset_last_error_line_number() # save the actual assert instance on the current thread context GdUnitThreadManager.get_current_context().set_assert(self) @@ -27,7 +27,7 @@ func _init(instance :Object, func_name :String, args := Array()): _current_value_provider = CallBackValueProvider.new(instance, func_name, args) -func _notification(_what): +func _notification(_what :int) -> void: if is_instance_valid(_current_value_provider): _current_value_provider.dispose() _current_value_provider = null @@ -49,7 +49,7 @@ func report_error(error_message :String) -> GdUnitFuncAssert: return self -func _failure_message() -> String: +func failure_message() -> String: return _current_error_message @@ -72,55 +72,55 @@ func wait_until(timeout := 2000) -> GdUnitFuncAssert: func is_null() -> GdUnitFuncAssert: - await _validate_callback(__is_null) + await _validate_callback(cb_is_null) return self func is_not_null() -> GdUnitFuncAssert: - await _validate_callback(__is_not_null) + await _validate_callback(cb_is_not_null) return self func is_false() -> GdUnitFuncAssert: - await _validate_callback(__is_false) + await _validate_callback(cb_is_false) return self func is_true() -> GdUnitFuncAssert: - await _validate_callback(__is_true) + await _validate_callback(cb_is_true) return self -func is_equal(expected) -> GdUnitFuncAssert: - await _validate_callback(__is_equal, expected) +func is_equal(expected :Variant) -> GdUnitFuncAssert: + await _validate_callback(cb_is_equal, expected) return self -func is_not_equal(expected) -> GdUnitFuncAssert: - await _validate_callback(__is_not_equal, expected) +func is_not_equal(expected :Variant) -> GdUnitFuncAssert: + await _validate_callback(cb_is_not_equal, expected) return self # we need actually to define this Callable as functions otherwise we results into leaked scripts here # this is actually a Godot bug and needs this kind of workaround -func __is_null(c, _e): return c == null -func __is_not_null(c, _e): return c != null -func __is_false(c, _e): return c == false -func __is_true(c, _e): return c == true -func __is_equal(c, e): return GdObjects.equals(c,e) -func __is_not_equal(c, e): return not GdObjects.equals(c, e) +func cb_is_null(c :Variant, _e :Variant) -> bool: return c == null +func cb_is_not_null(c :Variant, _e :Variant) -> bool: return c != null +func cb_is_false(c :Variant, _e :Variant) -> bool: return c == false +func cb_is_true(c :Variant, _e :Variant) -> bool: return c == true +func cb_is_equal(c :Variant, e :Variant) -> bool: return GdObjects.equals(c,e) +func cb_is_not_equal(c :Variant, e :Variant) -> bool: return not GdObjects.equals(c, e) -func _validate_callback(predicate :Callable, expected = null): +func _validate_callback(predicate :Callable, expected :Variant = null) -> void: if _interrupted: return GdUnitMemoryObserver.guard_instance(self) - var time_scale = Engine.get_time_scale() + var time_scale := Engine.get_time_scale() var timer := Timer.new() timer.set_name("gdunit_funcassert_interrupt_timer_%d" % timer.get_instance_id()) Engine.get_main_loop().root.add_child(timer) timer.add_to_group("GdUnitTimers") - timer.timeout.connect(func do_interrupt(): + timer.timeout.connect(func do_interrupt() -> void: _interrupted = true , CONNECT_DEFERRED) timer.set_one_shot(true) @@ -128,23 +128,23 @@ func _validate_callback(predicate :Callable, expected = null): _sleep_timer = Timer.new() _sleep_timer.set_name("gdunit_funcassert_sleep_timer_%d" % _sleep_timer.get_instance_id() ) Engine.get_main_loop().root.add_child(_sleep_timer) - + while true: - var current = await next_current_value() + var current :Variant = await next_current_value() # is interupted or predicate success if _interrupted or predicate.call(current, expected): break if is_instance_valid(_sleep_timer): _sleep_timer.start(0.05) await _sleep_timer.timeout - + _sleep_timer.stop() await Engine.get_main_loop().process_frame if _interrupted: # https://github.com/godotengine/godot/issues/73052 #var predicate_name = predicate.get_method() var predicate_name :String = str(predicate).split('::')[1] - report_error(GdAssertMessages.error_interrupted(predicate_name.strip_edges().trim_prefix("__"), expected, LocalTime.elapsed(_timeout))) + report_error(GdAssertMessages.error_interrupted(predicate_name.strip_edges().trim_prefix("cb_"), expected, LocalTime.elapsed(_timeout))) else: report_success() _sleep_timer.free() diff --git a/addons/gdUnit4/src/asserts/GdUnitGodotErrorAssertImpl.gd b/addons/gdUnit4/src/asserts/GdUnitGodotErrorAssertImpl.gd index 169994be..f08da5bd 100644 --- a/addons/gdUnit4/src/asserts/GdUnitGodotErrorAssertImpl.gd +++ b/addons/gdUnit4/src/asserts/GdUnitGodotErrorAssertImpl.gd @@ -4,9 +4,10 @@ var _current_error_message :String var _callable :Callable -func _init(callable :Callable): +func _init(callable :Callable) -> void: # we only support Godot 4.1.x+ because of await issue https://github.com/godotengine/godot/issues/80292 - assert(Engine.get_version_info().hex >= 0x40100, "This assertion is not supported for Godot 4.0.x. Please upgrade to the minimum version Godot 4.1.0!") + assert(Engine.get_version_info().hex >= 0x40100, + "This assertion is not supported for Godot 4.0.x. Please upgrade to the minimum version Godot 4.1.0!") # save the actual assert instance on the current thread context GdUnitThreadManager.get_current_context().set_assert(self) GdAssertReports.reset_last_error_line_number() @@ -15,17 +16,18 @@ func _init(callable :Callable): func _execute() -> Array[ErrorLogEntry]: # execute the given code and monitor for runtime errors - var monitor := GodotGdErrorMonitor.new(true) - monitor.start() if _callable == null or not _callable.is_valid(): _report_error("Invalid Callable '%s'" % _callable) else: await _callable.call() - monitor.stop() - return await monitor.scan() + return await _error_monitor().scan(true) -func _failure_message() -> String: +func _error_monitor() -> GodotGdErrorMonitor: + return GdUnitThreadManager.get_current_context().get_execution_context().error_monitor + + +func failure_message() -> String: return _current_error_message @@ -35,7 +37,7 @@ func _report_success() -> GdUnitAssert: func _report_error(error_message :String, failure_line_number: int = -1) -> GdUnitAssert: - var line_number := failure_line_number if failure_line_number != -1 else GdUnitAssert._get_line_number() + var line_number := failure_line_number if failure_line_number != -1 else GdUnitAssertions.get_line_number() _current_error_message = error_message GdAssertReports.report_error(error_message, line_number) return self @@ -44,6 +46,8 @@ func _report_error(error_message :String, failure_line_number: int = -1) -> GdUn func _has_log_entry(log_entries :Array[ErrorLogEntry], type :ErrorLogEntry.TYPE, error :String) -> bool: for entry in log_entries: if entry._type == type and entry._message == error: + # Erase the log entry we already handled it by this assertion, otherwise it will report at twice + _error_monitor().erase_log_entry(entry) return true return false diff --git a/addons/gdUnit4/src/asserts/GdUnitIntAssertImpl.gd b/addons/gdUnit4/src/asserts/GdUnitIntAssertImpl.gd index 7f39c060..98fd4453 100644 --- a/addons/gdUnit4/src/asserts/GdUnitIntAssertImpl.gd +++ b/addons/gdUnit4/src/asserts/GdUnitIntAssertImpl.gd @@ -3,23 +3,24 @@ extends GdUnitIntAssert var _base: GdUnitAssert -func _init(current): - _base = ResourceLoader.load("res://addons/gdUnit4/src/asserts/GdUnitAssertImpl.gd", "GDScript", ResourceLoader.CACHE_MODE_REUSE).new(current) +func _init(current :Variant) -> void: + _base = ResourceLoader.load("res://addons/gdUnit4/src/asserts/GdUnitAssertImpl.gd", "GDScript", + ResourceLoader.CACHE_MODE_REUSE).new(current) # save the actual assert instance on the current thread context GdUnitThreadManager.get_current_context().set_assert(self) - if not _base.__validate_value_type(current, TYPE_INT): + if not GdUnitAssertions.validate_value_type(current, TYPE_INT): report_error("GdUnitIntAssert inital error, unexpected type <%s>" % GdObjects.typeof_as_string(current)) -func _notification(event): +func _notification(event :int) -> void: if event == NOTIFICATION_PREDELETE: if _base != null: _base.notification(event) _base = null -func __current() -> Variant: - return _base.__current() +func current_value() -> Variant: + return _base.current_value() func report_success() -> GdUnitIntAssert: @@ -32,7 +33,7 @@ func report_error(error :String) -> GdUnitIntAssert: return self -func _failure_message() -> String: +func failure_message() -> String: return _base._current_error_message @@ -62,91 +63,91 @@ func is_not_equal(expected :int) -> GdUnitIntAssert: func is_less(expected :int) -> GdUnitIntAssert: - var current = __current() + var current :Variant = current_value() if current == null or current >= expected: return report_error(GdAssertMessages.error_is_value(Comparator.LESS_THAN, current, expected)) return report_success() func is_less_equal(expected :int) -> GdUnitIntAssert: - var current = __current() + var current :Variant = current_value() if current == null or current > expected: return report_error(GdAssertMessages.error_is_value(Comparator.LESS_EQUAL, current, expected)) return report_success() func is_greater(expected :int) -> GdUnitIntAssert: - var current = __current() + var current :Variant = current_value() if current == null or current <= expected: return report_error(GdAssertMessages.error_is_value(Comparator.GREATER_THAN, current, expected)) return report_success() func is_greater_equal(expected :int) -> GdUnitIntAssert: - var current = __current() + var current :Variant = current_value() if current == null or current < expected: return report_error(GdAssertMessages.error_is_value(Comparator.GREATER_EQUAL, current, expected)) return report_success() func is_even() -> GdUnitIntAssert: - var current = __current() + var current :Variant = current_value() if current == null or current % 2 != 0: return report_error(GdAssertMessages.error_is_even(current)) return report_success() func is_odd() -> GdUnitIntAssert: - var current = __current() + var current :Variant = current_value() if current == null or current % 2 == 0: return report_error(GdAssertMessages.error_is_odd(current)) return report_success() func is_negative() -> GdUnitIntAssert: - var current = __current() + var current :Variant = current_value() if current == null or current >= 0: return report_error(GdAssertMessages.error_is_negative(current)) return report_success() func is_not_negative() -> GdUnitIntAssert: - var current = __current() + var current :Variant = current_value() if current == null or current < 0: return report_error(GdAssertMessages.error_is_not_negative(current)) return report_success() func is_zero() -> GdUnitIntAssert: - var current = __current() + var current :Variant = current_value() if current != 0: return report_error(GdAssertMessages.error_is_zero(current)) return report_success() func is_not_zero() -> GdUnitIntAssert: - var current = __current() + var current :Variant= current_value() if current == 0: return report_error(GdAssertMessages.error_is_not_zero()) return report_success() func is_in(expected :Array) -> GdUnitIntAssert: - var current = __current() + var current :Variant = current_value() if not expected.has(current): return report_error(GdAssertMessages.error_is_in(current, expected)) return report_success() func is_not_in(expected :Array) -> GdUnitIntAssert: - var current = __current() + var current :Variant = current_value() if expected.has(current): return report_error(GdAssertMessages.error_is_not_in(current, expected)) return report_success() func is_between(from :int, to :int) -> GdUnitIntAssert: - var current = __current() + var current :Variant = current_value() if current == null or current < from or current > to: return report_error(GdAssertMessages.error_is_value(Comparator.BETWEEN_EQUAL, current, from, to)) return report_success() diff --git a/addons/gdUnit4/src/asserts/GdUnitObjectAssertImpl.gd b/addons/gdUnit4/src/asserts/GdUnitObjectAssertImpl.gd index 18c67bf7..b4090af6 100644 --- a/addons/gdUnit4/src/asserts/GdUnitObjectAssertImpl.gd +++ b/addons/gdUnit4/src/asserts/GdUnitObjectAssertImpl.gd @@ -3,27 +3,28 @@ extends GdUnitObjectAssert var _base :GdUnitAssert -func _init(current): - _base = ResourceLoader.load("res://addons/gdUnit4/src/asserts/GdUnitAssertImpl.gd", "GDScript", ResourceLoader.CACHE_MODE_REUSE).new(current) +func _init(current :Variant) -> void: + _base = ResourceLoader.load("res://addons/gdUnit4/src/asserts/GdUnitAssertImpl.gd", "GDScript", + ResourceLoader.CACHE_MODE_REUSE).new(current) # save the actual assert instance on the current thread context GdUnitThreadManager.get_current_context().set_assert(self) if (current != null - and (_base.__validate_value_type(current, TYPE_BOOL) - or _base.__validate_value_type(current, TYPE_INT) - or _base.__validate_value_type(current, TYPE_FLOAT) - or _base.__validate_value_type(current, TYPE_STRING))): + and (GdUnitAssertions.validate_value_type(current, TYPE_BOOL) + or GdUnitAssertions.validate_value_type(current, TYPE_INT) + or GdUnitAssertions.validate_value_type(current, TYPE_FLOAT) + or GdUnitAssertions.validate_value_type(current, TYPE_STRING))): report_error("GdUnitObjectAssert inital error, unexpected type <%s>" % GdObjects.typeof_as_string(current)) -func _notification(event): +func _notification(event :int) -> void: if event == NOTIFICATION_PREDELETE: if _base != null: _base.notification(event) _base = null -func __current() -> Variant: - return _base.__current() +func current_value() -> Variant: + return _base.current_value() func report_success() -> GdUnitObjectAssert: @@ -36,7 +37,7 @@ func report_error(error :String) -> GdUnitObjectAssert: return self -func _failure_message() -> String: +func failure_message() -> String: return _base._current_error_message @@ -45,12 +46,12 @@ func override_failure_message(message :String) -> GdUnitObjectAssert: return self -func is_equal(expected) -> GdUnitObjectAssert: +func is_equal(expected :Variant) -> GdUnitObjectAssert: _base.is_equal(expected) return self -func is_not_equal(expected) -> GdUnitObjectAssert: +func is_not_equal(expected :Variant) -> GdUnitObjectAssert: _base.is_not_equal(expected) return self @@ -66,8 +67,8 @@ func is_not_null() -> GdUnitObjectAssert: @warning_ignore("shadowed_global_identifier") -func is_same(expected) -> GdUnitObjectAssert: - var current :Variant = __current() +func is_same(expected :Variant) -> GdUnitObjectAssert: + var current :Variant = current_value() if not is_same(current, expected): report_error(GdAssertMessages.error_is_same(current, expected)) return self @@ -75,8 +76,8 @@ func is_same(expected) -> GdUnitObjectAssert: return self -func is_not_same(expected) -> GdUnitObjectAssert: - var current = __current() +func is_not_same(expected :Variant) -> GdUnitObjectAssert: + var current :Variant = current_value() if is_same(current, expected): report_error(GdAssertMessages.error_not_same(current, expected)) return self @@ -85,8 +86,8 @@ func is_not_same(expected) -> GdUnitObjectAssert: func is_instanceof(type :Object) -> GdUnitObjectAssert: - var current :Object = __current() - if not is_instance_of(current, type): + var current :Variant = current_value() + if current == null or not is_instance_of(current, type): var result_expected: = GdObjects.extract_class_name(type) var result_current: = GdObjects.extract_class_name(current) report_error(GdAssertMessages.error_is_instanceof(result_current, result_expected)) @@ -95,8 +96,8 @@ func is_instanceof(type :Object) -> GdUnitObjectAssert: return self -func is_not_instanceof(type) -> GdUnitObjectAssert: - var current :Variant = __current() +func is_not_instanceof(type :Variant) -> GdUnitObjectAssert: + var current :Variant = current_value() if is_instance_of(current, type): var result: = GdObjects.extract_class_name(type) if result.is_success(): diff --git a/addons/gdUnit4/src/asserts/GdUnitResultAssertImpl.gd b/addons/gdUnit4/src/asserts/GdUnitResultAssertImpl.gd index ccc92e56..e2460394 100644 --- a/addons/gdUnit4/src/asserts/GdUnitResultAssertImpl.gd +++ b/addons/gdUnit4/src/asserts/GdUnitResultAssertImpl.gd @@ -3,27 +3,28 @@ extends GdUnitResultAssert var _base :GdUnitAssert -func _init(current): - _base = ResourceLoader.load("res://addons/gdUnit4/src/asserts/GdUnitAssertImpl.gd", "GDScript", ResourceLoader.CACHE_MODE_REUSE).new(current) +func _init(current :Variant) -> void: + _base = ResourceLoader.load("res://addons/gdUnit4/src/asserts/GdUnitAssertImpl.gd", "GDScript", + ResourceLoader.CACHE_MODE_REUSE).new(current) # save the actual assert instance on the current thread context GdUnitThreadManager.get_current_context().set_assert(self) - if not __validate_value_type(current): + if not validate_value_type(current): report_error("GdUnitResultAssert inital error, unexpected type <%s>" % GdObjects.typeof_as_string(current)) -func _notification(event): +func _notification(event :int) -> void: if event == NOTIFICATION_PREDELETE: if _base != null: _base.notification(event) _base = null -func __validate_value_type(value) -> bool: +func validate_value_type(value :Variant) -> bool: return value == null or value is GdUnitResult -func __current() -> GdUnitResult: - return _base.__current() as GdUnitResult +func current_value() -> GdUnitResult: + return _base.current_value() as GdUnitResult func report_success() -> GdUnitResultAssert: @@ -36,7 +37,7 @@ func report_error(error :String) -> GdUnitResultAssert: return self -func _failure_message() -> String: +func failure_message() -> String: return _base._current_error_message @@ -49,13 +50,14 @@ func is_null() -> GdUnitResultAssert: _base.is_null() return self + func is_not_null() -> GdUnitResultAssert: _base.is_not_null() return self func is_empty() -> GdUnitResultAssert: - var result := __current() + var result := current_value() if result == null or not result.is_empty(): report_error(GdAssertMessages.error_result_is_empty(result)) else: @@ -64,7 +66,7 @@ func is_empty() -> GdUnitResultAssert: func is_success() -> GdUnitResultAssert: - var result := __current() + var result := current_value() if result == null or not result.is_success(): report_error(GdAssertMessages.error_result_is_success(result)) else: @@ -73,7 +75,7 @@ func is_success() -> GdUnitResultAssert: func is_warning() -> GdUnitResultAssert: - var result := __current() + var result := current_value() if result == null or not result.is_warn(): report_error(GdAssertMessages.error_result_is_warning(result)) else: @@ -82,7 +84,7 @@ func is_warning() -> GdUnitResultAssert: func is_error() -> GdUnitResultAssert: - var result := __current() + var result := current_value() if result == null or not result.is_error(): report_error(GdAssertMessages.error_result_is_error(result)) else: @@ -91,7 +93,7 @@ func is_error() -> GdUnitResultAssert: func contains_message(expected :String) -> GdUnitResultAssert: - var result := __current() + var result := current_value() if result == null: report_error(GdAssertMessages.error_result_has_message("", expected)) return self @@ -106,9 +108,9 @@ func contains_message(expected :String) -> GdUnitResultAssert: return self -func is_value(expected) -> GdUnitResultAssert: - var result := __current() - var value = null if result == null else result.value() +func is_value(expected :Variant) -> GdUnitResultAssert: + var result := current_value() + var value :Variant = null if result == null else result.value() if not GdObjects.equals(value, expected): report_error(GdAssertMessages.error_result_is_value(value, expected)) else: @@ -116,5 +118,5 @@ func is_value(expected) -> GdUnitResultAssert: return self -func is_equal(expected) -> GdUnitResultAssert: +func is_equal(expected :Variant) -> GdUnitResultAssert: return is_value(expected) diff --git a/addons/gdUnit4/src/asserts/GdUnitSignalAssertImpl.gd b/addons/gdUnit4/src/asserts/GdUnitSignalAssertImpl.gd index b82ebe46..9c0ea62e 100644 --- a/addons/gdUnit4/src/asserts/GdUnitSignalAssertImpl.gd +++ b/addons/gdUnit4/src/asserts/GdUnitSignalAssertImpl.gd @@ -11,12 +11,12 @@ var _timeout := DEFAULT_TIMEOUT var _interrupted := false -func _init(emitter :Object): +func _init(emitter :Object) -> void: # save the actual assert instance on the current thread context var context := GdUnitThreadManager.get_current_context() context.set_assert(self) _signal_collector = context.get_signal_collector() - _line_number = GdUnitAssert._get_line_number() + _line_number = GdUnitAssertions.get_line_number() _emitter = emitter GdAssertReports.reset_last_error_line_number() @@ -27,7 +27,7 @@ func report_success() -> GdUnitAssert: func report_warning(message :String) -> GdUnitAssert: - GdAssertReports.report_warning(message, GdUnitAssert._get_line_number()) + GdAssertReports.report_warning(message, GdUnitAssertions.get_line_number()) return self @@ -37,7 +37,7 @@ func report_error(error_message :String) -> GdUnitAssert: return self -func _failure_message() -> String: +func failure_message() -> String: return _current_error_message @@ -68,13 +68,13 @@ func is_signal_exists(signal_name :String) -> GdUnitSignalAssert: # Verifies that given signal is emitted until waiting time func is_emitted(name :String, args := []) -> GdUnitSignalAssert: - _line_number = GdUnitAssert._get_line_number() + _line_number = GdUnitAssertions.get_line_number() return await _wail_until_signal(name, args, false) # Verifies that given signal is NOT emitted until waiting time func is_not_emitted(name :String, args := []) -> GdUnitSignalAssert: - _line_number = GdUnitAssert._get_line_number() + _line_number = GdUnitAssertions.get_line_number() return await _wail_until_signal(name, args, true) @@ -87,24 +87,24 @@ func _wail_until_signal(signal_name :String, expected_args :Array, expect_not_em report_error("Can't wait for non-existion signal '%s' checked object '%s'." % [signal_name,_emitter.get_class()]) return self _signal_collector.register_emitter(_emitter) - var time_scale = Engine.get_time_scale() + var time_scale := Engine.get_time_scale() var timer := Timer.new() Engine.get_main_loop().root.add_child(timer) timer.add_to_group("GdUnitTimers") timer.set_one_shot(true) - timer.timeout.connect(func on_timeout(): _interrupted = true) + timer.timeout.connect(func on_timeout() -> void: _interrupted = true) timer.start((_timeout/1000.0)*time_scale) - var is_signal_emitted = false + var is_signal_emitted := false while not _interrupted and not is_signal_emitted: await Engine.get_main_loop().process_frame if is_instance_valid(_emitter): is_signal_emitted = _signal_collector.match(_emitter, signal_name, expected_args) if is_signal_emitted and expect_not_emitted: report_error(GdAssertMessages.error_signal_emitted(signal_name, expected_args, LocalTime.elapsed(int(_timeout-timer.time_left*1000)))) - + if _interrupted and not expect_not_emitted: report_error(GdAssertMessages.error_wait_signal(signal_name, expected_args, LocalTime.elapsed(_timeout))) timer.free() if is_instance_valid(_emitter): - _signal_collector.reset_received_signals(_emitter) + _signal_collector.reset_received_signals(_emitter, signal_name, expected_args) return self diff --git a/addons/gdUnit4/src/asserts/GdUnitStringAssertImpl.gd b/addons/gdUnit4/src/asserts/GdUnitStringAssertImpl.gd index 154b9e5c..6168aaa3 100644 --- a/addons/gdUnit4/src/asserts/GdUnitStringAssertImpl.gd +++ b/addons/gdUnit4/src/asserts/GdUnitStringAssertImpl.gd @@ -3,30 +3,28 @@ extends GdUnitStringAssert var _base :GdUnitAssert -func _init(current): - _base = ResourceLoader.load("res://addons/gdUnit4/src/asserts/GdUnitAssertImpl.gd", "GDScript", ResourceLoader.CACHE_MODE_REUSE).new(current) +func _init(current :Variant) -> void: + _base = ResourceLoader.load("res://addons/gdUnit4/src/asserts/GdUnitAssertImpl.gd", "GDScript", + ResourceLoader.CACHE_MODE_REUSE).new(current) # save the actual assert instance on the current thread context GdUnitThreadManager.get_current_context().set_assert(self) - if not _base.__validate_value_type(current, TYPE_STRING): + if current != null and typeof(current) != TYPE_STRING and typeof(current) != TYPE_STRING_NAME: report_error("GdUnitStringAssert inital error, unexpected type <%s>" % GdObjects.typeof_as_string(current)) -func _notification(event): +func _notification(event :int) -> void: if event == NOTIFICATION_PREDELETE: if _base != null: _base.notification(event) _base = null -func _failure_message() -> String: +func failure_message() -> String: return _base._current_error_message -func __current(): - var current = _base.__current() - if current == null: - return null - return current as String +func current_value() -> Variant: + return _base.current_value() func report_success() -> GdUnitStringAssert: @@ -54,103 +52,104 @@ func is_not_null() -> GdUnitStringAssert: return self -func is_equal(expected) -> GdUnitStringAssert: - var current = __current() +func is_equal(expected :Variant) -> GdUnitStringAssert: + var current :Variant = current_value() if current == null: return report_error(GdAssertMessages.error_equal(current, expected)) if not GdObjects.equals(current, expected): var diffs := GdDiffTool.string_diff(current, expected) - var formatted_current := GdAssertMessages._colored_array_div(diffs[1]) + var formatted_current := GdAssertMessages.colored_array_div(diffs[1]) return report_error(GdAssertMessages.error_equal(formatted_current, expected)) return report_success() -func is_equal_ignoring_case(expected) -> GdUnitStringAssert: - var current = __current() +func is_equal_ignoring_case(expected :Variant) -> GdUnitStringAssert: + var current :Variant = current_value() if current == null: return report_error(GdAssertMessages.error_equal_ignoring_case(current, expected)) - if not GdObjects.equals(current, expected, true): + if not GdObjects.equals(str(current), expected, true): var diffs := GdDiffTool.string_diff(current, expected) - var formatted_current := GdAssertMessages._colored_array_div(diffs[1]) + var formatted_current := GdAssertMessages.colored_array_div(diffs[1]) return report_error(GdAssertMessages.error_equal_ignoring_case(formatted_current, expected)) return report_success() -func is_not_equal(expected) -> GdUnitStringAssert: - var current = __current() +func is_not_equal(expected :Variant) -> GdUnitStringAssert: + var current :Variant = current_value() if GdObjects.equals(current, expected): return report_error(GdAssertMessages.error_not_equal(current, expected)) return report_success() -func is_not_equal_ignoring_case(expected) -> GdUnitStringAssert: - var current = __current() +func is_not_equal_ignoring_case(expected :Variant) -> GdUnitStringAssert: + var current :Variant = current_value() if GdObjects.equals(current, expected, true): return report_error(GdAssertMessages.error_not_equal(current, expected)) return report_success() func is_empty() -> GdUnitStringAssert: - var current = __current() + var current :Variant = current_value() if current == null or not current.is_empty(): return report_error(GdAssertMessages.error_is_empty(current)) return report_success() func is_not_empty() -> GdUnitStringAssert: - var current = __current() + var current :Variant = current_value() if current == null or current.is_empty(): return report_error(GdAssertMessages.error_is_not_empty()) return report_success() func contains(expected :String) -> GdUnitStringAssert: - var current = __current() + var current :Variant = current_value() if current == null or current.find(expected) == -1: return report_error(GdAssertMessages.error_contains(current, expected)) return report_success() func not_contains(expected :String) -> GdUnitStringAssert: - var current = __current() + var current :Variant = current_value() if current != null and current.find(expected) != -1: return report_error(GdAssertMessages.error_not_contains(current, expected)) return report_success() func contains_ignoring_case(expected :String) -> GdUnitStringAssert: - var current = __current() + var current :Variant = current_value() if current == null or current.findn(expected) == -1: return report_error(GdAssertMessages.error_contains_ignoring_case(current, expected)) return report_success() func not_contains_ignoring_case(expected :String) -> GdUnitStringAssert: - var current = __current() + var current :Variant = current_value() if current != null and current.findn(expected) != -1: return report_error(GdAssertMessages.error_not_contains_ignoring_case(current, expected)) return report_success() func starts_with(expected :String) -> GdUnitStringAssert: - var current = __current() + var current :Variant = current_value() if current == null or current.find(expected) != 0: return report_error(GdAssertMessages.error_starts_with(current, expected)) return report_success() func ends_with(expected :String) -> GdUnitStringAssert: - var current = __current() + var current :Variant = current_value() if current == null: return report_error(GdAssertMessages.error_ends_with(current, expected)) - var find = current.length() - expected.length() + var find :int = current.length() - expected.length() if current.rfind(expected) != find: return report_error(GdAssertMessages.error_ends_with(current, expected)) return report_success() -func has_length(expected :int, comparator :int = Comparator.EQUAL) -> GdUnitStringAssert: - var current = __current() +# gdlint:disable=max-returns +func has_length(expected :int, comparator := Comparator.EQUAL) -> GdUnitStringAssert: + var current :Variant = current_value() if current == null: return report_error(GdAssertMessages.error_has_length(current, expected, comparator)) match comparator: diff --git a/addons/gdUnit4/src/asserts/GdUnitVectorAssertImpl.gd b/addons/gdUnit4/src/asserts/GdUnitVectorAssertImpl.gd index 43a63f1d..048cc3f5 100644 --- a/addons/gdUnit4/src/asserts/GdUnitVectorAssertImpl.gd +++ b/addons/gdUnit4/src/asserts/GdUnitVectorAssertImpl.gd @@ -4,8 +4,9 @@ var _base: GdUnitAssert var _current_type :int -func _init(current :Variant): - _base = ResourceLoader.load("res://addons/gdUnit4/src/asserts/GdUnitAssertImpl.gd", "GDScript", ResourceLoader.CACHE_MODE_REUSE).new(current) +func _init(current :Variant) -> void: + _base = ResourceLoader.load("res://addons/gdUnit4/src/asserts/GdUnitAssertImpl.gd", "GDScript", + ResourceLoader.CACHE_MODE_REUSE).new(current) # save the actual assert instance on the current thread context GdUnitThreadManager.get_current_context().set_assert(self) if not _validate_value_type(current): @@ -13,22 +14,25 @@ func _init(current :Variant): _current_type = typeof(current) -func _notification(event): +func _notification(event :int) -> void: if event == NOTIFICATION_PREDELETE: if _base != null: _base.notification(event) _base = null -func _validate_value_type(value) -> bool: - return (value == null +func _validate_value_type(value :Variant) -> bool: + return ( + value == null or typeof(value) in [ TYPE_VECTOR2, TYPE_VECTOR2I, TYPE_VECTOR3, TYPE_VECTOR3I, TYPE_VECTOR4, - TYPE_VECTOR4I]) + TYPE_VECTOR4I + ] + ) func _validate_is_vector_type(value :Variant) -> bool: @@ -39,8 +43,8 @@ func _validate_is_vector_type(value :Variant) -> bool: return false -func __current() -> Variant: - return _base.__current() +func current_value() -> Variant: + return _base.current_value() func report_success() -> GdUnitVectorAssert: @@ -53,7 +57,7 @@ func report_error(error :String) -> GdUnitVectorAssert: return self -func _failure_message() -> String: +func failure_message() -> String: return _base._current_error_message @@ -90,15 +94,15 @@ func is_not_equal(expected :Variant) -> GdUnitVectorAssert: func is_equal_approx(expected :Variant, approx :Variant) -> GdUnitVectorAssert: if not _validate_is_vector_type(expected) or not _validate_is_vector_type(approx): return self - var current = __current() - var from = expected - approx - var to = expected + approx + var current :Variant = current_value() + var from :Variant = expected - approx + var to :Variant = expected + approx if current == null or (not _is_equal_approx(current, from, to)): return report_error(GdAssertMessages.error_is_value(Comparator.BETWEEN_EQUAL, current, from, to)) return report_success() -func _is_equal_approx(current, from, to) -> bool: +func _is_equal_approx(current :Variant, from :Variant, to :Variant) -> bool: match typeof(current): TYPE_VECTOR2, TYPE_VECTOR2I: return ((current.x >= from.x and current.y >= from.y) @@ -117,7 +121,7 @@ func _is_equal_approx(current, from, to) -> bool: func is_less(expected :Variant) -> GdUnitVectorAssert: if not _validate_is_vector_type(expected): return self - var current = __current() + var current :Variant = current_value() if current == null or current >= expected: return report_error(GdAssertMessages.error_is_value(Comparator.LESS_THAN, current, expected)) return report_success() @@ -126,7 +130,7 @@ func is_less(expected :Variant) -> GdUnitVectorAssert: func is_less_equal(expected :Variant) -> GdUnitVectorAssert: if not _validate_is_vector_type(expected): return self - var current = __current() + var current :Variant = current_value() if current == null or current > expected: return report_error(GdAssertMessages.error_is_value(Comparator.LESS_EQUAL, current, expected)) return report_success() @@ -135,7 +139,7 @@ func is_less_equal(expected :Variant) -> GdUnitVectorAssert: func is_greater(expected :Variant) -> GdUnitVectorAssert: if not _validate_is_vector_type(expected): return self - var current = __current() + var current :Variant = current_value() if current == null or current <= expected: return report_error(GdAssertMessages.error_is_value(Comparator.GREATER_THAN, current, expected)) return report_success() @@ -144,7 +148,7 @@ func is_greater(expected :Variant) -> GdUnitVectorAssert: func is_greater_equal(expected :Variant) -> GdUnitVectorAssert: if not _validate_is_vector_type(expected): return self - var current = __current() + var current :Variant = current_value() if current == null or current < expected: return report_error(GdAssertMessages.error_is_value(Comparator.GREATER_EQUAL, current, expected)) return report_success() @@ -153,7 +157,7 @@ func is_greater_equal(expected :Variant) -> GdUnitVectorAssert: func is_between(from :Variant, to :Variant) -> GdUnitVectorAssert: if not _validate_is_vector_type(from) or not _validate_is_vector_type(to): return self - var current = __current() + var current :Variant = current_value() if current == null or not (current >= from and current <= to): return report_error(GdAssertMessages.error_is_value(Comparator.BETWEEN_EQUAL, current, from, to)) return report_success() @@ -162,7 +166,7 @@ func is_between(from :Variant, to :Variant) -> GdUnitVectorAssert: func is_not_between(from :Variant, to :Variant) -> GdUnitVectorAssert: if not _validate_is_vector_type(from) or not _validate_is_vector_type(to): return self - var current = __current() + var current :Variant = current_value() if (current != null and current >= from and current <= to): return report_error(GdAssertMessages.error_is_value(Comparator.NOT_BETWEEN_EQUAL, current, from, to)) return report_success() diff --git a/addons/gdUnit4/src/asserts/ValueProvider.gd b/addons/gdUnit4/src/asserts/ValueProvider.gd index 5150f4c6..a94aa91d 100644 --- a/addons/gdUnit4/src/asserts/ValueProvider.gd +++ b/addons/gdUnit4/src/asserts/ValueProvider.gd @@ -2,5 +2,5 @@ class_name ValueProvider extends RefCounted -func get_value(): - pass +func get_value() -> Variant: + return null diff --git a/addons/gdUnit4/src/cmd/CmdArgumentParser.gd b/addons/gdUnit4/src/cmd/CmdArgumentParser.gd index 74adebb1..1abe67ff 100644 --- a/addons/gdUnit4/src/cmd/CmdArgumentParser.gd +++ b/addons/gdUnit4/src/cmd/CmdArgumentParser.gd @@ -6,28 +6,28 @@ var _tool_name :String var _parsed_commands :Dictionary = Dictionary() -func _init(p_options :CmdOptions, p_tool_name :String): +func _init(p_options :CmdOptions, p_tool_name :String) -> void: _options = p_options _tool_name = p_tool_name func parse(args :Array, ignore_unknown_cmd := false) -> GdUnitResult: _parsed_commands.clear() - + # parse until first program argument while not args.is_empty(): var arg :String = args.pop_front() if arg.find(_tool_name) != -1: break - + if args.is_empty(): return GdUnitResult.empty() - + # now parse all arguments while not args.is_empty(): var cmd :String = args.pop_front() var option := _options.get_option(cmd) - + if option: if _parse_cmd_arguments(option, args) == -1: return GdUnitResult.error("The '%s' command requires an argument!" % option.short_command()) @@ -43,7 +43,7 @@ func options() -> CmdOptions: func _parse_cmd_arguments(option :CmdOption, args :Array) -> int: var command_name := option.short_command() var command :CmdCommand = _parsed_commands.get(command_name, CmdCommand.new(command_name)) - + if option.has_argument(): if not option.is_argument_optional() and args.is_empty(): return -1 diff --git a/addons/gdUnit4/src/cmd/CmdCommand.gd b/addons/gdUnit4/src/cmd/CmdCommand.gd index c9c34143..58f09155 100644 --- a/addons/gdUnit4/src/cmd/CmdCommand.gd +++ b/addons/gdUnit4/src/cmd/CmdCommand.gd @@ -5,7 +5,7 @@ var _name: String var _arguments: PackedStringArray -func _init(p_name: String, p_arguments: = []): +func _init(p_name :String, p_arguments := []) -> void: _name = p_name _arguments = PackedStringArray(p_arguments) @@ -18,9 +18,9 @@ func arguments() -> PackedStringArray: return _arguments -func add_argument(arg: String) -> void: +func add_argument(arg :String) -> void: _arguments.append(arg) -func _to_string(): +func _to_string() -> String: return "%s:%s" % [_name, ", ".join(_arguments)] diff --git a/addons/gdUnit4/src/cmd/CmdCommandHandler.gd b/addons/gdUnit4/src/cmd/CmdCommandHandler.gd index e32557ec..5b3b1cdc 100644 --- a/addons/gdUnit4/src/cmd/CmdCommandHandler.gd +++ b/addons/gdUnit4/src/cmd/CmdCommandHandler.gd @@ -3,18 +3,18 @@ extends RefCounted const CB_SINGLE_ARG = 0 const CB_MULTI_ARGS = 1 +const NO_CB := Callable() var _cmd_options :CmdOptions # holds the command callbacks by key::String and value: [, ]:Array +# Dictionary[String, Array[Callback] var _command_cbs :Dictionary -const NO_CB := Callable() - # we only able to check cb function name since Godot 3.3.x var _enhanced_fr_test := false -func _init(cmd_options: CmdOptions): +func _init(cmd_options: CmdOptions) -> void: _cmd_options = cmd_options var major: int = Engine.get_version_info()["major"] var minor: int = Engine.get_version_info()["minor"] @@ -49,10 +49,13 @@ func register_cbv(cmd_name: String, cb: Callable) -> CmdCommandHandler: func _validate() -> GdUnitResult: var errors: = PackedStringArray() + # Dictionary[StringName, String] var registered_cbs: = Dictionary() - - for cmd_name in _command_cbs.keys(): - var cb: Callable = _command_cbs[cmd_name][CB_SINGLE_ARG] if _command_cbs[cmd_name][CB_SINGLE_ARG] else _command_cbs[cmd_name][CB_MULTI_ARGS] + + for cmd_name in _command_cbs.keys() as Array[String]: + var cb: Callable = (_command_cbs[cmd_name][CB_SINGLE_ARG] + if _command_cbs[cmd_name][CB_SINGLE_ARG] + else _command_cbs[cmd_name][CB_MULTI_ARGS]) if cb != NO_CB and not cb.is_valid(): errors.append("Invalid function reference for command '%s', Check the function reference!" % cmd_name) if _cmd_options.get_option(cmd_name) == null: @@ -61,32 +64,41 @@ func _validate() -> GdUnitResult: if _enhanced_fr_test and cb != NO_CB: var cb_method: = cb.get_method() if registered_cbs.has(cb_method): - var already_registered_cmd = registered_cbs[cb_method] + var already_registered_cmd :String = registered_cbs[cb_method] errors.append("The function reference '%s' already registerd for command '%s'!" % [cb_method, already_registered_cmd]) else: registered_cbs[cb_method] = cmd_name if errors.is_empty(): return GdUnitResult.success(true) - else: - return GdUnitResult.error("\n".join(errors)) + return GdUnitResult.error("\n".join(errors)) -func execute(commands :Array) -> GdUnitResult: +func execute(commands :Array[CmdCommand]) -> GdUnitResult: var result := _validate() if result.is_error(): return result - for index in commands.size(): - var cmd :CmdCommand = commands[index] - assert(cmd is CmdCommand) #,"commands contains invalid command object '%s'" % cmd) + for cmd in commands: var cmd_name := cmd.name() if _command_cbs.has(cmd_name): var cb_s :Callable = _command_cbs.get(cmd_name)[CB_SINGLE_ARG] - var cb_m :Callable = _command_cbs.get(cmd_name)[CB_MULTI_ARGS] - if cmd.arguments().is_empty(): + var arguments := cmd.arguments() + var cmd_option := _cmd_options.get_option(cmd_name) + if cb_s and arguments.size() == 0: cb_s.call() - else: - if cmd.arguments().size() == 1: - cb_s.call(cmd.arguments()[CB_SINGLE_ARG]) + elif cb_s: + if cmd_option.type() == TYPE_BOOL: + cb_s.call(true if arguments[0] == "true" else false) else: - cb_m.callv(cmd.arguments()) + cb_s.call(arguments[0]) + else: + var cb_m :Callable = _command_cbs.get(cmd_name)[CB_MULTI_ARGS] + # we need to find the method and determin the arguments to call the right function + for m in cb_m.get_object().get_method_list(): + if m["name"] == cb_m.get_method(): + if m["args"].size() > 1: + cb_m.callv(arguments) + break + else: + cb_m.call(arguments) + break return GdUnitResult.success(true) diff --git a/addons/gdUnit4/src/cmd/CmdConsole.gd b/addons/gdUnit4/src/cmd/CmdConsole.gd index 5a64b154..62a2949d 100644 --- a/addons/gdUnit4/src/cmd/CmdConsole.gd +++ b/addons/gdUnit4/src/cmd/CmdConsole.gd @@ -3,30 +3,29 @@ class_name CmdConsole extends RefCounted -const BOLD = 0x1 -const ITALIC = 0x2 -const UNDERLINE = 0x4 - -const __CSI_BOLD = "" -const __CSI_ITALIC = "" -const __CSI_UNDERLINE = "" - enum { COLOR_TABLE, COLOR_RGB } +const BOLD = 0x1 +const ITALIC = 0x2 +const UNDERLINE = 0x4 + +const CSI_BOLD = "" +const CSI_ITALIC = "" +const CSI_UNDERLINE = "" + # Control Sequence Introducer -#var csi := PackedByteArray([0x1b]).get_string_from_ascii() var _debug_show_color_codes := false -var _color_mode = COLOR_TABLE +var _color_mode := COLOR_TABLE func color(p_color :Color) -> CmdConsole: # using color table 16 - 231 a 6 x 6 x 6 RGB color cube (16 + R * 36 + G * 6 + B) if _color_mode == COLOR_TABLE: @warning_ignore("integer_division") - var c2 = 16 + (int(p_color.r8/42) * 36) + (int(p_color.g8/42) * 6) + int(p_color.b8/42) + var c2 := 16 + (int(p_color.r8/42) * 36) + (int(p_color.g8/42) * 6) + int(p_color.b8/42) if _debug_show_color_codes: printraw("%6d" % [c2]) printraw("[38;5;%dm" % c2 ) @@ -35,6 +34,16 @@ func color(p_color :Color) -> CmdConsole: return self +func save_cursor() -> CmdConsole: + printraw("") + return self + + +func restore_cursor() -> CmdConsole: + printraw("") + return self + + func end_color() -> CmdConsole: printraw("") return self @@ -45,12 +54,12 @@ func row_pos(row :int) -> CmdConsole: return self -func scrollArea(from :int, to :int ) -> CmdConsole: +func scroll_area(from :int, to :int) -> CmdConsole: printraw("[%d;%dr" % [from ,to]) return self -func progressBar(p_progress :int, p_color :Color = Color.POWDER_BLUE) -> CmdConsole: +func progress_bar(p_progress :int, p_color :Color = Color.POWDER_BLUE) -> CmdConsole: if p_progress < 0: p_progress = 0 if p_progress > 100: @@ -77,19 +86,19 @@ func reset() -> CmdConsole: func bold(enable :bool) -> CmdConsole: if enable: - printraw(__CSI_BOLD) + printraw(CSI_BOLD) return self func italic(enable :bool) -> CmdConsole: if enable: - printraw(__CSI_ITALIC) + printraw(CSI_ITALIC) return self func underline(enable :bool) -> CmdConsole: if enable: - printraw(__CSI_UNDERLINE) + printraw(CSI_UNDERLINE) return self @@ -114,7 +123,7 @@ func print_color(p_message :String, p_color :Color, p_flags := 0) -> CmdConsole: .end_color() -func print_color_table(): +func print_color_table() -> void: prints_color("Color Table 6x6x6", Color.ANTIQUE_WHITE) _debug_show_color_codes = true for green in range(0, 6): @@ -123,7 +132,7 @@ func print_color_table(): print_color("โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆ ", Color8(red*42, green*42, blue*42)) new_line() new_line() - + prints_color("Color Table RGB", Color.ANTIQUE_WHITE) _color_mode = COLOR_RGB for green in range(0, 6): diff --git a/addons/gdUnit4/src/cmd/CmdOption.gd b/addons/gdUnit4/src/cmd/CmdOption.gd index a562a037..a4982de2 100644 --- a/addons/gdUnit4/src/cmd/CmdOption.gd +++ b/addons/gdUnit4/src/cmd/CmdOption.gd @@ -15,7 +15,7 @@ var _arg_optional :bool = false # description: a full description of the command # type: the argument type # arg_optional: defines of the argument optional -func _init(p_commands :String, p_help :String, p_description :String, p_type :int = TYPE_NIL, p_arg_optional :bool = false): +func _init(p_commands :String, p_help :String, p_description :String, p_type :int = TYPE_NIL, p_arg_optional :bool = false) -> void: _commands = p_commands.replace(" ", "").replace("\t", "").split(",") _help = p_help _description = p_description @@ -57,5 +57,5 @@ func describe() -> String: return " %-32s %s \n %-32s %s\n" % [commands(), description(), "", help()] -func _to_string(): +func _to_string() -> String: return describe() diff --git a/addons/gdUnit4/src/cmd/CmdOptions.gd b/addons/gdUnit4/src/cmd/CmdOptions.gd index ddebe309..c6105298 100644 --- a/addons/gdUnit4/src/cmd/CmdOptions.gd +++ b/addons/gdUnit4/src/cmd/CmdOptions.gd @@ -2,25 +2,25 @@ class_name CmdOptions extends RefCounted -var _default_options :Array -var _advanced_options :Array +var _default_options :Array[CmdOption] +var _advanced_options :Array[CmdOption] -func _init(p_options :Array = Array(), p_advanced_options :Array = Array()): +func _init(p_options :Array[CmdOption] = [], p_advanced_options :Array[CmdOption] = []) -> void: # default help options - _default_options = p_options + _default_options = p_options _advanced_options = p_advanced_options -func default_options() -> Array: +func default_options() -> Array[CmdOption]: return _default_options -func advanced_options() -> Array: +func advanced_options() -> Array[CmdOption]: return _advanced_options -func options() -> Array: +func options() -> Array[CmdOption]: return default_options() + advanced_options() diff --git a/addons/gdUnit4/src/core/GdArrayTools.gd b/addons/gdUnit4/src/core/GdArrayTools.gd index 39cea360..3e3d3a9f 100644 --- a/addons/gdUnit4/src/core/GdArrayTools.gd +++ b/addons/gdUnit4/src/core/GdArrayTools.gd @@ -18,7 +18,7 @@ const ARRAY_TYPES := [ ] -static func is_array_type(value) -> bool: +static func is_array_type(value :Variant) -> bool: return is_type_array(typeof(value)) @@ -28,10 +28,10 @@ static func is_type_array(type :int) -> bool: ## Filters an array by given value[br] ## If the given value not an array it returns null, will remove all occurence of given value. -static func filter_value(array, value :Variant) -> Variant: +static func filter_value(array :Variant, value :Variant) -> Variant: if not is_array_type(array): return null - var filtered_array = array.duplicate() + var filtered_array :Variant = array.duplicate() var index :int = filtered_array.find(value) while index != -1: filtered_array.remove_at(index) @@ -40,8 +40,8 @@ static func filter_value(array, value :Variant) -> Variant: ## Erases a value from given array by using equals(l,r) to find the element to erase -static func erase_value(array :Array, value) -> void: - for element in array: +static func erase_value(array :Array, value :Variant) -> void: + for element :Variant in array: if GdObjects.equals(element, value): array.erase(element) @@ -53,7 +53,7 @@ static func scan_typed(array :Array) -> int: if array.is_empty(): return TYPE_NIL var actual_type := GdObjects.TYPE_VARIANT - for value in array: + for value :Variant in array: var current_type := typeof(value) if not actual_type in [GdObjects.TYPE_VARIANT, current_type]: return GdObjects.TYPE_VARIANT @@ -75,7 +75,7 @@ static func scan_typed(array :Array) -> int: static func as_string(elements :Variant, encode_value := true) -> String: if not is_array_type(elements): return "ERROR: Not an Array Type!" - var delemiter = ", " + var delemiter := ", " if elements == null: return "" if elements.is_empty(): @@ -83,9 +83,9 @@ static func as_string(elements :Variant, encode_value := true) -> String: var prefix := _typeof_as_string(elements) if encode_value else "" var formatted := "" var index := 0 - for element in elements: + for element :Variant in elements: if max_elements != -1 and index > max_elements: - return prefix + "[" + formatted + delemiter + "...]" + return prefix + "[" + formatted + delemiter + "...]" if formatted.length() > 0 : formatted += delemiter formatted += GdDefaultValueDecoder.decode(element) if encode_value else str(element) diff --git a/addons/gdUnit4/src/core/GdDiffTool.gd b/addons/gdUnit4/src/core/GdDiffTool.gd index a3d6737b..a918a990 100644 --- a/addons/gdUnit4/src/core/GdDiffTool.gd +++ b/addons/gdUnit4/src/core/GdDiffTool.gd @@ -7,10 +7,10 @@ const DIV_ADD :int = 214 const DIV_SUB :int = 215 -static func _diff(lb: PackedByteArray, rb: PackedByteArray, lookup: Array, ldiff: Array, rdiff: Array): - var loffset = lb.size() - var roffset = rb.size() - +static func _diff(lb: PackedByteArray, rb: PackedByteArray, lookup: Array, ldiff: Array, rdiff: Array) -> void: + var loffset := lb.size() + var roffset := rb.size() + while true: #if last character of X and Y matches if loffset > 0 && roffset > 0 && lb[loffset - 1] == rb[roffset - 1]: @@ -43,7 +43,7 @@ static func _createLookUp(lb: PackedByteArray, rb: PackedByteArray) -> Array: var lookup := Array() lookup.resize(lb.size() + 1) for i in lookup.size(): - var x = [] + var x := [] x.resize(rb.size() + 1) lookup[i] = x return lookup @@ -55,9 +55,9 @@ static func _buildLookup(lb: PackedByteArray, rb: PackedByteArray) -> Array: for i in lookup.size(): lookup[i][0] = 0 # first row of the lookup table will be all 0 - for j in lookup[0].size(): + for j :int in lookup[0].size(): lookup[0][j] = 0 - + # fill the lookup table in bottom-up manner for i in range(1, lookup.size()): for j in range(1, lookup[0].size()): @@ -70,9 +70,9 @@ static func _buildLookup(lb: PackedByteArray, rb: PackedByteArray) -> Array: return lookup -static func string_diff(left, right) -> Array[PackedByteArray]: - var lb := PackedByteArray() if left == null else str(left).to_ascii_buffer() - var rb := PackedByteArray() if right == null else str(right).to_ascii_buffer() +static func string_diff(left :Variant, right :Variant) -> Array[PackedByteArray]: + var lb := PackedByteArray() if left == null else str(left).to_utf8_buffer() + var rb := PackedByteArray() if right == null else str(right).to_utf8_buffer() var ldiff := Array() var rdiff := Array() var lookup := _buildLookup(lb, rb); @@ -92,16 +92,16 @@ static func longestCommonSubsequence(text1 :String, text2 :String) -> PackedStri for n in text2WordCount+1: ar.append(0) solutionMatrix.append(ar) - + for i in range(text1WordCount-1, 0, -1): for j in range(text2WordCount-1, 0, -1): if text1Words[i] == text2Words[j]: solutionMatrix[i][j] = solutionMatrix[i + 1][j + 1] + 1; else: solutionMatrix[i][j] = max(solutionMatrix[i + 1][j], solutionMatrix[i][j + 1]); - - var i = 0 - var j = 0 + + var i := 0 + var j := 0 var lcsResultList := PackedStringArray(); while (i < text1WordCount && j < text2WordCount): if text1Words[i] == text2Words[j]: @@ -116,16 +116,16 @@ static func longestCommonSubsequence(text1 :String, text2 :String) -> PackedStri static func markTextDifferences(text1 :String, text2 :String, lcsList :PackedStringArray, insertColor :Color, deleteColor:Color) -> String: - var stringBuffer = "" + var stringBuffer := "" if text1 == null and lcsList == null: return stringBuffer - + var text1Words := text1.split(" ") var text2Words := text2.split(" ") - var i = 0 - var j = 0 - var word1LastIndex = 0 - var word2LastIndex = 0 + var i := 0 + var j := 0 + var word1LastIndex := 0 + var word2LastIndex := 0 for k in lcsList.size(): while i < text1Words.size() and j < text2Words.size(): if text1Words[i] == lcsList[k] and text2Words[j] == lcsList[k]: @@ -134,7 +134,7 @@ static func markTextDifferences(text1 :String, text2 :String, lcsList :PackedStr word2LastIndex = j + 1 i = text1Words.size() j = text2Words.size() - + else: if text1Words[i] != lcsList[k]: while i < text1Words.size() and text1Words[i] != lcsList[k]: stringBuffer += "" + text1Words[i] + " " @@ -145,7 +145,7 @@ static func markTextDifferences(text1 :String, text2 :String, lcsList :PackedStr j += 1 i = word1LastIndex j = word2LastIndex - + while word1LastIndex < text1Words.size(): stringBuffer += "" + text1Words[word1LastIndex] + " " word1LastIndex += 1 diff --git a/addons/gdUnit4/src/core/GdFunctionDoubler.gd b/addons/gdUnit4/src/core/GdFunctionDoubler.gd index d3df8a9b..ade86539 100644 --- a/addons/gdUnit4/src/core/GdFunctionDoubler.gd +++ b/addons/gdUnit4/src/core/GdFunctionDoubler.gd @@ -39,7 +39,11 @@ const DEFAULT_TYPED_RETURN_VALUES := { TYPE_PACKED_STRING_ARRAY: "PackedStringArray()", TYPE_PACKED_VECTOR2_ARRAY: "PackedVector2Array()", TYPE_PACKED_VECTOR3_ARRAY: "PackedVector3Array()", + # since Godot 4.3.beta1 TYPE_PACKED_VECTOR4_ARRAY = 38 + GdObjects.TYPE_PACKED_VECTOR4_ARRAY: "PackedVector4Array()", TYPE_PACKED_COLOR_ARRAY: "PackedColorArray()", + GdObjects.TYPE_VARIANT: "null", + GdObjects.TYPE_ENUM: "0" } # @GlobalScript enums @@ -67,26 +71,45 @@ const DEFAULT_ENUM_RETURN_VALUES = { var _push_errors :String + +# Determine the enum default by reflection +static func get_enum_default(value :String) -> Variant: + var script := GDScript.new() + script.source_code = """ + extends Resource + + static func get_enum_default() -> Variant: + return %s.values()[0] + + """.dedent() % value + script.reload() + return script.new().call("get_enum_default") + + static func default_return_value(func_descriptor :GdFunctionDescriptor) -> String: var return_type :Variant = func_descriptor.return_type() if return_type == GdObjects.TYPE_ENUM: - var enum_path := func_descriptor._return_class.split(".") - if enum_path.size() == 2: + var enum_class := func_descriptor._return_class + var enum_path := enum_class.split(".") + if enum_path.size() >= 2: var keys := ClassDB.class_get_enum_constants(enum_path[0], enum_path[1]) - return "%s.%s" % [enum_path[0], keys[0]] + if not keys.is_empty(): + return "%s.%s" % [enum_path[0], keys[0]] + var enum_value :Variant = get_enum_default(enum_class) + if enum_value != null: + return str(enum_value) # we need fallback for @GlobalScript enums, return DEFAULT_ENUM_RETURN_VALUES.get(func_descriptor._return_class, "0") - return DEFAULT_TYPED_RETURN_VALUES.get(return_type, "null") + return DEFAULT_TYPED_RETURN_VALUES.get(return_type, "invalid") -func _init(push_errors :bool = false): +func _init(push_errors :bool = false) -> void: _push_errors = "true" if push_errors else "false" - if DEFAULT_TYPED_RETURN_VALUES.size() != TYPE_MAX: - push_error("missing default definitions! Expexting %d bud is %d" % [DEFAULT_TYPED_RETURN_VALUES.size(), TYPE_MAX]) - for type_key in range(0, DEFAULT_TYPED_RETURN_VALUES.size()): - if not DEFAULT_TYPED_RETURN_VALUES.has(type_key): - prints("missing default definition for type", type_key) - assert(DEFAULT_TYPED_RETURN_VALUES.has(type_key), "Missing Type default definition!") + for type_key in TYPE_MAX: + if not DEFAULT_TYPED_RETURN_VALUES.has(type_key): + push_error("missing default definitions! Expexting %d bud is %d" % [DEFAULT_TYPED_RETURN_VALUES.size(), TYPE_MAX]) + prints("missing default definition for type", type_key) + assert(DEFAULT_TYPED_RETURN_VALUES.has(type_key), "Missing Type default definition!") @warning_ignore("unused_parameter") @@ -94,7 +117,6 @@ func get_template(return_type :Variant, is_vararg :bool) -> String: push_error("Must be implemented!") return "" - func double(func_descriptor :GdFunctionDescriptor) -> PackedStringArray: var func_signature := func_descriptor.typeless() var is_static := func_descriptor.is_static() @@ -106,16 +128,20 @@ func double(func_descriptor :GdFunctionDescriptor) -> PackedStringArray: var return_value := GdFunctionDoubler.default_return_value(func_descriptor) var arg_names := extract_arg_names(args) var vararg_names := extract_arg_names(varargs) - + # save original constructor arguments if func_name == "_init": var constructor_args := ",".join(GdFunctionDoubler.extract_constructor_args(args)) - var constructor := "func _init(%s):\n super(%s)\n pass\n" % [constructor_args, ", ".join(arg_names)] + var constructor := "func _init(%s) -> void:\n super(%s)\n pass\n" % [constructor_args, ", ".join(arg_names)] return constructor.split("\n") - + var double_src := "" + double_src += '@warning_ignore("untyped_declaration")\n' if Engine.get_version_info().hex >= 0x40200 else '\n' if func_descriptor.is_engine(): double_src += '@warning_ignore("native_method_override")\n' + if func_descriptor.return_type() == GdObjects.TYPE_ENUM: + double_src += '@warning_ignore("int_as_enum_without_match")\n' + double_src += '@warning_ignore("int_as_enum_without_cast")\n' double_src += '@warning_ignore("shadowed_variable")\n' double_src += func_signature # fix to unix format, this is need when the template is edited under windows than the template is stored with \r\n @@ -127,7 +153,7 @@ func double(func_descriptor :GdFunctionDescriptor) -> PackedStringArray: .replace("$(func_name)", func_name )\ .replace("${default_return_value}", return_value)\ .replace("$(push_errors)", _push_errors) - + if is_static: double_src = double_src.replace("$(instance)", "__instance().") else: @@ -142,13 +168,15 @@ func extract_arg_names(argument_signatures :Array[GdFunctionArgument]) -> Packed return arg_names -static func extract_constructor_args(args :Array) -> PackedStringArray: +static func extract_constructor_args(args :Array[GdFunctionArgument]) -> PackedStringArray: var constructor_args := PackedStringArray() for arg in args: - var a := arg as GdFunctionArgument - var arg_name := a._name - var default_value = get_default(a) - constructor_args.append(arg_name + "=" + default_value) + var arg_name := arg._name + var default_value := get_default(arg) + if default_value == "null": + constructor_args.append(arg_name + ":Variant=" + default_value) + else: + constructor_args.append(arg_name + ":=" + default_value) return constructor_args diff --git a/addons/gdUnit4/src/core/GdObjects.gd b/addons/gdUnit4/src/core/GdObjects.gd index ee4bf792..5a2eb5c2 100644 --- a/addons/gdUnit4/src/core/GdObjects.gd +++ b/addons/gdUnit4/src/core/GdObjects.gd @@ -4,6 +4,10 @@ extends Resource const GdUnitTools := preload("res://addons/gdUnit4/src/core/GdUnitTools.gd") + +# introduced with Godot 4.3.beta1 +const TYPE_PACKED_VECTOR4_ARRAY = 38 #TYPE_PACKED_VECTOR4_ARRAY + const TYPE_VOID = TYPE_MAX + 1000 const TYPE_VARARG = TYPE_MAX + 1001 const TYPE_VARIANT = TYPE_MAX + 1002 @@ -59,6 +63,7 @@ const TYPE_AS_STRING_MAPPINGS := { TYPE_PACKED_STRING_ARRAY: "PackedStringArray", TYPE_PACKED_VECTOR2_ARRAY: "PackedVector2Array", TYPE_PACKED_VECTOR3_ARRAY: "PackedVector3Array", + TYPE_PACKED_VECTOR4_ARRAY: "PackedVector4Array", TYPE_PACKED_COLOR_ARRAY: "PackedColorArray", TYPE_VOID: "void", TYPE_VARARG: "VarArg", @@ -77,7 +82,7 @@ const NOTIFICATION_AS_STRING_MAPPINGS := { TYPE_NODE: { Node.NOTIFICATION_ENTER_TREE : "ENTER_TREE", Node.NOTIFICATION_EXIT_TREE: "EXIT_TREE", - Node.NOTIFICATION_MOVED_IN_PARENT: "MOVED_IN_PARENT", + Node.NOTIFICATION_CHILD_ORDER_CHANGED: "CHILD_ORDER_CHANGED", Node.NOTIFICATION_READY: "READY", Node.NOTIFICATION_PAUSED: "PAUSED", Node.NOTIFICATION_UNPAUSED: "UNPAUSED", @@ -119,6 +124,7 @@ const NOTIFICATION_AS_STRING_MAPPINGS := { #Popup.NOTIFICATION_POPUP_HIDE: "POPUP_HIDE", }, TYPE_CONTROL : { + Object.NOTIFICATION_PREDELETE: "PREDELETE", Container.NOTIFICATION_SORT_CHILDREN: "SORT_CHILDREN", Control.NOTIFICATION_RESIZED: "RESIZED", Control.NOTIFICATION_MOUSE_ENTER: "MOUSE_ENTER", @@ -146,7 +152,7 @@ static func obj2dict(obj :Object, hashed_objects := Dictionary()) -> Dictionary: var clazz_name := obj.get_class() var dict := Dictionary() var clazz_path := "" - + if is_instance_valid(obj) and obj.get_script() != null: var d := inst_to_dict(obj) clazz_path = d["@path"] @@ -156,12 +162,12 @@ static func obj2dict(obj :Object, hashed_objects := Dictionary()) -> Dictionary: else: clazz_name = clazz_path.get_file().replace(".gd", "") dict["@path"] = clazz_path - + for property in obj.get_property_list(): - var property_name = property["name"] - var property_type = property["type"] - var property_value = obj.get(property_name) - if property_value is GDScript or property_value is Callable: + var property_name :String = property["name"] + var property_type :int = property["type"] + var property_value :Variant = obj.get(property_name) + if property_value is GDScript or property_value is Callable or property_value is RegEx: continue if (property["usage"] & PROPERTY_USAGE_SCRIPT_VARIABLE|PROPERTY_USAGE_DEFAULT and not property["usage"] & PROPERTY_USAGE_CATEGORY @@ -175,10 +181,14 @@ static func obj2dict(obj :Object, hashed_objects := Dictionary()) -> Dictionary: dict[property_name] = obj2dict(property_value, hashed_objects) else: dict[property_name] = property_value + if obj.has_method("get_children"): + var childrens :Array = obj.get_children() + dict["childrens"] = childrens.map(func (child :Object) -> Dictionary: return obj2dict(child, hashed_objects)) + return {"%s" % clazz_name : dict} -static func equals(obj_a, obj_b, case_sensitive :bool = false, compare_mode :COMPARE_MODE = COMPARE_MODE.PARAMETER_DEEP_TEST) -> bool: +static func equals(obj_a :Variant, obj_b :Variant, case_sensitive :bool = false, compare_mode :COMPARE_MODE = COMPARE_MODE.PARAMETER_DEEP_TEST) -> bool: return _equals(obj_a, obj_b, case_sensitive, compare_mode, [], 0) @@ -190,14 +200,20 @@ static func equals_sorted(obj_a :Array, obj_b :Array, case_sensitive :bool = fal return equals(a, b, case_sensitive, compare_mode) -static func _equals(obj_a, obj_b, case_sensitive :bool, compare_mode :COMPARE_MODE, deep_stack, stack_depth :int ) -> bool: +static func _equals(obj_a :Variant, obj_b :Variant, case_sensitive :bool, compare_mode :COMPARE_MODE, deep_stack :Array, stack_depth :int ) -> bool: var type_a := typeof(obj_a) var type_b := typeof(obj_b) if stack_depth > 32: prints("stack_depth", stack_depth, deep_stack) push_error("GdUnit equals has max stack deep reached!") return false - + + # use argument matcher if requested + if is_instance_valid(obj_a) and obj_a is GdUnitArgumentMatcher: + return (obj_a as GdUnitArgumentMatcher).is_match(obj_b) + if is_instance_valid(obj_b) and obj_b is GdUnitArgumentMatcher: + return (obj_b as GdUnitArgumentMatcher).is_match(obj_a) + stack_depth += 1 # fast fail is different types if not _is_type_equivalent(type_a, type_b): @@ -210,7 +226,7 @@ static func _equals(obj_a, obj_b, case_sensitive :bool, compare_mode :COMPARE_MO return false if obj_b == null and obj_a != null: return false - + match type_a: TYPE_OBJECT: if deep_stack.has(obj_a) or deep_stack.has(obj_b): @@ -223,29 +239,29 @@ static func _equals(obj_a, obj_b, case_sensitive :bool, compare_mode :COMPARE_MO return false if obj_a.get_class() != obj_b.get_class(): return false - var a = obj2dict(obj_a) - var b = obj2dict(obj_b) + var a := obj2dict(obj_a) + var b := obj2dict(obj_b) return _equals(a, b, case_sensitive, compare_mode, deep_stack, stack_depth) return obj_a == obj_b - + TYPE_ARRAY: if obj_a.size() != obj_b.size(): return false - for index in obj_a.size(): + for index :int in obj_a.size(): if not _equals(obj_a[index], obj_b[index], case_sensitive, compare_mode, deep_stack, stack_depth): return false return true - + TYPE_DICTIONARY: if obj_a.size() != obj_b.size(): return false - for key in obj_a.keys(): - var value_a = obj_a[key] if obj_a.has(key) else null - var value_b = obj_b[key] if obj_b.has(key) else null + for key :Variant in obj_a.keys(): + var value_a :Variant = obj_a[key] if obj_a.has(key) else null + var value_b :Variant = obj_b[key] if obj_b.has(key) else null if not _equals(value_a, value_b, case_sensitive, compare_mode, deep_stack, stack_depth): return false return true - + TYPE_STRING: if case_sensitive: return obj_a.to_lower() == obj_b.to_lower() @@ -257,15 +273,15 @@ static func _equals(obj_a, obj_b, case_sensitive :bool, compare_mode :COMPARE_MO @warning_ignore("shadowed_variable_base_class") static func notification_as_string(instance :Variant, notification :int) -> String: var error := "Unknown notification: '%s' at instance: %s" % [notification, instance] - if instance is Node: + if instance is Node and NOTIFICATION_AS_STRING_MAPPINGS[TYPE_NODE].has(notification): return NOTIFICATION_AS_STRING_MAPPINGS[TYPE_NODE].get(notification, error) - if instance is Control: + if instance is Control and NOTIFICATION_AS_STRING_MAPPINGS[TYPE_CONTROL].has(notification): return NOTIFICATION_AS_STRING_MAPPINGS[TYPE_CONTROL].get(notification, error) return NOTIFICATION_AS_STRING_MAPPINGS[TYPE_OBJECT].get(notification, error) static func string_to_type(value :String) -> int: - for type in TYPE_AS_STRING_MAPPINGS.keys(): + for type :int in TYPE_AS_STRING_MAPPINGS.keys(): if TYPE_AS_STRING_MAPPINGS.get(type) == value: return type return TYPE_NIL @@ -283,9 +299,9 @@ static func to_pascal_case(value :String) -> String: static func to_snake_case(value :String) -> String: - var result = PackedStringArray() + var result := PackedStringArray() for ch in value: - var lower_ch = ch.to_lower() + var lower_ch := ch.to_lower() if ch != lower_ch and result.size() > 1: result.append('_') result.append(lower_ch) @@ -305,7 +321,7 @@ static func type_as_string(type :int) -> String: return TYPE_AS_STRING_MAPPINGS.get(type, "Variant") -static func typeof_as_string(value) -> String: +static func typeof_as_string(value :Variant) -> String: return TYPE_AS_STRING_MAPPINGS.get(typeof(value), "Unknown type") @@ -314,15 +330,15 @@ static func all_types() -> PackedInt32Array: static func string_as_typeof(type_name :String) -> int: - var type = TYPE_AS_STRING_MAPPINGS.find_key(type_name) + var type :Variant = TYPE_AS_STRING_MAPPINGS.find_key(type_name) return type if type != null else TYPE_VARIANT -static func is_primitive_type(value) -> bool: +static func is_primitive_type(value :Variant) -> bool: return typeof(value) in [TYPE_BOOL, TYPE_STRING, TYPE_STRING_NAME, TYPE_INT, TYPE_FLOAT] -static func _is_type_equivalent(type_a, type_b) -> bool: +static func _is_type_equivalent(type_a :int, type_b :int) -> bool: # don't test for TYPE_STRING_NAME equivalenz if type_a == TYPE_STRING_NAME or type_b == TYPE_STRING_NAME: return true @@ -348,13 +364,12 @@ static func is_type(value :Variant) -> bool: if is_engine_type(value): return true # is a custom class type - if value is GDScript and value.can_instantiate(): + if value is GDScript and (value as GDScript).can_instantiate(): return true return false -@warning_ignore("shadowed_global_identifier") -static func _is_same(left, right) -> bool: +static func _is_same(left :Variant, right :Variant) -> bool: var left_type := -1 if left == null else typeof(left) var right_type := -1 if right == null else typeof(right) @@ -366,27 +381,27 @@ static func _is_same(left, right) -> bool: return equals(left, right) -static func is_object(value) -> bool: +static func is_object(value :Variant) -> bool: return typeof(value) == TYPE_OBJECT -static func is_script(value) -> bool: +static func is_script(value :Variant) -> bool: return is_object(value) and value is Script static func is_test_suite(script :Script) -> bool: - return is_gd_testsuite(script) or GdUnit4MonoApiLoader.is_test_suite(script.resource_path) + return is_gd_testsuite(script) or GdUnit4CSharpApiLoader.is_test_suite(script.resource_path) -static func is_native_class(value) -> bool: +static func is_native_class(value :Variant) -> bool: return is_object(value) and is_engine_type(value) -static func is_scene(value) -> bool: +static func is_scene(value :Variant) -> bool: return is_object(value) and value is PackedScene -static func is_scene_resource_path(value) -> bool: +static func is_scene_resource_path(value :Variant) -> bool: return value is String and value.ends_with(".tscn") @@ -424,7 +439,7 @@ static func is_singleton(value :Variant) -> bool: static func is_instance(value :Variant) -> bool: if not is_instance_valid(value) or is_native_class(value): return false - if is_script(value) and value.get_instance_base_type() == "": + if is_script(value) and (value as Script).get_instance_base_type() == "": return true if is_scene(value): return true @@ -432,7 +447,7 @@ static func is_instance(value :Variant) -> bool: # only object form type Node and attached filename -static func is_instance_scene(instance) -> bool: +static func is_instance_scene(instance :Variant) -> bool: if instance is Node: var node := instance as Node return node.get_scene_file_path() != null and not node.get_scene_file_path().is_empty() @@ -445,7 +460,7 @@ static func can_be_instantiate(obj :Variant) -> bool: return obj.has_method("new") -static func create_instance(clazz) -> GdUnitResult: +static func create_instance(clazz :Variant) -> GdUnitResult: match typeof(clazz): TYPE_OBJECT: # test is given clazz already an instance @@ -463,7 +478,7 @@ static func create_instance(clazz) -> GdUnitResult: var clazz_path :String = extract_class_path(clazz)[0] if not FileAccess.file_exists(clazz_path): return GdUnitResult.error("Class '%s' not found." % clazz) - var script = load(clazz_path) + var script := load(clazz_path) if script != null: return GdUnitResult.success(script.new()) else: @@ -471,7 +486,7 @@ static func create_instance(clazz) -> GdUnitResult: return GdUnitResult.error("Can't create instance for class '%s'." % clazz) -static func extract_class_path(clazz) -> PackedStringArray: +static func extract_class_path(clazz :Variant) -> PackedStringArray: var clazz_path := PackedStringArray() if clazz is String: clazz_path.append(clazz) @@ -482,14 +497,14 @@ static func extract_class_path(clazz) -> PackedStringArray: if script != null: return extract_class_path(script) return clazz_path - + if clazz is GDScript: if not clazz.resource_path.is_empty(): clazz_path.append(clazz.resource_path) return clazz_path # if not found we go the expensive way and extract the path form the script by creating an instance var arg_list := build_function_default_arguments(clazz, "_init") - var instance = clazz.callv("new", arg_list) + var instance :Variant = clazz.callv("new", arg_list) var clazz_info := inst_to_dict(instance) GdUnitTools.free_instance(instance) clazz_path.append(clazz_info["@path"]) @@ -513,36 +528,36 @@ static func extract_class_name_from_class_path(clazz_path :PackedStringArray) -> return clazz_name -static func extract_class_name(clazz) -> GdUnitResult: +static func extract_class_name(clazz :Variant) -> GdUnitResult: if clazz == null: return GdUnitResult.error("Can't extract class name form a null value.") - + if is_instance(clazz): # is instance a script instance? var script := clazz.script as GDScript if script != null: return extract_class_name(script) - return GdUnitResult.success(clazz.get_class()) - + return GdUnitResult.success((clazz as Object).get_class()) + # extract name form full qualified class path if clazz is String: if ClassDB.class_exists(clazz): return GdUnitResult.success(clazz) var source_sript :Script = load(clazz) - var clazz_name = load("res://addons/gdUnit4/src/core/parse/GdScriptParser.gd").new().get_class_name(source_sript) + var clazz_name :String = load("res://addons/gdUnit4/src/core/parse/GdScriptParser.gd").new().get_class_name(source_sript) return GdUnitResult.success(to_pascal_case(clazz_name)) - + if is_primitive_type(clazz): return GdUnitResult.error("Can't extract class name for an primitive '%s'" % type_as_string(typeof(clazz))) - + if is_script(clazz): if clazz.resource_path.is_empty(): - var class_path = extract_class_name_from_class_path(extract_class_path(clazz)) + var class_path := extract_class_name_from_class_path(extract_class_path(clazz)) return GdUnitResult.success(class_path); return extract_class_name(clazz.resource_path) - + # need to create an instance for a class typ the extract the class name - var instance = clazz.new() + var instance :Variant = clazz.new() if instance == null: return GdUnitResult.error("Can't create a instance for class '%s'" % clazz) var result := extract_class_name(instance) @@ -552,13 +567,13 @@ static func extract_class_name(clazz) -> GdUnitResult: static func extract_inner_clazz_names(clazz_name :String, script_path :PackedStringArray) -> PackedStringArray: var inner_classes := PackedStringArray() - + if ClassDB.class_exists(clazz_name): return inner_classes var script :GDScript = load(script_path[0]) var map := script.get_script_constant_map() - for key in map.keys(): - var value = map.get(key) + for key :String in map.keys(): + var value :Variant = map.get(key) if value is GDScript: var class_path := extract_class_path(value) inner_classes.append(class_path[1]) @@ -568,7 +583,7 @@ static func extract_inner_clazz_names(clazz_name :String, script_path :PackedStr static func extract_class_functions(clazz_name :String, script_path :PackedStringArray) -> Array: if ClassDB.class_get_method_list(clazz_name): return ClassDB.class_get_method_list(clazz_name) - + if not FileAccess.file_exists(script_path[0]): return Array() var script :GDScript = load(script_path[0]) @@ -589,7 +604,7 @@ static func extract_class_functions(clazz_name :String, script_path :PackedStrin # scans all registert script classes for given # if the class is public in the global space than return true otherwise false # public class means the script class is defined by 'class_name ' -static func is_public_script_class(clazz_name) -> bool: +static func is_public_script_class(clazz_name :String) -> bool: var script_classes:Array[Dictionary] = ProjectSettings.get_global_class_list() for class_info in script_classes: if class_info.has("class"): @@ -602,19 +617,19 @@ static func build_function_default_arguments(script :GDScript, func_name :String var arg_list := Array() for func_sig in script.get_script_method_list(): if func_sig["name"] == func_name: - var args :Array = func_sig["args"] + var args :Array[Dictionary] = func_sig["args"] for arg in args: - var value_type := arg["type"] as int - var default_value = default_value_by_type(value_type) + var value_type :int = arg["type"] + var default_value :Variant = default_value_by_type(value_type) arg_list.append(default_value) return arg_list return arg_list -static func default_value_by_type(type :int): +static func default_value_by_type(type :int) -> Variant: assert(type < TYPE_MAX) assert(type >= 0) - + match type: TYPE_NIL: return null TYPE_BOOL: return false @@ -650,7 +665,7 @@ static func default_value_by_type(type :int): TYPE_PACKED_STRING_ARRAY: return PackedStringArray() TYPE_PACKED_VECTOR2_ARRAY: return PackedVector2Array() TYPE_PACKED_VECTOR3_ARRAY: return PackedVector3Array() - + push_error("Can't determine a default value for type: '%s', Please create a Bug issue and attach the stacktrace please." % type) return null diff --git a/addons/gdUnit4/src/core/GdUnit4Version.gd b/addons/gdUnit4/src/core/GdUnit4Version.gd index 86a89c30..5918353d 100644 --- a/addons/gdUnit4/src/core/GdUnit4Version.gd +++ b/addons/gdUnit4/src/core/GdUnit4Version.gd @@ -7,11 +7,13 @@ var _major :int var _minor :int var _patch :int -func _init(major :int,minor :int,patch :int): + +func _init(major :int, minor :int, patch :int) -> void: _major = major _minor = minor _patch = patch + static func parse(value :String) -> GdUnit4Version: var regex := RegEx.new() regex.compile("[a-zA-Z:,-]+") @@ -22,29 +24,34 @@ static func parse(value :String) -> GdUnit4Version: var patch := parts[2].to_int() if parts.size() > 2 else 0 return GdUnit4Version.new(major, minor, patch) + static func current() -> GdUnit4Version: - var config = ConfigFile.new() + var config := ConfigFile.new() config.load('addons/gdUnit4/plugin.cfg') return parse(config.get_value('plugin', 'version')) -func equals(other :) -> bool: + +func equals(other :GdUnit4Version) -> bool: return _major == other._major and _minor == other._minor and _patch == other._patch -func is_greater(other :) -> bool: + +func is_greater(other :GdUnit4Version) -> bool: if _major > other._major: return true if _major == other._major and _minor > other._minor: return true return _major == other._major and _minor == other._minor and _patch > other._patch + static func init_version_label(label :Control) -> void: - var config = ConfigFile.new() + var config := ConfigFile.new() config.load('addons/gdUnit4/plugin.cfg') - var version = config.get_value('plugin', 'version') + var version :String = config.get_value('plugin', 'version') if label is RichTextLabel: label.text = VERSION_PATTERN.replace('${version}', version) else: label.text = "gdUnit4 " + version + func _to_string() -> String: return "v%d.%d.%d" % [_major, _minor, _patch] diff --git a/addons/gdUnit4/src/core/GdUnitClassDoubler.gd b/addons/gdUnit4/src/core/GdUnitClassDoubler.gd index 3ff12e9f..4f0d2bbc 100644 --- a/addons/gdUnit4/src/core/GdUnitClassDoubler.gd +++ b/addons/gdUnit4/src/core/GdUnitClassDoubler.gd @@ -29,7 +29,7 @@ static func check_leaked_instances() -> void: ## we check that all registered spy/mock instances are removed from the engine meta data for key in Engine.get_meta_list(): if key.begins_with(DOUBLER_INSTANCE_ID_PREFIX): - var instance = Engine.get_meta(key) + var instance :Variant = Engine.get_meta(key) push_error("GdUnit internal error: an spy/mock instance '%s', class:'%s' is not removed from the engine and will lead in a leaked instance!" % [instance, instance.__SOURCE_CLASS]) @@ -37,7 +37,7 @@ static func check_leaked_instances() -> void: # class_info = { "class_name": <>, "class_path" : <>} static func load_template(template :String, class_info :Dictionary, instance :Object) -> PackedStringArray: # store instance id - var source_code = template\ + var source_code := template\ .replace("${instance_id}", "%s%d" % [DOUBLER_INSTANCE_ID_PREFIX, abs(instance.get_instance_id())])\ .replace("${source_class}", class_info.get("class_name")) var lines := GdScriptParser.to_unix_format(source_code).split("\n") @@ -66,7 +66,7 @@ static func double_functions(instance :Object, clazz_name :String, clazz_path :P var parser := GdScriptParser.new() var exclude_override_functions := EXCLUDE_VIRTUAL_FUNCTIONS + EXCLUDE_FUNCTIONS + exclude_functions var functions := Array() - + # double script functions if not ClassDB.class_exists(clazz_name): var result := parser.parse(clazz_name, clazz_path) @@ -84,10 +84,10 @@ static func double_functions(instance :Object, clazz_name :String, clazz_path :P doubled_source += func_doubler.double(func_descriptor) functions.append(func_descriptor.name()) class_descriptor = class_descriptor.parent() - + # double regular class functions var clazz_functions := GdObjects.extract_class_functions(clazz_name, clazz_path) - for method in clazz_functions: + for method : Dictionary in clazz_functions: var func_descriptor := GdFunctionDescriptor.extract_from(method) # exclude private core functions if func_descriptor.is_private(): @@ -109,7 +109,7 @@ static func double_functions(instance :Object, clazz_name :String, clazz_path :P # GD-110 static func is_invalid_method_descriptior(method :Dictionary) -> bool: - var return_info = method["return"] + var return_info :Dictionary = method["return"] var type :int = return_info["type"] var usage :int = return_info["usage"] var clazz_name :String = return_info["class_name"] diff --git a/addons/gdUnit4/src/core/GdUnitFileAccess.gd b/addons/gdUnit4/src/core/GdUnitFileAccess.gd new file mode 100644 index 00000000..01022ddb --- /dev/null +++ b/addons/gdUnit4/src/core/GdUnitFileAccess.gd @@ -0,0 +1,211 @@ +class_name GdUnitFileAccess +extends RefCounted + +const GDUNIT_TEMP := "user://tmp" + + +static func current_dir() -> String: + return ProjectSettings.globalize_path("res://") + + +static func clear_tmp() -> void: + delete_directory(GDUNIT_TEMP) + + +# Creates a new file under +static func create_temp_file(relative_path :String, file_name :String, mode := FileAccess.WRITE) -> FileAccess: + var file_path := create_temp_dir(relative_path) + "/" + file_name + var file := FileAccess.open(file_path, mode) + if file == null: + push_error("Error creating temporary file at: %s, %s" % [file_path, error_string(FileAccess.get_open_error())]) + return file + + +static func temp_dir() -> String: + if not DirAccess.dir_exists_absolute(GDUNIT_TEMP): + DirAccess.make_dir_recursive_absolute(GDUNIT_TEMP) + return GDUNIT_TEMP + + +static func create_temp_dir(folder_name :String) -> String: + var new_folder := temp_dir() + "/" + folder_name + if not DirAccess.dir_exists_absolute(new_folder): + DirAccess.make_dir_recursive_absolute(new_folder) + return new_folder + + +static func copy_file(from_file :String, to_dir :String) -> GdUnitResult: + var dir := DirAccess.open(to_dir) + if dir != null: + var to_file := to_dir + "/" + from_file.get_file() + prints("Copy %s to %s" % [from_file, to_file]) + var error := dir.copy(from_file, to_file) + if error != OK: + return GdUnitResult.error("Can't copy file form '%s' to '%s'. Error: '%s'" % [from_file, to_file, error_string(error)]) + return GdUnitResult.success(to_file) + return GdUnitResult.error("Directory not found: " + to_dir) + + +static func copy_directory(from_dir :String, to_dir :String, recursive :bool = false) -> bool: + if not DirAccess.dir_exists_absolute(from_dir): + push_error("Source directory not found '%s'" % from_dir) + return false + + # check if destination exists + if not DirAccess.dir_exists_absolute(to_dir): + # create it + var err := DirAccess.make_dir_recursive_absolute(to_dir) + if err != OK: + push_error("Can't create directory '%s'. Error: %s" % [to_dir, error_string(err)]) + return false + var source_dir := DirAccess.open(from_dir) + var dest_dir := DirAccess.open(to_dir) + if source_dir != null: + source_dir.list_dir_begin() + var next := "." + + while next != "": + next = source_dir.get_next() + if next == "" or next == "." or next == "..": + continue + var source := source_dir.get_current_dir() + "/" + next + var dest := dest_dir.get_current_dir() + "/" + next + if source_dir.current_is_dir(): + if recursive: + copy_directory(source + "/", dest, recursive) + continue + var err := source_dir.copy(source, dest) + if err != OK: + push_error("Error checked copy file '%s' to '%s'" % [source, dest]) + return false + + return true + else: + push_error("Directory not found: " + from_dir) + return false + + +static func delete_directory(path :String, only_content := false) -> void: + var dir := DirAccess.open(path) + if dir != null: + dir.list_dir_begin() + var file_name := "." + while file_name != "": + file_name = dir.get_next() + if file_name.is_empty() or file_name == "." or file_name == "..": + continue + var next := path + "/" +file_name + if dir.current_is_dir(): + delete_directory(next) + else: + # delete file + var err := dir.remove(next) + if err: + push_error("Delete %s failed: %s" % [next, error_string(err)]) + if not only_content: + var err := dir.remove(path) + if err: + push_error("Delete %s failed: %s" % [path, error_string(err)]) + + +static func delete_path_index_lower_equals_than(path :String, prefix :String, index :int) -> int: + var dir := DirAccess.open(path) + if dir == null: + return 0 + var deleted := 0 + dir.list_dir_begin() + var next := "." + while next != "": + next = dir.get_next() + if next.is_empty() or next == "." or next == "..": + continue + if next.begins_with(prefix): + var current_index := next.split("_")[1].to_int() + if current_index <= index: + deleted += 1 + delete_directory(path + "/" + next) + return deleted + + +# scans given path for sub directories by given prefix and returns the highest index numer +# e.g. +static func find_last_path_index(path :String, prefix :String) -> int: + var dir := DirAccess.open(path) + if dir == null: + return 0 + var last_iteration := 0 + dir.list_dir_begin() + var next := "." + while next != "": + next = dir.get_next() + if next.is_empty() or next == "." or next == "..": + continue + if next.begins_with(prefix): + var iteration := next.split("_")[1].to_int() + if iteration > last_iteration: + last_iteration = iteration + return last_iteration + + +static func scan_dir(path :String) -> PackedStringArray: + var dir := DirAccess.open(path) + if dir == null or not dir.dir_exists(path): + return PackedStringArray() + var content := PackedStringArray() + dir.list_dir_begin() + var next := "." + while next != "": + next = dir.get_next() + if next.is_empty() or next == "." or next == "..": + continue + content.append(next) + return content + + +static func resource_as_array(resource_path :String) -> PackedStringArray: + var file := FileAccess.open(resource_path, FileAccess.READ) + if file == null: + push_error("ERROR: Can't read resource '%s'. %s" % [resource_path, error_string(FileAccess.get_open_error())]) + return PackedStringArray() + var file_content := PackedStringArray() + while not file.eof_reached(): + file_content.append(file.get_line()) + return file_content + + +static func resource_as_string(resource_path :String) -> String: + var file := FileAccess.open(resource_path, FileAccess.READ) + if file == null: + push_error("ERROR: Can't read resource '%s'. %s" % [resource_path, error_string(FileAccess.get_open_error())]) + return "" + return file.get_as_text(true) + + +static func make_qualified_path(path :String) -> String: + if not path.begins_with("res://"): + if path.begins_with("//"): + return path.replace("//", "res://") + if path.begins_with("/"): + return "res:/" + path + return path + + +static func extract_zip(zip_package :String, dest_path :String) -> GdUnitResult: + var zip: ZIPReader = ZIPReader.new() + var err := zip.open(zip_package) + if err != OK: + return GdUnitResult.error("Extracting `%s` failed! Please collect the error log and report this. Error Code: %s" % [zip_package, err]) + var zip_entries: PackedStringArray = zip.get_files() + # Get base path and step over archive folder + var archive_path := zip_entries[0] + zip_entries.remove_at(0) + + for zip_entry in zip_entries: + var new_file_path: String = dest_path + "/" + zip_entry.replace(archive_path, "") + if zip_entry.ends_with("/"): + DirAccess.make_dir_recursive_absolute(new_file_path) + continue + var file: FileAccess = FileAccess.open(new_file_path, FileAccess.WRITE) + file.store_buffer(zip.read_file(zip_entry)) + zip.close() + return GdUnitResult.success(dest_path) diff --git a/addons/gdUnit4/src/core/GdUnitObjectInteractions.gd b/addons/gdUnit4/src/core/GdUnitObjectInteractions.gd index b317d392..36930fc7 100644 --- a/addons/gdUnit4/src/core/GdUnitObjectInteractions.gd +++ b/addons/gdUnit4/src/core/GdUnitObjectInteractions.gd @@ -2,41 +2,41 @@ class_name GdUnitObjectInteractions extends RefCounted -static func verify(obj :Object, times): - if not _is_mock_or_spy(obj, "__verify"): - return obj - return obj.__do_verify_interactions(times) +static func verify(interaction_object :Object, interactions_times :int) -> Variant: + if not _is_mock_or_spy(interaction_object, "__verify"): + return interaction_object + return interaction_object.__do_verify_interactions(interactions_times) -static func verify_no_interactions(obj :Object) -> GdUnitAssert: - var gd_assert = ResourceLoader.load("res://addons/gdUnit4/src/asserts/GdUnitAssertImpl.gd", "GDScript", ResourceLoader.CACHE_MODE_REUSE).new("") - if not _is_mock_or_spy(obj, "__verify"): - return gd_assert.report_success() - var summary :Dictionary = obj.__verify_no_interactions() - if summary.is_empty(): - return gd_assert.report_success() - return gd_assert.report_error(GdAssertMessages.error_no_more_interactions(summary)) +static func verify_no_interactions(interaction_object :Object) -> GdUnitAssert: + var __gd_assert :GdUnitAssert = ResourceLoader.load("res://addons/gdUnit4/src/asserts/GdUnitAssertImpl.gd", "GDScript", ResourceLoader.CACHE_MODE_REUSE).new("") + if not _is_mock_or_spy(interaction_object, "__verify"): + return __gd_assert.report_success() + var __summary :Dictionary = interaction_object.__verify_no_interactions() + if __summary.is_empty(): + return __gd_assert.report_success() + return __gd_assert.report_error(GdAssertMessages.error_no_more_interactions(__summary)) -static func verify_no_more_interactions(obj :Object) -> GdUnitAssert: - var gd_assert = ResourceLoader.load("res://addons/gdUnit4/src/asserts/GdUnitAssertImpl.gd", "GDScript", ResourceLoader.CACHE_MODE_REUSE).new("") - if not _is_mock_or_spy(obj, "__verify_no_more_interactions"): - return gd_assert - var summary :Dictionary = obj.__verify_no_more_interactions() - if summary.is_empty(): - return gd_assert - return gd_assert.report_error(GdAssertMessages.error_no_more_interactions(summary)) +static func verify_no_more_interactions(interaction_object :Object) -> GdUnitAssert: + var __gd_assert :GdUnitAssert = ResourceLoader.load("res://addons/gdUnit4/src/asserts/GdUnitAssertImpl.gd", "GDScript", ResourceLoader.CACHE_MODE_REUSE).new("") + if not _is_mock_or_spy(interaction_object, "__verify_no_more_interactions"): + return __gd_assert + var __summary :Dictionary = interaction_object.__verify_no_more_interactions() + if __summary.is_empty(): + return __gd_assert + return __gd_assert.report_error(GdAssertMessages.error_no_more_interactions(__summary)) -static func reset(obj :Object) -> Object: - if not _is_mock_or_spy( obj, "__reset"): - return obj - obj.__reset_interactions() - return obj +static func reset(interaction_object :Object) -> Object: + if not _is_mock_or_spy(interaction_object, "__reset"): + return interaction_object + interaction_object.__reset_interactions() + return interaction_object -static func _is_mock_or_spy(obj :Object, func_sig :String) -> bool: - if obj is GDScript and not obj.get_script().has_script_method(func_sig): +static func _is_mock_or_spy(interaction_object :Object, mock_function_signature :String) -> bool: + if interaction_object is GDScript and not interaction_object.get_script().has_script_method(mock_function_signature): push_error("Error: You try to use a non mock or spy!") return false return true diff --git a/addons/gdUnit4/src/core/GdUnitObjectInteractionsTemplate.gd b/addons/gdUnit4/src/core/GdUnitObjectInteractionsTemplate.gd index 5d455abb..c06b1d4d 100644 --- a/addons/gdUnit4/src/core/GdUnitObjectInteractionsTemplate.gd +++ b/addons/gdUnit4/src/core/GdUnitObjectInteractionsTemplate.gd @@ -5,81 +5,86 @@ var __saved_interactions := Dictionary() var __verified_interactions := Array() -func __save_function_interaction(args :Array) -> void: - var matcher := GdUnitArgumentMatchers.to_matcher(args, true) - for key in __saved_interactions.keys(): - if matcher.is_match(key): - __saved_interactions[key] += 1 +func __save_function_interaction(function_args :Array[Variant]) -> void: + var __matcher := GdUnitArgumentMatchers.to_matcher(function_args, true) + for __index in __saved_interactions.keys().size(): + var __key :Variant = __saved_interactions.keys()[__index] + if __matcher.is_match(__key): + __saved_interactions[__key] += 1 return - __saved_interactions[args] = 1 + __saved_interactions[function_args] = 1 func __is_verify_interactions() -> bool: return __expected_interactions != -1 -func __do_verify_interactions(times :int = 1) -> Object: - __expected_interactions = times +func __do_verify_interactions(interactions_times :int = 1) -> Object: + __expected_interactions = interactions_times return self -func __verify_interactions(args :Array): - var summary := Dictionary() - var total_interactions := 0 - var matcher := GdUnitArgumentMatchers.to_matcher(args, true) - for key in __saved_interactions.keys(): - if matcher.is_match(key): - var interactions :int = __saved_interactions.get(key, 0) - total_interactions += interactions - summary[key] = interactions +func __verify_interactions(function_args :Array[Variant]) -> void: + var __summary := Dictionary() + var __total_interactions := 0 + var __matcher := GdUnitArgumentMatchers.to_matcher(function_args, true) + for __index in __saved_interactions.keys().size(): + var __key :Variant = __saved_interactions.keys()[__index] + if __matcher.is_match(__key): + var __interactions :int = __saved_interactions.get(__key, 0) + __total_interactions += __interactions + __summary[__key] = __interactions # add as verified - __verified_interactions.append(key) - - var gd_assert := GdUnitAssertImpl.new("") - if total_interactions != __expected_interactions: - var expected_summary = {args : __expected_interactions} - var error_message :String - # if no interactions macht collect not verified interactions for failure report - if summary.is_empty(): - var current_summary = __verify_no_more_interactions() - error_message = GdAssertMessages.error_validate_interactions(current_summary, expected_summary) + __verified_interactions.append(__key) + + var __gd_assert := GdUnitAssertImpl.new("") + if __total_interactions != __expected_interactions: + var __expected_summary := {function_args : __expected_interactions} + var __error_message :String + # if no __interactions macht collect not verified __interactions for failure report + if __summary.is_empty(): + var __current_summary := __verify_no_more_interactions() + __error_message = GdAssertMessages.error_validate_interactions(__current_summary, __expected_summary) else: - error_message = GdAssertMessages.error_validate_interactions(summary, expected_summary) - gd_assert.report_error(error_message) + __error_message = GdAssertMessages.error_validate_interactions(__summary, __expected_summary) + __gd_assert.report_error(__error_message) else: - gd_assert.report_success() + __gd_assert.report_success() __expected_interactions = -1 func __verify_no_interactions() -> Dictionary: - var summary := Dictionary() + var __summary := Dictionary() if not __saved_interactions.is_empty(): - for func_call in __saved_interactions.keys(): - summary[func_call] = __saved_interactions[func_call] - return summary + for __index in __saved_interactions.keys().size(): + var func_call :Variant = __saved_interactions.keys()[__index] + __summary[func_call] = __saved_interactions[func_call] + return __summary func __verify_no_more_interactions() -> Dictionary: - var summary := Dictionary() - var called_functions :Array = __saved_interactions.keys() + var __summary := Dictionary() + var called_functions :Array[Variant] = __saved_interactions.keys() if called_functions != __verified_interactions: # collect the not verified functions var called_but_not_verified := called_functions.duplicate() - for verified_function in __verified_interactions: - called_but_not_verified.erase(verified_function) - - for not_verified in called_but_not_verified: - summary[not_verified] = __saved_interactions[not_verified] - return summary + for __index in __verified_interactions.size(): + called_but_not_verified.erase(__verified_interactions[__index]) + + for __index in called_but_not_verified.size(): + var not_verified :Variant = called_but_not_verified[__index] + __summary[not_verified] = __saved_interactions[not_verified] + return __summary func __reset_interactions() -> void: __saved_interactions.clear() -func __filter_vargs(arg_values :Array) -> Array: - var filtered := Array() - for arg in arg_values: +func __filter_vargs(arg_values :Array[Variant]) -> Array[Variant]: + var filtered :Array[Variant] = [] + for __index in arg_values.size(): + var arg :Variant = arg_values[__index] if typeof(arg) == TYPE_STRING and arg == GdObjects.TYPE_VARARG_PLACEHOLDER_VALUE: continue filtered.append(arg) diff --git a/addons/gdUnit4/src/core/GdUnitProperty.gd b/addons/gdUnit4/src/core/GdUnitProperty.gd index 9373d9c7..6e338a3f 100644 --- a/addons/gdUnit4/src/core/GdUnitProperty.gd +++ b/addons/gdUnit4/src/core/GdUnitProperty.gd @@ -1,15 +1,16 @@ class_name GdUnitProperty extends RefCounted + var _name :String var _help :String var _type :int -var _value +var _value :Variant var _value_set :PackedStringArray -var _default +var _default :Variant -func _init(p_name :String, p_type :int, p_value, p_default_value, p_help :="", p_value_set := PackedStringArray()): +func _init(p_name :String, p_type :int, p_value :Variant, p_default_value :Variant, p_help :="", p_value_set := PackedStringArray()) -> void: _name = p_name _type = p_type _value = p_value @@ -26,7 +27,7 @@ func type() -> int: return _type -func value(): +func value() -> Variant: return _value @@ -52,7 +53,7 @@ func set_value(p_value :Variant) -> void: _value = p_value -func default(): +func default() -> Variant: return _default diff --git a/addons/gdUnit4/src/core/GdUnitResult.gd b/addons/gdUnit4/src/core/GdUnitResult.gd index cda3d51a..f2d297f9 100644 --- a/addons/gdUnit4/src/core/GdUnitResult.gd +++ b/addons/gdUnit4/src/core/GdUnitResult.gd @@ -8,7 +8,7 @@ enum { EMPTY } -var _state +var _state :Variant var _warn_message := "" var _error_message := "" var _value :Variant = null @@ -66,7 +66,7 @@ func value() -> Variant: return _value -func or_else(p_value): +func or_else(p_value :Variant) -> Variant: if not is_success(): return p_value return value() diff --git a/addons/gdUnit4/src/core/GdUnitRunner.gd b/addons/gdUnit4/src/core/GdUnitRunner.gd index c383fc3f..68aadee3 100644 --- a/addons/gdUnit4/src/core/GdUnitRunner.gd +++ b/addons/gdUnit4/src/core/GdUnitRunner.gd @@ -1,8 +1,5 @@ extends Node -signal sync_rpc_id_result_received - - @onready var _client :GdUnitTcpClient = $GdUnitTcpClient @onready var _executor :GdUnitTestSuiteExecutor = GdUnitTestSuiteExecutor.new() @@ -16,12 +13,12 @@ enum { const GDUNIT_RUNNER = "GdUnitRunner" var _config := GdUnitRunnerConfig.new() -var _test_suites_to_process :Array -var _state = INIT -var _cs_executor +var _test_suites_to_process :Array[Node] +var _state :int = INIT +var _cs_executor :RefCounted -func _init(): +func _init() -> void: # minimize scene window checked debug mode if OS.get_cmdline_args().size() == 1: DisplayServer.window_set_title("GdUnit4 Runner (Debug Mode)") @@ -30,10 +27,10 @@ func _init(): DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_MINIMIZED) # store current runner instance to engine meta data to can be access in as a singleton Engine.set_meta(GDUNIT_RUNNER, self) - _cs_executor = GdUnit4MonoApiLoader.create_executor(self) + _cs_executor = GdUnit4CSharpApiLoader.create_executor(self) -func _ready(): +func _ready() -> void: var config_result := _config.load_config() if config_result.is_error(): push_error(config_result.error_message()) @@ -48,23 +45,23 @@ func _ready(): _state = INIT -func _on_connection_failed(message :String): +func _on_connection_failed(message :String) -> void: prints("_on_connection_failed", message, _test_suites_to_process) _state = STOP -func _notification(what): +func _notification(what :int) -> void: #prints("GdUnitRunner", self, GdObjects.notification_as_string(what)) if what == NOTIFICATION_PREDELETE: Engine.remove_meta(GDUNIT_RUNNER) -func _process(_delta): +func _process(_delta :float) -> void: match _state: INIT: # wait until client is connected to the GdUnitServer if _client.is_client_connected(): - var time = LocalTime.now() + var time := LocalTime.now() prints("Scan for test suites.") _test_suites_to_process = load_test_suits() prints("Scanning of %d test suites took" % _test_suites_to_process.size(), time.elapsed_since()) @@ -93,16 +90,16 @@ func _process(_delta): get_tree().quit(0) -func load_test_suits() -> Array: +func load_test_suits() -> Array[Node]: var to_execute := _config.to_execute() if to_execute.is_empty(): prints("No tests selected to execute!") _state = EXIT return [] # scan for the requested test suites - var test_suites := Array() + var test_suites :Array[Node] = [] var _scanner := GdUnitTestSuiteScanner.new() - for resource_path in to_execute.keys(): + for resource_path :String in to_execute.keys(): var selected_tests :PackedStringArray = to_execute.get(resource_path) var scaned_suites := _scanner.scan(resource_path) _filter_test_case(scaned_suites, selected_tests) @@ -113,13 +110,14 @@ func load_test_suits() -> Array: func gdUnitInit() -> void: #enable_manuall_polling() send_message("Scaned %d test suites" % _test_suites_to_process.size()) - var total_count = _collect_test_case_count(_test_suites_to_process) + var total_count := _collect_test_case_count(_test_suites_to_process) _on_gdunit_event(GdUnitInit.new(_test_suites_to_process.size(), total_count)) - for test_suite in _test_suites_to_process: - send_test_suite(test_suite) + if not GdUnitSettings.is_test_discover_enabled(): + for test_suite in _test_suites_to_process: + send_test_suite(test_suite) -func _filter_test_case(test_suites :Array, included_tests :PackedStringArray) -> void: +func _filter_test_case(test_suites :Array[Node], included_tests :PackedStringArray) -> void: if included_tests.is_empty(): return for test_suite in test_suites: @@ -142,23 +140,23 @@ func _do_filter_test_case(test_suite :Node, test_case :Node, included_tests :Pac test_case.free() -func _collect_test_case_count(testSuites :Array) -> int: +func _collect_test_case_count(testSuites :Array[Node]) -> int: var total :int = 0 for test_suite in testSuites: - total += (test_suite as Node).get_child_count() + total += test_suite.get_child_count() return total # RPC send functions -func send_message(message :String): +func send_message(message :String) -> void: _client.rpc_send(RPCMessage.of(message)) -func send_test_suite(test_suite): +func send_test_suite(test_suite :Node) -> void: _client.rpc_send(RPCGdUnitTestSuite.of(test_suite)) -func _on_gdunit_event(event :GdUnitEvent): +func _on_gdunit_event(event :GdUnitEvent) -> void: _client.rpc_send(RPCGdUnitEvent.of(event)) diff --git a/addons/gdUnit4/src/core/GdUnitRunnerConfig.gd b/addons/gdUnit4/src/core/GdUnitRunnerConfig.gd index f9816cc4..173b536d 100644 --- a/addons/gdUnit4/src/core/GdUnitRunnerConfig.gd +++ b/addons/gdUnit4/src/core/GdUnitRunnerConfig.gd @@ -72,10 +72,10 @@ func add_test_case(p_resource_path :String, test_name :StringName, test_param_in # '/path/path', res://path/path', 'res://path/path/testsuite.gd' or 'testsuite' # 'res://path/path/testsuite.gd:test_case' or 'testsuite:test_case' func skip_test_suite(value :StringName) -> GdUnitRunnerConfig: - var parts :Array = GdUnitTools.make_qualified_path(value).rsplit(":") + var parts :Array = GdUnitFileAccess.make_qualified_path(value).rsplit(":") if parts[0] == "res": parts.pop_front() - parts[0] = GdUnitTools.make_qualified_path(parts[0]) + parts[0] = GdUnitFileAccess.make_qualified_path(parts[0]) match parts.size(): 1: skipped()[parts[0]] = PackedStringArray() 2: skip_test_case(parts[0], parts[1]) @@ -96,19 +96,20 @@ func skip_test_case(p_resource_path :String, test_name :StringName) -> GdUnitRun return self +# Dictionary[String, Dictionary[String, PackedStringArray]] func to_execute() -> Dictionary: return _config.get(INCLUDED, {"res://" : PackedStringArray()}) func skipped() -> Dictionary: - return _config.get(SKIPPED, PackedStringArray()) + return _config.get(SKIPPED, {}) func save_config(path :String = CONFIG_FILE) -> GdUnitResult: var file := FileAccess.open(path, FileAccess.WRITE) if file == null: - var error = FileAccess.get_open_error() - return GdUnitResult.error("Can't write test runner configuration '%s'! %s" % [path, GdUnitTools.error_as_string(error)]) + var error := FileAccess.get_open_error() + return GdUnitResult.error("Can't write test runner configuration '%s'! %s" % [path, error_string(error)]) _config[VERSION] = CONFIG_VERSION file.store_string(JSON.stringify(_config)) return GdUnitResult.success(path) @@ -119,8 +120,8 @@ func load_config(path :String = CONFIG_FILE) -> GdUnitResult: return GdUnitResult.error("Can't find test runner configuration '%s'! Please select a test to run." % path) var file := FileAccess.open(path, FileAccess.READ) if file == null: - var error = FileAccess.get_open_error() - return GdUnitResult.error("Can't load test runner configuration '%s'! ERROR: %s." % [path, GdUnitTools.error_as_string(error)]) + var error := FileAccess.get_open_error() + return GdUnitResult.error("Can't load test runner configuration '%s'! ERROR: %s." % [path, error_string(error)]) var content := file.get_as_text() if not content.is_empty() and content[0] == '{': # Parse as json @@ -135,7 +136,7 @@ func load_config(path :String = CONFIG_FILE) -> GdUnitResult: return GdUnitResult.success(path) -func fix_value_types(): +func fix_value_types() -> void: # fix float value to int json stores all numbers as float var server_port_ :int = _config.get(SERVER_PORT, -1) _config[SERVER_PORT] = server_port_ @@ -143,8 +144,8 @@ func fix_value_types(): convert_Array_to_PackedStringArray(_config[SKIPPED]) -func convert_Array_to_PackedStringArray(data :Dictionary): - for key in data.keys(): +func convert_Array_to_PackedStringArray(data :Dictionary) -> void: + for key in data.keys() as Array[String]: var values :Array = data[key] data[key] = PackedStringArray(values) diff --git a/addons/gdUnit4/src/core/GdUnitSceneRunnerImpl.gd b/addons/gdUnit4/src/core/GdUnitSceneRunnerImpl.gd index ed6dffaa..bd2da3c8 100644 --- a/addons/gdUnit4/src/core/GdUnitSceneRunnerImpl.gd +++ b/addons/gdUnit4/src/core/GdUnitSceneRunnerImpl.gd @@ -18,7 +18,7 @@ const MAP_MOUSE_BUTTON_MASKS := { MOUSE_BUTTON_XBUTTON2 : MOUSE_BUTTON_MASK_MB_XBUTTON2, } -var _scene_tree :SceneTree = null +var _is_disposed := false var _current_scene :Node = null var _awaiter :GdUnitAwaiter = GdUnitAwaiter.new() var _verbose :bool @@ -26,27 +26,31 @@ var _simulate_start_time :LocalTime var _last_input_event :InputEvent = null var _mouse_button_on_press := [] var _key_on_press := [] +var _action_on_press := [] +var _curent_mouse_position :Vector2 # time factor settings var _time_factor := 1.0 var _saved_iterations_per_second :float +var _scene_auto_free := false -func _init(p_scene, p_verbose :bool, p_hide_push_errors = false): +func _init(p_scene :Variant, p_verbose :bool, p_hide_push_errors := false) -> void: _verbose = p_verbose _saved_iterations_per_second = Engine.get_physics_ticks_per_second() set_time_factor(1) # handle scene loading by resource path if typeof(p_scene) == TYPE_STRING: - if !FileAccess.file_exists(p_scene): + if !ResourceLoader.exists(p_scene): if not p_hide_push_errors: - push_error("GdUnitSceneRunner: Can't load scene by given resource path: '%s'. The resource not exists." % p_scene) + push_error("GdUnitSceneRunner: Can't load scene by given resource path: '%s'. The resource does not exists." % p_scene) return - if !str(p_scene).ends_with("tscn"): + if !str(p_scene).ends_with(".tscn") and !str(p_scene).ends_with(".scn") and !str(p_scene).begins_with("uid://"): if not p_hide_push_errors: push_error("GdUnitSceneRunner: The given resource: '%s'. is not a scene." % p_scene) return - _current_scene = load(p_scene).instantiate() + _current_scene = load(p_scene).instantiate() + _scene_auto_free = true else: # verify we have a node instance if not p_scene is Node: @@ -58,34 +62,65 @@ func _init(p_scene, p_verbose :bool, p_hide_push_errors = false): if not p_hide_push_errors: push_error("GdUnitSceneRunner: Scene must be not null!") return - _scene_tree = Engine.get_main_loop() - _scene_tree.root.add_child(_current_scene) + _scene_tree().root.add_child(_current_scene) + # do finally reset all open input events when the scene is removed + _scene_tree().root.child_exiting_tree.connect(func f(child :Node) -> void: + if child == _current_scene: + _reset_input_to_default() + ) _simulate_start_time = LocalTime.now() # we need to set inital a valid window otherwise the warp_mouse() is not handled DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_WINDOWED) # set inital mouse pos to 0,0 - var max_iteration_to_wait = 0 + var max_iteration_to_wait := 0 while get_global_mouse_position() != Vector2.ZERO and max_iteration_to_wait < 100: Input.warp_mouse(Vector2.ZERO) max_iteration_to_wait += 1 -func _notification(what): +func _notification(what :int) -> void: if what == NOTIFICATION_PREDELETE and is_instance_valid(self): # reset time factor to normal __deactivate_time_factor() - _reset_input_to_default() if is_instance_valid(_current_scene): - _scene_tree.root.remove_child(_current_scene) - # don't free already memory managed instances - if not GdUnitMemoryObserver.is_marked_auto_free(_current_scene): + _scene_tree().root.remove_child(_current_scene) + # do only free scenes instanciated by this runner + if _scene_auto_free: _current_scene.free() - _scene_tree = null + _is_disposed = true _current_scene = null - # we hide the scene/main window after runner is finished + # we hide the scene/main window after runner is finished DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_MINIMIZED) +func _scene_tree() -> SceneTree: + return Engine.get_main_loop() as SceneTree + + +func simulate_action_pressed(action :String) -> GdUnitSceneRunner: + simulate_action_press(action) + simulate_action_release(action) + return self + + +func simulate_action_press(action :String) -> GdUnitSceneRunner: + __print_current_focus() + var event := InputEventAction.new() + event.pressed = true + event.action = action + _action_on_press.append(action) + return _handle_input_event(event) + + +func simulate_action_release(action :String) -> GdUnitSceneRunner: + __print_current_focus() + var event := InputEventAction.new() + event.pressed = false + event.action = action + _action_on_press.erase(action) + return _handle_input_event(event) + + func simulate_key_pressed(key_code :int, shift_pressed := false, ctrl_pressed := false) -> GdUnitSceneRunner: simulate_key_press(key_code, shift_pressed, ctrl_pressed) simulate_key_release(key_code, shift_pressed, ctrl_pressed) @@ -94,10 +129,10 @@ func simulate_key_pressed(key_code :int, shift_pressed := false, ctrl_pressed := func simulate_key_press(key_code :int, shift_pressed := false, ctrl_pressed := false) -> GdUnitSceneRunner: __print_current_focus() - var event = InputEventKey.new() + var event := InputEventKey.new() event.pressed = true - event.keycode = key_code - event.physical_keycode = key_code + event.keycode = key_code as Key + event.physical_keycode = key_code as Key event.alt_pressed = key_code == KEY_ALT event.shift_pressed = shift_pressed or key_code == KEY_SHIFT event.ctrl_pressed = ctrl_pressed or key_code == KEY_CTRL @@ -108,10 +143,10 @@ func simulate_key_press(key_code :int, shift_pressed := false, ctrl_pressed := f func simulate_key_release(key_code :int, shift_pressed := false, ctrl_pressed := false) -> GdUnitSceneRunner: __print_current_focus() - var event = InputEventKey.new() + var event := InputEventKey.new() event.pressed = false - event.keycode = key_code - event.physical_keycode = key_code + event.keycode = key_code as Key + event.physical_keycode = key_code as Key event.alt_pressed = key_code == KEY_ALT event.shift_pressed = shift_pressed or key_code == KEY_SHIFT event.ctrl_pressed = ctrl_pressed or key_code == KEY_CTRL @@ -131,11 +166,14 @@ func set_mouse_pos(pos :Vector2) -> GdUnitSceneRunner: func get_mouse_position() -> Vector2: if _last_input_event is InputEventMouse: return _last_input_event.position - return _current_scene.get_viewport().get_mouse_position() + var current_scene := scene() + if current_scene != null: + return current_scene.get_viewport().get_mouse_position() + return Vector2.ZERO func get_global_mouse_position() -> Vector2: - return _scene_tree.root.get_mouse_position() + return Engine.get_main_loop().root.get_mouse_position() func simulate_mouse_move(pos :Vector2) -> GdUnitSceneRunner: @@ -148,19 +186,28 @@ func simulate_mouse_move(pos :Vector2) -> GdUnitSceneRunner: return _handle_input_event(event) -func simulate_mouse_move_relative(relative :Vector2, speed :Vector2 = Vector2.ONE) -> GdUnitSceneRunner: - if _last_input_event is InputEventMouse: - var current_pos :Vector2 = _last_input_event.position - var final_pos := current_pos + relative - var delta_milli := speed.x * 0.1 - var t := 0.0 - while not current_pos.is_equal_approx(final_pos): - t += delta_milli * speed.x - simulate_mouse_move(current_pos) - await _scene_tree.create_timer(delta_milli).timeout - current_pos = current_pos.lerp(final_pos, t) - simulate_mouse_move(final_pos) - await _scene_tree.process_frame +func simulate_mouse_move_relative(relative: Vector2, time: float = 1.0, trans_type: Tween.TransitionType = Tween.TRANS_LINEAR) -> GdUnitSceneRunner: + var tween := _scene_tree().create_tween() + _curent_mouse_position = get_mouse_position() + var final_position := _curent_mouse_position + relative + tween.tween_property(self, "_curent_mouse_position", final_position, time).set_trans(trans_type) + tween.play() + + while not get_mouse_position().is_equal_approx(final_position): + simulate_mouse_move(_curent_mouse_position) + await _scene_tree().process_frame + return self + + +func simulate_mouse_move_absolute(position: Vector2, time: float = 1.0, trans_type: Tween.TransitionType = Tween.TRANS_LINEAR) -> GdUnitSceneRunner: + var tween := _scene_tree().create_tween() + _curent_mouse_position = get_mouse_position() + tween.tween_property(self, "_curent_mouse_position", position, time).set_trans(trans_type) + tween.play() + + while not get_mouse_position().is_equal_approx(position): + simulate_mouse_move(_curent_mouse_position) + await _scene_tree().process_frame return self @@ -205,37 +252,60 @@ func simulate_frames(frames: int, delta_milli :int = -1) -> GdUnitSceneRunner: var time_shift_frames :int = max(1, frames / _time_factor) for frame in time_shift_frames: if delta_milli == -1: - await _scene_tree.process_frame + await _scene_tree().process_frame else: - await _scene_tree.create_timer(delta_milli * 0.001).timeout + await _scene_tree().create_timer(delta_milli * 0.001).timeout return self -func simulate_until_signal(signal_name :String, arg0=NO_ARG, arg1=NO_ARG, arg2=NO_ARG, arg3=NO_ARG, arg4=NO_ARG, arg5=NO_ARG, arg6=NO_ARG, arg7=NO_ARG, arg8=NO_ARG, arg9=NO_ARG) -> GdUnitSceneRunner: - var args = GdArrayTools.filter_value([arg0,arg1,arg2,arg3,arg4,arg5,arg6,arg7,arg8,arg9], NO_ARG) - await _awaiter.await_signal_idle_frames(_current_scene, signal_name, args, 10000) +func simulate_until_signal( + signal_name :String, + arg0 :Variant = NO_ARG, + arg1 :Variant = NO_ARG, + arg2 :Variant = NO_ARG, + arg3 :Variant = NO_ARG, + arg4 :Variant = NO_ARG, + arg5 :Variant = NO_ARG, + arg6 :Variant = NO_ARG, + arg7 :Variant = NO_ARG, + arg8 :Variant = NO_ARG, + arg9 :Variant = NO_ARG) -> GdUnitSceneRunner: + var args :Array = GdArrayTools.filter_value([arg0,arg1,arg2,arg3,arg4,arg5,arg6,arg7,arg8,arg9], NO_ARG) + await _awaiter.await_signal_idle_frames(scene(), signal_name, args, 10000) return self -func simulate_until_object_signal(source :Object, signal_name :String, arg0=NO_ARG, arg1=NO_ARG, arg2=NO_ARG, arg3=NO_ARG, arg4=NO_ARG, arg5=NO_ARG, arg6=NO_ARG, arg7=NO_ARG, arg8=NO_ARG, arg9=NO_ARG) -> GdUnitSceneRunner: - var args = GdArrayTools.filter_value([arg0,arg1,arg2,arg3,arg4,arg5,arg6,arg7,arg8,arg9], NO_ARG) +func simulate_until_object_signal( + source :Object, + signal_name :String, + arg0 :Variant = NO_ARG, + arg1 :Variant = NO_ARG, + arg2 :Variant = NO_ARG, + arg3 :Variant = NO_ARG, + arg4 :Variant = NO_ARG, + arg5 :Variant = NO_ARG, + arg6 :Variant = NO_ARG, + arg7 :Variant = NO_ARG, + arg8 :Variant = NO_ARG, + arg9 :Variant = NO_ARG) -> GdUnitSceneRunner: + var args :Array = GdArrayTools.filter_value([arg0,arg1,arg2,arg3,arg4,arg5,arg6,arg7,arg8,arg9], NO_ARG) await _awaiter.await_signal_idle_frames(source, signal_name, args, 10000) return self func await_func(func_name :String, args := []) -> GdUnitFuncAssert: - return GdUnitFuncAssertImpl.new(_current_scene, func_name, args) + return GdUnitFuncAssertImpl.new(scene(), func_name, args) func await_func_on(instance :Object, func_name :String, args := []) -> GdUnitFuncAssert: return GdUnitFuncAssertImpl.new(instance, func_name, args) -func await_signal(signal_name :String, args := [], timeout := 2000 ): - await _awaiter.await_signal_on(_current_scene, signal_name, args, timeout) +func await_signal(signal_name :String, args := [], timeout := 2000 ) -> void: + await _awaiter.await_signal_on(scene(), signal_name, args, timeout) -func await_signal_on(source :Object, signal_name :String, args := [], timeout := 2000 ): +func await_signal_on(source :Object, signal_name :String, args := [], timeout := 2000 ) -> void: await _awaiter.await_signal_on(source, signal_name, args, timeout) @@ -247,37 +317,48 @@ func maximize_view() -> GdUnitSceneRunner: func _property_exists(name :String) -> bool: - return _current_scene.get_property_list().any(func(properties :Dictionary) : return properties["name"] == name) + return scene().get_property_list().any(func(properties :Dictionary) -> bool: return properties["name"] == name) func get_property(name :String) -> Variant: if not _property_exists(name): return "The property '%s' not exist checked loaded scene." % name - return _current_scene.get(name) + return scene().get(name) func set_property(name :String, value :Variant) -> bool: if not _property_exists(name): push_error("The property named '%s' cannot be set, it does not exist!" % name) return false; - _current_scene.set(name, value) + scene().set(name, value) return true -func invoke(name :String, arg0=NO_ARG, arg1=NO_ARG, arg2=NO_ARG, arg3=NO_ARG, arg4=NO_ARG, arg5=NO_ARG, arg6=NO_ARG, arg7=NO_ARG, arg8=NO_ARG, arg9=NO_ARG): - var args = GdArrayTools.filter_value([arg0,arg1,arg2,arg3,arg4,arg5,arg6,arg7,arg8,arg9], NO_ARG) - if _current_scene.has_method(name): - return _current_scene.callv(name, args) +func invoke( + name :String, + arg0 :Variant = NO_ARG, + arg1 :Variant = NO_ARG, + arg2 :Variant = NO_ARG, + arg3 :Variant = NO_ARG, + arg4 :Variant = NO_ARG, + arg5 :Variant = NO_ARG, + arg6 :Variant = NO_ARG, + arg7 :Variant = NO_ARG, + arg8 :Variant = NO_ARG, + arg9 :Variant = NO_ARG) -> Variant: + var args :Array = GdArrayTools.filter_value([arg0,arg1,arg2,arg3,arg4,arg5,arg6,arg7,arg8,arg9], NO_ARG) + if scene().has_method(name): + return scene().callv(name, args) return "The method '%s' not exist checked loaded scene." % name func find_child(name :String, recursive :bool = true, owned :bool = false) -> Node: - return _current_scene.find_child(name, recursive, owned) + return scene().find_child(name, recursive, owned) func _scene_name() -> String: - var scene_script :GDScript = _current_scene.get_script() - var scene_name :String = _current_scene.get_name() + var scene_script :GDScript = scene().get_script() + var scene_name :String = scene().get_name() if not scene_script: return scene_name if not scene_name.begins_with("@"): @@ -312,7 +393,7 @@ func _apply_input_mouse_mask(event :InputEvent) -> void: if _last_input_event is InputEventMouse and event is InputEventMouse: event.button_mask |= _last_input_event.button_mask if event is InputEventMouseButton: - var button_mask = MAP_MOUSE_BUTTON_MASKS.get(event.get_button_index(), 0) + var button_mask :int = MAP_MOUSE_BUTTON_MASKS.get(event.get_button_index(), 0) if event.is_pressed(): event.button_mask |= button_mask else: @@ -325,20 +406,37 @@ func _apply_input_mouse_position(event :InputEvent) -> void: event.position = _last_input_event.position +## handle input action via Input modifieres +func _handle_actions(event :InputEventAction) -> bool: + if not InputMap.event_is_action(event, event.action, true): + return false + __print(" process action %s (%s) <- %s" % [scene(), _scene_name(), event.as_text()]) + if event.is_pressed(): + Input.action_press(event.action, InputMap.action_get_deadzone(event.action)) + else: + Input.action_release(event.action) + return true + + # for handling read https://docs.godotengine.org/en/stable/tutorials/inputs/inputevent.html?highlight=inputevent#how-does-it-work -func _handle_input_event(event :InputEvent): +func _handle_input_event(event :InputEvent) -> GdUnitSceneRunner: if event is InputEventMouse: Input.warp_mouse(event.position) Input.parse_input_event(event) + + if event is InputEventAction: + _handle_actions(event) + Input.flush_buffered_events() - - if is_instance_valid(_current_scene): - __print(" process event %s (%s) <- %s" % [_current_scene, _scene_name(), event.as_text()]) - if(_current_scene.has_method("_gui_input")): - _current_scene._gui_input(event) - if(_current_scene.has_method("_unhandled_input")): - _current_scene._unhandled_input(event) - _current_scene.get_viewport().set_input_as_handled() + var current_scene := scene() + if is_instance_valid(current_scene): + __print(" process event %s (%s) <- %s" % [current_scene, _scene_name(), event.as_text()]) + if(current_scene.has_method("_gui_input")): + current_scene._gui_input(event) + if(current_scene.has_method("_unhandled_input")): + current_scene._unhandled_input(event) + current_scene.get_viewport().set_input_as_handled() + # save last input event needs to be merged with next InputEventMouseButton _last_input_event = event return self @@ -346,16 +444,23 @@ func _handle_input_event(event :InputEvent): func _reset_input_to_default() -> void: # reset all mouse button to inital state if need - for m_button in _mouse_button_on_press.duplicate(): + for m_button :int in _mouse_button_on_press.duplicate(): if Input.is_mouse_button_pressed(m_button): simulate_mouse_button_release(m_button) _mouse_button_on_press.clear() - - for key_scancode in _key_on_press.duplicate(): + + for key_scancode :int in _key_on_press.duplicate(): if Input.is_key_pressed(key_scancode): simulate_key_release(key_scancode) _key_on_press.clear() + + for action :String in _action_on_press.duplicate(): + if Input.is_action_pressed(action): + simulate_action_release(action) + _action_on_press.clear() + Input.flush_buffered_events() + _last_input_event = null func __print(message :String) -> void: @@ -366,7 +471,7 @@ func __print(message :String) -> void: func __print_current_focus() -> void: if not _verbose: return - var focused_node = _current_scene.get_viewport().gui_get_focus_owner() + var focused_node := scene().get_viewport().gui_get_focus_owner() if focused_node: prints(" focus checked %s" % focused_node) else: @@ -374,4 +479,8 @@ func __print_current_focus() -> void: func scene() -> Node: - return _current_scene + if is_instance_valid(_current_scene): + return _current_scene + if not _is_disposed: + push_error("The current scene instance is not valid anymore! check your test is valid. e.g. check for missing awaits.") + return null diff --git a/addons/gdUnit4/src/core/GdUnitSettings.gd b/addons/gdUnit4/src/core/GdUnitSettings.gd index d9466a12..cbb6abc9 100644 --- a/addons/gdUnit4/src/core/GdUnitSettings.gd +++ b/addons/gdUnit4/src/core/GdUnitSettings.gd @@ -2,6 +2,7 @@ class_name GdUnitSettings extends RefCounted + const MAIN_CATEGORY = "gdunit4" # Common Settings const COMMON_SETTINGS = MAIN_CATEGORY + "/settings" @@ -14,6 +15,7 @@ const GROUP_TEST = COMMON_SETTINGS + "/test" const TEST_TIMEOUT = GROUP_TEST + "/test_timeout_seconds" const TEST_LOOKUP_FOLDER = GROUP_TEST + "/test_lookup_folder" const TEST_SITE_NAMING_CONVENTION = GROUP_TEST + "/test_suite_naming_convention" +const TEST_DISCOVER_ENABLED = GROUP_TEST + "/test_discovery" # Report Setiings @@ -44,6 +46,8 @@ const TEMPLATE_TS_CS = TEMPLATES_TS + "/CSharpScript" const UI_SETTINGS = MAIN_CATEGORY + "/ui" const GROUP_UI_INSPECTOR = UI_SETTINGS + "/inspector" const INSPECTOR_NODE_COLLAPSE = GROUP_UI_INSPECTOR + "/node_collapse" +const INSPECTOR_TREE_VIEW_MODE = GROUP_UI_INSPECTOR + "/tree_view_mode" +const INSPECTOR_TREE_SORT_MODE = GROUP_UI_INSPECTOR + "/tree_sort_mode" # Shortcut Setiings @@ -86,20 +90,28 @@ enum NAMING_CONVENTIONS { } -static func setup(): +static func setup() -> void: create_property_if_need(UPDATE_NOTIFICATION_ENABLED, true, "Enables/Disables the update notification checked startup.") create_property_if_need(SERVER_TIMEOUT, DEFAULT_SERVER_TIMEOUT, "Sets the server connection timeout in minutes.") create_property_if_need(TEST_TIMEOUT, DEFAULT_TEST_TIMEOUT, "Sets the test case runtime timeout in seconds.") create_property_if_need(TEST_LOOKUP_FOLDER, DEFAULT_TEST_LOOKUP_FOLDER, HELP_TEST_LOOKUP_FOLDER) create_property_if_need(TEST_SITE_NAMING_CONVENTION, NAMING_CONVENTIONS.AUTO_DETECT, "Sets test-suite genrate script name convention.", NAMING_CONVENTIONS.keys()) + create_property_if_need(TEST_DISCOVER_ENABLED, false, "Enables/Disables the automatic detection of tests by finding tests in test lookup folders at runtime.") create_property_if_need(REPORT_PUSH_ERRORS, false, "Enables/Disables report of push_error() as failure!") create_property_if_need(REPORT_SCRIPT_ERRORS, true, "Enables/Disables report of script errors as failure!") create_property_if_need(REPORT_ORPHANS, true, "Enables/Disables orphan reporting.") create_property_if_need(REPORT_ASSERT_ERRORS, true, "Enables/Disables error reporting checked asserts.") create_property_if_need(REPORT_ASSERT_WARNINGS, true, "Enables/Disables warning reporting checked asserts") create_property_if_need(REPORT_ASSERT_STRICT_NUMBER_TYPE_COMPARE, true, "Enabled/disabled number values will be compared strictly by type. (real vs int)") - create_property_if_need(INSPECTOR_NODE_COLLAPSE, true, "Enables/Disables that the testsuite node is closed after a successful test run.") - create_property_if_need(INSPECTOR_TOOLBAR_BUTTON_RUN_OVERALL, false, "Shows/Hides the 'Run overall Tests' button in the inspector toolbar.") + # inspector + create_property_if_need(INSPECTOR_NODE_COLLAPSE, true, + "Enables/Disables that the testsuite node is closed after a successful test run.") + create_property_if_need(INSPECTOR_TREE_VIEW_MODE, GdUnitInspectorTreeConstants.TREE_VIEW_MODE.TREE, + "Sets the inspector panel presentation.", GdUnitInspectorTreeConstants.TREE_VIEW_MODE.keys()) + create_property_if_need(INSPECTOR_TREE_SORT_MODE, GdUnitInspectorTreeConstants.SORT_MODE.UNSORTED, + "Sets the inspector panel presentation.", GdUnitInspectorTreeConstants.SORT_MODE.keys()) + create_property_if_need(INSPECTOR_TOOLBAR_BUTTON_RUN_OVERALL, false, + "Shows/Hides the 'Run overall Tests' button in the inspector toolbar.") create_property_if_need(TEMPLATE_TS_GD, GdUnitTestSuiteTemplate.default_GD_template(), "Defines the test suite template") create_shortcut_properties_if_need() migrate_properties() @@ -112,7 +124,7 @@ static func migrate_properties() -> void: TEST_LOOKUP_FOLDER,\ DEFAULT_TEST_LOOKUP_FOLDER,\ HELP_TEST_LOOKUP_FOLDER,\ - func(value): return DEFAULT_TEST_LOOKUP_FOLDER if value == null else value) + func(value :Variant) -> String: return DEFAULT_TEST_LOOKUP_FOLDER if value == null else value) static func create_shortcut_properties_if_need() -> void: @@ -176,6 +188,28 @@ static func set_log_path(path :String) -> void: ProjectSettings.save() +static func set_inspector_tree_sort_mode(sort_mode: GdUnitInspectorTreeConstants.SORT_MODE) -> void: + var property := get_property(INSPECTOR_TREE_SORT_MODE) + property.set_value(sort_mode) + update_property(property) + + +static func get_inspector_tree_sort_mode() -> GdUnitInspectorTreeConstants.SORT_MODE: + var property := get_property(INSPECTOR_TREE_SORT_MODE) + return property.value() if property != null else GdUnitInspectorTreeConstants.SORT_MODE.UNSORTED + + +static func set_inspector_tree_view_mode(tree_view_mode: GdUnitInspectorTreeConstants.TREE_VIEW_MODE) -> void: + var property := get_property(INSPECTOR_TREE_VIEW_MODE) + property.set_value(tree_view_mode) + update_property(property) + + +static func get_inspector_tree_view_mode() -> GdUnitInspectorTreeConstants.TREE_VIEW_MODE: + var property := get_property(INSPECTOR_TREE_VIEW_MODE) + return property.value() if property != null else GdUnitInspectorTreeConstants.TREE_VIEW_MODE.TREE + + # the configured server connection timeout in ms static func server_timeout() -> int: return get_setting(SERVER_TIMEOUT, DEFAULT_SERVER_TIMEOUT) * 60 * 1000 @@ -223,6 +257,16 @@ static func is_inspector_toolbar_button_show() -> bool: return get_setting(INSPECTOR_TOOLBAR_BUTTON_RUN_OVERALL, true) +static func is_test_discover_enabled() -> bool: + return get_setting(TEST_DISCOVER_ENABLED, false) + + +static func set_test_discover_enabled(enable :bool) -> void: + var property := get_property(TEST_DISCOVER_ENABLED) + property.set_value(enable) + update_property(property) + + static func is_log_enabled() -> bool: return ProjectSettings.get_setting(STDOUT_ENABLE_TO_FILE) @@ -232,7 +276,7 @@ static func list_settings(category :String) -> Array[GdUnitProperty]: for property in ProjectSettings.get_property_list(): var property_name :String = property["name"] if property_name.begins_with(category): - var value = ProjectSettings.get_setting(property_name) + var value :Variant = ProjectSettings.get_setting(property_name) var default :Variant = ProjectSettings.property_get_revert(property_name) var help :String = property["hint_string"] var value_set := extract_value_set_from_help(help) @@ -285,27 +329,27 @@ static func validate_lookup_folder(value :String) -> Variant: return null -static func save_property(name :String, value) -> void: +static func save_property(name :String, value :Variant) -> void: ProjectSettings.set_setting(name, value) _save_settings() static func _save_settings() -> void: - var err = ProjectSettings.save() + var err := ProjectSettings.save() if err != OK: push_error("Save GdUnit4 settings failed : %s" % error_string(err)) return static func has_property(name :String) -> bool: - return ProjectSettings.get_property_list().any( func(property): return property["name"] == name) + return ProjectSettings.get_property_list().any(func(property :Dictionary) -> bool: return property["name"] == name) static func get_property(name :String) -> GdUnitProperty: for property in ProjectSettings.get_property_list(): - var property_name = property["name"] + var property_name :String = property["name"] if property_name == name: - var value = ProjectSettings.get_setting(property_name) + var value :Variant = ProjectSettings.get_setting(property_name) var default :Variant = ProjectSettings.property_get_revert(property_name) var help :String = property["hint_string"] var value_set := extract_value_set_from_help(help) @@ -318,7 +362,7 @@ static func migrate_property(old_property :String, new_property :String, default if property == null: prints("Migration not possible, property '%s' not found" % old_property) return - var value = converter.call(property.value()) if converter.is_valid() else property.value() + var value :Variant = converter.call(property.value()) if converter.is_valid() else property.value() ProjectSettings.set_setting(new_property, value) ProjectSettings.set_initial_value(new_property, default_value) set_help(new_property, value, help) diff --git a/addons/gdUnit4/src/core/GdUnitSignalAwaiter.gd b/addons/gdUnit4/src/core/GdUnitSignalAwaiter.gd index e8ef765f..b2bb8348 100644 --- a/addons/gdUnit4/src/core/GdUnitSignalAwaiter.gd +++ b/addons/gdUnit4/src/core/GdUnitSignalAwaiter.gd @@ -1,22 +1,32 @@ class_name GdUnitSignalAwaiter extends RefCounted -signal signal_emitted(action) +signal signal_emitted(action :Variant) const NO_ARG :Variant = GdUnitConstants.NO_ARG -var _wait_on_idle_frame = false +var _wait_on_idle_frame := false var _interrupted := false -var _time_left := 0 +var _time_left :float = 0 var _timeout_millis :int -func _init(timeout_millis :int, wait_on_idle_frame := false): +func _init(timeout_millis :int, wait_on_idle_frame := false) -> void: _timeout_millis = timeout_millis _wait_on_idle_frame = wait_on_idle_frame -func _on_signal_emmited(arg0=NO_ARG, arg1=NO_ARG, arg2=NO_ARG, arg3=NO_ARG, arg4=NO_ARG, arg5=NO_ARG, arg6=NO_ARG, arg7=NO_ARG, arg8=NO_ARG, arg9=NO_ARG): +func _on_signal_emmited( + arg0 :Variant = NO_ARG, + arg1 :Variant = NO_ARG, + arg2 :Variant = NO_ARG, + arg3 :Variant = NO_ARG, + arg4 :Variant = NO_ARG, + arg5 :Variant = NO_ARG, + arg6 :Variant = NO_ARG, + arg7 :Variant = NO_ARG, + arg8 :Variant = NO_ARG, + arg9 :Variant = NO_ARG) -> void: var signal_args :Variant = GdArrayTools.filter_value([arg0,arg1,arg2,arg3,arg4,arg5,arg6,arg7,arg8,arg9], NO_ARG) signal_emitted.emit(signal_args) @@ -25,7 +35,7 @@ func is_interrupted() -> bool: return _interrupted -func elapsed_time() -> int: +func elapsed_time() -> float: return _time_left @@ -33,16 +43,13 @@ func on_signal(source :Object, signal_name :String, expected_signal_args :Array) # register checked signal to wait for source.connect(signal_name, _on_signal_emmited) # install timeout timer - var timer = Timer.new() + var timer := Timer.new() Engine.get_main_loop().root.add_child(timer) timer.add_to_group("GdUnitTimers") timer.set_one_shot(true) - timer.timeout.connect(func do_interrupt(): - _interrupted = true - signal_emitted.emit(null) - , CONNECT_DEFERRED) + timer.timeout.connect(_do_interrupt, CONNECT_DEFERRED) timer.start(_timeout_millis * 0.001 * Engine.get_time_scale()) - + # holds the emited value var value :Variant # wait for signal is emitted or a timeout is happen @@ -55,10 +62,15 @@ func on_signal(source :Object, signal_name :String, expected_signal_args :Array) if expected_signal_args.size() == 0 or GdObjects.equals(value, expected_signal_args): break await Engine.get_main_loop().process_frame - + source.disconnect(signal_name, _on_signal_emmited) _time_left = timer.time_left await Engine.get_main_loop().process_frame if value is Array and value.size() == 1: return value[0] return value + + +func _do_interrupt() -> void: + _interrupted = true + signal_emitted.emit(null) diff --git a/addons/gdUnit4/src/core/GdUnitSignalCollector.gd b/addons/gdUnit4/src/core/GdUnitSignalCollector.gd index cf55adcf..12e22b0e 100644 --- a/addons/gdUnit4/src/core/GdUnitSignalCollector.gd +++ b/addons/gdUnit4/src/core/GdUnitSignalCollector.gd @@ -16,14 +16,14 @@ var _collected_signals :Dictionary = {} func clear() -> void: - for emitter in _collected_signals.keys(): + for emitter :Object in _collected_signals.keys(): if is_instance_valid(emitter): unregister_emitter(emitter) # connect to all possible signals defined by the emitter # prepares the signal collection to store received signals and arguments -func register_emitter(emitter :Object): +func register_emitter(emitter :Object) -> void: if is_instance_valid(emitter): # check emitter is already registerd if _collected_signals.has(emitter): @@ -34,7 +34,7 @@ func register_emitter(emitter :Object): emitter.tree_exiting.connect(unregister_emitter.bind(emitter)) # connect to all signals of the emitter we want to collect for signal_def in emitter.get_signal_list(): - var signal_name = signal_def["name"] + var signal_name :String = signal_def["name"] # set inital collected to empty if not is_signal_collecting(emitter, signal_name): _collected_signals[emitter][signal_name] = Array() @@ -48,32 +48,45 @@ func register_emitter(emitter :Object): # unregister all acquired resources/connections, otherwise it ends up in orphans # is called when the emitter is removed from the parent -func unregister_emitter(emitter :Object): +func unregister_emitter(emitter :Object) -> void: if is_instance_valid(emitter): for signal_def in emitter.get_signal_list(): - var signal_name = signal_def["name"] + var signal_name :String = signal_def["name"] if emitter.is_connected(signal_name, _on_signal_emmited): emitter.disconnect(signal_name, _on_signal_emmited.bind(emitter, signal_name)) _collected_signals.erase(emitter) # receives the signal from the emitter with all emitted signal arguments and additional the emitter and signal_name as last two arguements -func _on_signal_emmited( arg0=NO_ARG, arg1=NO_ARG, arg2=NO_ARG, arg3=NO_ARG, arg4=NO_ARG, arg5=NO_ARG, arg6=NO_ARG, arg7=NO_ARG, arg8=NO_ARG, arg9=NO_ARG, arg10=NO_ARG, arg11=NO_ARG): - var signal_args = GdArrayTools.filter_value([arg0,arg1,arg2,arg3,arg4,arg5,arg6,arg7,arg8,arg9,arg10,arg11], NO_ARG) +func _on_signal_emmited( + arg0 :Variant= NO_ARG, + arg1 :Variant= NO_ARG, + arg2 :Variant= NO_ARG, + arg3 :Variant= NO_ARG, + arg4 :Variant= NO_ARG, + arg5 :Variant= NO_ARG, + arg6 :Variant= NO_ARG, + arg7 :Variant= NO_ARG, + arg8 :Variant= NO_ARG, + arg9 :Variant= NO_ARG, + arg10 :Variant= NO_ARG, + arg11 :Variant= NO_ARG) -> void: + var signal_args :Array = GdArrayTools.filter_value([arg0,arg1,arg2,arg3,arg4,arg5,arg6,arg7,arg8,arg9,arg10,arg11], NO_ARG) # extract the emitter and signal_name from the last two arguments (see line 61 where is added) var signal_name :String = signal_args.pop_back() var emitter :Object = signal_args.pop_back() - # prints("_on_signal_emmited:", emitter, signal_name, signal_args) + #prints("_on_signal_emmited:", emitter, signal_name, signal_args) if is_signal_collecting(emitter, signal_name): _collected_signals[emitter][signal_name].append(signal_args) -func reset_received_signals(emitter :Object): - # _debug_signal_list("before claer"); +func reset_received_signals(emitter :Object, signal_name: String, signal_args :Array) -> void: + #_debug_signal_list("before claer"); if _collected_signals.has(emitter): - for signal_name in _collected_signals[emitter]: - _collected_signals[emitter][signal_name].clear() - # _debug_signal_list("after claer"); + var signals_by_emitter :Dictionary = _collected_signals[emitter] + if signals_by_emitter.has(signal_name): + _collected_signals[emitter][signal_name].erase(signal_args) + #_debug_signal_list("after claer"); func is_signal_collecting(emitter :Object, signal_name :String) -> bool: @@ -84,19 +97,19 @@ func match(emitter :Object, signal_name :String, args :Array) -> bool: #prints("match", signal_name, _collected_signals[emitter][signal_name]); if _collected_signals.is_empty() or not _collected_signals.has(emitter): return false - for received_args in _collected_signals[emitter][signal_name]: - # prints("testing", signal_name, received_args, "vs", args) + for received_args :Variant in _collected_signals[emitter][signal_name]: + #prints("testing", signal_name, received_args, "vs", args) if GdObjects.equals(received_args, args): return true return false -func _debug_signal_list(message :String): +func _debug_signal_list(message :String) -> void: prints("-----", message, "-------") prints("senders {") - for emitter in _collected_signals: + for emitter :Object in _collected_signals: prints("\t", emitter) - for signal_name in _collected_signals[emitter]: - var args = _collected_signals[emitter][signal_name] + for signal_name :String in _collected_signals[emitter]: + var args :Variant = _collected_signals[emitter][signal_name] prints("\t\t", signal_name, args) prints("}") diff --git a/addons/gdUnit4/src/core/GdUnitSignals.gd b/addons/gdUnit4/src/core/GdUnitSignals.gd index faf089f4..652b7af6 100644 --- a/addons/gdUnit4/src/core/GdUnitSignals.gd +++ b/addons/gdUnit4/src/core/GdUnitSignals.gd @@ -1,17 +1,26 @@ class_name GdUnitSignals extends RefCounted +@warning_ignore("unused_signal") signal gdunit_client_connected(client_id :int) +@warning_ignore("unused_signal") signal gdunit_client_disconnected(client_id :int) +@warning_ignore("unused_signal") signal gdunit_client_terminated() +@warning_ignore("unused_signal") signal gdunit_event(event :GdUnitEvent) +@warning_ignore("unused_signal") signal gdunit_event_debug(event :GdUnitEvent) +@warning_ignore("unused_signal") signal gdunit_add_test_suite(test_suite :GdUnitTestSuiteDto) +@warning_ignore("unused_signal") signal gdunit_message(message :String) +@warning_ignore("unused_signal") signal gdunit_report(execution_context_id :int, report :GdUnitReport) +@warning_ignore("unused_signal") signal gdunit_set_test_failed(is_failed :bool) - +@warning_ignore("unused_signal") signal gdunit_settings_changed(property :GdUnitProperty) const META_KEY := "GdUnitSignals" @@ -31,4 +40,5 @@ static func dispose() -> void: for signal_ in signals.get_signal_list(): for connection in signals.get_signal_connection_list(signal_["name"]): connection["signal"].disconnect(connection["callable"]) + signals = null Engine.remove_meta(META_KEY) diff --git a/addons/gdUnit4/src/core/GdUnitSingleton.gd b/addons/gdUnit4/src/core/GdUnitSingleton.gd index 4fb05619..ed6103e7 100644 --- a/addons/gdUnit4/src/core/GdUnitSingleton.gd +++ b/addons/gdUnit4/src/core/GdUnitSingleton.gd @@ -1,11 +1,11 @@ ################################################################################ -# Provides access to a global accessible singleton -# -# This is a workarount to the existing auto load singleton because of some bugs -# around plugin handling +# Provides access to a global accessible singleton +# +# This is a workarount to the existing auto load singleton because of some bugs +# around plugin handling ################################################################################ class_name GdUnitSingleton -extends RefCounted +extends Object const GdUnitTools := preload("res://addons/gdUnit4/src/core/GdUnitTools.gd") @@ -16,6 +16,10 @@ static func instance(name :String, clazz :Callable) -> Variant: if Engine.has_meta(name): return Engine.get_meta(name) var singleton :Variant = clazz.call() + if is_instance_of(singleton, RefCounted): + push_error("Invalid singleton implementation detected for '%s' is `%s`!" % [name, singleton.get_class()]) + return + Engine.set_meta(name, singleton) GdUnitTools.prints_verbose("Register singleton '%s:%s'" % [name, singleton]) var singletons :PackedStringArray = Engine.get_meta(MEATA_KEY, PackedStringArray()) @@ -24,26 +28,26 @@ static func instance(name :String, clazz :Callable) -> Variant: return singleton -static func unregister(p_singleton :String) -> void: +static func unregister(p_singleton :String, use_call_deferred :bool = false) -> void: var singletons :PackedStringArray = Engine.get_meta(MEATA_KEY, PackedStringArray()) if singletons.has(p_singleton): GdUnitTools.prints_verbose("\n Unregister singleton '%s'" % p_singleton); var index := singletons.find(p_singleton) singletons.remove_at(index) - var instance_ :Variant = Engine.get_meta(p_singleton) + var instance_ :Object = Engine.get_meta(p_singleton) GdUnitTools.prints_verbose(" Free singleton instance '%s:%s'" % [p_singleton, instance_]) - GdUnitTools.free_instance(instance_) + GdUnitTools.free_instance(instance_, use_call_deferred) Engine.remove_meta(p_singleton) GdUnitTools.prints_verbose(" Successfully freed '%s'" % p_singleton) Engine.set_meta(MEATA_KEY, singletons) -static func dispose() -> void: +static func dispose(use_call_deferred :bool = false) -> void: # use a copy because unregister is modify the singletons array var singletons := PackedStringArray(Engine.get_meta(MEATA_KEY, PackedStringArray())) GdUnitTools.prints_verbose("----------------------------------------------------------------") GdUnitTools.prints_verbose("Cleanup singletons %s" % singletons) for singleton in singletons: - unregister(singleton) + unregister(singleton, use_call_deferred) Engine.remove_meta(MEATA_KEY) GdUnitTools.prints_verbose("----------------------------------------------------------------") diff --git a/addons/gdUnit4/src/core/GdUnitTestSuiteBuilder.gd b/addons/gdUnit4/src/core/GdUnitTestSuiteBuilder.gd index 7cd5dfdd..6cd2ee4b 100644 --- a/addons/gdUnit4/src/core/GdUnitTestSuiteBuilder.gd +++ b/addons/gdUnit4/src/core/GdUnitTestSuiteBuilder.gd @@ -6,9 +6,9 @@ static func create(source :Script, line_number :int) -> GdUnitResult: var test_suite_path := GdUnitTestSuiteScanner.resolve_test_suite_path(source.resource_path, GdUnitSettings.test_root_folder()) # we need to save and close the testsuite and source if is current opened before modify ScriptEditorControls.save_an_open_script(source.resource_path) - ScriptEditorControls.save_an_open_script(test_suite_path, true) + ScriptEditorControls.save_an_open_script(test_suite_path, true) if GdObjects.is_cs_script(source): - return GdUnit4MonoApiLoader.create_test_suite(source.resource_path, line_number+1, test_suite_path) + return GdUnit4CSharpApiLoader.create_test_suite(source.resource_path, line_number+1, test_suite_path) var parser := GdScriptParser.new() var lines := source.source_code.split("\n") var current_line := lines[line_number] diff --git a/addons/gdUnit4/src/core/GdUnitTestSuiteScanner.gd b/addons/gdUnit4/src/core/GdUnitTestSuiteScanner.gd index ff7ab6df..0f882090 100644 --- a/addons/gdUnit4/src/core/GdUnitTestSuiteScanner.gd +++ b/addons/gdUnit4/src/core/GdUnitTestSuiteScanner.gd @@ -17,28 +17,32 @@ const exclude_scan_directories = [ var _script_parser := GdScriptParser.new() -var _extends_test_suite_classes := Array() +var _included_resources :PackedStringArray = [] +var _excluded_resources :PackedStringArray = [] var _expression_runner := GdUnitExpressionRunner.new() +var _regex_extends_clazz_name := RegEx.create_from_string("extends[\\s]+([\\S]+)") -func scan_testsuite_classes() -> void: +func prescan_testsuite_classes() -> void: # scan and cache extends GdUnitTestSuite by class name an resource paths - _extends_test_suite_classes.append("GdUnitTestSuite") - if ProjectSettings.has_setting("_global_script_classes"): - var script_classes:Array = ProjectSettings.get_setting("_global_script_classes") as Array - for element in script_classes: - var script_meta = element as Dictionary - if script_meta["base"] == "GdUnitTestSuite": - _extends_test_suite_classes.append(script_meta["class"]) + var script_classes :Array[Dictionary] = ProjectSettings.get_global_class_list() + for script_meta in script_classes: + var base_class :String = script_meta["base"] + var resource_path :String = script_meta["path"] + if base_class == "GdUnitTestSuite": + _included_resources.append(resource_path) + elif ClassDB.class_exists(base_class): + _excluded_resources.append(resource_path) func scan(resource_path :String) -> Array[Node]: - scan_testsuite_classes() + prescan_testsuite_classes() # if single testsuite requested if FileAccess.file_exists(resource_path): var test_suite := _parse_is_test_suite(resource_path) - if test_suite: + if test_suite != null: return [test_suite] + return [] as Array[Node] var base_dir := DirAccess.open(resource_path) if base_dir == null: prints("Given directory or file does not exists:", resource_path) @@ -53,13 +57,13 @@ func _scan_test_suites(dir :DirAccess, collected_suites :Array[Node]) -> Array[N dir.list_dir_begin() # TODOGODOT4 fill missing arguments https://github.com/godotengine/godot/pull/40547 var file_name := dir.get_next() while file_name != "": - var resource_path = GdUnitTestSuiteScanner._file(dir, file_name) + var resource_path := GdUnitTestSuiteScanner._file(dir, file_name) if dir.current_is_dir(): var sub_dir := DirAccess.open(resource_path) if sub_dir != null: _scan_test_suites(sub_dir, collected_suites) else: - var time = LocalTime.now() + var time := LocalTime.now() var test_suite := _parse_is_test_suite(resource_path) if test_suite: collected_suites.append(test_suite) @@ -79,29 +83,44 @@ static func _file(dir :DirAccess, file_name :String) -> String: func _parse_is_test_suite(resource_path :String) -> Node: if not GdUnitTestSuiteScanner._is_script_format_supported(resource_path): return null - if GdUnit4MonoApiLoader.is_test_suite(resource_path): - return GdUnit4MonoApiLoader.parse_test_suite(resource_path) - var script :Script = ResourceLoader.load(resource_path) + if GdUnit4CSharpApiLoader.is_test_suite(resource_path): + return GdUnit4CSharpApiLoader.parse_test_suite(resource_path) + + # We use the global cache to fast scan for test suites. + if _excluded_resources.has(resource_path): + return null + # Check in the global class cache whether the GdUnitTestSuite class has been extended. + if _included_resources.has(resource_path): + return _parse_test_suite(ResourceLoader.load(resource_path)) + + # Otherwise we need to scan manual, we need to exclude classes where direct extends form Godot classes + # the resource loader can fail to load e.g. plugin classes with do preload other scripts + var extends_from := get_extends_classname(resource_path) + # If not extends is defined or extends from a Godot class + if extends_from.is_empty() or ClassDB.class_exists(extends_from): + return null + # Finally, we need to load the class to determine it is a test suite + var script := ResourceLoader.load(resource_path) if not GdObjects.is_test_suite(script): return null - if GdObjects.is_gd_script(script): - return _parse_test_suite(script) - return null + return _parse_test_suite(ResourceLoader.load(resource_path)) static func _is_script_format_supported(resource_path :String) -> bool: var ext := resource_path.get_extension() if ext == "gd": return true - return GdUnit4MonoApiLoader.is_csharp_file(resource_path) + return GdUnit4CSharpApiLoader.is_csharp_file(resource_path) func _parse_test_suite(script :GDScript) -> GdUnitTestSuite: - var test_suite = script.new() + if not GdObjects.is_test_suite(script): + return null + + var test_suite :GdUnitTestSuite = script.new() test_suite.set_name(GdUnitTestSuiteScanner.parse_test_suite_name(script)) - # find all test cases as array of names - var test_case_names := _extract_test_case_names(script) # add test cases to test suite and parse test case line nummber + var test_case_names := _extract_test_case_names(script) _parse_and_add_test_cases(test_suite, script, test_case_names) # not all test case parsed? # we have to scan the base class to @@ -128,11 +147,11 @@ static func parse_test_suite_name(script :Script) -> String: return script.resource_path.get_file().replace(".gd", "") -func _handle_test_suite_arguments(test_suite, script :GDScript, fd :GdFunctionDescriptor): +func _handle_test_suite_arguments(test_suite :Node, script :GDScript, fd :GdFunctionDescriptor) -> void: for arg in fd.args(): match arg.name(): _TestCase.ARGUMENT_SKIP: - var result = _expression_runner.execute(script, arg.value_as_string()) + var result :Variant = _expression_runner.execute(script, arg.value_as_string()) if result is bool: test_suite.__is_skipped = result else: @@ -143,7 +162,7 @@ func _handle_test_suite_arguments(test_suite, script :GDScript, fd :GdFunctionDe push_error("Unsuported argument `%s` found on before() at '%s'!" % [arg.name(), script.resource_path]) -func _handle_test_case_arguments(test_suite, script :GDScript, fd :GdFunctionDescriptor): +func _handle_test_case_arguments(test_suite :Node, script :GDScript, fd :GdFunctionDescriptor) -> void: var timeout := _TestCase.DEFAULT_TIMEOUT var iterations := Fuzzer.ITERATION_DEFAULT_COUNT var seed_value := -1 @@ -151,7 +170,7 @@ func _handle_test_case_arguments(test_suite, script :GDScript, fd :GdFunctionDes var skip_reason := "Unknown." var fuzzers :Array[GdFunctionArgument] = [] var test := _TestCase.new() - + for arg in fd.args(): # verify argument is allowed # is test using fuzzers? @@ -162,7 +181,7 @@ func _handle_test_case_arguments(test_suite, script :GDScript, fd :GdFunctionDes _TestCase.ARGUMENT_TIMEOUT: timeout = arg.default() _TestCase.ARGUMENT_SKIP: - var result = _expression_runner.execute(script, arg.value_as_string()) + var result :Variant = _expression_runner.execute(script, arg.value_as_string()) if result is bool: is_skipped = result else: @@ -175,21 +194,15 @@ func _handle_test_case_arguments(test_suite, script :GDScript, fd :GdFunctionDes seed_value = arg.default() # create new test test.configure(fd.name(), fd.line_number(), script.resource_path, timeout, fuzzers, iterations, seed_value) + test.set_function_descriptor(fd) test.skip(is_skipped, skip_reason) _validate_argument(fd, test) test_suite.add_child(test) - # is parameterized test? - if fd.is_parameterized(): - var test_paramaters := GdTestParameterSet.extract_test_parameters(test_suite.get_script(), fd) - var error := GdTestParameterSet.validate(fd.args(), test_paramaters) - if not error.is_empty(): - test.skip(true, error) - test.set_test_parameters(test_paramaters) -func _parse_and_add_test_cases(test_suite, script :GDScript, test_case_names :PackedStringArray): - var test_cases_to_find = Array(test_case_names) - var functions_to_scan := test_case_names +func _parse_and_add_test_cases(test_suite :Node, script :GDScript, test_case_names :PackedStringArray) -> void: + var test_cases_to_find := Array(test_case_names) + var functions_to_scan := test_case_names.duplicate() functions_to_scan.append("before") var source := _script_parser.load_source_code(script, [script.resource_path]) var function_descriptors := _script_parser.parse_functions(source, "", [script.resource_path], functions_to_scan) @@ -228,22 +241,22 @@ static func _to_naming_convention(file_name :String) -> String: static func resolve_test_suite_path(source_script_path :String, test_root_folder :String = "test") -> String: - var file_name = source_script_path.get_basename().get_file() + var file_name := source_script_path.get_basename().get_file() var suite_name := _to_naming_convention(file_name) if test_root_folder.is_empty() or test_root_folder == "/": return source_script_path.replace(file_name, suite_name) - + # is user tmp if source_script_path.begins_with("user://tmp"): return normalize_path(source_script_path.replace("user://tmp", "user://tmp/" + test_root_folder)).replace(file_name, suite_name) - + # at first look up is the script under a "src" folder located var test_suite_path :String - var src_folder = source_script_path.find("/src/") + var src_folder := source_script_path.find("/src/") if src_folder != -1: test_suite_path = source_script_path.replace("/src/", "/"+test_root_folder+"/") else: - var paths = source_script_path.split("/", false) + var paths := source_script_path.split("/", false) # is a plugin script? if paths[1] == "addons": test_suite_path = "%s//addons/%s/%s" % [paths[0], paths[2], test_root_folder] @@ -264,9 +277,9 @@ static func normalize_path(path :String) -> String: static func create_test_suite(test_suite_path :String, source_path :String) -> GdUnitResult: # create directory if not exists if not DirAccess.dir_exists_absolute(test_suite_path.get_base_dir()): - var error := DirAccess.make_dir_recursive_absolute(test_suite_path.get_base_dir()) - if error != OK: - return GdUnitResult.error("Can't create directoy at: %s. Error code %s" % [test_suite_path.get_base_dir(), error]) + var error_ := DirAccess.make_dir_recursive_absolute(test_suite_path.get_base_dir()) + if error_ != OK: + return GdUnitResult.error("Can't create directoy at: %s. Error code %s" % [test_suite_path.get_base_dir(), error_]) var script := GDScript.new() script.source_code = GdUnitTestSuiteTemplate.build_template(source_path) var error := ResourceSaver.save(script, test_suite_path) @@ -292,14 +305,30 @@ static func get_test_case_line_number(resource_path :String, func_name :String) return -1 +func get_extends_classname(resource_path :String) -> String: + var file := FileAccess.open(resource_path, FileAccess.READ) + if file != null: + while not file.eof_reached(): + var row := file.get_line() + # skip comments and empty lines + if row.begins_with("#") || row.length() == 0: + continue + # Stop at first function + if row.contains("func"): + return "" + var result := _regex_extends_clazz_name.search(row) + if result != null: + return result.get_string(1) + return "" + + static func add_test_case(resource_path :String, func_name :String) -> GdUnitResult: var script := load(resource_path) as GDScript # count all exiting lines and add two as space to add new test case var line_number := count_lines(script) + 2 var func_body := TEST_FUNC_TEMPLATE.replace("${func_name}", func_name) if Engine.is_editor_hint(): - var ep :EditorPlugin = Engine.get_meta("GdUnitEditorPlugin") - var settings := ep.get_editor_interface().get_editor_settings() + var settings := EditorInterface.get_editor_settings() var ident_type :int = settings.get_setting("text_editor/behavior/indent/type") var ident_size :int = settings.get_setting("text_editor/behavior/indent/size") if ident_type == 1: @@ -331,7 +360,7 @@ static func create_test_case(test_suite_path :String, func_name :String, source_ if test_case_exists(test_suite_path, func_name): var line_number := get_test_case_line_number(test_suite_path, func_name) return GdUnitResult.success({ "path" : test_suite_path, "line" : line_number}) - + if not test_suite_exists(test_suite_path): var result := create_test_suite(test_suite_path, source_script_path) if result.is_error(): diff --git a/addons/gdUnit4/src/core/GdUnitTools.gd b/addons/gdUnit4/src/core/GdUnitTools.gd index 1e4f660a..b3782716 100644 --- a/addons/gdUnit4/src/core/GdUnitTools.gd +++ b/addons/gdUnit4/src/core/GdUnitTools.gd @@ -1,183 +1,7 @@ extends RefCounted -const GDUNIT_TEMP := "user://tmp" - -static func temp_dir() -> String: - if not DirAccess.dir_exists_absolute(GDUNIT_TEMP): - DirAccess.make_dir_recursive_absolute(GDUNIT_TEMP) - return GDUNIT_TEMP - - -static func create_temp_dir(folder_name :String) -> String: - var new_folder = temp_dir() + "/" + folder_name - if not DirAccess.dir_exists_absolute(new_folder): - DirAccess.make_dir_recursive_absolute(new_folder) - return new_folder - - -static func clear_tmp(): - delete_directory(GDUNIT_TEMP) - - -# Creates a new file under -static func create_temp_file(relative_path :String, file_name :String, mode := FileAccess.WRITE) -> FileAccess: - var file_path := create_temp_dir(relative_path) + "/" + file_name - var file = FileAccess.open(file_path, mode) - if file == null: - push_error("Error creating temporary file at: %s, %s" % [file_path, error_as_string(FileAccess.get_open_error())]) - return file - - -static func current_dir() -> String: - return ProjectSettings.globalize_path("res://") - - -static func delete_directory(path :String, only_content := false) -> void: - var dir := DirAccess.open(path) - if dir != null: - dir.list_dir_begin() # TODOGODOT4 fill missing arguments https://github.com/godotengine/godot/pull/40547 - var file_name := "." - while file_name != "": - file_name = dir.get_next() - if file_name.is_empty() or file_name == "." or file_name == "..": - continue - var next := path + "/" +file_name - if dir.current_is_dir(): - delete_directory(next) - else: - # delete file - var err = dir.remove(next) - if err: - push_error("Delete %s failed: %s" % [next, error_as_string(err)]) - if not only_content: - var err := dir.remove(path) - if err: - push_error("Delete %s failed: %s" % [path, error_as_string(err)]) - - -static func copy_file(from_file :String, to_dir :String) -> GdUnitResult: - var dir := DirAccess.open(to_dir) - if dir != null: - var to_file := to_dir + "/" + from_file.get_file() - prints("Copy %s to %s" % [from_file, to_file]) - var error = dir.copy(from_file, to_file) - if error != OK: - return GdUnitResult.error("Can't copy file form '%s' to '%s'. Error: '%s'" % [from_file, to_file, error_as_string(error)]) - return GdUnitResult.success(to_file) - return GdUnitResult.error("Directory not found: " + to_dir) - - -static func copy_directory(from_dir :String, to_dir :String, recursive :bool = false) -> bool: - if not DirAccess.dir_exists_absolute(from_dir): - push_error("Source directory not found '%s'" % from_dir) - return false - - # check if destination exists - if not DirAccess.dir_exists_absolute(to_dir): - # create it - var err := DirAccess.make_dir_recursive_absolute(to_dir) - if err != OK: - push_error("Can't create directory '%s'. Error: %s" % [to_dir, error_as_string(err)]) - return false - var source_dir := DirAccess.open(from_dir) - var dest_dir := DirAccess.open(to_dir) - if source_dir != null: - source_dir.list_dir_begin() # TODOGODOT4 fill missing arguments https://github.com/godotengine/godot/pull/40547 - var next := "." - - while next != "": - next = source_dir.get_next() - if next == "" or next == "." or next == "..": - continue - var source := source_dir.get_current_dir() + "/" + next - var dest := dest_dir.get_current_dir() + "/" + next - if source_dir.current_is_dir(): - if recursive: - copy_directory(source + "/", dest, recursive) - continue - var err = source_dir.copy(source, dest) - if err != OK: - push_error("Error checked copy file '%s' to '%s'" % [source, dest]) - return false - - return true - else: - push_error("Directory not found: " + from_dir) - return false - - -# scans given path for sub directories by given prefix and returns the highest index numer -# e.g. -static func find_last_path_index(path :String, prefix :String) -> int: - var dir := DirAccess.open(path) - if dir == null: - return 0 - var last_iteration := 0 - dir.list_dir_begin() # TODOGODOT4 fill missing arguments https://github.com/godotengine/godot/pull/40547 - var next := "." - while next != "": - next = dir.get_next() - if next.is_empty() or next == "." or next == "..": - continue - if next.begins_with(prefix): - var iteration := next.split("_")[1].to_int() - if iteration > last_iteration: - last_iteration = iteration - return last_iteration - - -static func delete_path_index_lower_equals_than(path :String, prefix :String, index :int) -> int: - var dir := DirAccess.open(path) - if dir == null: - return 0 - var deleted := 0 - dir.list_dir_begin() # TODOGODOT4 fill missing arguments https://github.com/godotengine/godot/pull/40547 - var next := "." - while next != "": - next = dir.get_next() - if next.is_empty() or next == "." or next == "..": - continue - if next.begins_with(prefix): - var current_index := next.split("_")[1].to_int() - if current_index <= index: - deleted += 1 - delete_directory(path + "/" + next) - return deleted - - -static func scan_dir(path :String) -> PackedStringArray: - var dir := DirAccess.open(path) - if dir == null or not dir.dir_exists(path): - return PackedStringArray() - var content := PackedStringArray() - dir.list_dir_begin() # TODOGODOT4 fill missing arguments https://github.com/godotengine/godot/pull/40547 - var next := "." - while next != "": - next = dir.get_next() - if next.is_empty() or next == "." or next == "..": - continue - content.append(next) - return content - - -static func resource_as_array(resource_path :String) -> PackedStringArray: - var file := FileAccess.open(resource_path, FileAccess.READ) - if file == null: - push_error("ERROR: Can't read resource '%s'. %s" % [resource_path, error_as_string(FileAccess.get_open_error())]) - return PackedStringArray() - var file_content := PackedStringArray() - while not file.eof_reached(): - file_content.append(file.get_line()) - return file_content - - -static func resource_as_string(resource_path :String) -> String: - var file := FileAccess.open(resource_path, FileAccess.READ) - if file == null: - push_error("ERROR: Can't read resource '%s'. %s" % [resource_path, error_as_string(FileAccess.get_open_error())]) - return "" - return file.get_as_text(true) +static var _richtext_normalize: RegEx static func normalize_text(text :String) -> String: @@ -185,9 +9,9 @@ static func normalize_text(text :String) -> String: static func richtext_normalize(input :String) -> String: - return GdUnitSingleton.instance("regex_richtext", func _regex_richtext() -> RegEx: - return to_regex("\\[/?(b|color|bgcolor|right|table|cell).*?\\]") )\ - .sub(input, "", true).replace("\r", "") + if _richtext_normalize == null: + _richtext_normalize = to_regex("\\[/?(b|color|bgcolor|right|table|cell).*?\\]") + return _richtext_normalize.sub(input, "", true).replace("\r", "") static func to_regex(pattern :String) -> RegEx: @@ -203,9 +27,9 @@ static func prints_verbose(message :String) -> void: prints(message) -static func free_instance(instance :Variant, is_stdout_verbose :=false) -> bool: +static func free_instance(instance :Variant, use_call_deferred :bool = false, is_stdout_verbose := false) -> bool: if instance is Array: - for element in instance: + for element :Variant in instance: free_instance(element) instance.clear() return true @@ -219,8 +43,7 @@ static func free_instance(instance :Variant, is_stdout_verbose :=false) -> bool: print_verbose("GdUnit4:gc():free instance ", instance) release_double(instance) if instance is RefCounted: - instance.notification(Object.NOTIFICATION_PREDELETE) - await Engine.get_main_loop().process_frame + (instance as RefCounted).notification(Object.NOTIFICATION_PREDELETE) return true else: # is instance already freed? @@ -229,19 +52,30 @@ static func free_instance(instance :Variant, is_stdout_verbose :=false) -> bool: #release_connections(instance) if instance is Timer: instance.stop() - instance.call_deferred("free") - await Engine.get_main_loop().process_frame + if use_call_deferred: + instance.call_deferred("free") + else: + instance.free() + await Engine.get_main_loop().process_frame return true if instance is Node and instance.get_parent() != null: if is_stdout_verbose: print_verbose("GdUnit4:gc():remove node from parent ", instance.get_parent(), instance) - instance.get_parent().remove_child(instance) - instance.set_owner(null) - instance.free() + if use_call_deferred: + instance.get_parent().remove_child.call_deferred(instance) + #instance.call_deferred("set_owner", null) + else: + instance.get_parent().remove_child(instance) + if is_stdout_verbose: + print_verbose("GdUnit4:gc():freeing `free()` the instance ", instance) + if use_call_deferred: + instance.call_deferred("free") + else: + instance.free() return !is_instance_valid(instance) -static func _release_connections(instance :Object): +static func _release_connections(instance :Object) -> void: if is_instance_valid(instance): # disconnect from all connected signals to force freeing, otherwise it ends up in orphans for connection in instance.get_incoming_connections(): @@ -256,21 +90,23 @@ static func _release_connections(instance :Object): release_timers() -static func release_timers(): +static func release_timers() -> void: # we go the new way to hold all gdunit timers in group 'GdUnitTimers' - for node in Engine.get_main_loop().root.get_children(): + if Engine.get_main_loop().root == null: + return + for node :Node in Engine.get_main_loop().root.get_children(): if is_instance_valid(node) and node.is_in_group("GdUnitTimers"): if is_instance_valid(node): - Engine.get_main_loop().root.remove_child(node) + Engine.get_main_loop().root.remove_child.call_deferred(node) node.stop() - node.free() + node.queue_free() # the finally cleaup unfreed resources and singletons -static func dispose_all(): +static func dispose_all(use_call_deferred :bool = false) -> void: release_timers() + GdUnitSingleton.dispose(use_call_deferred) GdUnitSignals.dispose() - GdUnitSingleton.dispose() # if instance an mock or spy we need manually freeing the self reference @@ -279,46 +115,12 @@ static func release_double(instance :Object) -> void: instance.call("__release_double") -static func make_qualified_path(path :String) -> String: - if not path.begins_with("res://"): - if path.begins_with("//"): - return path.replace("//", "res://") - if path.begins_with("/"): - return "res:/" + path - return path - - -static func error_as_string(error_number :int) -> String: - return error_string(error_number) - - static func clear_push_errors() -> void: - var runner = Engine.get_meta("GdUnitRunner") + var runner :Node = Engine.get_meta("GdUnitRunner") if runner != null: runner.clear_push_errors() static func register_expect_interupted_by_timeout(test_suite :Node, test_case_name :String) -> void: - var test_case = test_suite.find_child(test_case_name, false, false) + var test_case :Node = test_suite.find_child(test_case_name, false, false) test_case.expect_to_interupt() - - -static func extract_zip(zip_package :String, dest_path :String) -> GdUnitResult: - var zip: ZIPReader = ZIPReader.new() - var err := zip.open(zip_package) - if err != OK: - return GdUnitResult.error("Extracting `%s` failed! Please collect the error log and report this. Error Code: %s" % [zip_package, err]) - var zip_entries: PackedStringArray = zip.get_files() - # Get base path and step over archive folder - var archive_path = zip_entries[0] - zip_entries.remove_at(0) - - for zip_entry in zip_entries: - var new_file_path: String = dest_path + "/" + zip_entry.replace(archive_path, "") - if zip_entry.ends_with("/"): - DirAccess.make_dir_recursive_absolute(new_file_path) - continue - var file: FileAccess = FileAccess.open(new_file_path, FileAccess.WRITE) - file.store_buffer(zip.read_file(zip_entry)) - zip.close() - return GdUnitResult.success(dest_path) diff --git a/addons/gdUnit4/src/core/GodotVersionFixures.gd b/addons/gdUnit4/src/core/GodotVersionFixures.gd index d0ce4e14..9d5b6bb3 100644 --- a/addons/gdUnit4/src/core/GodotVersionFixures.gd +++ b/addons/gdUnit4/src/core/GodotVersionFixures.gd @@ -3,9 +3,19 @@ class_name GodotVersionFixures extends RefCounted +@warning_ignore("shadowed_global_identifier") +static func type_convert(value: Variant, type: int) -> Variant: + return convert(value, type) -## Returns the icon property defined by name and theme_type, if it exists. -static func get_icon(control :Control, icon_name :String) -> Texture2D: - if Engine.get_version_info().hex >= 040200: - return control.get_theme_icon(icon_name, "EditorIcons") - return control.theme.get_icon(icon_name, "EditorIcons") + +@warning_ignore("shadowed_global_identifier") +static func convert(value: Variant, type: int) -> Variant: + return type_convert(value, type) + + +# handle global_position fixed by https://github.com/godotengine/godot/pull/88473 +static func set_event_global_position(event: InputEventMouseMotion, global_position: Vector2) -> void: + if Engine.get_version_info().hex >= 0x40202 or Engine.get_version_info().hex == 0x40104: + event.global_position = event.position + else: + event.global_position = global_position diff --git a/addons/gdUnit4/src/core/LocalTime.gd b/addons/gdUnit4/src/core/LocalTime.gd index 1ad986c0..64b9ba00 100644 --- a/addons/gdUnit4/src/core/LocalTime.gd +++ b/addons/gdUnit4/src/core/LocalTime.gd @@ -3,6 +3,7 @@ class_name LocalTime extends Resource enum TimeUnit { + DEFAULT = 0, MILLIS = 1, SECOND = 2, MINUTE = 3, @@ -12,7 +13,6 @@ enum TimeUnit { YEAR = 7 } - const SECONDS_PER_MINUTE:int = 60 const MINUTES_PER_HOUR:int = 60 const HOURS_PER_DAY:int = 24 @@ -78,7 +78,7 @@ static func elapsed(p_time_ms :int) -> String: @warning_ignore("integer_division") # create from epoch timestamp in ms -func _init(time :int): +func _init(time :int) -> void: _time = time _hour = (time / MILLIS_PER_HOUR) % 24 _minute = (time / MILLIS_PER_MINUTE) % 60 diff --git a/addons/gdUnit4/src/core/_TestCase.gd b/addons/gdUnit4/src/core/_TestCase.gd index 47ea60ff..31be7810 100644 --- a/addons/gdUnit4/src/core/_TestCase.gd +++ b/addons/gdUnit4/src/core/_TestCase.gd @@ -10,33 +10,24 @@ const ARGUMENT_SKIP := "do_skip" const ARGUMENT_SKIP_REASON := "skip_reason" var _iterations: int = 1 -var _current_iteration :int = -1 +var _current_iteration: int = -1 var _seed: int var _fuzzers: Array[GdFunctionArgument] = [] -var _test_parameters := Array() var _test_param_index := -1 var _line_number: int = -1 var _script_path: String var _skipped := false var _skip_reason := "" var _expect_to_interupt := false -var _timer : Timer -var _interupted :bool = false +var _timer: Timer +var _interupted: bool = false var _failed := false -var _report :GdUnitReport = null - - -var monitor : GodotGdErrorMonitor = null: - set (value): - monitor = value - get: - if monitor == null: - monitor = GodotGdErrorMonitor.new() - return monitor - +var _report: GdUnitReport = null +var _parameter_set_resolver: GdUnitTestParameterSetResolver +var _is_disposed := false -var timeout : int = DEFAULT_TIMEOUT: - set (value): +var timeout: int = DEFAULT_TIMEOUT: + set(value): timeout = value get: if timeout == DEFAULT_TIMEOUT: @@ -45,7 +36,7 @@ var timeout : int = DEFAULT_TIMEOUT: @warning_ignore("shadowed_variable_base_class") -func configure(p_name: String, p_line_number: int, p_script_path: String, p_timeout :int = DEFAULT_TIMEOUT, p_fuzzers :Array[GdFunctionArgument] = [], p_iterations: int = 1, p_seed :int = -1) -> _TestCase: +func configure(p_name: String, p_line_number: int, p_script_path: String, p_timeout: int=DEFAULT_TIMEOUT, p_fuzzers: Array[GdFunctionArgument]=[], p_iterations: int=1, p_seed: int=-1) -> _TestCase: set_name(p_name) _line_number = p_line_number _fuzzers = p_fuzzers @@ -56,42 +47,32 @@ func configure(p_name: String, p_line_number: int, p_script_path: String, p_time return self -func execute(p_test_parameter := Array(), p_iteration := 0): +func execute(p_test_parameter := Array(), p_iteration := 0) -> void: _failure_received(false) _current_iteration = p_iteration - 1 - if _current_iteration == -1: + if _current_iteration == - 1: _set_failure_handler() set_timeout() - monitor.start() if not p_test_parameter.is_empty(): update_fuzzers(p_test_parameter, p_iteration) - _execute_test_case(name, p_test_parameter) + _execute_test_case(name, p_test_parameter) else: _execute_test_case(name, []) await completed - monitor.stop() - for report_ in monitor.reports(): - if report_.is_error(): - _report = report_ - _interupted = true -func execute_paramaterized(p_test_parameter :Array): +func execute_paramaterized(p_test_parameter: Array) -> void: _failure_received(false) set_timeout() - monitor.start() - _execute_test_case(name, p_test_parameter) + # We need here to add a empty array to override the `test_parameters` to prevent initial "default" parameters from being used. + # This prevents objects in the argument list from being unnecessarily re-instantiated. + var test_parameters := p_test_parameter.duplicate() # is strictly need to duplicate the paramters before extend + test_parameters.append([]) + _execute_test_case(name, test_parameters) await completed - monitor.stop() - for report_ in monitor.reports(): - if report_.is_error(): - _report = report_ - _interupted = true - -var _is_disposed := false -func dispose(): +func dispose() -> void: if _is_disposed: return _is_disposed = true @@ -103,27 +84,27 @@ func dispose(): @warning_ignore("shadowed_variable_base_class", "redundant_await") -func _execute_test_case(name :String, test_parameter :Array): +func _execute_test_case(name: String, test_parameter: Array) -> void: # needs at least on await otherwise it breaks the awaiting chain await get_parent().callv(name, test_parameter) await Engine.get_main_loop().process_frame completed.emit() -func update_fuzzers(input_values :Array, iteration :int): - for fuzzer in input_values: +func update_fuzzers(input_values: Array, iteration: int) -> void: + for fuzzer :Variant in input_values: if fuzzer is Fuzzer: fuzzer._iteration_index = iteration + 1 -func set_timeout(): +func set_timeout() -> void: if is_instance_valid(_timer): return - var time :float = timeout / 1000.0 + var time: float = timeout / 1000.0 _timer = Timer.new() add_child(_timer) _timer.set_name("gdunit_test_case_timer_%d" % _timer.get_instance_id()) - _timer.timeout.connect(func do_interrupt(): + _timer.timeout.connect(func do_interrupt() -> void: if is_fuzzed(): _report = GdUnitReport.new().create(GdUnitReport.INTERUPTED, line_number(), GdAssertMessages.fuzzer_interuped(_current_iteration, "timedout")) else: @@ -145,9 +126,9 @@ func _set_failure_handler() -> void: func _remove_failure_handler() -> void: if GdUnitSignals.instance().gdunit_set_test_failed.is_connected(_failure_received): GdUnitSignals.instance().gdunit_set_test_failed.disconnect(_failure_received) - -func _failure_received(is_failed :bool) -> void: + +func _failure_received(is_failed: bool) -> void: # is already failed? if _failed: return @@ -155,7 +136,7 @@ func _failure_received(is_failed :bool) -> void: Engine.set_meta("GD_TEST_FAILURE", is_failed) -func stop_timer() : +func stop_timer() -> void: # finish outstanding timeouts if is_instance_valid(_timer): _timer.stop() @@ -176,7 +157,7 @@ func is_expect_interupted() -> bool: func is_parameterized() -> bool: - return _test_parameters.size() != 0 + return _parameter_set_resolver.is_parameterized() func is_skipped() -> bool: @@ -224,34 +205,34 @@ func generate_seed() -> void: seed(_seed) -func skip(skipped :bool, reason :String = "") -> void: +func skip(skipped: bool, reason: String="") -> void: _skipped = skipped _skip_reason = reason -func set_test_parameters(p_test_parameters :Array) -> void: - _test_parameters = p_test_parameters +func set_function_descriptor(fd: GdFunctionDescriptor) -> void: + _parameter_set_resolver = GdUnitTestParameterSetResolver.new(fd) -func set_test_parameter_index(index :int) -> void: +func set_test_parameter_index(index: int) -> void: _test_param_index = index -func test_parameters() -> Array: - return _test_parameters - - func test_parameter_index() -> int: return _test_param_index func test_case_names() -> PackedStringArray: - var test_cases := PackedStringArray() - var test_name = get_name() - for index in _test_parameters.size(): - test_cases.append("%s:%d %s" % [test_name, index, str(_test_parameters[index]).replace('"', "'").replace("&'", "'")]) - return test_cases + return _parameter_set_resolver.build_test_case_names(self) + + +func load_parameter_sets() -> Array: + return _parameter_set_resolver.load_parameter_sets(self, true) + + +func parameter_set_resolver() -> GdUnitTestParameterSetResolver: + return _parameter_set_resolver -func _to_string(): +func _to_string() -> String: return "%s :%d (%dms)" % [get_name(), _line_number, timeout] diff --git a/addons/gdUnit4/src/core/command/GdUnitCommand.gd b/addons/gdUnit4/src/core/command/GdUnitCommand.gd index 8a3f06f1..659b6a3f 100644 --- a/addons/gdUnit4/src/core/command/GdUnitCommand.gd +++ b/addons/gdUnit4/src/core/command/GdUnitCommand.gd @@ -2,7 +2,7 @@ class_name GdUnitCommand extends RefCounted -func _init(p_name :String, p_is_enabled: Callable, p_runnable: Callable, p_shortcut :GdUnitShortcut.ShortCut = GdUnitShortcut.ShortCut.NONE): +func _init(p_name :String, p_is_enabled: Callable, p_runnable: Callable, p_shortcut :GdUnitShortcut.ShortCut = GdUnitShortcut.ShortCut.NONE) -> void: assert(p_name != null, "(%s) missing parameter 'name'" % p_name) assert(p_is_enabled != null, "(%s) missing parameter 'is_enabled'" % p_name) assert(p_runnable != null, "(%s) missing parameter 'runnable'" % p_name) diff --git a/addons/gdUnit4/src/core/command/GdUnitCommandHandler.gd b/addons/gdUnit4/src/core/command/GdUnitCommandHandler.gd index ffe3c125..339fb8cd 100644 --- a/addons/gdUnit4/src/core/command/GdUnitCommandHandler.gd +++ b/addons/gdUnit4/src/core/command/GdUnitCommandHandler.gd @@ -1,5 +1,5 @@ class_name GdUnitCommandHandler -extends RefCounted +extends Object signal gdunit_runner_start() signal gdunit_runner_stop(client_id :int) @@ -30,8 +30,6 @@ const SETTINGS_SHORTCUT_MAPPING := { GdUnitSettings.SHORTCUT_FILESYSTEM_RUN_TEST_DEBUG : GdUnitShortcut.ShortCut.RUN_TESTCASE_DEBUG } - -var _editor_interface :EditorInterface # the current test runner config var _runner_config := GdUnitRunnerConfig.new() @@ -49,15 +47,12 @@ var _shortcuts := {} static func instance() -> GdUnitCommandHandler: - return GdUnitSingleton.instance("GdUnitCommandHandler", func(): return GdUnitCommandHandler.new()) + return GdUnitSingleton.instance("GdUnitCommandHandler", func() -> GdUnitCommandHandler: return GdUnitCommandHandler.new()) -func _init(): +func _init() -> void: assert_shortcut_mappings(SETTINGS_SHORTCUT_MAPPING) - - if Engine.is_editor_hint(): - var editor :EditorPlugin = Engine.get_meta("GdUnitEditorPlugin") - _editor_interface = editor.get_editor_interface() + GdUnitSignals.instance().gdunit_event.connect(_on_event) GdUnitSignals.instance().gdunit_client_connected.connect(_on_client_connected) GdUnitSignals.instance().gdunit_client_disconnected.connect(_on_client_disconnected) @@ -66,8 +61,8 @@ func _init(): _runner_config.load_config() init_shortcuts() - var is_running = func(_script :Script) : return _is_running - var is_not_running = func(_script :Script) : return !_is_running + var is_running := func(_script :Script) -> bool: return _is_running + var is_not_running := func(_script :Script) -> bool: return !_is_running register_command(GdUnitCommand.new(CMD_RUN_OVERALL, is_not_running, cmd_run_overall.bind(true), GdUnitShortcut.ShortCut.RUN_TESTS_OVERALL)) register_command(GdUnitCommand.new(CMD_RUN_TESTCASE, is_not_running, cmd_editor_run_test.bind(false), GdUnitShortcut.ShortCut.RUN_TESTCASE)) register_command(GdUnitCommand.new(CMD_RUN_TESTCASE_DEBUG, is_not_running, cmd_editor_run_test.bind(true), GdUnitShortcut.ShortCut.RUN_TESTCASE_DEBUG)) @@ -78,8 +73,13 @@ func _init(): register_command(GdUnitCommand.new(CMD_CREATE_TESTCASE, is_not_running, cmd_create_test, GdUnitShortcut.ShortCut.CREATE_TEST)) register_command(GdUnitCommand.new(CMD_STOP_TEST_RUN, is_running, cmd_stop.bind(_client_id), GdUnitShortcut.ShortCut.STOP_TEST_RUN)) + # schedule discover tests if enabled and running inside the editor + if Engine.is_editor_hint() and GdUnitSettings.is_test_discover_enabled(): + var timer :SceneTreeTimer = Engine.get_main_loop().create_timer(5) + timer.timeout.connect(cmd_discover_tests) -func _notification(what): + +func _notification(what :int) -> void: if what == NOTIFICATION_PREDELETE: _commands.clear() _shortcuts.clear() @@ -90,24 +90,24 @@ func _do_process() -> void: # is checking if the user has press the editor stop scene -func check_test_run_stopped_manually(): +func check_test_run_stopped_manually() -> void: if is_test_running_but_stop_pressed(): if GdUnitSettings.is_verbose_assert_warnings(): push_warning("Test Runner scene was stopped manually, force stopping the current test run!") cmd_stop(_client_id) -func is_test_running_but_stop_pressed(): - return _editor_interface and _running_debug_mode and _is_running and not _editor_interface.is_playing_scene() +func is_test_running_but_stop_pressed() -> bool: + return _running_debug_mode and _is_running and not EditorInterface.is_playing_scene() func assert_shortcut_mappings(mappings :Dictionary) -> void: - for shortcut in GdUnitShortcut.ShortCut.values(): + for shortcut :int in GdUnitShortcut.ShortCut.values(): assert(mappings.values().has(shortcut), "missing settings mapping for shortcut '%s'!" % GdUnitShortcut.ShortCut.keys()[shortcut]) func init_shortcuts() -> void: - for shortcut in GdUnitShortcut.ShortCut.values(): + for shortcut :int in GdUnitShortcut.ShortCut.values(): if shortcut == GdUnitShortcut.ShortCut.NONE: continue var property_name :String = SETTINGS_SHORTCUT_MAPPING.find_key(shortcut) @@ -226,15 +226,16 @@ func cmd_stop(client_id :int) -> void: _is_running = false gdunit_runner_stop.emit(client_id) if _running_debug_mode: - _editor_interface.stop_playing_scene() + EditorInterface.stop_playing_scene() else: if _current_runner_process_id > 0: - var result = OS.kill(_current_runner_process_id) - if result != OK: - push_error("ERROR checked stopping GdUnit Test Runner. error code: %s" % result) + if OS.is_process_running(_current_runner_process_id): + var result := OS.kill(_current_runner_process_id) + if result != OK: + push_error("ERROR checked stopping GdUnit Test Runner. error code: %s" % result) _current_runner_process_id = -1 -func cmd_editor_run_test(debug :bool): +func cmd_editor_run_test(debug :bool) -> void: var cursor_line := active_base_editor().get_caret_line() #run test case? var regex := RegEx.new() @@ -262,6 +263,10 @@ func cmd_create_test() -> void: ScriptEditorControls.edit_script(info.get("path"), info.get("line")) +func cmd_discover_tests() -> void: + await GdUnitTestDiscoverer.run() + + static func scan_test_directorys(base_directory :String, test_directory: String, test_suite_paths :PackedStringArray) -> PackedStringArray: print_verbose("Scannning for test directory '%s' at %s" % [test_directory, base_directory]) for directory in DirAccess.get_directories_at(base_directory): @@ -271,7 +276,6 @@ static func scan_test_directorys(base_directory :String, test_directory: String, if GdUnitTestSuiteScanner.exclude_scan_directories.has(current_directory): continue if match_test_directory(directory, test_directory): - prints("Collect tests at:", current_directory) test_suite_paths.append(current_directory) else: scan_test_directorys(current_directory, test_directory, test_suite_paths) @@ -286,12 +290,12 @@ static func match_test_directory(directory :String, test_directory: String) -> b return directory == test_directory or test_directory.is_empty() or test_directory == "/" or test_directory == "res://" -func run_debug_mode(): - _editor_interface.play_custom_scene("res://addons/gdUnit4/src/core/GdUnitRunner.tscn") +func run_debug_mode() -> void: + EditorInterface.play_custom_scene("res://addons/gdUnit4/src/core/GdUnitRunner.tscn") _is_running = true -func run_release_mode(): +func run_release_mode() -> void: var arguments := Array() if OS.is_stdout_verbose(): arguments.append("--verbose") @@ -303,45 +307,44 @@ func run_release_mode(): _is_running = true -func script_editor() -> ScriptEditor: - return _editor_interface.get_script_editor() - - func active_base_editor() -> TextEdit: - return script_editor().get_current_editor().get_base_editor() + return EditorInterface.get_script_editor().get_current_editor().get_base_editor() func active_script() -> Script: - return script_editor().get_current_script() + return EditorInterface.get_script_editor().get_current_script() ################################################################################ # signals handles ################################################################################ -func _on_event(event :GdUnitEvent): +func _on_event(event :GdUnitEvent) -> void: if event.type() == GdUnitEvent.STOP: cmd_stop(_client_id) -func _on_stop_pressed(): +func _on_stop_pressed() -> void: cmd_stop(_client_id) -func _on_run_pressed(debug := false): +func _on_run_pressed(debug := false) -> void: cmd_run(debug) -func _on_run_overall_pressed(_debug := false): +func _on_run_overall_pressed(_debug := false) -> void: cmd_run_overall(true) -func _on_settings_changed(property :GdUnitProperty): +func _on_settings_changed(property :GdUnitProperty) -> void: if SETTINGS_SHORTCUT_MAPPING.has(property.name()): var shortcut :GdUnitShortcut.ShortCut = SETTINGS_SHORTCUT_MAPPING.get(property.name()) var input_event := create_shortcut_input_even(property.value()) prints("Shortcut changed: '%s' to '%s'" % [GdUnitShortcut.ShortCut.keys()[shortcut], input_event.as_text()]) register_shortcut(shortcut, input_event) + if property.name() == GdUnitSettings.TEST_DISCOVER_ENABLED: + var timer :SceneTreeTimer = Engine.get_main_loop().create_timer(3) + timer.timeout.connect(cmd_discover_tests) ################################################################################ diff --git a/addons/gdUnit4/src/core/command/GdUnitShortcutAction.gd b/addons/gdUnit4/src/core/command/GdUnitShortcutAction.gd index ffc9a9cd..94b082e9 100644 --- a/addons/gdUnit4/src/core/command/GdUnitShortcutAction.gd +++ b/addons/gdUnit4/src/core/command/GdUnitShortcutAction.gd @@ -2,7 +2,7 @@ class_name GdUnitShortcutAction extends RefCounted -func _init(p_type :GdUnitShortcut.ShortCut, p_shortcut :Shortcut, p_command :String): +func _init(p_type :GdUnitShortcut.ShortCut, p_shortcut :Shortcut, p_command :String) -> void: assert(p_type != null, "missing parameter 'type'") assert(p_shortcut != null, "missing parameter 'shortcut'") assert(p_command != null, "missing parameter 'command'") diff --git a/addons/gdUnit4/src/core/discovery/GdUnitTestDiscoverGuard.gd b/addons/gdUnit4/src/core/discovery/GdUnitTestDiscoverGuard.gd new file mode 100644 index 00000000..fa9a65c0 --- /dev/null +++ b/addons/gdUnit4/src/core/discovery/GdUnitTestDiscoverGuard.gd @@ -0,0 +1,86 @@ +extends RefCounted + +# contains all tracked test suites where discovered since editor start +# key : test suite resource_path +# value: the list of discovered test case names +var _discover_cache := {} + + +func _init() -> void: + # Register for discovery events to sync the cache + GdUnitSignals.instance().gdunit_add_test_suite.connect(sync_cache) + + +func sync_cache(dto :GdUnitTestSuiteDto) -> void: + var resource_path := dto.path() + var discovered_test_cases :Array[String] = [] + for test_case in dto.test_cases(): + discovered_test_cases.append(test_case.name()) + _discover_cache[resource_path] = discovered_test_cases + + +func discover(script: Script) -> void: + if GdObjects.is_test_suite(script): + # a new test suite is discovered + if not _discover_cache.has(script.resource_path): + var scanner := GdUnitTestSuiteScanner.new() + var test_suite := scanner._parse_test_suite(script) + var dto :GdUnitTestSuiteDto = GdUnitTestSuiteDto.of(test_suite) + GdUnitSignals.instance().gdunit_event.emit(GdUnitEventTestDiscoverTestSuiteAdded.new(script.resource_path, test_suite.get_name(), dto)) + sync_cache(dto) + test_suite.queue_free() + return + + var tests_added :Array[String] = [] + var tests_removed := PackedStringArray() + var script_test_cases := extract_test_functions(script) + var discovered_test_cases :Array[String] = _discover_cache.get(script.resource_path, [] as Array[String]) + + # first detect removed/renamed tests + for test_case in discovered_test_cases: + if not script_test_cases.has(test_case): + tests_removed.append(test_case) + # second detect new added tests + for test_case in script_test_cases: + if not discovered_test_cases.has(test_case): + tests_added.append(test_case) + + # finally notify changes to the inspector + if not tests_removed.is_empty() or not tests_added.is_empty(): + var scanner := GdUnitTestSuiteScanner.new() + var test_suite := scanner._parse_test_suite(script) + var suite_name := test_suite.get_name() + + # emit deleted tests + for test_name in tests_removed: + discovered_test_cases.erase(test_name) + GdUnitSignals.instance().gdunit_event.emit(GdUnitEventTestDiscoverTestRemoved.new(script.resource_path, suite_name, test_name)) + + # emit new discovered tests + for test_name in tests_added: + discovered_test_cases.append(test_name) + var test_case := test_suite.find_child(test_name, false, false) + var dto := GdUnitTestCaseDto.new() + dto = dto.deserialize(dto.serialize(test_case)) + GdUnitSignals.instance().gdunit_event.emit(GdUnitEventTestDiscoverTestAdded.new(script.resource_path, suite_name, dto)) + # update the cache + _discover_cache[script.resource_path] = discovered_test_cases + test_suite.queue_free() + + +func extract_test_functions(script :Script) -> PackedStringArray: + return script.get_script_method_list()\ + .map(map_func_names)\ + .filter(filter_test_cases) + + +func map_func_names(method_info :Dictionary) -> String: + return method_info["name"] + + +func filter_test_cases(value :String) -> bool: + return value.begins_with("test_") + + +func filter_by_test_cases(method_info :Dictionary, value :String) -> bool: + return method_info["name"] == value diff --git a/addons/gdUnit4/src/core/discovery/GdUnitTestDiscoverer.gd b/addons/gdUnit4/src/core/discovery/GdUnitTestDiscoverer.gd new file mode 100644 index 00000000..7e8dc81e --- /dev/null +++ b/addons/gdUnit4/src/core/discovery/GdUnitTestDiscoverer.gd @@ -0,0 +1,37 @@ +class_name GdUnitTestDiscoverer +extends RefCounted + + +static func run() -> void: + prints("Running test discovery ..") + GdUnitSignals.instance().gdunit_event.emit(GdUnitEventTestDiscoverStart.new()) + await Engine.get_main_loop().create_timer(.5).timeout + + # We run the test discovery in an extra thread so that the main thread is not blocked + var t:= Thread.new() + t.start(func () -> void: + var test_suite_directories :PackedStringArray = GdUnitCommandHandler.scan_test_directorys("res://" , GdUnitSettings.test_root_folder(), []) + var scanner := GdUnitTestSuiteScanner.new() + var _test_suites_to_process :Array[Node] = [] + + for test_suite_dir in test_suite_directories: + _test_suites_to_process.append_array(scanner.scan(test_suite_dir)) + + # Do sync the main thread before emit the discovered test suites to the inspector + await Engine.get_main_loop().process_frame + var test_case_count :int = 0 + for test_suite in _test_suites_to_process: + test_case_count += test_suite.get_child_count() + var ts_dto := GdUnitTestSuiteDto.of(test_suite) + GdUnitSignals.instance().gdunit_add_test_suite.emit(ts_dto) + test_suite.free() + + prints("%d test suites discovered." % _test_suites_to_process.size()) + GdUnitSignals.instance().gdunit_event.emit(GdUnitEventTestDiscoverEnd.new(_test_suites_to_process.size(), test_case_count)) + _test_suites_to_process.clear() + ) + # wait unblocked to the tread is finished + while t.is_alive(): + await Engine.get_main_loop().process_frame + # needs finally to wait for finish + await t.wait_to_finish() diff --git a/addons/gdUnit4/src/core/event/GdUnitEvent.gd b/addons/gdUnit4/src/core/event/GdUnitEvent.gd index dbfcc326..0f8ad7d1 100644 --- a/addons/gdUnit4/src/core/event/GdUnitEvent.gd +++ b/addons/gdUnit4/src/core/event/GdUnitEvent.gd @@ -18,6 +18,11 @@ enum { TESTSUITE_AFTER, TESTCASE_BEFORE, TESTCASE_AFTER, + DISCOVER_START, + DISCOVER_END, + DISCOVER_SUITE_ADDED, + DISCOVER_TEST_ADDED, + DISCOVER_TEST_REMOVED, } var _event_type :int @@ -26,10 +31,10 @@ var _suite_name :String var _test_name :String var _total_count :int = 0 var _statistics := Dictionary() -var _reports := Array() +var _reports :Array[GdUnitReport] = [] -func suite_before(p_resource_path :String, p_suite_name :String, p_total_count) -> GdUnitEvent: +func suite_before(p_resource_path :String, p_suite_name :String, p_total_count :int) -> GdUnitEvent: _event_type = TESTSUITE_BEFORE _resource_path = p_resource_path _suite_name = p_suite_name @@ -38,7 +43,7 @@ func suite_before(p_resource_path :String, p_suite_name :String, p_total_count) return self -func suite_after(p_resource_path :String, p_suite_name :String, p_statistics :Dictionary = {}, p_reports :Array = []) -> GdUnitEvent: +func suite_after(p_resource_path :String, p_suite_name :String, p_statistics :Dictionary = {}, p_reports :Array[GdUnitReport] = []) -> GdUnitEvent: _event_type = TESTSUITE_AFTER _resource_path = p_resource_path _suite_name = p_suite_name @@ -56,7 +61,7 @@ func test_before(p_resource_path :String, p_suite_name :String, p_test_name :Str return self -func test_after(p_resource_path :String, p_suite_name :String, p_test_name :String, p_statistics :Dictionary = {}, p_reports :Array = []) -> GdUnitEvent: +func test_after(p_resource_path :String, p_suite_name :String, p_test_name :String, p_statistics :Dictionary = {}, p_reports :Array[GdUnitReport] = []) -> GdUnitEvent: _event_type = TESTCASE_AFTER _resource_path = p_resource_path _suite_name = p_suite_name @@ -134,11 +139,11 @@ func is_skipped() -> bool: return _statistics.get(SKIPPED, false) -func reports() -> Array: +func reports() -> Array[GdUnitReport]: return _reports -func _to_string(): +func _to_string() -> String: return "Event: %s %s:%s, %s, %s" % [_event_type, _suite_name, _test_name, _statistics, _reports] @@ -161,20 +166,24 @@ func deserialize(serialized :Dictionary) -> GdUnitEvent: _suite_name = serialized.get("suite_name", null) _test_name = serialized.get("test_name", "unknown") _total_count = serialized.get("total_count", 0) - _statistics = serialized.get("statistics", Dictionary()) - _reports = _deserialize_reports(serialized.get("reports",[])) + _statistics = serialized.get("statistics", Dictionary()) + if serialized.has("reports"): + # needs this workaround to copy typed values in the array + var reports_to_deserializ :Array[Dictionary] = [] + reports_to_deserializ.append_array(serialized.get("reports")) + _reports = _deserialize_reports(reports_to_deserializ) return self -func _serialize_TestReports() -> Array: - var serialized_reports := Array() +func _serialize_TestReports() -> Array[Dictionary]: + var serialized_reports :Array[Dictionary] = [] for report in _reports: serialized_reports.append(report.serialize()) return serialized_reports -func _deserialize_reports(p_reports :Array) -> Array: - var deserialized_reports := Array() +func _deserialize_reports(p_reports :Array[Dictionary]) -> Array[GdUnitReport]: + var deserialized_reports :Array[GdUnitReport] = [] for report in p_reports: var test_report := GdUnitReport.new().deserialize(report) deserialized_reports.append(test_report) diff --git a/addons/gdUnit4/src/core/event/GdUnitEventInit.gd b/addons/gdUnit4/src/core/event/GdUnitEventInit.gd index eeb88dce..8bb1d496 100644 --- a/addons/gdUnit4/src/core/event/GdUnitEventInit.gd +++ b/addons/gdUnit4/src/core/event/GdUnitEventInit.gd @@ -4,7 +4,8 @@ extends GdUnitEvent var _total_testsuites :int -func _init(p_total_testsuites :int, p_total_count :int): + +func _init(p_total_testsuites :int, p_total_count :int) -> void: _event_type = INIT _total_testsuites = p_total_testsuites _total_count = p_total_count diff --git a/addons/gdUnit4/src/core/event/GdUnitEventStop.gd b/addons/gdUnit4/src/core/event/GdUnitEventStop.gd index faa6a186..d7a3c11c 100644 --- a/addons/gdUnit4/src/core/event/GdUnitEventStop.gd +++ b/addons/gdUnit4/src/core/event/GdUnitEventStop.gd @@ -2,6 +2,5 @@ class_name GdUnitStop extends GdUnitEvent -func _init(): +func _init() -> void: _event_type = STOP - diff --git a/addons/gdUnit4/src/core/event/GdUnitEventTestDiscoverEnd.gd b/addons/gdUnit4/src/core/event/GdUnitEventTestDiscoverEnd.gd new file mode 100644 index 00000000..c6194ef3 --- /dev/null +++ b/addons/gdUnit4/src/core/event/GdUnitEventTestDiscoverEnd.gd @@ -0,0 +1,19 @@ +class_name GdUnitEventTestDiscoverEnd +extends GdUnitEvent + + +var _total_testsuites: int + + +func _init(testsuite_count: int, test_count: int) -> void: + _event_type = DISCOVER_END + _total_testsuites = testsuite_count + _total_count = test_count + + +func total_test_suites() -> int: + return _total_testsuites + + +func total_tests() -> int: + return _total_count diff --git a/addons/gdUnit4/src/core/event/GdUnitEventTestDiscoverStart.gd b/addons/gdUnit4/src/core/event/GdUnitEventTestDiscoverStart.gd new file mode 100644 index 00000000..c7dd36f7 --- /dev/null +++ b/addons/gdUnit4/src/core/event/GdUnitEventTestDiscoverStart.gd @@ -0,0 +1,6 @@ +class_name GdUnitEventTestDiscoverStart +extends GdUnitEvent + + +func _init() -> void: + _event_type = DISCOVER_START diff --git a/addons/gdUnit4/src/core/event/GdUnitEventTestDiscoverTestAdded.gd b/addons/gdUnit4/src/core/event/GdUnitEventTestDiscoverTestAdded.gd new file mode 100644 index 00000000..c5f44599 --- /dev/null +++ b/addons/gdUnit4/src/core/event/GdUnitEventTestDiscoverTestAdded.gd @@ -0,0 +1,17 @@ +class_name GdUnitEventTestDiscoverTestAdded +extends GdUnitEvent + + +var _test_case_dto: GdUnitTestCaseDto + + +func _init(arg_resource_path: String, arg_suite_name: String, arg_test_case_dto: GdUnitTestCaseDto) -> void: + _event_type = DISCOVER_TEST_ADDED + _resource_path = arg_resource_path + _suite_name = arg_suite_name + _test_name = arg_test_case_dto.name() + _test_case_dto = arg_test_case_dto + + +func test_case_dto() -> GdUnitTestCaseDto: + return _test_case_dto diff --git a/addons/gdUnit4/src/core/event/GdUnitEventTestDiscoverTestRemoved.gd b/addons/gdUnit4/src/core/event/GdUnitEventTestDiscoverTestRemoved.gd new file mode 100644 index 00000000..77617d0e --- /dev/null +++ b/addons/gdUnit4/src/core/event/GdUnitEventTestDiscoverTestRemoved.gd @@ -0,0 +1,9 @@ +class_name GdUnitEventTestDiscoverTestRemoved +extends GdUnitEvent + + +func _init(arg_resource_path: String, arg_suite_name: String, arg_test_name: String) -> void: + _event_type = DISCOVER_TEST_REMOVED + _resource_path = arg_resource_path + _suite_name = arg_suite_name + _test_name = arg_test_name diff --git a/addons/gdUnit4/src/core/event/GdUnitEventTestDiscoverTestSuiteAdded.gd b/addons/gdUnit4/src/core/event/GdUnitEventTestDiscoverTestSuiteAdded.gd new file mode 100644 index 00000000..b0e23f59 --- /dev/null +++ b/addons/gdUnit4/src/core/event/GdUnitEventTestDiscoverTestSuiteAdded.gd @@ -0,0 +1,16 @@ +class_name GdUnitEventTestDiscoverTestSuiteAdded +extends GdUnitEvent + + +var _dto: GdUnitTestSuiteDto + + +func _init(arg_resource_path: String, arg_suite_name: String, arg_dto: GdUnitTestSuiteDto) -> void: + _event_type = DISCOVER_SUITE_ADDED + _resource_path = arg_resource_path + _suite_name = arg_suite_name + _dto = arg_dto + + +func suite_dto() -> GdUnitTestSuiteDto: + return _dto diff --git a/addons/gdUnit4/src/core/execution/GdUnitExecutionContext.gd b/addons/gdUnit4/src/core/execution/GdUnitExecutionContext.gd index b55c125f..25d69b09 100644 --- a/addons/gdUnit4/src/core/execution/GdUnitExecutionContext.gd +++ b/addons/gdUnit4/src/core/execution/GdUnitExecutionContext.gd @@ -12,6 +12,15 @@ var _test_case_name: StringName var _name :String +var error_monitor : GodotGdErrorMonitor = null: + set (value): + error_monitor = value + get: + if _parent_context != null: + return _parent_context.error_monitor + return error_monitor + + var test_suite : GdUnitTestSuite = null: set (value): test_suite = value @@ -35,6 +44,7 @@ func _init(name :String, parent_context :GdUnitExecutionContext = null) -> void: _orphan_monitor = GdUnitOrphanNodesMonitor.new(name) _orphan_monitor.start() _memory_observer = GdUnitMemoryObserver.new() + error_monitor = GodotGdErrorMonitor.new() _report_collector = GdUnitTestReportCollector.new(get_instance_id()) if parent_context != null: parent_context._sub_context.append(self) @@ -84,6 +94,17 @@ func test_failed() -> bool: return has_failures() or has_errors() +func error_monitor_start() -> void: + error_monitor.start() + + +func error_monitor_stop() -> void: + await error_monitor.scan() + for error_report in error_monitor.to_reports(): + if error_report.is_error(): + _report_collector._reports.append(error_report) + + func orphan_monitor_start() -> void: _orphan_monitor.start() @@ -111,43 +132,47 @@ func build_report_statistics(orphans :int, recursive := true) -> Dictionary: func has_failures() -> bool: - return _sub_context.any(func(c): return c.has_failures()) or _report_collector.has_failures() + return _sub_context.any(func(c :GdUnitExecutionContext) -> bool: + return c.has_failures()) or _report_collector.has_failures() func has_errors() -> bool: - return _sub_context.any(func(c): return c.has_errors()) or _report_collector.has_errors() + return _sub_context.any(func(c :GdUnitExecutionContext) -> bool: + return c.has_errors()) or _report_collector.has_errors() func has_warnings() -> bool: - return _sub_context.any(func(c): return c.has_warnings()) or _report_collector.has_warnings() + return _sub_context.any(func(c :GdUnitExecutionContext) -> bool: + return c.has_warnings()) or _report_collector.has_warnings() func has_skipped() -> bool: - return _sub_context.any(func(c): return c.has_skipped()) or _report_collector.has_skipped() + return _sub_context.any(func(c :GdUnitExecutionContext) -> bool: + return c.has_skipped()) or _report_collector.has_skipped() func count_failures(recursive :bool) -> int: if not recursive: return _report_collector.count_failures() return _sub_context\ - .map(func(c): return c.count_failures(recursive))\ - .reduce(sum, _report_collector.count_failures()) + .map(func(c :GdUnitExecutionContext) -> int: + return c.count_failures(recursive)).reduce(sum, _report_collector.count_failures()) func count_errors(recursive :bool) -> int: if not recursive: return _report_collector.count_errors() return _sub_context\ - .map(func(c): return c.count_errors(recursive))\ - .reduce(sum, _report_collector.count_errors()) + .map(func(c :GdUnitExecutionContext) -> int: + return c.count_errors(recursive)).reduce(sum, _report_collector.count_errors()) func count_skipped(recursive :bool) -> int: if not recursive: return _report_collector.count_skipped() return _sub_context\ - .map(func(c): return c.count_skipped(recursive))\ - .reduce(sum, _report_collector.count_skipped()) + .map(func(c :GdUnitExecutionContext) -> int: + return c.count_skipped(recursive)).reduce(sum, _report_collector.count_skipped()) func count_orphans() -> int: diff --git a/addons/gdUnit4/src/core/execution/GdUnitMemoryObserver.gd b/addons/gdUnit4/src/core/execution/GdUnitMemoryObserver.gd index 6709c136..69b15f84 100644 --- a/addons/gdUnit4/src/core/execution/GdUnitMemoryObserver.gd +++ b/addons/gdUnit4/src/core/execution/GdUnitMemoryObserver.gd @@ -8,18 +8,13 @@ const GdUnitTools = preload("res://addons/gdUnit4/src/core/GdUnitTools.gd") var _store :Array[Variant] = [] -var _orphan_detection_enabled :bool = true # enable for debugging purposes var _is_stdout_verbose := false const _show_debug := false -func _init(): - _orphan_detection_enabled = GdUnitSettings.is_verbose_orphans() - - ## Registration of an instance to be released when an execution phase is completed -func register_auto_free(obj) -> Variant: +func register_auto_free(obj :Variant) -> Variant: if not is_instance_valid(obj): return obj # do not register on GDScriptNativeClass @@ -61,7 +56,7 @@ static func debug_observe(name :String, obj :Object, indent :int = 0) -> void: static func guard_instance(obj :Object) -> Object: if not _is_instance_guard_enabled(): - return + return var tag := TAG_OBSERVE_INSTANCE + str(abs(obj.get_instance_id())) if Engine.has_meta(tag): return @@ -72,7 +67,7 @@ static func guard_instance(obj :Object) -> Object: static func unguard_instance(obj :Object, verbose := true) -> void: if not _is_instance_guard_enabled(): - return + return var tag := TAG_OBSERVE_INSTANCE + str(abs(obj.get_instance_id())) if verbose: debug_observe("unguard instance", obj) @@ -82,7 +77,7 @@ static func unguard_instance(obj :Object, verbose := true) -> void: static func gc_guarded_instance(name :String, instance :Object) -> void: if not _is_instance_guard_enabled(): - return + return await Engine.get_main_loop().process_frame unguard_instance(instance, false) if is_instance_valid(instance) and instance is RefCounted: @@ -101,10 +96,10 @@ static func gc_guarded_instance(name :String, instance :Object) -> void: static func gc_on_guarded_instances() -> void: if not _is_instance_guard_enabled(): - return + return for tag in Engine.get_meta_list(): if tag.begins_with(TAG_OBSERVE_INSTANCE): - var instance = Engine.get_meta(tag) + var instance :Object = Engine.get_meta(tag) await gc_guarded_instance("Leaked instance detected:", instance) await GdUnitTools.free_instance(instance, false) @@ -132,5 +127,5 @@ func gc() -> void: ## Checks whether the specified object is registered for automatic release -static func is_marked_auto_free(obj) -> bool: +static func is_marked_auto_free(obj :Object) -> bool: return Engine.get_meta(TAG_AUTO_FREE, []).has(obj) diff --git a/addons/gdUnit4/src/core/execution/GdUnitTestReportCollector.gd b/addons/gdUnit4/src/core/execution/GdUnitTestReportCollector.gd index cde5cee2..8484f0d1 100644 --- a/addons/gdUnit4/src/core/execution/GdUnitTestReportCollector.gd +++ b/addons/gdUnit4/src/core/execution/GdUnitTestReportCollector.gd @@ -23,7 +23,7 @@ static func __filter_is_skipped(report :GdUnitReport) -> bool: return report.is_skipped() -func _init(execution_context_id :int): +func _init(execution_context_id :int) -> void: _execution_context_id = execution_context_id GdUnitSignals.instance().gdunit_report.connect(on_reports) diff --git a/addons/gdUnit4/src/core/execution/GdUnitTestSuiteExecutor.gd b/addons/gdUnit4/src/core/execution/GdUnitTestSuiteExecutor.gd index 47c02fb8..c9194694 100644 --- a/addons/gdUnit4/src/core/execution/GdUnitTestSuiteExecutor.gd +++ b/addons/gdUnit4/src/core/execution/GdUnitTestSuiteExecutor.gd @@ -8,15 +8,15 @@ var _assertions := GdUnitAssertions.new() var _executeStage :IGdUnitExecutionStage = GdUnitTestSuiteExecutionStage.new() -func _init(debug_mode :bool = false): +func _init(debug_mode :bool = false) -> void: _executeStage.set_debug_mode(debug_mode) func execute(test_suite :GdUnitTestSuite) -> void: - var orphan_detection_enabled = GdUnitSettings.is_verbose_orphans() + var orphan_detection_enabled := GdUnitSettings.is_verbose_orphans() if not orphan_detection_enabled: prints("!!! Reporting orphan nodes is disabled. Please check GdUnit settings.") - + Engine.get_main_loop().root.call_deferred("add_child", test_suite) await Engine.get_main_loop().process_frame await _executeStage.execute(GdUnitExecutionContext.of_test_suite(test_suite)) diff --git a/addons/gdUnit4/src/core/execution/stages/GdUnitTestCaseAfterStage.gd b/addons/gdUnit4/src/core/execution/stages/GdUnitTestCaseAfterStage.gd index d8db2c44..50699752 100644 --- a/addons/gdUnit4/src/core/execution/stages/GdUnitTestCaseAfterStage.gd +++ b/addons/gdUnit4/src/core/execution/stages/GdUnitTestCaseAfterStage.gd @@ -8,20 +8,19 @@ var _test_name :StringName = "" var _call_stage :bool -func _init(call_stage := true): +func _init(call_stage := true) -> void: _call_stage = call_stage func _execute(context :GdUnitExecutionContext) -> void: var test_suite := context.test_suite - if _call_stage: @warning_ignore("redundant_await") await test_suite.after_test() # unreference last used assert form the test to prevent memory leaks GdUnitThreadManager.get_current_context().set_assert(null) await context.gc() - + await context.error_monitor_stop() if context.test_case.is_skipped(): fire_test_skipped(context) else: @@ -30,7 +29,7 @@ func _execute(context :GdUnitExecutionContext) -> void: context.test_case.dispose() -func set_test_name(test_name :StringName): +func set_test_name(test_name :StringName) -> void: _test_name = test_name @@ -39,7 +38,7 @@ func fire_test_ended(context :GdUnitExecutionContext) -> void: var test_name := context._test_case_name if _test_name.is_empty() else _test_name var reports := collect_reports(context) var orphans := collect_orphans(context, reports) - + fire_event(GdUnitEvent.new()\ .test_after(test_suite.get_script().resource_path, test_suite.get_name(), test_name, context.build_report_statistics(orphans), reports)) @@ -55,7 +54,7 @@ func collect_orphans(context :GdUnitExecutionContext, reports :Array[GdUnitRepor func collect_reports(context :GdUnitExecutionContext) -> Array[GdUnitReport]: var reports := context.reports() var test_case := context.test_case - if test_case.is_interupted() and not test_case.is_expect_interupted(): + if test_case.is_interupted() and not test_case.is_expect_interupted() and test_case.report() != null: reports.push_back(test_case.report()) # we combine the reports of test_before(), test_after() and test() to be reported by `fire_test_ended` if not context._sub_context.is_empty(): @@ -81,11 +80,11 @@ func add_orphan_report_teststage(context :GdUnitExecutionContext, reports :Array return orphans -func fire_test_skipped(context :GdUnitExecutionContext): +func fire_test_skipped(context :GdUnitExecutionContext) -> void: var test_suite := context.test_suite var test_case := context.test_case var test_case_name := context._test_case_name if _test_name.is_empty() else _test_name - var statistics = { + var statistics := { GdUnitEvent.ORPHAN_NODES: 0, GdUnitEvent.ELAPSED_TIME: 0, GdUnitEvent.WARNINGS: false, diff --git a/addons/gdUnit4/src/core/execution/stages/GdUnitTestCaseBeforeStage.gd b/addons/gdUnit4/src/core/execution/stages/GdUnitTestCaseBeforeStage.gd index 0abc581f..ebbd6d52 100644 --- a/addons/gdUnit4/src/core/execution/stages/GdUnitTestCaseBeforeStage.gd +++ b/addons/gdUnit4/src/core/execution/stages/GdUnitTestCaseBeforeStage.gd @@ -8,21 +8,22 @@ var _test_name :StringName = "" var _call_stage :bool -func _init(call_stage := true): +func _init(call_stage := true) -> void: _call_stage = call_stage func _execute(context :GdUnitExecutionContext) -> void: var test_suite := context.test_suite var test_case_name := context._test_case_name if _test_name.is_empty() else _test_name - + fire_event(GdUnitEvent.new()\ .test_before(test_suite.get_script().resource_path, test_suite.get_name(), test_case_name)) - + if _call_stage: @warning_ignore("redundant_await") await test_suite.before_test() + context.error_monitor_start() -func set_test_name(test_name :StringName): +func set_test_name(test_name :StringName) -> void: _test_name = test_name diff --git a/addons/gdUnit4/src/core/execution/stages/GdUnitTestCaseExecutionStage.gd b/addons/gdUnit4/src/core/execution/stages/GdUnitTestCaseExecutionStage.gd index dc8c53d2..148d9af6 100644 --- a/addons/gdUnit4/src/core/execution/stages/GdUnitTestCaseExecutionStage.gd +++ b/addons/gdUnit4/src/core/execution/stages/GdUnitTestCaseExecutionStage.gd @@ -24,7 +24,7 @@ func _execute(context :GdUnitExecutionContext) -> void: await _stage_single_test.execute(context) -func set_debug_mode(debug_mode :bool = false): +func set_debug_mode(debug_mode :bool = false) -> void: super.set_debug_mode(debug_mode) _stage_single_test.set_debug_mode(debug_mode) _stage_fuzzer_test.set_debug_mode(debug_mode) diff --git a/addons/gdUnit4/src/core/execution/stages/GdUnitTestSuiteAfterStage.gd b/addons/gdUnit4/src/core/execution/stages/GdUnitTestSuiteAfterStage.gd index dab18da1..a6de3187 100644 --- a/addons/gdUnit4/src/core/execution/stages/GdUnitTestSuiteAfterStage.gd +++ b/addons/gdUnit4/src/core/execution/stages/GdUnitTestSuiteAfterStage.gd @@ -9,20 +9,20 @@ const GdUnitTools := preload("res://addons/gdUnit4/src/core/GdUnitTools.gd") func _execute(context :GdUnitExecutionContext) -> void: var test_suite := context.test_suite - + @warning_ignore("redundant_await") await test_suite.after() # unreference last used assert form the test to prevent memory leaks GdUnitThreadManager.get_current_context().set_assert(null) await context.gc() - + var reports := context.reports() var orphans := context.count_orphans() if orphans > 0: reports.push_front(GdUnitReport.new() \ .create(GdUnitReport.WARN, 1, GdAssertMessages.orphan_detected_on_suite_setup(orphans))) fire_event(GdUnitEvent.new().suite_after(test_suite.get_script().resource_path, test_suite.get_name(), context.build_report_statistics(orphans, false), reports)) - - GdUnitTools.clear_tmp() + + GdUnitFileAccess.clear_tmp() # Guard that checks if all doubled (spy/mock) objects are released GdUnitClassDoubler.check_leaked_instances() diff --git a/addons/gdUnit4/src/core/execution/stages/GdUnitTestSuiteBeforeStage.gd b/addons/gdUnit4/src/core/execution/stages/GdUnitTestSuiteBeforeStage.gd index 869f5adc..2260edb3 100644 --- a/addons/gdUnit4/src/core/execution/stages/GdUnitTestSuiteBeforeStage.gd +++ b/addons/gdUnit4/src/core/execution/stages/GdUnitTestSuiteBeforeStage.gd @@ -6,9 +6,9 @@ extends IGdUnitExecutionStage func _execute(context :GdUnitExecutionContext) -> void: var test_suite := context.test_suite - + fire_event(GdUnitEvent.new()\ .suite_before(test_suite.get_script().resource_path, test_suite.get_name(), test_suite.get_child_count())) - + @warning_ignore("redundant_await") await test_suite.before() diff --git a/addons/gdUnit4/src/core/execution/stages/GdUnitTestSuiteExecutionStage.gd b/addons/gdUnit4/src/core/execution/stages/GdUnitTestSuiteExecutionStage.gd index 9e796c35..ba223910 100644 --- a/addons/gdUnit4/src/core/execution/stages/GdUnitTestSuiteExecutionStage.gd +++ b/addons/gdUnit4/src/core/execution/stages/GdUnitTestSuiteExecutionStage.gd @@ -49,7 +49,7 @@ func clone_test_suite(test_suite :GdUnitTestSuite) -> GdUnitTestSuite: dispose_timers(test_suite) await GdUnitMemoryObserver.gc_guarded_instance("Manually free on awaiter", test_suite.__awaiter) var parent := test_suite.get_parent() - var _test_suite = GdUnitTestSuite.new() + var _test_suite := GdUnitTestSuite.new() parent.remove_child(test_suite) copy_properties(test_suite, _test_suite) for child in test_suite.get_children(): @@ -63,7 +63,7 @@ func clone_test_suite(test_suite :GdUnitTestSuite) -> GdUnitTestSuite: return _test_suite -func dispose_timers(test_suite :GdUnitTestSuite): +func dispose_timers(test_suite :GdUnitTestSuite) -> void: GdUnitTools.release_timers() for child in test_suite.get_children(): if child is Timer: @@ -72,11 +72,11 @@ func dispose_timers(test_suite :GdUnitTestSuite): child.free() -func copy_properties(source :Object, target :Object): +func copy_properties(source :Object, target :Object) -> void: if not source is _TestCase and not source is GdUnitTestSuite: return for property in source.get_property_list(): - var property_name = property["name"] + var property_name :String = property["name"] if property_name == "__awaiter": continue target.set(property_name, source.get(property_name)) @@ -87,7 +87,7 @@ func fire_test_suite_skipped(context :GdUnitExecutionContext) -> void: var skip_count := test_suite.get_child_count() fire_event(GdUnitEvent.new()\ .suite_before(test_suite.get_script().resource_path, test_suite.get_name(), skip_count)) - var statistics = { + var statistics := { GdUnitEvent.ORPHAN_NODES: 0, GdUnitEvent.ELAPSED_TIME: 0, GdUnitEvent.WARNINGS: false, @@ -103,7 +103,7 @@ func fire_test_suite_skipped(context :GdUnitExecutionContext) -> void: await Engine.get_main_loop().process_frame -func set_debug_mode(debug_mode :bool = false): +func set_debug_mode(debug_mode :bool = false) -> void: super.set_debug_mode(debug_mode) _stage_before.set_debug_mode(debug_mode) _stage_after.set_debug_mode(debug_mode) diff --git a/addons/gdUnit4/src/core/execution/stages/fuzzed/GdUnitTestCaseFuzzedExecutionStage.gd b/addons/gdUnit4/src/core/execution/stages/fuzzed/GdUnitTestCaseFuzzedExecutionStage.gd index 269a9ea8..d438b57c 100644 --- a/addons/gdUnit4/src/core/execution/stages/fuzzed/GdUnitTestCaseFuzzedExecutionStage.gd +++ b/addons/gdUnit4/src/core/execution/stages/fuzzed/GdUnitTestCaseFuzzedExecutionStage.gd @@ -14,7 +14,7 @@ func _execute(context :GdUnitExecutionContext) -> void: await _stage_after.execute(context) -func set_debug_mode(debug_mode :bool = false): +func set_debug_mode(debug_mode :bool = false) -> void: super.set_debug_mode(debug_mode) _stage_before.set_debug_mode(debug_mode) _stage_after.set_debug_mode(debug_mode) diff --git a/addons/gdUnit4/src/core/execution/stages/fuzzed/GdUnitTestCaseFuzzedTestStage.gd b/addons/gdUnit4/src/core/execution/stages/fuzzed/GdUnitTestCaseFuzzedTestStage.gd index 6b91d588..e6d98521 100644 --- a/addons/gdUnit4/src/core/execution/stages/fuzzed/GdUnitTestCaseFuzzedTestStage.gd +++ b/addons/gdUnit4/src/core/execution/stages/fuzzed/GdUnitTestCaseFuzzedTestStage.gd @@ -12,11 +12,11 @@ func _execute(context :GdUnitExecutionContext) -> void: var test_suite := context.test_suite var test_case := context.test_case var fuzzers := create_fuzzers(test_suite, test_case) - + # guard on fuzzers for fuzzer in fuzzers: GdUnitMemoryObserver.guard_instance(fuzzer) - + for iteration in test_case.iterations(): @warning_ignore("redundant_await") await test_suite.before_test() @@ -33,7 +33,7 @@ func _execute(context :GdUnitExecutionContext) -> void: .create(GdUnitReport.FAILURE, report.line_number(), GdAssertMessages.fuzzer_interuped(iteration, report.message()))) break await context.gc() - + # unguard on fuzzers if not test_case.is_interupted(): for fuzzer in fuzzers: diff --git a/addons/gdUnit4/src/core/execution/stages/parameterized/GdUnitTestCaseParameterizedExecutionStage.gd b/addons/gdUnit4/src/core/execution/stages/parameterized/GdUnitTestCaseParameterizedExecutionStage.gd index 52ccdc47..eb0dc277 100644 --- a/addons/gdUnit4/src/core/execution/stages/parameterized/GdUnitTestCaseParameterizedExecutionStage.gd +++ b/addons/gdUnit4/src/core/execution/stages/parameterized/GdUnitTestCaseParameterizedExecutionStage.gd @@ -15,7 +15,7 @@ func _execute(context :GdUnitExecutionContext) -> void: await _stage_after.execute(context) -func set_debug_mode(debug_mode :bool = false): +func set_debug_mode(debug_mode :bool = false) -> void: super.set_debug_mode(debug_mode) _stage_before.set_debug_mode(debug_mode) _stage_after.set_debug_mode(debug_mode) diff --git a/addons/gdUnit4/src/core/execution/stages/parameterized/GdUnitTestCaseParameterizedTestStage.gd b/addons/gdUnit4/src/core/execution/stages/parameterized/GdUnitTestCaseParameterizedTestStage.gd index 49202e77..99d616e4 100644 --- a/addons/gdUnit4/src/core/execution/stages/parameterized/GdUnitTestCaseParameterizedTestStage.gd +++ b/addons/gdUnit4/src/core/execution/stages/parameterized/GdUnitTestCaseParameterizedTestStage.gd @@ -2,39 +2,49 @@ class_name GdUnitTestCaseParamaterizedTestStage extends IGdUnitExecutionStage +var _stage_before: IGdUnitExecutionStage = GdUnitTestCaseBeforeStage.new() +var _stage_after: IGdUnitExecutionStage = GdUnitTestCaseAfterStage.new() -var _stage_before :IGdUnitExecutionStage = GdUnitTestCaseBeforeStage.new() -var _stage_after :IGdUnitExecutionStage = GdUnitTestCaseAfterStage.new() - -## Executes a paramaterized test case.[br] +## Executes a parameterized test case.[br] ## It executes synchronized following stages[br] ## -> test_case( ) [br] -func _execute(context :GdUnitExecutionContext) -> void: +func _execute(context: GdUnitExecutionContext) -> void: var test_case := context.test_case - var test_case_parameters := test_case.test_parameters() var test_parameter_index := test_case.test_parameter_index() - var test_case_names := test_case.test_case_names() var is_fail := false var is_error := false var failing_index := 0 - - for test_case_index in test_case.test_parameters().size(): + var parameter_set_resolver := test_case.parameter_set_resolver() + var test_names := parameter_set_resolver.build_test_case_names(test_case) + + # if all parameter sets has static values we can preload and reuse it for better performance + var parameter_sets :Array = [] + if parameter_set_resolver.is_parameter_sets_static(): + parameter_sets = parameter_set_resolver.load_parameter_sets(test_case, true) + + for parameter_set_index in test_names.size(): # is test_parameter_index is set, we run this parameterized test only - if test_parameter_index != -1 and test_parameter_index != test_case_index: + if test_parameter_index != -1 and test_parameter_index != parameter_set_index: continue - - _stage_before.set_test_name(test_case_names[test_case_index]) - _stage_after.set_test_name(test_case_names[test_case_index]) - + var current_test_case_name := test_names[parameter_set_index] + _stage_before.set_test_name(current_test_case_name) + _stage_after.set_test_name(current_test_case_name) + var test_context := GdUnitExecutionContext.of(context) await _stage_before.execute(test_context) - await test_case.execute_paramaterized(test_case_parameters[test_case_index]) + var current_parameter_set :Array + if parameter_set_resolver.is_parameter_set_static(parameter_set_index): + current_parameter_set = parameter_sets[parameter_set_index] + else: + current_parameter_set = _load_parameter_set(context, parameter_set_index) + if not test_case.is_interupted(): + await test_case.execute_paramaterized(current_parameter_set) await _stage_after.execute(test_context) # we need to clean up the reports here so they are not reported twice is_fail = is_fail or test_context.count_failures(false) > 0 is_error = is_error or test_context.count_errors(false) > 0 - failing_index = test_case_index - 1 + failing_index = parameter_set_index - 1 test_context.reports().clear() if test_case.is_interupted(): break @@ -46,7 +56,21 @@ func _execute(context :GdUnitExecutionContext) -> void: await context.gc() -func set_debug_mode(debug_mode :bool = false): +func _load_parameter_set(context: GdUnitExecutionContext, parameter_set_index: int) -> Array: + var test_case := context.test_case + var test_suite := context.test_suite + # we need to exchange temporary for parameter resolving the execution context + # this is necessary because of possible usage of `auto_free` and needs to run in the parent execution context + var save_execution_context: GdUnitExecutionContext = test_suite.__execution_context + context.set_active() + var parameters := test_case.load_parameter_sets() + # restore the original execution context and restart the orphan monitor to get new instances into account + save_execution_context.set_active() + save_execution_context.orphan_monitor_start() + return parameters[parameter_set_index] + + +func set_debug_mode(debug_mode: bool=false) -> void: super.set_debug_mode(debug_mode) _stage_before.set_debug_mode(debug_mode) _stage_after.set_debug_mode(debug_mode) diff --git a/addons/gdUnit4/src/core/execution/stages/single/GdUnitTestCaseSingleExecutionStage.gd b/addons/gdUnit4/src/core/execution/stages/single/GdUnitTestCaseSingleExecutionStage.gd index fde7eb29..b54d6a55 100644 --- a/addons/gdUnit4/src/core/execution/stages/single/GdUnitTestCaseSingleExecutionStage.gd +++ b/addons/gdUnit4/src/core/execution/stages/single/GdUnitTestCaseSingleExecutionStage.gd @@ -15,7 +15,7 @@ func _execute(context :GdUnitExecutionContext) -> void: await _stage_after.execute(context) -func set_debug_mode(debug_mode :bool = false): +func set_debug_mode(debug_mode :bool = false) -> void: super.set_debug_mode(debug_mode) _stage_before.set_debug_mode(debug_mode) _stage_after.set_debug_mode(debug_mode) diff --git a/addons/gdUnit4/src/core/parse/GdClassDescriptor.gd b/addons/gdUnit4/src/core/parse/GdClassDescriptor.gd index d2ac7048..87b1aeb5 100644 --- a/addons/gdUnit4/src/core/parse/GdClassDescriptor.gd +++ b/addons/gdUnit4/src/core/parse/GdClassDescriptor.gd @@ -3,18 +3,18 @@ extends RefCounted var _name :String -var _parent = null +var _parent :GdClassDescriptor = null var _is_inner_class :bool -var _functions +var _functions :Array[GdFunctionDescriptor] -func _init(p_name :String, p_is_inner_class :bool, p_functions :Array): +func _init(p_name :String, p_is_inner_class :bool, p_functions :Array[GdFunctionDescriptor]) -> void: _name = p_name _is_inner_class = p_is_inner_class _functions = p_functions -func set_parent_clazz(p_parent :GdClassDescriptor): +func set_parent_clazz(p_parent :GdClassDescriptor) -> void: _parent = p_parent @@ -30,5 +30,5 @@ func is_inner_class() -> bool: return _is_inner_class -func functions() -> Array: +func functions() -> Array[GdFunctionDescriptor]: return _functions diff --git a/addons/gdUnit4/src/core/parse/GdDefaultValueDecoder.gd b/addons/gdUnit4/src/core/parse/GdDefaultValueDecoder.gd index 846d414c..b1cc38a4 100644 --- a/addons/gdUnit4/src/core/parse/GdDefaultValueDecoder.gd +++ b/addons/gdUnit4/src/core/parse/GdDefaultValueDecoder.gd @@ -2,13 +2,14 @@ class_name GdDefaultValueDecoder extends GdUnitSingleton + @warning_ignore("unused_parameter") -var _decoders = { - TYPE_NIL: func(value): return "null", - TYPE_STRING: func(value): return '"%s"' % value, +var _decoders := { + TYPE_NIL: func(value :Variant) -> String: return "null", + TYPE_STRING: func(value :Variant) -> String: return '"%s"' % value, TYPE_STRING_NAME: _on_type_StringName, - TYPE_BOOL: func(value): return str(value).to_lower(), - TYPE_FLOAT: func(value): return '%f' % value, + TYPE_BOOL: func(value :Variant) -> String: return str(value).to_lower(), + TYPE_FLOAT: func(value :Variant) -> String: return '%f' % value, TYPE_COLOR: _on_type_Color, TYPE_ARRAY: _on_type_Array.bind(TYPE_ARRAY), TYPE_PACKED_BYTE_ARRAY: _on_type_Array.bind(TYPE_PACKED_BYTE_ARRAY), @@ -20,6 +21,7 @@ var _decoders = { TYPE_PACKED_COLOR_ARRAY: _on_type_Array.bind(TYPE_PACKED_COLOR_ARRAY), TYPE_PACKED_VECTOR2_ARRAY: _on_type_Array.bind(TYPE_PACKED_VECTOR2_ARRAY), TYPE_PACKED_VECTOR3_ARRAY: _on_type_Array.bind(TYPE_PACKED_VECTOR3_ARRAY), + GdObjects.TYPE_PACKED_VECTOR4_ARRAY: _on_type_Array.bind(GdObjects.TYPE_PACKED_VECTOR4_ARRAY), TYPE_DICTIONARY: _on_type_Dictionary, TYPE_RID: _on_type_RID, TYPE_NODE_PATH: _on_type_NodePath, @@ -45,7 +47,7 @@ var _decoders = { static func _regex(pattern :String) -> RegEx: var regex := RegEx.new() - var err = regex.compile(pattern) + var err := regex.compile(pattern) if err != OK: push_error("error '%s' checked pattern '%s'" % [err, pattern]) return null @@ -53,7 +55,7 @@ static func _regex(pattern :String) -> RegEx: func get_decoder(type :int) -> Callable: - return _decoders.get(type, func(value): return '%s' % value) + return _decoders.get(type, func(value :Variant) -> String: return '%s' % value) func _on_type_StringName(value :StringName) -> String: @@ -62,7 +64,7 @@ func _on_type_StringName(value :StringName) -> String: return 'StringName("%s")' % value -func _on_type_Object(value :Object, type :int) -> String: +func _on_type_Object(value :Object, _type :int) -> String: return str(value) @@ -78,11 +80,11 @@ func _on_type_NodePath(path :NodePath) -> String: return 'NodePath("%s")' % path -func _on_type_Callable(cb :Callable) -> String: +func _on_type_Callable(_cb :Callable) -> String: return 'Callable()' -func _on_type_Signal(s :Signal) -> String: +func _on_type_Signal(_s :Signal) -> String: return 'Signal()' @@ -92,7 +94,7 @@ func _on_type_Dictionary(dict :Dictionary) -> String: return str(dict) -func _on_type_Array(value, type :int) -> String: +func _on_type_Array(value :Variant, type :int) -> String: match type: TYPE_ARRAY: return str(value) @@ -121,6 +123,14 @@ func _on_type_Array(value, type :int) -> String: return "PackedVector3Array()" return "PackedVector3Array([%s])" % ", ".join(vectors) + GdObjects.TYPE_PACKED_VECTOR4_ARRAY: + var vectors := PackedStringArray() + for vector:Variant in value as Array: + vectors.append(_on_type_Vector(vector, TYPE_VECTOR4)) + if vectors.is_empty(): + return "PackedVector4Array()" + return "PackedVector4Array([%s])" % ", ".join(vectors) + TYPE_PACKED_STRING_ARRAY: var values := PackedStringArray() for v in value as PackedStringArray: @@ -135,7 +145,7 @@ func _on_type_Array(value, type :int) -> String: TYPE_PACKED_INT32_ARRAY,\ TYPE_PACKED_INT64_ARRAY: var vectors := PackedStringArray() - for vector in value as Array: + for vector :Variant in value as Array: vectors.append(str(vector)) if vectors.is_empty(): return GdObjects.type_as_string(type) + "()" @@ -233,7 +243,7 @@ static func decode(value :Variant) -> String: var type := typeof(value) if GdArrayTools.is_type_array(type) and value.is_empty(): return "" - var decoder :Callable = instance("GdUnitDefaultValueDecoders", func(): return GdDefaultValueDecoder.new()).get_decoder(type) + var decoder :Callable = instance("GdUnitDefaultValueDecoders", func() -> GdDefaultValueDecoder: return GdDefaultValueDecoder.new()).get_decoder(type) if decoder == null: push_error("No value decoder registered for type '%d'! Please open a Bug issue at 'https://github.com/MikeSchulze/gdUnit4/issues/new/choose'." % type) return "null" @@ -245,7 +255,7 @@ static func decode(value :Variant) -> String: static func decode_typed(type :int, value :Variant) -> String: if value == null: return "null" - var decoder :Callable = instance("GdUnitDefaultValueDecoders", func(): return GdDefaultValueDecoder.new()).get_decoder(type) + var decoder :Callable = instance("GdUnitDefaultValueDecoders", func() -> GdDefaultValueDecoder: return GdDefaultValueDecoder.new()).get_decoder(type) if decoder == null: push_error("No value decoder registered for type '%d'! Please open a Bug issue at 'https://github.com/MikeSchulze/gdUnit4/issues/new/choose'." % type) return "null" diff --git a/addons/gdUnit4/src/core/parse/GdFunctionArgument.gd b/addons/gdUnit4/src/core/parse/GdFunctionArgument.gd index 08f70079..57891860 100644 --- a/addons/gdUnit4/src/core/parse/GdFunctionArgument.gd +++ b/addons/gdUnit4/src/core/parse/GdFunctionArgument.gd @@ -1,18 +1,24 @@ class_name GdFunctionArgument extends RefCounted + +var _cleanup_leading_spaces := RegEx.create_from_string("(?m)^[ \t]+") +var _fix_comma_space := RegEx.create_from_string(""", {0,}\t{0,}(?=(?:[^"]*"[^"]*")*[^"]*$)(?!\\s)""") var _name: String var _type: int var _default_value :Variant +var _parameter_sets :PackedStringArray = [] const UNDEFINED :Variant = "<-NO_ARG->" const ARG_PARAMETERIZED_TEST := "test_parameters" -func _init(p_name :String, p_type :int = TYPE_MAX, p_default_value :Variant = UNDEFINED): +func _init(p_name :String, p_type :int = TYPE_MAX, value :Variant = UNDEFINED) -> void: _name = p_name _type = p_type - _default_value = p_default_value + if p_name == ARG_PARAMETERIZED_TEST: + _parameter_sets = _parse_parameter_set(value) + _default_value = value func name() -> String: @@ -20,7 +26,7 @@ func name() -> String: func default() -> Variant: - return convert(_default_value, _type) + return GodotVersionFixures.convert(_default_value, _type) func value_as_string() -> String: @@ -41,7 +47,11 @@ func is_parameter_set() -> bool: return _name == ARG_PARAMETERIZED_TEST -static func get_parameter_set(parameters :Array) -> GdFunctionArgument: +func parameter_sets() -> PackedStringArray: + return _parameter_sets + + +static func get_parameter_set(parameters :Array[GdFunctionArgument]) -> GdFunctionArgument: for current in parameters: if current != null and current.is_parameter_set(): return current @@ -49,9 +59,56 @@ static func get_parameter_set(parameters :Array) -> GdFunctionArgument: func _to_string() -> String: - var s = _name + var s := _name if _type != TYPE_MAX: s += ":" + GdObjects.type_as_string(_type) if _default_value != UNDEFINED: s += "=" + str(_default_value) return s + + +func _parse_parameter_set(input :String) -> PackedStringArray: + if not input.contains("["): + return [] + + input = _cleanup_leading_spaces.sub(input, "", true) + input = input.replace("\n", "").strip_edges().trim_prefix("[").trim_suffix("]").trim_prefix("]") + var single_quote := false + var double_quote := false + var array_end := 0 + var current_index := 0 + var output :PackedStringArray = [] + var buf := input.to_utf8_buffer() + var collected_characters: = PackedByteArray() + var matched :bool = false + + for c in buf: + current_index += 1 + matched = current_index == buf.size() + collected_characters.push_back(c) + + match c: + # ' ': ignore spaces between array elements + 32: if array_end == 0 and (not double_quote and not single_quote): + collected_characters.remove_at(collected_characters.size()-1) + # ',': step over array element seperator ',' + 44: if array_end == 0: + matched = true + collected_characters.remove_at(collected_characters.size()-1) + # '`': + 39: single_quote = !single_quote + # '"': + 34: if not single_quote: double_quote = !double_quote + # '[' + 91: if not double_quote and not single_quote: array_end +=1 # counts array open + # ']' + 93: if not double_quote and not single_quote: array_end -=1 # counts array closed + + # if array closed than collect the element + if matched: + var parameters := _fix_comma_space.sub(collected_characters.get_string_from_utf8(), ", ", true) + if not parameters.is_empty(): + output.append(parameters) + collected_characters.clear() + matched = false + return output diff --git a/addons/gdUnit4/src/core/parse/GdFunctionDescriptor.gd b/addons/gdUnit4/src/core/parse/GdFunctionDescriptor.gd index 51c18b0b..2633016e 100644 --- a/addons/gdUnit4/src/core/parse/GdFunctionDescriptor.gd +++ b/addons/gdUnit4/src/core/parse/GdFunctionDescriptor.gd @@ -21,7 +21,7 @@ func _init(p_name :String, p_return_type :int, p_return_class :String, p_args : Array[GdFunctionArgument], - p_varargs :Array[GdFunctionArgument] = []): + p_varargs :Array[GdFunctionArgument] = []) -> void: _name = p_name _line_number = p_line_number _return_type = p_return_type @@ -206,7 +206,7 @@ static func _extract_args(descriptor :Dictionary) -> Array[GdFunctionArgument]: var arg_type := _argument_type(arg) var arg_default :Variant = GdFunctionArgument.UNDEFINED if not defaults.is_empty(): - var default_value = defaults.pop_back() + var default_value :Variant = defaults.pop_back() arg_default = GdDefaultValueDecoder.decode_typed(arg_type, default_value) args_.push_front(GdFunctionArgument.new(arg_name, arg_type, arg_default)) return args_ diff --git a/addons/gdUnit4/src/core/parse/GdScriptParser.gd b/addons/gdUnit4/src/core/parse/GdScriptParser.gd index 0b2e55b6..38468f3e 100644 --- a/addons/gdUnit4/src/core/parse/GdScriptParser.gd +++ b/addons/gdUnit4/src/core/parse/GdScriptParser.gd @@ -35,7 +35,7 @@ var OPERATOR_MUL := Operator.new("*") var OPERATOR_DIV := Operator.new("/") var OPERATOR_REMAINDER := Operator.new("%") -var TOKENS := [ +var TOKENS :Array[Token] = [ TOKEN_SPACE, TOKEN_TABULATOR, TOKEN_NEW_LINE, @@ -65,8 +65,10 @@ var TOKENS := [ ] var _regex_clazz_name :RegEx +var _regex_strip_comments := GdUnitTools.to_regex("^([^#\"']|'[^']*'|\"[^\"]*\")*\\K#.*") var _base_clazz :String var _scanned_inner_classes := PackedStringArray() +var _script_constants := {} static func clean_up_row(row :String) -> String: @@ -82,14 +84,14 @@ class Token extends RefCounted: var _consumed: int var _is_operator: bool var _regex :RegEx - - + + func _init(p_token: String, p_is_operator := false, p_regex :RegEx = null) -> void: _token = p_token _is_operator = p_is_operator _consumed = p_token.length() _regex = p_regex - + func match(input: String, pos: int) -> bool: if _regex: var result := _regex.search(input, pos) @@ -98,40 +100,40 @@ class Token extends RefCounted: _consumed = result.get_end() - result.get_start() return pos == result.get_start() return input.findn(_token, pos) == pos - + func is_operator() -> bool: return _is_operator - + func is_inner_class() -> bool: return _token == "class" - + func is_variable() -> bool: return false - + func is_token(token_name :String) -> bool: return _token == token_name - + func is_skippable() -> bool: return false - - func _to_string(): + + func _to_string() -> String: return "Token{" + _token + "}" class Operator extends Token: - func _init(value: String): + func _init(value: String) -> void: super(value, true) - - func _to_string(): + + func _to_string() -> String: return "OperatorToken{%s}" % [_token] # A skippable token, is just a placeholder like space or tabs class SkippableToken extends Token: - - func _init(p_token: String): + + func _init(p_token: String) -> void: super(p_token) - + func is_skippable() -> bool: return true @@ -139,12 +141,12 @@ class SkippableToken extends Token: # Token to parse Fuzzers class FuzzerToken extends Token: var _name: String - - - func _init(regex: RegEx): + + + func _init(regex: RegEx) -> void: super("", false, regex) - - + + func match(input: String, pos: int) -> bool: if _regex: var result := _regex.search(input, pos) @@ -154,34 +156,34 @@ class FuzzerToken extends Token: _consumed = result.get_end() - result.get_start() return pos == result.get_start() return input.findn(_token, pos) == pos - - + + func name() -> String: return _name - - + + func type() -> int: return GdObjects.TYPE_FUZZER - - - func _to_string(): + + + func _to_string() -> String: return "FuzzerToken{%s: '%s'}" % [_name, _token] # Token to parse function arguments class Variable extends Token: - var _plain_value - var _typed_value + var _plain_value :String + var _typed_value :Variant var _type :int = TYPE_NIL - - - func _init(p_value: String): + + + func _init(p_value: String) -> void: super(p_value) _type = _scan_type(p_value) _plain_value = p_value _typed_value = _cast_to_type(p_value, _type) - - + + func _scan_type(p_value: String) -> int: if p_value.begins_with("\"") and p_value.ends_with("\""): return TYPE_STRING @@ -195,8 +197,8 @@ class Variable extends Token: if p_value.is_valid_hex_number(): return TYPE_INT return TYPE_OBJECT - - + + func _cast_to_type(p_value :String, p_type: int) -> Variant: match p_type: TYPE_STRING: @@ -206,59 +208,59 @@ class Variable extends Token: TYPE_FLOAT: return p_value.to_float() return p_value - - + + func is_variable() -> bool: return true - - + + func type() -> int: return _type - - - func value(): + + + func value() -> Variant: return _typed_value - - - func plain_value(): + + + func plain_value() -> String: return _plain_value - - - func _to_string(): + + + func _to_string() -> String: return "Variable{%s: %s : '%s'}" % [_plain_value, GdObjects.type_as_string(_type), _token] class TokenInnerClass extends Token: - var _clazz_name + var _clazz_name :String var _content := PackedStringArray() - - + + static func _strip_leading_spaces(input :String) -> String: - var characters := input.to_ascii_buffer() + var characters := input.to_utf8_buffer() while not characters.is_empty(): if characters[0] != 0x20: break characters.remove_at(0) - return characters.get_string_from_ascii() - - + return characters.get_string_from_utf8() + + static func _consumed_bytes(row :String) -> int: return row.replace(" ", "").replace(" ", "").length() - - - func _init(clazz_name :String): + + + func _init(clazz_name :String) -> void: super("class") _clazz_name = clazz_name - - + + func is_class_name(clazz_name :String) -> bool: return _clazz_name == clazz_name - - + + func content() -> PackedStringArray: return _content - - + + func parse(source_rows :PackedStringArray, offset :int) -> void: # add class signature _content.append(source_rows[offset]) @@ -266,7 +268,7 @@ class TokenInnerClass extends Token: for row_index in range(offset+1, source_rows.size()): # scan until next non tab var source_row := source_rows[row_index] - var row = TokenInnerClass._strip_leading_spaces(source_row) + var row := TokenInnerClass._strip_leading_spaces(source_row) if row.is_empty() or row.begins_with("\t") or row.begins_with("#"): # fold all line to left by removing leading tabs and spaces if source_row.begins_with("\t"): @@ -279,26 +281,26 @@ class TokenInnerClass extends Token: continue break _consumed += TokenInnerClass._consumed_bytes("".join(_content)) - - - func _to_string(): + + + func _to_string() -> String: return "TokenInnerClass{%s}" % [_clazz_name] -func _init(): +func _init() -> void: _regex_clazz_name = GdUnitTools.to_regex("(class)([a-zA-Z0-9]+)(extends[a-zA-Z]+:)|(class)([a-zA-Z0-9]+)(:)") -func get_token(input :String, current_index) -> Token: +func get_token(input :String, current_index :int) -> Token: for t in TOKENS: if t.match(input, current_index): return t return TOKEN_NOT_MATCH -func next_token(input: String, current_index: int) -> Token: +func next_token(input: String, current_index: int, ignore_tokens :Array[Token] = []) -> Token: var token := TOKEN_NOT_MATCH - for t in TOKENS: + for t :Token in TOKENS.filter(func(t :Token) -> bool: return not ignore_tokens.has(t)): if t.match(input, current_index): token = t break @@ -307,11 +309,11 @@ func next_token(input: String, current_index: int) -> Token: if token == TOKEN_INNER_CLASS: token = tokenize_inner_class(input, current_index, token) if token == TOKEN_NOT_MATCH: - return tokenize_value(input, current_index, token) + return tokenize_value(input, current_index, token, ignore_tokens.has(TOKEN_FUNCTION)) return token -func tokenize_value(input: String, current: int, token: Token) -> Token: +func tokenize_value(input: String, current: int, token: Token, ignore_dots := false) -> Token: var next := 0 var current_token := "" # test for '--', '+-', '*-', '/-', '%-', or at least '-x' @@ -323,7 +325,7 @@ func tokenize_value(input: String, current: int, token: Token) -> Token: # or is a float value if (test_for_sign and next==0) \ or character in ALLOWED_CHARACTERS \ - or (character == "." and current_token.is_valid_int()): + or (character == "." and (ignore_dots or current_token.is_valid_int())): current_token += character next += 1 continue @@ -354,9 +356,9 @@ func tokenize_inner_class(source_code: String, current: int, token: Token) -> To func _process_values(left: Token, token_stack: Array, operator: Token) -> Token: # precheck if left.is_variable() and operator.is_operator(): - var lvalue = left.value() - var value = null - var next_token_ = token_stack.pop_front() as Token + var lvalue :Variant = left.value() + var value :Variant = null + var next_token_ := token_stack.pop_front() as Token match operator: OPERATOR_ADD: value = lvalue + next_token_.value() @@ -386,10 +388,12 @@ func parse_return_token(input: String) -> Token: if index == -1: return TOKEN_NOT_MATCH index += TOKEN_FUNCTION_RETURN_TYPE._consumed - var token := next_token(input, index) + # We scan for the return value exclusive '.' token because it could be referenced to a + # external or internal class e.g. 'func foo() -> InnerClass.Bar:' + var token := next_token(input, index, [TOKEN_FUNCTION]) while !token.is_variable() and token != TOKEN_NOT_MATCH: index += token._consumed - token = next_token(input, index) + token = next_token(input, index, [TOKEN_FUNCTION]) return token @@ -443,7 +447,7 @@ func parse_arguments(input: String) -> Array[GdFunctionArgument]: if in_function and token.is_variable(): var arg_name :String = token.plain_value() var arg_type :int = TYPE_NIL - var arg_value = GdFunctionArgument.UNDEFINED + var arg_value :Variant = GdFunctionArgument.UNDEFINED # parse type and default value while current_index < len(input): token = next_token(input, current_index) @@ -457,6 +461,9 @@ func parse_arguments(input: String) -> Array[GdFunctionArgument]: current_index += token._consumed token = next_token(input, current_index) arg_type = GdObjects.string_as_typeof(token._token) + # handle enum detection as argument + if arg_type == GdObjects.TYPE_VARIANT and is_class_enum_type(token._token): + arg_type = GdObjects.TYPE_ENUM TOKEN_ARGUMENT_TYPE_ASIGNMENT: arg_value = _parse_end_function(input.substr(current_index), true) current_index += arg_value.length() @@ -469,8 +476,8 @@ func parse_arguments(input: String) -> Array[GdFunctionArgument]: # if value a function? if bracket > 1: # complete the argument value - var func_begin = input.substr(current_index-TOKEN_BRACKET_OPEN._consumed) - var func_body = _parse_end_function(func_begin) + var func_begin := input.substr(current_index-TOKEN_BRACKET_OPEN._consumed) + var func_body := _parse_end_function(func_begin) arg_value += func_body # fix parse index to end of value current_index += func_body.length() - TOKEN_BRACKET_OPEN._consumed - TOKEN_BRACKET_CLOSE._consumed @@ -506,40 +513,15 @@ func parse_arguments(input: String) -> Array[GdFunctionArgument]: return args -# Parse an string for an argument with given name and returns the value -# if the argument not found the is returned -func parse_argument(row: String, argument_name: String, default_value): - var input := GdScriptParser.clean_up_row(row) - var argument_found := false - var current_index := 0 - var token :Token = null - while current_index < len(input): - token = next_token(input, current_index) as Token - current_index += token._consumed - if token == TOKEN_NOT_MATCH: - return default_value - if not argument_found and not token.is_token(argument_name): - continue - argument_found = true - # extract value - if token == TOKEN_ARGUMENT_TYPE_ASIGNMENT: - token = next_token(input, current_index) as Token - return token.value() - elif token == TOKEN_ARGUMENT_ASIGNMENT: - token = next_token(input, current_index) as Token - return token.value() - return default_value - - func _parse_end_function(input: String, remove_trailing_char := false) -> String: # find end of function var current_index := 0 var bracket_count := 0 var in_array := 0 - var end_of_func = false - + var end_of_func := false + while current_index < len(input) and not end_of_func: - var character = input[current_index] + var character := input[current_index] # step over strings if character == "'" : current_index = input.find("'", current_index+1) + 1 @@ -557,7 +539,7 @@ func _parse_end_function(input: String, remove_trailing_char := false) -> String push_error("Parsing error on '%s', can't evaluate end of string." % input) return "" continue - + match character: # count if inside an array "[": in_array += 1 @@ -609,9 +591,12 @@ func extract_source_code(script_path :PackedStringArray) -> PackedStringArray: func extract_func_signature(rows :PackedStringArray, index :int) -> String: var signature := "" - + for rowIndex in range(index, rows.size()): var row := rows[rowIndex] + row = _regex_strip_comments.sub(row, "").strip_edges(false) + if row.is_empty(): + continue signature += row + "\n" if is_func_end(row): return signature.strip_edges() @@ -621,19 +606,19 @@ func extract_func_signature(rows :PackedStringArray, index :int) -> String: func load_source_code(script :GDScript, script_path :PackedStringArray) -> PackedStringArray: var map := script.get_script_constant_map() - for key in map.keys(): - var value = map.get(key) + for key :String in map.keys(): + var value :Variant = map.get(key) if value is GDScript: var class_path := GdObjects.extract_class_path(value) if class_path.size() > 1: _scanned_inner_classes.append(class_path[1]) - + var source_code := GdScriptParser.to_unix_format(script.source_code) var source_rows := source_code.split("\n") # extract all inner class names # want to extract an inner class? if script_path.size() > 1: - var inner_clazz = script_path[1] + var inner_clazz := script_path[1] source_rows = extract_inner_class(source_rows, inner_clazz) return PackedStringArray(source_rows) @@ -641,20 +626,23 @@ func load_source_code(script :GDScript, script_path :PackedStringArray) -> Packe func get_class_name(script :GDScript) -> String: var source_code := GdScriptParser.to_unix_format(script.source_code) var source_rows := source_code.split("\n") - - for index in min(10, source_rows.size()): - var input = GdScriptParser.clean_up_row(source_rows[index]) + + for index :int in min(10, source_rows.size()): + var input := source_rows[index] var token := next_token(input, 0) if token == TOKEN_CLASS_NAME: - token = tokenize_value(input, token._consumed, token) + var current_index := token._consumed + token = next_token(input, current_index) + current_index += token._consumed + token = tokenize_value(input, current_index, token) return token.value() # if no class_name found extract from file name return GdObjects.to_pascal_case(script.resource_path.get_basename().get_file()) func parse_func_name(row :String) -> String: - var input = GdScriptParser.clean_up_row(row) - var current_index = 0 + var input := GdScriptParser.clean_up_row(row) + var current_index := 0 var token := next_token(input, current_index) current_index += token._consumed if token != TOKEN_FUNCTION_STATIC_DECLARATION and token != TOKEN_FUNCTION_DECLARATION: @@ -668,18 +656,18 @@ func parse_func_name(row :String) -> String: func parse_functions(rows :PackedStringArray, clazz_name :String, clazz_path :PackedStringArray, included_functions := PackedStringArray()) -> Array[GdFunctionDescriptor]: var func_descriptors :Array[GdFunctionDescriptor] = [] for rowIndex in rows.size(): - var row = rows[rowIndex] + var row := rows[rowIndex] # step over inner class functions if row.begins_with("\t"): continue - var input = GdScriptParser.clean_up_row(row) + var input := GdScriptParser.clean_up_row(row) # skip comments and empty lines if input.begins_with("#") or input.length() == 0: continue var token := next_token(input, 0) if token == TOKEN_FUNCTION_STATIC_DECLARATION or token == TOKEN_FUNCTION_DECLARATION: if _is_func_included(input, included_functions): - var func_signature = extract_func_signature(rows, rowIndex) + var func_signature := extract_func_signature(rows, rowIndex) var fd := parse_func_description(func_signature, clazz_name, clazz_path, rowIndex+1) fd._is_coroutine = is_func_coroutine(rows, rowIndex) func_descriptors.append(fd) @@ -689,11 +677,11 @@ func parse_functions(rows :PackedStringArray, clazz_name :String, clazz_path :Pa func is_func_coroutine(rows :PackedStringArray, index :int) -> bool: var is_coroutine := false for rowIndex in range( index+1, rows.size()): - var row = rows[rowIndex] + var row := rows[rowIndex] is_coroutine = row.contains("await") if is_coroutine: return true - var input = GdScriptParser.clean_up_row(row) + var input := GdScriptParser.clean_up_row(row) var token := next_token(input, 0) if token == TOKEN_FUNCTION_STATIC_DECLARATION or token == TOKEN_FUNCTION_DECLARATION: break @@ -710,7 +698,7 @@ func _is_func_included(row :String, included_functions :PackedStringArray) -> bo func parse_func_description(func_signature :String, clazz_name :String, clazz_path :PackedStringArray, line_number :int) -> GdFunctionDescriptor: - var name = parse_func_name(func_signature) + var name := parse_func_name(func_signature) var return_type :int var return_clazz := "" var token := parse_return_token(func_signature) @@ -720,7 +708,10 @@ func parse_func_description(func_signature :String, clazz_name :String, clazz_pa return_type = token.type() if token.type() == TYPE_OBJECT: return_clazz = _patch_inner_class_names(token.value(), clazz_name) - + # is return type an enum? + if is_class_enum_type(return_clazz): + return_type = GdObjects.TYPE_ENUM + return GdFunctionDescriptor.new( name, line_number, @@ -742,7 +733,7 @@ func is_virtual_func(clazz_name :String, clazz_path :PackedStringArray, func_nam return _virtual_func_cache[clazz_name].has(func_name) var virtual_functions := Array() var method_list := GdObjects.extract_class_functions(clazz_name, clazz_path) - for method_descriptor in method_list: + for method_descriptor :Dictionary in method_list: var is_virtual_function :bool = method_descriptor["flags"] & METHOD_FLAG_VIRTUAL if is_virtual_function: virtual_functions.append(method_descriptor["name"]) @@ -764,17 +755,38 @@ func is_func_end(row :String) -> bool: return row.strip_edges(false, true).ends_with(":") -func _patch_inner_class_names(value :String, clazz_name :String) -> String: - var patch := value +func is_class_enum_type(value :String) -> bool: + if value == "Variant": + return false + # first check is given value a enum from the current class + if _script_constants.has(value): + return true + # otherwise we need to determie it by reflection + var script := GDScript.new() + script.source_code = """ + extends Resource + + static func is_class_enum_type() -> bool: + return typeof(%s) == TYPE_DICTIONARY + + """.dedent() % value + script.reload() + return script.call("is_class_enum_type") + + +func _patch_inner_class_names(clazz :String, clazz_name :String) -> String: var base_clazz := clazz_name.split(".")[0] - for inner_clazz_name in _scanned_inner_classes: - var full_inner_clazz_path = base_clazz + "." + inner_clazz_name - patch = patch.replace(inner_clazz_name, full_inner_clazz_path) - return patch + var inner_clazz_name := clazz.split(".")[0] + if _scanned_inner_classes.has(inner_clazz_name): + return base_clazz + "." + clazz + if _script_constants.has(clazz): + return clazz_name + "." + clazz + return clazz -func extract_functions(script :GDScript, clazz_name :String, clazz_path :PackedStringArray) -> Array: +func extract_functions(script :GDScript, clazz_name :String, clazz_path :PackedStringArray) -> Array[GdFunctionDescriptor]: var source_code := load_source_code(script, clazz_path) + _script_constants = script.get_script_constant_map() return parse_functions(source_code, clazz_name, clazz_path) diff --git a/addons/gdUnit4/src/core/parse/GdTestParameterSet.gd b/addons/gdUnit4/src/core/parse/GdTestParameterSet.gd deleted file mode 100644 index ba1a4411..00000000 --- a/addons/gdUnit4/src/core/parse/GdTestParameterSet.gd +++ /dev/null @@ -1,70 +0,0 @@ -class_name GdTestParameterSet -extends RefCounted - -const CLASS_TEMPLATE = """ -class_name _ParameterExtractor extends '${clazz_path}' - -func __extract_test_parameters() -> Array: - return ${test_params} - -""" - -# validates the given arguments are complete and matches to required input fields of the test function -static func validate(input_arguments :Array, input_value_set :Array) -> String: - # check given parameter set with test case arguments - var expected_arg_count = input_arguments.size() - 1 - for input_values in input_value_set: - var parameter_set_index := input_value_set.find(input_values) - if input_values is Array: - var current_arg_count = input_values.size() - if current_arg_count != expected_arg_count: - return "\n The parameter set at index [%d] does not match the expected input parameters!\n The test case requires [%d] input parameters, but the set contains [%d]" % [parameter_set_index, expected_arg_count, current_arg_count] - var error := validate_parameter_types(input_arguments, input_values, parameter_set_index) - if not error.is_empty(): - return error - else: - return "\n The parameter set at index [%d] does not match the expected input parameters!\n Expecting an array of input values." % parameter_set_index - return "" - -static func validate_parameter_types(input_arguments :Array, input_values :Array, parameter_set_index :int) -> String: - for i in input_arguments.size(): - var input_param :GdFunctionArgument = input_arguments[i] - # only check the test input arguments - if input_param.is_parameter_set(): - continue - var input_param_type := input_param.type() - var input_value = input_values[i] - var input_value_type := typeof(input_value) - # input parameter is not typed we skip the type test - if input_param_type == TYPE_NIL: - continue - # is input type enum allow int values - if input_param_type == GdObjects.TYPE_VARIANT and input_value_type == TYPE_INT: - continue - # allow only equal types and object == null - if input_param_type == TYPE_OBJECT and input_value_type == TYPE_NIL: - continue - if input_param_type != input_value_type: - return "\n The parameter set at index [%d] does not match the expected input parameters!\n The value '%s' does not match the required input parameter <%s>." % [parameter_set_index, input_value, input_param] - return "" - -# extracts the arguments from the given test case, using kind of reflection solution -# to restore the parameters from a string representation to real instance type -static func extract_test_parameters(source :GDScript, fd :GdFunctionDescriptor) -> Array: - var parameter_arg := GdFunctionArgument.get_parameter_set(fd.args()) - var source_code = CLASS_TEMPLATE\ - .replace("${clazz_path}", source.resource_path)\ - .replace("${test_params}", parameter_arg.value_as_string()) - var script = GDScript.new() - script.source_code = source_code - # enable this lines only for debuging - #script.resource_path = GdUnitTools.create_temp_dir("parameter_extract") + "/%s__.gd" % fd.name() - #DirAccess.remove_absolute(script.resource_path) - #ResourceSaver.save(script, script.resource_path) - var result = script.reload() - if result != OK: - push_error("Extracting test parameters failed! Script loading error: %s" % result) - return [] - var instance = script.new() - instance.queue_free() - return instance.call("__extract_test_parameters") diff --git a/addons/gdUnit4/src/core/parse/GdUnitTestParameterSetResolver.gd b/addons/gdUnit4/src/core/parse/GdUnitTestParameterSetResolver.gd new file mode 100644 index 00000000..d67ee254 --- /dev/null +++ b/addons/gdUnit4/src/core/parse/GdUnitTestParameterSetResolver.gd @@ -0,0 +1,193 @@ +class_name GdUnitTestParameterSetResolver +extends RefCounted + +const CLASS_TEMPLATE = """ +class_name _ParameterExtractor extends '${clazz_path}' + +func __extract_test_parameters() -> Array: + return ${test_params} + +""" + +const EXCLUDE_PROPERTIES_TO_COPY = [ + "script", + "type", + "Node", + "_import_path"] + + +var _fd: GdFunctionDescriptor +var _test_case_names_cache := PackedStringArray() +var _static_sets_by_index := {} +var _is_static := true + +func _init(fd: GdFunctionDescriptor) -> void: + _fd = fd + + +func is_parameterized() -> bool: + return _fd.is_parameterized() + + +func is_parameter_sets_static() -> bool: + return _is_static + + +func is_parameter_set_static(index: int) -> bool: + return _is_static and _static_sets_by_index.get(index, false) + + +# validates the given arguments are complete and matches to required input fields of the test function +func validate(input_value_set: Array) -> String: + var input_arguments := _fd.args() + # check given parameter set with test case arguments + var expected_arg_count := input_arguments.size() - 1 + for input_values :Variant in input_value_set: + var parameter_set_index := input_value_set.find(input_values) + if input_values is Array: + var current_arg_count :int = input_values.size() + if current_arg_count != expected_arg_count: + return "\n The parameter set at index [%d] does not match the expected input parameters!\n The test case requires [%d] input parameters, but the set contains [%d]" % [parameter_set_index, expected_arg_count, current_arg_count] + var error := GdUnitTestParameterSetResolver.validate_parameter_types(input_arguments, input_values, parameter_set_index) + if not error.is_empty(): + return error + else: + return "\n The parameter set at index [%d] does not match the expected input parameters!\n Expecting an array of input values." % parameter_set_index + return "" + + +static func validate_parameter_types(input_arguments: Array, input_values: Array, parameter_set_index: int) -> String: + for i in input_arguments.size(): + var input_param: GdFunctionArgument = input_arguments[i] + # only check the test input arguments + if input_param.is_parameter_set(): + continue + var input_param_type := input_param.type() + var input_value :Variant = input_values[i] + var input_value_type := typeof(input_value) + # input parameter is not typed or is Variant we skip the type test + if input_param_type == TYPE_NIL or input_param_type == GdObjects.TYPE_VARIANT: + continue + # is input type enum allow int values + if input_param_type == GdObjects.TYPE_VARIANT and input_value_type == TYPE_INT: + continue + # allow only equal types and object == null + if input_param_type == TYPE_OBJECT and input_value_type == TYPE_NIL: + continue + if input_param_type != input_value_type: + return "\n The parameter set at index [%d] does not match the expected input parameters!\n The value '%s' does not match the required input parameter <%s>." % [parameter_set_index, input_value, input_param] + return "" + + +func build_test_case_names(test_case: _TestCase) -> PackedStringArray: + if not is_parameterized(): + return [] + # if test names already resolved? + if not _test_case_names_cache.is_empty(): + return _test_case_names_cache + + var fa := GdFunctionArgument.get_parameter_set(_fd.args()) + var parameter_sets := fa.parameter_sets() + # if no parameter set detected we need to resolve it by using reflection + if parameter_sets.size() == 0: + _test_case_names_cache = _extract_test_names_by_reflection(test_case) + _is_static = false + else: + var property_names := _extract_property_names(test_case.get_parent()) + for parameter_set_index in parameter_sets.size(): + var parameter_set := parameter_sets[parameter_set_index] + _static_sets_by_index[parameter_set_index] = _is_static_parameter_set(parameter_set, property_names) + _test_case_names_cache.append(GdUnitTestParameterSetResolver._build_test_case_name(test_case, parameter_set, parameter_set_index)) + parameter_set_index += 1 + return _test_case_names_cache + + +func _extract_property_names(node :Node) -> PackedStringArray: + return node.get_property_list()\ + .map(func(property :Dictionary) -> String: return property["name"])\ + .filter(func(property :String) -> bool: return !EXCLUDE_PROPERTIES_TO_COPY.has(property)) + + +# tests if the test property set contains an property reference by name, if not the parameter set holds only static values +func _is_static_parameter_set(parameters :String, property_names :PackedStringArray) -> bool: + for property_name in property_names: + if parameters.contains(property_name): + _is_static = false + return false + return true + + +func _extract_test_names_by_reflection(test_case: _TestCase) -> PackedStringArray: + var parameter_sets := load_parameter_sets(test_case) + var test_case_names: PackedStringArray = [] + for index in parameter_sets.size(): + test_case_names.append(GdUnitTestParameterSetResolver._build_test_case_name(test_case, str(parameter_sets[index]), index)) + return test_case_names + + +static func _build_test_case_name(test_case: _TestCase, test_parameter: String, parameter_set_index: int) -> String: + if not test_parameter.begins_with("["): + test_parameter = "[" + test_parameter + return "%s:%d %s" % [test_case.get_name(), parameter_set_index, test_parameter.replace("\t", "").replace('"', "'").replace("&'", "'")] + + +# extracts the arguments from the given test case, using kind of reflection solution +# to restore the parameters from a string representation to real instance type +func load_parameter_sets(test_case: _TestCase, do_validate := false) -> Array: + var source_script :Script = test_case.get_parent().get_script() + var parameter_arg := GdFunctionArgument.get_parameter_set(_fd.args()) + var source_code := CLASS_TEMPLATE \ + .replace("${clazz_path}", source_script.resource_path) \ + .replace("${test_params}", parameter_arg.value_as_string()) + var script := GDScript.new() + script.source_code = source_code + # enable this lines only for debuging + #script.resource_path = GdUnitFileAccess.create_temp_dir("parameter_extract") + "/%s__.gd" % test_case.get_name() + #DirAccess.remove_absolute(script.resource_path) + #ResourceSaver.save(script, script.resource_path) + var result := script.reload() + if result != OK: + push_error("Extracting test parameters failed! Script loading error: %s" % result) + return [] + var instance :Variant = script.new() + GdUnitTestParameterSetResolver.copy_properties(test_case.get_parent(), instance) + instance.queue_free() + var parameter_sets :Variant = instance.call("__extract_test_parameters") + if not do_validate: + return parameter_sets + # validate the parameter set + var error := validate(parameter_sets) + if not error.is_empty(): + test_case.skip(true, error) + test_case._interupted = true + if parameter_sets.size() != _test_case_names_cache.size(): + push_error("Internal Error: The resolved test_case names has invalid size!") + error = """ + %s: + The resolved test_case names has invalid size! + %s + """.dedent().trim_prefix("\n") % [ + GdAssertMessages._error("Internal Error"), + GdAssertMessages._error("Please report this issue as a bug!")] + test_case.get_parent().__execution_context\ + .reports()\ + .append(GdUnitReport.new().create(GdUnitReport.INTERUPTED, test_case.line_number(), error)) + test_case.skip(true, error) + test_case._interupted = true + return parameter_sets + + +static func copy_properties(source: Object, dest: Object) -> void: + for property in source.get_property_list(): + var property_name :String = property["name"] + var property_value :Variant = source.get(property_name) + if EXCLUDE_PROPERTIES_TO_COPY.has(property_name): + continue + #if dest.get(property_name) == null: + # prints("|%s|" % property_name, source.get(property_name)) + + # check for invalid name property + if property_name == "name" and property_value == "": + dest.set(property_name, ""); + continue + dest.set(property_name, property_value) diff --git a/addons/gdUnit4/src/core/report/GdUnitReport.gd b/addons/gdUnit4/src/core/report/GdUnitReport.gd index 90b260a8..eb7ed2e5 100644 --- a/addons/gdUnit4/src/core/report/GdUnitReport.gd +++ b/addons/gdUnit4/src/core/report/GdUnitReport.gd @@ -18,7 +18,7 @@ var _line_number :int var _message :String -func create(p_type, p_line_number :int, p_message :String) -> GdUnitReport: +func create(p_type :int, p_line_number :int, p_message :String) -> GdUnitReport: _type = p_type _line_number = p_line_number _message = p_message @@ -53,7 +53,7 @@ func is_error() -> bool: return _type == TERMINATED or _type == INTERUPTED or _type == ABORT -func _to_string(): +func _to_string() -> String: if _line_number == -1: return "[color=green]line [/color][color=aqua]:[/color] %s" % [_message] return "[color=green]line [/color][color=aqua]%d:[/color] %s" % [_line_number, _message] @@ -67,7 +67,7 @@ func serialize() -> Dictionary: } -func deserialize(serialized:Dictionary) -> GdUnitReport: +func deserialize(serialized :Dictionary) -> GdUnitReport: _type = serialized["type"] _line_number = serialized["line_number"] _message = serialized["message"] diff --git a/addons/gdUnit4/src/core/templates/test_suite/GdUnitTestSuiteDefaultTemplate.gd b/addons/gdUnit4/src/core/templates/test_suite/GdUnitTestSuiteDefaultTemplate.gd index 504f11da..f7802d25 100644 --- a/addons/gdUnit4/src/core/templates/test_suite/GdUnitTestSuiteDefaultTemplate.gd +++ b/addons/gdUnit4/src/core/templates/test_suite/GdUnitTestSuiteDefaultTemplate.gd @@ -8,7 +8,7 @@ const DEFAULT_TEMP_TS_GD =""" extends GdUnitTestSuite @warning_ignore('unused_parameter') @warning_ignore('return_value_discarded') - + # TestSuite generated from const __source = '${source_resource_path}' """ @@ -16,21 +16,21 @@ const DEFAULT_TEMP_TS_GD =""" const DEFAULT_TEMP_TS_CS = """ // GdUnit generated TestSuite - + using Godot; using GdUnit3; - + namespace ${name_space} { using static Assertions; using static Utils; - + [TestSuite] public class ${suite_class_name} { // TestSuite generated from private const string sourceClazzPath = "${source_resource_path}"; - + } } """ diff --git a/addons/gdUnit4/src/core/templates/test_suite/GdUnitTestSuiteTemplate.gd b/addons/gdUnit4/src/core/templates/test_suite/GdUnitTestSuiteTemplate.gd index 78a8e52a..3e241f34 100644 --- a/addons/gdUnit4/src/core/templates/test_suite/GdUnitTestSuiteTemplate.gd +++ b/addons/gdUnit4/src/core/templates/test_suite/GdUnitTestSuiteTemplate.gd @@ -12,22 +12,22 @@ const SUPPORTED_TAGS_GD = """ # is used to build the test suite class name class_name ${suite_class_name} extends GdUnitTestSuite - - + + # The class name in pascal case, formed from the source script. ${source_class} # can be used to create the class e.g. for source 'MyClass' var my_test_class := ${source_class}.new() # will be result in var my_test_class := MyClass.new() - + # The class as variable name in snake case, formed from the source script. ${source_var} # Can be used to build the variable name e.g. for source 'MyClass' var ${source_var} := ${source_class}.new() # will be result in var my_class := MyClass.new() - + # The full resource path from which the file was created. ${source_resource_path} # Can be used to load the script in your test @@ -36,34 +36,33 @@ const SUPPORTED_TAGS_GD = """ var my_script := load("res://folder/my_class.gd") """ - const SUPPORTED_TAGS_CS = """ C# Tags are replaced when the test-suite is created. // The namespace name of the test-suite ${name_space} namespace ${name_space} - + // The class name of the test-suite, formed from the source class. ${suite_class_name} // is used to build the test suite class name [TestSuite] public class ${suite_class_name} - + // The class name formed from the source class. ${source_class} // can be used to create the class e.g. for source 'MyClass' private string myTestClass = new ${source_class}(); // will be result in private string myTestClass = new MyClass(); - + // The class as variable name in camelCase, formed from the source class. ${source_var} // Can be used to build the variable name e.g. for source 'MyClass' private object ${source_var} = new ${source_class}(); // will be result in private object myClass = new MyClass(); - + // The full resource path from which the file was created. ${source_resource_path} // Can be used to load the script in your test diff --git a/addons/gdUnit4/src/core/thread/GdUnitThreadContext.gd b/addons/gdUnit4/src/core/thread/GdUnitThreadContext.gd index d0eaa16d..fe326a68 100644 --- a/addons/gdUnit4/src/core/thread/GdUnitThreadContext.gd +++ b/addons/gdUnit4/src/core/thread/GdUnitThreadContext.gd @@ -9,7 +9,7 @@ var _signal_collector :GdUnitSignalCollector var _execution_context :GdUnitExecutionContext -func _init(thread :Thread = null): +func _init(thread :Thread = null) -> void: if thread != null: _thread = thread _thread_name = thread.get_meta("name") diff --git a/addons/gdUnit4/src/core/thread/GdUnitThreadManager.gd b/addons/gdUnit4/src/core/thread/GdUnitThreadManager.gd index 532946de..ad124ced 100644 --- a/addons/gdUnit4/src/core/thread/GdUnitThreadManager.gd +++ b/addons/gdUnit4/src/core/thread/GdUnitThreadManager.gd @@ -1,20 +1,20 @@ ## A manager to run new thread and crate a ThreadContext shared over the actual test run class_name GdUnitThreadManager -extends RefCounted +extends Object ## { = } var _thread_context_by_id := {} ## holds the current thread id var _current_thread_id :int = -1 -func _init(): +func _init() -> void: # add initail the main thread _current_thread_id = OS.get_thread_caller_id() _thread_context_by_id[OS.get_main_thread_id()] = GdUnitThreadContext.new() static func instance() -> GdUnitThreadManager: - return GdUnitSingleton.instance("GdUnitThreadManager", func(): return GdUnitThreadManager.new()) + return GdUnitSingleton.instance("GdUnitThreadManager", func() -> GdUnitThreadManager: return GdUnitThreadManager.new()) ## Runs a new thread by given name and Callable.[br] @@ -25,15 +25,15 @@ static func run(name :String, cb :Callable) -> Variant: return await instance()._run(name, cb) -## Returns the current valid thread context +## Returns the current valid thread context static func get_current_context() -> GdUnitThreadContext: return instance()._get_current_context() -func _run(name :String, cb :Callable): +func _run(name :String, cb :Callable) -> Variant: # we do this hack because of `OS.get_thread_caller_id()` not returns the current id # when await process_frame is called inside the fread - var save_current_thread_id = _current_thread_id + var save_current_thread_id := _current_thread_id var thread := Thread.new() thread.set_meta("name", name) thread.start(cb) diff --git a/addons/gdUnit4/src/extractors/GdUnitFuncValueExtractor.gd b/addons/gdUnit4/src/extractors/GdUnitFuncValueExtractor.gd index 74cded24..29cc62b6 100644 --- a/addons/gdUnit4/src/extractors/GdUnitFuncValueExtractor.gd +++ b/addons/gdUnit4/src/extractors/GdUnitFuncValueExtractor.gd @@ -1,15 +1,15 @@ # This class defines a value extractor by given function name and args extends GdUnitValueExtractor -var _func_names :Array +var _func_names :PackedStringArray var _args :Array -func _init(func_name :String, p_args :Array): +func _init(func_name :String, p_args :Array) -> void: _func_names = func_name.split(".") _args = p_args -func func_names() -> Array: +func func_names() -> PackedStringArray: return _func_names @@ -18,7 +18,7 @@ func args() -> Array: # Extracts a value by given `func_name` and `args`, -# Allows to use a chained list of functions setarated ba a dot. +# Allows to use a chained list of functions setarated ba a dot. # e.g. "func_a.func_b.name" # do calls instance.func_a().func_b().name() and returns finally the name # If a function returns an array, all elements will by collected in a array @@ -27,13 +27,13 @@ func args() -> Array: # # if the value not a Object or not accesible be `func_name` the value is converted to `"n.a."` # expecing null values -func extract_value(value): +func extract_value(value :Variant) -> Variant: if value == null: return null for func_name in func_names(): if GdArrayTools.is_array_type(value): var values := Array() - for element in Array(value): + for element :Variant in Array(value): values.append(_call_func(element, func_name)) value = values else: @@ -46,12 +46,12 @@ func extract_value(value): return value -func _call_func(value, func_name :String): +func _call_func(value :Variant, func_name :String) -> Variant: # for array types we need to call explicit by function name, using funcref is only supported for Objects # TODO extend to all array functions if GdArrayTools.is_array_type(value) and func_name == "empty": return value.is_empty() - + if is_instance_valid(value): # extract from function if value.has_method(func_name): @@ -60,7 +60,7 @@ func _call_func(value, func_name :String): return value.call(func_name) if args().is_empty() else value.callv(func_name, args()) else: # if no function exists than try to extract form parmeters - var parameter = value.get(func_name) + var parameter :Variant = value.get(func_name) if parameter != null: return parameter # nothing found than return 'n.a.' diff --git a/addons/gdUnit4/src/fuzzers/FloatFuzzer.gd b/addons/gdUnit4/src/fuzzers/FloatFuzzer.gd new file mode 100644 index 00000000..347513f3 --- /dev/null +++ b/addons/gdUnit4/src/fuzzers/FloatFuzzer.gd @@ -0,0 +1,13 @@ +class_name FloatFuzzer +extends Fuzzer + +var _from: float = 0 +var _to: float = 0 + +func _init(from: float, to: float) -> void: + assert(from <= to, "Invalid range!") + _from = from + _to = to + +func next_value() -> float: + return randf_range(_from, _to) diff --git a/addons/gdUnit4/src/fuzzers/Fuzzer.gd b/addons/gdUnit4/src/fuzzers/Fuzzer.gd index b17b6865..7cd6a588 100644 --- a/addons/gdUnit4/src/fuzzers/Fuzzer.gd +++ b/addons/gdUnit4/src/fuzzers/Fuzzer.gd @@ -23,7 +23,7 @@ var _iteration_limit :int = ITERATION_DEFAULT_COUNT # generates the next fuzz value -# needs to be implement +# needs to be implement func next_value() -> Variant: push_error("Invalid vall. Fuzzer not implemented 'next_value()'") return null diff --git a/addons/gdUnit4/src/fuzzers/IntFuzzer.gd b/addons/gdUnit4/src/fuzzers/IntFuzzer.gd index 0235ee20..064dc20a 100644 --- a/addons/gdUnit4/src/fuzzers/IntFuzzer.gd +++ b/addons/gdUnit4/src/fuzzers/IntFuzzer.gd @@ -12,14 +12,14 @@ var _to : int = 0 var _mode : int = NORMAL -func _init(from: int, to: int, mode :int = NORMAL): +func _init(from: int, to: int, mode :int = NORMAL) -> void: assert(from <= to, "Invalid range!") _from = from _to = to _mode = mode -func next_value() -> Variant: +func next_value() -> int: var value := randi_range(_from, _to) match _mode: NORMAL: diff --git a/addons/gdUnit4/src/fuzzers/StringFuzzer.gd b/addons/gdUnit4/src/fuzzers/StringFuzzer.gd index b05ee138..9b13e8aa 100644 --- a/addons/gdUnit4/src/fuzzers/StringFuzzer.gd +++ b/addons/gdUnit4/src/fuzzers/StringFuzzer.gd @@ -9,19 +9,20 @@ var _max_length :int var _charset :PackedByteArray -func _init(min_length :int,max_length :int,pattern :String = DEFAULT_CHARSET): +func _init(min_length :int, max_length :int, pattern :String = DEFAULT_CHARSET) -> void: assert(min_length>0 and min_length < max_length) assert(not null or not pattern.is_empty()) _min_length = min_length _max_length = max_length _charset = StringFuzzer.extract_charset(pattern) + static func extract_charset(pattern :String) -> PackedByteArray: var reg := RegEx.new() if reg.compile(pattern) != OK: push_error("Invalid pattern to generate Strings! Use e.g 'a-zA-Z0-9+-_'") return PackedByteArray() - + var charset := Array() var char_before := -1 var index := 0 @@ -46,16 +47,18 @@ static func extract_charset(pattern :String) -> PackedByteArray: charset.append(char_current) return PackedByteArray(charset) -static func build_chars(from :int, to :int) -> Array: - var characters := Array() + +static func build_chars(from :int, to :int) -> Array[int]: + var characters :Array[int] = [] for character in range(from+1, to+1): characters.append(character) return characters -func next_value() -> Variant: + +func next_value() -> String: var value := PackedByteArray() var max_char := len(_charset) var length :int = max(_min_length, randi() % _max_length) for i in length: value.append(_charset[randi() % max_char]) - return value.get_string_from_ascii() + return value.get_string_from_utf8() diff --git a/addons/gdUnit4/src/fuzzers/Vector2Fuzzer.gd b/addons/gdUnit4/src/fuzzers/Vector2Fuzzer.gd index 87c88900..855cf6a9 100644 --- a/addons/gdUnit4/src/fuzzers/Vector2Fuzzer.gd +++ b/addons/gdUnit4/src/fuzzers/Vector2Fuzzer.gd @@ -5,12 +5,14 @@ extends Fuzzer var _from :Vector2 var _to : Vector2 -func _init(from: Vector2,to: Vector2): - assert(from <= to) #,"Invalid range!") + +func _init(from: Vector2, to: Vector2) -> void: + assert(from <= to, "Invalid range!") _from = from _to = to -func next_value() -> Variant: - var x = randf_range(_from.x, _to.x) - var y = randf_range(_from.y, _to.y) + +func next_value() -> Vector2: + var x := randf_range(_from.x, _to.x) + var y := randf_range(_from.y, _to.y) return Vector2(x, y) diff --git a/addons/gdUnit4/src/fuzzers/Vector3Fuzzer.gd b/addons/gdUnit4/src/fuzzers/Vector3Fuzzer.gd index 16b6e876..c773ab51 100644 --- a/addons/gdUnit4/src/fuzzers/Vector3Fuzzer.gd +++ b/addons/gdUnit4/src/fuzzers/Vector3Fuzzer.gd @@ -5,13 +5,15 @@ extends Fuzzer var _from :Vector3 var _to : Vector3 -func _init(from: Vector3,to: Vector3): - assert(from <= to) #,"Invalid range!") + +func _init(from: Vector3, to: Vector3) -> void: + assert(from <= to, "Invalid range!") _from = from _to = to -func next_value() -> Variant: - var x = randf_range(_from.x, _to.x) - var y = randf_range(_from.y, _to.y) - var z = randf_range(_from.z, _to.z) + +func next_value() -> Vector3: + var x := randf_range(_from.x, _to.x) + var y := randf_range(_from.y, _to.y) + var z := randf_range(_from.z, _to.z) return Vector3(x, y, z) diff --git a/addons/gdUnit4/src/matchers/AnyArgumentMatcher.gd b/addons/gdUnit4/src/matchers/AnyArgumentMatcher.gd index 6eca57a6..bd503130 100644 --- a/addons/gdUnit4/src/matchers/AnyArgumentMatcher.gd +++ b/addons/gdUnit4/src/matchers/AnyArgumentMatcher.gd @@ -1,7 +1,11 @@ -class_name AnyArgumentMatcher +class_name AnyArgumentMatcher extends GdUnitArgumentMatcher @warning_ignore("unused_parameter") -func is_match(value) -> bool: +func is_match(value :Variant) -> bool: return true + + +func _to_string() -> String: + return "any()" diff --git a/addons/gdUnit4/src/matchers/AnyBuildInTypeArgumentMatcher.gd b/addons/gdUnit4/src/matchers/AnyBuildInTypeArgumentMatcher.gd index 3c4026a2..ba34431c 100644 --- a/addons/gdUnit4/src/matchers/AnyBuildInTypeArgumentMatcher.gd +++ b/addons/gdUnit4/src/matchers/AnyBuildInTypeArgumentMatcher.gd @@ -3,8 +3,48 @@ extends GdUnitArgumentMatcher var _type : PackedInt32Array = [] -func _init(type :PackedInt32Array): + +func _init(type :PackedInt32Array) -> void: _type = type -func is_match(value) -> bool: + +func is_match(value :Variant) -> bool: return _type.has(typeof(value)) + + +func _to_string() -> String: + match _type[0]: + TYPE_BOOL: return "any_bool()" + TYPE_STRING, TYPE_STRING_NAME: return "any_string()" + TYPE_INT: return "any_int()" + TYPE_FLOAT: return "any_float()" + TYPE_COLOR: return "any_color()" + TYPE_VECTOR2: return "any_vector2()" if _type.size() == 1 else "any_vector()" + TYPE_VECTOR2I: return "any_vector2i()" + TYPE_VECTOR3: return "any_vector3()" + TYPE_VECTOR3I: return "any_vector3i()" + TYPE_VECTOR4: return "any_vector4()" + TYPE_VECTOR4I: return "any_vector4i()" + TYPE_RECT2: return "any_rect2()" + TYPE_RECT2I: return "any_rect2i()" + TYPE_PLANE: return "any_plane()" + TYPE_QUATERNION: return "any_quat()" + TYPE_AABB: return "any_aabb()" + TYPE_BASIS: return "any_basis()" + TYPE_TRANSFORM2D: return "any_transform_2d()" + TYPE_TRANSFORM3D: return "any_transform_3d()" + TYPE_NODE_PATH: return "any_node_path()" + TYPE_RID: return "any_rid()" + TYPE_OBJECT: return "any_object()" + TYPE_DICTIONARY: return "any_dictionary()" + TYPE_ARRAY: return "any_array()" + TYPE_PACKED_BYTE_ARRAY: return "any_packed_byte_array()" + TYPE_PACKED_INT32_ARRAY: return "any_packed_int32_array()" + TYPE_PACKED_INT64_ARRAY: return "any_packed_int64_array()" + TYPE_PACKED_FLOAT32_ARRAY: return "any_packed_float32_array()" + TYPE_PACKED_FLOAT64_ARRAY: return "any_packed_float64_array()" + TYPE_PACKED_STRING_ARRAY: return "any_packed_string_array()" + TYPE_PACKED_VECTOR2_ARRAY: return "any_packed_vector2_array()" + TYPE_PACKED_VECTOR3_ARRAY: return "any_packed_vector3_array()" + TYPE_PACKED_COLOR_ARRAY: return "any_packed_color_array()" + _: return "any()" diff --git a/addons/gdUnit4/src/matchers/AnyClazzArgumentMatcher.gd b/addons/gdUnit4/src/matchers/AnyClazzArgumentMatcher.gd index f553bfe0..2cf07904 100644 --- a/addons/gdUnit4/src/matchers/AnyClazzArgumentMatcher.gd +++ b/addons/gdUnit4/src/matchers/AnyClazzArgumentMatcher.gd @@ -1,14 +1,30 @@ -class_name AnyClazzArgumentMatcher +class_name AnyClazzArgumentMatcher extends GdUnitArgumentMatcher - + var _clazz :Object -func _init(clazz :Object): + +func _init(clazz :Object) -> void: _clazz = clazz + func is_match(value :Variant) -> bool: if typeof(value) != TYPE_OBJECT: return false if is_instance_valid(value) and GdObjects.is_script(_clazz): return value.get_script() == _clazz return is_instance_of(value, _clazz) + + +func _to_string() -> String: + if (_clazz as Object).is_class("GDScriptNativeClass"): + var instance :Object = _clazz.new() + var clazz_name := instance.get_class() + if not instance is RefCounted: + instance.free() + return "any_class(<"+clazz_name+">)"; + if _clazz is GDScript: + var result := GdObjects.extract_class_name(_clazz) + if result.is_success(): + return "any_class(<"+ result.value() + ">)" + return "any_class()" diff --git a/addons/gdUnit4/src/matchers/ChainedArgumentMatcher.gd b/addons/gdUnit4/src/matchers/ChainedArgumentMatcher.gd index e42de50d..ec62ecf6 100644 --- a/addons/gdUnit4/src/matchers/ChainedArgumentMatcher.gd +++ b/addons/gdUnit4/src/matchers/ChainedArgumentMatcher.gd @@ -3,17 +3,20 @@ extends GdUnitArgumentMatcher var _matchers :Array -func _init(matchers :Array): + +func _init(matchers :Array) -> void: _matchers = matchers -func is_match(arguments :Array) -> bool: - if arguments.size() != _matchers.size(): + +func is_match(arguments :Variant) -> bool: + var arg_array := arguments as Array + if arg_array.size() != _matchers.size(): return false - - for index in arguments.size(): - var arg = arguments[index] - var matcher = _matchers[index] as GdUnitArgumentMatcher - + + for index in arg_array.size(): + var arg :Variant = arg_array[index] + var matcher := _matchers[index] as GdUnitArgumentMatcher + if not matcher.is_match(arg): return false return true diff --git a/addons/gdUnit4/src/matchers/EqualsArgumentMatcher.gd b/addons/gdUnit4/src/matchers/EqualsArgumentMatcher.gd index 9ad2e46f..2d387edc 100644 --- a/addons/gdUnit4/src/matchers/EqualsArgumentMatcher.gd +++ b/addons/gdUnit4/src/matchers/EqualsArgumentMatcher.gd @@ -1,21 +1,21 @@ -class_name EqualsArgumentMatcher +class_name EqualsArgumentMatcher extends GdUnitArgumentMatcher -var _current -var _auto_deep_check_mode +var _current :Variant +var _auto_deep_check_mode :bool -func _init(current, auto_deep_check_mode := false): +func _init(current :Variant, auto_deep_check_mode := false) -> void: _current = current _auto_deep_check_mode = auto_deep_check_mode -func is_match(value) -> bool: +func is_match(value :Variant) -> bool: var case_sensitive_check := true return GdObjects.equals(_current, value, case_sensitive_check, compare_mode(value)) -func compare_mode(value) -> GdObjects.COMPARE_MODE: +func compare_mode(value :Variant) -> GdObjects.COMPARE_MODE: if _auto_deep_check_mode and is_instance_valid(value): # we do deep check on all InputEvent's return GdObjects.COMPARE_MODE.PARAMETER_DEEP_TEST if value is InputEvent else GdObjects.COMPARE_MODE.OBJECT_REFERENCE diff --git a/addons/gdUnit4/src/matchers/GdUnitArgumentMatcher.gd b/addons/gdUnit4/src/matchers/GdUnitArgumentMatcher.gd index dff6dac2..aa43b80f 100644 --- a/addons/gdUnit4/src/matchers/GdUnitArgumentMatcher.gd +++ b/addons/gdUnit4/src/matchers/GdUnitArgumentMatcher.gd @@ -4,5 +4,5 @@ extends RefCounted @warning_ignore("unused_parameter") -func is_match(value) -> bool: +func is_match(value :Variant) -> bool: return true diff --git a/addons/gdUnit4/src/matchers/GdUnitArgumentMatchers.gd b/addons/gdUnit4/src/matchers/GdUnitArgumentMatchers.gd index 24e6e855..a40eacee 100644 --- a/addons/gdUnit4/src/matchers/GdUnitArgumentMatchers.gd +++ b/addons/gdUnit4/src/matchers/GdUnitArgumentMatchers.gd @@ -4,9 +4,9 @@ extends RefCounted const TYPE_ANY = TYPE_MAX + 100 -static func to_matcher(arguments :Array, auto_deep_check_mode := false) -> ChainedArgumentMatcher: - var matchers := Array() - for arg in arguments: +static func to_matcher(arguments :Array[Variant], auto_deep_check_mode := false) -> ChainedArgumentMatcher: + var matchers :Array[Variant] = [] + for arg :Variant in arguments: # argument is already a matcher if arg is GdUnitArgumentMatcher: matchers.append(arg) @@ -28,5 +28,5 @@ static func by_types(types :PackedInt32Array) -> GdUnitArgumentMatcher: return AnyBuildInTypeArgumentMatcher.new(types) -static func any_class(clazz) -> GdUnitArgumentMatcher: +static func any_class(clazz :Object) -> GdUnitArgumentMatcher: return AnyClazzArgumentMatcher.new(clazz) diff --git a/addons/gdUnit4/src/mocking/GdUnitMock.gd b/addons/gdUnit4/src/mocking/GdUnitMock.gd index ba993226..bb50e5eb 100644 --- a/addons/gdUnit4/src/mocking/GdUnitMock.gd +++ b/addons/gdUnit4/src/mocking/GdUnitMock.gd @@ -3,16 +3,16 @@ extends RefCounted ## do call the real implementation const CALL_REAL_FUNC = "CALL_REAL_FUNC" -## do return a default value for primitive types or null +## do return a default value for primitive types or null const RETURN_DEFAULTS = "RETURN_DEFAULTS" ## do return a default value for primitive types and a fully mocked value for Object types ## builds full deep mocked object const RETURN_DEEP_STUB = "RETURN_DEEP_STUB" -var _value +var _value :Variant -func _init(value): +func _init(value :Variant) -> void: _value = value @@ -28,7 +28,7 @@ func on(obj :Object) -> Object: ## [color=yellow]`checked` is obsolete, use `on` instead [/color] -func checked(obj :Object) -> Object: +func checked(obj :Object) -> Object: push_warning("Using a deprecated function 'checked' use `on` instead") return on(obj) diff --git a/addons/gdUnit4/src/mocking/GdUnitMockBuilder.gd b/addons/gdUnit4/src/mocking/GdUnitMockBuilder.gd index df7f02c3..b3bb41a2 100644 --- a/addons/gdUnit4/src/mocking/GdUnitMockBuilder.gd +++ b/addons/gdUnit4/src/mocking/GdUnitMockBuilder.gd @@ -9,7 +9,7 @@ static func is_push_errors() -> bool: return GdUnitSettings.is_report_push_errors() -static func build(clazz, mock_mode :String, debug_write := false) -> Object: +static func build(clazz :Variant, mock_mode :String, debug_write := false) -> Variant: var push_errors := is_push_errors() if not is_mockable(clazz, push_errors): return null @@ -25,14 +25,14 @@ static func build(clazz, mock_mode :String, debug_write := false) -> Object: instance.free() if mock == null: return null - var mock_instance = mock.new() + var mock_instance :Variant = mock.new() mock_instance.__set_script(mock) mock_instance.__set_singleton() mock_instance.__set_mode(mock_mode) return register_auto_free(mock_instance) -static func create_instance(clazz) -> Object: +static func create_instance(clazz :Variant) -> Object: if typeof(clazz) == TYPE_OBJECT and (clazz as Object).is_class("GDScriptNativeClass"): return clazz.new() elif (clazz is GDScript) || (typeof(clazz) == TYPE_STRING and clazz.ends_with(".gd")): @@ -41,8 +41,8 @@ static func create_instance(clazz) -> Object: script = clazz else: script = load(clazz) - - var args = GdObjects.build_function_default_arguments(script, "_init") + + var args := GdObjects.build_function_default_arguments(script, "_init") return script.callv("new", args) elif typeof(clazz) == TYPE_STRING and ClassDB.can_instantiate(clazz): return ClassDB.instantiate(clazz) @@ -56,16 +56,16 @@ static func mock_on_scene(scene :PackedScene, debug_write :bool) -> Object: if push_errors: push_error("Can't instanciate scene '%s'" % scene.resource_path) return null - var scene_instance = scene.instantiate() + var scene_instance := scene.instantiate() # we can only mock checked a scene with attached script if scene_instance.get_script() == null: if push_errors: push_error("Can't create a mockable instance for a scene without script '%s'" % scene.resource_path) GdUnitTools.free_instance(scene_instance) return null - - var script_path = scene_instance.get_script().get_path() - var mock = mock_on_script(scene_instance, script_path, GdUnitClassDoubler.EXLCUDE_SCENE_FUNCTIONS, debug_write) + + var script_path :String = scene_instance.get_script().get_path() + var mock := mock_on_script(scene_instance, script_path, GdUnitClassDoubler.EXLCUDE_SCENE_FUNCTIONS, debug_write) if mock == null: return null scene_instance.set_script(mock) @@ -88,20 +88,20 @@ static func mock_on_script(instance :Object, clazz :Variant, function_excludes : var function_doubler := GdUnitMockFunctionDoubler.new(push_errors) var class_info := get_class_info(clazz) var lines := load_template(MOCK_TEMPLATE.source_code, class_info, instance) - + var clazz_name :String = class_info.get("class_name") var clazz_path :PackedStringArray = class_info.get("class_path", [clazz_name]) lines += double_functions(instance, clazz_name, clazz_path, function_doubler, function_excludes) - + var mock := GDScript.new() mock.source_code = "\n".join(lines) mock.resource_name = "Mock%s.gd" % clazz_name - mock.resource_path = GdUnitTools.create_temp_dir("mock") + "/Mock%s_%d.gd" % [clazz_name, Time.get_ticks_msec()] - + mock.resource_path = GdUnitFileAccess.create_temp_dir("mock") + "/Mock%s_%d.gd" % [clazz_name, Time.get_ticks_msec()] + if debug_write: DirAccess.remove_absolute(mock.resource_path) ResourceSaver.save(mock, mock.resource_path) - var error = mock.reload(true) + var error := mock.reload(true) if error != OK: push_error("Critical!!!, MockBuilder error, please contact the developer.") return null @@ -124,7 +124,7 @@ static func is_mockable(clazz :Variant, push_errors :bool=false) -> bool: if push_errors: push_error("It is not allowed to mock an instance '%s', use class name instead, Read 'Mocker' documentation for details" % clazz) return false - + if not GdObjects.can_be_instantiate(clazz): if push_errors: push_error("Can't create a mockable instance for class '%s'" % clazz) @@ -154,7 +154,7 @@ static func is_mockable(clazz :Variant, push_errors :bool=false) -> bool: push_error("'%s' cannot be mocked for the specified resource path, the resource does not exist" % clazz_name) return false # finally verify is a script resource - var resource = load(clazz_path) + var resource := load(clazz_path) if resource == null: if push_errors: push_error("'%s' cannot be mocked the script cannot be loaded." % clazz_name) diff --git a/addons/gdUnit4/src/mocking/GdUnitMockFunctionDoubler.gd b/addons/gdUnit4/src/mocking/GdUnitMockFunctionDoubler.gd index e544bcef..4d8d300f 100644 --- a/addons/gdUnit4/src/mocking/GdUnitMockFunctionDoubler.gd +++ b/addons/gdUnit4/src/mocking/GdUnitMockFunctionDoubler.gd @@ -4,7 +4,7 @@ extends GdFunctionDoubler const TEMPLATE_FUNC_WITH_RETURN_VALUE = """ var args :Array = ["$(func_name)", $(arguments)] - + if $(instance)__is_prepare_return_value(): $(instance)__save_function_return_value(args) return ${default_return_value} @@ -13,7 +13,7 @@ const TEMPLATE_FUNC_WITH_RETURN_VALUE = """ return ${default_return_value} else: $(instance)__save_function_interaction(args) - + if $(instance)__do_call_real_func("$(func_name)", args): return $(await)super($(arguments)) return $(instance)__get_mocked_return_value_or_default(args, ${default_return_value}) @@ -23,7 +23,7 @@ const TEMPLATE_FUNC_WITH_RETURN_VALUE = """ const TEMPLATE_FUNC_WITH_RETURN_VOID = """ var args :Array = ["$(func_name)", $(arguments)] - + if $(instance)__is_prepare_return_value(): if $(push_errors): push_error(\"Mocking a void function '$(func_name)() -> void:' is not allowed.\") @@ -33,7 +33,7 @@ const TEMPLATE_FUNC_WITH_RETURN_VOID = """ return else: $(instance)__save_function_interaction(args) - + if $(instance)__do_call_real_func("$(func_name)"): $(await)super($(arguments)) @@ -43,7 +43,7 @@ const TEMPLATE_FUNC_WITH_RETURN_VOID = """ const TEMPLATE_FUNC_VARARG_RETURN_VALUE = """ var varargs :Array = __filter_vargs([$(varargs)]) var args :Array = ["$(func_name)", $(arguments)] + varargs - + if $(instance)__is_prepare_return_value(): if $(push_errors): push_error(\"Mocking a void function '$(func_name)() -> void:' is not allowed.\") @@ -54,7 +54,7 @@ const TEMPLATE_FUNC_VARARG_RETURN_VALUE = """ return ${default_return_value} else: $(instance)__save_function_interaction(args) - + if $(instance)__do_call_real_func("$(func_name)", args): match varargs.size(): 0: return $(await)super($(arguments)) @@ -73,7 +73,7 @@ const TEMPLATE_FUNC_VARARG_RETURN_VALUE = """ """ -func _init(push_errors :bool = false): +func _init(push_errors :bool = false) -> void: super._init(push_errors) diff --git a/addons/gdUnit4/src/mocking/GdUnitMockImpl.gd b/addons/gdUnit4/src/mocking/GdUnitMockImpl.gd index 94110c28..cbeb782b 100644 --- a/addons/gdUnit4/src/mocking/GdUnitMockImpl.gd +++ b/addons/gdUnit4/src/mocking/GdUnitMockImpl.gd @@ -5,9 +5,9 @@ const __INSTANCE_ID = "${instance_id}" const __SOURCE_CLASS = "${source_class}" -var __working_mode := GdUnitMock.RETURN_DEFAULTS +var __mock_working_mode := GdUnitMock.RETURN_DEFAULTS var __excluded_methods :PackedStringArray = [] -var __do_return_value = null +var __do_return_value :Variant = null var __prepare_return_value := false #{ = { @@ -17,11 +17,11 @@ var __prepare_return_value := false var __mocked_return_values := Dictionary() -static func __instance(): +static func __instance() -> Object: return Engine.get_meta(__INSTANCE_ID) -func _notification(what): +func _notification(what :int) -> void: if what == NOTIFICATION_PREDELETE: if Engine.has_meta(__INSTANCE_ID): Engine.remove_meta(__INSTANCE_ID) @@ -31,12 +31,12 @@ func __instance_id() -> String: return __INSTANCE_ID -func __set_singleton(): +func __set_singleton() -> void: # store self need to mock static functions Engine.set_meta(__INSTANCE_ID, self) -func __release_double(): +func __release_double() -> void: # we need to release the self reference manually to prevent orphan nodes Engine.remove_meta(__INSTANCE_ID) @@ -45,92 +45,96 @@ func __is_prepare_return_value() -> bool: return __prepare_return_value -func __sort_by_argument_matcher(left_args :Array, _right_args :Array) -> bool: - for larg in left_args: - if larg is GdUnitArgumentMatcher: +func __sort_by_argument_matcher(__left_args :Array, __right_args :Array) -> bool: + for __index in __left_args.size(): + var __larg :Variant = __left_args[__index] + if __larg is GdUnitArgumentMatcher: return false return true # we need to sort by matcher arguments so that they are all at the end of the list -func __sort_dictionary(unsorted_args :Dictionary) -> Dictionary: +func __sort_dictionary(__unsorted_args :Dictionary) -> Dictionary: # only need to sort if contains more than one entry - if unsorted_args.size() <= 1: - return unsorted_args - var sorted_args := unsorted_args.keys() - sorted_args.sort_custom(__sort_by_argument_matcher) - var sorted_result := {} - for key in sorted_args: - sorted_result[key] = unsorted_args[key] - return sorted_result - - -func __save_function_return_value(args :Array): - var func_name :String = args[0] - var func_args :Array = args.slice(1) - var mocked_return_value_by_args :Dictionary = __mocked_return_values.get(func_name, {}) - mocked_return_value_by_args[func_args] = __do_return_value - __mocked_return_values[func_name] = __sort_dictionary(mocked_return_value_by_args) + if __unsorted_args.size() <= 1: + return __unsorted_args + var __sorted_args := __unsorted_args.keys() + __sorted_args.sort_custom(__sort_by_argument_matcher) + var __sorted_result := {} + for __index in __sorted_args.size(): + var key :Variant = __sorted_args[__index] + __sorted_result[key] = __unsorted_args[key] + return __sorted_result + + +func __save_function_return_value(__fuction_args :Array) -> void: + var __func_name :String = __fuction_args[0] + var __func_args :Array = __fuction_args.slice(1) + var __mocked_return_value_by_args :Dictionary = __mocked_return_values.get(__func_name, {}) + __mocked_return_value_by_args[__func_args] = __do_return_value + __mocked_return_values[__func_name] = __sort_dictionary(__mocked_return_value_by_args) __do_return_value = null __prepare_return_value = false -func __is_mocked_args_match(func_args :Array, mocked_args :Array) -> bool: - var is_matching := false - for args in mocked_args: - if func_args.size() != args.size(): +func __is_mocked_args_match(__func_args :Array, __mocked_args :Array) -> bool: + var __is_matching := false + for __index in __mocked_args.size(): + var __fuction_args :Variant = __mocked_args[__index] + if __func_args.size() != __fuction_args.size(): continue - is_matching = true - for arg_index in func_args.size(): - var func_arg = func_args[arg_index] - var mock_arg = args[arg_index] - if mock_arg is GdUnitArgumentMatcher: - is_matching = is_matching and mock_arg.is_match(func_arg) + __is_matching = true + for __arg_index in __func_args.size(): + var __func_arg :Variant = __func_args[__arg_index] + var __mock_arg :Variant = __fuction_args[__arg_index] + if __mock_arg is GdUnitArgumentMatcher: + __is_matching = __is_matching and __mock_arg.is_match(__func_arg) else: - is_matching = is_matching and typeof(func_arg) == typeof(mock_arg) and func_arg == mock_arg - if not is_matching: + __is_matching = __is_matching and typeof(__func_arg) == typeof(__mock_arg) and __func_arg == __mock_arg + if not __is_matching: break - if is_matching: + if __is_matching: break - return is_matching + return __is_matching -func __get_mocked_return_value_or_default(args :Array, default_return_value :Variant) -> Variant: - var func_name :String = args[0] - if not __mocked_return_values.has(func_name): - return default_return_value - var func_args :Array = args.slice(1) - var mocked_args :Array = __mocked_return_values.get(func_name).keys() - for margs in mocked_args: - if __is_mocked_args_match(func_args, [margs]): - return __mocked_return_values[func_name][margs] - return default_return_value +func __get_mocked_return_value_or_default(__fuction_args :Array, __default_return_value :Variant) -> Variant: + var __func_name :String = __fuction_args[0] + if not __mocked_return_values.has(__func_name): + return __default_return_value + var __func_args :Array = __fuction_args.slice(1) + var __mocked_args :Array = __mocked_return_values.get(__func_name).keys() + for __index in __mocked_args.size(): + var __margs :Variant = __mocked_args[__index] + if __is_mocked_args_match(__func_args, [__margs]): + return __mocked_return_values[__func_name][__margs] + return __default_return_value -func __set_script(script :GDScript) -> void: - super.set_script(script) +func __set_script(__script :GDScript) -> void: + super.set_script(__script) -func __set_mode(working_mode :String): - __working_mode = working_mode +func __set_mode(mock_working_mode :String) -> Object: + __mock_working_mode = mock_working_mode return self -func __do_call_real_func(func_name :String, func_args := []) -> bool: - var is_call_real_func := __working_mode == GdUnitMock.CALL_REAL_FUNC and not __excluded_methods.has(func_name) +func __do_call_real_func(__func_name :String, __func_args := []) -> bool: + var __is_call_real_func := __mock_working_mode == GdUnitMock.CALL_REAL_FUNC and not __excluded_methods.has(__func_name) # do not call real funcions for mocked functions - if is_call_real_func and __mocked_return_values.has(func_name): - var args :Array = func_args.slice(1) - var mocked_args :Array = __mocked_return_values.get(func_name).keys() - return not __is_mocked_args_match(args, mocked_args) - return is_call_real_func + if __is_call_real_func and __mocked_return_values.has(__func_name): + var __fuction_args :Array = __func_args.slice(1) + var __mocked_args :Array = __mocked_return_values.get(__func_name).keys() + return not __is_mocked_args_match(__fuction_args, __mocked_args) + return __is_call_real_func func __exclude_method_call(exluded_methods :PackedStringArray) -> void: __excluded_methods.append_array(exluded_methods) -func __do_return(return_value): - __do_return_value = return_value +func __do_return(mock_do_return_value :Variant) -> Object: + __do_return_value = mock_do_return_value __prepare_return_value = true return self diff --git a/addons/gdUnit4/src/monitor/ErrorLogEntry.gd b/addons/gdUnit4/src/monitor/ErrorLogEntry.gd index f574147e..4f767bb9 100644 --- a/addons/gdUnit4/src/monitor/ErrorLogEntry.gd +++ b/addons/gdUnit4/src/monitor/ErrorLogEntry.gd @@ -15,6 +15,7 @@ const PATTERN_SCRIPT_ERROR := "USER SCRIPT ERROR:" const PATTERN_PUSH_ERROR := "USER ERROR:" const PATTERN_PUSH_WARNING := "USER WARNING:" +static var _regex_parse_error_line_number: RegEx var _type :TYPE var _line :int @@ -22,7 +23,7 @@ var _message :String var _details :String -func _init(type :TYPE, line :int, message :String, details :String): +func _init(type :TYPE, line :int, message :String, details :String) -> void: _type = type _line = line _message = message @@ -52,8 +53,9 @@ static func _extract(records :PackedStringArray, index :int, type :TYPE, pattern static func _parse_error_line_number(record :String) -> int: - var regex := GdUnitSingleton.instance("error_line_regex", func() : return GdUnitTools.to_regex("at: .*res://.*:(\\d+)")) as RegEx - var matches := regex.search(record) + if _regex_parse_error_line_number == null: + _regex_parse_error_line_number = GdUnitTools.to_regex("at: .*res://.*:(\\d+)") + var matches := _regex_parse_error_line_number.search(record) if matches != null: return matches.get_string(1).to_int() return -1 diff --git a/addons/gdUnit4/src/monitor/GdUnitMonitor.gd b/addons/gdUnit4/src/monitor/GdUnitMonitor.gd index 8ec2e633..b6429cad 100644 --- a/addons/gdUnit4/src/monitor/GdUnitMonitor.gd +++ b/addons/gdUnit4/src/monitor/GdUnitMonitor.gd @@ -5,7 +5,7 @@ extends RefCounted var _id :String # constructs new Monitor with given id -func _init(p_id :String): +func _init(p_id :String) -> void: _id = p_id @@ -15,10 +15,10 @@ func id() -> String: # starts monitoring -func start(): +func start() -> void: pass # stops monitoring -func stop(): +func stop() -> void: pass diff --git a/addons/gdUnit4/src/monitor/GdUnitOrphanNodesMonitor.gd b/addons/gdUnit4/src/monitor/GdUnitOrphanNodesMonitor.gd index 9bf76e46..725dd1fb 100644 --- a/addons/gdUnit4/src/monitor/GdUnitOrphanNodesMonitor.gd +++ b/addons/gdUnit4/src/monitor/GdUnitOrphanNodesMonitor.gd @@ -6,16 +6,16 @@ var _orphan_count := 0 var _orphan_detection_enabled :bool -func _init(name :String = ""): +func _init(name :String = "") -> void: super("OrphanNodesMonitor:" + name) _orphan_detection_enabled = GdUnitSettings.is_verbose_orphans() -func start(): +func start() -> void: _initial_count = _orphans() -func stop(): +func stop() -> void: _orphan_count = max(0, _orphans() - _initial_count) diff --git a/addons/gdUnit4/src/monitor/GodotGdErrorMonitor.gd b/addons/gdUnit4/src/monitor/GodotGdErrorMonitor.gd index 47250b65..720fe5c8 100644 --- a/addons/gdUnit4/src/monitor/GodotGdErrorMonitor.gd +++ b/addons/gdUnit4/src/monitor/GodotGdErrorMonitor.gd @@ -4,32 +4,31 @@ extends GdUnitMonitor var _godot_log_file :String var _eof :int var _report_enabled := false -var _report_force : bool +var _entries: Array[ErrorLogEntry] = [] -func _init(force := false): +func _init() -> void: super("GodotGdErrorMonitor") - _report_force = force _godot_log_file = GdUnitSettings.get_log_path() + _report_enabled = _is_reporting_enabled() -func start(): - _report_enabled = _report_force or is_reporting_enabled() - if _report_enabled: - var file = FileAccess.open(_godot_log_file, FileAccess.READ) - if file: - file.seek_end(0) - _eof = file.get_length() +func start() -> void: + var file := FileAccess.open(_godot_log_file, FileAccess.READ) + if file: + file.seek_end(0) + _eof = file.get_length() -func stop(): +func stop() -> void: pass -func reports() -> Array[GdUnitReport]: +func to_reports() -> Array[GdUnitReport]: var reports_ :Array[GdUnitReport] = [] if _report_enabled: - reports_.assign(_collect_log_entries().map(_to_report)) + reports_.assign(_entries.map(_to_report)) + _entries.clear() return reports_ @@ -38,39 +37,49 @@ static func _to_report(errorLog :ErrorLogEntry) -> GdUnitReport: GdAssertMessages._error("Godot Runtime Error !"), GdAssertMessages._colored_value(errorLog._details), GdAssertMessages._error("Error:"), - GdAssertMessages._colored_value(errorLog._message)] + GdAssertMessages._colored_value(errorLog._message)] return GdUnitReport.new().create(GdUnitReport.ABORT, errorLog._line, failure) -func scan() -> Array[ErrorLogEntry]: +func scan(force_collect_reports := false) -> Array[ErrorLogEntry]: await Engine.get_main_loop().process_frame - return _collect_log_entries() + await Engine.get_main_loop().physics_frame + _entries.append_array(_collect_log_entries(force_collect_reports)) + return _entries + + +func erase_log_entry(entry :ErrorLogEntry) -> void: + _entries.erase(entry) -func _collect_log_entries() -> Array[ErrorLogEntry]: - var file = FileAccess.open(_godot_log_file, FileAccess.READ) +func _collect_log_entries(force_collect_reports :bool) -> Array[ErrorLogEntry]: + var file := FileAccess.open(_godot_log_file, FileAccess.READ) file.seek(_eof) var records := PackedStringArray() while not file.eof_reached(): records.append(file.get_line()) + file.seek_end(0) + _eof = file.get_length() var log_entries :Array[ErrorLogEntry]= [] + var is_report_errors := force_collect_reports or _is_report_push_errors() + var is_report_script_errors := force_collect_reports or _is_report_script_errors() for index in records.size(): - if _report_force: + if force_collect_reports: log_entries.append(ErrorLogEntry.extract_push_warning(records, index)) - if _is_report_push_errors(): + if is_report_errors: log_entries.append(ErrorLogEntry.extract_push_error(records, index)) - if _is_report_script_errors(): + if is_report_script_errors: log_entries.append(ErrorLogEntry.extract_error(records, index)) - return log_entries.filter(func(value): return value != null ) + return log_entries.filter(func(value :ErrorLogEntry) -> bool: return value != null ) -func is_reporting_enabled() -> bool: +func _is_reporting_enabled() -> bool: return _is_report_script_errors() or _is_report_push_errors() func _is_report_push_errors() -> bool: - return _report_force or GdUnitSettings.is_report_push_errors() + return GdUnitSettings.is_report_push_errors() func _is_report_script_errors() -> bool: - return _report_force or GdUnitSettings.is_report_script_errors() + return GdUnitSettings.is_report_script_errors() diff --git a/addons/gdUnit4/src/mono/GdUnit4CSharpApi.cs b/addons/gdUnit4/src/mono/GdUnit4CSharpApi.cs new file mode 100644 index 00000000..557cc01a --- /dev/null +++ b/addons/gdUnit4/src/mono/GdUnit4CSharpApi.cs @@ -0,0 +1,50 @@ +using System; +using System.Reflection; +using System.Linq; + +using Godot; +using Godot.Collections; +using GdUnit4; + + +// GdUnit4 GDScript - C# API wrapper +public partial class GdUnit4CSharpApi : Godot.GodotObject +{ + private static Type? apiType; + + private static Type GetApiType() + { + if (apiType == null) + { + var assembly = Assembly.Load("gdUnit4Api"); + apiType = GdUnit4NetVersion() < new Version(4, 2, 2) ? + assembly.GetType("GdUnit4.GdUnit4MonoAPI") : + assembly.GetType("GdUnit4.GdUnit4NetAPI"); + Godot.GD.PrintS($"GdUnit4CSharpApi type:{apiType} loaded."); + } + return apiType!; + } + + private static Version GdUnit4NetVersion() + { + var assembly = Assembly.Load("gdUnit4Api"); + return assembly.GetName().Version!; + } + + private static T InvokeApiMethod(string methodName, params object[] args) + { + var method = GetApiType().GetMethod(methodName)!; + return (T)method.Invoke(null, args)!; + } + + public static string Version() => GdUnit4NetVersion().ToString(); + + public static bool IsTestSuite(string classPath) => InvokeApiMethod("IsTestSuite", classPath); + + public static RefCounted Executor(Node listener) => InvokeApiMethod("Executor", listener); + + public static CsNode? ParseTestSuite(string classPath) => InvokeApiMethod("ParseTestSuite", classPath); + + public static Dictionary CreateTestSuite(string sourcePath, int lineNumber, string testSuitePath) => + InvokeApiMethod("CreateTestSuite", sourcePath, lineNumber, testSuitePath); +} diff --git a/addons/gdUnit4/src/mono/GdUnit4MonoApiLoader.gd b/addons/gdUnit4/src/mono/GdUnit4CSharpApiLoader.gd similarity index 67% rename from addons/gdUnit4/src/mono/GdUnit4MonoApiLoader.gd rename to addons/gdUnit4/src/mono/GdUnit4CSharpApiLoader.gd index 4ad97a48..e828a9e8 100644 --- a/addons/gdUnit4/src/mono/GdUnit4MonoApiLoader.gd +++ b/addons/gdUnit4/src/mono/GdUnit4CSharpApiLoader.gd @@ -1,18 +1,17 @@ extends RefCounted -class_name GdUnit4MonoApiLoader +class_name GdUnit4CSharpApiLoader static func instance() -> Object: - return GdUnitSingleton.instance("GdUnit4MonoAPI", func(): - if not GdUnit4MonoApiLoader.is_mono_supported(): + return GdUnitSingleton.instance("GdUnit4CSharpApi", func() -> Object: + if not GdUnit4CSharpApiLoader.is_mono_supported(): return null - var GdUnit4MonoApi = load("res://addons/gdUnit4/src/mono/GdUnit4MonoApi.cs") - return GdUnit4MonoApi.new() + return load("res://addons/gdUnit4/src/mono/GdUnit4CSharpApi.cs").new() ) static func is_engine_version_supported(engine_version :int = Engine.get_version_info().hex) -> bool: - return engine_version >= 0x40100 + return engine_version >= 0x40200 # test is Godot mono running @@ -21,14 +20,14 @@ static func is_mono_supported() -> bool: static func version() -> String: - if not GdUnit4MonoApiLoader.is_mono_supported(): + if not GdUnit4CSharpApiLoader.is_mono_supported(): return "unknown" return instance().Version() static func create_test_suite(source_path :String, line_number :int, test_suite_path :String) -> GdUnitResult: - if not GdUnit4MonoApiLoader.is_mono_supported(): - return GdUnitResult.error("Can't create test suite. No c# support found.") + if not GdUnit4CSharpApiLoader.is_mono_supported(): + return GdUnitResult.error("Can't create test suite. No C# support found.") var result := instance().CreateTestSuite(source_path, line_number, test_suite_path) as Dictionary if result.has("error"): return GdUnitResult.error(result.get("error")) @@ -36,8 +35,9 @@ static func create_test_suite(source_path :String, line_number :int, test_suite_ static func is_test_suite(resource_path :String) -> bool: - if not is_csharp_file(resource_path) or not GdUnit4MonoApiLoader.is_mono_supported(): + if not is_csharp_file(resource_path) or not GdUnit4CSharpApiLoader.is_mono_supported(): return false + if resource_path.is_empty(): if GdUnitSettings.is_report_push_errors(): push_error("Can't create test suite. Missing resource path.") @@ -46,7 +46,7 @@ static func is_test_suite(resource_path :String) -> bool: static func parse_test_suite(source_path :String) -> Node: - if not GdUnit4MonoApiLoader.is_mono_supported(): + if not GdUnit4CSharpApiLoader.is_mono_supported(): if GdUnitSettings.is_report_push_errors(): push_error("Can't create test suite. No c# support found.") return null @@ -54,11 +54,11 @@ static func parse_test_suite(source_path :String) -> Node: static func create_executor(listener :Node) -> RefCounted: - if not GdUnit4MonoApiLoader.is_mono_supported(): + if not GdUnit4CSharpApiLoader.is_mono_supported(): return null return instance().Executor(listener) static func is_csharp_file(resource_path :String) -> bool: var ext := resource_path.get_extension() - return ext == "cs" and GdUnit4MonoApiLoader.is_mono_supported() + return ext == "cs" and GdUnit4CSharpApiLoader.is_mono_supported() diff --git a/addons/gdUnit4/src/mono/GdUnit4MonoApi.cs b/addons/gdUnit4/src/mono/GdUnit4MonoApi.cs deleted file mode 100644 index 0563ef92..00000000 --- a/addons/gdUnit4/src/mono/GdUnit4MonoApi.cs +++ /dev/null @@ -1,17 +0,0 @@ -using Godot; -using Godot.Collections; - -// GdUnit4 C# API wrapper -public partial class GdUnit4MonoApi : GdUnit4.GdUnit4MonoAPI -{ - public new string Version() => GdUnit4.GdUnit4MonoAPI.Version(); - - public new bool IsTestSuite(string classPath) => GdUnit4.GdUnit4MonoAPI.IsTestSuite(classPath); - - public new RefCounted Executor(Node listener) => (RefCounted)GdUnit4.GdUnit4MonoAPI.Executor(listener); - - public new GdUnit4.CsNode? ParseTestSuite(string classPath) => GdUnit4.GdUnit4MonoAPI.ParseTestSuite(classPath); - - public new Dictionary CreateTestSuite(string sourcePath, int lineNumber, string testSuitePath) => - GdUnit4.GdUnit4MonoAPI.CreateTestSuite(sourcePath, lineNumber, testSuitePath); -} diff --git a/addons/gdUnit4/src/network/GdUnitServer.gd b/addons/gdUnit4/src/network/GdUnitServer.gd index 1a0ae992..7b7be090 100644 --- a/addons/gdUnit4/src/network/GdUnitServer.gd +++ b/addons/gdUnit4/src/network/GdUnitServer.gd @@ -4,7 +4,7 @@ extends Node @onready var _server :GdUnitTcpServer = $TcpServer -func _ready(): +func _ready() -> void: var result := _server.start() if result.is_error(): push_error(result.error_message()) @@ -17,20 +17,20 @@ func _ready(): GdUnitCommandHandler.instance().gdunit_runner_stop.connect(_on_gdunit_runner_stop) -func _on_client_connected(client_id :int) -> void: +func _on_client_connected(client_id: int) -> void: GdUnitSignals.instance().gdunit_client_connected.emit(client_id) -func _on_client_disconnected(client_id :int) -> void: +func _on_client_disconnected(client_id: int) -> void: GdUnitSignals.instance().gdunit_client_disconnected.emit(client_id) -func _on_gdunit_runner_stop(client_id :int): +func _on_gdunit_runner_stop(client_id: int) -> void: if _server: _server.disconnect_client(client_id) -func _receive_rpc_data(p_rpc :RPC) -> void: +func _receive_rpc_data(p_rpc: Variant) -> void: if p_rpc is RPCMessage: GdUnitSignals.instance().gdunit_message.emit(p_rpc.message()) return diff --git a/addons/gdUnit4/src/network/GdUnitServer.tscn b/addons/gdUnit4/src/network/GdUnitServer.tscn index 4c7645c9..4dbe8c49 100644 --- a/addons/gdUnit4/src/network/GdUnitServer.tscn +++ b/addons/gdUnit4/src/network/GdUnitServer.tscn @@ -1,10 +1,10 @@ -[gd_scene load_steps=3 format=2] +[gd_scene load_steps=3 format=3 uid="uid://cn5mp3tmi2gb1"] -[ext_resource path="res://addons/gdUnit4/src/network/GdUnitServer.gd" type="Script" id=1] -[ext_resource path="res://addons/gdUnit4/src/network/GdUnitTcpServer.gd" type="Script" id=2] +[ext_resource type="Script" path="res://addons/gdUnit4/src/network/GdUnitServer.gd" id="1"] +[ext_resource type="Script" path="res://addons/gdUnit4/src/network/GdUnitTcpServer.gd" id="2"] [node name="Control" type="Node"] -script = ExtResource( 1 ) +script = ExtResource("1") [node name="TcpServer" type="Node" parent="."] -script = ExtResource( 2 ) +script = ExtResource("2") diff --git a/addons/gdUnit4/src/network/GdUnitTask.gd b/addons/gdUnit4/src/network/GdUnitTask.gd index c27fd982..e0188a04 100644 --- a/addons/gdUnit4/src/network/GdUnitTask.gd +++ b/addons/gdUnit4/src/network/GdUnitTask.gd @@ -7,15 +7,18 @@ const TASK_ARGS = "task_args" var _task_name :String var _fref :Callable -func _init(task_name :String,instance :Object,func_name :String): + +func _init(task_name :String,instance :Object,func_name :String) -> void: _task_name = task_name if not instance.has_method(func_name): push_error("Can't create GdUnitTask, Invalid func name '%s' for instance '%s'" % [instance, func_name]) _fref = Callable(instance, func_name) + func name() -> String: return _task_name + func execute(args :Array) -> GdUnitResult: if args.is_empty(): return _fref.call() diff --git a/addons/gdUnit4/src/network/GdUnitTcpClient.gd b/addons/gdUnit4/src/network/GdUnitTcpClient.gd index 0b5ee646..5cf8b053 100644 --- a/addons/gdUnit4/src/network/GdUnitTcpClient.gd +++ b/addons/gdUnit4/src/network/GdUnitTcpClient.gd @@ -1,10 +1,9 @@ class_name GdUnitTcpClient extends Node -signal connection_succeeded(message) -signal connection_failed(message) +signal connection_succeeded(message :String) +signal connection_failed(message :String) -var _timer :Timer var _host :String var _port :int @@ -13,14 +12,10 @@ var _connected :bool var _stream :StreamPeerTCP -func _ready(): +func _ready() -> void: _connected = false _stream = StreamPeerTCP.new() _stream.set_big_endian(true) - _timer = Timer.new() - add_child(_timer) - _timer.set_one_shot(true) - _timer.connect('timeout', Callable(self, '_connecting_timeout')) func stop() -> void: @@ -37,7 +32,7 @@ func start(host :String, port :int) -> GdUnitResult: _port = port if _connected: return GdUnitResult.warn("Client already connected ... %s:%d" % [_host, _port]) - + # Connect client to server if _stream.get_status() != StreamPeerTCP.STATUS_CONNECTED: var err := _stream.connect_to_host(host, port) @@ -47,11 +42,11 @@ func start(host :String, port :int) -> GdUnitResult: return GdUnitResult.success("GdUnit3: Client connected checked port %d" % port) -func _process(_delta): +func _process(_delta :float) -> void: match _stream.get_status(): StreamPeerTCP.STATUS_NONE: return - + StreamPeerTCP.STATUS_CONNECTING: set_process(false) # wait until client is connected to server @@ -66,11 +61,11 @@ func _process(_delta): set_process(true) _stream.disconnect_from_host() console("connection failed") - emit_signal("connection_failed", "Connect to TCP Server %s:%d faild!" % [_host, _port]) - + connection_failed.emit("Connect to TCP Server %s:%d faild!" % [_host, _port]) + StreamPeerTCP.STATUS_CONNECTED: if not _connected: - var rpc_ = null + var rpc_ :RPC = null set_process(false) while rpc_ == null: await get_tree().create_timer(0.500).timeout @@ -78,14 +73,14 @@ func _process(_delta): set_process(true) _client_id = rpc_.client_id() console("Connected to Server: %d" % _client_id) - emit_signal("connection_succeeded", "Connect to TCP Server %s:%d success." % [_host, _port]) + connection_succeeded.emit("Connect to TCP Server %s:%d success." % [_host, _port]) _connected = true process_rpc() - + StreamPeerTCP.STATUS_ERROR: console("connection failed") _stream.disconnect_from_host() - emit_signal("connection_failed", "Connect to TCP Server %s:%d faild!" % [_host, _port]) + connection_failed.emit("Connect to TCP Server %s:%d faild!" % [_host, _port]) return @@ -95,7 +90,7 @@ func is_client_connected() -> bool: func process_rpc() -> void: if _stream.get_available_bytes() > 0: - var rpc_ = rpc_receive() + var rpc_ := rpc_receive() if rpc_ is RPCClientDisconnect: stop() @@ -103,7 +98,7 @@ func process_rpc() -> void: func rpc_send(p_rpc :RPC) -> void: if _stream != null: var data := GdUnitServerConstants.JSON_RESPONSE_DELIMITER + p_rpc.serialize() + GdUnitServerConstants.JSON_RESPONSE_DELIMITER - _stream.put_data(data.to_ascii_buffer()) + _stream.put_data(data.to_utf8_buffer()) func rpc_receive() -> RPC: @@ -116,22 +111,22 @@ func rpc_receive() -> RPC: var header := Array(received_data.slice(0, 4)) if header == [0, 0, 0, 124]: received_data = received_data.slice(12, available_bytes) - var decoded := received_data.get_string_from_ascii() + var decoded := received_data.get_string_from_utf8() if decoded == "": - #prints("decoded is empty", available_bytes, received_data.get_string_from_ascii()) + #prints("decoded is empty", available_bytes, received_data.get_string_from_utf8()) return null return RPC.deserialize(decoded) return null -func console(message :String) -> void: - prints("TCP Client:", message) +func console(_message :String) -> void: + #prints("TCP Client:", _message) pass -func _on_connection_failed(message :String): +func _on_connection_failed(message :String) -> void: console("connection faild: " + message) -func _on_connection_succeeded(message :String): +func _on_connection_succeeded(message :String) -> void: console("connected: " + message) diff --git a/addons/gdUnit4/src/network/GdUnitTcpServer.gd b/addons/gdUnit4/src/network/GdUnitTcpServer.gd index b3ecf59f..e27fe18b 100644 --- a/addons/gdUnit4/src/network/GdUnitTcpServer.gd +++ b/addons/gdUnit4/src/network/GdUnitTcpServer.gd @@ -2,60 +2,63 @@ class_name GdUnitTcpServer extends Node -signal client_connected(client_id) -signal client_disconnected(client_id) -signal rpc_data(rpc_data) +signal client_connected(client_id :int) +signal client_disconnected(client_id :int) +signal rpc_data(rpc_data :RPC) var _server :TCPServer class TcpConnection extends Node: var _id :int + # we do use untyped here because we using a mock for testing and the static type is break the mock + @warning_ignore("untyped_declaration") var _stream var _readBuffer :String = "" - - - func _init(p_server): - #assert(p_server is TCPServer) + + + func _init(p_server :Variant) -> void: + assert(p_server is TCPServer) _stream = p_server.take_connection() _stream.set_big_endian(true) _id = _stream.get_instance_id() rpc_send(RPCClientConnect.new().with_id(_id)) - - - func _ready(): + + + func _ready() -> void: server().client_connected.emit(_id) - - + + func close() -> void: - rpc_send(RPCClientDisconnect.new().with_id(_id)) - server().client_disconnected.emit(_id) - _stream.disconnect_from_host() - _readBuffer = "" - - + if _stream != null: + _stream.disconnect_from_host() + _readBuffer = "" + _stream = null + queue_free() + + func id() -> int: return _id - - + + func server() -> GdUnitTcpServer: return get_parent() - - - func rpc_send(p_rpc :RPC) -> void: + + + func rpc_send(p_rpc: RPC) -> void: _stream.put_var(p_rpc.serialize(), true) - - - func _process(_delta): - if _stream.get_status() != StreamPeerTCP.STATUS_CONNECTED: + + + func _process(_delta: float) -> void: + if _stream == null or _stream.get_status() != StreamPeerTCP.STATUS_CONNECTED: return receive_packages() - - + + func receive_packages() -> void: - var available_bytes = _stream.get_available_bytes() + var available_bytes :int = _stream.get_available_bytes() if available_bytes > 0: - var partial_data = _stream.get_partial_data(available_bytes) + var partial_data :Array = _stream.get_partial_data(available_bytes) # Check for read error. if partial_data[0] != OK: push_error("Error getting data from stream: %s " % partial_data[0]) @@ -63,14 +66,14 @@ class TcpConnection extends Node: else: var received_data := partial_data[1] as PackedByteArray for package in _read_next_data_packages(received_data): - var rpc_ = RPC.deserialize(package) + var rpc_ := RPC.deserialize(package) if rpc_ is RPCClientDisconnect: close() server().rpc_data.emit(rpc_) - - - func _read_next_data_packages(data_package :PackedByteArray) -> PackedStringArray: - _readBuffer += data_package.get_string_from_ascii() + + + func _read_next_data_packages(data_package: PackedByteArray) -> PackedStringArray: + _readBuffer += data_package.get_string_from_utf8() var json_array := _readBuffer.split(GdUnitServerConstants.JSON_RESPONSE_DELIMITER) # We need to check if the current data is terminated by the delemiter (data packets can be split unspecifically). # If not, store the last part in _readBuffer and complete it on the next data packet that is received @@ -85,20 +88,20 @@ class TcpConnection extends Node: if index < json_array.size() and json_array[index].is_empty(): json_array.remove_at(index) return json_array - - + + func console(_message :String) -> void: #print_debug("TCP Connection:", _message) pass -func _ready(): +func _ready() -> void: _server = TCPServer.new() - client_connected.connect(Callable(self, "_on_client_connected")) - client_disconnected.connect(Callable(self, "_on_client_disconnected")) + client_connected.connect(_on_client_connected) + client_disconnected.connect(_on_client_disconnected) -func _notification(what): +func _notification(what: int) -> void: if what == NOTIFICATION_PREDELETE: stop() @@ -129,34 +132,32 @@ func stop() -> void: if connection is TcpConnection: connection.close() remove_child(connection) + _server = null -func disconnect_client(client_id :int) -> void: - for connection in get_children(): - if connection is TcpConnection and connection.id() == client_id: - connection.close() +func disconnect_client(client_id: int) -> void: + client_disconnected.emit(client_id) -func _process(_delta): - if not _server.is_listening(): +func _process(_delta: float) -> void: + if _server != null and not _server.is_listening(): return - # check is new connection incomming + # check if connection is ready to be used if _server.is_connection_available(): add_child(TcpConnection.new(_server)) -func _on_client_connected(client_id :int): +func _on_client_connected(client_id: int) -> void: console("Client connected %d" % client_id) -func _on_client_disconnected(client_id :int): - console("Client disconnected %d" % client_id) +func _on_client_disconnected(client_id: int) -> void: for connection in get_children(): if connection is TcpConnection and connection.id() == client_id: + connection.close() remove_child(connection) - -func console(_message :String) -> void: +func console(_message: String) -> void: #print_debug("TCP Server:", _message) pass diff --git a/addons/gdUnit4/src/network/rpc/RPC.gd b/addons/gdUnit4/src/network/rpc/RPC.gd index 93d5462a..190b05ab 100644 --- a/addons/gdUnit4/src/network/rpc/RPC.gd +++ b/addons/gdUnit4/src/network/rpc/RPC.gd @@ -1,17 +1,19 @@ class_name RPC extends RefCounted + func serialize() -> String: return JSON.stringify(inst_to_dict(self)) + # using untyped version see comments below -static func deserialize(json_value :String): +static func deserialize(json_value :String) -> Object: var json := JSON.new() var err := json.parse(json_value) if err != OK: push_error("Can't deserialize JSON, error at line %d: %s \n json: '%s'" % [json.get_error_line(), json.get_error_message(), json_value]) return null - var result = json.get_data() as Dictionary + var result := json.get_data() as Dictionary if not typeof(result) == TYPE_DICTIONARY: push_error("Can't deserialize JSON, error at line %d: %s \n json: '%s'" % [result.error_line, result.error_string, json_value]) return null diff --git a/addons/gdUnit4/src/network/rpc/RPCClientConnect.gd b/addons/gdUnit4/src/network/rpc/RPCClientConnect.gd index 2c8540bf..115fea26 100644 --- a/addons/gdUnit4/src/network/rpc/RPCClientConnect.gd +++ b/addons/gdUnit4/src/network/rpc/RPCClientConnect.gd @@ -6,7 +6,7 @@ var _client_id :int func with_id(p_client_id :int) -> RPCClientConnect: _client_id = p_client_id - return self + return self func client_id() -> int: diff --git a/addons/gdUnit4/src/network/rpc/RPCClientDisconnect.gd b/addons/gdUnit4/src/network/rpc/RPCClientDisconnect.gd index 1bfc3029..52ef259d 100644 --- a/addons/gdUnit4/src/network/rpc/RPCClientDisconnect.gd +++ b/addons/gdUnit4/src/network/rpc/RPCClientDisconnect.gd @@ -6,7 +6,7 @@ var _client_id :int func with_id(p_client_id :int) -> RPCClientDisconnect: _client_id = p_client_id - return self + return self func client_id() -> int: diff --git a/addons/gdUnit4/src/network/rpc/RPCData.gd b/addons/gdUnit4/src/network/rpc/RPCData.gd deleted file mode 100644 index 578c116d..00000000 --- a/addons/gdUnit4/src/network/rpc/RPCData.gd +++ /dev/null @@ -1,11 +0,0 @@ -class_name RPCData -extends RPC - -var _value - -func with_data(value) -> RPCData: - _value = value - return self - -func data() : - return _value diff --git a/addons/gdUnit4/src/network/rpc/RPCGdUnitEvent.gd b/addons/gdUnit4/src/network/rpc/RPCGdUnitEvent.gd index 2cc3e108..e692aff9 100644 --- a/addons/gdUnit4/src/network/rpc/RPCGdUnitEvent.gd +++ b/addons/gdUnit4/src/network/rpc/RPCGdUnitEvent.gd @@ -5,7 +5,7 @@ var _event :Dictionary static func of(p_event :GdUnitEvent) -> RPCGdUnitEvent: - var rpc = RPCGdUnitEvent.new() + var rpc := RPCGdUnitEvent.new() rpc._event = p_event.serialize() return rpc @@ -14,5 +14,5 @@ func event() -> GdUnitEvent: return GdUnitEvent.new().deserialize(_event) -func _to_string(): +func _to_string() -> String: return "RPCGdUnitEvent: " + str(_event) diff --git a/addons/gdUnit4/src/network/rpc/RPCGdUnitTestSuite.gd b/addons/gdUnit4/src/network/rpc/RPCGdUnitTestSuite.gd index d8d8b00e..96c4d96d 100644 --- a/addons/gdUnit4/src/network/rpc/RPCGdUnitTestSuite.gd +++ b/addons/gdUnit4/src/network/rpc/RPCGdUnitTestSuite.gd @@ -3,13 +3,16 @@ extends RPC var _data :Dictionary -static func of(test_suite) -> RPCGdUnitTestSuite: - var rpc = RPCGdUnitTestSuite.new() + +static func of(test_suite :Node) -> RPCGdUnitTestSuite: + var rpc := RPCGdUnitTestSuite.new() rpc._data = GdUnitTestSuiteDto.new().serialize(test_suite) return rpc + func dto() -> GdUnitResourceDto: return GdUnitTestSuiteDto.new().deserialize(_data) -func _to_string(): + +func _to_string() -> String: return "RPCGdUnitTestSuite: " + str(_data) diff --git a/addons/gdUnit4/src/network/rpc/RPCMessage.gd b/addons/gdUnit4/src/network/rpc/RPCMessage.gd index 6beafa2d..ddc49257 100644 --- a/addons/gdUnit4/src/network/rpc/RPCMessage.gd +++ b/addons/gdUnit4/src/network/rpc/RPCMessage.gd @@ -3,13 +3,16 @@ extends RPC var _message :String + static func of(p_message :String) -> RPCMessage: - var rpc = RPCMessage.new() + var rpc := RPCMessage.new() rpc._message = p_message return rpc + func message() -> String: return _message -func _to_string(): + +func _to_string() -> String: return "RPCMessage: " + _message diff --git a/addons/gdUnit4/src/network/rpc/dtos/GdUnitResourceDto.gd b/addons/gdUnit4/src/network/rpc/dtos/GdUnitResourceDto.gd index d2cea382..9152c8d2 100644 --- a/addons/gdUnit4/src/network/rpc/dtos/GdUnitResourceDto.gd +++ b/addons/gdUnit4/src/network/rpc/dtos/GdUnitResourceDto.gd @@ -4,19 +4,23 @@ extends Resource var _name :String var _path :String -func serialize(resource) -> Dictionary: + +func serialize(resource :Node) -> Dictionary: var serialized := Dictionary() serialized["name"] = resource.get_name() serialized["resource_path"] = resource.ResourcePath() return serialized + func deserialize(data :Dictionary) -> GdUnitResourceDto: _name = data.get("name", "n.a.") _path = data.get("resource_path", "") return self + func name() -> String: return _name + func path() -> String: return _path diff --git a/addons/gdUnit4/src/network/rpc/dtos/GdUnitTestCaseDto.gd b/addons/gdUnit4/src/network/rpc/dtos/GdUnitTestCaseDto.gd index d30a0696..26f5dda5 100644 --- a/addons/gdUnit4/src/network/rpc/dtos/GdUnitTestCaseDto.gd +++ b/addons/gdUnit4/src/network/rpc/dtos/GdUnitTestCaseDto.gd @@ -5,7 +5,7 @@ var _line_number :int = -1 var _test_case_names :PackedStringArray = [] -func serialize(test_case :Object) -> Dictionary: +func serialize(test_case :Node) -> Dictionary: var serialized := super.serialize(test_case) if test_case.has_method("line_number"): serialized["line_number"] = test_case.line_number() diff --git a/addons/gdUnit4/src/network/rpc/dtos/GdUnitTestSuiteDto.gd b/addons/gdUnit4/src/network/rpc/dtos/GdUnitTestSuiteDto.gd index fb93edc5..edbae381 100644 --- a/addons/gdUnit4/src/network/rpc/dtos/GdUnitTestSuiteDto.gd +++ b/addons/gdUnit4/src/network/rpc/dtos/GdUnitTestSuiteDto.gd @@ -1,9 +1,16 @@ class_name GdUnitTestSuiteDto extends GdUnitResourceDto + +# Dictionary[String, GdUnitTestCaseDto] var _test_cases_by_name := Dictionary() +static func of(test_suite :Node) -> GdUnitTestSuiteDto: + var dto := GdUnitTestSuiteDto.new() + return dto.deserialize(dto.serialize(test_suite)) + + func serialize(test_suite :Node) -> Dictionary: var serialized := super.serialize(test_suite) var test_cases_ := Array() @@ -15,13 +22,13 @@ func serialize(test_suite :Node) -> Dictionary: func deserialize(data :Dictionary) -> GdUnitResourceDto: super.deserialize(data) - var test_cases_ :Array = data.get("test_cases", Array()) - for test_case in test_cases_: + var test_cases_ :Array = data.get("test_cases", []) + for test_case :Dictionary in test_cases_: add_test_case(GdUnitTestCaseDto.new().deserialize(test_case)) return self -func add_test_case(test_case :GdUnitTestCaseDto): +func add_test_case(test_case :GdUnitTestCaseDto) -> void: _test_cases_by_name[test_case.name()] = test_case @@ -29,5 +36,7 @@ func test_case_count() -> int: return _test_cases_by_name.size() -func test_cases() -> Array: - return _test_cases_by_name.values() +func test_cases() -> Array[GdUnitTestCaseDto]: + var test_cases_ :Array[GdUnitTestCaseDto] = [] + test_cases_.append_array(_test_cases_by_name.values()) + return test_cases_ diff --git a/addons/gdUnit4/src/report/GdUnitByPathReport.gd b/addons/gdUnit4/src/report/GdUnitByPathReport.gd index a0c47fa3..d08600e9 100644 --- a/addons/gdUnit4/src/report/GdUnitByPathReport.gd +++ b/addons/gdUnit4/src/report/GdUnitByPathReport.gd @@ -2,14 +2,15 @@ class_name GdUnitByPathReport extends GdUnitReportSummary -func _init(path_ :String, reports_ :Array[GdUnitReportSummary]): - _resource_path = path_ - _reports = reports_ +func _init(path :String, report_summaries :Array[GdUnitReportSummary]) -> void: + _resource_path = path + _reports = report_summaries -static func sort_reports_by_path(reports_ :Array[GdUnitReportSummary]) -> Dictionary: +# -> Dictionary[String, Array[GdUnitReportSummary]] +static func sort_reports_by_path(report_summaries :Array[GdUnitReportSummary]) -> Dictionary: var by_path := Dictionary() - for report in reports_: + for report in report_summaries: var suite_path :String = report.path() var suite_report :Array[GdUnitReportSummary] = by_path.get(suite_path, [] as Array[GdUnitReportSummary]) suite_report.append(report) @@ -29,7 +30,7 @@ func write(report_dir :String) -> String: var template := GdUnitHtmlPatterns.load_template("res://addons/gdUnit4/src/report/template/folder_report.html") var path_report := GdUnitHtmlPatterns.build(template, self, "") path_report = apply_testsuite_reports(report_dir, path_report, _reports) - + var output_path := "%s/path/%s.html" % [report_dir, path().replace("/", ".")] var dir := output_path.get_base_dir() if not DirAccess.dir_exists_absolute(dir): @@ -38,10 +39,11 @@ func write(report_dir :String) -> String: return output_path -func apply_testsuite_reports(report_dir :String, template :String, reports_ :Array[GdUnitReportSummary]) -> String: +func apply_testsuite_reports(report_dir :String, template :String, test_suite_reports :Array[GdUnitReportSummary]) -> String: var table_records := PackedStringArray() - - for report in reports_: - var report_link = report.output_path(report_dir).replace(report_dir, "..") - table_records.append(report.create_record(report_link)) + for report in test_suite_reports: + if report is GdUnitTestSuiteReport: + var test_suite_report := report as GdUnitTestSuiteReport + var report_link := test_suite_report.output_path(report_dir).replace(report_dir, "..") + table_records.append(test_suite_report.create_record(report_link)) return template.replace(GdUnitHtmlPatterns.TABLE_BY_TESTSUITES, "\n".join(table_records)) diff --git a/addons/gdUnit4/src/report/GdUnitHtmlReport.gd b/addons/gdUnit4/src/report/GdUnitHtmlReport.gd index 41ea52a7..fda9833e 100644 --- a/addons/gdUnit4/src/report/GdUnitHtmlReport.gd +++ b/addons/gdUnit4/src/report/GdUnitHtmlReport.gd @@ -7,81 +7,85 @@ var _report_path :String var _iteration :int -func _init(path_ :String): - _iteration = GdUnitTools.find_last_path_index(path_, REPORT_DIR_PREFIX) + 1 - _report_path = "%s/%s%d" % [path_, REPORT_DIR_PREFIX, _iteration] +func _init(report_path :String) -> void: + _iteration = GdUnitFileAccess.find_last_path_index(report_path, REPORT_DIR_PREFIX) + 1 + _report_path = "%s/%s%d" % [report_path, REPORT_DIR_PREFIX, _iteration] DirAccess.make_dir_recursive_absolute(_report_path) -func add_testsuite_report(suite_report :GdUnitTestSuiteReport): +func add_testsuite_report(suite_report :GdUnitTestSuiteReport) -> void: _reports.append(suite_report) -func add_testcase_report(resource_path_ :String, suite_report :GdUnitTestCaseReport) -> void: +@warning_ignore("shadowed_variable") +func add_testcase_report(resource_path :String, suite_report :GdUnitTestCaseReport) -> void: for report in _reports: - if report.resource_path() == resource_path_: + if report.resource_path() == resource_path: report.add_report(suite_report) +@warning_ignore("shadowed_variable") func update_test_suite_report( - resource_path_ :String, - duration_ :int, + resource_path :String, + duration :int, _is_error :bool, - is_failed_: bool, + is_failed: bool, _is_warning :bool, - is_skipped_ :bool, - skipped_count_ :int, - failed_count_ :int, - orphan_count_ :int, - reports_ :Array = []) -> void: - + _is_skipped :bool, + skipped_count :int, + failed_count :int, + orphan_count :int, + reports :Array = []) -> void: + for report in _reports: - if report.resource_path() == resource_path_: - report.set_duration(duration_) - report.set_failed(is_failed_, failed_count_) - report.set_orphans(orphan_count_) - report.set_reports(reports_) - if is_skipped_: - _skipped_count = skipped_count_ + if report.resource_path() == resource_path: + report.set_duration(duration) + report.set_failed(is_failed, failed_count) + report.set_skipped(skipped_count) + report.set_orphans(orphan_count) + report.set_reports(reports) -func update_testcase_report(resource_path_ :String, test_report :GdUnitTestCaseReport): +@warning_ignore("shadowed_variable") +func update_testcase_report(resource_path :String, test_report :GdUnitTestCaseReport) -> void: for report in _reports: - if report.resource_path() == resource_path_: + if report.resource_path() == resource_path: report.update(test_report) func write() -> String: var template := GdUnitHtmlPatterns.load_template("res://addons/gdUnit4/src/report/template/index.html") - var to_write = GdUnitHtmlPatterns.build(template, self, "") + var to_write := GdUnitHtmlPatterns.build(template, self, "") to_write = apply_path_reports(_report_path, to_write, _reports) to_write = apply_testsuite_reports(_report_path, to_write, _reports) # write report var index_file := "%s/index.html" % _report_path FileAccess.open(index_file, FileAccess.WRITE).store_string(to_write) - GdUnitTools.copy_directory("res://addons/gdUnit4/src/report/template/css/", _report_path + "/css") + GdUnitFileAccess.copy_directory("res://addons/gdUnit4/src/report/template/css/", _report_path + "/css") return index_file func delete_history(max_reports :int) -> int: - return GdUnitTools.delete_path_index_lower_equals_than(_report_path.get_base_dir(), REPORT_DIR_PREFIX, _iteration-max_reports) + return GdUnitFileAccess.delete_path_index_lower_equals_than(_report_path.get_base_dir(), REPORT_DIR_PREFIX, _iteration-max_reports) -func apply_path_reports(report_dir :String, template :String, reports_ :Array) -> String: - var path_report_mapping := GdUnitByPathReport.sort_reports_by_path(reports_) +func apply_path_reports(report_dir :String, template :String, report_summaries :Array) -> String: + #Dictionary[String, Array[GdUnitReportSummary]] + var path_report_mapping := GdUnitByPathReport.sort_reports_by_path(report_summaries) var table_records := PackedStringArray() - var paths := path_report_mapping.keys() + var paths :Array[String] = [] + paths.append_array(path_report_mapping.keys()) paths.sort() - for path_ in paths: - var report := GdUnitByPathReport.new(path_, path_report_mapping.get(path_)) + for report_path in paths: + var report := GdUnitByPathReport.new(report_path, path_report_mapping.get(report_path)) var report_link :String = report.write(report_dir).replace(report_dir, ".") table_records.append(report.create_record(report_link)) return template.replace(GdUnitHtmlPatterns.TABLE_BY_PATHS, "\n".join(table_records)) -func apply_testsuite_reports(report_dir :String, template :String, reports_ :Array) -> String: +func apply_testsuite_reports(report_dir :String, template :String, test_suite_reports :Array[GdUnitReportSummary]) -> String: var table_records := PackedStringArray() - for report in reports_: + for report in test_suite_reports: var report_link :String = report.write(report_dir).replace(report_dir, ".") table_records.append(report.create_record(report_link)) return template.replace(GdUnitHtmlPatterns.TABLE_BY_TESTSUITES, "\n".join(table_records)) diff --git a/addons/gdUnit4/src/report/GdUnitReportSummary.gd b/addons/gdUnit4/src/report/GdUnitReportSummary.gd index 1aefa1fd..41d423e7 100644 --- a/addons/gdUnit4/src/report/GdUnitReportSummary.gd +++ b/addons/gdUnit4/src/report/GdUnitReportSummary.gd @@ -35,6 +35,14 @@ func suite_count() -> int: return _reports.size() +func suite_executed_count() -> int: + var executed := _reports.size() + for report in _reports: + if report.test_count() == report.skipped_count(): + executed -= 1 + return executed + + func test_count() -> int: var count := _test_count for report in _reports: @@ -42,6 +50,10 @@ func test_count() -> int: return count +func test_executed_count() -> int: + return test_count() - skipped_count() + + func error_count() -> int: var count := _error_count for report in _reports: @@ -106,7 +118,7 @@ func calculate_state(p_error_count :int, p_failure_count :int, p_orphan_count :i func calculate_succes_rate(p_test_count :int, p_error_count :int, p_failure_count :int) -> String: if p_failure_count == 0: return "100%" - var count = p_test_count-p_failure_count-p_error_count + var count := p_test_count-p_failure_count-p_error_count if count < 0: return "0%" return "%d" % (( 0 if count < 0 else count) * 100.0 / p_test_count) + "%" @@ -117,7 +129,7 @@ func create_summary(_report_dir :String) -> String: func html_encode(value :String) -> String: - for key in CHARACTERS_TO_ENCODE.keys(): + for key in CHARACTERS_TO_ENCODE.keys() as Array[String]: value =value.replace(key, CHARACTERS_TO_ENCODE[key]) return value diff --git a/addons/gdUnit4/src/report/GdUnitTestCaseReport.gd b/addons/gdUnit4/src/report/GdUnitTestCaseReport.gd index 68ac30fd..deaf7908 100644 --- a/addons/gdUnit4/src/report/GdUnitTestCaseReport.gd +++ b/addons/gdUnit4/src/report/GdUnitTestCaseReport.gd @@ -2,8 +2,10 @@ class_name GdUnitTestCaseReport extends GdUnitReportSummary var _suite_name :String -var _failure_reports :Array +var _failure_reports :Array[GdUnitReport] + +@warning_ignore("shadowed_variable") func _init( p_resource_path :String, p_suite_name :String, @@ -11,17 +13,16 @@ func _init( is_error := false, _is_failed := false, failed_count :int = 0, - orphan_count_ :int = 0, + orphan_count :int = 0, is_skipped := false, - failure_reports :Array = [], - p_duration :int = 0): + failure_reports :Array[GdUnitReport] = [], + p_duration :int = 0) -> void: _resource_path = p_resource_path _suite_name = p_suite_name _name = test_name - _test_count = 1 _error_count = is_error _failure_count = failed_count - _orphan_count = orphan_count_ + _orphan_count = orphan_count _skipped_count = is_skipped _failure_reports = failure_reports _duration = p_duration @@ -33,9 +34,8 @@ func suite_name() -> String: func failure_report() -> String: var html_report := "" - for r in _failure_reports: - var report: GdUnitReport = r - html_report += convert_rtf_to_html(report._to_string()) + for report in _failure_reports: + html_report += convert_rtf_to_html(str(report)) return html_report diff --git a/addons/gdUnit4/src/report/GdUnitTestSuiteReport.gd b/addons/gdUnit4/src/report/GdUnitTestSuiteReport.gd index ca4dc3fc..fa07c8de 100644 --- a/addons/gdUnit4/src/report/GdUnitTestSuiteReport.gd +++ b/addons/gdUnit4/src/report/GdUnitTestSuiteReport.gd @@ -2,13 +2,15 @@ class_name GdUnitTestSuiteReport extends GdUnitReportSummary var _time_stamp :int -var _failure_reports :Array = [] +var _failure_reports :Array[GdUnitReport] = [] -func _init(p_resource_path :String, p_name :String): - _resource_path = p_resource_path - _name = p_name +@warning_ignore("shadowed_variable") +func _init(resource_path :String, name :String, test_count :int) -> void: + _resource_path = resource_path + _name = name _time_stamp = Time.get_unix_time_from_system() as int + _test_count = test_count func create_record(report_link :String) -> String: @@ -25,9 +27,8 @@ func path_as_link() -> String: func failure_report() -> String: var html_report := "" - for r in _failure_reports: - var report: GdUnitReport = r - html_report += convert_rtf_to_html(report._to_string()) + for report in _failure_reports: + html_report += convert_rtf_to_html(str(report)) return html_report @@ -43,16 +44,16 @@ func write(report_dir :String) -> String: var template := GdUnitHtmlPatterns.load_template("res://addons/gdUnit4/src/report/template/suite_report.html") template = GdUnitHtmlPatterns.build(template, self, "")\ .replace(GdUnitHtmlPatterns.BREADCRUMP_PATH_LINK, path_as_link()) - + var report_output_path := output_path(report_dir) var test_report_table := PackedStringArray() if not _failure_reports.is_empty(): test_report_table.append(test_suite_failure_report()) for test_report in _reports: test_report_table.append(test_report.create_record(report_output_path)) - + template = template.replace(GdUnitHtmlPatterns.TABLE_BY_TESTCASES, "\n".join(test_report_table)) - + var dir := report_output_path.get_base_dir() if not DirAccess.dir_exists_absolute(dir): DirAccess.make_dir_recursive_absolute(dir) @@ -85,8 +86,8 @@ func set_failed(failed :bool, count :int) -> void: _failure_count += count -func set_reports(reports_ :Array) -> void: - _failure_reports = reports_ +func set_reports(failure_reports :Array[GdUnitReport]) -> void: + _failure_reports = failure_reports func update(test_report :GdUnitTestCaseReport) -> void: diff --git a/addons/gdUnit4/src/report/JUnitXmlReport.gd b/addons/gdUnit4/src/report/JUnitXmlReport.gd index 65a708bb..a4c040dc 100644 --- a/addons/gdUnit4/src/report/JUnitXmlReport.gd +++ b/addons/gdUnit4/src/report/JUnitXmlReport.gd @@ -25,14 +25,14 @@ var _report_path :String var _iteration :int -func _init(path :String,iteration :int): +func _init(path :String, iteration :int) -> void: _iteration = iteration _report_path = path func write(report :GdUnitReportSummary) -> String: var result_file: String = "%s/results.xml" % _report_path - var file = FileAccess.open(result_file, FileAccess.WRITE) + var file := FileAccess.open(result_file, FileAccess.WRITE) if file == null: push_warning("Can't saving the result to '%s'\n Error: %s" % [result_file, error_string(FileAccess.get_open_error())]) file.store_string(build_junit_report(report)) @@ -40,29 +40,29 @@ func write(report :GdUnitReportSummary) -> String: func build_junit_report(report :GdUnitReportSummary) -> String: - var ISO8601_datetime := Time.get_date_string_from_system() + var iso8601_datetime := Time.get_date_string_from_system() var test_suites := XmlElement.new("testsuites")\ - .attribute(ATTR_ID, ISO8601_datetime)\ + .attribute(ATTR_ID, iso8601_datetime)\ .attribute(ATTR_NAME, "report_%s" % _iteration)\ .attribute(ATTR_TESTS, report.test_count())\ .attribute(ATTR_FAILURES, report.failure_count())\ .attribute(ATTR_TIME, JUnitXmlReport.to_time(report.duration()))\ .add_childs(build_test_suites(report)) - var as_string = test_suites.to_xml() + var as_string := test_suites.to_xml() test_suites.dispose() return HEADER + as_string func build_test_suites(summary :GdUnitReportSummary) -> Array: - var test_suites :Array = Array() + var test_suites :Array[XmlElement] = [] for index in summary.reports().size(): var suite_report :GdUnitTestSuiteReport = summary.reports()[index] - var ISO8601_datetime = Time.get_datetime_string_from_unix_time(suite_report.time_stamp()) + var iso8601_datetime := Time.get_datetime_string_from_unix_time(suite_report.time_stamp()) test_suites.append(XmlElement.new("testsuite")\ .attribute(ATTR_ID, index)\ .attribute(ATTR_NAME, suite_report.name())\ .attribute(ATTR_PACKAGE, suite_report.path())\ - .attribute(ATTR_TIMESTAMP, ISO8601_datetime)\ + .attribute(ATTR_TIMESTAMP, iso8601_datetime)\ .attribute(ATTR_HOST, "localhost")\ .attribute(ATTR_TESTS, suite_report.test_count())\ .attribute(ATTR_FAILURES, suite_report.failure_count())\ @@ -74,37 +74,37 @@ func build_test_suites(summary :GdUnitReportSummary) -> Array: func build_test_cases(suite_report :GdUnitTestSuiteReport) -> Array: - var test_cases :Array = Array() + var test_cases :Array[XmlElement] = [] for index in suite_report.reports().size(): var report :GdUnitTestCaseReport = suite_report.reports()[index] test_cases.append( XmlElement.new("testcase")\ - .attribute(ATTR_NAME, encode_xml(report.name()))\ + .attribute(ATTR_NAME, JUnitXmlReport.encode_xml(report.name()))\ .attribute(ATTR_CLASSNAME, report.suite_name())\ .attribute(ATTR_TIME, JUnitXmlReport.to_time(report.duration()))\ .add_childs(build_reports(report))) return test_cases -func build_reports(testReport :GdUnitTestCaseReport) -> Array: - var failure_reports :Array = Array() - if testReport.failure_count() or testReport.error_count(): - for failure in testReport._failure_reports: +func build_reports(test_report :GdUnitTestCaseReport) -> Array: + var failure_reports :Array[XmlElement] = [] + if test_report.failure_count() or test_report.error_count(): + for failure in test_report._failure_reports: var report := failure as GdUnitReport if report.is_failure(): failure_reports.append( XmlElement.new("failure")\ - .attribute(ATTR_MESSAGE, "FAILED: %s:%d" % [testReport._resource_path, report.line_number()])\ + .attribute(ATTR_MESSAGE, "FAILED: %s:%d" % [test_report._resource_path, report.line_number()])\ .attribute(ATTR_TYPE, JUnitXmlReport.to_type(report.type()))\ .text(convert_rtf_to_text(report.message()))) elif report.is_error(): failure_reports.append( XmlElement.new("error")\ - .attribute(ATTR_MESSAGE, "ERROR: %s:%d" % [testReport._resource_path, report.line_number()])\ + .attribute(ATTR_MESSAGE, "ERROR: %s:%d" % [test_report._resource_path, report.line_number()])\ .attribute(ATTR_TYPE, JUnitXmlReport.to_type(report.type()))\ .text(convert_rtf_to_text(report.message()))) - if testReport.skipped_count(): - for failure in testReport._failure_reports: + if test_report.skipped_count(): + for failure in test_report._failure_reports: var report := failure as GdUnitReport failure_reports.append( XmlElement.new("skipped")\ - .attribute(ATTR_MESSAGE, "SKIPPED: %s:%d" % [testReport._resource_path, report.line_number()])) + .attribute(ATTR_MESSAGE, "SKIPPED: %s:%d" % [test_report._resource_path, report.line_number()])) return failure_reports diff --git a/addons/gdUnit4/src/report/XmlElement.gd b/addons/gdUnit4/src/report/XmlElement.gd index 62757781..b5d2ed38 100644 --- a/addons/gdUnit4/src/report/XmlElement.gd +++ b/addons/gdUnit4/src/report/XmlElement.gd @@ -1,18 +1,19 @@ -class_name XmlElement +class_name XmlElement extends RefCounted var _name :String +# Dictionary[String, String] var _attributes :Dictionary = {} -var _childs :Array = [] -var _parent = null +var _childs :Array[XmlElement] = [] +var _parent :XmlElement = null var _text :String = "" -func _init(name :String): +func _init(name :String) -> void: _name = name -func dispose(): +func dispose() -> void: for child in _childs: child.dispose() _childs.clear() @@ -20,7 +21,7 @@ func dispose(): _parent = null -func attribute(name :String, value) -> XmlElement: +func attribute(name :String, value :Variant) -> XmlElement: _attributes[name] = str(value) return self @@ -36,30 +37,30 @@ func add_child(child :XmlElement) -> XmlElement: return self -func add_childs(childs :Array) -> XmlElement: +func add_childs(childs :Array[XmlElement]) -> XmlElement: for child in childs: add_child(child) return self -func _indentation() -> String: - return "" if _parent == null else _parent._indentation() + " " +func indentation() -> String: + return "" if _parent == null else _parent.indentation() + " " func to_xml() -> String: var attributes := "" - for key in _attributes.keys(): + for key in _attributes.keys() as Array[String]: attributes += ' {attr}="{value}"'.format({"attr": key, "value": _attributes.get(key)}) - - var childs = "" + + var childs := "" for child in _childs: childs += child.to_xml() - + return "{_indentation}<{name}{attributes}>\n{childs}{text}{_indentation}\n"\ - .format({"name": _name, - "attributes": attributes, - "childs": childs, - "_indentation": _indentation(), + .format({"name": _name, + "attributes": attributes, + "childs": childs, + "_indentation": indentation(), "text": cdata(_text)}) diff --git a/addons/gdUnit4/src/report/template/folder_report.html b/addons/gdUnit4/src/report/template/folder_report.html index 95559314..6f217725 100644 --- a/addons/gdUnit4/src/report/template/folder_report.html +++ b/addons/gdUnit4/src/report/template/folder_report.html @@ -10,7 +10,7 @@
@@ -52,8 +52,8 @@

Success Rate

-

History

-

Comming Next

+

History

+

Coming Next

diff --git a/addons/gdUnit4/src/report/template/index.html b/addons/gdUnit4/src/report/template/index.html index 45ac171a..2f6571e6 100644 --- a/addons/gdUnit4/src/report/template/index.html +++ b/addons/gdUnit4/src/report/template/index.html @@ -9,7 +9,7 @@
-

GdUnit3 Report

+

GdUnit4 Report

@@ -51,8 +51,8 @@

Success Rate

-

History

-

Comming Next

+

History

+

Coming Next

@@ -113,7 +113,7 @@

${log_file}

- +