From 380434c2d51bf5937ce7eac007f7efa65441eaef Mon Sep 17 00:00:00 2001 From: Dan Date: Sun, 12 May 2024 15:32:56 +0100 Subject: [PATCH] Delete GdUnitBuildTool and GdUnitCmdTool scripts. Refactor code structure, remove unused variables, and update function implementations. --- addons/gdUnit4/LICENSE | 21 - addons/gdUnit4/bin/GdUnitBuildTool.gd | 110 -- addons/gdUnit4/bin/GdUnitCmdTool.gd | 600 --------- addons/gdUnit4/bin/GdUnitCopyLog.gd | 141 -- addons/gdUnit4/bin/ProjectScanner.gd | 99 -- addons/gdUnit4/plugin.cfg | 7 - addons/gdUnit4/plugin.gd | 47 - addons/gdUnit4/runtest.cmd | 25 - addons/gdUnit4/runtest.sh | 19 - addons/gdUnit4/src/Comparator.gd | 12 - addons/gdUnit4/src/Fuzzers.gd | 34 - addons/gdUnit4/src/GdUnitArrayAssert.gd | 160 --- addons/gdUnit4/src/GdUnitAssert.gd | 35 - addons/gdUnit4/src/GdUnitAwaiter.gd | 69 - addons/gdUnit4/src/GdUnitBoolAssert.gd | 41 - addons/gdUnit4/src/GdUnitConstants.gd | 6 - addons/gdUnit4/src/GdUnitDictionaryAssert.gd | 105 -- addons/gdUnit4/src/GdUnitFailureAssert.gd | 31 - addons/gdUnit4/src/GdUnitFileAssert.gd | 19 - addons/gdUnit4/src/GdUnitFloatAssert.gd | 83 -- addons/gdUnit4/src/GdUnitFuncAssert.gd | 56 - addons/gdUnit4/src/GdUnitGodotErrorAssert.gd | 46 - addons/gdUnit4/src/GdUnitIntAssert.gd | 87 -- addons/gdUnit4/src/GdUnitObjectAssert.gd | 49 - addons/gdUnit4/src/GdUnitResultAssert.gd | 45 - addons/gdUnit4/src/GdUnitSceneRunner.gd | 225 ---- addons/gdUnit4/src/GdUnitSignalAssert.gd | 38 - addons/gdUnit4/src/GdUnitStringAssert.gd | 79 -- addons/gdUnit4/src/GdUnitTestSuite.gd | 620 --------- addons/gdUnit4/src/GdUnitTuple.gd | 28 - addons/gdUnit4/src/GdUnitValueExtractor.gd | 9 - addons/gdUnit4/src/GdUnitVectorAssert.gd | 57 - .../src/asserts/CallBackValueProvider.gd | 25 - .../src/asserts/DefaultValueProvider.gd | 12 - .../gdUnit4/src/asserts/GdAssertMessages.gd | 608 --------- addons/gdUnit4/src/asserts/GdAssertReports.gd | 55 - .../src/asserts/GdUnitArrayAssertImpl.gd | 349 ----- .../gdUnit4/src/asserts/GdUnitAssertImpl.gd | 71 - .../gdUnit4/src/asserts/GdUnitAssertions.gd | 63 - .../src/asserts/GdUnitBoolAssertImpl.gd | 76 -- .../src/asserts/GdUnitDictionaryAssertImpl.gd | 182 --- .../src/asserts/GdUnitFailureAssertImpl.gd | 110 -- .../src/asserts/GdUnitFileAssertImpl.gd | 95 -- .../src/asserts/GdUnitFloatAssertImpl.gd | 144 --- .../src/asserts/GdUnitFuncAssertImpl.gd | 159 --- .../src/asserts/GdUnitGodotErrorAssertImpl.gd | 106 -- .../src/asserts/GdUnitIntAssertImpl.gd | 153 --- .../src/asserts/GdUnitObjectAssertImpl.gd | 109 -- .../src/asserts/GdUnitResultAssertImpl.gd | 121 -- .../src/asserts/GdUnitSignalAssertImpl.gd | 110 -- .../src/asserts/GdUnitStringAssertImpl.gd | 176 --- .../src/asserts/GdUnitVectorAssertImpl.gd | 172 --- addons/gdUnit4/src/asserts/ValueProvider.gd | 6 - addons/gdUnit4/src/cmd/CmdArgumentParser.gd | 61 - addons/gdUnit4/src/cmd/CmdCommand.gd | 26 - addons/gdUnit4/src/cmd/CmdCommandHandler.gd | 105 -- addons/gdUnit4/src/cmd/CmdConsole.gd | 147 --- addons/gdUnit4/src/cmd/CmdOption.gd | 61 - addons/gdUnit4/src/cmd/CmdOptions.gd | 31 - addons/gdUnit4/src/core/GdArrayTools.gd | 101 -- addons/gdUnit4/src/core/GdDiffTool.gd | 155 --- addons/gdUnit4/src/core/GdFunctionDoubler.gd | 191 --- addons/gdUnit4/src/core/GdObjects.gd | 686 ---------- addons/gdUnit4/src/core/GdUnit4Version.gd | 57 - addons/gdUnit4/src/core/GdUnitClassDoubler.gd | 122 -- addons/gdUnit4/src/core/GdUnitFileAccess.gd | 211 --- .../src/core/GdUnitObjectInteractions.gd | 42 - .../core/GdUnitObjectInteractionsTemplate.gd | 91 -- addons/gdUnit4/src/core/GdUnitProperty.gd | 72 -- addons/gdUnit4/src/core/GdUnitResult.gd | 104 -- addons/gdUnit4/src/core/GdUnitRunner.gd | 168 --- addons/gdUnit4/src/core/GdUnitRunner.tscn | 10 - addons/gdUnit4/src/core/GdUnitRunnerConfig.gd | 153 --- .../gdUnit4/src/core/GdUnitSceneRunnerImpl.gd | 418 ------ addons/gdUnit4/src/core/GdUnitScriptType.gd | 16 - addons/gdUnit4/src/core/GdUnitSettings.gd | 336 ----- .../gdUnit4/src/core/GdUnitSignalAwaiter.gd | 64 - .../gdUnit4/src/core/GdUnitSignalCollector.gd | 103 -- addons/gdUnit4/src/core/GdUnitSignals.gd | 34 - addons/gdUnit4/src/core/GdUnitSingleton.gd | 49 - .../src/core/GdUnitTestSuiteBuilder.gd | 18 - .../src/core/GdUnitTestSuiteScanner.gd | 339 ----- addons/gdUnit4/src/core/GdUnitTools.gd | 111 -- .../gdUnit4/src/core/GodotVersionFixures.gd | 21 - addons/gdUnit4/src/core/LocalTime.gd | 110 -- addons/gdUnit4/src/core/_TestCase.gd | 238 ---- .../gdUnit4/src/core/command/GdUnitCommand.gd | 41 - .../src/core/command/GdUnitCommandHandler.gd | 357 ----- .../src/core/command/GdUnitShortcut.gd | 58 - .../src/core/command/GdUnitShortcutAction.gd | 36 - addons/gdUnit4/src/core/event/GdUnitEvent.gd | 185 --- .../gdUnit4/src/core/event/GdUnitEventInit.gd | 19 - .../gdUnit4/src/core/event/GdUnitEventStop.gd | 6 - .../core/execution/GdUnitExecutionContext.gd | 192 --- .../core/execution/GdUnitMemoryObserver.gd | 131 -- .../execution/GdUnitTestReportCollector.gd | 70 - .../core/execution/GdUnitTestSuiteExecutor.gd | 26 - .../stages/GdUnitTestCaseAfterStage.gd | 100 -- .../stages/GdUnitTestCaseBeforeStage.gd | 29 - .../stages/GdUnitTestCaseExecutionStage.gd | 31 - .../stages/GdUnitTestSuiteAfterStage.gd | 28 - .../stages/GdUnitTestSuiteBeforeStage.gd | 14 - .../stages/GdUnitTestSuiteExecutionStage.gd | 114 -- .../execution/stages/IGdUnitExecutionStage.gd | 39 - .../GdUnitTestCaseFuzzedExecutionStage.gd | 21 - .../fuzzed/GdUnitTestCaseFuzzedTestStage.gd | 53 - ...UnitTestCaseParameterizedExecutionStage.gd | 22 - .../GdUnitTestCaseParameterizedTestStage.gd | 76 -- .../GdUnitTestCaseSingleExecutionStage.gd | 22 - .../single/GdUnitTestCaseSingleTestStage.gd | 11 - .../src/core/parse/GdClassDescriptor.gd | 34 - .../src/core/parse/GdDefaultValueDecoder.gd | 255 ---- .../src/core/parse/GdFunctionArgument.gd | 105 -- .../src/core/parse/GdFunctionDescriptor.gd | 250 ---- .../gdUnit4/src/core/parse/GdScriptParser.gd | 824 ------------ .../src/core/parse/GdUnitExpressionRunner.gd | 26 - .../parse/GdUnitTestParameterSetResolver.gd | 194 --- .../gdUnit4/src/core/report/GdUnitReport.gd | 74 -- .../GdUnitTestSuiteDefaultTemplate.gd | 36 - .../test_suite/GdUnitTestSuiteTemplate.gd | 142 -- .../src/core/thread/GdUnitThreadContext.gd | 62 - .../src/core/thread/GdUnitThreadManager.gd | 62 - .../extractors/GdUnitFuncValueExtractor.gd | 69 - addons/gdUnit4/src/fuzzers/FloatFuzzer.gd | 13 - addons/gdUnit4/src/fuzzers/Fuzzer.gd | 39 - addons/gdUnit4/src/fuzzers/IntFuzzer.gd | 32 - addons/gdUnit4/src/fuzzers/StringFuzzer.gd | 64 - addons/gdUnit4/src/fuzzers/Vector2Fuzzer.gd | 18 - addons/gdUnit4/src/fuzzers/Vector3Fuzzer.gd | 19 - .../src/matchers/AnyArgumentMatcher.gd | 11 - .../matchers/AnyBuildInTypeArgumentMatcher.gd | 50 - .../src/matchers/AnyClazzArgumentMatcher.gd | 30 - .../src/matchers/ChainedArgumentMatcher.gd | 22 - .../src/matchers/EqualsArgumentMatcher.gd | 22 - .../src/matchers/GdUnitArgumentMatcher.gd | 8 - .../src/matchers/GdUnitArgumentMatchers.gd | 32 - addons/gdUnit4/src/mocking/GdUnitMock.gd | 40 - .../gdUnit4/src/mocking/GdUnitMockBuilder.gd | 167 --- .../src/mocking/GdUnitMockFunctionDoubler.gd | 85 -- addons/gdUnit4/src/mocking/GdUnitMockImpl.gd | 140 -- addons/gdUnit4/src/monitor/ErrorLogEntry.gd | 59 - addons/gdUnit4/src/monitor/GdUnitMonitor.gd | 24 - .../src/monitor/GdUnitOrphanNodesMonitor.gd | 27 - .../src/monitor/GodotGdErrorMonitor.gd | 84 -- addons/gdUnit4/src/mono/GdUnit4CSharpApi.cs | 50 - .../src/mono/GdUnit4CSharpApiLoader.gd | 64 - addons/gdUnit4/src/network/GdUnitServer.gd | 41 - addons/gdUnit4/src/network/GdUnitServer.tscn | 10 - .../src/network/GdUnitServerConstants.gd | 6 - addons/gdUnit4/src/network/GdUnitTask.gd | 25 - addons/gdUnit4/src/network/GdUnitTcpClient.gd | 137 -- addons/gdUnit4/src/network/GdUnitTcpServer.gd | 162 --- addons/gdUnit4/src/network/rpc/RPC.gd | 24 - .../src/network/rpc/RPCClientConnect.gd | 13 - .../src/network/rpc/RPCClientDisconnect.gd | 13 - addons/gdUnit4/src/network/rpc/RPCData.gd | 13 - .../gdUnit4/src/network/rpc/RPCGdUnitEvent.gd | 18 - .../src/network/rpc/RPCGdUnitTestSuite.gd | 18 - addons/gdUnit4/src/network/rpc/RPCMessage.gd | 18 - .../src/network/rpc/dtos/GdUnitResourceDto.gd | 26 - .../src/network/rpc/dtos/GdUnitTestCaseDto.gd | 33 - .../network/rpc/dtos/GdUnitTestSuiteDto.gd | 33 - .../gdUnit4/src/report/GdUnitByPathReport.gd | 47 - .../gdUnit4/src/report/GdUnitHtmlPatterns.gd | 94 -- addons/gdUnit4/src/report/GdUnitHtmlReport.gd | 91 -- .../gdUnit4/src/report/GdUnitReportSummary.gd | 131 -- .../src/report/GdUnitTestCaseReport.gd | 59 - .../src/report/GdUnitTestSuiteReport.gd | 95 -- addons/gdUnit4/src/report/JUnitXmlReport.gd | 143 -- addons/gdUnit4/src/report/XmlElement.gd | 67 - .../src/report/template/css/breadcrumb.css | 67 - .../gdUnit4/src/report/template/css/icon.png | Bin 13817 -> 0 bytes .../src/report/template/css/icon.png.import | 34 - .../gdUnit4/src/report/template/css/style.css | 312 ----- .../src/report/template/folder_report.html | 99 -- addons/gdUnit4/src/report/template/index.html | 123 -- .../src/report/template/suite_report.html | 109 -- addons/gdUnit4/src/spy/GdUnitSpyBuilder.gd | 112 -- .../src/spy/GdUnitSpyFunctionDoubler.gd | 75 -- addons/gdUnit4/src/spy/GdUnitSpyImpl.gd | 44 - .../src/ui/EditorFileSystemControls.gd | 33 - addons/gdUnit4/src/ui/GdUnitConsole.gd | 161 --- addons/gdUnit4/src/ui/GdUnitConsole.tscn | 61 - addons/gdUnit4/src/ui/GdUnitFonts.gd | 44 - addons/gdUnit4/src/ui/GdUnitInspector.gd | 85 -- addons/gdUnit4/src/ui/GdUnitInspector.tscn | 58 - addons/gdUnit4/src/ui/ScriptEditorControls.gd | 128 -- 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 .../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 | 90 -- .../src/ui/menu/GdUnitContextMenuItem.gd | 65 - .../ui/menu/ScriptEditorContextMenuHandler.gd | 81 -- .../gdUnit4/src/ui/parts/InspectorMonitor.gd | 50 - .../src/ui/parts/InspectorMonitor.tscn | 91 -- .../src/ui/parts/InspectorProgressBar.gd | 45 - .../src/ui/parts/InspectorProgressBar.tscn | 34 - .../src/ui/parts/InspectorStatusBar.gd | 60 - .../src/ui/parts/InspectorStatusBar.tscn | 147 --- .../gdUnit4/src/ui/parts/InspectorToolBar.gd | 113 -- .../src/ui/parts/InspectorToolBar.tscn | 83 -- .../src/ui/parts/InspectorTreeMainPanel.gd | 589 --------- .../src/ui/parts/InspectorTreePanel.tscn | 74 -- .../src/ui/settings/GdUnitInputCapture.gd | 54 - .../src/ui/settings/GdUnitInputCapture.tscn | 33 - .../src/ui/settings/GdUnitSettingsDialog.gd | 292 ----- .../src/ui/settings/GdUnitSettingsDialog.tscn | 315 ----- .../src/ui/templates/TestSuiteTemplate.gd | 122 -- .../src/ui/templates/TestSuiteTemplate.tscn | 127 -- addons/gdUnit4/src/update/GdMarkDownReader.gd | 337 ----- addons/gdUnit4/src/update/GdUnitPatch.gd | 20 - addons/gdUnit4/src/update/GdUnitPatcher.gd | 72 -- addons/gdUnit4/src/update/GdUnitUpdate.gd | 230 ---- addons/gdUnit4/src/update/GdUnitUpdate.tscn | 74 -- .../gdUnit4/src/update/GdUnitUpdateClient.gd | 83 -- .../gdUnit4/src/update/GdUnitUpdateNotify.gd | 183 --- .../src/update/GdUnitUpdateNotify.tscn | 114 -- .../src/update/assets/border_bottom.png | Bin 1757 -> 0 bytes .../update/assets/border_bottom.png.import | 34 - .../gdUnit4/src/update/assets/border_top.png | Bin 1749 -> 0 bytes .../src/update/assets/border_top.png.import | 34 - addons/gdUnit4/src/update/assets/dot1.png | Bin 730 -> 0 bytes .../gdUnit4/src/update/assets/dot1.png.import | 34 - addons/gdUnit4/src/update/assets/dot2.png | Bin 883 -> 0 bytes .../gdUnit4/src/update/assets/dot2.png.import | 34 - addons/gdUnit4/src/update/assets/embedded.png | Bin 287 -> 0 bytes .../src/update/assets/embedded.png.import | 34 - .../src/update/assets/fonts/LICENSE.txt | 202 --- .../src/update/assets/fonts/README.txt | 77 -- .../assets/fonts/static/RobotoMono-Bold.ttf | Bin 87008 -> 0 bytes .../fonts/static/RobotoMono-Bold.ttf.import | 33 - .../fonts/static/RobotoMono-BoldItalic.ttf | Bin 94148 -> 0 bytes .../static/RobotoMono-BoldItalic.ttf.import | 33 - .../fonts/static/RobotoMono-ExtraLight.ttf | Bin 87788 -> 0 bytes .../static/RobotoMono-ExtraLight.ttf.import | 33 - .../static/RobotoMono-ExtraLightItalic.ttf | Bin 93408 -> 0 bytes .../RobotoMono-ExtraLightItalic.ttf.import | 33 - .../assets/fonts/static/RobotoMono-Italic.ttf | Bin 93904 -> 0 bytes .../fonts/static/RobotoMono-Italic.ttf.import | 33 - .../assets/fonts/static/RobotoMono-Light.ttf | Bin 87592 -> 0 bytes .../fonts/static/RobotoMono-Light.ttf.import | 33 - .../fonts/static/RobotoMono-LightItalic.ttf | Bin 93760 -> 0 bytes .../static/RobotoMono-LightItalic.ttf.import | 33 - .../assets/fonts/static/RobotoMono-Medium.ttf | Bin 86820 -> 0 bytes .../fonts/static/RobotoMono-Medium.ttf.import | 33 - .../fonts/static/RobotoMono-MediumItalic.ttf | Bin 93948 -> 0 bytes .../static/RobotoMono-MediumItalic.ttf.import | 33 - .../fonts/static/RobotoMono-Regular.ttf | Bin 86908 -> 0 bytes .../static/RobotoMono-Regular.ttf.import | 33 - .../fonts/static/RobotoMono-SemiBold.ttf | Bin 87076 -> 0 bytes .../static/RobotoMono-SemiBold.ttf.import | 33 - .../static/RobotoMono-SemiBoldItalic.ttf | Bin 93940 -> 0 bytes .../RobotoMono-SemiBoldItalic.ttf.import | 33 - .../assets/fonts/static/RobotoMono-Thin.ttf | Bin 87872 -> 0 bytes .../fonts/static/RobotoMono-Thin.ttf.import | 33 - .../fonts/static/RobotoMono-ThinItalic.ttf | Bin 93056 -> 0 bytes .../static/RobotoMono-ThinItalic.ttf.import | 33 - .../src/update/assets/horizontal-line2.png | Bin 332 -> 0 bytes .../update/assets/horizontal-line2.png.import | 34 - .../src/update/assets/progress-background.png | Bin 1047 -> 0 bytes .../assets/progress-background.png.import | 34 - addons/gdUnit4/test/GdUnitAwaiterTest.gd | 86 -- .../GdUnitScanTestSuitePerformanceTest.gd | 13 - .../gdUnit4/test/GdUnitScanTestSuiteTest.gd | 29 - addons/gdUnit4/test/GdUnitScriptTypeTest.gd | 17 - .../gdUnit4/test/GdUnitTestCaseTimeoutTest.gd | 125 -- .../gdUnit4/test/GdUnitTestCaseTimingTest.gd | 96 -- .../gdUnit4/test/GdUnitTestResourceLoader.gd | 77 -- .../test/GdUnitTestResourceLoaderTest.gd | 16 - .../test/GdUnitTestSuitePerformanceTest.gd | 13 - addons/gdUnit4/test/GdUnitTestSuiteTest.gd | 48 - .../test/asserts/CallBackValueProviderTest.gd | 23 - .../test/asserts/GdUnitArrayAssertImplTest.gd | 777 ----------- .../test/asserts/GdUnitAssertImplTest.gd | 118 -- .../test/asserts/GdUnitBoolAssertImplTest.gd | 117 -- .../asserts/GdUnitDictionaryAssertImplTest.gd | 470 ------- .../asserts/GdUnitFailureAssertImplTest.gd | 79 -- .../test/asserts/GdUnitFloatAssertImplTest.gd | 246 ---- .../test/asserts/GdUnitFuncAssertImplTest.gd | 368 ------ .../asserts/GdUnitGodotErrorAssertImplTest.gd | 137 -- .../asserts/GdUnitGodotErrorWithAssertTest.gd | 40 - .../test/asserts/GdUnitIntAssertImplTest.gd | 242 ---- .../asserts/GdUnitObjectAssertImplTest.gd | 178 --- .../asserts/GdUnitPackedArrayAssertTest.gd | 338 ----- .../asserts/GdUnitResultAssertImplTest.gd | 143 -- .../asserts/GdUnitSignalAssertImplTest.gd | 228 ---- .../asserts/GdUnitStringAssertImplTest.gd | 450 ------- .../asserts/GdUnitVectorAssertImplTest.gd | 367 ------ .../gdUnit4/test/cmd/CmdArgumentParserTest.gd | 120 -- .../gdUnit4/test/cmd/CmdCommandHandlerTest.gd | 123 -- addons/gdUnit4/test/cmd/CmdCommandTest.gd | 32 - addons/gdUnit4/test/cmd/CmdConsoleTest.gd | 85 -- addons/gdUnit4/test/cmd/CmdOptionTest.gd | 51 - addons/gdUnit4/test/cmd/CmdOptionsTest.gd | 57 - addons/gdUnit4/test/core/ExampleTestSuite.cs | 21 - addons/gdUnit4/test/core/GdArrayToolsTest.gd | 154 --- addons/gdUnit4/test/core/GdDiffToolTest.gd | 48 - addons/gdUnit4/test/core/GdObjectsTest.gd | 512 -------- .../gdUnit4/test/core/GdUnit4VersionTest.gd | 67 - .../test/core/GdUnitClassDoublerTest.gd | 13 - .../gdUnit4/test/core/GdUnitFileAccessTest.gd | 231 ---- .../GdUnitObjectInteractionsTemplateTest.gd | 36 - addons/gdUnit4/test/core/GdUnitResultTest.gd | 37 - .../test/core/GdUnitRunnerConfigTest.gd | 189 --- .../core/GdUnitSceneRunnerInputEventTest.gd | 527 -------- .../test/core/GdUnitSceneRunnerTest.gd | 356 ----- .../gdUnit4/test/core/GdUnitSettingsTest.gd | 155 --- .../test/core/GdUnitSignalAwaiterTest.gd | 46 - .../gdUnit4/test/core/GdUnitSingletonTest.gd | 11 - .../test/core/GdUnitTestSuiteBuilderTest.gd | 65 - .../test/core/GdUnitTestSuiteScannerTest.gd | 372 ------ addons/gdUnit4/test/core/GdUnitToolsTest.gd | 49 - addons/gdUnit4/test/core/LocalTimeTest.gd | 69 - .../test/core/ParameterizedTestCaseTest.gd | 296 ----- .../core/command/GdUnitCommandHandlerTest.gd | 70 - .../test/core/event/GdUnitEventTest.gd | 22 - .../core/event/GdUnitTestEventSerdeTest.gd | 51 - .../execution/GdUnitExecutionContextTest.gd | 222 ---- .../execution/GdUnitTestSuiteExecutorTest.gd | 708 ---------- .../core/parse/GdDefaultValueDecoderTest.gd | 255 ---- .../test/core/parse/GdFunctionArgumentTest.gd | 76 -- .../core/parse/GdFunctionDescriptorTest.gd | 146 --- .../test/core/parse/GdScriptParserTest.gd | 632 --------- .../core/parse/GdUnitExpressionRunnerTest.gd | 65 - .../GdUnitTestParameterSetResolverTest.gd | 222 ---- addons/gdUnit4/test/core/resources/Area4D.txt | 17 - .../test/core/resources/AtmosphereData.txt | 22 - .../resources/GdUnitRunner_old_format.cfg | Bin 84 -> 0 bytes .../gdUnit4/test/core/resources/SoundData.txt | 5 - .../resources/copy_test/folder_a/file_a.txt | 1 - .../resources/copy_test/folder_a/file_b.txt | 1 - .../resources/copy_test/folder_b/file_a.txt | 1 - .../resources/copy_test/folder_b/file_b.txt | 1 - .../copy_test/folder_b/folder_ba/file_x.txt | 1 - .../resources/copy_test/folder_c/file_z.txt | 0 .../PascalCaseWithClassName.gd | 7 - .../PascalCaseWithoutClassName.gd | 6 - .../snake_case_with_class_name.gd | 7 - .../snake_case_without_class_name.gd | 6 - .../by_class_name/BaseTest.gd | 22 - .../by_class_name/ExtendedTest.gd | 14 - .../by_class_name/ExtendsExtendedTest.gd | 4 - .../by_class_path/BaseTest.gd | 4 - .../by_class_path/ExtendedTest.gd | 4 - .../by_class_path/ExtendsExtendedTest.gd | 4 - .../drag_and_drop/DragAndDropControl.gd | 41 - .../drag_and_drop/DragAndDropControl.tscn | 16 - .../drag_and_drop/DragAndDropTestScene.gd | 18 - .../drag_and_drop/DragAndDropTestScene.tscn | 43 - .../resources/scenes/drag_and_drop/icon.png | Bin 13817 -> 0 bytes .../scenes/drag_and_drop/icon.png.import | 34 - .../input_actions/InputEventTestScene.gd | 15 - .../input_actions/InputEventTestScene.tscn | 12 - .../core/resources/scenes/simple_scene.gd | 15 - .../core/resources/scenes/simple_scene.tscn | 11 - .../core/resources/script_with_class_name.gd | 6 - .../resources/script_without_class_name.gd | 5 - .../core/resources/sources/test_person.gd | 17 - .../resources/test_script_with_arguments.gd | 49 - .../testsuites/GdUnitFuzzerTest.resource | 48 - .../resources/testsuites/NotATestSuite.cs | 14 - .../testsuites/TestCaseSkipped.resource | 11 - .../TestSuiteAllStagesSuccess.resource | 20 - .../TestSuiteErrorOnTestTimeout.resource | 24 - .../TestSuiteFailAddChildStageBefore.resource | 11 - .../TestSuiteFailAndOrpahnsDetected.resource | 37 - .../TestSuiteFailOnMultipeStages.resource | 21 - .../TestSuiteFailOnStageAfter.resource | 20 - .../TestSuiteFailOnStageAfterTest.resource | 20 - .../TestSuiteFailOnStageBefore.resource | 20 - .../TestSuiteFailOnStageBeforeTest.resource | 20 - .../TestSuiteFailOnStageTestCase1.resource | 20 - .../TestSuiteFuzzedMetricsTest.resource | 82 -- ...estSuiteInvalidParameterizedTests.resource | 56 - ...TestSuiteParameterizedMetricsTest.resource | 85 -- .../TestSuiteParameterizedTests.resource | 148 --- .../testsuites/TestSuiteSkipped.resource | 20 - .../testsuites/TestSuiteWithoutTests.gd | 9 - .../test_suite/GdUnitTestSuiteTemplateTest.gd | 97 -- .../GdUnitFuncValueExtractorTest.gd | 67 - .../fuzzers/GdUnitFuzzerValueInjectionTest.gd | 161 --- .../gdUnit4/test/fuzzers/StringFuzzerTest.gd | 34 - .../test/fuzzers/TestExternalFuzzer.gd | 10 - addons/gdUnit4/test/fuzzers/TestFuzzers.gd | 27 - .../test/matchers/AnyArgumentMatcherTest.gd | 29 - .../AnyBuildInTypeArgumentMatcherTest.gd | 228 ---- .../matchers/AnyClazzArgumentMatcherTest.gd | 43 - .../matchers/ChainedArgumentMatcherTest.gd | 36 - .../matchers/CustomArgumentMatcherTest.gd | 26 - .../matchers/GdUnitArgumentMatchersTest.gd | 16 - .../CustomArgumentMatcherTestClass.gd | 8 - addons/gdUnit4/test/mocker/CustomEnums.gd | 8 - .../test/mocker/GdUnitMockBuilderTest.gd | 282 ---- .../gdUnit4/test/mocker/GdUnitMockerTest.gd | 1148 ----------------- .../test/mocker/GodotClassNameFuzzer.gd | 43 - .../mocker/resources/AdvancedTestClass.gd | 84 -- .../resources/ClassWithCustomClassName.gd | 3 - .../resources/ClassWithCustomFormattings.gd | 52 - .../ClassWithDefaultBuildIntTypes.gd | 8 - .../resources/ClassWithEnumReturnTypes.gd | 26 - .../test/mocker/resources/ClassWithNameA.gd | 5 - .../test/mocker/resources/ClassWithNameB.gd | 8 - .../resources/ClassWithOverridenVirtuals.gd | 21 - .../resources/ClassWithPoolStringArrayFunc.gd | 7 - .../mocker/resources/ClassWithVariables.gd | 40 - .../mocker/resources/ClassWithoutNameA.gd | 4 - .../ClassWithoutNameAndNotExtends.gd | 5 - .../CustomClassExtendsCustomClass.gd | 9 - .../mocker/resources/CustomNodeTestClass.gd | 30 - .../resources/CustomResourceTestClass.gd | 17 - .../mocker/resources/DeepStubTestClass.gd | 16 - .../test/mocker/resources/GD-256/world.gd | 5 - .../resources/OverridenGetClassTestClass.gd | 11 - .../test/mocker/resources/TestPersion.gd | 22 - .../test/mocker/resources/capsuleshape2d.tres | 3 - .../test/mocker/resources/scenes/Spell.gd | 39 - .../test/mocker/resources/scenes/TestScene.gd | 104 -- .../mocker/resources/scenes/TestScene.tscn | 104 -- .../scenes/TestSceneWithoutScript.tscn | 67 - .../test/mocker/resources/snake_case.gd | 4 - .../mocker/resources/snake_case_class_name.gd | 6 - .../gdUnit4/test/monitor/ErrorLogEntryTest.gd | 27 - .../test/monitor/GodotGdErrorMonitorTest.gd | 125 -- addons/gdUnit4/test/mono/ExampleTestSuite.cs | 38 - .../test/mono/GdUnit4CSharpApiLoaderTest.gd | 71 - .../gdUnit4/test/mono/GdUnit4CSharpApiTest.cs | 22 - .../test/network/GdUnitTcpServerTest.gd | 89 -- .../gdUnit4/test/report/JUnitXmlReportTest.gd | 28 - addons/gdUnit4/test/report/XmlElementTest.gd | 212 --- addons/gdUnit4/test/resources/core/City.gd | 8 - .../test/resources/core/CustomClass.gd | 22 - .../resources/core/GeneratedPersonTest.gd | 8 - addons/gdUnit4/test/resources/core/Person.gd | 15 - addons/gdUnit4/test/resources/core/Udo.gd | 6 - .../test/resources/core/sources/TestPerson.cs | 28 - .../test/resources/issues/gd-166/issue.gd | 21 - .../test/resources/issues/gd-166/types.gd | 9 - .../gdUnit4/test/spy/GdUnitSpyBuilderTest.gd | 332 ----- addons/gdUnit4/test/spy/GdUnitSpyTest.gd | 621 --------- addons/gdUnit4/test/ui/GdUnitFontsTest.gd | 17 - .../test/ui/parts/InspectorProgressBarTest.gd | 84 -- .../InspectorTreeMainPanelPerformanceTest.gd | 168 --- .../ui/parts/InspectorTreeMainPanelTest.gd | 303 ----- .../resources/bar/ExampleTestSuiteA.resource | 17 - .../resources/foo/ExampleTestSuiteA.resource | 18 - .../resources/foo/ExampleTestSuiteB.resource | 12 - .../resources/foo/ExampleTestSuiteC.resource | 18 - .../ui/templates/TestSuiteTemplateTest.gd | 36 - .../test/update/GdMarkDownReaderTest.gd | 122 -- .../gdUnit4/test/update/GdUnitPatcherTest.gd | 114 -- .../gdUnit4/test/update/GdUnitUpdateTest.gd | 18 - addons/gdUnit4/test/update/bbcodeView.gd | 48 - addons/gdUnit4/test/update/bbcodeView.tscn | 39 - .../test/update/resources/bbcode_example.txt | 28 - .../test/update/resources/bbcode_header.txt | 40 - .../update/resources/bbcode_md_header.txt | 20 - .../test/update/resources/bbcode_table.txt | 9 - .../test/update/resources/html_header.txt | 21 - .../gdUnit4/test/update/resources/icon48.png | Bin 916 -> 0 bytes .../test/update/resources/icon48.png.import | 34 - .../test/update/resources/markdown.txt | 168 --- .../update/resources/markdown_example.txt | 22 - .../test/update/resources/markdown_table.txt | 6 - .../test/update/resources/md_header.txt | 11 - .../resources/patches/v0.9.5/patch_y.gd | 12 - .../resources/patches/v0.9.6/patch_x.gd | 12 - .../resources/patches/v0.9.9/patch_a.gd | 12 - .../resources/patches/v0.9.9/patch_b.gd | 12 - .../resources/patches/v1.1.4/patch_a.gd | 12 - .../gdUnit4/test/update/resources/update.zip | Bin 1194 -> 0 bytes export_presets.cfg | 2 +- project.godot | 4 +- resources/icons/gameicon.png | Bin 0 -> 16524 bytes .../icons/gameicon.png.import | 8 +- 545 files changed, 8 insertions(+), 45148 deletions(-) delete mode 100644 addons/gdUnit4/LICENSE delete mode 100644 addons/gdUnit4/bin/GdUnitBuildTool.gd delete mode 100644 addons/gdUnit4/bin/GdUnitCmdTool.gd delete mode 100644 addons/gdUnit4/bin/GdUnitCopyLog.gd delete mode 100644 addons/gdUnit4/bin/ProjectScanner.gd delete mode 100644 addons/gdUnit4/plugin.cfg delete mode 100644 addons/gdUnit4/plugin.gd delete mode 100644 addons/gdUnit4/runtest.cmd delete mode 100644 addons/gdUnit4/runtest.sh delete mode 100644 addons/gdUnit4/src/Comparator.gd delete mode 100644 addons/gdUnit4/src/Fuzzers.gd delete mode 100644 addons/gdUnit4/src/GdUnitArrayAssert.gd delete mode 100644 addons/gdUnit4/src/GdUnitAssert.gd delete mode 100644 addons/gdUnit4/src/GdUnitAwaiter.gd delete mode 100644 addons/gdUnit4/src/GdUnitBoolAssert.gd delete mode 100644 addons/gdUnit4/src/GdUnitConstants.gd delete mode 100644 addons/gdUnit4/src/GdUnitDictionaryAssert.gd delete mode 100644 addons/gdUnit4/src/GdUnitFailureAssert.gd delete mode 100644 addons/gdUnit4/src/GdUnitFileAssert.gd delete mode 100644 addons/gdUnit4/src/GdUnitFloatAssert.gd delete mode 100644 addons/gdUnit4/src/GdUnitFuncAssert.gd delete mode 100644 addons/gdUnit4/src/GdUnitGodotErrorAssert.gd delete mode 100644 addons/gdUnit4/src/GdUnitIntAssert.gd delete mode 100644 addons/gdUnit4/src/GdUnitObjectAssert.gd delete mode 100644 addons/gdUnit4/src/GdUnitResultAssert.gd delete mode 100644 addons/gdUnit4/src/GdUnitSceneRunner.gd delete mode 100644 addons/gdUnit4/src/GdUnitSignalAssert.gd delete mode 100644 addons/gdUnit4/src/GdUnitStringAssert.gd delete mode 100644 addons/gdUnit4/src/GdUnitTestSuite.gd delete mode 100644 addons/gdUnit4/src/GdUnitTuple.gd delete mode 100644 addons/gdUnit4/src/GdUnitValueExtractor.gd delete mode 100644 addons/gdUnit4/src/GdUnitVectorAssert.gd delete mode 100644 addons/gdUnit4/src/asserts/CallBackValueProvider.gd delete mode 100644 addons/gdUnit4/src/asserts/DefaultValueProvider.gd delete mode 100644 addons/gdUnit4/src/asserts/GdAssertMessages.gd delete mode 100644 addons/gdUnit4/src/asserts/GdAssertReports.gd delete mode 100644 addons/gdUnit4/src/asserts/GdUnitArrayAssertImpl.gd delete mode 100644 addons/gdUnit4/src/asserts/GdUnitAssertImpl.gd delete mode 100644 addons/gdUnit4/src/asserts/GdUnitAssertions.gd delete mode 100644 addons/gdUnit4/src/asserts/GdUnitBoolAssertImpl.gd delete mode 100644 addons/gdUnit4/src/asserts/GdUnitDictionaryAssertImpl.gd delete mode 100644 addons/gdUnit4/src/asserts/GdUnitFailureAssertImpl.gd delete mode 100644 addons/gdUnit4/src/asserts/GdUnitFileAssertImpl.gd delete mode 100644 addons/gdUnit4/src/asserts/GdUnitFloatAssertImpl.gd delete mode 100644 addons/gdUnit4/src/asserts/GdUnitFuncAssertImpl.gd delete mode 100644 addons/gdUnit4/src/asserts/GdUnitGodotErrorAssertImpl.gd delete mode 100644 addons/gdUnit4/src/asserts/GdUnitIntAssertImpl.gd delete mode 100644 addons/gdUnit4/src/asserts/GdUnitObjectAssertImpl.gd delete mode 100644 addons/gdUnit4/src/asserts/GdUnitResultAssertImpl.gd delete mode 100644 addons/gdUnit4/src/asserts/GdUnitSignalAssertImpl.gd delete mode 100644 addons/gdUnit4/src/asserts/GdUnitStringAssertImpl.gd delete mode 100644 addons/gdUnit4/src/asserts/GdUnitVectorAssertImpl.gd delete mode 100644 addons/gdUnit4/src/asserts/ValueProvider.gd delete mode 100644 addons/gdUnit4/src/cmd/CmdArgumentParser.gd delete mode 100644 addons/gdUnit4/src/cmd/CmdCommand.gd delete mode 100644 addons/gdUnit4/src/cmd/CmdCommandHandler.gd delete mode 100644 addons/gdUnit4/src/cmd/CmdConsole.gd delete mode 100644 addons/gdUnit4/src/cmd/CmdOption.gd delete mode 100644 addons/gdUnit4/src/cmd/CmdOptions.gd delete mode 100644 addons/gdUnit4/src/core/GdArrayTools.gd delete mode 100644 addons/gdUnit4/src/core/GdDiffTool.gd delete mode 100644 addons/gdUnit4/src/core/GdFunctionDoubler.gd delete mode 100644 addons/gdUnit4/src/core/GdObjects.gd delete mode 100644 addons/gdUnit4/src/core/GdUnit4Version.gd delete mode 100644 addons/gdUnit4/src/core/GdUnitClassDoubler.gd delete mode 100644 addons/gdUnit4/src/core/GdUnitFileAccess.gd delete mode 100644 addons/gdUnit4/src/core/GdUnitObjectInteractions.gd delete mode 100644 addons/gdUnit4/src/core/GdUnitObjectInteractionsTemplate.gd delete mode 100644 addons/gdUnit4/src/core/GdUnitProperty.gd delete mode 100644 addons/gdUnit4/src/core/GdUnitResult.gd delete mode 100644 addons/gdUnit4/src/core/GdUnitRunner.gd delete mode 100644 addons/gdUnit4/src/core/GdUnitRunner.tscn delete mode 100644 addons/gdUnit4/src/core/GdUnitRunnerConfig.gd delete mode 100644 addons/gdUnit4/src/core/GdUnitSceneRunnerImpl.gd delete mode 100644 addons/gdUnit4/src/core/GdUnitScriptType.gd delete mode 100644 addons/gdUnit4/src/core/GdUnitSettings.gd delete mode 100644 addons/gdUnit4/src/core/GdUnitSignalAwaiter.gd delete mode 100644 addons/gdUnit4/src/core/GdUnitSignalCollector.gd delete mode 100644 addons/gdUnit4/src/core/GdUnitSignals.gd delete mode 100644 addons/gdUnit4/src/core/GdUnitSingleton.gd delete mode 100644 addons/gdUnit4/src/core/GdUnitTestSuiteBuilder.gd delete mode 100644 addons/gdUnit4/src/core/GdUnitTestSuiteScanner.gd delete mode 100644 addons/gdUnit4/src/core/GdUnitTools.gd delete mode 100644 addons/gdUnit4/src/core/GodotVersionFixures.gd delete mode 100644 addons/gdUnit4/src/core/LocalTime.gd delete mode 100644 addons/gdUnit4/src/core/_TestCase.gd delete mode 100644 addons/gdUnit4/src/core/command/GdUnitCommand.gd delete mode 100644 addons/gdUnit4/src/core/command/GdUnitCommandHandler.gd delete mode 100644 addons/gdUnit4/src/core/command/GdUnitShortcut.gd delete mode 100644 addons/gdUnit4/src/core/command/GdUnitShortcutAction.gd delete mode 100644 addons/gdUnit4/src/core/event/GdUnitEvent.gd delete mode 100644 addons/gdUnit4/src/core/event/GdUnitEventInit.gd delete mode 100644 addons/gdUnit4/src/core/event/GdUnitEventStop.gd delete mode 100644 addons/gdUnit4/src/core/execution/GdUnitExecutionContext.gd delete mode 100644 addons/gdUnit4/src/core/execution/GdUnitMemoryObserver.gd delete mode 100644 addons/gdUnit4/src/core/execution/GdUnitTestReportCollector.gd delete mode 100644 addons/gdUnit4/src/core/execution/GdUnitTestSuiteExecutor.gd delete mode 100644 addons/gdUnit4/src/core/execution/stages/GdUnitTestCaseAfterStage.gd delete mode 100644 addons/gdUnit4/src/core/execution/stages/GdUnitTestCaseBeforeStage.gd delete mode 100644 addons/gdUnit4/src/core/execution/stages/GdUnitTestCaseExecutionStage.gd delete mode 100644 addons/gdUnit4/src/core/execution/stages/GdUnitTestSuiteAfterStage.gd delete mode 100644 addons/gdUnit4/src/core/execution/stages/GdUnitTestSuiteBeforeStage.gd delete mode 100644 addons/gdUnit4/src/core/execution/stages/GdUnitTestSuiteExecutionStage.gd delete mode 100644 addons/gdUnit4/src/core/execution/stages/IGdUnitExecutionStage.gd delete mode 100644 addons/gdUnit4/src/core/execution/stages/fuzzed/GdUnitTestCaseFuzzedExecutionStage.gd delete mode 100644 addons/gdUnit4/src/core/execution/stages/fuzzed/GdUnitTestCaseFuzzedTestStage.gd delete mode 100644 addons/gdUnit4/src/core/execution/stages/parameterized/GdUnitTestCaseParameterizedExecutionStage.gd delete mode 100644 addons/gdUnit4/src/core/execution/stages/parameterized/GdUnitTestCaseParameterizedTestStage.gd delete mode 100644 addons/gdUnit4/src/core/execution/stages/single/GdUnitTestCaseSingleExecutionStage.gd delete mode 100644 addons/gdUnit4/src/core/execution/stages/single/GdUnitTestCaseSingleTestStage.gd delete mode 100644 addons/gdUnit4/src/core/parse/GdClassDescriptor.gd delete mode 100644 addons/gdUnit4/src/core/parse/GdDefaultValueDecoder.gd delete mode 100644 addons/gdUnit4/src/core/parse/GdFunctionArgument.gd delete mode 100644 addons/gdUnit4/src/core/parse/GdFunctionDescriptor.gd delete mode 100644 addons/gdUnit4/src/core/parse/GdScriptParser.gd delete mode 100644 addons/gdUnit4/src/core/parse/GdUnitExpressionRunner.gd delete mode 100644 addons/gdUnit4/src/core/parse/GdUnitTestParameterSetResolver.gd delete mode 100644 addons/gdUnit4/src/core/report/GdUnitReport.gd delete mode 100644 addons/gdUnit4/src/core/templates/test_suite/GdUnitTestSuiteDefaultTemplate.gd delete mode 100644 addons/gdUnit4/src/core/templates/test_suite/GdUnitTestSuiteTemplate.gd delete mode 100644 addons/gdUnit4/src/core/thread/GdUnitThreadContext.gd delete mode 100644 addons/gdUnit4/src/core/thread/GdUnitThreadManager.gd delete mode 100644 addons/gdUnit4/src/extractors/GdUnitFuncValueExtractor.gd delete mode 100644 addons/gdUnit4/src/fuzzers/FloatFuzzer.gd delete mode 100644 addons/gdUnit4/src/fuzzers/Fuzzer.gd delete mode 100644 addons/gdUnit4/src/fuzzers/IntFuzzer.gd delete mode 100644 addons/gdUnit4/src/fuzzers/StringFuzzer.gd delete mode 100644 addons/gdUnit4/src/fuzzers/Vector2Fuzzer.gd delete mode 100644 addons/gdUnit4/src/fuzzers/Vector3Fuzzer.gd delete mode 100644 addons/gdUnit4/src/matchers/AnyArgumentMatcher.gd delete mode 100644 addons/gdUnit4/src/matchers/AnyBuildInTypeArgumentMatcher.gd delete mode 100644 addons/gdUnit4/src/matchers/AnyClazzArgumentMatcher.gd delete mode 100644 addons/gdUnit4/src/matchers/ChainedArgumentMatcher.gd delete mode 100644 addons/gdUnit4/src/matchers/EqualsArgumentMatcher.gd delete mode 100644 addons/gdUnit4/src/matchers/GdUnitArgumentMatcher.gd delete mode 100644 addons/gdUnit4/src/matchers/GdUnitArgumentMatchers.gd delete mode 100644 addons/gdUnit4/src/mocking/GdUnitMock.gd delete mode 100644 addons/gdUnit4/src/mocking/GdUnitMockBuilder.gd delete mode 100644 addons/gdUnit4/src/mocking/GdUnitMockFunctionDoubler.gd delete mode 100644 addons/gdUnit4/src/mocking/GdUnitMockImpl.gd delete mode 100644 addons/gdUnit4/src/monitor/ErrorLogEntry.gd delete mode 100644 addons/gdUnit4/src/monitor/GdUnitMonitor.gd delete mode 100644 addons/gdUnit4/src/monitor/GdUnitOrphanNodesMonitor.gd delete mode 100644 addons/gdUnit4/src/monitor/GodotGdErrorMonitor.gd delete mode 100644 addons/gdUnit4/src/mono/GdUnit4CSharpApi.cs delete mode 100644 addons/gdUnit4/src/mono/GdUnit4CSharpApiLoader.gd delete mode 100644 addons/gdUnit4/src/network/GdUnitServer.gd delete mode 100644 addons/gdUnit4/src/network/GdUnitServer.tscn delete mode 100644 addons/gdUnit4/src/network/GdUnitServerConstants.gd delete mode 100644 addons/gdUnit4/src/network/GdUnitTask.gd delete mode 100644 addons/gdUnit4/src/network/GdUnitTcpClient.gd delete mode 100644 addons/gdUnit4/src/network/GdUnitTcpServer.gd delete mode 100644 addons/gdUnit4/src/network/rpc/RPC.gd delete mode 100644 addons/gdUnit4/src/network/rpc/RPCClientConnect.gd delete mode 100644 addons/gdUnit4/src/network/rpc/RPCClientDisconnect.gd delete mode 100644 addons/gdUnit4/src/network/rpc/RPCData.gd delete mode 100644 addons/gdUnit4/src/network/rpc/RPCGdUnitEvent.gd delete mode 100644 addons/gdUnit4/src/network/rpc/RPCGdUnitTestSuite.gd delete mode 100644 addons/gdUnit4/src/network/rpc/RPCMessage.gd delete mode 100644 addons/gdUnit4/src/network/rpc/dtos/GdUnitResourceDto.gd delete mode 100644 addons/gdUnit4/src/network/rpc/dtos/GdUnitTestCaseDto.gd delete mode 100644 addons/gdUnit4/src/network/rpc/dtos/GdUnitTestSuiteDto.gd delete mode 100644 addons/gdUnit4/src/report/GdUnitByPathReport.gd delete mode 100644 addons/gdUnit4/src/report/GdUnitHtmlPatterns.gd delete mode 100644 addons/gdUnit4/src/report/GdUnitHtmlReport.gd delete mode 100644 addons/gdUnit4/src/report/GdUnitReportSummary.gd delete mode 100644 addons/gdUnit4/src/report/GdUnitTestCaseReport.gd delete mode 100644 addons/gdUnit4/src/report/GdUnitTestSuiteReport.gd delete mode 100644 addons/gdUnit4/src/report/JUnitXmlReport.gd delete mode 100644 addons/gdUnit4/src/report/XmlElement.gd delete mode 100644 addons/gdUnit4/src/report/template/css/breadcrumb.css delete mode 100644 addons/gdUnit4/src/report/template/css/icon.png delete mode 100644 addons/gdUnit4/src/report/template/css/icon.png.import delete mode 100644 addons/gdUnit4/src/report/template/css/style.css delete mode 100644 addons/gdUnit4/src/report/template/folder_report.html delete mode 100644 addons/gdUnit4/src/report/template/index.html delete mode 100644 addons/gdUnit4/src/report/template/suite_report.html delete mode 100644 addons/gdUnit4/src/spy/GdUnitSpyBuilder.gd delete mode 100644 addons/gdUnit4/src/spy/GdUnitSpyFunctionDoubler.gd delete mode 100644 addons/gdUnit4/src/spy/GdUnitSpyImpl.gd delete mode 100644 addons/gdUnit4/src/ui/EditorFileSystemControls.gd delete mode 100644 addons/gdUnit4/src/ui/GdUnitConsole.gd delete mode 100644 addons/gdUnit4/src/ui/GdUnitConsole.tscn delete mode 100644 addons/gdUnit4/src/ui/GdUnitFonts.gd delete mode 100644 addons/gdUnit4/src/ui/GdUnitInspector.gd delete mode 100644 addons/gdUnit4/src/ui/GdUnitInspector.tscn delete mode 100644 addons/gdUnit4/src/ui/ScriptEditorControls.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/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 delete mode 100644 addons/gdUnit4/src/ui/menu/EditorFileSystemContextMenuHandler.gd delete mode 100644 addons/gdUnit4/src/ui/menu/GdUnitContextMenuItem.gd delete mode 100644 addons/gdUnit4/src/ui/menu/ScriptEditorContextMenuHandler.gd delete mode 100644 addons/gdUnit4/src/ui/parts/InspectorMonitor.gd delete mode 100644 addons/gdUnit4/src/ui/parts/InspectorMonitor.tscn delete mode 100644 addons/gdUnit4/src/ui/parts/InspectorProgressBar.gd delete mode 100644 addons/gdUnit4/src/ui/parts/InspectorProgressBar.tscn delete mode 100644 addons/gdUnit4/src/ui/parts/InspectorStatusBar.gd delete mode 100644 addons/gdUnit4/src/ui/parts/InspectorStatusBar.tscn delete mode 100644 addons/gdUnit4/src/ui/parts/InspectorToolBar.gd delete mode 100644 addons/gdUnit4/src/ui/parts/InspectorToolBar.tscn delete mode 100644 addons/gdUnit4/src/ui/parts/InspectorTreeMainPanel.gd delete mode 100644 addons/gdUnit4/src/ui/parts/InspectorTreePanel.tscn delete mode 100644 addons/gdUnit4/src/ui/settings/GdUnitInputCapture.gd delete mode 100644 addons/gdUnit4/src/ui/settings/GdUnitInputCapture.tscn delete mode 100644 addons/gdUnit4/src/ui/settings/GdUnitSettingsDialog.gd delete mode 100644 addons/gdUnit4/src/ui/settings/GdUnitSettingsDialog.tscn delete mode 100644 addons/gdUnit4/src/ui/templates/TestSuiteTemplate.gd delete mode 100644 addons/gdUnit4/src/ui/templates/TestSuiteTemplate.tscn delete mode 100644 addons/gdUnit4/src/update/GdMarkDownReader.gd delete mode 100644 addons/gdUnit4/src/update/GdUnitPatch.gd delete mode 100644 addons/gdUnit4/src/update/GdUnitPatcher.gd delete mode 100644 addons/gdUnit4/src/update/GdUnitUpdate.gd delete mode 100644 addons/gdUnit4/src/update/GdUnitUpdate.tscn delete mode 100644 addons/gdUnit4/src/update/GdUnitUpdateClient.gd delete mode 100644 addons/gdUnit4/src/update/GdUnitUpdateNotify.gd delete mode 100644 addons/gdUnit4/src/update/GdUnitUpdateNotify.tscn delete mode 100644 addons/gdUnit4/src/update/assets/border_bottom.png delete mode 100644 addons/gdUnit4/src/update/assets/border_bottom.png.import delete mode 100644 addons/gdUnit4/src/update/assets/border_top.png delete mode 100644 addons/gdUnit4/src/update/assets/border_top.png.import delete mode 100644 addons/gdUnit4/src/update/assets/dot1.png delete mode 100644 addons/gdUnit4/src/update/assets/dot1.png.import delete mode 100644 addons/gdUnit4/src/update/assets/dot2.png delete mode 100644 addons/gdUnit4/src/update/assets/dot2.png.import delete mode 100644 addons/gdUnit4/src/update/assets/embedded.png delete mode 100644 addons/gdUnit4/src/update/assets/embedded.png.import delete mode 100644 addons/gdUnit4/src/update/assets/fonts/LICENSE.txt delete mode 100644 addons/gdUnit4/src/update/assets/fonts/README.txt delete mode 100644 addons/gdUnit4/src/update/assets/fonts/static/RobotoMono-Bold.ttf delete mode 100644 addons/gdUnit4/src/update/assets/fonts/static/RobotoMono-Bold.ttf.import delete mode 100644 addons/gdUnit4/src/update/assets/fonts/static/RobotoMono-BoldItalic.ttf delete mode 100644 addons/gdUnit4/src/update/assets/fonts/static/RobotoMono-BoldItalic.ttf.import delete mode 100644 addons/gdUnit4/src/update/assets/fonts/static/RobotoMono-ExtraLight.ttf delete mode 100644 addons/gdUnit4/src/update/assets/fonts/static/RobotoMono-ExtraLight.ttf.import delete mode 100644 addons/gdUnit4/src/update/assets/fonts/static/RobotoMono-ExtraLightItalic.ttf delete mode 100644 addons/gdUnit4/src/update/assets/fonts/static/RobotoMono-ExtraLightItalic.ttf.import delete mode 100644 addons/gdUnit4/src/update/assets/fonts/static/RobotoMono-Italic.ttf delete mode 100644 addons/gdUnit4/src/update/assets/fonts/static/RobotoMono-Italic.ttf.import delete mode 100644 addons/gdUnit4/src/update/assets/fonts/static/RobotoMono-Light.ttf delete mode 100644 addons/gdUnit4/src/update/assets/fonts/static/RobotoMono-Light.ttf.import delete mode 100644 addons/gdUnit4/src/update/assets/fonts/static/RobotoMono-LightItalic.ttf delete mode 100644 addons/gdUnit4/src/update/assets/fonts/static/RobotoMono-LightItalic.ttf.import delete mode 100644 addons/gdUnit4/src/update/assets/fonts/static/RobotoMono-Medium.ttf delete mode 100644 addons/gdUnit4/src/update/assets/fonts/static/RobotoMono-Medium.ttf.import delete mode 100644 addons/gdUnit4/src/update/assets/fonts/static/RobotoMono-MediumItalic.ttf delete mode 100644 addons/gdUnit4/src/update/assets/fonts/static/RobotoMono-MediumItalic.ttf.import delete mode 100644 addons/gdUnit4/src/update/assets/fonts/static/RobotoMono-Regular.ttf delete mode 100644 addons/gdUnit4/src/update/assets/fonts/static/RobotoMono-Regular.ttf.import delete mode 100644 addons/gdUnit4/src/update/assets/fonts/static/RobotoMono-SemiBold.ttf delete mode 100644 addons/gdUnit4/src/update/assets/fonts/static/RobotoMono-SemiBold.ttf.import delete mode 100644 addons/gdUnit4/src/update/assets/fonts/static/RobotoMono-SemiBoldItalic.ttf delete mode 100644 addons/gdUnit4/src/update/assets/fonts/static/RobotoMono-SemiBoldItalic.ttf.import delete mode 100644 addons/gdUnit4/src/update/assets/fonts/static/RobotoMono-Thin.ttf delete mode 100644 addons/gdUnit4/src/update/assets/fonts/static/RobotoMono-Thin.ttf.import delete mode 100644 addons/gdUnit4/src/update/assets/fonts/static/RobotoMono-ThinItalic.ttf delete mode 100644 addons/gdUnit4/src/update/assets/fonts/static/RobotoMono-ThinItalic.ttf.import delete mode 100644 addons/gdUnit4/src/update/assets/horizontal-line2.png delete mode 100644 addons/gdUnit4/src/update/assets/horizontal-line2.png.import delete mode 100644 addons/gdUnit4/src/update/assets/progress-background.png delete mode 100644 addons/gdUnit4/src/update/assets/progress-background.png.import delete mode 100644 addons/gdUnit4/test/GdUnitAwaiterTest.gd delete mode 100644 addons/gdUnit4/test/GdUnitScanTestSuitePerformanceTest.gd delete mode 100644 addons/gdUnit4/test/GdUnitScanTestSuiteTest.gd delete mode 100644 addons/gdUnit4/test/GdUnitScriptTypeTest.gd delete mode 100644 addons/gdUnit4/test/GdUnitTestCaseTimeoutTest.gd delete mode 100644 addons/gdUnit4/test/GdUnitTestCaseTimingTest.gd delete mode 100644 addons/gdUnit4/test/GdUnitTestResourceLoader.gd delete mode 100644 addons/gdUnit4/test/GdUnitTestResourceLoaderTest.gd delete mode 100644 addons/gdUnit4/test/GdUnitTestSuitePerformanceTest.gd delete mode 100644 addons/gdUnit4/test/GdUnitTestSuiteTest.gd delete mode 100644 addons/gdUnit4/test/asserts/CallBackValueProviderTest.gd delete mode 100644 addons/gdUnit4/test/asserts/GdUnitArrayAssertImplTest.gd delete mode 100644 addons/gdUnit4/test/asserts/GdUnitAssertImplTest.gd delete mode 100644 addons/gdUnit4/test/asserts/GdUnitBoolAssertImplTest.gd delete mode 100644 addons/gdUnit4/test/asserts/GdUnitDictionaryAssertImplTest.gd delete mode 100644 addons/gdUnit4/test/asserts/GdUnitFailureAssertImplTest.gd delete mode 100644 addons/gdUnit4/test/asserts/GdUnitFloatAssertImplTest.gd delete mode 100644 addons/gdUnit4/test/asserts/GdUnitFuncAssertImplTest.gd delete mode 100644 addons/gdUnit4/test/asserts/GdUnitGodotErrorAssertImplTest.gd delete mode 100644 addons/gdUnit4/test/asserts/GdUnitGodotErrorWithAssertTest.gd delete mode 100644 addons/gdUnit4/test/asserts/GdUnitIntAssertImplTest.gd delete mode 100644 addons/gdUnit4/test/asserts/GdUnitObjectAssertImplTest.gd delete mode 100644 addons/gdUnit4/test/asserts/GdUnitPackedArrayAssertTest.gd delete mode 100644 addons/gdUnit4/test/asserts/GdUnitResultAssertImplTest.gd delete mode 100644 addons/gdUnit4/test/asserts/GdUnitSignalAssertImplTest.gd delete mode 100644 addons/gdUnit4/test/asserts/GdUnitStringAssertImplTest.gd delete mode 100644 addons/gdUnit4/test/asserts/GdUnitVectorAssertImplTest.gd delete mode 100644 addons/gdUnit4/test/cmd/CmdArgumentParserTest.gd delete mode 100644 addons/gdUnit4/test/cmd/CmdCommandHandlerTest.gd delete mode 100644 addons/gdUnit4/test/cmd/CmdCommandTest.gd delete mode 100644 addons/gdUnit4/test/cmd/CmdConsoleTest.gd delete mode 100644 addons/gdUnit4/test/cmd/CmdOptionTest.gd delete mode 100644 addons/gdUnit4/test/cmd/CmdOptionsTest.gd delete mode 100644 addons/gdUnit4/test/core/ExampleTestSuite.cs delete mode 100644 addons/gdUnit4/test/core/GdArrayToolsTest.gd delete mode 100644 addons/gdUnit4/test/core/GdDiffToolTest.gd delete mode 100644 addons/gdUnit4/test/core/GdObjectsTest.gd delete mode 100644 addons/gdUnit4/test/core/GdUnit4VersionTest.gd delete mode 100644 addons/gdUnit4/test/core/GdUnitClassDoublerTest.gd delete mode 100644 addons/gdUnit4/test/core/GdUnitFileAccessTest.gd delete mode 100644 addons/gdUnit4/test/core/GdUnitObjectInteractionsTemplateTest.gd delete mode 100644 addons/gdUnit4/test/core/GdUnitResultTest.gd delete mode 100644 addons/gdUnit4/test/core/GdUnitRunnerConfigTest.gd delete mode 100644 addons/gdUnit4/test/core/GdUnitSceneRunnerInputEventTest.gd delete mode 100644 addons/gdUnit4/test/core/GdUnitSceneRunnerTest.gd delete mode 100644 addons/gdUnit4/test/core/GdUnitSettingsTest.gd delete mode 100644 addons/gdUnit4/test/core/GdUnitSignalAwaiterTest.gd delete mode 100644 addons/gdUnit4/test/core/GdUnitSingletonTest.gd delete mode 100644 addons/gdUnit4/test/core/GdUnitTestSuiteBuilderTest.gd delete mode 100644 addons/gdUnit4/test/core/GdUnitTestSuiteScannerTest.gd delete mode 100644 addons/gdUnit4/test/core/GdUnitToolsTest.gd delete mode 100644 addons/gdUnit4/test/core/LocalTimeTest.gd delete mode 100644 addons/gdUnit4/test/core/ParameterizedTestCaseTest.gd delete mode 100644 addons/gdUnit4/test/core/command/GdUnitCommandHandlerTest.gd delete mode 100644 addons/gdUnit4/test/core/event/GdUnitEventTest.gd delete mode 100644 addons/gdUnit4/test/core/event/GdUnitTestEventSerdeTest.gd delete mode 100644 addons/gdUnit4/test/core/execution/GdUnitExecutionContextTest.gd delete mode 100644 addons/gdUnit4/test/core/execution/GdUnitTestSuiteExecutorTest.gd delete mode 100644 addons/gdUnit4/test/core/parse/GdDefaultValueDecoderTest.gd delete mode 100644 addons/gdUnit4/test/core/parse/GdFunctionArgumentTest.gd delete mode 100644 addons/gdUnit4/test/core/parse/GdFunctionDescriptorTest.gd delete mode 100644 addons/gdUnit4/test/core/parse/GdScriptParserTest.gd delete mode 100644 addons/gdUnit4/test/core/parse/GdUnitExpressionRunnerTest.gd delete mode 100644 addons/gdUnit4/test/core/parse/GdUnitTestParameterSetResolverTest.gd delete mode 100644 addons/gdUnit4/test/core/resources/Area4D.txt delete mode 100644 addons/gdUnit4/test/core/resources/AtmosphereData.txt delete mode 100644 addons/gdUnit4/test/core/resources/GdUnitRunner_old_format.cfg delete mode 100644 addons/gdUnit4/test/core/resources/SoundData.txt delete mode 100644 addons/gdUnit4/test/core/resources/copy_test/folder_a/file_a.txt delete mode 100644 addons/gdUnit4/test/core/resources/copy_test/folder_a/file_b.txt delete mode 100644 addons/gdUnit4/test/core/resources/copy_test/folder_b/file_a.txt delete mode 100644 addons/gdUnit4/test/core/resources/copy_test/folder_b/file_b.txt delete mode 100644 addons/gdUnit4/test/core/resources/copy_test/folder_b/folder_ba/file_x.txt delete mode 100644 addons/gdUnit4/test/core/resources/copy_test/folder_c/file_z.txt delete mode 100644 addons/gdUnit4/test/core/resources/naming_conventions/PascalCaseWithClassName.gd delete mode 100644 addons/gdUnit4/test/core/resources/naming_conventions/PascalCaseWithoutClassName.gd delete mode 100644 addons/gdUnit4/test/core/resources/naming_conventions/snake_case_with_class_name.gd delete mode 100644 addons/gdUnit4/test/core/resources/naming_conventions/snake_case_without_class_name.gd delete mode 100644 addons/gdUnit4/test/core/resources/scan_testsuite_inheritance/by_class_name/BaseTest.gd delete mode 100644 addons/gdUnit4/test/core/resources/scan_testsuite_inheritance/by_class_name/ExtendedTest.gd delete mode 100644 addons/gdUnit4/test/core/resources/scan_testsuite_inheritance/by_class_name/ExtendsExtendedTest.gd delete mode 100644 addons/gdUnit4/test/core/resources/scan_testsuite_inheritance/by_class_path/BaseTest.gd delete mode 100644 addons/gdUnit4/test/core/resources/scan_testsuite_inheritance/by_class_path/ExtendedTest.gd delete mode 100644 addons/gdUnit4/test/core/resources/scan_testsuite_inheritance/by_class_path/ExtendsExtendedTest.gd delete mode 100644 addons/gdUnit4/test/core/resources/scenes/drag_and_drop/DragAndDropControl.gd delete mode 100644 addons/gdUnit4/test/core/resources/scenes/drag_and_drop/DragAndDropControl.tscn delete mode 100644 addons/gdUnit4/test/core/resources/scenes/drag_and_drop/DragAndDropTestScene.gd delete mode 100644 addons/gdUnit4/test/core/resources/scenes/drag_and_drop/DragAndDropTestScene.tscn delete mode 100644 addons/gdUnit4/test/core/resources/scenes/drag_and_drop/icon.png delete mode 100644 addons/gdUnit4/test/core/resources/scenes/drag_and_drop/icon.png.import delete mode 100644 addons/gdUnit4/test/core/resources/scenes/input_actions/InputEventTestScene.gd delete mode 100644 addons/gdUnit4/test/core/resources/scenes/input_actions/InputEventTestScene.tscn delete mode 100644 addons/gdUnit4/test/core/resources/scenes/simple_scene.gd delete mode 100644 addons/gdUnit4/test/core/resources/scenes/simple_scene.tscn delete mode 100644 addons/gdUnit4/test/core/resources/script_with_class_name.gd delete mode 100644 addons/gdUnit4/test/core/resources/script_without_class_name.gd delete mode 100644 addons/gdUnit4/test/core/resources/sources/test_person.gd delete mode 100644 addons/gdUnit4/test/core/resources/test_script_with_arguments.gd delete mode 100644 addons/gdUnit4/test/core/resources/testsuites/GdUnitFuzzerTest.resource delete mode 100644 addons/gdUnit4/test/core/resources/testsuites/NotATestSuite.cs delete mode 100644 addons/gdUnit4/test/core/resources/testsuites/TestCaseSkipped.resource delete mode 100644 addons/gdUnit4/test/core/resources/testsuites/TestSuiteAllStagesSuccess.resource delete mode 100644 addons/gdUnit4/test/core/resources/testsuites/TestSuiteErrorOnTestTimeout.resource delete mode 100644 addons/gdUnit4/test/core/resources/testsuites/TestSuiteFailAddChildStageBefore.resource delete mode 100644 addons/gdUnit4/test/core/resources/testsuites/TestSuiteFailAndOrpahnsDetected.resource delete mode 100644 addons/gdUnit4/test/core/resources/testsuites/TestSuiteFailOnMultipeStages.resource delete mode 100644 addons/gdUnit4/test/core/resources/testsuites/TestSuiteFailOnStageAfter.resource delete mode 100644 addons/gdUnit4/test/core/resources/testsuites/TestSuiteFailOnStageAfterTest.resource delete mode 100644 addons/gdUnit4/test/core/resources/testsuites/TestSuiteFailOnStageBefore.resource delete mode 100644 addons/gdUnit4/test/core/resources/testsuites/TestSuiteFailOnStageBeforeTest.resource delete mode 100644 addons/gdUnit4/test/core/resources/testsuites/TestSuiteFailOnStageTestCase1.resource delete mode 100644 addons/gdUnit4/test/core/resources/testsuites/TestSuiteFuzzedMetricsTest.resource delete mode 100644 addons/gdUnit4/test/core/resources/testsuites/TestSuiteInvalidParameterizedTests.resource delete mode 100644 addons/gdUnit4/test/core/resources/testsuites/TestSuiteParameterizedMetricsTest.resource delete mode 100644 addons/gdUnit4/test/core/resources/testsuites/TestSuiteParameterizedTests.resource delete mode 100644 addons/gdUnit4/test/core/resources/testsuites/TestSuiteSkipped.resource delete mode 100644 addons/gdUnit4/test/core/resources/testsuites/TestSuiteWithoutTests.gd delete mode 100644 addons/gdUnit4/test/core/templates/test_suite/GdUnitTestSuiteTemplateTest.gd delete mode 100644 addons/gdUnit4/test/extractors/GdUnitFuncValueExtractorTest.gd delete mode 100644 addons/gdUnit4/test/fuzzers/GdUnitFuzzerValueInjectionTest.gd delete mode 100644 addons/gdUnit4/test/fuzzers/StringFuzzerTest.gd delete mode 100644 addons/gdUnit4/test/fuzzers/TestExternalFuzzer.gd delete mode 100644 addons/gdUnit4/test/fuzzers/TestFuzzers.gd delete mode 100644 addons/gdUnit4/test/matchers/AnyArgumentMatcherTest.gd delete mode 100644 addons/gdUnit4/test/matchers/AnyBuildInTypeArgumentMatcherTest.gd delete mode 100644 addons/gdUnit4/test/matchers/AnyClazzArgumentMatcherTest.gd delete mode 100644 addons/gdUnit4/test/matchers/ChainedArgumentMatcherTest.gd delete mode 100644 addons/gdUnit4/test/matchers/CustomArgumentMatcherTest.gd delete mode 100644 addons/gdUnit4/test/matchers/GdUnitArgumentMatchersTest.gd delete mode 100644 addons/gdUnit4/test/matchers/resources/CustomArgumentMatcherTestClass.gd delete mode 100644 addons/gdUnit4/test/mocker/CustomEnums.gd delete mode 100644 addons/gdUnit4/test/mocker/GdUnitMockBuilderTest.gd delete mode 100644 addons/gdUnit4/test/mocker/GdUnitMockerTest.gd delete mode 100644 addons/gdUnit4/test/mocker/GodotClassNameFuzzer.gd delete mode 100644 addons/gdUnit4/test/mocker/resources/AdvancedTestClass.gd delete mode 100644 addons/gdUnit4/test/mocker/resources/ClassWithCustomClassName.gd delete mode 100644 addons/gdUnit4/test/mocker/resources/ClassWithCustomFormattings.gd delete mode 100644 addons/gdUnit4/test/mocker/resources/ClassWithDefaultBuildIntTypes.gd delete mode 100644 addons/gdUnit4/test/mocker/resources/ClassWithEnumReturnTypes.gd delete mode 100644 addons/gdUnit4/test/mocker/resources/ClassWithNameA.gd delete mode 100644 addons/gdUnit4/test/mocker/resources/ClassWithNameB.gd delete mode 100644 addons/gdUnit4/test/mocker/resources/ClassWithOverridenVirtuals.gd delete mode 100644 addons/gdUnit4/test/mocker/resources/ClassWithPoolStringArrayFunc.gd delete mode 100644 addons/gdUnit4/test/mocker/resources/ClassWithVariables.gd delete mode 100644 addons/gdUnit4/test/mocker/resources/ClassWithoutNameA.gd delete mode 100644 addons/gdUnit4/test/mocker/resources/ClassWithoutNameAndNotExtends.gd delete mode 100644 addons/gdUnit4/test/mocker/resources/CustomClassExtendsCustomClass.gd delete mode 100644 addons/gdUnit4/test/mocker/resources/CustomNodeTestClass.gd delete mode 100644 addons/gdUnit4/test/mocker/resources/CustomResourceTestClass.gd delete mode 100644 addons/gdUnit4/test/mocker/resources/DeepStubTestClass.gd delete mode 100644 addons/gdUnit4/test/mocker/resources/GD-256/world.gd delete mode 100644 addons/gdUnit4/test/mocker/resources/OverridenGetClassTestClass.gd delete mode 100644 addons/gdUnit4/test/mocker/resources/TestPersion.gd delete mode 100644 addons/gdUnit4/test/mocker/resources/capsuleshape2d.tres delete mode 100644 addons/gdUnit4/test/mocker/resources/scenes/Spell.gd delete mode 100644 addons/gdUnit4/test/mocker/resources/scenes/TestScene.gd delete mode 100644 addons/gdUnit4/test/mocker/resources/scenes/TestScene.tscn delete mode 100644 addons/gdUnit4/test/mocker/resources/scenes/TestSceneWithoutScript.tscn delete mode 100644 addons/gdUnit4/test/mocker/resources/snake_case.gd delete mode 100644 addons/gdUnit4/test/mocker/resources/snake_case_class_name.gd delete mode 100644 addons/gdUnit4/test/monitor/ErrorLogEntryTest.gd delete mode 100644 addons/gdUnit4/test/monitor/GodotGdErrorMonitorTest.gd delete mode 100644 addons/gdUnit4/test/mono/ExampleTestSuite.cs delete mode 100644 addons/gdUnit4/test/mono/GdUnit4CSharpApiLoaderTest.gd delete mode 100644 addons/gdUnit4/test/mono/GdUnit4CSharpApiTest.cs delete mode 100644 addons/gdUnit4/test/network/GdUnitTcpServerTest.gd delete mode 100644 addons/gdUnit4/test/report/JUnitXmlReportTest.gd delete mode 100644 addons/gdUnit4/test/report/XmlElementTest.gd delete mode 100644 addons/gdUnit4/test/resources/core/City.gd delete mode 100644 addons/gdUnit4/test/resources/core/CustomClass.gd delete mode 100644 addons/gdUnit4/test/resources/core/GeneratedPersonTest.gd delete mode 100644 addons/gdUnit4/test/resources/core/Person.gd delete mode 100644 addons/gdUnit4/test/resources/core/Udo.gd delete mode 100644 addons/gdUnit4/test/resources/core/sources/TestPerson.cs delete mode 100644 addons/gdUnit4/test/resources/issues/gd-166/issue.gd delete mode 100644 addons/gdUnit4/test/resources/issues/gd-166/types.gd delete mode 100644 addons/gdUnit4/test/spy/GdUnitSpyBuilderTest.gd delete mode 100644 addons/gdUnit4/test/spy/GdUnitSpyTest.gd delete mode 100644 addons/gdUnit4/test/ui/GdUnitFontsTest.gd delete mode 100644 addons/gdUnit4/test/ui/parts/InspectorProgressBarTest.gd delete mode 100644 addons/gdUnit4/test/ui/parts/InspectorTreeMainPanelPerformanceTest.gd delete mode 100644 addons/gdUnit4/test/ui/parts/InspectorTreeMainPanelTest.gd delete mode 100644 addons/gdUnit4/test/ui/parts/resources/bar/ExampleTestSuiteA.resource delete mode 100644 addons/gdUnit4/test/ui/parts/resources/foo/ExampleTestSuiteA.resource delete mode 100644 addons/gdUnit4/test/ui/parts/resources/foo/ExampleTestSuiteB.resource delete mode 100644 addons/gdUnit4/test/ui/parts/resources/foo/ExampleTestSuiteC.resource delete mode 100644 addons/gdUnit4/test/ui/templates/TestSuiteTemplateTest.gd delete mode 100644 addons/gdUnit4/test/update/GdMarkDownReaderTest.gd delete mode 100644 addons/gdUnit4/test/update/GdUnitPatcherTest.gd delete mode 100644 addons/gdUnit4/test/update/GdUnitUpdateTest.gd delete mode 100644 addons/gdUnit4/test/update/bbcodeView.gd delete mode 100644 addons/gdUnit4/test/update/bbcodeView.tscn delete mode 100644 addons/gdUnit4/test/update/resources/bbcode_example.txt delete mode 100644 addons/gdUnit4/test/update/resources/bbcode_header.txt delete mode 100644 addons/gdUnit4/test/update/resources/bbcode_md_header.txt delete mode 100644 addons/gdUnit4/test/update/resources/bbcode_table.txt delete mode 100644 addons/gdUnit4/test/update/resources/html_header.txt delete mode 100644 addons/gdUnit4/test/update/resources/icon48.png delete mode 100644 addons/gdUnit4/test/update/resources/icon48.png.import delete mode 100644 addons/gdUnit4/test/update/resources/markdown.txt delete mode 100644 addons/gdUnit4/test/update/resources/markdown_example.txt delete mode 100644 addons/gdUnit4/test/update/resources/markdown_table.txt delete mode 100644 addons/gdUnit4/test/update/resources/md_header.txt delete mode 100644 addons/gdUnit4/test/update/resources/patches/v0.9.5/patch_y.gd delete mode 100644 addons/gdUnit4/test/update/resources/patches/v0.9.6/patch_x.gd delete mode 100644 addons/gdUnit4/test/update/resources/patches/v0.9.9/patch_a.gd delete mode 100644 addons/gdUnit4/test/update/resources/patches/v0.9.9/patch_b.gd delete mode 100644 addons/gdUnit4/test/update/resources/patches/v1.1.4/patch_a.gd delete mode 100644 addons/gdUnit4/test/update/resources/update.zip create mode 100644 resources/icons/gameicon.png rename addons/gdUnit4/src/ui/assets/icon.png.import => resources/icons/gameicon.png.import (68%) diff --git a/addons/gdUnit4/LICENSE b/addons/gdUnit4/LICENSE deleted file mode 100644 index 8c60d13..0000000 --- a/addons/gdUnit4/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2023 Mike Schulze - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/addons/gdUnit4/bin/GdUnitBuildTool.gd b/addons/gdUnit4/bin/GdUnitBuildTool.gd deleted file mode 100644 index 7070992..0000000 --- a/addons/gdUnit4/bin/GdUnitBuildTool.gd +++ /dev/null @@ -1,110 +0,0 @@ -#!/usr/bin/env -S godot -s -extends SceneTree - -enum { - INIT, - PROCESSING, - EXIT -} - -const RETURN_SUCCESS = 0 -const RETURN_ERROR = 100 -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 - ) -]) - -var _status := INIT -var _source_file :String = "" -var _source_line :int = -1 - - -func _init(): - 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() - for cmd in cmd_options: - if cmd.name() == '-scp': - _source_file = cmd.arguments()[0] - _source_file = ProjectSettings.localize_path(ProjectSettings.localize_path(_source_file)) - if cmd.name() == '-scl': - _source_line = int(cmd.arguments()[0]) - # verify required arguments - if _source_file == "": - exit(RETURN_ERROR, "missing required argument -scp ") - return - if _source_line == -1: - exit(RETURN_ERROR, "missing required argument -scl ") - return - _status = PROCESSING - - -func _idle(_delta): - 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) - - -func exit(code :int, message :String = "") -> void: - _status = EXIT - if code == RETURN_ERROR: - if not message.is_empty(): - _console.prints_error(message) - _console.prints_error("Abnormal exit with %d" % code) - else: - _console.prints_color("Exit code: %d" % RETURN_SUCCESS, Color.DARK_SALMON) - quit(code) - - -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] - prints(json) - - -func print_json_error(error :String) -> void: - prints('JSON_RESULT:{"Error" : "%s"}' % error) - - -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() - for option in _cmd_options.default_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) - if not cmd_option.help().is_empty(): - _console.prints_color("%-4s %s" % ["", cmd_option.help()], Color.DARK_TURQUOISE) - _console.new_line() diff --git a/addons/gdUnit4/bin/GdUnitCmdTool.gd b/addons/gdUnit4/bin/GdUnitCmdTool.gd deleted file mode 100644 index 677ed99..0000000 --- a/addons/gdUnit4/bin/GdUnitCmdTool.gd +++ /dev/null @@ -1,600 +0,0 @@ -#!/usr/bin/env -S godot -s -extends SceneTree - -const GdUnitTools := preload("res://addons/gdUnit4/src/core/GdUnitTools.gd") - - -#warning-ignore-all:return_value_discarded -class CLIRunner: - extends Node - - enum { - READY, - INIT, - RUN, - STOP, - EXIT - } - - const DEFAULT_REPORT_COUNT = 20 - 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 - var _cs_executor - 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 _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.""".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 %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( - "--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(): - _state = INIT - _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 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 _notification(what: int) -> void: - if what == NOTIFICATION_PREDELETE: - prints("Finallize .. done") - - - func _process(_delta :float) -> void: - match _state: - INIT: - init_gd_unit() - _state = RUN - RUN: - # all test suites executed - if _test_suites_to_process.is_empty(): - _state = STOP - else: - set_process(false) - # process next test suite - var test_suite := _test_suites_to_process.pop_front() as Node - if _cs_executor != null and _cs_executor.IsExecutable(test_suite): - _cs_executor.Execute(test_suite) - await _cs_executor.ExecutionCompleted - else: - await _executor.execute(test_suite) - set_process(true) - STOP: - _state = EXIT - _on_gdunit_event(GdUnitStop.new()) - quit(report_exit_code(_report)) - - - 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(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] - ) - _report_max = DEFAULT_REPORT_COUNT - else: - _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 - ) - _executor.fail_fast(false) - - - func run_self_test() -> void: - _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 - ) - quit(RETURN_SUCCESS) - - - 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() - 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 - ) - if not cmd_option.help().is_empty(): - _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 - ) - _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 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(): - show_options() - _console.prints_error(result.error_message()) - _console.prints_error("Abnormal exit with %d" % RETURN_ERROR) - _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", 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(result.value()) - ) - 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) - _state = STOP - quit(RETURN_SUCCESS) - 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] = [] - var to_execute := config.to_execute() - # scan for the requested test suites - var ts_scanner := GdUnitTestSuiteScanner.new() - for as_resource_path in to_execute.keys(): - 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: - if test_case_names.is_empty(): - return - for test_suite in test_suites: - for test_case in test_suite.get_children(): - if not test_case_names.has(test_case.get_name()): - test_suite.remove_child(test_case) - test_case.free() - - - func skip_suites(test_suites: Array, config: GdUnitRunnerConfig) -> void: - var skipped := config.skipped() - for test_suite in test_suites: - 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 - 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 - ) - 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 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) - else: - # skip tests - for test_to_skip in skipped_tests: - 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]) - 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(test_suites: Array) -> int: - var total: int = 0 - for test_suite in test_suites: - total += (test_suite as Node).get_child_count() - return total - - - # gdlint: disable=function-name - func PublishEvent(data: Dictionary) -> void: - _on_gdunit_event(GdUnitEvent.new().deserialize(data)) - - - func _on_gdunit_event(event: GdUnitEvent): - 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 - ).prints_color( - "Total test cases: %s" % _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()) - ) - GdUnitEvent.TESTSUITE_AFTER: - _report.update_test_suite_report( - event.resource_path(), - event.elapsed_time(), - event.is_error(), - event.is_failed(), - event.is_warning(), - event.is_skipped(), - event.skipped_count(), - event.failed_count(), - event.orphan_nodes(), - event.reports() - ) - GdUnitEvent.TESTCASE_BEFORE: - _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(), - event.suite_name(), - event.test_name(), - event.is_error(), - event.is_failed(), - event.failed_count(), - event.orphan_nodes(), - event.is_skipped(), - event.reports(), - event.elapsed_time() - ) - _report.update_testcase_report(event.resource_path(), test_report) - print_status(event) - - - 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) - return RETURN_SUCCESS - - - 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 - ) - 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 - ).save_cursor() - GdUnitEvent.TESTCASE_AFTER: - #_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( - "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) -> 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(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: - if event.is_skipped(): - _console.print_color("SKIPPED", Color.GOLDENROD, CmdConsole.BOLD | CmdConsole.ITALIC) - elif event.is_failed() or event.is_error(): - _console.print_color("FAILED", Color.FIREBRICK, CmdConsole.BOLD) - elif event.orphan_nodes() > 0: - _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 - ) - - -var _cli_runner: CLIRunner - - -func _initialize(): - if Engine.get_version_info().hex < 0x40100: - prints("GdUnit4 requires a minimum of Godot 4.1.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) - - -# do not use print statements on _finalize it results in random crashes -#func _finalize(): -# 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 deleted file mode 100644 index f7bb475..0000000 --- a/addons/gdUnit4/bin/GdUnitCopyLog.gd +++ /dev/null @@ -1,141 +0,0 @@ -#!/usr/bin/env -S godot -s -extends MainLoop - -const GdUnitTools := preload("res://addons/gdUnit4/src/core/GdUnitTools.gd") - -# gdlint: disable=max-line-length -const NO_LOG_TEMPLATE = """ - - - - - - Logging - - - -
-

No logging available!

-
-

For logging to occur, you must check Enable File Logging in Project Settings.

-

You can enable Logging Project Settings > Logging > File Logging > Enable File Logging in the Project Settings.

-
- -""" - -#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 _report_root_path: String - - -func _init(): - _report_root_path = GdUnitFileAccess.current_dir() + "reports" - - -func _process(_delta): - # check if reports exists - if not reports_available(): - prints("no reports found") - return true - # scan for latest report path - 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, - 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", 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: - _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 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: - 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" - % 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() - 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 := 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, 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, "") - 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, 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 deleted file mode 100644 index c2fc0fd..0000000 --- a/addons/gdUnit4/bin/ProjectScanner.gd +++ /dev/null @@ -1,99 +0,0 @@ -#!/usr/bin/env -S godot -s -@tool -extends SceneTree - -const CmdConsole = preload("res://addons/gdUnit4/src/cmd/CmdConsole.gd") - - -func _initialize(): - set_auto_accept_quit(false) - var scanner := SourceScanner.new(self) - root.add_child(scanner) - - -# gdlint: disable=trailing-whitespace -class SourceScanner extends Node: - - enum { - INIT, - STARTUP, - SCAN, - QUIT, - DONE - } - - var _state = INIT - var _console := CmdConsole.new() - 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 - _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) - _console.progressBar(0) - await get_tree().process_frame - _fs.scan() - while _fs.is_scanning(): - await get_tree().process_frame - _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 - 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 deleted file mode 100644 index fce580f..0000000 --- a/addons/gdUnit4/plugin.cfg +++ /dev/null @@ -1,7 +0,0 @@ -[plugin] - -name="gdUnit4" -description="Unit Testing Framework for Godot Scripts" -author="Mike Schulze" -version="4.2.4" -script="plugin.gd" diff --git a/addons/gdUnit4/plugin.gd b/addons/gdUnit4/plugin.gd deleted file mode 100644 index 8aedfc0..0000000 --- a/addons/gdUnit4/plugin.gd +++ /dev/null @@ -1,47 +0,0 @@ -@tool -extends EditorPlugin - -const GdUnitTools := preload("res://addons/gdUnit4/src/core/GdUnitTools.gd") - -var _gd_inspector :Node -var _server_node :Node -var _gd_console :Node - - -func _enter_tree() -> void: - if Engine.get_version_info().hex < 0x40100: - prints("GdUnit4 plugin requires a minimum of Godot 4.1.x Version!") - return - Engine.set_meta("GdUnitEditorPlugin", self) - GdUnitSettings.setup() - # install the GdUnit inspector - _gd_inspector = load("res://addons/gdUnit4/src/ui/GdUnitInspector.tscn").instantiate() - add_control_to_dock(EditorPlugin.DOCK_SLOT_LEFT_UR, _gd_inspector) - # 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 :Node = load("res://addons/gdUnit4/src/update/GdUnitUpdateNotify.tscn").instantiate() - Engine.get_main_loop().root.call_deferred("add_child", update_tool) - if GdUnit4CSharpApiLoader.is_mono_supported(): - prints("GdUnit4Net version '%s' loaded." % GdUnit4CSharpApiLoader.version()) - - -func _exit_tree() -> void: - 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") diff --git a/addons/gdUnit4/runtest.cmd b/addons/gdUnit4/runtest.cmd deleted file mode 100644 index 0c8fbc5..0000000 --- a/addons/gdUnit4/runtest.cmd +++ /dev/null @@ -1,25 +0,0 @@ -@ECHO OFF -CLS - -IF NOT DEFINED GODOT_BIN ( - ECHO "GODOT_BIN is not set." - ECHO "Please set the environment variable 'setx GODOT_BIN '" - EXIT /b -1 -) - -REM scan if Godot mono used and compile c# classes -for /f "tokens=5 delims=. " %%i in ('%GODOT_BIN% --version') do set GODOT_TYPE=%%i -IF "%GODOT_TYPE%" == "mono" ( - ECHO "Godot mono detected" - ECHO Compiling c# classes ... Please Wait - dotnet build --debug - ECHO done %errorlevel% -) - -%GODOT_BIN% -s -d res://addons/gdUnit4/bin/GdUnitCmdTool.gd %* -SET exit_code=%errorlevel% -%GODOT_BIN% --headless --quiet -s -d res://addons/gdUnit4/bin/GdUnitCopyLog.gd %* - -ECHO %exit_code% - -EXIT /B %exit_code% diff --git a/addons/gdUnit4/runtest.sh b/addons/gdUnit4/runtest.sh deleted file mode 100644 index 83ed272..0000000 --- a/addons/gdUnit4/runtest.sh +++ /dev/null @@ -1,19 +0,0 @@ -#!/bin/sh - -if [ -z "$GODOT_BIN" ]; then - GODOT_BIN=$(which godot) -fi - -if [ -z "$GODOT_BIN" ]; then - echo "'GODOT_BIN' is not set." - echo "Please set the environment variable 'export GODOT_BIN=/Applications/Godot.app/Contents/MacOS/Godot'" - exit 1 -fi - -"$GODOT_BIN" --path . -s -d res://addons/gdUnit4/bin/GdUnitCmdTool.gd $* -exit_code=$? -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 deleted file mode 100644 index 096088a..0000000 --- a/addons/gdUnit4/src/Comparator.gd +++ /dev/null @@ -1,12 +0,0 @@ -class_name Comparator -extends Resource - -enum { - EQUAL, - LESS_THAN, - LESS_EQUAL, - GREATER_THAN, - GREATER_EQUAL, - BETWEEN_EQUAL, - NOT_BETWEEN_EQUAL, -} diff --git a/addons/gdUnit4/src/Fuzzers.gd b/addons/gdUnit4/src/Fuzzers.gd deleted file mode 100644 index 8affdbf..0000000 --- a/addons/gdUnit4/src/Fuzzers.gd +++ /dev/null @@ -1,34 +0,0 @@ -## A fuzzer implementation to provide default implementation -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: - return StringFuzzer.new(min_length, max_length, charset) - - -## 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 -static func rangev2(from: Vector2, to: Vector2) -> Fuzzer: - return Vector2Fuzzer.new(from, to) - - -## Generates an random Vector3 in a range form to -static func rangev3(from: Vector3, to: Vector3) -> Fuzzer: - return Vector3Fuzzer.new(from, to) - -## Generates an integer in a range form to that can be divided exactly by 2 -static func eveni(from: int, to: int) -> Fuzzer: - return IntFuzzer.new(from, to, IntFuzzer.EVEN) - -## Generates an integer in a range form to that cannot be divided exactly by 2 -static func oddi(from: int, to: int) -> Fuzzer: - return IntFuzzer.new(from, to, IntFuzzer.ODD) diff --git a/addons/gdUnit4/src/GdUnitArrayAssert.gd b/addons/gdUnit4/src/GdUnitArrayAssert.gd deleted file mode 100644 index 31cb088..0000000 --- a/addons/gdUnit4/src/GdUnitArrayAssert.gd +++ /dev/null @@ -1,160 +0,0 @@ -## An Assertion Tool to verify array values -class_name GdUnitArrayAssert -extends GdUnitAssert - - -## Verifies that the current value is null. -func is_null() -> GdUnitArrayAssert: - return self - - -## Verifies that the current value is not null. -func is_not_null() -> GdUnitArrayAssert: - return self - - -## Verifies that the current Array is equal to the given one. -@warning_ignore("unused_parameter") -func is_equal(expected) -> 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: - return self - - -## Verifies that the current Array is not equal to the given one. -@warning_ignore("unused_parameter") -func is_not_equal(expected) -> 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: - return self - - -## Verifies that the current Array is empty, it has a size of 0. -func is_empty() -> GdUnitArrayAssert: - return self - - -## Verifies that the current Array is not empty, it has a size of minimum 1. -func is_not_empty() -> GdUnitArrayAssert: - return self - -## 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: - 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: - return self - - -## Verifies that the current Array has a size of given value. -@warning_ignore("unused_parameter") -func has_size(expectd: int) -> GdUnitArrayAssert: - return self - - -## 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: - 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: - 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: - 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: - 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: - 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: - return self - - -## Verifies that the current Array do NOT 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 not_contains_same] -## [b]Example:[/b] -## [codeblock] -## # will succeed -## assert_array([1, 2, 3, 4, 5]).not_contains([6]) -## # will fail -## assert_array([1, 2, 3, 4, 5]).not_contains([2, 6]) -## [/codeblock] -@warning_ignore("unused_parameter") -func not_contains(expected) -> GdUnitArrayAssert: - return self - - -## Verifies that the current Array do NOT contains the given values, in any order.[br] -## The values are compared by object reference, for deep parameter comparision use [method not_contains] -## [b]Example:[/b] -## [codeblock] -## # will succeed -## assert_array([1, 2, 3, 4, 5]).not_contains([6]) -## # will fail -## assert_array([1, 2, 3, 4, 5]).not_contains([2, 6]) -## [/codeblock] -@warning_ignore("unused_parameter") -func not_contains_same(expected) -> GdUnitArrayAssert: - return self - - -## Extracts all values by given function name and optional arguments into a new ArrayAssert. -## If the elements not accessible by `func_name` the value is converted to `"n.a"`, expecting null values -@warning_ignore("unused_parameter") -func extract(func_name: String, args := Array()) -> GdUnitArrayAssert: - return self - - -## Extracts all values by given extractor's into a new ArrayAssert. -## If the elements not extractable than the value is converted to `"n.a"`, expecting null values -@warning_ignore("unused_parameter") -func extractv( - extractor0 :GdUnitValueExtractor, - extractor1 :GdUnitValueExtractor = null, - extractor2 :GdUnitValueExtractor = null, - extractor3 :GdUnitValueExtractor = null, - extractor4 :GdUnitValueExtractor = null, - extractor5 :GdUnitValueExtractor = null, - extractor6 :GdUnitValueExtractor = null, - extractor7 :GdUnitValueExtractor = null, - extractor8 :GdUnitValueExtractor = null, - extractor9 :GdUnitValueExtractor = null) -> GdUnitArrayAssert: - return self diff --git a/addons/gdUnit4/src/GdUnitAssert.gd b/addons/gdUnit4/src/GdUnitAssert.gd deleted file mode 100644 index 1674d26..0000000 --- a/addons/gdUnit4/src/GdUnitAssert.gd +++ /dev/null @@ -1,35 +0,0 @@ -## Base interface of all GdUnit asserts -class_name GdUnitAssert -extends RefCounted - - -## Verifies that the current value is null. -func is_null(): - return self - - -## Verifies that the current value is not null. -func is_not_null(): - return self - - -## Verifies that the current value is equal to expected one. -@warning_ignore("unused_parameter") -func is_equal(expected): - return self - - -## Verifies that the current value is not equal to expected one. -@warning_ignore("unused_parameter") -func is_not_equal(expected): - return self - - -func test_fail(): - return self - - -## Overrides the default failure message by given custom message. -@warning_ignore("unused_parameter") -func override_failure_message(message :String): - return self diff --git a/addons/gdUnit4/src/GdUnitAwaiter.gd b/addons/gdUnit4/src/GdUnitAwaiter.gd deleted file mode 100644 index a9b5bb1..0000000 --- a/addons/gdUnit4/src/GdUnitAwaiter.gd +++ /dev/null @@ -1,69 +0,0 @@ -class_name GdUnitAwaiter -extends RefCounted - -const GdUnitAssertImpl = preload("res://addons/gdUnit4/src/asserts/GdUnitAssertImpl.gd") - - -# Waits for a specified signal in an interval of 50ms sent from the , and terminates with an error after the specified timeout has elapsed. -# source: the object from which the signal is emitted -# signal_name: signal name -# args: the expected signal arguments as an array -# 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 assert_that := GdUnitAssertImpl.new(signal_name) - var line_number := GdUnitAssertions.get_line_number() - if not is_instance_valid(source): - 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): - 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] - assert_that.report_error(failure, line_number) - return value - - -# Waits for a specified signal sent from the between idle frames and aborts with an error after the specified timeout has elapsed -# source: the object from which the signal is emitted -# signal_name: signal name -# 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 := GdUnitAssertions.get_line_number() - # 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) - return await await_idle_frame() - 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] - GdUnitAssertImpl.new(signal_name).report_error(failure, line_number) - return value - - -# Waits for for a given amount of milliseconds -# example: -# # waits for 100ms -# await GdUnitAwaiter.await_millis(myNode, 100).completed -# use this waiter and not `await get_tree().create_timer().timeout to prevent errors when a test case is timed out -func await_millis(milliSec :int) -> void: - var timer :Timer = Timer.new() - timer.set_name("gdunit_await_millis_timer_%d" % timer.get_instance_id()) - Engine.get_main_loop().root.add_child(timer) - timer.add_to_group("GdUnitTimers") - timer.set_one_shot(true) - timer.start(milliSec / 1000.0) - await timer.timeout - timer.queue_free() - - -# Waits until the next idle frame -func await_idle_frame() -> void: - await Engine.get_main_loop().process_frame diff --git a/addons/gdUnit4/src/GdUnitBoolAssert.gd b/addons/gdUnit4/src/GdUnitBoolAssert.gd deleted file mode 100644 index 7e62dbe..0000000 --- a/addons/gdUnit4/src/GdUnitBoolAssert.gd +++ /dev/null @@ -1,41 +0,0 @@ -## An Assertion Tool to verify boolean values -class_name GdUnitBoolAssert -extends GdUnitAssert - - -## Verifies that the current value is null. -func is_null() -> GdUnitBoolAssert: - return self - - -## Verifies that the current value is not null. -func is_not_null() -> GdUnitBoolAssert: - return self - - -## Verifies that the current value is equal to the given one. -@warning_ignore("unused_parameter") -func is_equal(expected) -> 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: - return self - - -## Verifies that the current value is true. -func is_true() -> GdUnitBoolAssert: - return self - - -## Verifies that the current value is false. -func is_false() -> GdUnitBoolAssert: - return self - - -## Overrides the default failure message by given custom message. -@warning_ignore("unused_parameter") -func override_failure_message(message :String) -> GdUnitBoolAssert: - return self diff --git a/addons/gdUnit4/src/GdUnitConstants.gd b/addons/gdUnit4/src/GdUnitConstants.gd deleted file mode 100644 index 0445894..0000000 --- a/addons/gdUnit4/src/GdUnitConstants.gd +++ /dev/null @@ -1,6 +0,0 @@ -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 deleted file mode 100644 index 092a794..0000000 --- a/addons/gdUnit4/src/GdUnitDictionaryAssert.gd +++ /dev/null @@ -1,105 +0,0 @@ -## An Assertion Tool to verify dictionary -class_name GdUnitDictionaryAssert -extends GdUnitAssert - - -## Verifies that the current value is null. -func is_null() -> GdUnitDictionaryAssert: - return self - - -## Verifies that the current value is not null. -func is_not_null() -> GdUnitDictionaryAssert: - return self - - -## Verifies that the current dictionary is equal to the given one, ignoring order. -@warning_ignore("unused_parameter") -func is_equal(expected) -> 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: - return self - - -## Verifies that the current dictionary is empty, it has a size of 0. -func is_empty() -> GdUnitDictionaryAssert: - return self - - -## Verifies that the current dictionary is not empty, it has a size of minimum 1. -func is_not_empty() -> GdUnitDictionaryAssert: - return self - - -## 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: - 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: - return self - - -## Verifies that the current dictionary has a size of given value. -@warning_ignore("unused_parameter") -func has_size(expected: int) -> GdUnitDictionaryAssert: - return self - - -## Verifies that the current dictionary contains the given key(s).[br] -## The keys are compared by deep parameter comparision, for object reference compare you have to use [method contains_same_keys] -@warning_ignore("unused_parameter") -func contains_keys(expected :Array) -> GdUnitDictionaryAssert: - return self - - -## 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: - return self - - -## Verifies that the current dictionary not contains the given key(s).[br] -## This function is [b]deprecated[/b] you have to use [method not_contains_keys] instead -@warning_ignore("unused_parameter") -func contains_not_keys(expected :Array) -> GdUnitDictionaryAssert: - push_warning("Deprecated: 'contains_not_keys' is deprectated and will be removed soon, use `not_contains_keys` instead!") - return not_contains_keys(expected) - - -## Verifies that the current dictionary not contains the given key(s).[br] -## The keys are compared by deep parameter comparision, for object reference compare you have to use [method not_contains_same_keys] -@warning_ignore("unused_parameter") -func not_contains_keys(expected :Array) -> GdUnitDictionaryAssert: - return self - - -## Verifies that the current dictionary contains the given key(s).[br] -## The keys are compared by object reference, for deep parameter comparision use [method contains_keys] -@warning_ignore("unused_parameter") -func contains_same_keys(expected :Array) -> GdUnitDictionaryAssert: - return self - - -## 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: - return self - - -## Verifies that the current dictionary not contains the given key(s). -## The keys are compared by object reference, for deep parameter comparision use [method not_contains_keys] -@warning_ignore("unused_parameter") -func not_contains_same_keys(expected :Array) -> GdUnitDictionaryAssert: - return self diff --git a/addons/gdUnit4/src/GdUnitFailureAssert.gd b/addons/gdUnit4/src/GdUnitFailureAssert.gd deleted file mode 100644 index 64ff965..0000000 --- a/addons/gdUnit4/src/GdUnitFailureAssert.gd +++ /dev/null @@ -1,31 +0,0 @@ -## An assertion tool to verify GDUnit asserts. -## This assert is for internal use only, to verify that failed asserts work as expected. -class_name GdUnitFailureAssert -extends GdUnitAssert - - -## Verifies if the executed assert was successful -func is_success() -> GdUnitFailureAssert: - return self - -## Verifies if the executed assert has failed -func is_failed() -> GdUnitFailureAssert: - return self - - -## Verifies the failure line is equal to expected one. -@warning_ignore("unused_parameter") -func has_line(expected :int) -> GdUnitFailureAssert: - return self - - -## Verifies the failure message is equal to expected one. -@warning_ignore("unused_parameter") -func has_message(expected: String) -> GdUnitFailureAssert: - return self - - -## Verifies that the failure message starts with the expected message. -@warning_ignore("unused_parameter") -func starts_with_message(expected: String) -> GdUnitFailureAssert: - return self diff --git a/addons/gdUnit4/src/GdUnitFileAssert.gd b/addons/gdUnit4/src/GdUnitFileAssert.gd deleted file mode 100644 index 21bf21a..0000000 --- a/addons/gdUnit4/src/GdUnitFileAssert.gd +++ /dev/null @@ -1,19 +0,0 @@ -class_name GdUnitFileAssert -extends GdUnitAssert - - -func is_file() -> GdUnitFileAssert: - return self - - -func exists() -> GdUnitFileAssert: - return self - - -func is_script() -> GdUnitFileAssert: - return self - - -@warning_ignore("unused_parameter") -func contains_exactly(expected_rows :Array) -> GdUnitFileAssert: - return self diff --git a/addons/gdUnit4/src/GdUnitFloatAssert.gd b/addons/gdUnit4/src/GdUnitFloatAssert.gd deleted file mode 100644 index 8f24f56..0000000 --- a/addons/gdUnit4/src/GdUnitFloatAssert.gd +++ /dev/null @@ -1,83 +0,0 @@ -## An Assertion Tool to verify float values -class_name GdUnitFloatAssert -extends GdUnitAssert - - -## Verifies that the current value is equal to expected one. -@warning_ignore("unused_parameter") -func is_equal(expected :float) -> GdUnitFloatAssert: - return self - - -## Verifies that the current value is not equal to expected one. -@warning_ignore("unused_parameter") -func is_not_equal(expected :float) -> GdUnitFloatAssert: - return self - - -## Verifies that the current and expected value are approximately equal. -@warning_ignore("unused_parameter", "shadowed_global_identifier") -func is_equal_approx(expected :float, approx :float) -> GdUnitFloatAssert: - return self - - -## Verifies that the current value is less than the given one. -@warning_ignore("unused_parameter") -func is_less(expected :float) -> GdUnitFloatAssert: - return self - - -## Verifies that the current value is less than or equal the given one. -@warning_ignore("unused_parameter") -func is_less_equal(expected :float) -> GdUnitFloatAssert: - return self - - -## Verifies that the current value is greater than the given one. -@warning_ignore("unused_parameter") -func is_greater(expected :float) -> GdUnitFloatAssert: - return self - - -## Verifies that the current value is greater than or equal the given one. -@warning_ignore("unused_parameter") -func is_greater_equal(expected :float) -> GdUnitFloatAssert: - return self - - -## Verifies that the current value is negative. -func is_negative() -> GdUnitFloatAssert: - return self - - -## Verifies that the current value is not negative. -func is_not_negative() -> GdUnitFloatAssert: - return self - - -## Verifies that the current value is equal to zero. -func is_zero() -> GdUnitFloatAssert: - return self - - -## Verifies that the current value is not equal to zero. -func is_not_zero() -> GdUnitFloatAssert: - return self - - -## Verifies that the current value is in the given set of values. -@warning_ignore("unused_parameter") -func is_in(expected :Array) -> GdUnitFloatAssert: - return self - - -## Verifies that the current value is not in the given set of values. -@warning_ignore("unused_parameter") -func is_not_in(expected :Array) -> GdUnitFloatAssert: - return self - - -## Verifies that the current value is between the given boundaries (inclusive). -@warning_ignore("unused_parameter") -func is_between(from :float, to :float) -> GdUnitFloatAssert: - return self diff --git a/addons/gdUnit4/src/GdUnitFuncAssert.gd b/addons/gdUnit4/src/GdUnitFuncAssert.gd deleted file mode 100644 index 77c4609..0000000 --- a/addons/gdUnit4/src/GdUnitFuncAssert.gd +++ /dev/null @@ -1,56 +0,0 @@ -## An Assertion Tool to verify function callback values -class_name GdUnitFuncAssert -extends GdUnitAssert - - -## Verifies that the current value is null. -func is_null() -> GdUnitFuncAssert: - await Engine.get_main_loop().process_frame - return self - - -## Verifies that the current value is not null. -func is_not_null() -> GdUnitFuncAssert: - await Engine.get_main_loop().process_frame - return self - - -## Verifies that the current value is equal to the given one. -@warning_ignore("unused_parameter") -func is_equal(expected) -> 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: - await Engine.get_main_loop().process_frame - return self - - -## Verifies that the current value is true. -func is_true() -> GdUnitFuncAssert: - await Engine.get_main_loop().process_frame - return self - - -## Verifies that the current value is false. -func is_false() -> GdUnitFuncAssert: - await Engine.get_main_loop().process_frame - return self - - -## Overrides the default failure message by given custom message. -@warning_ignore("unused_parameter") -func override_failure_message(message :String) -> GdUnitFuncAssert: - return self - - -## Sets the timeout in ms to wait the function returnd the expected value, if the time over a failure is emitted.[br] -## e.g.[br] -## do wait until 5s the function `is_state` is returns 10 [br] -## [code]assert_func(instance, "is_state").wait_until(5000).is_equal(10)[/code] -@warning_ignore("unused_parameter") -func wait_until(timeout :int) -> GdUnitFuncAssert: - return self diff --git a/addons/gdUnit4/src/GdUnitGodotErrorAssert.gd b/addons/gdUnit4/src/GdUnitGodotErrorAssert.gd deleted file mode 100644 index d689625..0000000 --- a/addons/gdUnit4/src/GdUnitGodotErrorAssert.gd +++ /dev/null @@ -1,46 +0,0 @@ -## An assertion tool to verify for Godot runtime errors like assert() and push notifications like push_error(). -class_name GdUnitGodotErrorAssert -extends GdUnitAssert - - -## Verifies if the executed code runs without any runtime errors -## Usage: -## [codeblock] -## await assert_error().is_success() -## [/codeblock] -func is_success() -> GdUnitGodotErrorAssert: - await Engine.get_main_loop().process_frame - return self - - -## Verifies if the executed code runs into a runtime error -## Usage: -## [codeblock] -## await assert_error().is_runtime_error() -## [/codeblock] -@warning_ignore("unused_parameter") -func is_runtime_error(expected_error :String) -> GdUnitGodotErrorAssert: - await Engine.get_main_loop().process_frame - return self - - -## Verifies if the executed code has a push_warning() used -## Usage: -## [codeblock] -## await assert_error().is_push_warning() -## [/codeblock] -@warning_ignore("unused_parameter") -func is_push_warning(expected_warning :String) -> GdUnitGodotErrorAssert: - await Engine.get_main_loop().process_frame - return self - - -## Verifies if the executed code has a push_error() used -## Usage: -## [codeblock] -## await assert_error().is_push_error() -## [/codeblock] -@warning_ignore("unused_parameter") -func is_push_error(expected_error :String) -> GdUnitGodotErrorAssert: - await Engine.get_main_loop().process_frame - return self diff --git a/addons/gdUnit4/src/GdUnitIntAssert.gd b/addons/gdUnit4/src/GdUnitIntAssert.gd deleted file mode 100644 index 584788f..0000000 --- a/addons/gdUnit4/src/GdUnitIntAssert.gd +++ /dev/null @@ -1,87 +0,0 @@ -## An Assertion Tool to verify integer values -class_name GdUnitIntAssert -extends GdUnitAssert - - -## Verifies that the current value is equal to expected one. -@warning_ignore("unused_parameter") -func is_equal(expected :int) -> GdUnitIntAssert: - return self - - -## Verifies that the current value is not equal to expected one. -@warning_ignore("unused_parameter") -func is_not_equal(expected :int) -> GdUnitIntAssert: - return self - - -## Verifies that the current value is less than the given one. -@warning_ignore("unused_parameter") -func is_less(expected :int) -> GdUnitIntAssert: - return self - - -## Verifies that the current value is less than or equal the given one. -@warning_ignore("unused_parameter") -func is_less_equal(expected :int) -> GdUnitIntAssert: - return self - - -## Verifies that the current value is greater than the given one. -@warning_ignore("unused_parameter") -func is_greater(expected :int) -> GdUnitIntAssert: - return self - - -## Verifies that the current value is greater than or equal the given one. -@warning_ignore("unused_parameter") -func is_greater_equal(expected :int) -> GdUnitIntAssert: - return self - - -## Verifies that the current value is even. -func is_even() -> GdUnitIntAssert: - return self - - -## Verifies that the current value is odd. -func is_odd() -> GdUnitIntAssert: - return self - - -## Verifies that the current value is negative. -func is_negative() -> GdUnitIntAssert: - return self - - -## Verifies that the current value is not negative. -func is_not_negative() -> GdUnitIntAssert: - return self - - -## Verifies that the current value is equal to zero. -func is_zero() -> GdUnitIntAssert: - return self - - -## Verifies that the current value is not equal to zero. -func is_not_zero() -> GdUnitIntAssert: - return self - - -## Verifies that the current value is in the given set of values. -@warning_ignore("unused_parameter") -func is_in(expected :Array) -> GdUnitIntAssert: - return self - - -## Verifies that the current value is not in the given set of values. -@warning_ignore("unused_parameter") -func is_not_in(expected :Array) -> GdUnitIntAssert: - return self - - -## Verifies that the current value is between the given boundaries (inclusive). -@warning_ignore("unused_parameter") -func is_between(from :int, to :int) -> GdUnitIntAssert: - return self diff --git a/addons/gdUnit4/src/GdUnitObjectAssert.gd b/addons/gdUnit4/src/GdUnitObjectAssert.gd deleted file mode 100644 index d73bfd5..0000000 --- a/addons/gdUnit4/src/GdUnitObjectAssert.gd +++ /dev/null @@ -1,49 +0,0 @@ -## An Assertion Tool to verify Object values -class_name GdUnitObjectAssert -extends GdUnitAssert - - -## Verifies that the current value is equal to expected one. -@warning_ignore("unused_parameter") -func is_equal(expected) -> GdUnitObjectAssert: - return self - - -## Verifies that the current value is not equal to expected one. -@warning_ignore("unused_parameter") -func is_not_equal(expected) -> GdUnitObjectAssert: - return self - - -## Verifies that the current value is null. -func is_null() -> GdUnitObjectAssert: - return self - - -## Verifies that the current value is not null. -func is_not_null() -> GdUnitObjectAssert: - return self - - -## Verifies that the current value is the same as the given one. -@warning_ignore("unused_parameter", "shadowed_global_identifier") -func is_same(expected) -> 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: - return self - - -## Verifies that the current value is an instance of the given type. -@warning_ignore("unused_parameter") -func is_instanceof(expected :Object) -> GdUnitObjectAssert: - return self - - -## Verifies that the current value is not an instance of the given type. -@warning_ignore("unused_parameter") -func is_not_instanceof(expected) -> GdUnitObjectAssert: - return self diff --git a/addons/gdUnit4/src/GdUnitResultAssert.gd b/addons/gdUnit4/src/GdUnitResultAssert.gd deleted file mode 100644 index 4123d66..0000000 --- a/addons/gdUnit4/src/GdUnitResultAssert.gd +++ /dev/null @@ -1,45 +0,0 @@ -## An Assertion Tool to verify Results -class_name GdUnitResultAssert -extends GdUnitAssert - - -## Verifies that the current value is null. -func is_null() -> GdUnitResultAssert: - return self - - -## Verifies that the current value is not null. -func is_not_null() -> GdUnitResultAssert: - return self - - -## Verifies that the result is ends up with empty -func is_empty() -> GdUnitResultAssert: - return self - - -## Verifies that the result is ends up with success -func is_success() -> GdUnitResultAssert: - return self - - -## Verifies that the result is ends up with warning -func is_warning() -> GdUnitResultAssert: - return self - - -## Verifies that the result is ends up with error -func is_error() -> GdUnitResultAssert: - return self - - -## Verifies that the result contains the given message -@warning_ignore("unused_parameter") -func contains_message(expected :String) -> GdUnitResultAssert: - return self - - -## Verifies that the result contains the given value -@warning_ignore("unused_parameter") -func is_value(expected) -> GdUnitResultAssert: - return self diff --git a/addons/gdUnit4/src/GdUnitSceneRunner.gd b/addons/gdUnit4/src/GdUnitSceneRunner.gd deleted file mode 100644 index 07a6a9f..0000000 --- a/addons/gdUnit4/src/GdUnitSceneRunner.gd +++ /dev/null @@ -1,225 +0,0 @@ -## The scene runner for GdUnit to simmulate scene interactions -class_name GdUnitSceneRunner -extends RefCounted - -const NO_ARG = GdUnitConstants.NO_ARG - - -## Sets the mouse cursor to given position relative to the viewport. -@warning_ignore("unused_parameter") -func set_mouse_pos(pos :Vector2) -> GdUnitSceneRunner: - return self - - -## Gets the current mouse position of the current viewport -func get_mouse_position() -> Vector2: - return Vector2.ZERO - - -## Gets the current global mouse position of the current window -func get_global_mouse_position() -> Vector2: - return Vector2.ZERO - - -## 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] -## [member ctrl_pressed] : false by default set to true if simmulate control is press[br] -@warning_ignore("unused_parameter") -func simulate_key_pressed(key_code :int, shift_pressed := false, ctrl_pressed := false) -> GdUnitSceneRunner: - return self - - -## Simulates that a key is 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] -## [member ctrl_pressed] : false by default set to true if simmulate control is press[br] -@warning_ignore("unused_parameter") -func simulate_key_press(key_code :int, shift_pressed := false, ctrl_pressed := false) -> GdUnitSceneRunner: - return self - - -## Simulates that a key has been released.[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] -## [member ctrl_pressed] : false by default set to true if simmulate control is press[br] -@warning_ignore("unused_parameter") -func simulate_key_release(key_code :int, shift_pressed := false, ctrl_pressed := false) -> GdUnitSceneRunner: - return self - - -## Simulates a mouse moved to final position.[br] -## [member pos] : The final mouse position -@warning_ignore("unused_parameter") -func simulate_mouse_move(pos :Vector2) -> GdUnitSceneRunner: - return self - - -## Simulates a mouse move to the relative coordinates (offset).[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_absolute(position: 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 button pressed.[br] -## [member buttonIndex] : The mouse button identifier, one of the [enum MouseButton] or button wheel constants. -@warning_ignore("unused_parameter") -func simulate_mouse_button_pressed(buttonIndex :MouseButton, double_click := false) -> GdUnitSceneRunner: - return self - - -## Simulates a mouse button press (holding)[br] -## [member buttonIndex] : The mouse button identifier, one of the [enum MouseButton] or button wheel constants. -@warning_ignore("unused_parameter") -func simulate_mouse_button_press(buttonIndex :MouseButton, double_click := false) -> GdUnitSceneRunner: - return self - - -## Simulates a mouse button released.[br] -## [member buttonIndex] : The mouse button identifier, one of the [enum MouseButton] or button wheel constants. -@warning_ignore("unused_parameter") -func simulate_mouse_button_release(buttonIndex :MouseButton) -> GdUnitSceneRunner: - return self - - -## Sets how fast or slow the scene simulation is processed (clock ticks versus the real).[br] -## It defaults to 1.0. A value of 2.0 means the game moves twice as fast as real life, -## whilst a value of 0.5 means the game moves at half the regular speed. -@warning_ignore("unused_parameter") -func set_time_factor(time_factor := 1.0) -> GdUnitSceneRunner: - return self - - -## Simulates scene processing for a certain number of frames.[br] -## [member frames] : amount of frames to process[br] -## [member delta_milli] : the time delta between a frame in milliseconds -@warning_ignore("unused_parameter") -func simulate_frames(frames: int, delta_milli :int = -1) -> GdUnitSceneRunner: - await Engine.get_main_loop().process_frame - return self - - -## Simulates scene processing until the given signal is emitted by the scene.[br] -## [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: - await Engine.get_main_loop().process_frame - return self - - -## Simulates scene processing until the given signal is emitted by the given object.[br] -## [member source] : the object that should emit the signal[br] -## [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: - await Engine.get_main_loop().process_frame - return self - - -## Waits for the function return value until specified timeout or fails.[br] -## [member args] : optional function arguments -@warning_ignore("unused_parameter") -func await_func(func_name :String, args := []) -> GdUnitFuncAssert: - return null - - -## Waits for the function return value of specified source until specified timeout or fails.[br] -## [member source : the object where implements the function[br] -## [member args] : optional function arguments -@warning_ignore("unused_parameter") -func await_func_on(source :Object, func_name :String, args := []) -> GdUnitFuncAssert: - return null - - -## Waits for given signal is emited by the scene until a specified timeout to fail.[br] -## [member signal_name] : signal name[br] -## [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 ): - await Engine.get_main_loop().process_frame - pass - - -## Waits for given signal is emited by the until a specified timeout to fail.[br] -## [member source] : the object from which the signal is emitted[br] -## [member signal_name] : signal name[br] -## [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 ): - pass - - -## maximizes the window to bring the scene visible -func maximize_view() -> GdUnitSceneRunner: - return self - - -## Return the current value of the property with the name .[br] -## [member name] : name of property[br] -## [member return] : the value of the property -@warning_ignore("unused_parameter") -func get_property(name :String) -> Variant: - return null - -## Set the value of the property with the name .[br] -## [member name] : name of property[br] -## [member value] : value of property[br] -## [member return] : true|false depending on valid property name. -@warning_ignore("unused_parameter") -func set_property(name :String, value :Variant) -> bool: - return false - - -## executes the function specified by in the scene and returns the result.[br] -## [member name] : the name of the function to execute[br] -## [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 - - -## Searches for the specified node with the name in the current scene and returns it, otherwise null.[br] -## [member name] : the name of the node to find[br] -## [member recursive] : enables/disables seraching recursive[br] -## [member return] : the node if find otherwise null -@warning_ignore("unused_parameter") -func find_child(name :String, recursive :bool = true, owned :bool = false) -> Node: - return null - - -## Access to current running scene -func scene() -> Node: - return null diff --git a/addons/gdUnit4/src/GdUnitSignalAssert.gd b/addons/gdUnit4/src/GdUnitSignalAssert.gd deleted file mode 100644 index 8150df2..0000000 --- a/addons/gdUnit4/src/GdUnitSignalAssert.gd +++ /dev/null @@ -1,38 +0,0 @@ -## An Assertion Tool to verify for emitted signals until a waiting time -class_name GdUnitSignalAssert -extends GdUnitAssert - - -## Verifies that given signal is emitted until waiting time -@warning_ignore("unused_parameter") -func is_emitted(name :String, args := []) -> GdUnitSignalAssert: - await Engine.get_main_loop().process_frame - return self - - -## Verifies that given signal is NOT emitted until waiting time -@warning_ignore("unused_parameter") -func is_not_emitted(name :String, args := []) -> GdUnitSignalAssert: - await Engine.get_main_loop().process_frame - return self - - -## Verifies the signal exists checked the emitter -@warning_ignore("unused_parameter") -func is_signal_exists(name :String) -> GdUnitSignalAssert: - return self - - -## Overrides the default failure message by given custom message. -@warning_ignore("unused_parameter") -func override_failure_message(message :String) -> GdUnitSignalAssert: - return self - - -## Sets the assert signal timeout in ms, if the time over a failure is reported.[br] -## e.g.[br] -## do wait until 5s the instance has emitted the signal `signal_a`[br] -## [code]assert_signal(instance).wait_until(5000).is_emitted("signal_a")[/code] -@warning_ignore("unused_parameter") -func wait_until(timeout :int) -> GdUnitSignalAssert: - return self diff --git a/addons/gdUnit4/src/GdUnitStringAssert.gd b/addons/gdUnit4/src/GdUnitStringAssert.gd deleted file mode 100644 index 91e5bf0..0000000 --- a/addons/gdUnit4/src/GdUnitStringAssert.gd +++ /dev/null @@ -1,79 +0,0 @@ -## An Assertion Tool to verify String values -class_name GdUnitStringAssert -extends GdUnitAssert - - -## Verifies that the current String is equal to the given one. -@warning_ignore("unused_parameter") -func is_equal(expected) -> 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: - return self - - -## Verifies that the current String is not equal to the given one. -@warning_ignore("unused_parameter") -func is_not_equal(expected) -> 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: - return self - - -## Verifies that the current String is empty, it has a length of 0. -func is_empty() -> GdUnitStringAssert: - return self - - -## Verifies that the current String is not empty, it has a length of minimum 1. -func is_not_empty() -> GdUnitStringAssert: - return self - - -## Verifies that the current String contains the given String. -@warning_ignore("unused_parameter") -func contains(expected: String) -> GdUnitStringAssert: - return self - - -## Verifies that the current String does not contain the given String. -@warning_ignore("unused_parameter") -func not_contains(expected: String) -> GdUnitStringAssert: - return self - - -## Verifies that the current String does not contain the given String, ignoring case considerations. -@warning_ignore("unused_parameter") -func contains_ignoring_case(expected: String) -> GdUnitStringAssert: - return self - - -## Verifies that the current String does not contain the given String, ignoring case considerations. -@warning_ignore("unused_parameter") -func not_contains_ignoring_case(expected: String) -> GdUnitStringAssert: - return self - - -## Verifies that the current String starts with the given prefix. -@warning_ignore("unused_parameter") -func starts_with(expected: String) -> GdUnitStringAssert: - return self - - -## Verifies that the current String ends with the given suffix. -@warning_ignore("unused_parameter") -func ends_with(expected: String) -> GdUnitStringAssert: - return self - - -## 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: - return self diff --git a/addons/gdUnit4/src/GdUnitTestSuite.gd b/addons/gdUnit4/src/GdUnitTestSuite.gd deleted file mode 100644 index 5c1f898..0000000 --- a/addons/gdUnit4/src/GdUnitTestSuite.gd +++ /dev/null @@ -1,620 +0,0 @@ -## The main class for all GdUnit test suites[br] -## This class is the main class to implement your unit tests[br] -## You have to extend and implement your test cases as described[br] -## e.g MyTests.gd [br] -## [codeblock] -## extends GdUnitTestSuite -## # testcase -## func test_case_a(): -## assert_that("value").is_equal("value") -## [/codeblock] -## @tutorial: https://mikeschulze.github.io/gdUnit4/faq/test-suite/ - -@icon("res://addons/gdUnit4/src/ui/assets/TestSuite.svg") -class_name GdUnitTestSuite -extends Node - -const NO_ARG :Variant = GdUnitConstants.NO_ARG - -### internal runtime variables that must not be overwritten!!! -@warning_ignore("unused_private_class_variable") -var __is_skipped := false -@warning_ignore("unused_private_class_variable") -var __skip_reason :String = "Unknow." -var __active_test_case :String -var __awaiter := __gdunit_awaiter() -# holds the actual 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" -### 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 -func __lazy_load(script_path :String) -> GDScript: - return GdUnitAssertions.__lazy_load(script_path) - - -func __gdunit_assert() -> GDScript: - return __lazy_load("res://addons/gdUnit4/src/asserts/GdUnitAssertImpl.gd") - - -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() -> GDScript: - return __lazy_load("res://addons/gdUnit4/src/matchers/GdUnitArgumentMatchers.gd") - - -func __gdunit_object_interactions() -> GDScript: - return __lazy_load("res://addons/gdUnit4/src/core/GdUnitObjectInteractions.gd") - - -## This function is called before a test suite starts[br] -## You can overwrite to prepare test data or initalizize necessary variables -func before() -> void: - pass - - -## This function is called at least when a test suite is finished[br] -## You can overwrite to cleanup data created during test running -func after() -> void: - pass - - -## This function is called before a test case starts[br] -## You can overwrite to prepare test case specific data -func before_test() -> void: - pass - - -## This function is called after the test case is finished[br] -## You can overwrite to cleanup your test case specific data -func after_test() -> void: - pass - - -func is_failure(_expected_failure :String = NO_ARG) -> bool: - return Engine.get_meta("GD_TEST_FAILURE") if Engine.has_meta("GD_TEST_FAILURE") else false - - -func set_active_test_case(test_case :String) -> void: - __active_test_case = test_case - - -# === Tools ==================================================================== -# Mapps Godot error number to a readable error message. See at ERROR -# https://docs.godotengine.org/de/stable/classes/class_@globalscope.html#enum-globalscope-error -func error_as_string(error_number :int) -> String: - return error_string(error_number) - - -## A litle helper to auto freeing your created objects after test execution -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") -func add_child(node :Node, force_readable_name := false, internal := Node.INTERNAL_MODE_DISABLED) -> void: - super.add_child(node, force_readable_name, internal) - if __execution_context != null: - __execution_context.orphan_monitor_start() - - -## Discard the error message triggered by a timeout (interruption).[br] -## By default, an interrupted test is reported as an error.[br] -## This function allows you to change the message to Success when an interrupted error is reported. -func discard_error_interupted_by_timeout() -> void: - __gdunit_tools().register_expect_interupted_by_timeout(self, __active_test_case) - - -## Creates a new directory under the temporary directory *user://tmp*[br] -## 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_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() -> 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_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_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_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) -> Variant: - return str_to_var(__gdunit_file_access().resource_as_string(resource_path)) - - -## clears the debuger error list[br] -## PROTOTYPE!!!! Don't use it for now -func clear_push_errors() -> void: - __gdunit_tools().clear_push_errors() - - -## Waits for given signal is emited by the until a specified timeout to fail[br] -## source: the object from which the signal is emitted[br] -## signal_name: signal name[br] -## args: the expected signal arguments as an array[br] -## timeout: the timeout in ms, default is set to 2000ms -func await_signal_on(source :Object, signal_name :String, args :Array = [], timeout :int = 2000) -> Variant: - return await __awaiter.await_signal_on(source, signal_name, args, timeout) - - -## Waits until the next idle frame -func await_idle_frame() -> void: - await __awaiter.await_idle_frame() - - -## Waits for for a given amount of milliseconds[br] -## example:[br] -## [codeblock] -## # waits for 100ms -## 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) -> void: - await __awaiter.await_millis(timeout) - - -## Creates a new scene runner to allow simulate interactions checked a scene.[br] -## The runner will manage the scene instance and release after the runner is released[br] -## example:[br] -## [codeblock] -## # creates a runner by using a instanciated scene -## 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 :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 -const RETURN_DEFAULTS = GdUnitMock.RETURN_DEFAULTS -## do call the real implementation -const CALL_REAL_FUNC = GdUnitMock.CALL_REAL_FUNC -## 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 = GdUnitMock.RETURN_DEEP_STUB - - -## Creates a mock for given class name -func mock(clazz :Variant, mock_mode := RETURN_DEFAULTS) -> Object: - return __lazy_load("res://addons/gdUnit4/src/mocking/GdUnitMockBuilder.gd").build(clazz, mock_mode) - - -## Creates a spy checked given object instance -func spy(instance :Variant) -> Object: - return __lazy_load("res://addons/gdUnit4/src/spy/GdUnitSpyBuilder.gd").build(instance) - - -## Configures a return value for the specified function and used arguments.[br] -## [b]Example: -## [codeblock] -## # overrides the return value of myMock.is_selected() to false -## do_return(false).on(myMock).is_selected() -## [/codeblock] -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 :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 :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 :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 :Variant) -> void: - __gdunit_object_interactions().reset(obj) - - -## Starts monitoring the specified source to collect all transmitted signals.[br] -## The collected signals can then be checked with 'assert_signal'.[br] -## By default, the specified source is automatically released when the test ends. -## You can control this behavior by setting auto_free to false if you do not want the source to be automatically freed.[br] -## Usage: -## [codeblock] -## var emitter := monitor_signals(MyEmitter.new()) -## # call the function to send the signal -## emitter.do_it() -## # verify the signial is emitted -## await assert_signal(emitter).is_emitted('my_signal') -## [/codeblock] -func monitor_signals(source :Object, _auto_free := true) -> Object: - __lazy_load("res://addons/gdUnit4/src/core/thread/GdUnitThreadManager.gd")\ - .get_current_context()\ - .get_signal_collector()\ - .register_emitter(source) - return auto_free(source) if _auto_free else source - - -# === Argument matchers ======================================================== -## Argument matcher to match any argument -func any() -> GdUnitArgumentMatcher: - return __gdunit_argument_matchers().any() - - -## Argument matcher to match any boolean value -func any_bool() -> GdUnitArgumentMatcher: - return __gdunit_argument_matchers().by_type(TYPE_BOOL) - - -## Argument matcher to match any integer value -func any_int() -> GdUnitArgumentMatcher: - return __gdunit_argument_matchers().by_type(TYPE_INT) - - -## Argument matcher to match any float value -func any_float() -> GdUnitArgumentMatcher: - return __gdunit_argument_matchers().by_type(TYPE_FLOAT) - - -## Argument matcher to match any string value -func any_string() -> GdUnitArgumentMatcher: - return __gdunit_argument_matchers().by_type(TYPE_STRING) - - -## Argument matcher to match any Color value -func any_color() -> GdUnitArgumentMatcher: - return __gdunit_argument_matchers().by_type(TYPE_COLOR) - - -## Argument matcher to match any Vector typed value -func any_vector() -> GdUnitArgumentMatcher: - return __gdunit_argument_matchers().by_types([ - TYPE_VECTOR2, - TYPE_VECTOR2I, - TYPE_VECTOR3, - TYPE_VECTOR3I, - TYPE_VECTOR4, - TYPE_VECTOR4I, - ]) - - -## Argument matcher to match any Vector2 value -func any_vector2() -> GdUnitArgumentMatcher: - return __gdunit_argument_matchers().by_type(TYPE_VECTOR2) - - -## Argument matcher to match any Vector2i value -func any_vector2i() -> GdUnitArgumentMatcher: - return __gdunit_argument_matchers().by_type(TYPE_VECTOR2I) - - -## Argument matcher to match any Vector3 value -func any_vector3() -> GdUnitArgumentMatcher: - return __gdunit_argument_matchers().by_type(TYPE_VECTOR3) - - -## Argument matcher to match any Vector3i value -func any_vector3i() -> GdUnitArgumentMatcher: - return __gdunit_argument_matchers().by_type(TYPE_VECTOR3I) - - -## Argument matcher to match any Vector4 value -func any_vector4() -> GdUnitArgumentMatcher: - return __gdunit_argument_matchers().by_type(TYPE_VECTOR4) - - -## Argument matcher to match any Vector3i value -func any_vector4i() -> GdUnitArgumentMatcher: - return __gdunit_argument_matchers().by_type(TYPE_VECTOR4I) - - -## Argument matcher to match any Rect2 value -func any_rect2() -> GdUnitArgumentMatcher: - return __gdunit_argument_matchers().by_type(TYPE_RECT2) - - -## Argument matcher to match any Plane value -func any_plane() -> GdUnitArgumentMatcher: - return __gdunit_argument_matchers().by_type(TYPE_PLANE) - - -## Argument matcher to match any Quaternion value -func any_quat() -> GdUnitArgumentMatcher: - return __gdunit_argument_matchers().by_type(TYPE_QUATERNION) - - -## Argument matcher to match any AABB value -func any_aabb() -> GdUnitArgumentMatcher: - return __gdunit_argument_matchers().by_type(TYPE_AABB) - - -## Argument matcher to match any Basis value -func any_basis() -> GdUnitArgumentMatcher: - return __gdunit_argument_matchers().by_type(TYPE_BASIS) - - -## 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) - - -## Argument matcher to match any RID value -func any_rid() -> GdUnitArgumentMatcher: - return __gdunit_argument_matchers().by_type(TYPE_RID) - - -## Argument matcher to match any Object value -func any_object() -> GdUnitArgumentMatcher: - return __gdunit_argument_matchers().by_type(TYPE_OBJECT) - - -## Argument matcher to match any Dictionary value -func any_dictionary() -> GdUnitArgumentMatcher: - return __gdunit_argument_matchers().by_type(TYPE_DICTIONARY) - - -## Argument matcher to match any Array value -func any_array() -> GdUnitArgumentMatcher: - return __gdunit_argument_matchers().by_type(TYPE_ARRAY) - - -## Argument matcher to match any PackedByteArray value -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_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_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_packed_string_array() -> GdUnitArgumentMatcher: - return __gdunit_argument_matchers().by_type(TYPE_PACKED_STRING_ARRAY) - - -## Argument matcher to match any PackedVector2Array value -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_packed_vector3_array() -> GdUnitArgumentMatcher: - return __gdunit_argument_matchers().by_type(TYPE_PACKED_VECTOR3_ARRAY) - - -## Argument matcher to match any PackedColorArray value -func any_packed_color_array() -> GdUnitArgumentMatcher: - return __gdunit_argument_matchers().by_type(TYPE_PACKED_COLOR_ARRAY) - - -## Argument matcher to match any instance of given class -func any_class(clazz :Object) -> GdUnitArgumentMatcher: - return __gdunit_argument_matchers().any_class(clazz) - - -# === value extract utils ====================================================== -## Builds an extractor by given function name and optional arguments -func extr(func_name :String, args := Array()) -> GdUnitValueExtractor: - return __lazy_load("res://addons/gdUnit4/src/extractors/GdUnitFuncValueExtractor.gd").new(func_name, args) - - -## Constructs a tuple by given arguments -func tuple(arg0 :Variant, - 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) -> GdUnitTuple: - return GdUnitTuple.new(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9) - - -# === Asserts ================================================================== - -## The common assertion tool to verify values. -## It checks the given value by type to fit to the best assert -func assert_that(current :Variant) -> GdUnitAssert: - match typeof(current): - TYPE_BOOL: - return assert_bool(current) - TYPE_INT: - return assert_int(current) - TYPE_FLOAT: - return assert_float(current) - TYPE_STRING: - return assert_str(current) - TYPE_VECTOR2, TYPE_VECTOR2I, TYPE_VECTOR3, TYPE_VECTOR3I, TYPE_VECTOR4, TYPE_VECTOR4I: - return assert_vector(current) - TYPE_DICTIONARY: - return assert_dict(current) - TYPE_ARRAY, TYPE_PACKED_BYTE_ARRAY, TYPE_PACKED_INT32_ARRAY, TYPE_PACKED_INT64_ARRAY,\ - TYPE_PACKED_FLOAT32_ARRAY, TYPE_PACKED_FLOAT64_ARRAY, TYPE_PACKED_STRING_ARRAY,\ - TYPE_PACKED_VECTOR2_ARRAY, TYPE_PACKED_VECTOR3_ARRAY, TYPE_PACKED_COLOR_ARRAY: - return assert_array(current) - TYPE_OBJECT, TYPE_NIL: - return assert_object(current) - _: - return __gdunit_assert().new(current) - - -## An assertion tool to verify boolean values. -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 :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 :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 :Variant) -> GdUnitFloatAssert: - return __lazy_load("res://addons/gdUnit4/src/asserts/GdUnitFloatAssertImpl.gd").new(current) - - -## An assertion tool to verify Vector values.[br] -## This assertion supports all vector types.[br] -## Usage: -## [codeblock] -## assert_vector(Vector2(1.2, 1.000001)).is_equal(Vector2(1.2, 1.000001)) -## [/codeblock] -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 :Variant) -> GdUnitArrayAssert: - return __lazy_load("res://addons/gdUnit4/src/asserts/GdUnitArrayAssertImpl.gd").new(current) - - -## An assertion tool to verify dictionaries. -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 :Variant) -> GdUnitFileAssert: - return __lazy_load("res://addons/gdUnit4/src/asserts/GdUnitFileAssertImpl.gd").new(current) - - -## An assertion tool to verify Objects. -func assert_object(current :Variant) -> GdUnitObjectAssert: - return __lazy_load("res://addons/gdUnit4/src/asserts/GdUnitObjectAssertImpl.gd").new(current) - - -func assert_result(current :Variant) -> GdUnitResultAssert: - return __lazy_load("res://addons/gdUnit4/src/asserts/GdUnitResultAssertImpl.gd").new(current) - - -## An assertion tool that waits until a certain time for an expected function return value -func assert_func(instance :Object, func_name :String, args := Array()) -> GdUnitFuncAssert: - return __lazy_load("res://addons/gdUnit4/src/asserts/GdUnitFuncAssertImpl.gd").new(instance, func_name, args) - - -## An Assertion Tool to verify for emitted signals until a certain time. -func assert_signal(instance :Object) -> GdUnitSignalAssert: - return __lazy_load("res://addons/gdUnit4/src/asserts/GdUnitSignalAssertImpl.gd").new(instance) - - -## An assertion tool to test for failing assertions.[br] -## This assert is only designed for internal use to verify failing asserts working as expected.[br] -## Usage: -## [codeblock] -## assert_failure(func(): assert_bool(true).is_not_equal(true)) \ -## .has_message("Expecting:\n 'true'\n not equal to\n 'true'") -## [/codeblock] -func assert_failure(assertion :Callable) -> GdUnitFailureAssert: - return __lazy_load("res://addons/gdUnit4/src/asserts/GdUnitFailureAssertImpl.gd").new().execute(assertion) - - -## An assertion tool to test for failing assertions.[br] -## This assert is only designed for internal use to verify failing asserts working as expected.[br] -## Usage: -## [codeblock] -## await assert_failure_await(func(): assert_bool(true).is_not_equal(true)) \ -## .has_message("Expecting:\n 'true'\n not equal to\n 'true'") -## [/codeblock] -func assert_failure_await(assertion :Callable) -> GdUnitFailureAssert: - return await __lazy_load("res://addons/gdUnit4/src/asserts/GdUnitFailureAssertImpl.gd").new().execute_and_await(assertion) - - -## An assertion tool to verify for Godot errors.[br] -## You can use to verify for certain Godot erros like failing assertions, push_error, push_warn.[br] -## Usage: -## [codeblock] -## # 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') -## [/codeblock] -func assert_error(current :Callable) -> GdUnitGodotErrorAssert: - return __lazy_load("res://addons/gdUnit4/src/asserts/GdUnitGodotErrorAssertImpl.gd").new(current) - - -func assert_not_yet_implemented() -> void: - __gdunit_assert().new(null).test_fail() - - -func fail(message :String) -> void: - __gdunit_assert().new(null).report_error(message) - - -# --- internal stuff do not override!!! -func ResourcePath() -> String: - return get_script().resource_path diff --git a/addons/gdUnit4/src/GdUnitTuple.gd b/addons/gdUnit4/src/GdUnitTuple.gd deleted file mode 100644 index 6c91002..0000000 --- a/addons/gdUnit4/src/GdUnitTuple.gd +++ /dev/null @@ -1,28 +0,0 @@ -## A tuple implementation to hold two or many values -class_name GdUnitTuple -extends RefCounted - -const NO_ARG :Variant = GdUnitConstants.NO_ARG - -var __values :Array = Array() - - -func _init(arg0:Variant, - 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: - __values = GdArrayTools.filter_value([arg0,arg1,arg2,arg3,arg4,arg5,arg6,arg7,arg8,arg9], NO_ARG) - - -func values() -> Array: - return __values - - -func _to_string() -> String: - return "tuple(%s)" % str(__values) diff --git a/addons/gdUnit4/src/GdUnitValueExtractor.gd b/addons/gdUnit4/src/GdUnitValueExtractor.gd deleted file mode 100644 index be702cf..0000000 --- a/addons/gdUnit4/src/GdUnitValueExtractor.gd +++ /dev/null @@ -1,9 +0,0 @@ -## This is the base interface for value extraction -class_name GdUnitValueExtractor -extends RefCounted - - -## Extracts a value by given implementation -func extract_value(value): - push_error("Uninplemented func 'extract_value'") - return value diff --git a/addons/gdUnit4/src/GdUnitVectorAssert.gd b/addons/gdUnit4/src/GdUnitVectorAssert.gd deleted file mode 100644 index 915fd3b..0000000 --- a/addons/gdUnit4/src/GdUnitVectorAssert.gd +++ /dev/null @@ -1,57 +0,0 @@ -## An Assertion Tool to verify Vector values -class_name GdUnitVectorAssert -extends GdUnitAssert - - -## Verifies that the current value is equal to expected one. -@warning_ignore("unused_parameter") -func is_equal(expected :Variant) -> GdUnitVectorAssert: - return self - - -## Verifies that the current value is not equal to expected one. -@warning_ignore("unused_parameter") -func is_not_equal(expected :Variant) -> GdUnitVectorAssert: - return self - - -## Verifies that the current and expected value are approximately equal. -@warning_ignore("unused_parameter", "shadowed_global_identifier") -func is_equal_approx(expected :Variant, approx :Variant) -> GdUnitVectorAssert: - return self - - -## Verifies that the current value is less than the given one. -@warning_ignore("unused_parameter") -func is_less(expected :Variant) -> GdUnitVectorAssert: - return self - - -## Verifies that the current value is less than or equal the given one. -@warning_ignore("unused_parameter") -func is_less_equal(expected :Variant) -> GdUnitVectorAssert: - return self - - -## Verifies that the current value is greater than the given one. -@warning_ignore("unused_parameter") -func is_greater(expected :Variant) -> GdUnitVectorAssert: - return self - - -## Verifies that the current value is greater than or equal the given one. -@warning_ignore("unused_parameter") -func is_greater_equal(expected :Variant) -> GdUnitVectorAssert: - return self - - -## Verifies that the current value is between the given boundaries (inclusive). -@warning_ignore("unused_parameter") -func is_between(from :Variant, to :Variant) -> GdUnitVectorAssert: - return self - - -## Verifies that the current value is not between the given boundaries (inclusive). -@warning_ignore("unused_parameter") -func is_not_between(from :Variant, to :Variant) -> GdUnitVectorAssert: - return self diff --git a/addons/gdUnit4/src/asserts/CallBackValueProvider.gd b/addons/gdUnit4/src/asserts/CallBackValueProvider.gd deleted file mode 100644 index 27242e8..0000000 --- a/addons/gdUnit4/src/asserts/CallBackValueProvider.gd +++ /dev/null @@ -1,25 +0,0 @@ -# a value provider unsing a callback to get `next` value from a certain function -class_name CallBackValueProvider -extends ValueProvider - -var _cb :Callable -var _args :Array - - -func _init(instance :Object, func_name :String, args :Array = Array(), force_error := true): - _cb = Callable(instance, func_name); - _args = args - if force_error and not _cb.is_valid(): - push_error("Can't find function '%s' checked instance %s" % [func_name, instance]) - - -func get_value() -> Variant: - if not _cb.is_valid(): - return null - if _args.is_empty(): - return await _cb.call() - return await _cb.callv(_args) - - -func dispose(): - _cb = Callable() diff --git a/addons/gdUnit4/src/asserts/DefaultValueProvider.gd b/addons/gdUnit4/src/asserts/DefaultValueProvider.gd deleted file mode 100644 index 036a482..0000000 --- a/addons/gdUnit4/src/asserts/DefaultValueProvider.gd +++ /dev/null @@ -1,12 +0,0 @@ -# default value provider, simple returns the initial value -class_name DefaultValueProvider -extends ValueProvider - -var _value - -func _init(value): - _value = value - - -func get_value(): - return _value diff --git a/addons/gdUnit4/src/asserts/GdAssertMessages.gd b/addons/gdUnit4/src/asserts/GdAssertMessages.gd deleted file mode 100644 index 3411f42..0000000 --- a/addons/gdUnit4/src/asserts/GdAssertMessages.gd +++ /dev/null @@ -1,608 +0,0 @@ -class_name GdAssertMessages -extends Resource - -const WARN_COLOR = "#EFF883" -const ERROR_COLOR = "#CD5C5C" -const VALUE_COLOR = "#1E90FF" -const SUB_COLOR := Color(1, 0, 0, .3) -const ADD_COLOR := Color(0, 1, 0, .3) - - -static func format_dict(value :Dictionary) -> String: - if value.is_empty(): - return "{ }" - var as_rows := var_to_str(value).split("\n") - for index in range( 1, as_rows.size()-1): - as_rows[index] = " " + as_rows[index] - as_rows[-1] = " " + as_rows[-1] - return "\n".join(as_rows) - - -# improved version of InputEvent as text -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] - 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] - return text - - -static func _colored_string_div(characters :String) -> String: - return colored_array_div(characters.to_ascii_buffer()) - - -static func colored_array_div(characters :PackedByteArray) -> String: - if characters.is_empty(): - return "" - var result = PackedByteArray() - var index = 0 - var missing_chars := PackedByteArray() - var additional_chars := PackedByteArray() - - while index < characters.size(): - var character := characters[index] - match character: - GdDiffTool.DIV_ADD: - index += 1 - additional_chars.append(characters[index]) - GdDiffTool.DIV_SUB: - index += 1 - missing_chars.append(characters[index]) - _: - if not missing_chars.is_empty(): - result.append_array(format_chars(missing_chars, SUB_COLOR)) - missing_chars = PackedByteArray() - if not additional_chars.is_empty(): - result.append_array(format_chars(additional_chars, ADD_COLOR)) - 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: - return GdDefaultValueDecoder.decode(value) - - -static func _warning(error :String) -> String: - return "[color=%s]%s[/color]" % [WARN_COLOR, error] - - -static func _error(error :String) -> String: - return "[color=%s]%s[/color]" % [ERROR_COLOR, error] - - -static func _nerror(number) -> String: - match typeof(number): - TYPE_INT: - return "[color=%s]%d[/color]" % [ERROR_COLOR, number] - TYPE_FLOAT: - return "[color=%s]%f[/color]" % [ERROR_COLOR, number] - _: - return "[color=%s]%s[/color]" % [ERROR_COLOR, str(number)] - - -static func _colored_value(value, _delimiter ="\n") -> String: - match typeof(value): - TYPE_STRING, TYPE_STRING_NAME: - return "'[color=%s]%s[/color]'" % [VALUE_COLOR, _colored_string_div(value)] - TYPE_INT: - return "'[color=%s]%d[/color]'" % [VALUE_COLOR, value] - TYPE_FLOAT: - return "'[color=%s]%s[/color]'" % [VALUE_COLOR, _typed_value(value)] - TYPE_COLOR: - return "'[color=%s]%s[/color]'" % [VALUE_COLOR, _typed_value(value)] - TYPE_OBJECT: - if value == null: - return "'[color=%s][/color]'" % [VALUE_COLOR] - 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, str(value)] - return "[color=%s]<%s>[/color]" % [VALUE_COLOR, value.get_class()] - TYPE_DICTIONARY: - 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: - var index :String = str(report["index"]) - var current :String = str(report["current"]) - var expected :String = str(report["expected"]) - cells += cell.replace("$text", index) + cell.replace("$text", current) + cell.replace("$text", expected) - return table.replace("$cells", cells) - - -static func orphan_detected_on_suite_setup(count :int): - 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): - 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): - 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] - - -static func test_timeout(timeout :int) -> String: - return "%s\n %s" % [_error("Timeout !"), _colored_value("Test timed out after %s" % LocalTime.elapsed(timeout))] - - -# gdlint:disable = mixed-tabs-and-spaces -static func test_suite_skipped(hint :String, skip_count) -> String: - return """ - %s - Tests skipped: %s - Reason: %s - """.dedent().trim_prefix("\n")\ - % [_error("Entire test-suite is skipped!"), _colored_value(skip_count), _colored_value(hint)] - - -static func test_skipped(hint :String) -> String: - return """ - %s - Reason: %s - """.dedent().trim_prefix("\n")\ - % [_error("This test is skipped!"), _colored_value(hint)] - - -static func error_not_implemented() -> String: - return _error("Test not implemented!") - - -static func error_is_null(current) -> String: - return "%s %s but was %s" % [_error("Expecting:"), _colored_value(null), _colored_value(current)] - - -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 = """ - %s - %s - but was - %s""".dedent().trim_prefix("\n") % [_error("Expecting:"), _colored_value(expected, true), _colored_value(current, true)] - 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_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_is_empty(current) -> String: - return "%s\n must be empty but was\n %s" % [_error("Expecting:"), _colored_value(current)] - - -static func error_is_not_empty() -> String: - return "%s\n must not be empty" % [_error("Expecting:")] - - -static func error_is_same(current, expected) -> 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: - return "%s\n %s" % [_error("Expecting not same:"), _colored_value(expected)] - - -static func error_not_same_error(current, expected) -> String: - return "%s\n %s\n but was\n %s" % [_error("Expecting error message:"), _colored_value(expected), _colored_value(current)] - - -static func error_is_instanceof(current: GdUnitResult, expected :GdUnitResult) -> String: - return "%s\n %s\n But it was %s" % [_error("Expected instance of:"),\ - _colored_value(expected.or_else(null)), _colored_value(current.or_else(null))] - - -# -- Boolean Assert specific messages ----------------------------------------------------- -static func error_is_true(current) -> String: - return "%s %s but is %s" % [_error("Expecting:"), _colored_value(true), _colored_value(current)] - - -static func error_is_false(current) -> 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: - return "%s\n %s must be even" % [_error("Expecting:"), _colored_value(current)] - - -static func error_is_odd(current) -> String: - return "%s\n %s must be odd" % [_error("Expecting:"), _colored_value(current)] - - -static func error_is_negative(current) -> String: - return "%s\n %s be negative" % [_error("Expecting:"), _colored_value(current)] - - -static func error_is_not_negative(current) -> String: - return "%s\n %s be not negative" % [_error("Expecting:"), _colored_value(current)] - - -static func error_is_zero(current) -> String: - return "%s\n equal to 0 but is %s" % [_error("Expecting:"), _colored_value(current)] - - -static func error_is_not_zero() -> String: - return "%s\n not equal to 0" % [_error("Expecting:")] - - -static func error_is_wrong_type(current_type :Variant.Type, expected_type :Variant.Type) -> String: - return "%s\n Expecting type %s but is %s" % [ - _error("Unexpected type comparison:"), - _colored_value(GdObjects.type_as_string(current_type)), - _colored_value(GdObjects.type_as_string(expected_type))] - - -static func error_is_value(operation, current, expected, expected2=null) -> String: - match operation: - Comparator.EQUAL: - return "%s\n %s but was '%s'" % [_error("Expecting:"), _colored_value(expected), _nerror(current)] - Comparator.LESS_THAN: - return "%s\n %s but was '%s'" % [_error("Expecting to be less than:"), _colored_value(expected), _nerror(current)] - Comparator.LESS_EQUAL: - return "%s\n %s but was '%s'" % [_error("Expecting to be less than or equal:"), _colored_value(expected), _nerror(current)] - Comparator.GREATER_THAN: - return "%s\n %s but was '%s'" % [_error("Expecting to be greater than:"), _colored_value(expected), _nerror(current)] - 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)] - 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 "TODO create expected message" - - -static func error_is_in(current, 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: - 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: - 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: - 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: - 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: - 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: - 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: - 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: - 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 - 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)] - 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)] - 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)] - 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)] - 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 "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, ", ")] - if not not_expect.is_empty(): - 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, ", ")] - 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:" - ) - 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, ", ")] - if not not_expect.is_empty(): - 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, ", ")] - 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, ", ")] - if not not_expect.is_empty(): - 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, ", ")] - 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, ", ")] - if not found.is_empty(): - 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, ", ")] - - -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, ", ")] - - -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)] - - -# - ResultAssert specific errors ---------------------------------------------------- -static func error_result_is_empty(current :GdUnitResult) -> String: - return _result_error_message(current, GdUnitResult.EMPTY) - - -static func error_result_is_success(current :GdUnitResult) -> String: - return _result_error_message(current, GdUnitResult.SUCCESS) - - -static func error_result_is_warning(current :GdUnitResult) -> String: - return _result_error_message(current, GdUnitResult.WARN) - - -static func error_result_is_error(current :GdUnitResult) -> String: - return _result_error_message(current, GdUnitResult.ERROR) - - -static func error_result_has_message(current :String, expected :String) -> String: - return "%s\n %s\n but was\n %s." % [_error("Expecting:"), _colored_value(expected), _colored_value(current)] - - -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: - return "%s\n %s\n but was\n %s." % [_error("Expecting to contain same value:"), _colored_value(expected), _colored_value(current)] - - -static func _result_error_message(current :GdUnitResult, expected_type :int) -> String: - if current == null: - 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)] - return "%s\n %s" % [_error(error), _colored_value(result_message(current))] - - -static func error_interrupted(func_name :String, expected, elapsed :String) -> String: - func_name = humanized(func_name) - if expected == null: - return "%s %s but timed out after %s" % [_error("Expected:"), func_name, elapsed] - return "%s %s %s but timed out after %s" % [_error("Expected:"), func_name, _colored_value(expected), elapsed] - - -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] - - -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] - -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: - GdUnitResult.SUCCESS: return "SUCCESS" - GdUnitResult.WARN: return "WARNING" - GdUnitResult.ERROR: return "ERROR" - GdUnitResult.EMPTY: return "EMPTY" - return "UNKNOWN" - - -static func result_message(result :GdUnitResult) -> String: - match result._state: - GdUnitResult.SUCCESS: return "" - GdUnitResult.WARN: return result.warn_message() - GdUnitResult.ERROR: return result.error_message() - GdUnitResult.EMPTY: return "" - return "UNKNOWN" -# ----------------------------------------------------------------------------------- - -# - Spy|Mock specific errors ---------------------------------------------------- -static func error_no_more_interactions(summary :Dictionary) -> String: - var interactions := PackedStringArray() - for args 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)] - - -static func error_validate_interactions(current_interactions :Dictionary, expected_interactions :Dictionary) -> String: - var interactions := PackedStringArray() - for args 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)] - - -static func _format_arguments(args :Array, times :int) -> String: - var fname :String = args[0] - var fargs := args.slice(1) as Array - var typed_args := _to_typed_args(fargs) - var fsignature := _colored_value("%s(%s)" % [fname, ", ".join(typed_args)]) - return " %s %d time's" % [fsignature, times] - - -static func _to_typed_args(args :Array) -> PackedStringArray: - var typed := PackedStringArray() - for arg in args: - typed.append(_format_arg(arg) + " :" + GdObjects.type_as_string(typeof(arg))) - return typed - - -static func _format_arg(arg) -> String: - if arg is InputEvent: - return input_event_as_text(arg) - return str(arg) - - -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] - 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() - return "%s\n %s\n but was\n %s" % [_error("Expecting size:"), _colored_value(expected), _colored_value(current_size)] - - -static func error_contains_exactly(current: Array, expected: Array) -> String: - return "%s\n %s\n but was\n %s" % [_error("Expecting exactly equal:"), _colored_value(expected), _colored_value(current)] - - -static func format_chars(characters :PackedByteArray, type :Color) -> PackedByteArray: - 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", "")] - result.append_array(message.to_utf8_buffer()) - return result - - -static func format_invalid(value :String) -> String: - return "[bgcolor=#%s][color=with]%s[/color][/bgcolor]" % [SUB_COLOR.to_html(), value] - - -static func humanized(value :String) -> String: - return value.replace("_", " ") diff --git a/addons/gdUnit4/src/asserts/GdAssertReports.gd b/addons/gdUnit4/src/asserts/GdAssertReports.gd deleted file mode 100644 index 1ac9e04..0000000 --- a/addons/gdUnit4/src/asserts/GdAssertReports.gd +++ /dev/null @@ -1,55 +0,0 @@ -class_name GdAssertReports -extends RefCounted - -const LAST_ERROR = "last_assert_error_message" -const LAST_ERROR_LINE = "last_assert_error_line" - - -static func report_success() -> void: - GdUnitSignals.instance().gdunit_set_test_failed.emit(false) - GdAssertReports.set_last_error_line_number(-1) - Engine.remove_meta(LAST_ERROR) - - -static func report_warning(message :String, line_number :int) -> void: - GdUnitSignals.instance().gdunit_set_test_failed.emit(false) - send_report(GdUnitReport.new().create(GdUnitReport.WARN, line_number, message)) - - -static func report_error(message:String, line_number :int) -> void: - GdUnitSignals.instance().gdunit_set_test_failed.emit(true) - 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 _do_expect_assert_failing(): - return - send_report(GdUnitReport.new().create(GdUnitReport.FAILURE, line_number, message)) - - -static func reset_last_error_line_number() -> void: - Engine.remove_meta(LAST_ERROR_LINE) - - -static func set_last_error_line_number(line_number :int) -> void: - Engine.set_meta(LAST_ERROR_LINE, line_number) - - -static func get_last_error_line_number() -> int: - if Engine.has_meta(LAST_ERROR_LINE): - return Engine.get_meta(LAST_ERROR_LINE) - return -1 - - -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 - - -static func current_failure() -> String: - return Engine.get_meta(LAST_ERROR) - - -static func send_report(report :GdUnitReport) -> void: - var execution_context_id := GdUnitThreadManager.get_current_context().get_execution_context_id() - GdUnitSignals.instance().gdunit_report.emit(execution_context_id, report) diff --git a/addons/gdUnit4/src/asserts/GdUnitArrayAssertImpl.gd b/addons/gdUnit4/src/asserts/GdUnitArrayAssertImpl.gd deleted file mode 100644 index cc8c9da..0000000 --- a/addons/gdUnit4/src/asserts/GdUnitArrayAssertImpl.gd +++ /dev/null @@ -1,349 +0,0 @@ -extends GdUnitArrayAssert - - -var _base :GdUnitAssert -var _current_value_provider :ValueProvider - - -func _init(current): - _current_value_provider = DefaultValueProvider.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): - report_error("GdUnitArrayAssert inital error, unexpected type <%s>" % GdObjects.typeof_as_string(current)) - - -func _notification(event): - if event == NOTIFICATION_PREDELETE: - if _base != null: - _base.notification(event) - _base = null - - -func report_success() -> GdUnitArrayAssert: - _base.report_success() - return self - - -func report_error(error :String) -> GdUnitArrayAssert: - _base.report_error(error) - return self - -func failure_message() -> String: - return _base._current_error_message - - -func override_failure_message(message :String) -> GdUnitArrayAssert: - _base.override_failure_message(message) - return self - - -func _validate_value_type(value) -> bool: - return value == null or GdArrayTools.is_array_type(value) - - -func 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() - return rs if ls < rs else ls - - -func _array_equals_div(current, expected, case_sensitive :bool = false) -> Array: - var current_value := PackedStringArray(Array(current)) - var expected_value := PackedStringArray(Array(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_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_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] - for index_e in right.size(): - var e = right[index_e] - if GdObjects.equals(c, e, false, compare_mode): - GdArrayTools.erase_value(not_expect, e) - GdArrayTools.erase_value(not_found, c) - break - return [not_expect, not_found] - - -func _contains(expected, 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_value = 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_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): - return report_error("ERROR: expected value: <%s>\n is not a Array Type!" % GdObjects.typeof_as_string(expected)) - var current_value = 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_value), Array(expected), false, compare_mode): - return report_success() - # check has same elements but in different order - 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_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_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): - return report_error("ERROR: expected value: <%s>\n is not a Array Type!" % GdObjects.typeof_as_string(expected)) - var current_value = 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_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_value, expected, not_expect, not_found, compare_mode)) - - -func _not_contains(expected, 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_value = 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_value.size(): - return report_success() - var diffs2 := _array_div(compare_mode, expected, diffs[1]) - return report_error(GdAssertMessages.error_arr_not_contains(current_value, expected, diffs2[0], compare_mode)) - - -func is_null() -> GdUnitArrayAssert: - _base.is_null() - return self - - -func is_not_null() -> GdUnitArrayAssert: - _base.is_not_null() - return self - - -# Verifies that the current String is equal to the given one. -func is_equal(expected) -> 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_value = current_value() - if current_value == null and expected != null: - return report_error(GdAssertMessages.error_equal(null, expected)) - 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 = 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): - return report_error("ERROR: expected value: <%s>\n is not a Array Type!" % GdObjects.typeof_as_string(expected)) - var current_value = 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_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] - 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): - return report_error("ERROR: expected value: <%s>\n is not a Array Type!" % GdObjects.typeof_as_string(expected)) - var current_value = 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): - return report_error("ERROR: expected value: <%s>\n is not a Array Type!" % GdObjects.typeof_as_string(expected)) - var current_value = 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_value = 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_value = 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): - return report_error("ERROR: expected value: <%s>\n is not a Array Type!" % GdObjects.typeof_as_string(expected)) - var current = 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): - return report_error("ERROR: expected value: <%s>\n is not a Array Type!" % GdObjects.typeof_as_string(expected)) - var current = 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_value = 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: - return _contains(expected, GdObjects.COMPARE_MODE.PARAMETER_DEEP_TEST) - - -func contains_exactly(expected) -> GdUnitArrayAssert: - return _contains_exactly(expected, GdObjects.COMPARE_MODE.PARAMETER_DEEP_TEST) - - -func contains_exactly_in_any_order(expected) -> GdUnitArrayAssert: - return _contains_exactly_in_any_order(expected, GdObjects.COMPARE_MODE.PARAMETER_DEEP_TEST) - - -func contains_same(expected) -> GdUnitArrayAssert: - return _contains(expected, GdObjects.COMPARE_MODE.OBJECT_REFERENCE) - - -func contains_same_exactly(expected) -> GdUnitArrayAssert: - return _contains_exactly(expected, GdObjects.COMPARE_MODE.OBJECT_REFERENCE) - - -func contains_same_exactly_in_any_order(expected) -> GdUnitArrayAssert: - return _contains_exactly_in_any_order(expected, GdObjects.COMPARE_MODE.OBJECT_REFERENCE) - - -func not_contains(expected) -> GdUnitArrayAssert: - return _not_contains(expected, GdObjects.COMPARE_MODE.PARAMETER_DEEP_TEST) - - -func not_contains_same(expected) -> GdUnitArrayAssert: - return _not_contains(expected, GdObjects.COMPARE_MODE.OBJECT_REFERENCE) - - -func is_instanceof(expected) -> 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_value() - if current == null: - _current_value_provider = DefaultValueProvider.new(null) - else: - for element 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, - extr2 :GdUnitValueExtractor = null, - extr3 :GdUnitValueExtractor = null, - extr4 :GdUnitValueExtractor = null, - extr5 :GdUnitValueExtractor = null, - extr6 :GdUnitValueExtractor = null, - extr7 :GdUnitValueExtractor = null, - extr8 :GdUnitValueExtractor = null, - 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_value() - if current == null: - _current_value_provider = DefaultValueProvider.new(null) - else: - for element in current_value(): - 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(): - 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: - extracted_elements.append(ev[0]) - _current_value_provider = DefaultValueProvider.new(extracted_elements) - return self diff --git a/addons/gdUnit4/src/asserts/GdUnitAssertImpl.gd b/addons/gdUnit4/src/asserts/GdUnitAssertImpl.gd deleted file mode 100644 index 30694b0..0000000 --- a/addons/gdUnit4/src/asserts/GdUnitAssertImpl.gd +++ /dev/null @@ -1,71 +0,0 @@ -extends GdUnitAssert - - -var _current :Variant -var _current_error_message :String = "" -var _custom_failure_message :String = "" - - -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: - return _current_error_message - - -func current_value() -> Variant: - return _current - - -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 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) - return self - - -func test_fail(): - return report_error(GdAssertMessages.error_not_implemented()) - - -func override_failure_message(message :String): - _custom_failure_message = message - return self - - -func is_equal(expected) -> GdUnitAssert: - var current = 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_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_value() - if current != null: - return report_error(GdAssertMessages.error_is_null(current)) - return report_success() - - -func is_not_null() -> GdUnitAssert: - var current = 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 deleted file mode 100644 index a452791..0000000 --- a/addons/gdUnit4/src/asserts/GdUnitAssertions.gd +++ /dev/null @@ -1,63 +0,0 @@ -# Preloads all GdUnit assertions -class_name GdUnitAssertions -extends RefCounted - - -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") - GdUnitAssertions.__lazy_load("res://addons/gdUnit4/src/asserts/GdUnitIntAssertImpl.gd") - GdUnitAssertions.__lazy_load("res://addons/gdUnit4/src/asserts/GdUnitFloatAssertImpl.gd") - GdUnitAssertions.__lazy_load("res://addons/gdUnit4/src/asserts/GdUnitVectorAssertImpl.gd") - GdUnitAssertions.__lazy_load("res://addons/gdUnit4/src/asserts/GdUnitArrayAssertImpl.gd") - GdUnitAssertions.__lazy_load("res://addons/gdUnit4/src/asserts/GdUnitDictionaryAssertImpl.gd") - GdUnitAssertions.__lazy_load("res://addons/gdUnit4/src/asserts/GdUnitFileAssertImpl.gd") - GdUnitAssertions.__lazy_load("res://addons/gdUnit4/src/asserts/GdUnitObjectAssertImpl.gd") - GdUnitAssertions.__lazy_load("res://addons/gdUnit4/src/asserts/GdUnitResultAssertImpl.gd") - GdUnitAssertions.__lazy_load("res://addons/gdUnit4/src/asserts/GdUnitFuncAssertImpl.gd") - GdUnitAssertions.__lazy_load("res://addons/gdUnit4/src/asserts/GdUnitSignalAssertImpl.gd") - GdUnitAssertions.__lazy_load("res://addons/gdUnit4/src/asserts/GdUnitFailureAssertImpl.gd") - GdUnitAssertions.__lazy_load("res://addons/gdUnit4/src/asserts/GdUnitGodotErrorAssertImpl.gd") - - -### We now load all used asserts and tool scripts into the cache according to the principle of "lazy loading" -### 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, 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 deleted file mode 100644 index c2cdd34..0000000 --- a/addons/gdUnit4/src/asserts/GdUnitBoolAssertImpl.gd +++ /dev/null @@ -1,76 +0,0 @@ -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) - # save the actual assert instance on the current thread context - GdUnitThreadManager.get_current_context().set_assert(self) - 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): - if event == NOTIFICATION_PREDELETE: - if _base != null: - _base.notification(event) - _base = null - - -func current_value(): - return _base.current_value() - - -func report_success() -> GdUnitBoolAssert: - _base.report_success() - return self - - -func report_error(error :String) -> GdUnitBoolAssert: - _base.report_error(error) - return self - - -func failure_message() -> String: - return _base._current_error_message - - -func override_failure_message(message :String) -> GdUnitBoolAssert: - _base.override_failure_message(message) - return self - - -# Verifies that the current value is null. -func is_null() -> GdUnitBoolAssert: - _base.is_null() - return self - - -# Verifies that the current value is not null. -func is_not_null() -> GdUnitBoolAssert: - _base.is_not_null() - return self - - -func is_equal(expected) -> GdUnitBoolAssert: - _base.is_equal(expected) - return self - - -func is_not_equal(expected) -> GdUnitBoolAssert: - _base.is_not_equal(expected) - return self - - -func is_true() -> GdUnitBoolAssert: - if current_value() != true: - return report_error(GdAssertMessages.error_is_true(current_value())) - return report_success() - - -func is_false() -> GdUnitBoolAssert: - 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 deleted file mode 100644 index 1d9dab2..0000000 --- a/addons/gdUnit4/src/asserts/GdUnitDictionaryAssertImpl.gd +++ /dev/null @@ -1,182 +0,0 @@ -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) - # save the actual assert instance on the current thread context - GdUnitThreadManager.get_current_context().set_assert(self) - 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): - if event == NOTIFICATION_PREDELETE: - if _base != null: - _base.notification(event) - _base = null - - -func report_success() -> GdUnitDictionaryAssert: - _base.report_success() - return self - - -func report_error(error :String) -> GdUnitDictionaryAssert: - _base.report_error(error) - return self - - -func failure_message() -> String: - return _base._current_error_message - - -func override_failure_message(message :String) -> GdUnitDictionaryAssert: - _base.override_failure_message(message) - return self - - -func current_value() -> Variant: - return _base.current_value() - - -func is_null() -> GdUnitDictionaryAssert: - _base.is_null() - return self - - -func is_not_null() -> GdUnitDictionaryAssert: - _base.is_not_null() - return self - - -func is_equal(expected) -> GdUnitDictionaryAssert: - var current = current_value() - if current == null: - 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 diff := GdDiffTool.string_diff(c, 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_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_value() - if current == null: - 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 diff := GdDiffTool.string_diff(c, 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_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_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_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_value() - if current == null: - return report_error(GdAssertMessages.error_is_not_null()) - if current.size() != expected: - return report_error(GdAssertMessages.error_has_size(current, expected)) - return report_success() - - -func _contains_keys(expected :Array, compare_mode :GdObjects.COMPARE_MODE) -> GdUnitDictionaryAssert: - var current = current_value() - if current == null: - return report_error(GdAssertMessages.error_is_not_null()) - # find expected keys - 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_value() - var expected := [key] - if current == null: - return report_error(GdAssertMessages.error_is_not_null()) - 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)) - 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_value() - if current == null: - return report_error(GdAssertMessages.error_is_not_null()) - 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() - - -func contains_keys(expected :Array) -> GdUnitDictionaryAssert: - return _contains_keys(expected, GdObjects.COMPARE_MODE.PARAMETER_DEEP_TEST) - - -func contains_key_value(key, value) -> GdUnitDictionaryAssert: - return _contains_key_value(key, value, GdObjects.COMPARE_MODE.PARAMETER_DEEP_TEST) - - -func not_contains_keys(expected :Array) -> GdUnitDictionaryAssert: - return _not_contains_keys(expected, GdObjects.COMPARE_MODE.PARAMETER_DEEP_TEST) - - -func contains_same_keys(expected :Array) -> GdUnitDictionaryAssert: - return _contains_keys(expected, GdObjects.COMPARE_MODE.OBJECT_REFERENCE) - - -func contains_same_key_value(key, value) -> 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 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 deleted file mode 100644 index bcb949f..0000000 --- a/addons/gdUnit4/src/asserts/GdUnitFailureAssertImpl.gd +++ /dev/null @@ -1,110 +0,0 @@ -extends GdUnitFailureAssert - -const GdUnitTools := preload("res://addons/gdUnit4/src/core/GdUnitTools.gd") - -var _is_failed := false -var _failure_message :String - - -static func _set_do_expect_fail(enabled :bool = true): - 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 - _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) - # execute the given assertion as callable - if do_await: - await assertion.call() - else: - assertion.call() - _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 self - _failure_message = current_assert.failure_message() - return self - - -func execute(assertion :Callable) -> GdUnitFailureAssert: - execute_and_await(assertion, false) - return self - - -func _on_test_failed(value :bool) -> void: - _is_failed = value - - -@warning_ignore("unused_parameter") -func is_equal(_expected :GdUnitAssert) -> GdUnitFailureAssert: - return _report_error("Not implemented") - - -@warning_ignore("unused_parameter") -func is_not_equal(_expected :GdUnitAssert) -> GdUnitFailureAssert: - return _report_error("Not implemented") - - -func is_null() -> GdUnitFailureAssert: - return _report_error("Not implemented") - - -func is_not_null() -> GdUnitFailureAssert: - return _report_error("Not implemented") - - -func is_success() -> GdUnitFailureAssert: - if _is_failed: - return _report_error("Expect: assertion ends successfully.") - return self - - -func is_failed() -> GdUnitFailureAssert: - if not _is_failed: - return _report_error("Expect: assertion fails.") - return self - - -func has_line(expected :int) -> GdUnitFailureAssert: - var current := GdAssertReports.get_last_error_line_number() - if current != expected: - return _report_error("Expect: to failed on line '%d'\n but was '%d'." % [expected, current]) - return self - - -func has_message(expected :String) -> GdUnitFailureAssert: - 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]) - _report_error(GdAssertMessages.error_not_same_error(current, expected_error)) - return self - - -func starts_with_message(expected :String) -> GdUnitFailureAssert: - var expected_error := GdUnitTools.normalize_text(expected) - 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]) - _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 GdUnitAssertions.get_line_number() - GdAssertReports.report_error(error_message, line_number) - return self - - -func _report_success() -> GdUnitFailureAssert: - GdAssertReports.report_success() - return self diff --git a/addons/gdUnit4/src/asserts/GdUnitFileAssertImpl.gd b/addons/gdUnit4/src/asserts/GdUnitFileAssertImpl.gd deleted file mode 100644 index b23212a..0000000 --- a/addons/gdUnit4/src/asserts/GdUnitFileAssertImpl.gd +++ /dev/null @@ -1,95 +0,0 @@ -extends GdUnitFileAssert - -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) - # save the actual assert instance on the current thread context - GdUnitThreadManager.get_current_context().set_assert(self) - 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): - if event == NOTIFICATION_PREDELETE: - if _base != null: - _base.notification(event) - _base = null - - -func current_value() -> String: - return _base.current_value() as String - - -func report_success() -> GdUnitFileAssert: - _base.report_success() - return self - - -func report_error(error :String) -> GdUnitFileAssert: - _base.report_error(error) - return self - - -func failure_message() -> String: - return _base._current_error_message - - -func override_failure_message(message :String) -> GdUnitFileAssert: - _base.override_failure_message(message) - return self - - -func is_equal(expected) -> GdUnitFileAssert: - _base.is_equal(expected) - return self - - -func is_not_equal(expected) -> GdUnitFileAssert: - _base.is_not_equal(expected) - return self - - -func is_file() -> GdUnitFileAssert: - 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_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_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) - 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_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) - if script is GDScript: - var instance = 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) - return self diff --git a/addons/gdUnit4/src/asserts/GdUnitFloatAssertImpl.gd b/addons/gdUnit4/src/asserts/GdUnitFloatAssertImpl.gd deleted file mode 100644 index c0cd4b4..0000000 --- a/addons/gdUnit4/src/asserts/GdUnitFloatAssertImpl.gd +++ /dev/null @@ -1,144 +0,0 @@ -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) - # save the actual assert instance on the current thread context - GdUnitThreadManager.get_current_context().set_assert(self) - 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): - if event == NOTIFICATION_PREDELETE: - if _base != null: - _base.notification(event) - _base = null - - -func current_value(): - return _base.current_value() - - -func report_success() -> GdUnitFloatAssert: - _base.report_success() - return self - - -func report_error(error :String) -> GdUnitFloatAssert: - _base.report_error(error) - return self - - -func failure_message() -> String: - return _base._current_error_message - - -func override_failure_message(message :String) -> GdUnitFloatAssert: - _base.override_failure_message(message) - return self - - -func is_null() -> GdUnitFloatAssert: - _base.is_null() - return self - - -func is_not_null() -> GdUnitFloatAssert: - _base.is_not_null() - return self - - -func is_equal(expected :float) -> GdUnitFloatAssert: - _base.is_equal(expected) - return self - - -func is_not_equal(expected :float) -> GdUnitFloatAssert: - _base.is_not_equal(expected) - return self - - -@warning_ignore("shadowed_global_identifier") -func is_equal_approx(expected :float, approx :float) -> GdUnitFloatAssert: - return is_between(expected-approx, expected+approx) - - -func is_less(expected :float) -> GdUnitFloatAssert: - var current = 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_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_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_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_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_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_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_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_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_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_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 deleted file mode 100644 index 4902ba0..0000000 --- a/addons/gdUnit4/src/asserts/GdUnitFuncAssertImpl.gd +++ /dev/null @@ -1,159 +0,0 @@ -extends GdUnitFuncAssert - - -const GdUnitTools := preload("res://addons/gdUnit4/src/core/GdUnitTools.gd") -const DEFAULT_TIMEOUT := 2000 - - -var _current_value_provider :ValueProvider -var _current_error_message :String = "" -var _custom_failure_message :String = "" -var _line_number := -1 -var _timeout := DEFAULT_TIMEOUT -var _interrupted := false -var _sleep_timer :Timer = null - - -func _init(instance :Object, func_name :String, args := Array()): - _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) - # verify at first the function name exists - if not instance.has_method(func_name): - report_error("The function '%s' do not exists checked instance '%s'." % [func_name, instance]) - _interrupted = true - else: - _current_value_provider = CallBackValueProvider.new(instance, func_name, args) - - -func _notification(_what): - if is_instance_valid(_current_value_provider): - _current_value_provider.dispose() - _current_value_provider = null - if is_instance_valid(_sleep_timer): - Engine.get_main_loop().root.remove_child(_sleep_timer) - _sleep_timer.stop() - _sleep_timer.free() - _sleep_timer = null - - -func report_success() -> GdUnitFuncAssert: - GdAssertReports.report_success() - return self - - -func report_error(error_message :String) -> GdUnitFuncAssert: - _current_error_message = error_message if _custom_failure_message == "" else _custom_failure_message - GdAssertReports.report_error(_current_error_message, _line_number) - return self - - -func failure_message() -> String: - return _current_error_message - - -func send_report(report :GdUnitReport)-> void: - GdUnitSignals.instance().gdunit_report.emit(report) - - -func override_failure_message(message :String) -> GdUnitFuncAssert: - _custom_failure_message = message - return self - - -func wait_until(timeout := 2000) -> GdUnitFuncAssert: - if timeout <= 0: - push_warning("Invalid timeout param, alloed timeouts must be grater than 0. Use default timeout instead") - _timeout = DEFAULT_TIMEOUT - else: - _timeout = timeout - return self - - -func is_null() -> GdUnitFuncAssert: - await _validate_callback(cb_is_null) - return self - - -func is_not_null() -> GdUnitFuncAssert: - await _validate_callback(cb_is_not_null) - return self - - -func is_false() -> GdUnitFuncAssert: - await _validate_callback(cb_is_false) - return self - - -func is_true() -> GdUnitFuncAssert: - await _validate_callback(cb_is_true) - return self - - -func is_equal(expected) -> GdUnitFuncAssert: - await _validate_callback(cb_is_equal, expected) - return self - - -func is_not_equal(expected) -> 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 cb_is_null(c, _e): return c == null -func cb_is_not_null(c, _e): return c != null -func cb_is_false(c, _e): return c == false -func cb_is_true(c, _e): return c == true -func cb_is_equal(c, e): return GdObjects.equals(c,e) -func cb_is_not_equal(c, e): return not GdObjects.equals(c, e) - - -func _validate_callback(predicate :Callable, expected = null): - if _interrupted: - return - GdUnitMemoryObserver.guard_instance(self) - 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(): - _interrupted = true - , CONNECT_DEFERRED) - timer.set_one_shot(true) - timer.start((_timeout/1000.0)*time_scale) - _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() - # 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("cb_"), expected, LocalTime.elapsed(_timeout))) - else: - report_success() - _sleep_timer.free() - timer.free() - GdUnitMemoryObserver.unguard_instance(self) - - -func next_current_value() -> Variant: - @warning_ignore("redundant_await") - if is_instance_valid(_current_value_provider): - return await _current_value_provider.get_value() - return "invalid value" diff --git a/addons/gdUnit4/src/asserts/GdUnitGodotErrorAssertImpl.gd b/addons/gdUnit4/src/asserts/GdUnitGodotErrorAssertImpl.gd deleted file mode 100644 index dcd6a9b..0000000 --- a/addons/gdUnit4/src/asserts/GdUnitGodotErrorAssertImpl.gd +++ /dev/null @@ -1,106 +0,0 @@ -extends GdUnitGodotErrorAssert - -var _current_error_message :String -var _callable :Callable - - -func _init(callable :Callable): - # 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!") - # save the actual assert instance on the current thread context - GdUnitThreadManager.get_current_context().set_assert(self) - GdAssertReports.reset_last_error_line_number() - _callable = callable - - -func _execute() -> Array[ErrorLogEntry]: - # execute the given code and monitor for runtime errors - if _callable == null or not _callable.is_valid(): - _report_error("Invalid Callable '%s'" % _callable) - else: - await _callable.call() - return await _error_monitor().scan(true) - - -func _error_monitor() -> GodotGdErrorMonitor: - return GdUnitThreadManager.get_current_context().get_execution_context().error_monitor - - -func failure_message() -> String: - return _current_error_message - - -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 GdUnitAssertions.get_line_number() - _current_error_message = error_message - GdAssertReports.report_error(error_message, line_number) - return self - - -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 - - -func _to_list(log_entries :Array[ErrorLogEntry]) -> String: - if log_entries.is_empty(): - return "no errors" - if log_entries.size() == 1: - return log_entries[0]._message - var value := "" - for entry in log_entries: - value += "'%s'\n" % entry._message - return value - - -func is_success() -> GdUnitGodotErrorAssert: - var log_entries := await _execute() - if log_entries.is_empty(): - return _report_success() - return _report_error(""" - Expecting: no error's are ocured. - but found: '%s' - """.dedent().trim_prefix("\n") % _to_list(log_entries)) - - -func is_runtime_error(expected_error :String) -> GdUnitGodotErrorAssert: - var log_entries := await _execute() - if _has_log_entry(log_entries, ErrorLogEntry.TYPE.SCRIPT_ERROR, expected_error): - return _report_success() - return _report_error(""" - Expecting: a runtime error is triggered. - message: '%s' - found: %s - """.dedent().trim_prefix("\n") % [expected_error, _to_list(log_entries)]) - - -func is_push_warning(expected_warning :String) -> GdUnitGodotErrorAssert: - var log_entries := await _execute() - if _has_log_entry(log_entries, ErrorLogEntry.TYPE.PUSH_WARNING, expected_warning): - return _report_success() - return _report_error(""" - Expecting: push_warning() is called. - message: '%s' - found: %s - """.dedent().trim_prefix("\n") % [expected_warning, _to_list(log_entries)]) - - -func is_push_error(expected_error :String) -> GdUnitGodotErrorAssert: - var log_entries := await _execute() - if _has_log_entry(log_entries, ErrorLogEntry.TYPE.PUSH_ERROR, expected_error): - return _report_success() - return _report_error(""" - Expecting: push_error() is called. - message: '%s' - found: %s - """.dedent().trim_prefix("\n") % [expected_error, _to_list(log_entries)]) diff --git a/addons/gdUnit4/src/asserts/GdUnitIntAssertImpl.gd b/addons/gdUnit4/src/asserts/GdUnitIntAssertImpl.gd deleted file mode 100644 index 6fc16e1..0000000 --- a/addons/gdUnit4/src/asserts/GdUnitIntAssertImpl.gd +++ /dev/null @@ -1,153 +0,0 @@ -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) - # save the actual assert instance on the current thread context - GdUnitThreadManager.get_current_context().set_assert(self) - 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): - if event == NOTIFICATION_PREDELETE: - if _base != null: - _base.notification(event) - _base = null - - -func current_value() -> Variant: - return _base.current_value() - - -func report_success() -> GdUnitIntAssert: - _base.report_success() - return self - - -func report_error(error :String) -> GdUnitIntAssert: - _base.report_error(error) - return self - - -func failure_message() -> String: - return _base._current_error_message - - -func override_failure_message(message :String) -> GdUnitIntAssert: - _base.override_failure_message(message) - return self - - -func is_null() -> GdUnitIntAssert: - _base.is_null() - return self - - -func is_not_null() -> GdUnitIntAssert: - _base.is_not_null() - return self - - -func is_equal(expected :int) -> GdUnitIntAssert: - _base.is_equal(expected) - return self - - -func is_not_equal(expected :int) -> GdUnitIntAssert: - _base.is_not_equal(expected) - return self - - -func is_less(expected :int) -> GdUnitIntAssert: - var current = 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_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_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_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_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_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_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_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_value() - if current != 0: - return report_error(GdAssertMessages.error_is_zero(current)) - return report_success() - - -func is_not_zero() -> GdUnitIntAssert: - var current = 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_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_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_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 deleted file mode 100644 index 99a2a21..0000000 --- a/addons/gdUnit4/src/asserts/GdUnitObjectAssertImpl.gd +++ /dev/null @@ -1,109 +0,0 @@ -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) - # save the actual assert instance on the current thread context - GdUnitThreadManager.get_current_context().set_assert(self) - if (current != null - 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): - if event == NOTIFICATION_PREDELETE: - if _base != null: - _base.notification(event) - _base = null - - -func current_value() -> Variant: - return _base.current_value() - - -func report_success() -> GdUnitObjectAssert: - _base.report_success() - return self - - -func report_error(error :String) -> GdUnitObjectAssert: - _base.report_error(error) - return self - - -func failure_message() -> String: - return _base._current_error_message - - -func override_failure_message(message :String) -> GdUnitObjectAssert: - _base.override_failure_message(message) - return self - - -func is_equal(expected) -> GdUnitObjectAssert: - _base.is_equal(expected) - return self - - -func is_not_equal(expected) -> GdUnitObjectAssert: - _base.is_not_equal(expected) - return self - - -func is_null() -> GdUnitObjectAssert: - _base.is_null() - return self - - -func is_not_null() -> GdUnitObjectAssert: - _base.is_not_null() - return self - - -@warning_ignore("shadowed_global_identifier") -func is_same(expected) -> GdUnitObjectAssert: - var current :Variant = current_value() - if not is_same(current, expected): - report_error(GdAssertMessages.error_is_same(current, expected)) - return self - report_success() - return self - - -func is_not_same(expected) -> GdUnitObjectAssert: - var current = current_value() - if is_same(current, expected): - report_error(GdAssertMessages.error_not_same(current, expected)) - return self - report_success() - return self - - -func is_instanceof(type :Object) -> GdUnitObjectAssert: - var current = 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)) - return self - report_success() - return self - - -func is_not_instanceof(type) -> GdUnitObjectAssert: - var current :Variant = current_value() - if is_instance_of(current, type): - var result: = GdObjects.extract_class_name(type) - if result.is_success(): - report_error("Expected not be a instance of <%s>" % result.value()) - else: - push_error("Internal ERROR: %s" % result.error_message()) - return self - report_success() - return self diff --git a/addons/gdUnit4/src/asserts/GdUnitResultAssertImpl.gd b/addons/gdUnit4/src/asserts/GdUnitResultAssertImpl.gd deleted file mode 100644 index 9598b3e..0000000 --- a/addons/gdUnit4/src/asserts/GdUnitResultAssertImpl.gd +++ /dev/null @@ -1,121 +0,0 @@ -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) - # save the actual assert instance on the current thread context - GdUnitThreadManager.get_current_context().set_assert(self) - if not validate_value_type(current): - report_error("GdUnitResultAssert inital error, unexpected type <%s>" % GdObjects.typeof_as_string(current)) - - -func _notification(event): - if event == NOTIFICATION_PREDELETE: - if _base != null: - _base.notification(event) - _base = null - - -func validate_value_type(value) -> bool: - return value == null or value is GdUnitResult - - -func current_value() -> GdUnitResult: - return _base.current_value() as GdUnitResult - - -func report_success() -> GdUnitResultAssert: - _base.report_success() - return self - - -func report_error(error :String) -> GdUnitResultAssert: - _base.report_error(error) - return self - - -func failure_message() -> String: - return _base._current_error_message - - -func override_failure_message(message :String) -> GdUnitResultAssert: - _base.override_failure_message(message) - return self - - -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_value() - if result == null or not result.is_empty(): - report_error(GdAssertMessages.error_result_is_empty(result)) - else: - report_success() - return self - - -func is_success() -> GdUnitResultAssert: - var result := current_value() - if result == null or not result.is_success(): - report_error(GdAssertMessages.error_result_is_success(result)) - else: - report_success() - return self - - -func is_warning() -> GdUnitResultAssert: - var result := current_value() - if result == null or not result.is_warn(): - report_error(GdAssertMessages.error_result_is_warning(result)) - else: - report_success() - return self - - -func is_error() -> GdUnitResultAssert: - var result := current_value() - if result == null or not result.is_error(): - report_error(GdAssertMessages.error_result_is_error(result)) - else: - report_success() - return self - - -func contains_message(expected :String) -> GdUnitResultAssert: - var result := current_value() - if result == null: - report_error(GdAssertMessages.error_result_has_message("", expected)) - return self - if result.is_success(): - report_error(GdAssertMessages.error_result_has_message_on_success(expected)) - elif result.is_error() and result.error_message() != expected: - report_error(GdAssertMessages.error_result_has_message(result.error_message(), expected)) - elif result.is_warn() and result.warn_message() != expected: - report_error(GdAssertMessages.error_result_has_message(result.warn_message(), expected)) - else: - report_success() - return self - - -func is_value(expected) -> GdUnitResultAssert: - var result := current_value() - var value = null if result == null else result.value() - if not GdObjects.equals(value, expected): - report_error(GdAssertMessages.error_result_is_value(value, expected)) - else: - report_success() - return self - - -func is_equal(expected) -> GdUnitResultAssert: - return is_value(expected) diff --git a/addons/gdUnit4/src/asserts/GdUnitSignalAssertImpl.gd b/addons/gdUnit4/src/asserts/GdUnitSignalAssertImpl.gd deleted file mode 100644 index 4ef2eeb..0000000 --- a/addons/gdUnit4/src/asserts/GdUnitSignalAssertImpl.gd +++ /dev/null @@ -1,110 +0,0 @@ -extends GdUnitSignalAssert - -const DEFAULT_TIMEOUT := 2000 - -var _signal_collector :GdUnitSignalCollector -var _emitter :Object -var _current_error_message :String = "" -var _custom_failure_message :String = "" -var _line_number := -1 -var _timeout := DEFAULT_TIMEOUT -var _interrupted := false - - -func _init(emitter :Object): - # 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 = GdUnitAssertions.get_line_number() - _emitter = emitter - GdAssertReports.reset_last_error_line_number() - - -func report_success() -> GdUnitAssert: - GdAssertReports.report_success() - return self - - -func report_warning(message :String) -> GdUnitAssert: - GdAssertReports.report_warning(message, GdUnitAssertions.get_line_number()) - return self - - -func report_error(error_message :String) -> GdUnitAssert: - _current_error_message = error_message if _custom_failure_message == "" else _custom_failure_message - GdAssertReports.report_error(_current_error_message, _line_number) - return self - - -func failure_message() -> String: - return _current_error_message - - -func send_report(report :GdUnitReport)-> void: - GdUnitSignals.instance().gdunit_report.emit(report) - - -func override_failure_message(message :String) -> GdUnitSignalAssert: - _custom_failure_message = message - return self - - -func wait_until(timeout := 2000) -> GdUnitSignalAssert: - if timeout <= 0: - report_warning("Invalid timeout parameter, allowed timeouts must be greater than 0, use default timeout instead!") - _timeout = DEFAULT_TIMEOUT - else: - _timeout = timeout - return self - - -# Verifies the signal exists checked the emitter -func is_signal_exists(signal_name :String) -> GdUnitSignalAssert: - if not _emitter.has_signal(signal_name): - report_error("The signal '%s' not exists checked object '%s'." % [signal_name, _emitter.get_class()]) - return self - - -# Verifies that given signal is emitted until waiting time -func is_emitted(name :String, args := []) -> GdUnitSignalAssert: - _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 = GdUnitAssertions.get_line_number() - return await _wail_until_signal(name, args, true) - - -func _wail_until_signal(signal_name :String, expected_args :Array, expect_not_emitted: bool) -> GdUnitSignalAssert: - if _emitter == null: - report_error("Can't wait for signal checked a NULL object.") - return self - # first verify the signal is defined - if not _emitter.has_signal(signal_name): - 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 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.start((_timeout/1000.0)*time_scale) - 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_name, expected_args) - return self diff --git a/addons/gdUnit4/src/asserts/GdUnitStringAssertImpl.gd b/addons/gdUnit4/src/asserts/GdUnitStringAssertImpl.gd deleted file mode 100644 index 8c1814f..0000000 --- a/addons/gdUnit4/src/asserts/GdUnitStringAssertImpl.gd +++ /dev/null @@ -1,176 +0,0 @@ -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) - # save the actual assert instance on the current thread context - GdUnitThreadManager.get_current_context().set_assert(self) - 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): - if event == NOTIFICATION_PREDELETE: - if _base != null: - _base.notification(event) - _base = null - - -func failure_message() -> String: - return _base._current_error_message - - -func current_value(): - var current = _base.current_value() - if current == null: - return null - return current as String - - -func report_success() -> GdUnitStringAssert: - _base.report_success() - return self - - -func report_error(error :String) -> GdUnitStringAssert: - _base.report_error(error) - return self - - -func override_failure_message(message :String) -> GdUnitStringAssert: - _base.override_failure_message(message) - return self - - -func is_null() -> GdUnitStringAssert: - _base.is_null() - return self - - -func is_not_null() -> GdUnitStringAssert: - _base.is_not_null() - return self - - -func is_equal(expected) -> GdUnitStringAssert: - var current = 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]) - return report_error(GdAssertMessages.error_equal(formatted_current, expected)) - return report_success() - - -func is_equal_ignoring_case(expected) -> GdUnitStringAssert: - var current = current_value() - if current == null: - return report_error(GdAssertMessages.error_equal_ignoring_case(current, expected)) - if not GdObjects.equals(current, expected, true): - var diffs := GdDiffTool.string_diff(current, expected) - 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_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_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_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_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_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_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_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_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_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_value() - if current == null: - return report_error(GdAssertMessages.error_ends_with(current, expected)) - var find = current.length() - expected.length() - if current.rfind(expected) != find: - return report_error(GdAssertMessages.error_ends_with(current, expected)) - return report_success() - - -# gdlint:disable=max-returns -func has_length(expected :int, comparator :int = Comparator.EQUAL) -> GdUnitStringAssert: - var current = current_value() - if current == null: - return report_error(GdAssertMessages.error_has_length(current, expected, comparator)) - match comparator: - Comparator.EQUAL: - if current.length() != expected: - return report_error(GdAssertMessages.error_has_length(current, expected, comparator)) - Comparator.LESS_THAN: - if current.length() >= expected: - return report_error(GdAssertMessages.error_has_length(current, expected, comparator)) - Comparator.LESS_EQUAL: - if current.length() > expected: - return report_error(GdAssertMessages.error_has_length(current, expected, comparator)) - Comparator.GREATER_THAN: - if current.length() <= expected: - return report_error(GdAssertMessages.error_has_length(current, expected, comparator)) - Comparator.GREATER_EQUAL: - if current.length() < expected: - return report_error(GdAssertMessages.error_has_length(current, expected, comparator)) - _: - return report_error("Comparator '%d' not implemented!" % comparator) - return report_success() diff --git a/addons/gdUnit4/src/asserts/GdUnitVectorAssertImpl.gd b/addons/gdUnit4/src/asserts/GdUnitVectorAssertImpl.gd deleted file mode 100644 index 9fbe25c..0000000 --- a/addons/gdUnit4/src/asserts/GdUnitVectorAssertImpl.gd +++ /dev/null @@ -1,172 +0,0 @@ -extends GdUnitVectorAssert - -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) - # save the actual assert instance on the current thread context - GdUnitThreadManager.get_current_context().set_assert(self) - if not _validate_value_type(current): - report_error("GdUnitVectorAssert error, the type <%s> is not supported." % GdObjects.typeof_as_string(current)) - _current_type = typeof(current) - - -func _notification(event): - if event == NOTIFICATION_PREDELETE: - if _base != null: - _base.notification(event) - _base = null - - -func _validate_value_type(value) -> bool: - return ( - value == null - or typeof(value) in [ - TYPE_VECTOR2, - TYPE_VECTOR2I, - TYPE_VECTOR3, - TYPE_VECTOR3I, - TYPE_VECTOR4, - TYPE_VECTOR4I - ] - ) - - -func _validate_is_vector_type(value :Variant) -> bool: - var type := typeof(value) - if type == _current_type or _current_type == TYPE_NIL: - return true - report_error(GdAssertMessages.error_is_wrong_type(_current_type, type)) - return false - - -func current_value() -> Variant: - return _base.current_value() - - -func report_success() -> GdUnitVectorAssert: - _base.report_success() - return self - - -func report_error(error :String) -> GdUnitVectorAssert: - _base.report_error(error) - return self - - -func failure_message() -> String: - return _base._current_error_message - - -func override_failure_message(message :String) -> GdUnitVectorAssert: - _base.override_failure_message(message) - return self - - -func is_null() -> GdUnitVectorAssert: - _base.is_null() - return self - - -func is_not_null() -> GdUnitVectorAssert: - _base.is_not_null() - return self - - -func is_equal(expected :Variant) -> GdUnitVectorAssert: - if not _validate_is_vector_type(expected): - return self - _base.is_equal(expected) - return self - - -func is_not_equal(expected :Variant) -> GdUnitVectorAssert: - if not _validate_is_vector_type(expected): - return self - _base.is_not_equal(expected) - return self - - -@warning_ignore("shadowed_global_identifier") -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_value() - var from = expected - approx - var to = 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: - match typeof(current): - TYPE_VECTOR2, TYPE_VECTOR2I: - return ((current.x >= from.x and current.y >= from.y) - and (current.x <= to.x and current.y <= to.y)) - TYPE_VECTOR3, TYPE_VECTOR3I: - return ((current.x >= from.x and current.y >= from.y and current.z >= from.z) - and (current.x <= to.x and current.y <= to.y and current.z <= to.z)) - TYPE_VECTOR4, TYPE_VECTOR4I: - return ((current.x >= from.x and current.y >= from.y and current.z >= from.z and current.w >= from.w) - and (current.x <= to.x and current.y <= to.y and current.z <= to.z and current.w <= to.w)) - _: - push_error("Missing implementation '_is_equal_approx' for vector type %s" % typeof(current)) - return false - - -func is_less(expected :Variant) -> GdUnitVectorAssert: - if not _validate_is_vector_type(expected): - return self - var current = 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 :Variant) -> GdUnitVectorAssert: - if not _validate_is_vector_type(expected): - return self - var current = 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 :Variant) -> GdUnitVectorAssert: - if not _validate_is_vector_type(expected): - return self - var current = 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 :Variant) -> GdUnitVectorAssert: - if not _validate_is_vector_type(expected): - return self - var current = 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_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_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() - - -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_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 deleted file mode 100644 index 5150f4c..0000000 --- a/addons/gdUnit4/src/asserts/ValueProvider.gd +++ /dev/null @@ -1,6 +0,0 @@ -# base interface for assert value provider -class_name ValueProvider -extends RefCounted - -func get_value(): - pass diff --git a/addons/gdUnit4/src/cmd/CmdArgumentParser.gd b/addons/gdUnit4/src/cmd/CmdArgumentParser.gd deleted file mode 100644 index 350e53c..0000000 --- a/addons/gdUnit4/src/cmd/CmdArgumentParser.gd +++ /dev/null @@ -1,61 +0,0 @@ -class_name CmdArgumentParser -extends RefCounted - -var _options :CmdOptions -var _tool_name :String -var _parsed_commands :Dictionary = Dictionary() - - -func _init(p_options :CmdOptions, p_tool_name :String): - _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()) - elif not ignore_unknown_cmd: - return GdUnitResult.error("Unknown '%s' command!" % cmd) - return GdUnitResult.success(_parsed_commands.values()) - - -func options() -> CmdOptions: - return _options - - -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 - if _is_next_value_argument(args): - command.add_argument(args.pop_front()) - elif not option.is_argument_optional(): - return -1 - _parsed_commands[command_name] = command - return 0 - - -func _is_next_value_argument(args :Array) -> bool: - if args.is_empty(): - return false - return _options.get_option(args[0]) == null diff --git a/addons/gdUnit4/src/cmd/CmdCommand.gd b/addons/gdUnit4/src/cmd/CmdCommand.gd deleted file mode 100644 index c9c3414..0000000 --- a/addons/gdUnit4/src/cmd/CmdCommand.gd +++ /dev/null @@ -1,26 +0,0 @@ -class_name CmdCommand -extends RefCounted - -var _name: String -var _arguments: PackedStringArray - - -func _init(p_name: String, p_arguments: = []): - _name = p_name - _arguments = PackedStringArray(p_arguments) - - -func name() -> String: - return _name - - -func arguments() -> PackedStringArray: - return _arguments - - -func add_argument(arg: String) -> void: - _arguments.append(arg) - - -func _to_string(): - return "%s:%s" % [_name, ", ".join(_arguments)] diff --git a/addons/gdUnit4/src/cmd/CmdCommandHandler.gd b/addons/gdUnit4/src/cmd/CmdCommandHandler.gd deleted file mode 100644 index 3cc10cd..0000000 --- a/addons/gdUnit4/src/cmd/CmdCommandHandler.gd +++ /dev/null @@ -1,105 +0,0 @@ -class_name CmdCommandHandler -extends RefCounted - -const CB_SINGLE_ARG = 0 -const CB_MULTI_ARGS = 1 - -var _cmd_options :CmdOptions -# holds the command callbacks by key::String and value: [, ]:Array -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): - _cmd_options = cmd_options - var major: int = Engine.get_version_info()["major"] - var minor: int = Engine.get_version_info()["minor"] - if major == 3 and minor == 3: - _enhanced_fr_test = true - - -# register a callback function for given command -# cmd_name short name of the command -# fr_arg a funcref to a function with a single argument -func register_cb(cmd_name: String, cb: Callable = NO_CB) -> CmdCommandHandler: - var registered_cb: Array = _command_cbs.get(cmd_name, [NO_CB, NO_CB]) - if registered_cb[CB_SINGLE_ARG]: - push_error("A function for command '%s' is already registered!" % cmd_name) - return self - registered_cb[CB_SINGLE_ARG] = cb - _command_cbs[cmd_name] = registered_cb - return self - - -# register a callback function for given command -# cb a funcref to a function with a variable number of arguments but expects all parameters to be passed via a single Array. -func register_cbv(cmd_name: String, cb: Callable) -> CmdCommandHandler: - var registered_cb: Array = _command_cbs.get(cmd_name, [NO_CB, NO_CB]) - if registered_cb[CB_MULTI_ARGS]: - push_error("A function for command '%s' is already registered!" % cmd_name) - return self - registered_cb[CB_MULTI_ARGS] = cb - _command_cbs[cmd_name] = registered_cb - return self - - -func _validate() -> GdUnitResult: - var errors: = PackedStringArray() - 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] - 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: - errors.append("The command '%s' is unknown, verify your CmdOptions!" % cmd_name) - # verify for multiple registered command callbacks - 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] - 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)) - - -func execute(commands :Array) -> 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) - var cmd_name := cmd.name() - if _command_cbs.has(cmd_name): - var cb_s :Callable = _command_cbs.get(cmd_name)[CB_SINGLE_ARG] - var arguments := cmd.arguments() - var cmd_option := _cmd_options.get_option(cmd_name) - var argument = arguments[0] if arguments.size() > 0 else null - match cmd_option.type(): - TYPE_BOOL: - argument = true if argument == "true" else false - if cb_s and arguments.size() == 0: - cb_s.call() - elif cb_s: - cb_s.call(argument) - 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 deleted file mode 100644 index 8861e78..0000000 --- a/addons/gdUnit4/src/cmd/CmdConsole.gd +++ /dev/null @@ -1,147 +0,0 @@ -# prototype of console with CSI support -# https://notes.burke.libbey.me/ansi-escape-codes/ -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 -} - - -# Control Sequence Introducer -#var csi := PackedByteArray([0x1b]).get_string_from_ascii() -var _debug_show_color_codes := false -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) - if _debug_show_color_codes: - printraw("%6d" % [c2]) - printraw("[38;5;%dm" % c2 ) - else: - printraw("[38;2;%d;%d;%dm" % [p_color.r8, p_color.g8, p_color.b8] ) - return self - - -func save_cursor() -> CmdConsole: - printraw("") - return self - - -func restore_cursor() -> CmdConsole: - printraw("") - return self - - -func end_color() -> CmdConsole: - printraw("") - return self - - -func row_pos(row :int) -> CmdConsole: - printraw("[%d;0H" % row ) - return self - - -func scrollArea(from :int, to :int ) -> CmdConsole: - printraw("[%d;%dr" % [from ,to]) - return self - - -func progressBar(p_progress :int, p_color :Color = Color.POWDER_BLUE) -> CmdConsole: - if p_progress < 0: - p_progress = 0 - if p_progress > 100: - p_progress = 100 - color(p_color) - printraw("[%-50s] %-3d%%\r" % ["".lpad(int(p_progress/2.0), "■").rpad(50, "-"), p_progress]) - end_color() - return self - - -func printl(value :String) -> CmdConsole: - printraw(value) - return self - - -func new_line() -> CmdConsole: - prints() - return self - - -func reset() -> CmdConsole: - return self - - -func bold(enable :bool) -> CmdConsole: - if enable: - printraw(__CSI_BOLD) - return self - - -func italic(enable :bool) -> CmdConsole: - if enable: - printraw(__CSI_ITALIC) - return self - - -func underline(enable :bool) -> CmdConsole: - if enable: - printraw(__CSI_UNDERLINE) - return self - - -func prints_error(message :String) -> CmdConsole: - return color(Color.CRIMSON).printl(message).end_color().new_line() - - -func prints_warning(message :String) -> CmdConsole: - return color(Color.GOLDENROD).printl(message).end_color().new_line() - - -func prints_color(p_message :String, p_color :Color, p_flags := 0) -> CmdConsole: - return print_color(p_message, p_color, p_flags).new_line() - - -func print_color(p_message :String, p_color :Color, p_flags := 0) -> CmdConsole: - return color(p_color)\ - .bold(p_flags&BOLD == BOLD)\ - .italic(p_flags&ITALIC == ITALIC)\ - .underline(p_flags&UNDERLINE == UNDERLINE)\ - .printl(p_message)\ - .end_color() - - -func print_color_table(): - prints_color("Color Table 6x6x6", Color.ANTIQUE_WHITE) - _debug_show_color_codes = true - for green in range(0, 6): - for red in range(0, 6): - for blue in range(0, 6): - 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): - for red in range(0, 6): - for blue in range(0, 6): - print_color("████████ ", Color8(red*42, green*42, blue*42)) - new_line() - new_line() - _color_mode = COLOR_TABLE - _debug_show_color_codes = false diff --git a/addons/gdUnit4/src/cmd/CmdOption.gd b/addons/gdUnit4/src/cmd/CmdOption.gd deleted file mode 100644 index a562a03..0000000 --- a/addons/gdUnit4/src/cmd/CmdOption.gd +++ /dev/null @@ -1,61 +0,0 @@ -class_name CmdOption -extends RefCounted - - -var _commands :PackedStringArray -var _help :String -var _description :String -var _type :int -var _arg_optional :bool = false - - -# constructs a command option by given arguments -# commands : a string with comma separated list of available commands begining with the short form -# help: a help text show howto use -# 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): - _commands = p_commands.replace(" ", "").replace("\t", "").split(",") - _help = p_help - _description = p_description - _type = p_type - _arg_optional = p_arg_optional - - -func commands() -> PackedStringArray: - return _commands - - -func short_command() -> String: - return _commands[0] - - -func help() -> String: - return _help - - -func description() -> String: - return _description - - -func type() -> int: - return _type - - -func is_argument_optional() -> bool: - return _arg_optional - - -func has_argument() -> bool: - return _type != TYPE_NIL - - -func describe() -> String: - if help().is_empty(): - return " %-32s %s \n" % [commands(), description()] - return " %-32s %s \n %-32s %s\n" % [commands(), description(), "", help()] - - -func _to_string(): - return describe() diff --git a/addons/gdUnit4/src/cmd/CmdOptions.gd b/addons/gdUnit4/src/cmd/CmdOptions.gd deleted file mode 100644 index 2884614..0000000 --- a/addons/gdUnit4/src/cmd/CmdOptions.gd +++ /dev/null @@ -1,31 +0,0 @@ -class_name CmdOptions -extends RefCounted - - -var _default_options :Array -var _advanced_options :Array - - -func _init(p_options :Array = Array(), p_advanced_options :Array = Array()): - # default help options - _default_options = p_options - _advanced_options = p_advanced_options - - -func default_options() -> Array: - return _default_options - - -func advanced_options() -> Array: - return _advanced_options - - -func options() -> Array: - return default_options() + advanced_options() - - -func get_option(cmd :String) -> CmdOption: - for option in options(): - if Array(option.commands()).has(cmd): - return option - return null diff --git a/addons/gdUnit4/src/core/GdArrayTools.gd b/addons/gdUnit4/src/core/GdArrayTools.gd deleted file mode 100644 index 1d1b35b..0000000 --- a/addons/gdUnit4/src/core/GdArrayTools.gd +++ /dev/null @@ -1,101 +0,0 @@ -## Small helper tool to work with Godot Arrays -class_name GdArrayTools -extends RefCounted - - -const max_elements := 32 -const ARRAY_TYPES := [ - TYPE_ARRAY, - TYPE_PACKED_BYTE_ARRAY, - TYPE_PACKED_INT32_ARRAY, - TYPE_PACKED_INT64_ARRAY, - TYPE_PACKED_FLOAT32_ARRAY, - TYPE_PACKED_FLOAT64_ARRAY, - TYPE_PACKED_STRING_ARRAY, - TYPE_PACKED_VECTOR2_ARRAY, - TYPE_PACKED_VECTOR3_ARRAY, - TYPE_PACKED_COLOR_ARRAY -] - - -static func is_array_type(value) -> bool: - return is_type_array(typeof(value)) - - -static func is_type_array(type :int) -> bool: - return type in ARRAY_TYPES - - -## 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: - if not is_array_type(array): - return null - var filtered_array = array.duplicate() - var index :int = filtered_array.find(value) - while index != -1: - filtered_array.remove_at(index) - index = filtered_array.find(value) - return filtered_array - - -## 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: - if GdObjects.equals(element, value): - array.erase(element) - - -## Scans for the array build in type on a untyped array[br] -## Returns the buildin type by scan all values and returns the type if all values has the same type. -## If the values has different types TYPE_VARIANT is returend -static func scan_typed(array :Array) -> int: - if array.is_empty(): - return TYPE_NIL - var actual_type := GdObjects.TYPE_VARIANT - for value in array: - var current_type := typeof(value) - if not actual_type in [GdObjects.TYPE_VARIANT, current_type]: - return GdObjects.TYPE_VARIANT - actual_type = current_type - return actual_type - - -## Converts given array into a string presentation.[br] -## This function is different to the original Godot str() implementation. -## The string presentaion contains fullquallified typed informations. -##[br] -## Examples: -## [codeblock] -## # will result in PackedString(["a", "b"]) -## GdArrayTools.as_string(PackedStringArray("a", "b")) -## # will result in PackedString(["a", "b"]) -## GdArrayTools.as_string(PackedColorArray(Color.RED, COLOR.GREEN)) -## [/codeblock] -static func as_string(elements :Variant, encode_value := true) -> String: - if not is_array_type(elements): - return "ERROR: Not an Array Type!" - var delemiter = ", " - if elements == null: - return "" - if elements.is_empty(): - return "" - var prefix := _typeof_as_string(elements) if encode_value else "" - var formatted := "" - var index := 0 - for element in elements: - if max_elements != -1 and index > max_elements: - return prefix + "[" + formatted + delemiter + "...]" - if formatted.length() > 0 : - formatted += delemiter - formatted += GdDefaultValueDecoder.decode(element) if encode_value else str(element) - index += 1 - return prefix + "[" + formatted + "]" - - -static func _typeof_as_string(value :Variant) -> String: - var type := typeof(value) - # for untyped array we retun empty string - if type == TYPE_ARRAY: - return "" - return GdObjects.typeof_as_string(value) diff --git a/addons/gdUnit4/src/core/GdDiffTool.gd b/addons/gdUnit4/src/core/GdDiffTool.gd deleted file mode 100644 index fe996f2..0000000 --- a/addons/gdUnit4/src/core/GdDiffTool.gd +++ /dev/null @@ -1,155 +0,0 @@ -# A tool to find differences between two objects -class_name GdDiffTool -extends RefCounted - - -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() - - while true: - #if last character of X and Y matches - if loffset > 0 && roffset > 0 && lb[loffset - 1] == rb[roffset - 1]: - loffset -= 1 - roffset -= 1 - ldiff.push_front(lb[loffset]) - rdiff.push_front(rb[roffset]) - continue - #current character of Y is not present in X - else: if (roffset > 0 && (loffset == 0 || lookup[loffset][roffset - 1] >= lookup[loffset - 1][roffset])): - roffset -= 1 - ldiff.push_front(rb[roffset]) - ldiff.push_front(DIV_ADD) - rdiff.push_front(rb[roffset]) - rdiff.push_front(DIV_SUB) - continue - #current character of X is not present in Y - else: if (loffset > 0 && (roffset == 0 || lookup[loffset][roffset - 1] < lookup[loffset - 1][roffset])): - loffset -= 1 - ldiff.push_front(lb[loffset]) - ldiff.push_front(DIV_SUB) - rdiff.push_front(lb[loffset]) - rdiff.push_front(DIV_ADD) - continue - break - - -# lookup[i][j] stores the length of LCS of substring X[0..i-1], Y[0..j-1] -static func _createLookUp(lb: PackedByteArray, rb: PackedByteArray) -> Array: - var lookup := Array() - lookup.resize(lb.size() + 1) - for i in lookup.size(): - var x = [] - x.resize(rb.size() + 1) - lookup[i] = x - return lookup - - -static func _buildLookup(lb: PackedByteArray, rb: PackedByteArray) -> Array: - var lookup := _createLookUp(lb, rb) - # first column of the lookup table will be all 0 - 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(): - 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()): - # if current character of left and right matches - if lb[i - 1] == rb[j - 1]: - lookup[i][j] = lookup[i - 1][j - 1] + 1; - # else if current character of left and right don't match - else: - lookup[i][j] = max(lookup[i - 1][j], lookup[i][j - 1]); - 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() - var ldiff := Array() - var rdiff := Array() - var lookup := _buildLookup(lb, rb); - _diff(lb, rb, lookup, ldiff, rdiff) - return [PackedByteArray(ldiff), PackedByteArray(rdiff)] - - -# prototype -static func longestCommonSubsequence(text1 :String, text2 :String) -> PackedStringArray: - var text1Words := text1.split(" ") - var text2Words := text2.split(" ") - var text1WordCount := text1Words.size() - var text2WordCount := text2Words.size() - var solutionMatrix := Array() - for i in text1WordCount+1: - var ar := Array() - 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 lcsResultList := PackedStringArray(); - while (i < text1WordCount && j < text2WordCount): - if text1Words[i] == text2Words[j]: - lcsResultList.append(text2Words[j]) - i += 1 - j += 1 - else: if (solutionMatrix[i + 1][j] >= solutionMatrix[i][j + 1]): - i += 1 - else: - j += 1 - return lcsResultList - - -static func markTextDifferences(text1 :String, text2 :String, lcsList :PackedStringArray, insertColor :Color, deleteColor:Color) -> String: - 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 - for k in lcsList.size(): - while i < text1Words.size() and j < text2Words.size(): - if text1Words[i] == lcsList[k] and text2Words[j] == lcsList[k]: - stringBuffer += "" + lcsList[k] + " " - word1LastIndex = i + 1 - 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] + " " - i += 1 - else: if text2Words[j] != lcsList[k]: - while j < text2Words.size() and text2Words[j] != lcsList[k]: - stringBuffer += "" + text2Words[j] + " " - j += 1 - i = word1LastIndex - j = word2LastIndex - - while word1LastIndex < text1Words.size(): - stringBuffer += "" + text1Words[word1LastIndex] + " " - word1LastIndex += 1 - while word2LastIndex < text2Words.size(): - stringBuffer += "" + text2Words[word2LastIndex] + " " - word2LastIndex += 1 - return stringBuffer diff --git a/addons/gdUnit4/src/core/GdFunctionDoubler.gd b/addons/gdUnit4/src/core/GdFunctionDoubler.gd deleted file mode 100644 index 3d3e2ea..0000000 --- a/addons/gdUnit4/src/core/GdFunctionDoubler.gd +++ /dev/null @@ -1,191 +0,0 @@ -class_name GdFunctionDoubler -extends RefCounted - -const DEFAULT_TYPED_RETURN_VALUES := { - TYPE_NIL: "null", - TYPE_BOOL: "false", - TYPE_INT: "0", - TYPE_FLOAT: "0.0", - TYPE_STRING: "\"\"", - TYPE_STRING_NAME: "&\"\"", - TYPE_VECTOR2: "Vector2.ZERO", - TYPE_VECTOR2I: "Vector2i.ZERO", - TYPE_RECT2: "Rect2()", - TYPE_RECT2I: "Rect2i()", - TYPE_VECTOR3: "Vector3.ZERO", - TYPE_VECTOR3I: "Vector3i.ZERO", - TYPE_VECTOR4: "Vector4.ZERO", - TYPE_VECTOR4I: "Vector4i.ZERO", - TYPE_TRANSFORM2D: "Transform2D()", - TYPE_PLANE: "Plane()", - TYPE_QUATERNION: "Quaternion()", - TYPE_AABB: "AABB()", - TYPE_BASIS: "Basis()", - TYPE_TRANSFORM3D: "Transform3D()", - TYPE_PROJECTION: "Projection()", - TYPE_COLOR: "Color()", - TYPE_NODE_PATH: "NodePath()", - TYPE_RID: "RID()", - TYPE_OBJECT: "null", - TYPE_CALLABLE: "Callable()", - TYPE_SIGNAL: "Signal()", - TYPE_DICTIONARY: "Dictionary()", - TYPE_ARRAY: "Array()", - TYPE_PACKED_BYTE_ARRAY: "PackedByteArray()", - TYPE_PACKED_INT32_ARRAY: "PackedInt32Array()", - TYPE_PACKED_INT64_ARRAY: "PackedInt64Array()", - TYPE_PACKED_FLOAT32_ARRAY: "PackedFloat32Array()", - TYPE_PACKED_FLOAT64_ARRAY: "PackedFloat64Array()", - TYPE_PACKED_STRING_ARRAY: "PackedStringArray()", - TYPE_PACKED_VECTOR2_ARRAY: "PackedVector2Array()", - TYPE_PACKED_VECTOR3_ARRAY: "PackedVector3Array()", - TYPE_PACKED_COLOR_ARRAY: "PackedColorArray()", - GdObjects.TYPE_VARIANT: "null", - GdObjects.TYPE_ENUM: "0" -} - -# @GlobalScript enums -# needs to manually map because of https://github.com/godotengine/godot/issues/73835 -const DEFAULT_ENUM_RETURN_VALUES = { - "Side" : "SIDE_LEFT", - "Corner" : "CORNER_TOP_LEFT", - "Orientation" : "HORIZONTAL", - "ClockDirection" : "CLOCKWISE", - "HorizontalAlignment" : "HORIZONTAL_ALIGNMENT_LEFT", - "VerticalAlignment" : "VERTICAL_ALIGNMENT_TOP", - "InlineAlignment" : "INLINE_ALIGNMENT_TOP_TO", - "EulerOrder" : "EULER_ORDER_XYZ", - "Key" : "KEY_NONE", - "KeyModifierMask" : "KEY_CODE_MASK", - "MouseButton" : "MOUSE_BUTTON_NONE", - "MouseButtonMask" : "MOUSE_BUTTON_MASK_LEFT", - "JoyButton" : "JOY_BUTTON_INVALID", - "JoyAxis" : "JOY_AXIS_INVALID", - "MIDIMessage" : "MIDI_MESSAGE_NONE", - "Error" : "OK", - "PropertyHint" : "PROPERTY_HINT_NONE", - "Variant.Type" : "TYPE_NIL", -} - -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() - script.source_code - 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_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]) - if not keys.is_empty(): - return "%s.%s" % [enum_path[0], keys[0]] - var enum_value := 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, "invalid") - - -func _init(push_errors :bool = false): - _push_errors = "true" if push_errors else "false" - 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") -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() - var is_vararg := func_descriptor.is_vararg() - var is_coroutine := func_descriptor.is_coroutine() - var func_name := func_descriptor.name() - var args := func_descriptor.args() - var varargs := func_descriptor.varargs() - 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) -> 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 - var func_template := get_template(func_descriptor.return_type(), is_vararg).replace("\r\n", "\n") - double_src += func_template\ - .replace("$(arguments)", ", ".join(arg_names))\ - .replace("$(varargs)", ", ".join(vararg_names))\ - .replace("$(await)", GdFunctionDoubler.await_is_coroutine(is_coroutine)) \ - .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: - double_src = double_src.replace("$(instance)", "") - return double_src.split("\n") - - -func extract_arg_names(argument_signatures :Array[GdFunctionArgument]) -> PackedStringArray: - var arg_names := PackedStringArray() - for arg in argument_signatures: - arg_names.append(arg._name) - return arg_names - - -static func extract_constructor_args(args :Array) -> 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) - if default_value == "null": - constructor_args.append(arg_name + ":Variant=" + default_value) - else: - constructor_args.append(arg_name + ":=" + default_value) - return constructor_args - - -static func get_default(arg :GdFunctionArgument) -> String: - if arg.has_default(): - return arg.value_as_string() - else: - return DEFAULT_TYPED_RETURN_VALUES.get(arg.type(), "null") - - -static func await_is_coroutine(is_coroutine :bool) -> String: - return "await " if is_coroutine else "" diff --git a/addons/gdUnit4/src/core/GdObjects.gd b/addons/gdUnit4/src/core/GdObjects.gd deleted file mode 100644 index 66e94b7..0000000 --- a/addons/gdUnit4/src/core/GdObjects.gd +++ /dev/null @@ -1,686 +0,0 @@ -# This is a helper class to compare two objects by equals -class_name GdObjects -extends Resource - -const GdUnitTools := preload("res://addons/gdUnit4/src/core/GdUnitTools.gd") - -const TYPE_VOID = TYPE_MAX + 1000 -const TYPE_VARARG = TYPE_MAX + 1001 -const TYPE_VARIANT = TYPE_MAX + 1002 -const TYPE_FUNC = TYPE_MAX + 1003 -const TYPE_FUZZER = TYPE_MAX + 1004 - -const TYPE_NODE = TYPE_MAX + 2001 -# missing Godot types -const TYPE_CONTROL = TYPE_MAX + 2002 -const TYPE_CANVAS = TYPE_MAX + 2003 -const TYPE_ENUM = TYPE_MAX + 2004 - - -# used as default value for varargs -const TYPE_VARARG_PLACEHOLDER_VALUE = "__null__" - - -const TYPE_AS_STRING_MAPPINGS := { - TYPE_NIL: "null", - TYPE_BOOL: "bool", - TYPE_INT: "int", - TYPE_FLOAT: "float", - TYPE_STRING: "String", - TYPE_VECTOR2: "Vector2", - TYPE_VECTOR2I: "Vector2i", - TYPE_RECT2: "Rect2", - TYPE_RECT2I: "Rect2i", - TYPE_VECTOR3: "Vector3", - TYPE_VECTOR3I: "Vector3i", - TYPE_TRANSFORM2D: "Transform2D", - TYPE_VECTOR4: "Vector4", - TYPE_VECTOR4I: "Vector4i", - TYPE_PLANE: "Plane", - TYPE_QUATERNION: "Quaternion", - TYPE_AABB: "AABB", - TYPE_BASIS: "Basis", - TYPE_TRANSFORM3D: "Transform3D", - TYPE_PROJECTION: "Projection", - TYPE_COLOR: "Color", - TYPE_STRING_NAME: "StringName", - TYPE_NODE_PATH: "NodePath", - TYPE_RID: "RID", - TYPE_OBJECT: "Object", - TYPE_CALLABLE: "Callable", - TYPE_SIGNAL: "Signal", - TYPE_DICTIONARY: "Dictionary", - TYPE_ARRAY: "Array", - TYPE_PACKED_BYTE_ARRAY: "PackedByteArray", - TYPE_PACKED_INT32_ARRAY: "PackedInt32Array", - TYPE_PACKED_INT64_ARRAY: "PackedInt64Array", - TYPE_PACKED_FLOAT32_ARRAY: "PackedFloat32Array", - TYPE_PACKED_FLOAT64_ARRAY: "PackedFloat64Array", - TYPE_PACKED_STRING_ARRAY: "PackedStringArray", - TYPE_PACKED_VECTOR2_ARRAY: "PackedVector2Array", - TYPE_PACKED_VECTOR3_ARRAY: "PackedVector3Array", - TYPE_PACKED_COLOR_ARRAY: "PackedColorArray", - TYPE_VOID: "void", - TYPE_VARARG: "VarArg", - TYPE_FUNC: "Func", - TYPE_FUZZER: "Fuzzer", - TYPE_VARIANT: "Variant" -} - - -const NOTIFICATION_AS_STRING_MAPPINGS := { - TYPE_OBJECT: { - Object.NOTIFICATION_POSTINITIALIZE : "POSTINITIALIZE", - Object.NOTIFICATION_PREDELETE: "PREDELETE", - EditorSettings.NOTIFICATION_EDITOR_SETTINGS_CHANGED: "EDITOR_SETTINGS_CHANGED", - }, - TYPE_NODE: { - Node.NOTIFICATION_ENTER_TREE : "ENTER_TREE", - Node.NOTIFICATION_EXIT_TREE: "EXIT_TREE", - Node.NOTIFICATION_MOVED_IN_PARENT: "MOVED_IN_PARENT", - Node.NOTIFICATION_READY: "READY", - Node.NOTIFICATION_PAUSED: "PAUSED", - Node.NOTIFICATION_UNPAUSED: "UNPAUSED", - Node.NOTIFICATION_PHYSICS_PROCESS: "PHYSICS_PROCESS", - Node.NOTIFICATION_PROCESS: "PROCESS", - Node.NOTIFICATION_PARENTED: "PARENTED", - Node.NOTIFICATION_UNPARENTED: "UNPARENTED", - Node.NOTIFICATION_SCENE_INSTANTIATED: "INSTANCED", - Node.NOTIFICATION_DRAG_BEGIN: "DRAG_BEGIN", - Node.NOTIFICATION_DRAG_END: "DRAG_END", - Node.NOTIFICATION_PATH_RENAMED: "PATH_CHANGED", - Node.NOTIFICATION_INTERNAL_PROCESS: "INTERNAL_PROCESS", - Node.NOTIFICATION_INTERNAL_PHYSICS_PROCESS: "INTERNAL_PHYSICS_PROCESS", - Node.NOTIFICATION_POST_ENTER_TREE: "POST_ENTER_TREE", - Node.NOTIFICATION_WM_MOUSE_ENTER: "WM_MOUSE_ENTER", - Node.NOTIFICATION_WM_MOUSE_EXIT: "WM_MOUSE_EXIT", - Node.NOTIFICATION_APPLICATION_FOCUS_IN: "WM_FOCUS_IN", - Node.NOTIFICATION_APPLICATION_FOCUS_OUT: "WM_FOCUS_OUT", - #Node.NOTIFICATION_WM_QUIT_REQUEST: "WM_QUIT_REQUEST", - Node.NOTIFICATION_WM_GO_BACK_REQUEST: "WM_GO_BACK_REQUEST", - Node.NOTIFICATION_WM_WINDOW_FOCUS_OUT: "WM_UNFOCUS_REQUEST", - Node.NOTIFICATION_OS_MEMORY_WARNING: "OS_MEMORY_WARNING", - Node.NOTIFICATION_TRANSLATION_CHANGED: "TRANSLATION_CHANGED", - Node.NOTIFICATION_WM_ABOUT: "WM_ABOUT", - Node.NOTIFICATION_CRASH: "CRASH", - Node.NOTIFICATION_OS_IME_UPDATE: "OS_IME_UPDATE", - Node.NOTIFICATION_APPLICATION_RESUMED: "APP_RESUMED", - Node.NOTIFICATION_APPLICATION_PAUSED: "APP_PAUSED", - Node3D.NOTIFICATION_TRANSFORM_CHANGED: "TRANSFORM_CHANGED", - Node3D.NOTIFICATION_ENTER_WORLD: "ENTER_WORLD", - Node3D.NOTIFICATION_EXIT_WORLD: "EXIT_WORLD", - Node3D.NOTIFICATION_VISIBILITY_CHANGED: "VISIBILITY_CHANGED", - Skeleton3D.NOTIFICATION_UPDATE_SKELETON: "UPDATE_SKELETON", - CanvasItem.NOTIFICATION_DRAW: "DRAW", - CanvasItem.NOTIFICATION_VISIBILITY_CHANGED: "VISIBILITY_CHANGED", - CanvasItem.NOTIFICATION_ENTER_CANVAS: "ENTER_CANVAS", - CanvasItem.NOTIFICATION_EXIT_CANVAS: "EXIT_CANVAS", - #Popup.NOTIFICATION_POST_POPUP: "POST_POPUP", - #Popup.NOTIFICATION_POPUP_HIDE: "POPUP_HIDE", - }, - TYPE_CONTROL : { - Container.NOTIFICATION_SORT_CHILDREN: "SORT_CHILDREN", - Control.NOTIFICATION_RESIZED: "RESIZED", - Control.NOTIFICATION_MOUSE_ENTER: "MOUSE_ENTER", - Control.NOTIFICATION_MOUSE_EXIT: "MOUSE_EXIT", - Control.NOTIFICATION_FOCUS_ENTER: "FOCUS_ENTER", - Control.NOTIFICATION_FOCUS_EXIT: "FOCUS_EXIT", - Control.NOTIFICATION_THEME_CHANGED: "THEME_CHANGED", - #Control.NOTIFICATION_MODAL_CLOSE: "MODAL_CLOSE", - Control.NOTIFICATION_SCROLL_BEGIN: "SCROLL_BEGIN", - Control.NOTIFICATION_SCROLL_END: "SCROLL_END", - } -} - - -enum COMPARE_MODE { - OBJECT_REFERENCE, - PARAMETER_DEEP_TEST -} - - -# prototype of better object to dictionary -static func obj2dict(obj :Object, hashed_objects := Dictionary()) -> Dictionary: - if obj == null: - return {} - 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"] - if d["@subpath"] != NodePath(""): - clazz_name = d["@subpath"] - dict["@inner_class"] = true - 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: - continue - if (property["usage"] & PROPERTY_USAGE_SCRIPT_VARIABLE|PROPERTY_USAGE_DEFAULT - and not property["usage"] & PROPERTY_USAGE_CATEGORY - and not property["usage"] == 0): - if property_type == TYPE_OBJECT: - # prevent recursion - if hashed_objects.has(obj): - dict[property_name] = str(property_value) - continue - hashed_objects[obj] = true - dict[property_name] = obj2dict(property_value, hashed_objects) - else: - dict[property_name] = property_value - return {"%s" % clazz_name : dict} - - -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) - - -static func equals_sorted(obj_a :Array, obj_b :Array, case_sensitive :bool = false, compare_mode :COMPARE_MODE = COMPARE_MODE.PARAMETER_DEEP_TEST) -> bool: - var a := obj_a.duplicate() - var b := obj_b.duplicate() - a.sort() - b.sort() - return equals(a, b, case_sensitive, compare_mode) - - -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): - return false - # is same instance - if obj_a == obj_b: - return true - # handle null values - if obj_a == null and obj_b != null: - 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): - return true - deep_stack.append(obj_a) - deep_stack.append(obj_b) - if compare_mode == COMPARE_MODE.PARAMETER_DEEP_TEST: - # fail fast - if not is_instance_valid(obj_a) or not is_instance_valid(obj_b): - return false - if obj_a.get_class() != obj_b.get_class(): - return false - 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(): - 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 - 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() - else: - return obj_a == obj_b - return obj_a == obj_b - - -@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: - return NOTIFICATION_AS_STRING_MAPPINGS[TYPE_NODE].get(notification, error) - if instance is Control: - 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(): - if TYPE_AS_STRING_MAPPINGS.get(type) == value: - return type - return TYPE_NIL - - -static func to_camel_case(value :String) -> String: - var p := to_pascal_case(value) - if not p.is_empty(): - p[0] = p[0].to_lower() - return p - - -static func to_pascal_case(value :String) -> String: - return value.capitalize().replace(" ", "") - - -static func to_snake_case(value :String) -> String: - var result = PackedStringArray() - for ch in value: - var lower_ch = ch.to_lower() - if ch != lower_ch and result.size() > 1: - result.append('_') - result.append(lower_ch) - return ''.join(result) - - -static func is_snake_case(value :String) -> bool: - for ch in value: - if ch == '_': - continue - if ch == ch.to_upper(): - return false - return true - - -static func type_as_string(type :int) -> String: - return TYPE_AS_STRING_MAPPINGS.get(type, "Variant") - - -static func typeof_as_string(value :Variant) -> String: - return TYPE_AS_STRING_MAPPINGS.get(typeof(value), "Unknown type") - - -static func all_types() -> PackedInt32Array: - return PackedInt32Array(TYPE_AS_STRING_MAPPINGS.keys()) - - -static func string_as_typeof(type_name :String) -> int: - 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 :Variant) -> bool: - return typeof(value) in [TYPE_BOOL, TYPE_STRING, TYPE_STRING_NAME, TYPE_INT, TYPE_FLOAT] - - -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 - if GdUnitSettings.is_strict_number_type_compare(): - return type_a == type_b - return ( - (type_a == TYPE_FLOAT and type_b == TYPE_INT) - or (type_a == TYPE_INT and type_b == TYPE_FLOAT) - or type_a == type_b) - - -static func is_engine_type(value :Object) -> bool: - if value is GDScript or value is ScriptExtension: - return false - return value.is_class("GDScriptNativeClass") - - -static func is_type(value :Variant) -> bool: - # is an build-in type - if typeof(value) != TYPE_OBJECT: - return false - # is a engine class type - if is_engine_type(value): - return true - # is a custom class type - if value is GDScript and value.can_instantiate(): - return true - return false - - -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) - - # if typ different can't be the same - if left_type != right_type: - return false - if left_type == TYPE_OBJECT and right_type == TYPE_OBJECT: - return left.get_instance_id() == right.get_instance_id() - return equals(left, right) - - -static func is_object(value :Variant) -> bool: - return typeof(value) == TYPE_OBJECT - - -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 GdUnit4CSharpApiLoader.is_test_suite(script.resource_path) - - -static func is_native_class(value :Variant) -> bool: - return is_object(value) and is_engine_type(value) - - -static func is_scene(value :Variant) -> bool: - return is_object(value) and value is PackedScene - - -static func is_scene_resource_path(value :Variant) -> bool: - return value is String and value.ends_with(".tscn") - - -static func is_gd_script(script :Script) -> bool: - return script is GDScript - - -static func is_cs_script(script :Script) -> bool: - # we need to check by stringify name because checked non mono Godot the class CSharpScript is not available - return str(script).find("CSharpScript") != -1 - - -static func is_gd_testsuite(script :Script) -> bool: - if is_gd_script(script): - var stack := [script] - while not stack.is_empty(): - var current := stack.pop_front() as Script - var base := current.get_base_script() as Script - if base != null: - if base.resource_path.find("GdUnitTestSuite") != -1: - return true - stack.push_back(base) - return false - - -static func is_singleton(value :Variant) -> bool: - if not is_instance_valid(value) or is_native_class(value): - return false - for name in Engine.get_singleton_list(): - if value.is_class(name): - return true - return false - - -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() == "": - return true - if is_scene(value): - return true - return not value.has_method('new') and not value.has_method('instance') - - -# only object form type Node and attached filename -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() - return false - - -static func can_be_instantiate(obj :Variant) -> bool: - if not obj or is_engine_type(obj): - return false - return obj.has_method("new") - - -static func create_instance(clazz :Variant) -> GdUnitResult: - match typeof(clazz): - TYPE_OBJECT: - # test is given clazz already an instance - if is_instance(clazz): - return GdUnitResult.success(clazz) - return GdUnitResult.success(clazz.new()) - TYPE_STRING: - if ClassDB.class_exists(clazz): - if Engine.has_singleton(clazz): - return GdUnitResult.error("Not allowed to create a instance for singelton '%s'." % clazz) - if not ClassDB.can_instantiate(clazz): - return GdUnitResult.error("Can't instance Engine class '%s'." % clazz) - return GdUnitResult.success(ClassDB.instantiate(clazz)) - else: - 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) - if script != null: - return GdUnitResult.success(script.new()) - else: - return GdUnitResult.error("Can't create instance for '%s'." % clazz) - return GdUnitResult.error("Can't create instance for class '%s'." % clazz) - - -static func extract_class_path(clazz :Variant) -> PackedStringArray: - var clazz_path := PackedStringArray() - if clazz is String: - clazz_path.append(clazz) - return clazz_path - if is_instance(clazz): - # is instance a script instance? - var script := clazz.script as GDScript - 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 clazz_info := inst_to_dict(instance) - GdUnitTools.free_instance(instance) - clazz_path.append(clazz_info["@path"]) - if clazz_info.has("@subpath"): - var sub_path :String = clazz_info["@subpath"] - if not sub_path.is_empty(): - var sub_paths := sub_path.split("/") - clazz_path += sub_paths - return clazz_path - return clazz_path - - -static func extract_class_name_from_class_path(clazz_path :PackedStringArray) -> String: - var base_clazz := clazz_path[0] - # return original class name if engine class - if ClassDB.class_exists(base_clazz): - return base_clazz - var clazz_name := to_pascal_case(base_clazz.get_basename().get_file()) - for path_index in range(1, clazz_path.size()): - clazz_name += "." + clazz_path[path_index] - return clazz_name - - -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()) - - # 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 :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)) - 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 :Variant = clazz.new() - if instance == null: - return GdUnitResult.error("Can't create a instance for class '%s'" % clazz) - var result := extract_class_name(instance) - GdUnitTools.free_instance(instance) - return result - - -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) - if value is GDScript: - var class_path := extract_class_path(value) - inner_classes.append(class_path[1]) - return inner_classes - - -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]) - if script is GDScript: - # if inner class on class path we have to load the script from the script_constant_map - if script_path.size() == 2 and script_path[1] != "": - var inner_classes := script_path[1] - var map := script.get_script_constant_map() - script = map[inner_classes] - var clazz_functions :Array = script.get_method_list() - var base_clazz :String = script.get_instance_base_type() - if base_clazz: - return extract_class_functions(base_clazz, script_path) - return clazz_functions - return Array() - - -# 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 :String) -> bool: - var script_classes:Array[Dictionary] = ProjectSettings.get_global_class_list() - for class_info in script_classes: - if class_info.has("class"): - if class_info["class"] == clazz_name: - return true - return false - - -static func build_function_default_arguments(script :GDScript, func_name :String) -> Array: - 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"] - for arg in args: - var value_type := arg["type"] as int - var default_value = 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): - assert(type < TYPE_MAX) - assert(type >= 0) - - match type: - TYPE_NIL: return null - TYPE_BOOL: return false - TYPE_INT: return 0 - TYPE_FLOAT: return 0.0 - TYPE_STRING: return "" - TYPE_VECTOR2: return Vector2.ZERO - TYPE_VECTOR2I: return Vector2i.ZERO - TYPE_VECTOR3: return Vector3.ZERO - TYPE_VECTOR3I: return Vector3i.ZERO - TYPE_VECTOR4: return Vector4.ZERO - TYPE_VECTOR4I: return Vector4i.ZERO - TYPE_RECT2: return Rect2() - TYPE_RECT2I: return Rect2i() - TYPE_TRANSFORM2D: return Transform2D() - TYPE_PLANE: return Plane() - TYPE_QUATERNION: return Quaternion() - TYPE_AABB: return AABB() - TYPE_BASIS: return Basis() - TYPE_TRANSFORM3D: return Transform3D() - TYPE_COLOR: return Color() - TYPE_NODE_PATH: return NodePath() - TYPE_RID: return RID() - TYPE_OBJECT: return null - TYPE_ARRAY: return [] - TYPE_DICTIONARY: return {} - TYPE_PACKED_BYTE_ARRAY: return PackedByteArray() - TYPE_PACKED_COLOR_ARRAY: return PackedColorArray() - TYPE_PACKED_INT32_ARRAY: return PackedInt32Array() - TYPE_PACKED_INT64_ARRAY: return PackedInt64Array() - TYPE_PACKED_FLOAT32_ARRAY: return PackedFloat32Array() - TYPE_PACKED_FLOAT64_ARRAY: return PackedFloat64Array() - 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 - - -static func find_nodes_by_class(root: Node, cls: String, recursive: bool = false) -> Array[Node]: - if not recursive: - return _find_nodes_by_class_no_rec(root, cls) - return _find_nodes_by_class(root, cls) - - -static func _find_nodes_by_class_no_rec(parent: Node, cls: String) -> Array[Node]: - var result :Array[Node] = [] - for ch in parent.get_children(): - if ch.get_class() == cls: - result.append(ch) - return result - - -static func _find_nodes_by_class(root: Node, cls: String) -> Array[Node]: - var result :Array[Node] = [] - var stack :Array[Node] = [root] - while stack: - var node :Node = stack.pop_back() - if node.get_class() == cls: - result.append(node) - for ch in node.get_children(): - stack.push_back(ch) - return result diff --git a/addons/gdUnit4/src/core/GdUnit4Version.gd b/addons/gdUnit4/src/core/GdUnit4Version.gd deleted file mode 100644 index 14cef99..0000000 --- a/addons/gdUnit4/src/core/GdUnit4Version.gd +++ /dev/null @@ -1,57 +0,0 @@ -class_name GdUnit4Version -extends RefCounted - -const VERSION_PATTERN = "[center][color=#9887c4]gd[/color][color=#7a57d6]Unit[/color][color=#9887c4]4[/color] [color=#9887c4]${version}[/color][/center]" - -var _major :int -var _minor :int -var _patch :int - - -func _init(major :int,minor :int,patch :int): - _major = major - _minor = minor - _patch = patch - - -static func parse(value :String) -> GdUnit4Version: - var regex := RegEx.new() - regex.compile("[a-zA-Z:,-]+") - var cleaned := regex.sub(value, "", true) - var parts := cleaned.split(".") - var major := parts[0].to_int() - var minor := parts[1].to_int() - 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() - config.load('addons/gdUnit4/plugin.cfg') - return parse(config.get_value('plugin', 'version')) - - -func equals(other :) -> bool: - return _major == other._major and _minor == other._minor and _patch == other._patch - - -func is_greater(other :) -> 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() - config.load('addons/gdUnit4/plugin.cfg') - var version = 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 deleted file mode 100644 index 30be420..0000000 --- a/addons/gdUnit4/src/core/GdUnitClassDoubler.gd +++ /dev/null @@ -1,122 +0,0 @@ -# A class doubler used to mock and spy checked implementations -class_name GdUnitClassDoubler -extends RefCounted - - -const DOUBLER_INSTANCE_ID_PREFIX := "gdunit_doubler_instance_id_" -const DOUBLER_TEMPLATE :GDScript = preload("res://addons/gdUnit4/src/core/GdUnitObjectInteractionsTemplate.gd") -const EXCLUDE_VIRTUAL_FUNCTIONS = [ - # we have to exclude notifications because NOTIFICATION_PREDELETE is try - # to delete already freed spy/mock resources and will result in a conflict - "_notification", - # https://github.com/godotengine/godot/issues/67461 - "get_name", - "get_path", - "duplicate", - ] -# define functions to be exclude when spy or mock checked a scene -const EXLCUDE_SCENE_FUNCTIONS = [ - # needs to exclude get/set script functions otherwise it endsup in recursive endless loop - "set_script", - "get_script", - # needs to exclude otherwise verify fails checked collection arguments checked calling to string - "_to_string", -] -const EXCLUDE_FUNCTIONS = ["new", "free", "get_instance_id", "get_tree"] - - -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) - 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]) - - -# loads the doubler template -# 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\ - .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") - # replace template class_name with Doubled name and extends form source class - lines.insert(0, "class_name Doubled%s" % class_info.get("class_name").replace(".", "_")) - lines.insert(1, extends_clazz(class_info)) - # append Object interactions stuff - lines.append_array(GdScriptParser.to_unix_format(DOUBLER_TEMPLATE.source_code).split("\n")) - return lines - - -static func extends_clazz(class_info :Dictionary) -> String: - var clazz_name :String = class_info.get("class_name") - var clazz_path :PackedStringArray = class_info.get("class_path", []) - # is inner class? - if clazz_path.size() > 1: - return "extends %s" % clazz_name - if clazz_path.size() == 1 and clazz_path[0].ends_with(".gd"): - return "extends '%s'" % clazz_path[0] - return "extends %s" % clazz_name - - -# double all functions of given instance -static func double_functions(instance :Object, clazz_name :String, clazz_path :PackedStringArray, func_doubler: GdFunctionDoubler, exclude_functions :Array) -> PackedStringArray: - var doubled_source := PackedStringArray() - 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) - if result.is_error(): - push_error(result.error_message()) - return PackedStringArray() - var class_descriptor :GdClassDescriptor = result.value() - while class_descriptor != null: - for func_descriptor in class_descriptor.functions(): - if instance != null and not instance.has_method(func_descriptor.name()): - #prints("no virtual func implemented",clazz_name, func_descriptor.name() ) - continue - if functions.has(func_descriptor.name()) or exclude_override_functions.has(func_descriptor.name()): - continue - 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: - var func_descriptor := GdFunctionDescriptor.extract_from(method) - # exclude private core functions - if func_descriptor.is_private(): - continue - if functions.has(func_descriptor.name()) or exclude_override_functions.has(func_descriptor.name()): - continue - # GD-110: Hotfix do not double invalid engine functions - if is_invalid_method_descriptior(method): - #prints("'%s': invalid method descriptor found! %s" % [clazz_name, method]) - continue - # do not double on not implemented virtual functions - if instance != null and not instance.has_method(func_descriptor.name()): - #prints("no virtual func implemented",clazz_name, func_descriptor.name() ) - continue - functions.append(func_descriptor.name()) - doubled_source.append_array(func_doubler.double(func_descriptor)) - return doubled_source - - -# GD-110 -static func is_invalid_method_descriptior(method :Dictionary) -> bool: - var return_info = method["return"] - var type :int = return_info["type"] - var usage :int = return_info["usage"] - var clazz_name :String = return_info["class_name"] - # is method returning a type int with a given 'class_name' we have an enum - # and the PROPERTY_USAGE_CLASS_IS_ENUM must be set - if type == TYPE_INT and not clazz_name.is_empty() and not (usage & PROPERTY_USAGE_CLASS_IS_ENUM): - return true - if clazz_name == "Variant.Type": - return true - return false diff --git a/addons/gdUnit4/src/core/GdUnitFileAccess.gd b/addons/gdUnit4/src/core/GdUnitFileAccess.gd deleted file mode 100644 index 01022dd..0000000 --- a/addons/gdUnit4/src/core/GdUnitFileAccess.gd +++ /dev/null @@ -1,211 +0,0 @@ -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 deleted file mode 100644 index 7020a32..0000000 --- a/addons/gdUnit4/src/core/GdUnitObjectInteractions.gd +++ /dev/null @@ -1,42 +0,0 @@ -class_name GdUnitObjectInteractions -extends RefCounted - - -static func verify(interaction_object :Object, interactions_times): - 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(interaction_object :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(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(interaction_object :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(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(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(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 deleted file mode 100644 index c06b1d4..0000000 --- a/addons/gdUnit4/src/core/GdUnitObjectInteractionsTemplate.gd +++ /dev/null @@ -1,91 +0,0 @@ -const GdUnitAssertImpl := preload("res://addons/gdUnit4/src/asserts/GdUnitAssertImpl.gd") - -var __expected_interactions :int = -1 -var __saved_interactions := Dictionary() -var __verified_interactions := Array() - - -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[function_args] = 1 - - -func __is_verify_interactions() -> bool: - return __expected_interactions != -1 - - -func __do_verify_interactions(interactions_times :int = 1) -> Object: - __expected_interactions = interactions_times - return self - - -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 := {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) - else: - __gd_assert.report_success() - __expected_interactions = -1 - - -func __verify_no_interactions() -> Dictionary: - var __summary := Dictionary() - if not __saved_interactions.is_empty(): - 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[Variant] = __saved_interactions.keys() - if called_functions != __verified_interactions: - # collect the not verified functions - var called_but_not_verified := called_functions.duplicate() - 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[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) - return filtered diff --git a/addons/gdUnit4/src/core/GdUnitProperty.gd b/addons/gdUnit4/src/core/GdUnitProperty.gd deleted file mode 100644 index 6e338a3..0000000 --- a/addons/gdUnit4/src/core/GdUnitProperty.gd +++ /dev/null @@ -1,72 +0,0 @@ -class_name GdUnitProperty -extends RefCounted - - -var _name :String -var _help :String -var _type :int -var _value :Variant -var _value_set :PackedStringArray -var _default :Variant - - -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 - _value_set = p_value_set - _default = p_default_value - _help = p_help - - -func name() -> String: - return _name - - -func type() -> int: - return _type - - -func value() -> Variant: - return _value - - -func value_set() -> PackedStringArray: - return _value_set - - -func is_selectable_value() -> bool: - return not _value_set.is_empty() - - -func set_value(p_value :Variant) -> void: - match _type: - TYPE_STRING: - _value = str(p_value) - TYPE_BOOL: - _value = bool(p_value) - TYPE_INT: - _value = int(p_value) - TYPE_FLOAT: - _value = float(p_value) - _: - _value = p_value - - -func default() -> Variant: - return _default - - -func category() -> String: - var elements := _name.split("/") - if elements.size() > 3: - return elements[2] - return "" - - -func help() -> String: - return _help - - -func _to_string() -> String: - return "%-64s %-10s %-10s (%s) help:%s set:%s" % [name(), type(), value(), default(), help(), _value_set] diff --git a/addons/gdUnit4/src/core/GdUnitResult.gd b/addons/gdUnit4/src/core/GdUnitResult.gd deleted file mode 100644 index f2d297f..0000000 --- a/addons/gdUnit4/src/core/GdUnitResult.gd +++ /dev/null @@ -1,104 +0,0 @@ -class_name GdUnitResult -extends RefCounted - -enum { - SUCCESS, - WARN, - ERROR, - EMPTY -} - -var _state :Variant -var _warn_message := "" -var _error_message := "" -var _value :Variant = null - - -static func empty() -> GdUnitResult: - var result := GdUnitResult.new() - result._state = EMPTY - return result - - -static func success(p_value :Variant) -> GdUnitResult: - assert(p_value != null, "The value must not be NULL") - var result := GdUnitResult.new() - result._value = p_value - result._state = SUCCESS - return result - - -static func warn(p_warn_message :String, p_value :Variant = null) -> GdUnitResult: - assert(not p_warn_message.is_empty()) #,"The message must not be empty") - var result := GdUnitResult.new() - result._value = p_value - result._warn_message = p_warn_message - result._state = WARN - return result - - -static func error(p_error_message :String) -> GdUnitResult: - assert(not p_error_message.is_empty(), "The message must not be empty") - var result := GdUnitResult.new() - result._value = null - result._error_message = p_error_message - result._state = ERROR - return result - - -func is_success() -> bool: - return _state == SUCCESS - - -func is_warn() -> bool: - return _state == WARN - - -func is_error() -> bool: - return _state == ERROR - - -func is_empty() -> bool: - return _state == EMPTY - - -func value() -> Variant: - return _value - - -func or_else(p_value :Variant) -> Variant: - if not is_success(): - return p_value - return value() - - -func error_message() -> String: - return _error_message - - -func warn_message() -> String: - return _warn_message - - -func _to_string() -> String: - return str(GdUnitResult.serialize(self)) - - -static func serialize(result :GdUnitResult) -> Dictionary: - if result == null: - push_error("Can't serialize a Null object from type GdUnitResult") - return { - "state" : result._state, - "value" : var_to_str(result._value), - "warn_msg" : result._warn_message, - "err_msg" : result._error_message - } - - -static func deserialize(config :Dictionary) -> GdUnitResult: - var result := GdUnitResult.new() - result._value = str_to_var(config.get("value", "")) - result._warn_message = config.get("warn_msg", null) - result._error_message = config.get("err_msg", null) - result._state = config.get("state") - return result diff --git a/addons/gdUnit4/src/core/GdUnitRunner.gd b/addons/gdUnit4/src/core/GdUnitRunner.gd deleted file mode 100644 index 686e4f8..0000000 --- a/addons/gdUnit4/src/core/GdUnitRunner.gd +++ /dev/null @@ -1,168 +0,0 @@ -extends Node - -signal sync_rpc_id_result_received - - -@onready var _client :GdUnitTcpClient = $GdUnitTcpClient -@onready var _executor :GdUnitTestSuiteExecutor = GdUnitTestSuiteExecutor.new() - -enum { - INIT, - RUN, - STOP, - EXIT -} - -const GDUNIT_RUNNER = "GdUnitRunner" - -var _config := GdUnitRunnerConfig.new() -var _test_suites_to_process :Array -var _state :int = INIT -var _cs_executor :RefCounted - - -func _init() -> void: - # minimize scene window checked debug mode - if OS.get_cmdline_args().size() == 1: - DisplayServer.window_set_title("GdUnit4 Runner (Debug Mode)") - else: - DisplayServer.window_set_title("GdUnit4 Runner (Release Mode)") - 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 = GdUnit4CSharpApiLoader.create_executor(self) - - -func _ready() -> void: - var config_result := _config.load_config() - if config_result.is_error(): - push_error(config_result.error_message()) - _state = EXIT - return - _client.connect("connection_failed", _on_connection_failed) - GdUnitSignals.instance().gdunit_event.connect(_on_gdunit_event) - var result := _client.start("127.0.0.1", _config.server_port()) - if result.is_error(): - push_error(result.error_message()) - return - _state = INIT - - -func _on_connection_failed(message :String) -> void: - prints("_on_connection_failed", message, _test_suites_to_process) - _state = STOP - - -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 :float) -> void: - match _state: - INIT: - # wait until client is connected to the GdUnitServer - if _client.is_client_connected(): - 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()) - gdUnitInit() - _state = RUN - RUN: - # all test suites executed - if _test_suites_to_process.is_empty(): - _state = STOP - else: - # process next test suite - set_process(false) - var test_suite :Node = _test_suites_to_process.pop_front() - if _cs_executor != null and _cs_executor.IsExecutable(test_suite): - _cs_executor.Execute(test_suite) - await _cs_executor.ExecutionCompleted - else: - await _executor.execute(test_suite) - set_process(true) - STOP: - _state = EXIT - # give the engine small amount time to finish the rpc - _on_gdunit_event(GdUnitStop.new()) - await get_tree().create_timer(0.1).timeout - await get_tree().process_frame - get_tree().quit(0) - - -func load_test_suits() -> Array: - 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 _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) - _filter_test_case(scaned_suites, selected_tests) - test_suites += scaned_suites - return test_suites - - -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) - _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) - - -func _filter_test_case(test_suites :Array, included_tests :PackedStringArray) -> void: - if included_tests.is_empty(): - return - for test_suite in test_suites: - for test_case in test_suite.get_children(): - _do_filter_test_case(test_suite, test_case, included_tests) - - -func _do_filter_test_case(test_suite :Node, test_case :Node, included_tests :PackedStringArray) -> void: - for included_test in included_tests: - var test_meta :PackedStringArray = included_test.split(":") - var test_name := test_meta[0] - if test_case.get_name() == test_name: - # we have a paremeterized test selection - if test_meta.size() > 1: - var test_param_index := test_meta[1] - test_case.set_test_parameter_index(test_param_index.to_int()) - return - # the test is filtered out - test_suite.remove_child(test_case) - test_case.free() - - -func _collect_test_case_count(testSuites :Array) -> int: - var total :int = 0 - for test_suite in testSuites: - total += test_suite.get_child_count() - return total - - -# RPC send functions -func send_message(message :String) -> void: - _client.rpc_send(RPCMessage.of(message)) - - -func send_test_suite(test_suite :Node) -> void: - _client.rpc_send(RPCGdUnitTestSuite.of(test_suite)) - - -func _on_gdunit_event(event :GdUnitEvent) -> void: - _client.rpc_send(RPCGdUnitEvent.of(event)) - - -# Event bridge from C# GdUnit4.ITestEventListener.cs -func PublishEvent(data :Dictionary) -> void: - var event := GdUnitEvent.new().deserialize(data) - _client.rpc_send(RPCGdUnitEvent.of(event)) diff --git a/addons/gdUnit4/src/core/GdUnitRunner.tscn b/addons/gdUnit4/src/core/GdUnitRunner.tscn deleted file mode 100644 index c1f67b1..0000000 --- a/addons/gdUnit4/src/core/GdUnitRunner.tscn +++ /dev/null @@ -1,10 +0,0 @@ -[gd_scene load_steps=3 format=3 uid="uid://belidlfknh74r"] - -[ext_resource type="Script" path="res://addons/gdUnit4/src/core/GdUnitRunner.gd" id="1"] -[ext_resource type="Script" path="res://addons/gdUnit4/src/network/GdUnitTcpClient.gd" id="2"] - -[node name="Control" type="Node"] -script = ExtResource("1") - -[node name="GdUnitTcpClient" type="Node" parent="."] -script = ExtResource("2") diff --git a/addons/gdUnit4/src/core/GdUnitRunnerConfig.gd b/addons/gdUnit4/src/core/GdUnitRunnerConfig.gd deleted file mode 100644 index 080c18e..0000000 --- a/addons/gdUnit4/src/core/GdUnitRunnerConfig.gd +++ /dev/null @@ -1,153 +0,0 @@ -class_name GdUnitRunnerConfig -extends Resource - -const GdUnitTools := preload("res://addons/gdUnit4/src/core/GdUnitTools.gd") - -const CONFIG_VERSION = "1.0" -const VERSION = "version" -const INCLUDED = "included" -const SKIPPED = "skipped" -const SERVER_PORT = "server_port" -const EXIT_FAIL_FAST ="exit_on_first_fail" - -const CONFIG_FILE = "res://addons/gdUnit4/GdUnitRunner.cfg" - -var _config := { - VERSION : CONFIG_VERSION, - # a set of directories or testsuite paths as key and a optional set of testcases as values - INCLUDED : Dictionary(), - # a set of skipped directories or testsuite paths - SKIPPED : Dictionary(), - # the port of running test server for this session - SERVER_PORT : -1 - } - - -func clear() -> GdUnitRunnerConfig: - _config[INCLUDED] = Dictionary() - _config[SKIPPED] = Dictionary() - return self - - -func set_server_port(port :int) -> GdUnitRunnerConfig: - _config[SERVER_PORT] = port - return self - - -func server_port() -> int: - return _config.get(SERVER_PORT, -1) - - -func self_test() -> GdUnitRunnerConfig: - add_test_suite("res://addons/gdUnit4/test/") - add_test_suite("res://addons/gdUnit4/mono/test/") - return self - - -func add_test_suite(p_resource_path :String) -> GdUnitRunnerConfig: - var to_execute_ := to_execute() - to_execute_[p_resource_path] = to_execute_.get(p_resource_path, PackedStringArray()) - return self - - -func add_test_suites(resource_paths :PackedStringArray) -> GdUnitRunnerConfig: - for resource_path_ in resource_paths: - add_test_suite(resource_path_) - return self - - -func add_test_case(p_resource_path :String, test_name :StringName, test_param_index :int = -1) -> GdUnitRunnerConfig: - var to_execute_ := to_execute() - var test_cases :PackedStringArray = to_execute_.get(p_resource_path, PackedStringArray()) - if test_param_index != -1: - test_cases.append("%s:%d" % [test_name, test_param_index]) - else: - test_cases.append(test_name) - to_execute_[p_resource_path] = test_cases - return self - - -# supports full path or suite name with optional test case name -# [:] -# '/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 = GdUnitFileAccess.make_qualified_path(value).rsplit(":") - if parts[0] == "res": - parts.pop_front() - parts[0] = GdUnitFileAccess.make_qualified_path(parts[0]) - match parts.size(): - 1: skipped()[parts[0]] = PackedStringArray() - 2: skip_test_case(parts[0], parts[1]) - return self - - -func skip_test_suites(resource_paths :PackedStringArray) -> GdUnitRunnerConfig: - for resource_path_ in resource_paths: - skip_test_suite(resource_path_) - return self - - -func skip_test_case(p_resource_path :String, test_name :StringName) -> GdUnitRunnerConfig: - var to_ignore := skipped() - var test_cases :PackedStringArray = to_ignore.get(p_resource_path, PackedStringArray()) - test_cases.append(test_name) - to_ignore[p_resource_path] = test_cases - return self - - -func to_execute() -> Dictionary: - return _config.get(INCLUDED, {"res://" : PackedStringArray()}) - - -func skipped() -> Dictionary: - return _config.get(SKIPPED, PackedStringArray()) - - -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, error_string(error)]) - _config[VERSION] = CONFIG_VERSION - file.store_string(JSON.stringify(_config)) - return GdUnitResult.success(path) - - -func load_config(path :String = CONFIG_FILE) -> GdUnitResult: - if not FileAccess.file_exists(path): - 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, error_string(error)]) - var content := file.get_as_text() - if not content.is_empty() and content[0] == '{': - # Parse as json - var test_json_conv := JSON.new() - var error := test_json_conv.parse(content) - if error != OK: - return GdUnitResult.error("The runner configuration '%s' is invalid! The format is changed please delete it manually and start a new test run." % path) - _config = test_json_conv.get_data() as Dictionary - if not _config.has(VERSION): - return GdUnitResult.error("The runner configuration '%s' is invalid! The format is changed please delete it manually and start a new test run." % path) - fix_value_types() - return GdUnitResult.success(path) - - -func fix_value_types(): - # 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_ - convert_Array_to_PackedStringArray(_config[INCLUDED]) - convert_Array_to_PackedStringArray(_config[SKIPPED]) - - -func convert_Array_to_PackedStringArray(data :Dictionary): - for key in data.keys(): - var values :Array = data[key] - data[key] = PackedStringArray(values) - - -func _to_string() -> String: - return str(_config) diff --git a/addons/gdUnit4/src/core/GdUnitSceneRunnerImpl.gd b/addons/gdUnit4/src/core/GdUnitSceneRunnerImpl.gd deleted file mode 100644 index 44498a6..0000000 --- a/addons/gdUnit4/src/core/GdUnitSceneRunnerImpl.gd +++ /dev/null @@ -1,418 +0,0 @@ -# This class provides a runner for scense to simulate interactions like keyboard or mouse -class_name GdUnitSceneRunnerImpl -extends GdUnitSceneRunner - - -var GdUnitFuncAssertImpl := ResourceLoader.load("res://addons/gdUnit4/src/asserts/GdUnitFuncAssertImpl.gd", "GDScript", ResourceLoader.CACHE_MODE_REUSE) - - -# mapping of mouse buttons and his masks -const MAP_MOUSE_BUTTON_MASKS := { - MOUSE_BUTTON_LEFT : MOUSE_BUTTON_MASK_LEFT, - MOUSE_BUTTON_RIGHT : MOUSE_BUTTON_MASK_RIGHT, - MOUSE_BUTTON_MIDDLE : MOUSE_BUTTON_MASK_MIDDLE, - # https://github.com/godotengine/godot/issues/73632 - MOUSE_BUTTON_WHEEL_UP : 1 << (MOUSE_BUTTON_WHEEL_UP - 1), - MOUSE_BUTTON_WHEEL_DOWN : 1 << (MOUSE_BUTTON_WHEEL_DOWN - 1), - MOUSE_BUTTON_XBUTTON1 : MOUSE_BUTTON_MASK_MB_XBUTTON1, - MOUSE_BUTTON_XBUTTON2 : MOUSE_BUTTON_MASK_MB_XBUTTON2, -} - -var _is_disposed := false -var _current_scene :Node = null -var _awaiter :GdUnitAwaiter = GdUnitAwaiter.new() -var _verbose :bool -var _simulate_start_time :LocalTime -var _last_input_event :InputEvent = null -var _mouse_button_on_press := [] -var _key_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): - _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 not p_hide_push_errors: - push_error("GdUnitSceneRunner: Can't load scene by given resource path: '%s'. The resource not exists." % p_scene) - return - if !str(p_scene).ends_with("tscn"): - 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() - _scene_auto_free = true - else: - # verify we have a node instance - if not p_scene is Node: - if not p_hide_push_errors: - push_error("GdUnitSceneRunner: The given instance '%s' is not a Node." % p_scene) - return - _current_scene = p_scene - if _current_scene == null: - if not p_hide_push_errors: - push_error("GdUnitSceneRunner: Scene must be not null!") - return - _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): - 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 - 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): - if what == NOTIFICATION_PREDELETE and is_instance_valid(self): - # reset time factor to normal - __deactivate_time_factor() - if is_instance_valid(_current_scene): - _scene_tree().root.remove_child(_current_scene) - # do only free scenes instanciated by this runner - if _scene_auto_free: - _current_scene.free() - _is_disposed = true - _current_scene = null - # 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_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) - return self - - -func simulate_key_press(key_code :int, shift_pressed := false, ctrl_pressed := false) -> GdUnitSceneRunner: - __print_current_focus() - var event = InputEventKey.new() - event.pressed = true - event.keycode = key_code - event.physical_keycode = key_code - 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 - _apply_input_modifiers(event) - _key_on_press.append(key_code) - return _handle_input_event(event) - - -func simulate_key_release(key_code :int, shift_pressed := false, ctrl_pressed := false) -> GdUnitSceneRunner: - __print_current_focus() - var event = InputEventKey.new() - event.pressed = false - event.keycode = key_code - event.physical_keycode = key_code - 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 - _apply_input_modifiers(event) - _key_on_press.erase(key_code) - return _handle_input_event(event) - - -func set_mouse_pos(pos :Vector2) -> GdUnitSceneRunner: - var event := InputEventMouseMotion.new() - event.position = pos - event.global_position = get_global_mouse_position() - _apply_input_modifiers(event) - return _handle_input_event(event) - - -func get_mouse_position() -> Vector2: - if _last_input_event is InputEventMouse: - return _last_input_event.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 Engine.get_main_loop().root.get_mouse_position() - - -func simulate_mouse_move(pos :Vector2) -> GdUnitSceneRunner: - var event := InputEventMouseMotion.new() - event.position = pos - event.relative = pos - get_mouse_position() - event.global_position = get_global_mouse_position() - _apply_input_mouse_mask(event) - _apply_input_modifiers(event) - return _handle_input_event(event) - - -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 - - -func simulate_mouse_button_pressed(buttonIndex :MouseButton, double_click := false) -> GdUnitSceneRunner: - simulate_mouse_button_press(buttonIndex, double_click) - simulate_mouse_button_release(buttonIndex) - return self - - -func simulate_mouse_button_press(buttonIndex :MouseButton, double_click := false) -> GdUnitSceneRunner: - var event := InputEventMouseButton.new() - event.button_index = buttonIndex - event.pressed = true - event.double_click = double_click - _apply_input_mouse_position(event) - _apply_input_mouse_mask(event) - _apply_input_modifiers(event) - _mouse_button_on_press.append(buttonIndex) - return _handle_input_event(event) - - -func simulate_mouse_button_release(buttonIndex :MouseButton) -> GdUnitSceneRunner: - var event := InputEventMouseButton.new() - event.button_index = buttonIndex - event.pressed = false - _apply_input_mouse_position(event) - _apply_input_mouse_mask(event) - _apply_input_modifiers(event) - _mouse_button_on_press.erase(buttonIndex) - return _handle_input_event(event) - - -func set_time_factor(time_factor := 1.0) -> GdUnitSceneRunner: - _time_factor = min(9.0, time_factor) - __activate_time_factor() - __print("set time factor: %f" % _time_factor) - __print("set physics physics_ticks_per_second: %d" % (_saved_iterations_per_second*_time_factor)) - return self - - -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 - else: - 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(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) - await _awaiter.await_signal_idle_frames(source, signal_name, args, 10000) - return self - - -func await_func(func_name :String, args := []) -> GdUnitFuncAssert: - 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(scene(), signal_name, args, timeout) - - -func await_signal_on(source :Object, signal_name :String, args := [], timeout := 2000 ): - await _awaiter.await_signal_on(source, signal_name, args, timeout) - - -# maximizes the window to bring the scene visible -func maximize_view() -> GdUnitSceneRunner: - DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_WINDOWED) - DisplayServer.window_move_to_foreground() - return self - - -func _property_exists(name :String) -> bool: - return scene().get_property_list().any(func(properties :Dictionary) : 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 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; - 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 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 scene().find_child(name, recursive, owned) - - -func _scene_name() -> String: - 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("@"): - return scene_name - return scene_script.resource_name.get_basename() - - -func __activate_time_factor() -> void: - Engine.set_time_scale(_time_factor) - Engine.set_physics_ticks_per_second((_saved_iterations_per_second * _time_factor) as int) - - -func __deactivate_time_factor() -> void: - Engine.set_time_scale(1) - Engine.set_physics_ticks_per_second(_saved_iterations_per_second as int) - - -# copy over current active modifiers -func _apply_input_modifiers(event :InputEvent) -> void: - if _last_input_event is InputEventWithModifiers and event is InputEventWithModifiers: - event.meta_pressed = event.meta_pressed or _last_input_event.meta_pressed - event.alt_pressed = event.alt_pressed or _last_input_event.alt_pressed - event.shift_pressed = event.shift_pressed or _last_input_event.shift_pressed - event.ctrl_pressed = event.ctrl_pressed or _last_input_event.ctrl_pressed - # this line results into reset the control_pressed state!!! - #event.command_or_control_autoremap = event.command_or_control_autoremap or _last_input_event.command_or_control_autoremap - - -# copy over current active mouse mask and combine with curren mask -func _apply_input_mouse_mask(event :InputEvent) -> void: - # first apply last mask - 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) - if event.is_pressed(): - event.button_mask |= button_mask - else: - event.button_mask ^= button_mask - - -# copy over last mouse position if need -func _apply_input_mouse_position(event :InputEvent) -> void: - if _last_input_event is InputEventMouse and event is InputEventMouseButton: - event.position = _last_input_event.position - - -## just for testing maunally event to action handling -func _handle_actions(event :InputEvent) -> bool: - var is_action_match := false - for action in InputMap.get_actions(): - if InputMap.event_is_action(event, action, true): - is_action_match = true - prints(action, event, event.is_ctrl_pressed()) - if event.is_pressed(): - Input.action_press(action, InputMap.action_get_deadzone(action)) - else: - Input.action_release(action) - return is_action_match - - -# 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): - if event is InputEventMouse: - Input.warp_mouse(event.position) - Input.parse_input_event(event) - Input.flush_buffered_events() - 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 - - -func _reset_input_to_default() -> void: - # reset all mouse button to inital state if need - for m_button 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(): - if Input.is_key_pressed(key_scancode): - simulate_key_release(key_scancode) - _key_on_press.clear() - Input.flush_buffered_events() - _last_input_event = null - - -func __print(message :String) -> void: - if _verbose: - prints(message) - - -func __print_current_focus() -> void: - if not _verbose: - return - var focused_node = scene().get_viewport().gui_get_focus_owner() - if focused_node: - prints(" focus checked %s" % focused_node) - else: - prints(" no focus set") - - -func scene() -> Node: - 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/GdUnitScriptType.gd b/addons/gdUnit4/src/core/GdUnitScriptType.gd deleted file mode 100644 index 7e1be51..0000000 --- a/addons/gdUnit4/src/core/GdUnitScriptType.gd +++ /dev/null @@ -1,16 +0,0 @@ -class_name GdUnitScriptType -extends RefCounted - -const UNKNOWN := "" -const CS := "cs" -const GD := "gd" - - -static func type_of(script :Script) -> String: - if script == null: - return UNKNOWN - if GdObjects.is_gd_script(script): - return GD - if GdObjects.is_cs_script(script): - return CS - return UNKNOWN diff --git a/addons/gdUnit4/src/core/GdUnitSettings.gd b/addons/gdUnit4/src/core/GdUnitSettings.gd deleted file mode 100644 index f3e91f5..0000000 --- a/addons/gdUnit4/src/core/GdUnitSettings.gd +++ /dev/null @@ -1,336 +0,0 @@ -@tool -class_name GdUnitSettings -extends RefCounted - - -const MAIN_CATEGORY = "gdunit4" -# Common Settings -const COMMON_SETTINGS = MAIN_CATEGORY + "/settings" - -const GROUP_COMMON = COMMON_SETTINGS + "/common" -const UPDATE_NOTIFICATION_ENABLED = GROUP_COMMON + "/update_notification_enabled" -const SERVER_TIMEOUT = GROUP_COMMON + "/server_connection_timeout_minutes" - -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" - - -# Report Setiings -const REPORT_SETTINGS = MAIN_CATEGORY + "/report" -const GROUP_GODOT = REPORT_SETTINGS + "/godot" -const REPORT_PUSH_ERRORS = GROUP_GODOT + "/push_error" -const REPORT_SCRIPT_ERRORS = GROUP_GODOT + "/script_error" -const REPORT_ORPHANS = REPORT_SETTINGS + "/verbose_orphans" -const GROUP_ASSERT = REPORT_SETTINGS + "/assert" -const REPORT_ASSERT_WARNINGS = GROUP_ASSERT + "/verbose_warnings" -const REPORT_ASSERT_ERRORS = GROUP_ASSERT + "/verbose_errors" -const REPORT_ASSERT_STRICT_NUMBER_TYPE_COMPARE = GROUP_ASSERT + "/strict_number_type_compare" - -# Godot debug stdout/logging settings -const CATEGORY_LOGGING := "debug/file_logging/" -const STDOUT_ENABLE_TO_FILE = CATEGORY_LOGGING + "enable_file_logging" -const STDOUT_WITE_TO_FILE = CATEGORY_LOGGING + "log_path" - - -# GdUnit Templates -const TEMPLATES = MAIN_CATEGORY + "/templates" -const TEMPLATES_TS = TEMPLATES + "/testsuite" -const TEMPLATE_TS_GD = TEMPLATES_TS + "/GDScript" -const TEMPLATE_TS_CS = TEMPLATES_TS + "/CSharpScript" - - -# UI Setiings -const UI_SETTINGS = MAIN_CATEGORY + "/ui" -const GROUP_UI_INSPECTOR = UI_SETTINGS + "/inspector" -const INSPECTOR_NODE_COLLAPSE = GROUP_UI_INSPECTOR + "/node_collapse" - - -# Shortcut Setiings -const SHORTCUT_SETTINGS = MAIN_CATEGORY + "/Shortcuts" -const GROUP_SHORTCUT_INSPECTOR = SHORTCUT_SETTINGS + "/inspector" -const SHORTCUT_INSPECTOR_RERUN_TEST = GROUP_SHORTCUT_INSPECTOR + "/rerun_test" -const SHORTCUT_INSPECTOR_RERUN_TEST_DEBUG = GROUP_SHORTCUT_INSPECTOR + "/rerun_test_debug" -const SHORTCUT_INSPECTOR_RUN_TEST_OVERALL = GROUP_SHORTCUT_INSPECTOR + "/run_test_overall" -const SHORTCUT_INSPECTOR_RUN_TEST_STOP = GROUP_SHORTCUT_INSPECTOR + "/run_test_stop" - -const GROUP_SHORTCUT_EDITOR = SHORTCUT_SETTINGS + "/editor" -const SHORTCUT_EDITOR_RUN_TEST = GROUP_SHORTCUT_EDITOR + "/run_test" -const SHORTCUT_EDITOR_RUN_TEST_DEBUG = GROUP_SHORTCUT_EDITOR + "/run_test_debug" -const SHORTCUT_EDITOR_CREATE_TEST = GROUP_SHORTCUT_EDITOR + "/create_test" - -const GROUP_SHORTCUT_FILESYSTEM = SHORTCUT_SETTINGS + "/filesystem" -const SHORTCUT_FILESYSTEM_RUN_TEST = GROUP_SHORTCUT_FILESYSTEM + "/run_test" -const SHORTCUT_FILESYSTEM_RUN_TEST_DEBUG = GROUP_SHORTCUT_FILESYSTEM + "/run_test_debug" - - -# Toolbar Setiings -const GROUP_UI_TOOLBAR = UI_SETTINGS + "/toolbar" -const INSPECTOR_TOOLBAR_BUTTON_RUN_OVERALL = GROUP_UI_TOOLBAR + "/run_overall" - -# defaults -# server connection timeout in minutes -const DEFAULT_SERVER_TIMEOUT :int = 30 -# test case runtime timeout in seconds -const DEFAULT_TEST_TIMEOUT :int = 60*5 -# the folder to create new test-suites -const DEFAULT_TEST_LOOKUP_FOLDER := "test" - -# help texts -const HELP_TEST_LOOKUP_FOLDER := "Sets the subfolder for the search/creation of test suites. (leave empty to use source folder)" - -enum NAMING_CONVENTIONS { - AUTO_DETECT, - SNAKE_CASE, - PASCAL_CASE, -} - - -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(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.") - create_property_if_need(TEMPLATE_TS_GD, GdUnitTestSuiteTemplate.default_GD_template(), "Defines the test suite template") - create_shortcut_properties_if_need() - migrate_properties() - - - -static func migrate_properties() -> void: - var TEST_ROOT_FOLDER := "gdunit4/settings/test/test_root_folder" - if get_property(TEST_ROOT_FOLDER) != null: - migrate_property(TEST_ROOT_FOLDER,\ - TEST_LOOKUP_FOLDER,\ - DEFAULT_TEST_LOOKUP_FOLDER,\ - HELP_TEST_LOOKUP_FOLDER,\ - func(value): return DEFAULT_TEST_LOOKUP_FOLDER if value == null else value) - - -static func create_shortcut_properties_if_need() -> void: - # inspector - create_property_if_need(SHORTCUT_INSPECTOR_RERUN_TEST, GdUnitShortcut.default_keys(GdUnitShortcut.ShortCut.RERUN_TESTS), "Rerun of the last tests performed.") - create_property_if_need(SHORTCUT_INSPECTOR_RERUN_TEST_DEBUG, GdUnitShortcut.default_keys(GdUnitShortcut.ShortCut.RERUN_TESTS_DEBUG), "Rerun of the last tests performed (Debug).") - create_property_if_need(SHORTCUT_INSPECTOR_RUN_TEST_OVERALL, GdUnitShortcut.default_keys(GdUnitShortcut.ShortCut.RUN_TESTS_OVERALL), "Runs all tests (Debug).") - create_property_if_need(SHORTCUT_INSPECTOR_RUN_TEST_STOP, GdUnitShortcut.default_keys(GdUnitShortcut.ShortCut.STOP_TEST_RUN), "Stops the current test execution.") - # script editor - create_property_if_need(SHORTCUT_EDITOR_RUN_TEST, GdUnitShortcut.default_keys(GdUnitShortcut.ShortCut.RUN_TESTCASE), "Runs the currently selected test.") - create_property_if_need(SHORTCUT_EDITOR_RUN_TEST_DEBUG, GdUnitShortcut.default_keys(GdUnitShortcut.ShortCut.RUN_TESTCASE_DEBUG), "Runs the currently selected test (Debug).") - create_property_if_need(SHORTCUT_EDITOR_CREATE_TEST, GdUnitShortcut.default_keys(GdUnitShortcut.ShortCut.CREATE_TEST), "Creates a new test case for the currently selected function.") - # filesystem - create_property_if_need(SHORTCUT_FILESYSTEM_RUN_TEST, GdUnitShortcut.default_keys(GdUnitShortcut.ShortCut.NONE), "Runs all test suites on the selected folder or file.") - create_property_if_need(SHORTCUT_FILESYSTEM_RUN_TEST_DEBUG, GdUnitShortcut.default_keys(GdUnitShortcut.ShortCut.NONE), "Runs all test suites on the selected folder or file (Debug).") - - -static func create_property_if_need(name :String, default :Variant, help :="", value_set := PackedStringArray()) -> void: - if not ProjectSettings.has_setting(name): - #prints("GdUnit4: Set inital settings '%s' to '%s'." % [name, str(default)]) - ProjectSettings.set_setting(name, default) - - ProjectSettings.set_initial_value(name, default) - help += "" if value_set.is_empty() else " %s" % value_set - set_help(name, default, help) - - -static func set_help(property_name :String, value :Variant, help :String) -> void: - ProjectSettings.add_property_info({ - "name": property_name, - "type": typeof(value), - "hint": PROPERTY_HINT_TYPE_STRING, - "hint_string": help - }) - - -static func get_setting(name :String, default :Variant) -> Variant: - if ProjectSettings.has_setting(name): - return ProjectSettings.get_setting(name) - return default - - -static func is_update_notification_enabled() -> bool: - if ProjectSettings.has_setting(UPDATE_NOTIFICATION_ENABLED): - return ProjectSettings.get_setting(UPDATE_NOTIFICATION_ENABLED) - return false - - -static func set_update_notification(enable :bool) -> void: - ProjectSettings.set_setting(UPDATE_NOTIFICATION_ENABLED, enable) - ProjectSettings.save() - - -static func get_log_path() -> String: - return ProjectSettings.get_setting(STDOUT_WITE_TO_FILE) - - -static func set_log_path(path :String) -> void: - ProjectSettings.set_setting(STDOUT_ENABLE_TO_FILE, true) - ProjectSettings.set_setting(STDOUT_WITE_TO_FILE, path) - ProjectSettings.save() - - -# the configured server connection timeout in ms -static func server_timeout() -> int: - return get_setting(SERVER_TIMEOUT, DEFAULT_SERVER_TIMEOUT) * 60 * 1000 - - -# the configured test case timeout in ms -static func test_timeout() -> int: - return get_setting(TEST_TIMEOUT, DEFAULT_TEST_TIMEOUT) * 1000 - - -# the root folder to store/generate test-suites -static func test_root_folder() -> String: - return get_setting(TEST_LOOKUP_FOLDER, DEFAULT_TEST_LOOKUP_FOLDER) - - -static func is_verbose_assert_warnings() -> bool: - return get_setting(REPORT_ASSERT_WARNINGS, true) - - -static func is_verbose_assert_errors() -> bool: - return get_setting(REPORT_ASSERT_ERRORS, true) - - -static func is_verbose_orphans() -> bool: - return get_setting(REPORT_ORPHANS, true) - - -static func is_strict_number_type_compare() -> bool: - return get_setting(REPORT_ASSERT_STRICT_NUMBER_TYPE_COMPARE, true) - - -static func is_report_push_errors() -> bool: - return get_setting(REPORT_PUSH_ERRORS, false) - - -static func is_report_script_errors() -> bool: - return get_setting(REPORT_SCRIPT_ERRORS, true) - - -static func is_inspector_node_collapse() -> bool: - return get_setting(INSPECTOR_NODE_COLLAPSE, true) - - -static func is_inspector_toolbar_button_show() -> bool: - return get_setting(INSPECTOR_TOOLBAR_BUTTON_RUN_OVERALL, true) - - -static func is_log_enabled() -> bool: - return ProjectSettings.get_setting(STDOUT_ENABLE_TO_FILE) - - -static func list_settings(category :String) -> Array[GdUnitProperty]: - var settings :Array[GdUnitProperty] = [] - for property in ProjectSettings.get_property_list(): - var property_name :String = property["name"] - if property_name.begins_with(category): - 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) - settings.append(GdUnitProperty.new(property_name, property["type"], value, default, help, value_set)) - return settings - - -static func extract_value_set_from_help(value :String) -> PackedStringArray: - var regex := RegEx.new() - regex.compile("\\[(.+)\\]") - var matches := regex.search_all(value) - if matches.is_empty(): - return PackedStringArray() - var values :String = matches[0].get_string(1) - return values.replacen(" ", "").replacen("\"", "").split(",", false) - - -static func update_property(property :GdUnitProperty) -> Variant: - var current_value :Variant = ProjectSettings.get_setting(property.name()) - if current_value != property.value(): - var error :Variant = validate_property_value(property) - if error != null: - return error - ProjectSettings.set_setting(property.name(), property.value()) - GdUnitSignals.instance().gdunit_settings_changed.emit(property) - _save_settings() - return null - - -static func reset_property(property :GdUnitProperty) -> void: - ProjectSettings.set_setting(property.name(), property.default()) - GdUnitSignals.instance().gdunit_settings_changed.emit(property) - _save_settings() - - -static func validate_property_value(property :GdUnitProperty) -> Variant: - match property.name(): - TEST_LOOKUP_FOLDER: - return validate_lookup_folder(property.value()) - _: return null - - -static func validate_lookup_folder(value :String) -> Variant: - if value.is_empty() or value == "/": - return null - if value.contains("res:"): - return "Test Lookup Folder: do not allowed to contains 'res://'" - if not value.is_valid_filename(): - return "Test Lookup Folder: contains invalid characters! e.g (: / \\ ? * \" | % < >)" - return null - - -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() - 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 :Dictionary) -> bool: return property["name"] == name) - - -static func get_property(name :String) -> GdUnitProperty: - for property in ProjectSettings.get_property_list(): - var property_name :String = property["name"] - if property_name == 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) - return GdUnitProperty.new(property_name, property["type"], value, default, help, value_set) - return null - - -static func migrate_property(old_property :String, new_property :String, default_value :Variant, help :String, converter := Callable()) -> void: - var property := get_property(old_property) - if property == null: - prints("Migration not possible, property '%s' not found" % old_property) - return - 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) - ProjectSettings.clear(old_property) - prints("Succesfull migrated property '%s' -> '%s' value: %s" % [old_property, new_property, value]) - - -static func dump_to_tmp() -> void: - ProjectSettings.save_custom("user://project_settings.godot") - - -static func restore_dump_from_tmp() -> void: - DirAccess.copy_absolute("user://project_settings.godot", "res://project.godot") diff --git a/addons/gdUnit4/src/core/GdUnitSignalAwaiter.gd b/addons/gdUnit4/src/core/GdUnitSignalAwaiter.gd deleted file mode 100644 index 0172b73..0000000 --- a/addons/gdUnit4/src/core/GdUnitSignalAwaiter.gd +++ /dev/null @@ -1,64 +0,0 @@ -class_name GdUnitSignalAwaiter -extends RefCounted - -signal signal_emitted(action) - -const NO_ARG :Variant = GdUnitConstants.NO_ARG - -var _wait_on_idle_frame = false -var _interrupted := false -var _time_left := 0 -var _timeout_millis :int - - -func _init(timeout_millis :int, wait_on_idle_frame := false): - _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): - var signal_args :Variant = GdArrayTools.filter_value([arg0,arg1,arg2,arg3,arg4,arg5,arg6,arg7,arg8,arg9], NO_ARG) - signal_emitted.emit(signal_args) - - -func is_interrupted() -> bool: - return _interrupted - - -func elapsed_time() -> int: - return _time_left - - -func on_signal(source :Object, signal_name :String, expected_signal_args :Array) -> Variant: - # register checked signal to wait for - source.connect(signal_name, _on_signal_emmited) - # install timeout timer - 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.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 - while true: - value = await signal_emitted - if _interrupted: - break - if not (value is Array): - value = [value] - 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 diff --git a/addons/gdUnit4/src/core/GdUnitSignalCollector.gd b/addons/gdUnit4/src/core/GdUnitSignalCollector.gd deleted file mode 100644 index f797e15..0000000 --- a/addons/gdUnit4/src/core/GdUnitSignalCollector.gd +++ /dev/null @@ -1,103 +0,0 @@ -# It connects to all signals of given emitter and collects received signals and arguments -# The collected signals are cleand finally when the emitter is freed. -class_name GdUnitSignalCollector -extends RefCounted - -const NO_ARG :Variant = GdUnitConstants.NO_ARG -const SIGNAL_BLACK_LIST = []#["tree_exiting", "tree_exited", "child_exiting_tree"] - -# { -# emitter : { -# signal_name : [signal_args], -# ... -# } -# } -var _collected_signals :Dictionary = {} - - -func clear() -> void: - for emitter 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): - if is_instance_valid(emitter): - # check emitter is already registerd - if _collected_signals.has(emitter): - return - _collected_signals[emitter] = Dictionary() - # connect to 'tree_exiting' of the emitter to finally release all acquired resources/connections. - if emitter is Node and !emitter.tree_exiting.is_connected(unregister_emitter): - 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"] - # set inital collected to empty - if not is_signal_collecting(emitter, signal_name): - _collected_signals[emitter][signal_name] = Array() - if SIGNAL_BLACK_LIST.find(signal_name) != -1: - continue - if !emitter.is_connected(signal_name, _on_signal_emmited): - var err := emitter.connect(signal_name, _on_signal_emmited.bind(emitter, signal_name)) - if err != OK: - push_error("Can't connect to signal %s on %s. Error: %s" % [signal_name, emitter, error_string(err)]) - - -# 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): - if is_instance_valid(emitter): - for signal_def in emitter.get_signal_list(): - var signal_name = 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) - # 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) - if is_signal_collecting(emitter, signal_name): - _collected_signals[emitter][signal_name].append(signal_args) - - -func reset_received_signals(emitter :Object, signal_name: String, signal_args :Array): - #_debug_signal_list("before claer"); - if _collected_signals.has(emitter): - var signals_by_emitter = _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: - return _collected_signals.has(emitter) and _collected_signals[emitter].has(signal_name) - - -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) - if GdObjects.equals(received_args, args): - return true - return false - - -func _debug_signal_list(message :String): - prints("-----", message, "-------") - prints("senders {") - for emitter in _collected_signals: - prints("\t", emitter) - for signal_name in _collected_signals[emitter]: - var args = _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 deleted file mode 100644 index faf089f..0000000 --- a/addons/gdUnit4/src/core/GdUnitSignals.gd +++ /dev/null @@ -1,34 +0,0 @@ -class_name GdUnitSignals -extends RefCounted - -signal gdunit_client_connected(client_id :int) -signal gdunit_client_disconnected(client_id :int) -signal gdunit_client_terminated() - -signal gdunit_event(event :GdUnitEvent) -signal gdunit_event_debug(event :GdUnitEvent) -signal gdunit_add_test_suite(test_suite :GdUnitTestSuiteDto) -signal gdunit_message(message :String) -signal gdunit_report(execution_context_id :int, report :GdUnitReport) -signal gdunit_set_test_failed(is_failed :bool) - -signal gdunit_settings_changed(property :GdUnitProperty) - -const META_KEY := "GdUnitSignals" - - -static func instance() -> GdUnitSignals: - if Engine.has_meta(META_KEY): - return Engine.get_meta(META_KEY) - var instance_ := GdUnitSignals.new() - Engine.set_meta(META_KEY, instance_) - return instance_ - - -static func dispose() -> void: - var signals := instance() - # cleanup connected signals - for signal_ in signals.get_signal_list(): - for connection in signals.get_signal_connection_list(signal_["name"]): - connection["signal"].disconnect(connection["callable"]) - Engine.remove_meta(META_KEY) diff --git a/addons/gdUnit4/src/core/GdUnitSingleton.gd b/addons/gdUnit4/src/core/GdUnitSingleton.gd deleted file mode 100644 index 879c9d4..0000000 --- a/addons/gdUnit4/src/core/GdUnitSingleton.gd +++ /dev/null @@ -1,49 +0,0 @@ -################################################################################ -# 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 - - -const GdUnitTools := preload("res://addons/gdUnit4/src/core/GdUnitTools.gd") -const MEATA_KEY := "GdUnitSingletons" - - -static func instance(name :String, clazz :Callable) -> Variant: - if Engine.has_meta(name): - return Engine.get_meta(name) - var singleton :Variant = clazz.call() - Engine.set_meta(name, singleton) - GdUnitTools.prints_verbose("Register singleton '%s:%s'" % [name, singleton]) - var singletons :PackedStringArray = Engine.get_meta(MEATA_KEY, PackedStringArray()) - singletons.append(name) - Engine.set_meta(MEATA_KEY, singletons) - return singleton - - -static func unregister(p_singleton :String) -> 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) - GdUnitTools.prints_verbose(" Free singleton instance '%s:%s'" % [p_singleton, instance_]) - GdUnitTools.free_instance(instance_) - Engine.remove_meta(p_singleton) - GdUnitTools.prints_verbose(" Successfully freed '%s'" % p_singleton) - Engine.set_meta(MEATA_KEY, singletons) - - -static func dispose() -> 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) - Engine.remove_meta(MEATA_KEY) - GdUnitTools.prints_verbose("----------------------------------------------------------------") diff --git a/addons/gdUnit4/src/core/GdUnitTestSuiteBuilder.gd b/addons/gdUnit4/src/core/GdUnitTestSuiteBuilder.gd deleted file mode 100644 index 6cd2ee4..0000000 --- a/addons/gdUnit4/src/core/GdUnitTestSuiteBuilder.gd +++ /dev/null @@ -1,18 +0,0 @@ -class_name GdUnitTestSuiteBuilder -extends RefCounted - - -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) - if GdObjects.is_cs_script(source): - 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] - var func_name := parser.parse_func_name(current_line) - if func_name.is_empty(): - return GdUnitResult.error("No function found at line: %d." % line_number) - return GdUnitTestSuiteScanner.create_test_case(test_suite_path, func_name, source.resource_path) diff --git a/addons/gdUnit4/src/core/GdUnitTestSuiteScanner.gd b/addons/gdUnit4/src/core/GdUnitTestSuiteScanner.gd deleted file mode 100644 index af933b2..0000000 --- a/addons/gdUnit4/src/core/GdUnitTestSuiteScanner.gd +++ /dev/null @@ -1,339 +0,0 @@ -class_name GdUnitTestSuiteScanner -extends RefCounted - -const TEST_FUNC_TEMPLATE =""" - -func test_${func_name}() -> void: - # remove this line and complete your test - assert_not_yet_implemented() -""" - - -# we exclude the gdunit source directorys by default -const exclude_scan_directories = [ - "res://addons/gdUnit4/bin", - "res://addons/gdUnit4/src", - "res://reports"] - - -var _script_parser := GdScriptParser.new() -var _extends_test_suite_classes := Array() -var _expression_runner := GdUnitExpressionRunner.new() - - -func scan_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"]) - - -func scan(resource_path :String) -> Array[Node]: - scan_testsuite_classes() - # if single testsuite requested - if FileAccess.file_exists(resource_path): - var test_suite := _parse_is_test_suite(resource_path) - 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) - return [] - return _scan_test_suites(base_dir, []) - - -func _scan_test_suites(dir :DirAccess, collected_suites :Array[Node]) -> Array[Node]: - if exclude_scan_directories.has(dir.get_current_dir()): - return collected_suites - prints("Scanning for test suites in:", dir.get_current_dir()) - 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) - 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 test_suite := _parse_is_test_suite(resource_path) - if test_suite: - collected_suites.append(test_suite) - if OS.is_stdout_verbose() and time.elapsed_since_ms() > 300: - push_warning("Scanning of test-suite '%s' took more than 300ms: " % resource_path, time.elapsed_since()) - file_name = dir.get_next() - return collected_suites - - -static func _file(dir :DirAccess, file_name :String) -> String: - var current_dir := dir.get_current_dir() - if current_dir.ends_with("/"): - return current_dir + file_name - return current_dir + "/" + file_name - - -func _parse_is_test_suite(resource_path :String) -> Node: - if not GdUnitTestSuiteScanner._is_script_format_supported(resource_path): - return null - if GdUnit4CSharpApiLoader.is_test_suite(resource_path): - return GdUnit4CSharpApiLoader.parse_test_suite(resource_path) - var script :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 - - -static func _is_script_format_supported(resource_path :String) -> bool: - var ext := resource_path.get_extension() - if ext == "gd": - return true - return GdUnit4CSharpApiLoader.is_csharp_file(resource_path) - - -func _parse_test_suite(script :GDScript) -> GdUnitTestSuite: - # find all test cases - var test_case_names := _extract_test_case_names(script) - # test suite do not contains any tests - if test_case_names.is_empty(): - push_warning("The test suite %s do not contain any tests, it excludes from discovery." % script.resource_path) - return null; - - var test_suite = script.new() - test_suite.set_name(GdUnitTestSuiteScanner.parse_test_suite_name(script)) - # add test cases to test suite and parse test case line nummber - _parse_and_add_test_cases(test_suite, script, test_case_names) - # not all test case parsed? - # we have to scan the base class to - if not test_case_names.is_empty(): - var base_script :GDScript = test_suite.get_script().get_base_script() - while base_script is GDScript: - # do not parse testsuite itself - if base_script.resource_path.find("GdUnitTestSuite") == -1: - _parse_and_add_test_cases(test_suite, base_script, test_case_names) - base_script = base_script.get_base_script() - return test_suite - - -func _extract_test_case_names(script :GDScript) -> PackedStringArray: - var names := PackedStringArray() - for method in script.get_script_method_list(): - var funcName :String = method["name"] - if funcName.begins_with("test"): - names.append(funcName) - return names - - -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): - for arg in fd.args(): - match arg.name(): - _TestCase.ARGUMENT_SKIP: - var result = _expression_runner.execute(script, arg.value_as_string()) - if result is bool: - test_suite.__is_skipped = result - else: - push_error("Test expression '%s' cannot be evaluated because it is not of type bool!" % arg.value_as_string()) - _TestCase.ARGUMENT_SKIP_REASON: - test_suite.__skip_reason = arg.value_as_string() - _: - 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): - var timeout := _TestCase.DEFAULT_TIMEOUT - var iterations := Fuzzer.ITERATION_DEFAULT_COUNT - var seed_value := -1 - var is_skipped := false - 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? - if arg.type() == GdObjects.TYPE_FUZZER: - fuzzers.append(arg) - elif arg.has_default(): - match arg.name(): - _TestCase.ARGUMENT_TIMEOUT: - timeout = arg.default() - _TestCase.ARGUMENT_SKIP: - var result = _expression_runner.execute(script, arg.value_as_string()) - if result is bool: - is_skipped = result - else: - push_error("Test expression '%s' cannot be evaluated because it is not of type bool!" % arg.value_as_string()) - _TestCase.ARGUMENT_SKIP_REASON: - skip_reason = arg.value_as_string() - Fuzzer.ARGUMENT_ITERATIONS: - iterations = arg.default() - Fuzzer.ARGUMENT_SEED: - 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) - - -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.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) - for fd in function_descriptors: - if fd.name() == "before": - _handle_test_suite_arguments(test_suite, script, fd) - if test_cases_to_find.has(fd.name()): - _handle_test_case_arguments(test_suite, script, fd) - - -const TEST_CASE_ARGUMENTS = [_TestCase.ARGUMENT_TIMEOUT, _TestCase.ARGUMENT_SKIP, _TestCase.ARGUMENT_SKIP_REASON, Fuzzer.ARGUMENT_ITERATIONS, Fuzzer.ARGUMENT_SEED] - -func _validate_argument(fd :GdFunctionDescriptor, test_case :_TestCase) -> void: - if fd.is_parameterized(): - return - for argument in fd.args(): - if argument.type() == GdObjects.TYPE_FUZZER or argument.name() in TEST_CASE_ARGUMENTS: - continue - test_case.skip(true, "Unknown test case argument '%s' found." % argument.name()) - - -# converts given file name by configured naming convention -static func _to_naming_convention(file_name :String) -> String: - var nc :int = GdUnitSettings.get_setting(GdUnitSettings.TEST_SITE_NAMING_CONVENTION, 0) - match nc: - GdUnitSettings.NAMING_CONVENTIONS.AUTO_DETECT: - if GdObjects.is_snake_case(file_name): - return GdObjects.to_snake_case(file_name + "Test") - return GdObjects.to_pascal_case(file_name + "Test") - GdUnitSettings.NAMING_CONVENTIONS.SNAKE_CASE: - return GdObjects.to_snake_case(file_name + "Test") - GdUnitSettings.NAMING_CONVENTIONS.PASCAL_CASE: - return GdObjects.to_pascal_case(file_name + "Test") - push_error("Unexpected case") - return "--" - - -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 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/") - if src_folder != -1: - test_suite_path = source_script_path.replace("/src/", "/"+test_root_folder+"/") - else: - 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] - # rebuild plugin path - for index in range(3, paths.size()): - test_suite_path += "/" + paths[index] - else: - test_suite_path = paths[0] + "//" + test_root_folder - for index in range(1, paths.size()): - test_suite_path += "/" + paths[index] - return normalize_path(test_suite_path).replace(file_name, suite_name) - - -static func normalize_path(path :String) -> String: - return path.replace("///", "/") - - -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 script := GDScript.new() - script.source_code = GdUnitTestSuiteTemplate.build_template(source_path) - var error := ResourceSaver.save(script, test_suite_path) - if error != OK: - return GdUnitResult.error("Can't create test suite at: %s. Error code %s" % [test_suite_path, error]) - return GdUnitResult.success(test_suite_path) - - -static func get_test_case_line_number(resource_path :String, func_name :String) -> int: - var file := FileAccess.open(resource_path, FileAccess.READ) - if file != null: - var script_parser := GdScriptParser.new() - var line_number := 0 - while not file.eof_reached(): - var row := GdScriptParser.clean_up_row(file.get_line()) - line_number += 1 - # ignore comments and empty lines and not test functions - if row.begins_with("#") || row.length() == 0 || row.find("functest") == -1: - continue - # abort if test case name found - if script_parser.parse_func_name(row) == "test_" + func_name: - return line_number - return -1 - - -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 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: - func_body = func_body.replace(" ", "".lpad(ident_size, " ")) - script.source_code += func_body - var error := ResourceSaver.save(script, resource_path) - if error != OK: - return GdUnitResult.error("Can't add test case at: %s to '%s'. Error code %s" % [func_name, resource_path, error]) - return GdUnitResult.success({ "path" : resource_path, "line" : line_number}) - - -static func count_lines(script : GDScript) -> int: - return script.source_code.split("\n").size() - - -static func test_suite_exists(test_suite_path :String) -> bool: - return FileAccess.file_exists(test_suite_path) - -static func test_case_exists(test_suite_path :String, func_name :String) -> bool: - if not test_suite_exists(test_suite_path): - return false - var script := ResourceLoader.load(test_suite_path) as GDScript - for f in script.get_script_method_list(): - if f["name"] == "test_" + func_name: - return true - return false - -static func create_test_case(test_suite_path :String, func_name :String, source_script_path :String) -> GdUnitResult: - 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(): - return result - return add_test_case(test_suite_path, func_name) diff --git a/addons/gdUnit4/src/core/GdUnitTools.gd b/addons/gdUnit4/src/core/GdUnitTools.gd deleted file mode 100644 index d577c2b..0000000 --- a/addons/gdUnit4/src/core/GdUnitTools.gd +++ /dev/null @@ -1,111 +0,0 @@ -extends RefCounted - -static func normalize_text(text :String) -> String: - return text.replace("\r", ""); - - -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", "") - - -static func to_regex(pattern :String) -> RegEx: - var regex := RegEx.new() - var err := regex.compile(pattern) - if err != OK: - push_error("Can't compiling regx '%s'.\n ERROR: %s" % [pattern, error_string(err)]) - return regex - - -static func prints_verbose(message :String) -> void: - if OS.is_stdout_verbose(): - prints(message) - - -static func free_instance(instance :Variant, is_stdout_verbose :=false) -> bool: - if instance is Array: - for element in instance: - free_instance(element) - instance.clear() - return true - # do not free an already freed instance - if not is_instance_valid(instance): - return false - # do not free a class refernece - if typeof(instance) == TYPE_OBJECT and (instance as Object).is_class("GDScriptNativeClass"): - return false - if is_stdout_verbose: - 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 - await Engine.get_main_loop().physics_frame - return true - else: - # is instance already freed? - #if not is_instance_valid(instance) or ClassDB.class_get_property(instance, "new"): - # return false - #release_connections(instance) - if instance is Timer: - instance.stop() - instance.call_deferred("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() - return !is_instance_valid(instance) - - -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(): - var signal_ :Signal = connection["signal"] - var callable_ :Callable = connection["callable"] - #prints(instance, connection) - #prints("signal", signal_.get_name(), signal_.get_object()) - #prints("callable", callable_.get_object()) - if instance.has_signal(signal_.get_name()) and instance.is_connected(signal_.get_name(), callable_): - #prints("disconnect signal", signal_.get_name(), callable_) - instance.disconnect(signal_.get_name(), callable_) - 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 is_instance_valid(node) and node.is_in_group("GdUnitTimers"): - if is_instance_valid(node): - Engine.get_main_loop().root.remove_child(node) - node.stop() - node.free() - - -# the finally cleaup unfreed resources and singletons -static func dispose_all() -> void: - release_timers() - GdUnitSignals.dispose() - GdUnitSingleton.dispose() - - -# if instance an mock or spy we need manually freeing the self reference -static func release_double(instance :Object) -> void: - if instance.has_method("__release_double"): - instance.call("__release_double") - - -static func clear_push_errors() -> void: - 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 :Node = test_suite.find_child(test_case_name, false, false) - test_case.expect_to_interupt() diff --git a/addons/gdUnit4/src/core/GodotVersionFixures.gd b/addons/gdUnit4/src/core/GodotVersionFixures.gd deleted file mode 100644 index 2c331d5..0000000 --- a/addons/gdUnit4/src/core/GodotVersionFixures.gd +++ /dev/null @@ -1,21 +0,0 @@ -## This service class contains helpers to wrap Godot functions and handle them carefully depending on the current Godot version -class_name GodotVersionFixures -extends RefCounted - - - -## 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 type_convert(value: Variant, type: int): - return convert(value, type) - - -@warning_ignore("shadowed_global_identifier") -static func convert(value: Variant, type: int) -> Variant: - return type_convert(value, type) diff --git a/addons/gdUnit4/src/core/LocalTime.gd b/addons/gdUnit4/src/core/LocalTime.gd deleted file mode 100644 index fc8fc25..0000000 --- a/addons/gdUnit4/src/core/LocalTime.gd +++ /dev/null @@ -1,110 +0,0 @@ -# This class provides Date/Time functionallity to Godot -class_name LocalTime -extends Resource - -enum TimeUnit { - MILLIS = 1, - SECOND = 2, - MINUTE = 3, - HOUR = 4, - DAY = 5, - MONTH = 6, - YEAR = 7 -} - -const SECONDS_PER_MINUTE:int = 60 -const MINUTES_PER_HOUR:int = 60 -const HOURS_PER_DAY:int = 24 -const MILLIS_PER_SECOND:int = 1000 -const MILLIS_PER_MINUTE:int = MILLIS_PER_SECOND * SECONDS_PER_MINUTE -const MILLIS_PER_HOUR:int = MILLIS_PER_MINUTE * MINUTES_PER_HOUR - -var _time :int -var _hour :int -var _minute :int -var _second :int -var _millisecond :int - - -static func now() -> LocalTime: - return LocalTime.new(_get_system_time_msecs()) - - -static func of_unix_time(time_ms :int) -> LocalTime: - return LocalTime.new(time_ms) - - -static func local_time(hours :int, minutes :int, seconds :int, milliseconds :int) -> LocalTime: - return LocalTime.new(MILLIS_PER_HOUR * hours\ - + MILLIS_PER_MINUTE * minutes\ - + MILLIS_PER_SECOND * seconds\ - + milliseconds) - - -func elapsed_since() -> String: - return LocalTime.elapsed(LocalTime._get_system_time_msecs() - _time) - - -func elapsed_since_ms() -> int: - return LocalTime._get_system_time_msecs() - _time - - -func plus(time_unit :TimeUnit, value :int) -> LocalTime: - var addValue:int = 0 - match time_unit: - TimeUnit.MILLIS: - addValue = value - TimeUnit.SECOND: - addValue = value * MILLIS_PER_SECOND - TimeUnit.MINUTE: - addValue = value * MILLIS_PER_MINUTE - TimeUnit.HOUR: - addValue = value * MILLIS_PER_HOUR - _init(_time + addValue) - return self - - -static func elapsed(p_time_ms :int) -> String: - var local_time_ := LocalTime.new(p_time_ms) - if local_time_._hour > 0: - return "%dh %dmin %ds %dms" % [local_time_._hour, local_time_._minute, local_time_._second, local_time_._millisecond] - if local_time_._minute > 0: - return "%dmin %ds %dms" % [local_time_._minute, local_time_._second, local_time_._millisecond] - if local_time_._second > 0: - return "%ds %dms" % [local_time_._second, local_time_._millisecond] - return "%dms" % local_time_._millisecond - - -@warning_ignore("integer_division") -# create from epoch timestamp in ms -func _init(time :int): - _time = time - _hour = (time / MILLIS_PER_HOUR) % 24 - _minute = (time / MILLIS_PER_MINUTE) % 60 - _second = (time / MILLIS_PER_SECOND) % 60 - _millisecond = time % 1000 - - -func hour() -> int: - return _hour - - -func minute() -> int: - return _minute - - -func second() -> int: - return _second - - -func millis() -> int: - return _millisecond - - -func _to_string() -> String: - return "%02d:%02d:%02d.%03d" % [_hour, _minute, _second, _millisecond] - - -# wraper to old OS.get_system_time_msecs() function -static func _get_system_time_msecs() -> int: - return Time.get_unix_time_from_system() * 1000 as int diff --git a/addons/gdUnit4/src/core/_TestCase.gd b/addons/gdUnit4/src/core/_TestCase.gd deleted file mode 100644 index 868de15..0000000 --- a/addons/gdUnit4/src/core/_TestCase.gd +++ /dev/null @@ -1,238 +0,0 @@ -class_name _TestCase -extends Node - -signal completed() - -# default timeout 5min -const DEFAULT_TIMEOUT := -1 -const ARGUMENT_TIMEOUT := "timeout" -const ARGUMENT_SKIP := "do_skip" -const ARGUMENT_SKIP_REASON := "skip_reason" - -var _iterations: int = 1 -var _current_iteration: int = -1 -var _seed: int -var _fuzzers: Array[GdFunctionArgument] = [] -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 _failed := false -var _report: GdUnitReport = null -var _parameter_set_resolver: GdUnitTestParameterSetResolver -var _is_disposed := false - -var timeout: int = DEFAULT_TIMEOUT: - set(value): - timeout = value - get: - if timeout == DEFAULT_TIMEOUT: - timeout = GdUnitSettings.test_timeout() - return 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: - set_name(p_name) - _line_number = p_line_number - _fuzzers = p_fuzzers - _iterations = p_iterations - _seed = p_seed - _script_path = p_script_path - timeout = p_timeout - return self - - -func execute(p_test_parameter:=Array(), p_iteration:=0): - _failure_received(false) - _current_iteration = p_iteration - 1 - if _current_iteration == - 1: - _set_failure_handler() - set_timeout() - if not p_test_parameter.is_empty(): - update_fuzzers(p_test_parameter, p_iteration) - _execute_test_case(name, p_test_parameter) - else: - _execute_test_case(name, []) - await completed - - -func execute_paramaterized(p_test_parameter: Array): - _failure_received(false) - set_timeout() - # 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 - - -func dispose(): - if _is_disposed: - return - _is_disposed = true - Engine.remove_meta("GD_TEST_FAILURE") - stop_timer() - _remove_failure_handler() - _fuzzers.clear() - _report = null - - -@warning_ignore("shadowed_variable_base_class", "redundant_await") -func _execute_test_case(name: String, test_parameter: Array): - # 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: - if fuzzer is Fuzzer: - fuzzer._iteration_index = iteration + 1 - - -func set_timeout(): - if is_instance_valid(_timer): - return - 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(): - if is_fuzzed(): - _report = GdUnitReport.new().create(GdUnitReport.INTERUPTED, line_number(), GdAssertMessages.fuzzer_interuped(_current_iteration, "timedout")) - else: - _report = GdUnitReport.new().create(GdUnitReport.INTERUPTED, line_number(), GdAssertMessages.test_timeout(timeout)) - _interupted = true - completed.emit() - , CONNECT_DEFERRED) - _timer.set_one_shot(true) - _timer.set_wait_time(time) - _timer.set_autostart(false) - _timer.start() - - -func _set_failure_handler() -> void: - if not GdUnitSignals.instance().gdunit_set_test_failed.is_connected(_failure_received): - GdUnitSignals.instance().gdunit_set_test_failed.connect(_failure_received) - - -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: - # is already failed? - if _failed: - return - _failed = is_failed - Engine.set_meta("GD_TEST_FAILURE", is_failed) - - -func stop_timer(): - # finish outstanding timeouts - if is_instance_valid(_timer): - _timer.stop() - _timer.call_deferred("free") - _timer = null - - -func expect_to_interupt() -> void: - _expect_to_interupt = true - - -func is_interupted() -> bool: - return _interupted - - -func is_expect_interupted() -> bool: - return _expect_to_interupt - - -func is_parameterized() -> bool: - return _parameter_set_resolver.is_parameterized() - - -func is_skipped() -> bool: - return _skipped - - -func report() -> GdUnitReport: - return _report - - -func skip_info() -> String: - return _skip_reason - - -func line_number() -> int: - return _line_number - - -func iterations() -> int: - return _iterations - - -func seed_value() -> int: - return _seed - - -func is_fuzzed() -> bool: - return not _fuzzers.is_empty() - - -func fuzzer_arguments() -> Array[GdFunctionArgument]: - return _fuzzers - - -func script_path() -> String: - return _script_path - - -func ResourcePath() -> String: - return _script_path - - -func generate_seed() -> void: - if _seed != -1: - seed(_seed) - - -func skip(skipped: bool, reason: String="") -> void: - _skipped = skipped - _skip_reason = reason - - -func set_function_descriptor(fd: GdFunctionDescriptor) -> void: - _parameter_set_resolver = GdUnitTestParameterSetResolver.new(fd) - - -func set_test_parameter_index(index: int) -> void: - _test_param_index = index - - -func test_parameter_index() -> int: - return _test_param_index - - -func test_case_names() -> PackedStringArray: - 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(): - 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 deleted file mode 100644 index 8a3f06f..0000000 --- a/addons/gdUnit4/src/core/command/GdUnitCommand.gd +++ /dev/null @@ -1,41 +0,0 @@ -class_name GdUnitCommand -extends RefCounted - - -func _init(p_name :String, p_is_enabled: Callable, p_runnable: Callable, p_shortcut :GdUnitShortcut.ShortCut = GdUnitShortcut.ShortCut.NONE): - 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) - assert(p_shortcut != null, "(%s) missing parameter 'shortcut'" % p_name) - self.name = p_name - self.is_enabled = p_is_enabled - self.shortcut = p_shortcut - self.runnable = p_runnable - - -var name: String: - set(value): - name = value - get: - return name - - -var shortcut: GdUnitShortcut.ShortCut: - set(value): - shortcut = value - get: - return shortcut - - -var is_enabled: Callable: - set(value): - is_enabled = value - get: - return is_enabled - - -var runnable: Callable: - set(value): - runnable = value - get: - return runnable diff --git a/addons/gdUnit4/src/core/command/GdUnitCommandHandler.gd b/addons/gdUnit4/src/core/command/GdUnitCommandHandler.gd deleted file mode 100644 index 386fd26..0000000 --- a/addons/gdUnit4/src/core/command/GdUnitCommandHandler.gd +++ /dev/null @@ -1,357 +0,0 @@ -class_name GdUnitCommandHandler -extends RefCounted - -signal gdunit_runner_start() -signal gdunit_runner_stop(client_id :int) - - -const GdUnitTools := preload("res://addons/gdUnit4/src/core/GdUnitTools.gd") - -const CMD_RUN_OVERALL = "Debug Overall TestSuites" -const CMD_RUN_TESTCASE = "Run TestCases" -const CMD_RUN_TESTCASE_DEBUG = "Run TestCases (Debug)" -const CMD_RUN_TESTSUITE = "Run TestSuites" -const CMD_RUN_TESTSUITE_DEBUG = "Run TestSuites (Debug)" -const CMD_RERUN_TESTS = "ReRun Tests" -const CMD_RERUN_TESTS_DEBUG = "ReRun Tests (Debug)" -const CMD_STOP_TEST_RUN = "Stop Test Run" -const CMD_CREATE_TESTCASE = "Create TestCase" - -const SETTINGS_SHORTCUT_MAPPING := { - "N/A" : GdUnitShortcut.ShortCut.NONE, - GdUnitSettings.SHORTCUT_INSPECTOR_RERUN_TEST : GdUnitShortcut.ShortCut.RERUN_TESTS, - GdUnitSettings.SHORTCUT_INSPECTOR_RERUN_TEST_DEBUG : GdUnitShortcut.ShortCut.RERUN_TESTS_DEBUG, - GdUnitSettings.SHORTCUT_INSPECTOR_RUN_TEST_OVERALL : GdUnitShortcut.ShortCut.RUN_TESTS_OVERALL, - GdUnitSettings.SHORTCUT_INSPECTOR_RUN_TEST_STOP : GdUnitShortcut.ShortCut.STOP_TEST_RUN, - GdUnitSettings.SHORTCUT_EDITOR_RUN_TEST : GdUnitShortcut.ShortCut.RUN_TESTCASE, - GdUnitSettings.SHORTCUT_EDITOR_RUN_TEST_DEBUG : GdUnitShortcut.ShortCut.RUN_TESTCASE_DEBUG, - GdUnitSettings.SHORTCUT_EDITOR_CREATE_TEST : GdUnitShortcut.ShortCut.CREATE_TEST, - GdUnitSettings.SHORTCUT_FILESYSTEM_RUN_TEST : GdUnitShortcut.ShortCut.RUN_TESTCASE, - 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() - -# holds the current connected gdUnit runner client id -var _client_id :int -# if no debug mode we have an process id -var _current_runner_process_id :int = 0 -# hold is current an test running -var _is_running :bool = false -# holds if the current running tests started in debug mode -var _running_debug_mode :bool - -var _commands := {} -var _shortcuts := {} - - -static func instance() -> GdUnitCommandHandler: - return GdUnitSingleton.instance("GdUnitCommandHandler", func(): return GdUnitCommandHandler.new()) - - -func _init(): - 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) - GdUnitSignals.instance().gdunit_settings_changed.connect(_on_settings_changed) - # preload previous test execution - _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 - 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)) - register_command(GdUnitCommand.new(CMD_RUN_TESTSUITE, is_not_running, cmd_run_test_suites.bind(false))) - register_command(GdUnitCommand.new(CMD_RUN_TESTSUITE_DEBUG, is_not_running, cmd_run_test_suites.bind(true))) - register_command(GdUnitCommand.new(CMD_RERUN_TESTS, is_not_running, cmd_run.bind(false), GdUnitShortcut.ShortCut.RERUN_TESTS)) - register_command(GdUnitCommand.new(CMD_RERUN_TESTS_DEBUG, is_not_running, cmd_run.bind(true), GdUnitShortcut.ShortCut.RERUN_TESTS_DEBUG)) - 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)) - - -func _notification(what): - if what == NOTIFICATION_PREDELETE: - _commands.clear() - _shortcuts.clear() - - -func _do_process() -> void: - check_test_run_stopped_manually() - - -# is checking if the user has press the editor stop scene -func check_test_run_stopped_manually(): - 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 assert_shortcut_mappings(mappings :Dictionary) -> void: - for shortcut 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(): - if shortcut == GdUnitShortcut.ShortCut.NONE: - continue - var property_name :String = SETTINGS_SHORTCUT_MAPPING.find_key(shortcut) - var property := GdUnitSettings.get_property(property_name) - var keys := GdUnitShortcut.default_keys(shortcut) - if property != null: - keys = property.value() - var inputEvent := create_shortcut_input_even(keys) - register_shortcut(shortcut, inputEvent) - - -func create_shortcut_input_even(key_codes : PackedInt32Array) -> InputEventKey: - var inputEvent :InputEventKey = InputEventKey.new() - inputEvent.pressed = true - for key_code in key_codes: - match key_code: - KEY_ALT: - inputEvent.alt_pressed = true - KEY_SHIFT: - inputEvent.shift_pressed = true - KEY_CTRL: - inputEvent.ctrl_pressed = true - _: - inputEvent.keycode = key_code as Key - inputEvent.physical_keycode = key_code as Key - return inputEvent - - -func register_shortcut(p_shortcut :GdUnitShortcut.ShortCut, p_input_event :InputEvent) -> void: - GdUnitTools.prints_verbose("register shortcut: '%s' to '%s'" % [GdUnitShortcut.ShortCut.keys()[p_shortcut], p_input_event.as_text()]) - var shortcut := Shortcut.new() - shortcut.set_events([p_input_event]) - var command_name :String = get_shortcut_command(p_shortcut) - _shortcuts[p_shortcut] = GdUnitShortcutAction.new(p_shortcut, shortcut, command_name) - - -func get_shortcut(shortcut_type :GdUnitShortcut.ShortCut) -> Shortcut: - return get_shortcut_action(shortcut_type).shortcut - - -func get_shortcut_action(shortcut_type :GdUnitShortcut.ShortCut) -> GdUnitShortcutAction: - return _shortcuts.get(shortcut_type) - - -func get_shortcut_command(p_shortcut :GdUnitShortcut.ShortCut) -> String: - return GdUnitShortcut.CommandMapping.get(p_shortcut, "unknown command") - - -func register_command(p_command :GdUnitCommand) -> void: - _commands[p_command.name] = p_command - - -func command(cmd_name :String) -> GdUnitCommand: - return _commands.get(cmd_name) - - -func cmd_run_test_suites(test_suite_paths :PackedStringArray, debug :bool, rerun := false) -> void: - # create new runner runner_config for fresh run otherwise use saved one - if not rerun: - var result := _runner_config.clear()\ - .add_test_suites(test_suite_paths)\ - .save_config() - if result.is_error(): - push_error(result.error_message()) - return - cmd_run(debug) - - -func cmd_run_test_case(test_suite_resource_path :String, test_case :String, test_param_index :int, debug :bool, rerun := false) -> void: - # create new runner config for fresh run otherwise use saved one - if not rerun: - var result := _runner_config.clear()\ - .add_test_case(test_suite_resource_path, test_case, test_param_index)\ - .save_config() - if result.is_error(): - push_error(result.error_message()) - return - cmd_run(debug) - - -func cmd_run_overall(debug :bool) -> void: - var test_suite_paths :PackedStringArray = GdUnitCommandHandler.scan_test_directorys("res://" , GdUnitSettings.test_root_folder(), []) - var result := _runner_config.clear()\ - .add_test_suites(test_suite_paths)\ - .save_config() - if result.is_error(): - push_error(result.error_message()) - return - cmd_run(debug) - - -func cmd_run(debug :bool) -> void: - # don't start is already running - if _is_running: - return - # save current selected excution config - var result := _runner_config.set_server_port(Engine.get_meta("gdunit_server_port")).save_config() - if result.is_error(): - push_error(result.error_message()) - return - # before start we have to save all changes - ScriptEditorControls.save_all_open_script() - gdunit_runner_start.emit() - _current_runner_process_id = -1 - _running_debug_mode = debug - if debug: - run_debug_mode() - else: - run_release_mode() - - -func cmd_stop(client_id :int) -> void: - # don't stop if is already stopped - if not _is_running: - return - _is_running = false - gdunit_runner_stop.emit(client_id) - if _running_debug_mode: - _editor_interface.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) - _current_runner_process_id = -1 - - -func cmd_editor_run_test(debug :bool): - var cursor_line := active_base_editor().get_caret_line() - #run test case? - var regex := RegEx.new() - regex.compile("(^func[ ,\t])(test_[a-zA-Z0-9_]*)") - var result := regex.search(active_base_editor().get_line(cursor_line)) - if result: - var func_name := result.get_string(2).strip_edges() - prints("Run test:", func_name, "debug", debug) - if func_name.begins_with("test_"): - cmd_run_test_case(active_script().resource_path, func_name, -1, debug) - return - # otherwise run the full test suite - var selected_test_suites := [active_script().resource_path] - cmd_run_test_suites(selected_test_suites, debug) - - -func cmd_create_test() -> void: - var cursor_line := active_base_editor().get_caret_line() - var result := GdUnitTestSuiteBuilder.create(active_script(), cursor_line) - if result.is_error(): - # show error dialog - push_error("Failed to create test case: %s" % result.error_message()) - return - var info := result.value() as Dictionary - ScriptEditorControls.edit_script(info.get("path"), info.get("line")) - - -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): - if directory.begins_with("."): - continue - var current_directory := normalize_path(base_directory + "/" + directory) - 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) - return test_suite_paths - - -static func normalize_path(path :String) -> String: - return path.replace("///", "//") - - -static func match_test_directory(directory :String, test_directory: String) -> bool: - 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") - _is_running = true - - -func run_release_mode(): - var arguments := Array() - if OS.is_stdout_verbose(): - arguments.append("--verbose") - arguments.append("--no-window") - arguments.append("--path") - arguments.append(ProjectSettings.globalize_path("res://")) - arguments.append("res://addons/gdUnit4/src/core/GdUnitRunner.tscn") - _current_runner_process_id = OS.create_process(OS.get_executable_path(), arguments, false); - _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() - - -func active_script() -> Script: - return script_editor().get_current_script() - - - -################################################################################ -# signals handles -################################################################################ -func _on_event(event :GdUnitEvent): - if event.type() == GdUnitEvent.STOP: - cmd_stop(_client_id) - - -func _on_stop_pressed(): - cmd_stop(_client_id) - - -func _on_run_pressed(debug := false): - cmd_run(debug) - - -func _on_run_overall_pressed(_debug := false): - cmd_run_overall(true) - - -func _on_settings_changed(property :GdUnitProperty): - 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) - - -################################################################################ -# Network stuff -################################################################################ -func _on_client_connected(client_id :int) -> void: - _client_id = client_id - - -func _on_client_disconnected(client_id :int) -> void: - # only stops is not in debug mode running and the current client - if not _running_debug_mode and _client_id == client_id: - cmd_stop(client_id) - _client_id = -1 diff --git a/addons/gdUnit4/src/core/command/GdUnitShortcut.gd b/addons/gdUnit4/src/core/command/GdUnitShortcut.gd deleted file mode 100644 index bf8ac83..0000000 --- a/addons/gdUnit4/src/core/command/GdUnitShortcut.gd +++ /dev/null @@ -1,58 +0,0 @@ -class_name GdUnitShortcut -extends RefCounted - - -enum ShortCut { - NONE, - RUN_TESTS_OVERALL, - RUN_TESTCASE, - RUN_TESTCASE_DEBUG, - RERUN_TESTS, - RERUN_TESTS_DEBUG, - STOP_TEST_RUN, - CREATE_TEST, -} - - -const CommandMapping = { - ShortCut.RUN_TESTS_OVERALL: GdUnitCommandHandler.CMD_RUN_OVERALL, - ShortCut.RUN_TESTCASE: GdUnitCommandHandler.CMD_RUN_TESTCASE, - ShortCut.RUN_TESTCASE_DEBUG: GdUnitCommandHandler.CMD_RUN_TESTCASE_DEBUG, - ShortCut.RERUN_TESTS: GdUnitCommandHandler.CMD_RERUN_TESTS, - ShortCut.RERUN_TESTS_DEBUG: GdUnitCommandHandler.CMD_RERUN_TESTS_DEBUG, - ShortCut.STOP_TEST_RUN: GdUnitCommandHandler.CMD_STOP_TEST_RUN, - ShortCut.CREATE_TEST: GdUnitCommandHandler.CMD_CREATE_TESTCASE, -} - - -const DEFAULTS_MACOS := { - ShortCut.NONE : [], - ShortCut.RUN_TESTCASE : [Key.KEY_META, Key.KEY_ALT, Key.KEY_F5], - ShortCut.RUN_TESTCASE_DEBUG : [Key.KEY_META, Key.KEY_ALT, Key.KEY_F6], - ShortCut.RUN_TESTS_OVERALL : [Key.KEY_META, Key.KEY_F7], - ShortCut.STOP_TEST_RUN : [Key.KEY_META, Key.KEY_F8], - ShortCut.RERUN_TESTS : [Key.KEY_META, Key.KEY_F5], - ShortCut.RERUN_TESTS_DEBUG : [Key.KEY_META, Key.KEY_F6], - ShortCut.CREATE_TEST : [Key.KEY_META, Key.KEY_ALT, Key.KEY_F10], -} - -const DEFAULTS_WINDOWS := { - ShortCut.NONE : [], - ShortCut.RUN_TESTCASE : [Key.KEY_CTRL, Key.KEY_ALT, Key.KEY_F5], - ShortCut.RUN_TESTCASE_DEBUG : [Key.KEY_CTRL,Key.KEY_ALT, Key.KEY_F6], - ShortCut.RUN_TESTS_OVERALL : [Key.KEY_CTRL, Key.KEY_F7], - ShortCut.STOP_TEST_RUN : [Key.KEY_CTRL, Key.KEY_F8], - ShortCut.RERUN_TESTS : [Key.KEY_CTRL, Key.KEY_F5], - ShortCut.RERUN_TESTS_DEBUG : [Key.KEY_CTRL, Key.KEY_F6], - ShortCut.CREATE_TEST : [Key.KEY_CTRL, Key.KEY_ALT, Key.KEY_F10], -} - - -static func default_keys(shortcut :ShortCut) -> PackedInt32Array: - match OS.get_name().to_lower(): - 'windows': - return DEFAULTS_WINDOWS[shortcut] - 'macos': - return DEFAULTS_MACOS[shortcut] - _: - return DEFAULTS_WINDOWS[shortcut] diff --git a/addons/gdUnit4/src/core/command/GdUnitShortcutAction.gd b/addons/gdUnit4/src/core/command/GdUnitShortcutAction.gd deleted file mode 100644 index ffc9a9c..0000000 --- a/addons/gdUnit4/src/core/command/GdUnitShortcutAction.gd +++ /dev/null @@ -1,36 +0,0 @@ -class_name GdUnitShortcutAction -extends RefCounted - - -func _init(p_type :GdUnitShortcut.ShortCut, p_shortcut :Shortcut, p_command :String): - assert(p_type != null, "missing parameter 'type'") - assert(p_shortcut != null, "missing parameter 'shortcut'") - assert(p_command != null, "missing parameter 'command'") - self.type = p_type - self.shortcut = p_shortcut - self.command = p_command - - -var type: GdUnitShortcut.ShortCut: - set(value): - type = value - get: - return type - - -var shortcut: Shortcut: - set(value): - shortcut = value - get: - return shortcut - - -var command: String: - set(value): - command = value - get: - return command - - -func _to_string() -> String: - return "GdUnitShortcutAction: %s (%s) -> %s" % [GdUnitShortcut.ShortCut.keys()[type], shortcut.get_as_text(), command] diff --git a/addons/gdUnit4/src/core/event/GdUnitEvent.gd b/addons/gdUnit4/src/core/event/GdUnitEvent.gd deleted file mode 100644 index cd92208..0000000 --- a/addons/gdUnit4/src/core/event/GdUnitEvent.gd +++ /dev/null @@ -1,185 +0,0 @@ -class_name GdUnitEvent -extends Resource - -const WARNINGS = "warnings" -const FAILED = "failed" -const ERRORS = "errors" -const SKIPPED = "skipped" -const ELAPSED_TIME = "elapsed_time" -const ORPHAN_NODES = "orphan_nodes" -const ERROR_COUNT = "error_count" -const FAILED_COUNT = "failed_count" -const SKIPPED_COUNT = "skipped_count" - -enum { - INIT, - STOP, - TESTSUITE_BEFORE, - TESTSUITE_AFTER, - TESTCASE_BEFORE, - TESTCASE_AFTER, -} - -var _event_type :int -var _resource_path :String -var _suite_name :String -var _test_name :String -var _total_count :int = 0 -var _statistics := Dictionary() -var _reports :Array[GdUnitReport] = [] - - -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 - _test_name = "before" - _total_count = p_total_count - return self - - -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 - _test_name = "after" - _statistics = p_statistics - _reports = p_reports - return self - - -func test_before(p_resource_path :String, p_suite_name :String, p_test_name :String) -> GdUnitEvent: - _event_type = TESTCASE_BEFORE - _resource_path = p_resource_path - _suite_name = p_suite_name - _test_name = p_test_name - return self - - -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 - _test_name = p_test_name - _statistics = p_statistics - _reports = p_reports - return self - - -func type() -> int: - return _event_type - - -func suite_name() -> String: - return _suite_name - - -func test_name() -> String: - return _test_name - - -func elapsed_time() -> int: - return _statistics.get(ELAPSED_TIME, 0) - - -func orphan_nodes() -> int: - return _statistics.get(ORPHAN_NODES, 0) - - -func statistic(p_type :String) -> int: - return _statistics.get(p_type, 0) - - -func total_count() -> int: - return _total_count - - -func success_count() -> int: - return total_count() - error_count() - failed_count() - skipped_count() - - -func error_count() -> int: - return _statistics.get(ERROR_COUNT, 0) - - -func failed_count() -> int: - return _statistics.get(FAILED_COUNT, 0) - - -func skipped_count() -> int: - return _statistics.get(SKIPPED_COUNT, 0) - - -func resource_path() -> String: - return _resource_path - - -func is_success() -> bool: - return not is_warning() and not is_failed() and not is_error() and not is_skipped() - - -func is_warning() -> bool: - return _statistics.get(WARNINGS, false) - - -func is_failed() -> bool: - return _statistics.get(FAILED, false) - - -func is_error() -> bool: - return _statistics.get(ERRORS, false) - - -func is_skipped() -> bool: - return _statistics.get(SKIPPED, false) - - -func reports() -> Array: - return _reports - - -func _to_string() -> String: - return "Event: %s %s:%s, %s, %s" % [_event_type, _suite_name, _test_name, _statistics, _reports] - - -func serialize() -> Dictionary: - var serialized := { - "type" : _event_type, - "resource_path": _resource_path, - "suite_name" : _suite_name, - "test_name" : _test_name, - "total_count" : _total_count, - "statistics" : _statistics - } - serialized["reports"] = _serialize_TestReports() - return serialized - - -func deserialize(serialized :Dictionary) -> GdUnitEvent: - _event_type = serialized.get("type", null) - _resource_path = serialized.get("resource_path", null) - _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()) - if serialized.has("reports"): - # needs this workaround to copy typed values in the array - var reports :Array[Dictionary] = [] - reports.append_array(serialized.get("reports")) - _reports = _deserialize_reports(reports) - return self - - -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[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) - return deserialized_reports diff --git a/addons/gdUnit4/src/core/event/GdUnitEventInit.gd b/addons/gdUnit4/src/core/event/GdUnitEventInit.gd deleted file mode 100644 index 8bb1d49..0000000 --- a/addons/gdUnit4/src/core/event/GdUnitEventInit.gd +++ /dev/null @@ -1,19 +0,0 @@ -class_name GdUnitInit -extends GdUnitEvent - - -var _total_testsuites :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 - - -func total_test_suites() -> int: - return _total_testsuites - - -func total_tests() -> int: - return _total_count diff --git a/addons/gdUnit4/src/core/event/GdUnitEventStop.gd b/addons/gdUnit4/src/core/event/GdUnitEventStop.gd deleted file mode 100644 index d7a3c11..0000000 --- a/addons/gdUnit4/src/core/event/GdUnitEventStop.gd +++ /dev/null @@ -1,6 +0,0 @@ -class_name GdUnitStop -extends GdUnitEvent - - -func _init() -> void: - _event_type = STOP diff --git a/addons/gdUnit4/src/core/execution/GdUnitExecutionContext.gd b/addons/gdUnit4/src/core/execution/GdUnitExecutionContext.gd deleted file mode 100644 index 06e8dc3..0000000 --- a/addons/gdUnit4/src/core/execution/GdUnitExecutionContext.gd +++ /dev/null @@ -1,192 +0,0 @@ -## The execution context -## It contains all the necessary information about the executed stage, such as memory observers, reports, orphan monitor -class_name GdUnitExecutionContext - -var _parent_context :GdUnitExecutionContext -var _sub_context :Array[GdUnitExecutionContext] = [] -var _orphan_monitor :GdUnitOrphanNodesMonitor -var _memory_observer :GdUnitMemoryObserver -var _report_collector :GdUnitTestReportCollector -var _timer :LocalTime -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 - get: - if _parent_context != null: - return _parent_context.test_suite - return test_suite - - -var test_case : _TestCase = null: - get: - if _test_case_name.is_empty(): - return null - return test_suite.find_child(_test_case_name, false, false) - - -func _init(name :String, parent_context :GdUnitExecutionContext = null) -> void: - _name = name - _parent_context = parent_context - _timer = LocalTime.now() - _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) - - -func dispose() -> void: - _timer = null - _orphan_monitor = null - _report_collector = null - _memory_observer = null - _parent_context = null - test_suite = null - test_case = null - for context in _sub_context: - context.dispose() - _sub_context.clear() - - -func set_active() -> void: - test_suite.__execution_context = self - GdUnitThreadManager.get_current_context().set_execution_context(self) - - -static func of_test_suite(test_suite_ :GdUnitTestSuite) -> GdUnitExecutionContext: - assert(test_suite_, "test_suite is null") - var context := GdUnitExecutionContext.new(test_suite_.get_name()) - context.test_suite = test_suite_ - context.set_active() - return context - - -static func of_test_case(pe :GdUnitExecutionContext, test_case_name :StringName) -> GdUnitExecutionContext: - var context := GdUnitExecutionContext.new(test_case_name, pe) - context._test_case_name = test_case_name - context.set_active() - return context - - -static func of(pe :GdUnitExecutionContext) -> GdUnitExecutionContext: - var context := GdUnitExecutionContext.new(pe._test_case_name, pe) - context._test_case_name = pe._test_case_name - context.set_active() - return context - - -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() - - -func orphan_monitor_stop() -> void: - _orphan_monitor.stop() - - -func reports() -> Array[GdUnitReport]: - return _report_collector.reports() - - -func build_report_statistics(orphans :int, recursive := true) -> Dictionary: - return { - GdUnitEvent.ORPHAN_NODES: orphans, - GdUnitEvent.ELAPSED_TIME: _timer.elapsed_since_ms(), - GdUnitEvent.FAILED: has_failures(), - GdUnitEvent.ERRORS: has_errors(), - GdUnitEvent.WARNINGS: has_warnings(), - GdUnitEvent.SKIPPED: has_skipped(), - GdUnitEvent.FAILED_COUNT: count_failures(recursive), - GdUnitEvent.ERROR_COUNT: count_errors(recursive), - GdUnitEvent.SKIPPED_COUNT: count_skipped(recursive) - } - - -func has_failures() -> bool: - return _sub_context.any(func(c): 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() - - -func has_warnings() -> bool: - return _sub_context.any(func(c): 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() - - -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()) - - -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()) - - -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()) - - -func count_orphans() -> int: - var orphans := 0 - for c in _sub_context: - orphans += c._orphan_monitor.orphan_nodes() - return _orphan_monitor.orphan_nodes() - orphans - - -func sum(accum :int, number :int) -> int: - return accum + number - - -func register_auto_free(obj :Variant) -> Variant: - return _memory_observer.register_auto_free(obj) - - -## Runs the gdunit garbage collector to free registered object -func gc() -> void: - await _memory_observer.gc() - orphan_monitor_stop() diff --git a/addons/gdUnit4/src/core/execution/GdUnitMemoryObserver.gd b/addons/gdUnit4/src/core/execution/GdUnitMemoryObserver.gd deleted file mode 100644 index 084d028..0000000 --- a/addons/gdUnit4/src/core/execution/GdUnitMemoryObserver.gd +++ /dev/null @@ -1,131 +0,0 @@ -## The memory watcher for objects that have been registered and are released when 'gc' is called. -class_name GdUnitMemoryObserver -extends RefCounted - -const TAG_OBSERVE_INSTANCE := "GdUnit4_observe_instance_" -const TAG_AUTO_FREE = "GdUnit4_marked_auto_free" -const GdUnitTools = preload("res://addons/gdUnit4/src/core/GdUnitTools.gd") - - -var _store :Array[Variant] = [] -# enable for debugging purposes -var _is_stdout_verbose := false -const _show_debug := false - - -## Registration of an instance to be released when an execution phase is completed -func register_auto_free(obj) -> Variant: - if not is_instance_valid(obj): - return obj - # do not register on GDScriptNativeClass - if typeof(obj) == TYPE_OBJECT and (obj as Object).is_class("GDScriptNativeClass") : - return obj - #if obj is GDScript or obj is ScriptExtension: - # return obj - if obj is MainLoop: - push_error("GdUnit4: Avoid to add mainloop to auto_free queue %s" % obj) - return - if _is_stdout_verbose: - print_verbose("GdUnit4:gc():register auto_free(%s)" % obj) - # only register pure objects - if obj is GdUnitSceneRunner: - _store.push_back(obj) - else: - _store.append(obj) - _tag_object(obj) - return obj - - -# to disable instance guard when run into issues. -static func _is_instance_guard_enabled() -> bool: - return false - - -static func debug_observe(name :String, obj :Object, indent :int = 0) -> void: - if not _show_debug: - return - var script :GDScript= obj if obj is GDScript else obj.get_script() - if script: - var base_script :GDScript = script.get_base_script() - prints("".lpad(indent, " "), name, obj, obj.get_class(), "reference_count:", obj.get_reference_count() if obj is RefCounted else 0, "script:", script, script.resource_path) - if base_script: - debug_observe("+", base_script, indent+1) - else: - prints(name, obj, obj.get_class(), obj.get_name()) - - -static func guard_instance(obj :Object) -> Object: - if not _is_instance_guard_enabled(): - return - var tag := TAG_OBSERVE_INSTANCE + str(abs(obj.get_instance_id())) - if Engine.has_meta(tag): - return - debug_observe("Gard on instance", obj) - Engine.set_meta(tag, obj) - return obj - - -static func unguard_instance(obj :Object, verbose := true) -> void: - if not _is_instance_guard_enabled(): - return - var tag := TAG_OBSERVE_INSTANCE + str(abs(obj.get_instance_id())) - if verbose: - debug_observe("unguard instance", obj) - if Engine.has_meta(tag): - Engine.remove_meta(tag) - - -static func gc_guarded_instance(name :String, instance :Object) -> void: - if not _is_instance_guard_enabled(): - return - await Engine.get_main_loop().process_frame - unguard_instance(instance, false) - if is_instance_valid(instance) and instance is RefCounted: - # finally do this very hacky stuff - # we need to manually unreferece to avoid leaked scripts - # but still leaked GDScriptFunctionState exists - #var script :GDScript = instance.get_script() - #if script: - # var base_script :GDScript = script.get_base_script() - # if base_script: - # base_script.unreference() - debug_observe(name, instance) - instance.unreference() - await Engine.get_main_loop().process_frame - - -static func gc_on_guarded_instances() -> void: - if not _is_instance_guard_enabled(): - return - for tag in Engine.get_meta_list(): - if tag.begins_with(TAG_OBSERVE_INSTANCE): - var instance = Engine.get_meta(tag) - await gc_guarded_instance("Leaked instance detected:", instance) - await GdUnitTools.free_instance(instance, false) - - -# store the object into global store aswell to be verified by 'is_marked_auto_free' -func _tag_object(obj :Variant) -> void: - var tagged_object := Engine.get_meta(TAG_AUTO_FREE, []) as Array - tagged_object.append(obj) - Engine.set_meta(TAG_AUTO_FREE, tagged_object) - - -## Runs over all registered objects and releases them -func gc() -> void: - if _store.is_empty(): - return - # give engine time to free objects to process objects marked by queue_free() - await Engine.get_main_loop().process_frame - if _is_stdout_verbose: - print_verbose("GdUnit4:gc():running", " freeing %d objects .." % _store.size()) - var tagged_objects := Engine.get_meta(TAG_AUTO_FREE, []) as Array - while not _store.is_empty(): - var value :Variant = _store.pop_front() - tagged_objects.erase(value) - await GdUnitTools.free_instance(value, _is_stdout_verbose) - - -## Checks whether the specified object is registered for automatic release -static func is_marked_auto_free(obj) -> 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 deleted file mode 100644 index cde5cee..0000000 --- a/addons/gdUnit4/src/core/execution/GdUnitTestReportCollector.gd +++ /dev/null @@ -1,70 +0,0 @@ -# Collects all reports seperated as warnings, failures and errors -class_name GdUnitTestReportCollector -extends RefCounted - - -var _execution_context_id :int -var _reports :Array[GdUnitReport] = [] - - -static func __filter_is_error(report :GdUnitReport) -> bool: - return report.is_error() - - -static func __filter_is_failure(report :GdUnitReport) -> bool: - return report.is_failure() - - -static func __filter_is_warning(report :GdUnitReport) -> bool: - return report.is_warning() - - -static func __filter_is_skipped(report :GdUnitReport) -> bool: - return report.is_skipped() - - -func _init(execution_context_id :int): - _execution_context_id = execution_context_id - GdUnitSignals.instance().gdunit_report.connect(on_reports) - - -func count_failures() -> int: - return _reports.filter(__filter_is_failure).size() - - -func count_errors() -> int: - return _reports.filter(__filter_is_error).size() - - -func count_warnings() -> int: - return _reports.filter(__filter_is_warning).size() - - -func count_skipped() -> int: - return _reports.filter(__filter_is_skipped).size() - - -func has_failures() -> bool: - return _reports.any(__filter_is_failure) - - -func has_errors() -> bool: - return _reports.any(__filter_is_error) - - -func has_warnings() -> bool: - return _reports.any(__filter_is_warning) - - -func has_skipped() -> bool: - return _reports.any(__filter_is_skipped) - - -func reports() -> Array[GdUnitReport]: - return _reports - - -# Consumes reports emitted by tests -func on_reports(execution_context_id :int, report :GdUnitReport) -> void: - if execution_context_id == _execution_context_id: - _reports.append(report) diff --git a/addons/gdUnit4/src/core/execution/GdUnitTestSuiteExecutor.gd b/addons/gdUnit4/src/core/execution/GdUnitTestSuiteExecutor.gd deleted file mode 100644 index c919469..0000000 --- a/addons/gdUnit4/src/core/execution/GdUnitTestSuiteExecutor.gd +++ /dev/null @@ -1,26 +0,0 @@ -## The executor to run a test-suite -class_name GdUnitTestSuiteExecutor - - -# preload all asserts here -@warning_ignore("unused_private_class_variable") -var _assertions := GdUnitAssertions.new() -var _executeStage :IGdUnitExecutionStage = GdUnitTestSuiteExecutionStage.new() - - -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() - 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)) - - -func fail_fast(enabled :bool) -> void: - _executeStage.fail_fast(enabled) diff --git a/addons/gdUnit4/src/core/execution/stages/GdUnitTestCaseAfterStage.gd b/addons/gdUnit4/src/core/execution/stages/GdUnitTestCaseAfterStage.gd deleted file mode 100644 index 3460a68..0000000 --- a/addons/gdUnit4/src/core/execution/stages/GdUnitTestCaseAfterStage.gd +++ /dev/null @@ -1,100 +0,0 @@ -## The test case shutdown hook implementation.[br] -## It executes the 'test_after()' block from the test-suite. -class_name GdUnitTestCaseAfterStage -extends IGdUnitExecutionStage - - -var _test_name :StringName = "" -var _call_stage :bool - - -func _init(call_stage := true): - _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: - fire_test_ended(context) - if is_instance_valid(context.test_case): - context.test_case.dispose() - - -func set_test_name(test_name :StringName): - _test_name = test_name - - -func fire_test_ended(context :GdUnitExecutionContext) -> void: - var test_suite := context.test_suite - 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)) - - -func collect_orphans(context :GdUnitExecutionContext, reports :Array[GdUnitReport]) -> int: - var orphans := 0 - if not context._sub_context.is_empty(): - orphans += add_orphan_report_test(context._sub_context[0], reports) - orphans += add_orphan_report_teststage(context, reports) - return orphans - - -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() 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(): - reports.append_array(context._sub_context[0].reports()) - # needs finally to clean the test reports to avoid counting twice - context._sub_context[0].reports().clear() - return reports - - -func add_orphan_report_test(context :GdUnitExecutionContext, reports :Array[GdUnitReport]) -> int: - var orphans := context.count_orphans() - if orphans > 0: - reports.push_front(GdUnitReport.new()\ - .create(GdUnitReport.WARN, context.test_case.line_number(), GdAssertMessages.orphan_detected_on_test(orphans))) - return orphans - - -func add_orphan_report_teststage(context :GdUnitExecutionContext, reports :Array[GdUnitReport]) -> int: - var orphans := context.count_orphans() - if orphans > 0: - reports.push_front(GdUnitReport.new()\ - .create(GdUnitReport.WARN, context.test_case.line_number(), GdAssertMessages.orphan_detected_on_test_setup(orphans))) - return orphans - - -func fire_test_skipped(context :GdUnitExecutionContext): - 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 = { - GdUnitEvent.ORPHAN_NODES: 0, - GdUnitEvent.ELAPSED_TIME: 0, - GdUnitEvent.WARNINGS: false, - GdUnitEvent.ERRORS: false, - GdUnitEvent.ERROR_COUNT: 0, - GdUnitEvent.FAILED: false, - GdUnitEvent.FAILED_COUNT: 0, - GdUnitEvent.SKIPPED: true, - GdUnitEvent.SKIPPED_COUNT: 1, - } - var report := GdUnitReport.new().create(GdUnitReport.SKIPPED, test_case.line_number(), GdAssertMessages.test_skipped(test_case.skip_info())) - fire_event(GdUnitEvent.new()\ - .test_after(test_suite.get_script().resource_path, test_suite.get_name(), test_case_name, statistics, [report])) diff --git a/addons/gdUnit4/src/core/execution/stages/GdUnitTestCaseBeforeStage.gd b/addons/gdUnit4/src/core/execution/stages/GdUnitTestCaseBeforeStage.gd deleted file mode 100644 index 2210fb9..0000000 --- a/addons/gdUnit4/src/core/execution/stages/GdUnitTestCaseBeforeStage.gd +++ /dev/null @@ -1,29 +0,0 @@ -## The test case startup hook implementation.[br] -## It executes the 'test_before()' block from the test-suite. -class_name GdUnitTestCaseBeforeStage -extends IGdUnitExecutionStage - - -var _test_name :StringName = "" -var _call_stage :bool - - -func _init(call_stage := true): - _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): - _test_name = test_name diff --git a/addons/gdUnit4/src/core/execution/stages/GdUnitTestCaseExecutionStage.gd b/addons/gdUnit4/src/core/execution/stages/GdUnitTestCaseExecutionStage.gd deleted file mode 100644 index dc8c53d..0000000 --- a/addons/gdUnit4/src/core/execution/stages/GdUnitTestCaseExecutionStage.gd +++ /dev/null @@ -1,31 +0,0 @@ -## The test case execution stage.[br] -class_name GdUnitTestCaseExecutionStage -extends IGdUnitExecutionStage - - -var _stage_single_test :IGdUnitExecutionStage = GdUnitTestCaseSingleExecutionStage.new() -var _stage_fuzzer_test :IGdUnitExecutionStage = GdUnitTestCaseFuzzedExecutionStage.new() -var _stage_parameterized_test :IGdUnitExecutionStage= GdUnitTestCaseParameterizedExecutionStage.new() - - -## Executes the test case 'test_()'.[br] -## It executes synchronized following stages[br] -## -> test_before() [br] -## -> test_case() [br] -## -> test_after() [br] -@warning_ignore("redundant_await") -func _execute(context :GdUnitExecutionContext) -> void: - var test_case := context.test_case - if test_case.is_parameterized(): - await _stage_parameterized_test.execute(context) - elif test_case.is_fuzzed(): - await _stage_fuzzer_test.execute(context) - else: - await _stage_single_test.execute(context) - - -func set_debug_mode(debug_mode :bool = false): - super.set_debug_mode(debug_mode) - _stage_single_test.set_debug_mode(debug_mode) - _stage_fuzzer_test.set_debug_mode(debug_mode) - _stage_parameterized_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 deleted file mode 100644 index a6de318..0000000 --- a/addons/gdUnit4/src/core/execution/stages/GdUnitTestSuiteAfterStage.gd +++ /dev/null @@ -1,28 +0,0 @@ -## The test suite shutdown hook implementation.[br] -## It executes the 'after()' block from the test-suite. -class_name GdUnitTestSuiteAfterStage -extends IGdUnitExecutionStage - - -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)) - - 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 deleted file mode 100644 index 2260edb..0000000 --- a/addons/gdUnit4/src/core/execution/stages/GdUnitTestSuiteBeforeStage.gd +++ /dev/null @@ -1,14 +0,0 @@ -## The test suite startup hook implementation.[br] -## It executes the 'before()' block from the test-suite. -class_name GdUnitTestSuiteBeforeStage -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 deleted file mode 100644 index 9e796c3..0000000 --- a/addons/gdUnit4/src/core/execution/stages/GdUnitTestSuiteExecutionStage.gd +++ /dev/null @@ -1,114 +0,0 @@ -## The test suite main execution stage.[br] -class_name GdUnitTestSuiteExecutionStage -extends IGdUnitExecutionStage - -const GdUnitTools := preload("res://addons/gdUnit4/src/core/GdUnitTools.gd") - -var _stage_before :IGdUnitExecutionStage = GdUnitTestSuiteBeforeStage.new() -var _stage_after :IGdUnitExecutionStage = GdUnitTestSuiteAfterStage.new() -var _stage_test :IGdUnitExecutionStage = GdUnitTestCaseExecutionStage.new() -var _fail_fast := false - - -## Executes all tests of an test suite.[br] -## It executes synchronized following stages[br] -## -> before() [br] -## -> run all test cases [br] -## -> after() [br] -func _execute(context :GdUnitExecutionContext) -> void: - if context.test_suite.__is_skipped: - await fire_test_suite_skipped(context) - else: - GdUnitMemoryObserver.guard_instance(context.test_suite.__awaiter) - await _stage_before.execute(context) - for test_case_index in context.test_suite.get_child_count(): - # iterate only over test cases - var test_case := context.test_suite.get_child(test_case_index) as _TestCase - if not is_instance_valid(test_case): - continue - context.test_suite.set_active_test_case(test_case.get_name()) - await _stage_test.execute(GdUnitExecutionContext.of_test_case(context, test_case.get_name())) - # stop on first error or if fail fast is enabled - if _fail_fast and context.test_failed(): - break - if test_case.is_interupted(): - # it needs to go this hard way to kill the outstanding awaits of a test case when the test timed out - # we delete the current test suite where is execute the current test case to kill the function state - # and replace it by a clone without function state - context.test_suite = await clone_test_suite(context.test_suite) - await _stage_after.execute(context) - GdUnitMemoryObserver.unguard_instance(context.test_suite.__awaiter) - await Engine.get_main_loop().process_frame - context.test_suite.free() - context.dispose() - - -# clones a test suite and moves the test cases to new instance -func clone_test_suite(test_suite :GdUnitTestSuite) -> GdUnitTestSuite: - await Engine.get_main_loop().process_frame - 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() - parent.remove_child(test_suite) - copy_properties(test_suite, _test_suite) - for child in test_suite.get_children(): - test_suite.remove_child(child) - _test_suite.add_child(child) - parent.add_child(_test_suite) - GdUnitMemoryObserver.guard_instance(_test_suite.__awaiter) - # finally free current test suite instance - test_suite.free() - await Engine.get_main_loop().process_frame - return _test_suite - - -func dispose_timers(test_suite :GdUnitTestSuite): - GdUnitTools.release_timers() - for child in test_suite.get_children(): - if child is Timer: - child.stop() - test_suite.remove_child(child) - child.free() - - -func copy_properties(source :Object, target :Object): - if not source is _TestCase and not source is GdUnitTestSuite: - return - for property in source.get_property_list(): - var property_name = property["name"] - if property_name == "__awaiter": - continue - target.set(property_name, source.get(property_name)) - - -func fire_test_suite_skipped(context :GdUnitExecutionContext) -> void: - var test_suite := context.test_suite - 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 = { - GdUnitEvent.ORPHAN_NODES: 0, - GdUnitEvent.ELAPSED_TIME: 0, - GdUnitEvent.WARNINGS: false, - GdUnitEvent.ERRORS: false, - GdUnitEvent.ERROR_COUNT: 0, - GdUnitEvent.FAILED: false, - GdUnitEvent.FAILED_COUNT: 0, - GdUnitEvent.SKIPPED_COUNT: skip_count, - GdUnitEvent.SKIPPED: true - } - var report := GdUnitReport.new().create(GdUnitReport.SKIPPED, -1, GdAssertMessages.test_suite_skipped(test_suite.__skip_reason, skip_count)) - fire_event(GdUnitEvent.new().suite_after(test_suite.get_script().resource_path, test_suite.get_name(), statistics, [report])) - await Engine.get_main_loop().process_frame - - -func set_debug_mode(debug_mode :bool = false): - super.set_debug_mode(debug_mode) - _stage_before.set_debug_mode(debug_mode) - _stage_after.set_debug_mode(debug_mode) - _stage_test.set_debug_mode(debug_mode) - - -func fail_fast(enabled :bool) -> void: - _fail_fast = enabled diff --git a/addons/gdUnit4/src/core/execution/stages/IGdUnitExecutionStage.gd b/addons/gdUnit4/src/core/execution/stages/IGdUnitExecutionStage.gd deleted file mode 100644 index 0f6ae93..0000000 --- a/addons/gdUnit4/src/core/execution/stages/IGdUnitExecutionStage.gd +++ /dev/null @@ -1,39 +0,0 @@ -## The interface of execution stage.[br] -## An execution stage is defined as an encapsulated task that can execute 1-n substages covered by its own execution context.[br] -## Execution stage are always called synchronously. -class_name IGdUnitExecutionStage -extends RefCounted - -var _debug_mode := false - - -## Executes synchronized the implemented stage in its own execution context.[br] -## example:[br] -## [codeblock] -## # waits for 100ms -## await MyExecutionStage.new().execute() -## [/codeblock][br] -func execute(context :GdUnitExecutionContext) -> void: - context.set_active() - @warning_ignore("redundant_await") - await _execute(context) - - -## Sends the event to registered listeners -func fire_event(event :GdUnitEvent) -> void: - if _debug_mode: - GdUnitSignals.instance().gdunit_event_debug.emit(event) - else: - GdUnitSignals.instance().gdunit_event.emit(event) - - -## Internal testing stuff.[br] -## Sets the executor into debug mode to emit `GdUnitEvent` via signal `gdunit_event_debug` -func set_debug_mode(debug_mode :bool) -> void: - _debug_mode = debug_mode - - -## The execution phase to be carried out. -func _execute(_context :GdUnitExecutionContext) -> void: - @warning_ignore("assert_always_false") - assert(false, "The execution stage is not implemented") diff --git a/addons/gdUnit4/src/core/execution/stages/fuzzed/GdUnitTestCaseFuzzedExecutionStage.gd b/addons/gdUnit4/src/core/execution/stages/fuzzed/GdUnitTestCaseFuzzedExecutionStage.gd deleted file mode 100644 index 269a9ea..0000000 --- a/addons/gdUnit4/src/core/execution/stages/fuzzed/GdUnitTestCaseFuzzedExecutionStage.gd +++ /dev/null @@ -1,21 +0,0 @@ -## The test case execution stage.[br] -class_name GdUnitTestCaseFuzzedExecutionStage -extends IGdUnitExecutionStage - -var _stage_before :IGdUnitExecutionStage = GdUnitTestCaseBeforeStage.new(false) -var _stage_after :IGdUnitExecutionStage = GdUnitTestCaseAfterStage.new(false) -var _stage_test :IGdUnitExecutionStage = GdUnitTestCaseFuzzedTestStage.new() - - -func _execute(context :GdUnitExecutionContext) -> void: - await _stage_before.execute(context) - if not context.test_case.is_skipped(): - await _stage_test.execute(GdUnitExecutionContext.of(context)) - await _stage_after.execute(context) - - -func set_debug_mode(debug_mode :bool = false): - super.set_debug_mode(debug_mode) - _stage_before.set_debug_mode(debug_mode) - _stage_after.set_debug_mode(debug_mode) - _stage_test.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 deleted file mode 100644 index e6d9852..0000000 --- a/addons/gdUnit4/src/core/execution/stages/fuzzed/GdUnitTestCaseFuzzedTestStage.gd +++ /dev/null @@ -1,53 +0,0 @@ -## The fuzzed test case execution stage.[br] -class_name GdUnitTestCaseFuzzedTestStage -extends IGdUnitExecutionStage - -var _expression_runner := GdUnitExpressionRunner.new() - - -## Executes a test case with given fuzzers 'test_()' iterative.[br] -## It executes synchronized following stages[br] -## -> test_case() [br] -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() - await test_case.execute(fuzzers, iteration) - @warning_ignore("redundant_await") - await test_suite.after_test() - if test_case.is_interupted(): - break - # interrupt at first failure - var reports := context.reports() - if not reports.is_empty(): - var report :GdUnitReport = reports.pop_front() - reports.append(GdUnitReport.new() \ - .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: - GdUnitMemoryObserver.unguard_instance(fuzzer) - - -func create_fuzzers(test_suite :GdUnitTestSuite, test_case :_TestCase) -> Array[Fuzzer]: - if not test_case.is_fuzzed(): - return Array() - test_case.generate_seed() - var fuzzers :Array[Fuzzer] = [] - for fuzzer_arg in test_case.fuzzer_arguments(): - var fuzzer := _expression_runner.to_fuzzer(test_suite.get_script(), fuzzer_arg.value_as_string()) - fuzzer._iteration_index = 0 - fuzzer._iteration_limit = test_case.iterations() - fuzzers.append(fuzzer) - return fuzzers diff --git a/addons/gdUnit4/src/core/execution/stages/parameterized/GdUnitTestCaseParameterizedExecutionStage.gd b/addons/gdUnit4/src/core/execution/stages/parameterized/GdUnitTestCaseParameterizedExecutionStage.gd deleted file mode 100644 index 52ccdc4..0000000 --- a/addons/gdUnit4/src/core/execution/stages/parameterized/GdUnitTestCaseParameterizedExecutionStage.gd +++ /dev/null @@ -1,22 +0,0 @@ -## The test case execution stage.[br] -class_name GdUnitTestCaseParameterizedExecutionStage -extends IGdUnitExecutionStage - - -var _stage_before :IGdUnitExecutionStage = GdUnitTestCaseBeforeStage.new(false) -var _stage_after :IGdUnitExecutionStage = GdUnitTestCaseAfterStage.new(false) -var _stage_test :IGdUnitExecutionStage = GdUnitTestCaseParamaterizedTestStage.new() - - -func _execute(context :GdUnitExecutionContext) -> void: - await _stage_before.execute(context) - if not context.test_case.is_skipped(): - await _stage_test.execute(GdUnitExecutionContext.of(context)) - await _stage_after.execute(context) - - -func set_debug_mode(debug_mode :bool = false): - super.set_debug_mode(debug_mode) - _stage_before.set_debug_mode(debug_mode) - _stage_after.set_debug_mode(debug_mode) - _stage_test.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 deleted file mode 100644 index 5469f40..0000000 --- a/addons/gdUnit4/src/core/execution/stages/parameterized/GdUnitTestCaseParameterizedTestStage.gd +++ /dev/null @@ -1,76 +0,0 @@ -## The parameterized test case execution stage.[br] -class_name GdUnitTestCaseParamaterizedTestStage -extends IGdUnitExecutionStage - -var _stage_before: IGdUnitExecutionStage = GdUnitTestCaseBeforeStage.new() -var _stage_after: IGdUnitExecutionStage = GdUnitTestCaseAfterStage.new() - - -## Executes a parameterized test case.[br] -## It executes synchronized following stages[br] -## -> test_case( ) [br] -func _execute(context: GdUnitExecutionContext) -> void: - var test_case := context.test_case - var test_parameter_index := test_case.test_parameter_index() - var is_fail := false - var is_error := false - var failing_index := 0 - 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 != parameter_set_index: - continue - 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) - 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 = parameter_set_index - 1 - test_context.reports().clear() - if test_case.is_interupted(): - break - # add report to parent execution context if failed or an error is found - if is_fail: - context.reports().append(GdUnitReport.new().create(GdUnitReport.FAILURE, test_case.line_number(), "Test failed at parameterized index %d." % failing_index)) - if is_error: - context.reports().append(GdUnitReport.new().create(GdUnitReport.ABORT, test_case.line_number(), "Test aborted at parameterized index %d." % failing_index)) - await context.gc() - - -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): - 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 deleted file mode 100644 index fde7eb2..0000000 --- a/addons/gdUnit4/src/core/execution/stages/single/GdUnitTestCaseSingleExecutionStage.gd +++ /dev/null @@ -1,22 +0,0 @@ -## The test case execution stage.[br] -class_name GdUnitTestCaseSingleExecutionStage -extends IGdUnitExecutionStage - - -var _stage_before :IGdUnitExecutionStage = GdUnitTestCaseBeforeStage.new() -var _stage_after :IGdUnitExecutionStage = GdUnitTestCaseAfterStage.new() -var _stage_test :IGdUnitExecutionStage = GdUnitTestCaseSingleTestStage.new() - - -func _execute(context :GdUnitExecutionContext) -> void: - await _stage_before.execute(context) - if not context.test_case.is_skipped(): - await _stage_test.execute(GdUnitExecutionContext.of(context)) - await _stage_after.execute(context) - - -func set_debug_mode(debug_mode :bool = false): - super.set_debug_mode(debug_mode) - _stage_before.set_debug_mode(debug_mode) - _stage_after.set_debug_mode(debug_mode) - _stage_test.set_debug_mode(debug_mode) diff --git a/addons/gdUnit4/src/core/execution/stages/single/GdUnitTestCaseSingleTestStage.gd b/addons/gdUnit4/src/core/execution/stages/single/GdUnitTestCaseSingleTestStage.gd deleted file mode 100644 index 6882fe2..0000000 --- a/addons/gdUnit4/src/core/execution/stages/single/GdUnitTestCaseSingleTestStage.gd +++ /dev/null @@ -1,11 +0,0 @@ -## The single test case execution stage.[br] -class_name GdUnitTestCaseSingleTestStage -extends IGdUnitExecutionStage - - -## Executes a single test case 'test_()'.[br] -## It executes synchronized following stages[br] -## -> test_case() [br] -func _execute(context :GdUnitExecutionContext) -> void: - await context.test_case.execute() - await context.gc() diff --git a/addons/gdUnit4/src/core/parse/GdClassDescriptor.gd b/addons/gdUnit4/src/core/parse/GdClassDescriptor.gd deleted file mode 100644 index d2ac704..0000000 --- a/addons/gdUnit4/src/core/parse/GdClassDescriptor.gd +++ /dev/null @@ -1,34 +0,0 @@ -class_name GdClassDescriptor -extends RefCounted - - -var _name :String -var _parent = null -var _is_inner_class :bool -var _functions - - -func _init(p_name :String, p_is_inner_class :bool, p_functions :Array): - _name = p_name - _is_inner_class = p_is_inner_class - _functions = p_functions - - -func set_parent_clazz(p_parent :GdClassDescriptor): - _parent = p_parent - - -func name() -> String: - return _name - - -func parent() -> GdClassDescriptor: - return _parent - - -func is_inner_class() -> bool: - return _is_inner_class - - -func functions() -> Array: - return _functions diff --git a/addons/gdUnit4/src/core/parse/GdDefaultValueDecoder.gd b/addons/gdUnit4/src/core/parse/GdDefaultValueDecoder.gd deleted file mode 100644 index a67c2f4..0000000 --- a/addons/gdUnit4/src/core/parse/GdDefaultValueDecoder.gd +++ /dev/null @@ -1,255 +0,0 @@ -# holds all decodings for default values -class_name GdDefaultValueDecoder -extends GdUnitSingleton - - -@warning_ignore("unused_parameter") -var _decoders = { - TYPE_NIL: func(value): return "null", - TYPE_STRING: func(value): 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_COLOR: _on_type_Color, - TYPE_ARRAY: _on_type_Array.bind(TYPE_ARRAY), - TYPE_PACKED_BYTE_ARRAY: _on_type_Array.bind(TYPE_PACKED_BYTE_ARRAY), - TYPE_PACKED_STRING_ARRAY: _on_type_Array.bind(TYPE_PACKED_STRING_ARRAY), - TYPE_PACKED_FLOAT32_ARRAY: _on_type_Array.bind(TYPE_PACKED_FLOAT32_ARRAY), - TYPE_PACKED_FLOAT64_ARRAY: _on_type_Array.bind(TYPE_PACKED_FLOAT64_ARRAY), - TYPE_PACKED_INT32_ARRAY: _on_type_Array.bind(TYPE_PACKED_INT32_ARRAY), - TYPE_PACKED_INT64_ARRAY: _on_type_Array.bind(TYPE_PACKED_INT64_ARRAY), - 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), - TYPE_DICTIONARY: _on_type_Dictionary, - TYPE_RID: _on_type_RID, - TYPE_NODE_PATH: _on_type_NodePath, - TYPE_VECTOR2: _on_type_Vector.bind(TYPE_VECTOR2), - TYPE_VECTOR2I: _on_type_Vector.bind(TYPE_VECTOR2I), - TYPE_VECTOR3: _on_type_Vector.bind(TYPE_VECTOR3), - TYPE_VECTOR3I: _on_type_Vector.bind(TYPE_VECTOR3I), - TYPE_VECTOR4: _on_type_Vector.bind(TYPE_VECTOR4), - TYPE_VECTOR4I: _on_type_Vector.bind(TYPE_VECTOR4I), - TYPE_RECT2: _on_type_Rect2, - TYPE_RECT2I: _on_type_Rect2i, - TYPE_PLANE: _on_type_Plane, - TYPE_QUATERNION: _on_type_Quaternion, - TYPE_AABB: _on_type_AABB, - TYPE_BASIS: _on_type_Basis, - TYPE_CALLABLE: _on_type_Callable, - TYPE_SIGNAL: _on_type_Signal, - TYPE_TRANSFORM2D: _on_type_Transform2D, - TYPE_TRANSFORM3D: _on_type_Transform3D, - TYPE_PROJECTION: _on_type_Projection, - TYPE_OBJECT: _on_type_Object -} - -static func _regex(pattern :String) -> RegEx: - var regex := RegEx.new() - var err = regex.compile(pattern) - if err != OK: - push_error("error '%s' checked pattern '%s'" % [err, pattern]) - return null - return regex - - -func get_decoder(type :int) -> Callable: - return _decoders.get(type, func(value): return '%s' % value) - - -func _on_type_StringName(value :StringName) -> String: - if value.is_empty(): - return 'StringName()' - return 'StringName("%s")' % value - - -func _on_type_Object(value :Object, type :int) -> String: - return str(value) - - -func _on_type_Color(color :Color) -> String: - if color == Color.BLACK: - return "Color()" - return "Color%s" % color - - -func _on_type_NodePath(path :NodePath) -> String: - if path.is_empty(): - return 'NodePath()' - return 'NodePath("%s")' % path - - -func _on_type_Callable(cb :Callable) -> String: - return 'Callable()' - - -func _on_type_Signal(s :Signal) -> String: - return 'Signal()' - - -func _on_type_Dictionary(dict :Dictionary) -> String: - if dict.is_empty(): - return '{}' - return str(dict) - - -func _on_type_Array(value, type :int) -> String: - match type: - TYPE_ARRAY: - return str(value) - - TYPE_PACKED_COLOR_ARRAY: - var colors := PackedStringArray() - for color in value as PackedColorArray: - colors.append(_on_type_Color(color)) - if colors.is_empty(): - return "PackedColorArray()" - return "PackedColorArray([%s])" % ", ".join(colors) - - TYPE_PACKED_VECTOR2_ARRAY: - var vectors := PackedStringArray() - for vector in value as PackedVector2Array: - vectors.append(_on_type_Vector(vector, TYPE_VECTOR2)) - if vectors.is_empty(): - return "PackedVector2Array()" - return "PackedVector2Array([%s])" % ", ".join(vectors) - - TYPE_PACKED_VECTOR3_ARRAY: - var vectors := PackedStringArray() - for vector in value as PackedVector3Array: - vectors.append(_on_type_Vector(vector, TYPE_VECTOR3)) - if vectors.is_empty(): - return "PackedVector3Array()" - return "PackedVector3Array([%s])" % ", ".join(vectors) - - TYPE_PACKED_STRING_ARRAY: - var values := PackedStringArray() - for v in value as PackedStringArray: - values.append('"%s"' % v) - if values.is_empty(): - return "PackedStringArray()" - return "PackedStringArray([%s])" % ", ".join(values) - - TYPE_PACKED_BYTE_ARRAY,\ - TYPE_PACKED_FLOAT32_ARRAY,\ - TYPE_PACKED_FLOAT64_ARRAY,\ - TYPE_PACKED_INT32_ARRAY,\ - TYPE_PACKED_INT64_ARRAY: - var vectors := PackedStringArray() - for vector in value as Array: - vectors.append(str(vector)) - if vectors.is_empty(): - return GdObjects.type_as_string(type) + "()" - return "%s([%s])" % [GdObjects.type_as_string(type), ", ".join(vectors)] - return "unknown array type %d" % type - - -func _on_type_Vector(value :Variant, type :int) -> String: - match type: - TYPE_VECTOR2: - if value == Vector2(): - return "Vector2()" - return "Vector2%s" % value - TYPE_VECTOR2I: - if value == Vector2i(): - return "Vector2i()" - return "Vector2i%s" % value - TYPE_VECTOR3: - if value == Vector3(): - return "Vector3()" - return "Vector3%s" % value - TYPE_VECTOR3I: - if value == Vector3i(): - return "Vector3i()" - return "Vector3i%s" % value - TYPE_VECTOR4: - if value == Vector4(): - return "Vector4()" - return "Vector4%s" % value - TYPE_VECTOR4I: - if value == Vector4i(): - return "Vector4i()" - return "Vector4i%s" % value - return "unknown vector type %d" % type - - -func _on_type_Transform2D(transform :Transform2D) -> String: - if transform == Transform2D(): - return "Transform2D()" - return "Transform2D(Vector2%s, Vector2%s, Vector2%s)" % [transform.x, transform.y, transform.origin] - - -func _on_type_Transform3D(transform :Transform3D) -> String: - if transform == Transform3D(): - return "Transform3D()" - return "Transform3D(Vector3%s, Vector3%s, Vector3%s, Vector3%s)" % [transform.basis.x, transform.basis.y, transform.basis.z, transform.origin] - - -func _on_type_Projection(projection :Projection) -> String: - return "Projection(Vector4%s, Vector4%s, Vector4%s, Vector4%s)" % [projection.x, projection.y, projection.z, projection.w] - - -@warning_ignore("unused_parameter") -func _on_type_RID(value :RID) -> String: - return "RID()" - - -func _on_type_Rect2(rect :Rect2) -> String: - if rect == Rect2(): - return "Rect2()" - return "Rect2(Vector2%s, Vector2%s)" % [rect.position, rect.size] - - -func _on_type_Rect2i(rect :Variant) -> String: - if rect == Rect2i(): - return "Rect2i()" - return "Rect2i(Vector2i%s, Vector2i%s)" % [rect.position, rect.size] - - -func _on_type_Plane(plane :Plane) -> String: - if plane == Plane(): - return "Plane()" - return "Plane(%d, %d, %d, %d)" % [plane.x, plane.y, plane.z, plane.d] - - -func _on_type_Quaternion(quaternion :Quaternion) -> String: - if quaternion == Quaternion(): - return "Quaternion()" - return "Quaternion(%d, %d, %d, %d)" % [quaternion.x, quaternion.y, quaternion.z, quaternion.w] - - -func _on_type_AABB(aabb :AABB) -> String: - if aabb == AABB(): - return "AABB()" - return "AABB(Vector3%s, Vector3%s)" % [aabb.position, aabb.size] - - -func _on_type_Basis(basis :Basis) -> String: - if basis == Basis(): - return "Basis()" - return "Basis(Vector3%s, Vector3%s, Vector3%s)" % [basis.x, basis.y, basis.z] - - -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) - 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" - if type == TYPE_OBJECT: - return decoder.call(value, type) - return decoder.call(value) - - -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) - 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" - if type == TYPE_OBJECT: - return decoder.call(value, type) - return decoder.call(value) diff --git a/addons/gdUnit4/src/core/parse/GdFunctionArgument.gd b/addons/gdUnit4/src/core/parse/GdFunctionArgument.gd deleted file mode 100644 index 4ecbe12..0000000 --- a/addons/gdUnit4/src/core/parse/GdFunctionArgument.gd +++ /dev/null @@ -1,105 +0,0 @@ -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, value :Variant = UNDEFINED): - _name = p_name - _type = p_type - if p_name == ARG_PARAMETERIZED_TEST: - _parameter_sets = _parse_parameter_set(value) - _default_value = value - - -func name() -> String: - return _name - - -func default() -> Variant: - return GodotVersionFixures.convert(_default_value, _type) - - -func value_as_string() -> String: - if has_default(): - return str(_default_value) - return "" - - -func type() -> int: - return _type - - -func has_default() -> bool: - return not is_same(_default_value, UNDEFINED) - - -func is_parameter_set() -> bool: - return _name == ARG_PARAMETERIZED_TEST - - -func parameter_sets() -> PackedStringArray: - return _parameter_sets - - -static func get_parameter_set(parameters :Array) -> GdFunctionArgument: - for current in parameters: - if current != null and current.is_parameter_set(): - return current - return null - - -func _to_string() -> String: - 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.trim_prefix("[").trim_suffix("]").replace("\n", "").trim_prefix(" ") - var single_quote := false - var double_quote := false - var array_end := 0 - var current_index = 0 - var output :PackedStringArray = [] - var start_index := 0 - var buf = input.to_ascii_buffer() - for c in buf: - current_index += 1 - match c: - # ignore spaces between array elements - 32: if array_end == 0: - start_index += 1 - continue - # step over array element seperator ',' - 44: if array_end == 0: - start_index += 1 - continue - 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 array_end == 0 and current_index > start_index: - var parameters := input.substr(start_index, current_index-start_index) - parameters = _fix_comma_space.sub(parameters, ", ", true) - output.append(parameters) - start_index = current_index - return output diff --git a/addons/gdUnit4/src/core/parse/GdFunctionDescriptor.gd b/addons/gdUnit4/src/core/parse/GdFunctionDescriptor.gd deleted file mode 100644 index 51c18b0..0000000 --- a/addons/gdUnit4/src/core/parse/GdFunctionDescriptor.gd +++ /dev/null @@ -1,250 +0,0 @@ -class_name GdFunctionDescriptor -extends RefCounted - -var _is_virtual :bool -var _is_static :bool -var _is_engine :bool -var _is_coroutine :bool -var _name :String -var _line_number :int -var _return_type :int -var _return_class :String -var _args : Array[GdFunctionArgument] -var _varargs :Array[GdFunctionArgument] - - -func _init(p_name :String, - p_line_number :int, - p_is_virtual :bool, - p_is_static :bool, - p_is_engine :bool, - p_return_type :int, - p_return_class :String, - p_args : Array[GdFunctionArgument], - p_varargs :Array[GdFunctionArgument] = []): - _name = p_name - _line_number = p_line_number - _return_type = p_return_type - _return_class = p_return_class - _is_virtual = p_is_virtual - _is_static = p_is_static - _is_engine = p_is_engine - _is_coroutine = false - _args = p_args - _varargs = p_varargs - - -func name() -> String: - return _name - - -func line_number() -> int: - return _line_number - - -func is_virtual() -> bool: - return _is_virtual - - -func is_static() -> bool: - return _is_static - - -func is_engine() -> bool: - return _is_engine - - -func is_vararg() -> bool: - return not _varargs.is_empty() - - -func is_coroutine() -> bool: - return _is_coroutine - - -func is_parameterized() -> bool: - for current in _args: - var arg :GdFunctionArgument = current - if arg.name() == GdFunctionArgument.ARG_PARAMETERIZED_TEST: - return true - return false - - -func is_private() -> bool: - return name().begins_with("_") and not is_virtual() - - -func return_type() -> Variant: - return _return_type - - -func return_type_as_string() -> String: - if (return_type() == TYPE_OBJECT or return_type() == GdObjects.TYPE_ENUM) and not _return_class.is_empty(): - return _return_class - return GdObjects.type_as_string(return_type()) - - -func args() -> Array[GdFunctionArgument]: - return _args - - -func varargs() -> Array[GdFunctionArgument]: - return _varargs - - -func typeless() -> String: - var func_signature := "" - if _return_type == TYPE_NIL: - func_signature = "func %s(%s) -> void:" % [name(), typeless_args()] - elif _return_type == GdObjects.TYPE_VARIANT: - func_signature = "func %s(%s) -> Variant:" % [name(), typeless_args()] - else: - func_signature = "func %s(%s) -> %s:" % [name(), typeless_args(), return_type_as_string()] - return "static " + func_signature if is_static() else func_signature - - -func typeless_args() -> String: - var collect := PackedStringArray() - for arg in args(): - if arg.has_default(): - collect.push_back( arg.name() + "=" + arg.value_as_string()) - else: - collect.push_back(arg.name()) - for arg in varargs(): - collect.push_back(arg.name() + "=" + arg.value_as_string()) - return ", ".join(collect) - - -func typed_args() -> String: - var collect := PackedStringArray() - for arg in args(): - collect.push_back(arg._to_string()) - for arg in varargs(): - collect.push_back(arg._to_string()) - return ", ".join(collect) - - -func _to_string() -> String: - var fsignature := "virtual " if is_virtual() else "" - if _return_type == TYPE_NIL: - return fsignature + "[Line:%s] func %s(%s):" % [line_number(), name(), typed_args()] - var func_template := fsignature + "[Line:%s] func %s(%s) -> %s:" - if is_static(): - func_template= "[Line:%s] static func %s(%s) -> %s:" - return func_template % [line_number(), name(), typed_args(), return_type_as_string()] - - -# extract function description given by Object.get_method_list() -static func extract_from(descriptor :Dictionary) -> GdFunctionDescriptor: - var function_flags :int = descriptor["flags"] - var is_virtual_ :bool = function_flags & METHOD_FLAG_VIRTUAL - var is_static_ :bool = function_flags & METHOD_FLAG_STATIC - var is_vararg_ :bool = function_flags & METHOD_FLAG_VARARG - #var is_const :bool = function_flags & METHOD_FLAG_CONST - #var is_core :bool = function_flags & METHOD_FLAG_OBJECT_CORE - #var is_default :bool = function_flags & METHOD_FLAGS_DEFAULT - #prints("is_virtual: ", is_virtual) - #prints("is_static: ", is_static) - #prints("is_const: ", is_const) - #prints("is_core: ", is_core) - #prints("is_default: ", is_default) - #prints("is_vararg: ", is_vararg) - return GdFunctionDescriptor.new( - descriptor["name"], - -1, - is_virtual_, - is_static_, - true, - _extract_return_type(descriptor["return"]), - descriptor["return"]["class_name"], - _extract_args(descriptor), - _build_varargs(is_vararg_) - ) - -# temporary exclude GlobalScope enums -const enum_fix := [ - "Side", - "Corner", - "Orientation", - "ClockDirection", - "HorizontalAlignment", - "VerticalAlignment", - "InlineAlignment", - "EulerOrder", - "Error", - "Key", - "MIDIMessage", - "MouseButton", - "MouseButtonMask", - "JoyButton", - "JoyAxis", - "PropertyHint", - "PropertyUsageFlags", - "MethodFlags", - "Variant.Type", - "Control.LayoutMode"] - - -static func _extract_return_type(return_info :Dictionary) -> Variant: - var type :int = return_info["type"] - var usage :int = return_info["usage"] - if type == TYPE_INT and usage & PROPERTY_USAGE_CLASS_IS_ENUM: - return GdObjects.TYPE_ENUM - if type == TYPE_NIL and usage & PROPERTY_USAGE_NIL_IS_VARIANT: - return GdObjects.TYPE_VARIANT - return type - - -static func _extract_args(descriptor :Dictionary) -> Array[GdFunctionArgument]: - var args_ :Array[GdFunctionArgument] = [] - var arguments :Array = descriptor["args"] - var defaults :Array = descriptor["default_args"] - # iterate backwards because the default values are stored from right to left - while not arguments.is_empty(): - var arg :Dictionary = arguments.pop_back() - var arg_name := _argument_name(arg) - var arg_type := _argument_type(arg) - var arg_default :Variant = GdFunctionArgument.UNDEFINED - if not defaults.is_empty(): - var default_value = 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_ - - -static func _build_varargs(p_is_vararg :bool) -> Array[GdFunctionArgument]: - var varargs_ :Array[GdFunctionArgument] = [] - if not p_is_vararg: - return varargs_ - # if function has vararg we need to handle this manually by adding 10 default arguments - var type := GdObjects.TYPE_VARARG - for index in 10: - varargs_.push_back(GdFunctionArgument.new("vararg%d_" % index, type, "\"%s\"" % GdObjects.TYPE_VARARG_PLACEHOLDER_VALUE)) - return varargs_ - - -static func _argument_name(arg :Dictionary) -> String: - # add suffix to the name to prevent clash with reserved names - return (arg["name"] + "_") as String - - -static func _argument_type(arg :Dictionary) -> int: - var type :int = arg["type"] - if type == TYPE_OBJECT: - if arg["class_name"] == "Node": - return GdObjects.TYPE_NODE - return type - - -static func _argument_type_as_string(arg :Dictionary) -> String: - var type := _argument_type(arg) - match type: - TYPE_NIL: - return "" - TYPE_OBJECT: - var clazz_name :String = arg["class_name"] - if not clazz_name.is_empty(): - return clazz_name - return "" - _: - return GdObjects.type_as_string(type) diff --git a/addons/gdUnit4/src/core/parse/GdScriptParser.gd b/addons/gdUnit4/src/core/parse/GdScriptParser.gd deleted file mode 100644 index 37e0067..0000000 --- a/addons/gdUnit4/src/core/parse/GdScriptParser.gd +++ /dev/null @@ -1,824 +0,0 @@ -class_name GdScriptParser -extends RefCounted - -const GdUnitTools := preload("res://addons/gdUnit4/src/core/GdUnitTools.gd") - -const ALLOWED_CHARACTERS := "0123456789_abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ\"" - -var TOKEN_NOT_MATCH := Token.new("") -var TOKEN_SPACE := SkippableToken.new(" ") -var TOKEN_TABULATOR := SkippableToken.new("\t") -var TOKEN_NEW_LINE := SkippableToken.new("\n") -var TOKEN_COMMENT := SkippableToken.new("#") -var TOKEN_CLASS_NAME := Token.new("class_name") -var TOKEN_INNER_CLASS := Token.new("class") -var TOKEN_EXTENDS := Token.new("extends") -var TOKEN_ENUM := Token.new("enum") -var TOKEN_FUNCTION_STATIC_DECLARATION := Token.new("staticfunc") -var TOKEN_FUNCTION_DECLARATION := Token.new("func") -var TOKEN_FUNCTION := Token.new(".") -var TOKEN_FUNCTION_RETURN_TYPE := Token.new("->") -var TOKEN_FUNCTION_END := Token.new("):") -var TOKEN_ARGUMENT_ASIGNMENT := Token.new("=") -var TOKEN_ARGUMENT_TYPE_ASIGNMENT := Token.new(":=") -var TOKEN_ARGUMENT_FUZZER := FuzzerToken.new(GdUnitTools.to_regex("((?!(fuzzer_(seed|iterations)))fuzzer?\\w+)( ?+= ?+| ?+:= ?+| ?+:Fuzzer ?+= ?+|)")) -var TOKEN_ARGUMENT_TYPE := Token.new(":") -var TOKEN_ARGUMENT_SEPARATOR := Token.new(",") -var TOKEN_BRACKET_OPEN := Token.new("(") -var TOKEN_BRACKET_CLOSE := Token.new(")") -var TOKEN_ARRAY_OPEN := Token.new("[") -var TOKEN_ARRAY_CLOSE := Token.new("]") - -var OPERATOR_ADD := Operator.new("+") -var OPERATOR_SUB := Operator.new("-") -var OPERATOR_MUL := Operator.new("*") -var OPERATOR_DIV := Operator.new("/") -var OPERATOR_REMAINDER := Operator.new("%") - -var TOKENS := [ - TOKEN_SPACE, - TOKEN_TABULATOR, - TOKEN_NEW_LINE, - TOKEN_COMMENT, - TOKEN_BRACKET_OPEN, - TOKEN_BRACKET_CLOSE, - TOKEN_ARRAY_OPEN, - TOKEN_ARRAY_CLOSE, - TOKEN_CLASS_NAME, - TOKEN_INNER_CLASS, - TOKEN_EXTENDS, - TOKEN_ENUM, - TOKEN_FUNCTION_STATIC_DECLARATION, - TOKEN_FUNCTION_DECLARATION, - TOKEN_ARGUMENT_FUZZER, - TOKEN_ARGUMENT_TYPE_ASIGNMENT, - TOKEN_ARGUMENT_ASIGNMENT, - TOKEN_ARGUMENT_TYPE, - TOKEN_FUNCTION, - TOKEN_ARGUMENT_SEPARATOR, - TOKEN_FUNCTION_RETURN_TYPE, - OPERATOR_ADD, - OPERATOR_SUB, - OPERATOR_MUL, - OPERATOR_DIV, - OPERATOR_REMAINDER, -] - -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: - return to_unix_format(row.replace(" ", "").replace("\t", "")) - - -static func to_unix_format(input :String) -> String: - return input.replace("\r\n", "\n") - - -class Token extends RefCounted: - var _token: String - 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) - if result == null: - return false - _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(): - return "Token{" + _token + "}" - - -class Operator extends Token: - func _init(value: String): - super(value, true) - - func _to_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): - super(p_token) - - func is_skippable() -> bool: - return true - - -# Token to parse Fuzzers -class FuzzerToken extends Token: - var _name: String - - - func _init(regex: RegEx): - super("", false, regex) - - - func match(input: String, pos: int) -> bool: - if _regex: - var result := _regex.search(input, pos) - if result == null: - return false - _name = result.strings[1] - _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(): - return "FuzzerToken{%s: '%s'}" % [_name, _token] - - -# Token to parse function arguments -class Variable extends Token: - var _plain_value - var _typed_value - var _type :int = TYPE_NIL - - - func _init(p_value: String): - 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 - var type_ := GdObjects.string_to_type(p_value) - if type_ != TYPE_NIL: - return type_ - if p_value.is_valid_int(): - return TYPE_INT - if p_value.is_valid_float(): - return TYPE_FLOAT - 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: - return p_value#.substr(1, p_value.length() - 2) - TYPE_INT: - return p_value.to_int() - TYPE_FLOAT: - return p_value.to_float() - return p_value - - - func is_variable() -> bool: - return true - - - func type() -> int: - return _type - - - func value(): - return _typed_value - - - func plain_value(): - return _plain_value - - - func _to_string(): - return "Variable{%s: %s : '%s'}" % [_plain_value, GdObjects.type_as_string(_type), _token] - - -class TokenInnerClass extends Token: - var _clazz_name - var _content := PackedStringArray() - - - static func _strip_leading_spaces(input :String) -> String: - var characters := input.to_ascii_buffer() - while not characters.is_empty(): - if characters[0] != 0x20: - break - characters.remove_at(0) - return characters.get_string_from_ascii() - - - static func _consumed_bytes(row :String) -> int: - return row.replace(" ", "").replace(" ", "").length() - - - func _init(clazz_name :String): - 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]) - # parse class content - 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) - 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"): - source_row = source_row.trim_prefix("\t") - # refomat invalid empty lines - if source_row.dedent().is_empty(): - _content.append("") - else: - _content.append(source_row) - continue - break - _consumed += TokenInnerClass._consumed_bytes("".join(_content)) - - - func _to_string(): - return "TokenInnerClass{%s}" % [_clazz_name] - - -func _init(): - _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: - for t in TOKENS: - if t.match(input, current_index): - return t - return TOKEN_NOT_MATCH - - -func next_token(input: String, current_index: int, ignore_tokens :Array[Token] = []) -> Token: - var token := TOKEN_NOT_MATCH - for t in TOKENS.filter(func(token): return not ignore_tokens.has(token)): - if t.match(input, current_index): - token = t - break - if token == OPERATOR_SUB: - token = tokenize_value(input, current_index, 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, ignore_tokens.has(TOKEN_FUNCTION)) - return 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' - var test_for_sign := (token == null or token.is_operator()) and input[current] == "-" - while current + next < len(input): - var character := input[current + next] as String - # if first charater a sign - # or allowend charset - # or is a float value - if (test_for_sign and next==0) \ - or character in ALLOWED_CHARACTERS \ - or (character == "." and (ignore_dots or current_token.is_valid_int())): - current_token += character - next += 1 - continue - break - if current_token != "": - return Variable.new(current_token) - return TOKEN_NOT_MATCH - - -func extract_clazz_name(value :String) -> String: - var result := _regex_clazz_name.search(value) - if result == null: - push_error("Can't extract class name from '%s'" % value) - return "" - if result.get_string(2).is_empty(): - return result.get_string(5) - else: - return result.get_string(2) - - -@warning_ignore("unused_parameter") -func tokenize_inner_class(source_code: String, current: int, token: Token) -> Token: - var clazz_name := extract_clazz_name(source_code.substr(current, 64)) - return TokenInnerClass.new(clazz_name) - - -@warning_ignore("assert_always_false") -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 - match operator: - OPERATOR_ADD: - value = lvalue + next_token_.value() - OPERATOR_SUB: - value = lvalue - next_token_.value() - OPERATOR_MUL: - value = lvalue * next_token_.value() - OPERATOR_DIV: - value = lvalue / next_token_.value() - OPERATOR_REMAINDER: - value = lvalue & next_token_.value() - _: - assert(false, "Unsupported operator %s" % operator) - return Variable.new( str(value)) - return operator - - -func parse_func_return_type(row: String) -> int: - var token := parse_return_token(row) - if token == TOKEN_NOT_MATCH: - return TYPE_NIL - return token.type() - - -func parse_return_token(input: String) -> Token: - var index := input.rfind(TOKEN_FUNCTION_RETURN_TYPE._token) - if index == -1: - return TOKEN_NOT_MATCH - index += TOKEN_FUNCTION_RETURN_TYPE._consumed - # 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_FUNCTION]) - return token - - -# Parses the argument into a argument signature -# e.g. func foo(arg1 :int, arg2 = 20) -> [arg1, arg2] -func parse_arguments(input: String) -> Array[GdFunctionArgument]: - var args :Array[GdFunctionArgument] = [] - var current_index := 0 - var token :Token = null - var bracket := 0 - var in_function := false - while current_index < len(input): - token = next_token(input, current_index) - # fallback to not end in a endless loop - if token == TOKEN_NOT_MATCH: - var error : = """ - Parsing Error: Invalid token at pos %d found. - Please report this error! - source_code: - -------------------------------------------------------------- - %s - -------------------------------------------------------------- - """.dedent() % [current_index, input] - push_error(error) - current_index += 1 - continue - current_index += token._consumed - if token.is_skippable(): - continue - if token == TOKEN_BRACKET_OPEN: - in_function = true - bracket += 1 - continue - if token == TOKEN_BRACKET_CLOSE: - bracket -= 1 - # if function end? - if in_function and bracket == 0: - return args - # is function - if token == TOKEN_FUNCTION_DECLARATION: - token = next_token(input, current_index) - current_index += token._consumed - continue - # is fuzzer argument - if token is FuzzerToken: - var arg_value := _parse_end_function(input.substr(current_index), true) - current_index += arg_value.length() - args.append(GdFunctionArgument.new(token.name(), token.type(), arg_value)) - continue - # is value argument - 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 - # parse type and default value - while current_index < len(input): - token = next_token(input, current_index) - current_index += token._consumed - if token.is_skippable(): - continue - match token: - TOKEN_ARGUMENT_TYPE: - token = next_token(input, current_index) - if token == TOKEN_SPACE: - current_index += token._consumed - token = next_token(input, current_index) - arg_type = GdObjects.string_as_typeof(token._token) - TOKEN_ARGUMENT_TYPE_ASIGNMENT: - arg_value = _parse_end_function(input.substr(current_index), true) - current_index += arg_value.length() - TOKEN_ARGUMENT_ASIGNMENT: - token = next_token(input, current_index) - arg_value = _parse_end_function(input.substr(current_index), true) - current_index += arg_value.length() - TOKEN_BRACKET_OPEN: - bracket += 1 - # 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) - arg_value += func_body - # fix parse index to end of value - current_index += func_body.length() - TOKEN_BRACKET_OPEN._consumed - TOKEN_BRACKET_CLOSE._consumed - TOKEN_BRACKET_CLOSE: - bracket -= 1 - # end of function - if bracket == 0: - break - TOKEN_ARGUMENT_SEPARATOR: - if bracket <= 1: - break - arg_value = arg_value.lstrip(" ") - if arg_type == TYPE_NIL and arg_value != GdFunctionArgument.UNDEFINED: - if arg_value.begins_with("Color."): - arg_type = TYPE_COLOR - elif arg_value.begins_with("Vector2."): - arg_type = TYPE_VECTOR2 - elif arg_value.begins_with("Vector3."): - arg_type = TYPE_VECTOR3 - elif arg_value.begins_with("AABB("): - arg_type = TYPE_AABB - elif arg_value.begins_with("["): - arg_type = TYPE_ARRAY - elif arg_value.begins_with("{"): - arg_type = TYPE_DICTIONARY - else: - arg_type = typeof(str_to_var(arg_value)) - if arg_value.rfind(")") == arg_value.length()-1: - arg_type = GdObjects.TYPE_FUNC - elif arg_type == TYPE_NIL: - arg_type = TYPE_STRING - args.append(GdFunctionArgument.new(arg_name, arg_type, arg_value)) - 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 - - while current_index < len(input) and not end_of_func: - var character = input[current_index] - # step over strings - if character == "'" : - current_index = input.find("'", current_index+1) + 1 - if current_index == 0: - push_error("Parsing error on '%s', can't evaluate end of string." % input) - return "" - continue - if character == '"' : - # test for string blocks - if input.find('"""', current_index) == current_index: - current_index = input.find('"""', current_index+3) + 3 - else: - current_index = input.find('"', current_index+1) + 1 - if current_index == 0: - 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 - "]": in_array -= 1 - # count if inside a function - "(": bracket_count += 1 - ")": - bracket_count -= 1 - if bracket_count < 0 and in_array <= 0: - end_of_func = true - ",": - if bracket_count == 0 and in_array == 0: - end_of_func = true - current_index += 1 - if remove_trailing_char: - # check if the parsed value ends with comma or end of doubled breaked - # `,` or `())` - var trailing_char := input[current_index-1] - if trailing_char == ',' or (bracket_count < 0 and trailing_char == ')'): - return input.substr(0, current_index-1) - return input.substr(0, current_index) - - -func extract_inner_class(source_rows: PackedStringArray, clazz_name :String) -> PackedStringArray: - for row_index in source_rows.size(): - var input := GdScriptParser.clean_up_row(source_rows[row_index]) - var token := next_token(input, 0) - if token.is_inner_class(): - if token.is_class_name(clazz_name): - token.parse(source_rows, row_index) - return token.content() - return PackedStringArray() - - -func extract_source_code(script_path :PackedStringArray) -> PackedStringArray: - if script_path.is_empty(): - push_error("Invalid script path '%s'" % script_path) - return PackedStringArray() - #load the source code - var resource_path := script_path[0] - var script :GDScript = load(resource_path) - var source_code := load_source_code(script, script_path) - var base_script := script.get_base_script() - if base_script: - _base_clazz = GdObjects.extract_class_name_from_class_path([base_script.resource_path]) - source_code += load_source_code(base_script, script_path) - return source_code - - -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() - push_error("Can't fully extract function signature of '%s'" % rows[index]) - return "" - - -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) - 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] - source_rows = extract_inner_class(source_rows, inner_clazz) - return PackedStringArray(source_rows) - - -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]) - var token := next_token(input, 0) - if token == TOKEN_CLASS_NAME: - token = tokenize_value(input, token._consumed, 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 token := next_token(input, current_index) - current_index += token._consumed - if token != TOKEN_FUNCTION_STATIC_DECLARATION and token != TOKEN_FUNCTION_DECLARATION: - return "" - while not token is Variable: - token = next_token(input, current_index) - current_index += token._consumed - return token._token - - -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] - # step over inner class functions - if row.begins_with("\t"): - continue - 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 fd := parse_func_description(func_signature, clazz_name, clazz_path, rowIndex+1) - fd._is_coroutine = is_func_coroutine(rows, rowIndex) - func_descriptors.append(fd) - return func_descriptors - - -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] - is_coroutine = row.contains("await") - if is_coroutine: - return true - 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 - return is_coroutine - - -func _is_func_included(row :String, included_functions :PackedStringArray) -> bool: - if included_functions.is_empty(): - return true - for name in included_functions: - if row.find(name) != -1: - return true - return false - - -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 return_type :int - var return_clazz := "" - var token := parse_return_token(func_signature) - if token == TOKEN_NOT_MATCH: - return_type = GdObjects.TYPE_VARIANT - else: - 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, - is_virtual_func(clazz_name, clazz_path, name), - is_static_func(func_signature), - false, - return_type, - return_clazz, - parse_arguments(func_signature) - ) - - -# caches already parsed classes for virtual functions -# key: value: a Array of virtual function names -var _virtual_func_cache := Dictionary() - -func is_virtual_func(clazz_name :String, clazz_path :PackedStringArray, func_name :String) -> bool: - if _virtual_func_cache.has(clazz_name): - 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: - var is_virtual_function :bool = method_descriptor["flags"] & METHOD_FLAG_VIRTUAL - if is_virtual_function: - virtual_functions.append(method_descriptor["name"]) - _virtual_func_cache[clazz_name] = virtual_functions - return _virtual_func_cache[clazz_name].has(func_name) - - -func is_static_func(func_signature :String) -> bool: - var input := GdScriptParser.clean_up_row(func_signature) - var token := next_token(input, 0) - return token == TOKEN_FUNCTION_STATIC_DECLARATION - - -func is_inner_class(clazz_path :PackedStringArray) -> bool: - return clazz_path.size() > 1 - - -func is_func_end(row :String) -> bool: - return row.strip_edges(false, true).ends_with(":") - - -func is_class_enum_type(value :String) -> bool: - # 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] - 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[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) - - -func parse(clazz_name :String, clazz_path :PackedStringArray) -> GdUnitResult: - if clazz_path.is_empty(): - return GdUnitResult.error("Invalid script path '%s'" % clazz_path) - var is_inner_class_ := is_inner_class(clazz_path) - var script :GDScript = load(clazz_path[0]) - var function_descriptors := extract_functions(script, clazz_name, clazz_path) - var gd_class := GdClassDescriptor.new(clazz_name, is_inner_class_, function_descriptors) - # iterate over class dependencies - script = script.get_base_script() - while script != null: - clazz_name = GdObjects.extract_class_name_from_class_path([script.resource_path]) - function_descriptors = extract_functions(script, clazz_name, clazz_path) - gd_class.set_parent_clazz(GdClassDescriptor.new(clazz_name, is_inner_class_, function_descriptors)) - script = script.get_base_script() - return GdUnitResult.success(gd_class) diff --git a/addons/gdUnit4/src/core/parse/GdUnitExpressionRunner.gd b/addons/gdUnit4/src/core/parse/GdUnitExpressionRunner.gd deleted file mode 100644 index f3130a0..0000000 --- a/addons/gdUnit4/src/core/parse/GdUnitExpressionRunner.gd +++ /dev/null @@ -1,26 +0,0 @@ -class_name GdUnitExpressionRunner -extends RefCounted - -const CLASS_TEMPLATE = """ -class_name _ExpressionRunner extends '${clazz_path}' - -func __run_expression() -> Variant: - return $expression - -""" - -func execute(src_script :GDScript, expression :String) -> Variant: - var script := GDScript.new() - var resource_path := "res://addons/gdUnit4/src/Fuzzers.gd" if src_script.resource_path.is_empty() else src_script.resource_path - script.source_code = CLASS_TEMPLATE.dedent()\ - .replace("${clazz_path}", resource_path)\ - .replace("$expression", expression) - script.reload(false) - var runner :Variant = script.new() - if runner.has_method("queue_free"): - runner.queue_free() - return runner.__run_expression() - - -func to_fuzzer(src_script :GDScript, expression :String) -> Fuzzer: - return execute(src_script, expression) as Fuzzer diff --git a/addons/gdUnit4/src/core/parse/GdUnitTestParameterSetResolver.gd b/addons/gdUnit4/src/core/parse/GdUnitTestParameterSetResolver.gd deleted file mode 100644 index b0865b2..0000000 --- a/addons/gdUnit4/src/core/parse/GdUnitTestParameterSetResolver.gd +++ /dev/null @@ -1,194 +0,0 @@ -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 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 "" - - -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(test_case, parameter_set, property_names) - _test_case_names_cache.append(_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): return property["name"])\ - .filter(func(property): 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(test_case: _TestCase, 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(_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, validate := false) -> Array: - var source_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 = 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() - copy_properties(test_case.get_parent(), instance) - instance.queue_free() - var parameter_sets = instance.call("__extract_test_parameters") - if not 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: - var context := GdUnitThreadManager.get_current_context().get_execution_context() - for property in source.get_property_list(): - var property_name = property["name"] - var property_value = 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 deleted file mode 100644 index eb7ed2e..0000000 --- a/addons/gdUnit4/src/core/report/GdUnitReport.gd +++ /dev/null @@ -1,74 +0,0 @@ -class_name GdUnitReport -extends Resource - -# report type -enum { - SUCCESS, - WARN, - FAILURE, - ORPHAN, - TERMINATED, - INTERUPTED, - ABORT, - SKIPPED, -} - -var _type :int -var _line_number :int -var _message :String - - -func create(p_type :int, p_line_number :int, p_message :String) -> GdUnitReport: - _type = p_type - _line_number = p_line_number - _message = p_message - return self - - -func type() -> int: - return _type - - -func line_number() -> int: - return _line_number - - -func message() -> String: - return _message - - -func is_skipped() -> bool: - return _type == SKIPPED - - -func is_warning() -> bool: - return _type == WARN - - -func is_failure() -> bool: - return _type == FAILURE - - -func is_error() -> bool: - return _type == TERMINATED or _type == INTERUPTED or _type == ABORT - - -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] - - -func serialize() -> Dictionary: - return { - "type" :_type, - "line_number" :_line_number, - "message" :_message - } - - -func deserialize(serialized :Dictionary) -> GdUnitReport: - _type = serialized["type"] - _line_number = serialized["line_number"] - _message = serialized["message"] - return self diff --git a/addons/gdUnit4/src/core/templates/test_suite/GdUnitTestSuiteDefaultTemplate.gd b/addons/gdUnit4/src/core/templates/test_suite/GdUnitTestSuiteDefaultTemplate.gd deleted file mode 100644 index f7802d2..0000000 --- a/addons/gdUnit4/src/core/templates/test_suite/GdUnitTestSuiteDefaultTemplate.gd +++ /dev/null @@ -1,36 +0,0 @@ -class_name GdUnitTestSuiteDefaultTemplate -extends RefCounted - - -const DEFAULT_TEMP_TS_GD =""" - # GdUnit generated TestSuite - class_name ${suite_class_name} - extends GdUnitTestSuite - @warning_ignore('unused_parameter') - @warning_ignore('return_value_discarded') - - # TestSuite generated from - const __source = '${source_resource_path}' -""" - - -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 deleted file mode 100644 index 3e241f3..0000000 --- a/addons/gdUnit4/src/core/templates/test_suite/GdUnitTestSuiteTemplate.gd +++ /dev/null @@ -1,142 +0,0 @@ -class_name GdUnitTestSuiteTemplate -extends RefCounted - -const TEMPLATE_ID_GD = 1000 -const TEMPLATE_ID_CS = 2000 - -const SUPPORTED_TAGS_GD = """ - GdScript Tags are replaced when the test-suite is created. - - # The class name of the test-suite, formed from the source script. - ${suite_class_name} - # 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 - var my_script := load(${source_resource_path}) - # will be result in - 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 - private object myScript = GD.Load(${source_resource_path}); - // will be result in - private object myScript = GD.Load("res://folder/MyClass.cs"); -""" - -const TAG_TEST_SUITE_CLASS = "${suite_class_name}" -const TAG_SOURCE_CLASS_NAME = "${source_class}" -const TAG_SOURCE_CLASS_VARNAME = "${source_var}" -const TAG_SOURCE_RESOURCE_PATH = "${source_resource_path}" - - -static func default_GD_template() -> String: - return GdUnitTestSuiteDefaultTemplate.DEFAULT_TEMP_TS_GD.dedent().trim_prefix("\n") - - -static func default_CS_template() -> String: - return GdUnitTestSuiteDefaultTemplate.DEFAULT_TEMP_TS_CS.dedent().trim_prefix("\n") - - -static func build_template(source_path: String) -> String: - var clazz_name :String = GdObjects.to_pascal_case(GdObjects.extract_class_name(source_path).value() as String) - return GdUnitSettings.get_setting(GdUnitSettings.TEMPLATE_TS_GD, default_GD_template())\ - .replace(TAG_TEST_SUITE_CLASS, clazz_name+"Test")\ - .replace(TAG_SOURCE_RESOURCE_PATH, source_path)\ - .replace(TAG_SOURCE_CLASS_NAME, clazz_name)\ - .replace(TAG_SOURCE_CLASS_VARNAME, GdObjects.to_snake_case(clazz_name)) - - -static func default_template(template_id :int) -> String: - if template_id != TEMPLATE_ID_GD and template_id != TEMPLATE_ID_CS: - push_error("Invalid template '%d' id! Cant load testsuite template" % template_id) - return "" - if template_id == TEMPLATE_ID_GD: - return default_GD_template() - return default_CS_template() - - -static func load_template(template_id :int) -> String: - if template_id != TEMPLATE_ID_GD and template_id != TEMPLATE_ID_CS: - push_error("Invalid template '%d' id! Cant load testsuite template" % template_id) - return "" - if template_id == TEMPLATE_ID_GD: - return GdUnitSettings.get_setting(GdUnitSettings.TEMPLATE_TS_GD, default_GD_template()) - return GdUnitSettings.get_setting(GdUnitSettings.TEMPLATE_TS_CS, default_CS_template()) - - -static func save_template(template_id :int, template :String) -> void: - if template_id != TEMPLATE_ID_GD and template_id != TEMPLATE_ID_CS: - push_error("Invalid template '%d' id! Cant load testsuite template" % template_id) - return - if template_id == TEMPLATE_ID_GD: - GdUnitSettings.save_property(GdUnitSettings.TEMPLATE_TS_GD, template.dedent().trim_prefix("\n")) - elif template_id == TEMPLATE_ID_CS: - GdUnitSettings.save_property(GdUnitSettings.TEMPLATE_TS_CS, template.dedent().trim_prefix("\n")) - - -static func reset_to_default(template_id :int) -> void: - if template_id != TEMPLATE_ID_GD and template_id != TEMPLATE_ID_CS: - push_error("Invalid template '%d' id! Cant load testsuite template" % template_id) - return - if template_id == TEMPLATE_ID_GD: - GdUnitSettings.save_property(GdUnitSettings.TEMPLATE_TS_GD, default_GD_template()) - else: - GdUnitSettings.save_property(GdUnitSettings.TEMPLATE_TS_CS, default_CS_template()) - - -static func load_tags(template_id :int) -> String: - if template_id != TEMPLATE_ID_GD and template_id != TEMPLATE_ID_CS: - push_error("Invalid template '%d' id! Cant load testsuite template" % template_id) - return "Error checked loading tags" - if template_id == TEMPLATE_ID_GD: - return SUPPORTED_TAGS_GD - else: - return SUPPORTED_TAGS_CS diff --git a/addons/gdUnit4/src/core/thread/GdUnitThreadContext.gd b/addons/gdUnit4/src/core/thread/GdUnitThreadContext.gd deleted file mode 100644 index d0eaa16..0000000 --- a/addons/gdUnit4/src/core/thread/GdUnitThreadContext.gd +++ /dev/null @@ -1,62 +0,0 @@ -class_name GdUnitThreadContext -extends RefCounted - -var _thread :Thread -var _thread_name :String -var _thread_id :int -var _assert :GdUnitAssert -var _signal_collector :GdUnitSignalCollector -var _execution_context :GdUnitExecutionContext - - -func _init(thread :Thread = null): - if thread != null: - _thread = thread - _thread_name = thread.get_meta("name") - _thread_id = thread.get_id() as int - else: - _thread_name = "main" - _thread_id = OS.get_main_thread_id() - _signal_collector = GdUnitSignalCollector.new() - - -func dispose() -> void: - _assert = null - if is_instance_valid(_signal_collector): - _signal_collector.clear() - _signal_collector = null - _execution_context = null - _thread = null - - -func set_assert(value :GdUnitAssert) -> GdUnitThreadContext: - _assert = value - return self - - -func get_assert() -> GdUnitAssert: - return _assert - - -func set_execution_context(context :GdUnitExecutionContext) -> void: - _execution_context = context - - -func get_execution_context() -> GdUnitExecutionContext: - return _execution_context - - -func get_execution_context_id() -> int: - return _execution_context.get_instance_id() - - -func get_signal_collector() -> GdUnitSignalCollector: - return _signal_collector - - -func thread_id() -> int: - return _thread_id - - -func _to_string() -> String: - return "ThreadContext <%s>: %s " % [_thread_name, _thread_id] diff --git a/addons/gdUnit4/src/core/thread/GdUnitThreadManager.gd b/addons/gdUnit4/src/core/thread/GdUnitThreadManager.gd deleted file mode 100644 index 532946d..0000000 --- a/addons/gdUnit4/src/core/thread/GdUnitThreadManager.gd +++ /dev/null @@ -1,62 +0,0 @@ -## A manager to run new thread and crate a ThreadContext shared over the actual test run -class_name GdUnitThreadManager -extends RefCounted - -## { = } -var _thread_context_by_id := {} -## holds the current thread id -var _current_thread_id :int = -1 - -func _init(): - # 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()) - - -## Runs a new thread by given name and Callable.[br] -## A new GdUnitThreadContext is created, which is used for the actual test execution.[br] -## We need this custom implementation while this bug is not solved -## Godot issue https://github.com/godotengine/godot/issues/79637 -static func run(name :String, cb :Callable) -> Variant: - return await instance()._run(name, cb) - - -## Returns the current valid thread context -static func get_current_context() -> GdUnitThreadContext: - return instance()._get_current_context() - - -func _run(name :String, cb :Callable): - # 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 thread := Thread.new() - thread.set_meta("name", name) - thread.start(cb) - _current_thread_id = thread.get_id() as int - _register_thread(thread, _current_thread_id) - var result :Variant = await thread.wait_to_finish() - _unregister_thread(_current_thread_id) - # restore original thread id - _current_thread_id = save_current_thread_id - return result - - -func _register_thread(thread :Thread, thread_id :int) -> void: - var context := GdUnitThreadContext.new(thread) - _thread_context_by_id[thread_id] = context - - -func _unregister_thread(thread_id :int) -> void: - var context := _thread_context_by_id.get(thread_id) as GdUnitThreadContext - if context: - _thread_context_by_id.erase(thread_id) - context.dispose() - - -func _get_current_context() -> GdUnitThreadContext: - return _thread_context_by_id.get(_current_thread_id) diff --git a/addons/gdUnit4/src/extractors/GdUnitFuncValueExtractor.gd b/addons/gdUnit4/src/extractors/GdUnitFuncValueExtractor.gd deleted file mode 100644 index 096149c..0000000 --- a/addons/gdUnit4/src/extractors/GdUnitFuncValueExtractor.gd +++ /dev/null @@ -1,69 +0,0 @@ -# This class defines a value extractor by given function name and args -extends GdUnitValueExtractor - -var _func_names :Array -var _args :Array - -func _init(func_name :String, p_args :Array): - _func_names = func_name.split(".") - _args = p_args - - -func func_names() -> Array: - return _func_names - - -func args() -> Array: - return _args - - -# Extracts a value by given `func_name` and `args`, -# 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 -# e.g. "get_children.get_name" checked a node -# do calls node.get_children() for all childs get_name() and returns all names in an 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): - 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): - values.append(_call_func(element, func_name)) - value = values - else: - value = _call_func(value, func_name) - var type := typeof(value) - if type == TYPE_STRING_NAME: - return str(value) - if type == TYPE_STRING and value == "n.a.": - return value - return value - - -func _call_func(value, func_name :String): - # 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): - var extract := Callable(value, func_name) - if extract.is_valid(): - 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) - if parameter != null: - return parameter - # nothing found than return 'n.a.' - if GdUnitSettings.is_verbose_assert_warnings(): - push_warning("Extracting value from element '%s' by func '%s' failed! Converting to \"n.a.\"" % [value, func_name]) - return "n.a." diff --git a/addons/gdUnit4/src/fuzzers/FloatFuzzer.gd b/addons/gdUnit4/src/fuzzers/FloatFuzzer.gd deleted file mode 100644 index 6a87c37..0000000 --- a/addons/gdUnit4/src/fuzzers/FloatFuzzer.gd +++ /dev/null @@ -1,13 +0,0 @@ -class_name FloatFuzzer -extends Fuzzer - -var _from: float = 0 -var _to: float = 0 - -func _init(from: float, to: float): - assert(from <= to, "Invalid range!") - _from = from - _to = to - -func next_value() -> Variant: - return randf_range(_from, _to) diff --git a/addons/gdUnit4/src/fuzzers/Fuzzer.gd b/addons/gdUnit4/src/fuzzers/Fuzzer.gd deleted file mode 100644 index 7cd6a58..0000000 --- a/addons/gdUnit4/src/fuzzers/Fuzzer.gd +++ /dev/null @@ -1,39 +0,0 @@ -# Base interface for fuzz testing -# https://en.wikipedia.org/wiki/Fuzzing -class_name Fuzzer -extends RefCounted -# To run a test with a specific fuzzer you have to add defailt argument checked your test case -# all arguments are optional [] -# syntax: -# func test_foo([fuzzer = ], [fuzzer_iterations=], [fuzzer_seed=]) -# example: -# # runs the test 'test_foo' 10 times with a random int value generated by the IntFuzzer -# func test_foo(fuzzer = Fuzzers.randomInt(), fuzzer_iterations=10) -# -# # runs the test 'test_foo2' 1000 times as default with a random seed='101010101' -# func test_foo2(fuzzer = Fuzzers.randomInt(), fuzzer_seed=101010101) - -const ITERATION_DEFAULT_COUNT = 1000 -const ARGUMENT_FUZZER_INSTANCE := "fuzzer" -const ARGUMENT_ITERATIONS := "fuzzer_iterations" -const ARGUMENT_SEED := "fuzzer_seed" - -var _iteration_index :int = 0 -var _iteration_limit :int = ITERATION_DEFAULT_COUNT - - -# generates the next fuzz value -# needs to be implement -func next_value() -> Variant: - push_error("Invalid vall. Fuzzer not implemented 'next_value()'") - return null - - -# returns the current iteration index -func iteration_index() -> int: - return _iteration_index - - -# returns the amount of iterations where the fuzzer will be run -func iteration_limit() -> int: - return _iteration_limit diff --git a/addons/gdUnit4/src/fuzzers/IntFuzzer.gd b/addons/gdUnit4/src/fuzzers/IntFuzzer.gd deleted file mode 100644 index 0235ee2..0000000 --- a/addons/gdUnit4/src/fuzzers/IntFuzzer.gd +++ /dev/null @@ -1,32 +0,0 @@ -class_name IntFuzzer -extends Fuzzer - -enum { - NORMAL, - EVEN, - ODD -} - -var _from :int = 0 -var _to : int = 0 -var _mode : int = NORMAL - - -func _init(from: int, to: int, mode :int = NORMAL): - assert(from <= to, "Invalid range!") - _from = from - _to = to - _mode = mode - - -func next_value() -> Variant: - var value := randi_range(_from, _to) - match _mode: - NORMAL: - return value - EVEN: - return int((value / 2.0) * 2) - ODD: - return int((value / 2.0) * 2 + 1) - _: - return value diff --git a/addons/gdUnit4/src/fuzzers/StringFuzzer.gd b/addons/gdUnit4/src/fuzzers/StringFuzzer.gd deleted file mode 100644 index 40537e7..0000000 --- a/addons/gdUnit4/src/fuzzers/StringFuzzer.gd +++ /dev/null @@ -1,64 +0,0 @@ -class_name StringFuzzer -extends Fuzzer - - -const DEFAULT_CHARSET = "a-zA-Z0-9+-_" - -var _min_length :int -var _max_length :int -var _charset :PackedByteArray - - -func _init(min_length :int,max_length :int,pattern :String = DEFAULT_CHARSET): - 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 - while index < pattern.length(): - var char_current := pattern.unicode_at(index) - # - range token at first or last pos? - if char_current == 45 and (index == 0 or index == pattern.length()-1): - charset.append(char_current) - index += 1 - continue - index += 1 - # range starts - if char_current == 45 and char_before != -1: - var char_next := pattern.unicode_at(index) - var characters := build_chars(char_before, char_next) - for character in characters: - charset.append(character) - char_before = -1 - index += 1 - continue - char_before = char_current - charset.append(char_current) - return PackedByteArray(charset) - - -static func build_chars(from :int, to :int) -> Array: - var characters := Array() - for character in range(from+1, to+1): - characters.append(character) - return characters - - -func next_value() -> Variant: - 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() diff --git a/addons/gdUnit4/src/fuzzers/Vector2Fuzzer.gd b/addons/gdUnit4/src/fuzzers/Vector2Fuzzer.gd deleted file mode 100644 index 7dcbbe7..0000000 --- a/addons/gdUnit4/src/fuzzers/Vector2Fuzzer.gd +++ /dev/null @@ -1,18 +0,0 @@ -class_name Vector2Fuzzer -extends Fuzzer - - -var _from :Vector2 -var _to : Vector2 - - -func _init(from: Vector2,to: Vector2): - 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) - return Vector2(x, y) diff --git a/addons/gdUnit4/src/fuzzers/Vector3Fuzzer.gd b/addons/gdUnit4/src/fuzzers/Vector3Fuzzer.gd deleted file mode 100644 index ca3e718..0000000 --- a/addons/gdUnit4/src/fuzzers/Vector3Fuzzer.gd +++ /dev/null @@ -1,19 +0,0 @@ -class_name Vector3Fuzzer -extends Fuzzer - - -var _from :Vector3 -var _to : Vector3 - - -func _init(from: Vector3,to: Vector3): - 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) - return Vector3(x, y, z) diff --git a/addons/gdUnit4/src/matchers/AnyArgumentMatcher.gd b/addons/gdUnit4/src/matchers/AnyArgumentMatcher.gd deleted file mode 100644 index 0c0af7f..0000000 --- a/addons/gdUnit4/src/matchers/AnyArgumentMatcher.gd +++ /dev/null @@ -1,11 +0,0 @@ -class_name AnyArgumentMatcher -extends GdUnitArgumentMatcher - - -@warning_ignore("unused_parameter") -func is_match(value) -> 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 deleted file mode 100644 index 04faae9..0000000 --- a/addons/gdUnit4/src/matchers/AnyBuildInTypeArgumentMatcher.gd +++ /dev/null @@ -1,50 +0,0 @@ -class_name AnyBuildInTypeArgumentMatcher -extends GdUnitArgumentMatcher - -var _type : PackedInt32Array = [] - - -func _init(type :PackedInt32Array): - _type = type - - -func is_match(value) -> 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 deleted file mode 100644 index 2cf0790..0000000 --- a/addons/gdUnit4/src/matchers/AnyClazzArgumentMatcher.gd +++ /dev/null @@ -1,30 +0,0 @@ -class_name AnyClazzArgumentMatcher -extends GdUnitArgumentMatcher - -var _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 deleted file mode 100644 index d2a0cef..0000000 --- a/addons/gdUnit4/src/matchers/ChainedArgumentMatcher.gd +++ /dev/null @@ -1,22 +0,0 @@ -class_name ChainedArgumentMatcher -extends GdUnitArgumentMatcher - -var _matchers :Array - - -func _init(matchers :Array): - _matchers = matchers - - -func is_match(arguments :Variant) -> bool: - var arg_array := arguments as Array - if arg_array.size() != _matchers.size(): - return false - - 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 deleted file mode 100644 index 2adc75c..0000000 --- a/addons/gdUnit4/src/matchers/EqualsArgumentMatcher.gd +++ /dev/null @@ -1,22 +0,0 @@ -class_name EqualsArgumentMatcher -extends GdUnitArgumentMatcher - -var _current -var _auto_deep_check_mode - - -func _init(current, auto_deep_check_mode := false): - _current = current - _auto_deep_check_mode = auto_deep_check_mode - - -func is_match(value) -> bool: - var case_sensitive_check := true - return GdObjects.equals(_current, value, case_sensitive_check, compare_mode(value)) - - -func compare_mode(value) -> 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 - return GdObjects.COMPARE_MODE.OBJECT_REFERENCE diff --git a/addons/gdUnit4/src/matchers/GdUnitArgumentMatcher.gd b/addons/gdUnit4/src/matchers/GdUnitArgumentMatcher.gd deleted file mode 100644 index aa43b80..0000000 --- a/addons/gdUnit4/src/matchers/GdUnitArgumentMatcher.gd +++ /dev/null @@ -1,8 +0,0 @@ -## The base class of all argument matchers -class_name GdUnitArgumentMatcher -extends RefCounted - - -@warning_ignore("unused_parameter") -func is_match(value :Variant) -> bool: - return true diff --git a/addons/gdUnit4/src/matchers/GdUnitArgumentMatchers.gd b/addons/gdUnit4/src/matchers/GdUnitArgumentMatchers.gd deleted file mode 100644 index ddd58f1..0000000 --- a/addons/gdUnit4/src/matchers/GdUnitArgumentMatchers.gd +++ /dev/null @@ -1,32 +0,0 @@ -class_name GdUnitArgumentMatchers -extends RefCounted - -const TYPE_ANY = TYPE_MAX + 100 - - -static func to_matcher(arguments :Array[Variant], auto_deep_check_mode := false) -> ChainedArgumentMatcher: - var matchers :Array[Variant] = [] - for arg in arguments: - # argument is already a matcher - if arg is GdUnitArgumentMatcher: - matchers.append(arg) - else: - # pass argument into equals matcher - matchers.append(EqualsArgumentMatcher.new(arg, auto_deep_check_mode)) - return ChainedArgumentMatcher.new(matchers) - - -static func any() -> GdUnitArgumentMatcher: - return AnyArgumentMatcher.new() - - -static func by_type(type :int) -> GdUnitArgumentMatcher: - return AnyBuildInTypeArgumentMatcher.new([type]) - - -static func by_types(types :PackedInt32Array) -> GdUnitArgumentMatcher: - return AnyBuildInTypeArgumentMatcher.new(types) - - -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 deleted file mode 100644 index bb50e5e..0000000 --- a/addons/gdUnit4/src/mocking/GdUnitMock.gd +++ /dev/null @@ -1,40 +0,0 @@ -class_name GdUnitMock -extends RefCounted - -## do call the real implementation -const CALL_REAL_FUNC = "CALL_REAL_FUNC" -## 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 :Variant - - -func _init(value :Variant) -> void: - _value = value - - -## Selects the mock to work on, used in combination with [method GdUnitTestSuite.do_return][br] -## Example: -## [codeblock] -## do_return(false).on(myMock).is_selected() -## [/codeblock] -func on(obj :Object) -> Object: - if not GdUnitMock._is_mock_or_spy( obj, "__do_return"): - return obj - return obj.__do_return(_value) - - -## [color=yellow]`checked` is obsolete, use `on` instead [/color] -func checked(obj :Object) -> Object: - push_warning("Using a deprecated function 'checked' use `on` instead") - return on(obj) - - -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): - push_error("Error: You try to use a non mock or spy!") - return false - return true diff --git a/addons/gdUnit4/src/mocking/GdUnitMockBuilder.gd b/addons/gdUnit4/src/mocking/GdUnitMockBuilder.gd deleted file mode 100644 index 3270e38..0000000 --- a/addons/gdUnit4/src/mocking/GdUnitMockBuilder.gd +++ /dev/null @@ -1,167 +0,0 @@ -class_name GdUnitMockBuilder -extends GdUnitClassDoubler - -const GdUnitTools := preload("res://addons/gdUnit4/src/core/GdUnitTools.gd") -const MOCK_TEMPLATE :GDScript = preload("res://addons/gdUnit4/src/mocking/GdUnitMockImpl.gd") - - -static func is_push_errors() -> bool: - return GdUnitSettings.is_report_push_errors() - - -static func build(clazz, mock_mode :String, debug_write := false) -> Object: - var push_errors := is_push_errors() - if not is_mockable(clazz, push_errors): - return null - # mocking a scene? - if GdObjects.is_scene(clazz): - return mock_on_scene(clazz as PackedScene, debug_write) - elif typeof(clazz) == TYPE_STRING and clazz.ends_with(".tscn"): - return mock_on_scene(load(clazz), debug_write) - # mocking a script - var instance := create_instance(clazz) - var mock := mock_on_script(instance, clazz, [ "get_script"], debug_write) - if not instance is RefCounted: - instance.free() - if mock == null: - return null - var mock_instance = 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: - 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")): - var script :GDScript = null - if clazz is GDScript: - script = clazz - else: - script = load(clazz) - - 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) - push_error("Can't create a mock validation instance from class: `%s`" % clazz) - return null - - -static func mock_on_scene(scene :PackedScene, debug_write :bool) -> Object: - var push_errors := is_push_errors() - if not scene.can_instantiate(): - if push_errors: - push_error("Can't instanciate scene '%s'" % scene.resource_path) - return null - 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) - if mock == null: - return null - scene_instance.set_script(mock) - scene_instance.__set_singleton() - scene_instance.__set_mode(GdUnitMock.CALL_REAL_FUNC) - return register_auto_free(scene_instance) - - -static func get_class_info(clazz :Variant) -> Dictionary: - var clazz_name :String = GdObjects.extract_class_name(clazz).value() - var clazz_path := GdObjects.extract_class_path(clazz) - return { - "class_name" : clazz_name, - "class_path" : clazz_path - } - - -static func mock_on_script(instance :Object, clazz :Variant, function_excludes :PackedStringArray, debug_write :bool) -> GDScript: - var push_errors := is_push_errors() - 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 = 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) - if error != OK: - push_error("Critical!!!, MockBuilder error, please contact the developer.") - return null - return mock - - -static func is_mockable(clazz :Variant, push_errors :bool=false) -> bool: - var clazz_type := typeof(clazz) - if clazz_type != TYPE_OBJECT and clazz_type != TYPE_STRING: - push_error("Invalid clazz type is used") - return false - # is PackedScene - if GdObjects.is_scene(clazz): - return true - if GdObjects.is_native_class(clazz): - return true - # verify class type - if GdObjects.is_object(clazz): - if GdObjects.is_instance(clazz): - 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) - return false - return true - # verify by class name checked registered classes - var clazz_name := clazz as String - if ClassDB.class_exists(clazz_name): - if Engine.has_singleton(clazz_name): - if push_errors: - push_error("Mocking a singelton class '%s' is not allowed! Read 'Mocker' documentation for details" % clazz_name) - return false - if not ClassDB.can_instantiate(clazz_name): - if push_errors: - push_error("Mocking class '%s' is not allowed it cannot be instantiated!" % clazz_name) - return false - # exclude classes where name starts with a underscore - if clazz_name.find("_") == 0: - if push_errors: - push_error("Can't create a mockable instance for protected class '%s'" % clazz_name) - return false - return true - # at least try to load as a script - var clazz_path := clazz_name - if not FileAccess.file_exists(clazz_path): - if push_errors: - 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) - if resource == null: - if push_errors: - push_error("'%s' cannot be mocked the script cannot be loaded." % clazz_name) - return false - # finally check is extending from script - return GdObjects.is_script(resource) or GdObjects.is_scene(resource) - - -static func register_auto_free(obj :Variant) -> Variant: - return GdUnitThreadManager.get_current_context().get_execution_context().register_auto_free(obj) diff --git a/addons/gdUnit4/src/mocking/GdUnitMockFunctionDoubler.gd b/addons/gdUnit4/src/mocking/GdUnitMockFunctionDoubler.gd deleted file mode 100644 index 60249bf..0000000 --- a/addons/gdUnit4/src/mocking/GdUnitMockFunctionDoubler.gd +++ /dev/null @@ -1,85 +0,0 @@ -class_name GdUnitMockFunctionDoubler -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} - if $(instance)__is_verify_interactions(): - $(instance)__verify_interactions(args) - 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}) - -""" - - -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.\") - return - if $(instance)__is_verify_interactions(): - $(instance)__verify_interactions(args) - return - else: - $(instance)__save_function_interaction(args) - - if $(instance)__do_call_real_func("$(func_name)"): - $(await)super($(arguments)) - -""" - - -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.\") - $(instance)__save_function_return_value(args) - return ${default_return_value} - if $(instance)__is_verify_interactions(): - $(instance)__verify_interactions(args) - 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)) - 1: return $(await)super($(arguments), varargs[0]) - 2: return $(await)super($(arguments), varargs[0], varargs[1]) - 3: return $(await)super($(arguments), varargs[0], varargs[1], varargs[2]) - 4: return $(await)super($(arguments), varargs[0], varargs[1], varargs[2], varargs[3]) - 5: return $(await)super($(arguments), varargs[0], varargs[1], varargs[2], varargs[3], varargs[4]) - 6: return $(await)super($(arguments), varargs[0], varargs[1], varargs[2], varargs[3], varargs[4], varargs[5]) - 7: return $(await)super($(arguments), varargs[0], varargs[1], varargs[2], varargs[3], varargs[4], varargs[5], varargs[6]) - 8: return $(await)super($(arguments), varargs[0], varargs[1], varargs[2], varargs[3], varargs[4], varargs[5], varargs[6], varargs[7]) - 9: return $(await)super($(arguments), varargs[0], varargs[1], varargs[2], varargs[3], varargs[4], varargs[5], varargs[6], varargs[7], varargs[8]) - 10: return $(await)super($(arguments), varargs[0], varargs[1], varargs[2], varargs[3], varargs[4], varargs[5], varargs[6], varargs[7], varargs[8], varargs[9]) - return __get_mocked_return_value_or_default(args, ${default_return_value}) - -""" - - -func _init(push_errors :bool = false): - super._init(push_errors) - - -func get_template(return_type :Variant, is_vararg :bool) -> String: - if is_vararg: - return TEMPLATE_FUNC_VARARG_RETURN_VALUE - if return_type is StringName: - return TEMPLATE_FUNC_WITH_RETURN_VALUE - return TEMPLATE_FUNC_WITH_RETURN_VOID if (return_type == TYPE_NIL or return_type == GdObjects.TYPE_VOID) else TEMPLATE_FUNC_WITH_RETURN_VALUE diff --git a/addons/gdUnit4/src/mocking/GdUnitMockImpl.gd b/addons/gdUnit4/src/mocking/GdUnitMockImpl.gd deleted file mode 100644 index cbeb782..0000000 --- a/addons/gdUnit4/src/mocking/GdUnitMockImpl.gd +++ /dev/null @@ -1,140 +0,0 @@ - -################################################################################ -# internal mocking stuff -################################################################################ -const __INSTANCE_ID = "${instance_id}" -const __SOURCE_CLASS = "${source_class}" - -var __mock_working_mode := GdUnitMock.RETURN_DEFAULTS -var __excluded_methods :PackedStringArray = [] -var __do_return_value :Variant = null -var __prepare_return_value := false - -#{ = { -# = -# } -#} -var __mocked_return_values := Dictionary() - - -static func __instance() -> Object: - return Engine.get_meta(__INSTANCE_ID) - - -func _notification(what :int) -> void: - if what == NOTIFICATION_PREDELETE: - if Engine.has_meta(__INSTANCE_ID): - Engine.remove_meta(__INSTANCE_ID) - - -func __instance_id() -> String: - return __INSTANCE_ID - - -func __set_singleton() -> void: - # store self need to mock static functions - Engine.set_meta(__INSTANCE_ID, self) - - -func __release_double() -> void: - # we need to release the self reference manually to prevent orphan nodes - Engine.remove_meta(__INSTANCE_ID) - - -func __is_prepare_return_value() -> bool: - return __prepare_return_value - - -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: - # 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 __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 __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 :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: - break - if __is_matching: - break - return __is_matching - - -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_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 := __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 __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(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 deleted file mode 100644 index f574147..0000000 --- a/addons/gdUnit4/src/monitor/ErrorLogEntry.gd +++ /dev/null @@ -1,59 +0,0 @@ -extends RefCounted -class_name ErrorLogEntry - - -enum TYPE { - SCRIPT_ERROR, - PUSH_ERROR, - PUSH_WARNING -} - - -const GdUnitTools := preload("res://addons/gdUnit4/src/core/GdUnitTools.gd") - -const PATTERN_SCRIPT_ERROR := "USER SCRIPT ERROR:" -const PATTERN_PUSH_ERROR := "USER ERROR:" -const PATTERN_PUSH_WARNING := "USER WARNING:" - - -var _type :TYPE -var _line :int -var _message :String -var _details :String - - -func _init(type :TYPE, line :int, message :String, details :String): - _type = type - _line = line - _message = message - _details = details - - -static func extract_push_warning(records :PackedStringArray, index :int) -> ErrorLogEntry: - return _extract(records, index, TYPE.PUSH_WARNING, PATTERN_PUSH_WARNING) - - -static func extract_push_error(records :PackedStringArray, index :int) -> ErrorLogEntry: - return _extract(records, index, TYPE.PUSH_ERROR, PATTERN_PUSH_ERROR) - - -static func extract_error(records :PackedStringArray, index :int) -> ErrorLogEntry: - return _extract(records, index, TYPE.SCRIPT_ERROR, PATTERN_SCRIPT_ERROR) - - -static func _extract(records :PackedStringArray, index :int, type :TYPE, pattern :String) -> ErrorLogEntry: - var message := records[index] - if message.contains(pattern): - var error := message.replace(pattern, "").strip_edges() - var details := records[index+1].strip_edges() - var line := _parse_error_line_number(details) - return ErrorLogEntry.new(type, line, error, details) - return null - - -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 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 deleted file mode 100644 index 8ec2e63..0000000 --- a/addons/gdUnit4/src/monitor/GdUnitMonitor.gd +++ /dev/null @@ -1,24 +0,0 @@ -# GdUnit Monitoring Base Class -class_name GdUnitMonitor -extends RefCounted - -var _id :String - -# constructs new Monitor with given id -func _init(p_id :String): - _id = p_id - - -# Returns the id of the monitor to uniqe identify -func id() -> String: - return _id - - -# starts monitoring -func start(): - pass - - -# stops monitoring -func stop(): - pass diff --git a/addons/gdUnit4/src/monitor/GdUnitOrphanNodesMonitor.gd b/addons/gdUnit4/src/monitor/GdUnitOrphanNodesMonitor.gd deleted file mode 100644 index 9bf76e4..0000000 --- a/addons/gdUnit4/src/monitor/GdUnitOrphanNodesMonitor.gd +++ /dev/null @@ -1,27 +0,0 @@ -class_name GdUnitOrphanNodesMonitor -extends GdUnitMonitor - -var _initial_count := 0 -var _orphan_count := 0 -var _orphan_detection_enabled :bool - - -func _init(name :String = ""): - super("OrphanNodesMonitor:" + name) - _orphan_detection_enabled = GdUnitSettings.is_verbose_orphans() - - -func start(): - _initial_count = _orphans() - - -func stop(): - _orphan_count = max(0, _orphans() - _initial_count) - - -func _orphans() -> int: - return Performance.get_monitor(Performance.OBJECT_ORPHAN_NODE_COUNT) as int - - -func orphan_nodes() -> int: - return _orphan_count if _orphan_detection_enabled else 0 diff --git a/addons/gdUnit4/src/monitor/GodotGdErrorMonitor.gd b/addons/gdUnit4/src/monitor/GodotGdErrorMonitor.gd deleted file mode 100644 index a2f8f14..0000000 --- a/addons/gdUnit4/src/monitor/GodotGdErrorMonitor.gd +++ /dev/null @@ -1,84 +0,0 @@ -class_name GodotGdErrorMonitor -extends GdUnitMonitor - -var _godot_log_file :String -var _eof :int -var _report_enabled := false -var _entries: Array[ErrorLogEntry] = [] - - -func _init(): - super("GodotGdErrorMonitor") - _godot_log_file = GdUnitSettings.get_log_path() - _report_enabled = _is_reporting_enabled() - - -func start(): - var file = FileAccess.open(_godot_log_file, FileAccess.READ) - if file: - file.seek_end(0) - _eof = file.get_length() - - -func stop(): - pass - - -func to_reports() -> Array[GdUnitReport]: - var reports_ :Array[GdUnitReport] = [] - if _report_enabled: - reports_.assign(_entries.map(_to_report)) - return reports_ - - -static func _to_report(errorLog :ErrorLogEntry) -> GdUnitReport: - var failure := "%s\n\t%s\n%s %s" % [ - GdAssertMessages._error("Godot Runtime Error !"), - GdAssertMessages._colored_value(errorLog._details), - GdAssertMessages._error("Error:"), - GdAssertMessages._colored_value(errorLog._message)] - return GdUnitReport.new().create(GdUnitReport.ABORT, errorLog._line, failure) - - -func scan(force_collect_reports := false) -> Array[ErrorLogEntry]: - await Engine.get_main_loop().process_frame - 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(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 force_collect_reports: - log_entries.append(ErrorLogEntry.extract_push_warning(records, index)) - if is_report_errors: - log_entries.append(ErrorLogEntry.extract_push_error(records, index)) - if is_report_script_errors: - log_entries.append(ErrorLogEntry.extract_error(records, index)) - return log_entries.filter(func(value): return value != null ) - - -func _is_reporting_enabled() -> bool: - return _is_report_script_errors() or _is_report_push_errors() - - -func _is_report_push_errors() -> bool: - return GdUnitSettings.is_report_push_errors() - - -func _is_report_script_errors() -> bool: - return GdUnitSettings.is_report_script_errors() diff --git a/addons/gdUnit4/src/mono/GdUnit4CSharpApi.cs b/addons/gdUnit4/src/mono/GdUnit4CSharpApi.cs deleted file mode 100644 index ec07ff4..0000000 --- a/addons/gdUnit4/src/mono/GdUnit4CSharpApi.cs +++ /dev/null @@ -1,50 +0,0 @@ -using System; -using System.Reflection; -using System.Linq; - -using Godot; -using Godot.Collections; -using GdUnit4; - - -// GdUnit4 GDScript - C# API wrapper -public partial class GdUnit4CSharpApi : RefCounted -{ - 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/GdUnit4CSharpApiLoader.gd b/addons/gdUnit4/src/mono/GdUnit4CSharpApiLoader.gd deleted file mode 100644 index 29c4b41..0000000 --- a/addons/gdUnit4/src/mono/GdUnit4CSharpApiLoader.gd +++ /dev/null @@ -1,64 +0,0 @@ -extends RefCounted -class_name GdUnit4CSharpApiLoader - - -static func instance() -> Object: - return GdUnitSingleton.instance("GdUnit4CSharpApi", func() -> Object: - if not GdUnit4CSharpApiLoader.is_mono_supported(): - return null - return load("res://addons/gdUnit4/src/mono/GdUnit4CSharpApi.cs") - ) - - -static func is_engine_version_supported(engine_version :int = Engine.get_version_info().hex) -> bool: - return engine_version >= 0x40200 - - -# test is Godot mono running -static func is_mono_supported() -> bool: - return ClassDB.class_exists("CSharpScript") and is_engine_version_supported() - - -static func version() -> String: - 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 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")) - return GdUnitResult.success(result) - - -static func is_test_suite(resource_path :String) -> bool: - 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.") - return false - return instance().IsTestSuite(resource_path) - - -static func parse_test_suite(source_path :String) -> Node: - 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 - return instance().ParseTestSuite(source_path) - - -static func create_executor(listener :Node) -> RefCounted: - 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 GdUnit4CSharpApiLoader.is_mono_supported() diff --git a/addons/gdUnit4/src/network/GdUnitServer.gd b/addons/gdUnit4/src/network/GdUnitServer.gd deleted file mode 100644 index 1a0ae99..0000000 --- a/addons/gdUnit4/src/network/GdUnitServer.gd +++ /dev/null @@ -1,41 +0,0 @@ -@tool -extends Node - -@onready var _server :GdUnitTcpServer = $TcpServer - - -func _ready(): - var result := _server.start() - if result.is_error(): - push_error(result.error_message()) - return - var server_port :int = result.value() - Engine.set_meta("gdunit_server_port", server_port) - _server.client_connected.connect(_on_client_connected) - _server.client_disconnected.connect(_on_client_disconnected) - _server.rpc_data.connect(_receive_rpc_data) - GdUnitCommandHandler.instance().gdunit_runner_stop.connect(_on_gdunit_runner_stop) - - -func _on_client_connected(client_id :int) -> void: - GdUnitSignals.instance().gdunit_client_connected.emit(client_id) - - -func _on_client_disconnected(client_id :int) -> void: - GdUnitSignals.instance().gdunit_client_disconnected.emit(client_id) - - -func _on_gdunit_runner_stop(client_id :int): - if _server: - _server.disconnect_client(client_id) - - -func _receive_rpc_data(p_rpc :RPC) -> void: - if p_rpc is RPCMessage: - GdUnitSignals.instance().gdunit_message.emit(p_rpc.message()) - return - if p_rpc is RPCGdUnitEvent: - GdUnitSignals.instance().gdunit_event.emit(p_rpc.event()) - return - if p_rpc is RPCGdUnitTestSuite: - GdUnitSignals.instance().gdunit_add_test_suite.emit(p_rpc.dto()) diff --git a/addons/gdUnit4/src/network/GdUnitServer.tscn b/addons/gdUnit4/src/network/GdUnitServer.tscn deleted file mode 100644 index 4c7645c..0000000 --- a/addons/gdUnit4/src/network/GdUnitServer.tscn +++ /dev/null @@ -1,10 +0,0 @@ -[gd_scene load_steps=3 format=2] - -[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] - -[node name="Control" type="Node"] -script = ExtResource( 1 ) - -[node name="TcpServer" type="Node" parent="."] -script = ExtResource( 2 ) diff --git a/addons/gdUnit4/src/network/GdUnitServerConstants.gd b/addons/gdUnit4/src/network/GdUnitServerConstants.gd deleted file mode 100644 index d31eee7..0000000 --- a/addons/gdUnit4/src/network/GdUnitServerConstants.gd +++ /dev/null @@ -1,6 +0,0 @@ -class_name GdUnitServerConstants -extends RefCounted - -const DEFAULT_SERVER_START_RETRY_TIMES :int = 5 -const GD_TEST_SERVER_PORT :int = 31002 -const JSON_RESPONSE_DELIMITER :String = "<>" diff --git a/addons/gdUnit4/src/network/GdUnitTask.gd b/addons/gdUnit4/src/network/GdUnitTask.gd deleted file mode 100644 index ee48bee..0000000 --- a/addons/gdUnit4/src/network/GdUnitTask.gd +++ /dev/null @@ -1,25 +0,0 @@ -class_name GdUnitTask -extends RefCounted - -const TASK_NAME = "task_name" -const TASK_ARGS = "task_args" - -var _task_name :String -var _fref :Callable - - -func _init(task_name :String,instance :Object,func_name :String): - _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() - return _fref.callv(args) diff --git a/addons/gdUnit4/src/network/GdUnitTcpClient.gd b/addons/gdUnit4/src/network/GdUnitTcpClient.gd deleted file mode 100644 index 1a9b66e..0000000 --- a/addons/gdUnit4/src/network/GdUnitTcpClient.gd +++ /dev/null @@ -1,137 +0,0 @@ -class_name GdUnitTcpClient -extends Node - -signal connection_succeeded(message) -signal connection_failed(message) - -var _timer :Timer - -var _host :String -var _port :int -var _client_id :int -var _connected :bool -var _stream :StreamPeerTCP - - -func _ready(): - _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: - console("Client: disconnect from server") - if _stream != null: - rpc_send(RPCClientDisconnect.new().with_id(_client_id)) - if _stream != null: - _stream.disconnect_from_host() - _connected = false - - -func start(host :String, port :int) -> GdUnitResult: - _host = host - _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) - #prints("connect_to_host", host, port, err) - if err != OK: - return GdUnitResult.error("GdUnit3: Can't establish client, error code: %s" % err) - return GdUnitResult.success("GdUnit3: Client connected checked port %d" % port) - - -func _process(_delta): - match _stream.get_status(): - StreamPeerTCP.STATUS_NONE: - return - - StreamPeerTCP.STATUS_CONNECTING: - set_process(false) - # wait until client is connected to server - for retry in 10: - _stream.poll() - console("wait to connect ..") - if _stream.get_status() == StreamPeerTCP.STATUS_CONNECTING: - await get_tree().create_timer(0.500).timeout - if _stream.get_status() == StreamPeerTCP.STATUS_CONNECTED: - set_process(true) - return - set_process(true) - _stream.disconnect_from_host() - console("connection failed") - emit_signal("connection_failed", "Connect to TCP Server %s:%d faild!" % [_host, _port]) - - StreamPeerTCP.STATUS_CONNECTED: - if not _connected: - var rpc_ = null - set_process(false) - while rpc_ == null: - await get_tree().create_timer(0.500).timeout - rpc_ = rpc_receive() - 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]) - _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]) - return - - -func is_client_connected() -> bool: - return _connected - - -func process_rpc() -> void: - if _stream.get_available_bytes() > 0: - var rpc_ = rpc_receive() - if rpc_ is RPCClientDisconnect: - stop() - - -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()) - - -func rpc_receive() -> RPC: - if _stream != null: - while _stream.get_available_bytes() > 0: - var available_bytes := _stream.get_available_bytes() - var data := _stream.get_data(available_bytes) - var received_data := data[1] as PackedByteArray - # data send by Godot has this magic header of 12 bytes - 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() - if decoded == "": - #prints("decoded is empty", available_bytes, received_data.get_string_from_ascii()) - return null - return RPC.deserialize(decoded) - return null - - -func console(message :String) -> void: - prints("TCP Client:", message) - pass - - -func _on_connection_failed(message :String): - console("connection faild: " + message) - - -func _on_connection_succeeded(message :String): - console("connected: " + message) diff --git a/addons/gdUnit4/src/network/GdUnitTcpServer.gd b/addons/gdUnit4/src/network/GdUnitTcpServer.gd deleted file mode 100644 index 8cf28b5..0000000 --- a/addons/gdUnit4/src/network/GdUnitTcpServer.gd +++ /dev/null @@ -1,162 +0,0 @@ -@tool -class_name GdUnitTcpServer -extends Node - -signal client_connected(client_id) -signal client_disconnected(client_id) -signal rpc_data(rpc_data) - -var _server :TCPServer - - -class TcpConnection extends Node: - var _id :int - var _stream - var _readBuffer :String = "" - - - func _init(p_server): - #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(): - 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 = "" - - - func id() -> int: - return _id - - - func server() -> GdUnitTcpServer: - return get_parent() - - - func rpc_send(p_rpc :RPC) -> void: - _stream.put_var(p_rpc.serialize(), true) - - - func _process(_delta): - if _stream.get_status() != StreamPeerTCP.STATUS_CONNECTED: - return - receive_packages() - - - func receive_packages() -> void: - var available_bytes = _stream.get_available_bytes() - if available_bytes > 0: - var partial_data = _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]) - return - else: - var received_data := partial_data[1] as PackedByteArray - for package in _read_next_data_packages(received_data): - 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() - 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 - if not _readBuffer.ends_with(GdUnitServerConstants.JSON_RESPONSE_DELIMITER): - _readBuffer = json_array[-1] - json_array.remove_at(json_array.size()-1) - else: - # Reset the buffer if a completely terminated packet was received - _readBuffer = "" - # remove empty packages - for index in json_array.size(): - 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(): - _server = TCPServer.new() - client_connected.connect(Callable(self, "_on_client_connected")) - client_disconnected.connect(Callable(self, "_on_client_disconnected")) - - -func _notification(what): - if what == NOTIFICATION_PREDELETE: - stop() - - -func start() -> GdUnitResult: - var server_port := GdUnitServerConstants.GD_TEST_SERVER_PORT - var err := OK - for retry in GdUnitServerConstants.DEFAULT_SERVER_START_RETRY_TIMES: - err = _server.listen(server_port, "127.0.0.1") - if err != OK: - prints("GdUnit4: Can't establish server checked port: %d, Error: %s" % [server_port, error_string(err)]) - server_port += 1 - prints("GdUnit4: Retry (%d) ..." % retry) - else: - break - if err != OK: - if err == ERR_ALREADY_IN_USE: - return GdUnitResult.error("GdUnit3: Can't establish server, the server is already in use. Error: %s, " % error_string(err)) - return GdUnitResult.error("GdUnit3: Can't establish server. Error: %s." % error_string(err)) - prints("GdUnit4: Test server successfully started checked port: %d" % server_port) - return GdUnitResult.success(server_port) - - -func stop() -> void: - if _server: - _server.stop() - for connection in get_children(): - if connection is TcpConnection: - connection.close() - remove_child(connection) - - -func disconnect_client(client_id :int) -> void: - for connection in get_children(): - if connection is TcpConnection and connection.id() == client_id: - connection.close() - - -func _process(_delta): - if not _server.is_listening(): - return - # 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): - console("Client connected %d" % client_id) - - -func _on_client_disconnected(client_id :int): - console("Client disconnected %d" % client_id) - for connection in get_children(): - if connection is TcpConnection and connection.id() == client_id: - remove_child(connection) - - - -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 deleted file mode 100644 index 190b05a..0000000 --- a/addons/gdUnit4/src/network/rpc/RPC.gd +++ /dev/null @@ -1,24 +0,0 @@ -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) -> 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 - 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 - return dict_to_inst(result) - -# this results in orpan node, for more details https://github.com/godotengine/godot/issues/50069 -#func deserialize2(data :Dictionary) -> RPC: -# return dict_to_inst(data) as RPC diff --git a/addons/gdUnit4/src/network/rpc/RPCClientConnect.gd b/addons/gdUnit4/src/network/rpc/RPCClientConnect.gd deleted file mode 100644 index 115fea2..0000000 --- a/addons/gdUnit4/src/network/rpc/RPCClientConnect.gd +++ /dev/null @@ -1,13 +0,0 @@ -class_name RPCClientConnect -extends RPC - -var _client_id :int - - -func with_id(p_client_id :int) -> RPCClientConnect: - _client_id = p_client_id - return self - - -func client_id() -> int: - return _client_id diff --git a/addons/gdUnit4/src/network/rpc/RPCClientDisconnect.gd b/addons/gdUnit4/src/network/rpc/RPCClientDisconnect.gd deleted file mode 100644 index 52ef259..0000000 --- a/addons/gdUnit4/src/network/rpc/RPCClientDisconnect.gd +++ /dev/null @@ -1,13 +0,0 @@ -class_name RPCClientDisconnect -extends RPC - -var _client_id :int - - -func with_id(p_client_id :int) -> RPCClientDisconnect: - _client_id = p_client_id - return self - - -func client_id() -> int: - return _client_id diff --git a/addons/gdUnit4/src/network/rpc/RPCData.gd b/addons/gdUnit4/src/network/rpc/RPCData.gd deleted file mode 100644 index 7b6bac8..0000000 --- a/addons/gdUnit4/src/network/rpc/RPCData.gd +++ /dev/null @@ -1,13 +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 deleted file mode 100644 index 2cc3e10..0000000 --- a/addons/gdUnit4/src/network/rpc/RPCGdUnitEvent.gd +++ /dev/null @@ -1,18 +0,0 @@ -class_name RPCGdUnitEvent -extends RPC - -var _event :Dictionary - - -static func of(p_event :GdUnitEvent) -> RPCGdUnitEvent: - var rpc = RPCGdUnitEvent.new() - rpc._event = p_event.serialize() - return rpc - - -func event() -> GdUnitEvent: - return GdUnitEvent.new().deserialize(_event) - - -func _to_string(): - return "RPCGdUnitEvent: " + str(_event) diff --git a/addons/gdUnit4/src/network/rpc/RPCGdUnitTestSuite.gd b/addons/gdUnit4/src/network/rpc/RPCGdUnitTestSuite.gd deleted file mode 100644 index 96c4d96..0000000 --- a/addons/gdUnit4/src/network/rpc/RPCGdUnitTestSuite.gd +++ /dev/null @@ -1,18 +0,0 @@ -class_name RPCGdUnitTestSuite -extends RPC - -var _data :Dictionary - - -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() -> String: - return "RPCGdUnitTestSuite: " + str(_data) diff --git a/addons/gdUnit4/src/network/rpc/RPCMessage.gd b/addons/gdUnit4/src/network/rpc/RPCMessage.gd deleted file mode 100644 index 6b47b5a..0000000 --- a/addons/gdUnit4/src/network/rpc/RPCMessage.gd +++ /dev/null @@ -1,18 +0,0 @@ -class_name RPCMessage -extends RPC - -var _message :String - - -static func of(p_message :String) -> RPCMessage: - var rpc = RPCMessage.new() - rpc._message = p_message - return rpc - - -func message() -> String: - return _message - - -func _to_string(): - return "RPCMessage: " + _message diff --git a/addons/gdUnit4/src/network/rpc/dtos/GdUnitResourceDto.gd b/addons/gdUnit4/src/network/rpc/dtos/GdUnitResourceDto.gd deleted file mode 100644 index 9152c8d..0000000 --- a/addons/gdUnit4/src/network/rpc/dtos/GdUnitResourceDto.gd +++ /dev/null @@ -1,26 +0,0 @@ -class_name GdUnitResourceDto -extends Resource - -var _name :String -var _path :String - - -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 deleted file mode 100644 index 26f5dda..0000000 --- a/addons/gdUnit4/src/network/rpc/dtos/GdUnitTestCaseDto.gd +++ /dev/null @@ -1,33 +0,0 @@ -class_name GdUnitTestCaseDto -extends GdUnitResourceDto - -var _line_number :int = -1 -var _test_case_names :PackedStringArray = [] - - -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() - else: - serialized["line_number"] = test_case.get("LineNumber") - if test_case.has_method("test_case_names"): - serialized["test_case_names"] = test_case.test_case_names() - elif test_case.has_method("TestCaseNames"): - serialized["test_case_names"] = test_case.TestCaseNames() - return serialized - - -func deserialize(data :Dictionary) -> GdUnitResourceDto: - super.deserialize(data) - _line_number = data.get("line_number", -1) - _test_case_names = data.get("test_case_names", []) - return self - - -func line_number() -> int: - return _line_number - - -func test_case_names() -> PackedStringArray: - return _test_case_names diff --git a/addons/gdUnit4/src/network/rpc/dtos/GdUnitTestSuiteDto.gd b/addons/gdUnit4/src/network/rpc/dtos/GdUnitTestSuiteDto.gd deleted file mode 100644 index 9ecc9f6..0000000 --- a/addons/gdUnit4/src/network/rpc/dtos/GdUnitTestSuiteDto.gd +++ /dev/null @@ -1,33 +0,0 @@ -class_name GdUnitTestSuiteDto -extends GdUnitResourceDto - -var _test_cases_by_name := Dictionary() - - -func serialize(test_suite :Node) -> Dictionary: - var serialized := super.serialize(test_suite) - var test_cases_ := Array() - serialized["test_cases"] = test_cases_ - for test_case in test_suite.get_children(): - test_cases_.append(GdUnitTestCaseDto.new().serialize(test_case)) - return serialized - - -func deserialize(data :Dictionary) -> GdUnitResourceDto: - super.deserialize(data) - var test_cases_ :Array = data.get("test_cases", []) - for test_case in test_cases_: - add_test_case(GdUnitTestCaseDto.new().deserialize(test_case)) - return self - - -func add_test_case(test_case :GdUnitTestCaseDto) -> void: - _test_cases_by_name[test_case.name()] = test_case - - -func test_case_count() -> int: - return _test_cases_by_name.size() - - -func test_cases() -> Array: - return _test_cases_by_name.values() diff --git a/addons/gdUnit4/src/report/GdUnitByPathReport.gd b/addons/gdUnit4/src/report/GdUnitByPathReport.gd deleted file mode 100644 index 81d8281..0000000 --- a/addons/gdUnit4/src/report/GdUnitByPathReport.gd +++ /dev/null @@ -1,47 +0,0 @@ -class_name GdUnitByPathReport -extends GdUnitReportSummary - - -func _init(path_ :String, reports_ :Array[GdUnitReportSummary]): - _resource_path = path_ - _reports = reports_ - - -static func sort_reports_by_path(reports_ :Array[GdUnitReportSummary]) -> Dictionary: - var by_path := Dictionary() - for report in reports_: - var suite_path :String = report.path() - var suite_report :Array[GdUnitReportSummary] = by_path.get(suite_path, [] as Array[GdUnitReportSummary]) - suite_report.append(report) - by_path[suite_path] = suite_report - return by_path - - -func path() -> String: - return _resource_path.replace("res://", "") - - -func create_record(report_link :String) -> String: - return GdUnitHtmlPatterns.build(GdUnitHtmlPatterns.TABLE_RECORD_PATH, self, report_link) - - -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): - DirAccess.make_dir_recursive_absolute(dir) - FileAccess.open(output_path, FileAccess.WRITE).store_string(path_report) - return output_path - - -func apply_testsuite_reports(report_dir :String, template :String, 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)) - return template.replace(GdUnitHtmlPatterns.TABLE_BY_TESTSUITES, "\n".join(table_records)) diff --git a/addons/gdUnit4/src/report/GdUnitHtmlPatterns.gd b/addons/gdUnit4/src/report/GdUnitHtmlPatterns.gd deleted file mode 100644 index 1d9cf86..0000000 --- a/addons/gdUnit4/src/report/GdUnitHtmlPatterns.gd +++ /dev/null @@ -1,94 +0,0 @@ -class_name GdUnitHtmlPatterns -extends RefCounted - -const TABLE_RECORD_TESTSUITE = """ - - ${testsuite_name} - ${test_count} - ${skipped_count} - ${failure_count} - ${orphan_count} - ${duration} - ${success_percent} - -""" - -const TABLE_RECORD_PATH = """ - - ${path} - ${test_count} - ${skipped_count} - ${failure_count} - ${orphan_count} - ${duration} - ${success_percent} - -""" - - -const TABLE_REPORT_TESTSUITE = """ - - TestSuite hooks - n/a - ${orphan_count} - ${duration} - ${failure-report} - -""" - - -const TABLE_RECORD_TESTCASE = """ - - ${testcase_name} - ${skipped_count} - ${orphan_count} - ${duration} - ${failure-report} - -""" - -const TABLE_BY_PATHS = "${report_table_paths}" -const TABLE_BY_TESTSUITES = "${report_table_testsuites}" -const TABLE_BY_TESTCASES = "${report_table_tests}" - -# the report state success, error, warning -const REPORT_STATE = "${report_state}" -const PATH = "${path}" -const TESTSUITE_COUNT = "${suite_count}" -const TESTCASE_COUNT = "${test_count}" -const FAILURE_COUNT = "${failure_count}" -const SKIPPED_COUNT = "${skipped_count}" -const ORPHAN_COUNT = "${orphan_count}" -const DURATION = "${duration}" -const FAILURE_REPORT = "${failure-report}" -const SUCCESS_PERCENT = "${success_percent}" - -const TESTSUITE_NAME = "${testsuite_name}" -const TESTCASE_NAME = "${testcase_name}" -const REPORT_LINK = "${report_link}" -const BREADCRUMP_PATH_LINK = "${breadcrumb_path_link}" -const BUILD_DATE = "${buid_date}" - - -static func current_date() -> String: - return Time.get_datetime_string_from_system(true, true) - - -static func build(template :String, report :GdUnitReportSummary, report_link :String) -> String: - return template\ - .replace(PATH, report.path())\ - .replace(TESTSUITE_NAME, report.name())\ - .replace(TESTSUITE_COUNT, str(report.suite_count()))\ - .replace(TESTCASE_COUNT, str(report.test_count()))\ - .replace(FAILURE_COUNT, str(report.error_count() + report.failure_count()))\ - .replace(SKIPPED_COUNT, str(report.skipped_count()))\ - .replace(ORPHAN_COUNT, str(report.orphan_count()))\ - .replace(DURATION, LocalTime.elapsed(report.duration()))\ - .replace(SUCCESS_PERCENT, report.calculate_succes_rate(report.test_count(), report.error_count(), report.failure_count()))\ - .replace(REPORT_STATE, report.report_state())\ - .replace(REPORT_LINK, report_link)\ - .replace(BUILD_DATE, current_date()) - - -static func load_template(template_name :String) -> String: - return FileAccess.open(template_name, FileAccess.READ).get_as_text() diff --git a/addons/gdUnit4/src/report/GdUnitHtmlReport.gd b/addons/gdUnit4/src/report/GdUnitHtmlReport.gd deleted file mode 100644 index e5a0c92..0000000 --- a/addons/gdUnit4/src/report/GdUnitHtmlReport.gd +++ /dev/null @@ -1,91 +0,0 @@ -class_name GdUnitHtmlReport -extends GdUnitReportSummary - -const REPORT_DIR_PREFIX = "report_" - -var _report_path :String -var _iteration :int - - -func _init(path_ :String): - _iteration = GdUnitFileAccess.find_last_path_index(path_, REPORT_DIR_PREFIX) + 1 - _report_path = "%s/%s%d" % [path_, REPORT_DIR_PREFIX, _iteration] - DirAccess.make_dir_recursive_absolute(_report_path) - - -func add_testsuite_report(suite_report :GdUnitTestSuiteReport): - _reports.append(suite_report) - - -func add_testcase_report(resource_path_ :String, suite_report :GdUnitTestCaseReport) -> void: - for report in _reports: - if report.resource_path() == resource_path_: - report.add_report(suite_report) - - -func update_test_suite_report( - resource_path_ :String, - duration_ :int, - _is_error :bool, - is_failed_: bool, - _is_warning :bool, - 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_ - - -func update_testcase_report(resource_path_ :String, test_report :GdUnitTestCaseReport): - for report in _reports: - 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, "") - 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) - GdUnitFileAccess.copy_directory("res://addons/gdUnit4/src/report/template/css/", _report_path + "/css") - return index_file - - -func delete_history(max_reports :int) -> int: - 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_) - var table_records := PackedStringArray() - var paths := path_report_mapping.keys() - paths.sort() - for path_ in paths: - var report := GdUnitByPathReport.new(path_, path_report_mapping.get(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: - var table_records := PackedStringArray() - for report in 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)) - - -func iteration() -> int: - return _iteration diff --git a/addons/gdUnit4/src/report/GdUnitReportSummary.gd b/addons/gdUnit4/src/report/GdUnitReportSummary.gd deleted file mode 100644 index 1aefa1f..0000000 --- a/addons/gdUnit4/src/report/GdUnitReportSummary.gd +++ /dev/null @@ -1,131 +0,0 @@ -class_name GdUnitReportSummary -extends RefCounted - -const GdUnitTools := preload("res://addons/gdUnit4/src/core/GdUnitTools.gd") - -const CHARACTERS_TO_ENCODE := { - '<' : '<', - '>' : '>' -} - -var _resource_path :String -var _name :String -var _test_count := 0 -var _failure_count := 0 -var _error_count := 0 -var _orphan_count := 0 -var _skipped_count := 0 -var _duration := 0 -var _reports :Array[GdUnitReportSummary] = [] - - -func name() -> String: - return html_encode(_name) - - -func path() -> String: - return _resource_path.get_base_dir().replace("res://", "") - - -func resource_path() -> String: - return _resource_path - - -func suite_count() -> int: - return _reports.size() - - -func test_count() -> int: - var count := _test_count - for report in _reports: - count += report.test_count() - return count - - -func error_count() -> int: - var count := _error_count - for report in _reports: - count += report.error_count() - return count - - -func failure_count() -> int: - var count := _failure_count - for report in _reports: - count += report.failure_count() - return count - - -func skipped_count() -> int: - var count := _skipped_count - for report in _reports: - count += report.skipped_count() - return count - - -func orphan_count() -> int: - var count := _orphan_count - for report in _reports: - count += report.orphan_count() - return count - - -func duration() -> int: - var count := _duration - for report in _reports: - count += report.duration() - return count - - -func reports() -> Array: - return _reports - - -func add_report(report :GdUnitReportSummary) -> void: - _reports.append(report) - - -func report_state() -> String: - return calculate_state(error_count(), failure_count(), orphan_count()) - - -func succes_rate() -> String: - return calculate_succes_rate(test_count(), error_count(), failure_count()) - - -func calculate_state(p_error_count :int, p_failure_count :int, p_orphan_count :int) -> String: - if p_error_count > 0: - return "error" - if p_failure_count > 0: - return "failure" - if p_orphan_count > 0: - return "warning" - return "success" - - -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 - if count < 0: - return "0%" - return "%d" % (( 0 if count < 0 else count) * 100.0 / p_test_count) + "%" - - -func create_summary(_report_dir :String) -> String: - return "" - - -func html_encode(value :String) -> String: - for key in CHARACTERS_TO_ENCODE.keys(): - value =value.replace(key, CHARACTERS_TO_ENCODE[key]) - return value - - -func convert_rtf_to_html(bbcode :String) -> String: - var as_text: = GdUnitTools.richtext_normalize(bbcode) - var converted := PackedStringArray() - var lines := as_text.split("\n") - for line in lines: - converted.append("

%s

" % line) - return "\n".join(converted) diff --git a/addons/gdUnit4/src/report/GdUnitTestCaseReport.gd b/addons/gdUnit4/src/report/GdUnitTestCaseReport.gd deleted file mode 100644 index d153ec7..0000000 --- a/addons/gdUnit4/src/report/GdUnitTestCaseReport.gd +++ /dev/null @@ -1,59 +0,0 @@ -class_name GdUnitTestCaseReport -extends GdUnitReportSummary - -var _suite_name :String -var _failure_reports :Array - - -func _init( - p_resource_path :String, - p_suite_name :String, - test_name :String, - is_error := false, - _is_failed := false, - failed_count :int = 0, - orphan_count_ :int = 0, - is_skipped := false, - failure_reports :Array = [], - p_duration :int = 0): - _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_ - _skipped_count = is_skipped - _failure_reports = failure_reports - _duration = p_duration - - -func suite_name() -> String: - return _suite_name - - -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()) - return html_report - - -func create_record(_report_dir :String) -> String: - return GdUnitHtmlPatterns.TABLE_RECORD_TESTCASE\ - .replace(GdUnitHtmlPatterns.REPORT_STATE, report_state())\ - .replace(GdUnitHtmlPatterns.TESTCASE_NAME, name())\ - .replace(GdUnitHtmlPatterns.SKIPPED_COUNT, str(skipped_count()))\ - .replace(GdUnitHtmlPatterns.ORPHAN_COUNT, str(orphan_count()))\ - .replace(GdUnitHtmlPatterns.DURATION, LocalTime.elapsed(_duration))\ - .replace(GdUnitHtmlPatterns.FAILURE_REPORT, failure_report()) - - -func update(report :GdUnitTestCaseReport) -> void: - _error_count += report.error_count() - _failure_count += report.failure_count() - _orphan_count += report.orphan_count() - _skipped_count += report.skipped_count() - _failure_reports += report._failure_reports - _duration += report.duration() diff --git a/addons/gdUnit4/src/report/GdUnitTestSuiteReport.gd b/addons/gdUnit4/src/report/GdUnitTestSuiteReport.gd deleted file mode 100644 index 2ffe75c..0000000 --- a/addons/gdUnit4/src/report/GdUnitTestSuiteReport.gd +++ /dev/null @@ -1,95 +0,0 @@ -class_name GdUnitTestSuiteReport -extends GdUnitReportSummary - -var _time_stamp :int -var _failure_reports :Array = [] - - -func _init(p_resource_path :String, p_name :String): - _resource_path = p_resource_path - _name = p_name - _time_stamp = Time.get_unix_time_from_system() as int - - -func create_record(report_link :String) -> String: - return GdUnitHtmlPatterns.build(GdUnitHtmlPatterns.TABLE_RECORD_TESTSUITE, self, report_link) - - -func output_path(report_dir :String) -> String: - return "%s/test_suites/%s.%s.html" % [report_dir, path().replace("/", "."), name()] - - -func path_as_link() -> String: - return "../path/%s.html" % path().replace("/", ".") - - -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()) - return html_report - - -func test_suite_failure_report() -> String: - return GdUnitHtmlPatterns.TABLE_REPORT_TESTSUITE\ - .replace(GdUnitHtmlPatterns.REPORT_STATE, report_state())\ - .replace(GdUnitHtmlPatterns.ORPHAN_COUNT, str(orphan_count()))\ - .replace(GdUnitHtmlPatterns.DURATION, LocalTime.elapsed(_duration))\ - .replace(GdUnitHtmlPatterns.FAILURE_REPORT, failure_report()) - - -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) - FileAccess.open(report_output_path, FileAccess.WRITE).store_string(template) - return report_output_path - - -func set_duration(p_duration :int) -> void: - _duration = p_duration - - -func time_stamp() -> int: - return _time_stamp - - -func duration() -> int: - return _duration - - -func set_skipped(skipped :int) -> void: - _skipped_count = skipped - - -func set_orphans(orphans :int) -> void: - _orphan_count = orphans - - -func set_failed(failed :bool, count :int) -> void: - if failed: - _failure_count += count - - -func set_reports(reports_ :Array) -> void: - _failure_reports = reports_ - - -func update(test_report :GdUnitTestCaseReport) -> void: - for report in _reports: - if report.name() == test_report.name(): - report.update(test_report) diff --git a/addons/gdUnit4/src/report/JUnitXmlReport.gd b/addons/gdUnit4/src/report/JUnitXmlReport.gd deleted file mode 100644 index 65a708b..0000000 --- a/addons/gdUnit4/src/report/JUnitXmlReport.gd +++ /dev/null @@ -1,143 +0,0 @@ -# This class implements the JUnit XML file format -# based checked https://github.com/windyroad/JUnit-Schema/blob/master/JUnit.xsd -class_name JUnitXmlReport -extends RefCounted - -const GdUnitTools := preload("res://addons/gdUnit4/src/core/GdUnitTools.gd") - -const ATTR_CLASSNAME := "classname" -const ATTR_ERRORS := "errors" -const ATTR_FAILURES := "failures" -const ATTR_HOST := "hostname" -const ATTR_ID := "id" -const ATTR_MESSAGE := "message" -const ATTR_NAME := "name" -const ATTR_PACKAGE := "package" -const ATTR_SKIPPED := "skipped" -const ATTR_TESTS := "tests" -const ATTR_TIME := "time" -const ATTR_TIMESTAMP := "timestamp" -const ATTR_TYPE := "type" - -const HEADER := '\n' - -var _report_path :String -var _iteration :int - - -func _init(path :String,iteration :int): - _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) - 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)) - return result_file - - -func build_junit_report(report :GdUnitReportSummary) -> String: - var ISO8601_datetime := Time.get_date_string_from_system() - var test_suites := XmlElement.new("testsuites")\ - .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() - test_suites.dispose() - return HEADER + as_string - - -func build_test_suites(summary :GdUnitReportSummary) -> Array: - var test_suites :Array = Array() - 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()) - 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_HOST, "localhost")\ - .attribute(ATTR_TESTS, suite_report.test_count())\ - .attribute(ATTR_FAILURES, suite_report.failure_count())\ - .attribute(ATTR_ERRORS, suite_report.error_count())\ - .attribute(ATTR_SKIPPED, suite_report.skipped_count())\ - .attribute(ATTR_TIME, JUnitXmlReport.to_time(suite_report.duration()))\ - .add_childs(build_test_cases(suite_report))) - return test_suites - - -func build_test_cases(suite_report :GdUnitTestSuiteReport) -> Array: - var test_cases :Array = Array() - 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_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: - 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_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_TYPE, JUnitXmlReport.to_type(report.type()))\ - .text(convert_rtf_to_text(report.message()))) - if testReport.skipped_count(): - for failure in testReport._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()])) - return failure_reports - - -func convert_rtf_to_text(bbcode :String) -> String: - return GdUnitTools.richtext_normalize(bbcode) - - -static func to_type(type :int) -> String: - match type: - GdUnitReport.SUCCESS: - return "SUCCESS" - GdUnitReport.WARN: - return "WARN" - GdUnitReport.FAILURE: - return "FAILURE" - GdUnitReport.ORPHAN: - return "ORPHAN" - GdUnitReport.TERMINATED: - return "TERMINATED" - GdUnitReport.INTERUPTED: - return "INTERUPTED" - GdUnitReport.ABORT: - return "ABORT" - return "UNKNOWN" - - -static func to_time(duration :int) -> String: - return "%4.03f" % (duration / 1000.0) - - -static func encode_xml(value :String) -> String: - return value.xml_escape(true) - - -#static func to_ISO8601_datetime() -> String: - #return "%04d-%02d-%02dT%02d:%02d:%02d" % [date["year"], date["month"], date["day"], date["hour"], date["minute"], date["second"]] diff --git a/addons/gdUnit4/src/report/XmlElement.gd b/addons/gdUnit4/src/report/XmlElement.gd deleted file mode 100644 index 815f169..0000000 --- a/addons/gdUnit4/src/report/XmlElement.gd +++ /dev/null @@ -1,67 +0,0 @@ -class_name XmlElement -extends RefCounted - -var _name :String -var _attributes :Dictionary = {} -var _childs :Array = [] -var _parent = null -var _text :String = "" - - -func _init(name :String): - _name = name - - -func dispose(): - for child in _childs: - child.dispose() - _childs.clear() - _attributes.clear() - _parent = null - - -func attribute(name :String, value) -> XmlElement: - _attributes[name] = str(value) - return self - - -func text(p_text :String) -> XmlElement: - _text = p_text if p_text.ends_with("\n") else p_text + "\n" - return self - - -func add_child(child :XmlElement) -> XmlElement: - _childs.append(child) - child._parent = self - return self - - -func add_childs(childs :Array) -> XmlElement: - for child in childs: - add_child(child) - return self - - -func _indentation() -> String: - return "" if _parent == null else _parent._indentation() + " " - - -func to_xml() -> String: - var attributes := "" - for key in _attributes.keys(): - attributes += ' {attr}="{value}"'.format({"attr": key, "value": _attributes.get(key)}) - - 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(), - "text": cdata(_text)}) - - -func cdata(p_text :String) -> String: - return "" if p_text.is_empty() else "\n".format({"text" : p_text}) diff --git a/addons/gdUnit4/src/report/template/css/breadcrumb.css b/addons/gdUnit4/src/report/template/css/breadcrumb.css deleted file mode 100644 index 2dd65fe..0000000 --- a/addons/gdUnit4/src/report/template/css/breadcrumb.css +++ /dev/null @@ -1,67 +0,0 @@ - -.breadcrumb { - display: flex; - border-radius: 6px; - overflow: hidden; - height: 45px; - z-index: 1; - background-color: #055d9c; - font-weight: bold; - font-size: 16px; - margin-top: 0px; - margin-bottom: 20px; - box-shadow: 0 0 3px black; -} - -.breadcrumb a { - position: relative; - display: flex; - -ms-flex-positive: 1; - flex-grow: 1; - text-decoration: none; - margin: auto; - height: 100%; - color: white; -} - -.breadcrumb a:first-child { - padding-left: 5.2px; -} - -.breadcrumb a:last-child { - padding-right: 5.2px; -} - -.breadcrumb a:after { - content: ""; - position: absolute; - display: inline-block; - width: 45px; - height: 45px; - top: 0; - right: -20px; - background-color: #055d9c; - border-top-right-radius: 5px; - transform: scale(0.707) rotate(45deg); - box-shadow: 2px -2px rgba(0,0,0,0.25); - z-index: 1; -} - -.breadcrumb a:last-child:after { - content: none; -} - -.breadcrumb a.active, .breadcrumb a:hover { - background: #347bad; - color: white; - text-decoration: underline; -} - -.breadcrumb a.active:after, .breadcrumb a:hover:after { - background: #347bad; -} - -.breadcrumb span { - margin:inherit; - z-index: 2; -} diff --git a/addons/gdUnit4/src/report/template/css/icon.png b/addons/gdUnit4/src/report/template/css/icon.png deleted file mode 100644 index eeac2924071bb62e97d1f372ca66c0e7e1a256f0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 13817 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 - - - - - Testsuite Results - - - - - - - -
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - -
TestSuites${suite_count}
Tests${test_count}
Skipped${skipped_count}
Failures${failure_count}
Orphans${orphan_count}
Duration${duration}
-
-
-

Success Rate

- - - - -
${success_percent}
-
-
-

History

-

Coming Next

-
-
- - - -
-
- - - -
-
- - - - - - - - - - - - - - ${report_table_testsuites} - -
TestsSkippedFailuresOrphansDurationSuccess rate
-
-
-
-
-
- -
-

Generated byGdUnit4 at ${buid_date}

-
- - - diff --git a/addons/gdUnit4/src/report/template/index.html b/addons/gdUnit4/src/report/template/index.html deleted file mode 100644 index 2f6571e..0000000 --- a/addons/gdUnit4/src/report/template/index.html +++ /dev/null @@ -1,123 +0,0 @@ - - - - - - Report Summary - - - - -
-

GdUnit4 Report

-
- -
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - -
TestSuites${suite_count}
Tests${test_count}
Skipped${skipped_count}
Failures${failure_count}
Orphans${orphan_count}
Duration${duration}
-
-
-

Success Rate

- - - - -
${success_percent}
-
-
-

History

-

Coming Next

-
-
- -
-

Reports

-
- - - - - - - -
-
- - - - - - - - - - - - - - ${report_table_testsuites} - -
TestsSkippedFailuresOrphansDurationSuccess rate
-
-
-
-
- - - - - - - - - - - - - - ${report_table_paths} - -
TestsSkippedFailuresOrphansDurationSuccess rate
-
-
-
-
-

${log_file}

-

-
-
-
-
-
- -
-

Generated byGdUnit4 at ${buid_date}

-
- - diff --git a/addons/gdUnit4/src/report/template/suite_report.html b/addons/gdUnit4/src/report/template/suite_report.html deleted file mode 100644 index 94fa47d..0000000 --- a/addons/gdUnit4/src/report/template/suite_report.html +++ /dev/null @@ -1,109 +0,0 @@ - - - - - - - Testsuite results - - - - - - - - - - -
-
-
- - - - - - - - - - - - - - - - - - - - - -
Tests${test_count}
Skipped${skipped_count}
Failures${failure_count}
Orphans${orphan_count}
Duration${duration}
-
-
-

Success Rate

- - - - -
${success_percent}
-
-
-

History

-

Coming Next

-
-
- - - -
-
-
- - - - - - - - - - - - ${report_table_tests} - -
TestcaseSkippedOrphansDurationReport
-
-
-

Failure Report

-
-
-
-
-
- -
-

Generated byGdUnit4 at ${buid_date}

-
- - - \ No newline at end of file diff --git a/addons/gdUnit4/src/spy/GdUnitSpyBuilder.gd b/addons/gdUnit4/src/spy/GdUnitSpyBuilder.gd deleted file mode 100644 index 1254868..0000000 --- a/addons/gdUnit4/src/spy/GdUnitSpyBuilder.gd +++ /dev/null @@ -1,112 +0,0 @@ -class_name GdUnitSpyBuilder -extends GdUnitClassDoubler - -const GdUnitTools := preload("res://addons/gdUnit4/src/core/GdUnitTools.gd") -const SPY_TEMPLATE :GDScript = preload("res://addons/gdUnit4/src/spy/GdUnitSpyImpl.gd") - - -static func build(to_spy, debug_write := false) -> Object: - if GdObjects.is_singleton(to_spy): - push_error("Spy on a Singleton is not allowed! '%s'" % to_spy.get_class()) - return null - # if resource path load it before - if GdObjects.is_scene_resource_path(to_spy): - if not FileAccess.file_exists(to_spy): - push_error("Can't build spy on scene '%s'! The given resource not exists!" % to_spy) - return null - to_spy = load(to_spy) - # spy checked PackedScene - if GdObjects.is_scene(to_spy): - return spy_on_scene(to_spy.instantiate(), debug_write) - # spy checked a scene instance - if GdObjects.is_instance_scene(to_spy): - return spy_on_scene(to_spy, debug_write) - - var spy := spy_on_script(to_spy, [], debug_write) - if spy == null: - return null - var spy_instance = spy.new() - copy_properties(to_spy, spy_instance) - GdUnitObjectInteractions.reset(spy_instance) - spy_instance.__set_singleton(to_spy) - # we do not call the original implementation for _ready and all input function, this is actualy done by the engine - spy_instance.__exclude_method_call([ "_input", "_gui_input", "_input_event", "_unhandled_input"]) - return register_auto_free(spy_instance) - - -static func get_class_info(clazz :Variant) -> Dictionary: - var clazz_path := GdObjects.extract_class_path(clazz) - var clazz_name :String = GdObjects.extract_class_name(clazz).value() - return { - "class_name" : clazz_name, - "class_path" : clazz_path - } - - -static func spy_on_script(instance, function_excludes :PackedStringArray, debug_write) -> GDScript: - if GdArrayTools.is_array_type(instance): - if GdUnitSettings.is_verbose_assert_errors(): - push_error("Can't build spy checked type '%s'! Spy checked Container Built-In Type not supported!" % instance.get_class()) - return null - var class_info := get_class_info(instance) - var clazz_name :String = class_info.get("class_name") - var clazz_path :PackedStringArray = class_info.get("class_path", [clazz_name]) - if not GdObjects.is_instance(instance): - if GdUnitSettings.is_verbose_assert_errors(): - push_error("Can't build spy for class type '%s'! Using an instance instead e.g. 'spy()'" % [clazz_name]) - return null - var lines := load_template(SPY_TEMPLATE.source_code, class_info, instance) - lines += double_functions(instance, clazz_name, clazz_path, GdUnitSpyFunctionDoubler.new(), function_excludes) - - var spy := GDScript.new() - spy.source_code = "\n".join(lines) - spy.resource_name = "Spy%s.gd" % clazz_name - spy.resource_path = GdUnitFileAccess.create_temp_dir("spy") + "/Spy%s_%d.gd" % [clazz_name, Time.get_ticks_msec()] - - if debug_write: - DirAccess.remove_absolute(spy.resource_path) - ResourceSaver.save(spy, spy.resource_path) - var error := spy.reload(true) - if error != OK: - push_error("Unexpected Error!, SpyBuilder error, please contact the developer.") - return null - return spy - - -static func spy_on_scene(scene :Node, debug_write) -> Object: - if scene.get_script() == null: - if GdUnitSettings.is_verbose_assert_errors(): - push_error("Can't create a spy checked a scene without script '%s'" % scene.get_scene_file_path()) - return null - # buils spy checked original script - var scene_script = scene.get_script().new() - var spy := spy_on_script(scene_script, GdUnitClassDoubler.EXLCUDE_SCENE_FUNCTIONS, debug_write) - scene_script.free() - if spy == null: - return null - # replace original script whit spy - scene.set_script(spy) - return register_auto_free(scene) - - -const EXCLUDE_PROPERTIES_TO_COPY = ["script", "type"] - - -static func copy_properties(source :Object, dest :Object) -> void: - for property in source.get_property_list(): - var property_name = property["name"] - var property_value = 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) - - -static func register_auto_free(obj :Variant) -> Variant: - return GdUnitThreadManager.get_current_context().get_execution_context().register_auto_free(obj) diff --git a/addons/gdUnit4/src/spy/GdUnitSpyFunctionDoubler.gd b/addons/gdUnit4/src/spy/GdUnitSpyFunctionDoubler.gd deleted file mode 100644 index a8c80ea..0000000 --- a/addons/gdUnit4/src/spy/GdUnitSpyFunctionDoubler.gd +++ /dev/null @@ -1,75 +0,0 @@ -class_name GdUnitSpyFunctionDoubler -extends GdFunctionDoubler - - -const TEMPLATE_RETURN_VARIANT = """ - var args :Array = ["$(func_name)", $(arguments)] - - if $(instance)__is_verify_interactions(): - $(instance)__verify_interactions(args) - return ${default_return_value} - else: - $(instance)__save_function_interaction(args) - - if $(instance)__do_call_real_func("$(func_name)"): - return $(await)super($(arguments)) - return ${default_return_value} - -""" - - -const TEMPLATE_RETURN_VOID = """ - var args :Array = ["$(func_name)", $(arguments)] - - if $(instance)__is_verify_interactions(): - $(instance)__verify_interactions(args) - return - else: - $(instance)__save_function_interaction(args) - - if $(instance)__do_call_real_func("$(func_name)"): - $(await)super($(arguments)) - -""" - - -const TEMPLATE_RETURN_VOID_VARARG = """ - var varargs :Array = __filter_vargs([$(varargs)]) - var args :Array = ["$(func_name)", $(arguments)] + varargs - - if $(instance)__is_verify_interactions(): - $(instance)__verify_interactions(args) - return - else: - $(instance)__save_function_interaction(args) - - $(await)$(instance)__call_func("$(func_name)", [$(arguments)] + varargs) - -""" - - -const TEMPLATE_RETURN_VARIANT_VARARG = """ - var varargs :Array = __filter_vargs([$(varargs)]) - var args :Array = ["$(func_name)", $(arguments)] + varargs - - if $(instance)__is_verify_interactions(): - $(instance)__verify_interactions(args) - return ${default_return_value} - else: - $(instance)__save_function_interaction(args) - - return $(await)$(instance)__call_func("$(func_name)", [$(arguments)] + varargs) - -""" - - -func _init(push_errors :bool = false): - super._init(push_errors) - - -func get_template(return_type :Variant, is_vararg :bool) -> String: - if is_vararg: - return TEMPLATE_RETURN_VOID_VARARG if return_type == TYPE_NIL else TEMPLATE_RETURN_VARIANT_VARARG - if return_type is StringName: - return TEMPLATE_RETURN_VARIANT - return TEMPLATE_RETURN_VOID if (return_type == TYPE_NIL or return_type == GdObjects.TYPE_VOID) else TEMPLATE_RETURN_VARIANT diff --git a/addons/gdUnit4/src/spy/GdUnitSpyImpl.gd b/addons/gdUnit4/src/spy/GdUnitSpyImpl.gd deleted file mode 100644 index 3b61e86..0000000 --- a/addons/gdUnit4/src/spy/GdUnitSpyImpl.gd +++ /dev/null @@ -1,44 +0,0 @@ - -const __INSTANCE_ID = "${instance_id}" -const __SOURCE_CLASS = "${source_class}" - -var __instance_delegator :Object -var __excluded_methods :PackedStringArray = [] - - -static func __instance() -> Variant: - return Engine.get_meta(__INSTANCE_ID) - - -func _notification(what :int) -> void: - if what == NOTIFICATION_PREDELETE: - if Engine.has_meta(__INSTANCE_ID): - Engine.remove_meta(__INSTANCE_ID) - - -func __instance_id() -> String: - return __INSTANCE_ID - - -func __set_singleton(delegator :Object) -> void: - # store self need to mock static functions - Engine.set_meta(__INSTANCE_ID, self) - __instance_delegator = delegator - - -func __release_double() -> void: - # we need to release the self reference manually to prevent orphan nodes - Engine.remove_meta(__INSTANCE_ID) - __instance_delegator = null - - -func __do_call_real_func(func_name :String) -> bool: - return not __excluded_methods.has(func_name) - - -func __exclude_method_call(exluded_methods :PackedStringArray) -> void: - __excluded_methods.append_array(exluded_methods) - - -func __call_func(func_name :String, arguments :Array) -> Variant: - return __instance_delegator.callv(func_name, arguments) diff --git a/addons/gdUnit4/src/ui/EditorFileSystemControls.gd b/addons/gdUnit4/src/ui/EditorFileSystemControls.gd deleted file mode 100644 index eef80e1..0000000 --- a/addons/gdUnit4/src/ui/EditorFileSystemControls.gd +++ /dev/null @@ -1,33 +0,0 @@ -# A tool to provide extended filesystem editor functionallity -class_name EditorFileSystemControls -extends RefCounted - - -# Returns the EditorInterface instance -static func editor_interface() -> EditorInterface: - if not Engine.has_meta("GdUnitEditorPlugin"): - return null - var plugin :EditorPlugin = Engine.get_meta("GdUnitEditorPlugin") - return plugin.get_editor_interface() - - -# Register the given context menu to the filesystem dock -# Is called when the plugin is activated -# The filesystem popup is connected to the EditorFileSystemContextMenuHandler -static func register_context_menu(menu :Array[GdUnitContextMenuItem]) -> void: - Engine.get_main_loop().root.call_deferred("add_child", EditorFileSystemContextMenuHandler.new(menu)) - - -# Unregisteres all registerend context menus and gives the EditorFileSystemContextMenuHandler> free -# Is called when the plugin is deactivated -static func unregister_context_menu() -> void: - EditorFileSystemContextMenuHandler.dispose() - - -static func _print_menu(popup :PopupMenu): - for itemIndex in popup.item_count: - prints( "get_item_id", popup.get_item_id(itemIndex)) - prints( "get_item_accelerator", popup.get_item_accelerator(itemIndex)) - prints( "get_item_shortcut", popup.get_item_shortcut(itemIndex)) - prints( "get_item_text", popup.get_item_text(itemIndex)) - prints() diff --git a/addons/gdUnit4/src/ui/GdUnitConsole.gd b/addons/gdUnit4/src/ui/GdUnitConsole.gd deleted file mode 100644 index cd823fc..0000000 --- a/addons/gdUnit4/src/ui/GdUnitConsole.gd +++ /dev/null @@ -1,161 +0,0 @@ -@tool -extends Control - -const TITLE = "gdUnit4 ${version} Console" - -@onready var header := $VBoxContainer/Header -@onready var title :RichTextLabel = $VBoxContainer/Header/header_title -@onready var output :RichTextLabel = $VBoxContainer/Console/TextEdit - -var _text_color :Color -var _function_color :Color -var _engine_type_color :Color -var _statistics = {} -var _summary = { - "total_count": 0, - "error_count": 0, - "failed_count": 0, - "skipped_count": 0, - "orphan_nodes": 0 -} - - -func _ready(): - init_colors() - GdUnitFonts.init_fonts(output) - GdUnit4Version.init_version_label(title) - GdUnitSignals.instance().gdunit_event.connect(_on_gdunit_event) - GdUnitSignals.instance().gdunit_message.connect(_on_gdunit_message) - GdUnitSignals.instance().gdunit_client_connected.connect(_on_gdunit_client_connected) - GdUnitSignals.instance().gdunit_client_disconnected.connect(_on_gdunit_client_disconnected) - output.clear() - - -func _notification(what): - if what == EditorSettings.NOTIFICATION_EDITOR_SETTINGS_CHANGED: - init_colors() - if what == NOTIFICATION_PREDELETE: - GdUnitSignals.instance().gdunit_event.disconnect(_on_gdunit_event) - GdUnitSignals.instance().gdunit_message.disconnect(_on_gdunit_message) - GdUnitSignals.instance().gdunit_client_connected.disconnect(_on_gdunit_client_connected) - GdUnitSignals.instance().gdunit_client_disconnected.disconnect(_on_gdunit_client_disconnected) - - -func init_colors() -> void: - var plugin :EditorPlugin = Engine.get_meta("GdUnitEditorPlugin") - var settings := plugin.get_editor_interface().get_editor_settings() - _text_color = settings.get_setting("text_editor/theme/highlighting/text_color") - _function_color = settings.get_setting("text_editor/theme/highlighting/function_color") - _engine_type_color = settings.get_setting("text_editor/theme/highlighting/engine_type_color") - - -func init_statistics(event :GdUnitEvent) : - _statistics["total_count"] = event.total_count() - _statistics["error_count"] = 0 - _statistics["failed_count"] = 0 - _statistics["skipped_count"] = 0 - _statistics["orphan_nodes"] = 0 - _summary["total_count"] += event.total_count() - - -func reset_statistics() -> void: - for k in _statistics.keys(): - _statistics[k] = 0 - for k in _summary.keys(): - _summary[k] = 0 - - -func update_statistics(event :GdUnitEvent) : - _statistics["error_count"] += event.error_count() - _statistics["failed_count"] += event.failed_count() - _statistics["skipped_count"] += event.skipped_count() - _statistics["orphan_nodes"] += event.orphan_nodes() - _summary["error_count"] += event.error_count() - _summary["failed_count"] += event.failed_count() - _summary["skipped_count"] += event.skipped_count() - _summary["orphan_nodes"] += event.orphan_nodes() - - -func print_message(message :String, color :Color = _text_color, indent :int = 0) -> void: - for i in indent: - output.push_indent(1) - output.push_color(color) - output.append_text(message) - output.pop() - for i in indent: - output.pop() - - -func println_message(message :String, color :Color = _text_color, indent :int = -1) -> void: - print_message(message, color, indent) - output.newline() - - -func _on_gdunit_event(event :GdUnitEvent): - match event.type(): - GdUnitEvent.INIT: - reset_statistics() - - GdUnitEvent.STOP: - print_message("Summary:", Color.DODGER_BLUE) - println_message("| %d total | %d error | %d failed | %d skipped | %d orphans |" % [_summary["total_count"], _summary["error_count"], _summary["failed_count"], _summary["skipped_count"], _summary["orphan_nodes"]], _text_color, 1) - print_message("[wave][/wave]") - - GdUnitEvent.TESTSUITE_BEFORE: - init_statistics(event) - print_message("Execute: ", Color.DODGER_BLUE) - println_message(event._suite_name, _engine_type_color) - - GdUnitEvent.TESTSUITE_AFTER: - update_statistics(event) - if not event.reports().is_empty(): - var report :GdUnitReport = event.reports().front() - println_message("\t" +event._suite_name, _engine_type_color) - println_message("line %d %s" % [report._line_number, report._message], _text_color, 2) - if event.is_success(): - print_message("[wave]PASSED[/wave]", Color.LIGHT_GREEN) - else: - print_message("[shake rate=5 level=10][b]FAILED[/b][/shake]", Color.FIREBRICK) - print_message(" | %d total | %d error | %d failed | %d skipped | %d orphans |" % [_statistics["total_count"], _statistics["error_count"], _statistics["failed_count"], _statistics["skipped_count"], _statistics["orphan_nodes"]]) - println_message("%+12s" % LocalTime.elapsed(event.elapsed_time())) - println_message(" ") - - GdUnitEvent.TESTCASE_BEFORE: - var spaces = "-%d" % (80 - event._suite_name.length()) - print_message(event._suite_name, _engine_type_color, 1) - print_message(":") - print_message(("%"+spaces+"s") % event._test_name, _function_color) - - GdUnitEvent.TESTCASE_AFTER: - var reports := event.reports() - update_statistics(event) - if event.is_success(): - print_message("PASSED", Color.LIGHT_GREEN) - elif event.is_skipped(): - print_message("SKIPPED", Color.GOLDENROD) - elif event.is_error() or event.is_failed(): - print_message("[wave]FAILED[/wave]", Color.FIREBRICK) - elif event.is_warning(): - print_message("WARNING", Color.YELLOW) - println_message(" %+12s" % LocalTime.elapsed(event.elapsed_time())) - - var report :GdUnitReport = null if reports.is_empty() else reports[0] - if report: - println_message("line %d %s" % [report._line_number, report._message], _text_color, 2) - - -func _on_gdunit_client_connected(client_id :int) -> void: - output.clear() - output.append_text("[color=#9887c4]GdUnit Test Client connected with id %d[/color]\n" % client_id) - output.newline() - - -func _on_gdunit_client_disconnected(client_id :int) -> void: - output.append_text("[color=#9887c4]GdUnit Test Client disconnected with id %d[/color]\n" % client_id) - output.newline() - - -func _on_gdunit_message(message :String): - output.newline() - output.append_text(message) - output.newline() diff --git a/addons/gdUnit4/src/ui/GdUnitConsole.tscn b/addons/gdUnit4/src/ui/GdUnitConsole.tscn deleted file mode 100644 index 6aa9c78..0000000 --- a/addons/gdUnit4/src/ui/GdUnitConsole.tscn +++ /dev/null @@ -1,61 +0,0 @@ -[gd_scene load_steps=2 format=3 uid="uid://dm0wvfyeew7vd"] - -[ext_resource type="Script" path="res://addons/gdUnit4/src/ui/GdUnitConsole.gd" id="1"] - -[node name="Control" type="Control"] -use_parent_material = true -clip_contents = true -custom_minimum_size = Vector2(0, 200) -layout_mode = 3 -anchors_preset = 15 -anchor_right = 1.0 -anchor_bottom = 1.0 -grow_horizontal = 2 -grow_vertical = 2 -size_flags_horizontal = 3 -size_flags_vertical = 3 -script = ExtResource("1") - -[node name="VBoxContainer" type="VBoxContainer" parent="."] -use_parent_material = true -clip_contents = true -layout_mode = 1 -anchors_preset = 15 -anchor_right = 1.0 -anchor_bottom = 1.0 -grow_horizontal = 2 -grow_vertical = 2 -size_flags_horizontal = 3 -size_flags_vertical = 3 - -[node name="Header" type="PanelContainer" parent="VBoxContainer"] -custom_minimum_size = Vector2(0, 32) -layout_mode = 2 -auto_translate = false -localize_numeral_system = false -mouse_filter = 2 - -[node name="header_title" type="RichTextLabel" parent="VBoxContainer/Header"] -layout_mode = 2 -size_flags_horizontal = 3 -size_flags_vertical = 3 -auto_translate = false -localize_numeral_system = false -mouse_filter = 2 -bbcode_enabled = true -scroll_active = false -autowrap_mode = 0 -shortcut_keys_enabled = false - -[node name="Console" type="ScrollContainer" parent="VBoxContainer"] -layout_mode = 2 -size_flags_horizontal = 3 -size_flags_vertical = 3 - -[node name="TextEdit" type="RichTextLabel" parent="VBoxContainer/Console"] -use_parent_material = true -layout_mode = 2 -size_flags_horizontal = 3 -size_flags_vertical = 3 -bbcode_enabled = true -scroll_following = true diff --git a/addons/gdUnit4/src/ui/GdUnitFonts.gd b/addons/gdUnit4/src/ui/GdUnitFonts.gd deleted file mode 100644 index 46ee80c..0000000 --- a/addons/gdUnit4/src/ui/GdUnitFonts.gd +++ /dev/null @@ -1,44 +0,0 @@ -class_name GdUnitFonts -extends RefCounted - -const FONT_MONO = "res://addons/gdUnit4/src/update/assets/fonts/static/RobotoMono-Regular.ttf" -const FONT_MONO_BOLT = "res://addons/gdUnit4/src/update/assets/fonts/static/RobotoMono-Bold.ttf" -const FONT_MONO_BOLT_ITALIC = "res://addons/gdUnit4/src/update/assets/fonts/static/RobotoMono-BoldItalic.ttf" -const FONT_MONO_ITALIC = "res://addons/gdUnit4/src/update/assets/fonts/static/RobotoMono-Italic.ttf" - - -static func init_fonts(item: CanvasItem) -> float: - # add a default fallback font - item.set("theme_override_fonts/font", load_and_resize_font(FONT_MONO, 16)) - item.set("theme_override_fonts/normal_font", load_and_resize_font(FONT_MONO, 16)) - item.set("theme_override_font_sizes/font_size", 16) - if Engine.is_editor_hint(): - var plugin :EditorPlugin = Engine.get_meta("GdUnitEditorPlugin") - var settings := plugin.get_editor_interface().get_editor_settings() - var scale_factor := plugin.get_editor_interface().get_editor_scale() - var font_size :float = settings.get_setting("interface/editor/main_font_size") - font_size *= scale_factor - var font_mono := load_and_resize_font(FONT_MONO, font_size) - item.set("theme_override_fonts/normal_font", font_mono) - item.set("theme_override_fonts/bold_font", load_and_resize_font(FONT_MONO_BOLT, font_size)) - item.set("theme_override_fonts/italics_font", load_and_resize_font(FONT_MONO_ITALIC, font_size)) - item.set("theme_override_fonts/bold_italics_font", load_and_resize_font(FONT_MONO_BOLT_ITALIC, font_size)) - item.set("theme_override_fonts/mono_font", font_mono) - item.set("theme_override_font_sizes/font_size", font_size) - item.set("theme_override_font_sizes/normal_font_size", font_size) - item.set("theme_override_font_sizes/bold_font_size", font_size) - item.set("theme_override_font_sizes/italics_font_size", font_size) - item.set("theme_override_font_sizes/bold_italics_font_size", font_size) - item.set("theme_override_font_sizes/mono_font_size", font_size) - return font_size - return 16.0 - - -static func load_and_resize_font(font_resource: String, size: float) -> Font: - var font :FontFile = ResourceLoader.load(font_resource, "FontFile") - if font == null: - push_error("Can't load font '%s'" % font_resource) - return null - var resized_font := font.duplicate() - resized_font.fixed_size = int(size) - return resized_font diff --git a/addons/gdUnit4/src/ui/GdUnitInspector.gd b/addons/gdUnit4/src/ui/GdUnitInspector.gd deleted file mode 100644 index 1eb0eba..0000000 --- a/addons/gdUnit4/src/ui/GdUnitInspector.gd +++ /dev/null @@ -1,85 +0,0 @@ -@tool -class_name GdUnitInspecor -extends Panel - - -var _command_handler := GdUnitCommandHandler.instance() - - -func _ready(): - if Engine.is_editor_hint(): - var plugin :EditorPlugin = Engine.get_meta("GdUnitEditorPlugin") - _getEditorThemes(plugin.get_editor_interface()) - GdUnitCommandHandler.instance().gdunit_runner_start.connect(func(): - var tab_container :TabContainer = get_parent_control() - for tab_index in tab_container.get_tab_count(): - if tab_container.get_tab_title(tab_index) == "GdUnit": - tab_container.set_current_tab(tab_index) - ) - - -func _enter_tree(): - if Engine.is_editor_hint(): - add_script_editor_context_menu() - add_file_system_dock_context_menu() - - -func _exit_tree(): - if Engine.is_editor_hint(): - ScriptEditorControls.unregister_context_menu() - EditorFileSystemControls.unregister_context_menu() - - -func _process(_delta): - _command_handler._do_process() - - -func _getEditorThemes(interface :EditorInterface) -> void: - if interface == null: - return - # example to access current theme - #var editiorTheme := interface.get_base_control().theme - # setup inspector button icons - #var stylebox_types :PackedStringArray = editiorTheme.get_stylebox_type_list() - #for stylebox_type in stylebox_types: - #prints("stylebox_type", stylebox_type) - # if "Tree" == stylebox_type: - # prints(editiorTheme.get_stylebox_list(stylebox_type)) - #var style:StyleBoxFlat = editiorTheme.get_stylebox("panel", "Tree") - #style.bg_color = Color.RED - #var locale = interface.get_editor_settings().get_setting("interface/editor/editor_language") - #sessions_label.add_theme_color_override("font_color", get_color("contrast_color_2", "Editor")) - #status_label.add_theme_color_override("font_color", get_color("contrast_color_2", "Editor")) - #no_sessions_label.add_theme_color_override("font_color", get_color("contrast_color_2", "Editor")) - - -# Context menu registrations ---------------------------------------------------------------------- -func add_file_system_dock_context_menu() -> void: - var is_test_suite := func is_visible(script :Script, is_test_suite :bool): - if script == null: - return true - return GdObjects.is_test_suite(script) == is_test_suite - var menu :Array[GdUnitContextMenuItem] = [ - GdUnitContextMenuItem.new(GdUnitContextMenuItem.MENU_ID.TEST_RUN, "Run Testsuites", is_test_suite.bind(true), _command_handler.command(GdUnitCommandHandler.CMD_RUN_TESTSUITE)), - GdUnitContextMenuItem.new(GdUnitContextMenuItem.MENU_ID.TEST_DEBUG, "Debug Testsuites", is_test_suite.bind(true), _command_handler.command(GdUnitCommandHandler.CMD_RUN_TESTSUITE_DEBUG)), - ] - EditorFileSystemControls.register_context_menu(menu) - - -func add_script_editor_context_menu(): - var is_test_suite := func is_visible(script :Script, is_test_suite :bool): - return GdObjects.is_test_suite(script) == is_test_suite - var menu :Array[GdUnitContextMenuItem] = [ - GdUnitContextMenuItem.new(GdUnitContextMenuItem.MENU_ID.TEST_RUN, "Run Tests", is_test_suite.bind(true), _command_handler.command(GdUnitCommandHandler.CMD_RUN_TESTCASE)), - GdUnitContextMenuItem.new(GdUnitContextMenuItem.MENU_ID.TEST_DEBUG, "Debug Tests", is_test_suite.bind(true),_command_handler.command(GdUnitCommandHandler.CMD_RUN_TESTCASE_DEBUG)), - GdUnitContextMenuItem.new(GdUnitContextMenuItem.MENU_ID.CREATE_TEST, "Create Test", is_test_suite.bind(false), _command_handler.command(GdUnitCommandHandler.CMD_CREATE_TESTCASE)) - ] - ScriptEditorControls.register_context_menu(menu) - - -func _on_MainPanel_run_testsuite(test_suite_paths :Array, debug :bool): - _command_handler.cmd_run_test_suites(test_suite_paths, debug) - - -func _on_MainPanel_run_testcase(resource_path :String, test_case :String, test_param_index :int, debug :bool): - _command_handler.cmd_run_test_case(resource_path, test_case, test_param_index, debug) diff --git a/addons/gdUnit4/src/ui/GdUnitInspector.tscn b/addons/gdUnit4/src/ui/GdUnitInspector.tscn deleted file mode 100644 index 7bfc79c..0000000 --- a/addons/gdUnit4/src/ui/GdUnitInspector.tscn +++ /dev/null @@ -1,58 +0,0 @@ -[gd_scene load_steps=7 format=3 uid="uid://mpo5o6d4uybu"] - -[ext_resource type="PackedScene" uid="uid://dx7xy4dgi3wwb" path="res://addons/gdUnit4/src/ui/parts/InspectorToolBar.tscn" id="1"] -[ext_resource type="PackedScene" uid="uid://dva3tonxsxrlk" path="res://addons/gdUnit4/src/ui/parts/InspectorProgressBar.tscn" id="2"] -[ext_resource type="PackedScene" path="res://addons/gdUnit4/src/ui/parts/InspectorStatusBar.tscn" id="3"] -[ext_resource type="PackedScene" path="res://addons/gdUnit4/src/ui/parts/InspectorMonitor.tscn" id="4"] -[ext_resource type="Script" path="res://addons/gdUnit4/src/ui/GdUnitInspector.gd" id="5"] -[ext_resource type="PackedScene" uid="uid://bqfpidewtpeg0" path="res://addons/gdUnit4/src/ui/parts/InspectorTreePanel.tscn" id="7"] - -[node name="GdUnit" type="Panel"] -use_parent_material = true -clip_contents = true -anchors_preset = 15 -anchor_right = 1.0 -anchor_bottom = 1.0 -size_flags_horizontal = 11 -size_flags_vertical = 3 -focus_mode = 2 -script = ExtResource("5") - -[node name="VBoxContainer" type="VBoxContainer" parent="."] -use_parent_material = true -clip_contents = true -layout_mode = 0 -anchor_right = 1.0 -anchor_bottom = 1.0 -size_flags_vertical = 11 - -[node name="Header" type="VBoxContainer" parent="VBoxContainer"] -use_parent_material = true -clip_contents = true -layout_mode = 2 -size_flags_horizontal = 9 - -[node name="ToolBar" parent="VBoxContainer/Header" instance=ExtResource("1")] -layout_mode = 2 -size_flags_vertical = 1 - -[node name="ProgressBar" parent="VBoxContainer/Header" instance=ExtResource("2")] -custom_minimum_size = Vector2(0, 20) -layout_mode = 2 -size_flags_horizontal = 5 - -[node name="StatusBar" parent="VBoxContainer/Header" instance=ExtResource("3")] -layout_mode = 2 -size_flags_horizontal = 11 - -[node name="MainPanel" parent="VBoxContainer" instance=ExtResource("7")] -layout_mode = 2 - -[node name="Monitor" parent="VBoxContainer" instance=ExtResource("4")] -layout_mode = 2 - -[connection signal="failure_next" from="VBoxContainer/Header/StatusBar" to="VBoxContainer/MainPanel" method="_on_StatusBar_failure_next"] -[connection signal="failure_prevous" from="VBoxContainer/Header/StatusBar" to="VBoxContainer/MainPanel" method="_on_StatusBar_failure_prevous"] -[connection signal="run_testcase" from="VBoxContainer/MainPanel" to="." method="_on_MainPanel_run_testcase"] -[connection signal="run_testsuite" from="VBoxContainer/MainPanel" to="." method="_on_MainPanel_run_testsuite"] -[connection signal="jump_to_orphan_nodes" from="VBoxContainer/Monitor" to="VBoxContainer/MainPanel" method="_on_Monitor_jump_to_orphan_nodes"] diff --git a/addons/gdUnit4/src/ui/ScriptEditorControls.gd b/addons/gdUnit4/src/ui/ScriptEditorControls.gd deleted file mode 100644 index 29d2992..0000000 --- a/addons/gdUnit4/src/ui/ScriptEditorControls.gd +++ /dev/null @@ -1,128 +0,0 @@ -# A tool to provide extended script editor functionallity -class_name ScriptEditorControls -extends RefCounted - - -# https://github.com/godotengine/godot/blob/master/editor/plugins/script_editor_plugin.h -# the Editor menu popup items -enum { - FILE_NEW, - FILE_NEW_TEXTFILE, - FILE_OPEN, - FILE_REOPEN_CLOSED, - FILE_OPEN_RECENT, - FILE_SAVE, - FILE_SAVE_AS, - FILE_SAVE_ALL, - FILE_THEME, - FILE_RUN, - FILE_CLOSE, - CLOSE_DOCS, - CLOSE_ALL, - CLOSE_OTHER_TABS, - TOGGLE_SCRIPTS_PANEL, - SHOW_IN_FILE_SYSTEM, - FILE_COPY_PATH, - FILE_TOOL_RELOAD_SOFT, - SEARCH_IN_FILES, - REPLACE_IN_FILES, - SEARCH_HELP, - SEARCH_WEBSITE, - HELP_SEARCH_FIND, - HELP_SEARCH_FIND_NEXT, - HELP_SEARCH_FIND_PREVIOUS, - WINDOW_MOVE_UP, - WINDOW_MOVE_DOWN, - WINDOW_NEXT, - WINDOW_PREV, - WINDOW_SORT, - WINDOW_SELECT_BASE = 100 -} - - -# Returns the EditorInterface instance -static func editor_interface() -> EditorInterface: - if not Engine.has_meta("GdUnitEditorPlugin"): - return null - var plugin :EditorPlugin = Engine.get_meta("GdUnitEditorPlugin") - return plugin.get_editor_interface() - - -# Returns the ScriptEditor instance -static func script_editor() -> ScriptEditor: - return editor_interface().get_script_editor() - - -# Saves the given script and closes if requested by -# The script is saved when is opened in the editor. -# The script is closed when is set to true. -static func save_an_open_script(script_path :String, close := false) -> bool: - #prints("save_an_open_script", script_path, close) - if !Engine.is_editor_hint(): - return false - var interface := editor_interface() - var editor := script_editor() - var editor_popup := _menu_popup() - # search for the script in all opened editor scrips - for open_script in editor.get_open_scripts(): - if open_script.resource_path == script_path: - # select the script in the editor - interface.edit_script(open_script, 0); - # save and close - editor_popup.id_pressed.emit(FILE_SAVE) - if close: - editor_popup.id_pressed.emit(FILE_CLOSE) - return true - return false - - -# Saves all opened script -static func save_all_open_script() -> void: - if Engine.is_editor_hint(): - _menu_popup().id_pressed.emit(FILE_SAVE_ALL) - - -static func close_open_editor_scripts() -> void: - if Engine.is_editor_hint(): - _menu_popup().id_pressed.emit(CLOSE_ALL) - - -# Edits the given script. -# The script is openend in the current editor and selected in the file system dock. -# The line and column on which to open the script can also be specified. -# The script will be open with the user-configured editor for the script's language which may be an external editor. -static func edit_script(script_path :String, line_number :int = -1): - var interface := editor_interface() - var file_system := interface.get_resource_filesystem() - file_system.update_file(script_path) - var file_system_dock := interface.get_file_system_dock() - file_system_dock.navigate_to_path(script_path) - interface.select_file(script_path) - var script = load(script_path) - interface.edit_script(script, line_number) - - -# Register the given context menu to the current script editor -# Is called when the plugin is activated -# The active script is connected to the ScriptEditorContextMenuHandler -static func register_context_menu(menu :Array[GdUnitContextMenuItem]) -> void: - Engine.get_main_loop().root.call_deferred("add_child", ScriptEditorContextMenuHandler.new(menu, script_editor())) - - -# Unregisteres all registerend context menus and gives the ScriptEditorContextMenuHandler> free -# Is called when the plugin is deactivated -static func unregister_context_menu() -> void: - ScriptEditorContextMenuHandler.dispose(script_editor()) - - -static func _menu_popup() -> PopupMenu: - return script_editor().get_child(0).get_child(0).get_child(0).get_popup() - - -static func _print_menu(popup :PopupMenu): - for itemIndex in popup.item_count: - prints( "get_item_id", popup.get_item_id(itemIndex)) - prints( "get_item_accelerator", popup.get_item_accelerator(itemIndex)) - prints( "get_item_shortcut", popup.get_item_shortcut(itemIndex)) - prints( "get_item_text", popup.get_item_text(itemIndex)) - prints() diff --git a/addons/gdUnit4/src/ui/assets/PlayDebug.svg b/addons/gdUnit4/src/ui/assets/PlayDebug.svg deleted file mode 100644 index a0be440..0000000 --- a/addons/gdUnit4/src/ui/assets/PlayDebug.svg +++ /dev/null @@ -1,121 +0,0 @@ - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/addons/gdUnit4/src/ui/assets/PlayDebug.svg.import b/addons/gdUnit4/src/ui/assets/PlayDebug.svg.import deleted file mode 100644 index d6a3fcc..0000000 --- a/addons/gdUnit4/src/ui/assets/PlayDebug.svg.import +++ /dev/null @@ -1,38 +0,0 @@ -[remap] - -importer="texture" -type="CompressedTexture2D" -uid="uid://tkrsqx2oxw6o" -path="res://.godot/imported/PlayDebug.svg-d3618ec14e2e4cb6b467c3249916f8dd.ctex" -metadata={ -"has_editor_variant": true, -"vram_texture": false -} - -[deps] - -source_file="res://addons/gdUnit4/src/ui/assets/PlayDebug.svg" -dest_files=["res://.godot/imported/PlayDebug.svg-d3618ec14e2e4cb6b467c3249916f8dd.ctex"] - -[params] - -compress/mode=0 -compress/high_quality=false -compress/lossy_quality=0.7 -compress/hdr_compression=1 -compress/normal_map=0 -compress/channel_pack=0 -mipmaps/generate=false -mipmaps/limit=-1 -roughness/mode=0 -roughness/src_normal="" -process/fix_alpha_border=true -process/premult_alpha=false -process/normal_map_invert_y=false -process/hdr_as_srgb=false -process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=1 -svg/scale=1.0 -editor/scale_with_editor_scale=true -editor/convert_colors_with_editor_theme=true diff --git a/addons/gdUnit4/src/ui/assets/PlayOverall.svg b/addons/gdUnit4/src/ui/assets/PlayOverall.svg deleted file mode 100644 index 5efccab..0000000 --- a/addons/gdUnit4/src/ui/assets/PlayOverall.svg +++ /dev/null @@ -1,61 +0,0 @@ - - - - - - image/svg+xml - - - - - - - - - diff --git a/addons/gdUnit4/src/ui/assets/PlayOverall.svg.import b/addons/gdUnit4/src/ui/assets/PlayOverall.svg.import deleted file mode 100644 index 4e73567..0000000 --- a/addons/gdUnit4/src/ui/assets/PlayOverall.svg.import +++ /dev/null @@ -1,38 +0,0 @@ -[remap] - -importer="texture" -type="CompressedTexture2D" -uid="uid://de1q5raia84bn" -path="res://.godot/imported/PlayOverall.svg-d07157735d6bab5d74465733e8213328.ctex" -metadata={ -"has_editor_variant": true, -"vram_texture": false -} - -[deps] - -source_file="res://addons/gdUnit4/src/ui/assets/PlayOverall.svg" -dest_files=["res://.godot/imported/PlayOverall.svg-d07157735d6bab5d74465733e8213328.ctex"] - -[params] - -compress/mode=0 -compress/high_quality=false -compress/lossy_quality=0.7 -compress/hdr_compression=1 -compress/normal_map=0 -compress/channel_pack=0 -mipmaps/generate=false -mipmaps/limit=-1 -roughness/mode=0 -roughness/src_normal="" -process/fix_alpha_border=true -process/premult_alpha=false -process/normal_map_invert_y=false -process/hdr_as_srgb=false -process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=1 -svg/scale=1.0 -editor/scale_with_editor_scale=true -editor/convert_colors_with_editor_theme=true diff --git a/addons/gdUnit4/src/ui/assets/TestCase.svg b/addons/gdUnit4/src/ui/assets/TestCase.svg deleted file mode 100644 index e6a40f0..0000000 --- a/addons/gdUnit4/src/ui/assets/TestCase.svg +++ /dev/null @@ -1,62 +0,0 @@ - - - - - - image/svg+xml - - - - - - - - - - diff --git a/addons/gdUnit4/src/ui/assets/TestCase.svg.import b/addons/gdUnit4/src/ui/assets/TestCase.svg.import deleted file mode 100644 index 0e29cda..0000000 --- a/addons/gdUnit4/src/ui/assets/TestCase.svg.import +++ /dev/null @@ -1,38 +0,0 @@ -[remap] - -importer="texture" -type="CompressedTexture2D" -uid="uid://chwatiivlcovb" -path="res://.godot/imported/TestCase.svg-f6ee172ad0e725d3612bec1b6f3c8078.ctex" -metadata={ -"has_editor_variant": true, -"vram_texture": false -} - -[deps] - -source_file="res://addons/gdUnit4/src/ui/assets/TestCase.svg" -dest_files=["res://.godot/imported/TestCase.svg-f6ee172ad0e725d3612bec1b6f3c8078.ctex"] - -[params] - -compress/mode=0 -compress/high_quality=false -compress/lossy_quality=0.7 -compress/hdr_compression=1 -compress/normal_map=0 -compress/channel_pack=0 -mipmaps/generate=false -mipmaps/limit=-1 -roughness/mode=0 -roughness/src_normal="" -process/fix_alpha_border=true -process/premult_alpha=false -process/normal_map_invert_y=false -process/hdr_as_srgb=false -process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=1 -svg/scale=1.0 -editor/scale_with_editor_scale=true -editor/convert_colors_with_editor_theme=true diff --git a/addons/gdUnit4/src/ui/assets/TestCaseError.svg b/addons/gdUnit4/src/ui/assets/TestCaseError.svg deleted file mode 100644 index 0858086..0000000 --- a/addons/gdUnit4/src/ui/assets/TestCaseError.svg +++ /dev/null @@ -1,65 +0,0 @@ - - - - - - image/svg+xml - - - - - - - - - - - diff --git a/addons/gdUnit4/src/ui/assets/TestCaseError.svg.import b/addons/gdUnit4/src/ui/assets/TestCaseError.svg.import deleted file mode 100644 index 3b8ea16..0000000 --- a/addons/gdUnit4/src/ui/assets/TestCaseError.svg.import +++ /dev/null @@ -1,38 +0,0 @@ -[remap] - -importer="texture" -type="CompressedTexture2D" -uid="uid://bafys3jjkjhqw" -path="res://.godot/imported/TestCaseError.svg-373307086979f3f0e012eb3660cc91ec.ctex" -metadata={ -"has_editor_variant": true, -"vram_texture": false -} - -[deps] - -source_file="res://addons/gdUnit4/src/ui/assets/TestCaseError.svg" -dest_files=["res://.godot/imported/TestCaseError.svg-373307086979f3f0e012eb3660cc91ec.ctex"] - -[params] - -compress/mode=0 -compress/high_quality=false -compress/lossy_quality=0.7 -compress/hdr_compression=1 -compress/normal_map=0 -compress/channel_pack=0 -mipmaps/generate=false -mipmaps/limit=-1 -roughness/mode=0 -roughness/src_normal="" -process/fix_alpha_border=true -process/premult_alpha=false -process/normal_map_invert_y=false -process/hdr_as_srgb=false -process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=1 -svg/scale=1.0 -editor/scale_with_editor_scale=true -editor/convert_colors_with_editor_theme=true diff --git a/addons/gdUnit4/src/ui/assets/TestCaseFailed.svg b/addons/gdUnit4/src/ui/assets/TestCaseFailed.svg deleted file mode 100644 index 10531b8..0000000 --- a/addons/gdUnit4/src/ui/assets/TestCaseFailed.svg +++ /dev/null @@ -1,66 +0,0 @@ - - - - - - image/svg+xml - - - - - - - - - - - diff --git a/addons/gdUnit4/src/ui/assets/TestCaseFailed.svg.import b/addons/gdUnit4/src/ui/assets/TestCaseFailed.svg.import deleted file mode 100644 index 636cc7c..0000000 --- a/addons/gdUnit4/src/ui/assets/TestCaseFailed.svg.import +++ /dev/null @@ -1,38 +0,0 @@ -[remap] - -importer="texture" -type="CompressedTexture2D" -uid="uid://b4ej20o0cedro" -path="res://.godot/imported/TestCaseFailed.svg-df47525fd14d5e4149690cacd8eb08db.ctex" -metadata={ -"has_editor_variant": true, -"vram_texture": false -} - -[deps] - -source_file="res://addons/gdUnit4/src/ui/assets/TestCaseFailed.svg" -dest_files=["res://.godot/imported/TestCaseFailed.svg-df47525fd14d5e4149690cacd8eb08db.ctex"] - -[params] - -compress/mode=0 -compress/high_quality=false -compress/lossy_quality=0.7 -compress/hdr_compression=1 -compress/normal_map=0 -compress/channel_pack=0 -mipmaps/generate=false -mipmaps/limit=-1 -roughness/mode=0 -roughness/src_normal="" -process/fix_alpha_border=true -process/premult_alpha=false -process/normal_map_invert_y=false -process/hdr_as_srgb=false -process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=1 -svg/scale=1.0 -editor/scale_with_editor_scale=true -editor/convert_colors_with_editor_theme=true diff --git a/addons/gdUnit4/src/ui/assets/TestCaseSuccess.svg b/addons/gdUnit4/src/ui/assets/TestCaseSuccess.svg deleted file mode 100644 index b080b69..0000000 --- a/addons/gdUnit4/src/ui/assets/TestCaseSuccess.svg +++ /dev/null @@ -1,66 +0,0 @@ - - - - - - image/svg+xml - - - - - - - - - - - diff --git a/addons/gdUnit4/src/ui/assets/TestCaseSuccess.svg.import b/addons/gdUnit4/src/ui/assets/TestCaseSuccess.svg.import deleted file mode 100644 index cee02fc..0000000 --- a/addons/gdUnit4/src/ui/assets/TestCaseSuccess.svg.import +++ /dev/null @@ -1,38 +0,0 @@ -[remap] - -importer="texture" -type="CompressedTexture2D" -uid="uid://4yciagtse0nx" -path="res://.godot/imported/TestCaseSuccess.svg-aaf852c6aeda68c93a410c7480502895.ctex" -metadata={ -"has_editor_variant": true, -"vram_texture": false -} - -[deps] - -source_file="res://addons/gdUnit4/src/ui/assets/TestCaseSuccess.svg" -dest_files=["res://.godot/imported/TestCaseSuccess.svg-aaf852c6aeda68c93a410c7480502895.ctex"] - -[params] - -compress/mode=0 -compress/high_quality=false -compress/lossy_quality=0.7 -compress/hdr_compression=1 -compress/normal_map=0 -compress/channel_pack=0 -mipmaps/generate=false -mipmaps/limit=-1 -roughness/mode=0 -roughness/src_normal="" -process/fix_alpha_border=true -process/premult_alpha=false -process/normal_map_invert_y=false -process/hdr_as_srgb=false -process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=1 -svg/scale=1.0 -editor/scale_with_editor_scale=true -editor/convert_colors_with_editor_theme=true diff --git a/addons/gdUnit4/src/ui/assets/TestCase_error_orphan.tres b/addons/gdUnit4/src/ui/assets/TestCase_error_orphan.tres deleted file mode 100644 index 49a70cd..0000000 --- a/addons/gdUnit4/src/ui/assets/TestCase_error_orphan.tres +++ /dev/null @@ -1,12 +0,0 @@ -[gd_resource type="AnimatedTexture" load_steps=3 format=2] - -[ext_resource path="res://addons/gdUnit4/src/ui/assets/orphan/TestCaseError1.svg" type="Texture2D" id=1] -[ext_resource path="res://addons/gdUnit4/src/ui/assets/orphan/TestCaseError2.svg" type="Texture2D" id=2] - -[resource] -flags = 4 -frames = 2 -fps = 1.1 -frame_0/texture = ExtResource( 1 ) -frame_1/texture = ExtResource( 2 ) -frame_1/delay_sec = 0.0 diff --git a/addons/gdUnit4/src/ui/assets/TestCase_failed_orphan.tres b/addons/gdUnit4/src/ui/assets/TestCase_failed_orphan.tres deleted file mode 100644 index 532b843..0000000 --- a/addons/gdUnit4/src/ui/assets/TestCase_failed_orphan.tres +++ /dev/null @@ -1,12 +0,0 @@ -[gd_resource type="AnimatedTexture" load_steps=3 format=2] - -[ext_resource path="res://addons/gdUnit4/src/ui/assets/orphan/TestCaseFailed2.svg" type="Texture2D" id=1] -[ext_resource path="res://addons/gdUnit4/src/ui/assets/orphan/TestCaseFailed1.svg" type="Texture2D" id=2] - -[resource] -flags = 4 -frames = 2 -fps = 1.1 -frame_0/texture = ExtResource( 2 ) -frame_1/texture = ExtResource( 1 ) -frame_1/delay_sec = 0.0 diff --git a/addons/gdUnit4/src/ui/assets/TestCase_success_orphan.tres b/addons/gdUnit4/src/ui/assets/TestCase_success_orphan.tres deleted file mode 100644 index c1b8a33..0000000 --- a/addons/gdUnit4/src/ui/assets/TestCase_success_orphan.tres +++ /dev/null @@ -1,12 +0,0 @@ -[gd_resource type="AnimatedTexture" load_steps=3 format=2] - -[ext_resource path="res://addons/gdUnit4/src/ui/assets/orphan/TestCaseSuccess1.svg" type="Texture2D" id=1] -[ext_resource path="res://addons/gdUnit4/src/ui/assets/orphan/TestCaseSuccess2.svg" type="Texture2D" id=2] - -[resource] -flags = 4 -frames = 2 -fps = 1.1 -frame_0/texture = ExtResource( 1 ) -frame_1/texture = ExtResource( 2 ) -frame_1/delay_sec = 0.0 diff --git a/addons/gdUnit4/src/ui/assets/TestSuite.svg b/addons/gdUnit4/src/ui/assets/TestSuite.svg deleted file mode 100644 index 3d9b84c..0000000 --- a/addons/gdUnit4/src/ui/assets/TestSuite.svg +++ /dev/null @@ -1,70 +0,0 @@ - - - - - - image/svg+xml - - - - - - - - - - - diff --git a/addons/gdUnit4/src/ui/assets/TestSuite.svg.import b/addons/gdUnit4/src/ui/assets/TestSuite.svg.import deleted file mode 100644 index d23a52f..0000000 --- a/addons/gdUnit4/src/ui/assets/TestSuite.svg.import +++ /dev/null @@ -1,38 +0,0 @@ -[remap] - -importer="texture" -type="CompressedTexture2D" -uid="uid://bo6xbdqouosbs" -path="res://.godot/imported/TestSuite.svg-f3ba31540dedae19e6c1b7b050a1b5d7.ctex" -metadata={ -"has_editor_variant": true, -"vram_texture": false -} - -[deps] - -source_file="res://addons/gdUnit4/src/ui/assets/TestSuite.svg" -dest_files=["res://.godot/imported/TestSuite.svg-f3ba31540dedae19e6c1b7b050a1b5d7.ctex"] - -[params] - -compress/mode=0 -compress/high_quality=false -compress/lossy_quality=0.7 -compress/hdr_compression=1 -compress/normal_map=0 -compress/channel_pack=0 -mipmaps/generate=false -mipmaps/limit=-1 -roughness/mode=0 -roughness/src_normal="" -process/fix_alpha_border=true -process/premult_alpha=false -process/normal_map_invert_y=false -process/hdr_as_srgb=false -process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=1 -svg/scale=1.0 -editor/scale_with_editor_scale=true -editor/convert_colors_with_editor_theme=true diff --git a/addons/gdUnit4/src/ui/assets/clock.svg b/addons/gdUnit4/src/ui/assets/clock.svg deleted file mode 100644 index 88d9d07..0000000 --- a/addons/gdUnit4/src/ui/assets/clock.svg +++ /dev/null @@ -1,151 +0,0 @@ - -image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/addons/gdUnit4/src/ui/assets/clock.svg.import b/addons/gdUnit4/src/ui/assets/clock.svg.import deleted file mode 100644 index 9422c2c..0000000 --- a/addons/gdUnit4/src/ui/assets/clock.svg.import +++ /dev/null @@ -1,38 +0,0 @@ -[remap] - -importer="texture" -type="CompressedTexture2D" -uid="uid://dmu35vmwstrwg" -path="res://.godot/imported/clock.svg-b16f5d68e1dedc017f1ce1df1e590248.ctex" -metadata={ -"has_editor_variant": true, -"vram_texture": false -} - -[deps] - -source_file="res://addons/gdUnit4/src/ui/assets/clock.svg" -dest_files=["res://.godot/imported/clock.svg-b16f5d68e1dedc017f1ce1df1e590248.ctex"] - -[params] - -compress/mode=0 -compress/high_quality=false -compress/lossy_quality=0.7 -compress/hdr_compression=1 -compress/normal_map=0 -compress/channel_pack=0 -mipmaps/generate=false -mipmaps/limit=-1 -roughness/mode=0 -roughness/src_normal="" -process/fix_alpha_border=true -process/premult_alpha=false -process/normal_map_invert_y=false -process/hdr_as_srgb=false -process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=1 -svg/scale=1.0 -editor/scale_with_editor_scale=true -editor/convert_colors_with_editor_theme=true diff --git a/addons/gdUnit4/src/ui/assets/errors.svg b/addons/gdUnit4/src/ui/assets/errors.svg deleted file mode 100644 index c8af23c..0000000 --- a/addons/gdUnit4/src/ui/assets/errors.svg +++ /dev/null @@ -1,57 +0,0 @@ - - - - - - image/svg+xml - - - - - - - - - diff --git a/addons/gdUnit4/src/ui/assets/errors.svg.import b/addons/gdUnit4/src/ui/assets/errors.svg.import deleted file mode 100644 index 05cf51c..0000000 --- a/addons/gdUnit4/src/ui/assets/errors.svg.import +++ /dev/null @@ -1,38 +0,0 @@ -[remap] - -importer="texture" -type="CompressedTexture2D" -uid="uid://bftg23n8uymlk" -path="res://.godot/imported/errors.svg-53aa38a5c5d3309095cd34f36952f16b.ctex" -metadata={ -"has_editor_variant": true, -"vram_texture": false -} - -[deps] - -source_file="res://addons/gdUnit4/src/ui/assets/errors.svg" -dest_files=["res://.godot/imported/errors.svg-53aa38a5c5d3309095cd34f36952f16b.ctex"] - -[params] - -compress/mode=0 -compress/high_quality=false -compress/lossy_quality=0.7 -compress/hdr_compression=1 -compress/normal_map=0 -compress/channel_pack=0 -mipmaps/generate=false -mipmaps/limit=-1 -roughness/mode=0 -roughness/src_normal="" -process/fix_alpha_border=true -process/premult_alpha=false -process/normal_map_invert_y=false -process/hdr_as_srgb=false -process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=1 -svg/scale=1.0 -editor/scale_with_editor_scale=true -editor/convert_colors_with_editor_theme=true diff --git a/addons/gdUnit4/src/ui/assets/failures.svg b/addons/gdUnit4/src/ui/assets/failures.svg deleted file mode 100644 index 1a7a96e..0000000 --- a/addons/gdUnit4/src/ui/assets/failures.svg +++ /dev/null @@ -1,56 +0,0 @@ - - - - - - image/svg+xml - - - - - - - - - diff --git a/addons/gdUnit4/src/ui/assets/failures.svg.import b/addons/gdUnit4/src/ui/assets/failures.svg.import deleted file mode 100644 index 7270f85..0000000 --- a/addons/gdUnit4/src/ui/assets/failures.svg.import +++ /dev/null @@ -1,38 +0,0 @@ -[remap] - -importer="texture" -type="CompressedTexture2D" -uid="uid://davt5rxc7ao4s" -path="res://.godot/imported/failures.svg-8659a946adf9b0616e867a8bf0855d3d.ctex" -metadata={ -"has_editor_variant": true, -"vram_texture": false -} - -[deps] - -source_file="res://addons/gdUnit4/src/ui/assets/failures.svg" -dest_files=["res://.godot/imported/failures.svg-8659a946adf9b0616e867a8bf0855d3d.ctex"] - -[params] - -compress/mode=0 -compress/high_quality=false -compress/lossy_quality=0.7 -compress/hdr_compression=1 -compress/normal_map=0 -compress/channel_pack=0 -mipmaps/generate=false -mipmaps/limit=-1 -roughness/mode=0 -roughness/src_normal="" -process/fix_alpha_border=true -process/premult_alpha=false -process/normal_map_invert_y=false -process/hdr_as_srgb=false -process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=1 -svg/scale=1.0 -editor/scale_with_editor_scale=true -editor/convert_colors_with_editor_theme=true diff --git a/addons/gdUnit4/src/ui/assets/icon.png b/addons/gdUnit4/src/ui/assets/icon.png deleted file mode 100644 index eeac2924071bb62e97d1f372ca66c0e7e1a256f0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 13817 zcmV86eRa1bI>dphKGBpiGa9r`0RXV%ox-M>z0!YtU|LCt zr;2$`ZCpHcXYESOutZ1=c8E1r2&c`&XtwjE{S6z8{bD`=UDv0c6H{bhZ<8a z&ryPI-%cDN_!ITuPZ&uhBUhA;O8C&$OZU-F4^J%!{%pGke+a>z^}@!{h62;wnLDbM zM8@kl_22}s@mjC0wePFKrU6lN*E}|DZGflgmdhUa;rS^h`94LTsE$u21VVWe_=0*g zn4H#A7sb5O*gx{cpYEMf9Po*DBQ99Mp84!A84=}8cVum@xFb4Fqf!lu2bsb zlzdlUJ#k-z;majUB>&IUGjpEKOPTQ1iPD(M2XyMYl4`85AFe^2)zMfmvDf3y$cXoU z_{YRXf6ueKaX|_8MQ`rm;;ZYf&e&G{c$!t}tsRjBwwJ!k{qm=b*c3(Gx%%mOulZq` zK5^CU->#mh(JW-*G+pjXthItaR7*;Tbo|uh#J{fj-&vddFvV`k1tHjTUj8v6^04!v z{(Gt)He|-I%0cmzU0H2UghwD%1eL=oQdS&!Dm= z_nW5XsmlU7SP<#af=!&FgHp3W8!vKF8kKsW{+VEmm62Eeo_6SpR+OwbCaB)8g{U{9=$YEL`+XV<}g)wLY2ACc#*o2`> zGA{;oO#S3^Dm$HOL6qNlcAloF-s z?Qa{rH%JZvYG}fO-5s#TYHDPA@KM|*I`)N#Ad}GObOEAA3-;zardWtW zyr5Q^P$2;qjD8LDaKQ!u(RpD#LPG`5&=<^hI&c99wj^FykI+zIk5Qfs^&I?3j~8qw zO)jWMXsD=5(d-TN9Q;Yo7iG5-xN)bSXXU~PGLKcJK_dVl;7T$dc0s)C+Ri~7UUbj_4tQR$dGLU zL<1MF+$m!|DU8Vfzn^b4pR9G+u8h`VTyMkE+kwOW8nees17~)YOHF*Jr_rM!Rsj>2 zZ3|fBx@~{W6mwCX+jhReipKrr*ZXtK9xd3HF8hAgkbL8MS{pov{0kAbmmc((Wb37m ztsfkS{;PMKx(~#&mSH?vb(KHI?2*B|GoJpr*TA1nZ|sv6)nA$IQf&*<3G90+a99x5 z*N#uS;=6|@c=Yre|8A|G<{bA@z=e&LgEFFvc_r@=>OMC z9yX`<0N4Jy@hJ8tRi(eB=(1pA^l_i*X?m5+_uS^cnKP9{YuyQSMj zk482-QMd7=xO-;$jcfirV}+}8#(#V|yYi=lo6EjGkVtb3;>Y!^jJqi9-hKaf(HCdW z8UD(#UiBM}y1x6drt)!9B>aY=3-8((jJcKFuH zzZvFdQDJ>Lc!+g2RBkPn_s{Qh*AI_Qe|_Amg_jn*zwPV#y(K|;R5B_Ky0jRW3aVVT zM-5Jvvu?ZOrr++&{J3pfrv&@TyH;O2=J$q=_46{%T~8z#_T7~zZD=~0zc6*)&kxQz z)tSCqGUJ(FGPBm6+|n;bm#DrdG!FQGcw_?eFU8n0Aa=)=btR)s$Ev!O{+Bp8OU}Aw z+{7){r*Ch0+-E!XqQ9@XWb~H0k9G6=wYn)NhK6QrGP0dX15!4W7WG~9+2*3j$=9sA zcT#0%CVct*-(5fKqaz>0&dZ|d$iOCz3F85pf__Q{GOPKE*Ht%lvJ(nGGJpgC+A}`_ z0Lz)55&)fxO0(CPX(ChYRz06q?72_;JZ!?ZCHtZ->rYXX=SyXq0L!msWdFdZ*D7Ll z4|OaIN30MfKU`(-dcg9KOE`c%me^)0Hpam2F;29 zy&JM~BLMe7B#eqf|G!RNKXB~$IbSZJ+MVc?U|*7(ry9P#sI=dt^f)F$+3^J~5UVVx z`@O1h`+}s4TTgc0->Ly*e$_0Kg{dkT|Db)VyPS(Lbhr z_}k*#m5Q@%cg+9zlj&vlZw+(%g@wAC!+?Ou?u2?efH_?jY5;IrqQP*t0Z4S&UbnkA zS;<7ZwJ3BrgDdY@^THrPjpzK3Kpg;-{i2cDH)?eJ;aa}`@Hf{e1bIr#;9feq3pY)e z;IZvU9pG|zd;);+t&U`6n2_Digzq<`cC@38*k}i4Osv2+Ytz6xn^j#&;J_+xgvh@M zpmkb7b5Si_x#8HSX9jMEM`d}zqd#9iZhO_!y2 zV;~5vRm~v-8gR!=`Ov7F-~S?X0tWYQ#IL&(&fN$^i~y(x05z?ym7OBYC3Tngx$&V* z^}9YSYuV;fsxHaRQw`r(RGv8@JtnLo{4o(}8diouos*!b){ftPEkXINVi#wo0=T)= z5t=5rYGDy3jcL7OSX-~cD<2F*O`RG5Fkw_Ru32;vG<{xAaqtY@TG1c*$6IG==CpMX ztgcG{DWbF_adPG+!@%C3=(FOs@_bFHOToVGrf3$H=+ucQj z$JXC|kH;(T<-q1RzlOFI>Z65`c>b-y9SC*DW1WRoa_8;Xi&w5>a=t6t8Z$Su!|n1g ze*nCa0q)F?X9|i8_+VwG%eLxTHD3L2;5jRZXS~~uW|ZuC!DfGCp6fF(F*fWL8U_GZ zwKsAgocZxgdWr?tUG6#vJ|WJITdpbq$9eOFuy}C}z?@yNFZ=iU0T)#}?%~qBTk6AH z_^_Ie)Pl2k_V^o^WyC$V97crB{S7dDP(ALw3nn)=mrh(IltjcilFOC}qpI;gR~>J)8asFfZ+7~gVg3k=PS#-9lq?wf#zE4l zU}>@|FCMSFp98z2*(!2&ce15Wvzl4} z?NbZVERCyg9*H=+RbXWNt7RK=c<;)>{^<*+N9mNQj6S@59>zUt3aH?es5sU(52zW< zZb#<>3rZ_=D67=MX6NwU7g=pq-`3POs&Kd<8d{BrH$E8vV+%EeRX8C!J_2BCcU_<9 z*;wFoVBheVrQbg~^VgPbGyoJ&PncD=_PFT1Q9jHcm03CNQeSKWu&33LgGZzB+3G%S zz4}^mx=Mq0KFfyHc77v=W<`j?hj6#sIDT*hJk{PW2V5b+OlmU1H zKqf$43<_x=#IrontQE3&5JcVyZ#QCUc9-fSKhOIJz%^~#+9^M?pWJZq^z{|L>Sp!{ zZ`WLa>^oeIw>~}8;d$3C8i0volF$7v1NisqB2?}y>cprVnmzO4(d_S`1t6mNrPBhS z7=Y{C*&U&X(qQ1dGw#^`=&{QQBLo120Dc9q4!}2PwoB)sIgd!^gc<;7jsYc81KJ&Tb@HHyUH2Ci)tAY( z&{e$+6BsJ0y>lcgY_qT_WEnS1NI}#9*HPo4K%E$Yfp<@T`}f>oceQPEDcHHWxpMxj zq>+t3pA471y}&3cF?M_sfVu`d8jP)zcl9Cy{&wRq#12hyc@3vmAtECNib!>r=Sg!6 zIIRMdn|NDIawG=cHT8{sj|{oZRl7$$df~DkW=tux|EwO-eeTjQou}Yr1D3zjKHA$I z^QQH|g6VzQ9XHY}Uj6Z z-tFC)oivSich@4lmkwj~G}io72)&NO{Aqo`wXmq|Y7p@88^1v?drsX*F%)jT<6?|X z(zI){?LLjXfiVk=nVFI1(uO(e<{~Z5*b;{h#I51&DNq4~R z5b?&x2T)MdtOwIHg=yn^W8SpBP`Rk`@cWS(eEI$HcB+Lpa2$(JqVMzIoC)=ssVT0FbujDPId1cXuz(|JwGY>U6O?dX0;DV6e}&*TW5af{S}M< z?9046j>q}ezo7r?1Ml?OQu2^8Ju)oWA;xSK@WR`BajNXRt(Op>sMLgCcb^7LQ&1^6 zSnUGx3mdTF+hbV2wFGuY=PGl{GQqKv4fu6W1qcC>6LsL&<`sGG!D{^XlLN3j&cBKo zO*{_d*J4;s9MoqLAw?t`uzP2j8@JbHW-S?tbaiX-B6bl)+e@nAN1d3mdGltUYCw7| zs8`+d^}LCc?t3P_vnHP~UBzM*u>769I9=tPWg`%%RV*frNrqa*VfD`^LEPjni;q>~ zzCVvdWP}nTMdQWYwK(y$>yk0*ga|zP;CMs{t*vnO1J&3xGCFR}15@1B(sP5?g1z9m zyx8dlmRe<>C|}12;|Hv^W}z;x44qK>NKJW|jDCb1CXe8bAmEMFEUP z0Y=3kJu413_ie9R+YeOZ-p{Y-l$OwAlIF=a0nRcLNUdqom=mi*Ohn za5V9tbxLqaIxx`}&dV+#1n+!ys7DDk094o7@Wv+x@Zjwuku36fU_t`KiHYE88ib<2 z5CWAfgX$<_Whdy?f=vj4^LRZd9gBs_CL%2{%1%7*^+kWF>r3GlRaJKuet z*z#=w_I`X2>XAw4srgdkV>L*K(|F~))ZK`VR3RzBKPjkvp<4>JZP#fmeq}aNlD%7@ zJuzufHYP60#_KnK3o2d@CaUKZYT{TLw_Y;@|9Nc}1V03W#K&vVzke*^53Lx-wiwRX*=f@5jie%(+g6~Xru?N)+K2!UnODcrm6Ixv1rT}%PE zsQHMpd`{B#)k9X?&%*<%qcH2lSUOW(~sU83Ql0>j&O9@u2 zIE0TsJ^-HgO?{v!;D#%6keVF))f(`oR7Vi8Y%Ib9>#z5#P`6~tRf90?FC)=YJr}2r z>y6$?z6nZPoCa^dy#V?7ci{f}FZN2P0f1pBOqr67x8GiX6DRJ*Wta8$Y25kK`(o74 zgpN-P5Zy+wEt`vR|Arenb*U$Is0| z{?8{N%LD(L2-F9e0evOE1l7Cy2lA-;XuNSKxPfGfWXxkTg6XQBas7kl550 zQ}vf)=GeJNPEKyu=U@IZ5(x=fELrj;B+30enk-K2&#%SsoY1AB4*3QRaY{(haG3wV z#T}koaP%1V-ghs?Z`q18oBI`)fWo%8I2>QH1alvG1W8G*y%S$p^f|O+dv|*7q2TeC zcXnvczUii6c<;T1t*k%?4;{dN4&R6J$vZ%Lc_4r5F6V*D*dgu z_TskNzV|qX-bs2ayK8ipEKyzApb5DuV%v4vE1}--!3S`TABSsx`pKhE0{{hriM2Jj z`SsVZeZmC%y7?EE=S~0DB_Kl2aXCLxZ0t~|hYwH0n{UoPCs2eCytU?K+@+X=va2_F z7HR-s2&Bo?$h%=VuK8s=cJJHW?vz_^y$DxbHOON(WtC>^KG3;nt7VSF<2LCD!Y){lGe1L9n4-k#U4`SsW5A-MyK$G7WFz!{>2BcwVuf2BpoABnFhhy;I^Lp+3tKP<@c`t)RdVV?dggm+q|Jrs9dKLbH ztjw%)$D^WDSiXEVmMrgwvCe&7MbJAC&e zZp(z4YW#NV?GV~{kX?G`Xpm02bhgE|>ogomTHO29lGZ}q0=*uFM57uQ!+`fa>L2Y$y7Uw(x}*Ia$>7{}5OD$G#$ z;icGK)PTQyvZ%xJ6f6zK?0OYFTu_ITB_`C?+fY+yLq(0nD@(|@alO&M|M?faGrmy0@Hc#yw+f4#$i~tY@^3(4;R$oa6uiC;J{p`gP1Wo=4Lwue3WZ*7(P3j-nYvd07cyrkfY$ zjlY&+ZQd#DIarO-3bWVdz4`Ocs|iM<5qk%%^1DZgXQ6%Y)#`gu z5{TVDDiF}TVxML6j*XDU`s|zEAr+a2P+#9VLFAAj39Vury9=Qc><-?Rt9R~zUhscj zlcB~&oIF<0dM+M9mVCm8Pm`i3Nan8Vzh}v5u^IB4V*}t-u_E6fnP!|k(K?qp%hE{p zgR`2y37KG1Znk*OB9xVRZ0{@@NrI`oy!BlCBm)%f+s7&U=}EzHG@_%=do9-Dv|svd zhgw3psifUuciZ)$By@t^f%Ct_2QCYhWNov-!}{j$?GDPvD1O=6c{5x~G#W)KbD9YM zJqg+(xiF2&vi~Dsx=IKI+jGquWwPr^R9%Gth)inTH{~xK(MMaYzf#EA?XCNXm0f3Q z>M{-nNm1?lmb@xW9b7^t*tCL1dAa-DFwLOBaQgUY_l63!8gW^zH%FM*_JO3G!q^NpjUFQR>x$Spt`#Ic}j*;BhBO{~LvC>i# z9Dds1@+Tn`>{<^_GKqsk?b`$MlHXNAD%h&bSXg=Ycl*$%oSLER4B{`ThCIyK;Bq#dd&DKx9y9fLm)bIXtF;;((jq5JIson8) z>%1?e7$2*~gi*=3ad8fAxq7hQ=H`l!ZmBb|YP@^HD%}0WVwdehM~%csf4U0Se(*k+ zF6N%#83x76mSJGR`EVt(U4X)$5!8MNT8J_zF=luY#td(M!EH40*tV-2KW{C8Ah>@? zJ6>$Wkt4Op$%z91Oqw_eqaME=yL-RcWq%RsumN|*JPAV^)zD<(v1-*(&(~7G#L+33 zF)6-}gO*-fzwXW$VKFRief0&G%v9f=k;q zz5VuHnBDlioMqnBKDcI4|DXyr&9=i62IWcFO}op{tYaU8z7 zc`2^==)+d};qTkapw9F@e$X3yb==w4?%4#oyPyu=eVc#oca27amu`Cx zeV1Pj)O+W{(rSK(4Op=LA9(mLkF;y6uD0UIC%1aMK0a2B*;CSe9@ig)T(AK^Gb$M$ zc^kJuDCd_R&SA_urW0aJKuc|e|q;FaNK#jxy2!1^YWcw{blAm z3*+0zNhy6hf5xPAkE=ETaN~8~qNYWla%^lY-h5Xof4%?!6?#cTK~(T#EcoFO(1#;A7WgBRY%K7VGe`sZ5bj7U68DM?Ht@SzuMh^>JC5&zn zr49g;n#HMuHCO`~3zoQlV~&f9!_`kb0X*>pN=izxud)J`@^TQh29fb`NJ~q{i1;{{ z*IIc2|DE$0^mBcSUhx)M>D$Lit9;+eA3Gum`Gu}aFrTHg(Sd=f%dz9|Uy;({9imrK z5}sc876>6IE-prCQ3+1lPeDN|kgm-_MtUE_40M0v5JK?3|C@&=p75-K0KlNEm~(lw zLqWF@YyhBj3X~tJ$6Nb1;?GY^?NDb;OG|Z|VxcXEcAduhC$~XA(|iA6pYbvICEJON zu<>51Pvu4j24%gB&%RoMIc>twD2hTx#@Wr5OT6l@uFi(P{N)FH^;LeyCnNNZQQbln zX$h_6Ky)fRf7ur(s`N1c)div?a9-a;S)s}@4vqvg9g3swNL}V!K1Yy3*-g5pPzEKUg@uQ-HHKRbl91RbU>8Gup4k}xnc z1{yU7qtSu7IvciaC_>))lQ>#x>avg4<{f1i+&>lr{cKkChLC51@)?OhYOq6g2#|RR zw2}d>;XoTy9R@E11wzoYwG>M~y$mr??Vl2RHWXvq8@r(%>6=yN>mLiTertOt|L%@n zr@$k3jX`9DFSl_se~3~Y=U^x>aT=dJM1uxJA!1S*K7V{G90#jGIYcP*3OMZ&py!|( zn&h)k1Hi#!ov$|@O3W4~e%f4&s}^Plb=;mO*gXU`P7B?%^Hpsj9?H}MS2hvMd8LPb0EvaWhT?)L>u}MK1Y9;d6Z`Y4@%4`>db3QMl&P{!lA96c#sP>5@Ti z{hetV7%?aT{rkq?`M37?RIWNLB?9-}a*@YOPost=U~vB!lC1MzDH|>2|P4SAu}TqhmLieurfYI1C2UB4|ExjEIf$W38{&P zWOhQb@{sHTWQhP;2I_(Fh`TJa8eeyA2(M>8ZBRVHpm+*5wQqBZGU}~7>dibFPFql0 zW-xrv8l zX%<@9ENA$2bM5RO^p;rW=bqd7ll+e0!Bc#}~ow1q3QTZO6QH?Ke;j4qXZ3KPoYL1+KhfRH%kONl{e4&RporQXkUo_i13L1=x1}X4RjrW9tfw`9)JCY1)47qbWme2Y$xPoo!dKp4b z-2*5BkPo2X%uham0*a!Hxw*M?ZflBtQANI=Kp(2@W$%^ znxYVu5`nMF*touwObt><^b}U2 zVi|#szVp`Kb^9s%13+&3GG*RQ-K&!XiZd7$Gyu(+pI!i3CI1ju86qd+@U)o;8~!m1HLx zMxs(#i5|oVj7lL=6f4lk6g}5?RK#~B;h>j-O_7eXig6+hPNbo>vq1C89CPO9yc0no z(DKh%7V2E_0-s=w-{IKPA=99^7lJ+d+a@>O`#cPwhv4l8~r}v(yA- zkhZ!b$F$cfKi^)8!&{qRZ=x7REa!m5hoI(&3Ta&#`_E=v+H#FW;vtLQ_q2g>Xkv-(d_UkxS z0a0=7vrdV|;N0;75xXSVUfpqoB0(uKB3R%te^=CqL>Ld&13s3W^A*`9Ap6SpQPpdI zsYF$Ab2N(GF4fAQM2aDVX4@zGpgn24+~FNVh(q(Noj9 zwv>5^+{7cwMLIRv794$$a7>p>GF}O`z#%B+2-!(IdZJmJ3TSKrxvaqR2kp!I>Z=SyZMBHAP+k_iEv!CA3I zO6*c8GVfFoM(%nNJ#P>wk4m(`pbU!1&w1L6C~fO6)hWs#=D>fcf|F@Olq2AW+ z-Lb-Ghtd)FG(C)yiUMGB*Pa}uflwWIi;Y-gfu@Zssl_3H6$9^CA#f5T;^@*J>D4TG zMmz;7@VXT(K|wqi_4Fzh^@R=KkJSNPEVkqc*@gU`D7Ao*0{^8dd6sLiI#0Q4SFkjA zdtkO{h$KU-w7~w`X_za_?QU+9T>vWt{xjf}5?InThdZxyY?&h`c!dNdAJ~lnR%9rL zQ?jTPj7#fCtWJp`@6JQhX)~-RjBp&N0x>zk$OJUYfZ=FRN*0ug1+8S7|5cs&UClM8 z?d{-u5o@f7bzu=HKIL{1^(!g|jr{VanhDvlt{vI|2*VRV>>?;HB3xvL2slK@b`i2e z0C7kl4iTc6hiu^?*aZ+75LFD=tY~Nk#6mwh9+`h{znx&R31H+PsAEbZ+bne81)I=p zzC$HL8MxZ>002&)V6-_+YK7XRU@JKWS558Mi_s~{FgpZTcoD{08yah^XsWlNq1Fmx znF-Dk3-}r{=XlcCTv3T-*ys zp4tx1#Dk?M&?*Luf&r`Iz{F{wj8sFF5eaRA29a?FM8s_2Rso%gg-*#LQlkLl zWBI*o!Cb5=I=Di%r)9)td- zzBV{64K|IqtE+u@tuWcu;{L7<)-e<+tDV7F8eO1KutXk`MT@bzrxN05Ao59A7A{wLujGQ{Pz> zJQE?wq%vp&dP!4p;5VSn2EJh9J=5DKCiD@n1$)EaXP8Wp{HCrB*b!U{riKy!Ms;^l!sQM{(IX+L)w7l|DK`5usOcL3HmsPvPM%?e6*vEmK(e z{pr$w-WvSGj*?7Zb_g(<1=Q9$QE}SNAIUe9`h!lDiUjuMxi-#j*C`aA_@ghMm!Yfw zvExuikztTO2KNP#l_794L{1iE4m@FqlUBmPXeeH(rg=>SBSb{9VoZ#J#K$Y>*hrN! zN~h39sudA>6>Cs*vOdkpQY+qiq&O%W6D_HabyP2ZVep4)1^ZBEuD1&kEDo_*NNs}D zP{-S9Yn^{4#-rT*@n8_4@#1e?C42dO>7+E4Lamp-l(hAzaaNrdhnpbNn zr#6z6A~XuhpyrsE7zL|iS&h>Q{qcOWzW6sg%>>EurzL~}>SApb=_%Unqeny!lVmy7 zYL_T0FFMRkl4!LE#AcTWZzZ&oml;_gj4V(LkqAqR6hlj(7>NQS)1ZUk-_cc0r&z3w z@y?|09-iPA6|_@-@sj^z0z)vWNKtz$+yj9oTUDkAI*1roZ~^)bqw1DKD)~ zvIaDSIm{mgg0fWep&uWf^p-z+?UZ1zyLVD$Ws>bCi!OMH2YLvQ;8gYlMLe}0w?2ChUweymQ8#(P-Ar2 zSh?=L*$s^b^XTF}O(ntbB?;5B5jYtq`x$>~fVkM-dUZ?uVuk#%~%REa!8v?f1 zI%oNoD-+7pA0}03HR1QlKPD0*qdeJGR+nU%v+lmBM}j$aK%EF?KD#bHvPt)Ta=Cu4 zx@*ZZ!}KuBIwvVlv%KkuY`SOd-E)H;6%`ckm^m+OT@YQ(eUMzPiw?HeSeW1Y8buR4n^38_DZWODUo%iB##{vR9uKBHTN z^@VKNocUZ{e?ybx>$aNMQeWW=LcuignCXb3AsKn z=eZ37)fUyVxEl46=mr%R%-F&(z9Vol>f-Fi#%TT(-XOoS_U=iwp`Mc~x{VZF@>-rQ zs*%0b(5SpGszH?;wxV<&2n94nI7ySikzXIfKWTHwD+9fA*j2iXV7FxUb9obVX7+Cp zjjDMu4Qe$P=6(6s?6rSZ(o)Qz65u5r{)Ap0?@4^$mLGw`wqX{)!IoF7#Nz zZp)mf^HK;+jnY}znHr07yn?4P)ecsrv2j5uFVP*5X#%TOfL$$_c(s^s(Fl2Vy>pW& zNPD`Ay*W@^(BJsXXMf3{X);o6VJ2(s+%Scm?W^ROI3>?8N}di%g@Rxp(*zEc2!~2` zI+aqjlM_$!8fl+7Lfps^dDof;rqqRcj=|=F7Hn7K=H}A7bQ61vV6wD!W`xp459dTS zUE!pYSdoe5L|Vf-o3kN>O0AJ80zv~aBSU0bB>iEDC{bu4lx&h|QX_J5sa-AZHS2`! zw2D5~U2V#t;14I*-Z1UO4GFA7Cn^LsU1?$ZsRXV!D^dxRL`6}A=xI<91W?N~Q~*(c z0s{aNEpvov{=W<;2t`<#mH|*aMIZuz#KI0j1c@awDA*;MSP4jz$jS|%P%pBi4v - - - - - image/svg+xml - - - - - - - - - - - diff --git a/addons/gdUnit4/src/ui/assets/orphan/TestCaseError1.svg.import b/addons/gdUnit4/src/ui/assets/orphan/TestCaseError1.svg.import deleted file mode 100644 index f2c1fe8..0000000 --- a/addons/gdUnit4/src/ui/assets/orphan/TestCaseError1.svg.import +++ /dev/null @@ -1,38 +0,0 @@ -[remap] - -importer="texture" -type="CompressedTexture2D" -uid="uid://bdl7p3rf6wpfw" -path="res://.godot/imported/TestCaseError1.svg-43d13ea25f9f8c66fecf5a0ab4a752ad.ctex" -metadata={ -"has_editor_variant": true, -"vram_texture": false -} - -[deps] - -source_file="res://addons/gdUnit4/src/ui/assets/orphan/TestCaseError1.svg" -dest_files=["res://.godot/imported/TestCaseError1.svg-43d13ea25f9f8c66fecf5a0ab4a752ad.ctex"] - -[params] - -compress/mode=0 -compress/high_quality=false -compress/lossy_quality=0.7 -compress/hdr_compression=1 -compress/normal_map=0 -compress/channel_pack=0 -mipmaps/generate=false -mipmaps/limit=-1 -roughness/mode=0 -roughness/src_normal="" -process/fix_alpha_border=true -process/premult_alpha=false -process/normal_map_invert_y=false -process/hdr_as_srgb=false -process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=1 -svg/scale=1.0 -editor/scale_with_editor_scale=true -editor/convert_colors_with_editor_theme=true diff --git a/addons/gdUnit4/src/ui/assets/orphan/TestCaseError2.svg b/addons/gdUnit4/src/ui/assets/orphan/TestCaseError2.svg deleted file mode 100644 index 364a44e..0000000 --- a/addons/gdUnit4/src/ui/assets/orphan/TestCaseError2.svg +++ /dev/null @@ -1,166 +0,0 @@ - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/addons/gdUnit4/src/ui/assets/orphan/TestCaseError2.svg.import b/addons/gdUnit4/src/ui/assets/orphan/TestCaseError2.svg.import deleted file mode 100644 index f5da025..0000000 --- a/addons/gdUnit4/src/ui/assets/orphan/TestCaseError2.svg.import +++ /dev/null @@ -1,38 +0,0 @@ -[remap] - -importer="texture" -type="CompressedTexture2D" -uid="uid://fqf674gwq375" -path="res://.godot/imported/TestCaseError2.svg-27dc52b88f226d741b1f9c1294295841.ctex" -metadata={ -"has_editor_variant": true, -"vram_texture": false -} - -[deps] - -source_file="res://addons/gdUnit4/src/ui/assets/orphan/TestCaseError2.svg" -dest_files=["res://.godot/imported/TestCaseError2.svg-27dc52b88f226d741b1f9c1294295841.ctex"] - -[params] - -compress/mode=0 -compress/high_quality=false -compress/lossy_quality=0.7 -compress/hdr_compression=1 -compress/normal_map=0 -compress/channel_pack=0 -mipmaps/generate=false -mipmaps/limit=-1 -roughness/mode=0 -roughness/src_normal="" -process/fix_alpha_border=true -process/premult_alpha=false -process/normal_map_invert_y=false -process/hdr_as_srgb=false -process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=1 -svg/scale=1.0 -editor/scale_with_editor_scale=true -editor/convert_colors_with_editor_theme=true diff --git a/addons/gdUnit4/src/ui/assets/orphan/TestCaseFailed.svg b/addons/gdUnit4/src/ui/assets/orphan/TestCaseFailed.svg deleted file mode 100644 index 10531b8..0000000 --- a/addons/gdUnit4/src/ui/assets/orphan/TestCaseFailed.svg +++ /dev/null @@ -1,66 +0,0 @@ - - - - - - image/svg+xml - - - - - - - - - - - diff --git a/addons/gdUnit4/src/ui/assets/orphan/TestCaseFailed.svg.import b/addons/gdUnit4/src/ui/assets/orphan/TestCaseFailed.svg.import deleted file mode 100644 index 23d9eb3..0000000 --- a/addons/gdUnit4/src/ui/assets/orphan/TestCaseFailed.svg.import +++ /dev/null @@ -1,38 +0,0 @@ -[remap] - -importer="texture" -type="CompressedTexture2D" -uid="uid://bqvlmgi6qpre0" -path="res://.godot/imported/TestCaseFailed.svg-151182f42f6b32bd8c6dc168e9469b54.ctex" -metadata={ -"has_editor_variant": true, -"vram_texture": false -} - -[deps] - -source_file="res://addons/gdUnit4/src/ui/assets/orphan/TestCaseFailed.svg" -dest_files=["res://.godot/imported/TestCaseFailed.svg-151182f42f6b32bd8c6dc168e9469b54.ctex"] - -[params] - -compress/mode=0 -compress/high_quality=false -compress/lossy_quality=0.7 -compress/hdr_compression=1 -compress/normal_map=0 -compress/channel_pack=0 -mipmaps/generate=false -mipmaps/limit=-1 -roughness/mode=0 -roughness/src_normal="" -process/fix_alpha_border=true -process/premult_alpha=false -process/normal_map_invert_y=false -process/hdr_as_srgb=false -process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=1 -svg/scale=1.0 -editor/scale_with_editor_scale=true -editor/convert_colors_with_editor_theme=true diff --git a/addons/gdUnit4/src/ui/assets/orphan/TestCaseFailed1.svg b/addons/gdUnit4/src/ui/assets/orphan/TestCaseFailed1.svg deleted file mode 100644 index 1e36768..0000000 --- a/addons/gdUnit4/src/ui/assets/orphan/TestCaseFailed1.svg +++ /dev/null @@ -1,69 +0,0 @@ - - - - - - image/svg+xml - - - - - - - - - - - diff --git a/addons/gdUnit4/src/ui/assets/orphan/TestCaseFailed1.svg.import b/addons/gdUnit4/src/ui/assets/orphan/TestCaseFailed1.svg.import deleted file mode 100644 index 6d870d6..0000000 --- a/addons/gdUnit4/src/ui/assets/orphan/TestCaseFailed1.svg.import +++ /dev/null @@ -1,38 +0,0 @@ -[remap] - -importer="texture" -type="CompressedTexture2D" -uid="uid://deo4r8koimsfd" -path="res://.godot/imported/TestCaseFailed1.svg-32464e8f6fa7f74ad74a7534dfaba019.ctex" -metadata={ -"has_editor_variant": true, -"vram_texture": false -} - -[deps] - -source_file="res://addons/gdUnit4/src/ui/assets/orphan/TestCaseFailed1.svg" -dest_files=["res://.godot/imported/TestCaseFailed1.svg-32464e8f6fa7f74ad74a7534dfaba019.ctex"] - -[params] - -compress/mode=0 -compress/high_quality=false -compress/lossy_quality=0.7 -compress/hdr_compression=1 -compress/normal_map=0 -compress/channel_pack=0 -mipmaps/generate=false -mipmaps/limit=-1 -roughness/mode=0 -roughness/src_normal="" -process/fix_alpha_border=true -process/premult_alpha=false -process/normal_map_invert_y=false -process/hdr_as_srgb=false -process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=1 -svg/scale=1.0 -editor/scale_with_editor_scale=true -editor/convert_colors_with_editor_theme=true diff --git a/addons/gdUnit4/src/ui/assets/orphan/TestCaseFailed2.svg b/addons/gdUnit4/src/ui/assets/orphan/TestCaseFailed2.svg deleted file mode 100644 index 7112f3f..0000000 --- a/addons/gdUnit4/src/ui/assets/orphan/TestCaseFailed2.svg +++ /dev/null @@ -1,166 +0,0 @@ - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/addons/gdUnit4/src/ui/assets/orphan/TestCaseFailed2.svg.import b/addons/gdUnit4/src/ui/assets/orphan/TestCaseFailed2.svg.import deleted file mode 100644 index 4934f26..0000000 --- a/addons/gdUnit4/src/ui/assets/orphan/TestCaseFailed2.svg.import +++ /dev/null @@ -1,38 +0,0 @@ -[remap] - -importer="texture" -type="CompressedTexture2D" -uid="uid://ctl52ddcptdxb" -path="res://.godot/imported/TestCaseFailed2.svg-aa38e10f09edf43b31b1c0a4caf549c5.ctex" -metadata={ -"has_editor_variant": true, -"vram_texture": false -} - -[deps] - -source_file="res://addons/gdUnit4/src/ui/assets/orphan/TestCaseFailed2.svg" -dest_files=["res://.godot/imported/TestCaseFailed2.svg-aa38e10f09edf43b31b1c0a4caf549c5.ctex"] - -[params] - -compress/mode=0 -compress/high_quality=false -compress/lossy_quality=0.7 -compress/hdr_compression=1 -compress/normal_map=0 -compress/channel_pack=0 -mipmaps/generate=false -mipmaps/limit=-1 -roughness/mode=0 -roughness/src_normal="" -process/fix_alpha_border=true -process/premult_alpha=false -process/normal_map_invert_y=false -process/hdr_as_srgb=false -process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=1 -svg/scale=1.0 -editor/scale_with_editor_scale=true -editor/convert_colors_with_editor_theme=true diff --git a/addons/gdUnit4/src/ui/assets/orphan/TestCaseSuccess1.svg b/addons/gdUnit4/src/ui/assets/orphan/TestCaseSuccess1.svg deleted file mode 100644 index b8e15d8..0000000 --- a/addons/gdUnit4/src/ui/assets/orphan/TestCaseSuccess1.svg +++ /dev/null @@ -1,129 +0,0 @@ - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/addons/gdUnit4/src/ui/assets/orphan/TestCaseSuccess1.svg.import b/addons/gdUnit4/src/ui/assets/orphan/TestCaseSuccess1.svg.import deleted file mode 100644 index fb750b7..0000000 --- a/addons/gdUnit4/src/ui/assets/orphan/TestCaseSuccess1.svg.import +++ /dev/null @@ -1,38 +0,0 @@ -[remap] - -importer="texture" -type="CompressedTexture2D" -uid="uid://bufumcx0iq38d" -path="res://.godot/imported/TestCaseSuccess1.svg-3eadebb620a3275b67f53d505bc0f96b.ctex" -metadata={ -"has_editor_variant": true, -"vram_texture": false -} - -[deps] - -source_file="res://addons/gdUnit4/src/ui/assets/orphan/TestCaseSuccess1.svg" -dest_files=["res://.godot/imported/TestCaseSuccess1.svg-3eadebb620a3275b67f53d505bc0f96b.ctex"] - -[params] - -compress/mode=0 -compress/high_quality=false -compress/lossy_quality=0.7 -compress/hdr_compression=1 -compress/normal_map=0 -compress/channel_pack=0 -mipmaps/generate=false -mipmaps/limit=-1 -roughness/mode=0 -roughness/src_normal="" -process/fix_alpha_border=true -process/premult_alpha=false -process/normal_map_invert_y=false -process/hdr_as_srgb=false -process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=1 -svg/scale=1.0 -editor/scale_with_editor_scale=true -editor/convert_colors_with_editor_theme=true diff --git a/addons/gdUnit4/src/ui/assets/orphan/TestCaseSuccess2.svg b/addons/gdUnit4/src/ui/assets/orphan/TestCaseSuccess2.svg deleted file mode 100644 index f8448b0..0000000 --- a/addons/gdUnit4/src/ui/assets/orphan/TestCaseSuccess2.svg +++ /dev/null @@ -1,166 +0,0 @@ - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/addons/gdUnit4/src/ui/assets/orphan/TestCaseSuccess2.svg.import b/addons/gdUnit4/src/ui/assets/orphan/TestCaseSuccess2.svg.import deleted file mode 100644 index 567d828..0000000 --- a/addons/gdUnit4/src/ui/assets/orphan/TestCaseSuccess2.svg.import +++ /dev/null @@ -1,38 +0,0 @@ -[remap] - -importer="texture" -type="CompressedTexture2D" -uid="uid://clj5nj84vnocn" -path="res://.godot/imported/TestCaseSuccess2.svg-7767c6ebe7bd14d031b8c87b24e08595.ctex" -metadata={ -"has_editor_variant": true, -"vram_texture": false -} - -[deps] - -source_file="res://addons/gdUnit4/src/ui/assets/orphan/TestCaseSuccess2.svg" -dest_files=["res://.godot/imported/TestCaseSuccess2.svg-7767c6ebe7bd14d031b8c87b24e08595.ctex"] - -[params] - -compress/mode=0 -compress/high_quality=false -compress/lossy_quality=0.7 -compress/hdr_compression=1 -compress/normal_map=0 -compress/channel_pack=0 -mipmaps/generate=false -mipmaps/limit=-1 -roughness/mode=0 -roughness/src_normal="" -process/fix_alpha_border=true -process/premult_alpha=false -process/normal_map_invert_y=false -process/hdr_as_srgb=false -process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=1 -svg/scale=1.0 -editor/scale_with_editor_scale=true -editor/convert_colors_with_editor_theme=true diff --git a/addons/gdUnit4/src/ui/assets/orphan/orphan_animated_icon.tres b/addons/gdUnit4/src/ui/assets/orphan/orphan_animated_icon.tres deleted file mode 100644 index 832928c..0000000 --- a/addons/gdUnit4/src/ui/assets/orphan/orphan_animated_icon.tres +++ /dev/null @@ -1,32 +0,0 @@ -[gd_resource type="AnimatedTexture" load_steps=7 format=2] - -[ext_resource path="res://addons/gdUnit4/src/ui/assets/orphan/orphan_red4.svg" type="Texture2D" id=1] -[ext_resource path="res://addons/gdUnit4/src/ui/assets/orphan/orphan_red5.svg" type="Texture2D" id=2] -[ext_resource path="res://addons/gdUnit4/src/ui/assets/orphan/orphan_red6.svg" type="Texture2D" id=3] -[ext_resource path="res://addons/gdUnit4/src/ui/assets/orphan/orphan_red3.svg" type="Texture2D" id=4] -[ext_resource path="res://addons/gdUnit4/src/ui/assets/orphan/orphan_red2.svg" type="Texture2D" id=5] -[ext_resource path="res://addons/gdUnit4/src/ui/assets/orphan/orphan_red7.svg" type="Texture2D" id=6] - -[resource] -flags = 4 -frames = 10 -fps = 9.0 -frame_0/texture = ExtResource( 5 ) -frame_1/texture = ExtResource( 4 ) -frame_1/delay_sec = 0.0 -frame_2/texture = ExtResource( 1 ) -frame_2/delay_sec = 0.0 -frame_3/texture = ExtResource( 2 ) -frame_3/delay_sec = 0.0 -frame_4/texture = ExtResource( 3 ) -frame_4/delay_sec = 0.0 -frame_5/texture = ExtResource( 6 ) -frame_5/delay_sec = 0.5 -frame_6/texture = ExtResource( 3 ) -frame_6/delay_sec = 0.0 -frame_7/texture = ExtResource( 2 ) -frame_7/delay_sec = 0.0 -frame_8/texture = ExtResource( 1 ) -frame_8/delay_sec = 0.0 -frame_9/texture = ExtResource( 4 ) -frame_9/delay_sec = 0.0 diff --git a/addons/gdUnit4/src/ui/assets/orphan/orphan_green.svg b/addons/gdUnit4/src/ui/assets/orphan/orphan_green.svg deleted file mode 100644 index 64612eb..0000000 --- a/addons/gdUnit4/src/ui/assets/orphan/orphan_green.svg +++ /dev/null @@ -1,156 +0,0 @@ - -image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/addons/gdUnit4/src/ui/assets/orphan/orphan_green.svg.import b/addons/gdUnit4/src/ui/assets/orphan/orphan_green.svg.import deleted file mode 100644 index 5b3d517..0000000 --- a/addons/gdUnit4/src/ui/assets/orphan/orphan_green.svg.import +++ /dev/null @@ -1,38 +0,0 @@ -[remap] - -importer="texture" -type="CompressedTexture2D" -uid="uid://cqrikobu314r3" -path="res://.godot/imported/orphan_green.svg-9ce01031b489dea619e91196cb66dce9.ctex" -metadata={ -"has_editor_variant": true, -"vram_texture": false -} - -[deps] - -source_file="res://addons/gdUnit4/src/ui/assets/orphan/orphan_green.svg" -dest_files=["res://.godot/imported/orphan_green.svg-9ce01031b489dea619e91196cb66dce9.ctex"] - -[params] - -compress/mode=0 -compress/high_quality=false -compress/lossy_quality=0.7 -compress/hdr_compression=1 -compress/normal_map=0 -compress/channel_pack=0 -mipmaps/generate=false -mipmaps/limit=-1 -roughness/mode=0 -roughness/src_normal="" -process/fix_alpha_border=true -process/premult_alpha=false -process/normal_map_invert_y=false -process/hdr_as_srgb=false -process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=1 -svg/scale=1.0 -editor/scale_with_editor_scale=true -editor/convert_colors_with_editor_theme=true diff --git a/addons/gdUnit4/src/ui/assets/orphan/orphan_red1.svg b/addons/gdUnit4/src/ui/assets/orphan/orphan_red1.svg deleted file mode 100644 index 16883e1..0000000 --- a/addons/gdUnit4/src/ui/assets/orphan/orphan_red1.svg +++ /dev/null @@ -1,156 +0,0 @@ - -image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/addons/gdUnit4/src/ui/assets/orphan/orphan_red1.svg.import b/addons/gdUnit4/src/ui/assets/orphan/orphan_red1.svg.import deleted file mode 100644 index e6af64e..0000000 --- a/addons/gdUnit4/src/ui/assets/orphan/orphan_red1.svg.import +++ /dev/null @@ -1,38 +0,0 @@ -[remap] - -importer="texture" -type="CompressedTexture2D" -uid="uid://da6s7yd3mpcbp" -path="res://.godot/imported/orphan_red1.svg-8ddad0e8a9c8884f19621c8f733ce341.ctex" -metadata={ -"has_editor_variant": true, -"vram_texture": false -} - -[deps] - -source_file="res://addons/gdUnit4/src/ui/assets/orphan/orphan_red1.svg" -dest_files=["res://.godot/imported/orphan_red1.svg-8ddad0e8a9c8884f19621c8f733ce341.ctex"] - -[params] - -compress/mode=0 -compress/high_quality=false -compress/lossy_quality=0.7 -compress/hdr_compression=1 -compress/normal_map=0 -compress/channel_pack=0 -mipmaps/generate=false -mipmaps/limit=-1 -roughness/mode=0 -roughness/src_normal="" -process/fix_alpha_border=true -process/premult_alpha=false -process/normal_map_invert_y=false -process/hdr_as_srgb=false -process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=1 -svg/scale=1.0 -editor/scale_with_editor_scale=true -editor/convert_colors_with_editor_theme=true diff --git a/addons/gdUnit4/src/ui/assets/orphan/orphan_red2.svg b/addons/gdUnit4/src/ui/assets/orphan/orphan_red2.svg deleted file mode 100644 index 7a124c0..0000000 --- a/addons/gdUnit4/src/ui/assets/orphan/orphan_red2.svg +++ /dev/null @@ -1,156 +0,0 @@ - -image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/addons/gdUnit4/src/ui/assets/orphan/orphan_red2.svg.import b/addons/gdUnit4/src/ui/assets/orphan/orphan_red2.svg.import deleted file mode 100644 index 639d3ca..0000000 --- a/addons/gdUnit4/src/ui/assets/orphan/orphan_red2.svg.import +++ /dev/null @@ -1,38 +0,0 @@ -[remap] - -importer="texture" -type="CompressedTexture2D" -uid="uid://cnvenq5hici48" -path="res://.godot/imported/orphan_red2.svg-3ab7610a37b37b2e46ca7faaba5a2c40.ctex" -metadata={ -"has_editor_variant": true, -"vram_texture": false -} - -[deps] - -source_file="res://addons/gdUnit4/src/ui/assets/orphan/orphan_red2.svg" -dest_files=["res://.godot/imported/orphan_red2.svg-3ab7610a37b37b2e46ca7faaba5a2c40.ctex"] - -[params] - -compress/mode=0 -compress/high_quality=false -compress/lossy_quality=0.7 -compress/hdr_compression=1 -compress/normal_map=0 -compress/channel_pack=0 -mipmaps/generate=false -mipmaps/limit=-1 -roughness/mode=0 -roughness/src_normal="" -process/fix_alpha_border=true -process/premult_alpha=false -process/normal_map_invert_y=false -process/hdr_as_srgb=false -process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=1 -svg/scale=1.0 -editor/scale_with_editor_scale=true -editor/convert_colors_with_editor_theme=true diff --git a/addons/gdUnit4/src/ui/assets/orphan/orphan_red3.svg b/addons/gdUnit4/src/ui/assets/orphan/orphan_red3.svg deleted file mode 100644 index bb73371..0000000 --- a/addons/gdUnit4/src/ui/assets/orphan/orphan_red3.svg +++ /dev/null @@ -1,156 +0,0 @@ - -image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/addons/gdUnit4/src/ui/assets/orphan/orphan_red3.svg.import b/addons/gdUnit4/src/ui/assets/orphan/orphan_red3.svg.import deleted file mode 100644 index 14993e0..0000000 --- a/addons/gdUnit4/src/ui/assets/orphan/orphan_red3.svg.import +++ /dev/null @@ -1,38 +0,0 @@ -[remap] - -importer="texture" -type="CompressedTexture2D" -uid="uid://cbnqfoh4n6bj5" -path="res://.godot/imported/orphan_red3.svg-2f1ae0a474446c730d13e401878da7f2.ctex" -metadata={ -"has_editor_variant": true, -"vram_texture": false -} - -[deps] - -source_file="res://addons/gdUnit4/src/ui/assets/orphan/orphan_red3.svg" -dest_files=["res://.godot/imported/orphan_red3.svg-2f1ae0a474446c730d13e401878da7f2.ctex"] - -[params] - -compress/mode=0 -compress/high_quality=false -compress/lossy_quality=0.7 -compress/hdr_compression=1 -compress/normal_map=0 -compress/channel_pack=0 -mipmaps/generate=false -mipmaps/limit=-1 -roughness/mode=0 -roughness/src_normal="" -process/fix_alpha_border=true -process/premult_alpha=false -process/normal_map_invert_y=false -process/hdr_as_srgb=false -process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=1 -svg/scale=1.0 -editor/scale_with_editor_scale=true -editor/convert_colors_with_editor_theme=true diff --git a/addons/gdUnit4/src/ui/assets/orphan/orphan_red4.svg b/addons/gdUnit4/src/ui/assets/orphan/orphan_red4.svg deleted file mode 100644 index 67b172d..0000000 --- a/addons/gdUnit4/src/ui/assets/orphan/orphan_red4.svg +++ /dev/null @@ -1,156 +0,0 @@ - -image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/addons/gdUnit4/src/ui/assets/orphan/orphan_red4.svg.import b/addons/gdUnit4/src/ui/assets/orphan/orphan_red4.svg.import deleted file mode 100644 index f80ecd4..0000000 --- a/addons/gdUnit4/src/ui/assets/orphan/orphan_red4.svg.import +++ /dev/null @@ -1,38 +0,0 @@ -[remap] - -importer="texture" -type="CompressedTexture2D" -uid="uid://bjm3fxk3ieapk" -path="res://.godot/imported/orphan_red4.svg-b6aa919f74c7c5f9e6a5a87210e2194e.ctex" -metadata={ -"has_editor_variant": true, -"vram_texture": false -} - -[deps] - -source_file="res://addons/gdUnit4/src/ui/assets/orphan/orphan_red4.svg" -dest_files=["res://.godot/imported/orphan_red4.svg-b6aa919f74c7c5f9e6a5a87210e2194e.ctex"] - -[params] - -compress/mode=0 -compress/high_quality=false -compress/lossy_quality=0.7 -compress/hdr_compression=1 -compress/normal_map=0 -compress/channel_pack=0 -mipmaps/generate=false -mipmaps/limit=-1 -roughness/mode=0 -roughness/src_normal="" -process/fix_alpha_border=true -process/premult_alpha=false -process/normal_map_invert_y=false -process/hdr_as_srgb=false -process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=1 -svg/scale=1.0 -editor/scale_with_editor_scale=true -editor/convert_colors_with_editor_theme=true diff --git a/addons/gdUnit4/src/ui/assets/orphan/orphan_red5.svg b/addons/gdUnit4/src/ui/assets/orphan/orphan_red5.svg deleted file mode 100644 index 38f86b5..0000000 --- a/addons/gdUnit4/src/ui/assets/orphan/orphan_red5.svg +++ /dev/null @@ -1,156 +0,0 @@ - -image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/addons/gdUnit4/src/ui/assets/orphan/orphan_red5.svg.import b/addons/gdUnit4/src/ui/assets/orphan/orphan_red5.svg.import deleted file mode 100644 index e62f742..0000000 --- a/addons/gdUnit4/src/ui/assets/orphan/orphan_red5.svg.import +++ /dev/null @@ -1,38 +0,0 @@ -[remap] - -importer="texture" -type="CompressedTexture2D" -uid="uid://nj6xpoxpf6i5" -path="res://.godot/imported/orphan_red5.svg-94eac4c39a2d4020f502eab72982b3f2.ctex" -metadata={ -"has_editor_variant": true, -"vram_texture": false -} - -[deps] - -source_file="res://addons/gdUnit4/src/ui/assets/orphan/orphan_red5.svg" -dest_files=["res://.godot/imported/orphan_red5.svg-94eac4c39a2d4020f502eab72982b3f2.ctex"] - -[params] - -compress/mode=0 -compress/high_quality=false -compress/lossy_quality=0.7 -compress/hdr_compression=1 -compress/normal_map=0 -compress/channel_pack=0 -mipmaps/generate=false -mipmaps/limit=-1 -roughness/mode=0 -roughness/src_normal="" -process/fix_alpha_border=true -process/premult_alpha=false -process/normal_map_invert_y=false -process/hdr_as_srgb=false -process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=1 -svg/scale=1.0 -editor/scale_with_editor_scale=true -editor/convert_colors_with_editor_theme=true diff --git a/addons/gdUnit4/src/ui/assets/orphan/orphan_red6.svg b/addons/gdUnit4/src/ui/assets/orphan/orphan_red6.svg deleted file mode 100644 index b143d54..0000000 --- a/addons/gdUnit4/src/ui/assets/orphan/orphan_red6.svg +++ /dev/null @@ -1,156 +0,0 @@ - -image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/addons/gdUnit4/src/ui/assets/orphan/orphan_red6.svg.import b/addons/gdUnit4/src/ui/assets/orphan/orphan_red6.svg.import deleted file mode 100644 index 61256b8..0000000 --- a/addons/gdUnit4/src/ui/assets/orphan/orphan_red6.svg.import +++ /dev/null @@ -1,38 +0,0 @@ -[remap] - -importer="texture" -type="CompressedTexture2D" -uid="uid://bylpj2dbacbaw" -path="res://.godot/imported/orphan_red6.svg-fb927f6dc4442199133d2e25e9d9d21a.ctex" -metadata={ -"has_editor_variant": true, -"vram_texture": false -} - -[deps] - -source_file="res://addons/gdUnit4/src/ui/assets/orphan/orphan_red6.svg" -dest_files=["res://.godot/imported/orphan_red6.svg-fb927f6dc4442199133d2e25e9d9d21a.ctex"] - -[params] - -compress/mode=0 -compress/high_quality=false -compress/lossy_quality=0.7 -compress/hdr_compression=1 -compress/normal_map=0 -compress/channel_pack=0 -mipmaps/generate=false -mipmaps/limit=-1 -roughness/mode=0 -roughness/src_normal="" -process/fix_alpha_border=true -process/premult_alpha=false -process/normal_map_invert_y=false -process/hdr_as_srgb=false -process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=1 -svg/scale=1.0 -editor/scale_with_editor_scale=true -editor/convert_colors_with_editor_theme=true diff --git a/addons/gdUnit4/src/ui/assets/orphan/orphan_red7.svg b/addons/gdUnit4/src/ui/assets/orphan/orphan_red7.svg deleted file mode 100644 index bd9ae54..0000000 --- a/addons/gdUnit4/src/ui/assets/orphan/orphan_red7.svg +++ /dev/null @@ -1,156 +0,0 @@ - -image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/addons/gdUnit4/src/ui/assets/orphan/orphan_red7.svg.import b/addons/gdUnit4/src/ui/assets/orphan/orphan_red7.svg.import deleted file mode 100644 index f78f85d..0000000 --- a/addons/gdUnit4/src/ui/assets/orphan/orphan_red7.svg.import +++ /dev/null @@ -1,38 +0,0 @@ -[remap] - -importer="texture" -type="CompressedTexture2D" -uid="uid://bpqfgn22gmpjm" -path="res://.godot/imported/orphan_red7.svg-4b4ab8aec1bc8343d5df6b64a24f7c22.ctex" -metadata={ -"has_editor_variant": true, -"vram_texture": false -} - -[deps] - -source_file="res://addons/gdUnit4/src/ui/assets/orphan/orphan_red7.svg" -dest_files=["res://.godot/imported/orphan_red7.svg-4b4ab8aec1bc8343d5df6b64a24f7c22.ctex"] - -[params] - -compress/mode=0 -compress/high_quality=false -compress/lossy_quality=0.7 -compress/hdr_compression=1 -compress/normal_map=0 -compress/channel_pack=0 -mipmaps/generate=false -mipmaps/limit=-1 -roughness/mode=0 -roughness/src_normal="" -process/fix_alpha_border=true -process/premult_alpha=false -process/normal_map_invert_y=false -process/hdr_as_srgb=false -process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=1 -svg/scale=1.0 -editor/scale_with_editor_scale=true -editor/convert_colors_with_editor_theme=true diff --git a/addons/gdUnit4/src/ui/assets/running.png b/addons/gdUnit4/src/ui/assets/running.png deleted file mode 100644 index 0150b587c9ffad7d2e7921bffa6caca760bb2ae0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 515 zcmV+e0{s1nP)qu06A>LM2oG7XL-Y{5YWCL5i(||M9u*$BF&2ECEk}P@B`m4mS6{- zlt`Xo5xWu$3x!fPKA@IZ$)vK8VmZl_`XEq8Gr?1g^+8}%=(tiLAOj_m*M!vX)UE#u zr*V5sM2>Tr zu{8+URJtilt6ovV0yf~7Ft`6I@EwP7Px$^bU4({k29I$EhwwG;kPq?{Ug1%^uM2N< z0I!Aq{x-vmOuS|RvIhW{$c diff --git a/addons/gdUnit4/src/ui/assets/spinner/Progress1.svg.import b/addons/gdUnit4/src/ui/assets/spinner/Progress1.svg.import deleted file mode 100644 index 17fe27b..0000000 --- a/addons/gdUnit4/src/ui/assets/spinner/Progress1.svg.import +++ /dev/null @@ -1,38 +0,0 @@ -[remap] - -importer="texture" -type="CompressedTexture2D" -uid="uid://ddxpytkht0m5p" -path="res://.godot/imported/Progress1.svg-baca226eb5c6ca50a0b5f3af77fe615c.ctex" -metadata={ -"has_editor_variant": true, -"vram_texture": false -} - -[deps] - -source_file="res://addons/gdUnit4/src/ui/assets/spinner/Progress1.svg" -dest_files=["res://.godot/imported/Progress1.svg-baca226eb5c6ca50a0b5f3af77fe615c.ctex"] - -[params] - -compress/mode=0 -compress/high_quality=false -compress/lossy_quality=0.7 -compress/hdr_compression=1 -compress/normal_map=0 -compress/channel_pack=0 -mipmaps/generate=false -mipmaps/limit=-1 -roughness/mode=0 -roughness/src_normal="" -process/fix_alpha_border=true -process/premult_alpha=false -process/normal_map_invert_y=false -process/hdr_as_srgb=false -process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=1 -svg/scale=1.0 -editor/scale_with_editor_scale=true -editor/convert_colors_with_editor_theme=true diff --git a/addons/gdUnit4/src/ui/assets/spinner/Progress2.svg b/addons/gdUnit4/src/ui/assets/spinner/Progress2.svg deleted file mode 100644 index 0a48f7d..0000000 --- a/addons/gdUnit4/src/ui/assets/spinner/Progress2.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/addons/gdUnit4/src/ui/assets/spinner/Progress2.svg.import b/addons/gdUnit4/src/ui/assets/spinner/Progress2.svg.import deleted file mode 100644 index 250618b..0000000 --- a/addons/gdUnit4/src/ui/assets/spinner/Progress2.svg.import +++ /dev/null @@ -1,38 +0,0 @@ -[remap] - -importer="texture" -type="CompressedTexture2D" -uid="uid://dowca7ike2thl" -path="res://.godot/imported/Progress2.svg-6a0cbcb42a8df535c533cf79599952d6.ctex" -metadata={ -"has_editor_variant": true, -"vram_texture": false -} - -[deps] - -source_file="res://addons/gdUnit4/src/ui/assets/spinner/Progress2.svg" -dest_files=["res://.godot/imported/Progress2.svg-6a0cbcb42a8df535c533cf79599952d6.ctex"] - -[params] - -compress/mode=0 -compress/high_quality=false -compress/lossy_quality=0.7 -compress/hdr_compression=1 -compress/normal_map=0 -compress/channel_pack=0 -mipmaps/generate=false -mipmaps/limit=-1 -roughness/mode=0 -roughness/src_normal="" -process/fix_alpha_border=true -process/premult_alpha=false -process/normal_map_invert_y=false -process/hdr_as_srgb=false -process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=1 -svg/scale=1.0 -editor/scale_with_editor_scale=true -editor/convert_colors_with_editor_theme=true diff --git a/addons/gdUnit4/src/ui/assets/spinner/Progress3.svg b/addons/gdUnit4/src/ui/assets/spinner/Progress3.svg deleted file mode 100644 index a7f0f9c..0000000 --- a/addons/gdUnit4/src/ui/assets/spinner/Progress3.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/addons/gdUnit4/src/ui/assets/spinner/Progress3.svg.import b/addons/gdUnit4/src/ui/assets/spinner/Progress3.svg.import deleted file mode 100644 index 662380f..0000000 --- a/addons/gdUnit4/src/ui/assets/spinner/Progress3.svg.import +++ /dev/null @@ -1,38 +0,0 @@ -[remap] - -importer="texture" -type="CompressedTexture2D" -uid="uid://cwh8md6qipmdw" -path="res://.godot/imported/Progress3.svg-0b465f11e95f98f7b157a0bf0ded40c1.ctex" -metadata={ -"has_editor_variant": true, -"vram_texture": false -} - -[deps] - -source_file="res://addons/gdUnit4/src/ui/assets/spinner/Progress3.svg" -dest_files=["res://.godot/imported/Progress3.svg-0b465f11e95f98f7b157a0bf0ded40c1.ctex"] - -[params] - -compress/mode=0 -compress/high_quality=false -compress/lossy_quality=0.7 -compress/hdr_compression=1 -compress/normal_map=0 -compress/channel_pack=0 -mipmaps/generate=false -mipmaps/limit=-1 -roughness/mode=0 -roughness/src_normal="" -process/fix_alpha_border=true -process/premult_alpha=false -process/normal_map_invert_y=false -process/hdr_as_srgb=false -process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=1 -svg/scale=1.0 -editor/scale_with_editor_scale=true -editor/convert_colors_with_editor_theme=true diff --git a/addons/gdUnit4/src/ui/assets/spinner/Progress4.svg b/addons/gdUnit4/src/ui/assets/spinner/Progress4.svg deleted file mode 100644 index 1719209..0000000 --- a/addons/gdUnit4/src/ui/assets/spinner/Progress4.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/addons/gdUnit4/src/ui/assets/spinner/Progress4.svg.import b/addons/gdUnit4/src/ui/assets/spinner/Progress4.svg.import deleted file mode 100644 index 023ebb8..0000000 --- a/addons/gdUnit4/src/ui/assets/spinner/Progress4.svg.import +++ /dev/null @@ -1,38 +0,0 @@ -[remap] - -importer="texture" -type="CompressedTexture2D" -uid="uid://dm0jpqdjetv2c" -path="res://.godot/imported/Progress4.svg-09def7d3fee66ec2764c9bbd72c3a961.ctex" -metadata={ -"has_editor_variant": true, -"vram_texture": false -} - -[deps] - -source_file="res://addons/gdUnit4/src/ui/assets/spinner/Progress4.svg" -dest_files=["res://.godot/imported/Progress4.svg-09def7d3fee66ec2764c9bbd72c3a961.ctex"] - -[params] - -compress/mode=0 -compress/high_quality=false -compress/lossy_quality=0.7 -compress/hdr_compression=1 -compress/normal_map=0 -compress/channel_pack=0 -mipmaps/generate=false -mipmaps/limit=-1 -roughness/mode=0 -roughness/src_normal="" -process/fix_alpha_border=true -process/premult_alpha=false -process/normal_map_invert_y=false -process/hdr_as_srgb=false -process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=1 -svg/scale=1.0 -editor/scale_with_editor_scale=true -editor/convert_colors_with_editor_theme=true diff --git a/addons/gdUnit4/src/ui/assets/spinner/Progress5.svg b/addons/gdUnit4/src/ui/assets/spinner/Progress5.svg deleted file mode 100644 index 7289b7b..0000000 --- a/addons/gdUnit4/src/ui/assets/spinner/Progress5.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/addons/gdUnit4/src/ui/assets/spinner/Progress5.svg.import b/addons/gdUnit4/src/ui/assets/spinner/Progress5.svg.import deleted file mode 100644 index 289c8a9..0000000 --- a/addons/gdUnit4/src/ui/assets/spinner/Progress5.svg.import +++ /dev/null @@ -1,38 +0,0 @@ -[remap] - -importer="texture" -type="CompressedTexture2D" -uid="uid://bkj6kjyjyi7cd" -path="res://.godot/imported/Progress5.svg-9874b4bd1c734fd859a28d95960c17c5.ctex" -metadata={ -"has_editor_variant": true, -"vram_texture": false -} - -[deps] - -source_file="res://addons/gdUnit4/src/ui/assets/spinner/Progress5.svg" -dest_files=["res://.godot/imported/Progress5.svg-9874b4bd1c734fd859a28d95960c17c5.ctex"] - -[params] - -compress/mode=0 -compress/high_quality=false -compress/lossy_quality=0.7 -compress/hdr_compression=1 -compress/normal_map=0 -compress/channel_pack=0 -mipmaps/generate=false -mipmaps/limit=-1 -roughness/mode=0 -roughness/src_normal="" -process/fix_alpha_border=true -process/premult_alpha=false -process/normal_map_invert_y=false -process/hdr_as_srgb=false -process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=1 -svg/scale=1.0 -editor/scale_with_editor_scale=true -editor/convert_colors_with_editor_theme=true diff --git a/addons/gdUnit4/src/ui/assets/spinner/Progress6.svg b/addons/gdUnit4/src/ui/assets/spinner/Progress6.svg deleted file mode 100644 index 3deba6d..0000000 --- a/addons/gdUnit4/src/ui/assets/spinner/Progress6.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/addons/gdUnit4/src/ui/assets/spinner/Progress6.svg.import b/addons/gdUnit4/src/ui/assets/spinner/Progress6.svg.import deleted file mode 100644 index 7259c6a..0000000 --- a/addons/gdUnit4/src/ui/assets/spinner/Progress6.svg.import +++ /dev/null @@ -1,38 +0,0 @@ -[remap] - -importer="texture" -type="CompressedTexture2D" -uid="uid://bsljbs1aiyels" -path="res://.godot/imported/Progress6.svg-0a3b2b954e3a285cee1f29222aac701b.ctex" -metadata={ -"has_editor_variant": true, -"vram_texture": false -} - -[deps] - -source_file="res://addons/gdUnit4/src/ui/assets/spinner/Progress6.svg" -dest_files=["res://.godot/imported/Progress6.svg-0a3b2b954e3a285cee1f29222aac701b.ctex"] - -[params] - -compress/mode=0 -compress/high_quality=false -compress/lossy_quality=0.7 -compress/hdr_compression=1 -compress/normal_map=0 -compress/channel_pack=0 -mipmaps/generate=false -mipmaps/limit=-1 -roughness/mode=0 -roughness/src_normal="" -process/fix_alpha_border=true -process/premult_alpha=false -process/normal_map_invert_y=false -process/hdr_as_srgb=false -process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=1 -svg/scale=1.0 -editor/scale_with_editor_scale=true -editor/convert_colors_with_editor_theme=true diff --git a/addons/gdUnit4/src/ui/assets/spinner/Progress7.svg b/addons/gdUnit4/src/ui/assets/spinner/Progress7.svg deleted file mode 100644 index 546155d..0000000 --- a/addons/gdUnit4/src/ui/assets/spinner/Progress7.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/addons/gdUnit4/src/ui/assets/spinner/Progress7.svg.import b/addons/gdUnit4/src/ui/assets/spinner/Progress7.svg.import deleted file mode 100644 index 85b3ebc..0000000 --- a/addons/gdUnit4/src/ui/assets/spinner/Progress7.svg.import +++ /dev/null @@ -1,38 +0,0 @@ -[remap] - -importer="texture" -type="CompressedTexture2D" -uid="uid://cct6crbhix7u8" -path="res://.godot/imported/Progress7.svg-e824844cb9cfdf076f9196cf47098a7d.ctex" -metadata={ -"has_editor_variant": true, -"vram_texture": false -} - -[deps] - -source_file="res://addons/gdUnit4/src/ui/assets/spinner/Progress7.svg" -dest_files=["res://.godot/imported/Progress7.svg-e824844cb9cfdf076f9196cf47098a7d.ctex"] - -[params] - -compress/mode=0 -compress/high_quality=false -compress/lossy_quality=0.7 -compress/hdr_compression=1 -compress/normal_map=0 -compress/channel_pack=0 -mipmaps/generate=false -mipmaps/limit=-1 -roughness/mode=0 -roughness/src_normal="" -process/fix_alpha_border=true -process/premult_alpha=false -process/normal_map_invert_y=false -process/hdr_as_srgb=false -process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=1 -svg/scale=1.0 -editor/scale_with_editor_scale=true -editor/convert_colors_with_editor_theme=true diff --git a/addons/gdUnit4/src/ui/assets/spinner/Progress8.svg b/addons/gdUnit4/src/ui/assets/spinner/Progress8.svg deleted file mode 100644 index b56ffcb..0000000 --- a/addons/gdUnit4/src/ui/assets/spinner/Progress8.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/addons/gdUnit4/src/ui/assets/spinner/Progress8.svg.import b/addons/gdUnit4/src/ui/assets/spinner/Progress8.svg.import deleted file mode 100644 index 6d3c23d..0000000 --- a/addons/gdUnit4/src/ui/assets/spinner/Progress8.svg.import +++ /dev/null @@ -1,38 +0,0 @@ -[remap] - -importer="texture" -type="CompressedTexture2D" -uid="uid://dqc521iq12a7l" -path="res://.godot/imported/Progress8.svg-b37d84176a257f378f0f5dff81bfc322.ctex" -metadata={ -"has_editor_variant": true, -"vram_texture": false -} - -[deps] - -source_file="res://addons/gdUnit4/src/ui/assets/spinner/Progress8.svg" -dest_files=["res://.godot/imported/Progress8.svg-b37d84176a257f378f0f5dff81bfc322.ctex"] - -[params] - -compress/mode=0 -compress/high_quality=false -compress/lossy_quality=0.7 -compress/hdr_compression=1 -compress/normal_map=0 -compress/channel_pack=0 -mipmaps/generate=false -mipmaps/limit=-1 -roughness/mode=0 -roughness/src_normal="" -process/fix_alpha_border=true -process/premult_alpha=false -process/normal_map_invert_y=false -process/hdr_as_srgb=false -process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=1 -svg/scale=1.0 -editor/scale_with_editor_scale=true -editor/convert_colors_with_editor_theme=true diff --git a/addons/gdUnit4/src/ui/menu/EditorFileSystemContextMenuHandler.gd b/addons/gdUnit4/src/ui/menu/EditorFileSystemContextMenuHandler.gd deleted file mode 100644 index 3d8cf9b..0000000 --- a/addons/gdUnit4/src/ui/menu/EditorFileSystemContextMenuHandler.gd +++ /dev/null @@ -1,90 +0,0 @@ -class_name EditorFileSystemContextMenuHandler -extends Control - - -var _context_menus := Dictionary() - - -func _init(context_menus :Array[GdUnitContextMenuItem]): - set_name("EditorFileSystemContextMenuHandler") - for menu in context_menus: - _context_menus[menu.id] = menu - var popup := _menu_popup() - var file_tree := _file_tree() - popup.about_to_popup.connect(on_context_menu_show.bind(popup, file_tree)) - popup.id_pressed.connect(on_context_menu_pressed.bind(file_tree)) - - -static func dispose(): - var handler :EditorFileSystemContextMenuHandler = Engine.get_main_loop().root.find_child("EditorFileSystemContextMenuHandler*", false, false) - if handler: - var popup := _menu_popup() - if popup.about_to_popup.is_connected(Callable(handler, "on_context_menu_show")): - popup.about_to_popup.disconnect(Callable(handler, "on_context_menu_show")) - if popup.id_pressed.is_connected(Callable(handler, "on_context_menu_pressed")): - popup.id_pressed.disconnect(Callable(handler, "on_context_menu_pressed")) - Engine.get_main_loop().root.call_deferred("remove_child", handler) - handler.queue_free() - - -func on_context_menu_show(context_menu :PopupMenu, file_tree :Tree) -> void: - context_menu.add_separator() - var current_index := context_menu.get_item_count() - var selected_test_suites := collect_testsuites(_context_menus.values()[0], file_tree) - - for menu_id in _context_menus.keys(): - var menu_item :GdUnitContextMenuItem = _context_menus[menu_id] - if selected_test_suites.size() != 0: - context_menu.add_item(menu_item.name, menu_id) - context_menu.set_item_disabled(current_index, !menu_item.is_enabled(null)) - current_index += 1 - - -func on_context_menu_pressed(id :int, file_tree :Tree) -> void: - #prints("on_context_menu_pressed", id) - if !_context_menus.has(id): - return - var menu_item :GdUnitContextMenuItem = _context_menus[id] - var selected_test_suites := collect_testsuites(menu_item, file_tree) - menu_item.execute([selected_test_suites]) - - -func collect_testsuites(_menu_item :GdUnitContextMenuItem, file_tree :Tree) -> PackedStringArray: - var file_system := editor_interface().get_resource_filesystem() - var selected_item := file_tree.get_selected() - var selected_test_suites := PackedStringArray() - while selected_item: - var resource_path :String = selected_item.get_metadata(0) - var file_type := file_system.get_file_type(resource_path) - var is_dir := DirAccess.dir_exists_absolute(resource_path) - if is_dir: - selected_test_suites.append(resource_path) - elif is_dir or file_type == "GDScript" or file_type == "CSharpScript": - # find a performant way to check if the selected item a testsuite - #var resource := ResourceLoader.load(resource_path, "GDScript", ResourceLoader.CACHE_MODE_REUSE) - #prints("loaded", resource) - #if resource is GDScript and menu_item.is_visible(resource): - selected_test_suites.append(resource_path) - selected_item = file_tree.get_next_selected(selected_item) - return selected_test_suites - - -# Returns the EditorInterface instance -static func editor_interface() -> EditorInterface: - if not Engine.has_meta("GdUnitEditorPlugin"): - return null - var plugin :EditorPlugin = Engine.get_meta("GdUnitEditorPlugin") - return plugin.get_editor_interface() - - -# Returns the FileSystemDock instance -static func filesystem_dock() -> FileSystemDock: - return editor_interface().get_file_system_dock() - - -static func _file_tree() -> Tree: - return GdObjects.find_nodes_by_class(filesystem_dock(), "Tree", true)[-1] - - -static func _menu_popup() -> PopupMenu: - return GdObjects.find_nodes_by_class(filesystem_dock(), "PopupMenu")[-1] diff --git a/addons/gdUnit4/src/ui/menu/GdUnitContextMenuItem.gd b/addons/gdUnit4/src/ui/menu/GdUnitContextMenuItem.gd deleted file mode 100644 index edb5d80..0000000 --- a/addons/gdUnit4/src/ui/menu/GdUnitContextMenuItem.gd +++ /dev/null @@ -1,65 +0,0 @@ -class_name GdUnitContextMenuItem - -enum MENU_ID { - TEST_RUN = 1000, - TEST_DEBUG = 1001, - TEST_RERUN = 1002, - CREATE_TEST = 1010, -} - - -func _init(p_id :MENU_ID, p_name :StringName, p_is_visible :Callable, p_command :GdUnitCommand): - assert(p_id != null, "(%s) missing parameter 'MENU_ID'" % p_name) - assert(p_is_visible != null, "(%s) missing parameter 'GdUnitCommand'" % p_name) - assert(p_command != null, "(%s) missing parameter 'GdUnitCommand'" % p_name) - self.id = p_id - self.name = p_name - self.command = p_command - self.visible = p_is_visible - - -var id: MENU_ID: - set(value): - id = value - get: - return id - - -var name: StringName: - set(value): - name = value - get: - return name - - -var command: GdUnitCommand: - set(value): - command = value - get: - return command - - -var visible: Callable: - set(value): - visible = value - get: - return visible - - -func shortcut() -> Shortcut: - return GdUnitCommandHandler.instance().get_shortcut(command.shortcut) - - -func is_enabled(script :Script) -> bool: - return command.is_enabled.call(script) - - -func is_visible(script :Script) -> bool: - return visible.call(script) - - -func execute(arguments := []) -> void: - if arguments.is_empty(): - command.runnable.call() - else: - command.runnable.callv(arguments) diff --git a/addons/gdUnit4/src/ui/menu/ScriptEditorContextMenuHandler.gd b/addons/gdUnit4/src/ui/menu/ScriptEditorContextMenuHandler.gd deleted file mode 100644 index cda0ca5..0000000 --- a/addons/gdUnit4/src/ui/menu/ScriptEditorContextMenuHandler.gd +++ /dev/null @@ -1,81 +0,0 @@ -class_name ScriptEditorContextMenuHandler -extends Control - -var _context_menus := Dictionary() -var _editor :ScriptEditor - - -func _init(context_menus :Array[GdUnitContextMenuItem], p_editor :ScriptEditor): - set_name("ScriptEditorContextMenuHandler") - for menu in context_menus: - _context_menus[menu.id] = menu - _editor = p_editor - p_editor.editor_script_changed.connect(on_script_changed) - on_script_changed(active_script()) - - -static func dispose(p_editor :ScriptEditor) -> void: - var handler :ScriptEditorContextMenuHandler = Engine.get_main_loop().root.find_child("ScriptEditorContextMenuHandler*", false, false) - if handler: - if p_editor.editor_script_changed.is_connected(handler.on_script_changed): - p_editor.editor_script_changed.disconnect(handler.on_script_changed) - Engine.get_main_loop().root.call_deferred("remove_child", handler) - handler.queue_free() - - -func _input(event): - if event is InputEventKey and event.is_pressed(): - for shortcut_action in _context_menus.values(): - var action :GdUnitContextMenuItem = shortcut_action - if action.shortcut().matches_event(event) and action.is_visible(active_script()): - #if not has_editor_focus(): - # return - action.execute() - accept_event() - return - - -func has_editor_focus() -> bool: - return Engine.get_main_loop().root.gui_get_focus_owner() == active_base_editor() - - -func on_script_changed(script :Script): - if script is Script: - var popups :Array[Node] = GdObjects.find_nodes_by_class(active_editor(), "PopupMenu", true) - for popup in popups: - if not popup.about_to_popup.is_connected(on_context_menu_show): - popup.about_to_popup.connect(on_context_menu_show.bind(script, popup)) - if not popup.id_pressed.is_connected(on_context_menu_pressed): - popup.id_pressed.connect(on_context_menu_pressed) - - -func on_context_menu_show(script :Script, context_menu :PopupMenu): - #prints("on_context_menu_show", _context_menus.keys(), context_menu, self) - context_menu.add_separator() - var current_index := context_menu.get_item_count() - for menu_id in _context_menus.keys(): - var menu_item :GdUnitContextMenuItem = _context_menus[menu_id] - if menu_item.is_visible(script): - context_menu.add_item(menu_item.name, menu_id) - context_menu.set_item_disabled(current_index, !menu_item.is_enabled(script)) - context_menu.set_item_shortcut(current_index, menu_item.shortcut(), true) - current_index += 1 - - -func on_context_menu_pressed(id :int): - if !_context_menus.has(id): - return - var menu_item :GdUnitContextMenuItem = _context_menus[id] - menu_item.execute() - - -func active_editor() -> ScriptEditorBase: - return _editor.get_current_editor() - - -func active_base_editor() -> TextEdit: - return active_editor().get_base_editor() - - -func active_script() -> Script: - return _editor.get_current_script() diff --git a/addons/gdUnit4/src/ui/parts/InspectorMonitor.gd b/addons/gdUnit4/src/ui/parts/InspectorMonitor.gd deleted file mode 100644 index ae6cbf9..0000000 --- a/addons/gdUnit4/src/ui/parts/InspectorMonitor.gd +++ /dev/null @@ -1,50 +0,0 @@ -@tool -extends PanelContainer - -signal jump_to_orphan_nodes - -@onready var ICON_GREEN = load("res://addons/gdUnit4/src/ui/assets/orphan/orphan_green.svg") -@onready var ICON_RED = load("res://addons/gdUnit4/src/ui/assets/orphan/orphan_animated_icon.tres") - -@onready var _time = $GridContainer/Time/value -@onready var _orphans = $GridContainer/Orphan/value -@onready var _orphan_button := $GridContainer/Orphan/Button - -var total_elapsed_time := 0 -var total_orphans := 0 - - -func _ready(): - GdUnitSignals.instance().gdunit_event.connect(_on_gdunit_event) - _time.text = "" - _orphans.text = "0" - - -func status_changed(elapsed_time :int, orphan_nodes :int): - total_elapsed_time += elapsed_time - total_orphans += orphan_nodes - _time.text = LocalTime.elapsed(total_elapsed_time) - _orphans.text = str(total_orphans) - if total_orphans > 0: - _orphan_button.icon = ICON_RED - - -func _on_gdunit_event(event :GdUnitEvent) -> void: - match event.type(): - GdUnitEvent.INIT: - _orphan_button.icon = ICON_GREEN - total_elapsed_time = 0 - total_orphans = 0 - status_changed(0, 0) - GdUnitEvent.TESTCASE_BEFORE: - pass - GdUnitEvent.TESTCASE_AFTER: - status_changed(0, event.orphan_nodes()) - GdUnitEvent.TESTSUITE_BEFORE: - pass - GdUnitEvent.TESTSUITE_AFTER: - status_changed(event.elapsed_time(), event.orphan_nodes()) - - -func _on_ToolButton_pressed(): - emit_signal("jump_to_orphan_nodes") diff --git a/addons/gdUnit4/src/ui/parts/InspectorMonitor.tscn b/addons/gdUnit4/src/ui/parts/InspectorMonitor.tscn deleted file mode 100644 index 1bbe911..0000000 --- a/addons/gdUnit4/src/ui/parts/InspectorMonitor.tscn +++ /dev/null @@ -1,91 +0,0 @@ -[gd_scene load_steps=4 format=2] - -[ext_resource path="res://addons/gdUnit4/src/ui/assets/orphan/orphan_green.svg" type="Texture2D" id=1] -[ext_resource path="res://addons/gdUnit4/src/ui/assets/clock.svg" type="Texture2D" id=2] -[ext_resource path="res://addons/gdUnit4/src/ui/parts/InspectorMonitor.gd" type="Script" id=3] - -[node name="Monitor" type="PanelContainer"] -anchor_right = 1.0 -anchor_bottom = 1.0 -offset_right = -793.0 -offset_bottom = -564.0 -clip_contents = true -size_flags_horizontal = 9 -size_flags_vertical = 9 -script = ExtResource( 3 ) -__meta__ = { -"_edit_use_anchors_": false -} - -[node name="GridContainer" type="GridContainer" parent="."] -offset_left = 7.0 -offset_top = 7.0 -offset_right = 224.0 -offset_bottom = 29.0 -clip_contents = true -size_flags_horizontal = 9 -columns = 2 - -[node name="Time" type="GridContainer" parent="GridContainer"] -offset_right = 63.0 -offset_bottom = 22.0 -clip_contents = true -columns = 2 - -[node name="Button" type="Button" parent="GridContainer/Time"] -offset_right = 59.0 -offset_bottom = 22.0 -tooltip_text = "Shows the total elapsed time of test execution." -size_flags_horizontal = 3 -text = "Time" -icon = ExtResource( 2 ) -align = 0 - -[node name="value" type="Label" parent="GridContainer/Time"] -use_parent_material = true -offset_left = 63.0 -offset_right = 63.0 -offset_bottom = 22.0 -size_flags_horizontal = 3 -size_flags_vertical = 1 -align = 2 -max_lines_visible = 1 -__meta__ = { -"_edit_use_anchors_": false -} - -[node name="Orphan" type="GridContainer" parent="GridContainer"] -offset_left = 67.0 -offset_right = 160.0 -offset_bottom = 22.0 -clip_contents = true -size_flags_horizontal = 9 -columns = 2 - -[node name="Button" type="Button" parent="GridContainer/Orphan"] -offset_right = 81.0 -offset_bottom = 22.0 -clip_contents = true -tooltip_text = "Shows the total detected orphan nodes. - -(Click) to jump to test." -size_flags_horizontal = 9 -size_flags_vertical = 3 -text = "Orphans" -icon = ExtResource( 1 ) -align = 0 - -[node name="value" type="Label" parent="GridContainer/Orphan"] -use_parent_material = true -offset_left = 85.0 -offset_right = 93.0 -offset_bottom = 22.0 -size_flags_horizontal = 3 -size_flags_vertical = 1 -text = "0" -align = 2 -max_lines_visible = 1 -__meta__ = { -"_edit_use_anchors_": false -} -[connection signal="pressed" from="GridContainer/Orphan/Button" to="." method="_on_ToolButton_pressed"] diff --git a/addons/gdUnit4/src/ui/parts/InspectorProgressBar.gd b/addons/gdUnit4/src/ui/parts/InspectorProgressBar.gd deleted file mode 100644 index 60ad483..0000000 --- a/addons/gdUnit4/src/ui/parts/InspectorProgressBar.gd +++ /dev/null @@ -1,45 +0,0 @@ -@tool -extends ProgressBar - -@onready var bar = $"." -@onready var status = $Label -@onready var style :StyleBoxFlat = bar.get("theme_override_styles/fill") - - -func _ready(): - GdUnitSignals.instance().gdunit_event.connect(_on_gdunit_event) - style.bg_color = Color.DARK_GREEN - update_text() - - -func progress_init(p_max_value :int) -> void: - bar.value = 0 - bar.max_value = p_max_value - style.bg_color = Color.DARK_GREEN - update_text() - - -func progress_update(p_value :int, is_failed :bool) -> void: - bar.value += p_value - update_text() - if is_failed: - style.bg_color = Color.DARK_RED - - -func update_text() -> void: - status.text = "%d:%d" % [bar.value, bar.max_value] - - -func _on_gdunit_event(event :GdUnitEvent) -> void: - match event.type(): - GdUnitEvent.INIT: - progress_init(event.total_count()) - - GdUnitEvent.TESTCASE_AFTER: - # we only count when the test is finished (excluding parameterized test iterrations) - # test_name: indicates a parameterized test run - if event.test_name().find(":") == -1: - progress_update(1, event.is_failed() or event.is_error()) - - GdUnitEvent.TESTSUITE_AFTER: - progress_update(0, event.is_failed() or event.is_error()) diff --git a/addons/gdUnit4/src/ui/parts/InspectorProgressBar.tscn b/addons/gdUnit4/src/ui/parts/InspectorProgressBar.tscn deleted file mode 100644 index 582d483..0000000 --- a/addons/gdUnit4/src/ui/parts/InspectorProgressBar.tscn +++ /dev/null @@ -1,34 +0,0 @@ -[gd_scene load_steps=3 format=3 uid="uid://dva3tonxsxrlk"] - -[ext_resource type="Script" path="res://addons/gdUnit4/src/ui/parts/InspectorProgressBar.gd" id="1"] - -[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_ayfir"] -bg_color = Color(0, 0.392157, 0, 1) - -[node name="ProgressBar" type="ProgressBar"] -custom_minimum_size = Vector2(0, 20) -anchors_preset = 15 -anchor_right = 1.0 -anchor_bottom = 1.0 -grow_horizontal = 2 -grow_vertical = 2 -size_flags_vertical = 9 -theme_override_styles/fill = SubResource("StyleBoxFlat_ayfir") -max_value = 0.0 -rounded = true -allow_greater = true -show_percentage = false -script = ExtResource("1") - -[node name="Label" type="Label" parent="."] -use_parent_material = true -layout_mode = 1 -anchors_preset = 15 -anchor_right = 1.0 -anchor_bottom = 1.0 -grow_horizontal = 2 -grow_vertical = 2 -size_flags_vertical = 3 -horizontal_alignment = 1 -vertical_alignment = 1 -max_lines_visible = 1 diff --git a/addons/gdUnit4/src/ui/parts/InspectorStatusBar.gd b/addons/gdUnit4/src/ui/parts/InspectorStatusBar.gd deleted file mode 100644 index 9622058..0000000 --- a/addons/gdUnit4/src/ui/parts/InspectorStatusBar.gd +++ /dev/null @@ -1,60 +0,0 @@ -@tool -extends PanelContainer - -signal failure_next -signal failure_prevous - -@onready var _errors = $GridContainer/Errors/value -@onready var _failures = $GridContainer/Failures/value -@onready var _button_failure_up := $GridContainer/Failures/buttons/failure_up -@onready var _button_failure_down := $GridContainer/Failures/buttons/failure_down - -var total_failed := 0 -var total_errors := 0 - - -func _ready(): - GdUnitSignals.instance().gdunit_event.connect(_on_gdunit_event) - _failures.text = "0" - _errors.text = "0" - var editor :EditorPlugin = Engine.get_meta("GdUnitEditorPlugin") - var editior_control := editor.get_editor_interface().get_base_control() - _button_failure_up.icon = GodotVersionFixures.get_icon(editior_control, "ArrowUp") - _button_failure_down.icon = GodotVersionFixures.get_icon(editior_control, "ArrowDown") - - -func status_changed(errors :int, failed :int): - total_failed += failed - total_errors += errors - _failures.text = str(total_failed) - _errors.text = str(total_errors) - - -func _on_gdunit_event(event :GdUnitEvent) -> void: - match event.type(): - GdUnitEvent.INIT: - total_failed = 0 - total_errors = 0 - status_changed(0, 0) - GdUnitEvent.TESTCASE_BEFORE: - pass - GdUnitEvent.TESTCASE_AFTER: - if event.is_error(): - status_changed(event.error_count(), 0) - else: - status_changed(0, event.failed_count()) - GdUnitEvent.TESTSUITE_BEFORE: - pass - GdUnitEvent.TESTSUITE_AFTER: - if event.is_error(): - status_changed(event.error_count(), 0) - else: - status_changed(0, event.failed_count()) - - -func _on_failure_up_pressed(): - emit_signal("failure_prevous") - - -func _on_failure_down_pressed(): - emit_signal("failure_next") diff --git a/addons/gdUnit4/src/ui/parts/InspectorStatusBar.tscn b/addons/gdUnit4/src/ui/parts/InspectorStatusBar.tscn deleted file mode 100644 index 86982f3..0000000 --- a/addons/gdUnit4/src/ui/parts/InspectorStatusBar.tscn +++ /dev/null @@ -1,147 +0,0 @@ -[gd_scene load_steps=8 format=2] - -[ext_resource path="res://addons/gdUnit4/src/ui/parts/InspectorStatusBar.gd" type="Script" id=3] -[ext_resource path="res://addons/gdUnit4/src/ui/assets/failures.svg" type="Texture2D" id=4] -[ext_resource path="res://addons/gdUnit4/src/ui/assets/errors.svg" type="Texture2D" id=5] - -[sub_resource type="Image" id=1] -data = { -"data": PackedByteArray( 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 218, 218, 218, 0, 222, 222, 222, 0, 222, 222, 222, 0, 218, 218, 218, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 218, 218, 218, 0, 218, 218, 218, 21, 222, 222, 222, 199, 222, 222, 222, 198, 218, 218, 218, 21, 218, 218, 218, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 218, 218, 218, 0, 218, 218, 218, 21, 223, 223, 223, 211, 223, 223, 223, 254, 223, 223, 223, 254, 223, 223, 223, 209, 218, 218, 218, 21, 217, 217, 217, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 217, 217, 217, 0, 218, 218, 218, 21, 223, 223, 223, 210, 223, 223, 223, 254, 223, 223, 223, 254, 223, 223, 223, 254, 223, 223, 223, 254, 223, 223, 223, 209, 216, 216, 216, 20, 215, 215, 215, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 216, 216, 216, 0, 216, 216, 216, 20, 223, 223, 223, 209, 223, 223, 223, 254, 222, 222, 222, 206, 223, 223, 223, 254, 223, 223, 223, 251, 222, 222, 222, 207, 223, 223, 223, 254, 223, 223, 223, 209, 214, 214, 214, 19, 214, 214, 214, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 223, 223, 223, 0, 223, 223, 223, 193, 223, 223, 223, 254, 222, 222, 222, 206, 214, 214, 214, 19, 223, 223, 223, 254, 223, 223, 223, 249, 214, 214, 214, 19, 223, 223, 223, 208, 223, 223, 223, 254, 223, 223, 223, 193, 223, 223, 223, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 223, 223, 223, 0, 223, 223, 223, 176, 223, 223, 223, 192, 214, 214, 214, 19, 217, 217, 217, 0, 223, 223, 223, 254, 223, 223, 223, 249, 217, 217, 217, 0, 214, 214, 214, 19, 223, 223, 223, 192, 223, 223, 223, 176, 223, 223, 223, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 223, 223, 223, 0, 223, 223, 223, 0, 214, 214, 214, 0, 223, 223, 223, 0, 223, 223, 223, 254, 223, 223, 223, 249, 223, 223, 223, 0, 214, 214, 214, 0, 223, 223, 223, 0, 223, 223, 223, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 223, 223, 223, 0, 223, 223, 223, 254, 223, 223, 223, 249, 223, 223, 223, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 223, 223, 223, 0, 223, 223, 223, 176, 223, 223, 223, 176, 223, 223, 223, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 223, 223, 223, 0, 223, 223, 223, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ), -"format": "RGBA8", -"height": 16, -"mipmaps": false, -"width": 16 -} - -[sub_resource type="ImageTexture" id=2] -flags = 0 -flags = 0 -image = SubResource( 1 ) -size = Vector2( 16, 16 ) - -[sub_resource type="Image" id=3] -data = { -"data": PackedByteArray( 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 223, 223, 223, 0, 223, 223, 223, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 223, 223, 223, 0, 223, 223, 223, 176, 223, 223, 223, 176, 223, 223, 223, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 223, 223, 223, 0, 223, 223, 223, 249, 223, 223, 223, 254, 223, 223, 223, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 223, 223, 223, 0, 223, 223, 223, 0, 214, 214, 214, 0, 223, 223, 223, 0, 223, 223, 223, 249, 223, 223, 223, 254, 223, 223, 223, 0, 212, 212, 212, 0, 222, 222, 222, 0, 223, 223, 223, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 223, 223, 223, 0, 223, 223, 223, 176, 223, 223, 223, 192, 214, 214, 214, 19, 216, 216, 216, 0, 223, 223, 223, 249, 223, 223, 223, 254, 215, 215, 215, 0, 212, 212, 212, 18, 222, 222, 222, 191, 223, 223, 223, 176, 223, 223, 223, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 223, 223, 223, 0, 223, 223, 223, 194, 223, 223, 223, 254, 222, 222, 222, 206, 212, 212, 212, 18, 223, 223, 223, 249, 223, 223, 223, 254, 210, 210, 210, 17, 222, 222, 222, 206, 223, 223, 223, 254, 223, 223, 223, 193, 223, 223, 223, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 218, 218, 218, 0, 218, 218, 218, 21, 223, 223, 223, 212, 223, 223, 223, 254, 222, 222, 222, 206, 223, 223, 223, 251, 223, 223, 223, 254, 222, 222, 222, 206, 223, 223, 223, 254, 223, 223, 223, 212, 218, 218, 218, 21, 218, 218, 218, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 218, 218, 218, 0, 218, 218, 218, 21, 223, 223, 223, 212, 223, 223, 223, 254, 223, 223, 223, 254, 223, 223, 223, 254, 223, 223, 223, 254, 223, 223, 223, 212, 220, 220, 220, 22, 219, 219, 219, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 219, 219, 219, 0, 220, 220, 220, 22, 223, 223, 223, 212, 223, 223, 223, 254, 223, 223, 223, 254, 223, 223, 223, 212, 221, 221, 221, 23, 220, 220, 220, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 220, 220, 220, 0, 221, 221, 221, 23, 222, 222, 222, 198, 223, 223, 223, 200, 221, 221, 221, 23, 221, 221, 221, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 221, 221, 221, 0, 222, 222, 222, 0, 223, 223, 223, 0, 221, 221, 221, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ), -"format": "RGBA8", -"height": 16, -"mipmaps": false, -"width": 16 -} - -[sub_resource type="ImageTexture" id=4] -flags = 0 -flags = 0 -image = SubResource( 3 ) -size = Vector2( 16, 16 ) - -[node name="StatusBar" type="PanelContainer"] -anchor_right = 1.0 -anchor_bottom = 1.0 -offset_right = -793.0 -offset_bottom = -564.0 -clip_contents = true -size_flags_horizontal = 9 -size_flags_vertical = 9 -script = ExtResource( 3 ) -__meta__ = { -"_edit_use_anchors_": false -} - -[node name="GridContainer" type="GridContainer" parent="."] -offset_left = 7.0 -offset_top = 7.0 -offset_right = 240.0 -offset_bottom = 31.0 -clip_contents = true -size_flags_horizontal = 9 -columns = 3 - -[node name="Errors" type="GridContainer" parent="GridContainer"] -offset_right = 76.0 -offset_bottom = 24.0 -clip_contents = true -columns = 2 - -[node name="Button" type="Button" parent="GridContainer/Errors"] -offset_right = 64.0 -offset_bottom = 24.0 -tooltip_text = "Shows the total test errors." -size_flags_horizontal = 3 -size_flags_vertical = 3 -text = "Errors" -icon = ExtResource( 5 ) -align = 0 - -[node name="value" type="Label" parent="GridContainer/Errors"] -use_parent_material = true -offset_left = 68.0 -offset_right = 76.0 -offset_bottom = 24.0 -size_flags_horizontal = 3 -size_flags_vertical = 3 -text = "0" -align = 2 -max_lines_visible = 1 -__meta__ = { -"_edit_use_anchors_": false -} - -[node name="Failures" type="GridContainer" parent="GridContainer"] -offset_left = 80.0 -offset_right = 233.0 -offset_bottom = 24.0 -clip_contents = true -size_flags_horizontal = 9 -columns = 3 - -[node name="Button" type="Button" parent="GridContainer/Failures"] -offset_right = 77.0 -offset_bottom = 24.0 -clip_contents = true -tooltip_text = "Shows the total test failures." -size_flags_horizontal = 9 -size_flags_vertical = 3 -text = "Failures" -icon = ExtResource( 4 ) -align = 0 - -[node name="value" type="Label" parent="GridContainer/Failures"] -use_parent_material = true -offset_left = 81.0 -offset_right = 89.0 -offset_bottom = 24.0 -size_flags_horizontal = 3 -size_flags_vertical = 3 -text = "0" -align = 2 -max_lines_visible = 1 -__meta__ = { -"_edit_use_anchors_": false -} - -[node name="buttons" type="GridContainer" parent="GridContainer/Failures"] -offset_left = 93.0 -offset_right = 153.0 -offset_bottom = 24.0 -size_flags_vertical = 3 -columns = 2 - -[node name="failure_up" type="Button" parent="GridContainer/Failures/buttons"] -offset_right = 28.0 -offset_bottom = 24.0 -tooltip_text = "Shows the total test errors." -size_flags_horizontal = 3 -size_flags_vertical = 3 -icon = SubResource( 2 ) - -[node name="failure_down" type="Button" parent="GridContainer/Failures/buttons"] -offset_left = 32.0 -offset_right = 60.0 -offset_bottom = 24.0 -tooltip_text = "Shows the total test errors." -size_flags_horizontal = 3 -size_flags_vertical = 3 -icon = SubResource( 4 ) - -[connection signal="pressed" from="GridContainer/Failures/buttons/failure_up" to="." method="_on_failure_up_pressed"] -[connection signal="pressed" from="GridContainer/Failures/buttons/failure_down" to="." method="_on_failure_down_pressed"] diff --git a/addons/gdUnit4/src/ui/parts/InspectorToolBar.gd b/addons/gdUnit4/src/ui/parts/InspectorToolBar.gd deleted file mode 100644 index 0072249..0000000 --- a/addons/gdUnit4/src/ui/parts/InspectorToolBar.gd +++ /dev/null @@ -1,113 +0,0 @@ -@tool -extends HBoxContainer - -signal run_overall_pressed(debug :bool) -signal run_pressed(debug :bool) -signal stop_pressed() - -@onready var debug_icon_image :Texture2D = load("res://addons/gdUnit4/src/ui/assets/PlayDebug.svg") -@onready var overall_icon_image :Texture2D = load("res://addons/gdUnit4/src/ui/assets/PlayOverall.svg") -@onready var _version_label := %version -@onready var _button_wiki := %help -@onready var _tool_button := %tool -@onready var _button_run_overall :Button = %"run-overall" -@onready var _button_run := %run -@onready var _button_run_debug := %debug -@onready var _button_stop := %stop - - -const SETTINGS_SHORTCUT_MAPPING := { - GdUnitSettings.SHORTCUT_INSPECTOR_RERUN_TEST : GdUnitShortcut.ShortCut.RERUN_TESTS, - GdUnitSettings.SHORTCUT_INSPECTOR_RERUN_TEST_DEBUG : GdUnitShortcut.ShortCut.RERUN_TESTS_DEBUG, - GdUnitSettings.SHORTCUT_INSPECTOR_RUN_TEST_OVERALL : GdUnitShortcut.ShortCut.RUN_TESTS_OVERALL, - GdUnitSettings.SHORTCUT_INSPECTOR_RUN_TEST_STOP : GdUnitShortcut.ShortCut.STOP_TEST_RUN, -} - - -func _ready(): - GdUnit4Version.init_version_label(_version_label) - var command_handler := GdUnitCommandHandler.instance() - run_pressed.connect(command_handler._on_run_pressed) - run_overall_pressed.connect(command_handler._on_run_overall_pressed) - stop_pressed.connect(command_handler._on_stop_pressed) - command_handler.gdunit_runner_start.connect(_on_gdunit_runner_start) - command_handler.gdunit_runner_stop.connect(_on_gdunit_runner_stop) - GdUnitSignals.instance().gdunit_settings_changed.connect(_on_gdunit_settings_changed) - init_buttons() - init_shortcuts(command_handler) - - -func init_buttons() -> void: - var editor :EditorPlugin = EditorPlugin.new() - var editior_control := editor.get_editor_interface().get_base_control() - _button_run_overall.icon = overall_icon_image - _button_run_overall.visible = GdUnitSettings.is_inspector_toolbar_button_show() - _button_run.icon = GodotVersionFixures.get_icon(editior_control, "Play") - _button_run_debug.icon = debug_icon_image - _button_stop.icon = GodotVersionFixures.get_icon(editior_control, "Stop") - _tool_button.icon = GodotVersionFixures.get_icon(editior_control, "Tools") - _button_wiki.icon = GodotVersionFixures.get_icon(editior_control, "HelpSearch") - - -func init_shortcuts(command_handler :GdUnitCommandHandler) -> void: - _button_run.shortcut = command_handler.get_shortcut(GdUnitShortcut.ShortCut.RERUN_TESTS) - _button_run_overall.shortcut = command_handler.get_shortcut(GdUnitShortcut.ShortCut.RUN_TESTS_OVERALL) - _button_run_debug.shortcut = command_handler.get_shortcut(GdUnitShortcut.ShortCut.RERUN_TESTS_DEBUG) - _button_stop.shortcut = command_handler.get_shortcut(GdUnitShortcut.ShortCut.STOP_TEST_RUN) - # register for shortcut changes - GdUnitSignals.instance().gdunit_settings_changed.connect(_on_settings_changed.bind(command_handler)) - - -func _on_runoverall_pressed(debug := false): - run_overall_pressed.emit(debug) - - -func _on_run_pressed(debug := false): - run_pressed.emit(debug) - - -func _on_stop_pressed(): - stop_pressed.emit() - - -func _on_gdunit_runner_start(): - _button_run_overall.disabled = true - _button_run.disabled = true - _button_run_debug.disabled = true - _button_stop.disabled = false - - -func _on_gdunit_runner_stop(_client_id :int): - _button_run_overall.disabled = false - _button_run.disabled = false - _button_run_debug.disabled = false - _button_stop.disabled = true - - -func _on_gdunit_settings_changed(_property :GdUnitProperty): - _button_run_overall.visible = GdUnitSettings.is_inspector_toolbar_button_show() - - -func _on_wiki_pressed(): - OS.shell_open("https://mikeschulze.github.io/gdUnit4/") - - -func _on_btn_tool_pressed(): - var tool_popup = load("res://addons/gdUnit4/src/ui/settings/GdUnitSettingsDialog.tscn").instantiate() - get_parent_control().add_child(tool_popup) - - -func _on_settings_changed(property :GdUnitProperty, command_handler :GdUnitCommandHandler): - # needs to wait a frame to be command handler notified first for settings changes - await get_tree().process_frame - if SETTINGS_SHORTCUT_MAPPING.has(property.name()): - var shortcut :GdUnitShortcut.ShortCut = SETTINGS_SHORTCUT_MAPPING.get(property.name(), GdUnitShortcut.ShortCut.NONE) - match shortcut: - GdUnitShortcut.ShortCut.RERUN_TESTS: - _button_run.shortcut = command_handler.get_shortcut(shortcut) - GdUnitShortcut.ShortCut.RUN_TESTS_OVERALL: - _button_run_overall.shortcut = command_handler.get_shortcut(shortcut) - GdUnitShortcut.ShortCut.RERUN_TESTS_DEBUG: - _button_run_debug.shortcut = command_handler.get_shortcut(shortcut) - GdUnitShortcut.ShortCut.STOP_TEST_RUN: - _button_stop.shortcut = command_handler.get_shortcut(shortcut) diff --git a/addons/gdUnit4/src/ui/parts/InspectorToolBar.tscn b/addons/gdUnit4/src/ui/parts/InspectorToolBar.tscn deleted file mode 100644 index c44fea8..0000000 --- a/addons/gdUnit4/src/ui/parts/InspectorToolBar.tscn +++ /dev/null @@ -1,83 +0,0 @@ -[gd_scene load_steps=4 format=3 uid="uid://dx7xy4dgi3wwb"] - -[ext_resource type="Texture2D" uid="uid://tkrsqx2oxw6o" path="res://addons/gdUnit4/src/ui/assets/PlayDebug.svg" id="2_4h4dw"] -[ext_resource type="Texture2D" uid="uid://de1q5raia84bn" path="res://addons/gdUnit4/src/ui/assets/PlayOverall.svg" id="2_s3tbo"] -[ext_resource type="Script" path="res://addons/gdUnit4/src/ui/parts/InspectorToolBar.gd" id="3"] - -[node name="ToolBar" type="HBoxContainer"] -anchors_preset = 15 -anchor_right = 1.0 -anchor_bottom = 1.0 -grow_horizontal = 0 -grow_vertical = 2 -size_flags_vertical = 3 -script = ExtResource("3") - -[node name="Tools" type="HBoxContainer" parent="."] -layout_mode = 2 - -[node name="VSeparator2" type="VSeparator" parent="Tools"] -layout_mode = 2 -size_flags_horizontal = 3 -size_flags_vertical = 9 - -[node name="help" type="Button" parent="Tools"] -unique_name_in_owner = true -layout_mode = 2 - -[node name="tool" type="Button" parent="Tools"] -unique_name_in_owner = true -layout_mode = 2 -tooltip_text = "GdUnit Settings" - -[node name="VSeparator" type="VSeparator" parent="Tools"] -layout_mode = 2 - -[node name="run-overall" type="Button" parent="Tools"] -unique_name_in_owner = true -use_parent_material = true -layout_mode = 2 -tooltip_text = "Run overall tests" -icon = ExtResource("2_s3tbo") - -[node name="run" type="Button" parent="Tools"] -unique_name_in_owner = true -use_parent_material = true -layout_mode = 2 -tooltip_text = "Rerun unit tests" - -[node name="debug" type="Button" parent="Tools"] -unique_name_in_owner = true -use_parent_material = true -layout_mode = 2 -tooltip_text = "Rerun unit tests (Debug)" -icon = ExtResource("2_4h4dw") - -[node name="stop" type="Button" parent="Tools"] -unique_name_in_owner = true -use_parent_material = true -layout_mode = 2 -tooltip_text = "Stops runing unit tests" -disabled = true - -[node name="VSeparator3" type="VSeparator" parent="Tools"] -layout_mode = 2 - -[node name="CenterContainer" type="MarginContainer" parent="Tools"] -use_parent_material = true -layout_mode = 2 -size_flags_horizontal = 3 - -[node name="version" type="Label" parent="Tools/CenterContainer"] -unique_name_in_owner = true -use_parent_material = true -layout_mode = 2 -size_flags_horizontal = 3 -size_flags_vertical = 13 - -[connection signal="pressed" from="Tools/help" to="." method="_on_wiki_pressed"] -[connection signal="pressed" from="Tools/tool" to="." method="_on_btn_tool_pressed"] -[connection signal="pressed" from="Tools/run-overall" to="." method="_on_runoverall_pressed" binds= [false]] -[connection signal="pressed" from="Tools/run" to="." method="_on_run_pressed" binds= [false]] -[connection signal="pressed" from="Tools/debug" to="." method="_on_run_pressed" binds= [true]] -[connection signal="pressed" from="Tools/stop" to="." method="_on_stop_pressed"] diff --git a/addons/gdUnit4/src/ui/parts/InspectorTreeMainPanel.gd b/addons/gdUnit4/src/ui/parts/InspectorTreeMainPanel.gd deleted file mode 100644 index fdd6f2f..0000000 --- a/addons/gdUnit4/src/ui/parts/InspectorTreeMainPanel.gd +++ /dev/null @@ -1,589 +0,0 @@ -@tool -extends VSplitContainer - -signal run_testcase(test_suite_resource_path, test_case, test_param_index, run_debug) -signal run_testsuite - -const CONTEXT_MENU_RUN_ID = 0 -const CONTEXT_MENU_DEBUG_ID = 1 - -@onready var _tree :Tree = $Panel/Tree -@onready var _report_list :Node = $report/ScrollContainer/list -@onready var _report_template :RichTextLabel = $report/report_template -@onready var _context_menu :PopupMenu = $contextMenu - - -# tree icons -@onready var ICON_SPINNER = load("res://addons/gdUnit4/src/ui/assets/spinner.tres") -@onready var ICON_TEST_DEFAULT = load("res://addons/gdUnit4/src/ui/assets/TestCase.svg") -@onready var ICON_TEST_SUCCESS = load("res://addons/gdUnit4/src/ui/assets/TestCaseSuccess.svg") -@onready var ICON_TEST_FAILED = load("res://addons/gdUnit4/src/ui/assets/TestCaseFailed.svg") -@onready var ICON_TEST_ERROR = load("res://addons/gdUnit4/src/ui/assets/TestCaseError.svg") -@onready var ICON_TEST_SUCCESS_ORPHAN = load("res://addons/gdUnit4/src/ui/assets/TestCase_success_orphan.tres") -@onready var ICON_TEST_FAILED_ORPHAN = load("res://addons/gdUnit4/src/ui/assets/TestCase_failed_orphan.tres") -@onready var ICON_TEST_ERRORS_ORPHAN = load("res://addons/gdUnit4/src/ui/assets/TestCase_error_orphan.tres") -@onready var debug_icon_image :Texture2D = load("res://addons/gdUnit4/src/ui/assets/PlayDebug.svg") - -enum GdUnitType { - TEST_SUITE, - TEST_CASE -} - -enum STATE { - INITIAL, - RUNNING, - SUCCESS, - WARNING, - FAILED, - ERROR, - ABORDED, - SKIPPED -} - -const META_GDUNIT_NAME := "gdUnit_name" -const META_GDUNIT_STATE := "gdUnit_state" -const META_GDUNIT_TYPE := "gdUnit_type" -const META_GDUNIT_TOTAL_TESTS := "gdUnit_suite_total_tests" -const META_GDUNIT_SUCCESS_TESTS := "gdUnit_suite_success_tests" -const META_GDUNIT_REPORT := "gdUnit_report" -const META_GDUNIT_ORPHAN := "gdUnit_orphan" -const META_RESOURCE_PATH := "resource_path" -const META_LINE_NUMBER := "line_number" -const META_TEST_PARAM_INDEX := "test_param_index" - -var _editor :EditorPlugin -var _tree_root :TreeItem -var _current_failures := Array() -var _item_hash := Dictionary() - - -func _build_cache_key(resource_path :String, test_name :String) -> Array: - return [resource_path, test_name] - - -func get_tree_item(event :GdUnitEvent) -> TreeItem: - var key := _build_cache_key(event.resource_path(), event.test_name()) - return _item_hash.get(key, null) - - -func add_tree_item_to_cache(resource_path :String, test_name :String, item :TreeItem) -> void: - var key := _build_cache_key(resource_path, test_name) - _item_hash[key] = item - - -func clear_tree_item_cache() -> void: - _item_hash.clear() - - -func _find_item(parent :TreeItem, resource_path :String, test_case :String = "") -> TreeItem: - var item = _find_by_resource_path(parent, resource_path) - if not item: - return null - if test_case.is_empty(): - return item - return _find_by_name(item, test_case) - - -func _find_by_resource_path(parent :TreeItem, resource_path :String) -> TreeItem: - for item in parent.get_children(): - if item.get_meta(META_RESOURCE_PATH) == resource_path: - return item - return null - - -func _find_by_name(parent :TreeItem, item_name :String) -> TreeItem: - for item in parent.get_children(): - if item.get_meta(META_GDUNIT_NAME) == item_name: - return item - return null - - -func is_state_running(item :TreeItem) -> bool: - return item.has_meta(META_GDUNIT_STATE) and item.get_meta(META_GDUNIT_STATE) == STATE.RUNNING - - -func is_state_success(item :TreeItem) -> bool: - return item.has_meta(META_GDUNIT_STATE) and item.get_meta(META_GDUNIT_STATE) == STATE.SUCCESS - - -func is_state_warning(item :TreeItem) -> bool: - return item.has_meta(META_GDUNIT_STATE) and item.get_meta(META_GDUNIT_STATE) == STATE.WARNING - - -func is_state_failed(item :TreeItem) -> bool: - return item.has_meta(META_GDUNIT_STATE) and item.get_meta(META_GDUNIT_STATE) == STATE.FAILED - - -func is_state_error(item :TreeItem) -> bool: - return item.has_meta(META_GDUNIT_STATE) and (item.get_meta(META_GDUNIT_STATE) == STATE.ERROR or item.get_meta(META_GDUNIT_STATE) == STATE.ABORDED) - - -func is_item_state_orphan(item :TreeItem) -> bool: - return item.has_meta(META_GDUNIT_ORPHAN) - - -func is_test_suite(item :TreeItem) -> bool: - return item.has_meta(META_GDUNIT_TYPE) and item.get_meta(META_GDUNIT_TYPE) == GdUnitType.TEST_SUITE - - -func _ready(): - if Engine.is_editor_hint(): - _editor = Engine.get_meta("GdUnitEditorPlugin") - var editior_control := _editor.get_editor_interface().get_base_control() - _context_menu.set_item_icon(CONTEXT_MENU_RUN_ID, GodotVersionFixures.get_icon(editior_control, "Play")) - _context_menu.set_item_icon(CONTEXT_MENU_DEBUG_ID, debug_icon_image) - init_tree() - GdUnitSignals.instance().gdunit_add_test_suite.connect(_on_gdunit_add_test_suite) - GdUnitSignals.instance().gdunit_event.connect(_on_gdunit_event) - var command_handler := GdUnitCommandHandler.instance() - command_handler.gdunit_runner_start.connect(_on_gdunit_runner_start) - command_handler.gdunit_runner_stop.connect(_on_gdunit_runner_stop) - - -# we need current to manually redraw bacause of the animation bug -# https://github.com/godotengine/godot/issues/69330 -func _process(_delta): - if is_visible_in_tree(): - queue_redraw() - - -func init_tree() -> void: - cleanup_tree() - _tree.set_hide_root(true) - _tree.ensure_cursor_is_visible() - _tree.allow_rmb_select = true - _tree_root = _tree.create_item() - # fix tree icon scaling - var scale_factor := _editor.get_editor_interface().get_editor_scale() if Engine.is_editor_hint() else 1.0 - _tree.set("theme_override_constants/icon_max_width", 16*scale_factor) - - -func cleanup_tree() -> void: - clear_failures() - clear_tree_item_cache() - if not _tree_root: - return - _free_recursive() - _tree.clear() - # clear old reports - for child in _report_list.get_children(): - _report_list.remove_child(child) - - -func _free_recursive(items := _tree_root.get_children()) -> void: - for item in items: - _free_recursive(item.get_children()) - item.call_deferred("free") - - -func select_item(item :TreeItem) -> void: - if not item.is_selected(0): - item.select(0) - # _tree.ensure_cursor_is_visible() - _tree.scroll_to_item(item) - - -func set_state_running(item :TreeItem) -> void: - item.set_custom_color(0, Color.DARK_GREEN) - item.set_icon(0, ICON_SPINNER) - item.set_tooltip_text(0, "") - item.set_meta(META_GDUNIT_STATE, STATE.RUNNING) - item.remove_meta(META_GDUNIT_REPORT) - item.remove_meta(META_GDUNIT_ORPHAN) - item.collapsed = false - # force scrolling to current test case - select_item(item) - - -func set_state_succeded(item :TreeItem) -> void: - item.set_custom_color(0, Color.GREEN) - item.set_icon(0, ICON_TEST_SUCCESS) - item.set_meta(META_GDUNIT_STATE, STATE.SUCCESS) - item.collapsed = GdUnitSettings.is_inspector_node_collapse() - - -func set_state_skipped(item :TreeItem) -> void: - item.set_meta(META_GDUNIT_STATE, STATE.SKIPPED) - item.set_suffix(0, "(skipped)") - item.set_custom_color(0, Color.DARK_GRAY) - item.set_icon(0, ICON_TEST_DEFAULT) - item.collapsed = false - - -func set_state_warnings(item :TreeItem) -> void: - # Do not overwrite higher states - if is_state_error(item) or is_state_failed(item): - return - item.set_meta(META_GDUNIT_STATE, STATE.WARNING) - item.set_custom_color(0, Color.YELLOW) - item.set_icon(0, ICON_TEST_SUCCESS) - item.collapsed = false - - -func set_state_failed(item :TreeItem) -> void: - # Do not overwrite higher states - if is_state_error(item): - return - item.set_meta(META_GDUNIT_STATE, STATE.FAILED) - item.set_custom_color(0, Color.LIGHT_BLUE) - item.set_icon(0, ICON_TEST_FAILED) - item.collapsed = false - - -func set_state_error(item :TreeItem) -> void: - item.set_meta(META_GDUNIT_STATE, STATE.ERROR) - item.set_custom_color(0, Color.DARK_RED) - item.set_suffix(0, item.get_suffix(0)) - item.set_icon(0, ICON_TEST_ERROR) - item.collapsed = false - - -func set_state_aborted(item :TreeItem) -> void: - item.set_meta(META_GDUNIT_STATE, STATE.ABORDED) - item.set_icon(0, ICON_TEST_ERROR) - item.set_custom_color(0, Color.DARK_RED) - item.set_suffix(0, "(aborted)") - item.clear_custom_bg_color(0) - item.collapsed = false - - -func set_elapsed_time(item :TreeItem, time :int) -> void: - item.set_suffix(0, "(%s)" % LocalTime.elapsed(time)) - - -func set_state_orphan(item :TreeItem, event: GdUnitEvent) -> void: - var orphan_count = event.statistic(GdUnitEvent.ORPHAN_NODES) - if orphan_count == 0: - return - if item.has_meta(META_GDUNIT_ORPHAN): - orphan_count += item.get_meta(META_GDUNIT_ORPHAN) - item.set_meta(META_GDUNIT_ORPHAN, orphan_count) - item.set_custom_color(0, Color.YELLOW) - item.set_tooltip_text(0, "Total <%d> orphan nodes detected." % orphan_count) - if is_state_error(item): - item.set_icon(0, ICON_TEST_ERRORS_ORPHAN) - elif is_state_failed(item): - item.set_icon(0, ICON_TEST_FAILED_ORPHAN) - elif is_state_warning(item): - item.set_icon(0, ICON_TEST_SUCCESS_ORPHAN) - - -func update_state(item: TreeItem, event :GdUnitEvent) -> void: - if is_state_running(item) and event.is_success(): - set_state_succeded(item) - else: - if event.is_skipped(): - set_state_skipped(item) - elif event.is_error(): - set_state_error(item) - elif event.is_failed(): - set_state_failed(item) - elif event.is_warning(): - set_state_warnings(item) - for report in event.reports(): - add_report(item, report) - set_state_orphan(item, event) - - -func add_report(item :TreeItem, report: GdUnitReport) -> void: - var reports = [] - if item.has_meta(META_GDUNIT_REPORT): - reports = item.get_meta(META_GDUNIT_REPORT) - reports.append(report) - item.set_meta(META_GDUNIT_REPORT, reports) - - -func abort_running(items := _tree_root.get_children()) -> void: - for item in items: - if is_state_running(item): - set_state_aborted(item) - abort_running(item.get_children()) - - -func select_first_failure() -> void: - if not _current_failures.is_empty(): - select_item(_current_failures[0]) - - -func select_last_failure() -> void: - if not _current_failures.is_empty(): - select_item(_current_failures[-1]) - - -func clear_failures() -> void: - _current_failures.clear() - - -func collect_failures_and_errors(items := _tree_root.get_children()) -> Array: - for item in items: - if not is_test_suite(item) and (is_state_failed(item) or is_state_error(item)): - _current_failures.append(item) - collect_failures_and_errors(item.get_children()) - return _current_failures - - -func select_next_failure() -> void: - var current_selected := _tree.get_selected() - if current_selected == null: - select_first_failure() - return - if _current_failures.is_empty(): - return - var index := _current_failures.find(current_selected) - if index == -1 or index == _current_failures.size()-1: - select_item(_current_failures[0]) - else: - select_item(_current_failures[index+1]) - - -func select_previous_failure() -> void: - var current_selected := _tree.get_selected() - if current_selected == null: - select_last_failure() - return - if _current_failures.is_empty(): - return - var index := _current_failures.find(current_selected) - if index == -1 or index == 0: - select_item(_current_failures[_current_failures.size()-1]) - else: - select_item(_current_failures[index-1]) - - -func select_first_orphan() -> void: - for parent in _tree_root.get_children(): - if not is_state_success(parent): - for item in parent.get_children(): - if is_item_state_orphan(item): - parent.set_collapsed(false) - select_item(item) - return - - -func show_failed_report(selected_item) -> void: - # clear old reports - for child in _report_list.get_children(): - _report_list.remove_child(child) - child.queue_free() - - if selected_item == null or not selected_item.has_meta(META_GDUNIT_REPORT): - return - # add new reports - for r in selected_item.get_meta(META_GDUNIT_REPORT): - var report := r as GdUnitReport - var reportNode :RichTextLabel = _report_template.duplicate() - _report_list.add_child(reportNode) - reportNode.append_text(report.to_string()) - reportNode.visible = true - - -func update_test_suite(event :GdUnitEvent) -> void: - var item := _find_by_resource_path(_tree_root, event.resource_path()) - if not item: - push_error("Internal Error: Can't find test suite %s" % event.suite_name()) - return - if event.type() == GdUnitEvent.TESTSUITE_BEFORE: - set_state_running(item) - return - if event.type() == GdUnitEvent.TESTSUITE_AFTER: - set_elapsed_time(item, event.elapsed_time()) - update_state(item, event) - - -func update_test_case(event :GdUnitEvent) -> void: - var item := get_tree_item(event) - if not item: - push_error("Internal Error: Can't find test case %s:%s" % [event.suite_name(), event.test_name()]) - return - if event.type() == GdUnitEvent.TESTCASE_BEFORE: - set_state_running(item) - return - if event.type() == GdUnitEvent.TESTCASE_AFTER: - set_elapsed_time(item, event.elapsed_time()) - _update_parent_item_state(item, event.is_success()) - update_state(item, event) - - -func add_test_suite(test_suite :GdUnitTestSuiteDto) -> void: - var item := _tree.create_item(_tree_root) - var suite_name := test_suite.name() - var test_count := test_suite.test_case_count() - - item.set_icon(0, ICON_TEST_DEFAULT) - item.set_meta(META_GDUNIT_STATE, STATE.INITIAL) - item.set_meta(META_GDUNIT_NAME, suite_name) - item.set_meta(META_GDUNIT_TYPE, GdUnitType.TEST_SUITE) - item.set_meta(META_GDUNIT_TOTAL_TESTS, test_count) - item.set_meta(META_GDUNIT_SUCCESS_TESTS, 0) - item.set_meta(META_RESOURCE_PATH, test_suite.path()) - item.set_meta(META_LINE_NUMBER, 1) - item.collapsed = true - _update_item_counter(item) - for test_case in test_suite.test_cases(): - add_test(item, test_case) - - -func _update_item_counter(item: TreeItem): - if item.has_meta(META_GDUNIT_TOTAL_TESTS): - item.set_text(0, "(%s/%s) %s" % [ - item.get_meta(META_GDUNIT_SUCCESS_TESTS), - item.get_meta(META_GDUNIT_TOTAL_TESTS), - item.get_meta(META_GDUNIT_NAME)]) - - -func _update_parent_item_state(item: TreeItem, success : bool): - if success: - var parent_item := item.get_parent() - var successes: int = parent_item.get_meta(META_GDUNIT_SUCCESS_TESTS) - parent_item.set_meta(META_GDUNIT_SUCCESS_TESTS, successes + 1) - _update_item_counter(parent_item) - - -func add_test(parent :TreeItem, test_case :GdUnitTestCaseDto) -> void: - var item := _tree.create_item(parent) - var test_name := test_case.name() - item.set_text(0, test_name) - item.set_icon(0, ICON_TEST_DEFAULT) - item.set_meta(META_GDUNIT_STATE, STATE.INITIAL) - item.set_meta(META_GDUNIT_NAME, test_name) - item.set_meta(META_GDUNIT_TYPE, GdUnitType.TEST_CASE) - item.set_meta(META_RESOURCE_PATH, parent.get_meta(META_RESOURCE_PATH)) - item.set_meta(META_LINE_NUMBER, test_case.line_number()) - item.set_meta(META_TEST_PARAM_INDEX, -1) - add_tree_item_to_cache(parent.get_meta(META_RESOURCE_PATH), test_name, item) - - var test_case_names := test_case.test_case_names() - if not test_case_names.is_empty(): - item.set_meta(META_GDUNIT_TOTAL_TESTS, test_case_names.size()) - item.set_meta(META_GDUNIT_SUCCESS_TESTS, 0) - _update_item_counter(item) - add_test_cases(item, test_case_names) - - -func add_test_cases(parent :TreeItem, test_case_names :Array) -> void: - for index in test_case_names.size(): - var test_case_name = test_case_names[index] - var item := _tree.create_item(parent) - item.set_text(0, test_case_name) - item.set_icon(0, ICON_TEST_DEFAULT) - item.set_meta(META_GDUNIT_STATE, STATE.INITIAL) - item.set_meta(META_GDUNIT_NAME, test_case_name) - item.set_meta(META_GDUNIT_TYPE, GdUnitType.TEST_CASE) - item.set_meta(META_RESOURCE_PATH, parent.get_meta(META_RESOURCE_PATH)) - item.set_meta(META_LINE_NUMBER, parent.get_meta(META_LINE_NUMBER)) - item.set_meta(META_TEST_PARAM_INDEX, index) - add_tree_item_to_cache(parent.get_meta(META_RESOURCE_PATH), test_case_name, item) - - -################################################################################ -# Tree signal receiver -################################################################################ -func _on_tree_item_mouse_selected(mouse_position :Vector2, mouse_button_index :int): - if mouse_button_index == MOUSE_BUTTON_RIGHT: - _context_menu.position = get_screen_position() + mouse_position - _context_menu.popup() - - -func _on_run_pressed(run_debug :bool) -> void: - _context_menu.hide() - var item := _tree.get_selected() - if item.get_meta(META_GDUNIT_TYPE) == GdUnitType.TEST_SUITE: - var resource_path = item.get_meta(META_RESOURCE_PATH) - run_testsuite.emit([resource_path], run_debug) - return - var parent = item.get_parent() - var test_suite_resource_path = parent.get_meta(META_RESOURCE_PATH) - var test_case = item.get_meta(META_GDUNIT_NAME) - # handle parameterized test selection - var test_param_index = item.get_meta(META_TEST_PARAM_INDEX) - if test_param_index != -1: - test_case = parent.get_meta(META_GDUNIT_NAME) - run_testcase.emit(test_suite_resource_path, test_case, test_param_index, run_debug) - - -func _on_Tree_item_selected() -> void: - # only show report checked manual item selection - # we need to check the run mode here otherwise it will be called every selection - if not _context_menu.is_item_disabled(CONTEXT_MENU_RUN_ID): - var selected_item :TreeItem = _tree.get_selected() - show_failed_report(selected_item) - - -# Opens the test suite -func _on_Tree_item_activated() -> void: - var selected_item := _tree.get_selected() - var resource_path = selected_item.get_meta(META_RESOURCE_PATH) - var line_number = selected_item.get_meta(META_LINE_NUMBER) - var resource = load(resource_path) - - if selected_item.has_meta(META_GDUNIT_REPORT): - var reports :Array = selected_item.get_meta(META_GDUNIT_REPORT) - var report_line_number = reports[0].line_number() - # if number -1 we use original stored line number of the test case - # in non debug mode the line number is not available - if report_line_number != -1: - line_number = report_line_number - - var editor_interface := _editor.get_editor_interface() - editor_interface.get_file_system_dock().navigate_to_path(resource_path) - editor_interface.edit_resource(resource) - editor_interface.get_script_editor().goto_line(line_number-1) - - -################################################################################ -# external signal receiver -################################################################################ -func _on_gdunit_runner_start(): - _context_menu.set_item_disabled(CONTEXT_MENU_RUN_ID, true) - _context_menu.set_item_disabled(CONTEXT_MENU_DEBUG_ID, true) - clear_failures() - - -func _on_gdunit_runner_stop(_client_id :int): - _context_menu.set_item_disabled(CONTEXT_MENU_RUN_ID, false) - _context_menu.set_item_disabled(CONTEXT_MENU_DEBUG_ID, false) - abort_running() - clear_failures() - collect_failures_and_errors() - select_first_failure() - - -func _on_gdunit_add_test_suite(test_suite :GdUnitTestSuiteDto) -> void: - add_test_suite(test_suite) - - -func _on_gdunit_event(event :GdUnitEvent) -> void: - match event.type(): - GdUnitEvent.INIT: - init_tree() - GdUnitEvent.STOP: - select_first_failure() - show_failed_report(_tree.get_selected()) - GdUnitEvent.TESTCASE_BEFORE: - update_test_case(event) - GdUnitEvent.TESTCASE_AFTER: - update_test_case(event) - GdUnitEvent.TESTSUITE_BEFORE: - update_test_suite(event) - GdUnitEvent.TESTSUITE_AFTER: - update_test_suite(event) - - -func _on_Monitor_jump_to_orphan_nodes(): - select_first_orphan() - - -func _on_StatusBar_failure_next(): - select_next_failure() - - -func _on_StatusBar_failure_prevous(): - select_previous_failure() - - -func _on_context_m_index_pressed(index): - match index: - CONTEXT_MENU_DEBUG_ID: - _on_run_pressed(true) - CONTEXT_MENU_RUN_ID: - _on_run_pressed(false) diff --git a/addons/gdUnit4/src/ui/parts/InspectorTreePanel.tscn b/addons/gdUnit4/src/ui/parts/InspectorTreePanel.tscn deleted file mode 100644 index 0f00baf..0000000 --- a/addons/gdUnit4/src/ui/parts/InspectorTreePanel.tscn +++ /dev/null @@ -1,74 +0,0 @@ -[gd_scene load_steps=2 format=3 uid="uid://bqfpidewtpeg0"] - -[ext_resource type="Script" path="res://addons/gdUnit4/src/ui/parts/InspectorTreeMainPanel.gd" id="1"] - -[node name="MainPanel" type="VSplitContainer"] -use_parent_material = true -clip_contents = true -anchors_preset = 15 -anchor_right = 1.0 -anchor_bottom = 1.0 -offset_right = -924.0 -grow_horizontal = 2 -grow_vertical = 2 -size_flags_horizontal = 3 -size_flags_vertical = 3 -split_offset = 200 -script = ExtResource("1") - -[node name="Panel" type="PanelContainer" parent="."] -layout_mode = 2 -size_flags_horizontal = 3 -size_flags_vertical = 3 - -[node name="Tree" type="Tree" parent="Panel"] -use_parent_material = true -layout_mode = 2 -size_flags_horizontal = 3 -size_flags_vertical = 3 -theme_override_constants/icon_max_width = 16 -allow_rmb_select = true -hide_root = true -select_mode = 1 - -[node name="report" type="PanelContainer" parent="."] -clip_contents = true -layout_mode = 2 -size_flags_horizontal = 11 -size_flags_vertical = 11 - -[node name="report_template" type="RichTextLabel" parent="report"] -use_parent_material = true -clip_contents = false -layout_mode = 2 -size_flags_horizontal = 3 -focus_mode = 2 -bbcode_enabled = true -fit_content = true -selection_enabled = true - -[node name="ScrollContainer" type="ScrollContainer" parent="report"] -use_parent_material = true -layout_mode = 2 -size_flags_horizontal = 3 -size_flags_vertical = 11 - -[node name="list" type="VBoxContainer" parent="report/ScrollContainer"] -clip_contents = true -layout_mode = 2 -size_flags_horizontal = 3 -size_flags_vertical = 3 - -[node name="contextMenu" type="PopupMenu" parent="."] -size = Vector2i(120, 60) -auto_translate = false -item_count = 2 -item_0/text = "Run" -item_0/id = 0 -item_1/text = "Debug" -item_1/id = 1 - -[connection signal="item_activated" from="Panel/Tree" to="." method="_on_Tree_item_activated"] -[connection signal="item_mouse_selected" from="Panel/Tree" to="." method="_on_tree_item_mouse_selected"] -[connection signal="item_selected" from="Panel/Tree" to="." method="_on_Tree_item_selected"] -[connection signal="index_pressed" from="contextMenu" to="." method="_on_context_m_index_pressed"] diff --git a/addons/gdUnit4/src/ui/settings/GdUnitInputCapture.gd b/addons/gdUnit4/src/ui/settings/GdUnitInputCapture.gd deleted file mode 100644 index 839d82e..0000000 --- a/addons/gdUnit4/src/ui/settings/GdUnitInputCapture.gd +++ /dev/null @@ -1,54 +0,0 @@ -@tool -class_name GdUnitInputCapture -extends Control - - -signal input_completed(input_event :InputEventKey) - -@onready var _label = %Label - - -var _tween :Tween -var _input_event :InputEventKey - - -func _ready(): - reset() - _tween = create_tween() - _tween.set_loops(-1) - _tween.tween_property(self, "modulate", Color(0, 0, 0, .1), 1.0).from_current().set_trans(Tween.TRANS_QUAD).set_ease(Tween.EASE_IN) - - -func reset() -> void: - _input_event = InputEventKey.new() - - -func _input(event :InputEvent): - if not is_visible_in_tree(): - return - if event is InputEventKey and event.is_pressed() and not event.is_echo(): - match event.keycode: - KEY_CTRL: - _input_event.ctrl_pressed = true - KEY_SHIFT: - _input_event.shift_pressed = true - KEY_ALT: - _input_event.alt_pressed = true - KEY_META: - _input_event.meta_pressed = true - _: - _input_event.keycode = event.keycode - _apply_input_modifiers(event) - accept_event() - - if event is InputEventKey and not event.is_pressed(): - input_completed.emit(_input_event) - hide() - - -func _apply_input_modifiers(event :InputEvent) -> void: - if event is InputEventWithModifiers: - _input_event.meta_pressed = event.meta_pressed or _input_event.meta_pressed - _input_event.alt_pressed = event.alt_pressed or _input_event.alt_pressed - _input_event.shift_pressed = event.shift_pressed or _input_event.shift_pressed - _input_event.ctrl_pressed = event.ctrl_pressed or _input_event.ctrl_pressed diff --git a/addons/gdUnit4/src/ui/settings/GdUnitInputCapture.tscn b/addons/gdUnit4/src/ui/settings/GdUnitInputCapture.tscn deleted file mode 100644 index 8081a66..0000000 --- a/addons/gdUnit4/src/ui/settings/GdUnitInputCapture.tscn +++ /dev/null @@ -1,33 +0,0 @@ -[gd_scene load_steps=2 format=3] - -[ext_resource type="Script" path="res://addons/gdUnit4/src/ui/settings/GdUnitInputCapture.gd" id="1_gki1u"] - -[node name="GdUnitInputMapper" type="Control"] -top_level = true -layout_mode = 3 -anchors_preset = 15 -anchor_right = 1.0 -anchor_bottom = 1.0 -grow_horizontal = 2 -grow_vertical = 2 -size_flags_horizontal = 3 -size_flags_vertical = 3 -script = ExtResource("1_gki1u") - -[node name="Label" type="Label" parent="."] -unique_name_in_owner = true -layout_mode = 1 -anchors_preset = 8 -anchor_left = 0.5 -anchor_top = 0.5 -anchor_right = 0.5 -anchor_bottom = 0.5 -offset_left = -60.5 -offset_top = -19.5 -offset_right = 60.5 -offset_bottom = 19.5 -grow_horizontal = 2 -grow_vertical = 2 -theme_override_colors/font_color = Color(1, 1, 1, 1) -theme_override_font_sizes/font_size = 26 -text = "Press keys for shortcut" diff --git a/addons/gdUnit4/src/ui/settings/GdUnitSettingsDialog.gd b/addons/gdUnit4/src/ui/settings/GdUnitSettingsDialog.gd deleted file mode 100644 index 8ea55fd..0000000 --- a/addons/gdUnit4/src/ui/settings/GdUnitSettingsDialog.gd +++ /dev/null @@ -1,292 +0,0 @@ -@tool -extends Window - -const EAXAMPLE_URL := "https://github.com/MikeSchulze/gdUnit4-examples/archive/refs/heads/master.zip" - -const GdUnitTools := preload("res://addons/gdUnit4/src/core/GdUnitTools.gd") -const GdUnitUpdateClient = preload("res://addons/gdUnit4/src/update/GdUnitUpdateClient.gd") - -@onready var _update_client :GdUnitUpdateClient = $GdUnitUpdateClient -@onready var _version_label :RichTextLabel = %version -@onready var _btn_install :Button = %btn_install_examples -@onready var _progress_bar :ProgressBar = %ProgressBar -@onready var _progress_text :Label = %progress_lbl -@onready var _properties_template :Node = $property_template -@onready var _properties_common :Node = %"common-content" -@onready var _properties_ui :Node = %"ui-content" -@onready var _properties_shortcuts :Node = %"shortcut-content" -@onready var _properties_report :Node = %"report-content" -@onready var _input_capture :GdUnitInputCapture = %GdUnitInputCapture -@onready var _property_error :Window = %"propertyError" -var _font_size :float - - -func _ready(): - # initialize for testing - if not Engine.is_editor_hint(): - GdUnitSettings.setup() - GdUnit4Version.init_version_label(_version_label) - _font_size = GdUnitFonts.init_fonts(_version_label) - setup_properties(_properties_common, GdUnitSettings.COMMON_SETTINGS) - setup_properties(_properties_ui, GdUnitSettings.UI_SETTINGS) - setup_properties(_properties_report, GdUnitSettings.REPORT_SETTINGS) - setup_properties(_properties_shortcuts, GdUnitSettings.SHORTCUT_SETTINGS) - await get_tree().process_frame - if not Engine.is_editor_hint(): - DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_WINDOWED) - DisplayServer.window_set_size(Vector2i(1600, 800)) - popup_centered_ratio(1) - else: - popup_centered_ratio(.75) - - -func _sort_by_key(left :GdUnitProperty, right :GdUnitProperty) -> bool: - return left.name() < right.name() - - -func setup_properties(properties_parent :Node, property_category) -> void: - var category_properties := GdUnitSettings.list_settings(property_category) - # sort by key - category_properties.sort_custom(_sort_by_key) - var theme_ := Theme.new() - theme_.set_constant("h_separation", "GridContainer", 12) - var last_category := "!" - var min_size_overall := 0.0 - for p in category_properties: - var min_size_ := 0.0 - var grid := GridContainer.new() - grid.columns = 4 - grid.theme = theme_ - var property : GdUnitProperty = p - var current_category = property.category() - if current_category != last_category: - var sub_category :Node = _properties_template.get_child(3).duplicate() - sub_category.get_child(0).text = current_category.capitalize() - sub_category.custom_minimum_size.y = _font_size + 16 - properties_parent.add_child(sub_category) - last_category = current_category - # property name - var label :Label = _properties_template.get_child(0).duplicate() - label.text = _to_human_readable(property.name()) - label.custom_minimum_size = Vector2(_font_size * 20, 0) - grid.add_child(label) - min_size_ += label.size.x - - # property reset btn - var reset_btn :Button = _properties_template.get_child(1).duplicate() - reset_btn.icon = _get_btn_icon("Reload") - reset_btn.disabled = property.value() == property.default() - grid.add_child(reset_btn) - min_size_ += reset_btn.size.x - - # property type specific input element - var input :Node = _create_input_element(property, reset_btn) - input.custom_minimum_size = Vector2(_font_size * 15, 0) - grid.add_child(input) - min_size_ += input.size.x - reset_btn.pressed.connect(_on_btn_property_reset_pressed.bind(property, input, reset_btn)) - # property help text - var info :Node = _properties_template.get_child(2).duplicate() - info.text = property.help() - grid.add_child(info) - min_size_ += info.text.length() * _font_size - if min_size_overall < min_size_: - min_size_overall = min_size_ - properties_parent.add_child(grid) - properties_parent.custom_minimum_size.x = min_size_overall - - -func _create_input_element(property: GdUnitProperty, reset_btn :Button) -> Node: - if property.is_selectable_value(): - var options := OptionButton.new() - options.alignment = HORIZONTAL_ALIGNMENT_CENTER - var values_set := Array(property.value_set()) - for value in values_set: - options.add_item(value) - options.item_selected.connect(_on_option_selected.bind(property, reset_btn)) - options.select(property.value()) - return options - if property.type() == TYPE_BOOL: - var check_btn := CheckButton.new() - check_btn.toggled.connect(_on_property_text_changed.bind(property, reset_btn)) - check_btn.button_pressed = property.value() - return check_btn - if property.type() in [TYPE_INT, TYPE_STRING]: - var input := LineEdit.new() - input.text_changed.connect(_on_property_text_changed.bind(property, reset_btn)) - input.set_context_menu_enabled(false) - input.set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER) - input.set_expand_to_text_length_enabled(true) - input.text = str(property.value()) - return input - if property.type() == TYPE_PACKED_INT32_ARRAY: - var key_input_button := Button.new() - key_input_button.text = to_shortcut(property.value()) - key_input_button.pressed.connect(_on_shortcut_change.bind(key_input_button, property, reset_btn)) - return key_input_button - return Control.new() - - -func to_shortcut(keys :PackedInt32Array) -> String: - var input_event := InputEventKey.new() - for key in keys: - match key: - KEY_CTRL: input_event.ctrl_pressed = true - KEY_SHIFT: input_event.shift_pressed = true - KEY_ALT: input_event.alt_pressed = true - KEY_META: input_event.meta_pressed = true - _: - input_event.keycode = key as Key - return input_event.as_text() - - -func to_keys(input_event :InputEventKey) -> PackedInt32Array: - var keys := PackedInt32Array() - if input_event.ctrl_pressed: - keys.append(KEY_CTRL) - if input_event.shift_pressed: - keys.append(KEY_SHIFT) - if input_event.alt_pressed: - keys.append(KEY_ALT) - if input_event.meta_pressed: - keys.append(KEY_META) - keys.append(input_event.keycode) - return keys - - -func _to_human_readable(value :String) -> String: - return value.split("/")[-1].capitalize() - - -func _get_btn_icon(p_name :String) -> Texture2D: - if not Engine.is_editor_hint(): - var placeholder := PlaceholderTexture2D.new() - placeholder.size = Vector2(8,8) - return placeholder - var editor :EditorPlugin = Engine.get_meta("GdUnitEditorPlugin") - if editor: - var editior_control := editor.get_editor_interface().get_base_control() - return GodotVersionFixures.get_icon(editior_control, p_name) - return null - - -func _install_examples() -> void: - _init_progress(5) - update_progress("Downloading examples") - await get_tree().process_frame - var tmp_path := GdUnitFileAccess.create_temp_dir("download") - var zip_file := tmp_path + "/examples.zip" - var response :GdUnitUpdateClient.HttpResponse = await _update_client.request_zip_package(EAXAMPLE_URL, zip_file) - if response.code() != 200: - push_warning("Examples cannot be retrieved from GitHub! \n Error code: %d : %s" % [response.code(), response.response()]) - update_progress("Install examples failed! Try it later again.") - await get_tree().create_timer(3).timeout - stop_progress() - return - # extract zip to tmp - update_progress("Install examples into project") - var result := GdUnitFileAccess.extract_zip(zip_file, "res://gdUnit4-examples/") - if result.is_error(): - update_progress("Install examples failed! %s" % result.error_message()) - await get_tree().create_timer(3).timeout - stop_progress() - return - update_progress("Refresh project") - await rescan(true) - update_progress("Examples successfully installed") - await get_tree().create_timer(3).timeout - stop_progress() - - -func rescan(update_scripts :bool = false) -> void: - await get_tree().idle_frame - var plugin :EditorPlugin = Engine.get_meta("GdUnitEditorPlugin") - var fs := plugin.get_editor_interface().get_resource_filesystem() - fs.scan_sources() - while fs.is_scanning(): - await get_tree().create_timer(1).timeout - if update_scripts: - plugin.get_editor_interface().get_resource_filesystem().update_script_classes() - - -func _on_btn_report_bug_pressed(): - OS.shell_open("https://github.com/MikeSchulze/gdUnit4/issues/new?assignees=MikeSchulze&labels=bug&template=bug_report.md&title=") - - -func _on_btn_request_feature_pressed(): - OS.shell_open("https://github.com/MikeSchulze/gdUnit4/issues/new?assignees=MikeSchulze&labels=enhancement&template=feature_request.md&title=") - - -func _on_btn_install_examples_pressed(): - _btn_install.disabled = true - await _install_examples() - _btn_install.disabled = false - - -func _on_btn_close_pressed(): - hide() - - -func _on_btn_property_reset_pressed(property: GdUnitProperty, input :Node, reset_btn :Button): - if input is CheckButton: - input.button_pressed = property.default() - elif input is LineEdit: - input.text = str(property.default()) - # we have to update manually for text input fields because of no change event is emited - _on_property_text_changed(property.default(), property, reset_btn) - elif input is OptionButton: - input.select(0) - _on_option_selected(0, property, reset_btn) - elif input is Button: - input.text = to_shortcut(property.default()) - _on_property_text_changed(property.default(), property, reset_btn) - - -func _on_property_text_changed(new_value :Variant, property: GdUnitProperty, reset_btn :Button): - property.set_value(new_value) - reset_btn.disabled = property.value() == property.default() - var error :Variant = GdUnitSettings.update_property(property) - if error: - var label :Label = _property_error.get_child(0) as Label - label.set_text(error) - var control := gui_get_focus_owner() - _property_error.show() - if control != null: - _property_error.position = control.global_position + Vector2(self.position) + Vector2(40, 40) - - -func _on_option_selected(index :int, property: GdUnitProperty, reset_btn :Button): - property.set_value(index) - reset_btn.disabled = property.value() == property.default() - GdUnitSettings.update_property(property) - - -func _on_shortcut_change(input_button :Button, property: GdUnitProperty, reset_btn :Button) -> void: - _input_capture.set_custom_minimum_size(_properties_shortcuts.get_size()) - _input_capture.visible = true - _input_capture.show() - set_process_input(false) - _input_capture.reset() - var input_event :InputEventKey = await _input_capture.input_completed - input_button.text = input_event.as_text() - _on_property_text_changed(to_keys(input_event), property, reset_btn) - set_process_input(true) - - -func _init_progress(max_value : int) -> void: - _progress_bar.visible = true - _progress_bar.max_value = max_value - _progress_bar.value = 0 - - -func _progress() -> void: - _progress_bar.value += 1 - - -func stop_progress() -> void: - _progress_bar.visible = false - - -func update_progress(message :String) -> void: - _progress_text.text = message - _progress_bar.value += 1 diff --git a/addons/gdUnit4/src/ui/settings/GdUnitSettingsDialog.tscn b/addons/gdUnit4/src/ui/settings/GdUnitSettingsDialog.tscn deleted file mode 100644 index 0650fba..0000000 --- a/addons/gdUnit4/src/ui/settings/GdUnitSettingsDialog.tscn +++ /dev/null @@ -1,315 +0,0 @@ -[gd_scene load_steps=7 format=3 uid="uid://dwgat6j2u77g4"] - -[ext_resource type="Script" path="res://addons/gdUnit4/src/ui/settings/GdUnitSettingsDialog.gd" id="2"] -[ext_resource type="Texture2D" uid="uid://c7sk0yhd52lg3" path="res://addons/gdUnit4/src/ui/assets/icon.png" id="2_w63lb"] -[ext_resource type="PackedScene" uid="uid://dte0m2endcgtu" path="res://addons/gdUnit4/src/ui/templates/TestSuiteTemplate.tscn" id="4"] -[ext_resource type="PackedScene" path="res://addons/gdUnit4/src/ui/settings/GdUnitInputCapture.tscn" id="5_xu3j8"] -[ext_resource type="Script" path="res://addons/gdUnit4/src/update/GdUnitUpdateClient.gd" id="8_2ggr0"] - -[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_hbbq5"] -content_margin_left = 10.0 -content_margin_right = 10.0 -bg_color = Color(0.172549, 0.113725, 0.141176, 1) -border_width_left = 4 -border_width_top = 4 -border_width_right = 4 -border_width_bottom = 4 -border_color = Color(0.87451, 0.0705882, 0.160784, 1) -border_blend = true -corner_radius_top_left = 8 -corner_radius_top_right = 8 -corner_radius_bottom_right = 8 -corner_radius_bottom_left = 8 -shadow_color = Color(0, 0, 0, 0.756863) -shadow_size = 10 -shadow_offset = Vector2(10, 10) - -[node name="Control" type="Window"] -disable_3d = true -title = "GdUnitSettings" -initial_position = 1 -visible = false -wrap_controls = true -transient = true -exclusive = true -script = ExtResource("2") - -[node name="property_template" type="Control" parent="."] -visible = false -layout_mode = 3 -anchors_preset = 0 -offset_left = 4.0 -offset_top = 4.0 -offset_right = 956.0 -offset_bottom = 656.0 -size_flags_horizontal = 3 - -[node name="Label" type="Label" parent="property_template"] -layout_mode = 0 -offset_top = 13.0 -offset_right = 131.0 -offset_bottom = 27.0 - -[node name="btn_reset" type="Button" parent="property_template"] -layout_mode = 0 -offset_right = 12.0 -offset_bottom = 40.0 -tooltip_text = "Reset to default value" -clip_text = true - -[node name="info" type="Label" parent="property_template"] -layout_mode = 0 -offset_left = 390.0 -offset_top = 11.0 -offset_right = 590.0 -offset_bottom = 25.0 -size_flags_horizontal = 3 -text = "Enables/disables the update notification " -clip_text = true -max_lines_visible = 1 - -[node name="sub_category" type="Panel" parent="property_template"] -layout_mode = 1 -anchors_preset = 10 -anchor_right = 1.0 -offset_bottom = 30.0 -grow_horizontal = 2 -size_flags_horizontal = 3 - -[node name="Label" type="Label" parent="property_template/sub_category"] -layout_mode = 1 -anchors_preset = 15 -anchor_right = 1.0 -anchor_bottom = 1.0 -grow_horizontal = 2 -grow_vertical = 2 -theme_override_colors/font_color = Color(0.196078, 0.631373, 0.639216, 1) - -[node name="GdUnitUpdateClient" type="Node" parent="."] -script = ExtResource("8_2ggr0") - -[node name="Panel" type="Panel" parent="."] -clip_contents = true -anchors_preset = 15 -anchor_right = 1.0 -anchor_bottom = 1.0 -grow_horizontal = 2 -grow_vertical = 2 -size_flags_horizontal = 3 -size_flags_vertical = 3 - -[node name="v" type="VBoxContainer" parent="Panel"] -layout_mode = 1 -anchors_preset = 15 -anchor_right = 1.0 -anchor_bottom = 1.0 -grow_horizontal = 2 -grow_vertical = 2 -size_flags_horizontal = 3 -size_flags_vertical = 3 - -[node name="MarginContainer" type="MarginContainer" parent="Panel/v"] -use_parent_material = true -layout_mode = 2 -size_flags_horizontal = 3 -size_flags_vertical = 3 -theme_override_constants/margin_left = 4 -theme_override_constants/margin_top = 4 -theme_override_constants/margin_right = 4 - -[node name="GridContainer" type="HBoxContainer" parent="Panel/v/MarginContainer"] -use_parent_material = true -layout_mode = 2 -size_flags_horizontal = 3 -size_flags_vertical = 3 - -[node name="PanelContainer" type="MarginContainer" parent="Panel/v/MarginContainer/GridContainer"] -use_parent_material = true -layout_mode = 2 -size_flags_vertical = 3 - -[node name="Panel" type="VBoxContainer" parent="Panel/v/MarginContainer/GridContainer/PanelContainer"] -use_parent_material = true -layout_mode = 2 -size_flags_horizontal = 3 -size_flags_vertical = 3 - -[node name="CenterContainer" type="CenterContainer" parent="Panel/v/MarginContainer/GridContainer/PanelContainer/Panel"] -use_parent_material = true -layout_mode = 2 -size_flags_horizontal = 3 - -[node name="logo" type="TextureRect" parent="Panel/v/MarginContainer/GridContainer/PanelContainer/Panel/CenterContainer"] -custom_minimum_size = Vector2(120, 120) -layout_mode = 2 -size_flags_horizontal = 0 -size_flags_vertical = 4 -texture = ExtResource("2_w63lb") -expand_mode = 1 -stretch_mode = 5 - -[node name="CenterContainer2" type="MarginContainer" parent="Panel/v/MarginContainer/GridContainer/PanelContainer/Panel"] -use_parent_material = true -custom_minimum_size = Vector2(0, 30) -layout_mode = 2 -size_flags_horizontal = 3 - -[node name="version" type="RichTextLabel" parent="Panel/v/MarginContainer/GridContainer/PanelContainer/Panel/CenterContainer2"] -unique_name_in_owner = true -use_parent_material = true -clip_contents = false -layout_mode = 2 -size_flags_horizontal = 3 -auto_translate = false -localize_numeral_system = false -bbcode_enabled = true -scroll_active = false -meta_underlined = false - -[node name="VBoxContainer" type="VBoxContainer" parent="Panel/v/MarginContainer/GridContainer/PanelContainer"] -custom_minimum_size = Vector2(200, 0) -layout_mode = 2 -size_flags_horizontal = 3 -size_flags_vertical = 3 -alignment = 2 - -[node name="btn_report_bug" type="Button" parent="Panel/v/MarginContainer/GridContainer/PanelContainer/VBoxContainer"] -layout_mode = 2 -size_flags_horizontal = 3 -tooltip_text = "Press to create a bug report" -text = "Report Bug" - -[node name="btn_request_feature" type="Button" parent="Panel/v/MarginContainer/GridContainer/PanelContainer/VBoxContainer"] -layout_mode = 2 -size_flags_horizontal = 3 -tooltip_text = "Press to create a feature request" -text = "Request Feature" - -[node name="btn_install_examples" type="Button" parent="Panel/v/MarginContainer/GridContainer/PanelContainer/VBoxContainer"] -unique_name_in_owner = true -layout_mode = 2 -size_flags_horizontal = 3 -tooltip_text = "Press to install the advanced test examples" -disabled = true -text = "Install Examples" - -[node name="Properties" type="TabContainer" parent="Panel/v/MarginContainer/GridContainer"] -layout_mode = 2 -size_flags_horizontal = 11 - -[node name="Common" type="ScrollContainer" parent="Panel/v/MarginContainer/GridContainer/Properties"] -layout_mode = 2 - -[node name="common-content" type="VBoxContainer" parent="Panel/v/MarginContainer/GridContainer/Properties/Common"] -unique_name_in_owner = true -clip_contents = true -custom_minimum_size = Vector2(1445, 0) -layout_mode = 2 -size_flags_horizontal = 3 -size_flags_vertical = 3 - -[node name="UI" type="ScrollContainer" parent="Panel/v/MarginContainer/GridContainer/Properties"] -visible = false -layout_mode = 2 - -[node name="ui-content" type="VBoxContainer" parent="Panel/v/MarginContainer/GridContainer/Properties/UI"] -unique_name_in_owner = true -clip_contents = true -custom_minimum_size = Vector2(1249, 0) -layout_mode = 2 -size_flags_horizontal = 3 -size_flags_vertical = 3 - -[node name="Shortcuts" type="ScrollContainer" parent="Panel/v/MarginContainer/GridContainer/Properties"] -visible = false -layout_mode = 2 - -[node name="shortcut-content" type="VBoxContainer" parent="Panel/v/MarginContainer/GridContainer/Properties/Shortcuts"] -unique_name_in_owner = true -clip_contents = true -custom_minimum_size = Vector2(983, 0) -layout_mode = 2 -size_flags_horizontal = 3 -size_flags_vertical = 3 - -[node name="GdUnitInputCapture" parent="Panel/v/MarginContainer/GridContainer/Properties/Shortcuts/shortcut-content" instance=ExtResource("5_xu3j8")] -unique_name_in_owner = true -visible = false -modulate = Color(0.000201742, 0.000201742, 0.000201742, 0.100182) -z_index = 1 -z_as_relative = false -layout_mode = 2 -size_flags_horizontal = 1 -size_flags_vertical = 1 - -[node name="Report" type="ScrollContainer" parent="Panel/v/MarginContainer/GridContainer/Properties"] -visible = false -layout_mode = 2 - -[node name="report-content" type="VBoxContainer" parent="Panel/v/MarginContainer/GridContainer/Properties/Report"] -unique_name_in_owner = true -clip_contents = true -custom_minimum_size = Vector2(1249, 0) -layout_mode = 2 -size_flags_horizontal = 3 -size_flags_vertical = 3 - -[node name="Templates" parent="Panel/v/MarginContainer/GridContainer/Properties" instance=ExtResource("4")] -visible = false -layout_mode = 2 - -[node name="propertyError" type="PopupPanel" parent="Panel/v/MarginContainer/GridContainer/Properties"] -unique_name_in_owner = true -initial_position = 1 -size = Vector2i(400, 100) -theme_override_styles/panel = SubResource("StyleBoxFlat_hbbq5") - -[node name="Label" type="Label" parent="Panel/v/MarginContainer/GridContainer/Properties/propertyError"] -offset_left = 10.0 -offset_top = 4.0 -offset_right = 390.0 -offset_bottom = 96.0 -theme_override_colors/font_color = Color(0.858824, 0, 0.109804, 1) -horizontal_alignment = 1 -vertical_alignment = 1 - -[node name="MarginContainer2" type="MarginContainer" parent="Panel/v"] -layout_mode = 2 -size_flags_horizontal = 3 -theme_override_constants/margin_left = 4 -theme_override_constants/margin_right = 4 -theme_override_constants/margin_bottom = 4 - -[node name="HBoxContainer" type="HBoxContainer" parent="Panel/v/MarginContainer2"] -layout_mode = 2 -size_flags_horizontal = 3 -alignment = 2 - -[node name="ProgressBar" type="ProgressBar" parent="Panel/v/MarginContainer2/HBoxContainer"] -unique_name_in_owner = true -visible = false -layout_mode = 2 -size_flags_horizontal = 3 -size_flags_vertical = 3 - -[node name="progress_lbl" type="Label" parent="Panel/v/MarginContainer2/HBoxContainer/ProgressBar"] -unique_name_in_owner = true -layout_mode = 1 -anchors_preset = 15 -anchor_right = 1.0 -anchor_bottom = 1.0 -grow_horizontal = 2 -grow_vertical = 2 -size_flags_horizontal = 3 -size_flags_vertical = 3 -clip_text = true - -[node name="btn_close" type="Button" parent="Panel/v/MarginContainer2/HBoxContainer"] -custom_minimum_size = Vector2(200, 0) -layout_mode = 2 -text = "Close" - -[connection signal="pressed" from="Panel/v/MarginContainer/GridContainer/PanelContainer/VBoxContainer/btn_report_bug" to="." method="_on_btn_report_bug_pressed"] -[connection signal="pressed" from="Panel/v/MarginContainer/GridContainer/PanelContainer/VBoxContainer/btn_request_feature" to="." method="_on_btn_request_feature_pressed"] -[connection signal="pressed" from="Panel/v/MarginContainer/GridContainer/PanelContainer/VBoxContainer/btn_install_examples" to="." method="_on_btn_install_examples_pressed"] -[connection signal="pressed" from="Panel/v/MarginContainer2/HBoxContainer/btn_close" to="." method="_on_btn_close_pressed"] diff --git a/addons/gdUnit4/src/ui/templates/TestSuiteTemplate.gd b/addons/gdUnit4/src/ui/templates/TestSuiteTemplate.gd deleted file mode 100644 index 323ed46..0000000 --- a/addons/gdUnit4/src/ui/templates/TestSuiteTemplate.gd +++ /dev/null @@ -1,122 +0,0 @@ -@tool -extends MarginContainer - -@onready var _template_editor :CodeEdit = $VBoxContainer/EdiorLayout/Editor -@onready var _tags_editor :CodeEdit = $Tags/MarginContainer/TextEdit -@onready var _title_bar :Panel = $VBoxContainer/sub_category -@onready var _save_button :Button = $VBoxContainer/Panel/HBoxContainer/Save -@onready var _selected_type :OptionButton = $VBoxContainer/EdiorLayout/Editor/MarginContainer/HBoxContainer/SelectType -@onready var _show_tags :PopupPanel = $Tags - - -var gd_key_words :PackedStringArray = ["extends", "class_name", "const", "var", "onready", "func", "void", "pass"] -var gdunit_key_words :PackedStringArray = ["GdUnitTestSuite", "before", "after", "before_test", "after_test"] -var _selected_template :int - - -func _ready() -> void: - setup_editor_colors() - setup_fonts() - setup_supported_types() - load_template(GdUnitTestSuiteTemplate.TEMPLATE_ID_GD) - setup_tags_help() - - -func _notification(what :int) -> void: - if what == EditorSettings.NOTIFICATION_EDITOR_SETTINGS_CHANGED: - setup_fonts() - - -func setup_editor_colors() -> void: - if not Engine.is_editor_hint(): - return - var plugin :EditorPlugin = Engine.get_meta("GdUnitEditorPlugin") - var settings := plugin.get_editor_interface().get_editor_settings() - var background_color :Color = settings.get_setting("text_editor/theme/highlighting/background_color") - var text_color :Color = settings.get_setting("text_editor/theme/highlighting/text_color") - var selection_color :Color = settings.get_setting("text_editor/theme/highlighting/selection_color") - - for e in [_template_editor, _tags_editor]: - var editor :CodeEdit = e - editor.add_theme_color_override("background_color", background_color) - editor.add_theme_color_override("font_color", text_color) - editor.add_theme_color_override("font_readonly_color", text_color) - editor.add_theme_color_override("font_selected_color", selection_color) - setup_highlighter(editor, settings) - - -func setup_highlighter(editor :CodeEdit, settings :EditorSettings) -> void: - var highlighter := CodeHighlighter.new() - editor.set_syntax_highlighter(highlighter) - var number_color :Color = settings.get_setting("text_editor/theme/highlighting/number_color") - var symbol_color :Color = settings.get_setting("text_editor/theme/highlighting/symbol_color") - var function_color :Color = settings.get_setting("text_editor/theme/highlighting/function_color") - var member_variable_color :Color = settings.get_setting("text_editor/theme/highlighting/member_variable_color") - var comment_color :Color = settings.get_setting("text_editor/theme/highlighting/comment_color") - var keyword_color :Color = settings.get_setting("text_editor/theme/highlighting/keyword_color") - var base_type_color :Color = settings.get_setting("text_editor/theme/highlighting/base_type_color") - var annotation_color :Color = settings.get_setting("text_editor/theme/highlighting/gdscript/annotation_color") - - highlighter.clear_color_regions() - highlighter.clear_keyword_colors() - highlighter.add_color_region("#", "", comment_color, true) - highlighter.add_color_region("${", "}", Color.YELLOW) - highlighter.add_color_region("'", "'", Color.YELLOW) - highlighter.add_color_region("\"", "\"", Color.YELLOW) - highlighter.number_color = number_color - highlighter.symbol_color = symbol_color - highlighter.function_color = function_color - highlighter.member_variable_color = member_variable_color - highlighter.add_keyword_color("@", annotation_color) - highlighter.add_keyword_color("warning_ignore", annotation_color) - for word in gd_key_words: - highlighter.add_keyword_color(word, keyword_color) - for word in gdunit_key_words: - highlighter.add_keyword_color(word, base_type_color) - - -func setup_fonts() -> void: - if _template_editor: - GdUnitFonts.init_fonts(_template_editor) - var font_size := GdUnitFonts.init_fonts(_tags_editor) - _title_bar.size.y = font_size + 16 - _title_bar.custom_minimum_size.y = font_size + 16 - - -func setup_supported_types() -> void: - _selected_type.clear() - _selected_type.add_item("GD - GDScript", GdUnitTestSuiteTemplate.TEMPLATE_ID_GD) - _selected_type.add_item("C# - CSharpScript", GdUnitTestSuiteTemplate.TEMPLATE_ID_CS) - - -func setup_tags_help() -> void: - _tags_editor.set_text(GdUnitTestSuiteTemplate.load_tags(_selected_template)) - - -func load_template(template_id :int) -> void: - _selected_template = template_id - _template_editor.set_text(GdUnitTestSuiteTemplate.load_template(template_id)) - - -func _on_Restore_pressed() -> void: - _template_editor.set_text(GdUnitTestSuiteTemplate.default_template(_selected_template)) - GdUnitTestSuiteTemplate.reset_to_default(_selected_template) - _save_button.disabled = true - - -func _on_Save_pressed() -> void: - GdUnitTestSuiteTemplate.save_template(_selected_template, _template_editor.get_text()) - _save_button.disabled = true - - -func _on_Tags_pressed() -> void: - _show_tags.popup_centered_ratio(.5) - - -func _on_Editor_text_changed() -> void: - _save_button.disabled = false - - -func _on_SelectType_item_selected(index :int) -> void: - load_template(_selected_type.get_item_id(index)) - setup_tags_help() diff --git a/addons/gdUnit4/src/ui/templates/TestSuiteTemplate.tscn b/addons/gdUnit4/src/ui/templates/TestSuiteTemplate.tscn deleted file mode 100644 index 5ccc443..0000000 --- a/addons/gdUnit4/src/ui/templates/TestSuiteTemplate.tscn +++ /dev/null @@ -1,127 +0,0 @@ -[gd_scene load_steps=2 format=3 uid="uid://dte0m2endcgtu"] - -[ext_resource type="Script" path="res://addons/gdUnit4/src/ui/templates/TestSuiteTemplate.gd" id="1"] - -[node name="TestSuiteTemplate" type="MarginContainer"] -anchors_preset = 15 -anchor_right = 1.0 -anchor_bottom = 1.0 -grow_horizontal = 2 -grow_vertical = 2 -size_flags_horizontal = 3 -size_flags_vertical = 3 -script = ExtResource("1") - -[node name="VBoxContainer" type="VBoxContainer" parent="."] -layout_mode = 2 -size_flags_horizontal = 3 -size_flags_vertical = 3 - -[node name="sub_category" type="Panel" parent="VBoxContainer"] -custom_minimum_size = Vector2(0, 30) -layout_mode = 2 -size_flags_horizontal = 3 - -[node name="Label" type="Label" parent="VBoxContainer/sub_category"] -layout_mode = 1 -anchors_preset = 15 -anchor_right = 1.0 -anchor_bottom = 1.0 -offset_left = 4.0 -offset_right = 4.0 -offset_bottom = 1.0 -grow_horizontal = 2 -grow_vertical = 2 -text = "Test Suite Template -" - -[node name="EdiorLayout" type="VBoxContainer" parent="VBoxContainer"] -layout_mode = 2 -size_flags_vertical = 3 - -[node name="Editor" type="CodeEdit" parent="VBoxContainer/EdiorLayout"] -layout_mode = 2 -size_flags_horizontal = 3 -size_flags_vertical = 3 - -[node name="MarginContainer" type="MarginContainer" parent="VBoxContainer/EdiorLayout/Editor"] -layout_mode = 1 -anchors_preset = 12 -anchor_top = 1.0 -anchor_right = 1.0 -anchor_bottom = 1.0 -offset_top = -31.0 -grow_horizontal = 2 -grow_vertical = 0 -size_flags_horizontal = 3 -size_flags_vertical = 3 - -[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer/EdiorLayout/Editor/MarginContainer"] -layout_mode = 2 -size_flags_vertical = 8 -alignment = 2 - -[node name="Tags" type="Button" parent="VBoxContainer/EdiorLayout/Editor/MarginContainer/HBoxContainer"] -layout_mode = 2 -tooltip_text = "Shows supported tags." -text = "Supported Tags" - -[node name="SelectType" type="OptionButton" parent="VBoxContainer/EdiorLayout/Editor/MarginContainer/HBoxContainer"] -layout_mode = 2 -tooltip_text = "Select the script type specific template." -item_count = 2 -selected = 0 -popup/item_0/text = "GD - GDScript" -popup/item_0/id = 1000 -popup/item_1/text = "C# - CSharpScript" -popup/item_1/id = 2000 - -[node name="Panel" type="MarginContainer" parent="VBoxContainer"] -layout_mode = 2 -size_flags_horizontal = 3 - -[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer/Panel"] -layout_mode = 2 -size_flags_horizontal = 3 -size_flags_vertical = 3 -alignment = 2 - -[node name="Restore" type="Button" parent="VBoxContainer/Panel/HBoxContainer"] -layout_mode = 2 -text = "Restore" - -[node name="Save" type="Button" parent="VBoxContainer/Panel/HBoxContainer"] -layout_mode = 2 -disabled = true -text = "Save" - -[node name="Tags" type="PopupPanel" parent="."] -size = Vector2i(300, 100) -unresizable = false -content_scale_aspect = 4 - -[node name="MarginContainer" type="MarginContainer" parent="Tags"] -anchors_preset = 15 -anchor_right = 1.0 -anchor_bottom = 1.0 -offset_left = 4.0 -offset_top = 4.0 -offset_right = -856.0 -offset_bottom = -552.0 -grow_horizontal = 2 -grow_vertical = 2 - -[node name="TextEdit" type="CodeEdit" parent="Tags/MarginContainer"] -layout_mode = 2 -size_flags_horizontal = 3 -size_flags_vertical = 3 -editable = false -context_menu_enabled = false -shortcut_keys_enabled = false -virtual_keyboard_enabled = false - -[connection signal="text_changed" from="VBoxContainer/EdiorLayout/Editor" to="." method="_on_Editor_text_changed"] -[connection signal="pressed" from="VBoxContainer/EdiorLayout/Editor/MarginContainer/HBoxContainer/Tags" to="." method="_on_Tags_pressed"] -[connection signal="item_selected" from="VBoxContainer/EdiorLayout/Editor/MarginContainer/HBoxContainer/SelectType" to="." method="_on_SelectType_item_selected"] -[connection signal="pressed" from="VBoxContainer/Panel/HBoxContainer/Restore" to="." method="_on_Restore_pressed"] -[connection signal="pressed" from="VBoxContainer/Panel/HBoxContainer/Save" to="." method="_on_Save_pressed"] diff --git a/addons/gdUnit4/src/update/GdMarkDownReader.gd b/addons/gdUnit4/src/update/GdMarkDownReader.gd deleted file mode 100644 index cc5bfb3..0000000 --- a/addons/gdUnit4/src/update/GdMarkDownReader.gd +++ /dev/null @@ -1,337 +0,0 @@ -extends RefCounted - -const FONT_H1 := 32 -const FONT_H2 := 28 -const FONT_H3 := 24 -const FONT_H4 := 20 -const FONT_H5 := 16 -const FONT_H6 := 12 - -const HORIZONTAL_RULE := "[img=4000x2]res://addons/gdUnit4/src/update/assets/horizontal-line2.png[/img]\n" -const HEADER_RULE := "[font_size=%d]$1[/font_size]\n" -const HEADER_CENTERED_RULE := "[font_size=%d][center]$1[/center][/font_size]\n" - -const image_download_folder := "res://addons/gdUnit4/tmp-update/" - -const exclude_font_size := "\b(?!(?:(font_size))\b)" - -var md_replace_patterns := [ - # horizontal rules - [regex("(?m)^[ ]{0,3}---$"), HORIZONTAL_RULE], - [regex("(?m)^[ ]{0,3}___$"), HORIZONTAL_RULE], - [regex("(?m)^[ ]{0,3}\\*\\*\\*$"), HORIZONTAL_RULE], - - # headers - [regex("(?m)^###### (.*)"), HEADER_RULE % FONT_H6], - [regex("(?m)^##### (.*)"), HEADER_RULE % FONT_H5], - [regex("(?m)^#### (.*)"), HEADER_RULE % FONT_H4], - [regex("(?m)^### (.*)"), HEADER_RULE % FONT_H3], - [regex("(?m)^## (.*)"), (HEADER_RULE + HORIZONTAL_RULE) % FONT_H2], - [regex("(?m)^# (.*)"), (HEADER_RULE + HORIZONTAL_RULE) % FONT_H1], - [regex("(?m)^(.+)=={2,}$"), HEADER_RULE % FONT_H1], - [regex("(?m)^(.+)--{2,}$"), HEADER_RULE % FONT_H2], - # html headers - [regex("

((.*?\\R?)+)<\\/h1>"), (HEADER_RULE + HORIZONTAL_RULE) % FONT_H1], - [regex("((.*?\\R?)+)<\\/h1>"), (HEADER_CENTERED_RULE + HORIZONTAL_RULE) % FONT_H1], - [regex("

((.*?\\R?)+)<\\/h2>"), (HEADER_RULE + HORIZONTAL_RULE) % FONT_H2], - [regex("((.*?\\R?)+)<\\/h2>"), (HEADER_CENTERED_RULE + HORIZONTAL_RULE) % FONT_H1], - [regex("

((.*?\\R?)+)<\\/h3>"), HEADER_RULE % FONT_H3], - [regex("((.*?\\R?)+)<\\/h3>"), HEADER_CENTERED_RULE % FONT_H3], - [regex("

((.*?\\R?)+)<\\/h4>"), HEADER_RULE % FONT_H4], - [regex("((.*?\\R?)+)<\\/h4>"), HEADER_CENTERED_RULE % FONT_H4], - [regex("
((.*?\\R?)+)<\\/h5>"), HEADER_RULE % FONT_H5], - [regex("((.*?\\R?)+)<\\/h5>"), HEADER_CENTERED_RULE % FONT_H5], - [regex("
((.*?\\R?)+)<\\/h6>"), HEADER_RULE % FONT_H6], - [regex("((.*?\\R?)+)<\\/h6>"), HEADER_CENTERED_RULE % FONT_H6], - - # asterics - #[regex("(\\*)"), "xxx$1xxx"], - - # extract/compile image references - [regex("!\\[(.*?)\\]\\[(.*?)\\]"), Callable(self, "process_image_references")], - # extract images with path and optional tool tip - [regex("!\\[(.*?)\\]\\((.*?)(( )+(.*?))?\\)"), Callable(self, "process_image")], - - # links - [regex("([!]|)\\[(.+)\\]\\(([^ ]+?)\\)"), "[url={\"url\":\"$3\"}]$2[/url]"], - # links with tool tip - [regex("([!]|)\\[(.+)\\]\\(([^ ]+?)( \"(.+)\")?\\)"), "[url={\"url\":\"$3\", \"tool_tip\":\"$5\"}]$2[/url]"], - - # embeded text - [regex("(?m)^[ ]{0,3}>(.*?)$"), "[img=50x14]res://addons/gdUnit4/src/update/assets/embedded.png[/img][i]$1[/i]"], - - # italic + bold font - [regex("[_]{3}(.*?)[_]{3}"), "[i][b]$1[/b][/i]"], - [regex("[\\*]{3}(.*?)[\\*]{3}"), "[i][b]$1[/b][/i]"], - # bold font - [regex("(.*?)<\\/b>"), "[b]$1[/b]"], - [regex("[_]{2}(.*?)[_]{2}"), "[b]$1[/b]"], - [regex("[\\*]{2}(.*?)[\\*]{2}"), "[b]$1[/b]"], - # italic font - [regex("(.*?)<\\/i>"), "[i]$1[/i]"], - [regex(exclude_font_size+"_(.*?)_"), "[i]$1[/i]"], - [regex("\\*(.*?)\\*"), "[i]$1[/i]"], - - # strikethrough font - [regex("(.*?)"), "[s]$1[/s]"], - [regex("~~(.*?)~~"), "[s]$1[/s]"], - [regex("~(.*?)~"), "[s]$1[/s]"], - - # handling lists - # using an image for dots as workaroud because list is not supported checked Godot 3.x - [regex("(?m)^[ ]{0,1}[*\\-+] (.*)$"), list_replace(0)], - [regex("(?m)^[ ]{2,3}[*\\-+] (.*)$"), list_replace(1)], - [regex("(?m)^[ ]{4,5}[*\\-+] (.*)$"), list_replace(2)], - [regex("(?m)^[ ]{6,7}[*\\-+] (.*)$"), list_replace(3)], - [regex("(?m)^[ ]{8,9}[*\\-+] (.*)$"), list_replace(4)], - - # code blocks, code blocks looks not like code blocks in richtext - [regex("```(javascript|python|shell|gdscript)([\\s\\S]*?\n)```"), code_block("$2", true)], - [regex("``([\\s\\S]*?)``"), code_block("$1")], - [regex("`([\\s\\S]*?)`{1,2}"), code_block("$1")], -] - -var _img_replace_regex := RegEx.new() -var _image_urls := PackedStringArray() -var _on_table_tag := false -var _client - - -func regex(pattern :String) -> RegEx: - var regex_ := RegEx.new() - var err = regex_.compile(pattern) - if err != OK: - push_error("error '%s' checked pattern '%s'" % [err, pattern]) - return null - return regex_ - - -func _init(): - _img_replace_regex.compile("\\[img\\]((.*?))\\[/img\\]") - - -func set_http_client(client) -> void: - _client = client - - -func _notification(what): - if what == NOTIFICATION_PREDELETE: - # finally remove_at the downloaded images - for image in _image_urls: - DirAccess.remove_absolute(image) - DirAccess.remove_absolute(image + ".import") - - -func list_replace(indent :int) -> String: - var replace_pattern := "[img=12x12]res://addons/gdUnit4/src/update/assets/dot2.png[/img]" if indent %2 else "[img=12x12]res://addons/gdUnit4/src/update/assets/dot1.png[/img]" - replace_pattern += " $1" - - for index in indent: - replace_pattern = replace_pattern.insert(0, " ") - return replace_pattern - - -func code_block(replace :String, border :bool = false) -> String: - var cb := "[code][color=aqua][font_size=16]%s[/font_size][/color][/code]" % replace - if border: - return "[img=1400x14]res://addons/gdUnit4/src/update/assets/border_top.png[/img]"\ - + "[indent]" + cb + "[/indent]"\ - + "[img=1400x14]res://addons/gdUnit4/src/update/assets/border_bottom.png[/img]\n" - return cb - - -func to_bbcode(input :String) -> String: - input = process_tables(input) - - for pattern in md_replace_patterns: - var regex_ :RegEx = pattern[0] - var bb_replace = pattern[1] - if bb_replace is Callable: - input = await bb_replace.call(regex_, input) - else: - input = regex_.sub(input, bb_replace, true) - return input + "\n" - - -func process_tables(input :String) -> String: - var bbcode := Array() - var lines := Array(input.split("\n")) - while not lines.is_empty(): - if is_table(lines[0]): - bbcode.append_array(parse_table(lines)) - continue - bbcode.append(lines.pop_front()) - return "\n".join(PackedStringArray(bbcode)) - - -class Table: - var _columns :int - var _rows := Array() - - class Row: - var _cells := PackedStringArray() - - func _init(cells :PackedStringArray,columns :int): - _cells = cells - for i in range(_cells.size(), columns): - _cells.append("") - - func to_bbcode(cell_sizes :PackedInt32Array, bold :bool) -> String: - var cells := PackedStringArray() - for cell_index in _cells.size(): - var cell :String = _cells[cell_index] - if cell.strip_edges() == "--": - cell = create_line(cell_sizes[cell_index]) - if bold: - cell = "[b]%s[/b]" % cell - cells.append("[cell]%s[/cell]" % cell) - return "|".join(cells) - - func create_line(length :int) -> String: - var line := "" - for i in length: - line += "-" - return line - - func _init(columns :int): - _columns = columns - - func parse_row(line :String) -> bool: - # is line containing cells? - if line.find("|") == -1: - return false - _rows.append(Row.new(line.split("|"), _columns)) - return true - - func calculate_max_cell_sizes() -> PackedInt32Array: - var cells_size := PackedInt32Array() - for column in _columns: - cells_size.append(0) - - for row_index in _rows.size(): - var row :Row = _rows[row_index] - for cell_index in row._cells.size(): - var cell_size :int = cells_size[cell_index] - var size := row._cells[cell_index].length() - if size > cell_size: - cells_size[cell_index] = size - return cells_size - - func to_bbcode() -> PackedStringArray: - var cell_sizes := calculate_max_cell_sizes() - var bb_code := PackedStringArray() - - bb_code.append("[table=%d]" % _columns) - for row_index in _rows.size(): - bb_code.append(_rows[row_index].to_bbcode(cell_sizes, row_index==0)) - bb_code.append("[/table]\n") - return bb_code - - -func parse_table(lines :Array) -> PackedStringArray: - var line :String = lines[0] - var table := Table.new(line.count("|") + 1) - while not lines.is_empty(): - line = lines.pop_front() - if not table.parse_row(line): - break - return table.to_bbcode() - - -func is_table(line :String) -> bool: - return line.find("|") != -1 - - -func open_table(line :String) -> String: - _on_table_tag = true - return "[table=%d]" % (line.count("|") + 1) - - -func close_table() -> String: - _on_table_tag = false - return "[/table]" - - -func extract_cells(line :String, bold := false) -> String: - var cells := "" - for cell in line.split("|"): - if bold: - cell = "[b]%s[/b]" % cell - cells += "[cell]%s[/cell]" % cell - return cells - - -func process_image_references(p_regex :RegEx, p_input :String) -> String: - # exists references? - var matches := p_regex.search_all(p_input) - if matches.is_empty(): - return p_input - # collect image references and remove_at it - var references := Dictionary() - var link_regex := regex("\\[(\\S+)\\]:(\\S+)([ ]\"(.*)\")?") - # create copy of original source to replace checked it - var input := p_input.replace("\r", "") - var extracted_references := p_input.replace("\r", "") - for reg_match in link_regex.search_all(input): - var line = reg_match.get_string(0) + "\n" - var ref = reg_match.get_string(1) - #var topl_tip = reg_match.get_string(4) - # collect reference and url - references[ref] = reg_match.get_string(2) - extracted_references = extracted_references.replace(line, "") - - # replace image references by collected url's - for reference_key in references.keys(): - var regex_key := regex("\\](\\[%s\\])" % reference_key) - for reg_match in regex_key.search_all(extracted_references): - var ref :String = reg_match.get_string(0) - var image_url :String = "](%s)" % references.get(reference_key) - extracted_references = extracted_references.replace(ref, image_url) - return extracted_references - - -func process_image(p_regex :RegEx, p_input :String) -> String: - var to_replace := PackedStringArray() - var tool_tips := PackedStringArray() - # find all matches - var matches := p_regex.search_all(p_input) - if matches.is_empty(): - return p_input - for reg_match in matches: - # grap the parts to replace and store temporay because a direct replace will distort the offsets - to_replace.append(p_input.substr(reg_match.get_start(0), reg_match.get_end(0))) - # grap optional tool tips - tool_tips.append(reg_match.get_string(5)) - # finally replace all findings - for replace in to_replace: - var re := p_regex.sub(replace, "[img]$2[/img]") - p_input = p_input.replace(replace, re) - return await _process_external_image_resources(p_input) - - -func _process_external_image_resources(input :String) -> String: - DirAccess.make_dir_recursive_absolute(image_download_folder) - # scan all img for external resources and download it - for value in _img_replace_regex.search_all(input): - if value.get_group_count() >= 1: - var image_url :String = value.get_string(1) - # if not a local resource we need to download it - if image_url.begins_with("http"): - if OS.is_stdout_verbose(): - prints("download image:", image_url) - var response = await _client.request_image(image_url) - if response.code() == 200: - var image = Image.new() - var error = image.load_png_from_buffer(response.body()) - if error != OK: - prints("Error creating image from response", error) - # replace characters where format characters - var new_url := image_download_folder + image_url.get_file().replace("_", "-") - if new_url.get_extension() != 'png': - new_url = new_url + '.png' - var err := image.save_png(new_url) - if err: - push_error("Can't save image to '%s'. Error: %s" % [new_url, error_string(err)]) - _image_urls.append(new_url) - input = input.replace(image_url, new_url) - return input diff --git a/addons/gdUnit4/src/update/GdUnitPatch.gd b/addons/gdUnit4/src/update/GdUnitPatch.gd deleted file mode 100644 index 3745687..0000000 --- a/addons/gdUnit4/src/update/GdUnitPatch.gd +++ /dev/null @@ -1,20 +0,0 @@ -class_name GdUnitPatch -extends RefCounted - -const PATCH_VERSION = "patch_version" - -var _version :GdUnit4Version - - -func _init(version_ :GdUnit4Version): - _version = version_ - - -func version() -> GdUnit4Version: - return _version - - -# this function needs to be implement -func execute() -> bool: - push_error("The function 'execute()' is not implemented at %s" % self) - return false diff --git a/addons/gdUnit4/src/update/GdUnitPatcher.gd b/addons/gdUnit4/src/update/GdUnitPatcher.gd deleted file mode 100644 index d83cb82..0000000 --- a/addons/gdUnit4/src/update/GdUnitPatcher.gd +++ /dev/null @@ -1,72 +0,0 @@ -class_name GdUnitPatcher -extends RefCounted - - -const _base_dir := "res://addons/gdUnit4/src/update/patches/" - -var _patches := Dictionary() - - -func scan(current :) -> void: - _scan(_base_dir, current) - - -func _scan(scan_path :String, current) -> void: - _patches = Dictionary() - var patch_paths := _collect_patch_versions(scan_path, current) - for path in patch_paths: - prints("scan for patches checked '%s'" % path) - _patches[path] = _scan_patches(path) - - -func patch_count() -> int: - var count := 0 - for key in _patches.keys(): - count += _patches[key].size() - return count - - -func execute() -> void: - for key in _patches.keys(): - var patch_root :String = key - for path in _patches[key]: - var patch :GdUnitPatch = load(patch_root + "/" + path).new() - if patch: - prints("execute patch", patch.version(), patch.get_script().resource_path) - if not patch.execute(): - prints("error checked execution patch %s" % patch_root + "/" + path) - - -func _collect_patch_versions(scan_path :String, current :) -> PackedStringArray: - if not DirAccess.dir_exists_absolute(scan_path): - return PackedStringArray() - var patches := Array() - var dir := DirAccess.open(scan_path) - if dir != null: - dir.list_dir_begin() # TODO GODOT4 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 - var version := GdUnit4Version.parse(next) - if version.is_greater(current): - patches.append(scan_path + next) - patches.sort() - return PackedStringArray(patches) - - -func _scan_patches(path :String) -> PackedStringArray: - var patches := Array() - var dir := DirAccess.open(path) - if dir != null: - 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 - patches.append(next) - # make sorted from lowest to high version - patches.sort() - return PackedStringArray(patches) diff --git a/addons/gdUnit4/src/update/GdUnitUpdate.gd b/addons/gdUnit4/src/update/GdUnitUpdate.gd deleted file mode 100644 index 815ee42..0000000 --- a/addons/gdUnit4/src/update/GdUnitUpdate.gd +++ /dev/null @@ -1,230 +0,0 @@ -@tool -extends ConfirmationDialog - -const GdUnitTools := preload("res://addons/gdUnit4/src/core/GdUnitTools.gd") -const GdUnitUpdateClient := preload("res://addons/gdUnit4/src/update/GdUnitUpdateClient.gd") - -const spinner_icon := "res://addons/gdUnit4/src/ui/assets/spinner.tres" - - -@onready var _progress_content :RichTextLabel = %message -@onready var _progress_bar :TextureProgressBar = %progress - - -var _debug_mode := false -var _editor_interface :EditorInterface -var _update_client :GdUnitUpdateClient -var _download_url :String - - -func _ready(): - message_h4("Press 'Update' to start!", Color.GREEN) - init_progress(5) - - -func _process(_delta): - if _progress_content != null and _progress_content.is_visible_in_tree(): - _progress_content.queue_redraw() - - -func init_progress(max_value : int) -> void: - _progress_bar.max_value = max_value - _progress_bar.value = 1 - - -func setup(editor_interface :EditorInterface, update_client :GdUnitUpdateClient, download_url :String) -> void: - _editor_interface = editor_interface - _update_client = update_client - _download_url = download_url - - -func update_progress(message :String) -> void: - message_h4(message, Color.GREEN) - _progress_bar.value += 1 - if _debug_mode: - await get_tree().create_timer(3).timeout - await get_tree().create_timer(.2).timeout - - -func _colored(message :String, color :Color) -> String: - return "[color=#%s]%s[/color]" % [color.to_html(), message] - - -func message_h4(message :String, color :Color) -> void: - _progress_content.clear() - _progress_content.append_text("[font_size=16]%s[/font_size]" % _colored(message, color)) - - -func run_update() -> void: - get_cancel_button().disabled = true - get_ok_button().disabled = true - - await update_progress("Download Release ... [img=24x24]%s[/img]" % spinner_icon) - await download_release() - await update_progress("Extract update ... [img=24x24]%s[/img]" % spinner_icon) - var zip_file := temp_dir() + "/update.zip" - var tmp_path := create_temp_dir("update") - var result :Variant = extract_zip(zip_file, tmp_path) - if result == null: - await update_progress("Update failed!") - await get_tree().create_timer(3).timeout - queue_free() - return - - await update_progress("Uninstall GdUnit4 ... [img=24x24]%s[/img]" % spinner_icon) - disable_gdUnit() - if not _debug_mode: - delete_directory("res://addons/gdUnit4/") - # give editor time to react on deleted files - await get_tree().create_timer(1).timeout - - await update_progress("Install new GdUnit4 version ...") - if _debug_mode: - copy_directory(tmp_path, "res://debug") - else: - copy_directory(tmp_path, "res://") - - await update_progress("New GdUnit version successfully installed, Restarting Godot ...") - await get_tree().create_timer(3).timeout - enable_gdUnit() - hide() - delete_directory("res://addons/.gdunit_update") - restart_godot() - - -func restart_godot() -> void: - prints("Force restart Godot") - if _editor_interface: - _editor_interface.restart_editor(true) - - -func enable_gdUnit() -> void: - var enabled_plugins := PackedStringArray() - if ProjectSettings.has_setting("editor_plugins/enabled"): - enabled_plugins = ProjectSettings.get_setting("editor_plugins/enabled") - if not enabled_plugins.has("res://addons/gdUnit4/plugin.cfg"): - enabled_plugins.append("res://addons/gdUnit4/plugin.cfg") - ProjectSettings.set_setting("editor_plugins/enabled", enabled_plugins) - ProjectSettings.save() - - -func disable_gdUnit() -> void: - if _editor_interface: - _editor_interface.set_plugin_enabled("gdUnit4", false) - - -const GDUNIT_TEMP := "user://tmp" - -func temp_dir() -> String: - if not DirAccess.dir_exists_absolute(GDUNIT_TEMP): - DirAccess.make_dir_recursive_absolute(GDUNIT_TEMP) - return GDUNIT_TEMP - - -func create_temp_dir(folder_name :String) -> String: - var new_folder = temp_dir() + "/" + folder_name - delete_directory(new_folder) - if not DirAccess.dir_exists_absolute(new_folder): - DirAccess.make_dir_recursive_absolute(new_folder) - return new_folder - - -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)]) - - -func copy_directory(from_dir :String, to_dir :String) -> 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(): - copy_directory(source + "/", dest) - 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 - - -func extract_zip(zip_package :String, dest_path :String) -> Variant: - var zip: ZIPReader = ZIPReader.new() - var err := zip.open(zip_package) - if err != OK: - push_error("Extracting `%s` failed! Please collect the error log and report this. Error Code: %s" % [zip_package, err]) - return null - 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 dest_path - - -func download_release() -> void: - var zip_file := GdUnitFileAccess.temp_dir() + "/update.zip" - var response :GdUnitUpdateClient.HttpResponse - if _debug_mode: - response = GdUnitUpdateClient.HttpResponse.new(200, PackedByteArray()) - zip_file = "res://update.zip" - else: - response = await _update_client.request_zip_package(_download_url, zip_file) - _update_client.queue_free() - if response.code() != 200: - push_warning("Update information cannot be retrieved from GitHub! \n Error code: %d : %s" % [response.code(), response.response()]) - message_h4("Update failed! Try it later again.", Color.RED) - await get_tree().create_timer(3).timeout - return - - -func _on_confirmed(): - await run_update() diff --git a/addons/gdUnit4/src/update/GdUnitUpdate.tscn b/addons/gdUnit4/src/update/GdUnitUpdate.tscn deleted file mode 100644 index 81fff38..0000000 --- a/addons/gdUnit4/src/update/GdUnitUpdate.tscn +++ /dev/null @@ -1,74 +0,0 @@ -[gd_scene load_steps=3 format=3 uid="uid://2eahgaw88y6q"] - -[ext_resource type="Script" path="res://addons/gdUnit4/src/update/GdUnitUpdate.gd" id="1"] -[ext_resource type="Texture2D" uid="uid://cwxuep3lbnu3p" path="res://addons/gdUnit4/src/update/assets/progress-background.png" id="2_q6rkd"] - -[node name="GdUnitUpdate" type="ConfirmationDialog"] -disable_3d = true -title = "Update GdUnit4 to new version" -size = Vector2i(1000, 141) -visible = true -extend_to_title = true -min_size = Vector2i(1000, 140) -ok_button_text = "Update" -dialog_hide_on_ok = false -script = ExtResource("1") - -[node name="UpdateProgress" type="PanelContainer" parent="."] -custom_minimum_size = Vector2(0, 80) -anchors_preset = 15 -anchor_right = 1.0 -anchor_bottom = 1.0 -offset_left = 8.0 -offset_top = 8.0 -offset_right = -8.0 -offset_bottom = -49.0 -grow_horizontal = 2 -grow_vertical = 2 -size_flags_horizontal = 3 -size_flags_vertical = 3 - -[node name="VBoxContainer" type="VBoxContainer" parent="UpdateProgress"] -layout_mode = 2 - -[node name="Panel" type="Panel" parent="UpdateProgress/VBoxContainer"] -custom_minimum_size = Vector2(0, 40) -layout_mode = 2 - -[node name="message" type="RichTextLabel" parent="UpdateProgress/VBoxContainer/Panel"] -unique_name_in_owner = true -custom_minimum_size = Vector2(400, 40) -layout_mode = 1 -anchors_preset = 15 -anchor_right = 1.0 -anchor_bottom = 1.0 -grow_horizontal = 2 -grow_vertical = 2 -bbcode_enabled = true -fit_content = true -scroll_active = false -shortcut_keys_enabled = false - -[node name="Panel2" type="Panel" parent="UpdateProgress/VBoxContainer"] -custom_minimum_size = Vector2(0, 40) -layout_mode = 2 - -[node name="progress" type="TextureProgressBar" parent="UpdateProgress/VBoxContainer/Panel2"] -unique_name_in_owner = true -clip_contents = true -custom_minimum_size = Vector2(980, 30) -layout_mode = 0 -offset_left = 2.0 -offset_right = 982.0 -offset_bottom = 36.0 -auto_translate = false -localize_numeral_system = false -min_value = 1.0 -max_value = 5.0 -value = 1.0 -rounded = true -nine_patch_stretch = true -texture_progress = ExtResource("2_q6rkd") -texture_progress_offset = Vector2(0, 2) - -[connection signal="confirmed" from="." to="." method="_on_confirmed"] diff --git a/addons/gdUnit4/src/update/GdUnitUpdateClient.gd b/addons/gdUnit4/src/update/GdUnitUpdateClient.gd deleted file mode 100644 index 3d1e876..0000000 --- a/addons/gdUnit4/src/update/GdUnitUpdateClient.gd +++ /dev/null @@ -1,83 +0,0 @@ -@tool -extends Node - -signal request_completed(response) - -class HttpResponse: - var _code :int - var _body :PackedByteArray - - - func _init(code_ :int, body_ :PackedByteArray): - _code = code_ - _body = body_ - - func code() -> int: - return _code - - func response() -> Variant: - var test_json_conv := JSON.new() - test_json_conv.parse(_body.get_string_from_utf8()) - return test_json_conv.get_data() - - func body() -> PackedByteArray: - return _body - -var _http_request :HTTPRequest = HTTPRequest.new() - - -func _ready(): - add_child(_http_request) - _http_request.connect("request_completed", Callable(self, "_on_request_completed")) - - -func _notification(what): - if what == NOTIFICATION_PREDELETE: - if is_instance_valid(_http_request): - _http_request.queue_free() - - -#func list_tags() -> void: -# _http_request.connect("request_completed",Callable(self,"_response_request_tags")) -# var error = _http_request.request("https://api.github.com/repos/MikeSchulze/gdUnit4/tags") -# if error != OK: -# push_error("An error occurred in the HTTP request.") - - -func request_latest_version() -> HttpResponse: - var error = _http_request.request("https://api.github.com/repos/MikeSchulze/gdUnit4/tags") - if error != OK: - var message = "request_latest_version failed: %d" % error - return HttpResponse.new(error, message.to_utf8_buffer()) - return await self.request_completed - - -func request_releases() -> HttpResponse: - var error = _http_request.request("https://api.github.com/repos/MikeSchulze/gdUnit4/releases") - if error != OK: - var message = "request_releases failed: %d" % error - return HttpResponse.new(error, message.to_utf8_buffer()) - return await self.request_completed - - -func request_image(url :String) -> HttpResponse: - var error = _http_request.request(url) - if error != OK: - var message = "request_image failed: %d" % error - return HttpResponse.new(error, message.to_utf8_buffer()) - return await self.request_completed - - -func request_zip_package(url :String, file :String) -> HttpResponse: - _http_request.set_download_file(file) - var error = _http_request.request(url) - if error != OK: - var message = "request_zip_package failed: %d" % error - return HttpResponse.new(error, message.to_utf8_buffer()) - return await self.request_completed - - -func _on_request_completed(_result :int, response_code :int, _headers :PackedStringArray, body :PackedByteArray): - if _http_request.get_http_client_status() != HTTPClient.STATUS_DISCONNECTED: - _http_request.set_download_file("") - request_completed.emit(HttpResponse.new(response_code, body)) diff --git a/addons/gdUnit4/src/update/GdUnitUpdateNotify.gd b/addons/gdUnit4/src/update/GdUnitUpdateNotify.gd deleted file mode 100644 index cc363fc..0000000 --- a/addons/gdUnit4/src/update/GdUnitUpdateNotify.gd +++ /dev/null @@ -1,183 +0,0 @@ -@tool -extends Window - -signal request_completed(response) - -const GdMarkDownReader = preload("res://addons/gdUnit4/src/update/GdMarkDownReader.gd") -const GdUnitUpdateClient = preload("res://addons/gdUnit4/src/update/GdUnitUpdateClient.gd") -const spinner_icon := "res://addons/gdUnit4/src/ui/assets/spinner.tres" - -@onready var _md_reader :GdMarkDownReader = GdMarkDownReader.new() -@onready var _update_client :GdUnitUpdateClient = $GdUnitUpdateClient -@onready var _header :Label = $Panel/GridContainer/PanelContainer/header -@onready var _update_button :Button = $Panel/GridContainer/Panel/HBoxContainer/update -@onready var _close_button :Button = $Panel/GridContainer/Panel/HBoxContainer/close -@onready var _content :RichTextLabel = $Panel/GridContainer/PanelContainer2/ScrollContainer/MarginContainer/content - -var _debug_mode := false - -var _editor_interface :EditorInterface -var _patcher :GdUnitPatcher = GdUnitPatcher.new() -var _current_version := GdUnit4Version.current() -var _available_versions :Array -var _download_zip_url :String - - -func _ready(): - var plugin :EditorPlugin = Engine.get_meta("GdUnitEditorPlugin") - _editor_interface = plugin.get_editor_interface() - _update_button.disabled = true - _md_reader.set_http_client(_update_client) - GdUnitFonts.init_fonts(_content) - await request_releases() - - -func request_releases() -> void: - if _debug_mode: - _header.text = "A new version 'v4.1.0_debug' is available" - await show_update() - return - - # wait 20s to allow the editor to initialize itself - await get_tree().create_timer(20).timeout - var response :GdUnitUpdateClient.HttpResponse = await _update_client.request_latest_version() - if response.code() != 200: - push_warning("Update information cannot be retrieved from GitHub! \n %s" % response.response()) - return - var latest_version := extract_latest_version(response) - # if same version exit here no update need - if latest_version.is_greater(_current_version): - _patcher.scan(_current_version) - _header.text = "A new version '%s' is available" % latest_version - _download_zip_url = extract_zip_url(response) - await show_update() - - -func _colored(message :String, color :Color) -> String: - return "[color=#%s]%s[/color]" % [color.to_html(), message] - - -func message_h4(message :String, color :Color, clear := true) -> void: - if clear: - _content.clear() - _content.append_text("[font_size=16]%s[/font_size]" % _colored(message, color)) - - -func message(message :String, color :Color) -> void: - _content.clear() - _content.append_text(_colored(message, color)) - - -func _process(_delta): - if _content != null and _content.is_visible_in_tree(): - _content.queue_redraw() - - -func show_update() -> void: - message_h4("\n\n\nRequest release infos ... [img=24x24]%s[/img]" % spinner_icon, Color.SNOW) - popup_centered_ratio(.5) - prints("Scan for GdUnit4 Update ...") - var content :String - if _debug_mode: - var template = FileAccess.open("res://addons/gdUnit4/test/update/resources/markdown.txt", FileAccess.READ).get_as_text() - content = await _md_reader.to_bbcode(template) - else: - var response :GdUnitUpdateClient.HttpResponse = await _update_client.request_releases() - if response.code() == 200: - content = await extract_releases(response, _current_version) - else: - message_h4("\n\n\nError checked request available releases!", Color.RED) - return - - # finally force rescan to import images as textures - if Engine.is_editor_hint(): - await rescan() - message(content, Color.DODGER_BLUE) - _update_button.set_disabled(false) - - -static func extract_latest_version(response :GdUnitUpdateClient.HttpResponse) -> GdUnit4Version: - var body :Array = response.response() - return GdUnit4Version.parse(body[0]["name"]) - - -static func extract_zip_url(response :GdUnitUpdateClient.HttpResponse) -> String: - var body :Array = response.response() - return body[0]["zipball_url"] - - -func extract_releases(response :GdUnitUpdateClient.HttpResponse, current_version) -> String: - await get_tree().process_frame - var result := "" - for release in response.response(): - if GdUnit4Version.parse(release["tag_name"]).equals(current_version): - break - var release_description :String = release["body"] - result += await _md_reader.to_bbcode(release_description) - return result - - -func rescan() -> void: - if Engine.is_editor_hint(): - if OS.is_stdout_verbose(): - prints(".. reimport release resources") - var fs := _editor_interface.get_resource_filesystem() - fs.scan() - while fs.is_scanning(): - if OS.is_stdout_verbose(): - progressBar(fs.get_scanning_progress() * 100 as int) - await Engine.get_main_loop().process_frame - await Engine.get_main_loop().process_frame - await get_tree().create_timer(1).timeout - - -func progressBar(p_progress :int, p_color :Color = Color.POWDER_BLUE): - if p_progress < 0: - p_progress = 0 - if p_progress > 100: - p_progress = 100 - printraw("scan [%-50s] %-3d%%\r" % ["".lpad(int(p_progress/2.0), "#").rpad(50, "-"), p_progress]) - - -func _on_update_pressed(): - _update_button.set_disabled(true) - # close all opend scripts before start the update - if not _debug_mode: - ScriptEditorControls.close_open_editor_scripts() - # copy update source to a temp because the update is deleting the whole gdUnit folder - DirAccess.make_dir_absolute("res://addons/.gdunit_update") - DirAccess.copy_absolute("res://addons/gdUnit4/src/update/GdUnitUpdate.tscn", "res://addons/.gdunit_update/GdUnitUpdate.tscn") - DirAccess.copy_absolute("res://addons/gdUnit4/src/update/GdUnitUpdate.gd", "res://addons/.gdunit_update/GdUnitUpdate.gd") - var source := FileAccess.open("res://addons/gdUnit4/src/update/GdUnitUpdate.tscn", FileAccess.READ) - var content := source.get_as_text().replace("res://addons/gdUnit4/src/update/GdUnitUpdate.gd", "res://addons/.gdunit_update/GdUnitUpdate.gd") - var dest := FileAccess.open("res://addons/.gdunit_update/GdUnitUpdate.tscn", FileAccess.WRITE) - dest.store_string(content) - hide() - var update = load("res://addons/.gdunit_update/GdUnitUpdate.tscn").instantiate() - update.setup(_editor_interface, _update_client, _download_zip_url) - Engine.get_main_loop().root.add_child(update) - update.popup_centered() - - -func _on_show_next_toggled(enabled :bool): - GdUnitSettings.set_update_notification(enabled) - - -func _on_cancel_pressed(): - hide() - - -func _on_content_meta_clicked(meta :String): - var properties = str_to_var(meta) - if properties.has("url"): - OS.shell_open(properties.get("url")) - - -func _on_content_meta_hover_started(meta :String): - var properties = str_to_var(meta) - if properties.has("tool_tip"): - _content.set_tooltip_text(properties.get("tool_tip")) - - -func _on_content_meta_hover_ended(meta): - _content.set_tooltip_text("") diff --git a/addons/gdUnit4/src/update/GdUnitUpdateNotify.tscn b/addons/gdUnit4/src/update/GdUnitUpdateNotify.tscn deleted file mode 100644 index 11993eb..0000000 --- a/addons/gdUnit4/src/update/GdUnitUpdateNotify.tscn +++ /dev/null @@ -1,114 +0,0 @@ -[gd_scene load_steps=3 format=3 uid="uid://0xyeci1tqebj"] - -[ext_resource type="Script" path="res://addons/gdUnit4/src/update/GdUnitUpdateNotify.gd" id="1_112wo"] -[ext_resource type="Script" path="res://addons/gdUnit4/src/update/GdUnitUpdateClient.gd" id="2_18asx"] - -[node name="Control" type="Window"] -disable_3d = true -gui_embed_subwindows = true -initial_position = 1 -title = "GdUnit Update Notification" -size = Vector2i(800, 400) -visible = false -wrap_controls = true -transient = true -exclusive = true -min_size = Vector2i(800, 400) -script = ExtResource("1_112wo") - -[node name="GdUnitUpdateClient" type="Node" parent="."] -script = ExtResource("2_18asx") - -[node name="Panel" type="Panel" parent="."] -anchors_preset = 15 -anchor_right = 1.0 -anchor_bottom = 1.0 -grow_horizontal = 2 -grow_vertical = 2 - -[node name="GridContainer" type="VBoxContainer" parent="Panel"] -layout_mode = 1 -anchors_preset = 15 -anchor_right = 1.0 -anchor_bottom = 1.0 -grow_horizontal = 2 -grow_vertical = 2 -size_flags_horizontal = 3 -size_flags_vertical = 3 -alignment = 1 - -[node name="PanelContainer" type="MarginContainer" parent="Panel/GridContainer"] -layout_mode = 2 -theme_override_constants/margin_left = 4 -theme_override_constants/margin_top = 4 -theme_override_constants/margin_right = 4 -theme_override_constants/margin_bottom = 4 - -[node name="header" type="Label" parent="Panel/GridContainer/PanelContainer"] -layout_mode = 2 -size_flags_horizontal = 9 - -[node name="PanelContainer2" type="PanelContainer" parent="Panel/GridContainer"] -layout_mode = 2 -size_flags_vertical = 3 - -[node name="ScrollContainer" type="ScrollContainer" parent="Panel/GridContainer/PanelContainer2"] -layout_mode = 2 -size_flags_horizontal = 3 -size_flags_vertical = 3 - -[node name="MarginContainer" type="MarginContainer" parent="Panel/GridContainer/PanelContainer2/ScrollContainer"] -layout_mode = 2 -size_flags_horizontal = 3 -size_flags_vertical = 3 -theme_override_constants/margin_left = 4 -theme_override_constants/margin_top = 4 -theme_override_constants/margin_right = 4 -theme_override_constants/margin_bottom = 4 - -[node name="content" type="RichTextLabel" parent="Panel/GridContainer/PanelContainer2/ScrollContainer/MarginContainer"] -layout_mode = 2 -size_flags_horizontal = 3 -size_flags_vertical = 3 -bbcode_enabled = true - -[node name="Panel" type="MarginContainer" parent="Panel/GridContainer"] -layout_mode = 2 -size_flags_vertical = 4 -theme_override_constants/margin_left = 4 -theme_override_constants/margin_top = 4 -theme_override_constants/margin_right = 4 -theme_override_constants/margin_bottom = 4 - -[node name="HBoxContainer" type="HBoxContainer" parent="Panel/GridContainer/Panel"] -use_parent_material = true -layout_mode = 2 -theme_override_constants/separation = 4 - -[node name="show_next" type="CheckBox" parent="Panel/GridContainer/Panel/HBoxContainer"] -layout_mode = 2 -size_flags_horizontal = 3 -size_flags_vertical = 4 -text = "show next time" - -[node name="update" type="Button" parent="Panel/GridContainer/Panel/HBoxContainer"] -custom_minimum_size = Vector2(100, 40) -layout_mode = 2 -size_flags_horizontal = 3 -size_flags_vertical = 4 -disabled = true -text = "Update" - -[node name="close" type="Button" parent="Panel/GridContainer/Panel/HBoxContainer"] -custom_minimum_size = Vector2(100, 40) -layout_mode = 2 -size_flags_horizontal = 10 -size_flags_vertical = 4 -text = "Close" - -[connection signal="meta_clicked" from="Panel/GridContainer/PanelContainer2/ScrollContainer/MarginContainer/content" to="." method="_on_content_meta_clicked"] -[connection signal="meta_hover_ended" from="Panel/GridContainer/PanelContainer2/ScrollContainer/MarginContainer/content" to="." method="_on_content_meta_hover_ended"] -[connection signal="meta_hover_started" from="Panel/GridContainer/PanelContainer2/ScrollContainer/MarginContainer/content" to="." method="_on_content_meta_hover_started"] -[connection signal="toggled" from="Panel/GridContainer/Panel/HBoxContainer/show_next" to="." method="_on_show_next_toggled"] -[connection signal="pressed" from="Panel/GridContainer/Panel/HBoxContainer/update" to="." method="_on_update_pressed"] -[connection signal="pressed" from="Panel/GridContainer/Panel/HBoxContainer/close" to="." method="_on_cancel_pressed"] diff --git a/addons/gdUnit4/src/update/assets/border_bottom.png b/addons/gdUnit4/src/update/assets/border_bottom.png deleted file mode 100644 index aa16bb72598cb6f1c703257f4fb38bd64aa5bc8d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1757 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 M07*qoM6N<$g8z+L0RR91 diff --git a/addons/gdUnit4/src/update/assets/dot1.png.import b/addons/gdUnit4/src/update/assets/dot1.png.import deleted file mode 100644 index f01f709..0000000 --- a/addons/gdUnit4/src/update/assets/dot1.png.import +++ /dev/null @@ -1,34 +0,0 @@ -[remap] - -importer="texture" -type="CompressedTexture2D" -uid="uid://ce2eojg0pwpov" -path="res://.godot/imported/dot1.png-380baf1b5247addda93bce3c799aa4e7.ctex" -metadata={ -"vram_texture": false -} - -[deps] - -source_file="res://addons/gdUnit4/src/update/assets/dot1.png" -dest_files=["res://.godot/imported/dot1.png-380baf1b5247addda93bce3c799aa4e7.ctex"] - -[params] - -compress/mode=0 -compress/high_quality=false -compress/lossy_quality=0.7 -compress/hdr_compression=1 -compress/normal_map=0 -compress/channel_pack=0 -mipmaps/generate=false -mipmaps/limit=-1 -roughness/mode=0 -roughness/src_normal="" -process/fix_alpha_border=true -process/premult_alpha=false -process/normal_map_invert_y=false -process/hdr_as_srgb=false -process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=1 diff --git a/addons/gdUnit4/src/update/assets/dot2.png b/addons/gdUnit4/src/update/assets/dot2.png deleted file mode 100644 index 4a744986cde29235547d0252db909b92a7e962f1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 883 zcmV-(1C0EMP)@lpPePF)v>>8_Plb8v5AiZ!A1sQ{7nz6% z^+g|QDI5omL|SiHlTLauHK)l;a?Z(4cK6v!wmKjM{oeLseIM)l)?O0)gJQ7=v$L~s zcz6h7V`EdLQt72^Hd`7Fhfu54j*gFyzoL|WUnmq{Z*LDyPf!0Ms*eP0+XlyRBCD&b z?=LSezco5Kin1(o#+cOW^<*Kuv$OM2r_;IZx~}_(CnXXI==b}OPN$!&R4Vu8=H?Q< z?{^%>Ic1Dpa?S&)s%CXvztLPp1CX?|2;Ah|Wf6z3oCnh%x<5e}2$rNQ-M#fkd03ISDyH5TW2qFqgBoc{$ zu%n7SGsZww)gWU`L&X1NBccodAMV$Z6oW*B2nF~*Gl5xAi!$~dJo z+HSYOb=~`NyO&|a^nbi3UgBHjuHgRl4d{bxrakAnZoxFouw_|8&+|qCfq*A% zY;1rm%g|^vis^LvUb$ROL3CWo=;9rK9MBp_WAjF*KwS`uTeCJ2?S!X znA2{z-!x70PPJNnQP=fxQTIHrP6+uL5v`Gt5r{^k;&K51;7_G{#P;7*2HgMv002ov JPDHLkV1hPknQj08 diff --git a/addons/gdUnit4/src/update/assets/dot2.png.import b/addons/gdUnit4/src/update/assets/dot2.png.import deleted file mode 100644 index f1e1017..0000000 --- a/addons/gdUnit4/src/update/assets/dot2.png.import +++ /dev/null @@ -1,34 +0,0 @@ -[remap] - -importer="texture" -type="CompressedTexture2D" -uid="uid://cvwa5lg3qj0e2" -path="res://.godot/imported/dot2.png-86a9db80ef4413e353c4339ad8f68a5f.ctex" -metadata={ -"vram_texture": false -} - -[deps] - -source_file="res://addons/gdUnit4/src/update/assets/dot2.png" -dest_files=["res://.godot/imported/dot2.png-86a9db80ef4413e353c4339ad8f68a5f.ctex"] - -[params] - -compress/mode=0 -compress/high_quality=false -compress/lossy_quality=0.7 -compress/hdr_compression=1 -compress/normal_map=0 -compress/channel_pack=0 -mipmaps/generate=false -mipmaps/limit=-1 -roughness/mode=0 -roughness/src_normal="" -process/fix_alpha_border=true -process/premult_alpha=false -process/normal_map_invert_y=false -process/hdr_as_srgb=false -process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=1 diff --git a/addons/gdUnit4/src/update/assets/embedded.png b/addons/gdUnit4/src/update/assets/embedded.png deleted file mode 100644 index 15cb9ab0b6eca8d6a4394a33bb8fd646a96449e4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 287 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

41`eR`3l$Vv=zMNJq(73Y|ij<8OWC=ZbCZutR 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 MmDnSJgEZ}b0mzqS2><{9 diff --git a/addons/gdUnit4/src/update/assets/fonts/static/RobotoMono-Bold.ttf.import b/addons/gdUnit4/src/update/assets/fonts/static/RobotoMono-Bold.ttf.import deleted file mode 100644 index 0bc39e4..0000000 --- a/addons/gdUnit4/src/update/assets/fonts/static/RobotoMono-Bold.ttf.import +++ /dev/null @@ -1,33 +0,0 @@ -[remap] - -importer="font_data_dynamic" -type="FontFile" -uid="uid://dgpj1y3a73vc" -path="res://.godot/imported/RobotoMono-Bold.ttf-ea008af97d359b7630bd271235703cae.fontdata" - -[deps] - -source_file="res://addons/gdUnit4/src/update/assets/fonts/static/RobotoMono-Bold.ttf" -dest_files=["res://.godot/imported/RobotoMono-Bold.ttf-ea008af97d359b7630bd271235703cae.fontdata"] - -[params] - -Rendering=null -antialiasing=1 -generate_mipmaps=false -multichannel_signed_distance_field=false -msdf_pixel_range=8 -msdf_size=48 -allow_system_fallback=true -force_autohinter=false -hinting=1 -subpixel_positioning=1 -oversampling=0.0 -Fallbacks=null -fallbacks=[] -Compress=null -compress=true -preload=[] -language_support={} -script_support={} -opentype_features={} diff --git a/addons/gdUnit4/src/update/assets/fonts/static/RobotoMono-BoldItalic.ttf b/addons/gdUnit4/src/update/assets/fonts/static/RobotoMono-BoldItalic.ttf deleted file mode 100644 index 4bfe29ae89075e46bc813f4530cb3d6a9c5a5ae8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 94148 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

8H^QD9=0*hH)U-KYUYGJwz2G~l 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 diff --git a/addons/gdUnit4/src/update/assets/fonts/static/RobotoMono-BoldItalic.ttf.import b/addons/gdUnit4/src/update/assets/fonts/static/RobotoMono-BoldItalic.ttf.import deleted file mode 100644 index e7dab2b..0000000 --- a/addons/gdUnit4/src/update/assets/fonts/static/RobotoMono-BoldItalic.ttf.import +++ /dev/null @@ -1,33 +0,0 @@ -[remap] - -importer="font_data_dynamic" -type="FontFile" -uid="uid://cnrsmyjdnlikm" -path="res://.godot/imported/RobotoMono-BoldItalic.ttf-6e10905211cda810d470782293480777.fontdata" - -[deps] - -source_file="res://addons/gdUnit4/src/update/assets/fonts/static/RobotoMono-BoldItalic.ttf" -dest_files=["res://.godot/imported/RobotoMono-BoldItalic.ttf-6e10905211cda810d470782293480777.fontdata"] - -[params] - -Rendering=null -antialiasing=1 -generate_mipmaps=false -multichannel_signed_distance_field=false -msdf_pixel_range=8 -msdf_size=48 -allow_system_fallback=true -force_autohinter=false -hinting=1 -subpixel_positioning=1 -oversampling=0.0 -Fallbacks=null -fallbacks=[] -Compress=null -compress=true -preload=[] -language_support={} -script_support={} -opentype_features={} diff --git a/addons/gdUnit4/src/update/assets/fonts/static/RobotoMono-ExtraLight.ttf b/addons/gdUnit4/src/update/assets/fonts/static/RobotoMono-ExtraLight.ttf deleted file mode 100644 index d5358845531f601bdf67156a2271a656bb2fa408..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 87788 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�?D37n?91#G?aGd zI)+$v<6t{b7EoxRY(cQ#At5225(|)!P#363AQlKw7A%ku{{XQchaK$;$qrU#_y0cmrM;u6JW6SHQ|;1p=x(Aa5&)V&@; zhwLyE%kI>Bt$OErtIlhOXPRp~+ikVmtwz7q$Pe1_zDLd((A4SU^>)8-qS?I$GH|8c zvr+r#Dpl#io?P6Mi+g20I$qh!?bY`?y?(c|zuUAmWxdYI9v_dg$H$}W@$vAM%lY_Y z5i$YtmX~s*2{twz_IIt3jl*GU<*IgT)wYAMcCnhcj3&);YtK~&@rHHDSFBk`sEhSy z{YG;Kr&PCIE=+-u*w}b6JOnlujsWD=Q|s1KO0FMXw?4XVBhfjxnc8DBwa4b6J#w4L zqPfk{{kBtkY^V0vKD5W&_UO7x$+dZrJst=NptbX2f1L1>6~z?y)1<|~XR7e3~%YNHIkic{%B!wezBmnn?D^i%Z%>VSN}T zTfdlMmNqb-rkC7{9FwzYhN;=aoNP=vIZEEiHEGtV*N>YU9PNL*6pGHS&;brIwMgayzn6Yv;vNHIba{j1g8FUKnAvT9&%+#T1XV z*$t=a5TFfzc^yvg2Qgb-+ZvM zow|5tGbRi+J|FXqfhIOGWeAfskddQ8n7oY61P~_U&8JS+_xJ1LXAj0V4)EoHVN9Ig z?Mz$%H4)5&zuSTa zYX11%!9)YdX`>2JZatf}Tf-9%s%IU4)N(%lI zL^%deCFd#VWrTvhf=In}oTqi3f<{lluBTvEZ!lL@a9GxUWd)TCkVnraK`$awnc=V(QUge{ z$O{=xLK>nM4QQ0YT&Hl>9PDs-O((HsZJH4=G_Y+e1 zHR0EUPnw5$;n##;6FzAl=7nDqeogoTe<%IJb;7R+pZ?~B^fyT1lP01s`+ed2!Y6IS zyzKXd?+f1-zAyWI+3yRV{_KVHXGqyke@0*S)1T27KK&Ve*-yHPzVPYa=nJ2IjlS^d z*XYZB(p)bj&4rZx^mFuOKm8ni*-t-5U-r|_(U<-7bM$3D{TzMSPe1oU`Z=WRr(dHl zdn4gRvX_32d9$}%8hnnd8E(m~ncXEvKZexvLO(>G&x50%l^p#H(r_RRr{sA5AoZM5 z4?ItrU(@w#>eH{VUe7Q6ujE#Rx2k=X?R=r*~dziCPjEs-n&HVN++135~=2!nYBYXcfH<5vT z8F|v%$gRGFjOlxpVSNQz(+`kmeII$#7m;iI7+KRe-}(tMuOA_YdJ`GcPc4V~8ZxMH z2KEc&Vn0VN^)uyC?;x8R=Tg5yHuVkUX73_9`xdgaUn8^nCi1G^AhY@{a;o1Uw|Wbi z)pw9rjkC6IBe(iJva3HJ*Ln{*T;^DRMwa!zYk645|69Ww<2O*(u!z4E+}AVs<4|dH z4!w6#yRwXmhc8jvatC!Q8>nvi5%o7ep<1?KH4<&qNBoWoraw??^Bihz{zOg7U#S1Q zg$mFkncHL2s7ZMp^(c#|&3F*CB_E=$#G%F&)g!26eR%97{!g}3sQkExx{q_HK-n7G zMxDt;RF3|FO#1VgUy)0H0hJi=*VxCP-!c35q0IO*_(CamzE;29Ms*Bo{4qCyfpJvF Ryb2N5OvmdosFK0Ie*@7}kFNj# diff --git a/addons/gdUnit4/src/update/assets/fonts/static/RobotoMono-Italic.ttf.import b/addons/gdUnit4/src/update/assets/fonts/static/RobotoMono-Italic.ttf.import deleted file mode 100644 index 40dab27..0000000 --- a/addons/gdUnit4/src/update/assets/fonts/static/RobotoMono-Italic.ttf.import +++ /dev/null @@ -1,33 +0,0 @@ -[remap] - -importer="font_data_dynamic" -type="FontFile" -uid="uid://hp3750m8e3nq" -path="res://.godot/imported/RobotoMono-Italic.ttf-328fe6d9b2ac5d629c43c335b916d307.fontdata" - -[deps] - -source_file="res://addons/gdUnit4/src/update/assets/fonts/static/RobotoMono-Italic.ttf" -dest_files=["res://.godot/imported/RobotoMono-Italic.ttf-328fe6d9b2ac5d629c43c335b916d307.fontdata"] - -[params] - -Rendering=null -antialiasing=1 -generate_mipmaps=false -multichannel_signed_distance_field=false -msdf_pixel_range=8 -msdf_size=48 -allow_system_fallback=true -force_autohinter=false -hinting=1 -subpixel_positioning=1 -oversampling=0.0 -Fallbacks=null -fallbacks=[] -Compress=null -compress=true -preload=[] -language_support={} -script_support={} -opentype_features={} diff --git a/addons/gdUnit4/src/update/assets/fonts/static/RobotoMono-Light.ttf b/addons/gdUnit4/src/update/assets/fonts/static/RobotoMono-Light.ttf deleted file mode 100644 index 276af4c55a60d3327513b6bdd08b54400d149d29..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 87592 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

eNPK7RZkaz3zjf1&A1zt3 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

09kRVf9S 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

;k(N+E3~9 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 diff --git a/addons/gdUnit4/src/update/assets/fonts/static/RobotoMono-SemiBoldItalic.ttf.import b/addons/gdUnit4/src/update/assets/fonts/static/RobotoMono-SemiBoldItalic.ttf.import deleted file mode 100644 index d135b44..0000000 --- a/addons/gdUnit4/src/update/assets/fonts/static/RobotoMono-SemiBoldItalic.ttf.import +++ /dev/null @@ -1,33 +0,0 @@ -[remap] - -importer="font_data_dynamic" -type="FontFile" -uid="uid://xuc8ovbe0rku" -path="res://.godot/imported/RobotoMono-SemiBoldItalic.ttf-12b525223c8f2dfb78bca4e7ecaf3ca5.fontdata" - -[deps] - -source_file="res://addons/gdUnit4/src/update/assets/fonts/static/RobotoMono-SemiBoldItalic.ttf" -dest_files=["res://.godot/imported/RobotoMono-SemiBoldItalic.ttf-12b525223c8f2dfb78bca4e7ecaf3ca5.fontdata"] - -[params] - -Rendering=null -antialiasing=1 -generate_mipmaps=false -multichannel_signed_distance_field=false -msdf_pixel_range=8 -msdf_size=48 -allow_system_fallback=true -force_autohinter=false -hinting=1 -subpixel_positioning=1 -oversampling=0.0 -Fallbacks=null -fallbacks=[] -Compress=null -compress=true -preload=[] -language_support={} -script_support={} -opentype_features={} diff --git a/addons/gdUnit4/src/update/assets/fonts/static/RobotoMono-Thin.ttf b/addons/gdUnit4/src/update/assets/fonts/static/RobotoMono-Thin.ttf deleted file mode 100644 index ee8a3fd41a9d3daea9cf8358289e4ec10d482c5c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 87872 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ⅆ_9 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 diff --git a/addons/gdUnit4/src/update/assets/progress-background.png.import b/addons/gdUnit4/src/update/assets/progress-background.png.import deleted file mode 100644 index 3f105e8..0000000 --- a/addons/gdUnit4/src/update/assets/progress-background.png.import +++ /dev/null @@ -1,34 +0,0 @@ -[remap] - -importer="texture" -type="CompressedTexture2D" -uid="uid://cwxuep3lbnu3p" -path="res://.godot/imported/progress-background.png-20022b3af2be583c006365edbc69d914.ctex" -metadata={ -"vram_texture": false -} - -[deps] - -source_file="res://addons/gdUnit4/src/update/assets/progress-background.png" -dest_files=["res://.godot/imported/progress-background.png-20022b3af2be583c006365edbc69d914.ctex"] - -[params] - -compress/mode=0 -compress/high_quality=false -compress/lossy_quality=0.7 -compress/hdr_compression=1 -compress/normal_map=0 -compress/channel_pack=0 -mipmaps/generate=false -mipmaps/limit=-1 -roughness/mode=0 -roughness/src_normal="" -process/fix_alpha_border=true -process/premult_alpha=false -process/normal_map_invert_y=false -process/hdr_as_srgb=false -process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=1 diff --git a/addons/gdUnit4/test/GdUnitAwaiterTest.gd b/addons/gdUnit4/test/GdUnitAwaiterTest.gd deleted file mode 100644 index 54f0e3a..0000000 --- a/addons/gdUnit4/test/GdUnitAwaiterTest.gd +++ /dev/null @@ -1,86 +0,0 @@ -# GdUnit generated TestSuite -#warning-ignore-all:unused_argument -#warning-ignore-all:return_value_discarded -class_name GdUnitAwaiterTest -extends GdUnitTestSuite - -# TestSuite generated from -const __source = 'res://addons/gdUnit4/src/GdUnitAwaiter.gd' - -signal test_signal_a() -signal test_signal_b() -signal test_signal_c(value) -signal test_signal_d(value_a, value_b) - - -func after_test(): - for node in get_children(): - if node is Timer: - remove_child(node) - node.stop() - node.free() - - -func install_signal_emitter(signal_name :String, signal_args: Array = [], time_out : float = 0.020): - var timer := Timer.new() - add_child(timer) - timer.timeout.connect(Callable(self, "emit_test_signal").bind(signal_name, signal_args)) - timer.one_shot = true - timer.start(time_out) - - -func emit_test_signal(signal_name :String, signal_args: Array): - match signal_args.size(): - 0: emit_signal(signal_name) - 1: emit_signal(signal_name, signal_args[0]) - 2: emit_signal(signal_name, signal_args[0], signal_args[1]) - 3: emit_signal(signal_name, signal_args[0], signal_args[1], signal_args[2]) - - -func test_await_signal_on() -> void: - install_signal_emitter("test_signal_a") - await await_signal_on(self, "test_signal_a", [], 100) - - install_signal_emitter("test_signal_b") - await await_signal_on(self, "test_signal_b", [], 100) - - install_signal_emitter("test_signal_c", []) - await await_signal_on(self, "test_signal_c", [], 100) - - install_signal_emitter("test_signal_c", ["abc"]) - await await_signal_on(self, "test_signal_c", ["abc"], 100) - - install_signal_emitter("test_signal_c", ["abc", "eee"]) - await await_signal_on(self, "test_signal_c", ["abc", "eee"], 100) - - -func test_await_signal_on_manysignals_emitted() -> void: - # emits many different signals - install_signal_emitter("test_signal_a") - install_signal_emitter("test_signal_a") - install_signal_emitter("test_signal_a") - install_signal_emitter("test_signal_c", ["xxx"]) - # the signal we want to wait for - install_signal_emitter("test_signal_c", ["abc"], .200) - install_signal_emitter("test_signal_c", ["yyy"], .100) - # we only wait for 'test_signal_c("abc")' is emitted - await await_signal_on(self, "test_signal_c", ["abc"], 300) - - -func test_await_signal_on_never_emitted_timedout() -> void: - ( - # we wait for 'test_signal_c("yyy")' which is never emitted - await assert_failure_await(func x(): await await_signal_on(self, "test_signal_c", ["yyy"], 200)) - ).has_line(73)\ - .has_message("await_signal_on(test_signal_c, [\"yyy\"]) timed out after 200ms") - - -func test_await_signal_on_invalid_source_timedout() -> void: - ( - # we wait for a signal on a already freed instance - await assert_failure_await(func x(): await await_signal_on(invalid_node(), "tree_entered", [], 300)) - ).has_line(81).has_message(GdAssertMessages.error_await_signal_on_invalid_instance(null, "tree_entered", [])) - - -func invalid_node() -> Node: - return null diff --git a/addons/gdUnit4/test/GdUnitScanTestSuitePerformanceTest.gd b/addons/gdUnit4/test/GdUnitScanTestSuitePerformanceTest.gd deleted file mode 100644 index e57484e..0000000 --- a/addons/gdUnit4/test/GdUnitScanTestSuitePerformanceTest.gd +++ /dev/null @@ -1,13 +0,0 @@ -extends GdUnitTestSuite - - -func test_load_performance() -> void: - var time = LocalTime.now() - prints("Scan for test suites.") - var test_suites := GdUnitTestSuiteScanner.new().scan("res://addons/gdUnit4/test/") - assert_int(time.elapsed_since_ms())\ - .override_failure_message("Expecting the loading time overall is less than 10s")\ - .is_less(10*1000) - prints("Scanning of %d test suites took" % test_suites.size(), time.elapsed_since()) - for ts in test_suites: - ts.free() diff --git a/addons/gdUnit4/test/GdUnitScanTestSuiteTest.gd b/addons/gdUnit4/test/GdUnitScanTestSuiteTest.gd deleted file mode 100644 index cec2975..0000000 --- a/addons/gdUnit4/test/GdUnitScanTestSuiteTest.gd +++ /dev/null @@ -1,29 +0,0 @@ -# This test verifys only the scanner functionallity -# to find all given tests by pattern 'func test_():' -extends GdUnitTestSuite - - -func test_example(): - assert_that("This is a example message").has_length(25) - assert_that("This is a example message").starts_with("This is a ex") - - -func test_b(): - pass - - -# test name starts with same name e.g. test_b vs test_b2 -func test_b2(): - pass - - -# test scanning success with invalid formatting -func test_b21 ( ) : - pass - - -# finally verify all tests are found -func after(): - assert_array(get_children())\ - .extract("get_name")\ - .contains_exactly(["test_example", "test_b", "test_b2", "test_b21"]) diff --git a/addons/gdUnit4/test/GdUnitScriptTypeTest.gd b/addons/gdUnit4/test/GdUnitScriptTypeTest.gd deleted file mode 100644 index badc982..0000000 --- a/addons/gdUnit4/test/GdUnitScriptTypeTest.gd +++ /dev/null @@ -1,17 +0,0 @@ -# GdUnit generated TestSuite -#warning-ignore-all:unused_argument -#warning-ignore-all:return_value_discarded -class_name GdUnitScriptTypeTest -extends GdUnitTestSuite - -# TestSuite generated from -const __source = 'res://addons/gdUnit4/src/core/GdUnitScriptType.gd' - - -func test_type_of() -> void: - assert_str(GdUnitScriptType.type_of(null)).is_equal(GdUnitScriptType.UNKNOWN) - assert_str(GdUnitScriptType.type_of(ClassDB.instantiate("GDScript"))).is_equal(GdUnitScriptType.GD) - #if GdUnit4CSharpApiLoader.is_mono_supported(): - # assert_str(GdUnitScriptType.type_of(ClassDB.instantiate("CSharpScript"))).is_equal(GdUnitScriptType.CS) - #assert_str(GdUnitScriptType.type_of(ClassDB.instantiate("VisualScript"))).is_equal(GdUnitScriptType.VS) - #assert_str(GdUnitScriptType.type_of(ClassDB.instantiate("NativeScript"))).is_equal(GdUnitScriptType.NATIVE) diff --git a/addons/gdUnit4/test/GdUnitTestCaseTimeoutTest.gd b/addons/gdUnit4/test/GdUnitTestCaseTimeoutTest.gd deleted file mode 100644 index fee6900..0000000 --- a/addons/gdUnit4/test/GdUnitTestCaseTimeoutTest.gd +++ /dev/null @@ -1,125 +0,0 @@ -# this test suite simulates long running test cases -extends GdUnitTestSuite - -const SECOND :int = 1000 -const MINUTE :int = SECOND*60 - -var _before_arg -var _test_arg - - -func before(): - # use some variables to test clone test suite works as expected - _before_arg = "---before---" - - -func before_test(): - # set failing test to success if failed by timeout - discard_error_interupted_by_timeout() - _test_arg = "abc" - - -# without custom timeout should execute the complete test -func test_timeout_after_test_completes(): - assert_str(_before_arg).is_equal("---before---") - var counter := 0 - await await_millis(1000) - prints("A","1s") - counter += 1 - await await_millis(1000) - prints("A","2s") - counter += 1 - await await_millis(1000) - prints("A","3s") - counter += 1 - await await_millis(1000) - prints("A","5s") - counter += 2 - prints("A","end test test_timeout_after_test_completes") - assert_int(counter).is_equal(5) - - -# set test timeout to 2s -@warning_ignore("unused_parameter") -func test_timeout_2s(timeout=2000): - assert_str(_before_arg).is_equal("---before---") - prints("B", "0s") - await await_millis(1000) - prints("B", "1s") - await await_millis(1000) - prints("B", "2s") - await await_millis(1000) - # this line should not reach if timeout aborts the test case after 2s - fail("The test case must be interupted by a timeout after 2s") - prints("B", "3s") - prints("B", "end") - - -# set test timeout to 4s -@warning_ignore("unused_parameter") -func test_timeout_4s(timeout=4000): - assert_str(_before_arg).is_equal("---before---") - prints("C", "0s") - await await_millis(1000) - prints("C", "1s") - await await_millis(1000) - prints("C", "2s") - await await_millis(1000) - prints("C", "3s") - await await_millis(4000) - # this line should not reach if timeout aborts the test case after 4s - fail("The test case must be interupted by a timeout after 4s") - prints("C", "7s") - prints("C", "end") - - -@warning_ignore("unused_parameter") -func test_timeout_single_yield_wait(timeout=3000): - assert_str(_before_arg).is_equal("---before---") - prints("D", "0s") - await await_millis(6000) - prints("D", "6s") - # this line should not reach if timeout aborts the test case after 3s - fail("The test case must be interupted by a timeout after 3s") - prints("D", "end test test_timeout") - - -@warning_ignore("unused_parameter") -func test_timeout_long_running_test_abort(timeout=4000): - assert_str(_before_arg).is_equal("---before---") - prints("E", "0s") - var start_time := Time.get_ticks_msec() - var sec_start_time := Time.get_ticks_msec() - - # simulate long running function - while true: - var elapsed_time := Time.get_ticks_msec() - start_time - var sec_time = Time.get_ticks_msec() - sec_start_time - - if sec_time > 1000: - sec_start_time = Time.get_ticks_msec() - prints("E", LocalTime.elapsed(elapsed_time)) - - # give system time to check for timeout - await await_millis(200) - - # exit while after 4500ms inclusive 500ms offset - if elapsed_time > 4500: - break - - # this line should not reach if timeout aborts the test case after 4s - fail("The test case must be abort interupted by a timeout 4s") - prints("F", "end test test_timeout") - - -@warning_ignore("unused_parameter", "unused_variable") -func test_timeout_fuzzer(fuzzer := Fuzzers.rangei(-23, 22), timeout=2000): - discard_error_interupted_by_timeout() - var value = fuzzer.next_value() - # wait each iteration 200ms - await await_millis(200) - # we expects the test is interupped after 10 iterations because each test takes 200ms - # and the test should not longer run than 2000ms - assert_int(fuzzer.iteration_index())\ - .override_failure_message("The test must be interupted after around 10 iterations")\ - .is_less_equal(10) diff --git a/addons/gdUnit4/test/GdUnitTestCaseTimingTest.gd b/addons/gdUnit4/test/GdUnitTestCaseTimingTest.gd deleted file mode 100644 index 6fbf138..0000000 --- a/addons/gdUnit4/test/GdUnitTestCaseTimingTest.gd +++ /dev/null @@ -1,96 +0,0 @@ -# this test suite simulates long running test cases -extends GdUnitTestSuite - -var _iteration_timer_start = 0 -var _test_values_current :Dictionary -var _test_values_expected :Dictionary - -const SECOND:int = 1000 -const MINUTE:int = SECOND*60 - -class TestCaseStatistics: - var _test_before_calls :int - var _test_after_calls :int - - - func _init(before_calls := 0, after_calls := 0): - _test_before_calls = before_calls - _test_after_calls = after_calls - - -func before(): - _test_values_current = { - "test_2s" : TestCaseStatistics.new(), - "test_multi_yielding" : TestCaseStatistics.new(), - "test_multi_yielding_with_fuzzer" : TestCaseStatistics.new() - } - _test_values_expected = { - "test_2s" : TestCaseStatistics.new(1, 1), - "test_multi_yielding" : TestCaseStatistics.new(1, 1), - "test_multi_yielding_with_fuzzer" : TestCaseStatistics.new(5 , 5) - } - - -func after(): - for test_case in _test_values_expected.keys(): - var current := _test_values_current[test_case] as TestCaseStatistics - var expected := _test_values_expected[test_case] as TestCaseStatistics - assert_int(current._test_before_calls)\ - .override_failure_message("Expect before_test called %s times but is %s for test case %s" % [expected._test_before_calls, current._test_before_calls, test_case])\ - .is_equal(expected._test_before_calls) - assert_int(current._test_after_calls)\ - .override_failure_message("Expect after_test called %s times but is %s for test case %s" % [expected._test_before_calls, current._test_before_calls, test_case])\ - .is_equal(expected._test_after_calls) - - -func before_test(): - var current = _test_values_current[__active_test_case] as TestCaseStatistics - current._test_before_calls +=1 - - -func after_test(): - var current = _test_values_current[__active_test_case] as TestCaseStatistics - current._test_after_calls +=1 - - -func test_2s(): - var timer_start := Time.get_ticks_msec() - await await_millis(2000) - # subtract an offset of 100ms because the time is not accurate - assert_int(Time.get_ticks_msec()-timer_start).is_between(2*SECOND-100, 2*SECOND+100) - - -func test_multi_yielding(): - var timer_start := Time.get_ticks_msec() - prints("test_yielding") - await get_tree().process_frame - prints("4") - await get_tree().create_timer(1.0).timeout - prints("3") - await get_tree().create_timer(1.0).timeout - prints("2") - await get_tree().create_timer(1.0).timeout - prints("1") - await get_tree().create_timer(1.0).timeout - prints("Go") - assert_int(Time.get_ticks_msec()-timer_start).is_greater_equal(4*(SECOND-50)) - - -func test_multi_yielding_with_fuzzer(fuzzer := Fuzzers.rangei(0, 1000), fuzzer_iterations = 5): - if fuzzer.iteration_index() > 5: - fail("Should only called 5 times") - if fuzzer.iteration_index() == 1: - _iteration_timer_start = Time.get_ticks_msec() - prints("test iteration %d" % fuzzer.iteration_index()) - prints("4") - await get_tree().create_timer(1.0).timeout - prints("3") - await get_tree().create_timer(1.0).timeout - prints("2") - await get_tree().create_timer(1.0).timeout - prints("1") - await get_tree().create_timer(1.0).timeout - prints("Go") - if fuzzer.iteration_index() == 5: - # elapsed time must be fuzzer_iterations times * 4s = 40s (using 3,9s because of inaccurate timings) - assert_int(Time.get_ticks_msec()-_iteration_timer_start).is_greater_equal(3900*fuzzer_iterations) diff --git a/addons/gdUnit4/test/GdUnitTestResourceLoader.gd b/addons/gdUnit4/test/GdUnitTestResourceLoader.gd deleted file mode 100644 index 665c1ee..0000000 --- a/addons/gdUnit4/test/GdUnitTestResourceLoader.gd +++ /dev/null @@ -1,77 +0,0 @@ -class_name GdUnitTestResourceLoader -extends RefCounted - -const GdUnitTools := preload("res://addons/gdUnit4/src/core/GdUnitTools.gd") - -enum { - GD_SUITE, - CS_SUITE -} - - -static func load_test_suite(resource_path :String, script_type = GD_SUITE) -> Node: - match script_type: - GD_SUITE: - return load_test_suite_gd(resource_path) - CS_SUITE: - return load_test_suite_cs(resource_path) - assert("type '%s' is not implemented" % script_type) - return null - - -static func load_test_suite_gd(resource_path :String) -> GdUnitTestSuite: - var script := load_gd_script(resource_path) - var test_suite :GdUnitTestSuite = script.new() - test_suite.set_name(resource_path.get_file().replace(".resource", "").replace(".gd", "")) - # complete test suite wiht parsed test cases - var suite_scanner := GdUnitTestSuiteScanner.new() - var test_case_names := suite_scanner._extract_test_case_names(script) - # add test cases to test suite and parse test case line nummber - suite_scanner._parse_and_add_test_cases(test_suite, script, test_case_names) - return test_suite - - -static func load_test_suite_cs(resource_path :String) -> Node: - if not GdUnit4CSharpApiLoader.is_mono_supported(): - return null - var script = ClassDB.instantiate("CSharpScript") - script.source_code = GdUnitFileAccess.resource_as_string(resource_path) - script.resource_path = resource_path - script.reload() - return null - - -static func load_cs_script(resource_path :String, debug_write := false) -> Script: - if not GdUnit4CSharpApiLoader.is_mono_supported(): - return null - var script = ClassDB.instantiate("CSharpScript") - script.source_code = GdUnitFileAccess.resource_as_string(resource_path) - script.resource_path = GdUnitFileAccess.create_temp_dir("test") + "/%s" % resource_path.get_file().replace(".resource", ".cs") - if debug_write: - print_debug("save resource:", script.resource_path) - DirAccess.remove_absolute(script.resource_path) - var err := ResourceSaver.save(script, script.resource_path) - if err != OK: - print_debug("Can't save debug resource", script.resource_path, "Error:", error_string(err)) - script.take_over_path(script.resource_path) - else: - script.take_over_path(resource_path) - script.reload() - return script - - -static func load_gd_script(resource_path :String, debug_write := false) -> GDScript: - var script := GDScript.new() - script.source_code = GdUnitFileAccess.resource_as_string(resource_path) - script.resource_path = GdUnitFileAccess.create_temp_dir("test") + "/%s" % resource_path.get_file().replace(".resource", ".gd") - if debug_write: - print_debug("save resource:", script.resource_path) - DirAccess.remove_absolute(script.resource_path) - var err := ResourceSaver.save(script, script.resource_path) - if err != OK: - print_debug("Can't save debug resource", script.resource_path, "Error:", error_string(err)) - script.take_over_path(script.resource_path) - else: - script.take_over_path(resource_path) - script.reload() - return script diff --git a/addons/gdUnit4/test/GdUnitTestResourceLoaderTest.gd b/addons/gdUnit4/test/GdUnitTestResourceLoaderTest.gd deleted file mode 100644 index d75de1c..0000000 --- a/addons/gdUnit4/test/GdUnitTestResourceLoaderTest.gd +++ /dev/null @@ -1,16 +0,0 @@ -# GdUnit generated TestSuite -#warning-ignore-all:unused_argument -#warning-ignore-all:return_value_discarded -class_name GdUnitTestResourceLoaderTest -extends GdUnitTestSuite - -# TestSuite generated from -const __source = 'res://addons/gdUnit4/test/GdUnitTestResourceLoader.gd' - - -func test_load_test_suite_gd() -> void: - var resource_path = "res://addons/gdUnit4/test/core/resources/testsuites/TestSuiteParameterizedTests.resource" - var test_suite := GdUnitTestResourceLoader.load_test_suite_gd(resource_path) - assert_that(test_suite).is_not_null() - assert_object(test_suite).is_instanceof(GdUnitTestSuite) - test_suite.free() diff --git a/addons/gdUnit4/test/GdUnitTestSuitePerformanceTest.gd b/addons/gdUnit4/test/GdUnitTestSuitePerformanceTest.gd deleted file mode 100644 index f2770c0..0000000 --- a/addons/gdUnit4/test/GdUnitTestSuitePerformanceTest.gd +++ /dev/null @@ -1,13 +0,0 @@ -extends GdUnitTestSuite - - -func test_testsuite_loading_performance(): - var time := LocalTime.now() - var reload_counter := 100 - for i in range(1, reload_counter): - ResourceLoader.load("res://addons/gdUnit4/src/GdUnitTestSuite.gd", "GDScript", ResourceLoader.CACHE_MODE_IGNORE) - var error_message := "Expecting the loading time of test-suite is less than 50ms\n But was %s" % (time.elapsed_since_ms() / reload_counter) - assert_int(time.elapsed_since_ms()/ reload_counter)\ - .override_failure_message(error_message)\ - .is_less(50) - prints("loading takes %d ms" % (time.elapsed_since_ms() / reload_counter)) diff --git a/addons/gdUnit4/test/GdUnitTestSuiteTest.gd b/addons/gdUnit4/test/GdUnitTestSuiteTest.gd deleted file mode 100644 index c7c8f6a..0000000 --- a/addons/gdUnit4/test/GdUnitTestSuiteTest.gd +++ /dev/null @@ -1,48 +0,0 @@ -# GdUnit generated TestSuite -class_name GdUnitTestSuiteTest -extends GdUnitTestSuite - -# TestSuite generated from -const __source = 'res://addons/gdUnit4/src/GdUnitTestSuite.gd' -const GdUnitAssertImpl = preload("res://addons/gdUnit4/src/asserts/GdUnitAssertImpl.gd") - -var _events :Array[GdUnitEvent] = [] - - -func collect_report(event :GdUnitEvent): - _events.push_back(event) - - -func before(): - # register to receive test reports - GdUnitSignals.instance().gdunit_event.connect(collect_report) - - -func after(): - # verify the test case `test_unknown_argument_in_test_case` was skipped - assert_array(_events).extractv(extr("type"), extr("is_skipped"), extr("test_name"))\ - .contains([tuple(GdUnitEvent.TESTCASE_AFTER, true, "test_unknown_argument_in_test_case")]) - GdUnitSignals.instance().gdunit_event.disconnect(collect_report) - - -func test_assert_that_types() -> void: - assert_object(assert_that(true)).is_instanceof(GdUnitBoolAssert) - assert_object(assert_that(1)).is_instanceof(GdUnitIntAssert) - assert_object(assert_that(3.12)).is_instanceof(GdUnitFloatAssert) - assert_object(assert_that("abc")).is_instanceof(GdUnitStringAssert) - assert_object(assert_that(Vector2.ONE)).is_instanceof(GdUnitVectorAssert) - assert_object(assert_that(Vector2i.ONE)).is_instanceof(GdUnitVectorAssert) - assert_object(assert_that(Vector3.ONE)).is_instanceof(GdUnitVectorAssert) - assert_object(assert_that(Vector3i.ONE)).is_instanceof(GdUnitVectorAssert) - assert_object(assert_that(Vector4.ONE)).is_instanceof(GdUnitVectorAssert) - assert_object(assert_that(Vector4i.ONE)).is_instanceof(GdUnitVectorAssert) - assert_object(assert_that([])).is_instanceof(GdUnitArrayAssert) - assert_object(assert_that({})).is_instanceof(GdUnitDictionaryAssert) - assert_object(assert_that(GdUnitResult.new())).is_instanceof(GdUnitObjectAssert) - # all not a built-in types mapped to default GdUnitAssert - assert_object(assert_that(Color.RED)).is_instanceof(GdUnitAssertImpl) - assert_object(assert_that(Plane.PLANE_XY)).is_instanceof(GdUnitAssertImpl) - - -func test_unknown_argument_in_test_case(_invalid_arg) -> void: - fail("This test case should be not executed, it must be skipped.") diff --git a/addons/gdUnit4/test/asserts/CallBackValueProviderTest.gd b/addons/gdUnit4/test/asserts/CallBackValueProviderTest.gd deleted file mode 100644 index 1698977..0000000 --- a/addons/gdUnit4/test/asserts/CallBackValueProviderTest.gd +++ /dev/null @@ -1,23 +0,0 @@ -# GdUnit generated TestSuite -#warning-ignore-all:unused_argument -#warning-ignore-all:return_value_discarded -class_name CallBackValueProviderTest -extends GdUnitTestSuite - -# TestSuite generated from -const __source = 'res://addons/gdUnit4/src/asserts/CallBackValueProvider.gd' - - -func next_value() -> String: - return "a value" - - -func test_get_value() -> void: - var vp := CallBackValueProvider.new(self, "next_value") - assert_str(await vp.get_value()).is_equal("a value") - - -func test_construct_invalid() -> void: - var vp := CallBackValueProvider.new(self, "invalid_func", Array(), false) - # will return null because of invalid function name - assert_str(await vp.get_value()).is_null() diff --git a/addons/gdUnit4/test/asserts/GdUnitArrayAssertImplTest.gd b/addons/gdUnit4/test/asserts/GdUnitArrayAssertImplTest.gd deleted file mode 100644 index d70dbb6..0000000 --- a/addons/gdUnit4/test/asserts/GdUnitArrayAssertImplTest.gd +++ /dev/null @@ -1,777 +0,0 @@ -# GdUnit generated TestSuite -extends GdUnitTestSuite - -# TestSuite generated from -const __source = 'res://addons/gdUnit4/src/asserts/GdUnitArrayAssertImpl.gd' - -var _saved_report_assert_warnings - - -func before(): - _saved_report_assert_warnings = ProjectSettings.get_setting(GdUnitSettings.REPORT_ASSERT_WARNINGS) - ProjectSettings.set_setting(GdUnitSettings.REPORT_ASSERT_WARNINGS, false) - - -func after(): - ProjectSettings.set_setting(GdUnitSettings.REPORT_ASSERT_WARNINGS, _saved_report_assert_warnings) - - -func test_is_null() -> void: - assert_array(null).is_null() - - assert_failure(func(): assert_array([]).is_null()) \ - .is_failed() \ - .has_message("Expecting: '' but was ''") - - -func test_is_not_null() -> void: - assert_array([]).is_not_null() - - assert_failure(func(): assert_array(null).is_not_null()) \ - .is_failed() \ - .has_message("Expecting: not to be ''") - - -func test_is_equal(): - assert_array([1, 2, 3, 4, 2, 5]).is_equal([1, 2, 3, 4, 2, 5]) - # should fail because the array not contains same elements and has diff size - assert_failure(func(): assert_array([1, 2, 4, 5]).is_equal([1, 2, 3, 4, 2, 5])) \ - .is_failed() - assert_failure(func(): assert_array([1, 2, 3, 4, 5]).is_equal([1, 2, 3, 4])) \ - .is_failed() - # current array is bigger than expected - assert_failure(func(): assert_array([1, 2222, 3, 4, 5, 6]).is_equal([1, 2, 3, 4])) \ - .is_failed() \ - .has_message(""" - Expecting: - '[1, 2, 3, 4]' - but was - '[1, 2222, 3, 4, 5, 6]' - - Differences found: - Index Current Expected 1 2222 2 4 5 5 6 """ - .dedent().trim_prefix("\n")) - - # expected array is bigger than current - assert_failure(func(): assert_array([1, 222, 3, 4]).is_equal([1, 2, 3, 4, 5, 6])) \ - .is_failed() \ - .has_message(""" - Expecting: - '[1, 2, 3, 4, 5, 6]' - but was - '[1, 222, 3, 4]' - - Differences found: - Index Current Expected 1 222 2 4 5 5 6 """ - .dedent().trim_prefix("\n")) - - assert_failure(func(): assert_array(null).is_equal([1, 2, 3])) \ - .is_failed() \ - .has_message(""" - Expecting: - '[1, 2, 3]' - but was - ''""" - .dedent().trim_prefix("\n")) - - -func test_is_equal_big_arrays(): - var expeted := Array() - expeted.resize(1000) - for i in 1000: - expeted[i] = i - var current := expeted.duplicate() - current[10] = "invalid" - current[40] = "invalid" - current[100] = "invalid" - current[888] = "invalid" - - assert_failure(func(): assert_array(current).is_equal(expeted)) \ - .is_failed() \ - .has_message(""" - Expecting: - '[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, ...]' - but was - '[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, invalid, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, ...]' - - Differences found: - Index Current Expected 10 invalid 10 40 invalid 40 100 invalid 100 888 invalid 888 """ - .dedent().trim_prefix("\n")) - - -func test_is_equal_ignoring_case(): - assert_array(["this", "is", "a", "message"]).is_equal_ignoring_case(["This", "is", "a", "Message"]) - # should fail because the array not contains same elements - assert_failure(func(): assert_array(["this", "is", "a", "message"]).is_equal_ignoring_case(["This", "is", "an", "Message"])) \ - .is_failed() - assert_failure(func(): assert_array(null).is_equal_ignoring_case(["This", "is"])) \ - .is_failed() \ - .has_message(""" - Expecting: - '["This", "is"]' - but was - ''""" - .dedent().trim_prefix("\n")) - - -func test_is_not_equal(): - assert_array(null).is_not_equal([1, 2, 3]) - assert_array([1, 2, 3, 4, 5]).is_not_equal([1, 2, 3, 4, 5, 6]) - # should fail because the array contains same elements - assert_failure(func(): assert_array([1, 2, 3, 4, 5]).is_not_equal([1, 2, 3, 4, 5])) \ - .is_failed() \ - .has_message(""" - Expecting: - '[1, 2, 3, 4, 5]' - not equal to - '[1, 2, 3, 4, 5]'""" - .dedent().trim_prefix("\n")) - - -func test_is_not_equal_ignoring_case(): - assert_array(null).is_not_equal_ignoring_case(["This", "is", "an", "Message"]) - assert_array(["this", "is", "a", "message"]).is_not_equal_ignoring_case(["This", "is", "an", "Message"]) - # should fail because the array contains same elements ignoring case sensitive - assert_failure(func(): assert_array(["this", "is", "a", "message"]).is_not_equal_ignoring_case(["This", "is", "a", "Message"])) \ - .is_failed() \ - .has_message(""" - Expecting: - '["This", "is", "a", "Message"]' - not equal to (case insensitiv) - '["this", "is", "a", "message"]'""" - .dedent().trim_prefix("\n")) - - -func test_is_empty(): - assert_array([]).is_empty() - - assert_failure(func(): assert_array([1, 2, 3]).is_empty()) \ - .is_failed() \ - .has_message(""" - Expecting: - must be empty but was - '[1, 2, 3]'""" - .dedent().trim_prefix("\n")) - assert_failure(func(): assert_array(null).is_empty()) \ - .is_failed() \ - .has_message(""" - Expecting: - must be empty but was - ''""" - .dedent().trim_prefix("\n")) - - -func test_is_not_empty(): - assert_array(null).is_not_empty() - assert_array([1]).is_not_empty() - - assert_failure(func(): assert_array([]).is_not_empty()) \ - .is_failed() \ - .has_message("Expecting:\n must not be empty") - - -func test_is_same() -> void: - var value := [0] - assert_array(value).is_same(value) - - assert_failure(func(): assert_array(value).is_same(value.duplicate()))\ - .is_failed()\ - .has_message("Expecting:\n '[0]'\n to refer to the same object\n '[0]'") - - -func test_is_not_same() -> void: - assert_array([0]).is_not_same([0]) - var value := [0] - assert_failure(func(): assert_array(value).is_not_same(value))\ - .is_failed()\ - .has_message("Expecting not same:\n '[0]'") - - -func test_has_size(): - assert_array([1, 2, 3, 4, 5]).has_size(5) - assert_array(["a", "b", "c", "d", "e", "f"]).has_size(6) - - assert_failure(func(): assert_array([1, 2, 3, 4, 5]).has_size(4)) \ - .is_failed() \ - .has_message(""" - Expecting size: - '4' - but was - '5'""" - .dedent().trim_prefix("\n")) - assert_failure(func(): assert_array(null).has_size(4)) \ - .is_failed() \ - .has_message(""" - Expecting size: - '4' - but was - ''""" - .dedent().trim_prefix("\n")) - - -func test_contains(): - assert_array([1, 2, 3, 4, 5]).contains([]) - assert_array([1, 2, 3, 4, 5]).contains([5, 2]) - assert_array([1, 2, 3, 4, 5]).contains([5, 4, 3, 2, 1]) - var valueA := TestObj.new("A", 0) - var valueB := TestObj.new("B", 0) - assert_array([valueA, valueB]).contains([TestObj.new("A", 0)]) - - # should fail because the array not contains 7 and 6 - assert_failure(func(): assert_array([1, 2, 3, 4, 5]).contains([2, 7, 6])) \ - .is_failed() \ - .has_message(""" - Expecting contains elements: - '[1, 2, 3, 4, 5]' - do contains (in any order) - '[2, 7, 6]' - but could not find elements: - '[7, 6]'""" - .dedent().trim_prefix("\n")) - - assert_failure(func(): assert_array(null).contains([2, 7, 6])) \ - .is_failed() \ - .has_message(""" - Expecting contains elements: - '' - do contains (in any order) - '[2, 7, 6]' - but could not find elements: - '[2, 7, 6]'""" - .dedent().trim_prefix("\n")) - - assert_failure(func(): assert_array([valueA, valueB]).contains([TestObj.new("C", 0)])) \ - .is_failed() \ - .has_message(""" - Expecting contains elements: - '[class:A, class:B]' - do contains (in any order) - '[class:C]' - but could not find elements: - '[class:C]'""" - .dedent().trim_prefix("\n")) - - -func test_contains_exactly(): - assert_array([1, 2, 3, 4, 5]).contains_exactly([1, 2, 3, 4, 5]) - var valueA := TestObj.new("A", 0) - var valueB := TestObj.new("B", 0) - assert_array([valueA, valueB]).contains_exactly([TestObj.new("A", 0), valueB]) - - # should fail because the array contains the same elements but in a different order - assert_failure(func(): assert_array([1, 2, 3, 4, 5]).contains_exactly([1, 4, 3, 2, 5])) \ - .is_failed() \ - .has_message(""" - Expecting contains exactly elements: - '[1, 2, 3, 4, 5]' - do contains (in same order) - '[1, 4, 3, 2, 5]' - but has different order at position '1' - '2' vs '4'""" - .dedent().trim_prefix("\n")) - - # should fail because the array contains more elements and in a different order - assert_failure(func(): assert_array([1, 2, 3, 4, 5, 6, 7]).contains_exactly([1, 4, 3, 2, 5])) \ - .is_failed() \ - .has_message(""" - Expecting contains exactly elements: - '[1, 2, 3, 4, 5, 6, 7]' - do contains (in same order) - '[1, 4, 3, 2, 5]' - but some elements where not expected: - '[6, 7]'""" - .dedent().trim_prefix("\n")) - - # should fail because the array contains less elements and in a different order - assert_failure(func(): assert_array([1, 2, 3, 4, 5]).contains_exactly([1, 4, 3, 2, 5, 6, 7])) \ - .is_failed() \ - .has_message(""" - Expecting contains exactly elements: - '[1, 2, 3, 4, 5]' - do contains (in same order) - '[1, 4, 3, 2, 5, 6, 7]' - but could not find elements: - '[6, 7]'""" - .dedent().trim_prefix("\n")) - - assert_failure(func(): assert_array(null).contains_exactly([1, 4, 3])) \ - .is_failed() \ - .has_message(""" - Expecting contains exactly elements: - '' - do contains (in same order) - '[1, 4, 3]' - but could not find elements: - '[1, 4, 3]'""" - .dedent().trim_prefix("\n")) - - assert_failure(func(): assert_array([valueA, valueB]).contains_exactly([valueB, TestObj.new("A", 0)])) \ - .is_failed() \ - .has_message(""" - Expecting contains exactly elements: - '[class:A, class:B]' - do contains (in same order) - '[class:B, class:A]' - but has different order at position '0' - 'class:A' vs 'class:B'""" - .dedent().trim_prefix("\n")) - - -func test_contains_exactly_in_any_order(): - assert_array([1, 2, 3, 4, 5]).contains_exactly_in_any_order([1, 2, 3, 4, 5]) - assert_array([1, 2, 3, 4, 5]).contains_exactly_in_any_order([5, 3, 2, 4, 1]) - assert_array([1, 2, 3, 4, 5]).contains_exactly_in_any_order([5, 1, 2, 4, 3]) - var valueA := TestObj.new("A", 0) - var valueB := TestObj.new("B", 0) - assert_array([valueA, valueB]).contains_exactly_in_any_order([valueB, TestObj.new("A", 0)]) - - # should fail because the array contains not exactly the same elements in any order - assert_failure(func(): assert_array([1, 2, 6, 4, 5]).contains_exactly_in_any_order([5, 3, 2, 4, 1, 9, 10])) \ - .is_failed() \ - .has_message(""" - Expecting contains exactly elements: - '[1, 2, 6, 4, 5]' - do contains exactly (in any order) - '[5, 3, 2, 4, 1, 9, 10]' - but some elements where not expected: - '[6]' - and could not find elements: - '[3, 9, 10]'""" - .dedent().trim_prefix("\n")) - - #should fail because the array contains the same elements but in a different order - assert_failure(func(): assert_array([1, 2, 6, 9, 10, 4, 5]).contains_exactly_in_any_order([5, 3, 2, 4, 1])) \ - .is_failed() \ - .has_message(""" - Expecting contains exactly elements: - '[1, 2, 6, 9, 10, 4, 5]' - do contains exactly (in any order) - '[5, 3, 2, 4, 1]' - but some elements where not expected: - '[6, 9, 10]' - and could not find elements: - '[3]'""" - .dedent().trim_prefix("\n")) - - assert_failure(func(): assert_array(null).contains_exactly_in_any_order([1, 4, 3])) \ - .is_failed() \ - .has_message(""" - Expecting contains exactly elements: - '' - do contains exactly (in any order) - '[1, 4, 3]' - but could not find elements: - '[1, 4, 3]'""" - .dedent().trim_prefix("\n")) - - assert_failure(func(): assert_array([valueA, valueB]).contains_exactly_in_any_order([valueB, TestObj.new("C", 0)])) \ - .is_failed() \ - .has_message(""" - Expecting contains exactly elements: - '[class:A, class:B]' - do contains exactly (in any order) - '[class:B, class:C]' - but some elements where not expected: - '[class:A]' - and could not find elements: - '[class:C]'""" - .dedent().trim_prefix("\n")) - - -func test_contains_same(): - var valueA := TestObj.new("A", 0) - var valueB := TestObj.new("B", 0) - - assert_array([valueA, valueB]).contains_same([valueA]) - - assert_failure(func(): assert_array([valueA, valueB]).contains_same([TestObj.new("A", 0)])) \ - .is_failed() \ - .has_message(""" - Expecting contains SAME elements: - '[class:A, class:B]' - do contains (in any order) - '[class:A]' - but could not find elements: - '[class:A]'""" - .dedent().trim_prefix("\n")) - - -func test_contains_same_exactly(): - var valueA := TestObj.new("A", 0) - var valueB := TestObj.new("B", 0) - assert_array([valueA, valueB]).contains_same_exactly([valueA, valueB]) - - assert_failure(func(): assert_array([valueA, valueB]).contains_same_exactly([valueB, valueA])) \ - .is_failed() \ - .has_message(""" - Expecting contains SAME exactly elements: - '[class:A, class:B]' - do contains (in same order) - '[class:B, class:A]' - but has different order at position '0' - 'class:A' vs 'class:B'""" - .dedent().trim_prefix("\n")) - - assert_failure(func(): assert_array([valueA, valueB]).contains_same_exactly([TestObj.new("A", 0), valueB])) \ - .is_failed() \ - .has_message(""" - Expecting contains SAME exactly elements: - '[class:A, class:B]' - do contains (in same order) - '[class:A, class:B]' - but some elements where not expected: - '[class:A]' - and could not find elements: - '[class:A]'""" - .dedent().trim_prefix("\n")) - - -func test_contains_same_exactly_in_any_order(): - var valueA := TestObj.new("A", 0) - var valueB := TestObj.new("B", 0) - assert_array([valueA, valueB]).contains_same_exactly_in_any_order([valueB, valueA]) - - assert_failure(func(): assert_array([valueA, valueB]).contains_same_exactly_in_any_order([valueB, TestObj.new("A", 0)])) \ - .is_failed() \ - .has_message(""" - Expecting contains SAME exactly elements: - '[class:A, class:B]' - do contains exactly (in any order) - '[class:B, class:A]' - but some elements where not expected: - '[class:A]' - and could not find elements: - '[class:A]'""" - .dedent().trim_prefix("\n")) - - -func test_not_contains(): - assert_array([]).not_contains([0]) - assert_array([1, 2, 3, 4, 5]).not_contains([0]) - assert_array([1, 2, 3, 4, 5]).not_contains([0, 6]) - var valueA := TestObj.new("A", 0) - var valueB := TestObj.new("B", 0) - assert_array([valueA, valueB]).not_contains([TestObj.new("C", 0)]) - - assert_failure(func(): assert_array([1, 2, 3, 4, 5]).not_contains([5]))\ - .is_failed() \ - .has_message(""" - Expecting: - '[1, 2, 3, 4, 5]' - do not contains - '[5]' - but found elements: - '[5]'""" - .dedent().trim_prefix("\n") - ) - - assert_failure(func(): assert_array([1, 2, 3, 4, 5]).not_contains([1, 4, 6])) \ - .is_failed() \ - .has_message(""" - Expecting: - '[1, 2, 3, 4, 5]' - do not contains - '[1, 4, 6]' - but found elements: - '[1, 4]'""" - .dedent().trim_prefix("\n") - ) - - assert_failure(func(): assert_array([1, 2, 3, 4, 5]).not_contains([6, 4, 1])) \ - .is_failed() \ - .has_message(""" - Expecting: - '[1, 2, 3, 4, 5]' - do not contains - '[6, 4, 1]' - but found elements: - '[4, 1]'""" - .dedent().trim_prefix("\n") - ) - - assert_failure(func(): assert_array([valueA, valueB]).not_contains([TestObj.new("A", 0)])) \ - .is_failed() \ - .has_message(""" - Expecting: - '[class:A, class:B]' - do not contains - '[class:A]' - but found elements: - '[class:A]'""" - .dedent().trim_prefix("\n") - ) - - -func test_not_contains_same(): - var valueA := TestObj.new("A", 0) - var valueB := TestObj.new("B", 0) - var valueC := TestObj.new("B", 0) - assert_array([valueA, valueB]).not_contains_same([valueC]) - - assert_failure(func(): assert_array([valueA, valueB]).not_contains_same([valueB])) \ - .is_failed() \ - .has_message(""" - Expecting SAME: - '[class:A, class:B]' - do not contains - '[class:B]' - but found elements: - '[class:B]'""" - .dedent().trim_prefix("\n") - ) - - -func test_fluent(): - assert_array([])\ - .has_size(0)\ - .is_empty()\ - .is_not_null()\ - .contains([])\ - .contains_exactly([]) - - -func test_must_fail_has_invlalid_type(): - assert_failure(func(): assert_array(1)) \ - .is_failed() \ - .has_message("GdUnitArrayAssert inital error, unexpected type ") - assert_failure(func(): assert_array(1.3)) \ - .is_failed() \ - .has_message("GdUnitArrayAssert inital error, unexpected type ") - assert_failure(func(): assert_array(true)) \ - .is_failed() \ - .has_message("GdUnitArrayAssert inital error, unexpected type ") - assert_failure(func(): assert_array(Resource.new())) \ - .is_failed() \ - .has_message("GdUnitArrayAssert inital error, unexpected type ") - - -func test_extract() -> void: - # try to extract checked base types - assert_array([1, false, 3.14, null, Color.ALICE_BLUE]).extract("get_class") \ - .contains_exactly(["n.a.", "n.a.", "n.a.", null, "n.a."]) - # extracting by a func without arguments - assert_array([RefCounted.new(), 2, AStar3D.new(), auto_free(Node.new())]).extract("get_class") \ - .contains_exactly(["RefCounted", "n.a.", "AStar3D", "Node"]) - # extracting by a func with arguments - assert_array([RefCounted.new(), 2, AStar3D.new(), auto_free(Node.new())]).extract("has_signal", ["tree_entered"]) \ - .contains_exactly([false, "n.a.", false, true]) - - # try extract checked object via a func that not exists - assert_array([RefCounted.new(), 2, AStar3D.new(), auto_free(Node.new())]).extract("invalid_func") \ - .contains_exactly(["n.a.", "n.a.", "n.a.", "n.a."]) - # try extract checked object via a func that has no return value - assert_array([RefCounted.new(), 2, AStar3D.new(), auto_free(Node.new())]).extract("remove_meta", [""]) \ - .contains_exactly([null, "n.a.", null, null]) - - assert_failure(func(): assert_array(null).extract("get_class").contains_exactly(["AStar3D", "Node"])) \ - .is_failed() \ - .has_message(""" - Expecting contains exactly elements: - '' - do contains (in same order) - '["AStar3D", "Node"]' - but could not find elements: - '["AStar3D", "Node"]'""" - .dedent().trim_prefix("\n")) - - -class TestObj: - var _name :String - var _value - var _x - - func _init(name :String, value, x = null): - _name = name - _value = value - _x = x - - func get_name() -> String: - return _name - - func get_value(): - return _value - - func get_x(): - return _x - - func get_x1() -> String: - return "x1" - - func get_x2() -> String: - return "x2" - - func get_x3() -> String: - return "x3" - - func get_x4() -> String: - return "x4" - - func get_x5() -> String: - return "x5" - - func get_x6() -> String: - return "x6" - - func get_x7() -> String: - return "x7" - - func get_x8() -> String: - return "x8" - - func get_x9() -> String: - return "x9" - - func _to_string() -> String: - return "class:" + _name - -func test_extractv() -> void: - # single extract - assert_array([1, false, 3.14, null, Color.ALICE_BLUE])\ - .extractv(extr("get_class"))\ - .contains_exactly(["n.a.", "n.a.", "n.a.", null, "n.a."]) - # tuple of two - assert_array([TestObj.new("A", 10), TestObj.new("B", "foo"), Color.ALICE_BLUE, TestObj.new("C", 11)])\ - .extractv(extr("get_name"), extr("get_value"))\ - .contains_exactly([tuple("A", 10), tuple("B", "foo"), tuple("n.a.", "n.a."), tuple("C", 11)]) - # tuple of three - assert_array([TestObj.new("A", 10), TestObj.new("B", "foo", "bar"), TestObj.new("C", 11, 42)])\ - .extractv(extr("get_name"), extr("get_value"), extr("get_x"))\ - .contains_exactly([tuple("A", 10, null), tuple("B", "foo", "bar"), tuple("C", 11, 42)]) - - assert_failure(func(): - assert_array(null) \ - .extractv(extr("get_name"), extr("get_value"), extr("get_x")) \ - .contains_exactly([tuple("A", 10, null), tuple("B", "foo", "bar"), tuple("C", 11, 42)])) \ - .is_failed() \ - .has_message(""" - Expecting contains exactly elements: - '' - do contains (in same order) - '[tuple(["A", 10, ]), tuple(["B", "foo", "bar"]), tuple(["C", 11, 42])]' - but could not find elements: - '[tuple(["A", 10, ]), tuple(["B", "foo", "bar"]), tuple(["C", 11, 42])]'""" - .dedent().trim_prefix("\n")) - - -func test_extractv_chained_func() -> void: - var root_a = TestObj.new("root_a", null) - var obj_a = TestObj.new("A", root_a) - var obj_b = TestObj.new("B", root_a) - var obj_c = TestObj.new("C", root_a) - var root_b = TestObj.new("root_b", root_a) - var obj_x = TestObj.new("X", root_b) - var obj_y = TestObj.new("Y", root_b) - - assert_array([obj_a, obj_b, obj_c, obj_x, obj_y])\ - .extractv(extr("get_name"), extr("get_value.get_name"))\ - .contains_exactly([ - tuple("A", "root_a"), - tuple("B", "root_a"), - tuple("C", "root_a"), - tuple("X", "root_b"), - tuple("Y", "root_b") - ]) - - -func test_extract_chained_func() -> void: - var root_a = TestObj.new("root_a", null) - var obj_a = TestObj.new("A", root_a) - var obj_b = TestObj.new("B", root_a) - var obj_c = TestObj.new("C", root_a) - var root_b = TestObj.new("root_b", root_a) - var obj_x = TestObj.new("X", root_b) - var obj_y = TestObj.new("Y", root_b) - - assert_array([obj_a, obj_b, obj_c, obj_x, obj_y])\ - .extract("get_value.get_name")\ - .contains_exactly([ - "root_a", - "root_a", - "root_a", - "root_b", - "root_b", - ]) - - -func test_extractv_max_args() -> void: - assert_array([TestObj.new("A", 10), TestObj.new("B", "foo", "bar"), TestObj.new("C", 11, 42)])\ - .extractv(\ - extr("get_name"), - extr("get_x1"), - extr("get_x2"), - extr("get_x3"), - extr("get_x4"), - extr("get_x5"), - extr("get_x6"), - extr("get_x7"), - extr("get_x8"), - extr("get_x9"))\ - .contains_exactly([ - tuple("A", "x1", "x2", "x3", "x4", "x5", "x6", "x7", "x8", "x9"), - tuple("B", "x1", "x2", "x3", "x4", "x5", "x6", "x7", "x8", "x9"), - tuple("C", "x1", "x2", "x3", "x4", "x5", "x6", "x7", "x8", "x9")]) - - -func test_override_failure_message() -> void: - assert_failure(func(): assert_array([]) \ - .override_failure_message("Custom failure message") \ - .is_null()) \ - .is_failed() \ - .has_message("Custom failure message") - - -# tests if an assert fails the 'is_failure' reflects the failure status -func test_is_failure() -> void: - # initial is false - assert_bool(is_failure()).is_false() - - # checked success assert - assert_array([]).is_empty() - assert_bool(is_failure()).is_false() - - # checked faild assert - assert_failure(func(): assert_array([]).is_not_empty()) \ - .is_failed() - assert_bool(is_failure()).is_true() - - # checked next success assert - assert_array([]).is_empty() - # is true because we have an already failed assert - assert_bool(is_failure()).is_true() - - # should abort here because we had an failing assert - if is_failure(): - return - assert_bool(true).override_failure_message("This line shold never be called").is_false() - - -class ExampleTestClass extends RefCounted: - var _childs := Array() - var _parent = null - - - func add_child(child :ExampleTestClass) -> ExampleTestClass: - _childs.append(child) - child._parent = self - return self - - - func dispose(): - _parent = null - _childs.clear() - - -func test_contains_exactly_stuck() -> void: - var example_a := ExampleTestClass.new()\ - .add_child(ExampleTestClass.new())\ - .add_child(ExampleTestClass.new()) - var example_b := ExampleTestClass.new()\ - .add_child(ExampleTestClass.new())\ - .add_child(ExampleTestClass.new()) - # this test was stuck and ends after a while into an aborted test case - # https://github.com/MikeSchulze/gdUnit3/issues/244 - assert_failure(func(): assert_array([example_a, example_b]).contains_exactly([example_a, example_b, example_a]))\ - .is_failed() - # manual free because of cross references - example_a.dispose() - example_b.dispose() diff --git a/addons/gdUnit4/test/asserts/GdUnitAssertImplTest.gd b/addons/gdUnit4/test/asserts/GdUnitAssertImplTest.gd deleted file mode 100644 index dda3b2d..0000000 --- a/addons/gdUnit4/test/asserts/GdUnitAssertImplTest.gd +++ /dev/null @@ -1,118 +0,0 @@ -# GdUnit generated TestSuite -class_name GdUnitAssertImplTest -extends GdUnitTestSuite - -# TestSuite generated from -const __source = 'res://addons/gdUnit4/src/asserts/GdUnitAssertImpl.gd' - - -func before(): - assert_int(GdUnitAssertions.get_line_number()).is_equal(10) - assert_failure(func(): assert_int(10).is_equal(42)) \ - .is_failed() \ - .has_line(11) \ - .has_message("Expecting:\n '42'\n but was\n '10'") - - -func after(): - assert_failure(func(): assert_int(10).is_equal(42)) \ - .is_failed() \ - .has_line(18) \ - .has_message("Expecting:\n '42'\n but was\n '10'") - - -func before_test(): - assert_failure(func(): assert_int(10).is_equal(42)) \ - .is_failed() \ - .has_line(25) \ - .has_message("Expecting:\n '42'\n but was\n '10'") - - -func after_test(): - assert_failure(func(): assert_int(10).is_equal(42)) \ - .is_failed() \ - .has_line(32) \ - .has_message("Expecting:\n '42'\n but was\n '10'") - - -func test_get_line_number(): - # test to return the current line number for an failure - assert_failure(func(): assert_int(10).is_equal(42)) \ - .is_failed() \ - .has_line(40) \ - .has_message("Expecting:\n '42'\n but was\n '10'") - - -func test_get_line_number_yielded(): - # test to return the current line number after using yield - await get_tree().create_timer(0.100).timeout - assert_failure(func(): assert_int(10).is_equal(42)) \ - .is_failed() \ - .has_line(49) \ - .has_message("Expecting:\n '42'\n but was\n '10'") - - -func test_get_line_number_multiline(): - # test to return the current line number for an failure - # https://github.com/godotengine/godot/issues/43326 - assert_failure(func(): assert_int(10)\ - .is_not_negative()\ - .is_equal(42)) \ - .is_failed() \ - .has_line(58) \ - .has_message("Expecting:\n '42'\n but was\n '10'") - - -func test_get_line_number_verify(): - var obj = mock(RefCounted) - assert_failure(func(): verify(obj, 1).get_reference_count()) \ - .is_failed() \ - .has_line(68) \ - .has_message("Expecting interaction on:\n 'get_reference_count()' 1 time's\nBut found interactions on:\n") - - -func test_is_null(): - assert_that(null).is_null() - - assert_failure(func(): assert_that(Color.RED).is_null()) \ - .is_failed() \ - .has_line(77) \ - .starts_with_message("Expecting: '' but was 'Color(1, 0, 0, 1)'") - - -func test_is_not_null(): - assert_that(Color.RED).is_not_null() - - assert_failure(func(): assert_that(null).is_not_null()) \ - .is_failed() \ - .has_line(86) \ - .has_message("Expecting: not to be ''") - - -func test_is_equal(): - assert_that(Color.RED).is_equal(Color.RED) - assert_that(Plane.PLANE_XY).is_equal(Plane.PLANE_XY) - - assert_failure(func(): assert_that(Color.RED).is_equal(Color.GREEN)) \ - .is_failed() \ - .has_line(96) \ - .has_message("Expecting:\n 'Color(0, 1, 0, 1)'\n but was\n 'Color(1, 0, 0, 1)'") - - -func test_is_not_equal(): - assert_that(Color.RED).is_not_equal(Color.GREEN) - assert_that(Plane.PLANE_XY).is_not_equal(Plane.PLANE_XZ) - - assert_failure(func(): assert_that(Color.RED).is_not_equal(Color.RED)) \ - .is_failed() \ - .has_line(106) \ - .has_message("Expecting:\n 'Color(1, 0, 0, 1)'\n not equal to\n 'Color(1, 0, 0, 1)'") - - -func test_override_failure_message() -> void: - assert_failure(func(): assert_that(Color.RED) \ - .override_failure_message("Custom failure message") \ - .is_null()) \ - .is_failed() \ - .has_line(113) \ - .has_message("Custom failure message") diff --git a/addons/gdUnit4/test/asserts/GdUnitBoolAssertImplTest.gd b/addons/gdUnit4/test/asserts/GdUnitBoolAssertImplTest.gd deleted file mode 100644 index 39b327b..0000000 --- a/addons/gdUnit4/test/asserts/GdUnitBoolAssertImplTest.gd +++ /dev/null @@ -1,117 +0,0 @@ -# GdUnit generated TestSuite -class_name GdUnitBoolAssertImplTest -extends GdUnitTestSuite - -# TestSuite generated from -const __source = 'res://addons/gdUnit4/src/asserts/GdUnitBoolAssertImpl.gd' - - -func test_is_true(): - assert_bool(true).is_true() - - assert_failure(func(): assert_bool(false).is_true())\ - .is_failed() \ - .has_message("Expecting: 'true' but is 'false'") - assert_failure(func(): assert_bool(null).is_true()) \ - .is_failed() \ - .has_message("Expecting: 'true' but is ''") - - -func test_isFalse(): - assert_bool(false).is_false() - - assert_failure(func(): assert_bool(true).is_false()) \ - .is_failed() \ - .has_message("Expecting: 'false' but is 'true'") - assert_failure(func(): assert_bool(null).is_false()) \ - .is_failed() \ - .has_message("Expecting: 'false' but is ''") - - -func test_is_null(): - assert_bool(null).is_null() - # should fail because the current is not null - assert_failure(func(): assert_bool(true).is_null())\ - .is_failed() \ - .starts_with_message("Expecting: '' but was 'true'") - - -func test_is_not_null(): - assert_bool(true).is_not_null() - # should fail because the current is null - assert_failure(func(): assert_bool(null).is_not_null())\ - .is_failed() \ - .has_message("Expecting: not to be ''") - - -func test_is_equal(): - assert_bool(true).is_equal(true) - assert_bool(false).is_equal(false) - - assert_failure(func(): assert_bool(true).is_equal(false)) \ - .is_failed() \ - .has_message("Expecting:\n 'false'\n but was\n 'true'") - assert_failure(func(): assert_bool(null).is_equal(false)) \ - .is_failed() \ - .has_message("Expecting:\n 'false'\n but was\n ''") - - -func test_is_not_equal(): - assert_bool(null).is_not_equal(false) - assert_bool(true).is_not_equal(false) - assert_bool(false).is_not_equal(true) - - assert_failure(func(): assert_bool(true).is_not_equal(true)) \ - .is_failed() \ - .has_message("Expecting:\n 'true'\n not equal to\n 'true'") - - -func test_fluent(): - assert_bool(true).is_true().is_equal(true).is_not_equal(false) - - -func test_must_fail_has_invlalid_type(): - assert_failure(func(): assert_bool(1)) \ - .is_failed() \ - .has_message("GdUnitBoolAssert inital error, unexpected type ") - assert_failure(func(): assert_bool(3.13)) \ - .is_failed() \ - .has_message("GdUnitBoolAssert inital error, unexpected type ") - assert_failure(func(): assert_bool("foo")) \ - .is_failed() \ - .has_message("GdUnitBoolAssert inital error, unexpected type ") - assert_failure(func(): assert_bool(Resource.new())) \ - .is_failed() \ - .has_message("GdUnitBoolAssert inital error, unexpected type ") - - -func test_override_failure_message() -> void: - assert_failure(func(): assert_bool(true) \ - .override_failure_message("Custom failure message") \ - .is_null()) \ - .is_failed() \ - .has_message("Custom failure message") - - -# tests if an assert fails the 'is_failure' reflects the failure status -func test_is_failure() -> void: - # initial is false - assert_bool(is_failure()).is_false() - - # checked success assert - assert_bool(true).is_true() - assert_bool(is_failure()).is_false() - - # checked faild assert - assert_failure(func(): assert_bool(true).is_false()).is_failed() - assert_bool(is_failure()).is_true() - - # checked next success assert - assert_bool(true).is_true() - # is true because we have an already failed assert - assert_bool(is_failure()).is_true() - - # should abort here because we had an failing assert - if is_failure(): - return - assert_bool(true).override_failure_message("This line shold never be called").is_false() diff --git a/addons/gdUnit4/test/asserts/GdUnitDictionaryAssertImplTest.gd b/addons/gdUnit4/test/asserts/GdUnitDictionaryAssertImplTest.gd deleted file mode 100644 index 7d66191..0000000 --- a/addons/gdUnit4/test/asserts/GdUnitDictionaryAssertImplTest.gd +++ /dev/null @@ -1,470 +0,0 @@ -# GdUnit generated TestSuite -extends GdUnitTestSuite - -# TestSuite generated from -const __source = 'res://addons/gdUnit4/src/asserts/GdUnitDictionaryAssertImpl.gd' - - -func test_must_fail_has_invlalid_type() -> void: - assert_failure(func(): assert_dict(1)) \ - .is_failed() \ - .has_message("GdUnitDictionaryAssert inital error, unexpected type ") - assert_failure(func(): assert_dict(1.3)) \ - .is_failed() \ - .has_message("GdUnitDictionaryAssert inital error, unexpected type ") - assert_failure(func(): assert_dict(true)) \ - .is_failed() \ - .has_message("GdUnitDictionaryAssert inital error, unexpected type ") - assert_failure(func(): assert_dict("abc")) \ - .is_failed() \ - .has_message("GdUnitDictionaryAssert inital error, unexpected type ") - assert_failure(func(): assert_dict([])) \ - .is_failed() \ - .has_message("GdUnitDictionaryAssert inital error, unexpected type ") - assert_failure(func(): assert_dict(Resource.new())) \ - .is_failed() \ - .has_message("GdUnitDictionaryAssert inital error, unexpected type ") - - -func test_is_null() -> void: - assert_dict(null).is_null() - - assert_failure(func(): assert_dict({}).is_null()) \ - .is_failed() \ - .has_message("Expecting: '' but was '{ }'") - - -func test_is_not_null() -> void: - assert_dict({}).is_not_null() - - assert_failure(func(): assert_dict(null).is_not_null()) \ - .is_failed() \ - .has_message("Expecting: not to be ''") - - -func test_is_equal() -> void: - assert_dict({}).is_equal({}) - assert_dict({1:1}).is_equal({1:1}) - assert_dict({1:1, "key_a": "value_a"}).is_equal({1:1, "key_a": "value_a" }) - # different order is also equals - assert_dict({"key_a": "value_a", 1:1}).is_equal({1:1, "key_a": "value_a" }) - - # should fail - assert_failure(func(): assert_dict(null).is_equal({1:1})) \ - .is_failed() \ - .has_message(""" - Expecting: - '{ - 1: 1 - }' - but was - ''""" - .dedent() - .trim_prefix("\n") - ) - assert_failure(func(): assert_dict({}).is_equal({1:1})).is_failed() - assert_failure(func(): assert_dict({1:1}).is_equal({})).is_failed() - assert_failure(func(): assert_dict({1:1}).is_equal({1:2})).is_failed() - assert_failure(func(): assert_dict({1:2}).is_equal({1:1})).is_failed() - assert_failure(func(): assert_dict({1:1}).is_equal({1:1, "key_a": "value_a"})).is_failed() - assert_failure(func(): assert_dict({1:1, "key_a": "value_a"}).is_equal({1:1})).is_failed() - assert_failure(func(): assert_dict({1:1, "key_a": "value_a"}).is_equal({1:1, "key_b": "value_b"})).is_failed() - assert_failure(func(): assert_dict({1:1, "key_b": "value_b"}).is_equal({1:1, "key_a": "value_a"})).is_failed() - assert_failure(func(): assert_dict({"key_a": "value_a", 1:1}).is_equal({1:1, "key_b": "value_b"})).is_failed() - assert_failure(func(): assert_dict({1:1, "key_b": "value_b"}).is_equal({"key_a": "value_a", 1:1})) \ - .is_failed() \ - .has_message(""" - Expecting: - '{ - 1: 1, - "key_a": "value_a" - }' - but was - '{ - 1: 1, - "key_ab": "value_ab" - }'""" - .dedent() - .trim_prefix("\n") - ) - - -func test_is_not_equal() -> void: - assert_dict(null).is_not_equal({}) - assert_dict({}).is_not_equal(null) - assert_dict({}).is_not_equal({1:1}) - assert_dict({1:1}).is_not_equal({}) - assert_dict({1:1}).is_not_equal({1:2}) - assert_dict({2:1}).is_not_equal({1:1}) - assert_dict({1:1}).is_not_equal({1:1, "key_a": "value_a"}) - assert_dict({1:1, "key_a": "value_a"}).is_not_equal({1:1}) - assert_dict({1:1, "key_a": "value_a"}).is_not_equal({1:1, "key_b": "value_b"}) - - # should fail - assert_failure(func(): assert_dict({}).is_not_equal({})).is_failed() - assert_failure(func(): assert_dict({1:1}).is_not_equal({1:1})).is_failed() - assert_failure(func(): assert_dict({1:1, "key_a": "value_a"}).is_not_equal({1:1, "key_a": "value_a"})).is_failed() - assert_failure(func(): assert_dict({"key_a": "value_a", 1:1}).is_not_equal({1:1, "key_a": "value_a"})) \ - .is_failed() \ - .has_message(""" - Expecting: - '{ - 1: 1, - "key_a": "value_a" - }' - not equal to - '{ - 1: 1, - "key_a": "value_a" - }'""" - .dedent() - .trim_prefix("\n") - ) - - -func test_is_same() -> void: - var dict_a := {} - var dict_b := {"key"="value", "key2"="value"} - var dict_c := {1:1, "key_a": "value_a"} - var dict_d := {"key_a": "value_a", 1:1} - assert_dict(dict_a).is_same(dict_a) - assert_dict(dict_b).is_same(dict_b) - assert_dict(dict_c).is_same(dict_c) - assert_dict(dict_d).is_same(dict_d) - - assert_failure( func(): assert_dict({}).is_same({})) \ - .is_failed()\ - .has_message(""" - Expecting: - '{ }' - to refer to the same object - '{ }'""" - .dedent() - .trim_prefix("\n") - ) - assert_failure( func(): assert_dict({1:1, "key_a": "value_a"}).is_same({1:1, "key_a": "value_a" })) \ - .is_failed()\ - .has_message(""" - Expecting: - '{ - 1: 1, - "key_a": "value_a" - }' - to refer to the same object - '{ - 1: 1, - "key_a": "value_a" - }'""" - .dedent() - .trim_prefix("\n") - ) - - -func test_is_not_same() -> void: - var dict_a := {} - var dict_b := {} - var dict_c := {1:1, "key_a": "value_a"} - var dict_d := {1:1, "key_a": "value_a"} - assert_dict(dict_a).is_not_same(dict_b).is_not_same(dict_c).is_not_same(dict_d) - assert_dict(dict_b).is_not_same(dict_a).is_not_same(dict_c).is_not_same(dict_d) - assert_dict(dict_c).is_not_same(dict_a).is_not_same(dict_b).is_not_same(dict_d) - assert_dict(dict_d).is_not_same(dict_a).is_not_same(dict_b).is_not_same(dict_c) - - assert_failure( func(): assert_dict(dict_a).is_not_same(dict_a)) \ - .is_failed()\ - .has_message(""" - Expecting not same: - '{ }'""" - .dedent() - .trim_prefix("\n") - ) - assert_failure( func(): assert_dict(dict_c).is_not_same(dict_c)) \ - .is_failed()\ - .has_message(""" - Expecting not same: - '{ - 1: 1, - "key_a": "value_a" - }'""" - .dedent() - .trim_prefix("\n") - ) - - -func test_is_empty() -> void: - assert_dict({}).is_empty() - - assert_failure(func(): assert_dict(null).is_empty()) \ - .is_failed() \ - .has_message("Expecting:\n" - + " must be empty but was\n" - + " ''") - assert_failure(func(): assert_dict({1:1}).is_empty()) \ - .is_failed() \ - .has_message(""" - Expecting: - must be empty but was - '{ - 1: 1 - }'""" - .dedent() - .trim_prefix("\n") - ) - - -func test_is_not_empty() -> void: - assert_dict({1:1}).is_not_empty() - assert_dict({1:1, "key_a": "value_a"}).is_not_empty() - - assert_failure(func(): assert_dict(null).is_not_empty()) \ - .is_failed() \ - .has_message("Expecting:\n" - + " must not be empty") - assert_failure(func(): assert_dict({}).is_not_empty()).is_failed() - - -func test_has_size() -> void: - assert_dict({}).has_size(0) - assert_dict({1:1}).has_size(1) - assert_dict({1:1, 2:1}).has_size(2) - assert_dict({1:1, 2:1, 3:1}).has_size(3) - - assert_failure(func(): assert_dict(null).has_size(0))\ - .is_failed() \ - .has_message("Expecting: not to be ''") - assert_failure(func(): assert_dict(null).has_size(1)).is_failed() - assert_failure(func(): assert_dict({}).has_size(1)).is_failed() - assert_failure(func(): assert_dict({1:1}).has_size(0)).is_failed() - assert_failure(func(): assert_dict({1:1}).has_size(2)) \ - .is_failed() \ - .has_message(""" - Expecting size: - '2' - but was - '1'""" - .dedent() - .trim_prefix("\n") - ) - -class TestObj: - var _name :String - var _value :int - - func _init(name :String = "Foo", value :int = 0): - _name = name - _value = value - - func _to_string() -> String: - return "class:%s:%d" % [_name, _value] - - -func test_contains_keys() -> void: - var key_a := TestObj.new() - var key_b := TestObj.new() - var key_c := TestObj.new() - var key_d := TestObj.new("D") - - assert_dict({1:1, 2:2, 3:3}).contains_keys([2]) - assert_dict({1:1, 2:2, "key_a": "value_a"}).contains_keys([2, "key_a"]) - assert_dict({key_a:1, key_b:2, key_c:3}).contains_keys([key_a, key_b]) - assert_dict({key_a:1, key_c:3 }).contains_keys([key_b]) - assert_dict({key_a:1, 3:3}).contains_keys([key_a, key_b]) - - - assert_failure(func(): assert_dict({1:1, 3:3}).contains_keys([2])) \ - .is_failed() \ - .has_message(""" - Expecting contains keys: - '[1, 3]' - to contains: - '[2]' - but can't find key's: - '[2]'""" - .dedent() - .trim_prefix("\n") - ) - assert_failure(func(): assert_dict({1:1, 3:3}).contains_keys([1, 4])) \ - .is_failed() \ - .has_message(""" - Expecting contains keys: - '[1, 3]' - to contains: - '[1, 4]' - but can't find key's: - '[4]'""" - .dedent() - .trim_prefix("\n") - ) - assert_failure(func(): assert_dict(null).contains_keys([1, 4])) \ - .is_failed() \ - .has_message("Expecting: not to be ''") - assert_failure(func(): assert_dict({key_a:1, 3:3}).contains_keys([key_a, key_d])) \ - .is_failed() \ - .has_message(""" - Expecting contains keys: - '[class:Foo:0, 3]' - to contains: - '[class:Foo:0, class:D:0]' - but can't find key's: - '[class:D:0]'""" - .dedent().trim_prefix("\n")) - - -func test_contains_key_value() -> void: - assert_dict({1:1}).contains_key_value(1, 1) - assert_dict({1:1, 2:2, 3:3}).contains_key_value(3, 3).contains_key_value(1, 1) - - assert_failure(func(): assert_dict({1:1}).contains_key_value(1, 2)) \ - .is_failed() \ - .has_message(""" - Expecting contains key and value: - '1' : '2' - but contains - '1' : '1'""" - .dedent() - .trim_prefix("\n") - ) - assert_failure(func(): assert_dict(null).contains_key_value(1, 2)) \ - .is_failed() \ - .has_message("Expecting: not to be ''") - - -func test_not_contains_keys() -> void: - assert_dict({}).not_contains_keys([2]) - assert_dict({1:1, 3:3}).not_contains_keys([2]) - assert_dict({1:1, 3:3}).not_contains_keys([2, 4]) - - assert_failure(func(): assert_dict({1:1, 2:2, 3:3}).not_contains_keys([2, 4])) \ - .is_failed() \ - .has_message(""" - Expecting NOT contains keys: - '[1, 2, 3]' - do not contains: - '[2, 4]' - but contains key's: - '[2]'""" - .dedent() - .trim_prefix("\n") - ) - assert_failure(func(): assert_dict({1:1, 2:2, 3:3}).not_contains_keys([1, 2, 3, 4])) \ - .is_failed() \ - .has_message(""" - Expecting NOT contains keys: - '[1, 2, 3]' - do not contains: - '[1, 2, 3, 4]' - but contains key's: - '[1, 2, 3]'""" - .dedent() - .trim_prefix("\n") - ) - assert_failure(func(): assert_dict(null).not_contains_keys([1, 4])) \ - .is_failed() \ - .has_message("Expecting: not to be ''") - - -func test_contains_same_keys() -> void: - var key_a := TestObj.new() - var key_b := TestObj.new() - var key_c := TestObj.new() - - assert_dict({1:1, 2:2, 3:3}).contains_same_keys([2]) - assert_dict({1:1, 2:2, "key_a": "value_a"}).contains_same_keys([2, "key_a"]) - assert_dict({key_a:1, key_b:2, 3:3}).contains_same_keys([key_b]) - assert_dict({key_a:1, key_b:2, 3:3}).contains_same_keys([key_a, key_b]) - - assert_failure(func(): assert_dict({key_a:1, key_c:3 }).contains_same_keys([key_a, key_b])) \ - .is_failed() \ - .has_message(""" - Expecting contains SAME keys: - '[class:Foo:0, class:Foo:0]' - to contains: - '[class:Foo:0, class:Foo:0]' - but can't find key's: - '[class:Foo:0]'""" - .dedent().trim_prefix("\n") - ) - - -func test_contains_same_key_value() -> void: - var key_a := TestObj.new("A") - var key_b := TestObj.new("B") - var key_c := TestObj.new("C") - var key_d := TestObj.new("A") - - assert_dict({key_a:1, key_b:2, key_c:3})\ - .contains_same_key_value(key_a, 1)\ - .contains_same_key_value(key_b, 2) - - assert_failure(func(): assert_dict({key_a:1, key_b:2, key_c:3}).contains_same_key_value(key_a, 2)) \ - .is_failed() \ - .has_message(""" - Expecting contains SAME key and value: - : '2' - but contains - : '1'""" - .dedent().trim_prefix("\n") - ) - assert_failure(func(): assert_dict({key_a:1, key_b:2, key_c:3}).contains_same_key_value(key_d, 1)) \ - .is_failed() \ - .has_message(""" - Expecting contains SAME key and value: - : '1' - but contains - : '[class:A:0, class:B:0, class:C:0]'""" - .dedent().trim_prefix("\n") - ) - - -func test_not_contains_same_keys() -> void: - var key_a := TestObj.new("A") - var key_b := TestObj.new("B") - var key_c := TestObj.new("C") - var key_d := TestObj.new("A") - - assert_dict({}).not_contains_same_keys([key_a]) - assert_dict({key_a:1, key_b:2}).not_contains_same_keys([key_c, key_d]) - - assert_failure(func(): assert_dict({key_a:1, key_b:2}).not_contains_same_keys([key_c, key_b])) \ - .is_failed() \ - .has_message(""" - Expecting NOT contains SAME keys - '[class:A:0, class:B:0]' - do not contains: - '[class:C:0, class:B:0]' - but contains key's: - '[class:B:0]'""" - .dedent().trim_prefix("\n") - ) - - -func test_override_failure_message() -> void: - assert_failure(func(): assert_dict({1:1}) \ - .override_failure_message("Custom failure message") \ - .is_null()) \ - .is_failed() \ - .has_message("Custom failure message") - - -# tests if an assert fails the 'is_failure' reflects the failure status -func test_is_failure() -> void: - # initial is false - assert_bool(is_failure()).is_false() - - # checked success assert - assert_dict({}).is_empty() - assert_bool(is_failure()).is_false() - - # checked faild assert - assert_failure(func(): assert_dict({}).is_not_empty()).is_failed() - assert_bool(is_failure()).is_true() - - # checked next success assert - assert_dict({}).is_empty() - # is true because we have an already failed assert - assert_bool(is_failure()).is_true() - - # should abort here because we had an failing assert - if is_failure(): - return - assert_bool(true).override_failure_message("This line shold never be called").is_false() diff --git a/addons/gdUnit4/test/asserts/GdUnitFailureAssertImplTest.gd b/addons/gdUnit4/test/asserts/GdUnitFailureAssertImplTest.gd deleted file mode 100644 index 3fbde29..0000000 --- a/addons/gdUnit4/test/asserts/GdUnitFailureAssertImplTest.gd +++ /dev/null @@ -1,79 +0,0 @@ -# GdUnit generated TestSuite -class_name GdUnitFailureAssertImplTest -extends GdUnitTestSuite -@warning_ignore('unused_parameter') -@warning_ignore('return_value_discarded') - -# TestSuite generated from -const __source = 'res://addons/gdUnit4/src/asserts/GdUnitFailureAssertImpl.gd' - - -func last_assert() -> Variant: - return GdUnitThreadManager.get_current_context().get_assert() - - -func test_has_line() -> void: - assert_failure(func(): assert_bool(true).is_false()) \ - .is_failed() \ - .has_line(16) - - -func test_has_message() -> void: - assert_failure(func(): assert_bool(true).is_true()) \ - .is_success() - assert_failure(func(): assert_bool(true).is_false()) \ - .is_failed()\ - .has_message("Expecting: 'false' but is 'true'") - - -func test_starts_with_message() -> void: - assert_failure(func(): assert_bool(true).is_false()) \ - .is_failed()\ - .starts_with_message("Expecting: 'false' bu") - - -func test_assert_failure_on_invalid_cb() -> void: - assert_failure(func(): prints())\ - .is_failed()\ - .has_message("Invalid Callable! It must be a callable of 'GdUnitAssert'") - - -@warning_ignore("unused_parameter") -func test_assert_failure_on_assert(test_name :String, assert_type, value, test_parameters = [ - ["GdUnitBoolAssert", GdUnitBoolAssert, true], - ["GdUnitStringAssert", GdUnitStringAssert, "value"], - ["GdUnitIntAssert", GdUnitIntAssert, 42], - ["GdUnitFloatAssert", GdUnitFloatAssert, 42.0], - ["GdUnitObjectAssert", GdUnitObjectAssert, RefCounted.new()], - ["GdUnitVectorAssert", GdUnitVectorAssert, Vector2.ZERO], - ["GdUnitVectorAssert", GdUnitVectorAssert, Vector3.ZERO], - ["GdUnitArrayAssert", GdUnitArrayAssert, Array()], - ["GdUnitDictionaryAssert", GdUnitDictionaryAssert, {}], -]) -> void: - var instance := assert_failure(func(): assert_that(value)) - assert_object(last_assert()).is_instanceof(assert_type) - assert_object(instance).is_instanceof(GdUnitFailureAssert) - - -func test_assert_failure_on_assert_file() -> void: - var instance := assert_failure(func(): assert_file("res://foo.gd")) - assert_object(last_assert()).is_instanceof(GdUnitFileAssert) - assert_object(instance).is_instanceof(GdUnitFailureAssert) - - -func test_assert_failure_on_assert_func() -> void: - var instance := assert_failure(func(): assert_func(RefCounted.new(), "_to_string")) - assert_object(last_assert()).is_instanceof(GdUnitFuncAssert) - assert_object(instance).is_instanceof(GdUnitFailureAssert) - - -func test_assert_failure_on_assert_signal() -> void: - var instance := assert_failure(func(): assert_signal(null)) - assert_object(last_assert()).is_instanceof(GdUnitSignalAssert) - assert_object(instance).is_instanceof(GdUnitFailureAssert) - - -func test_assert_failure_on_assert_result() -> void: - var instance := assert_failure(func(): assert_result(null)) - assert_object(last_assert()).is_instanceof(GdUnitResultAssert) - assert_object(instance).is_instanceof(GdUnitFailureAssert) diff --git a/addons/gdUnit4/test/asserts/GdUnitFloatAssertImplTest.gd b/addons/gdUnit4/test/asserts/GdUnitFloatAssertImplTest.gd deleted file mode 100644 index 4a299c0..0000000 --- a/addons/gdUnit4/test/asserts/GdUnitFloatAssertImplTest.gd +++ /dev/null @@ -1,246 +0,0 @@ -# GdUnit generated TestSuite -class_name GdUnitFloatAssertImplTest -extends GdUnitTestSuite - -# TestSuite generated from -const __source = 'res://addons/gdUnit4/src/asserts/GdUnitFloatAssertImpl.gd' - - -func test_is_null(): - assert_float(null).is_null() - - assert_failure(func(): assert_float(23.2).is_null()) \ - .is_failed() \ - .starts_with_message("Expecting: '' but was '23.200000'") - - -func test_is_not_null(): - assert_float(23.2).is_not_null() - - assert_failure(func(): assert_float(null).is_not_null()) \ - .is_failed() \ - .has_message("Expecting: not to be ''") - - -func test_is_equal(): - assert_float(23.2).is_equal(23.2) - - assert_failure(func(): assert_float(23.2).is_equal(23.4)) \ - .is_failed() \ - .has_message("Expecting:\n '23.400000'\n but was\n '23.200000'") - assert_failure(func(): assert_float(null).is_equal(23.4)) \ - .is_failed() \ - .has_message("Expecting:\n '23.400000'\n but was\n ''") - - -func test_is_not_equal(): - assert_float(null).is_not_equal(23.4) - assert_float(23.2).is_not_equal(23.4) - - assert_failure(func(): assert_float(23.2).is_not_equal(23.2)) \ - .is_failed() \ - .has_message("Expecting:\n '23.200000'\n not equal to\n '23.200000'") - - -func test_is_equal_approx() -> void: - assert_float(23.2).is_equal_approx(23.2, 0.01) - assert_float(23.19).is_equal_approx(23.2, 0.01) - assert_float(23.20).is_equal_approx(23.2, 0.01) - assert_float(23.21).is_equal_approx(23.2, 0.01) - - assert_failure(func(): assert_float(23.18).is_equal_approx(23.2, 0.01)) \ - .is_failed() \ - .has_message("Expecting:\n '23.180000'\n in range between\n '23.190000' <> '23.210000'") - assert_failure(func(): assert_float(23.22).is_equal_approx(23.2, 0.01)) \ - .is_failed() \ - .has_message("Expecting:\n '23.220000'\n in range between\n '23.190000' <> '23.210000'") - assert_failure(func(): assert_float(null).is_equal_approx(23.2, 0.01)) \ - .is_failed() \ - .has_message("Expecting:\n ''\n in range between\n '23.190000' <> '23.210000'") - - - -func test_is_less_(): - assert_failure(func(): assert_float(23.2).is_less(23.2)) \ - .is_failed() \ - .has_message("Expecting to be less than:\n '23.200000' but was '23.200000'") - -func test_is_less(): - assert_float(23.2).is_less(23.4) - assert_float(23.2).is_less(26.0) - - assert_failure(func(): assert_float(23.2).is_less(23.2)) \ - .is_failed() \ - .has_message("Expecting to be less than:\n '23.200000' but was '23.200000'") - assert_failure(func(): assert_float(null).is_less(23.2)) \ - .is_failed() \ - .has_message("Expecting to be less than:\n '23.200000' but was ''") - - -func test_is_less_equal(): - assert_float(23.2).is_less_equal(23.4) - assert_float(23.2).is_less_equal(23.2) - - assert_failure(func(): assert_float(23.2).is_less_equal(23.1)) \ - .is_failed() \ - .has_message("Expecting to be less than or equal:\n '23.100000' but was '23.200000'") - assert_failure(func(): assert_float(null).is_less_equal(23.1)) \ - .is_failed() \ - .has_message("Expecting to be less than or equal:\n '23.100000' but was ''") - - -func test_is_greater(): - assert_float(23.2).is_greater(23.0) - assert_float(23.4).is_greater(22.1) - - assert_failure(func(): assert_float(23.2).is_greater(23.2)) \ - .is_failed() \ - .has_message("Expecting to be greater than:\n '23.200000' but was '23.200000'") - assert_failure(func(): assert_float(null).is_greater(23.2)) \ - .is_failed() \ - .has_message("Expecting to be greater than:\n '23.200000' but was ''") - - -func test_is_greater_equal(): - assert_float(23.2).is_greater_equal(20.2) - assert_float(23.2).is_greater_equal(23.2) - - assert_failure(func(): assert_float(23.2).is_greater_equal(23.3)) \ - .is_failed() \ - .has_message("Expecting to be greater than or equal:\n '23.300000' but was '23.200000'") - assert_failure(func(): assert_float(null).is_greater_equal(23.3)) \ - .is_failed() \ - .has_message("Expecting to be greater than or equal:\n '23.300000' but was ''") - - -func test_is_negative(): - assert_float(-13.2).is_negative() - - assert_failure(func(): assert_float(13.2).is_negative()) \ - .is_failed() \ - .has_message("Expecting:\n '13.200000' be negative") - assert_failure(func(): assert_float(null).is_negative()) \ - .is_failed() \ - .has_message("Expecting:\n '' be negative") - - -func test_is_not_negative(): - assert_float(13.2).is_not_negative() - - assert_failure(func(): assert_float(-13.2).is_not_negative()) \ - .is_failed() \ - .has_message("Expecting:\n '-13.200000' be not negative") - assert_failure(func(): assert_float(null).is_not_negative()) \ - .is_failed() \ - .has_message("Expecting:\n '' be not negative") - - -func test_is_zero(): - assert_float(0.0).is_zero() - - assert_failure(func(): assert_float(0.00001).is_zero()) \ - .is_failed() \ - .has_message("Expecting:\n equal to 0 but is '0.000010'") - assert_failure(func(): assert_float(null).is_zero()) \ - .is_failed() \ - .has_message("Expecting:\n equal to 0 but is ''") - - -func test_is_not_zero(): - assert_float(0.00001).is_not_zero() - - assert_failure(func(): assert_float(0.000001).is_not_zero()) \ - .is_failed() \ - .has_message("Expecting:\n not equal to 0") - assert_failure(func(): assert_float(null).is_not_zero()) \ - .is_failed() \ - .has_message("Expecting:\n not equal to 0") - - -func test_is_in(): - assert_float(5.2).is_in([5.1, 5.2, 5.3, 5.4]) - # this assertion fail because 5.5 is not in [5.1, 5.2, 5.3, 5.4] - assert_failure(func(): assert_float(5.5).is_in([5.1, 5.2, 5.3, 5.4])) \ - .is_failed() \ - .has_message("Expecting:\n '5.500000'\n is in\n '[5.1, 5.2, 5.3, 5.4]'") - assert_failure(func(): assert_float(null).is_in([5.1, 5.2, 5.3, 5.4])) \ - .is_failed() \ - .has_message("Expecting:\n ''\n is in\n '[5.1, 5.2, 5.3, 5.4]'") - - -func test_is_not_in(): - assert_float(null).is_not_in([5.1, 5.3, 5.4]) - assert_float(5.2).is_not_in([5.1, 5.3, 5.4]) - # this assertion fail because 5.2 is not in [5.1, 5.2, 5.3, 5.4] - assert_failure(func(): assert_float(5.2).is_not_in([5.1, 5.2, 5.3, 5.4])) \ - .is_failed() \ - .has_message("Expecting:\n '5.200000'\n is not in\n '[5.1, 5.2, 5.3, 5.4]'") - - -func test_is_between(): - assert_float(-20.0).is_between(-20.0, 20.9) - assert_float(10.0).is_between(-20.0, 20.9) - assert_float(20.9).is_between(-20.0, 20.9) - - -func test_is_between_must_fail(): - assert_failure(func(): assert_float(-10.0).is_between(-9.0, 0.0)) \ - .is_failed() \ - .has_message("Expecting:\n '-10.000000'\n in range between\n '-9.000000' <> '0.000000'") - assert_failure(func(): assert_float(0.0).is_between(1, 10)) \ - .is_failed() \ - .has_message("Expecting:\n '0.000000'\n in range between\n '1.000000' <> '10.000000'") - assert_failure(func(): assert_float(10.0).is_between(11, 21)) \ - .is_failed() \ - .has_message("Expecting:\n '10.000000'\n in range between\n '11.000000' <> '21.000000'") - assert_failure(func(): assert_float(null).is_between(11, 21)) \ - .is_failed() \ - .has_message("Expecting:\n ''\n in range between\n '11.000000' <> '21.000000'") - - -func test_must_fail_has_invlalid_type(): - assert_failure(func(): assert_float(1)) \ - .is_failed() \ - .has_message("GdUnitFloatAssert inital error, unexpected type ") - assert_failure(func(): assert_float(true)) \ - .is_failed() \ - .has_message("GdUnitFloatAssert inital error, unexpected type ") - assert_failure(func(): assert_float("foo")) \ - .is_failed() \ - .has_message("GdUnitFloatAssert inital error, unexpected type ") - assert_failure(func(): assert_float(Resource.new())) \ - .is_failed() \ - .has_message("GdUnitFloatAssert inital error, unexpected type ") - - -func test_override_failure_message() -> void: - assert_failure(func(): assert_float(3.14) \ - .override_failure_message("Custom failure message") \ - .is_null()) \ - .is_failed() \ - .has_message("Custom failure message") - - -# tests if an assert fails the 'is_failure' reflects the failure status -func test_is_failure() -> void: - # initial is false - assert_bool(is_failure()).is_false() - - # checked success assert - assert_float(0.0).is_zero() - assert_bool(is_failure()).is_false() - - # checked faild assert - assert_failure(func(): assert_float(1.0).is_zero()) \ - .is_failed() - assert_bool(is_failure()).is_true() - - # checked next success assert - assert_float(0.0).is_zero() - # is true because we have an already failed assert - assert_bool(is_failure()).is_true() - - # should abort here because we had an failing assert - if is_failure(): - return - assert_bool(true).override_failure_message("This line shold never be called").is_false() diff --git a/addons/gdUnit4/test/asserts/GdUnitFuncAssertImplTest.gd b/addons/gdUnit4/test/asserts/GdUnitFuncAssertImplTest.gd deleted file mode 100644 index 8678a6f..0000000 --- a/addons/gdUnit4/test/asserts/GdUnitFuncAssertImplTest.gd +++ /dev/null @@ -1,368 +0,0 @@ -# GdUnit generated TestSuite -class_name GdUnitFuncAssertImplTest -extends GdUnitTestSuite -@warning_ignore("unused_parameter") - - -# TestSuite generated from -const __source = 'res://addons/gdUnit4/src/asserts/GdUnitFuncAssertImpl.gd' -const GdUnitTools = preload("res://addons/gdUnit4/src/core/GdUnitTools.gd") - - -# we need to skip await fail test because of an bug in Godot 4.0 stable -func is_skip_fail_await() -> bool: - return Engine.get_version_info().hex < 0x40002 - - -class TestValueProvider: - var _max_iterations :int - var _current_itteration := 0 - - func _init(iterations := 0): - _max_iterations = iterations - - func bool_value() -> bool: - _current_itteration += 1 - if _current_itteration == _max_iterations: - return true - return false - - func int_value() -> int: - return 0 - - func float_value() -> float: - return 0.0 - - func string_value() -> String: - return "value" - - func object_value() -> Object: - return Resource.new() - - func array_value() -> Array: - return [] - - func dict_value() -> Dictionary: - return {} - - func vec2_value() -> Vector2: - return Vector2.ONE - - func vec3_value() -> Vector3: - return Vector3.ONE - - func no_value() -> void: - pass - - func unknown_value(): - return Vector3.ONE - - -class ValueProvidersWithArguments: - - func is_type(_type :int) -> bool: - return true - - func get_index(_instance :Object, _name :String) -> int: - return 1 - - func get_index2(_instance :Object, _name :String, _recursive := false) -> int: - return 1 - - -class TestIterativeValueProvider: - var _max_iterations :int - var _current_itteration := 0 - var _inital_value - var _final_value - - func _init(inital_value, iterations :int, final_value): - _max_iterations = iterations - _inital_value = inital_value - _final_value = final_value - - func bool_value() -> bool: - _current_itteration += 1 - if _current_itteration >= _max_iterations: - return _final_value - return _inital_value - - func int_value() -> int: - _current_itteration += 1 - if _current_itteration >= _max_iterations: - return _final_value - return _inital_value - - func obj_value() -> Variant: - _current_itteration += 1 - if _current_itteration >= _max_iterations: - return _final_value - return _inital_value - - func has_type(type :int, _recursive :bool = true) -> int: - _current_itteration += 1 - #await Engine.get_main_loop().idle_frame - if type == _current_itteration: - return _final_value - return _inital_value - - func await_value() -> int: - _current_itteration += 1 - await Engine.get_main_loop().process_frame - prints("yielded_value", _current_itteration) - if _current_itteration >= _max_iterations: - return _final_value - return _inital_value - - func reset() -> void: - _current_itteration = 0 - - func iteration() -> int: - return _current_itteration - - -@warning_ignore("unused_parameter") -func test_is_null(timeout = 2000) -> void: - var value_provider := TestIterativeValueProvider.new(RefCounted.new(), 5, null) - # without default timeout od 2000ms - assert_func(value_provider, "obj_value").is_not_null() - await assert_func(value_provider, "obj_value").is_null() - assert_int(value_provider.iteration()).is_equal(5) - - # with a timeout of 5s - value_provider.reset() - assert_func(value_provider, "obj_value").is_not_null() - await assert_func(value_provider, "obj_value").wait_until(5000).is_null() - assert_int(value_provider.iteration()).is_equal(5) - - # failure case - if is_skip_fail_await(): - return - value_provider = TestIterativeValueProvider.new(RefCounted.new(), 1, RefCounted.new()) - ( - await assert_failure_await(func(): await assert_func(value_provider, "obj_value", []).wait_until(100).is_null()) - ).has_message("Expected: is null but timed out after 100ms") - - -@warning_ignore("unused_parameter") -func test_is_not_null(timeout = 2000) -> void: - var value_provider := TestIterativeValueProvider.new(null, 5, RefCounted.new()) - # without default timeout od 2000ms - assert_func(value_provider, "obj_value").is_null() - await assert_func(value_provider, "obj_value").is_not_null() - assert_int(value_provider.iteration()).is_equal(5) - - # with a timeout of 5s - value_provider.reset() - assert_func(value_provider, "obj_value").is_null() - await assert_func(value_provider, "obj_value").wait_until(5000).is_not_null() - assert_int(value_provider.iteration()).is_equal(5) - - # failure case - value_provider = TestIterativeValueProvider.new(null, 1, null) - if is_skip_fail_await(): - return - ( - await assert_failure_await(func(): await assert_func(value_provider, "obj_value", []).wait_until(100).is_not_null()) - ).has_message("Expected: is not null but timed out after 100ms") - - -@warning_ignore("unused_parameter") -func test_is_true(timeout = 2000) -> void: - var value_provider := TestIterativeValueProvider.new(false, 5, true) - # without default timeout od 2000ms - assert_func(value_provider, "bool_value").is_false() - await assert_func(value_provider, "bool_value").is_true() - assert_int(value_provider.iteration()).is_equal(5) - - # with a timeout of 5s - value_provider.reset() - assert_func(value_provider, "bool_value").is_false() - await assert_func(value_provider, "bool_value").wait_until(5000).is_true() - assert_int(value_provider.iteration()).is_equal(5) - - # failure case - value_provider = TestIterativeValueProvider.new(false, 1, false) - if is_skip_fail_await(): - return - ( - await assert_failure_await(func(): await assert_func(value_provider, "bool_value", []).wait_until(100).is_true()) - ).has_message("Expected: is true but timed out after 100ms") - - -@warning_ignore("unused_parameter") -func test_is_false(timeout = 2000) -> void: - var value_provider := TestIterativeValueProvider.new(true, 5, false) - # without default timeout od 2000ms - assert_func(value_provider, "bool_value").is_true() - await assert_func(value_provider, "bool_value").is_false() - assert_int(value_provider.iteration()).is_equal(5) - - # with a timeout of 5s - value_provider.reset() - assert_func(value_provider, "bool_value").is_true() - await assert_func(value_provider, "bool_value").wait_until(5000).is_false() - assert_int(value_provider.iteration()).is_equal(5) - - # failure case - value_provider = TestIterativeValueProvider.new(true, 1, true) - if is_skip_fail_await(): - return - ( - await assert_failure_await(func(): await assert_func(value_provider, "bool_value", []).wait_until(100).is_false()) - ).has_message("Expected: is false but timed out after 100ms") - - -@warning_ignore("unused_parameter") -func test_is_equal(timeout = 2000) -> void: - var value_provider := TestIterativeValueProvider.new(42, 5, 23) - # without default timeout od 2000ms - assert_func(value_provider, "int_value").is_equal(42) - await assert_func(value_provider, "int_value").is_equal(23) - assert_int(value_provider.iteration()).is_equal(5) - - # with a timeout of 5s - value_provider.reset() - assert_func(value_provider, "int_value").is_equal(42) - await assert_func(value_provider, "int_value").wait_until(5000).is_equal(23) - assert_int(value_provider.iteration()).is_equal(5) - - # failing case - value_provider = TestIterativeValueProvider.new(23, 1, 23) - if is_skip_fail_await(): - return - ( - await assert_failure_await(func(): await assert_func(value_provider, "int_value", []).wait_until(100).is_equal(25)) - ).has_message("Expected: is equal '25' but timed out after 100ms") - - -@warning_ignore("unused_parameter") -func test_is_not_equal(timeout = 2000) -> void: - var value_provider := TestIterativeValueProvider.new(42, 5, 23) - # without default timeout od 2000ms - assert_func(value_provider, "int_value").is_equal(42) - await assert_func(value_provider, "int_value").is_not_equal(42) - assert_int(value_provider.iteration()).is_equal(5) - - # with a timeout of 5s - value_provider.reset() - assert_func(value_provider, "int_value").is_equal(42) - await assert_func(value_provider, "int_value").wait_until(5000).is_not_equal(42) - assert_int(value_provider.iteration()).is_equal(5) - - # failing case - value_provider = TestIterativeValueProvider.new(23, 1, 23) - if is_skip_fail_await(): - return - ( - await assert_failure_await(func(): await assert_func(value_provider, "int_value", []).wait_until(100).is_not_equal(23)) - ).has_message("Expected: is not equal '23' but timed out after 100ms") - - -@warning_ignore("unused_parameter") -func test_is_equal_wiht_func_arg(timeout = 1300) -> void: - var value_provider := TestIterativeValueProvider.new(42, 10, 23) - # without default timeout od 2000ms - assert_func(value_provider, "has_type", [1]).is_equal(42) - await assert_func(value_provider, "has_type", [10]).is_equal(23) - assert_int(value_provider.iteration()).is_equal(10) - - # with a timeout of 5s - value_provider.reset() - assert_func(value_provider, "has_type", [1]).is_equal(42) - await assert_func(value_provider, "has_type", [10]).wait_until(5000).is_equal(23) - assert_int(value_provider.iteration()).is_equal(10) - - -# abort test after 500ms to fail -@warning_ignore("unused_parameter") -func test_timeout_and_assert_fails(timeout = 500) -> void: - # disable temporary the timeout errors for this test - discard_error_interupted_by_timeout() - var value_provider := TestIterativeValueProvider.new(1, 10, 10) - # wait longer than test timeout, the value will be never '42' - await assert_func(value_provider, "int_value").wait_until(1000).is_equal(42) - fail("The test must be interrupted after 500ms") - - -func timed_function() -> Color: - var color = Color.RED - await await_millis(20) - color = Color.GREEN - await await_millis(20) - color = Color.BLUE - await await_millis(20) - color = Color.BLACK - return color - - -func test_timer_yielded_function() -> void: - await assert_func(self, "timed_function").is_equal(Color.BLACK) - # will be never red - await assert_func(self, "timed_function").wait_until(100).is_not_equal(Color.RED) - # failure case - if is_skip_fail_await(): - return - ( - await assert_failure_await(func(): await assert_func(self, "timed_function", []).wait_until(100).is_equal(Color.RED)) - ).has_message("Expected: is equal 'Color(1, 0, 0, 1)' but timed out after 100ms") - - -func test_timer_yielded_function_timeout() -> void: - if is_skip_fail_await(): - return - ( - await assert_failure_await(func(): await assert_func(self, "timed_function", []).wait_until(40).is_equal(Color.BLACK)) - ).has_message("Expected: is equal 'Color()' but timed out after 40ms") - - -func yielded_function() -> Color: - var color = Color.RED - await get_tree().process_frame - color = Color.GREEN - await get_tree().process_frame - color = Color.BLUE - await get_tree().process_frame - color = Color.BLACK - return color - - -func test_idle_frame_yielded_function() -> void: - await assert_func(self, "yielded_function").is_equal(Color.BLACK) - if is_skip_fail_await(): - return - ( - await assert_failure_await(func(): await assert_func(self, "yielded_function", []).wait_until(500).is_equal(Color.RED)) - ).has_message("Expected: is equal 'Color(1, 0, 0, 1)' but timed out after 500ms") - - -func test_has_failure_message() -> void: - if is_skip_fail_await(): - return - var value_provider := TestIterativeValueProvider.new(10, 1, 10) - ( - await assert_failure_await(func(): await assert_func(value_provider, "int_value", []).wait_until(500).is_equal(42)) - ).has_message("Expected: is equal '42' but timed out after 500ms") - - -func test_override_failure_message() -> void: - if is_skip_fail_await(): - return - var value_provider := TestIterativeValueProvider.new(10, 1, 20) - ( - await assert_failure_await(func(): await assert_func(value_provider, "int_value", []) \ - .override_failure_message("Custom failure message") \ - .wait_until(100) \ - .is_equal(42)) - ).has_message("Custom failure message") - - -@warning_ignore("unused_parameter") -func test_invalid_function(timeout = 100): - if is_skip_fail_await(): - return - ( - await assert_failure_await(func(): await assert_func(self, "invalid_func_name", [])\ - .wait_until(1000)\ - .is_equal(42)) - ).starts_with_message("The function 'invalid_func_name' do not exists checked instance") diff --git a/addons/gdUnit4/test/asserts/GdUnitGodotErrorAssertImplTest.gd b/addons/gdUnit4/test/asserts/GdUnitGodotErrorAssertImplTest.gd deleted file mode 100644 index 766723e..0000000 --- a/addons/gdUnit4/test/asserts/GdUnitGodotErrorAssertImplTest.gd +++ /dev/null @@ -1,137 +0,0 @@ -# GdUnit generated TestSuite -class_name GdUnitGodotErrorAssertImplTest -extends GdUnitTestSuite -@warning_ignore('unused_parameter') -@warning_ignore('return_value_discarded') - -# TestSuite generated from -const __source = 'res://addons/gdUnit4/src/asserts/GdUnitGodotErrorAssertImpl.gd' - - -class GodotErrorTestClass: - - func test(value :int) -> void: - match value: - 0: - @warning_ignore("assert_always_true") - assert(true, "no error" ) - 1: # failing assert - await Engine.get_main_loop().process_frame - if OS.is_debug_build(): - # do not break the debug session we simmulate a assert by writing the error manually - prints(""" - USER SCRIPT ERROR: Assertion failed: this is an assert error - at: GodotErrorTestClass.test (res://addons/gdUnit4/test/asserts/GdUnitGodotErrorAssertImplTest.gd:18) - """.dedent()) - else: - assert(false, "this is an assert error" ) - 2: # push_warning - push_warning('this is an push_warning') - 3: # push_error - push_error('this is an push_error') - pass - 4: # runtime error - if OS.is_debug_build(): - # do not break the debug session we simmulate a assert by writing the error manually - prints(""" - USER SCRIPT ERROR: Division by zero error in operator '/'. - at: GodotErrorTestClass.test (res://addons/gdUnit4/test/asserts/GdUnitGodotErrorAssertImplTest.gd:32) - """.dedent()) - else: - var a = 0 - @warning_ignore("integer_division") - @warning_ignore("unused_variable") - var x = 1/a - - -var _save_is_report_push_errors :bool -var _save_is_report_script_errors :bool - - -# skip see https://github.com/godotengine/godot/issues/80292 -@warning_ignore('unused_parameter') -func before(do_skip=Engine.get_version_info().hex < 0x40100, skip_reason="Exclude this test suite for Godot versions <= 4.1.x"): - _save_is_report_push_errors = GdUnitSettings.is_report_push_errors() - _save_is_report_script_errors = GdUnitSettings.is_report_script_errors() - # disable default error reporting for testing - ProjectSettings.set_setting(GdUnitSettings.REPORT_PUSH_ERRORS, false) - ProjectSettings.set_setting(GdUnitSettings.REPORT_SCRIPT_ERRORS, false) - - -func after(): - ProjectSettings.set_setting(GdUnitSettings.REPORT_PUSH_ERRORS, _save_is_report_push_errors) - ProjectSettings.set_setting(GdUnitSettings.REPORT_SCRIPT_ERRORS, _save_is_report_script_errors) - - -func after_test(): - # Cleanup report artifacts - GdUnitThreadManager.get_current_context().get_execution_context().error_monitor._entries.clear() - - -func test_invalid_callable() -> void: - assert_failure(func(): assert_error(Callable()).is_success())\ - .is_failed()\ - .has_message("Invalid Callable 'null::null'") - - -func test_is_success() -> void: - await assert_error(func (): await GodotErrorTestClass.new().test(0)).is_success() - - var assert_ = await assert_failure_await(func(): - await assert_error(func (): await GodotErrorTestClass.new().test(1)).is_success()) - assert_.is_failed().has_message(""" - Expecting: no error's are ocured. - but found: 'Assertion failed: this is an assert error' - """.dedent().trim_prefix("\n")) - - -func test_is_assert_failed() -> void: - await assert_error(func (): await GodotErrorTestClass.new().test(1))\ - .is_runtime_error('Assertion failed: this is an assert error') - - var assert_ = await assert_failure_await(func(): - await assert_error(func (): GodotErrorTestClass.new().test(0)).is_runtime_error('Assertion failed: this is an assert error')) - assert_.is_failed().has_message(""" - Expecting: a runtime error is triggered. - message: 'Assertion failed: this is an assert error' - found: no errors - """.dedent().trim_prefix("\n")) - - -func test_is_push_warning() -> void: - await assert_error(func (): GodotErrorTestClass.new().test(2))\ - .is_push_warning('this is an push_warning') - - var assert_ = await assert_failure_await(func(): - await assert_error(func (): GodotErrorTestClass.new().test(0)).is_push_warning('this is an push_warning')) - assert_.is_failed().has_message(""" - Expecting: push_warning() is called. - message: 'this is an push_warning' - found: no errors - """.dedent().trim_prefix("\n")) - - -func test_is_push_error() -> void: - await assert_error(func (): GodotErrorTestClass.new().test(3))\ - .is_push_error('this is an push_error') - - var assert_ = await assert_failure_await(func(): - await assert_error(func (): GodotErrorTestClass.new().test(0)).is_push_error('this is an push_error')) - assert_.is_failed().has_message(""" - Expecting: push_error() is called. - message: 'this is an push_error' - found: no errors - """.dedent().trim_prefix("\n")) - - -func test_is_runtime_error() -> void: - await assert_error(func (): GodotErrorTestClass.new().test(4))\ - .is_runtime_error("Division by zero error in operator '/'.") - - var assert_ = await assert_failure_await(func(): - await assert_error(func (): GodotErrorTestClass.new().test(0)).is_runtime_error("Division by zero error in operator '/'.")) - assert_.is_failed().has_message(""" - Expecting: a runtime error is triggered. - message: 'Division by zero error in operator '/'.' - found: no errors - """.dedent().trim_prefix("\n")) diff --git a/addons/gdUnit4/test/asserts/GdUnitGodotErrorWithAssertTest.gd b/addons/gdUnit4/test/asserts/GdUnitGodotErrorWithAssertTest.gd deleted file mode 100644 index cb58348..0000000 --- a/addons/gdUnit4/test/asserts/GdUnitGodotErrorWithAssertTest.gd +++ /dev/null @@ -1,40 +0,0 @@ -extends GdUnitTestSuite - - -var _catched_events :Array[GdUnitEvent] = [] - - -func test_assert_method_with_enabled_global_error_report() -> void: - ProjectSettings.set_setting(GdUnitSettings.REPORT_SCRIPT_ERRORS, true) - await assert_error(do_a_fail).is_runtime_error('Assertion failed: test') - - -func test_assert_method_with_disabled_global_error_report() -> void: - ProjectSettings.set_setting(GdUnitSettings.REPORT_SCRIPT_ERRORS, false) - await assert_error(do_a_fail).is_runtime_error('Assertion failed: test') - - -@warning_ignore("assert_always_false") -func do_a_fail(): - if OS.is_debug_build(): - # On debug level we need to simulate the assert log entry, otherwise we stuck on a breakpoint - prints(""" - USER SCRIPT ERROR: Assertion failed: test - at: do_a_fail (res://addons/gdUnit4/test/asserts/GdUnitErrorAssertTest.gd:20)""") - else: - assert(3 == 1, 'test') - - -func catch_test_events(event :GdUnitEvent) -> void: - _catched_events.append(event) - - -func before() -> void: - GdUnitSignals.instance().gdunit_event.connect(catch_test_events) - - -func after() -> void: - # We expect no errors or failures, as we caught already the assert error by using the assert `assert_error` on the test case - assert_array(_catched_events).extractv(extr("error_count"), extr("failed_count"))\ - .contains_exactly([tuple(0, 0), tuple(0,0), tuple(0,0), tuple(0,0)]) - GdUnitSignals.instance().gdunit_event.disconnect(catch_test_events) diff --git a/addons/gdUnit4/test/asserts/GdUnitIntAssertImplTest.gd b/addons/gdUnit4/test/asserts/GdUnitIntAssertImplTest.gd deleted file mode 100644 index 9ad4e73..0000000 --- a/addons/gdUnit4/test/asserts/GdUnitIntAssertImplTest.gd +++ /dev/null @@ -1,242 +0,0 @@ -# GdUnit generated TestSuite -class_name GdUnitIntAssertImplTest -extends GdUnitTestSuite - -# TestSuite generated from -const __source = 'res://addons/gdUnit4/src/asserts/GdUnitIntAssertImpl.gd' - - -func test_is_null(): - assert_int(null).is_null() - - assert_failure(func(): assert_int(23).is_null()) \ - .is_failed() \ - .starts_with_message("Expecting: '' but was '23'") - - -func test_is_not_null(): - assert_int(23).is_not_null() - - assert_failure(func(): assert_int(null).is_not_null()) \ - .is_failed() \ - .has_message("Expecting: not to be ''") - - -func test_is_equal(): - assert_int(23).is_equal(23) - - assert_failure(func(): assert_int(23).is_equal(42)) \ - .is_failed() \ - .has_message("Expecting:\n '42'\n but was\n '23'") - assert_failure(func(): assert_int(null).is_equal(42)) \ - .is_failed() \ - .has_message("Expecting:\n '42'\n but was\n ''") - - -func test_is_not_equal(): - assert_int(null).is_not_equal(42) - assert_int(23).is_not_equal(42) - - assert_failure(func(): assert_int(23).is_not_equal(23)) \ - .is_failed() \ - .has_message("Expecting:\n '23'\n not equal to\n '23'") - - -func test_is_less(): - assert_int(23).is_less(42) - assert_int(23).is_less(24) - - assert_failure(func(): assert_int(23).is_less(23)) \ - .is_failed() \ - .has_message("Expecting to be less than:\n '23' but was '23'") - assert_failure(func(): assert_int(null).is_less(23)) \ - .is_failed() \ - .has_message("Expecting to be less than:\n '23' but was ''") - - -func test_is_less_equal(): - assert_int(23).is_less_equal(42) - assert_int(23).is_less_equal(23) - - assert_failure(func(): assert_int(23).is_less_equal(22)) \ - .is_failed() \ - .has_message("Expecting to be less than or equal:\n '22' but was '23'") - assert_failure(func(): assert_int(null).is_less_equal(22)) \ - .is_failed() \ - .has_message("Expecting to be less than or equal:\n '22' but was ''") - - -func test_is_greater(): - assert_int(23).is_greater(20) - assert_int(23).is_greater(22) - - assert_failure(func(): assert_int(23).is_greater(23)) \ - .is_failed() \ - .has_message("Expecting to be greater than:\n '23' but was '23'") - assert_failure(func(): assert_int(null).is_greater(23)) \ - .is_failed() \ - .has_message("Expecting to be greater than:\n '23' but was ''") - - -func test_is_greater_equal(): - assert_int(23).is_greater_equal(20) - assert_int(23).is_greater_equal(23) - - assert_failure(func(): assert_int(23).is_greater_equal(24)) \ - .is_failed() \ - .has_message("Expecting to be greater than or equal:\n '24' but was '23'") - assert_failure(func(): assert_int(null).is_greater_equal(24)) \ - .is_failed() \ - .has_message("Expecting to be greater than or equal:\n '24' but was ''") - - -func test_is_even(): - assert_int(12).is_even() - - assert_failure(func(): assert_int(13).is_even()) \ - .is_failed() \ - .has_message("Expecting:\n '13' must be even") - assert_failure(func(): assert_int(null).is_even()) \ - .is_failed() \ - .has_message("Expecting:\n '' must be even") - - -func test_is_odd(): - assert_int(13).is_odd() - - assert_failure(func(): assert_int(12).is_odd()) \ - .is_failed() \ - .has_message("Expecting:\n '12' must be odd") - assert_failure(func(): assert_int(null).is_odd()) \ - .is_failed() \ - .has_message("Expecting:\n '' must be odd") - - -func test_is_negative(): - assert_int(-13).is_negative() - - assert_failure(func(): assert_int(13).is_negative()) \ - .is_failed() \ - .has_message("Expecting:\n '13' be negative") - assert_failure(func(): assert_int(null).is_negative()) \ - .is_failed() \ - .has_message("Expecting:\n '' be negative") - - -func test_is_not_negative(): - assert_int(13).is_not_negative() - - assert_failure(func(): assert_int(-13).is_not_negative()) \ - .is_failed() \ - .has_message("Expecting:\n '-13' be not negative") - assert_failure(func(): assert_int(null).is_not_negative()) \ - .is_failed() \ - .has_message("Expecting:\n '' be not negative") - - -func test_is_zero(): - assert_int(0).is_zero() - - assert_failure(func(): assert_int(1).is_zero()) \ - .is_failed() \ - .has_message("Expecting:\n equal to 0 but is '1'") - assert_failure(func(): assert_int(null).is_zero()) \ - .is_failed() \ - .has_message("Expecting:\n equal to 0 but is ''") - - -func test_is_not_zero(): - assert_int(null).is_not_zero() - assert_int(1).is_not_zero() - - assert_failure(func(): assert_int(0).is_not_zero()) \ - .is_failed() \ - .has_message("Expecting:\n not equal to 0") - - -func test_is_in(): - assert_int(5).is_in([3, 4, 5, 6]) - # this assertion fail because 7 is not in [3, 4, 5, 6] - assert_failure(func(): assert_int(7).is_in([3, 4, 5, 6])) \ - .is_failed() \ - .has_message("Expecting:\n '7'\n is in\n '[3, 4, 5, 6]'") - assert_failure(func(): assert_int(null).is_in([3, 4, 5, 6])) \ - .is_failed() \ - .has_message("Expecting:\n ''\n is in\n '[3, 4, 5, 6]'") - - -func test_is_not_in(): - assert_int(null).is_not_in([3, 4, 6, 7]) - assert_int(5).is_not_in([3, 4, 6, 7]) - # this assertion fail because 7 is not in [3, 4, 5, 6] - assert_failure(func(): assert_int(5).is_not_in([3, 4, 5, 6])) \ - .is_failed() \ - .has_message("Expecting:\n '5'\n is not in\n '[3, 4, 5, 6]'") - - -func test_is_between(fuzzer = Fuzzers.rangei(-20, 20)): - var value = fuzzer.next_value() as int - assert_int(value).is_between(-20, 20) - - -func test_is_between_must_fail(): - assert_failure(func(): assert_int(-10).is_between(-9, 0)) \ - .is_failed() \ - .has_message("Expecting:\n '-10'\n in range between\n '-9' <> '0'") - assert_failure(func(): assert_int(0).is_between(1, 10)) \ - .is_failed() \ - .has_message("Expecting:\n '0'\n in range between\n '1' <> '10'") - assert_failure(func(): assert_int(10).is_between(11, 21)) \ - .is_failed() \ - .has_message("Expecting:\n '10'\n in range between\n '11' <> '21'") - assert_failure(func(): assert_int(null).is_between(11, 21)) \ - .is_failed() \ - .has_message("Expecting:\n ''\n in range between\n '11' <> '21'") - - -func test_must_fail_has_invlalid_type(): - assert_failure(func(): assert_int(3.3)) \ - .is_failed() \ - .has_message("GdUnitIntAssert inital error, unexpected type ") - assert_failure(func(): assert_int(true)) \ - .is_failed() \ - .has_message("GdUnitIntAssert inital error, unexpected type ") - assert_failure(func(): assert_int("foo")) \ - .is_failed() \ - .has_message("GdUnitIntAssert inital error, unexpected type ") - assert_failure(func(): assert_int(Resource.new())) \ - .is_failed() \ - .has_message("GdUnitIntAssert inital error, unexpected type ") - - -func test_override_failure_message() -> void: - assert_failure(func(): assert_int(314)\ - .override_failure_message("Custom failure message") \ - .is_null()) \ - .is_failed() \ - .has_message("Custom failure message") - - -# tests if an assert fails the 'is_failure' reflects the failure status -func test_is_failure() -> void: - # initial is false - assert_bool(is_failure()).is_false() - - # checked success assert - assert_int(0).is_zero() - assert_bool(is_failure()).is_false() - - # checked faild assert - assert_failure(func(): assert_int(1).is_zero()) \ - .is_failed() - assert_bool(is_failure()).is_true() - - # checked next success assert - assert_int(0).is_zero() - # is true because we have an already failed assert - assert_bool(is_failure()).is_true() - - # should abort here because we had an failing assert - if is_failure(): - return - assert_bool(true).override_failure_message("This line shold never be called").is_false() diff --git a/addons/gdUnit4/test/asserts/GdUnitObjectAssertImplTest.gd b/addons/gdUnit4/test/asserts/GdUnitObjectAssertImplTest.gd deleted file mode 100644 index 1abd80e..0000000 --- a/addons/gdUnit4/test/asserts/GdUnitObjectAssertImplTest.gd +++ /dev/null @@ -1,178 +0,0 @@ -# GdUnit generated TestSuite -class_name GdUnitObjectAssertImplTest -extends GdUnitTestSuite - -# TestSuite generated from -const __source = 'res://addons/gdUnit4/src/asserts/GdUnitObjectAssertImpl.gd' - - -func test_is_equal(): - assert_object(Mesh.new()).is_equal(Mesh.new()) - - assert_failure(func(): assert_object(Mesh.new()).is_equal(Skin.new())) \ - .is_failed() - assert_failure(func(): assert_object(null).is_equal(Skin.new())) \ - .is_failed() \ - .has_message("Expecting:\n" - + " \n" - + " but was\n" - + " ''") - - -func test_is_not_equal(): - assert_object(null).is_not_equal(Skin.new()) - assert_object(Mesh.new()).is_not_equal(Skin.new()) - - assert_failure(func(): assert_object(Mesh.new()).is_not_equal(Mesh.new())) \ - .is_failed() - - -func test_is_instanceof(): - # engine class test - assert_object(auto_free(Path3D.new())).is_instanceof(Node) - assert_object(auto_free(Camera3D.new())).is_instanceof(Camera3D) - # script class test - assert_object(auto_free(Udo.new())).is_instanceof(Person) - # inner class test - assert_object(auto_free(CustomClass.InnerClassA.new())).is_instanceof(Node) - assert_object(auto_free(CustomClass.InnerClassB.new())).is_instanceof(CustomClass.InnerClassA) - - assert_failure(func(): assert_object(auto_free(Path3D.new())).is_instanceof(Tree)) \ - .is_failed() \ - .has_message("Expected instance of:\n 'Tree'\n But it was 'Path3D'") - assert_failure(func(): assert_object(null).is_instanceof(Tree)) \ - .is_failed() \ - .has_message("Expected instance of:\n 'Tree'\n But it was ''") - - -func test_is_not_instanceof(): - assert_object(null).is_not_instanceof(Tree) - # engine class test - assert_object(auto_free(Path3D.new())).is_not_instanceof(Tree) - # script class test - assert_object(auto_free(City.new())).is_not_instanceof(Person) - # inner class test - assert_object(auto_free(CustomClass.InnerClassA.new())).is_not_instanceof(Tree) - assert_object(auto_free(CustomClass.InnerClassB.new())).is_not_instanceof(CustomClass.InnerClassC) - - assert_failure(func(): assert_object(auto_free(Path3D.new())).is_not_instanceof(Node)) \ - .is_failed() \ - .has_message("Expected not be a instance of ") - - -func test_is_null(): - assert_object(null).is_null() - - assert_failure(func(): assert_object(auto_free(Node.new())).is_null()) \ - .is_failed() \ - .starts_with_message("Expecting: '' but was ") - - -func test_is_not_null(): - assert_object(auto_free(Node.new())).is_not_null() - - assert_failure(func(): assert_object(null).is_not_null()) \ - .is_failed() \ - .has_message("Expecting: not to be ''") - - -func test_is_same(): - var obj1 = auto_free(Node.new()) - var obj2 = obj1 - var obj3 = auto_free(obj1.duplicate()) - assert_object(obj1).is_same(obj1) - assert_object(obj1).is_same(obj2) - assert_object(obj2).is_same(obj1) - - assert_failure(func(): assert_object(null).is_same(obj1)) \ - .is_failed() \ - .has_message("Expecting:\n" - + " \n" - + " to refer to the same object\n" - + " ''") - assert_failure(func(): assert_object(obj1).is_same(obj3)) \ - .is_failed() - assert_failure(func(): assert_object(obj3).is_same(obj1)) \ - .is_failed() - assert_failure(func(): assert_object(obj3).is_same(obj2)) \ - .is_failed() - - -func test_is_not_same(): - var obj1 = auto_free(Node.new()) - var obj2 = obj1 - var obj3 = auto_free(obj1.duplicate()) - assert_object(null).is_not_same(obj1) - assert_object(obj1).is_not_same(obj3) - assert_object(obj3).is_not_same(obj1) - assert_object(obj3).is_not_same(obj2) - - assert_failure(func(): assert_object(obj1).is_not_same(obj1)) \ - .is_failed() \ - .has_message(""" - Expecting not same: - """ - .dedent() - .trim_prefix("\n")) - assert_failure(func(): assert_object(obj1).is_not_same(obj2)) \ - .is_failed() \ - .has_message(""" - Expecting not same: - """ - .dedent() - .trim_prefix("\n")) - assert_failure(func(): assert_object(obj2).is_not_same(obj1)) \ - .is_failed() \ - .has_message(""" - Expecting not same: - """ - .dedent() - .trim_prefix("\n")) - - -func test_must_fail_has_invlalid_type(): - assert_failure(func(): assert_object(1)) \ - .is_failed() \ - .has_message("GdUnitObjectAssert inital error, unexpected type ") - assert_failure(func(): assert_object(1.3)) \ - .is_failed() \ - .has_message("GdUnitObjectAssert inital error, unexpected type ") - assert_failure(func(): assert_object(true)) \ - .is_failed() \ - .has_message("GdUnitObjectAssert inital error, unexpected type ") - assert_failure(func(): assert_object("foo")) \ - .is_failed() \ - .has_message("GdUnitObjectAssert inital error, unexpected type ") - - -func test_override_failure_message() -> void: - assert_failure(func(): assert_object(auto_free(Node.new())) \ - .override_failure_message("Custom failure message") \ - .is_null()) \ - .is_failed() \ - .has_message("Custom failure message") - - -# tests if an assert fails the 'is_failure' reflects the failure status -func test_is_failure() -> void: - # initial is false - assert_bool(is_failure()).is_false() - - # checked success assert - assert_object(null).is_null() - assert_bool(is_failure()).is_false() - - # checked faild assert - assert_failure(func(): assert_object(RefCounted.new()).is_null()) \ - .is_failed() - assert_bool(is_failure()).is_true() - - # checked next success assert - assert_object(null).is_null() - # is true because we have an already failed assert - assert_bool(is_failure()).is_true() - - # should abort here because we had an failing assert - if is_failure(): - return - assert_bool(true).override_failure_message("This line shold never be called").is_false() diff --git a/addons/gdUnit4/test/asserts/GdUnitPackedArrayAssertTest.gd b/addons/gdUnit4/test/asserts/GdUnitPackedArrayAssertTest.gd deleted file mode 100644 index 26c2543..0000000 --- a/addons/gdUnit4/test/asserts/GdUnitPackedArrayAssertTest.gd +++ /dev/null @@ -1,338 +0,0 @@ -# GdUnit generated TestSuite -extends GdUnitTestSuite - -# TestSuite generated from -const __source = 'res://addons/gdUnit4/src/asserts/GdUnitArrayAssertImpl.gd' - - -@warning_ignore("unused_parameter") -func test_is_array_assert(_test :String, array, test_parameters = [ - ["Array", Array()], - ["PackedByteArray", PackedByteArray()], - ["PackedInt32Array", PackedInt32Array()], - ["PackedInt64Array", PackedInt64Array()], - ["PackedFloat32Array", PackedFloat32Array()], - ["PackedFloat64Array", PackedFloat64Array()], - ["PackedStringArray", PackedStringArray()], - ["PackedVector2Array", PackedVector2Array()], - ["PackedVector3Array", PackedVector3Array()], - ["PackedColorArray", PackedColorArray()] ] - ) -> void: - var assert_ = assert_that(array) - assert_object(assert_).is_instanceof(GdUnitArrayAssert) - - -@warning_ignore("unused_parameter") -func test_is_null(_test :String, value, test_parameters = [ - ["Array", Array()], - ["PackedByteArray", PackedByteArray()], - ["PackedInt32Array", PackedInt32Array()], - ["PackedInt64Array", PackedInt64Array()], - ["PackedFloat32Array", PackedFloat32Array()], - ["PackedFloat64Array", PackedFloat64Array()], - ["PackedStringArray", PackedStringArray()], - ["PackedVector2Array", PackedVector2Array()], - ["PackedVector3Array", PackedVector3Array()], - ["PackedColorArray", PackedColorArray()] ] - ) -> void: - assert_array(null).is_null() - assert_failure(func(): assert_array(value).is_null()) \ - .is_failed() \ - .has_message("Expecting: '' but was '%s'" % GdDefaultValueDecoder.decode(value)) - - -@warning_ignore("unused_parameter") -func test_is_not_null(_test :String, array, test_parameters = [ - ["Array", Array()], - ["PackedByteArray", PackedByteArray()], - ["PackedInt32Array", PackedInt32Array()], - ["PackedInt64Array", PackedInt64Array()], - ["PackedFloat32Array", PackedFloat32Array()], - ["PackedFloat64Array", PackedFloat64Array()], - ["PackedStringArray", PackedStringArray()], - ["PackedVector2Array", PackedVector2Array()], - ["PackedVector3Array", PackedVector3Array()], - ["PackedColorArray", PackedColorArray()] ] - ) -> void: - assert_array(array).is_not_null() - - assert_failure(func(): assert_array(null).is_not_null()) \ - .is_failed() \ - .has_message("Expecting: not to be ''") - - -@warning_ignore("unused_parameter") -func test_is_equal(_test :String, array, test_parameters = [ - ["Array", Array([1, 2, 3, 4, 5])], - ["PackedByteArray", PackedByteArray([1, 2, 3, 4, 5])], - ["PackedInt32Array", PackedInt32Array([1, 2, 3, 4, 5])], - ["PackedInt64Array", PackedInt64Array([1, 2, 3, 4, 5])], - ["PackedFloat32Array", PackedFloat32Array([1, 2, 3, 4, 5])], - ["PackedFloat64Array", PackedFloat64Array([1, 2, 3, 4, 5])], - ["PackedStringArray", PackedStringArray([1, 2, 3, 4, 5])], - ["PackedVector2Array", PackedVector2Array([Vector2.ZERO, Vector2.LEFT, Vector2.RIGHT, Vector2.UP, Vector2.DOWN])], - ["PackedVector3Array", PackedVector3Array([Vector3.ZERO, Vector3.LEFT, Vector3.RIGHT, Vector3.UP, Vector3.DOWN])], - ["PackedColorArray", PackedColorArray([Color.RED, Color.GREEN, Color.BLUE, Color.YELLOW, Color.BLACK])] ] - ) -> void: - - var other = array.duplicate() - assert_array(array).is_equal(other) - # should fail because the array not contains same elements and has diff size - other.append(array[2]) - assert_failure(func(): assert_array(array).is_equal(other)) \ - .is_failed() \ - .has_message(""" - Expecting: - '%s' - but was - '%s' - - Differences found: - Index Current Expected 5 $value """ - .dedent() - .trim_prefix("\n") - .replace("$value", str(array[2]) ) % [GdArrayTools.as_string(other, false), GdArrayTools.as_string(array, false)]) - - -@warning_ignore("unused_parameter") -func test_is_not_equal(_test :String, array, test_parameters = [ - ["Array", Array([1, 2, 3, 4, 5])], - ["PackedByteArray", PackedByteArray([1, 2, 3, 4, 5])], - ["PackedInt32Array", PackedInt32Array([1, 2, 3, 4, 5])], - ["PackedInt64Array", PackedInt64Array([1, 2, 3, 4, 5])], - ["PackedFloat32Array", PackedFloat32Array([1, 2, 3, 4, 5])], - ["PackedFloat64Array", PackedFloat64Array([1, 2, 3, 4, 5])], - ["PackedStringArray", PackedStringArray([1, 2, 3, 4, 5])], - ["PackedVector2Array", PackedVector2Array([Vector2.ZERO, Vector2.LEFT, Vector2.RIGHT, Vector2.UP, Vector2.DOWN])], - ["PackedVector3Array", PackedVector3Array([Vector3.ZERO, Vector3.LEFT, Vector3.RIGHT, Vector3.UP, Vector3.DOWN])], - ["PackedColorArray", PackedColorArray([Color.RED, Color.GREEN, Color.BLUE, Color.YELLOW, Color.BLACK])] ] - ) -> void: - - var other = array.duplicate() - other.append(array[2]) - assert_array(array).is_not_equal(other) - # should fail because the array contains same elements - assert_failure(func(): assert_array(array).is_not_equal(array.duplicate())) \ - .is_failed() \ - .has_message(""" - Expecting: - '%s' - not equal to - '%s'""" - .dedent() - .trim_prefix("\n") % [GdDefaultValueDecoder.decode(array), GdDefaultValueDecoder.decode(array)]) - - -@warning_ignore("unused_parameter") -func test_is_empty(_test :String, array, test_parameters = [ - ["Array", Array([1, 2, 3, 4, 5])], - ["PackedByteArray", PackedByteArray([1, 2, 3, 4, 5])], - ["PackedInt32Array", PackedInt32Array([1, 2, 3, 4, 5])], - ["PackedInt64Array", PackedInt64Array([1, 2, 3, 4, 5])], - ["PackedFloat32Array", PackedFloat32Array([1, 2, 3, 4, 5])], - ["PackedFloat64Array", PackedFloat64Array([1, 2, 3, 4, 5])], - ["PackedStringArray", PackedStringArray([1, 2, 3, 4, 5])], - ["PackedVector2Array", PackedVector2Array([Vector2.ZERO, Vector2.LEFT, Vector2.RIGHT, Vector2.UP, Vector2.DOWN])], - ["PackedVector3Array", PackedVector3Array([Vector3.ZERO, Vector3.LEFT, Vector3.RIGHT, Vector3.UP, Vector3.DOWN])], - ["PackedColorArray", PackedColorArray([Color.RED, Color.GREEN, Color.BLUE, Color.YELLOW, Color.BLACK])] ] - ) -> void: - - var empty = array.duplicate() - empty.clear() - assert_array(empty).is_empty() - # should fail because the array is not empty - assert_failure(func(): assert_array(array).is_empty()) \ - .is_failed() \ - .has_message(""" - Expecting: - must be empty but was - '%s'""" - .dedent() - .trim_prefix("\n") % GdDefaultValueDecoder.decode(array)) - - -@warning_ignore("unused_parameter") -func test_is_not_empty(_test :String, array, test_parameters = [ - ["Array", Array([1, 2, 3, 4, 5])], - ["PackedByteArray", PackedByteArray([1, 2, 3, 4, 5])], - ["PackedInt32Array", PackedInt32Array([1, 2, 3, 4, 5])], - ["PackedInt64Array", PackedInt64Array([1, 2, 3, 4, 5])], - ["PackedFloat32Array", PackedFloat32Array([1, 2, 3, 4, 5])], - ["PackedFloat64Array", PackedFloat64Array([1, 2, 3, 4, 5])], - ["PackedStringArray", PackedStringArray([1, 2, 3, 4, 5])], - ["PackedVector2Array", PackedVector2Array([Vector2.ZERO, Vector2.LEFT, Vector2.RIGHT, Vector2.UP, Vector2.DOWN])], - ["PackedVector3Array", PackedVector3Array([Vector3.ZERO, Vector3.LEFT, Vector3.RIGHT, Vector3.UP, Vector3.DOWN])], - ["PackedColorArray", PackedColorArray([Color.RED, Color.GREEN, Color.BLUE, Color.YELLOW, Color.BLACK])] ] - ) -> void: - - assert_array(array).is_not_empty() - # should fail because the array is empty - var empty = array.duplicate() - empty.clear() - assert_failure(func(): assert_array(empty).is_not_empty()) \ - .is_failed() \ - .has_message("Expecting:\n must not be empty") - - -@warning_ignore("unused_parameter") -func test_is_same(value, test_parameters = [ - [[0]], - [PackedByteArray([0])], - [PackedFloat32Array([0.0])], - [PackedFloat64Array([0.0])], - [PackedInt32Array([0])], - [PackedInt64Array([0])], - [PackedStringArray([""])], - [PackedColorArray([Color.RED])], - [PackedVector2Array([Vector2.ZERO])], - [PackedVector3Array([Vector3.ZERO])], -]) -> void: - assert_array(value).is_same(value) - - var v := GdDefaultValueDecoder.decode(value) - assert_failure(func(): assert_array(value).is_same(value.duplicate()))\ - .is_failed()\ - .has_message(""" - Expecting: - '%s' - to refer to the same object - '%s'""" - .dedent() - .trim_prefix("\n") % [v, v]) - - -@warning_ignore("unused_parameter") -func test_is_not_same(value, test_parameters = [ - [[0]], - [PackedByteArray([0])], - [PackedFloat32Array([0.0])], - [PackedFloat64Array([0.0])], - [PackedInt32Array([0])], - [PackedInt64Array([0])], - [PackedStringArray([""])], - [PackedColorArray([Color.RED])], - [PackedVector2Array([Vector2.ZERO])], - [PackedVector3Array([Vector3.ZERO])], -]) -> void: - assert_array(value).is_not_same(value.duplicate()) - - assert_failure(func(): assert_array(value).is_not_same(value))\ - .is_failed()\ - .has_message("Expecting not same:\n '%s'" % GdDefaultValueDecoder.decode(value)) - - -@warning_ignore("unused_parameter") -func test_has_size(_test :String, array, test_parameters = [ - ["Array", Array([1, 2, 3, 4, 5])], - ["PackedByteArray", PackedByteArray([1, 2, 3, 4, 5])], - ["PackedInt32Array", PackedInt32Array([1, 2, 3, 4, 5])], - ["PackedInt64Array", PackedInt64Array([1, 2, 3, 4, 5])], - ["PackedFloat32Array", PackedFloat32Array([1, 2, 3, 4, 5])], - ["PackedFloat64Array", PackedFloat64Array([1, 2, 3, 4, 5])], - ["PackedStringArray", PackedStringArray([1, 2, 3, 4, 5])], - ["PackedVector2Array", PackedVector2Array([Vector2.ZERO, Vector2.LEFT, Vector2.RIGHT, Vector2.UP, Vector2.DOWN])], - ["PackedVector3Array", PackedVector3Array([Vector3.ZERO, Vector3.LEFT, Vector3.RIGHT, Vector3.UP, Vector3.DOWN])], - ["PackedColorArray", PackedColorArray([Color.RED, Color.GREEN, Color.BLUE, Color.YELLOW, Color.BLACK])] ] - ) -> void: - - assert_array(array).has_size(5) - # should fail because the array has a size of 5 - assert_failure(func(): assert_array(array).has_size(4)) \ - .is_failed() \ - .has_message(""" - Expecting size: - '4' - but was - '5'""" - .dedent() - .trim_prefix("\n")) - - -@warning_ignore("unused_parameter") -func test_contains(_test :String, array, test_parameters = [ - ["Array", Array([1, 2, 3, 4, 5])], - ["PackedByteArray", PackedByteArray([1, 2, 3, 4, 5])], - ["PackedInt32Array", PackedInt32Array([1, 2, 3, 4, 5])], - ["PackedInt64Array", PackedInt64Array([1, 2, 3, 4, 5])], - ["PackedFloat32Array", PackedFloat32Array([1, 2, 3, 4, 5])], - ["PackedFloat64Array", PackedFloat64Array([1, 2, 3, 4, 5])], - ["PackedStringArray", PackedStringArray([1, 2, 3, 4, 5])], - ["PackedVector2Array", PackedVector2Array([Vector2.ZERO, Vector2.LEFT, Vector2.RIGHT, Vector2.UP, Vector2.DOWN])], - ["PackedVector3Array", PackedVector3Array([Vector3.ZERO, Vector3.LEFT, Vector3.RIGHT, Vector3.UP, Vector3.DOWN])], - ["PackedColorArray", PackedColorArray([Color.RED, Color.GREEN, Color.BLUE, Color.YELLOW, Color.BLACK])] ] - ) -> void: - - assert_array(array).contains([array[1], array[3], array[4]]) - # should fail because the array not contains 7 and 6 - var do_contains := [array[1], 7, 6] - assert_failure(func(): assert_array(array).contains(do_contains)) \ - .is_failed() \ - .has_message(""" - Expecting contains elements: - '$source' - do contains (in any order) - '$contains' - but could not find elements: - '[7, 6]'""" - .dedent() - .trim_prefix("\n") - .replace("$source", GdDefaultValueDecoder.decode(array)) - .replace("$contains", GdDefaultValueDecoder.decode(do_contains)) - ) - - -@warning_ignore("unused_parameter") -func test_contains_exactly(_test :String, array, test_parameters = [ - ["Array", Array([1, 2, 3, 4, 5])], - ["PackedByteArray", PackedByteArray([1, 2, 3, 4, 5])], - ["PackedInt32Array", PackedInt32Array([1, 2, 3, 4, 5])], - ["PackedInt64Array", PackedInt64Array([1, 2, 3, 4, 5])], - ["PackedFloat32Array", PackedFloat32Array([1, 2, 3, 4, 5])], - ["PackedFloat64Array", PackedFloat64Array([1, 2, 3, 4, 5])], - ["PackedStringArray", PackedStringArray([1, 2, 3, 4, 5])], - ["PackedVector2Array", PackedVector2Array([Vector2.ZERO, Vector2.LEFT, Vector2.RIGHT, Vector2.UP, Vector2.DOWN])], - ["PackedVector3Array", PackedVector3Array([Vector3.ZERO, Vector3.LEFT, Vector3.RIGHT, Vector3.UP, Vector3.DOWN])], - ["PackedColorArray", PackedColorArray([Color.RED, Color.GREEN, Color.BLUE, Color.YELLOW, Color.BLACK])] ] - ) -> void: - - assert_array(array).contains_exactly(array.duplicate()) - # should fail because the array not contains same elements but in different order - var shuffled = array.duplicate() - shuffled[1] = array[3] - shuffled[3] = array[1] - assert_failure(func(): assert_array(array).contains_exactly(shuffled)) \ - .is_failed() \ - .has_message(""" - Expecting contains exactly elements: - '$source' - do contains (in same order) - '$contains' - but has different order at position '1' - '$A' vs '$B'""" - .dedent() - .trim_prefix("\n") - .replace("$A", GdDefaultValueDecoder.decode(array[1])) - .replace("$B", GdDefaultValueDecoder.decode(array[3])) - .replace("$source", GdDefaultValueDecoder.decode(array)) - .replace("$contains", GdDefaultValueDecoder.decode(shuffled)) - ) - -@warning_ignore("unused_parameter") -func test_override_failure_message(_test :String, array, test_parameters = [ - ["Array", Array()], - ["PackedByteArray", PackedByteArray()], - ["PackedInt32Array", PackedInt32Array()], - ["PackedInt64Array", PackedInt64Array()], - ["PackedFloat32Array", PackedFloat32Array()], - ["PackedFloat64Array", PackedFloat64Array()], - ["PackedStringArray", PackedStringArray()], - ["PackedVector2Array", PackedVector2Array()], - ["PackedVector3Array", PackedVector3Array()], - ["PackedColorArray", PackedColorArray()] ] - ) -> void: - - assert_failure(func(): assert_array(array) \ - .override_failure_message("Custom failure message") \ - .is_null()) \ - .is_failed() \ - .has_message("Custom failure message") diff --git a/addons/gdUnit4/test/asserts/GdUnitResultAssertImplTest.gd b/addons/gdUnit4/test/asserts/GdUnitResultAssertImplTest.gd deleted file mode 100644 index 94d009a..0000000 --- a/addons/gdUnit4/test/asserts/GdUnitResultAssertImplTest.gd +++ /dev/null @@ -1,143 +0,0 @@ -# GdUnit generated TestSuite -class_name GdUnitResultAssertImplTest -extends GdUnitTestSuite - - -# TestSuite generated from -const __source = 'res://addons/gdUnit4/src/asserts/GdUnitResultAssertImpl.gd' - - -func test_is_null(): - assert_result(null).is_null() - - assert_failure(func(): assert_result(GdUnitResult.success("")).is_null()) \ - .is_failed() \ - .has_message('Expecting: \'\' but was <{ "state": 0, "value": "\\"\\"", "warn_msg": "", "err_msg": "" }>') - - -func test_is_not_null(): - assert_result(GdUnitResult.success("")).is_not_null() - - assert_failure(func(): assert_result(null).is_not_null()) \ - .is_failed() \ - .has_message("Expecting: not to be ''") - - -func test_is_empty(): - assert_result(GdUnitResult.empty()).is_empty() - - assert_failure(func(): assert_result(GdUnitResult.warn("a warning")).is_empty()) \ - .is_failed() \ - .has_message("Expecting the result must be a EMPTY but was WARNING:\n 'a warning'") - assert_failure(func(): assert_result(GdUnitResult.error("a error")).is_empty()) \ - .is_failed() \ - .has_message("Expecting the result must be a EMPTY but was ERROR:\n 'a error'") - assert_failure(func(): assert_result(null).is_empty()) \ - .is_failed() \ - .has_message("Expecting the result must be a EMPTY but was .") - - -func test_is_success(): - assert_result(GdUnitResult.success("")).is_success() - - assert_failure(func(): assert_result(GdUnitResult.warn("a warning")).is_success()) \ - .is_failed() \ - .has_message("Expecting the result must be a SUCCESS but was WARNING:\n 'a warning'") - assert_failure(func(): assert_result(GdUnitResult.error("a error")).is_success()) \ - .is_failed() \ - .has_message("Expecting the result must be a SUCCESS but was ERROR:\n 'a error'") - assert_failure(func(): assert_result(null).is_success()) \ - .is_failed() \ - .has_message("Expecting the result must be a SUCCESS but was .") - - -func test_is_warning(): - assert_result(GdUnitResult.warn("a warning")).is_warning() - - assert_failure(func(): assert_result(GdUnitResult.success("value")).is_warning()) \ - .is_failed() \ - .has_message("Expecting the result must be a WARNING but was SUCCESS.") - assert_failure(func(): assert_result(GdUnitResult.error("a error")).is_warning()) \ - .is_failed() \ - .has_message("Expecting the result must be a WARNING but was ERROR:\n 'a error'") - assert_failure(func(): assert_result(null).is_warning()) \ - .is_failed() \ - .has_message("Expecting the result must be a WARNING but was .") - - -func test_is_error(): - assert_result(GdUnitResult.error("a error")).is_error() - - assert_failure(func(): assert_result(GdUnitResult.success("")).is_error()) \ - .is_failed() \ - .has_message("Expecting the result must be a ERROR but was SUCCESS.") - assert_failure(func(): assert_result(GdUnitResult.warn("a warning")).is_error()) \ - .is_failed() \ - .has_message("Expecting the result must be a ERROR but was WARNING:\n 'a warning'") - assert_failure(func(): assert_result(null).is_error()) \ - .is_failed() \ - .has_message("Expecting the result must be a ERROR but was .") - - -func test_contains_message(): - assert_result(GdUnitResult.error("a error")).contains_message("a error") - assert_result(GdUnitResult.warn("a warning")).contains_message("a warning") - - assert_failure(func(): assert_result(GdUnitResult.success("")).contains_message("Error 500")) \ - .is_failed() \ - .has_message("Expecting:\n 'Error 500'\n but the GdUnitResult is a success.") - assert_failure(func(): assert_result(GdUnitResult.warn("Warning xyz!")).contains_message("Warning aaa!")) \ - .is_failed() \ - .has_message("Expecting:\n 'Warning aaa!'\n but was\n 'Warning xyz!'.") - assert_failure(func(): assert_result(GdUnitResult.error("Error 410")).contains_message("Error 500")) \ - .is_failed() \ - .has_message("Expecting:\n 'Error 500'\n but was\n 'Error 410'.") - assert_failure(func(): assert_result(null).contains_message("Error 500")) \ - .is_failed() \ - .has_message("Expecting:\n 'Error 500'\n but was\n ''.") - - -func test_is_value(): - assert_result(GdUnitResult.success("")).is_value("") - var result_value = auto_free(Node.new()) - assert_result(GdUnitResult.success(result_value)).is_value(result_value) - - assert_failure(func(): assert_result(GdUnitResult.success("")).is_value("abc")) \ - .is_failed() \ - .has_message("Expecting to contain same value:\n 'abc'\n but was\n ''.") - assert_failure(func(): assert_result(GdUnitResult.success("abc")).is_value("")) \ - .is_failed() \ - .has_message("Expecting to contain same value:\n ''\n but was\n 'abc'.") - assert_failure(func(): assert_result(GdUnitResult.success(result_value)).is_value("")) \ - .is_failed() \ - .has_message("Expecting to contain same value:\n ''\n but was\n .") - assert_failure(func(): assert_result(null).is_value("")) \ - .is_failed() \ - .has_message("Expecting to contain same value:\n ''\n but was\n ''.") - - -func test_override_failure_message() -> void: - assert_failure(func(): assert_result(GdUnitResult.success("")) \ - .override_failure_message("Custom failure message") \ - .is_null()) \ - .is_failed() \ - .has_message("Custom failure message") - - -# tests if an assert fails the 'is_failure' reflects the failure status -func test_is_failure() -> void: - # initial is false - assert_bool(is_failure()).is_false() - - # checked success assert - assert_result(null).is_null() - assert_bool(is_failure()).is_false() - - # checked faild assert - assert_failure(func(): assert_result(RefCounted.new()).is_null()).is_failed() - assert_bool(is_failure()).is_true() - - # checked next success assert - assert_result(null).is_null() - # is true because we have an already failed assert - assert_bool(is_failure()).is_true() diff --git a/addons/gdUnit4/test/asserts/GdUnitSignalAssertImplTest.gd b/addons/gdUnit4/test/asserts/GdUnitSignalAssertImplTest.gd deleted file mode 100644 index 1c635c6..0000000 --- a/addons/gdUnit4/test/asserts/GdUnitSignalAssertImplTest.gd +++ /dev/null @@ -1,228 +0,0 @@ -# GdUnit generated TestSuite -#warning-ignore-all:unused_argument -#warning-ignore-all:return_value_discarded -class_name GdUnitSignalAssertImplTest -extends GdUnitTestSuite - -# TestSuite generated from -const __source = 'res://addons/gdUnit4/src/asserts/GdUnitSignalAssertImpl.gd' -const GdUnitTools = preload("res://addons/gdUnit4/src/core/GdUnitTools.gd") - - -class TestEmitter extends Node: - signal test_signal_counted(value) - signal test_signal(value :int) - signal test_signal_unused() - - var _trigger_count :int - var _count := 0 - - func _init(trigger_count := 10): - _trigger_count = trigger_count - - func _process(_delta): - if _count >= _trigger_count: - test_signal_counted.emit(_count) - - if _count == 20: - test_signal.emit(10) - test_signal.emit(20) - _count += 1 - - func reset_trigger(trigger_count := 10) -> void: - _trigger_count = trigger_count - - -var signal_emitter :TestEmitter - - -func before_test(): - signal_emitter = auto_free(TestEmitter.new()) - add_child(signal_emitter) - - -# we need to skip await fail test because of an bug in Godot 4.0 stable -func is_skip_fail_await() -> bool: - return Engine.get_version_info().hex < 0x40002 - - -func test_invalid_arg() -> void: - ( - await assert_failure_await(func(): await assert_signal(null).wait_until(50).is_emitted("test_signal_counted")) - ).has_message("Can't wait for signal checked a NULL object.") - ( - await assert_failure_await(func(): await assert_signal(null).wait_until(50).is_not_emitted("test_signal_counted")) - ).has_message("Can't wait for signal checked a NULL object.") - - -func test_unknown_signal() -> void: - ( - await assert_failure_await(func(): await assert_signal(signal_emitter).wait_until(50).is_emitted("unknown")) - ).has_message("Can't wait for non-existion signal 'unknown' checked object 'Node'.") - - -func test_signal_is_emitted_without_args() -> void: - # wait until signal 'test_signal_counted' without args - await assert_signal(signal_emitter).is_emitted("test_signal", [10]) - await assert_signal(signal_emitter).is_emitted("test_signal", [20]) - # wait until signal 'test_signal_unused' where is never emitted - - if is_skip_fail_await(): - return - ( - await assert_failure_await(func(): await assert_signal(signal_emitter).wait_until(500).is_emitted("test_signal_unused")) - ).has_message("Expecting emit signal: 'test_signal_unused()' but timed out after 500ms") - - -func test_signal_is_emitted_with_args() -> void: - # wait until signal 'test_signal_counted' is emitted with value 20 - await assert_signal(signal_emitter).is_emitted("test_signal_counted", [20]) - - if is_skip_fail_await(): - return - ( - await assert_failure_await(func(): await assert_signal(signal_emitter).wait_until(50).is_emitted("test_signal_counted", [500])) - ).has_message("Expecting emit signal: 'test_signal_counted([500])' but timed out after 50ms") - - -func test_signal_is_emitted_use_argument_matcher() -> void: - # wait until signal 'test_signal_counted' is emitted by using any_int() matcher for signal arguments - await assert_signal(signal_emitter).is_emitted("test_signal_counted", [any_int()]) - - # should also work with any() matcher - signal_emitter.reset_trigger() - await assert_signal(signal_emitter).is_emitted("test_signal_counted", [any()]) - - # should fail because the matcher uses the wrong type - signal_emitter.reset_trigger() - ( - await assert_failure_await( func(): await assert_signal(signal_emitter).wait_until(50).is_emitted("test_signal_counted", [any_string()])) - ).has_message("Expecting emit signal: 'test_signal_counted([any_string()])' but timed out after 50ms") - - -func test_signal_is_not_emitted() -> void: - # wait to verify signal 'test_signal_counted()' is not emitted until the first 50ms - await assert_signal(signal_emitter).wait_until(50).is_not_emitted("test_signal_counted") - # wait to verify signal 'test_signal_counted(50)' is not emitted until the NEXT first 80ms - await assert_signal(signal_emitter).wait_until(30).is_not_emitted("test_signal_counted", [50]) - - if is_skip_fail_await(): - return - # until the next 500ms the signal is emitted and ends in a failure - ( - await assert_failure_await(func(): await assert_signal(signal_emitter).wait_until(1000).is_not_emitted("test_signal_counted", [50])) - ).starts_with_message("Expecting do not emit signal: 'test_signal_counted([50])' but is emitted after") - - -func test_override_failure_message() -> void: - if is_skip_fail_await(): - return - - ( - await assert_failure_await(func(): await assert_signal(signal_emitter) \ - .override_failure_message("Custom failure message")\ - .wait_until(100)\ - .is_emitted("test_signal_unused")) - ).has_message("Custom failure message") - - -func test_node_changed_emitting_signals(): - var node :Node2D = auto_free(Node2D.new()) - add_child(node) - - await assert_signal(node).wait_until(200).is_emitted("draw") - - node.visible = false; - await assert_signal(node).wait_until(200).is_emitted("visibility_changed") - - # expecting to fail, we not changed the visibility - #node.visible = true; - if not is_skip_fail_await(): - ( - await assert_failure_await(func(): await assert_signal(node).wait_until(200).is_emitted("visibility_changed")) - ).has_message("Expecting emit signal: 'visibility_changed()' but timed out after 200ms") - - node.show() - await assert_signal(node).wait_until(200).is_emitted("draw") - - -func test_is_signal_exists() -> void: - var node :Node2D = auto_free(Node2D.new()) - - assert_signal(node).is_signal_exists("visibility_changed")\ - .is_signal_exists("draw")\ - .is_signal_exists("visibility_changed")\ - .is_signal_exists("tree_entered")\ - .is_signal_exists("tree_exiting")\ - .is_signal_exists("tree_exited") - - if is_skip_fail_await(): - return - - ( - await assert_failure_await(func(): assert_signal(node).is_signal_exists("not_existing_signal")) - ).has_message("The signal 'not_existing_signal' not exists checked object 'Node2D'.") - - -class MyEmitter extends Node: - - signal my_signal_a - signal my_signal_b(value :String) - - - func do_emit_a() -> void: - my_signal_a.emit() - - - func do_emit_b() -> void: - my_signal_b.emit("foo") - - -func test_monitor_signals() -> void: - # start to watch on the emitter to collect all emitted signals - var emitter_a := monitor_signals(MyEmitter.new()) - var emitter_b := monitor_signals(MyEmitter.new()) - - # verify the signals are not emitted initial - await assert_signal(emitter_a).wait_until(50).is_not_emitted('my_signal_a') - await assert_signal(emitter_a).wait_until(50).is_not_emitted('my_signal_b') - await assert_signal(emitter_b).wait_until(50).is_not_emitted('my_signal_a') - await assert_signal(emitter_b).wait_until(50).is_not_emitted('my_signal_b') - - # emit signal `my_signal_a` on emitter_a - emitter_a.do_emit_a() - await assert_signal(emitter_a).is_emitted('my_signal_a') - - # emit signal `my_signal_b` on emitter_a - emitter_a.do_emit_b() - await assert_signal(emitter_a).is_emitted('my_signal_b', ["foo"]) - # verify emitter_b still has nothing emitted - await assert_signal(emitter_b).wait_until(50).is_not_emitted('my_signal_a') - await assert_signal(emitter_b).wait_until(50).is_not_emitted('my_signal_b') - - # now verify emitter b - emitter_b.do_emit_a() - await assert_signal(emitter_b).wait_until(50).is_emitted('my_signal_a') - - -class ExampleResource extends Resource: - @export var title := "Title": - set(new_value): - title = new_value - changed.emit() - - - func change_title(p_title: String) -> void: - title = p_title - - -func test_monitor_signals_on_resource_set() -> void: - var sut = ExampleResource.new() - var emitter := monitor_signals(sut) - - sut.change_title("Some title") - - # title change should emit "changed" signal - await assert_signal(emitter).is_emitted("changed") - assert_str(sut.title).is_equal("Some title") - diff --git a/addons/gdUnit4/test/asserts/GdUnitStringAssertImplTest.gd b/addons/gdUnit4/test/asserts/GdUnitStringAssertImplTest.gd deleted file mode 100644 index a5dd4dd..0000000 --- a/addons/gdUnit4/test/asserts/GdUnitStringAssertImplTest.gd +++ /dev/null @@ -1,450 +0,0 @@ -# GdUnit generated TestSuite -class_name GdUnitStringAssertImplTest -extends GdUnitTestSuite - -# TestSuite generated from -const __source = 'res://addons/gdUnit4/src/asserts/GdUnitStringAssertImpl.gd' - - - -func test_is_null(): - assert_str(null).is_null() - - assert_failure(func(): assert_str("abc").is_null()) \ - .is_failed() \ - .starts_with_message("Expecting: '' but was 'abc'") - - -func test_is_not_null(): - assert_str("abc").is_not_null() - assert_str(&"abc").is_not_null() - - assert_failure(func(): assert_str(null).is_not_null()) \ - .is_failed() \ - .has_message("Expecting: not to be ''") - - -func test_is_equal(): - assert_str("This is a test message").is_equal("This is a test message") - assert_str("abc").is_equal("abc") - assert_str("abc").is_equal(&"abc") - assert_str(&"abc").is_equal("abc") - assert_str(&"abc").is_equal(&"abc") - - assert_failure(func(): assert_str("This is a test message").is_equal("This is a test Message")) \ - .is_failed() \ - .has_message(""" - Expecting: - 'This is a test Message' - but was - 'This is a test Mmessage'""".dedent().trim_prefix("\n")) - assert_failure(func(): assert_str(null).is_equal("This is a test Message")) \ - .is_failed() \ - .has_message(""" - Expecting: - 'This is a test Message' - but was - ''""".dedent().trim_prefix("\n")) - - -func test_is_equal_pipe_character() -> void: - assert_failure(func(): assert_str("AAA|BBB|CCC").is_equal("AAA|BBB.CCC")) \ - .is_failed() - - -func test_is_equal_ignoring_case(): - assert_str("This is a test message").is_equal_ignoring_case("This is a test Message") - assert_str("This is a test message").is_equal_ignoring_case(&"This is a test Message") - assert_str(&"This is a test message").is_equal_ignoring_case("This is a test Message") - assert_str(&"This is a test message").is_equal_ignoring_case(&"This is a test Message") - - assert_failure(func(): assert_str("This is a test message").is_equal_ignoring_case("This is a Message")) \ - .is_failed() \ - .has_message(""" - Expecting: - 'This is a Message' - but was - 'This is a test Mmessage' (ignoring case)""".dedent().trim_prefix("\n")) - assert_failure(func(): assert_str(null).is_equal_ignoring_case("This is a Message")) \ - .is_failed() \ - .has_message(""" - Expecting: - 'This is a Message' - but was - '' (ignoring case)""".dedent().trim_prefix("\n")) - - -func test_is_not_equal(): - assert_str(null).is_not_equal("This is a test Message") - assert_str("This is a test message").is_not_equal("This is a test Message") - assert_str("This is a test message").is_not_equal(&"This is a test Message") - assert_str(&"This is a test message").is_not_equal("This is a test Message") - assert_str(&"This is a test message").is_not_equal(&"This is a test Message") - - assert_failure(func(): assert_str("This is a test message").is_not_equal("This is a test message")) \ - .is_failed() \ - .has_message(""" - Expecting: - 'This is a test message' - not equal to - 'This is a test message'""".dedent().trim_prefix("\n")) - - -func test_is_not_equal_ignoring_case(): - assert_str(null).is_not_equal_ignoring_case("This is a Message") - assert_str("This is a test message").is_not_equal_ignoring_case("This is a Message") - assert_str("This is a test message").is_not_equal_ignoring_case(&"This is a Message") - assert_str(&"This is a test message").is_not_equal_ignoring_case("This is a Message") - assert_str(&"This is a test message").is_not_equal_ignoring_case(&"This is a Message") - - assert_failure(func(): assert_str("This is a test message").is_not_equal_ignoring_case("This is a test Message")) \ - .is_failed() \ - .has_message(""" - Expecting: - 'This is a test Message' - not equal to - 'This is a test message'""".dedent().trim_prefix("\n")) - - -func test_is_empty(): - assert_str("").is_empty() - assert_str(&"").is_empty() - - assert_failure(func(): assert_str(" ").is_empty()) \ - .is_failed() \ - .has_message(""" - Expecting: - must be empty but was - ' '""".dedent().trim_prefix("\n")) - assert_failure(func(): assert_str("abc").is_empty()) \ - .is_failed() \ - .has_message(""" - Expecting: - must be empty but was - 'abc'""".dedent().trim_prefix("\n")) - assert_failure(func(): assert_str(&"abc").is_empty()) \ - .is_failed() \ - .has_message(""" - Expecting: - must be empty but was - 'abc'""".dedent().trim_prefix("\n")) - assert_failure(func(): assert_str(null).is_empty()) \ - .is_failed() \ - .has_message(""" - Expecting: - must be empty but was - ''""".dedent().trim_prefix("\n")) - - -func test_is_not_empty(): - assert_str(" ").is_not_empty() - assert_str(" ").is_not_empty() - assert_str("abc").is_not_empty() - assert_str(&"abc").is_not_empty() - - assert_failure(func(): assert_str("").is_not_empty()) \ - .is_failed() \ - .has_message(""" - Expecting: - must not be empty""".dedent().trim_prefix("\n")) - assert_failure(func(): assert_str(null).is_not_empty()) \ - .is_failed() \ - .has_message(""" - Expecting: - must not be empty""".dedent().trim_prefix("\n")) - - -func test_contains(): - assert_str("This is a test message").contains("a test") - assert_str("This is a test message").contains(&"a test") - assert_str(&"This is a test message").contains("a test") - assert_str(&"This is a test message").contains(&"a test") - # must fail because of camel case difference - assert_failure(func(): assert_str("This is a test message").contains("a Test")) \ - .is_failed() \ - .has_message(""" - Expecting: - 'This is a test message' - do contains - 'a Test'""".dedent().trim_prefix("\n")) - assert_failure(func(): assert_str(null).contains("a Test")) \ - .is_failed() \ - .has_message(""" - Expecting: - '' - do contains - 'a Test'""".dedent().trim_prefix("\n")) - - -func test_not_contains(): - assert_str(null).not_contains("a tezt") - assert_str("This is a test message").not_contains("a tezt") - assert_str("This is a test message").not_contains(&"a tezt") - assert_str(&"This is a test message").not_contains("a tezt") - assert_str(&"This is a test message").not_contains(&"a tezt") - - assert_failure(func(): assert_str("This is a test message").not_contains("a test")) \ - .is_failed() \ - .has_message(""" - Expecting: - 'This is a test message' - not do contain - 'a test'""".dedent().trim_prefix("\n")) - assert_failure(func(): assert_str(&"This is a test message").not_contains("a test")) \ - .is_failed() \ - .has_message(""" - Expecting: - 'This is a test message' - not do contain - 'a test'""".dedent().trim_prefix("\n")) - - -func test_contains_ignoring_case(): - assert_str("This is a test message").contains_ignoring_case("a Test") - assert_str("This is a test message").contains_ignoring_case(&"a Test") - assert_str(&"This is a test message").contains_ignoring_case("a Test") - assert_str(&"This is a test message").contains_ignoring_case(&"a Test") - - assert_failure(func(): assert_str("This is a test message").contains_ignoring_case("a Tesd")) \ - .is_failed() \ - .has_message(""" - Expecting: - 'This is a test message' - contains - 'a Tesd' - (ignoring case)""".dedent().trim_prefix("\n")) - assert_failure(func(): assert_str(null).contains_ignoring_case("a Tesd")) \ - .is_failed() \ - .has_message(""" - Expecting: - '' - contains - 'a Tesd' - (ignoring case)""".dedent().trim_prefix("\n")) - - -func test_not_contains_ignoring_case(): - assert_str(null).not_contains_ignoring_case("a Test") - assert_str("This is a test message").not_contains_ignoring_case("a Tezt") - assert_str("This is a test message").not_contains_ignoring_case(&"a Tezt") - assert_str(&"This is a test message").not_contains_ignoring_case("a Tezt") - assert_str(&"This is a test message").not_contains_ignoring_case(&"a Tezt") - - assert_failure(func(): assert_str("This is a test message").not_contains_ignoring_case("a Test")) \ - .is_failed() \ - .has_message(""" - Expecting: - 'This is a test message' - not do contains - 'a Test' - (ignoring case)""".dedent().trim_prefix("\n")) - - -func test_starts_with(): - assert_str("This is a test message").starts_with("This is") - assert_str("This is a test message").starts_with(&"This is") - assert_str(&"This is a test message").starts_with("This is") - assert_str(&"This is a test message").starts_with(&"This is") - - assert_failure(func(): assert_str("This is a test message").starts_with("This iss")) \ - .is_failed() \ - .has_message(""" - Expecting: - 'This is a test message' - to start with - 'This iss'""".dedent().trim_prefix("\n")) - assert_failure(func(): assert_str("This is a test message").starts_with("this is")) \ - .is_failed() \ - .has_message(""" - Expecting: - 'This is a test message' - to start with - 'this is'""".dedent().trim_prefix("\n")) - assert_failure(func(): assert_str("This is a test message").starts_with("test")) \ - .is_failed() \ - .has_message(""" - Expecting: - 'This is a test message' - to start with - 'test'""".dedent().trim_prefix("\n")) - assert_failure(func(): assert_str(null).starts_with("test")) \ - .is_failed() \ - .has_message(""" - Expecting: - '' - to start with - 'test'""".dedent().trim_prefix("\n")) - - -func test_ends_with(): - assert_str("This is a test message").ends_with("test message") - assert_str("This is a test message").ends_with(&"test message") - assert_str(&"This is a test message").ends_with("test message") - assert_str(&"This is a test message").ends_with(&"test message") - - assert_failure(func(): assert_str("This is a test message").ends_with("tes message")) \ - .is_failed() \ - .has_message(""" - Expecting: - 'This is a test message' - to end with - 'tes message'""".dedent().trim_prefix("\n")) - assert_failure(func(): assert_str("This is a test message").ends_with("a test")) \ - .is_failed() \ - .has_message(""" - Expecting: - 'This is a test message' - to end with - 'a test'""".dedent().trim_prefix("\n")) - assert_failure(func(): assert_str(null).ends_with("a test")) \ - .is_failed() \ - .has_message(""" - Expecting: - '' - to end with - 'a test'""".dedent().trim_prefix("\n")) - - -func test_has_lenght(): - assert_str("This is a test message").has_length(22) - assert_str(&"This is a test message").has_length(22) - assert_str("").has_length(0) - assert_str(&"").has_length(0) - - assert_failure(func(): assert_str("This is a test message").has_length(23)) \ - .is_failed() \ - .has_message(""" - Expecting size: - '23' but was '22' in - 'This is a test message'""".dedent().trim_prefix("\n")) - assert_failure(func(): assert_str(null).has_length(23)) \ - .is_failed() \ - .has_message(""" - Expecting size: - '23' but was '' in - ''""".dedent().trim_prefix("\n")) - - -func test_has_lenght_less_than(): - assert_str("This is a test message").has_length(23, Comparator.LESS_THAN) - assert_str("This is a test message").has_length(42, Comparator.LESS_THAN) - assert_str(&"This is a test message").has_length(42, Comparator.LESS_THAN) - - assert_failure(func(): assert_str("This is a test message").has_length(22, Comparator.LESS_THAN)) \ - .is_failed() \ - .has_message(""" - Expecting size to be less than: - '22' but was '22' in - 'This is a test message'""".dedent().trim_prefix("\n")) - assert_failure(func(): assert_str(null).has_length(22, Comparator.LESS_THAN)) \ - .is_failed() \ - .has_message(""" - Expecting size to be less than: - '22' but was '' in - ''""".dedent().trim_prefix("\n")) - - -func test_has_lenght_less_equal(): - assert_str("This is a test message").has_length(22, Comparator.LESS_EQUAL) - assert_str("This is a test message").has_length(23, Comparator.LESS_EQUAL) - assert_str(&"This is a test message").has_length(23, Comparator.LESS_EQUAL) - - assert_failure(func(): assert_str("This is a test message").has_length(21, Comparator.LESS_EQUAL)) \ - .is_failed() \ - .has_message(""" - Expecting size to be less than or equal: - '21' but was '22' in - 'This is a test message'""".dedent().trim_prefix("\n")) - assert_failure(func(): assert_str(null).has_length(21, Comparator.LESS_EQUAL)) \ - .is_failed() \ - .has_message(""" - Expecting size to be less than or equal: - '21' but was '' in - ''""".dedent().trim_prefix("\n")) - - -func test_has_lenght_greater_than(): - assert_str("This is a test message").has_length(21, Comparator.GREATER_THAN) - assert_str(&"This is a test message").has_length(21, Comparator.GREATER_THAN) - - assert_failure(func(): assert_str("This is a test message").has_length(22, Comparator.GREATER_THAN)) \ - .is_failed() \ - .has_message(""" - Expecting size to be greater than: - '22' but was '22' in - 'This is a test message'""".dedent().trim_prefix("\n")) - assert_failure(func(): assert_str(null).has_length(22, Comparator.GREATER_THAN)) \ - .is_failed() \ - .has_message(""" - Expecting size to be greater than: - '22' but was '' in - ''""".dedent().trim_prefix("\n")) - - -func test_has_lenght_greater_equal(): - assert_str("This is a test message").has_length(21, Comparator.GREATER_EQUAL) - assert_str("This is a test message").has_length(22, Comparator.GREATER_EQUAL) - assert_str(&"This is a test message").has_length(22, Comparator.GREATER_EQUAL) - - assert_failure(func(): assert_str("This is a test message").has_length(23, Comparator.GREATER_EQUAL)) \ - .is_failed() \ - .has_message(""" - Expecting size to be greater than or equal: - '23' but was '22' in - 'This is a test message'""".dedent().trim_prefix("\n")) - assert_failure(func(): assert_str(null).has_length(23, Comparator.GREATER_EQUAL)) \ - .is_failed() \ - .has_message(""" - Expecting size to be greater than or equal: - '23' but was '' in - ''""".dedent().trim_prefix("\n")) - - -func test_fluentable(): - assert_str("value a").is_not_equal("a")\ - .is_equal("value a")\ - .has_length(7)\ - .is_equal("value a") - - -func test_must_fail_has_invlalid_type(): - assert_failure(func(): assert_str(1)) \ - .is_failed() \ - .has_message("GdUnitStringAssert inital error, unexpected type ") - assert_failure(func(): assert_str(1.3)) \ - .is_failed() \ - .has_message("GdUnitStringAssert inital error, unexpected type ") - assert_failure(func(): assert_str(true)) \ - .is_failed() \ - .has_message("GdUnitStringAssert inital error, unexpected type ") - assert_failure(func(): assert_str(Resource.new())) \ - .is_failed() \ - .has_message("GdUnitStringAssert inital error, unexpected type ") - - -func test_override_failure_message() -> void: - assert_failure(func(): assert_str("")\ - .override_failure_message("Custom failure message")\ - .is_null())\ - .is_failed() \ - .has_message("Custom failure message") - - -# tests if an assert fails the 'is_failure' reflects the failure status -func test_is_failure() -> void: - # initial is false - assert_bool(is_failure()).is_false() - - # checked success assert - assert_str(null).is_null() - assert_bool(is_failure()).is_false() - - # checked failed assert - assert_failure(func(): assert_str(RefCounted.new()).is_null()) \ - .is_failed() - assert_bool(is_failure()).is_true() - - # checked next success assert - assert_str(null).is_null() - # is true because we have an already failed assert - assert_bool(is_failure()).is_true() diff --git a/addons/gdUnit4/test/asserts/GdUnitVectorAssertImplTest.gd b/addons/gdUnit4/test/asserts/GdUnitVectorAssertImplTest.gd deleted file mode 100644 index 7c04da9..0000000 --- a/addons/gdUnit4/test/asserts/GdUnitVectorAssertImplTest.gd +++ /dev/null @@ -1,367 +0,0 @@ -# GdUnit generated TestSuite -class_name GdUnitVectorAssertImplTest -extends GdUnitTestSuite -@warning_ignore('unused_parameter') -@warning_ignore('return_value_discarded') - -# TestSuite generated from -const __source = 'res://addons/gdUnit4/src/asserts/GdUnitVectorAssertImpl.gd' - - -var _test_seta =[ - [null], - [Vector2.ONE], - [Vector2i.ONE], - [Vector3.ONE], - [Vector3i.ONE], - [Vector4.ONE], - [Vector4i.ONE], -] - - -@warning_ignore("unused_parameter") -func test_supported_types(value, test_parameters = _test_seta): - assert_object(assert_vector(value))\ - .is_not_null()\ - .is_instanceof(GdUnitVectorAssert) - - -@warning_ignore("unused_parameter") -func test_unsupported_types(value, details :String, test_parameters =[ - [true, 'bool'], - [42, 'int'], - [42.0, 'float'], - ['foo', 'String'], -] ): - assert_failure(func(): assert_vector(value))\ - .is_failed()\ - .has_message("GdUnitVectorAssert error, the type <%s> is not supported." % details) - - -@warning_ignore("unused_parameter") -func test_is_null(value, test_parameters = _test_seta): - if value == null: - assert_vector(null).is_null() - else: - assert_failure(func(): assert_vector(value).is_null()) \ - .is_failed() \ - .starts_with_message("Expecting: '' but was '%s'" % value) - - -@warning_ignore("unused_parameter") -func test_is_not_null(value, test_parameters = _test_seta): - if value == null: - assert_failure(func(): assert_vector(null).is_not_null()) \ - .is_failed() \ - .has_message("Expecting: not to be ''") - else: - assert_vector(value).is_not_null() - - -@warning_ignore("unused_parameter") -func test_is_equal() -> void: - assert_vector(Vector2.ONE).is_equal(Vector2.ONE) - assert_vector(Vector2.LEFT).is_equal(Vector2.LEFT) - assert_vector(Vector2(1.2, 1.000001)).is_equal(Vector2(1.2, 1.000001)) - - # is not equal - assert_failure(func(): assert_vector(Vector2.ONE).is_equal(Vector2(1.2, 1.000001))) \ - .is_failed() \ - .has_message("Expecting:\n '(1.2, 1.000001)'\n but was\n '(1, 1)'") - # is null - assert_failure(func(): assert_vector(null).is_equal(Vector2(1.2, 1.000001))) \ - .is_failed() \ - .has_message("Expecting:\n '(1.2, 1.000001)'\n but was\n ''") - # comparing different vector types - assert_failure(func(): assert_vector(Vector2.ONE).is_equal(Vector3.ONE)) \ - .is_failed() \ - .has_message("Unexpected type comparison:\n Expecting type 'Vector2' but is 'Vector3'") - - -@warning_ignore("unused_parameter") -func test_is_equal_over_all_types(value, test_parameters = _test_seta) -> void: - assert_vector(value).is_equal(value) - - -func test_is_not_equal() -> void: - assert_vector(null).is_not_equal(Vector2.LEFT) - assert_vector(Vector2.ONE).is_not_equal(Vector2.LEFT) - assert_vector(Vector2.LEFT).is_not_equal(Vector2.ONE) - assert_vector(Vector2(1.2, 1.000001)).is_not_equal(Vector2(1.2, 1.000002)) - - assert_failure(func(): assert_vector(Vector2(1.2, 1.000001)).is_not_equal(Vector2(1.2, 1.000001))) \ - .is_failed() \ - .has_message("Expecting:\n '(1.2, 1.000001)'\n not equal to\n '(1.2, 1.000001)'") - assert_failure(func(): assert_vector(Vector2(1.2, 1.000001)).is_not_equal(Vector3(1.2, 1.000001, 1.0))) \ - .is_failed() \ - .has_message("Unexpected type comparison:\n Expecting type 'Vector2' but is 'Vector3'") - - -@warning_ignore("unused_parameter") -func test_is_not_equal_over_all_types(value, test_parameters = _test_seta) -> void: - var expected = Vector2.LEFT if value == null else value * 2 - assert_vector(value).is_not_equal(expected) - - -func test_is_equal_approx() -> void: - assert_vector(Vector2.ONE).is_equal_approx(Vector2.ONE, Vector2(0.004, 0.004)) - assert_vector(Vector2(0.996, 0.996)).is_equal_approx(Vector2.ONE, Vector2(0.004, 0.004)) - assert_vector(Vector2(1.004, 1.004)).is_equal_approx(Vector2.ONE, Vector2(0.004, 0.004)) - - assert_failure(func(): assert_vector(Vector2(1.005, 1)).is_equal_approx(Vector2.ONE, Vector2(0.004, 0.004))) \ - .is_failed() \ - .has_message("Expecting:\n '(1.005, 1)'\n in range between\n '(0.996, 0.996)' <> '(1.004, 1.004)'") - assert_failure(func(): assert_vector(Vector2(1, 0.995)).is_equal_approx(Vector2.ONE, Vector2(0, 0.004))) \ - .is_failed() \ - .has_message("Expecting:\n '(1, 0.995)'\n in range between\n '(1, 0.996)' <> '(1, 1.004)'") - assert_failure(func(): assert_vector(null).is_equal_approx(Vector2.ONE, Vector2(0, 0.004))) \ - .is_failed() \ - .has_message("Expecting:\n ''\n in range between\n '(1, 0.996)' <> '(1, 1.004)'") - assert_failure(func(): assert_vector(Vector2(1.2, 1.000001)).is_equal_approx(Vector3.ONE, Vector3(1.2, 1.000001, 1.0))) \ - .is_failed() \ - .has_message("Unexpected type comparison:\n Expecting type 'Vector2' but is 'Vector3'") - assert_failure(func(): assert_vector(Vector2(0.878431, 0.505882)).is_equal_approx(Vector2(0.878431, 0.105882), Vector2(0.000001, 0.000001))) \ - .is_failed() \ - .has_message(""" - Expecting: - '(0.878431, 0.505882)' - in range between - '(0.87843, 0.105881)' <> '(0.878432, 0.105883)'""" - .dedent().trim_prefix("\n") - ) - assert_failure(func(): assert_vector(Vector3(0.0, 0.878431, 0.505882)).is_equal_approx(Vector3(0.0, 0.878431, 0.105882), Vector3(0.000001, 0.000001, 0.000001))) \ - .is_failed() \ - .has_message(""" - Expecting: - '(0, 0.878431, 0.505882)' - in range between - '(-0.000001, 0.87843, 0.105881)' <> '(0.000001, 0.878432, 0.105883)'""" - .dedent().trim_prefix("\n") - ) - - -@warning_ignore("unused_parameter") -func test_is_equal_approx_over_all_types(value, expected, approx, test_parameters = [ - [Vector2(0.996, 1.004), Vector2.ONE, Vector2(0.004, 0.004)], - [Vector2i(9, 11), Vector2i(10, 10), Vector2i(1, 1)], - [Vector3(0.996, 0.996, 1.004), Vector3.ONE, Vector3(0.004, 0.004, 0.004)], - [Vector3i(10, 9, 11), Vector3i(10, 10, 10), Vector3i(1, 1, 1)], - [Vector4(0.996, 0.996, 1.004, 1.004), Vector4.ONE, Vector4(0.004, 0.004, 0.004, 0.004)], - [Vector4i(10, 9, 11, 9), Vector4i(10, 10, 10, 10), Vector4i(1, 1, 1, 1)] -]) -> void: - assert_vector(value).is_equal_approx(expected, approx) - - -func test_is_less() -> void: - assert_vector(Vector2.LEFT).is_less(Vector2.ONE) - assert_vector(Vector2(1.2, 1.000001)).is_less(Vector2(1.2, 1.000002)) - - assert_failure(func(): assert_vector(Vector2.ONE).is_less(Vector2.ONE)) \ - .is_failed() \ - .has_message("Expecting to be less than:\n '(1, 1)' but was '(1, 1)'") - assert_failure(func(): assert_vector(Vector2(1.2, 1.000001)).is_less(Vector2(1.2, 1.000001))) \ - .is_failed() \ - .has_message("Expecting to be less than:\n '(1.2, 1.000001)' but was '(1.2, 1.000001)'") - assert_failure(func(): assert_vector(null).is_less(Vector2(1.2, 1.000001))) \ - .is_failed() \ - .has_message("Expecting to be less than:\n '(1.2, 1.000001)' but was ''") - assert_failure(func(): assert_vector(Vector2(1.2, 1.000001)).is_less(Vector3(1.2, 1.000001, 1.0))) \ - .is_failed() \ - .has_message("Unexpected type comparison:\n Expecting type 'Vector2' but is 'Vector3'") - - -@warning_ignore("unused_parameter") -func test_is_less_over_all_types(value, expected, test_parameters = [ - [Vector2(1.0, 1.0), Vector2(1.0001, 1.0001)], - [Vector2i(1, 1), Vector2i(2, 1)], - [Vector3(1.0, 1.0, 1.0), Vector3(1.0001, 1.0001, 1.0)], - [Vector3i(1, 1, 1), Vector3i(2, 1, 1)], - [Vector4(1.0, 1.0, 1.0, 1.0), Vector4(1.0001, 1.0001, 1.0, 1.0)], - [Vector4i(1, 1, 1, 1), Vector4i(2, 1, 1, 1)], -]) -> void: - assert_vector(value).is_less(expected) - - -func test_is_less_equal() -> void: - assert_vector(Vector2.ONE).is_less_equal(Vector2.ONE) - assert_vector(Vector2(1.2, 1.000001)).is_less_equal(Vector2(1.2, 1.000001)) - assert_vector(Vector2(1.2, 1.000001)).is_less_equal(Vector2(1.2, 1.000002)) - - assert_failure(func(): assert_vector(Vector2.ONE).is_less_equal(Vector2.ZERO)) \ - .is_failed() \ - .has_message("Expecting to be less than or equal:\n '(0, 0)' but was '(1, 1)'") - assert_failure(func(): assert_vector(Vector2(1.2, 1.000002)).is_less_equal(Vector2(1.2, 1.000001))) \ - .is_failed() \ - .has_message("Expecting to be less than or equal:\n '(1.2, 1.000001)' but was '(1.2, 1.000002)'") - assert_failure(func(): assert_vector(null).is_less_equal(Vector2(1.2, 1.000001))) \ - .is_failed() \ - .has_message("Expecting to be less than or equal:\n '(1.2, 1.000001)' but was ''") - assert_failure(func(): assert_vector(Vector2(1.2, 1.000002)).is_less_equal(Vector3(1.2, 1.000001, 1.0))) \ - .is_failed() \ - .has_message("Unexpected type comparison:\n Expecting type 'Vector2' but is 'Vector3'") - - -@warning_ignore("unused_parameter") -func test_is_less_equal_over_all_types(value, expected, test_parameters = [ - [Vector2(1.0, 1.0), Vector2(1.0001, 1.0001)], - [Vector2(1.0, 1.0), Vector2(1.0, 1.0)], - [Vector2i(1, 1), Vector2i(2, 1)], - [Vector2i(1, 1), Vector2i(1, 1)], - [Vector3(1.0, 1.0, 1.0), Vector3(1.0001, 1.0001, 1.0)], - [Vector3(1.0, 1.0, 1.0), Vector3(1.0, 1.0, 1.0)], - [Vector3i(1, 1, 1), Vector3i(2, 1, 1)], - [Vector3i(1, 1, 1), Vector3i(1, 1, 1)], - [Vector4(1.0, 1.0, 1.0, 1.0), Vector4(1.0001, 1.0001, 1.0, 1.0)], - [Vector4(1.0, 1.0, 1.0, 1.0), Vector4(1.0, 1.0, 1.0, 1.0)], - [Vector4i(1, 1, 1, 1), Vector4i(2, 1, 1, 1)], - [Vector4i(1, 1, 1, 1), Vector4i(1, 1, 1, 1)], -]) -> void: - assert_vector(value).is_less_equal(expected) - - -func test_is_greater() -> void: - assert_vector(Vector2.ONE).is_greater(Vector2.RIGHT) - assert_vector(Vector2(1.2, 1.000002)).is_greater(Vector2(1.2, 1.000001)) - - assert_failure(func(): assert_vector(Vector2.ZERO).is_greater(Vector2.ONE)) \ - .is_failed() \ - .has_message("Expecting to be greater than:\n '(1, 1)' but was '(0, 0)'") - assert_failure(func(): assert_vector(Vector2(1.2, 1.000001)).is_greater(Vector2(1.2, 1.000001))) \ - .is_failed() \ - .has_message("Expecting to be greater than:\n '(1.2, 1.000001)' but was '(1.2, 1.000001)'") - assert_failure(func(): assert_vector(null).is_greater(Vector2(1.2, 1.000001))) \ - .is_failed() \ - .has_message("Expecting to be greater than:\n '(1.2, 1.000001)' but was ''") - assert_failure(func(): assert_vector(Vector2(1.2, 1.000001)).is_greater(Vector3(1.2, 1.000001, 1.0))) \ - .is_failed() \ - .has_message("Unexpected type comparison:\n Expecting type 'Vector2' but is 'Vector3'") - - -@warning_ignore("unused_parameter") -func test_is_greater_over_all_types(value, expected, test_parameters = [ - [Vector2(1.0001, 1.0001), Vector2(1.0, 1.0)], - [Vector2i(2, 1), Vector2i(1, 1)], - [Vector3(1.0001, 1.0001, 1.0), Vector3(1.0, 1.0, 1.0)], - [Vector3i(2, 1, 1), Vector3i(1, 1, 1)], - [Vector4(1.0001, 1.0001, 1.0, 1.0), Vector4(1.0, 1.0, 1.0, 1.0)], - [Vector4i(2, 1, 1, 1), Vector4i(1, 1, 1, 1)], -]) -> void: - assert_vector(value).is_greater(expected) - - -func test_is_greater_equal() -> void: - assert_vector(Vector2.ONE*2).is_greater_equal(Vector2.ONE) - assert_vector(Vector2.ONE).is_greater_equal(Vector2.ONE) - assert_vector(Vector2(1.2, 1.000001)).is_greater_equal(Vector2(1.2, 1.000001)) - assert_vector(Vector2(1.2, 1.000002)).is_greater_equal(Vector2(1.2, 1.000001)) - - assert_failure(func(): assert_vector(Vector2.ZERO).is_greater_equal(Vector2.ONE)) \ - .is_failed() \ - .has_message("Expecting to be greater than or equal:\n '(1, 1)' but was '(0, 0)'") - assert_failure(func(): assert_vector(Vector2(1.2, 1.000002)).is_greater_equal(Vector2(1.2, 1.000003))) \ - .is_failed() \ - .has_message("Expecting to be greater than or equal:\n '(1.2, 1.000003)' but was '(1.2, 1.000002)'") - assert_failure(func(): assert_vector(null).is_greater_equal(Vector2(1.2, 1.000003))) \ - .is_failed() \ - .has_message("Expecting to be greater than or equal:\n '(1.2, 1.000003)' but was ''") - assert_failure(func(): assert_vector(Vector2(1.2, 1.000002)).is_greater_equal(Vector3(1.2, 1.000003, 1.0))) \ - .is_failed() \ - .has_message("Unexpected type comparison:\n Expecting type 'Vector2' but is 'Vector3'") - - -@warning_ignore("unused_parameter") -func test_is_greater_equal_over_all_types(value, expected, test_parameters = [ - [Vector2(1.0001, 1.0001), Vector2(1.0, 1.0)], - [Vector2(1.0, 1.0), Vector2(1.0, 1.0)], - [Vector2i(2, 1), Vector2i(1, 1)], - [Vector2i(1, 1), Vector2i(1, 1)], - [Vector3(1.0001, 1.0001, 1.0), Vector3(1.0, 1.0, 1.0)], - [Vector3(1.0, 1.0, 1.0), Vector3(1.0, 1.0, 1.0)], - [Vector3i(2, 1, 1), Vector3i(1, 1, 1)], - [Vector3i(1, 1, 1), Vector3i(1, 1, 1)], - [Vector4(1.0001, 1.0001, 1.0, 1.0), Vector4(1.0, 1.0, 1.0, 1.0)], - [Vector4(1.0, 1.0, 1.0, 1.0), Vector4(1.0, 1.0, 1.0, 1.0)], - [Vector4i(2, 1, 1, 1), Vector4i(1, 1, 1, 1)], - [Vector4i(1, 1, 1, 1), Vector4i(1, 1, 1, 1)], -]) -> void: - assert_vector(value).is_greater_equal(expected) - - -func test_is_between(fuzzer = Fuzzers.rangev2(Vector2.ZERO, Vector2.ONE)): - var value :Vector2 = fuzzer.next_value() - assert_vector(value).is_between(Vector2.ZERO, Vector2.ONE) - - assert_failure(func(): assert_vector(Vector2(1, 1.00001)).is_between(Vector2.ZERO, Vector2.ONE)) \ - .is_failed() \ - .has_message("Expecting:\n '(1, 1.00001)'\n in range between\n '(0, 0)' <> '(1, 1)'") - assert_failure(func(): assert_vector(null).is_between(Vector2.ZERO, Vector2.ONE)) \ - .is_failed() \ - .has_message("Expecting:\n ''\n in range between\n '(0, 0)' <> '(1, 1)'") - assert_failure(func(): assert_vector(Vector2(1, 1.00001)).is_between(Vector2.ZERO, Vector3.ONE)) \ - .is_failed() \ - .has_message("Unexpected type comparison:\n Expecting type 'Vector2' but is 'Vector3'") - - -@warning_ignore("unused_parameter") -func test_is_between_over_all_types(value, from, to, test_parameters = [ - [Vector2(1.2, 1.2), Vector2(1.0, 1.0), Vector2(1.2, 1.2)], - [Vector2i(1, 1), Vector2i(1, 1), Vector2i(2, 2)], - [Vector3(1.2, 1.2, 1.2), Vector3(1.0, 1.0, 1.0), Vector3(1.2, 1.2, 1.2)], - [Vector3i(1, 1, 1), Vector3i(1, 1, 1), Vector3i(2, 2, 2)], - [Vector4(1.2, 1.2, 1.2, 1.2), Vector4(1.0, 1.0, 1.0, 1.0), Vector4(1.2, 1.2, 1.2, 1.2)], - [Vector4i(1, 1, 1, 1), Vector4i(1, 1, 1, 1), Vector4i(2, 2, 2, 2)], -]) -> void: - assert_vector(value).is_between(from, to) - - -func test_is_not_between(fuzzer = Fuzzers.rangev2(Vector2.ONE, Vector2.ONE*2)): - var value :Vector2 = fuzzer.next_value() - assert_vector(null).is_not_between(Vector2.ZERO, Vector2.ONE) - assert_vector(value).is_not_between(Vector2.ZERO, Vector2.ONE) - - assert_failure(func(): assert_vector(Vector2.ONE).is_not_between(Vector2.ZERO, Vector2.ONE)) \ - .is_failed() \ - .has_message("Expecting:\n '(1, 1)'\n not in range between\n '(0, 0)' <> '(1, 1)'") - assert_failure(func(): assert_vector(Vector2.ONE).is_not_between(Vector3.ZERO, Vector2.ONE)) \ - .is_failed() \ - .has_message("Unexpected type comparison:\n Expecting type 'Vector2' but is 'Vector3'") - - -@warning_ignore("unused_parameter") -func test_is_not_between_over_all_types(value, from, to, test_parameters = [ - [Vector2(3.2, 1.2), Vector2(1.0, 1.0), Vector2(1.2, 1.2)], - [Vector2i(3, 1), Vector2i(1, 1), Vector2i(2, 2)], - [Vector3(3.2, 1.2, 1.2), Vector3(1.0, 1.0, 1.0), Vector3(1.2, 1.2, 1.2)], - [Vector3i(3, 1, 1), Vector3i(1, 1, 1), Vector3i(2, 2, 2)], - [Vector4(3.2, 1.2, 1.2, 1.2), Vector4(1.0, 1.0, 1.0, 1.0), Vector4(1.2, 1.2, 1.2, 1.2)], - [Vector4i(3, 1, 1, 1), Vector4i(1, 1, 1, 1), Vector4i(2, 2, 2, 2)], -]) -> void: - assert_vector(value).is_not_between(from, to) - - -func test_override_failure_message() -> void: - assert_failure(func(): assert_vector(Vector2.ONE) \ - .override_failure_message("Custom failure message") \ - .is_null()) \ - .is_failed() \ - .has_message("Custom failure message") - - -# tests if an assert fails the 'is_failure' reflects the failure status -func test_is_failure() -> void: - # initial is false - assert_bool(is_failure()).is_false() - - # checked success assert - assert_vector(null).is_null() - assert_bool(is_failure()).is_false() - - # checked faild assert - assert_failure(func(): assert_vector(RefCounted.new()).is_null()) \ - .is_failed() - assert_bool(is_failure()).is_true() - - # checked next success assert - assert_vector(null).is_null() - # is true because we have an already failed assert - assert_bool(is_failure()).is_true() diff --git a/addons/gdUnit4/test/cmd/CmdArgumentParserTest.gd b/addons/gdUnit4/test/cmd/CmdArgumentParserTest.gd deleted file mode 100644 index bc2207b..0000000 --- a/addons/gdUnit4/test/cmd/CmdArgumentParserTest.gd +++ /dev/null @@ -1,120 +0,0 @@ -# GdUnit generated TestSuite -class_name CmdArgumentParserTest -extends GdUnitTestSuite - -# TestSuite generated from -const __source = 'res://addons/gdUnit4/src/cmd/CmdArgumentParser.gd' - -var option_a := CmdOption.new("-a", "some help text a", "some description a") -var option_f := CmdOption.new("-f, --foo", "some help text foo", "some description foo") -var option_b := CmdOption.new("-b, --bar", "-b ", "comand with required argument", TYPE_STRING) -var option_c := CmdOption.new("-c, --calc", "-c [value]", "command with optional argument", TYPE_STRING, true) -var option_x := CmdOption.new("-x", "some help text x", "some description x") - -var _cmd_options :CmdOptions - - -func before(): - # setup command options - _cmd_options = CmdOptions.new([ - option_a, - option_f, - option_b, - option_c, - ], - # advnaced options - [ - option_x, - ]) - - -func test_parse_success(): - var parser := CmdArgumentParser.new(_cmd_options, "CmdTool.gd") - - assert_result(parser.parse([])).is_empty() - # check with godot cmd argumnents before tool argument - assert_result(parser.parse(["-d", "dir/dir/CmdTool.gd"])).is_empty() - - # if valid argument set than don't show the help by default - var result := parser.parse(["-d", "dir/dir/CmdTool.gd", "-a"]) - assert_result(result).is_success() - assert_array(result.value()).contains_exactly([ - CmdCommand.new("-a"), - ]) - - -func test_parse_success_required_arg(): - var parser := CmdArgumentParser.new(_cmd_options, "CmdTool.gd") - - var result := parser.parse(["-d", "dir/dir/CmdTool.gd", "-a", "-b", "valueA", "-b", "valueB"]) - assert_result(result).is_success() - assert_array(result.value()).contains_exactly([ - CmdCommand.new("-a"), - CmdCommand.new("-b", ["valueA", "valueB"]), - ]) - - # useing command long term - result = parser.parse(["-d", "dir/dir/CmdTool.gd", "-a", "--bar", "value"]) - assert_result(result).is_success() - assert_array(result.value()).contains_exactly([ - CmdCommand.new("-a"), - CmdCommand.new("-b", ["value"]) - ]) - - -func test_parse_success_optional_arg(): - var parser := CmdArgumentParser.new(_cmd_options, "CmdTool.gd") - - # without argument - var result := parser.parse(["-d", "dir/dir/CmdTool.gd", "-c", "-a"]) - assert_result(result).is_success() - assert_array(result.value()).contains_exactly([ - CmdCommand.new("-c"), - CmdCommand.new("-a") - ]) - - # without argument at end - result = parser.parse(["-d", "dir/dir/CmdTool.gd", "-a", "-c"]) - assert_result(result).is_success() - assert_array(result.value()).contains_exactly([ - CmdCommand.new("-a"), - CmdCommand.new("-c") - ]) - - # with argument - result = parser.parse(["-d", "dir/dir/CmdTool.gd", "-c", "argument", "-a"]) - assert_result(result).is_success() - assert_array(result.value()).contains_exactly([ - CmdCommand.new("-c", ["argument"]), - CmdCommand.new("-a") - ]) - - -func test_parse_success_repead_cmd_args(): - var parser := CmdArgumentParser.new(_cmd_options, "CmdTool.gd") - - # without argument - var result := parser.parse(["-d", "dir/dir/CmdTool.gd", "-c", "argument", "-a"]) - assert_result(result).is_success() - assert_array(result.value()).contains_exactly([ - CmdCommand.new("-c", ["argument"]), - CmdCommand.new("-a") - ]) - - # with repeading commands argument - result = parser.parse(["-d", "dir/dir/CmdTool.gd", "-c", "argument1", "-a", "-c", "argument2", "-c", "argument3"]) - assert_result(result).is_success() - assert_array(result.value()).contains_exactly([ - CmdCommand.new("-c", ["argument1", "argument2", "argument3"]), - CmdCommand.new("-a") - ]) - - -func test_parse_error(): - var parser := CmdArgumentParser.new(_cmd_options, "CmdTool.gd") - - assert_result(parser.parse([])).is_empty() - - # if invalid arguemens set than return with error and show the help by default - assert_result(parser.parse(["-d", "dir/dir/CmdTool.gd", "-unknown"])).is_error()\ - .contains_message("Unknown '-unknown' command!") diff --git a/addons/gdUnit4/test/cmd/CmdCommandHandlerTest.gd b/addons/gdUnit4/test/cmd/CmdCommandHandlerTest.gd deleted file mode 100644 index 4630309..0000000 --- a/addons/gdUnit4/test/cmd/CmdCommandHandlerTest.gd +++ /dev/null @@ -1,123 +0,0 @@ -# GdUnit generated TestSuite -class_name CmdCommandHandlerTest -extends GdUnitTestSuite - -# TestSuite generated from -const __source = 'res://addons/gdUnit4/src/cmd/CmdCommandHandler.gd' - -var _cmd_options: CmdOptions -var _cmd_instance: TestCommands - - -# small example of command class -class TestCommands: - func cmd_a() -> String: - return "cmd_a" - - func cmd_foo() -> String: - return "cmd_foo" - - func cmd_bar(value :String) -> String: - return value - - func cmd_bar2(value_a: String, value_b: String) -> Array: - return [value_a, value_b] - - func cmd_x() -> String: - return "cmd_x" - - -func before() -> void: - # setup command options - _cmd_options = CmdOptions.new([ - CmdOption.new("-a", "some help text a", "some description a"), - CmdOption.new("-f, --foo", "some help text foo", "some description foo"), - CmdOption.new("-b, --bar", "some help text bar", "some description bar"), - CmdOption.new("-b2, --bar2", "some help text bar", "some description bar"), - ], - # advnaced options - [ - CmdOption.new("-x", "some help text x", "some description x"), - ]) - _cmd_instance = TestCommands.new() - - -func test__validate_no_registerd_commands() -> void: - var cmd_handler := CmdCommandHandler.new(_cmd_options) - - assert_result(cmd_handler._validate()).is_success() - - -func test__validate_registerd_commands() -> void: - var cmd_handler: = CmdCommandHandler.new(_cmd_options) - cmd_handler.register_cb("-a", Callable(_cmd_instance, "cmd_a")) - cmd_handler.register_cb("-f", Callable(_cmd_instance, "cmd_foo")) - cmd_handler.register_cb("-b", Callable(_cmd_instance, "cmd_bar")) - - assert_result(cmd_handler._validate()).is_success() - - -func test__validate_registerd_unknown_commands() -> void: - var cmd_handler: = CmdCommandHandler.new(_cmd_options) - cmd_handler.register_cb("-a", Callable(_cmd_instance, "cmd_a")) - cmd_handler.register_cb("-d", Callable(_cmd_instance, "cmd_foo")) - cmd_handler.register_cb("-b", Callable(_cmd_instance, "cmd_bar")) - cmd_handler.register_cb("-y", Callable(_cmd_instance, "cmd_x")) - - assert_result(cmd_handler._validate())\ - .is_error()\ - .contains_message("The command '-d' is unknown, verify your CmdOptions!\nThe command '-y' is unknown, verify your CmdOptions!") - - -func test__validate_registerd_invalid_callbacks() -> void: - var cmd_handler := CmdCommandHandler.new(_cmd_options) - cmd_handler.register_cb("-a", Callable(_cmd_instance, "cmd_a")) - cmd_handler.register_cb("-f") - cmd_handler.register_cb("-b", Callable(_cmd_instance, "cmd_not_exists")) - - assert_result(cmd_handler._validate())\ - .is_error()\ - .contains_message("Invalid function reference for command '-b', Check the function reference!") - - -func test__validate_registerd_register_same_callback_twice() -> void: - var cmd_handler: = CmdCommandHandler.new(_cmd_options) - cmd_handler.register_cb("-a", Callable(_cmd_instance, "cmd_a")) - cmd_handler.register_cb("-b", Callable(_cmd_instance, "cmd_a")) - if cmd_handler._enhanced_fr_test: - assert_result(cmd_handler._validate())\ - .is_error()\ - .contains_message("The function reference 'cmd_a' already registerd for command '-a'!") - - -func test_execute_no_commands() -> void: - var cmd_handler: = CmdCommandHandler.new(_cmd_options) - assert_result(cmd_handler.execute([])).is_success() - - -func test_execute_commands_no_cb_registered() -> void: - var cmd_handler: = CmdCommandHandler.new(_cmd_options) - assert_result(cmd_handler.execute([CmdCommand.new("-a")])).is_success() - - -func test_execute_commands_with_cb_registered() -> void: - var cmd_handler: = CmdCommandHandler.new(_cmd_options) - var cmd_spy = spy(_cmd_instance) - - cmd_handler.register_cb("-a", Callable(cmd_spy, "cmd_a")) - cmd_handler.register_cb("-b", Callable(cmd_spy, "cmd_bar")) - cmd_handler.register_cbv("-b2", Callable(cmd_spy, "cmd_bar2")) - - assert_result(cmd_handler.execute([CmdCommand.new("-a")])).is_success() - verify(cmd_spy).cmd_a() - verify_no_more_interactions(cmd_spy) - - reset(cmd_spy) - assert_result(cmd_handler.execute([ - CmdCommand.new("-a"), - CmdCommand.new("-b", ["some_value"]), - CmdCommand.new("-b2", ["value1", "value2"])])).is_success() - verify(cmd_spy).cmd_a() - verify(cmd_spy).cmd_bar("some_value") - verify(cmd_spy).cmd_bar2("value1", "value2") - verify_no_more_interactions(cmd_spy) diff --git a/addons/gdUnit4/test/cmd/CmdCommandTest.gd b/addons/gdUnit4/test/cmd/CmdCommandTest.gd deleted file mode 100644 index 32cfaa6..0000000 --- a/addons/gdUnit4/test/cmd/CmdCommandTest.gd +++ /dev/null @@ -1,32 +0,0 @@ -# GdUnit generated TestSuite -class_name CmdCommandTest -extends GdUnitTestSuite - -# TestSuite generated from -const __source = 'res://addons/gdUnit4/src/cmd/CmdCommand.gd' - - -func test_create(): - var cmd_a := CmdCommand.new("cmd_a") - assert_str(cmd_a.name()).is_equal("cmd_a") - assert_array(cmd_a.arguments()).is_empty() - - var cmd_b := CmdCommand.new("cmd_b", ["arg1"]) - assert_str(cmd_b.name()).is_equal("cmd_b") - assert_array(cmd_b.arguments()).contains_exactly(["arg1"]) - - assert_object(cmd_a).is_not_equal(cmd_b) - - -func test_add_argument(): - var cmd_a := CmdCommand.new("cmd_a") - cmd_a.add_argument("arg1") - cmd_a.add_argument("arg2") - assert_str(cmd_a.name()).is_equal("cmd_a") - assert_array(cmd_a.arguments()).contains_exactly(["arg1", "arg2"]) - - var cmd_b := CmdCommand.new("cmd_b", ["arg1"]) - cmd_b.add_argument("arg2") - cmd_b.add_argument("arg3") - assert_str(cmd_b.name()).is_equal("cmd_b") - assert_array(cmd_b.arguments()).contains_exactly(["arg1", "arg2", "arg3"]) diff --git a/addons/gdUnit4/test/cmd/CmdConsoleTest.gd b/addons/gdUnit4/test/cmd/CmdConsoleTest.gd deleted file mode 100644 index 3fdb150..0000000 --- a/addons/gdUnit4/test/cmd/CmdConsoleTest.gd +++ /dev/null @@ -1,85 +0,0 @@ -# GdUnit generated TestSuite -class_name CmdConsoleTest -extends GdUnitTestSuite - -# TestSuite generated from -const __source = 'res://addons/gdUnit4/src/cmd/CmdConsole.gd' - - -func test_print_color_default() -> void: - var console :CmdConsole = spy(CmdConsole.new()) - - console.print_color("test message", Color.RED) - verify(console).color(Color.RED) - verify(console).end_color() - verify(console).printl("test message") - verify(console).bold(false) - verify(console).italic(false) - verify(console).underline(false) - verify(console, 0).new_line() - - reset(console) - console.print_color("test message2", Color.BLUE) - verify(console).color(Color.BLUE) - verify(console).end_color() - verify(console).printl("test message2") - verify(console).bold(false) - verify(console).italic(false) - verify(console).underline(false) - verify(console, 0).new_line() - - -func test_print_color_with_flags() -> void: - var console :CmdConsole = spy(CmdConsole.new()) - - # bold - console.print_color("test message", Color.RED, CmdConsole.BOLD) - verify(console).bold(true) - verify(console).italic(false) - verify(console).underline(false) - reset(console) - - # italic - console.print_color("test message", Color.RED, CmdConsole.ITALIC) - verify(console).bold(false) - verify(console).italic(true) - verify(console).underline(false) - reset(console) - - # underline - console.print_color("test message", Color.RED, CmdConsole.UNDERLINE) - verify(console).bold(false) - verify(console).italic(false) - verify(console).underline(true) - reset(console) - - # combile italic & underline - console.print_color("test message", Color.RED, CmdConsole.ITALIC|CmdConsole.UNDERLINE) - verify(console).bold(false) - verify(console).italic(true) - verify(console).underline(true) - reset(console) - - # combile bold & italic - console.print_color("test message", Color.RED, CmdConsole.BOLD|CmdConsole.ITALIC) - verify(console).bold(true) - verify(console).italic(true) - verify(console).underline(false) - reset(console) - - # combile all - console.print_color("test message", Color.RED, CmdConsole.BOLD|CmdConsole.ITALIC|CmdConsole.UNDERLINE) - verify(console).bold(true) - verify(console).italic(true) - verify(console).underline(true) - reset(console) - - -func test_prints_color() -> void: - var console :CmdConsole = spy(CmdConsole.new()) - - console.prints_color("test message", Color.RED, CmdConsole.BOLD|CmdConsole.ITALIC) - # verify prints delegates to print_color - verify(console).print_color("test message", Color.RED, CmdConsole.BOLD|CmdConsole.ITALIC) - # and adds a new line - verify(console).new_line() diff --git a/addons/gdUnit4/test/cmd/CmdOptionTest.gd b/addons/gdUnit4/test/cmd/CmdOptionTest.gd deleted file mode 100644 index f9fcf83..0000000 --- a/addons/gdUnit4/test/cmd/CmdOptionTest.gd +++ /dev/null @@ -1,51 +0,0 @@ -# GdUnit generated TestSuite -class_name CmdOptionTest -extends GdUnitTestSuite - -# TestSuite generated from -const __source = 'res://addons/gdUnit4/src/cmd/CmdOption.gd' - - -func test_commands(): - assert_array(CmdOption.new("-a", "help a", "describe a").commands())\ - .contains_exactly(["-a"]) - assert_array(CmdOption.new("-a, --aaa", "help a", "describe a").commands())\ - .contains_exactly(["-a", "--aaa"]) - # containing space or tabs - assert_array(CmdOption.new("-b , --bb ", "help a", "describe a")\ - .commands()).contains_exactly(["-b", "--bb"]) - - -func test_short_command(): - assert_str(CmdOption.new("-a, --aaa", "help a", "describe a").short_command()).is_equal("-a") - - -func test_help(): - assert_str(CmdOption.new("-a, --aaa", "help a", "describe a").help()).is_equal("help a") - - -func test_description(): - assert_str(CmdOption.new("-a, --aaa", "help a", "describe a").description()).is_equal("describe a") - - -func test_type(): - assert_int(CmdOption.new("-a", "", "").type()).is_equal(TYPE_NIL) - assert_int(CmdOption.new("-a", "", "", TYPE_STRING).type()).is_equal(TYPE_STRING) - assert_int(CmdOption.new("-a", "", "", TYPE_BOOL).type()).is_equal(TYPE_BOOL) - - -func test_is_argument_optional(): - assert_bool(CmdOption.new("-a", "", "").is_argument_optional()).is_false() - assert_bool(CmdOption.new("-a", "", "", TYPE_BOOL, false).is_argument_optional()).is_false() - assert_bool(CmdOption.new("-a", "", "", TYPE_BOOL, true).is_argument_optional()).is_true() - - -func test_has_argument(): - assert_bool(CmdOption.new("-a", "", "").has_argument()).is_false() - assert_bool(CmdOption.new("-a", "", "", TYPE_NIL).has_argument()).is_false() - assert_bool(CmdOption.new("-a", "", "", TYPE_BOOL).has_argument()).is_true() - - -func test_describe(): - assert_str(CmdOption.new("-a, --aaa", "help a", "describe a").describe())\ - .is_equal(' ["-a", "--aaa"] describe a \n help a\n') diff --git a/addons/gdUnit4/test/cmd/CmdOptionsTest.gd b/addons/gdUnit4/test/cmd/CmdOptionsTest.gd deleted file mode 100644 index 911b2e2..0000000 --- a/addons/gdUnit4/test/cmd/CmdOptionsTest.gd +++ /dev/null @@ -1,57 +0,0 @@ -# GdUnit generated TestSuite -class_name CmdOptionsTest -extends GdUnitTestSuite - -# TestSuite generated from -const __source = 'res://addons/gdUnit4/src/cmd/CmdOptions.gd' - - -var option_a := CmdOption.new("-a", "some help text a", "some description a") -var option_f := CmdOption.new("-f, --foo", "some help text foo", "some description foo") -var option_b := CmdOption.new("-b, --bar", "some help text bar", "some description bar") -var option_x := CmdOption.new("-x", "some help text x", "some description x") - -var _cmd_options :CmdOptions - - -func before(): - # setup command options - _cmd_options = CmdOptions.new([ - option_a, - option_f, - option_b, - ], - # advnaced options - [ - option_x, - ]) - - -func test_get_option(): - assert_object(_cmd_options.get_option("-a")).is_same(option_a) - assert_object(_cmd_options.get_option("-f")).is_same(option_f) - assert_object(_cmd_options.get_option("--foo")).is_same(option_f) - assert_object(_cmd_options.get_option("-b")).is_same(option_b) - assert_object(_cmd_options.get_option("--bar")).is_same(option_b) - assert_object(_cmd_options.get_option("-x")).is_same(option_x) - # for not existsing command - assert_object(_cmd_options.get_option("-z")).is_null() - - -func test_default_options(): - assert_array(_cmd_options.default_options()).contains_exactly([ - option_a, - option_f, - option_b]) - - -func test_advanced_options(): - assert_array(_cmd_options.advanced_options()).contains_exactly([option_x]) - - -func test_options(): - assert_array(_cmd_options.options()).contains_exactly([ - option_a, - option_f, - option_b, - option_x]) diff --git a/addons/gdUnit4/test/core/ExampleTestSuite.cs b/addons/gdUnit4/test/core/ExampleTestSuite.cs deleted file mode 100644 index 17b591a..0000000 --- a/addons/gdUnit4/test/core/ExampleTestSuite.cs +++ /dev/null @@ -1,21 +0,0 @@ -namespace GdUnit4.Tests.Resource -{ - using static Assertions; - - [TestSuite] - public partial class ExampleTestSuiteA - { - - [TestCase] - public void TestCase1() - { - AssertBool(true).IsEqual(true); - } - - [TestCase] - public void TestCase2() - { - AssertBool(false).IsEqual(false); - } - } -} diff --git a/addons/gdUnit4/test/core/GdArrayToolsTest.gd b/addons/gdUnit4/test/core/GdArrayToolsTest.gd deleted file mode 100644 index 350527f..0000000 --- a/addons/gdUnit4/test/core/GdArrayToolsTest.gd +++ /dev/null @@ -1,154 +0,0 @@ -# GdUnit generated TestSuite -class_name GdArrayToolsTest -extends GdUnitTestSuite -@warning_ignore('unused_parameter') -@warning_ignore('return_value_discarded') - -# TestSuite generated from -const __source = 'res://addons/gdUnit4/src/core/GdArrayTools.gd' - - -@warning_ignore('unused_parameter') -func test_as_string(_test :String, value, expected :String, test_parameters = [ - ['Array', Array([1, 2]), '[1, 2]'], - ['Array', Array([1.0, 2.212]), '[1.000000, 2.212000]'], - ['Array', Array([true, false]), '[true, false]'], - ['Array', Array(["1", "2"]), '["1", "2"]'], - ['Array', Array([Vector2.ZERO, Vector2.LEFT]), '[Vector2(), Vector2(-1, 0)]'], - ['Array', Array([Vector3.ZERO, Vector3.LEFT]), '[Vector3(), Vector3(-1, 0, 0)]'], - ['Array', Array([Color.RED, Color.GREEN]), '[Color(1, 0, 0, 1), Color(0, 1, 0, 1)]'], - ['ArrayInt', Array([1, 2]) as Array[int], '[1, 2]'], - ['ArrayFloat', Array([1.0, 2.212]) as Array[float], '[1.000000, 2.212000]'], - ['ArrayBool', Array([true, false]) as Array[bool], '[true, false]'], - ['ArrayString', Array(["1", "2"]) as Array[String], '["1", "2"]'], - ['ArrayVector2', Array([Vector2.ZERO, Vector2.LEFT]) as Array[Vector2], '[Vector2(), Vector2(-1, 0)]'], - ['ArrayVector2i', Array([Vector2i.ZERO, Vector2i.LEFT]) as Array[Vector2i], '[Vector2i(), Vector2i(-1, 0)]'], - ['ArrayVector3', Array([Vector3.ZERO, Vector3.LEFT]) as Array[Vector3], '[Vector3(), Vector3(-1, 0, 0)]'], - ['ArrayVector3i', Array([Vector3i.ZERO, Vector3i.LEFT]) as Array[Vector3i], '[Vector3i(), Vector3i(-1, 0, 0)]'], - ['ArrayVector4', Array([Vector4.ZERO, Vector4.ONE]) as Array[Vector4], '[Vector4(), Vector4(1, 1, 1, 1)]'], - ['ArrayVector4i', Array([Vector4i.ZERO, Vector4i.ONE]) as Array[Vector4i], '[Vector4i(), Vector4i(1, 1, 1, 1)]'], - ['ArrayColor', Array([Color.RED, Color.GREEN]) as Array[Color], '[Color(1, 0, 0, 1), Color(0, 1, 0, 1)]'], - ['PackedByteArray', PackedByteArray([1, 2]), 'PackedByteArray[1, 2]'], - ['PackedInt32Array', PackedInt32Array([1, 2]), 'PackedInt32Array[1, 2]'], - ['PackedInt64Array', PackedInt64Array([1, 2]), 'PackedInt64Array[1, 2]'], - ['PackedFloat32Array', PackedFloat32Array([1, 2.212]), 'PackedFloat32Array[1.000000, 2.212000]'], - ['PackedFloat64Array', PackedFloat64Array([1, 2.212]), 'PackedFloat64Array[1.000000, 2.212000]'], - ['PackedStringArray', PackedStringArray([1, 2]), 'PackedStringArray["1", "2"]'], - ['PackedVector2Array', PackedVector2Array([Vector2.ZERO, Vector2.LEFT]), 'PackedVector2Array[Vector2(), Vector2(-1, 0)]'], - ['PackedVector3Array', PackedVector3Array([Vector3.ZERO, Vector3.LEFT]), 'PackedVector3Array[Vector3(), Vector3(-1, 0, 0)]'], - ['PackedColorArray', PackedColorArray([Color.RED, Color.GREEN]), 'PackedColorArray[Color(1, 0, 0, 1), Color(0, 1, 0, 1)]'], -]) -> void: - - assert_that(GdArrayTools.as_string(value)).is_equal(expected) - - -func test_as_string_simple_format(): - var value := PackedStringArray(["a", "b"]) - prints(GdArrayTools.as_string(value, false)) - assert_that(GdArrayTools.as_string(value, false)).is_equal('[a, b]') - - -@warning_ignore("unused_parameter") -func test_is_array_type(_test :String, value, expected :bool, test_parameters = [ - ['bool', true, false], - ['int', 42, false], - ['float', 1.21, false], - ['String', "abc", false], - ['Dictionary', {}, false], - ['RefCounted', RefCounted.new(), false], - ['Array', Array([1, 2]), true], - ['Array', Array([1.0, 2.212]), true], - ['Array', Array([true, false]), true], - ['Array', Array(["1", "2"]), true], - ['Array', Array([Vector2.ZERO, Vector2.LEFT]), true], - ['Array', Array([Vector3.ZERO, Vector3.LEFT]), true], - ['Array', Array([Color.RED, Color.GREEN]), true], - ['ArrayInt', Array([1, 2]) as Array[int], true], - ['ArrayFloat', Array([1.0, 2.212]) as Array[float], true], - ['ArrayBool', Array([true, false]) as Array[bool], true], - ['ArrayString', Array(["1", "2"]) as Array[String], true], - ['ArrayVector2', Array([Vector2.ZERO, Vector2.LEFT]) as Array[Vector2], true], - ['ArrayVector2i', Array([Vector2i.ZERO, Vector2i.LEFT]) as Array[Vector2i], true], - ['ArrayVector3', Array([Vector3.ZERO, Vector3.LEFT]) as Array[Vector3], true], - ['ArrayVector3i', Array([Vector3i.ZERO, Vector3i.LEFT]) as Array[Vector3i], true], - ['ArrayVector4', Array([Vector4.ZERO, Vector4.ONE]) as Array[Vector4], true], - ['ArrayVector4i', Array([Vector4i.ZERO, Vector4i.ONE]) as Array[Vector4i], true], - ['ArrayColor', Array([Color.RED, Color.GREEN]) as Array[Color], true], - ['PackedByteArray', PackedByteArray([1, 2]), true], - ['PackedInt32Array', PackedInt32Array([1, 2]), true], - ['PackedInt64Array', PackedInt64Array([1, 2]), true], - ['PackedFloat32Array', PackedFloat32Array([1, 2.212]), true], - ['PackedFloat64Array', PackedFloat64Array([1, 2.212]), true], - ['PackedStringArray', PackedStringArray([1, 2]), true], - ['PackedVector2Array', PackedVector2Array([Vector2.ZERO, Vector2.LEFT]), true], - ['PackedVector3Array', PackedVector3Array([Vector3.ZERO, Vector3.LEFT]), true], - ['PackedColorArray', PackedColorArray([Color.RED, Color.GREEN]), true], -]) -> void: - - assert_that(GdArrayTools.is_array_type(value)).is_equal(expected) - - -func test_is_type_array() -> void: - for type in [TYPE_NIL, TYPE_MAX]: - if type in [TYPE_ARRAY, TYPE_PACKED_COLOR_ARRAY]: - assert_that(GdArrayTools.is_type_array(type)).is_true() - else: - assert_that(GdArrayTools.is_type_array(type)).is_false() - - -@warning_ignore("unused_parameter") -func test_filter_value(value, expected_type :int, test_parameters = [ - [[1, 2, 3, 1], TYPE_ARRAY], - [Array([1, 2, 3, 1]) as Array[int], TYPE_ARRAY], - [PackedByteArray([1, 2, 3, 1]), TYPE_PACKED_BYTE_ARRAY], - [PackedInt32Array([1, 2, 3, 1]), TYPE_PACKED_INT32_ARRAY], - [PackedInt64Array([1, 2, 3, 1]), TYPE_PACKED_INT64_ARRAY], - [PackedFloat32Array([1.0, 2, 1.1, 1.0]), TYPE_PACKED_FLOAT32_ARRAY], - [PackedFloat64Array([1.0, 2, 1.1, 1.0]), TYPE_PACKED_FLOAT64_ARRAY], - [PackedStringArray(["1", "2", "3", "1"]), TYPE_PACKED_STRING_ARRAY], - [PackedVector2Array([Vector2.ZERO, Vector2.ONE, Vector2.DOWN, Vector2.ZERO]), TYPE_PACKED_VECTOR2_ARRAY], - [PackedVector3Array([Vector3.ZERO, Vector3.ONE, Vector3.DOWN, Vector3.ZERO]), TYPE_PACKED_VECTOR3_ARRAY], - [PackedColorArray([Color.RED, Color.GREEN, Color.BLUE, Color.RED]), TYPE_PACKED_COLOR_ARRAY] - ]) -> void: - - var value_to_remove = value[0] - var result :Variant = GdArrayTools.filter_value(value, value_to_remove) - assert_array(result).not_contains([value_to_remove]).has_size(2) - assert_that(typeof(result)).is_equal(expected_type) - - -func test_filter_value_() -> void: - assert_array(GdArrayTools.filter_value([], null)).is_empty() - assert_array(GdArrayTools.filter_value([], "")).is_empty() - - var current :Array = [null, "a", "b", null, "c", null] - var filtered :Variant= GdArrayTools.filter_value(current, null) - assert_array(filtered).contains_exactly(["a", "b", "c"]) - # verify the source is not affected - assert_array(current).contains_exactly([null, "a", "b", null, "c", null]) - - current = [null, "a", "xxx", null, "xx", null] - filtered = GdArrayTools.filter_value(current, "xxx") - assert_array(filtered).contains_exactly([null, "a", null, "xx", null]) - # verify the source is not affected - assert_array(current).contains_exactly([null, "a", "xxx", null, "xx", null]) - - -func test_erase_value() -> void: - var current := [] - GdArrayTools.erase_value(current, null) - assert_array(current).is_empty() - - current = [null] - GdArrayTools.erase_value(current, null) - assert_array(current).is_empty() - - current = [null, "a", "b", null, "c", null] - GdArrayTools.erase_value(current, null) - # verify the source is affected - assert_array(current).contains_exactly(["a", "b", "c"]) - - -func test_scan_typed() -> void: - assert_that(GdArrayTools.scan_typed([1, 2, 3])).is_equal(TYPE_INT) - assert_that(GdArrayTools.scan_typed([1, 2.2, 3])).is_equal(GdObjects.TYPE_VARIANT) diff --git a/addons/gdUnit4/test/core/GdDiffToolTest.gd b/addons/gdUnit4/test/core/GdDiffToolTest.gd deleted file mode 100644 index 5b138fc..0000000 --- a/addons/gdUnit4/test/core/GdDiffToolTest.gd +++ /dev/null @@ -1,48 +0,0 @@ -# GdUnit generated TestSuite -#warning-ignore-all:unused_argument -#warning-ignore-all:return_value_discarded -class_name GdDiffToolTest -extends GdUnitTestSuite - -# TestSuite generated from -const __source = 'res://addons/gdUnit4/src/core/GdDiffTool.gd' - - -func test_string_diff_empty(): - var diffs := GdDiffTool.string_diff("", "") - assert_array(diffs).has_size(2) - assert_array(diffs[0]).is_empty() - assert_array(diffs[1]).is_empty() - - -func test_string_diff_equals(): - var diffs := GdDiffTool.string_diff("Abc", "Abc") - var expected_l_diff := "Abc".to_ascii_buffer() - var expected_r_diff := "Abc".to_ascii_buffer() - - assert_array(diffs).has_size(2) - assert_array(diffs[0]).contains_exactly(expected_l_diff) - assert_array(diffs[1]).contains_exactly(expected_r_diff) - - -func test_string_diff(): - # tests the result of string diff function like assert_str("Abc").is_equal("abc") - var diffs := GdDiffTool.string_diff("Abc", "abc") - var chars := "Aabc".to_ascii_buffer() - var ord_A := chars[0] - var ord_a := chars[1] - var ord_b := chars[2] - var ord_c := chars[3] - var expected_l_diff := PackedByteArray([GdDiffTool.DIV_SUB, ord_A, GdDiffTool.DIV_ADD, ord_a, ord_b, ord_c]) - var expected_r_diff := PackedByteArray([GdDiffTool.DIV_ADD, ord_A, GdDiffTool.DIV_SUB, ord_a, ord_b, ord_c]) - - assert_array(diffs).has_size(2) - assert_array(diffs[0]).contains_exactly(expected_l_diff) - assert_array(diffs[1]).contains_exactly(expected_r_diff) - - -@warning_ignore("unused_parameter") -func test_string_diff_large_value(fuzzer := Fuzzers.rand_str(1000, 4000), fuzzer_iterations = 10): - # test diff with large values not crashes the API GD-100 - var value :String = fuzzer.next_value() - GdDiffTool.string_diff(value, value) diff --git a/addons/gdUnit4/test/core/GdObjectsTest.gd b/addons/gdUnit4/test/core/GdObjectsTest.gd deleted file mode 100644 index c93ef19..0000000 --- a/addons/gdUnit4/test/core/GdObjectsTest.gd +++ /dev/null @@ -1,512 +0,0 @@ -extends GdUnitTestSuite - - -func test_equals_string(): - var a := "" - var b := "" - var c := "abc" - var d := "abC" - - assert_bool(GdObjects.equals("", "")).is_true() - assert_bool(GdObjects.equals(a, "")).is_true() - assert_bool(GdObjects.equals("", a)).is_true() - assert_bool(GdObjects.equals(a, a)).is_true() - assert_bool(GdObjects.equals(a, b)).is_true() - assert_bool(GdObjects.equals(b, a)).is_true() - assert_bool(GdObjects.equals(c, c)).is_true() - assert_bool(GdObjects.equals(c, String(c))).is_true() - - assert_bool(GdObjects.equals(a, null)).is_false() - assert_bool(GdObjects.equals(null, a)).is_false() - assert_bool(GdObjects.equals("", c)).is_false() - assert_bool(GdObjects.equals(c, "")).is_false() - assert_bool(GdObjects.equals(c, d)).is_false() - assert_bool(GdObjects.equals(d, c)).is_false() - # against diverent type - assert_bool(GdObjects.equals(d, Array())).is_false() - assert_bool(GdObjects.equals(d, Dictionary())).is_false() - assert_bool(GdObjects.equals(d, Vector2.ONE)).is_false() - assert_bool(GdObjects.equals(d, Vector3.ONE)).is_false() - - -func test_equals_stringname(): - assert_bool(GdObjects.equals("", &"")).is_true() - assert_bool(GdObjects.equals("abc", &"abc")).is_true() - assert_bool(GdObjects.equals("abc", &"abC")).is_false() - - -func test_equals_array(): - var a := [] - var b := [] - var c := Array() - var d := [1,2,3,4,5] - var e := [1,2,3,4,5] - var x := [1,2,3,6,4,5] - - assert_bool(GdObjects.equals(a, a)).is_true() - assert_bool(GdObjects.equals(a, b)).is_true() - assert_bool(GdObjects.equals(b, a)).is_true() - assert_bool(GdObjects.equals(a, c)).is_true() - assert_bool(GdObjects.equals(c, b)).is_true() - assert_bool(GdObjects.equals(d, d)).is_true() - assert_bool(GdObjects.equals(d, e)).is_true() - assert_bool(GdObjects.equals(e, d)).is_true() - - assert_bool(GdObjects.equals(a, null)).is_false() - assert_bool(GdObjects.equals(null, a)).is_false() - assert_bool(GdObjects.equals(a, d)).is_false() - assert_bool(GdObjects.equals(d, a)).is_false() - assert_bool(GdObjects.equals(d, x)).is_false() - assert_bool(GdObjects.equals(x, d)).is_false() - # against diverent type - assert_bool(GdObjects.equals(a, "")).is_false() - assert_bool(GdObjects.equals(a, Dictionary())).is_false() - assert_bool(GdObjects.equals(a, Vector2.ONE)).is_false() - assert_bool(GdObjects.equals(a, Vector3.ONE)).is_false() - - -func test_equals_dictionary(): - var a := {} - var b := {} - var c := {"a":"foo"} - var d := {"a":"foo"} - var e1 := {"a":"foo", "b":"bar"} - var e2 := {"b":"bar", "a":"foo"} - - assert_bool(GdObjects.equals(a, a)).is_true() - assert_bool(GdObjects.equals(a, b)).is_true() - assert_bool(GdObjects.equals(b, a)).is_true() - assert_bool(GdObjects.equals(c, c)).is_true() - assert_bool(GdObjects.equals(c, d)).is_true() - assert_bool(GdObjects.equals(e1, e2)).is_true() - assert_bool(GdObjects.equals(e2, e1)).is_true() - - assert_bool(GdObjects.equals(a, null)).is_false() - assert_bool(GdObjects.equals(null, a)).is_false() - assert_bool(GdObjects.equals(a, c)).is_false() - assert_bool(GdObjects.equals(c, a)).is_false() - assert_bool(GdObjects.equals(a, e1)).is_false() - assert_bool(GdObjects.equals(e1, a)).is_false() - assert_bool(GdObjects.equals(c, e1)).is_false() - assert_bool(GdObjects.equals(e1, c)).is_false() - - -class TestClass extends Resource: - - enum { - A, - B - } - - var _a:int - var _b:String - var _c:Array - - func _init(a:int = 0,b:String = "",c:Array = []): - _a = a - _b = b - _c = c - - -func test_equals_class(): - var a := TestClass.new() - var b := TestClass.new() - var c := TestClass.new(1, "foo", ["bar", "xxx"]) - var d := TestClass.new(1, "foo", ["bar", "xxx"]) - var x := TestClass.new(1, "foo", ["bar", "xsxx"]) - - assert_bool(GdObjects.equals(a, a)).is_true() - assert_bool(GdObjects.equals(a, b)).is_true() - assert_bool(GdObjects.equals(b, a)).is_true() - assert_bool(GdObjects.equals(c, d)).is_true() - assert_bool(GdObjects.equals(d, c)).is_true() - - assert_bool(GdObjects.equals(a, null)).is_false() - assert_bool(GdObjects.equals(null, a)).is_false() - assert_bool(GdObjects.equals(a, c)).is_false() - assert_bool(GdObjects.equals(c, a)).is_false() - assert_bool(GdObjects.equals(d, x)).is_false() - assert_bool(GdObjects.equals(x, d)).is_false() - - -func test_equals_with_stack_deep(): - # more extended version - var x2 := TestClass.new(1, "foo", [TestClass.new(22, "foo"), TestClass.new(22, "foo")]) - var x3 := TestClass.new(1, "foo", [TestClass.new(22, "foo"), TestClass.new(23, "foo")]) - assert_bool(GdObjects.equals(x2, x3)).is_false() - - -func test_equals_Node_with_deep_check(): - var nodeA = auto_free(Node.new()) - var nodeB = auto_free(Node.new()) - - # compares by default with deep parameter ckeck - assert_bool(GdObjects.equals(nodeA, nodeA)).is_true() - assert_bool(GdObjects.equals(nodeB, nodeB)).is_true() - assert_bool(GdObjects.equals(nodeA, nodeB)).is_true() - assert_bool(GdObjects.equals(nodeB, nodeA)).is_true() - # compares by object reference - assert_bool(GdObjects.equals(nodeA, nodeA, false, GdObjects.COMPARE_MODE.OBJECT_REFERENCE)).is_true() - assert_bool(GdObjects.equals(nodeB, nodeB, false, GdObjects.COMPARE_MODE.OBJECT_REFERENCE)).is_true() - assert_bool(GdObjects.equals(nodeA, nodeB, false, GdObjects.COMPARE_MODE.OBJECT_REFERENCE)).is_false() - assert_bool(GdObjects.equals(nodeB, nodeA, false, GdObjects.COMPARE_MODE.OBJECT_REFERENCE)).is_false() - - -func test_is_primitive_type(): - assert_bool(GdObjects.is_primitive_type(false)).is_true() - assert_bool(GdObjects.is_primitive_type(true)).is_true() - assert_bool(GdObjects.is_primitive_type(0)).is_true() - assert_bool(GdObjects.is_primitive_type(0.1)).is_true() - assert_bool(GdObjects.is_primitive_type("")).is_true() - assert_bool(GdObjects.is_primitive_type(Vector2.ONE)).is_false() - - -class TestClassForIsType: - var x - - -func test_is_type(): - # check build-in types - assert_bool(GdObjects.is_type(1)).is_false() - assert_bool(GdObjects.is_type(1.3)).is_false() - assert_bool(GdObjects.is_type(true)).is_false() - assert_bool(GdObjects.is_type(false)).is_false() - assert_bool(GdObjects.is_type([])).is_false() - assert_bool(GdObjects.is_type("abc")).is_false() - - assert_bool(GdObjects.is_type(null)).is_false() - # an object type - assert_bool(GdObjects.is_type(Node)).is_true() - # an reference type - assert_bool(GdObjects.is_type(AStar3D)).is_true() - # an script type - assert_bool(GdObjects.is_type(GDScript)).is_true() - # an custom type - assert_bool(GdObjects.is_type(TestClassForIsType)).is_true() - # checked inner class type - assert_bool(GdObjects.is_type(CustomClass.InnerClassA)).is_true() - assert_bool(GdObjects.is_type(CustomClass.InnerClassC)).is_true() - - # for instances must allways endup with false - assert_bool(GdObjects.is_type(auto_free(Node.new()))).is_false() - assert_bool(GdObjects.is_type(AStar3D.new())).is_false() - assert_bool(GdObjects.is_type(Dictionary())).is_false() - assert_bool(GdObjects.is_type(PackedColorArray())).is_false() - assert_bool(GdObjects.is_type(GDScript.new())).is_false() - assert_bool(GdObjects.is_type(TestClassForIsType.new())).is_false() - assert_bool(GdObjects.is_type(auto_free(CustomClass.InnerClassC.new()))).is_false() - - -func test_is_singleton() -> void: - for singleton_name in Engine.get_singleton_list(): - var singleton = Engine.get_singleton(singleton_name) - assert_bool(GdObjects.is_singleton(singleton)) \ - .override_failure_message("Expect to a singleton: '%s' Instance: %s, Class: %s" % [singleton_name, singleton, singleton.get_class()]) \ - .is_true() - # false tests - assert_bool(GdObjects.is_singleton(10)).is_false() - assert_bool(GdObjects.is_singleton(true)).is_false() - assert_bool(GdObjects.is_singleton(Node)).is_false() - assert_bool(GdObjects.is_singleton(auto_free(Node.new()))).is_false() - - -func _is_instance(value) -> bool: - return GdObjects.is_instance(auto_free(value)) - - -func test_is_instance_true(): - assert_bool(_is_instance(RefCounted.new())).is_true() - assert_bool(_is_instance(Node.new())).is_true() - assert_bool(_is_instance(AStar3D.new())).is_true() - assert_bool(_is_instance(PackedScene.new())).is_true() - assert_bool(_is_instance(GDScript.new())).is_true() - assert_bool(_is_instance(Person.new())).is_true() - assert_bool(_is_instance(CustomClass.new())).is_true() - assert_bool(_is_instance(CustomNodeTestClass.new())).is_true() - assert_bool(_is_instance(TestClassForIsType.new())).is_true() - assert_bool(_is_instance(CustomClass.InnerClassC.new())).is_true() - - -func test_is_instance_false(): - assert_bool(_is_instance(RefCounted)).is_false() - assert_bool(_is_instance(Node)).is_false() - assert_bool(_is_instance(AStar3D)).is_false() - assert_bool(_is_instance(PackedScene)).is_false() - assert_bool(_is_instance(GDScript)).is_false() - assert_bool(_is_instance(Dictionary())).is_false() - assert_bool(_is_instance(PackedColorArray())).is_false() - assert_bool(_is_instance(Person)).is_false() - assert_bool(_is_instance(CustomClass)).is_false() - assert_bool(_is_instance(CustomNodeTestClass)).is_false() - assert_bool(_is_instance(TestClassForIsType)).is_false() - assert_bool(_is_instance(CustomClass.InnerClassC)).is_false() - - -# shorter helper func to extract class name and using auto_free -func extract_class_name(value) -> GdUnitResult: - return GdObjects.extract_class_name(auto_free(value)) - - -func test_get_class_name_from_class_path(): - # extract class name by resoure path - assert_result(extract_class_name("res://addons/gdUnit4/test/resources/core/Person.gd"))\ - .is_success().is_value("Person") - assert_result(extract_class_name("res://addons/gdUnit4/test/resources/core/CustomClass.gd"))\ - .is_success().is_value("CustomClass") - assert_result(extract_class_name("res://addons/gdUnit4/test/mocker/resources/CustomNodeTestClass.gd"))\ - .is_success().is_value("CustomNodeTestClass") - assert_result(extract_class_name("res://addons/gdUnit4/test/mocker/resources/CustomResourceTestClass.gd"))\ - .is_success().is_value("CustomResourceTestClass") - assert_result(extract_class_name("res://addons/gdUnit4/test/mocker/resources/OverridenGetClassTestClass.gd"))\ - .is_success().is_value("OverridenGetClassTestClass") - - -func test_get_class_name_from_snake_case_class_path(): - assert_result(extract_class_name("res://addons/gdUnit4/test/core/resources/naming_conventions/snake_case_with_class_name.gd"))\ - .is_success().is_value("SnakeCaseWithClassName") - # without class_name - assert_result(extract_class_name("res://addons/gdUnit4/test/core/resources/naming_conventions/snake_case_without_class_name.gd"))\ - .is_success().is_value("SnakeCaseWithoutClassName") - - -func test_get_class_name_from_pascal_case_class_path(): - assert_result(extract_class_name("res://addons/gdUnit4/test/core/resources/naming_conventions/PascalCaseWithClassName.gd"))\ - .is_success().is_value("PascalCaseWithClassName") - # without class_name - assert_result(extract_class_name("res://addons/gdUnit4/test/core/resources/naming_conventions/PascalCaseWithoutClassName.gd"))\ - .is_success().is_value("PascalCaseWithoutClassName") - - -func test_get_class_name_from_type(): - assert_result(extract_class_name(Animation)).is_success().is_value("Animation") - assert_result(extract_class_name(GDScript)).is_success().is_value("GDScript") - assert_result(extract_class_name(Camera3D)).is_success().is_value("Camera3D") - assert_result(extract_class_name(Node)).is_success().is_value("Node") - assert_result(extract_class_name(Tree)).is_success().is_value("Tree") - # extract class name from custom classes - assert_result(extract_class_name(Person)).is_success().is_value("Person") - assert_result(extract_class_name(CustomClass)).is_success().is_value("CustomClass") - assert_result(extract_class_name(CustomNodeTestClass)).is_success().is_value("CustomNodeTestClass") - assert_result(extract_class_name(CustomResourceTestClass)).is_success().is_value("CustomResourceTestClass") - assert_result(extract_class_name(OverridenGetClassTestClass)).is_success().is_value("OverridenGetClassTestClass") - assert_result(extract_class_name(AdvancedTestClass)).is_success().is_value("AdvancedTestClass") - - -func test_get_class_name_from_inner_class(): - assert_result(extract_class_name(CustomClass))\ - .is_success().is_value("CustomClass") - assert_result(extract_class_name(CustomClass.InnerClassA))\ - .is_success().is_value("CustomClass.InnerClassA") - assert_result(extract_class_name(CustomClass.InnerClassB))\ - .is_success().is_value("CustomClass.InnerClassB") - assert_result(extract_class_name(CustomClass.InnerClassC))\ - .is_success().is_value("CustomClass.InnerClassC") - assert_result(extract_class_name(CustomClass.InnerClassD))\ - .is_success().is_value("CustomClass.InnerClassD") - assert_result(extract_class_name(AdvancedTestClass.SoundData))\ - .is_success().is_value("AdvancedTestClass.SoundData") - assert_result(extract_class_name(AdvancedTestClass.AtmosphereData))\ - .is_success().is_value("AdvancedTestClass.AtmosphereData") - assert_result(extract_class_name(AdvancedTestClass.Area4D))\ - .is_success().is_value("AdvancedTestClass.Area4D") - - -func test_extract_class_name_from_instance(): - assert_result(extract_class_name(Camera3D.new())).is_equal("Camera3D") - assert_result(extract_class_name(GDScript.new())).is_equal("GDScript") - assert_result(extract_class_name(Node.new())).is_equal("Node") - - # extract class name from custom classes - assert_result(extract_class_name(Person.new())).is_equal("Person") - assert_result(extract_class_name(ClassWithNameA.new())).is_equal("ClassWithNameA") - assert_result(extract_class_name(ClassWithNameB.new())).is_equal("ClassWithNameB") - var classWithoutNameA = load("res://addons/gdUnit4/test/mocker/resources/ClassWithoutNameA.gd") - assert_result(extract_class_name(classWithoutNameA.new())).is_equal("ClassWithoutNameA") - assert_result(extract_class_name(CustomNodeTestClass.new())).is_equal("CustomNodeTestClass") - assert_result(extract_class_name(CustomResourceTestClass.new())).is_equal("CustomResourceTestClass") - assert_result(extract_class_name(OverridenGetClassTestClass.new())).is_equal("OverridenGetClassTestClass") - assert_result(extract_class_name(AdvancedTestClass.new())).is_equal("AdvancedTestClass") - # extract inner class name - assert_result(extract_class_name(AdvancedTestClass.SoundData.new())).is_equal("AdvancedTestClass.SoundData") - assert_result(extract_class_name(AdvancedTestClass.AtmosphereData.new())).is_equal("AdvancedTestClass.AtmosphereData") - assert_result(extract_class_name(AdvancedTestClass.Area4D.new(0))).is_equal("AdvancedTestClass.Area4D") - assert_result(extract_class_name(CustomClass.InnerClassC.new())).is_equal("CustomClass.InnerClassC") - - -# verify enigne class names are not converted by configured naming convention -@warning_ignore("unused_parameter") -func test_extract_class_name_from_class_path(fuzzer=GodotClassNameFuzzer.new(true, true), fuzzer_iterations = 100) -> void: - var clazz_name :String = fuzzer.next_value() - assert_str(GdObjects.extract_class_name_from_class_path(PackedStringArray([clazz_name]))).is_equal(clazz_name) - - -@warning_ignore("unused_parameter") -func test_extract_class_name_godot_classes(fuzzer=GodotClassNameFuzzer.new(true, true), fuzzer_iterations = 100): - var extract_class_name_ := fuzzer.next_value() as String - var instance :Variant = ClassDB.instantiate(extract_class_name_) - assert_result(extract_class_name(instance)).is_equal(extract_class_name_) - - -func test_extract_class_path_by_clazz(): - # engine classes has no class path - assert_array(GdObjects.extract_class_path(Animation)).is_empty() - assert_array(GdObjects.extract_class_path(GDScript)).is_empty() - assert_array(GdObjects.extract_class_path(Camera3D)).is_empty() - assert_array(GdObjects.extract_class_path(Tree)).is_empty() - assert_array(GdObjects.extract_class_path(Node)).is_empty() - - # script classes - assert_array(GdObjects.extract_class_path(Person))\ - .contains_exactly(["res://addons/gdUnit4/test/resources/core/Person.gd"]) - assert_array(GdObjects.extract_class_path(CustomClass))\ - .contains_exactly(["res://addons/gdUnit4/test/resources/core/CustomClass.gd"]) - assert_array(GdObjects.extract_class_path(CustomNodeTestClass))\ - .contains_exactly(["res://addons/gdUnit4/test/mocker/resources/CustomNodeTestClass.gd"]) - assert_array(GdObjects.extract_class_path(CustomResourceTestClass))\ - .contains_exactly(["res://addons/gdUnit4/test/mocker/resources/CustomResourceTestClass.gd"]) - assert_array(GdObjects.extract_class_path(OverridenGetClassTestClass))\ - .contains_exactly(["res://addons/gdUnit4/test/mocker/resources/OverridenGetClassTestClass.gd"]) - - # script inner classes - assert_array(GdObjects.extract_class_path(CustomClass.InnerClassA))\ - .contains_exactly(["res://addons/gdUnit4/test/resources/core/CustomClass.gd", "InnerClassA"]) - assert_array(GdObjects.extract_class_path(CustomClass.InnerClassB))\ - .contains_exactly(["res://addons/gdUnit4/test/resources/core/CustomClass.gd", "InnerClassB"]) - assert_array(GdObjects.extract_class_path(CustomClass.InnerClassC))\ - .contains_exactly(["res://addons/gdUnit4/test/resources/core/CustomClass.gd", "InnerClassC"]) - assert_array(GdObjects.extract_class_path(AdvancedTestClass.SoundData))\ - .contains_exactly(["res://addons/gdUnit4/test/mocker/resources/AdvancedTestClass.gd", "SoundData"]) - assert_array(GdObjects.extract_class_path(AdvancedTestClass.AtmosphereData))\ - .contains_exactly(["res://addons/gdUnit4/test/mocker/resources/AdvancedTestClass.gd", "AtmosphereData"]) - assert_array(GdObjects.extract_class_path(AdvancedTestClass.Area4D))\ - .contains_exactly(["res://addons/gdUnit4/test/mocker/resources/AdvancedTestClass.gd", "Area4D"]) - - # inner inner class - assert_array(GdObjects.extract_class_path(CustomClass.InnerClassD.InnerInnerClassA))\ - .contains_exactly(["res://addons/gdUnit4/test/resources/core/CustomClass.gd", "InnerClassD", "InnerInnerClassA"]) - - -#func __test_can_instantiate(): -# assert_bool(GdObjects.can_instantiate(GDScript)).is_true() -# assert_bool(GdObjects.can_instantiate(Node)).is_true() -# assert_bool(GdObjects.can_instantiate(Tree)).is_true() -# assert_bool(GdObjects.can_instantiate(Camera3D)).is_true() -# assert_bool(GdObjects.can_instantiate(Person)).is_true() -# assert_bool(GdObjects.can_instantiate(CustomClass.InnerClassA)).is_true() -# assert_bool(GdObjects.can_instantiate(TreeItem)).is_true() -# -# creates a test instance by given class name or resource path -# instances created with auto free -func create_instance(clazz): - var result := GdObjects.create_instance(clazz) - if result.is_success(): - return auto_free(result.value()) - return null - - -func test_create_instance_by_class_name(): - # instance of engine classes - assert_object(create_instance(Node))\ - .is_not_null()\ - .is_instanceof(Node) - assert_object(create_instance(Camera3D))\ - .is_not_null()\ - .is_instanceof(Camera3D) - # instance of custom classes - assert_object(create_instance(Person))\ - .is_not_null()\ - .is_instanceof(Person) - # instance of inner classes - assert_object(create_instance(CustomClass.InnerClassA))\ - .is_not_null()\ - .is_instanceof(CustomClass.InnerClassA) - - -func test_extract_class_name_on_null_value(): - # we can't extract class name from a null value - assert_result(GdObjects.extract_class_name(null))\ - .is_error()\ - .contains_message("Can't extract class name form a null value.") - - -func test_is_public_script_class() -> void: - # snake case format class names - assert_bool(GdObjects.is_public_script_class("ScriptWithClassName")).is_true() - assert_bool(GdObjects.is_public_script_class("script_without_class_name")).is_false() - assert_bool(GdObjects.is_public_script_class("CustomClass")).is_true() - # inner classes not listed as public classes - assert_bool(GdObjects.is_public_script_class("CustomClass.InnerClassA")).is_false() - - -func test_is_instance_scene() -> void: - # checked none scene objects - assert_bool(GdObjects.is_instance_scene(RefCounted.new())).is_false() - assert_bool(GdObjects.is_instance_scene(CustomClass.new())).is_false() - assert_bool(GdObjects.is_instance_scene(auto_free(Control.new()))).is_false() - - # now check checked a loaded scene - var resource = load("res://addons/gdUnit4/test/mocker/resources/scenes/TestScene.tscn") - assert_bool(GdObjects.is_instance_scene(resource)).is_false() - # checked a instance of a scene - assert_bool(GdObjects.is_instance_scene(auto_free(resource.instantiate()))).is_true() - - -func test_is_scene_resource_path() -> void: - assert_bool(GdObjects.is_scene_resource_path(RefCounted.new())).is_false() - assert_bool(GdObjects.is_scene_resource_path(CustomClass.new())).is_false() - assert_bool(GdObjects.is_scene_resource_path(auto_free(Control.new()))).is_false() - - # check checked a loaded scene - var resource = load("res://addons/gdUnit4/test/mocker/resources/scenes/TestScene.tscn") - assert_bool(GdObjects.is_scene_resource_path(resource)).is_false() - # checked resource path - assert_bool(GdObjects.is_scene_resource_path("res://addons/gdUnit4/test/mocker/resources/scenes/TestScene.tscn")).is_true() - - -func test_extract_class_functions() -> void: - var functions := GdObjects.extract_class_functions("Resource", [""]) - for f in functions: - if f["name"] == "get_path": - assert_str(GdFunctionDescriptor.extract_from(f)._to_string()).is_equal("[Line:-1] func get_path() -> String:") - - functions = GdObjects.extract_class_functions("CustomResourceTestClass", ["res://addons/gdUnit4/test/mocker/resources/CustomResourceTestClass.gd"]) - for f in functions: - if f["name"] == "get_path": - assert_str(GdFunctionDescriptor.extract_from(f)._to_string()).is_equal("[Line:-1] func get_path() -> String:") - - -func test_all_types() -> void: - var expected_types :Array[int] = [] - for type_index in TYPE_MAX: - expected_types.append(type_index) - expected_types.append(GdObjects.TYPE_VOID) - expected_types.append(GdObjects.TYPE_VARARG) - expected_types.append(GdObjects.TYPE_FUNC) - expected_types.append(GdObjects.TYPE_FUZZER) - expected_types.append(GdObjects.TYPE_VARIANT) - assert_array(GdObjects.all_types()).contains_exactly_in_any_order(expected_types) - - -func test_to_camel_case() -> void: - assert_str(GdObjects.to_camel_case("MyClassName")).is_equal("myClassName") - assert_str(GdObjects.to_camel_case("my_class_name")).is_equal("myClassName") - assert_str(GdObjects.to_camel_case("myClassName")).is_equal("myClassName") - - -func test_to_pascal_case() -> void: - assert_str(GdObjects.to_pascal_case("MyClassName")).is_equal("MyClassName") - assert_str(GdObjects.to_pascal_case("my_class_name")).is_equal("MyClassName") - assert_str(GdObjects.to_pascal_case("myClassName")).is_equal("MyClassName") - - -func test_to_snake_case() -> void: - assert_str(GdObjects.to_snake_case("MyClassName")).is_equal("my_class_name") - assert_str(GdObjects.to_snake_case("my_class_name")).is_equal("my_class_name") - assert_str(GdObjects.to_snake_case("myClassName")).is_equal("my_class_name") - - -func test_is_snake_case() -> void: - assert_bool(GdObjects.is_snake_case("my_class_name")).is_true() - assert_bool(GdObjects.is_snake_case("myclassname")).is_true() - assert_bool(GdObjects.is_snake_case("MyClassName")).is_false() - assert_bool(GdObjects.is_snake_case("my_class_nameTest")).is_false() diff --git a/addons/gdUnit4/test/core/GdUnit4VersionTest.gd b/addons/gdUnit4/test/core/GdUnit4VersionTest.gd deleted file mode 100644 index 94a9c12..0000000 --- a/addons/gdUnit4/test/core/GdUnit4VersionTest.gd +++ /dev/null @@ -1,67 +0,0 @@ -# GdUnit generated TestSuite -class_name GdUnit4VersionTest -extends GdUnitTestSuite - -# TestSuite generated from -const __source = 'res://addons/gdUnit4/src/core/.gd' - - -func test_parse() -> void: - var expected := GdUnit4Version.new(0, 9, 1) - assert_object(GdUnit4Version.parse("v0.9.1-rc")).is_equal(expected) - assert_object(GdUnit4Version.parse("v0.9.1RC")).is_equal(expected) - assert_object(GdUnit4Version.parse("0.9.1 rc")).is_equal(expected) - assert_object(GdUnit4Version.parse("0.9.1")).is_equal(expected) - - -func test_equals() -> void: - var version := GdUnit4Version.new(0, 9, 1) - assert_bool(version.equals(version)).is_true() - assert_bool(version.equals(GdUnit4Version.new(0, 9, 1))).is_true() - assert_bool(GdUnit4Version.new(0, 9, 1).equals(version)).is_true() - - assert_bool(GdUnit4Version.new(0, 9, 2).equals(version)).is_false() - assert_bool(GdUnit4Version.new(0, 8, 1).equals(version)).is_false() - assert_bool(GdUnit4Version.new(1, 9, 1).equals(version)).is_false() - - -func test_to_string() -> void: - var version := GdUnit4Version.new(0, 9, 1) - assert_str(str(version)).is_equal("v0.9.1") - assert_str("%s" % version).is_equal("v0.9.1") - - -@warning_ignore("unused_parameter") -func test_is_greater_major(fuzzer_major := Fuzzers.rangei(1, 20), fuzzer_minor := Fuzzers.rangei(0, 20), fuzzer_patch := Fuzzers.rangei(0, 20), fuzzer_iterations = 500) -> void: - var version := GdUnit4Version.new(0, 9, 1) - var current := GdUnit4Version.new(fuzzer_major.next_value(), fuzzer_minor.next_value(), fuzzer_patch.next_value()); - assert_bool(current.is_greater(version))\ - .override_failure_message("Expect %s is greater then %s" % [current, version])\ - .is_true() - - -@warning_ignore("unused_parameter") -func test_is_not_greater_major(fuzzer_major := Fuzzers.rangei(1, 10), fuzzer_minor := Fuzzers.rangei(0, 20), fuzzer_patch := Fuzzers.rangei(0, 20), fuzzer_iterations = 500) -> void: - var version := GdUnit4Version.new(11, 0, 0) - var current := GdUnit4Version.new(fuzzer_major.next_value(), fuzzer_minor.next_value(), fuzzer_patch.next_value()); - assert_bool(current.is_greater(version))\ - .override_failure_message("Expect %s is not greater then %s" % [current, version])\ - .is_false() - - -@warning_ignore("unused_parameter") -func test_is_greater_minor(fuzzer_minor := Fuzzers.rangei(3, 20), fuzzer_patch := Fuzzers.rangei(0, 20), fuzzer_iterations = 500) -> void: - var version := GdUnit4Version.new(0, 2, 1) - var current := GdUnit4Version.new(0, fuzzer_minor.next_value(), fuzzer_patch.next_value()); - assert_bool(current.is_greater(version))\ - .override_failure_message("Expect %s is greater then %s" % [current, version])\ - .is_true() - - -@warning_ignore("unused_parameter") -func test_is_greater_patch(fuzzer_patch := Fuzzers.rangei(1, 20), fuzzer_iterations = 500) -> void: - var version := GdUnit4Version.new(0, 2, 0) - var current := GdUnit4Version.new(0, 2, fuzzer_patch.next_value()); - assert_bool(current.is_greater(version))\ - .override_failure_message("Expect %s is greater then %s" % [current, version])\ - .is_true() diff --git a/addons/gdUnit4/test/core/GdUnitClassDoublerTest.gd b/addons/gdUnit4/test/core/GdUnitClassDoublerTest.gd deleted file mode 100644 index 21c266f..0000000 --- a/addons/gdUnit4/test/core/GdUnitClassDoublerTest.gd +++ /dev/null @@ -1,13 +0,0 @@ -# GdUnit generated TestSuite -class_name GdUnitClassDoublerTest -extends GdUnitTestSuite - -# TestSuite generated from -const __source = 'res://addons/gdUnit4/src/core/GdUnitClassDoubler.gd' - -# simple function doubler whitout any modifications -class TestFunctionDoubler extends GdFunctionDoubler: - func double(_func_descriptor :GdFunctionDescriptor) -> PackedStringArray: - return PackedStringArray([]) - - diff --git a/addons/gdUnit4/test/core/GdUnitFileAccessTest.gd b/addons/gdUnit4/test/core/GdUnitFileAccessTest.gd deleted file mode 100644 index d160394..0000000 --- a/addons/gdUnit4/test/core/GdUnitFileAccessTest.gd +++ /dev/null @@ -1,231 +0,0 @@ -# GdUnit generated TestSuite -extends GdUnitTestSuite -@warning_ignore('unused_parameter') -@warning_ignore('return_value_discarded') - -# TestSuite generated from -const __source = 'res://addons/gdUnit4/src/core/GdUnitFileAccess.gd' - - -var file_to_save :String - - -func after() -> void: - # verify tmp files are deleted automatically - assert_bool(FileAccess.file_exists(file_to_save)).is_false() - - -func _create_file(p_path :String, p_name :String) -> void: - var file := create_temp_file(p_path, p_name) - file.store_string("some content") - - -func test_copy_directory() -> void: - var temp_dir := create_temp_dir("test_copy_directory") - assert_bool(GdUnitFileAccess.copy_directory("res://addons/gdUnit4/test/core/resources/copy_test/folder_a/", temp_dir)).is_true() - assert_file("%s/file_a.txt" % temp_dir).exists() - assert_file("%s/file_b.txt" % temp_dir).exists() - - -func test_copy_directory_recursive() -> void: - var temp_dir := create_temp_dir("test_copy_directory_recursive") - assert_bool(GdUnitFileAccess.copy_directory("res://addons/gdUnit4/test/core/resources/copy_test/", temp_dir, true)).is_true() - assert_file("%s/folder_a/file_a.txt" % temp_dir).exists() - assert_file("%s/folder_a/file_b.txt" % temp_dir).exists() - assert_file("%s/folder_b/file_a.txt" % temp_dir).exists() - assert_file("%s/folder_b/file_b.txt" % temp_dir).exists() - assert_file("%s/folder_b/folder_ba/file_x.txt" % temp_dir).exists() - assert_file("%s/folder_c/file_z.txt" % temp_dir).exists() - - -func test_create_temp_dir() -> void: - var temp_dir := create_temp_dir("examples/game/save") - file_to_save = temp_dir + "/save_game.dat" - - var data := { - 'user': "Hoschi", - 'level': 42 - } - var file := FileAccess.open(file_to_save, FileAccess.WRITE) - file.store_line(JSON.stringify(data)) - assert_bool(FileAccess.file_exists(file_to_save)).is_true() - - -func test_create_temp_file() -> void: - # setup - stores a tmp file with "user://tmp/examples/game/game.sav" (auto closed) - var file := create_temp_file("examples/game", "game.sav") - assert_object(file).is_not_null() - # write some example data - file.store_line("some data") - file.close() - - # verify - var file_read := create_temp_file("examples/game", "game.sav", FileAccess.READ) - assert_object(file_read).is_not_null() - assert_str(file_read.get_as_text()).is_equal("some data\n") - # not needs to be manually close, will be auto closed after test suite execution - - -func test_make_qualified_path() -> void: - assert_str(GdUnitFileAccess.make_qualified_path("MyTest")).is_equal("MyTest") - assert_str(GdUnitFileAccess.make_qualified_path("/MyTest.gd")).is_equal("res://MyTest.gd") - assert_str(GdUnitFileAccess.make_qualified_path("/foo/bar/MyTest.gd")).is_equal("res://foo/bar/MyTest.gd") - assert_str(GdUnitFileAccess.make_qualified_path("res://MyTest.gd")).is_equal("res://MyTest.gd") - assert_str(GdUnitFileAccess.make_qualified_path("res://foo/bar/MyTest.gd")).is_equal("res://foo/bar/MyTest.gd") - - -func test_find_last_path_index() -> void: - # not existing directory - assert_int(GdUnitFileAccess.find_last_path_index("/foo", "report_")).is_equal(0) - # empty directory - var temp_dir := create_temp_dir("test_reports") - assert_int(GdUnitFileAccess.find_last_path_index(temp_dir, "report_")).is_equal(0) - # create some report directories - create_temp_dir("test_reports/report_1") - assert_int(GdUnitFileAccess.find_last_path_index(temp_dir, "report_")).is_equal(1) - create_temp_dir("test_reports/report_2") - assert_int(GdUnitFileAccess.find_last_path_index(temp_dir, "report_")).is_equal(2) - create_temp_dir("test_reports/report_3") - assert_int(GdUnitFileAccess.find_last_path_index(temp_dir, "report_")).is_equal(3) - create_temp_dir("test_reports/report_5") - assert_int(GdUnitFileAccess.find_last_path_index(temp_dir, "report_")).is_equal(5) - # create some more - for index in range(10, 42): - create_temp_dir("test_reports/report_%d" % index) - assert_int(GdUnitFileAccess.find_last_path_index(temp_dir, "report_")).is_equal(41) - - -func test_delete_path_index_lower_equals_than() -> void: - var temp_dir := create_temp_dir("test_reports_delete") - assert_array(GdUnitFileAccess.scan_dir(temp_dir)).is_empty() - assert_int(GdUnitFileAccess.delete_path_index_lower_equals_than(temp_dir, "report_", 0)).is_equal(0) - - # create some directories - for index in range(10, 42): - create_temp_dir("test_reports_delete/report_%d" % index) - assert_array(GdUnitFileAccess.scan_dir(temp_dir)).has_size(32) - - # try to delete directories with index lower than 0, shold delete nothing - assert_int(GdUnitFileAccess.delete_path_index_lower_equals_than(temp_dir, "report_", 0)).is_equal(0) - assert_array(GdUnitFileAccess.scan_dir(temp_dir)).has_size(32) - - # try to delete directories with index lower_equals than 30 - # shold delet directories report_10 to report_30 = 21 - assert_int(GdUnitFileAccess.delete_path_index_lower_equals_than(temp_dir, "report_", 30)).is_equal(21) - # and 12 directories are left - assert_array(GdUnitFileAccess.scan_dir(temp_dir))\ - .has_size(11)\ - .contains([ - "report_31", - "report_32", - "report_33", - "report_34", - "report_35", - "report_36", - "report_37", - "report_38", - "report_39", - "report_40", - "report_41", - ]) - - -func test_scan_dir() -> void: - var temp_dir := create_temp_dir("test_scan_dir") - assert_array(GdUnitFileAccess.scan_dir(temp_dir)).is_empty() - - create_temp_dir("test_scan_dir/report_2") - assert_array(GdUnitFileAccess.scan_dir(temp_dir)).contains_exactly(["report_2"]) - # create some more directories and files - create_temp_dir("test_scan_dir/report_4") - create_temp_dir("test_scan_dir/report_5") - create_temp_dir("test_scan_dir/report_6") - create_temp_file("test_scan_dir", "file_a") - create_temp_file("test_scan_dir", "file_b") - # this shoul not be counted it is a file in a subdirectory - create_temp_file("test_scan_dir/report_6", "file_b") - assert_array(GdUnitFileAccess.scan_dir(temp_dir))\ - .has_size(6)\ - .contains([ - "report_2", - "report_4", - "report_5", - "report_6", - "file_a", - "file_b"]) - - -func test_delete_directory() -> void: - var tmp_dir := create_temp_dir("test_delete_dir") - create_temp_dir("test_delete_dir/data1") - create_temp_dir("test_delete_dir/data2") - _create_file("test_delete_dir", "example_a.txt") - _create_file("test_delete_dir", "example_b.txt") - _create_file("test_delete_dir/data1", "example.txt") - _create_file("test_delete_dir/data2", "example2.txt") - - assert_array(GdUnitFileAccess.scan_dir(tmp_dir)).contains_exactly_in_any_order([ - "data1", - "data2", - "example_a.txt", - "example_b.txt" - ]) - - # Delete the entire directory and its contents - GdUnitFileAccess.delete_directory(tmp_dir) - assert_bool(DirAccess.dir_exists_absolute(tmp_dir)).is_false() - assert_array(GdUnitFileAccess.scan_dir(tmp_dir)).is_empty() - - -func test_delete_directory_content_only() -> void: - var tmp_dir := create_temp_dir("test_delete_dir") - create_temp_dir("test_delete_dir/data1") - create_temp_dir("test_delete_dir/data2") - _create_file("test_delete_dir", "example_a.txt") - _create_file("test_delete_dir", "example_b.txt") - _create_file("test_delete_dir/data1", "example.txt") - _create_file("test_delete_dir/data2", "example2.txt") - - assert_array(GdUnitFileAccess.scan_dir(tmp_dir)).contains_exactly_in_any_order([ - "data1", - "data2", - "example_a.txt", - "example_b.txt" - ]) - - # Delete the entire directory and its contents - GdUnitFileAccess.delete_directory(tmp_dir, true) - assert_bool(DirAccess.dir_exists_absolute(tmp_dir)).is_true() - assert_array(GdUnitFileAccess.scan_dir(tmp_dir)).is_empty() - - -func test_extract_package() -> void: - clean_temp_dir() - var tmp_path := GdUnitFileAccess.create_temp_dir("test_update") - var source := "res://addons/gdUnit4/test/update/resources/update.zip" - - # the temp should be inital empty - assert_array(GdUnitFileAccess.scan_dir(tmp_path)).is_empty() - # now extract to temp - var result := GdUnitFileAccess.extract_zip(source, tmp_path) - assert_result(result).is_success() - assert_array(GdUnitFileAccess.scan_dir(tmp_path)).contains_exactly_in_any_order([ - "addons", - "runtest.cmd", - "runtest.sh", - ]) - - -func test_extract_package_invalid_package() -> void: - clean_temp_dir() - var tmp_path := GdUnitFileAccess.create_temp_dir("test_update") - var source := "res://addons/gdUnit4/test/update/resources/update_invalid.zip" - - # the temp should be inital empty - assert_array(GdUnitFileAccess.scan_dir(tmp_path)).is_empty() - # now extract to temp - var result := GdUnitFileAccess.extract_zip(source, tmp_path) - assert_result(result).is_error()\ - .contains_message("Extracting `%s` failed! Please collect the error log and report this. Error Code: 1" % source) - assert_array(GdUnitFileAccess.scan_dir(tmp_path)).is_empty() - diff --git a/addons/gdUnit4/test/core/GdUnitObjectInteractionsTemplateTest.gd b/addons/gdUnit4/test/core/GdUnitObjectInteractionsTemplateTest.gd deleted file mode 100644 index 55950fe..0000000 --- a/addons/gdUnit4/test/core/GdUnitObjectInteractionsTemplateTest.gd +++ /dev/null @@ -1,36 +0,0 @@ -# GdUnit generated TestSuite -extends GdUnitTestSuite - -# TestSuite generated from -const __source = 'res://addons/gdUnit4/src/core/GdUnitObjectInteractionsTemplate.gd' - - -func test___filter_vargs(): - var template = load(__source).new() - - var varags :Array = [ - GdObjects.TYPE_VARARG_PLACEHOLDER_VALUE, - GdObjects.TYPE_VARARG_PLACEHOLDER_VALUE, - GdObjects.TYPE_VARARG_PLACEHOLDER_VALUE, - GdObjects.TYPE_VARARG_PLACEHOLDER_VALUE] - assert_array(template.__filter_vargs(varags)).is_empty() - - var object := RefCounted.new() - varags = [ - "foo", - "bar", - null, - true, - 1, - object, - GdObjects.TYPE_VARARG_PLACEHOLDER_VALUE, - GdObjects.TYPE_VARARG_PLACEHOLDER_VALUE, - GdObjects.TYPE_VARARG_PLACEHOLDER_VALUE, - GdObjects.TYPE_VARARG_PLACEHOLDER_VALUE] - assert_array(template.__filter_vargs(varags)).contains_exactly([ - "foo", - "bar", - null, - true, - 1, - object]) diff --git a/addons/gdUnit4/test/core/GdUnitResultTest.gd b/addons/gdUnit4/test/core/GdUnitResultTest.gd deleted file mode 100644 index 1094f9c..0000000 --- a/addons/gdUnit4/test/core/GdUnitResultTest.gd +++ /dev/null @@ -1,37 +0,0 @@ -# GdUnit generated TestSuite -class_name GdUnitResultTest -extends GdUnitTestSuite - -# TestSuite generated from -const __source = 'res://addons/gdUnit4/src/core/GdUnitResult.gd' - - -func test_serde(): - var value = { - "info" : "test", - "meta" : 42 - } - var source := GdUnitResult.success(value) - var serialized_result = GdUnitResult.serialize(source) - var deserialised_result := GdUnitResult.deserialize(serialized_result) - assert_object(deserialised_result)\ - .is_instanceof(GdUnitResult) \ - .is_equal(source) - - -func test_or_else_on_success(): - var result := GdUnitResult.success("some value") - assert_str(result.value()).is_equal("some value") - assert_str(result.or_else("other value")).is_equal("some value") - - -func test_or_else_on_warning(): - var result := GdUnitResult.warn("some warning message") - assert_object(result.value()).is_null() - assert_str(result.or_else("other value")).is_equal("other value") - - -func test_or_else_on_error(): - var result := GdUnitResult.error("some error message") - assert_object(result.value()).is_null() - assert_str(result.or_else("other value")).is_equal("other value") diff --git a/addons/gdUnit4/test/core/GdUnitRunnerConfigTest.gd b/addons/gdUnit4/test/core/GdUnitRunnerConfigTest.gd deleted file mode 100644 index 03b245f..0000000 --- a/addons/gdUnit4/test/core/GdUnitRunnerConfigTest.gd +++ /dev/null @@ -1,189 +0,0 @@ -# GdUnit generated TestSuite -class_name GdUnitRunnerConfigTest -extends GdUnitTestSuite - -# TestSuite generated from -const __source = 'res://addons/gdUnit4/src/core/GdUnitRunnerConfig.gd' - - -func test_initial_config(): - var config := GdUnitRunnerConfig.new() - assert_dict(config.to_execute()).is_empty() - assert_dict(config.skipped()).is_empty() - - -func test_clear_on_initial_config(): - var config := GdUnitRunnerConfig.new() - config.clear() - assert_dict(config.to_execute()).is_empty() - assert_dict(config.skipped()).is_empty() - - -func test_clear_on_filled_config(): - var config := GdUnitRunnerConfig.new() - config.add_test_suite("res://foo") - config.skip_test_suite("res://bar") - assert_dict(config.to_execute()).is_not_empty() - assert_dict(config.skipped()).is_not_empty() - - # clear it - config.clear() - assert_dict(config.to_execute()).is_empty() - assert_dict(config.skipped()).is_empty() - - -func test_set_server_port(): - var config := GdUnitRunnerConfig.new() - # intial value - assert_int(config.server_port()).is_equal(-1) - - config.set_server_port(1000) - assert_int(config.server_port()).is_equal(1000) - - -func test_self_test(): - var config := GdUnitRunnerConfig.new() - # initial is empty - assert_dict(config.to_execute()).is_empty() - - # configure self test - var to_execute := config.self_test().to_execute() - assert_dict(to_execute).contains_key_value("res://addons/gdUnit4/test/", PackedStringArray()) - - -func test_add_test_suite(): - var config := GdUnitRunnerConfig.new() - # skip should have no affect - config.skip_test_suite("res://bar") - - config.add_test_suite("res://foo") - assert_dict(config.to_execute()).contains_key_value("res://foo", PackedStringArray()) - # add two more - config.add_test_suite("res://foo2") - config.add_test_suite("res://bar/foo") - assert_dict(config.to_execute())\ - .contains_key_value("res://foo", PackedStringArray())\ - .contains_key_value("res://foo2", PackedStringArray())\ - .contains_key_value("res://bar/foo", PackedStringArray()) - - -func test_add_test_suites(): - var config := GdUnitRunnerConfig.new() - # skip should have no affect - config.skip_test_suite("res://bar") - - config.add_test_suites(PackedStringArray(["res://foo2", "res://bar/foo", "res://foo1"])) - - assert_dict(config.to_execute())\ - .contains_key_value("res://foo1", PackedStringArray())\ - .contains_key_value("res://foo2", PackedStringArray())\ - .contains_key_value("res://bar/foo", PackedStringArray()) - - -func test_add_test_case(): - var config := GdUnitRunnerConfig.new() - # skip should have no affect - config.skip_test_suite("res://bar") - - config.add_test_case("res://foo1", "testcaseA") - assert_dict(config.to_execute()).contains_key_value("res://foo1", PackedStringArray(["testcaseA"])) - # add two more - config.add_test_case("res://foo1", "testcaseB") - config.add_test_case("res://foo2", "testcaseX") - assert_dict(config.to_execute())\ - .contains_key_value("res://foo1", PackedStringArray(["testcaseA", "testcaseB"]))\ - .contains_key_value("res://foo2", PackedStringArray(["testcaseX"])) - - -func test_add_test_suites_and_test_cases_combi(): - var config := GdUnitRunnerConfig.new() - - config.add_test_suite("res://foo1") - config.add_test_suite("res://foo2") - config.add_test_suite("res://bar/foo") - config.add_test_case("res://foo1", "testcaseA") - config.add_test_case("res://foo1", "testcaseB") - config.add_test_suites(PackedStringArray(["res://foo3", "res://bar/foo3", "res://foo4"])) - - assert_dict(config.to_execute())\ - .has_size(6)\ - .contains_key_value("res://foo1", PackedStringArray(["testcaseA", "testcaseB"]))\ - .contains_key_value("res://foo2", PackedStringArray())\ - .contains_key_value("res://foo3", PackedStringArray())\ - .contains_key_value("res://foo4", PackedStringArray())\ - .contains_key_value("res://bar/foo3", PackedStringArray())\ - .contains_key_value("res://bar/foo", PackedStringArray()) - - -func test_skip_test_suite(): - var config := GdUnitRunnerConfig.new() - - config.skip_test_suite("res://foo1") - assert_dict(config.skipped()).contains_key_value("res://foo1", PackedStringArray()) - # add two more - config.skip_test_suite("res://foo2") - config.skip_test_suite("res://bar/foo1") - assert_dict(config.skipped())\ - .contains_key_value("res://foo1", PackedStringArray())\ - .contains_key_value("res://foo2", PackedStringArray())\ - .contains_key_value("res://bar/foo1", PackedStringArray()) - - -func test_skip_test_suite_and_test_case(): - var possible_paths :PackedStringArray = [ - "/foo/MyTest.gd", - "res://foo/MyTest.gd", - "/foo/MyTest.gd:test_x", - "res://foo/MyTest.gd:test_y", - "MyTest", - "MyTest:test", - "MyTestX", - ] - var config := GdUnitRunnerConfig.new() - for path in possible_paths: - config.skip_test_suite(path) - assert_dict(config.skipped())\ - .has_size(3)\ - .contains_key_value("res://foo/MyTest.gd", PackedStringArray(["test_x", "test_y"]))\ - .contains_key_value("MyTest", PackedStringArray(["test"]))\ - .contains_key_value("MyTestX", PackedStringArray()) - - -func test_skip_test_case(): - var config := GdUnitRunnerConfig.new() - - config.skip_test_case("res://foo1", "testcaseA") - assert_dict(config.skipped()).contains_key_value("res://foo1", PackedStringArray(["testcaseA"])) - # add two more - config.skip_test_case("res://foo1", "testcaseB") - config.skip_test_case("res://foo2", "testcaseX") - assert_dict(config.skipped())\ - .contains_key_value("res://foo1", PackedStringArray(["testcaseA", "testcaseB"]))\ - .contains_key_value("res://foo2", PackedStringArray(["testcaseX"])) - - -func test_load_fail(): - var config := GdUnitRunnerConfig.new() - - assert_result(config.load_config("invalid_path"))\ - .is_error()\ - .contains_message("Can't find test runner configuration 'invalid_path'! Please select a test to run.") - - -func test_save_load(): - var config := GdUnitRunnerConfig.new() - # add some dummy conf - config.set_server_port(1000) - config.skip_test_suite("res://bar") - config.add_test_suite("res://foo2") - config.add_test_case("res://foo1", "testcaseA") - - var config_file := create_temp_dir("test_save_load") + "/testconf.cfg" - - assert_result(config.save_config(config_file)).is_success() - assert_file(config_file).exists() - - var config2 := GdUnitRunnerConfig.new() - assert_result(config2.load_config(config_file)).is_success() - # verify the config has original enties - assert_object(config2).is_equal(config).is_not_same(config) diff --git a/addons/gdUnit4/test/core/GdUnitSceneRunnerInputEventTest.gd b/addons/gdUnit4/test/core/GdUnitSceneRunnerInputEventTest.gd deleted file mode 100644 index 926c176..0000000 --- a/addons/gdUnit4/test/core/GdUnitSceneRunnerInputEventTest.gd +++ /dev/null @@ -1,527 +0,0 @@ -# GdUnit generated TestSuite -extends GdUnitTestSuite - -# TestSuite generated from -const __source = 'res://addons/gdUnit4/src/core/GdUnitSceneRunner.gd' - - -var _runner :GdUnitSceneRunner -var _scene_spy :Node - - -func before_test(): - _scene_spy = spy("res://addons/gdUnit4/test/mocker/resources/scenes/TestScene.tscn") - _runner = scene_runner(_scene_spy) - assert_inital_mouse_state() - assert_inital_key_state() - - -# asserts to KeyList Enums -func assert_inital_key_state(): - # scacode 4194304-4194415 - for key in range(KEY_SPECIAL, KEY_LAUNCHF): - assert_that(Input.is_key_pressed(key)).is_false() - assert_that(Input.is_physical_key_pressed(key)).is_false() - # keycode 32-255 - for key in range(KEY_SPACE, KEY_SECTION): - assert_that(Input.is_key_pressed(key)).is_false() - assert_that(Input.is_physical_key_pressed(key)).is_false() - - -#asserts to Mouse ButtonList Enums -func assert_inital_mouse_state(): - for button in [ - MOUSE_BUTTON_LEFT, - MOUSE_BUTTON_MIDDLE, - MOUSE_BUTTON_RIGHT, - MOUSE_BUTTON_XBUTTON1, - MOUSE_BUTTON_XBUTTON2, - MOUSE_BUTTON_WHEEL_UP, - MOUSE_BUTTON_WHEEL_DOWN, - MOUSE_BUTTON_WHEEL_LEFT, - MOUSE_BUTTON_WHEEL_RIGHT, - ]: - assert_that(Input.is_mouse_button_pressed(button)).is_false() - assert_that(Input.get_mouse_button_mask()).is_equal(0) - - -func test_reset_to_inital_state_on_release(): - var runner = scene_runner("res://addons/gdUnit4/test/mocker/resources/scenes/TestScene.tscn") - # simulate mouse buttons and key press but we never released it - runner.simulate_mouse_button_press(MOUSE_BUTTON_LEFT) - runner.simulate_mouse_button_press(MOUSE_BUTTON_RIGHT) - runner.simulate_mouse_button_press(MOUSE_BUTTON_MIDDLE) - runner.simulate_key_press(KEY_0) - runner.simulate_key_press(KEY_X) - await await_idle_frame() - assert_that(Input.is_mouse_button_pressed(MOUSE_BUTTON_LEFT)).is_true() - assert_that(Input.is_mouse_button_pressed(MOUSE_BUTTON_RIGHT)).is_true() - assert_that(Input.is_mouse_button_pressed(MOUSE_BUTTON_MIDDLE)).is_true() - assert_that(Input.is_key_pressed(KEY_0)).is_true() - assert_that(Input.is_key_pressed(KEY_X)).is_true() - # unreference the scene runner to enforce reset to initial Input state - runner._notification(NOTIFICATION_PREDELETE) - await await_idle_frame() - assert_that(Input.is_mouse_button_pressed(MOUSE_BUTTON_LEFT)).is_false() - assert_that(Input.is_mouse_button_pressed(MOUSE_BUTTON_RIGHT)).is_false() - assert_that(Input.is_mouse_button_pressed(MOUSE_BUTTON_MIDDLE)).is_false() - assert_that(Input.is_key_pressed(KEY_0)).is_false() - assert_that(Input.is_key_pressed(KEY_X)).is_false() - - -func test_simulate_key_press() -> void: - # iterate over some example keys - for key in [KEY_A, KEY_D, KEY_X, KEY_0]: - _runner.simulate_key_press(key) - await await_idle_frame() - - var event := InputEventKey.new() - event.keycode = key - event.physical_keycode = key - event.pressed = true - verify(_scene_spy, 1)._input(event) - assert_that(Input.is_key_pressed(key)).is_true() - # verify all this keys are still handled as pressed - assert_that(Input.is_key_pressed(KEY_A)).is_true() - assert_that(Input.is_key_pressed(KEY_D)).is_true() - assert_that(Input.is_key_pressed(KEY_X)).is_true() - assert_that(Input.is_key_pressed(KEY_0)).is_true() - # other keys are not pressed - assert_that(Input.is_key_pressed(KEY_B)).is_false() - assert_that(Input.is_key_pressed(KEY_G)).is_false() - assert_that(Input.is_key_pressed(KEY_Z)).is_false() - assert_that(Input.is_key_pressed(KEY_1)).is_false() - - -func test_simulate_key_press_with_modifiers() -> void: - # press shift key + A - _runner.simulate_key_press(KEY_SHIFT) - _runner.simulate_key_press(KEY_A) - await await_idle_frame() - - # results in two events, first is the shift key is press - var event := InputEventKey.new() - event.keycode = KEY_SHIFT - event.physical_keycode = KEY_SHIFT - event.pressed = true - event.shift_pressed = true - verify(_scene_spy, 1)._input(event) - - # second is the comnbination of current press shift and key A - event = InputEventKey.new() - event.keycode = KEY_A - event.physical_keycode = KEY_A - event.pressed = true - event.shift_pressed = true - verify(_scene_spy, 1)._input(event) - assert_that(Input.is_key_pressed(KEY_SHIFT)).is_true() - assert_that(Input.is_key_pressed(KEY_A)).is_true() - - -func test_simulate_many_keys_press() -> void: - # press and hold keys W and Z - _runner.simulate_key_press(KEY_W) - _runner.simulate_key_press(KEY_Z) - await await_idle_frame() - - assert_that(Input.is_key_pressed(KEY_W)).is_true() - assert_that(Input.is_physical_key_pressed(KEY_W)).is_true() - assert_that(Input.is_key_pressed(KEY_Z)).is_true() - assert_that(Input.is_physical_key_pressed(KEY_Z)).is_true() - - #now release key w - _runner.simulate_key_release(KEY_W) - await await_idle_frame() - - assert_that(Input.is_key_pressed(KEY_W)).is_false() - assert_that(Input.is_physical_key_pressed(KEY_W)).is_false() - assert_that(Input.is_key_pressed(KEY_Z)).is_true() - assert_that(Input.is_physical_key_pressed(KEY_Z)).is_true() - - -func test_simulate_keypressed_as_action() -> void: - # add custom action `player_jump` for key 'Space' is pressed - var event := InputEventKey.new() - event.keycode = KEY_SPACE - InputMap.add_action("player_jump") - InputMap.action_add_event("player_jump", event) - var runner := scene_runner("res://addons/gdUnit4/test/core/resources/scenes/input_actions/InputEventTestScene.tscn") - - # precondition checks - var action_event = InputMap.action_get_events("player_jump") - assert_array(action_event).contains_exactly([event]) - assert_bool(Input.is_action_just_released("player_jump", true)).is_false() - assert_bool(Input.is_action_just_released("ui_accept", true)).is_false() - assert_bool(Input.is_action_just_released("ui_select", true)).is_false() - assert_bool(runner.scene()._player_jump_action_released).is_false() - - # test a key event is trigger action event - # simulate press space - runner.simulate_key_pressed(KEY_SPACE) - # it is important do not wait for next frame here, otherwise the input action cache is cleared and can't be use to verify - assert_bool(Input.is_action_just_released("player_jump", true)).is_true() - assert_bool(Input.is_action_just_released("ui_accept", true)).is_true() - assert_bool(Input.is_action_just_released("ui_select", true)).is_true() - assert_bool(runner.scene()._player_jump_action_released).is_true() - - # test a key event is not trigger the custom action event - # simulate press only space+ctrl - runner._reset_input_to_default() - runner.simulate_key_pressed(KEY_SPACE, false, true) - # it is important do not wait for next frame here, otherwise the input action cache is cleared and can't be use to verify - assert_bool(Input.is_action_just_released("player_jump", true)).is_false() - assert_bool(Input.is_action_just_released("ui_accept", true)).is_false() - assert_bool(Input.is_action_just_released("ui_select", true)).is_false() - assert_bool(runner.scene()._player_jump_action_released).is_false() - - # cleanup custom action - InputMap.erase_action("player_jump") - InputMap.action_erase_events("player_jump") - - -func test_simulate_set_mouse_pos(): - # save current global mouse pos - var gmp := _runner.get_global_mouse_position() - # set mouse to pos 100, 100 - _runner.set_mouse_pos(Vector2(100, 100)) - await await_idle_frame() - var event := InputEventMouseMotion.new() - event.position = Vector2(100, 100) - event.global_position = gmp - verify(_scene_spy, 1)._input(event) - - # set mouse to pos 800, 400 - gmp = _runner.get_global_mouse_position() - _runner.set_mouse_pos(Vector2(800, 400)) - await await_idle_frame() - event = InputEventMouseMotion.new() - event.position = Vector2(800, 400) - event.global_position = gmp - verify(_scene_spy, 1)._input(event) - - # and again back to 100,100 - gmp = _runner.get_global_mouse_position() - _runner.set_mouse_pos(Vector2(100, 100)) - await await_idle_frame() - event = InputEventMouseMotion.new() - event.position = Vector2(100, 100) - event.global_position = gmp - verify(_scene_spy, 1)._input(event) - - -func test_simulate_set_mouse_pos_with_modifiers(): - var is_alt := false - var is_control := false - var is_shift := false - - for modifier in [KEY_SHIFT, KEY_CTRL, KEY_ALT]: - is_alt = is_alt or KEY_ALT == modifier - is_control = is_control or KEY_CTRL == modifier - is_shift = is_shift or KEY_SHIFT == modifier - - for mouse_button in [MOUSE_BUTTON_LEFT, MOUSE_BUTTON_MIDDLE, MOUSE_BUTTON_RIGHT]: - # simulate press shift, set mouse pos and final press mouse button - var gmp := _runner.get_global_mouse_position() - _runner.simulate_key_press(modifier) - _runner.set_mouse_pos(Vector2.ZERO) - _runner.simulate_mouse_button_press(mouse_button) - await await_idle_frame() - - var event := InputEventMouseButton.new() - event.position = Vector2.ZERO - event.global_position = gmp - event.alt_pressed = is_alt - event.ctrl_pressed = is_control - event.shift_pressed = is_shift - event.pressed = true - event.button_index = mouse_button - event.button_mask = GdUnitSceneRunnerImpl.MAP_MOUSE_BUTTON_MASKS.get(mouse_button) - verify(_scene_spy, 1)._input(event) - assert_that(Input.is_mouse_button_pressed(mouse_button)).is_true() - # finally release it - _runner.simulate_mouse_button_release(mouse_button) - await await_idle_frame() - - -func test_simulate_mouse_move(): - _runner.set_mouse_pos(Vector2(10, 10)) - var gmp := _runner.get_global_mouse_position() - _runner.simulate_mouse_move(Vector2(400, 100)) - await await_idle_frame() - - var event = InputEventMouseMotion.new() - event.position = Vector2(400, 100) - event.global_position = gmp - event.relative = Vector2(400, 100) - Vector2(10, 10) - verify(_scene_spy, 1)._input(event) - - # move mouse to next pos - gmp = _runner.get_global_mouse_position() - _runner.simulate_mouse_move(Vector2(55, 42)) - await await_idle_frame() - - event = InputEventMouseMotion.new() - event.position = Vector2(55, 42) - event.global_position = gmp - event.relative = Vector2(55, 42) - Vector2(400, 100) - verify(_scene_spy, 1)._input(event) - - -func test_simulate_mouse_move_relative(): - #OS.window_minimized = false - _runner.set_mouse_pos(Vector2(10, 10)) - await await_idle_frame() - assert_that(_runner.get_mouse_position()).is_equal(Vector2(10, 10)) - - # move the mouse in time of 1 second - # the final position is current + relative = Vector2(10, 10) + (Vector2(900, 400) - await _runner.simulate_mouse_move_relative(Vector2(900, 400), 1) - assert_vector(_runner.get_mouse_position()).is_equal_approx(Vector2(910, 410), Vector2.ONE) - - # move the mouse back in time of 0.1 second - # Use the negative value of the previously moved action to move it back to the starting position - await _runner.simulate_mouse_move_relative(Vector2(-900, -400), 0.1) - assert_vector(_runner.get_mouse_position()).is_equal_approx(Vector2(10, 10), Vector2.ONE) - - -func test_simulate_mouse_move_absolute(): - #OS.window_minimized = false - _runner.set_mouse_pos(Vector2(10, 10)) - await await_idle_frame() - assert_that(_runner.get_mouse_position()).is_equal(Vector2(10, 10)) - - # move the mouse in time of 1 second - await _runner.simulate_mouse_move_absolute(Vector2(900, 400), 1) - assert_vector(_runner.get_mouse_position()).is_equal_approx(Vector2(900, 400), Vector2.ONE) - - # move the mouse back in time of 0.1 second - await _runner.simulate_mouse_move_absolute(Vector2(10, 10), 0.1) - assert_vector(_runner.get_mouse_position()).is_equal_approx(Vector2(10, 10), Vector2.ONE) - - -func test_simulate_mouse_button_press_left(): - # simulate mouse button press and hold - var gmp := _runner.get_global_mouse_position() - _runner.simulate_mouse_button_press(MOUSE_BUTTON_LEFT) - await await_idle_frame() - - var event := InputEventMouseButton.new() - event.position = Vector2.ZERO - event.global_position = gmp - event.pressed = true - event.button_index = MOUSE_BUTTON_LEFT - event.button_mask = GdUnitSceneRunnerImpl.MAP_MOUSE_BUTTON_MASKS.get(MOUSE_BUTTON_LEFT) - verify(_scene_spy, 1)._input(event) - assert_that(Input.is_mouse_button_pressed(MOUSE_BUTTON_LEFT)).is_true() - - -func test_simulate_mouse_button_press_left_doubleclick(): - # simulate mouse button press double_click - var gmp := _runner.get_global_mouse_position() - _runner.simulate_mouse_button_press(MOUSE_BUTTON_LEFT, true) - await await_idle_frame() - - var event := InputEventMouseButton.new() - event.position = Vector2.ZERO - event.global_position = gmp - event.pressed = true - event.double_click = true - event.button_index = MOUSE_BUTTON_LEFT - event.button_mask = GdUnitSceneRunnerImpl.MAP_MOUSE_BUTTON_MASKS.get(MOUSE_BUTTON_LEFT) - verify(_scene_spy, 1)._input(event) - assert_that(Input.is_mouse_button_pressed(MOUSE_BUTTON_LEFT)).is_true() - - -func test_simulate_mouse_button_press_right(): - # simulate mouse button press and hold - var gmp := _runner.get_global_mouse_position() - _runner.simulate_mouse_button_press(MOUSE_BUTTON_RIGHT) - await await_idle_frame() - - var event := InputEventMouseButton.new() - event.position = Vector2.ZERO - event.global_position = gmp - event.pressed = true - event.button_index = MOUSE_BUTTON_RIGHT - event.button_mask = GdUnitSceneRunnerImpl.MAP_MOUSE_BUTTON_MASKS.get(MOUSE_BUTTON_RIGHT) - verify(_scene_spy, 1)._input(event) - assert_that(Input.is_mouse_button_pressed(MOUSE_BUTTON_RIGHT)).is_true() - - -func test_simulate_mouse_button_press_left_and_right(): - # simulate mouse button press left+right - var gmp := _runner.get_global_mouse_position() - _runner.simulate_mouse_button_press(MOUSE_BUTTON_LEFT) - _runner.simulate_mouse_button_press(MOUSE_BUTTON_RIGHT) - await await_idle_frame() - - # results in two events, first is left mouse button - var event := InputEventMouseButton.new() - event.position = Vector2.ZERO - event.global_position = gmp - event.pressed = true - event.button_index = MOUSE_BUTTON_LEFT - event.button_mask = MOUSE_BUTTON_MASK_LEFT - verify(_scene_spy, 1)._input(event) - - # second is left+right and combined mask - event = InputEventMouseButton.new() - event.position = Vector2.ZERO - event.global_position = gmp - event.pressed = true - event.button_index = MOUSE_BUTTON_RIGHT - event.button_mask = MOUSE_BUTTON_MASK_LEFT|MOUSE_BUTTON_MASK_RIGHT - verify(_scene_spy, 1)._input(event) - assert_that(Input.is_mouse_button_pressed(MOUSE_BUTTON_LEFT)).is_true() - assert_that(Input.is_mouse_button_pressed(MOUSE_BUTTON_RIGHT)).is_true() - assert_that(Input.get_mouse_button_mask()).is_equal(MOUSE_BUTTON_MASK_LEFT|MOUSE_BUTTON_MASK_RIGHT) - - -func test_simulate_mouse_button_press_left_and_right_and_release(): - # simulate mouse button press left+right - var gmp := _runner.get_global_mouse_position() - _runner.simulate_mouse_button_press(MOUSE_BUTTON_LEFT) - _runner.simulate_mouse_button_press(MOUSE_BUTTON_RIGHT) - await await_idle_frame() - - # will results into two events - # first for left mouse button - var event := InputEventMouseButton.new() - event.position = Vector2.ZERO - event.global_position = gmp - event.pressed = true - event.button_index = MOUSE_BUTTON_LEFT - event.button_mask = MOUSE_BUTTON_MASK_LEFT - verify(_scene_spy, 1)._input(event) - - # second is left+right and combined mask - event = InputEventMouseButton.new() - event.position = Vector2.ZERO - event.global_position = gmp - event.pressed = true - event.button_index = MOUSE_BUTTON_RIGHT - event.button_mask = MOUSE_BUTTON_MASK_LEFT|MOUSE_BUTTON_MASK_RIGHT - verify(_scene_spy, 1)._input(event) - assert_that(Input.is_mouse_button_pressed(MOUSE_BUTTON_LEFT)).is_true() - assert_that(Input.is_mouse_button_pressed(MOUSE_BUTTON_RIGHT)).is_true() - assert_that(Input.get_mouse_button_mask()).is_equal(MOUSE_BUTTON_MASK_LEFT|MOUSE_BUTTON_MASK_RIGHT) - - # now release the right button - gmp = _runner.get_global_mouse_position() - _runner.simulate_mouse_button_pressed(MOUSE_BUTTON_RIGHT) - await await_idle_frame() - # will result in right button press false but stay with mask for left pressed - event = InputEventMouseButton.new() - event.position = Vector2.ZERO - event.global_position = gmp - event.pressed = false - event.button_index = MOUSE_BUTTON_RIGHT - event.button_mask = MOUSE_BUTTON_MASK_LEFT - verify(_scene_spy, 1)._input(event) - assert_that(Input.is_mouse_button_pressed(MOUSE_BUTTON_LEFT)).is_true() - assert_that(Input.is_mouse_button_pressed(MOUSE_BUTTON_RIGHT)).is_false() - assert_that(Input.get_mouse_button_mask()).is_equal(MOUSE_BUTTON_MASK_LEFT) - - # finally relase left button - gmp = _runner.get_global_mouse_position() - _runner.simulate_mouse_button_pressed(MOUSE_BUTTON_LEFT) - await await_idle_frame() - # will result in right button press false but stay with mask for left pressed - event = InputEventMouseButton.new() - event.position = Vector2.ZERO - event.global_position = gmp - event.pressed = false - event.button_index = MOUSE_BUTTON_LEFT - event.button_mask = 0 - verify(_scene_spy, 1)._input(event) - assert_that(Input.is_mouse_button_pressed(MOUSE_BUTTON_LEFT)).is_false() - assert_that(Input.is_mouse_button_pressed(MOUSE_BUTTON_RIGHT)).is_false() - assert_that(Input.get_mouse_button_mask()).is_equal(0) - - -func test_simulate_mouse_button_pressed(): - for mouse_button in [MOUSE_BUTTON_LEFT, MOUSE_BUTTON_MIDDLE, MOUSE_BUTTON_RIGHT]: - # simulate mouse button press and release - var gmp := _runner.get_global_mouse_position() - _runner.simulate_mouse_button_pressed(mouse_button) - await await_idle_frame() - - # it genrates two events, first for press and second as released - var event := InputEventMouseButton.new() - event.position = Vector2.ZERO - event.global_position = gmp - event.pressed = true - event.button_index = mouse_button - event.button_mask = GdUnitSceneRunnerImpl.MAP_MOUSE_BUTTON_MASKS.get(mouse_button) - verify(_scene_spy, 1)._input(event) - - event = InputEventMouseButton.new() - event.position = Vector2.ZERO - event.global_position = gmp - event.pressed = false - event.button_index = mouse_button - event.button_mask = 0 - verify(_scene_spy, 1)._input(event) - assert_that(Input.is_mouse_button_pressed(mouse_button)).is_false() - verify(_scene_spy, 2)._input(any_class(InputEventMouseButton)) - reset(_scene_spy) - -func test_simulate_mouse_button_pressed_doubleclick(): - for mouse_button in [MOUSE_BUTTON_LEFT, MOUSE_BUTTON_MIDDLE, MOUSE_BUTTON_RIGHT]: - # simulate mouse button press and release by double_click - var gmp := _runner.get_global_mouse_position() - _runner.simulate_mouse_button_pressed(mouse_button, true) - await await_idle_frame() - - # it genrates two events, first for press and second as released - var event := InputEventMouseButton.new() - event.position = Vector2.ZERO - event.global_position = gmp - event.pressed = true - event.double_click = true - event.button_index = mouse_button - event.button_mask = GdUnitSceneRunnerImpl.MAP_MOUSE_BUTTON_MASKS.get(mouse_button) - verify(_scene_spy, 1)._input(event) - - event = InputEventMouseButton.new() - event.position = Vector2.ZERO - event.global_position = gmp - event.pressed = false - event.double_click = false - event.button_index = mouse_button - event.button_mask = 0 - verify(_scene_spy, 1)._input(event) - assert_that(Input.is_mouse_button_pressed(mouse_button)).is_false() - verify(_scene_spy, 2)._input(any_class(InputEventMouseButton)) - reset(_scene_spy) - - -func test_simulate_mouse_button_press_and_release(): - for mouse_button in [MOUSE_BUTTON_LEFT, MOUSE_BUTTON_MIDDLE, MOUSE_BUTTON_RIGHT]: - var gmp := _runner.get_global_mouse_position() - # simulate mouse button press and release - _runner.simulate_mouse_button_press(mouse_button) - await await_idle_frame() - - var event := InputEventMouseButton.new() - event.position = Vector2.ZERO - event.global_position = gmp - event.pressed = true - event.button_index = mouse_button - event.button_mask = GdUnitSceneRunnerImpl.MAP_MOUSE_BUTTON_MASKS.get(mouse_button) - verify(_scene_spy, 1)._input(event) - assert_that(Input.is_mouse_button_pressed(mouse_button)).is_true() - - # now simulate mouse button release - gmp = _runner.get_global_mouse_position() - _runner.simulate_mouse_button_release(mouse_button) - await await_idle_frame() - - event = InputEventMouseButton.new() - event.position = Vector2.ZERO - event.global_position = gmp - event.pressed = false - event.button_index = mouse_button - #event.button_mask = 0 - verify(_scene_spy, 1)._input(event) - assert_that(Input.is_mouse_button_pressed(mouse_button)).is_false() diff --git a/addons/gdUnit4/test/core/GdUnitSceneRunnerTest.gd b/addons/gdUnit4/test/core/GdUnitSceneRunnerTest.gd deleted file mode 100644 index c5edd60..0000000 --- a/addons/gdUnit4/test/core/GdUnitSceneRunnerTest.gd +++ /dev/null @@ -1,356 +0,0 @@ -# GdUnit generated TestSuite -class_name GdUnitSceneRunnerTest -extends GdUnitTestSuite - -# TestSuite generated from -const __source = 'res://addons/gdUnit4/src/core/GdUnitSceneRunnerImpl.gd' - - -# loads the test runner and register for auto freeing after test -func load_test_scene() -> Node: - return auto_free(load("res://addons/gdUnit4/test/mocker/resources/scenes/TestScene.tscn").instantiate()) - - -func before(): - # use a dedicated FPS because we calculate frames by time - Engine.set_max_fps(60) - - -func after(): - Engine.set_max_fps(0) - - -func test_get_property() -> void: - var runner := scene_runner(load_test_scene()) - - assert_that(runner.get_property("_box1")).is_instanceof(ColorRect) - assert_that(runner.get_property("_invalid")).is_equal("The property '_invalid' not exist checked loaded scene.") - assert_that(runner.get_property("_nullable")).is_null() - - -func test_set_property() -> void: - var runner := scene_runner(load_test_scene()) - - assert_that(runner.set_property("_invalid", 42)).is_equal(false) - - assert_that(runner.set_property("_nullable", RefCounted.new())).is_equal(true) - assert_that(runner.get_property("_nullable")).is_instanceof(RefCounted) - -func test_invoke_method() -> void: - var runner := scene_runner(load_test_scene()) - - assert_that(runner.invoke("add", 10, 12)).is_equal(22) - assert_that(runner.invoke("sub", 10, 12)).is_equal("The method 'sub' not exist checked loaded scene.") - - -@warning_ignore("unused_parameter") -func test_simulate_frames(timeout = 5000) -> void: - var runner := scene_runner(load_test_scene()) - var box1 :ColorRect = runner.get_property("_box1") - # initial is white - assert_object(box1.color).is_equal(Color.WHITE) - - # start color cycle by invoke the function 'start_color_cycle' - runner.invoke("start_color_cycle") - - # we wait for 10 frames - await runner.simulate_frames(10) - # after 10 frame is still white - assert_object(box1.color).is_equal(Color.WHITE) - - # we wait 30 more frames - await runner.simulate_frames(30) - # after 40 frames the box one should be changed to red - assert_object(box1.color).is_equal(Color.RED) - - -@warning_ignore("unused_parameter") -func test_simulate_frames_withdelay(timeout = 4000) -> void: - var runner := scene_runner(load_test_scene()) - var box1 :ColorRect = runner.get_property("_box1") - # initial is white - assert_object(box1.color).is_equal(Color.WHITE) - - # start color cycle by invoke the function 'start_color_cycle' - runner.invoke("start_color_cycle") - - # we wait for 10 frames each with a 50ms delay - await runner.simulate_frames(10, 50) - # after 10 frame and in sum 500ms is should be changed to red - assert_object(box1.color).is_equal(Color.RED) - - -@warning_ignore("unused_parameter") -func test_run_scene_colorcycle(timeout=2000) -> void: - var runner := scene_runner(load_test_scene()) - var box1 :ColorRect = runner.get_property("_box1") - # verify inital color - assert_object(box1.color).is_equal(Color.WHITE) - - # start color cycle by invoke the function 'start_color_cycle' - runner.invoke("start_color_cycle") - - # await for each color cycle is emited - await runner.await_signal("panel_color_change", [box1, Color.RED]) - assert_object(box1.color).is_equal(Color.RED) - await runner.await_signal("panel_color_change", [box1, Color.BLUE]) - assert_object(box1.color).is_equal(Color.BLUE) - await runner.await_signal("panel_color_change", [box1, Color.GREEN]) - assert_object(box1.color).is_equal(Color.GREEN) - - -func test_simulate_scene_inteaction_by_press_enter(timeout=2000) -> void: - var runner := scene_runner(load_test_scene()) - - # inital no spell is fired - assert_object(runner.find_child("Spell")).is_null() - - # fire spell be pressing enter key - runner.simulate_key_pressed(KEY_ENTER) - # wait until next frame - await await_idle_frame() - - # verify a spell is created - assert_object(runner.find_child("Spell")).is_not_null() - - # wait until spell is explode after around 1s - var spell = runner.find_child("Spell") - if spell == null: - return - await await_signal_on(spell, "spell_explode", [spell], timeout) - - # verify spell is removed when is explode - assert_object(runner.find_child("Spell")).is_null() - - -# mock on a runner and spy on created spell -func test_simulate_scene_inteaction_in_combination_with_spy(): - var spy_ = spy(load_test_scene()) - # create a runner runner - var runner := scene_runner(spy_) - - # simulate a key event to fire a spell - runner.simulate_key_pressed(KEY_ENTER) - verify(spy_).create_spell() - - var spell = runner.find_child("Spell") - assert_that(spell).is_not_null() - assert_that(spell.is_connected("spell_explode", Callable(spy_, "_destroy_spell"))).is_true() - - -func test_simulate_scene_interact_with_buttons(): - var spyed_scene = spy("res://addons/gdUnit4/test/mocker/resources/scenes/TestScene.tscn") - var runner := scene_runner(spyed_scene) - # test button 1 interaction - await await_millis(1000) - runner.set_mouse_pos(Vector2(60, 20)) - runner.simulate_mouse_button_pressed(MOUSE_BUTTON_LEFT) - await await_idle_frame() - verify(spyed_scene)._on_panel_color_changed(spyed_scene._box1, Color.RED) - verify(spyed_scene)._on_panel_color_changed(spyed_scene._box1, Color.GRAY) - verify(spyed_scene, 0)._on_panel_color_changed(spyed_scene._box2, any_color()) - verify(spyed_scene, 0)._on_panel_color_changed(spyed_scene._box3, any_color()) - - # test button 2 interaction - reset(spyed_scene) - await await_millis(1000) - runner.set_mouse_pos(Vector2(160, 20)) - runner.simulate_mouse_button_pressed(MOUSE_BUTTON_LEFT) - await await_idle_frame() - verify(spyed_scene, 0)._on_panel_color_changed(spyed_scene._box1, any_color()) - verify(spyed_scene)._on_panel_color_changed(spyed_scene._box2, Color.RED) - verify(spyed_scene)._on_panel_color_changed(spyed_scene._box2, Color.GRAY) - verify(spyed_scene, 0)._on_panel_color_changed(spyed_scene._box3, any_color()) - - # test button 3 interaction (is changed after 1s to gray) - reset(spyed_scene) - await await_millis(1000) - runner.set_mouse_pos(Vector2(260, 20)) - runner.simulate_mouse_button_pressed(MOUSE_BUTTON_LEFT) - await await_idle_frame() - verify(spyed_scene, 0)._on_panel_color_changed(spyed_scene._box1, any_color()) - verify(spyed_scene, 0)._on_panel_color_changed(spyed_scene._box2, any_color()) - # is changed to red - verify(spyed_scene)._on_panel_color_changed(spyed_scene._box3, Color.RED) - # no gray - verify(spyed_scene, 0)._on_panel_color_changed(spyed_scene._box3, Color.GRAY) - # after one second is changed to gray - await await_millis(1200) - verify(spyed_scene)._on_panel_color_changed(spyed_scene._box3, Color.GRAY) - - -func test_await_func_without_time_factor() -> void: - var runner := scene_runner(load_test_scene()) - await runner.await_func("color_cycle").is_equal("black") - - -func test_await_func_with_time_factor() -> void: - var runner := scene_runner(load_test_scene()) - - # set max time factor to minimize waiting time checked `runner.wait_func` - runner.set_time_factor(10) - await runner.await_func("color_cycle").wait_until(200).is_equal("black") - - -func test_await_signal_without_time_factor() -> void: - var runner := scene_runner(load_test_scene()) - var box1 :ColorRect = runner.get_property("_box1") - - runner.invoke("start_color_cycle") - await runner.await_signal("panel_color_change", [box1, Color.RED]) - await runner.await_signal("panel_color_change", [box1, Color.BLUE]) - await runner.await_signal("panel_color_change", [box1, Color.GREEN]) - ( - # should be interrupted is will never change to Color.KHAKI - await assert_failure_await(func x(): await runner.await_signal( "panel_color_change", [box1, Color.KHAKI], 300)) - ).has_message("await_signal_on(panel_color_change, [%s, %s]) timed out after 300ms" % [str(box1), str(Color.KHAKI)])\ - .has_line(205) - - -func test_await_signal_with_time_factor() -> void: - var runner := scene_runner(load_test_scene()) - var box1 :ColorRect = runner.get_property("_box1") - # set max time factor to minimize waiting time checked `runner.wait_func` - runner.set_time_factor(10) - runner.invoke("start_color_cycle") - - await runner.await_signal("panel_color_change", [box1, Color.RED], 100) - await runner.await_signal("panel_color_change", [box1, Color.BLUE], 100) - await runner.await_signal("panel_color_change", [box1, Color.GREEN], 100) - ( - # should be interrupted is will never change to Color.KHAKI - await assert_failure_await(func x(): await runner.await_signal("panel_color_change", [box1, Color.KHAKI], 30)) - ).has_message("await_signal_on(panel_color_change, [%s, %s]) timed out after 30ms" % [str(box1), str(Color.KHAKI)])\ - .has_line(222) - - -func test_simulate_until_signal() -> void: - var runner := scene_runner(load_test_scene()) - var box1 :ColorRect = runner.get_property("_box1") - - # set max time factor to minimize waiting time checked `runner.wait_func` - runner.invoke("start_color_cycle") - - await runner.simulate_until_signal("panel_color_change", box1, Color.RED) - await runner.simulate_until_signal("panel_color_change", box1, Color.BLUE) - await runner.simulate_until_signal("panel_color_change", box1, Color.GREEN) - - -@warning_ignore("unused_parameter") -func test_simulate_until_object_signal(timeout=2000) -> void: - var runner := scene_runner(load_test_scene()) - - # inital no spell is fired - assert_object(runner.find_child("Spell")).is_null() - - # fire spell be pressing enter key - runner.simulate_key_pressed(KEY_ENTER) - # wait until next frame - await await_idle_frame() - var spell = runner.find_child("Spell") - prints(spell) - - # simmulate scene until the spell is explode - await runner.simulate_until_object_signal(spell, "spell_explode", spell) - - # verify spell is removed when is explode - assert_object(runner.find_child("Spell")).is_null() - - -func test_runner_by_null_instance() -> void: - var runner := scene_runner(null) - assert_object(runner._current_scene).is_null() - - -func test_runner_by_invalid_resource_path() -> void: - # not existing scene - assert_object(scene_runner("res://test_scene.tscn")._current_scene).is_null() - # not a path to a scene - assert_object(scene_runner("res://addons/gdUnit4/test/core/resources/scenes/simple_scene.gd")._current_scene).is_null() - - -func test_runner_by_resource_path() -> void: - var runner = scene_runner("res://addons/gdUnit4/test/core/resources/scenes/simple_scene.tscn") - assert_object(runner.scene()).is_instanceof(Node2D) - - # verify the scene is freed when the runner is freed - var scene = runner.scene() - assert_bool(is_instance_valid(scene)).is_true() - runner._notification(NOTIFICATION_PREDELETE) - # give engine time to free the resources - await await_idle_frame() - # verify runner and scene is freed - assert_bool(is_instance_valid(scene)).is_false() - - -func test_runner_by_invalid_scene_instance() -> void: - var scene = RefCounted.new() - var runner := scene_runner(scene) - assert_object(runner._current_scene).is_null() - - -func test_runner_by_scene_instance() -> void: - var scene = load("res://addons/gdUnit4/test/core/resources/scenes/simple_scene.tscn").instantiate() - var runner := scene_runner(scene) - assert_object(runner.scene()).is_instanceof(Node2D) - - # verify the scene is freed when the runner is freed - runner._notification(NOTIFICATION_PREDELETE) - # give engine time to free the resources - await await_idle_frame() - # scene runner using external scene do not free the scene at exit - assert_bool(is_instance_valid(scene)).is_true() - # needs to be manually freed - scene.free() - - -func test_mouse_drag_and_drop() -> void: - var spy_scene = spy("res://addons/gdUnit4/test/core/resources/scenes/drag_and_drop/DragAndDropTestScene.tscn") - var runner := scene_runner(spy_scene) - - var slot_left :TextureRect = $"/root/DragAndDropScene/left/TextureRect" - var slot_right :TextureRect = $"/root/DragAndDropScene/right/TextureRect" - - var save_mouse_pos := get_tree().root.get_mouse_position() - # set inital mouse pos over the left slot - var mouse_pos := slot_left.global_position + Vector2(10, 10) - runner.set_mouse_pos(mouse_pos) - await await_millis(1000) - - await await_idle_frame() - var event := InputEventMouseMotion.new() - event.position = mouse_pos - event.global_position = save_mouse_pos - verify(spy_scene, 1)._gui_input(event) - - runner.simulate_mouse_button_press(MOUSE_BUTTON_LEFT) - await await_idle_frame() - assert_bool(Input.is_mouse_button_pressed(MOUSE_BUTTON_LEFT)).is_true() - - # start drag&drop to left pannel - for i in 20: - runner.simulate_mouse_move(mouse_pos + Vector2(i*.5*i, 0)) - await await_millis(40) - - runner.simulate_mouse_button_release(MOUSE_BUTTON_LEFT) - await await_idle_frame() - assert_that(slot_right.texture).is_equal(slot_left.texture) - - -func test_runner_GD_356() -> void: - # to avoid reporting the expected push_error as test failure we disable it - ProjectSettings.set_setting(GdUnitSettings.REPORT_PUSH_ERRORS, false) - var runner = scene_runner("res://addons/gdUnit4/test/core/resources/scenes/simple_scene.tscn") - var player = runner.invoke("find_child", "Player", true, false) - assert_that(player).is_not_null() - await assert_func(player, "is_on_floor").wait_until(500).is_true() - assert_that(runner.scene()).is_not_null() - # run simulate_mouse_move_relative without await to reproduce https://github.com/MikeSchulze/gdUnit4/issues/356 - # this results into releasing the scene while `simulate_mouse_move_relative` is processing the mouse move - runner.simulate_mouse_move_relative(Vector2(100, 100), 1.0) - assert_that(runner.scene()).is_not_null() - - -# we override the scene runner function for test purposes to hide push_error notifications -func scene_runner(scene, verbose := false) -> GdUnitSceneRunner: - return auto_free(GdUnitSceneRunnerImpl.new(scene, verbose, true)) diff --git a/addons/gdUnit4/test/core/GdUnitSettingsTest.gd b/addons/gdUnit4/test/core/GdUnitSettingsTest.gd deleted file mode 100644 index 5e52a02..0000000 --- a/addons/gdUnit4/test/core/GdUnitSettingsTest.gd +++ /dev/null @@ -1,155 +0,0 @@ -# GdUnit generated TestSuite -class_name GdUnitSettingsTest -extends GdUnitTestSuite - -# TestSuite generated from -const __source = 'res://addons/gdUnit4/src/core/GdUnitSettings.gd' - -const MAIN_CATEGORY = "unit_test" -const CATEGORY_A = MAIN_CATEGORY + "/category_a" -const CATEGORY_B = MAIN_CATEGORY + "/category_b" -const TEST_PROPERTY_A = CATEGORY_A + "/a/prop_a" -const TEST_PROPERTY_B = CATEGORY_A + "/a/prop_b" -const TEST_PROPERTY_C = CATEGORY_A + "/a/prop_c" -const TEST_PROPERTY_D = CATEGORY_B + "/prop_d" -const TEST_PROPERTY_E = CATEGORY_B + "/c/prop_e" -const TEST_PROPERTY_F = CATEGORY_B + "/c/prop_f" -const TEST_PROPERTY_G = CATEGORY_B + "/a/prop_g" - - -func before() -> void: - GdUnitSettings.dump_to_tmp() - - -func after() -> void: - GdUnitSettings.restore_dump_from_tmp() - - -func before_test() -> void: - GdUnitSettings.create_property_if_need(TEST_PROPERTY_A, true, "helptext TEST_PROPERTY_A.") - GdUnitSettings.create_property_if_need(TEST_PROPERTY_B, false, "helptext TEST_PROPERTY_B.") - GdUnitSettings.create_property_if_need(TEST_PROPERTY_C, 100, "helptext TEST_PROPERTY_C.") - GdUnitSettings.create_property_if_need(TEST_PROPERTY_D, true, "helptext TEST_PROPERTY_D.") - GdUnitSettings.create_property_if_need(TEST_PROPERTY_E, false, "helptext TEST_PROPERTY_E.") - GdUnitSettings.create_property_if_need(TEST_PROPERTY_F, "abc", "helptext TEST_PROPERTY_F.") - GdUnitSettings.create_property_if_need(TEST_PROPERTY_G, 200, "helptext TEST_PROPERTY_G.") - - -func after_test() -> void: - ProjectSettings.clear(TEST_PROPERTY_A) - ProjectSettings.clear(TEST_PROPERTY_B) - ProjectSettings.clear(TEST_PROPERTY_C) - ProjectSettings.clear(TEST_PROPERTY_D) - ProjectSettings.clear(TEST_PROPERTY_E) - ProjectSettings.clear(TEST_PROPERTY_F) - ProjectSettings.clear(TEST_PROPERTY_G) - - -func test_list_settings() -> void: - var settingsA := GdUnitSettings.list_settings(CATEGORY_A) - assert_array(settingsA).extractv(extr("name"), extr("type"), extr("value"), extr("default"), extr("help"))\ - .contains_exactly_in_any_order([ - tuple(TEST_PROPERTY_A, TYPE_BOOL, true, true, "helptext TEST_PROPERTY_A."), - tuple(TEST_PROPERTY_B, TYPE_BOOL,false, false, "helptext TEST_PROPERTY_B."), - tuple(TEST_PROPERTY_C, TYPE_INT, 100, 100, "helptext TEST_PROPERTY_C.") - ]) - var settingsB := GdUnitSettings.list_settings(CATEGORY_B) - assert_array(settingsB).extractv(extr("name"), extr("type"), extr("value"), extr("default"), extr("help"))\ - .contains_exactly_in_any_order([ - tuple(TEST_PROPERTY_D, TYPE_BOOL, true, true, "helptext TEST_PROPERTY_D."), - tuple(TEST_PROPERTY_E, TYPE_BOOL, false, false, "helptext TEST_PROPERTY_E."), - tuple(TEST_PROPERTY_F, TYPE_STRING, "abc", "abc", "helptext TEST_PROPERTY_F."), - tuple(TEST_PROPERTY_G, TYPE_INT, 200, 200, "helptext TEST_PROPERTY_G.") - ]) - - -func test_enum_property() -> void: - var value_set :PackedStringArray = GdUnitSettings.NAMING_CONVENTIONS.keys() - GdUnitSettings.create_property_if_need("test/enum", GdUnitSettings.NAMING_CONVENTIONS.AUTO_DETECT, "help", value_set) - - var property := GdUnitSettings.get_property("test/enum") - assert_that(property.default()).is_equal(GdUnitSettings.NAMING_CONVENTIONS.AUTO_DETECT) - assert_that(property.value()).is_equal(GdUnitSettings.NAMING_CONVENTIONS.AUTO_DETECT) - assert_that(property.type()).is_equal(TYPE_INT) - assert_that(property.help()).is_equal('help ["AUTO_DETECT", "SNAKE_CASE", "PASCAL_CASE"]') - assert_that(property.value_set()).is_equal(value_set) - - -func test_migrate_property_change_key() -> void: - # setup old property - var old_property_X = "/category_patch/group_old/name" - var new_property_X = "/category_patch/group_new/name" - GdUnitSettings.create_property_if_need(old_property_X, "foo") - assert_str(GdUnitSettings.get_setting(old_property_X, null)).is_equal("foo") - assert_str(GdUnitSettings.get_setting(new_property_X, null)).is_null() - var old_property := GdUnitSettings.get_property(old_property_X) - - # migrate - GdUnitSettings.migrate_property(old_property.name(),\ - new_property_X,\ - old_property.default(),\ - old_property.help()) - - var new_property := GdUnitSettings.get_property(new_property_X) - assert_str(GdUnitSettings.get_setting(old_property_X, null)).is_null() - assert_str(GdUnitSettings.get_setting(new_property_X, null)).is_equal("foo") - assert_object(new_property).is_not_equal(old_property) - assert_str(new_property.value()).is_equal(old_property.value()) - assert_array(new_property.value_set()).is_equal(old_property.value_set()) - assert_int(new_property.type()).is_equal(old_property.type()) - assert_str(new_property.default()).is_equal(old_property.default()) - assert_str(new_property.help()).is_equal(old_property.help()) - - # cleanup - ProjectSettings.clear(new_property_X) - - -func test_migrate_property_change_value() -> void: - # setup old property - var old_property_X = "/category_patch/group_old/name" - var new_property_X = "/category_patch/group_new/name" - GdUnitSettings.create_property_if_need(old_property_X, "foo", "help to foo") - assert_str(GdUnitSettings.get_setting(old_property_X, null)).is_equal("foo") - assert_str(GdUnitSettings.get_setting(new_property_X, null)).is_null() - var old_property := GdUnitSettings.get_property(old_property_X) - - # migrate property - GdUnitSettings.migrate_property(old_property.name(),\ - new_property_X,\ - old_property.default(),\ - old_property.help(),\ - func(_value): return "bar") - - var new_property := GdUnitSettings.get_property(new_property_X) - assert_str(GdUnitSettings.get_setting(old_property_X, null)).is_null() - assert_str(GdUnitSettings.get_setting(new_property_X, null)).is_equal("bar") - assert_object(new_property).is_not_equal(old_property) - assert_str(new_property.value()).is_equal("bar") - assert_array(new_property.value_set()).is_equal(old_property.value_set()) - assert_int(new_property.type()).is_equal(old_property.type()) - assert_str(new_property.default()).is_equal(old_property.default()) - assert_str(new_property.help()).is_equal(old_property.help()) - # cleanup - ProjectSettings.clear(new_property_X) - - -const TEST_ROOT_FOLDER := "gdunit4/settings/test/test_root_folder" -const HELP_TEST_ROOT_FOLDER := "Sets the root folder where test-suites located/generated." - -func test_migrate_properties_v215() -> void: - # rebuild original settings - GdUnitSettings.create_property_if_need(TEST_ROOT_FOLDER, "test", HELP_TEST_ROOT_FOLDER) - ProjectSettings.set_setting(TEST_ROOT_FOLDER, "xxx") - - # migrate - GdUnitSettings.migrate_properties() - - # verify - var property := GdUnitSettings.get_property(GdUnitSettings.TEST_LOOKUP_FOLDER) - assert_str(property.value()).is_equal("xxx") - assert_array(property.value_set()).is_empty() - assert_int(property.type()).is_equal(TYPE_STRING) - assert_str(property.default()).is_equal(GdUnitSettings.DEFAULT_TEST_LOOKUP_FOLDER) - assert_str(property.help()).is_equal(GdUnitSettings.HELP_TEST_LOOKUP_FOLDER) - assert_that(GdUnitSettings.get_property(TEST_ROOT_FOLDER)).is_null() - ProjectSettings.clear(GdUnitSettings.TEST_LOOKUP_FOLDER) diff --git a/addons/gdUnit4/test/core/GdUnitSignalAwaiterTest.gd b/addons/gdUnit4/test/core/GdUnitSignalAwaiterTest.gd deleted file mode 100644 index f0d6a56..0000000 --- a/addons/gdUnit4/test/core/GdUnitSignalAwaiterTest.gd +++ /dev/null @@ -1,46 +0,0 @@ -# GdUnit generated TestSuite -#warning-ignore-all:unused_argument -#warning-ignore-all:return_value_discarded -class_name GdUnitSignalAwaiterTest -extends GdUnitTestSuite - -# TestSuite generated from -const __source = 'res://addons/gdUnit4/src/core/GdUnitSignalAwaiter.gd' - - -class Monster extends Node: - - signal move(value :float) - signal slide(value, x, z ) - - var _pos :float = 0.0 - - func _process(_delta): - _pos += 0.2 - emit_signal("move", _pos) - emit_signal("slide", _pos, 1 , 2) - - -func test_on_signal_with_single_arg() -> void: - var monster = auto_free(Monster.new()) - add_child(monster) - var signal_arg = await await_signal_on(monster, "move", [1.0]) - assert_float(signal_arg).is_equal(1.0) - remove_child(monster) - - -func test_on_signal_with_many_args() -> void: - var monster = auto_free(Monster.new()) - add_child(monster) - var signal_args = await await_signal_on(monster, "slide", [1.0, 1, 2]) - assert_array(signal_args).is_equal([1.0, 1, 2]) - remove_child(monster) - - -func test_on_signal_fail() -> void: - var monster = auto_free(Monster.new()) - add_child(monster) - ( - await assert_failure_await( func x(): await await_signal_on(monster, "move", [4.0])) - ).has_message("await_signal_on(move, [4]) timed out after 2000ms") - remove_child(monster) diff --git a/addons/gdUnit4/test/core/GdUnitSingletonTest.gd b/addons/gdUnit4/test/core/GdUnitSingletonTest.gd deleted file mode 100644 index 4e9935a..0000000 --- a/addons/gdUnit4/test/core/GdUnitSingletonTest.gd +++ /dev/null @@ -1,11 +0,0 @@ -extends GdUnitTestSuite - - -func test_instance() -> void: - var n = GdUnitSingleton.instance("singelton_test", func(): return Node.new() ) - assert_object(n).is_instanceof(Node) - assert_bool(is_instance_valid(n)).is_true() - - # free the singleton - GdUnitSingleton.unregister("singelton_test") - assert_bool(is_instance_valid(n)).is_false() diff --git a/addons/gdUnit4/test/core/GdUnitTestSuiteBuilderTest.gd b/addons/gdUnit4/test/core/GdUnitTestSuiteBuilderTest.gd deleted file mode 100644 index c9ba2eb..0000000 --- a/addons/gdUnit4/test/core/GdUnitTestSuiteBuilderTest.gd +++ /dev/null @@ -1,65 +0,0 @@ -# GdUnit generated TestSuite -#warning-ignore-all:unused_argument -#warning-ignore-all:return_value_discarded -class_name GdUnitTestSuiteBuilderTest -extends GdUnitTestSuite - -const GdUnitTools := preload("res://addons/gdUnit4/src/core/GdUnitTools.gd") - - -# TestSuite generated from -const __source = 'res://addons/gdUnit4/src/core/GdUnitTestSuiteBuilder.gd' - -var _example_source_gd :String - - -func before_test(): - var temp := create_temp_dir("examples") - var result := GdUnitFileAccess.copy_file("res://addons/gdUnit4/test/core/resources/sources/test_person.gd", temp) - assert_result(result).is_success() - _example_source_gd = result.value() as String - - -func after_test(): - clean_temp_dir() - - -func assert_tests(test_suite :Script) -> GdUnitArrayAssert: - # needs to be reload to get fresh method list - test_suite.reload() - var methods := test_suite.get_script_method_list() - var test_cases := Array() - for method in methods: - if method.name.begins_with("test_"): - test_cases.append(method.name) - return assert_array(test_cases) - - -func test_create_gd_success() -> void: - var source := load(_example_source_gd) - - # create initial test suite based checked function selected by line 9 - var result := GdUnitTestSuiteBuilder.create(source, 9) - - assert_result(result).is_success() - var info := result.value() as Dictionary - assert_str(info.get("path")).is_equal("user://tmp/test/examples/test_person_test.gd") - assert_int(info.get("line")).is_equal(11) - assert_tests(load(info.get("path"))).contains_exactly(["test_first_name"]) - - # create additional test checked existing suite based checked function selected by line 15 - result = GdUnitTestSuiteBuilder.create(source, 15) - - assert_result(result).is_success() - info = result.value() as Dictionary - assert_str(info.get("path")).is_equal("user://tmp/test/examples/test_person_test.gd") - assert_int(info.get("line")).is_equal(16) - assert_tests(load(info.get("path"))).contains_exactly_in_any_order(["test_first_name", "test_fully_name"]) - - -func test_create_gd_fail() -> void: - var source := load(_example_source_gd) - - # attempt to create an initial test suite based checked the function selected in line 8, which has no function definition - var result := GdUnitTestSuiteBuilder.create(source, 8) - assert_result(result).is_error().contains_message("No function found at line: 8.") diff --git a/addons/gdUnit4/test/core/GdUnitTestSuiteScannerTest.gd b/addons/gdUnit4/test/core/GdUnitTestSuiteScannerTest.gd deleted file mode 100644 index 6ee5a4f..0000000 --- a/addons/gdUnit4/test/core/GdUnitTestSuiteScannerTest.gd +++ /dev/null @@ -1,372 +0,0 @@ -# GdUnit generated TestSuite -#warning-ignore-all:unused_argument -#warning-ignore-all:return_value_discarded -class_name TestSuiteScannerTest -extends GdUnitTestSuite - -# TestSuite generated from -const __source = 'res://addons/gdUnit4/src/core/GdUnitTestSuiteScanner.gd' - -func before_test(): - ProjectSettings.set_setting(GdUnitSettings.TEST_SITE_NAMING_CONVENTION, GdUnitSettings.NAMING_CONVENTIONS.AUTO_DETECT) - clean_temp_dir() - - -func after(): - clean_temp_dir() - - -func resolve_path(source_file :String) -> String: - return GdUnitTestSuiteScanner.resolve_test_suite_path(source_file, "_test_") - - -func test_resolve_test_suite_path_project() -> void: - # if no `src` folder found use test folder as root - assert_str(resolve_path("res://foo.gd")).is_equal("res://_test_/foo_test.gd") - assert_str(resolve_path("res://project_name/module/foo.gd")).is_equal("res://_test_/project_name/module/foo_test.gd") - # otherwise build relative to 'src' - assert_str(resolve_path("res://src/foo.gd")).is_equal("res://_test_/foo_test.gd") - assert_str(resolve_path("res://project_name/src/foo.gd")).is_equal("res://project_name/_test_/foo_test.gd") - assert_str(resolve_path("res://project_name/src/module/foo.gd")).is_equal("res://project_name/_test_/module/foo_test.gd") - - -func test_resolve_test_suite_path_plugins() -> void: - assert_str(resolve_path("res://addons/plugin_a/foo.gd")).is_equal("res://addons/plugin_a/_test_/foo_test.gd") - assert_str(resolve_path("res://addons/plugin_a/src/foo.gd")).is_equal("res://addons/plugin_a/_test_/foo_test.gd") - - -func test_resolve_test_suite_path__no_test_root(): - # from a project path - assert_str(GdUnitTestSuiteScanner.resolve_test_suite_path("res://project/src/models/events/ModelChangedEvent.gd", ""))\ - .is_equal("res://project/src/models/events/ModelChangedEventTest.gd") - # from a plugin path - assert_str(GdUnitTestSuiteScanner.resolve_test_suite_path("res://addons/MyPlugin/src/models/events/ModelChangedEvent.gd", ""))\ - .is_equal("res://addons/MyPlugin/src/models/events/ModelChangedEventTest.gd") - # located in user path - assert_str(GdUnitTestSuiteScanner.resolve_test_suite_path("user://project/src/models/events/ModelChangedEvent.gd", ""))\ - .is_equal("user://project/src/models/events/ModelChangedEventTest.gd") - - -func test_resolve_test_suite_path__path_contains_src_folder(): - # from a project path - assert_str(GdUnitTestSuiteScanner.resolve_test_suite_path("res://project/src/models/events/ModelChangedEvent.gd"))\ - .is_equal("res://project/test/models/events/ModelChangedEventTest.gd") - assert_str(GdUnitTestSuiteScanner.resolve_test_suite_path("res://project/src/models/events/ModelChangedEvent.gd", "custom_test"))\ - .is_equal("res://project/custom_test/models/events/ModelChangedEventTest.gd") - # from a plugin path - assert_str(GdUnitTestSuiteScanner.resolve_test_suite_path("res://addons/MyPlugin/src/models/events/ModelChangedEvent.gd"))\ - .is_equal("res://addons/MyPlugin/test/models/events/ModelChangedEventTest.gd") - assert_str(GdUnitTestSuiteScanner.resolve_test_suite_path("res://addons/MyPlugin/src/models/events/ModelChangedEvent.gd", "custom_test"))\ - .is_equal("res://addons/MyPlugin/custom_test/models/events/ModelChangedEventTest.gd") - # located in user path - assert_str(GdUnitTestSuiteScanner.resolve_test_suite_path("user://project/src/models/events/ModelChangedEvent.gd"))\ - .is_equal("user://project/test/models/events/ModelChangedEventTest.gd") - assert_str(GdUnitTestSuiteScanner.resolve_test_suite_path("user://project/src/models/events/ModelChangedEvent.gd", "custom_test"))\ - .is_equal("user://project/custom_test/models/events/ModelChangedEventTest.gd") - - -func test_resolve_test_suite_path__path_not_contains_src_folder(): - # from a project path - assert_str(GdUnitTestSuiteScanner.resolve_test_suite_path("res://project/models/events/ModelChangedEvent.gd"))\ - .is_equal("res://test/project/models/events/ModelChangedEventTest.gd") - assert_str(GdUnitTestSuiteScanner.resolve_test_suite_path("res://project/models/events/ModelChangedEvent.gd", "custom_test"))\ - .is_equal("res://custom_test/project/models/events/ModelChangedEventTest.gd") - # from a plugin path - assert_str(GdUnitTestSuiteScanner.resolve_test_suite_path("res://addons/MyPlugin/models/events/ModelChangedEvent.gd"))\ - .is_equal("res://addons/MyPlugin/test/models/events/ModelChangedEventTest.gd") - assert_str(GdUnitTestSuiteScanner.resolve_test_suite_path("res://addons/MyPlugin/models/events/ModelChangedEvent.gd", "custom_test"))\ - .is_equal("res://addons/MyPlugin/custom_test/models/events/ModelChangedEventTest.gd") - # located in user path - assert_str(GdUnitTestSuiteScanner.resolve_test_suite_path("user://project/models/events/ModelChangedEvent.gd"))\ - .is_equal("user://test/project/models/events/ModelChangedEventTest.gd") - assert_str(GdUnitTestSuiteScanner.resolve_test_suite_path("user://project/models/events/ModelChangedEvent.gd", "custom_test"))\ - .is_equal("user://custom_test/project/models/events/ModelChangedEventTest.gd") - - -func test_test_suite_exists(): - var path_exists := "res://addons/gdUnit4/test/resources/core/GeneratedPersonTest.gd" - var path_not_exists := "res://addons/gdUnit4/test/resources/core/FamilyTest.gd" - assert_that(GdUnitTestSuiteScanner.test_suite_exists(path_exists)).is_true() - assert_that(GdUnitTestSuiteScanner.test_suite_exists(path_not_exists)).is_false() - - -func test_test_case_exists(): - var test_suite_path := "res://addons/gdUnit4/test/resources/core/GeneratedPersonTest.gd" - assert_that(GdUnitTestSuiteScanner.test_case_exists(test_suite_path, "name")).is_true() - assert_that(GdUnitTestSuiteScanner.test_case_exists(test_suite_path, "last_name")).is_false() - - -func test_create_test_suite_pascal_case_path(): - var temp_dir := create_temp_dir("TestSuiteScannerTest") - # checked source with class_name is set - var source_path := "res://addons/gdUnit4/test/core/resources/naming_conventions/PascalCaseWithClassName.gd" - var suite_path := temp_dir + "/test/MyClassTest1.gd" - var result := GdUnitTestSuiteScanner.create_test_suite(suite_path, source_path) - assert_that(result.is_success()).is_true() - assert_str(result.value()).is_equal(suite_path) - assert_file(result.value()).exists()\ - .is_file()\ - .is_script()\ - .contains_exactly([ - "# GdUnit generated TestSuite", - "class_name PascalCaseWithClassNameTest", - "extends GdUnitTestSuite", - "@warning_ignore('unused_parameter')", - "@warning_ignore('return_value_discarded')", - "", - "# TestSuite generated from", - "const __source = '%s'" % source_path, - ""]) - # checked source with class_name is NOT set - source_path = "res://addons/gdUnit4/test/core/resources/naming_conventions/PascalCaseWithoutClassName.gd" - suite_path = temp_dir + "/test/MyClassTest2.gd" - result = GdUnitTestSuiteScanner.create_test_suite(suite_path, source_path) - assert_that(result.is_success()).is_true() - assert_str(result.value()).is_equal(suite_path) - assert_file(result.value()).exists()\ - .is_file()\ - .is_script()\ - .contains_exactly([ - "# GdUnit generated TestSuite", - "class_name PascalCaseWithoutClassNameTest", - "extends GdUnitTestSuite", - "@warning_ignore('unused_parameter')", - "@warning_ignore('return_value_discarded')", - "", - "# TestSuite generated from", - "const __source = '%s'" % source_path, - ""]) - - -func test_create_test_suite_snake_case_path(): - var temp_dir := create_temp_dir("TestSuiteScannerTest") - # checked source with class_name is set - var source_path :="res://addons/gdUnit4/test/core/resources/naming_conventions/snake_case_with_class_name.gd" - var suite_path := temp_dir + "/test/my_class_test1.gd" - var result := GdUnitTestSuiteScanner.create_test_suite(suite_path, source_path) - assert_that(result.is_success()).is_true() - assert_str(result.value()).is_equal(suite_path) - assert_file(result.value()).exists()\ - .is_file()\ - .is_script()\ - .contains_exactly([ - "# GdUnit generated TestSuite", - "class_name SnakeCaseWithClassNameTest", - "extends GdUnitTestSuite", - "@warning_ignore('unused_parameter')", - "@warning_ignore('return_value_discarded')", - "", - "# TestSuite generated from", - "const __source = '%s'" % source_path, - ""]) - # checked source with class_name is NOT set - source_path ="res://addons/gdUnit4/test/core/resources/naming_conventions/snake_case_without_class_name.gd" - suite_path = temp_dir + "/test/my_class_test2.gd" - result = GdUnitTestSuiteScanner.create_test_suite(suite_path, source_path) - assert_that(result.is_success()).is_true() - assert_str(result.value()).is_equal(suite_path) - assert_file(result.value()).exists()\ - .is_file()\ - .is_script()\ - .contains_exactly([ - "# GdUnit generated TestSuite", - "class_name SnakeCaseWithoutClassNameTest", - "extends GdUnitTestSuite", - "@warning_ignore('unused_parameter')", - "@warning_ignore('return_value_discarded')", - "", - "# TestSuite generated from", - "const __source = '%s'" % source_path, - ""]) - - -func test_create_test_case(): - # store test class checked temp dir - var tmp_path := create_temp_dir("TestSuiteScannerTest") - var source_path := "res://addons/gdUnit4/test/resources/core/Person.gd" - # generate new test suite with test 'test_last_name()' - var test_suite_path = tmp_path + "/test/PersonTest.gd" - var result := GdUnitTestSuiteScanner.create_test_case(test_suite_path, "last_name", source_path) - assert_that(result.is_success()).is_true() - var info :Dictionary = result.value() - assert_int(info.get("line")).is_equal(11) - assert_file(info.get("path")).exists()\ - .is_file()\ - .is_script()\ - .contains_exactly([ - "# GdUnit generated TestSuite", - "class_name PersonTest", - "extends GdUnitTestSuite", - "@warning_ignore('unused_parameter')", - "@warning_ignore('return_value_discarded')", - "", - "# TestSuite generated from", - "const __source = '%s'" % source_path, - "", - "", - "func test_last_name() -> void:", - " # remove this line and complete your test", - " assert_not_yet_implemented()", - ""]) - # try to add again - result = GdUnitTestSuiteScanner.create_test_case(test_suite_path, "last_name", source_path) - assert_that(result.is_success()).is_true() - assert_that(result.value()).is_equal({"line" : 16, "path": test_suite_path}) - - -# https://github.com/MikeSchulze/gdUnit4/issues/25 -func test_build_test_suite_path() -> void: - # checked project root - assert_str(GdUnitTestSuiteScanner.resolve_test_suite_path("res://new_script.gd")).is_equal("res://test/new_script_test.gd") - - # checked project without src folder - assert_str(GdUnitTestSuiteScanner.resolve_test_suite_path("res://foo/bar/new_script.gd")).is_equal("res://test/foo/bar/new_script_test.gd") - - # project code structured by 'src' - assert_str(GdUnitTestSuiteScanner.resolve_test_suite_path("res://src/new_script.gd")).is_equal("res://test/new_script_test.gd") - assert_str(GdUnitTestSuiteScanner.resolve_test_suite_path("res://src/foo/bar/new_script.gd")).is_equal("res://test/foo/bar/new_script_test.gd") - # folder name contains 'src' in name - assert_str(GdUnitTestSuiteScanner.resolve_test_suite_path("res://foo/srcare/new_script.gd")).is_equal("res://test/foo/srcare/new_script_test.gd") - - # checked plugins without src folder - assert_str(GdUnitTestSuiteScanner.resolve_test_suite_path("res://addons/plugin/foo/bar/new_script.gd")).is_equal("res://addons/plugin/test/foo/bar/new_script_test.gd") - # plugin code structured by 'src' - assert_str(GdUnitTestSuiteScanner.resolve_test_suite_path("res://addons/plugin/src/foo/bar/new_script.gd")).is_equal("res://addons/plugin/test/foo/bar/new_script_test.gd") - - # checked user temp folder - var tmp_path := create_temp_dir("projectX/entity") - var source_path := tmp_path + "/Person.gd" - assert_str(GdUnitTestSuiteScanner.resolve_test_suite_path(source_path)).is_equal("user://tmp/test/projectX/entity/PersonTest.gd") - - -func test_parse_and_add_test_cases() -> void: - var default_time := GdUnitSettings.test_timeout() - var scanner :GdUnitTestSuiteScanner = auto_free(GdUnitTestSuiteScanner.new()) - # fake a test suite - var test_suite :GdUnitTestSuite = auto_free(GdUnitTestSuite.new()) - test_suite.set_script( load("res://addons/gdUnit4/test/core/resources/test_script_with_arguments.gd")) - - var test_case_names := PackedStringArray([ - "test_no_args", - "test_with_timeout", - "test_with_fuzzer", - "test_with_fuzzer_iterations", - "test_with_multible_fuzzers", - "test_multiline_arguments_a", - "test_multiline_arguments_b", - "test_multiline_arguments_c"]) - scanner._parse_and_add_test_cases(test_suite, test_suite.get_script(), test_case_names) - assert_array(test_suite.get_children())\ - .extractv(extr("get_name"), extr("timeout"), extr("fuzzer_arguments"), extr("iterations"))\ - .contains_exactly([ - tuple("test_no_args", default_time, [], Fuzzer.ITERATION_DEFAULT_COUNT), - tuple("test_with_timeout", 2000, [], Fuzzer.ITERATION_DEFAULT_COUNT), - tuple("test_with_fuzzer", default_time, [GdFunctionArgument.new("fuzzer", GdObjects.TYPE_FUZZER, "Fuzzers.rangei(-10, 22)")], Fuzzer.ITERATION_DEFAULT_COUNT), - tuple("test_with_fuzzer_iterations", default_time, [GdFunctionArgument.new("fuzzer", GdObjects.TYPE_FUZZER, "Fuzzers.rangei(-10, 22)")], 10), - tuple("test_with_multible_fuzzers", default_time, [GdFunctionArgument.new("fuzzer_a", GdObjects.TYPE_FUZZER, "Fuzzers.rangei(-10, 22)"), - GdFunctionArgument.new("fuzzer_b", GdObjects.TYPE_FUZZER, "Fuzzers.rangei(23, 42)")], 10), - tuple("test_multiline_arguments_a", default_time, [GdFunctionArgument.new("fuzzer_a", GdObjects.TYPE_FUZZER, "Fuzzers.rangei(-10, 22)"), - GdFunctionArgument.new("fuzzer_b", GdObjects.TYPE_FUZZER, "Fuzzers.rangei(23, 42)")], 42), - tuple("test_multiline_arguments_b", default_time, [GdFunctionArgument.new("fuzzer_a", GdObjects.TYPE_FUZZER, "Fuzzers.rangei(-10, 22)"), - GdFunctionArgument.new("fuzzer_b", GdObjects.TYPE_FUZZER, "Fuzzers.rangei(23, 42)")], 23), - tuple("test_multiline_arguments_c", 2000, [GdFunctionArgument.new("fuzzer_a", GdObjects.TYPE_FUZZER, "Fuzzers.rangei(-10, 22)"), - GdFunctionArgument.new("fuzzer_b", GdObjects.TYPE_FUZZER, "Fuzzers.rangei(23, 42)")], 33) - ]) - - -func test_scan_by_inheritance_class_name() -> void: - var scanner :GdUnitTestSuiteScanner = auto_free(GdUnitTestSuiteScanner.new()) - var test_suites := scanner.scan("res://addons/gdUnit4/test/core/resources/scan_testsuite_inheritance/by_class_name/") - - assert_array(test_suites).has_size(3) - # sort by names - test_suites.sort_custom(func by_name(a, b): return a.get_name() <= b.get_name()) - assert_array(test_suites).extract("get_name")\ - .contains_exactly(["BaseTest", "ExtendedTest", "ExtendsExtendedTest"]) - assert_array(test_suites).extract("get_script.get_path")\ - .contains_exactly([ - "res://addons/gdUnit4/test/core/resources/scan_testsuite_inheritance/by_class_name/BaseTest.gd", - "res://addons/gdUnit4/test/core/resources/scan_testsuite_inheritance/by_class_name/ExtendedTest.gd", - "res://addons/gdUnit4/test/core/resources/scan_testsuite_inheritance/by_class_name/ExtendsExtendedTest.gd"]) - assert_array(test_suites[0].get_children()).extract("name")\ - .contains_same_exactly_in_any_order([&"test_foo1"]) - assert_array(test_suites[1].get_children()).extract("name")\ - .contains_same_exactly_in_any_order([&"test_foo2", &"test_foo1"]) - assert_array(test_suites[2].get_children()).extract("name")\ - .contains_same_exactly_in_any_order([&"test_foo3", &"test_foo2", &"test_foo1"]) - # finally free all scaned test suites - for ts in test_suites: - ts.free() - - -func test_scan_by_inheritance_class_path() -> void: - var scanner :GdUnitTestSuiteScanner = auto_free(GdUnitTestSuiteScanner.new()) - var test_suites := scanner.scan("res://addons/gdUnit4/test/core/resources/scan_testsuite_inheritance/by_class_path/") - - assert_array(test_suites).extractv(extr("get_name"), extr("get_script.get_path"), extr("get_children.get_name"))\ - .contains_exactly_in_any_order([ - tuple("BaseTest", "res://addons/gdUnit4/test/core/resources/scan_testsuite_inheritance/by_class_path/BaseTest.gd", [&"test_foo1"]), - tuple("ExtendedTest","res://addons/gdUnit4/test/core/resources/scan_testsuite_inheritance/by_class_path/ExtendedTest.gd", [&"test_foo2", &"test_foo1"]), - tuple("ExtendsExtendedTest", "res://addons/gdUnit4/test/core/resources/scan_testsuite_inheritance/by_class_path/ExtendsExtendedTest.gd", [&"test_foo3", &"test_foo2", &"test_foo1"]) - ]) - # finally free all scaned test suites - for ts in test_suites: - ts.free() - - -func test_get_test_case_line_number() -> void: - assert_int(GdUnitTestSuiteScanner.get_test_case_line_number("res://addons/gdUnit4/test/core/GdUnitTestSuiteScannerTest.gd", "get_test_case_line_number")).is_equal(317) - assert_int(GdUnitTestSuiteScanner.get_test_case_line_number("res://addons/gdUnit4/test/core/GdUnitTestSuiteScannerTest.gd", "unknown")).is_equal(-1) - - -func test__to_naming_convention() -> void: - ProjectSettings.set_setting(GdUnitSettings.TEST_SITE_NAMING_CONVENTION, GdUnitSettings.NAMING_CONVENTIONS.AUTO_DETECT) - assert_str(GdUnitTestSuiteScanner._to_naming_convention("MyClass")).is_equal("MyClassTest") - assert_str(GdUnitTestSuiteScanner._to_naming_convention("my_class")).is_equal("my_class_test") - assert_str(GdUnitTestSuiteScanner._to_naming_convention("myclass")).is_equal("myclass_test") - - ProjectSettings.set_setting(GdUnitSettings.TEST_SITE_NAMING_CONVENTION, GdUnitSettings.NAMING_CONVENTIONS.SNAKE_CASE) - assert_str(GdUnitTestSuiteScanner._to_naming_convention("MyClass")).is_equal("my_class_test") - assert_str(GdUnitTestSuiteScanner._to_naming_convention("my_class")).is_equal("my_class_test") - assert_str(GdUnitTestSuiteScanner._to_naming_convention("myclass")).is_equal("myclass_test") - - ProjectSettings.set_setting(GdUnitSettings.TEST_SITE_NAMING_CONVENTION, GdUnitSettings.NAMING_CONVENTIONS.PASCAL_CASE) - assert_str(GdUnitTestSuiteScanner._to_naming_convention("MyClass")).is_equal("MyClassTest") - assert_str(GdUnitTestSuiteScanner._to_naming_convention("my_class")).is_equal("MyClassTest") - assert_str(GdUnitTestSuiteScanner._to_naming_convention("myclass")).is_equal("MyclassTest") - - -func test_is_script_format_supported() -> void: - assert_bool(GdUnitTestSuiteScanner._is_script_format_supported("res://exampe.gd")).is_true() - assert_bool(GdUnitTestSuiteScanner._is_script_format_supported("res://exampe.gdns")).is_false() - assert_bool(GdUnitTestSuiteScanner._is_script_format_supported("res://exampe.vs")).is_false() - assert_bool(GdUnitTestSuiteScanner._is_script_format_supported("res://exampe.tres")).is_false() - - -func test_resolve_test_suite_path() -> void: - # forcing the use of a test folder next to the source folder - assert_str(GdUnitTestSuiteScanner.resolve_test_suite_path("res://project/src/folder/myclass.gd", "test")).is_equal("res://project/test/folder/myclass_test.gd") - assert_str(GdUnitTestSuiteScanner.resolve_test_suite_path("res://project/src/folder/MyClass.gd", "test")).is_equal("res://project/test/folder/MyClassTest.gd") - # forcing to use source directory to create the test - assert_str(GdUnitTestSuiteScanner.resolve_test_suite_path("res://project/src/folder/myclass.gd", "")).is_equal("res://project/src/folder/myclass_test.gd") - assert_str(GdUnitTestSuiteScanner.resolve_test_suite_path("res://project/src/folder/MyClass.gd", "")).is_equal("res://project/src/folder/MyClassTest.gd") - assert_str(GdUnitTestSuiteScanner.resolve_test_suite_path("res://project/src/folder/myclass.gd", "/")).is_equal("res://project/src/folder/myclass_test.gd") - assert_str(GdUnitTestSuiteScanner.resolve_test_suite_path("res://project/src/folder/MyClass.gd", "/")).is_equal("res://project/src/folder/MyClassTest.gd") - - -func test_resolve_test_suite_path_with_src_folders() -> void: - # forcing the use of a test folder next - assert_str(GdUnitTestSuiteScanner.resolve_test_suite_path("res://project/folder/myclass.gd", "test")).is_equal("res://test/project/folder/myclass_test.gd") - assert_str(GdUnitTestSuiteScanner.resolve_test_suite_path("res://project/folder/MyClass.gd", "test")).is_equal("res://test/project/folder/MyClassTest.gd") - # forcing to use source directory to create the test - assert_str(GdUnitTestSuiteScanner.resolve_test_suite_path("res://project/folder/myclass.gd", "")).is_equal("res://project/folder/myclass_test.gd") - assert_str(GdUnitTestSuiteScanner.resolve_test_suite_path("res://project/folder/MyClass.gd", "")).is_equal("res://project/folder/MyClassTest.gd") - assert_str(GdUnitTestSuiteScanner.resolve_test_suite_path("res://project/folder/myclass.gd", "/")).is_equal("res://project/folder/myclass_test.gd") - assert_str(GdUnitTestSuiteScanner.resolve_test_suite_path("res://project/folder/MyClass.gd", "/")).is_equal("res://project/folder/MyClassTest.gd") - - -func test_scan_test_suite_without_tests() -> void: - var scanner :GdUnitTestSuiteScanner = auto_free(GdUnitTestSuiteScanner.new()) - var test_suites := scanner.scan("res://addons/gdUnit4/test/core/resources/testsuites/TestSuiteWithoutTests.gd") - - assert_that(test_suites).is_empty() diff --git a/addons/gdUnit4/test/core/GdUnitToolsTest.gd b/addons/gdUnit4/test/core/GdUnitToolsTest.gd deleted file mode 100644 index c17b5a7..0000000 --- a/addons/gdUnit4/test/core/GdUnitToolsTest.gd +++ /dev/null @@ -1,49 +0,0 @@ -# GdUnit generated TestSuite -class_name GdUnitToolsTest -extends GdUnitTestSuite - -const GdUnitTools := preload("res://addons/gdUnit4/src/core/GdUnitTools.gd") - -# TestSuite generated from -const __source = 'res://addons/gdUnit4/src/core/GdUnitTools.gd' - - -class InnerTestNodeClass extends Node: - pass - -class InnerTestRefCountedClass extends RefCounted: - pass - - -func test_free_instance() -> void: - # on valid instances - assert_that(await GdUnitTools.free_instance(RefCounted.new())).is_true() - assert_that(await GdUnitTools.free_instance(Node.new())).is_true() - assert_that(await GdUnitTools.free_instance(JavaClass.new())).is_true() - assert_that(await GdUnitTools.free_instance(InnerTestNodeClass.new())).is_true() - assert_that(await GdUnitTools.free_instance(InnerTestRefCountedClass.new())).is_true() - - # on invalid instances - assert_that(await GdUnitTools.free_instance(null)).is_false() - assert_that(await GdUnitTools.free_instance(RefCounted)).is_false() - - # on already freed instances - var node := Node.new() - node.free() - assert_that(await GdUnitTools.free_instance(node)).is_false() - - -func test_richtext_normalize() -> void: - assert_that(GdUnitTools.richtext_normalize("")).is_equal("") - assert_that(GdUnitTools.richtext_normalize("This is a Color Message")).is_equal("This is a Color Message") - - var message = """ - [color=green]line [/color][color=aqua]11:[/color] [color=#CD5C5C]Expecting:[/color] - must be empty but was - '[color=#1E90FF]after[/color]' - """ - assert_that(GdUnitTools.richtext_normalize(message)).is_equal(""" - line 11: Expecting: - must be empty but was - 'after' - """) diff --git a/addons/gdUnit4/test/core/LocalTimeTest.gd b/addons/gdUnit4/test/core/LocalTimeTest.gd deleted file mode 100644 index 458197b..0000000 --- a/addons/gdUnit4/test/core/LocalTimeTest.gd +++ /dev/null @@ -1,69 +0,0 @@ -# GdUnit generated TestSuite -class_name LocalTimeTest -extends GdUnitTestSuite - -# TestSuite generated from -const __source = 'res://addons/gdUnit4/src/core/LocalTime.gd' - - -func test_time_constants(): - assert_int(LocalTime.MILLIS_PER_HOUR).is_equal(1000*60*60) - assert_int(LocalTime.MILLIS_PER_MINUTE).is_equal(1000*60) - assert_int(LocalTime.MILLIS_PER_SECOND).is_equal(1000) - assert_int(LocalTime.HOURS_PER_DAY).is_equal(24) - assert_int(LocalTime.MINUTES_PER_HOUR).is_equal(60) - assert_int(LocalTime.SECONDS_PER_MINUTE).is_equal(60) - - -func test_now(): - var current := Time.get_datetime_dict_from_system(true) - var local_time := LocalTime.now() - assert_int(local_time.hour()).is_equal(current.get("hour")) - assert_int(local_time.minute()).is_equal(current.get("minute")) - assert_int(local_time.second()).is_equal(current.get("second")) - # Time.get_datetime_dict_from_system() does not provide milliseconds - #assert_that(local_time.millis()).is_equal(0) - - -@warning_ignore("integer_division") -func test_of_unix_time(): - var time := LocalTime._get_system_time_msecs() - var local_time := LocalTime.of_unix_time(time) - assert_int(local_time.hour()).is_equal((time / LocalTime.MILLIS_PER_HOUR) % 24) - assert_int(local_time.minute()).is_equal((time / LocalTime.MILLIS_PER_MINUTE) % 60) - assert_int(local_time.second()).is_equal((time / LocalTime.MILLIS_PER_SECOND) % 60) - assert_int(local_time.millis()).is_equal(time % 1000) - - -func test_to_string(): - assert_str(LocalTime.local_time(10, 12, 22, 333)._to_string()).is_equal("10:12:22.333") - assert_str(LocalTime.local_time(23, 59, 59, 999)._to_string()).is_equal("23:59:59.999") - assert_str(LocalTime.local_time( 0, 0, 0, 000)._to_string()).is_equal("00:00:00.000") - assert_str(LocalTime.local_time( 2, 4, 3, 10)._to_string()).is_equal("02:04:03.010") - - -func test_plus_seconds(): - var time := LocalTime.local_time(10, 12, 22, 333) - assert_str(time.plus(LocalTime.TimeUnit.SECOND, 10)._to_string()).is_equal("10:12:32.333") - assert_str(time.plus(LocalTime.TimeUnit.SECOND, 27)._to_string()).is_equal("10:12:59.333") - assert_str(time.plus(LocalTime.TimeUnit.SECOND, 1)._to_string()).is_equal("10:13:00.333") - - # test overflow - var time2 := LocalTime.local_time(10, 59, 59, 333) - var start_time = time2._time - for iteration in 10000: - var t = LocalTime.of_unix_time(start_time) - var seconds:int = randi_range(0, 1000) - t.plus(LocalTime.TimeUnit.SECOND, seconds) - var expected := LocalTime.of_unix_time(start_time + (seconds * LocalTime.MILLIS_PER_SECOND)) - assert_str(t._to_string()).is_equal(expected._to_string()) - - -func test_elapsed(): - assert_str(LocalTime.elapsed(10)).is_equal("10ms") - assert_str(LocalTime.elapsed(201)).is_equal("201ms") - assert_str(LocalTime.elapsed(999)).is_equal("999ms") - assert_str(LocalTime.elapsed(1000)).is_equal("1s 0ms") - assert_str(LocalTime.elapsed(2000)).is_equal("2s 0ms") - assert_str(LocalTime.elapsed(3040)).is_equal("3s 40ms") - assert_str(LocalTime.elapsed(LocalTime.MILLIS_PER_MINUTE * 6 + 3040)).is_equal("6min 3s 40ms") diff --git a/addons/gdUnit4/test/core/ParameterizedTestCaseTest.gd b/addons/gdUnit4/test/core/ParameterizedTestCaseTest.gd deleted file mode 100644 index 04ad2a1..0000000 --- a/addons/gdUnit4/test/core/ParameterizedTestCaseTest.gd +++ /dev/null @@ -1,296 +0,0 @@ -#warning-ignore-all:unused_argument -class_name ParameterizedTestCaseTest -extends GdUnitTestSuite - -var _collected_tests = {} -var _expected_tests = { - "test_parameterized_bool_value" : [ - [0, false], - [1, true] - ], - "test_parameterized_int_values" : [ - [1, 2, 3, 6], - [3, 4, 5, 12], - [6, 7, 8, 21] - ], - "test_parameterized_float_values" : [ - [2.2, 2.2, 4.4], - [2.2, 2.3, 4.5], - [3.3, 2.2, 5.5] - ], - "test_parameterized_string_values" : [ - ["2.2", "2.2", "2.22.2"], - ["foo", "bar", "foobar"], - ["a", "b", "ab"] - ], - "test_parameterized_Vector2_values" : [ - [Vector2.ONE, Vector2.ONE, Vector2(2, 2)], - [Vector2.LEFT, Vector2.RIGHT, Vector2.ZERO], - [Vector2.ZERO, Vector2.LEFT, Vector2.LEFT] - ], - "test_parameterized_Vector3_values" : [ - [Vector3.ONE, Vector3.ONE, Vector3(2, 2, 2)], - [Vector3.LEFT, Vector3.RIGHT, Vector3.ZERO], - [Vector3.ZERO, Vector3.LEFT, Vector3.LEFT] - ], - "test_parameterized_obj_values" : [ - [TestObj.new("abc"), TestObj.new("def"), "abcdef"] - ], - "test_parameterized_dict_values" : [ - [{"key_a":"value_a"}, '{"key_a":"value_a"}'], - [{"key_b":"value_b"}, '{"key_b":"value_b"}'] - ], - "test_with_dynamic_paramater_resolving" : [ - ["test_a"], - ["test_b"], - ["test_c"], - ["test_d"] - ], - "test_with_dynamic_paramater_resolving2" : [ - ["test_a"], - ["test_b"], - ["test_c"] - ], - "test_with_extern_parameter_set" : [ - ["test_a"], - ["test_b"], - ["test_c"] - ] -} - - -var _test_node_before :Node -var _test_node_before_test :Node - - -func before() -> void: - _test_node_before = auto_free(SubViewport.new()) - - -func before_test() -> void: - _test_node_before_test = auto_free(SubViewport.new()) - - -func after(): - for test_name in _expected_tests.keys(): - if _collected_tests.has(test_name): - var current_values = _collected_tests[test_name] - var expected_values = _expected_tests[test_name] - assert_that(current_values)\ - .override_failure_message("Expecting '%s' called with parameters:\n %s\n but was\n %s" % [test_name, expected_values, current_values])\ - .is_equal(expected_values) - else: - fail("Missing test '%s' executed!" % test_name) - - -func collect_test_call(test_name :String, values :Array) -> void: - if not _collected_tests.has(test_name): - _collected_tests[test_name] = Array() - _collected_tests[test_name].append(values) - - -@warning_ignore("unused_parameter") -func test_parameterized_bool_value(a: int, expected :bool, test_parameters := [ - [0, false], - [1, true]]): - collect_test_call("test_parameterized_bool_value", [a, expected]) - assert_that(bool(a)).is_equal(expected) - - -@warning_ignore("unused_parameter") -func test_parameterized_int_values(a: int, b :int, c :int, expected :int, test_parameters := [ - [1, 2, 3, 6], - [3, 4, 5, 12], - [6, 7, 8, 21] ]): - - collect_test_call("test_parameterized_int_values", [a, b, c, expected]) - assert_that(a+b+c).is_equal(expected) - - -@warning_ignore("unused_parameter") -func test_parameterized_float_values(a: float, b :float, expected :float, test_parameters := [ - [2.2, 2.2, 4.4], - [2.2, 2.3, 4.5], - [3.3, 2.2, 5.5] ]): - - collect_test_call("test_parameterized_float_values", [a, b, expected]) - assert_float(a+b).is_equal(expected) - - -@warning_ignore("unused_parameter") -func test_parameterized_string_values(a: String, b :String, expected :String, test_parameters := [ - ["2.2", "2.2", "2.22.2"], - ["foo", "bar", "foobar"], - ["a", "b", "ab"] ]): - - collect_test_call("test_parameterized_string_values", [a, b, expected]) - assert_that(a+b).is_equal(expected) - - -@warning_ignore("unused_parameter") -func test_parameterized_Vector2_values(a: Vector2, b :Vector2, expected :Vector2, test_parameters := [ - [Vector2.ONE, Vector2.ONE, Vector2(2, 2)], - [Vector2.LEFT, Vector2.RIGHT, Vector2.ZERO], - [Vector2.ZERO, Vector2.LEFT, Vector2.LEFT] ]): - - collect_test_call("test_parameterized_Vector2_values", [a, b, expected]) - assert_that(a+b).is_equal(expected) - - -@warning_ignore("unused_parameter") -func test_parameterized_Vector3_values(a: Vector3, b :Vector3, expected :Vector3, test_parameters := [ - [Vector3.ONE, Vector3.ONE, Vector3(2, 2, 2)], - [Vector3.LEFT, Vector3.RIGHT, Vector3.ZERO], - [Vector3.ZERO, Vector3.LEFT, Vector3.LEFT] ]): - - collect_test_call("test_parameterized_Vector3_values", [a, b, expected]) - assert_that(a+b).is_equal(expected) - - -class TestObj extends RefCounted: - var _value :String - - func _init(value :String): - _value = value - - func _to_string() -> String: - return _value - - -@warning_ignore("unused_parameter") -func test_parameterized_obj_values(a: Object, b :Object, expected :String, test_parameters := [ - [TestObj.new("abc"), TestObj.new("def"), "abcdef"]]): - - collect_test_call("test_parameterized_obj_values", [a, b, expected]) - assert_that(a.to_string()+b.to_string()).is_equal(expected) - - -@warning_ignore("unused_parameter") -func test_parameterized_dict_values(data: Dictionary, expected :String, test_parameters := [ - [{"key_a" : "value_a"}, '{"key_a":"value_a"}'], - [{"key_b" : "value_b"}, '{"key_b":"value_b"}'] - ]): - collect_test_call("test_parameterized_dict_values", [data, expected]) - assert_that(str(data).replace(" ", "")).is_equal(expected) - - -@warning_ignore("unused_parameter") -func test_dictionary_div_number_types( - value : Dictionary, - expected : Dictionary, - test_parameters : Array = [ - [{ top = 50.0, bottom = 50.0, left = 50.0, right = 50.0}, { top = 50, bottom = 50, left = 50, right = 50}], - [{ top = 50.0, bottom = 50.0, left = 50.0, right = 50.0}, { top = 50.0, bottom = 50.0, left = 50.0, right = 50.0}], - [{ top = 50, bottom = 50, left = 50, right = 50}, { top = 50.0, bottom = 50.0, left = 50.0, right = 50.0}], - [{ top = 50, bottom = 50, left = 50, right = 50}, { top = 50, bottom = 50, left = 50, right = 50}], - ] -) -> void: - # allow to compare type unsave - ProjectSettings.set_setting(GdUnitSettings.REPORT_ASSERT_STRICT_NUMBER_TYPE_COMPARE, false) - assert_that(value).is_equal(expected) - ProjectSettings.set_setting(GdUnitSettings.REPORT_ASSERT_STRICT_NUMBER_TYPE_COMPARE, true) - - -@warning_ignore("unused_parameter") -func test_with_string_paramset( - values : Array, - expected : String, - test_parameters : Array = [ - [ ["a"], "a" ], - [ ["a", "very", "long", "argument"], "a very long argument" ], - ] -): - var current := " ".join(values) - assert_that(current.strip_edges()).is_equal(expected) - - -# https://github.com/MikeSchulze/gdUnit4/issues/213 -@warning_ignore("unused_parameter") -func test_with_string_contains_brackets( - test_index :int, - value :String, - test_parameters := [ - [1, "flowchart TD\nid>This is a flag shaped node]"], - [2, "flowchart TD\nid(((This is a double circle node)))"], - [3, "flowchart TD\nid((This is a circular node))"], - [4, "flowchart TD\nid>This is a flag shaped node]"], - [5, "flowchart TD\nid{'This is a rhombus node'}"], - [6, 'flowchart TD\nid((This is a circular node))'], - [7, 'flowchart TD\nid>This is a flag shaped node]'], - [8, 'flowchart TD\nid{"This is a rhombus node"}'], - [9, """ - flowchart TD - id{"This is a rhombus node"} - """], - ] -): - match test_index: - 1: assert_str(value).is_equal("flowchart TD\nid>This is a flag shaped node]") - 2: assert_str(value).is_equal("flowchart TD\nid(((This is a double circle node)))") - 3: assert_str(value).is_equal("flowchart TD\nid((This is a circular node))") - 4: assert_str(value).is_equal("flowchart TD\nid>" + "This is a flag shaped node]") - 5: assert_str(value).is_equal("flowchart TD\nid{'This is a rhombus node'}") - 6: assert_str(value).is_equal('flowchart TD\nid((This is a circular node))') - 7: assert_str(value).is_equal('flowchart TD\nid>This is a flag shaped node]') - 8: assert_str(value).is_equal('flowchart TD\nid{"This is a rhombus node"}') - 9: assert_str(value).is_equal(""" - flowchart TD - id{"This is a rhombus node"} - """) - - -func test_with_dynamic_parameter_resolving(name: String, value, expected, test_parameters := [ - ["test_a", auto_free(Node2D.new()), Node2D], - ["test_b", auto_free(Node3D.new()), Node3D], - ["test_c", _test_node_before, SubViewport], - ["test_d", _test_node_before_test, SubViewport], -]) -> void: - # all values must be resolved - assert_that(value).is_not_null().is_instanceof(expected) - if name == "test_c": - assert_that(value).is_same(_test_node_before) - if name == "test_d": - assert_that(value).is_same(_test_node_before_test) - # the argument 'test_parameters' must be replaced by set to avoid re-instantiate of test arguments - assert_that(test_parameters).is_empty() - collect_test_call("test_with_dynamic_paramater_resolving", [name]) - - -@warning_ignore("unused_parameter") -func test_with_dynamic_parameter_resolving2( - name: String, - type, - log_level, - expected_logs, - test_parameters = [ - ["test_a", null, "LOG", {}], - [ - "test_b", - Node2D, - null, - {Node2D: "ERROR"} - ], - [ - "test_c", - Node2D, - "LOG", - {Node2D: "LOG"} - ] - ] -): - # the argument 'test_parameters' must be replaced by set to avoid re-instantiate of test arguments - assert_that(test_parameters).is_empty() - collect_test_call("test_with_dynamic_paramater_resolving2", [name]) - - -var _test_set =[ - ["test_a"], - ["test_b"], - ["test_c"] -] - -@warning_ignore("unused_parameter") -func test_with_extern_parameter_set(value, test_parameters = _test_set): - assert_that(value).is_not_empty() - assert_that(test_parameters).is_empty() - collect_test_call("test_with_extern_parameter_set", [value]) diff --git a/addons/gdUnit4/test/core/command/GdUnitCommandHandlerTest.gd b/addons/gdUnit4/test/core/command/GdUnitCommandHandlerTest.gd deleted file mode 100644 index 231cba8..0000000 --- a/addons/gdUnit4/test/core/command/GdUnitCommandHandlerTest.gd +++ /dev/null @@ -1,70 +0,0 @@ - -# GdUnit generated TestSuite -extends GdUnitTestSuite -@warning_ignore('unused_parameter') -@warning_ignore('return_value_discarded') - -# TestSuite generated from -const __source = 'res://addons/gdUnit4/src/core/command/GdUnitCommandHandler.gd' - -var _handler :GdUnitCommandHandler - - -func before(): - _handler = GdUnitCommandHandler.new() - - -func after(): - _handler._notification(NOTIFICATION_PREDELETE) - _handler = null - - -@warning_ignore('unused_parameter') -func test_create_shortcuts_defaults(shortcut :GdUnitShortcut.ShortCut, expected :String, test_parameters := [ - [GdUnitShortcut.ShortCut.RUN_TESTCASE, "GdUnitShortcutAction: RUN_TESTCASE (Ctrl+Alt+F5) -> Run TestCases"], - [GdUnitShortcut.ShortCut.RUN_TESTCASE_DEBUG, "GdUnitShortcutAction: RUN_TESTCASE_DEBUG (Ctrl+Alt+F6) -> Run TestCases (Debug)"], - [GdUnitShortcut.ShortCut.RERUN_TESTS, "GdUnitShortcutAction: RERUN_TESTS (Ctrl+F5) -> ReRun Tests"], - [GdUnitShortcut.ShortCut.RERUN_TESTS_DEBUG, "GdUnitShortcutAction: RERUN_TESTS_DEBUG (Ctrl+F6) -> ReRun Tests (Debug)"], - [GdUnitShortcut.ShortCut.RUN_TESTS_OVERALL, "GdUnitShortcutAction: RUN_TESTS_OVERALL (Ctrl+F7) -> Debug Overall TestSuites"], - [GdUnitShortcut.ShortCut.STOP_TEST_RUN, "GdUnitShortcutAction: STOP_TEST_RUN (Ctrl+F8) -> Stop Test Run"], - [GdUnitShortcut.ShortCut.CREATE_TEST, "GdUnitShortcutAction: CREATE_TEST (Ctrl+Alt+F10) -> Create TestCase"],]) -> void: - - if OS.get_name().to_lower() == "macos": - expected.replace("Ctrl", "Command") - - var action := _handler.get_shortcut_action(shortcut) - assert_that(str(action)).is_equal(expected) - - -## actually needs to comment out, it produces a lot of leaked instances -func _test__check_test_run_stopped_manually() -> void: - var inspector :GdUnitCommandHandler = mock(GdUnitCommandHandler, CALL_REAL_FUNC) - inspector._client_id = 1 - - # simulate no test is running - do_return(false).on(inspector).is_test_running_but_stop_pressed() - inspector.check_test_run_stopped_manually() - verify(inspector, 0).cmd_stop(any_int()) - - # simulate the test runner was manually stopped by the editor - do_return(true).on(inspector).is_test_running_but_stop_pressed() - inspector.check_test_run_stopped_manually() - verify(inspector, 1).cmd_stop(inspector._client_id) - - -func test_scan_test_directorys() -> void: - assert_array(GdUnitCommandHandler.scan_test_directorys("res://", "test", [])).contains_exactly([ - "res://addons/gdUnit4/test" - ]) - # for root folders - assert_array(GdUnitCommandHandler.scan_test_directorys("res://", "", [])).contains_exactly([ - "res://addons", "res://assets", "res://gdUnit3-examples" - ]) - assert_array(GdUnitCommandHandler.scan_test_directorys("res://", "/", [])).contains_exactly([ - "res://addons", "res://assets", "res://gdUnit3-examples" - ]) - assert_array(GdUnitCommandHandler.scan_test_directorys("res://", "res://", [])).contains_exactly([ - "res://addons", "res://assets", "res://gdUnit3-examples" - ]) - # a test folder not exists - assert_array(GdUnitCommandHandler.scan_test_directorys("res://", "notest", [])).is_empty() diff --git a/addons/gdUnit4/test/core/event/GdUnitEventTest.gd b/addons/gdUnit4/test/core/event/GdUnitEventTest.gd deleted file mode 100644 index cbe8aa2..0000000 --- a/addons/gdUnit4/test/core/event/GdUnitEventTest.gd +++ /dev/null @@ -1,22 +0,0 @@ -# GdUnit generated TestSuite -class_name GdUnitEventTest -extends GdUnitTestSuite - -# TestSuite generated from -const __source = 'res://addons/gdUnit4/src/core/event/GdUnitEvent.gd' - - -func test_GdUnitEvent_defaults() -> void: - var event := GdUnitEvent.new() - - assert_bool(event.is_success()).is_true() - assert_bool(event.is_warning()).is_false() - assert_bool(event.is_failed()).is_false() - assert_bool(event.is_error()).is_false() - assert_bool(event.is_skipped()).is_false() - - assert_int(event.elapsed_time()).is_zero() - assert_int(event.orphan_nodes()).is_zero() - assert_int(event.total_count()).is_zero() - assert_int(event.failed_count()).is_zero() - assert_int(event.skipped_count()).is_zero() diff --git a/addons/gdUnit4/test/core/event/GdUnitTestEventSerdeTest.gd b/addons/gdUnit4/test/core/event/GdUnitTestEventSerdeTest.gd deleted file mode 100644 index 9f231e1..0000000 --- a/addons/gdUnit4/test/core/event/GdUnitTestEventSerdeTest.gd +++ /dev/null @@ -1,51 +0,0 @@ -# this test test for serialization and deserialization succcess -# of GdUnitEvent class -extends GdUnitTestSuite - - -func test_serde_suite_before(): - var event := GdUnitEvent.new().suite_before("path", "test_suite_a", 22) - var serialized := event.serialize() - var deserialized := GdUnitEvent.new().deserialize(serialized) - assert_that(deserialized).is_instanceof(GdUnitEvent) - assert_that(deserialized).is_equal(event) - - -func test_serde_suite_after(): - var event := GdUnitEvent.new().suite_after("path","test_suite_a") - var serialized := event.serialize() - var deserialized := GdUnitEvent.new().deserialize(serialized) - assert_that(deserialized).is_equal(event) - - -func test_serde_test_before(): - var event := GdUnitEvent.new().test_before("path", "test_suite_a", "test_foo") - var serialized := event.serialize() - var deserialized := GdUnitEvent.new().deserialize(serialized) - assert_that(deserialized).is_equal(event) - - -func test_serde_test_after_no_report(): - var event := GdUnitEvent.new().test_after("path", "test_suite_a", "test_foo") - var serialized := event.serialize() - var deserialized := GdUnitEvent.new().deserialize(serialized) - assert_that(deserialized).is_equal(event) - - -func test_serde_test_after_with_report(): - var reports :Array[GdUnitReport] = [\ - GdUnitReport.new().create(GdUnitReport.FAILURE, 24, "this is a error a"), \ - GdUnitReport.new().create(GdUnitReport.FAILURE, 26, "this is a error b")] - var event := GdUnitEvent.new().test_after("path", "test_suite_a", "test_foo", {}, reports) - - var serialized := event.serialize() - var deserialized := GdUnitEvent.new().deserialize(serialized) - assert_that(deserialized).is_equal(event) - assert_array(deserialized.reports()).contains_exactly(reports) - - -func test_serde_TestReport(): - var report := GdUnitReport.new().create(GdUnitReport.FAILURE, 24, "this is a error") - var serialized := report.serialize() - var deserialized := GdUnitReport.new().deserialize(serialized) - assert_that(deserialized).is_equal(report) diff --git a/addons/gdUnit4/test/core/execution/GdUnitExecutionContextTest.gd b/addons/gdUnit4/test/core/execution/GdUnitExecutionContextTest.gd deleted file mode 100644 index 429efc5..0000000 --- a/addons/gdUnit4/test/core/execution/GdUnitExecutionContextTest.gd +++ /dev/null @@ -1,222 +0,0 @@ -# GdUnit generated TestSuite -class_name GdUnitExecutionContextTest -extends GdUnitTestSuite -@warning_ignore('unused_parameter') -@warning_ignore('return_value_discarded') - -# TestSuite generated from -const __source = 'res://addons/gdUnit4/src/core/execution/GdUnitExecutionContext.gd' - - -func add_report(ec :GdUnitExecutionContext, report :GdUnitReport) -> void: - ec._report_collector.on_reports(ec.get_instance_id(), report) - - -func assert_statistics(ec :GdUnitExecutionContext): - assert_that(ec.has_failures()).is_false() - assert_that(ec.has_errors()).is_false() - assert_that(ec.has_warnings()).is_false() - assert_that(ec.has_skipped()).is_false() - assert_that(ec.count_failures(true)).is_equal(0) - assert_that(ec.count_errors(true)).is_equal(0) - assert_that(ec.count_skipped(true)).is_equal(0) - assert_that(ec.count_orphans()).is_equal(0) - assert_dict(ec.build_report_statistics(0))\ - .contains_key_value(GdUnitEvent.FAILED, false)\ - .contains_key_value(GdUnitEvent.ERRORS, false)\ - .contains_key_value(GdUnitEvent.WARNINGS, false)\ - .contains_key_value(GdUnitEvent.SKIPPED, false)\ - .contains_key_value(GdUnitEvent.ORPHAN_NODES, 0)\ - .contains_key_value(GdUnitEvent.FAILED_COUNT, 0)\ - .contains_key_value(GdUnitEvent.ERROR_COUNT, 0)\ - .contains_key_value(GdUnitEvent.SKIPPED_COUNT, 0)\ - .contains_keys([GdUnitEvent.ELAPSED_TIME]) - - -func test_create_context_of_test_suite() -> void: - var ts :GdUnitTestSuite = auto_free(GdUnitTestSuite.new()) - var ec := GdUnitExecutionContext.of_test_suite(ts) - # verify the current context is not affected by this test itself - assert_object(__execution_context).is_not_same(ec) - - # verify the execution context is assigned to the test suite - assert_object(ts.__execution_context).is_same(ec) - - # verify execution context is fully initialized - assert_that(ec).is_not_null() - assert_object(ec.test_suite).is_same(ts) - assert_object(ec.test_case).is_null() - assert_array(ec._sub_context).is_empty() - assert_object(ec._orphan_monitor).is_not_null() - assert_object(ec._memory_observer).is_not_null() - assert_object(ec._report_collector).is_not_null() - assert_statistics(ec) - ec.dispose() - - -func test_create_context_of_test_case() -> void: - var ts :GdUnitTestSuite = auto_free(GdUnitTestSuite.new()) - var tc :_TestCase = auto_free(_TestCase.new().configure("test_case1", 0, "")) - ts.add_child(tc) - var ec1 := GdUnitExecutionContext.of_test_suite(ts) - var ec2 := GdUnitExecutionContext.of_test_case(ec1, "test_case1") - # verify the current context is not affected by this test itself - assert_object(__execution_context).is_not_same(ec1) - assert_object(__execution_context).is_not_same(ec2) - - # verify current execution contest is assigned to the test suite - assert_object(ts.__execution_context).is_same(ec2) - # verify execution context is fully initialized - assert_that(ec2).is_not_null() - assert_object(ec2.test_suite).is_same(ts) - assert_object(ec2.test_case).is_same(tc) - assert_array(ec2._sub_context).is_empty() - assert_object(ec2._orphan_monitor).is_not_null().is_not_same(ec1._orphan_monitor) - assert_object(ec2._memory_observer).is_not_null().is_not_same(ec1._memory_observer) - assert_object(ec2._report_collector).is_not_null().is_not_same(ec1._report_collector) - assert_statistics(ec2) - # check parent context ec1 is still valid - assert_that(ec1).is_not_null() - assert_object(ec1.test_suite).is_same(ts) - assert_object(ec1.test_case).is_null() - assert_array(ec1._sub_context).contains_exactly([ec2]) - assert_object(ec1._orphan_monitor).is_not_null() - assert_object(ec1._memory_observer).is_not_null() - assert_object(ec1._report_collector).is_not_null() - assert_statistics(ec1) - ec1.dispose() - - -func test_create_context_of_test() -> void: - var ts :GdUnitTestSuite = auto_free(GdUnitTestSuite.new()) - var tc :_TestCase = auto_free(_TestCase.new().configure("test_case1", 0, "")) - ts.add_child(tc) - var ec1 := GdUnitExecutionContext.of_test_suite(ts) - var ec2 := GdUnitExecutionContext.of_test_case(ec1, "test_case1") - var ec3 := GdUnitExecutionContext.of(ec2) - # verify the current context is not affected by this test itself - assert_object(__execution_context).is_not_same(ec1) - assert_object(__execution_context).is_not_same(ec2) - assert_object(__execution_context).is_not_same(ec3) - - # verify current execution contest is assigned to the test suite - assert_object(ts.__execution_context).is_same(ec3) - # verify execution context is fully initialized - assert_that(ec3).is_not_null() - assert_object(ec3.test_suite).is_same(ts) - assert_object(ec3.test_case).is_same(tc) - assert_array(ec3._sub_context).is_empty() - assert_object(ec3._orphan_monitor).is_not_null()\ - .is_not_same(ec1._orphan_monitor)\ - .is_not_same(ec2._orphan_monitor) - assert_object(ec3._memory_observer).is_not_null()\ - .is_not_same(ec1._memory_observer)\ - .is_not_same(ec2._memory_observer) - assert_object(ec3._report_collector).is_not_null()\ - .is_not_same(ec1._report_collector)\ - .is_not_same(ec2._report_collector) - assert_statistics(ec3) - # check parent context ec2 is still valid - assert_that(ec2).is_not_null() - assert_object(ec2.test_suite).is_same(ts) - assert_object(ec2.test_case).is_same(tc) - assert_array(ec2._sub_context).contains_exactly([ec3]) - assert_object(ec2._orphan_monitor).is_not_null()\ - .is_not_same(ec1._orphan_monitor) - assert_object(ec2._memory_observer).is_not_null()\ - .is_not_same(ec1._memory_observer) - assert_object(ec2._report_collector).is_not_null()\ - .is_not_same(ec1._report_collector) - assert_statistics(ec2) - # check parent context ec1 is still valid - assert_that(ec1).is_not_null() - assert_object(ec1.test_suite).is_same(ts) - assert_object(ec1.test_case).is_null() - assert_array(ec1._sub_context).contains_exactly([ec2]) - assert_object(ec1._orphan_monitor).is_not_null() - assert_object(ec1._memory_observer).is_not_null() - assert_object(ec1._report_collector).is_not_null() - assert_statistics(ec1) - ec1.dispose() - - -func test_report_collectors() -> void: - # setup - var ts :GdUnitTestSuite = auto_free(GdUnitTestSuite.new()) - var tc :_TestCase = auto_free(_TestCase.new().configure("test_case1", 0, "")) - ts.add_child(tc) - var ec1 := GdUnitExecutionContext.of_test_suite(ts) - var ec2 := GdUnitExecutionContext.of_test_case(ec1, "test_case1") - var ec3 := GdUnitExecutionContext.of(ec2) - - # add reports - var failure11 := GdUnitReport.new().create(GdUnitReport.FAILURE, 1, "error_ec11") - add_report(ec1, failure11) - var failure21 := GdUnitReport.new().create(GdUnitReport.FAILURE, 3, "error_ec21") - var failure22 := GdUnitReport.new().create(GdUnitReport.FAILURE, 3, "error_ec22") - add_report(ec2, failure21) - add_report(ec2, failure22) - var failure31 := GdUnitReport.new().create(GdUnitReport.FAILURE, 3, "error_ec31") - var failure32 := GdUnitReport.new().create(GdUnitReport.FAILURE, 3, "error_ec32") - var failure33 := GdUnitReport.new().create(GdUnitReport.FAILURE, 3, "error_ec33") - add_report(ec3, failure31) - add_report(ec3, failure32) - add_report(ec3, failure33) - # verify - assert_array(ec1.reports()).contains_exactly([failure11]) - assert_array(ec2.reports()).contains_exactly([failure21, failure22]) - assert_array(ec3.reports()).contains_exactly([failure31, failure32, failure33]) - ec1.dispose() - - -func test_has_and_count_failures() -> void: - # setup - var ts :GdUnitTestSuite = auto_free(GdUnitTestSuite.new()) - var tc :_TestCase = auto_free(_TestCase.new().configure("test_case1", 0, "")) - ts.add_child(tc) - var ec1 := GdUnitExecutionContext.of_test_suite(ts) - var ec2 := GdUnitExecutionContext.of_test_case(ec1, "test_case1") - var ec3 := GdUnitExecutionContext.of(ec2) - - # precheck - assert_that(ec1.has_failures()).is_false() - assert_that(ec1.count_failures(true)).is_equal(0) - assert_that(ec2.has_failures()).is_false() - assert_that(ec2.count_failures(true)).is_equal(0) - assert_that(ec3.has_failures()).is_false() - assert_that(ec3.count_failures(true)).is_equal(0) - - # add four failure report to test - add_report(ec3, GdUnitReport.new().create(GdUnitReport.FAILURE, 42, "error_ec31")) - add_report(ec3, GdUnitReport.new().create(GdUnitReport.FAILURE, 43, "error_ec32")) - add_report(ec3, GdUnitReport.new().create(GdUnitReport.FAILURE, 44, "error_ec33")) - add_report(ec3, GdUnitReport.new().create(GdUnitReport.FAILURE, 45, "error_ec34")) - # verify - assert_that(ec1.has_failures()).is_true() - assert_that(ec1.count_failures(true)).is_equal(4) - assert_that(ec2.has_failures()).is_true() - assert_that(ec2.count_failures(true)).is_equal(4) - assert_that(ec3.has_failures()).is_true() - assert_that(ec3.count_failures(true)).is_equal(4) - - # add two failure report to test_case_stage - add_report(ec2, GdUnitReport.new().create(GdUnitReport.FAILURE, 42, "error_ec21")) - add_report(ec2, GdUnitReport.new().create(GdUnitReport.FAILURE, 43, "error_ec22")) - # verify - assert_that(ec1.has_failures()).is_true() - assert_that(ec1.count_failures(true)).is_equal(6) - assert_that(ec2.has_failures()).is_true() - assert_that(ec2.count_failures(true)).is_equal(6) - assert_that(ec3.has_failures()).is_true() - assert_that(ec3.count_failures(true)).is_equal(4) - - # add one failure report to test_suite_stage - add_report(ec1, GdUnitReport.new().create(GdUnitReport.FAILURE, 42, "error_ec1")) - # verify - assert_that(ec1.has_failures()).is_true() - assert_that(ec1.count_failures(true)).is_equal(7) - assert_that(ec2.has_failures()).is_true() - assert_that(ec2.count_failures(true)).is_equal(6) - assert_that(ec3.has_failures()).is_true() - assert_that(ec3.count_failures(true)).is_equal(4) - ec1.dispose() diff --git a/addons/gdUnit4/test/core/execution/GdUnitTestSuiteExecutorTest.gd b/addons/gdUnit4/test/core/execution/GdUnitTestSuiteExecutorTest.gd deleted file mode 100644 index 71af96d..0000000 --- a/addons/gdUnit4/test/core/execution/GdUnitTestSuiteExecutorTest.gd +++ /dev/null @@ -1,708 +0,0 @@ -# GdUnit generated TestSuite -class_name GdUnitTestSuiteExecutorTest -extends GdUnitTestSuite -@warning_ignore('unused_parameter') -@warning_ignore('return_value_discarded') - -# TestSuite generated from -const __source = 'res://addons/gdUnit4/src/core/execution/GdUnitTestSuiteExecutor.gd' -const GdUnitTools := preload("res://addons/gdUnit4/src/core/GdUnitTools.gd") - - -const SUCCEEDED = true -const FAILED = false -const SKIPPED = true -const NOT_SKIPPED = false - -var _collected_events :Array[GdUnitEvent] = [] - - -func before() -> void: - GdUnitSignals.instance().gdunit_event_debug.connect(_on_gdunit_event_debug) - - -func after() -> void: - GdUnitSignals.instance().gdunit_event_debug.disconnect(_on_gdunit_event_debug) - - -func after_test(): - _collected_events.clear() - - -func _on_gdunit_event_debug(event :GdUnitEvent) -> void: - _collected_events.append(event) - - -func _load(resource_path :String) -> GdUnitTestSuite: - return GdUnitTestResourceLoader.load_test_suite(resource_path) as GdUnitTestSuite - - -func flating_message(message :String) -> String: - return GdUnitTools.richtext_normalize(message) - - -func execute(test_suite :GdUnitTestSuite) -> Array[GdUnitEvent]: - await GdUnitThreadManager.run("test_executor", func(): - var executor := GdUnitTestSuiteExecutor.new(true) - await executor.execute(test_suite)) - return _collected_events - - -func assert_event_list(events :Array[GdUnitEvent], suite_name :String, test_case_names :Array[String]) -> void: - var expected_events := Array() - expected_events.append(tuple(GdUnitEvent.TESTSUITE_BEFORE, suite_name, "before", test_case_names.size())) - for test_case in test_case_names: - expected_events.append(tuple(GdUnitEvent.TESTCASE_BEFORE, suite_name, test_case, 0)) - expected_events.append(tuple(GdUnitEvent.TESTCASE_AFTER, suite_name, test_case, 0)) - expected_events.append(tuple(GdUnitEvent.TESTSUITE_AFTER, suite_name, "after", 0)) - - var expected_event_count := 2 + test_case_names.size() * 2 - assert_array(events)\ - .override_failure_message("Expecting be %d events emitted, but counts %d." % [expected_event_count, events.size()])\ - .has_size(expected_event_count) - - assert_array(events)\ - .extractv(extr("type"), extr("suite_name"), extr("test_name"), extr("total_count"))\ - .contains_exactly(expected_events) - - -func assert_event_counters(events :Array[GdUnitEvent]) -> GdUnitArrayAssert: - return assert_array(events).extractv(extr("type"), extr("error_count"), extr("failed_count"), extr("orphan_nodes")) - - -func assert_event_states(events :Array[GdUnitEvent]) -> GdUnitArrayAssert: - return assert_array(events).extractv(extr("test_name"), extr("is_success"), extr("is_skipped"), extr("is_warning"), extr("is_failed"), extr("is_error")) - - -func assert_event_reports(events :Array[GdUnitEvent], expected_reports :Array) -> void: - for event_index in events.size(): - var current :Array = events[event_index].reports() - var expected = expected_reports[event_index] if expected_reports.size() > event_index else [] - if expected.is_empty(): - for m in current.size(): - assert_str(flating_message(current[m].message())).is_empty() - - for m in expected.size(): - if m < current.size(): - assert_str(flating_message(current[m].message())).is_equal(expected[m]) - else: - assert_str("").is_equal(expected[m]) - - -func test_execute_success() -> void: - var test_suite := _load("res://addons/gdUnit4/test/core/resources/testsuites/TestSuiteAllStagesSuccess.resource") - # simulate test suite execution - var events := await execute(test_suite) - assert_event_list(events,\ - "TestSuiteAllStagesSuccess",\ - ["test_case1", "test_case2"]) - # verify all counters are zero / no errors, failures, orphans - assert_event_counters(events).contains_exactly([ - tuple(GdUnitEvent.TESTSUITE_BEFORE, 0, 0, 0), - tuple(GdUnitEvent.TESTCASE_BEFORE, 0, 0, 0), - tuple(GdUnitEvent.TESTCASE_AFTER, 0, 0, 0), - tuple(GdUnitEvent.TESTCASE_BEFORE, 0, 0, 0), - tuple(GdUnitEvent.TESTCASE_AFTER, 0, 0, 0), - tuple(GdUnitEvent.TESTSUITE_AFTER, 0, 0, 0), - ]) - assert_event_states(events).contains_exactly([ - tuple("before", SUCCEEDED, NOT_SKIPPED, false, false, false), - tuple("test_case1", SUCCEEDED, NOT_SKIPPED, false, false, false), - tuple("test_case1", SUCCEEDED, NOT_SKIPPED, false, false, false), - tuple("test_case2", SUCCEEDED, NOT_SKIPPED, false, false, false), - tuple("test_case2", SUCCEEDED, NOT_SKIPPED, false, false, false), - tuple("after", SUCCEEDED, NOT_SKIPPED, false, false, false), - ]) - # all success no reports expected - assert_event_reports(events, [ - [], [], [], [], [], [] - ]) - - -func test_execute_failure_on_stage_before() -> void: - var test_suite := _load("res://addons/gdUnit4/test/core/resources/testsuites/TestSuiteFailOnStageBefore.resource") - # simulate test suite execution - var events = await execute(test_suite) - # verify basis infos - assert_event_list(events,\ - "TestSuiteFailOnStageBefore",\ - ["test_case1", "test_case2"]) - # we expect the testsuite is failing on stage 'before()' and commits one failure - # reported finally at TESTSUITE_AFTER event - assert_event_counters(events).contains_exactly([ - tuple(GdUnitEvent.TESTSUITE_BEFORE, 0, 0, 0), - tuple(GdUnitEvent.TESTCASE_BEFORE, 0, 0, 0), - tuple(GdUnitEvent.TESTCASE_AFTER, 0, 0, 0), - tuple(GdUnitEvent.TESTCASE_BEFORE, 0, 0, 0), - tuple(GdUnitEvent.TESTCASE_AFTER, 0, 0, 0), - # report failure failed_count = 1 - tuple(GdUnitEvent.TESTSUITE_AFTER, 0, 1, 0), - ]) - assert_event_states(events).contains_exactly([ - tuple("before", SUCCEEDED, NOT_SKIPPED, false, false, false), - tuple("test_case1", SUCCEEDED, NOT_SKIPPED, false, false, false), - tuple("test_case1", SUCCEEDED, NOT_SKIPPED, false, false, false), - tuple("test_case2", SUCCEEDED, NOT_SKIPPED, false, false, false), - tuple("test_case2", SUCCEEDED, NOT_SKIPPED, false, false, false), - # report suite is not success, is failed - tuple("after", FAILED, NOT_SKIPPED, false, true, false), - ]) - # one failure at before() - assert_event_reports(events, [ - [], - [], - [], - [], - [], - ["failed on before()"] - ]) - - -func test_execute_failure_on_stage_after() -> void: - var test_suite := _load("res://addons/gdUnit4/test/core/resources/testsuites/TestSuiteFailOnStageAfter.resource") - # simulate test suite execution - var events = await execute(test_suite) - # verify basis infos - assert_event_list(events,\ - "TestSuiteFailOnStageAfter",\ - ["test_case1", "test_case2"]) - # we expect the testsuite is failing on stage 'before()' and commits one failure - # reported finally at TESTSUITE_AFTER event - assert_event_counters(events).contains_exactly([ - tuple(GdUnitEvent.TESTSUITE_BEFORE, 0, 0, 0), - tuple(GdUnitEvent.TESTCASE_BEFORE, 0, 0, 0), - tuple(GdUnitEvent.TESTCASE_AFTER, 0, 0, 0), - tuple(GdUnitEvent.TESTCASE_BEFORE, 0, 0, 0), - tuple(GdUnitEvent.TESTCASE_AFTER, 0, 0, 0), - # report failure failed_count = 1 - tuple(GdUnitEvent.TESTSUITE_AFTER, 0, 1, 0), - ]) - assert_event_states(events).contains_exactly([ - tuple("before", SUCCEEDED, NOT_SKIPPED, false, false, false), - tuple("test_case1", SUCCEEDED, NOT_SKIPPED, false, false, false), - tuple("test_case1", SUCCEEDED, NOT_SKIPPED, false, false, false), - tuple("test_case2", SUCCEEDED, NOT_SKIPPED, false, false, false), - tuple("test_case2", SUCCEEDED, NOT_SKIPPED, false, false, false), - # report suite is not success, is failed - tuple("after", FAILED, NOT_SKIPPED, false, true, false), - ]) - # one failure at after() - assert_event_reports(events, [ - [], - [], - [], - [], - [], - ["failed on after()"] - ]) - - -func test_execute_failure_on_stage_before_test() -> void: - var test_suite := _load("res://addons/gdUnit4/test/core/resources/testsuites/TestSuiteFailOnStageBeforeTest.resource") - # simulate test suite execution - var events = await execute(test_suite) - # verify basis infos - assert_event_list(events,\ - "TestSuiteFailOnStageBeforeTest",\ - ["test_case1", "test_case2"]) - # we expect the testsuite is failing on stage 'before_test()' and commits one failure on each test case - # because is in scope of test execution - assert_event_counters(events).contains_exactly([ - tuple(GdUnitEvent.TESTSUITE_BEFORE, 0, 0, 0), - tuple(GdUnitEvent.TESTCASE_BEFORE, 0, 0, 0), - # failure is count to the test - tuple(GdUnitEvent.TESTCASE_AFTER, 0, 1, 0), - tuple(GdUnitEvent.TESTCASE_BEFORE, 0, 0, 0), - # failure is count to the test - tuple(GdUnitEvent.TESTCASE_AFTER, 0, 1, 0), - tuple(GdUnitEvent.TESTSUITE_AFTER, 0, 0, 0), - ]) - assert_event_states(events).contains_exactly([ - tuple("before", SUCCEEDED, NOT_SKIPPED, false, false, false), - tuple("test_case1", SUCCEEDED, NOT_SKIPPED, false, false, false), - tuple("test_case1", FAILED, NOT_SKIPPED, false, true, false), - tuple("test_case2", SUCCEEDED, NOT_SKIPPED, false, false, false), - tuple("test_case2", FAILED, NOT_SKIPPED, false, true, false), - # report suite is not success, is failed - tuple("after", FAILED, NOT_SKIPPED, false, true, false), - ]) - # before_test() failure report is append to each test - assert_event_reports(events, [ - [], - [], - # verify failure report is append to 'test_case1' - ["failed on before_test()"], - [], - # verify failure report is append to 'test_case2' - ["failed on before_test()"], - [] - ]) - - -func test_execute_failure_on_stage_after_test() -> void: - var test_suite := _load("res://addons/gdUnit4/test/core/resources/testsuites/TestSuiteFailOnStageAfterTest.resource") - # simulate test suite execution - var events = await execute(test_suite) - # verify basis infos - assert_event_list(events,\ - "TestSuiteFailOnStageAfterTest",\ - ["test_case1", "test_case2"]) - # we expect the testsuite is failing on stage 'after_test()' and commits one failure on each test case - # because is in scope of test execution - assert_event_counters(events).contains_exactly([ - tuple(GdUnitEvent.TESTSUITE_BEFORE, 0, 0, 0), - tuple(GdUnitEvent.TESTCASE_BEFORE, 0, 0, 0), - # failure is count to the test - tuple(GdUnitEvent.TESTCASE_AFTER, 0, 1, 0), - tuple(GdUnitEvent.TESTCASE_BEFORE, 0, 0, 0), - # failure is count to the test - tuple(GdUnitEvent.TESTCASE_AFTER, 0, 1, 0), - tuple(GdUnitEvent.TESTSUITE_AFTER, 0, 0, 0), - ]) - assert_event_states(events).contains_exactly([ - tuple("before", SUCCEEDED, NOT_SKIPPED, false, false, false), - tuple("test_case1", SUCCEEDED, NOT_SKIPPED, false, false, false), - tuple("test_case1", FAILED, NOT_SKIPPED, false, true, false), - tuple("test_case2", SUCCEEDED, NOT_SKIPPED, false, false, false), - tuple("test_case2", FAILED, NOT_SKIPPED, false, true, false), - # report suite is not success, is failed - tuple("after", FAILED, NOT_SKIPPED, false, true, false), - ]) - # 'after_test' failure report is append to each test - assert_event_reports(events, [ - [], - [], - # verify failure report is append to 'test_case1' - ["failed on after_test()"], - [], - # verify failure report is append to 'test_case2' - ["failed on after_test()"], - [] - ]) - - -func test_execute_failure_on_stage_test_case1() -> void: - var test_suite := _load("res://addons/gdUnit4/test/core/resources/testsuites/TestSuiteFailOnStageTestCase1.resource") - # simulate test suite execution - var events = await execute(test_suite) - # verify basis infos - assert_event_list(events,\ - "TestSuiteFailOnStageTestCase1",\ - ["test_case1", "test_case2"]) - # we expect the test case 'test_case1' is failing and commits one failure - assert_event_counters(events).contains_exactly([ - tuple(GdUnitEvent.TESTSUITE_BEFORE, 0, 0, 0), - tuple(GdUnitEvent.TESTCASE_BEFORE, 0, 0, 0), - # test has one failure - tuple(GdUnitEvent.TESTCASE_AFTER, 0, 1, 0), - tuple(GdUnitEvent.TESTCASE_BEFORE, 0, 0, 0), - tuple(GdUnitEvent.TESTCASE_AFTER, 0, 0, 0), - tuple(GdUnitEvent.TESTSUITE_AFTER, 0, 0, 0), - ]) - assert_event_states(events).contains_exactly([ - tuple("before", SUCCEEDED, NOT_SKIPPED, false, false, false), - tuple("test_case1", SUCCEEDED, NOT_SKIPPED, false, false, false), - tuple("test_case1", FAILED, NOT_SKIPPED, false, true, false), - tuple("test_case2", SUCCEEDED, NOT_SKIPPED, false, false, false), - tuple("test_case2", SUCCEEDED, NOT_SKIPPED, false, false, false), - # report suite is not success, is failed - tuple("after", FAILED, NOT_SKIPPED, false, true, false), - ]) - # only 'test_case1' reports a failure - assert_event_reports(events, [ - [], - [], - # verify failure report is append to 'test_case1' - ["failed on test_case1()"], - [], - [], - [] - ]) - - -func test_execute_failure_on_multiple_stages() -> void: - # this is a more complex failure state, we expect to find multipe failures on different stages - var test_suite := _load("res://addons/gdUnit4/test/core/resources/testsuites/TestSuiteFailOnMultipeStages.resource") - # simulate test suite execution - var events = await execute(test_suite) - # verify basis infos - assert_event_list(events,\ - "TestSuiteFailOnMultipeStages",\ - ["test_case1", "test_case2"]) - # we expect failing on multiple stages - assert_event_counters(events).contains_exactly([ - tuple(GdUnitEvent.TESTSUITE_BEFORE, 0, 0, 0), - tuple(GdUnitEvent.TESTCASE_BEFORE, 0, 0, 0), - # the first test has two failures plus one from 'before_test' - tuple(GdUnitEvent.TESTCASE_AFTER, 0, 3, 0), - tuple(GdUnitEvent.TESTCASE_BEFORE, 0, 0, 0), - # the second test has no failures but one from 'before_test' - tuple(GdUnitEvent.TESTCASE_AFTER, 0, 1, 0), - # and one failure is on stage 'after' found - tuple(GdUnitEvent.TESTSUITE_AFTER, 0, 1, 0), - ]) - assert_event_states(events).contains_exactly([ - tuple("before", SUCCEEDED, NOT_SKIPPED, false, false, false), - tuple("test_case1", SUCCEEDED, NOT_SKIPPED, false, false, false), - tuple("test_case1", FAILED, NOT_SKIPPED, false, true, false), - tuple("test_case2", SUCCEEDED, NOT_SKIPPED, false, false, false), - tuple("test_case2", FAILED, NOT_SKIPPED, false, true, false), - # report suite is not success, is failed - tuple("after", FAILED, NOT_SKIPPED, false, true, false), - ]) - # only 'test_case1' reports a 'real' failures plus test setup stage failures - assert_event_reports(events, [ - [], - [], - # verify failure reports to 'test_case1' - ["failed on before_test()", "failed 1 on test_case1()", "failed 2 on test_case1()"], - [], - # verify failure reports to 'test_case2' - ["failed on before_test()"], - # and one failure detected at stage 'after' - ["failed on after()"] - ]) - - -# GD-63 -func test_execute_failure_and_orphans() -> void: - # this is a more complex failure state, we expect to find multipe orphans on different stages - var test_suite := _load("res://addons/gdUnit4/test/core/resources/testsuites/TestSuiteFailAndOrpahnsDetected.resource") - # simulate test suite execution - var events = await execute(test_suite) - # verify basis infos - assert_event_list(events,\ - "TestSuiteFailAndOrpahnsDetected",\ - ["test_case1", "test_case2"]) - # we expect failing on multiple stages - assert_event_counters(events).contains_exactly([ - tuple(GdUnitEvent.TESTSUITE_BEFORE, 0, 0, 0), - tuple(GdUnitEvent.TESTCASE_BEFORE, 0, 0, 0), - # the first test ends with a warning and in summ 5 orphans detected - # 2 from stage 'before_test' + 3 from test itself - tuple(GdUnitEvent.TESTCASE_AFTER, 0, 0, 5), - tuple(GdUnitEvent.TESTCASE_BEFORE, 0, 0, 0), - # the second test ends with a one failure and in summ 6 orphans detected - # 2 from stage 'before_test' + 4 from test itself - tuple(GdUnitEvent.TESTCASE_AFTER, 0, 1, 6), - # and one orphan detected from stage 'before' - tuple(GdUnitEvent.TESTSUITE_AFTER, 0, 0, 1), - ]) - # is_success, is_warning, is_failed, is_error - assert_event_states(events).contains_exactly([ - tuple("before", SUCCEEDED, NOT_SKIPPED, false, false, false), - tuple("test_case1", SUCCEEDED, NOT_SKIPPED, false, false, false), - # test case has only warnings - tuple("test_case1", FAILED, NOT_SKIPPED, true, false, false), - tuple("test_case2", SUCCEEDED, NOT_SKIPPED, false, false, false), - # test case has failures and warnings - tuple("test_case2", FAILED, NOT_SKIPPED, true, true, false), - # report suite is not success, has warnings and failures - tuple("after", FAILED, NOT_SKIPPED, true, true, false), - ]) - # only 'test_case1' reports a 'real' failures plus test setup stage failures - assert_event_reports(events, [ - [], - [], - # ends with warnings - ["WARNING:\n Detected <2> orphan nodes during test setup! Check before_test() and after_test()!", - "WARNING:\n Detected <3> orphan nodes during test execution!"], - [], - # ends with failure and warnings - ["WARNING:\n Detected <2> orphan nodes during test setup! Check before_test() and after_test()!", - "WARNING:\n Detected <4> orphan nodes during test execution!", - "faild on test_case2()"], - # and one failure detected at stage 'after' - ["WARNING:\n Detected <1> orphan nodes during test suite setup stage! Check before() and after()!"] - ]) - - -func test_execute_failure_and_orphans_report_orphan_disabled() -> void: - # this is a more complex failure state, we expect to find multipe orphans on different stages - var test_suite := _load("res://addons/gdUnit4/test/core/resources/testsuites/TestSuiteFailAndOrpahnsDetected.resource") - # simulate test suite execution whit disabled orphan detection - - ProjectSettings.set_setting(GdUnitSettings.REPORT_ORPHANS, false) - var events = await execute(test_suite) - ProjectSettings.set_setting(GdUnitSettings.REPORT_ORPHANS, true) - - # verify basis infos - assert_event_list(events,\ - "TestSuiteFailAndOrpahnsDetected",\ - ["test_case1", "test_case2"]) - # we expect failing on multiple stages, no orphans reported - assert_event_counters(events).contains_exactly([ - tuple(GdUnitEvent.TESTSUITE_BEFORE, 0, 0, 0), - tuple(GdUnitEvent.TESTCASE_BEFORE, 0, 0, 0), - tuple(GdUnitEvent.TESTCASE_AFTER, 0, 0, 0), - tuple(GdUnitEvent.TESTCASE_BEFORE, 0, 0, 0), - # one failure - tuple(GdUnitEvent.TESTCASE_AFTER, 0, 1, 0), - tuple(GdUnitEvent.TESTSUITE_AFTER, 0, 0, 0), - ]) - # is_success, is_warning, is_failed, is_error - assert_event_states(events).contains_exactly([ - tuple("before", SUCCEEDED, NOT_SKIPPED, false, false, false), - tuple("test_case1", SUCCEEDED, NOT_SKIPPED, false, false, false), - # test case has success - tuple("test_case1", SUCCEEDED, NOT_SKIPPED, false, false, false), - tuple("test_case2", SUCCEEDED, NOT_SKIPPED, false, false, false), - # test case has a failure - tuple("test_case2", FAILED, NOT_SKIPPED, false, true, false), - # report suite is not success, has warnings and failures - tuple("after", FAILED, NOT_SKIPPED, false, true, false), - ]) - # only 'test_case1' reports a failure, orphans are not reported - assert_event_reports(events, [ - [], - [], - [], - [], - # ends with a failure - ["faild on test_case2()"], - [] - ]) - - -func test_execute_error_on_test_timeout() -> void: - # this tests a timeout on a test case reported as error - var test_suite := _load("res://addons/gdUnit4/test/core/resources/testsuites/TestSuiteErrorOnTestTimeout.resource") - # simulate test suite execution - var events = await execute(test_suite) - # verify basis infos - assert_event_list(events,\ - "TestSuiteErrorOnTestTimeout",\ - ["test_case1", "test_case2"]) - # we expect test_case1 fails by a timeout - assert_event_counters(events).contains_exactly([ - tuple(GdUnitEvent.TESTSUITE_BEFORE, 0, 0, 0), - # the first test timed out after 2s - tuple(GdUnitEvent.TESTCASE_BEFORE, 0, 0, 0), - tuple(GdUnitEvent.TESTCASE_AFTER, 1, 0, 0), - tuple(GdUnitEvent.TESTCASE_BEFORE, 0, 0, 0), - tuple(GdUnitEvent.TESTCASE_AFTER, 0, 0, 0), - tuple(GdUnitEvent.TESTSUITE_AFTER, 0, 0, 0), - ]) - assert_event_states(events).contains_exactly([ - tuple("before", SUCCEEDED, NOT_SKIPPED, false, false, false), - tuple("test_case1", SUCCEEDED, NOT_SKIPPED, false, false, false), - # testcase ends with a timeout error - tuple("test_case1", FAILED, NOT_SKIPPED, false, false, true), - tuple("test_case2", SUCCEEDED, NOT_SKIPPED, false, false, false), - tuple("test_case2", SUCCEEDED, NOT_SKIPPED, false, false, false), - # report suite is not success, is error - tuple("after", FAILED, NOT_SKIPPED, false, false, true), - ]) - # 'test_case1' reports a error triggered by test timeout - assert_event_reports(events, [ - [], - [], - # verify error reports to 'test_case1' - ["Timeout !\n 'Test timed out after 2s 0ms'"], - [], - [], - [] - ]) - - -# This test checks if all test stages are called at each test iteration. -func test_execute_fuzzed_metrics() -> void: - var test_suite := _load("res://addons/gdUnit4/test/core/resources/testsuites/TestSuiteFuzzedMetricsTest.resource") - - var events = await execute(test_suite) - assert_event_states(events).contains([ - tuple("before", SUCCEEDED, NOT_SKIPPED, false, false, false), - tuple("after", SUCCEEDED, NOT_SKIPPED, false, false, false), - ]) - assert_event_reports(events, [ - [], - [], - [], - [], - [], - [] - ]) - - -# This test checks if all test stages are called at each test iteration. -func test_execute_parameterized_metrics() -> void: - var test_suite := _load("res://addons/gdUnit4/test/core/resources/testsuites/TestSuiteParameterizedMetricsTest.resource") - - var events = await execute(test_suite) - assert_event_states(events).contains([ - tuple("before", SUCCEEDED, NOT_SKIPPED, false, false, false), - tuple("after", SUCCEEDED, NOT_SKIPPED, false, false, false), - ]) - assert_event_reports(events, [ - [], - [], - [], - [], - [], - [] - ]) - - -func test_execute_failure_fuzzer_iteration() -> void: - # this tests a timeout on a test case reported as error - var test_suite := _load("res://addons/gdUnit4/test/core/resources/testsuites/GdUnitFuzzerTest.resource") - - # simulate test suite execution - var events = await execute(test_suite) - - # verify basis infos - assert_event_list(events, "GdUnitFuzzerTest", [ - "test_multi_yielding_with_fuzzer", - "test_multi_yielding_with_fuzzer_fail_after_3_iterations"]) - # we expect failing at 'test_multi_yielding_with_fuzzer_fail_after_3_iterations' after three iterations - assert_event_counters(events).contains_exactly([ - tuple(GdUnitEvent.TESTSUITE_BEFORE, 0, 0, 0), - tuple(GdUnitEvent.TESTCASE_BEFORE, 0, 0, 0), - tuple(GdUnitEvent.TESTCASE_AFTER, 0, 0, 0), - tuple(GdUnitEvent.TESTCASE_BEFORE, 0, 0, 0), - # test failed after 3 iterations - tuple(GdUnitEvent.TESTCASE_AFTER, 0, 1, 0), - tuple(GdUnitEvent.TESTSUITE_AFTER, 0, 0, 0), - ]) - # is_success, is_warning, is_failed, is_error - assert_event_states(events).contains_exactly([ - tuple("before", SUCCEEDED, NOT_SKIPPED, false, false, false), - tuple("test_multi_yielding_with_fuzzer", SUCCEEDED, NOT_SKIPPED, false, false, false), - tuple("test_multi_yielding_with_fuzzer", SUCCEEDED, NOT_SKIPPED, false, false, false), - tuple("test_multi_yielding_with_fuzzer_fail_after_3_iterations", SUCCEEDED, NOT_SKIPPED, false, false, false), - tuple("test_multi_yielding_with_fuzzer_fail_after_3_iterations", FAILED, NOT_SKIPPED, false, true, false), - tuple("after", FAILED, NOT_SKIPPED, false, true, false), - ]) - # 'test_case1' reports a error triggered by test timeout - assert_event_reports(events, [ - [], - [], - [], - [], - # must fail after three iterations - ["Found an error after '3' test iterations\n Expecting: 'false' but is 'true'"], - [] - ]) - - -func test_execute_add_child_on_before_GD_106() -> void: - var test_suite := _load("res://addons/gdUnit4/test/core/resources/testsuites/TestSuiteFailAddChildStageBefore.resource") - # simulate test suite execution - var events = await execute(test_suite) - # verify basis infos - assert_event_list(events,\ - "TestSuiteFailAddChildStageBefore",\ - ["test_case1", "test_case2"]) - # verify all counters are zero / no errors, failures, orphans - assert_event_counters(events).contains_exactly([ - tuple(GdUnitEvent.TESTSUITE_BEFORE, 0, 0, 0), - tuple(GdUnitEvent.TESTCASE_BEFORE, 0, 0, 0), - tuple(GdUnitEvent.TESTCASE_AFTER, 0, 0, 0), - tuple(GdUnitEvent.TESTCASE_BEFORE, 0, 0, 0), - tuple(GdUnitEvent.TESTCASE_AFTER, 0, 0, 0), - tuple(GdUnitEvent.TESTSUITE_AFTER, 0, 0, 0), - ]) - assert_event_states(events).contains_exactly([ - tuple("before", SUCCEEDED, NOT_SKIPPED, false, false, false), - tuple("test_case1", SUCCEEDED, NOT_SKIPPED, false, false, false), - tuple("test_case1", SUCCEEDED, NOT_SKIPPED, false, false, false), - tuple("test_case2", SUCCEEDED, NOT_SKIPPED, false, false, false), - tuple("test_case2", SUCCEEDED, NOT_SKIPPED, false, false, false), - tuple("after", SUCCEEDED, NOT_SKIPPED, false, false, false), - ]) - # all success no reports expected - assert_event_reports(events, [ - [], [], [], [], [], [] - ]) - - -func test_execute_parameterizied_tests() -> void: - # this is a more complex failure state, we expect to find multipe failures on different stages - var test_suite := _load("res://addons/gdUnit4/test/core/resources/testsuites/TestSuiteParameterizedTests.resource") - # simulate test suite execution - # run the tests with to compare type save - var original_mode = ProjectSettings.get_setting(GdUnitSettings.REPORT_ASSERT_STRICT_NUMBER_TYPE_COMPARE) - ProjectSettings.set_setting(GdUnitSettings.REPORT_ASSERT_STRICT_NUMBER_TYPE_COMPARE, true) - var events = await execute(test_suite) - var suite_name = "TestSuiteParameterizedTests" - # the test is partial failing because of diverent type in the dictionary - assert_array(events).extractv( - extr("type"), extr("suite_name"), TestCaseNameExtractor.new(), extr("is_error"), extr("is_failed"), extr("orphan_nodes"))\ - .contains([ - tuple(GdUnitEvent.TESTCASE_AFTER, suite_name, "test_dictionary_div_number_types:0", false, true, 0), - tuple(GdUnitEvent.TESTCASE_AFTER, suite_name, "test_dictionary_div_number_types:1", false, false, 0), - tuple(GdUnitEvent.TESTCASE_AFTER, suite_name, "test_dictionary_div_number_types:2", false, true, 0), - tuple(GdUnitEvent.TESTCASE_AFTER, suite_name, "test_dictionary_div_number_types:3", false, false, 0) - ]) - - # rerun the same tests again with allow to compare type unsave - ProjectSettings.set_setting(GdUnitSettings.REPORT_ASSERT_STRICT_NUMBER_TYPE_COMPARE, false) - # simulate test suite execution - test_suite = _load("res://addons/gdUnit4/test/core/resources/testsuites/TestSuiteParameterizedTests.resource") - events = await execute(test_suite) - ProjectSettings.set_setting(GdUnitSettings.REPORT_ASSERT_STRICT_NUMBER_TYPE_COMPARE, original_mode) - - # the test should now be successful - assert_array(events).extractv( - extr("type"), extr("suite_name"), TestCaseNameExtractor.new(), extr("is_error"), extr("is_failed"), extr("orphan_nodes"))\ - .contains([ - tuple(GdUnitEvent.TESTCASE_AFTER, suite_name, "test_dictionary_div_number_types:0", false, false, 0), - tuple(GdUnitEvent.TESTCASE_AFTER, suite_name, "test_dictionary_div_number_types:1", false, false, 0), - tuple(GdUnitEvent.TESTCASE_AFTER, suite_name, "test_dictionary_div_number_types:2", false, false, 0), - tuple(GdUnitEvent.TESTCASE_AFTER, suite_name, "test_dictionary_div_number_types:3", false, false, 0) - ]) - - -func test_execute_test_suite_is_skipped() -> void: - var test_suite := _load("res://addons/gdUnit4/test/core/resources/testsuites/TestSuiteSkipped.resource") - # simulate test suite execution - var events = await execute(test_suite) - # the entire test-suite is skipped - assert_event_states(events).contains_exactly([ - tuple("before", SUCCEEDED, NOT_SKIPPED, false, false, false), - tuple("after", FAILED, SKIPPED, false, false, false), - ]) - assert_event_reports(events, [ - [], - # must fail after three iterations - [""" - Entire test-suite is skipped! - Tests skipped: '2' - Reason: '"do not run this"' - """.dedent().trim_prefix("\n")] - ]) - - -func test_execute_test_case_is_skipped() -> void: - var test_suite := _load("res://addons/gdUnit4/test/core/resources/testsuites/TestCaseSkipped.resource") - # simulate test suite execution - var events = await execute(test_suite) - # the test_case1 is skipped - assert_event_states(events).contains_exactly([ - tuple("before", SUCCEEDED, NOT_SKIPPED, false, false, false), - tuple("test_case1", SUCCEEDED, NOT_SKIPPED, false, false, false), - tuple("test_case1", FAILED, SKIPPED, false, false, false), - tuple("test_case2", SUCCEEDED, NOT_SKIPPED, false, false, false), - tuple("test_case2", SUCCEEDED, NOT_SKIPPED, false, false, false), - tuple("after", SUCCEEDED, NOT_SKIPPED, false, false, false), - ]) - - assert_event_reports(events, [ - [], - [], - [""" - This test is skipped! - Reason: '"do not run this"' - """.dedent().trim_prefix("\n")], - [], - [], - [] - ]) - - -class TestCaseNameExtractor extends GdUnitValueExtractor: - var r := RegEx.create_from_string("^.*:\\d") - - func extract_value(value): - var m := r.search(value.test_name()) - return m.get_string(0) if m != null else value.test_name() diff --git a/addons/gdUnit4/test/core/parse/GdDefaultValueDecoderTest.gd b/addons/gdUnit4/test/core/parse/GdDefaultValueDecoderTest.gd deleted file mode 100644 index ae64c8d..0000000 --- a/addons/gdUnit4/test/core/parse/GdDefaultValueDecoderTest.gd +++ /dev/null @@ -1,255 +0,0 @@ -# GdUnit generated TestSuite -class_name GdDefaultValueDecoderTest -extends GdUnitTestSuite -@warning_ignore('unused_parameter') -@warning_ignore('return_value_discarded') - -# TestSuite generated from -const __source = 'res://addons/gdUnit4/src/core/parse/GdDefaultValueDecoder.gd' - - -var _tested_types = {} - - -func after(): - # we verify we have covered all variant types - for type_id in TYPE_MAX: - if type_id == TYPE_OBJECT: - continue - assert_that(_tested_types.get(type_id))\ - .override_failure_message("Missing Variant type '%s'" % GdObjects.type_as_string(type_id))\ - .is_not_null() - - -@warning_ignore("unused_parameter") -func test_decode_Primitives(variant_type :int, value, expected :String, test_parameters := [ - [TYPE_NIL, null, "null"], - [TYPE_BOOL, true, "true"], - [TYPE_BOOL, false, "false"], - [TYPE_INT, -100, "-100"], - [TYPE_INT, 0, "0"], - [TYPE_INT, 100, "100"], - [TYPE_FLOAT, -100.123, "-100.123000"], - [TYPE_FLOAT, 0.00, "0.000000"], - [TYPE_FLOAT, 100, "100.000000"], - [TYPE_FLOAT, 100.123, "100.123000"], - [TYPE_STRING, "hello", '"hello"'], - [TYPE_STRING, "", '""'], - [TYPE_STRING_NAME, StringName("hello"), 'StringName("hello")'], - [TYPE_STRING_NAME, StringName(""), 'StringName()'], - ]) -> void: - - assert_that(GdDefaultValueDecoder.decode_typed(variant_type, value)).is_equal(expected) - _tested_types[variant_type] = 1 - - -@warning_ignore("unused_parameter") -func test_decode_Vectors(variant_type :int, value, expected :String, test_parameters := [ - [TYPE_VECTOR2, Vector2(), "Vector2()"], - [TYPE_VECTOR2, Vector2(1,2), "Vector2(1, 2)"], - [TYPE_VECTOR2I, Vector2i(), "Vector2i()"], - [TYPE_VECTOR2I, Vector2i(1,2), "Vector2i(1, 2)"], - [TYPE_VECTOR3, Vector3(), "Vector3()"], - [TYPE_VECTOR3, Vector3(1,2,3), "Vector3(1, 2, 3)"], - [TYPE_VECTOR3I, Vector3i(), "Vector3i()"], - [TYPE_VECTOR3I, Vector3i(1,2,3), "Vector3i(1, 2, 3)"], - [TYPE_VECTOR4, Vector4(), "Vector4()"], - [TYPE_VECTOR4, Vector4(1,2,3,4), "Vector4(1, 2, 3, 4)"], - [TYPE_VECTOR4I, Vector4i(), "Vector4i()"], - [TYPE_VECTOR4I, Vector4i(1,2,3,4), "Vector4i(1, 2, 3, 4)"], - ]) -> void: - - assert_that(GdDefaultValueDecoder.decode_typed(variant_type, value)).is_equal(expected) - _tested_types[variant_type] = 1 - - -@warning_ignore("unused_parameter") -func test_decode_Rect2(variant_type :int, value, expected :String, test_parameters := [ - [TYPE_RECT2, Rect2(), "Rect2()"], - [TYPE_RECT2, Rect2(1,2, 10,20), "Rect2(Vector2(1, 2), Vector2(10, 20))"], - [TYPE_RECT2I, Rect2i(), "Rect2i()"], - [TYPE_RECT2I, Rect2i(1,2, 10,20), "Rect2i(Vector2i(1, 2), Vector2i(10, 20))"], - ]) -> void: - - assert_that(GdDefaultValueDecoder.decode_typed(variant_type, value)).is_equal(expected) - _tested_types[variant_type] = 1 - - -@warning_ignore("unused_parameter") -func test_decode_Transforms(variant_type :int, value, expected :String, test_parameters := [ - [TYPE_TRANSFORM2D, Transform2D(), - "Transform2D()"], - [TYPE_TRANSFORM2D, Transform2D(2.0, Vector2(1,2)), - "Transform2D(Vector2(-0.416147, 0.909297), Vector2(-0.909297, -0.416147), Vector2(1, 2))"], - [TYPE_TRANSFORM2D, Transform2D(2.0, Vector2(1,2), 2.0, Vector2(3,4)), - "Transform2D(Vector2(-0.416147, 0.909297), Vector2(1.513605, -1.307287), Vector2(3, 4))"], - [TYPE_TRANSFORM2D, Transform2D(Vector2(1,2), Vector2(3,4), Vector2.ONE), - "Transform2D(Vector2(1, 2), Vector2(3, 4), Vector2(1, 1))"], - [TYPE_TRANSFORM3D, Transform3D(), - "Transform3D()"], - [TYPE_TRANSFORM3D, Transform3D(Basis.FLIP_X, Vector3.ONE), - "Transform3D(Vector3(-1, 0, 0), Vector3(0, 1, 0), Vector3(0, 0, 1), Vector3(1, 1, 1))"], - [TYPE_TRANSFORM3D, Transform3D(Vector3(1,2,3), Vector3(4,5,6), Vector3(7,8,9), Vector3.ONE), - "Transform3D(Vector3(1, 2, 3), Vector3(4, 5, 6), Vector3(7, 8, 9), Vector3(1, 1, 1))"], - [TYPE_PROJECTION, Projection(), - "Projection(Vector4(1, 0, 0, 0), Vector4(0, 1, 0, 0), Vector4(0, 0, 1, 0), Vector4(0, 0, 0, 1))"], - [TYPE_PROJECTION, Projection(Vector4.ONE, Vector4.ONE*2, Vector4.ONE*3, Vector4.ZERO), - "Projection(Vector4(1, 1, 1, 1), Vector4(2, 2, 2, 2), Vector4(3, 3, 3, 3), Vector4(0, 0, 0, 0))"] - ]) -> void: - assert_that(GdDefaultValueDecoder.decode_typed(variant_type, value)).is_equal(expected) - _tested_types[variant_type] = 1 - - -@warning_ignore("unused_parameter") -func test_decode_Plane(variant_type :int, value, expected :String, test_parameters := [ - [TYPE_PLANE, Plane(), "Plane()"], - [TYPE_PLANE, Plane(1,2,3,4), "Plane(1, 2, 3, 4)"], - [TYPE_PLANE, Plane(Vector3.ONE, Vector3.ZERO), "Plane(1, 1, 1, 0)"], - ]) -> void: - assert_that(GdDefaultValueDecoder.decode_typed(variant_type, value)).is_equal(expected) - _tested_types[variant_type] = 1 - - -@warning_ignore("unused_parameter") -func test_decode_Quaternion(variant_type :int, value, expected :String, test_parameters := [ - [TYPE_QUATERNION, Quaternion(), "Quaternion()"], - [TYPE_QUATERNION, Quaternion(1,2,3,4), "Quaternion(1, 2, 3, 4)"], - ]) -> void: - assert_that(GdDefaultValueDecoder.decode_typed(variant_type, value)).is_equal(expected) - _tested_types[variant_type] = 1 - - -@warning_ignore("unused_parameter") -func test_decode_AABB(variant_type :int, value, expected :String, test_parameters := [ - [TYPE_AABB, AABB(), "AABB()"], - [TYPE_AABB, AABB(Vector3.ONE, Vector3(10,20,30)), "AABB(Vector3(1, 1, 1), Vector3(10, 20, 30))"], - ]) -> void: - assert_that(GdDefaultValueDecoder.decode_typed(variant_type, value)).is_equal(expected) - _tested_types[variant_type] = 1 - - -@warning_ignore("unused_parameter") -func test_decode_Basis(variant_type :int, value, expected :String, test_parameters := [ - [TYPE_BASIS, Basis(), "Basis()"], - [TYPE_BASIS, Basis(Vector3(0.1,0.2,0.3).normalized(), .1), - "Basis(Vector3(0.995361, 0.080758, -0.052293), Vector3(-0.079331, 0.996432, 0.028823), Vector3(0.054434, -0.024541, 0.998216))"], - [TYPE_BASIS, Basis(Vector3.ONE, Vector3.ONE*2, Vector3.ONE*3), - "Basis(Vector3(1, 1, 1), Vector3(2, 2, 2), Vector3(3, 3, 3))"], - ]) -> void: - assert_that(GdDefaultValueDecoder.decode_typed(variant_type, value)).is_equal(expected) - _tested_types[variant_type] = 1 - - -@warning_ignore("unused_parameter") -func test_decode_Color(variant_type :int, value, expected :String, test_parameters := [ - [TYPE_COLOR, Color(), "Color()"], - [TYPE_COLOR, Color.RED, "Color(1, 0, 0, 1)"], - [TYPE_COLOR, Color(1,.2,.5,.5), "Color(1, 0.2, 0.5, 0.5)"], - ]) -> void: - assert_that(GdDefaultValueDecoder.decode_typed(variant_type, value)).is_equal(expected) - _tested_types[variant_type] = 1 - - -@warning_ignore("unused_parameter") -func test_decode_NodePath(variant_type :int, value, expected :String, test_parameters := [ - [TYPE_NODE_PATH, NodePath(), 'NodePath()'], - [TYPE_NODE_PATH, NodePath("/foo/bar"), 'NodePath("/foo/bar")'], - ]) -> void: - assert_that(GdDefaultValueDecoder.decode_typed(variant_type, value)).is_equal(expected) - _tested_types[variant_type] = 1 - - -@warning_ignore("unused_parameter") -func test_decode_RID(variant_type :int, value, expected :String, test_parameters := [ - [TYPE_RID, RID(), 'RID()'], - ]) -> void: - assert_that(GdDefaultValueDecoder.decode_typed(variant_type, value)).is_equal(expected) - _tested_types[variant_type] = 1 - - -@warning_ignore("unused_parameter") -func _test_decode_Object(variant_type :int, value, expected :String, test_parameters := [ - [TYPE_OBJECT, Node.new(), 'Node.new()'], - ]) -> void: - assert_that(GdDefaultValueDecoder.decode_typed(variant_type, value)).is_equal(expected) - _tested_types[variant_type] = 1 - - -@warning_ignore("unused_parameter") -func test_decode_Callable(variant_type :int, value, expected :String, test_parameters := [ - [TYPE_CALLABLE, Callable(), 'Callable()'], - ]) -> void: - assert_that(GdDefaultValueDecoder.decode_typed(variant_type, value)).is_equal(expected) - _tested_types[variant_type] = 1 - - -@warning_ignore("unused_parameter") -func test_decode_Signal(variant_type :int, value, expected :String, test_parameters := [ - [TYPE_SIGNAL, Signal(), 'Signal()'], - ]) -> void: - assert_that(GdDefaultValueDecoder.decode_typed(variant_type, value)).is_equal(expected) - _tested_types[variant_type] = 1 - - -@warning_ignore("unused_parameter") -func test_decode_Dictionary(variant_type :int, value, expected :String, test_parameters := [ - [TYPE_DICTIONARY, {}, '{}'], - [TYPE_DICTIONARY, Dictionary(), '{}'], - [TYPE_DICTIONARY, {1:2, 2:3}, '{ 1: 2, 2: 3 }'], - [TYPE_DICTIONARY, {"aa":2, "bb":"cc"}, '{ "aa": 2, "bb": "cc" }'], - ]) -> void: - assert_that(GdDefaultValueDecoder.decode_typed(variant_type, value)).is_equal(expected) - _tested_types[variant_type] = 1 - - -@warning_ignore("unused_parameter") -func test_decode_Array(variant_type :int, value, expected :String, test_parameters := [ - [TYPE_ARRAY, [], '[]'], - [TYPE_ARRAY, Array(), '[]'], - [TYPE_ARRAY, [1,2,3], '[1, 2, 3]'], - ]) -> void: - assert_that(GdDefaultValueDecoder.decode_typed(variant_type, value)).is_equal(expected) - _tested_types[variant_type] = 1 - - -@warning_ignore("unused_parameter") -func test_decode_typedArrays(variant_type :int, value, expected :String, test_parameters := [ - [TYPE_PACKED_BYTE_ARRAY, PackedByteArray(), - 'PackedByteArray()'], - [TYPE_PACKED_BYTE_ARRAY, PackedByteArray([1, 2, 3]), - 'PackedByteArray([1, 2, 3])'], - [TYPE_PACKED_COLOR_ARRAY, PackedColorArray(), - 'PackedColorArray()'], - [TYPE_PACKED_COLOR_ARRAY, PackedColorArray([Color.RED, Color.BLUE]), - 'PackedColorArray([Color(1, 0, 0, 1), Color(0, 0, 1, 1)])'], - [TYPE_PACKED_FLOAT32_ARRAY, PackedFloat32Array(), - 'PackedFloat32Array()'], - [TYPE_PACKED_FLOAT32_ARRAY, PackedFloat32Array([1.2, 2.3]), - 'PackedFloat32Array([1.20000004768372, 2.29999995231628])'], - [TYPE_PACKED_FLOAT64_ARRAY, PackedFloat64Array(), - 'PackedFloat64Array()'], - [TYPE_PACKED_FLOAT64_ARRAY, PackedFloat64Array([1.2, 2.3]), - 'PackedFloat64Array([1.2, 2.3])'], - [TYPE_PACKED_INT32_ARRAY, PackedInt32Array(), - 'PackedInt32Array()'], - [TYPE_PACKED_INT32_ARRAY, PackedInt32Array([1, 2]), - 'PackedInt32Array([1, 2])'], - [TYPE_PACKED_INT64_ARRAY, PackedInt64Array(), - 'PackedInt64Array()'], - [TYPE_PACKED_INT64_ARRAY, PackedInt64Array([1, 2]), - 'PackedInt64Array([1, 2])'], - [TYPE_PACKED_STRING_ARRAY, PackedStringArray(), - 'PackedStringArray()'], - [TYPE_PACKED_STRING_ARRAY, PackedStringArray(["aa", "bb"]), - 'PackedStringArray(["aa", "bb"])'], - [TYPE_PACKED_VECTOR2_ARRAY, PackedVector2Array(), - 'PackedVector2Array()'], - [TYPE_PACKED_VECTOR2_ARRAY, PackedVector2Array([Vector2.ONE, Vector2.ONE*2]), - 'PackedVector2Array([Vector2(1, 1), Vector2(2, 2)])'], - [TYPE_PACKED_VECTOR3_ARRAY, PackedVector3Array(), - 'PackedVector3Array()'], - [TYPE_PACKED_VECTOR3_ARRAY, PackedVector3Array([Vector3.ONE, Vector3.ONE*2]), - 'PackedVector3Array([Vector3(1, 1, 1), Vector3(2, 2, 2)])'], - ]) -> void: - assert_that(GdDefaultValueDecoder.decode_typed(variant_type, value)).is_equal(expected) - _tested_types[variant_type] = 1 diff --git a/addons/gdUnit4/test/core/parse/GdFunctionArgumentTest.gd b/addons/gdUnit4/test/core/parse/GdFunctionArgumentTest.gd deleted file mode 100644 index 08f5b6e..0000000 --- a/addons/gdUnit4/test/core/parse/GdFunctionArgumentTest.gd +++ /dev/null @@ -1,76 +0,0 @@ -# GdUnit generated TestSuite -class_name GdFunctionArgumentTest -extends GdUnitTestSuite -@warning_ignore('unused_parameter') -@warning_ignore('return_value_discarded') - -# TestSuite generated from -const __source = 'res://addons/gdUnit4/src/core/parse/GdFunctionArgument.gd' - - -func test__parse_argument_as_array_typ1() -> void: - var test_parameters := """[ - [1, "flowchart TD\nid>This is a flag shaped node]"], - [ - 2, - "flowchart TD\nid(((This is a\tdouble circle node)))" - ], - [3, - "flowchart TD\nid((This is a circular node))"], - [ - 4, "flowchart TD\nid>This is a flag shaped node]" - ], - [5, "flowchart TD\nid{'This is a rhombus node'}"], - [6, 'flowchart TD\nid((This is a circular node))'], - [7, 'flowchart TD\nid>This is a flag shaped node]'], [8, 'flowchart TD\nid{"This is a rhombus node"}'], - [9, \"\"\" - flowchart TD - id{"This is a rhombus node"} - \"\"\"] - ]""" - - var fa := GdFunctionArgument.new(GdFunctionArgument.ARG_PARAMETERIZED_TEST, TYPE_STRING, test_parameters) - assert_array(fa.parameter_sets()).contains_exactly([ - """[1, "flowchart TDid>This is a flag shaped node]"]""", - """[2, "flowchart TDid(((This is a\tdouble circle node)))"]""", - """[3, "flowchart TDid((This is a circular node))"]""", - """[4, "flowchart TDid>This is a flag shaped node]"]""", - """[5, "flowchart TDid{'This is a rhombus node'}"]""", - """[6, 'flowchart TDid((This is a circular node))']""", - """[7, 'flowchart TDid>This is a flag shaped node]']""", - """[8, 'flowchart TDid{"This is a rhombus node"}']""", - """[9, \"\"\"flowchart TDid{"This is a rhombus node"}\"\"\"]""" - ] - ) - - -func test__parse_argument_as_array_typ2() -> void: - var test_parameters := """[ - ["test_a", null, "LOG", {}], - [ - "test_b", - Node2D, - null, - {Node2D: "ER,ROR"} - ], - [ - "test_c", - Node2D, - "LOG", - {Node2D: "LOG"} - ] - ]""" - var fa := GdFunctionArgument.new(GdFunctionArgument.ARG_PARAMETERIZED_TEST, TYPE_STRING, test_parameters) - assert_array(fa.parameter_sets()).contains_exactly([ - """["test_a", null, "LOG", {}]""", - """["test_b", Node2D, null, {Node2D: "ER,ROR"}]""", - """["test_c", Node2D, "LOG", {Node2D: "LOG"}]""" - ] - ) - - -func test__parse_argument_as_reference() -> void: - var test_parameters := "_test_args()" - - var fa := GdFunctionArgument.new(GdFunctionArgument.ARG_PARAMETERIZED_TEST, TYPE_STRING, test_parameters) - assert_array(fa.parameter_sets()).is_empty() diff --git a/addons/gdUnit4/test/core/parse/GdFunctionDescriptorTest.gd b/addons/gdUnit4/test/core/parse/GdFunctionDescriptorTest.gd deleted file mode 100644 index 42a28f7..0000000 --- a/addons/gdUnit4/test/core/parse/GdFunctionDescriptorTest.gd +++ /dev/null @@ -1,146 +0,0 @@ -# GdUnit generated TestSuite -class_name GdFunctionDescriptorTest -extends GdUnitTestSuite - -# TestSuite generated from -const __source = 'res://addons/gdUnit4/src/core/parse/GdFunctionDescriptor.gd' - - -# helper to get method descriptor -func get_method_description(clazz_name :String, method_name :String) -> Dictionary: - var method_list :Array = ClassDB.class_get_method_list(clazz_name) - for method_descriptor in method_list: - if method_descriptor["name"] == method_name: - return method_descriptor - return Dictionary() - - -func test_extract_from_func_without_return_type(): - # void add_sibling(sibling: Node, force_readable_name: bool = false) - var method_descriptor := get_method_description("Node", "add_sibling") - var fd := GdFunctionDescriptor.extract_from(method_descriptor) - assert_str(fd.name()).is_equal("add_sibling") - assert_bool(fd.is_virtual()).is_false() - assert_bool(fd.is_static()).is_false() - assert_bool(fd.is_engine()).is_true() - assert_bool(fd.is_vararg()).is_false() - assert_int(fd.return_type()).is_equal(TYPE_NIL) - assert_array(fd.args()).contains_exactly([ - GdFunctionArgument.new("sibling_", GdObjects.TYPE_NODE), - GdFunctionArgument.new("force_readable_name_", TYPE_BOOL, "false") - ]) - # void add_sibling(node: Node, child_node: Node, legible_unique_name: bool = false) - assert_str(fd.typeless()).is_equal("func add_sibling(sibling_, force_readable_name_=false) -> void:") - - -func test_extract_from_func_with_return_type(): - # Node find_child(pattern: String, recursive: bool = true, owned: bool = true) const - var method_descriptor := get_method_description("Node", "find_child") - var fd := GdFunctionDescriptor.extract_from(method_descriptor) - assert_str(fd.name()).is_equal("find_child") - assert_bool(fd.is_virtual()).is_false() - assert_bool(fd.is_static()).is_false() - assert_bool(fd.is_engine()).is_true() - assert_bool(fd.is_vararg()).is_false() - assert_int(fd.return_type()).is_equal(TYPE_OBJECT) - assert_array(fd.args()).contains_exactly([ - GdFunctionArgument.new("pattern_", TYPE_STRING), - GdFunctionArgument.new("recursive_", TYPE_BOOL, "true"), - GdFunctionArgument.new("owned_", TYPE_BOOL, "true"), - ]) - # Node find_child(mask: String, recursive: bool = true, owned: bool = true) const - assert_str(fd.typeless()).is_equal("func find_child(pattern_, recursive_=true, owned_=true) -> Node:") - - -func test_extract_from_func_with_vararg(): - # Error emit_signal(signal: StringName, ...) vararg - var method_descriptor := get_method_description("Node", "emit_signal") - var fd := GdFunctionDescriptor.extract_from(method_descriptor) - assert_str(fd.name()).is_equal("emit_signal") - assert_bool(fd.is_virtual()).is_false() - assert_bool(fd.is_static()).is_false() - assert_bool(fd.is_engine()).is_true() - assert_bool(fd.is_vararg()).is_true() - assert_int(fd.return_type()).is_equal(GdObjects.TYPE_ENUM) - assert_array(fd.args()).contains_exactly([GdFunctionArgument.new("signal_", TYPE_STRING_NAME)]) - assert_array(fd.varargs()).contains_exactly([ - GdFunctionArgument.new("vararg0_", GdObjects.TYPE_VARARG, "\"%s\"" % GdObjects.TYPE_VARARG_PLACEHOLDER_VALUE), - GdFunctionArgument.new("vararg1_", GdObjects.TYPE_VARARG, "\"%s\"" % GdObjects.TYPE_VARARG_PLACEHOLDER_VALUE), - GdFunctionArgument.new("vararg2_", GdObjects.TYPE_VARARG, "\"%s\"" % GdObjects.TYPE_VARARG_PLACEHOLDER_VALUE), - GdFunctionArgument.new("vararg3_", GdObjects.TYPE_VARARG, "\"%s\"" % GdObjects.TYPE_VARARG_PLACEHOLDER_VALUE), - GdFunctionArgument.new("vararg4_", GdObjects.TYPE_VARARG, "\"%s\"" % GdObjects.TYPE_VARARG_PLACEHOLDER_VALUE), - GdFunctionArgument.new("vararg5_", GdObjects.TYPE_VARARG, "\"%s\"" % GdObjects.TYPE_VARARG_PLACEHOLDER_VALUE), - GdFunctionArgument.new("vararg6_", GdObjects.TYPE_VARARG, "\"%s\"" % GdObjects.TYPE_VARARG_PLACEHOLDER_VALUE), - GdFunctionArgument.new("vararg7_", GdObjects.TYPE_VARARG, "\"%s\"" % GdObjects.TYPE_VARARG_PLACEHOLDER_VALUE), - GdFunctionArgument.new("vararg8_", GdObjects.TYPE_VARARG, "\"%s\"" % GdObjects.TYPE_VARARG_PLACEHOLDER_VALUE), - GdFunctionArgument.new("vararg9_", GdObjects.TYPE_VARARG, "\"%s\"" % GdObjects.TYPE_VARARG_PLACEHOLDER_VALUE) - ]) - assert_str(fd.typeless()).is_equal("func emit_signal(signal_, vararg0_=\"__null__\", vararg1_=\"__null__\", vararg2_=\"__null__\", vararg3_=\"__null__\", vararg4_=\"__null__\", vararg5_=\"__null__\", vararg6_=\"__null__\", vararg7_=\"__null__\", vararg8_=\"__null__\", vararg9_=\"__null__\") -> Error:") - - -func test_extract_from_descriptor_is_virtual_func(): - var method_descriptor := get_method_description("Node", "_enter_tree") - var fd := GdFunctionDescriptor.extract_from(method_descriptor) - assert_str(fd.name()).is_equal("_enter_tree") - assert_bool(fd.is_virtual()).is_true() - assert_bool(fd.is_static()).is_false() - assert_bool(fd.is_engine()).is_true() - assert_bool(fd.is_vararg()).is_false() - assert_int(fd.return_type()).is_equal(TYPE_NIL) - assert_array(fd.args()).is_empty() - # void _enter_tree() virtual - assert_str(fd.typeless()).is_equal("func _enter_tree() -> void:") - - -func test_extract_from_descriptor_is_virtual_func_full_check(): - var methods := ClassDB.class_get_method_list("Node") - var expected_virtual_functions := [ - # Object virtuals - "_get", - "_get_property_list", - "_init", - "_notification", - "_property_can_revert", - "_property_get_revert", - "_set", - "_to_string", - # Note virtuals - "_enter_tree", - "_exit_tree", - "_get_configuration_warnings", - "_input", - "_physics_process", - "_process", - "_ready", - "_shortcut_input", - "_unhandled_input", - "_unhandled_key_input" - ] - # since Godot 4.2 there are more virtual functions - if Engine.get_version_info().hex >= 0x40200: - expected_virtual_functions.append("_validate_property") - - var _count := 0 - for method_descriptor in methods: - var fd := GdFunctionDescriptor.extract_from(method_descriptor) - - if fd.is_virtual(): - _count += 1 - assert_array(expected_virtual_functions).contains([fd.name()]) - assert_int(_count).is_equal(expected_virtual_functions.size()) - - -func test_extract_from_func_with_return_type_variant(): - var method_descriptor := get_method_description("Node", "get") - var fd := GdFunctionDescriptor.extract_from(method_descriptor) - assert_str(fd.name()).is_equal("get") - assert_bool(fd.is_virtual()).is_false() - assert_bool(fd.is_static()).is_false() - assert_bool(fd.is_engine()).is_true() - assert_bool(fd.is_vararg()).is_false() - assert_int(fd.return_type()).is_equal(GdObjects.TYPE_VARIANT) - assert_array(fd.args()).contains_exactly([ - GdFunctionArgument.new("property_", TYPE_STRING_NAME), - ]) - # Variant get(property: String) const - assert_str(fd.typeless()).is_equal("func get(property_) -> Variant:") diff --git a/addons/gdUnit4/test/core/parse/GdScriptParserTest.gd b/addons/gdUnit4/test/core/parse/GdScriptParserTest.gd deleted file mode 100644 index cd2bd96..0000000 --- a/addons/gdUnit4/test/core/parse/GdScriptParserTest.gd +++ /dev/null @@ -1,632 +0,0 @@ -extends GdUnitTestSuite - -var _parser: GdScriptParser - - -func before(): - _parser = GdScriptParser.new() - - -func test_parse_argument(): - # create example row whit different assignment types - var row = "func test_foo(arg1 = 41, arg2 := 42, arg3 : int = 43)" - assert_that(_parser.parse_argument(row, "arg1", 23)).is_equal(41) - assert_that(_parser.parse_argument(row, "arg2", 23)).is_equal(42) - assert_that(_parser.parse_argument(row, "arg3", 23)).is_equal(43) - - -func test_parse_argument_default_value(): - # arg4 not exists expect to return the default value - var row = "func test_foo(arg1 = 41, arg2 := 42, arg3 : int = 43)" - assert_that(_parser.parse_argument(row, "arg4", 23)).is_equal(23) - - -func test_parse_argument_has_no_arguments(): - assert_that(_parser.parse_argument("func test_foo()", "arg4", 23)).is_equal(23) - - -func test_parse_argument_with_bad_formatting(): - var row = "func test_foo( arg1 = 41, arg2 : = 42, arg3 : int = 43 )" - assert_that(_parser.parse_argument(row, "arg3", 23)).is_equal(43) - - -func test_parse_argument_with_same_func_name(): - var row = "func test_arg1(arg1 = 41)" - assert_that(_parser.parse_argument(row, "arg1", 23)).is_equal(41) - - -func test_parse_argument_timeout(): - var DEFAULT_TIMEOUT = 1000 - assert_that(_parser.parse_argument("func test_foo()", "timeout", DEFAULT_TIMEOUT)).is_equal(DEFAULT_TIMEOUT) - assert_that(_parser.parse_argument("func test_foo(timeout = 2000)", "timeout", DEFAULT_TIMEOUT)).is_equal(2000) - assert_that(_parser.parse_argument("func test_foo(timeout: = 2000)", "timeout", DEFAULT_TIMEOUT)).is_equal(2000) - assert_that(_parser.parse_argument("func test_foo(timeout:int = 2000)", "timeout", DEFAULT_TIMEOUT)).is_equal(2000) - assert_that(_parser.parse_argument("func test_foo(arg1 = false, timeout=2000)", "timeout", DEFAULT_TIMEOUT)).is_equal(2000) - - -func test_parse_arguments(): - assert_array(_parser.parse_arguments("func foo():")) \ - .has_size(0) - - assert_array(_parser.parse_arguments("func foo() -> String:\n")) \ - .has_size(0) - - assert_array(_parser.parse_arguments("func foo(arg1, arg2, name):")) \ - .contains_exactly([ - GdFunctionArgument.new("arg1", TYPE_NIL), - GdFunctionArgument.new("arg2", TYPE_NIL), - GdFunctionArgument.new("name", TYPE_NIL)]) - - assert_array(_parser.parse_arguments('func foo(arg1 :int, arg2 :bool, name :String = "abc"):')) \ - .contains_exactly([ - GdFunctionArgument.new("arg1", TYPE_INT), - GdFunctionArgument.new("arg2", TYPE_BOOL), - GdFunctionArgument.new("name", TYPE_STRING, '"abc"')]) - - assert_array(_parser.parse_arguments('func bar(arg1 :int, arg2 :int = 23, name :String = "test") -> String:')) \ - .contains_exactly([ - GdFunctionArgument.new("arg1", TYPE_INT), - GdFunctionArgument.new("arg2", TYPE_INT, "23"), - GdFunctionArgument.new("name", TYPE_STRING, '"test"')]) - - assert_array(_parser.parse_arguments("func foo(arg1, arg2=value(1,2,3), name:=foo()):")) \ - .contains_exactly([ - GdFunctionArgument.new("arg1", TYPE_NIL), - GdFunctionArgument.new("arg2", GdObjects.TYPE_FUNC, "value(1,2,3)"), - GdFunctionArgument.new("name", GdObjects.TYPE_FUNC, "foo()")]) - # enum as prefix in value name - assert_array(_parser.parse_arguments("func get_value( type := ENUM_A) -> int:"))\ - .contains_exactly([GdFunctionArgument.new("type", TYPE_STRING, "ENUM_A")]) - - assert_array(_parser.parse_arguments("func create_timer(timeout :float) -> Timer:")) \ - .contains_exactly([ - GdFunctionArgument.new("timeout", TYPE_FLOAT)]) - - # array argument - assert_array(_parser.parse_arguments("func foo(a :int, b :int, parameters = [[1, 2], [3, 4], [5, 6]]):")) \ - .contains_exactly([ - GdFunctionArgument.new("a", TYPE_INT), - GdFunctionArgument.new("b", TYPE_INT), - GdFunctionArgument.new("parameters", TYPE_ARRAY, "[[1, 2], [3, 4], [5, 6]]")]) - - assert_array(_parser.parse_arguments("func test_values(a:Vector2, b:Vector2, expected:Vector2, test_parameters:=[[Vector2.ONE,Vector2.ONE,Vector2(1,1)]]):"))\ - .contains_exactly([ - GdFunctionArgument.new("a", TYPE_VECTOR2), - GdFunctionArgument.new("b", TYPE_VECTOR2), - GdFunctionArgument.new("expected", TYPE_VECTOR2), - GdFunctionArgument.new("test_parameters", TYPE_ARRAY, "[[Vector2.ONE,Vector2.ONE,Vector2(1,1)]]"), - ]) - - -func test_parse_arguments_with_super_constructor(): - assert_array(_parser.parse_arguments('func foo().foo("abc"):')).is_empty() - assert_array(_parser.parse_arguments('func foo(arg1 = "arg").foo("abc", arg1):'))\ - .contains_exactly([GdFunctionArgument.new("arg1", TYPE_STRING, '"arg"')]) - - -func test_parse_arguments_default_build_in_type_String(): - assert_array(_parser.parse_arguments('func foo(arg1 :String, arg2="default"):')) \ - .contains_exactly([ - GdFunctionArgument.new("arg1", TYPE_STRING), - GdFunctionArgument.new("arg2", TYPE_STRING, '"default"')]) - - assert_array(_parser.parse_arguments('func foo(arg1 :String, arg2 :="default"):')) \ - .contains_exactly([ - GdFunctionArgument.new("arg1", TYPE_STRING), - GdFunctionArgument.new("arg2", TYPE_STRING, '"default"')]) - - assert_array(_parser.parse_arguments('func foo(arg1 :String, arg2 :String ="default"):')) \ - .contains_exactly([ - GdFunctionArgument.new("arg1", TYPE_STRING), - GdFunctionArgument.new("arg2", TYPE_STRING, '"default"')]) - - -func test_parse_arguments_default_build_in_type_Boolean(): - assert_array(_parser.parse_arguments("func foo(arg1 :String, arg2=false):")) \ - .contains_exactly([ - GdFunctionArgument.new("arg1", TYPE_STRING), - GdFunctionArgument.new("arg2", TYPE_BOOL, "false")]) - - assert_array(_parser.parse_arguments("func foo(arg1 :String, arg2 :=false):")) \ - .contains_exactly([ - GdFunctionArgument.new("arg1", TYPE_STRING), - GdFunctionArgument.new("arg2", TYPE_BOOL, "false")]) - - assert_array(_parser.parse_arguments("func foo(arg1 :String, arg2 :bool=false):")) \ - .contains_exactly([ - GdFunctionArgument.new("arg1", TYPE_STRING), - GdFunctionArgument.new("arg2", TYPE_BOOL, "false")]) - - -func test_parse_arguments_default_build_in_type_Real(): - assert_array(_parser.parse_arguments("func foo(arg1 :String, arg2=3.14):")) \ - .contains_exactly([ - GdFunctionArgument.new("arg1", TYPE_STRING), - GdFunctionArgument.new("arg2", TYPE_FLOAT, "3.14")]) - - assert_array(_parser.parse_arguments("func foo(arg1 :String, arg2 :=3.14):")) \ - .contains_exactly([ - GdFunctionArgument.new("arg1", TYPE_STRING), - GdFunctionArgument.new("arg2", TYPE_FLOAT, "3.14")]) - - assert_array(_parser.parse_arguments("func foo(arg1 :String, arg2 :float=3.14):")) \ - .contains_exactly([ - GdFunctionArgument.new("arg1", TYPE_STRING), - GdFunctionArgument.new("arg2", TYPE_FLOAT, "3.14")]) - - -func test_parse_arguments_default_build_in_type_Array(): - assert_array(_parser.parse_arguments("func foo(arg1 :String, arg2 :Array=[]):")) \ - .contains_exactly([ - GdFunctionArgument.new("arg1", TYPE_STRING), - GdFunctionArgument.new("arg2", TYPE_ARRAY, "[]")]) - - assert_array(_parser.parse_arguments("func foo(arg1 :String, arg2 :Array=Array()):")) \ - .contains_exactly([ - GdFunctionArgument.new("arg1", TYPE_STRING), - GdFunctionArgument.new("arg2", TYPE_ARRAY, "Array()")]) - - assert_array(_parser.parse_arguments("func foo(arg1 :String, arg2 :Array=[1, 2, 3]):")) \ - .contains_exactly([ - GdFunctionArgument.new("arg1", TYPE_STRING), - GdFunctionArgument.new("arg2", TYPE_ARRAY, "[1, 2, 3]")]) - - assert_array(_parser.parse_arguments("func foo(arg1 :String, arg2 :=[1, 2, 3]):")) \ - .contains_exactly([ - GdFunctionArgument.new("arg1", TYPE_STRING), - GdFunctionArgument.new("arg2", TYPE_ARRAY, "[1, 2, 3]")]) - - assert_array(_parser.parse_arguments("func foo(arg1 :String, arg2=[]):")) \ - .contains_exactly([ - GdFunctionArgument.new("arg1", TYPE_STRING), - GdFunctionArgument.new("arg2", TYPE_ARRAY, "[]")]) - - assert_array(_parser.parse_arguments("func foo(arg1 :String, arg2 :Array=[1, 2, 3], arg3 := false):")) \ - .contains_exactly([ - GdFunctionArgument.new("arg1", TYPE_STRING), - GdFunctionArgument.new("arg2", TYPE_ARRAY, "[1, 2, 3]"), - GdFunctionArgument.new("arg3", TYPE_BOOL, "false")]) - - -func test_parse_arguments_default_build_in_type_Color(): - assert_array(_parser.parse_arguments("func foo(arg1 :String, arg2=Color.RED):")) \ - .contains_exactly([ - GdFunctionArgument.new("arg1", TYPE_STRING), - GdFunctionArgument.new("arg2", TYPE_COLOR, "Color.RED")]) - - assert_array(_parser.parse_arguments("func foo(arg1 :String, arg2 :=Color.RED):")) \ - .contains_exactly([ - GdFunctionArgument.new("arg1", TYPE_STRING), - GdFunctionArgument.new("arg2", TYPE_COLOR, "Color.RED")]) - - assert_array(_parser.parse_arguments("func foo(arg1 :String, arg2 :Color=Color.RED):")) \ - .contains_exactly([ - GdFunctionArgument.new("arg1", TYPE_STRING), - GdFunctionArgument.new("arg2", TYPE_COLOR, "Color.RED")]) - - -func test_parse_arguments_default_build_in_type_Vector(): - assert_array(_parser.parse_arguments("func bar(arg1 :String, arg2 =Vector3.FORWARD):")) \ - .contains_exactly([ - GdFunctionArgument.new("arg1", TYPE_STRING), - GdFunctionArgument.new("arg2", TYPE_VECTOR3, "Vector3.FORWARD")]) - - assert_array(_parser.parse_arguments("func bar(arg1 :String, arg2 :=Vector3.FORWARD):")) \ - .contains_exactly([ - GdFunctionArgument.new("arg1", TYPE_STRING), - GdFunctionArgument.new("arg2", TYPE_VECTOR3, "Vector3.FORWARD")]) - - assert_array(_parser.parse_arguments("func bar(arg1 :String, arg2 :Vector3=Vector3.FORWARD):")) \ - .contains_exactly([ - GdFunctionArgument.new("arg1", TYPE_STRING), - GdFunctionArgument.new("arg2", TYPE_VECTOR3, "Vector3.FORWARD")]) - - -func test_parse_arguments_default_build_in_type_AABB(): - assert_array(_parser.parse_arguments("func bar(arg1 :String, arg2 := AABB()):")) \ - .contains_exactly([ - GdFunctionArgument.new("arg1", TYPE_STRING), - GdFunctionArgument.new("arg2", TYPE_AABB, "AABB()")]) - - assert_array(_parser.parse_arguments("func bar(arg1 :String, arg2 :AABB=AABB()):")) \ - .contains_exactly([ - GdFunctionArgument.new("arg1", TYPE_STRING), - GdFunctionArgument.new("arg2", TYPE_AABB, "AABB()")]) - - -func test_parse_arguments_default_build_in_types(): - assert_array(_parser.parse_arguments("func bar(arg1 :String, arg2 := Vector3.FORWARD, aabb := AABB()):")) \ - .contains_exactly([ - GdFunctionArgument.new("arg1", TYPE_STRING), - GdFunctionArgument.new("arg2", TYPE_VECTOR3, "Vector3.FORWARD"), - GdFunctionArgument.new("aabb", TYPE_AABB, "AABB()")]) - - -func test_parse_arguments_fuzzers() -> void: - assert_array(_parser.parse_arguments("func test_foo(fuzzer_a = fuzz_a(), fuzzer_b := fuzz_b(), fuzzer_c :Fuzzer = fuzz_c(), fuzzer_iterations = 234, fuzzer_seed = 100):")) \ - .contains_exactly([ - GdFunctionArgument.new("fuzzer_a", GdObjects.TYPE_FUZZER, "fuzz_a()"), - GdFunctionArgument.new("fuzzer_b", GdObjects.TYPE_FUZZER, "fuzz_b()"), - GdFunctionArgument.new("fuzzer_c", GdObjects.TYPE_FUZZER, "fuzz_c()"), - GdFunctionArgument.new("fuzzer_iterations", TYPE_INT, "234"), - GdFunctionArgument.new("fuzzer_seed", TYPE_INT, "100"),]) - - -func test_parse_arguments_no_function(): - assert_array(_parser.parse_arguments("var x:=10")) \ - .has_size(0) - - -class TestObject: - var x - - -func test_parse_function_return_type(): - assert_that(_parser.parse_func_return_type("func foo():")).is_equal(TYPE_NIL) - assert_that(_parser.parse_func_return_type("func foo() -> void:")).is_equal(GdObjects.TYPE_VOID) - assert_that(_parser.parse_func_return_type("func foo() -> TestObject:")).is_equal(TYPE_OBJECT) - assert_that(_parser.parse_func_return_type("func foo() -> bool:")).is_equal(TYPE_BOOL) - assert_that(_parser.parse_func_return_type("func foo() -> String:")).is_equal(TYPE_STRING) - assert_that(_parser.parse_func_return_type("func foo() -> int:")).is_equal(TYPE_INT) - assert_that(_parser.parse_func_return_type("func foo() -> float:")).is_equal(TYPE_FLOAT) - assert_that(_parser.parse_func_return_type("func foo() -> Vector2:")).is_equal(TYPE_VECTOR2) - assert_that(_parser.parse_func_return_type("func foo() -> Rect2:")).is_equal(TYPE_RECT2) - assert_that(_parser.parse_func_return_type("func foo() -> Vector3:")).is_equal(TYPE_VECTOR3) - assert_that(_parser.parse_func_return_type("func foo() -> Transform2D:")).is_equal(TYPE_TRANSFORM2D) - assert_that(_parser.parse_func_return_type("func foo() -> Plane:")).is_equal(TYPE_PLANE) - assert_that(_parser.parse_func_return_type("func foo() -> Quaternion:")).is_equal(TYPE_QUATERNION) - assert_that(_parser.parse_func_return_type("func foo() -> AABB:")).is_equal(TYPE_AABB) - assert_that(_parser.parse_func_return_type("func foo() -> Basis:")).is_equal(TYPE_BASIS) - assert_that(_parser.parse_func_return_type("func foo() -> Transform3D:")).is_equal(TYPE_TRANSFORM3D) - assert_that(_parser.parse_func_return_type("func foo() -> Color:")).is_equal(TYPE_COLOR) - assert_that(_parser.parse_func_return_type("func foo() -> NodePath:")).is_equal(TYPE_NODE_PATH) - assert_that(_parser.parse_func_return_type("func foo() -> RID:")).is_equal(TYPE_RID) - assert_that(_parser.parse_func_return_type("func foo() -> Dictionary:")).is_equal(TYPE_DICTIONARY) - assert_that(_parser.parse_func_return_type("func foo() -> Array:")).is_equal(TYPE_ARRAY) - assert_that(_parser.parse_func_return_type("func foo() -> PackedByteArray:")).is_equal(TYPE_PACKED_BYTE_ARRAY) - assert_that(_parser.parse_func_return_type("func foo() -> PackedInt32Array:")).is_equal(TYPE_PACKED_INT32_ARRAY) - assert_that(_parser.parse_func_return_type("func foo() -> PackedFloat32Array:")).is_equal(TYPE_PACKED_FLOAT32_ARRAY) - assert_that(_parser.parse_func_return_type("func foo() -> PackedStringArray:")).is_equal(TYPE_PACKED_STRING_ARRAY) - assert_that(_parser.parse_func_return_type("func foo() -> PackedVector2Array:")).is_equal(TYPE_PACKED_VECTOR2_ARRAY) - assert_that(_parser.parse_func_return_type("func foo() -> PackedVector3Array:")).is_equal(TYPE_PACKED_VECTOR3_ARRAY) - assert_that(_parser.parse_func_return_type("func foo() -> PackedColorArray:")).is_equal(TYPE_PACKED_COLOR_ARRAY) - - -func test_parse_func_name(): - assert_str(_parser.parse_func_name("func foo():")).is_equal("foo") - assert_str(_parser.parse_func_name("static func foo():")).is_equal("foo") - assert_str(_parser.parse_func_name("func a() -> String:")).is_equal("a") - # function name contains tokens e.g func or class - assert_str(_parser.parse_func_name("func foo_func_class():")).is_equal("foo_func_class") - # should fail - assert_str(_parser.parse_func_name("#func foo():")).is_empty() - assert_str(_parser.parse_func_name("var x")).is_empty() - - -func test_extract_source_code(): - var path := GdObjects.extract_class_path(AdvancedTestClass) - var rows = _parser.extract_source_code(path) - - var file_content := resource_as_array(path[0]) - assert_array(rows).contains_exactly(file_content) - - -func test_extract_source_code_inner_class_AtmosphereData(): - var path := GdObjects.extract_class_path(AdvancedTestClass.AtmosphereData) - var rows = _parser.extract_source_code(path) - var file_content := resource_as_array("res://addons/gdUnit4/test/core/resources/AtmosphereData.txt") - assert_array(rows).contains_exactly(file_content) - - -func test_extract_source_code_inner_class_SoundData(): - var path := GdObjects.extract_class_path(AdvancedTestClass.SoundData) - var rows = _parser.extract_source_code(path) - var file_content := resource_as_array("res://addons/gdUnit4/test/core/resources/SoundData.txt") - assert_array(rows).contains_exactly(file_content) - - -func test_extract_source_code_inner_class_Area4D(): - var path := GdObjects.extract_class_path(AdvancedTestClass.Area4D) - var rows = _parser.extract_source_code(path) - var file_content := resource_as_array("res://addons/gdUnit4/test/core/resources/Area4D.txt") - assert_array(rows).contains_exactly(file_content) - - -func test_extract_function_signature() -> void: - var path := GdObjects.extract_class_path("res://addons/gdUnit4/test/mocker/resources/ClassWithCustomFormattings.gd") - var rows = _parser.extract_source_code(path) - - assert_that(_parser.extract_func_signature(rows, 12))\ - .is_equal(""" - func a1(set_name:String, path:String="", load_on_init:bool=false, - set_auto_save:bool=false, set_network_sync:bool=false - ) -> void:""".dedent().trim_prefix("\n")) - assert_that(_parser.extract_func_signature(rows, 19))\ - .is_equal(""" - func a2(set_name:String, path:String="", load_on_init:bool=false, - set_auto_save:bool=false, set_network_sync:bool=false - ) -> void:""".dedent().trim_prefix("\n")) - assert_that(_parser.extract_func_signature(rows, 26))\ - .is_equal(""" - func a3(set_name:String, path:String="", load_on_init:bool=false, - set_auto_save:bool=false, set_network_sync:bool=false - ) :""".dedent().trim_prefix("\n")) - assert_that(_parser.extract_func_signature(rows, 33))\ - .is_equal(""" - func a4(set_name:String, - path:String="", - load_on_init:bool=false, - set_auto_save:bool=false, - set_network_sync:bool=false - ):""".dedent().trim_prefix("\n")) - assert_that(_parser.extract_func_signature(rows, 43))\ - .is_equal(""" - func a5( - value : Array, - expected : String, - test_parameters : Array = [ - [ ["a"], "a" ], - [ ["a", "very", "long", "argument"], "a very long argument" ], - ] - ):""".dedent().trim_prefix("\n")) - - -func test_strip_leading_spaces(): - assert_str(GdScriptParser.TokenInnerClass._strip_leading_spaces("")).is_empty() - assert_str(GdScriptParser.TokenInnerClass._strip_leading_spaces(" ")).is_empty() - assert_str(GdScriptParser.TokenInnerClass._strip_leading_spaces(" ")).is_empty() - assert_str(GdScriptParser.TokenInnerClass._strip_leading_spaces(" ")).is_equal(" ") - assert_str(GdScriptParser.TokenInnerClass._strip_leading_spaces("var x=")).is_equal("var x=") - assert_str(GdScriptParser.TokenInnerClass._strip_leading_spaces("class foo")).is_equal("class foo") - - -func test_extract_clazz_name(): - assert_str(_parser.extract_clazz_name("classSoundData:\n")).is_equal("SoundData") - assert_str(_parser.extract_clazz_name("classSoundDataextendsNode:\n")).is_equal("SoundData") - - -func test_is_virtual_func() -> void: - # checked non virtual func - assert_bool(_parser.is_virtual_func("UnknownClass", [""], "")).is_false() - assert_bool(_parser.is_virtual_func("Node", [""], "")).is_false() - assert_bool(_parser.is_virtual_func("Node", [""], "func foo():")).is_false() - # checked virtual func - assert_bool(_parser.is_virtual_func("Node", [""], "_exit_tree")).is_true() - assert_bool(_parser.is_virtual_func("Node", [""], "_ready")).is_true() - assert_bool(_parser.is_virtual_func("Node", [""], "_init")).is_true() - - -func test_is_static_func(): - assert_bool(_parser.is_static_func("")).is_false() - assert_bool(_parser.is_static_func("var a=0")).is_false() - assert_bool(_parser.is_static_func("func foo():")).is_false() - assert_bool(_parser.is_static_func("func foo() -> void:")).is_false() - assert_bool(_parser.is_static_func("static func foo():")).is_true() - assert_bool(_parser.is_static_func("static func foo() -> void:")).is_true() - - -func test_parse_func_description(): - var fd := _parser.parse_func_description("func foo():", "clazz_name", [""], 10) - assert_str(fd.name()).is_equal("foo") - assert_bool(fd.is_static()).is_false() - assert_int(fd.return_type()).is_equal(GdObjects.TYPE_VARIANT) - assert_array(fd.args()).is_empty() - assert_str(fd.typeless()).is_equal("func foo() -> Variant:") - - # static function - fd = _parser.parse_func_description("static func foo(arg1 :int, arg2:=false) -> String:", "clazz_name", [""], 22) - assert_str(fd.name()).is_equal("foo") - assert_bool(fd.is_static()).is_true() - assert_int(fd.return_type()).is_equal(TYPE_STRING) - assert_array(fd.args()).contains_exactly([ - GdFunctionArgument.new("arg1", TYPE_INT), - GdFunctionArgument.new("arg2", TYPE_BOOL, "false") - ]) - assert_str(fd.typeless()).is_equal("static func foo(arg1, arg2=false) -> String:") - - # static function without return type - fd = _parser.parse_func_description("static func foo(arg1 :int, arg2:=false):", "clazz_name", [""], 23) - assert_str(fd.name()).is_equal("foo") - assert_bool(fd.is_static()).is_true() - assert_int(fd.return_type()).is_equal(GdObjects.TYPE_VARIANT) - assert_array(fd.args()).contains_exactly([ - GdFunctionArgument.new("arg1", TYPE_INT), - GdFunctionArgument.new("arg2", TYPE_BOOL, "false") - ]) - assert_str(fd.typeless()).is_equal("static func foo(arg1, arg2=false) -> Variant:") - - -func test_parse_func_description_return_type_enum(): - var result := _parser.parse("ClassWithEnumReturnTypes", ["res://addons/gdUnit4/test/mocker/resources/ClassWithEnumReturnTypes.gd"]) - assert_result(result).is_success() - - var fd := _parser.parse_func_description("func get_enum() -> TEST_ENUM:", "ClassWithEnumReturnTypes", ["res://addons/gdUnit4/test/mocker/resources/ClassWithEnumReturnTypes.gd"], 33) - assert_that(fd.name()).is_equal("get_enum") - assert_that(fd.is_static()).is_false() - assert_that(fd.return_type()).is_equal(GdObjects.TYPE_ENUM) - assert_that(fd._return_class).is_equal("ClassWithEnumReturnTypes.TEST_ENUM") - assert_that(fd.args()).is_empty() - - -func test_parse_func_description_return_type_internal_class_enum(): - var result := _parser.parse("ClassWithEnumReturnTypes", ["res://addons/gdUnit4/test/mocker/resources/ClassWithEnumReturnTypes.gd"]) - assert_result(result).is_success() - - var fd := _parser.parse_func_description("func get_inner_class_enum() -> InnerClass.TEST_ENUM:", "ClassWithEnumReturnTypes", ["res://addons/gdUnit4/test/mocker/resources/ClassWithEnumReturnTypes.gd"], 33) - assert_that(fd.name()).is_equal("get_inner_class_enum") - assert_that(fd.is_static()).is_false() - assert_that(fd.return_type()).is_equal(GdObjects.TYPE_ENUM) - assert_that(fd._return_class).is_equal("ClassWithEnumReturnTypes.InnerClass.TEST_ENUM") - assert_that(fd.args()).is_empty() - - -func test_parse_func_description_return_type_external_class_enum(): - var result := _parser.parse("ClassWithEnumReturnTypes", ["res://addons/gdUnit4/test/mocker/resources/ClassWithEnumReturnTypes.gd"]) - assert_result(result).is_success() - - var fd := _parser.parse_func_description("func get_external_class_enum() -> CustomEnums.TEST_ENUM:", "ClassWithEnumReturnTypes", ["res://addons/gdUnit4/test/mocker/resources/ClassWithEnumReturnTypes.gd"], 33) - assert_that(fd.name()).is_equal("get_external_class_enum") - assert_that(fd.is_static()).is_false() - assert_that(fd.return_type()).is_equal(GdObjects.TYPE_ENUM) - assert_that(fd._return_class).is_equal("CustomEnums.TEST_ENUM") - assert_that(fd.args()).is_empty() - - -func test_parse_class_inherits(): - var clazz_path := GdObjects.extract_class_path(CustomClassExtendsCustomClass) - var clazz_name := GdObjects.extract_class_name_from_class_path(clazz_path) - var result := _parser.parse(clazz_name, clazz_path) - assert_result(result).is_success() - - # verify class extraction - var clazz_desccriptor :GdClassDescriptor = result.value() - assert_object(clazz_desccriptor).is_not_null() - assert_str(clazz_desccriptor.name()).is_equal("CustomClassExtendsCustomClass") - assert_bool(clazz_desccriptor.is_inner_class()).is_false() - assert_array(clazz_desccriptor.functions()).contains_exactly([ - GdFunctionDescriptor.new("foo2", 5, false, false, false, GdObjects.TYPE_VARIANT, "", []), - GdFunctionDescriptor.new("bar2", 8, false, false, false, TYPE_STRING, "", []) - ]) - - # extends from CustomResourceTestClass - clazz_desccriptor = clazz_desccriptor.parent() - assert_object(clazz_desccriptor).is_not_null() - assert_str(clazz_desccriptor.name()).is_equal("CustomResourceTestClass") - assert_bool(clazz_desccriptor.is_inner_class()).is_false() - assert_array(clazz_desccriptor.functions()).contains_exactly([ - GdFunctionDescriptor.new("foo", 4, false, false, false, TYPE_STRING, "", []), - GdFunctionDescriptor.new("foo2", 7, false, false, false, GdObjects.TYPE_VARIANT, "", []), - GdFunctionDescriptor.new("foo_void", 10, false, false, false, GdObjects.TYPE_VOID, "", []), - GdFunctionDescriptor.new("bar", 13, false, false, false, TYPE_STRING, "", [ - GdFunctionArgument.new("arg1", TYPE_INT), - GdFunctionArgument.new("arg2", TYPE_INT, "23"), - GdFunctionArgument.new("name", TYPE_STRING, '"test"'), - ]), - GdFunctionDescriptor.new("foo5", 16, false, false, false, GdObjects.TYPE_VARIANT, "", []), - ]) - - # no other class extends - clazz_desccriptor = clazz_desccriptor.parent() - assert_object(clazz_desccriptor).is_null() - - -func test_get_class_name_pascal_case() -> void: - assert_str(_parser.get_class_name(load("res://addons/gdUnit4/test/core/resources/naming_conventions/PascalCaseWithClassName.gd")))\ - .is_equal("PascalCaseWithClassName") - assert_str(_parser.get_class_name(load("res://addons/gdUnit4/test/core/resources/naming_conventions/PascalCaseWithoutClassName.gd")))\ - .is_equal("PascalCaseWithoutClassName") - - -func test_get_class_name_snake_case() -> void: - assert_str(_parser.get_class_name(load("res://addons/gdUnit4/test/core/resources/naming_conventions/snake_case_with_class_name.gd")))\ - .is_equal("SnakeCaseWithClassName") - assert_str(_parser.get_class_name(load("res://addons/gdUnit4/test/core/resources/naming_conventions/snake_case_without_class_name.gd")))\ - .is_equal("SnakeCaseWithoutClassName") - - -func test_is_func_end() -> void: - assert_bool(_parser.is_func_end("")).is_false() - assert_bool(_parser.is_func_end("func test_a():")).is_true() - assert_bool(_parser.is_func_end("func test_a() -> void:")).is_true() - assert_bool(_parser.is_func_end("func test_a(arg1) :")).is_true() - assert_bool(_parser.is_func_end("func test_a(arg1 ): ")).is_true() - assert_bool(_parser.is_func_end("func test_a(arg1 ): ")).is_true() - assert_bool(_parser.is_func_end(" ):")).is_true() - assert_bool(_parser.is_func_end(" ):")).is_true() - assert_bool(_parser.is_func_end(" -> void:")).is_true() - assert_bool(_parser.is_func_end(" ) -> void :")).is_true() - assert_bool(_parser.is_func_end("func test_a(arg1, arg2 = {1:2} ):")).is_true() - - -func test_extract_func_signature_multiline() -> void: - var source_code = """ - - func test_parameterized(a: int, b :int, c :int, expected :int, parameters = [ - [1, 2, 3, 6], - [3, 4, 5, 11], - [6, 7, 8, 21] ]): - - assert_that(a+b+c).is_equal(expected) - """.dedent().split("\n") - - var fs = _parser.extract_func_signature(source_code, 0) - - assert_that(fs).is_equal(""" - func test_parameterized(a: int, b :int, c :int, expected :int, parameters = [ - [1, 2, 3, 6], - [3, 4, 5, 11], - [6, 7, 8, 21] ]):""" - .dedent() - .trim_prefix("\n") - ) - - -func test_parse_func_description_paramized_test(): - var fd = _parser.parse_func_description("functest_parameterized(a:int,b:int,c:int,expected:int,parameters=[[1,2,3,6],[3,4,5,11],[6,7,8,21]]):", "class", ["path"], 22) - - assert_that(fd).is_equal(GdFunctionDescriptor.new("test_parameterized", 22, false, false, false, GdObjects.TYPE_VARIANT, "", [ - GdFunctionArgument.new("a", TYPE_INT), - GdFunctionArgument.new("b", TYPE_INT), - GdFunctionArgument.new("c", TYPE_INT), - GdFunctionArgument.new("expected", TYPE_INT), - GdFunctionArgument.new("parameters", TYPE_ARRAY, "[[1,2,3,6],[3,4,5,11],[6,7,8,21]]"), - ])) - - -func test_parse_func_description_paramized_test_with_comments() -> void: - var source_code = """ - func test_parameterized(a: int, b :int, c :int, expected :int, parameters = [ - # before data set - [1, 2, 3, 6], # after data set - # between data sets - [3, 4, 5, 11], - [6, 7, 'string #ABCD', 21], # dataset with [comment] singn - [6, 7, "string #ABCD", 21] # dataset with "#comment" singn - #eof - ]): - pass - """.dedent().split("\n") - - var fs = _parser.extract_func_signature(source_code, 0) - - assert_that(fs).is_equal(""" - func test_parameterized(a: int, b :int, c :int, expected :int, parameters = [ - [1, 2, 3, 6], - [3, 4, 5, 11], - [6, 7, 'string #ABCD', 21], - [6, 7, "string #ABCD", 21] - ]):""" - .dedent() - .trim_prefix("\n") - ) - - -func test_parse_func_descriptor_with_fuzzers(): - var source_code := """ - func test_foo(fuzzer_a = fuzz_a(), fuzzer_b := fuzz_b(), - fuzzer_c :Fuzzer = fuzz_c(), - fuzzer = Fuzzers.random_rangei(-23, 22), - fuzzer_iterations = 234, - fuzzer_seed = 100): - """.split("\n") - var fs = _parser.extract_func_signature(source_code, 0) - var fd = _parser.parse_func_description(fs, "class", ["path"], 22) - - assert_that(fd).is_equal(GdFunctionDescriptor.new("test_foo", 22, false, false, false, GdObjects.TYPE_VARIANT, "", [ - GdFunctionArgument.new("fuzzer_a", GdObjects.TYPE_FUZZER, "fuzz_a()"), - GdFunctionArgument.new("fuzzer_b", GdObjects.TYPE_FUZZER, "fuzz_b()"), - GdFunctionArgument.new("fuzzer_c", GdObjects.TYPE_FUZZER, "fuzz_c()"), - GdFunctionArgument.new("fuzzer", GdObjects.TYPE_FUZZER, "Fuzzers.random_rangei(-23, 22)"), - GdFunctionArgument.new("fuzzer_iterations", TYPE_INT, "234"), - GdFunctionArgument.new("fuzzer_seed", TYPE_INT, "100") - ])) - - -func test_is_class_enum_type() -> void: - var parser := GdScriptParser.new() - assert_that(parser.is_class_enum_type("ClassWithEnumReturnTypes.InnerClass.TEST_ENUM")).is_true() - assert_that(parser.is_class_enum_type("ClassWithEnumReturnTypes.InnerClass")).is_false() - assert_that(parser.is_class_enum_type("ClassWithEnumReturnTypes.TEST_ENUM")).is_true() - assert_that(parser.is_class_enum_type("CustomEnums.TEST_ENUM")).is_true() - assert_that(parser.is_class_enum_type("CustomEnums")).is_false() - assert_that(parser.is_class_enum_type("ClassWithEnumReturnTypes.NOT_AN_ENUM")).is_false() diff --git a/addons/gdUnit4/test/core/parse/GdUnitExpressionRunnerTest.gd b/addons/gdUnit4/test/core/parse/GdUnitExpressionRunnerTest.gd deleted file mode 100644 index 5c0ce73..0000000 --- a/addons/gdUnit4/test/core/parse/GdUnitExpressionRunnerTest.gd +++ /dev/null @@ -1,65 +0,0 @@ -# GdUnit generated TestSuite -class_name GdUnitExpressionsTest -extends GdUnitTestSuite -@warning_ignore('unused_parameter') -@warning_ignore('return_value_discarded') - -# TestSuite generated from -const __source = 'res://addons/gdUnit4/src/core/parse/GdUnitExpressionRunner.gd' - -const TestFuzzers := preload("res://addons/gdUnit4/test/fuzzers/TestFuzzers.gd") - - -func test_create_fuzzer_argument_default(): - var fuzzer := GdUnitExpressionRunner.new().to_fuzzer(GDScript.new(), "Fuzzers.rangei(-10, 22)") - assert_that(fuzzer).is_not_null() - assert_that(fuzzer).is_instanceof(Fuzzer) - assert_int(fuzzer.next_value()).is_between(-10, 22) - - -func test_create_fuzzer_argument_with_constants(): - var fuzzer := GdUnitExpressionRunner.new().to_fuzzer(TestFuzzers, "Fuzzers.rangei(-10, MAX_VALUE)") - assert_that(fuzzer).is_not_null() - assert_that(fuzzer).is_instanceof(Fuzzer) - assert_int(fuzzer.next_value()).is_between(-10, 22) - - -func test_create_fuzzer_argument_with_custom_function(): - var fuzzer := GdUnitExpressionRunner.new().to_fuzzer(TestFuzzers, "get_fuzzer()") - assert_that(fuzzer).is_not_null() - assert_that(fuzzer).is_instanceof(Fuzzer) - assert_int(fuzzer.next_value()).is_between(TestFuzzers.MIN_VALUE, TestFuzzers.MAX_VALUE) - - -func test_create_fuzzer_do_fail(): - var fuzzer := GdUnitExpressionRunner.new().to_fuzzer(TestFuzzers, "non_fuzzer()") - assert_that(fuzzer).is_null() - - -func test_create_nested_fuzzer_do_fail(): - var fuzzer := GdUnitExpressionRunner.new().to_fuzzer(TestFuzzers, "NestedFuzzer.new()") - assert_that(fuzzer).is_not_null() - assert_that(fuzzer is Fuzzer).is_true() - assert_bool(fuzzer is TestFuzzers.NestedFuzzer).is_true() - - -func test_create_external_fuzzer(): - var fuzzer := GdUnitExpressionRunner.new().to_fuzzer(GDScript.new(), "TestExternalFuzzer.new()") - assert_that(fuzzer).is_not_null() - assert_that(fuzzer is Fuzzer).is_true() - assert_bool(fuzzer is TestExternalFuzzer).is_true() - - -func test_create_multipe_fuzzers(): - var fuzzer_a := GdUnitExpressionRunner.new().to_fuzzer(TestFuzzers, "Fuzzers.rangei(-10, MAX_VALUE)") - var fuzzer_b := GdUnitExpressionRunner.new().to_fuzzer(GDScript.new(), "Fuzzers.rangei(10, 20)") - assert_that(fuzzer_a).is_not_null() - assert_that(fuzzer_a).is_instanceof(IntFuzzer) - var a :IntFuzzer = fuzzer_a - assert_int(a._from).is_equal(-10) - assert_int(a._to).is_equal(TestFuzzers.MAX_VALUE) - assert_that(fuzzer_b).is_not_null() - assert_that(fuzzer_b).is_instanceof(IntFuzzer) - var b :IntFuzzer = fuzzer_b - assert_int(b._from).is_equal(10) - assert_int(b._to).is_equal(20) diff --git a/addons/gdUnit4/test/core/parse/GdUnitTestParameterSetResolverTest.gd b/addons/gdUnit4/test/core/parse/GdUnitTestParameterSetResolverTest.gd deleted file mode 100644 index 5372f2b..0000000 --- a/addons/gdUnit4/test/core/parse/GdUnitTestParameterSetResolverTest.gd +++ /dev/null @@ -1,222 +0,0 @@ -# GdUnit generated TestSuite -extends GdUnitTestSuite - -# TestSuite generated from -const __source = 'res://addons/gdUnit4/src/core/parse/GdUnitTestParameterSetResolver.gd' - - -var _test_param1 := 10 -var _test_param2 := 20 - - -func before(): - _test_param1 = 11 - - -func test_before(): - _test_param2 = 22 - - -@warning_ignore("unused_parameter") -func test_example_a(a: int, b: int, test_parameters=[[1, 2], [3,4]]) -> void: - pass - - -@warning_ignore("unused_parameter") -func test_example_b(a: Vector2, b: Vector2, test_parameters=[ - [Vector2.ZERO, Vector2.ONE], [Vector2(1.1, 3.2), Vector2.DOWN]] ) -> void: - pass - - -@warning_ignore("unused_parameter") -func test_example_c(a: Object, b: Object, test_parameters=[ - [Resource.new(), Resource.new()], - [Resource.new(), null] - ] ) -> void: - pass - - -@warning_ignore("unused_parameter") -func test_resolve_parameters_static(a: int, b: int, test_parameters=[ - [1, 10], - [2, 20] - ]) -> void: - pass - - -@warning_ignore("unused_parameter") -func test_resolve_parameters_at_runtime(a: int, b: int, test_parameters=[ - [1, _test_param1], - [2, _test_param2], - [3, 30] - ]) -> void: - pass - - -@warning_ignore("unused_parameter") -func test_parameterized_with_comments(a: int, b :int, c :String, expected :int, test_parameters = [ - # before data set - [1, 2, '3', 6], # after data set - # between data sets - [3, 4, '5', 11], - [6, 7, 'string #ABCD', 21], # dataset with [comment] singn - [6, 7, "string #ABCD", 21] # dataset with "comment" singn - #eof -]): - pass - - -func build_param(value: float) -> Vector3: - return Vector3(value, value, value) - - -@warning_ignore("unused_parameter") -func test_example_d(a: Vector3, b: Vector3, test_parameters=[ - [build_param(1), build_param(3)], - [Vector3.BACK, Vector3.UP] - ] ) -> void: - pass - - -class TestObj extends RefCounted: - var _value: String - - func _init(value: String): - _value = value - - func _to_string() -> String: - return _value - - -@warning_ignore("unused_parameter") -func test_example_e(a: Object, b: Object, expected: String, test_parameters:=[ - [TestObj.new("abc"), TestObj.new("def"), "abcdef"]]): - pass - - -# verify the used 'test_parameters' is completly resolved -func test_load_parameter_sets() -> void: - var tc := get_test_case("test_example_a") - assert_array(tc.parameter_set_resolver().load_parameter_sets(tc)) \ - .is_equal([[1, 2], [3, 4]]) - - tc = get_test_case("test_example_b") - assert_array(tc.parameter_set_resolver().load_parameter_sets(tc)) \ - .is_equal([[Vector2.ZERO, Vector2.ONE], [Vector2(1.1, 3.2), Vector2.DOWN]]) - - tc = get_test_case("test_example_c") - assert_array(tc.parameter_set_resolver().load_parameter_sets(tc)) \ - .is_equal([[Resource.new(), Resource.new()], [Resource.new(), null]]) - - tc = get_test_case("test_example_d") - assert_array(tc.parameter_set_resolver().load_parameter_sets(tc)) \ - .is_equal([[Vector3(1, 1, 1), Vector3(3, 3, 3)], [Vector3.BACK, Vector3.UP]]) - - tc = get_test_case("test_example_e") - assert_array(tc.parameter_set_resolver().load_parameter_sets(tc)) \ - .is_equal([[TestObj.new("abc"), TestObj.new("def"), "abcdef"]]) - - -func test_load_parameter_sets_at_runtime() -> void: - var tc := get_test_case("test_resolve_parameters_at_runtime") - assert_that(tc).is_not_null() - # check the parameters resolved at runtime - assert_array(tc.parameter_set_resolver().load_parameter_sets(tc)) \ - .is_equal([ - # the value `_test_param1` is changed from 10 to 11 on `before` stage - [1, 11], - # the value `_test_param2` is changed from 20 to 2 on `test_before` stage - [2, 22], - # the value is static initial `30` - [3, 30]]) - - -func test_load_parameter_with_comments() -> void: - var tc := get_test_case("test_parameterized_with_comments") - assert_that(tc).is_not_null() - # check the parameters resolved at runtime - assert_array(tc.parameter_set_resolver().load_parameter_sets(tc)) \ - .is_equal([ - [1, 2, '3', 6], - [3, 4, '5', 11], - [6, 7, 'string #ABCD', 21], - [6, 7, "string #ABCD", 21]]) - - -func test_build_test_case_names_on_static_parameter_set() -> void: - var test_case := get_test_case("test_resolve_parameters_static") - var resolver := test_case.parameter_set_resolver() - - assert_array(resolver.build_test_case_names(test_case))\ - .contains_exactly([ - "test_resolve_parameters_static:0 [1, 10]", - "test_resolve_parameters_static:1 [2, 20]"]) - assert_that(resolver.is_parameter_sets_static()).is_true() - assert_that(resolver.is_parameter_set_static(0)).is_true() - assert_that(resolver.is_parameter_set_static(1)).is_true() - - -func test_build_test_case_names_on_runtime_parameter_set() -> void: - var test_case := get_test_case("test_resolve_parameters_at_runtime") - var resolver := test_case.parameter_set_resolver() - - assert_array(resolver.build_test_case_names(test_case))\ - .contains_exactly([ - "test_resolve_parameters_at_runtime:0 [1, _test_param1]", - "test_resolve_parameters_at_runtime:1 [2, _test_param2]", - "test_resolve_parameters_at_runtime:2 [3, 30]"]) - assert_that(resolver.is_parameter_sets_static()).is_false() - assert_that(resolver.is_parameter_set_static(0)).is_false() - assert_that(resolver.is_parameter_set_static(1)).is_false() - assert_that(resolver.is_parameter_set_static(2)).is_false() - - -func test_validate_test_parameter_set(): - var test_suite :GdUnitTestSuite = auto_free(GdUnitTestResourceLoader.load_test_suite("res://addons/gdUnit4/test/core/resources/testsuites/TestSuiteInvalidParameterizedTests.resource")) - - assert_is_not_skipped(test_suite, "test_no_parameters") - assert_is_not_skipped(test_suite, "test_parameterized_success") - assert_is_not_skipped(test_suite, "test_parameterized_failed") - assert_is_skipped(test_suite, "test_parameterized_to_less_args")\ - .contains("The parameter set at index [0] does not match the expected input parameters!")\ - .contains("The test case requires [3] input parameters, but the set contains [4]") - assert_is_skipped(test_suite, "test_parameterized_to_many_args")\ - .contains("The parameter set at index [0] does not match the expected input parameters!")\ - .contains("The test case requires [5] input parameters, but the set contains [4]") - assert_is_skipped(test_suite, "test_parameterized_to_less_args_at_index_1")\ - .contains("The parameter set at index [1] does not match the expected input parameters!")\ - .contains("The test case requires [3] input parameters, but the set contains [4]") - assert_is_skipped(test_suite, "test_parameterized_invalid_struct")\ - .contains("The parameter set at index [1] does not match the expected input parameters!")\ - .contains("The test case requires [3] input parameters, but the set contains [1]") - assert_is_skipped(test_suite, "test_parameterized_invalid_args")\ - .contains("The parameter set at index [1] does not match the expected input parameters!")\ - .contains("The value '4' does not match the required input parameter .") - - -func assert_is_not_skipped(test_suite :GdUnitTestSuite, test_case :String) -> void: - # set actual execution context for this test suite - test_suite.__execution_context = GdUnitExecutionContext.new(test_suite.get_name()) - var test :_TestCase = test_suite.find_child(test_case, true, false) - if test.is_parameterized(): - # to load parameter set and force validate - var resolver := test.parameter_set_resolver() - resolver.build_test_case_names(test) - resolver.load_parameter_sets(test, true) - assert_that(test.is_skipped()).is_false() - - -func assert_is_skipped(test_suite :GdUnitTestSuite, test_case :String) -> GdUnitStringAssert: - # set actual execution context for this test suite - test_suite.__execution_context = GdUnitExecutionContext.new(test_suite.get_name()) - var test :_TestCase = test_suite.find_child(test_case, true, false) - if test.is_parameterized(): - # to load parameter set and force validate - var resolver := test.parameter_set_resolver() - resolver.build_test_case_names(test) - resolver.load_parameter_sets(test, true) - assert_that(test.is_skipped()).is_true() - return assert_str(test.skip_info()) - -func get_test_case(name: String) -> _TestCase: - return find_child(name, true, false) diff --git a/addons/gdUnit4/test/core/resources/Area4D.txt b/addons/gdUnit4/test/core/resources/Area4D.txt deleted file mode 100644 index e3da456..0000000 --- a/addons/gdUnit4/test/core/resources/Area4D.txt +++ /dev/null @@ -1,17 +0,0 @@ -class Area4D extends Resource: - -const SOUND := 1 -const ATMOSPHERE := 2 -var _meta := Dictionary() - -func _init(_x :int, atmospere :AtmosphereData = null): - _meta[ATMOSPHERE] = atmospere - -func get_sound() -> SoundData: - # sounds are optional - if _meta.has(SOUND): - return _meta[SOUND] as SoundData - return null - -func get_atmoshere() -> AtmosphereData: - return _meta[ATMOSPHERE] as AtmosphereData diff --git a/addons/gdUnit4/test/core/resources/AtmosphereData.txt b/addons/gdUnit4/test/core/resources/AtmosphereData.txt deleted file mode 100644 index 3a69974..0000000 --- a/addons/gdUnit4/test/core/resources/AtmosphereData.txt +++ /dev/null @@ -1,22 +0,0 @@ -class AtmosphereData: -enum { - WATER, - AIR, - SMOKY, -} -var _toxigen :float -var _type :int - -func _init(type := AIR, toxigen := 0.0): - _type = type - _toxigen = toxigen -# some comment, and an row staring with an space to simmulate invalid formatting - - -# seter func with default values -func set_data(type := AIR, toxigen := 0.0) : - _type = type - _toxigen = toxigen - -static func to_atmosphere(_value :Dictionary) -> AtmosphereData: - return null diff --git a/addons/gdUnit4/test/core/resources/GdUnitRunner_old_format.cfg b/addons/gdUnit4/test/core/resources/GdUnitRunner_old_format.cfg deleted file mode 100644 index cc6eeced569da1ff4783209755d2886098b43f49..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 84 zcmWGwU|jtxi`r50Q1>qFFX0r@GZNu}xW Mx%nxn5OpB60KygvO8@`> diff --git a/addons/gdUnit4/test/core/resources/SoundData.txt b/addons/gdUnit4/test/core/resources/SoundData.txt deleted file mode 100644 index 91e62ba..0000000 --- a/addons/gdUnit4/test/core/resources/SoundData.txt +++ /dev/null @@ -1,5 +0,0 @@ -class SoundData: -@warning_ignore("unused_private_class_variable") -var _sample :String -@warning_ignore("unused_private_class_variable") -var _randomnes :float diff --git a/addons/gdUnit4/test/core/resources/copy_test/folder_a/file_a.txt b/addons/gdUnit4/test/core/resources/copy_test/folder_a/file_a.txt deleted file mode 100644 index d6459e0..0000000 --- a/addons/gdUnit4/test/core/resources/copy_test/folder_a/file_a.txt +++ /dev/null @@ -1 +0,0 @@ -xxx diff --git a/addons/gdUnit4/test/core/resources/copy_test/folder_a/file_b.txt b/addons/gdUnit4/test/core/resources/copy_test/folder_a/file_b.txt deleted file mode 100644 index d6459e0..0000000 --- a/addons/gdUnit4/test/core/resources/copy_test/folder_a/file_b.txt +++ /dev/null @@ -1 +0,0 @@ -xxx diff --git a/addons/gdUnit4/test/core/resources/copy_test/folder_b/file_a.txt b/addons/gdUnit4/test/core/resources/copy_test/folder_b/file_a.txt deleted file mode 100644 index d6459e0..0000000 --- a/addons/gdUnit4/test/core/resources/copy_test/folder_b/file_a.txt +++ /dev/null @@ -1 +0,0 @@ -xxx diff --git a/addons/gdUnit4/test/core/resources/copy_test/folder_b/file_b.txt b/addons/gdUnit4/test/core/resources/copy_test/folder_b/file_b.txt deleted file mode 100644 index d6459e0..0000000 --- a/addons/gdUnit4/test/core/resources/copy_test/folder_b/file_b.txt +++ /dev/null @@ -1 +0,0 @@ -xxx diff --git a/addons/gdUnit4/test/core/resources/copy_test/folder_b/folder_ba/file_x.txt b/addons/gdUnit4/test/core/resources/copy_test/folder_b/folder_ba/file_x.txt deleted file mode 100644 index d6459e0..0000000 --- a/addons/gdUnit4/test/core/resources/copy_test/folder_b/folder_ba/file_x.txt +++ /dev/null @@ -1 +0,0 @@ -xxx diff --git a/addons/gdUnit4/test/core/resources/copy_test/folder_c/file_z.txt b/addons/gdUnit4/test/core/resources/copy_test/folder_c/file_z.txt deleted file mode 100644 index e69de29..0000000 diff --git a/addons/gdUnit4/test/core/resources/naming_conventions/PascalCaseWithClassName.gd b/addons/gdUnit4/test/core/resources/naming_conventions/PascalCaseWithClassName.gd deleted file mode 100644 index 32df9ea..0000000 --- a/addons/gdUnit4/test/core/resources/naming_conventions/PascalCaseWithClassName.gd +++ /dev/null @@ -1,7 +0,0 @@ -class_name PascalCaseWithClassName -extends Resource - - -# Called when the node enters the scene tree for the first time. -func _ready(): - pass # Replace with function body. diff --git a/addons/gdUnit4/test/core/resources/naming_conventions/PascalCaseWithoutClassName.gd b/addons/gdUnit4/test/core/resources/naming_conventions/PascalCaseWithoutClassName.gd deleted file mode 100644 index 7f377d4..0000000 --- a/addons/gdUnit4/test/core/resources/naming_conventions/PascalCaseWithoutClassName.gd +++ /dev/null @@ -1,6 +0,0 @@ -extends RefCounted - - -# Called when the node enters the scene tree for the first time. -func _ready(): - pass # Replace with function body. diff --git a/addons/gdUnit4/test/core/resources/naming_conventions/snake_case_with_class_name.gd b/addons/gdUnit4/test/core/resources/naming_conventions/snake_case_with_class_name.gd deleted file mode 100644 index d3e93f0..0000000 --- a/addons/gdUnit4/test/core/resources/naming_conventions/snake_case_with_class_name.gd +++ /dev/null @@ -1,7 +0,0 @@ -class_name SnakeCaseWithClassName -extends Resource - - -# Called when the node enters the scene tree for the first time. -func _ready(): - pass # Replace with function body. diff --git a/addons/gdUnit4/test/core/resources/naming_conventions/snake_case_without_class_name.gd b/addons/gdUnit4/test/core/resources/naming_conventions/snake_case_without_class_name.gd deleted file mode 100644 index 7f377d4..0000000 --- a/addons/gdUnit4/test/core/resources/naming_conventions/snake_case_without_class_name.gd +++ /dev/null @@ -1,6 +0,0 @@ -extends RefCounted - - -# Called when the node enters the scene tree for the first time. -func _ready(): - pass # Replace with function body. diff --git a/addons/gdUnit4/test/core/resources/scan_testsuite_inheritance/by_class_name/BaseTest.gd b/addons/gdUnit4/test/core/resources/scan_testsuite_inheritance/by_class_name/BaseTest.gd deleted file mode 100644 index d974340..0000000 --- a/addons/gdUnit4/test/core/resources/scan_testsuite_inheritance/by_class_name/BaseTest.gd +++ /dev/null @@ -1,22 +0,0 @@ -class_name BaseTest -extends GdUnitTestSuite - - -func before(): - pass - - -func after(): - pass - - -func before_test(): - pass - - -func after_test(): - pass - - -func test_foo1() -> void: - pass diff --git a/addons/gdUnit4/test/core/resources/scan_testsuite_inheritance/by_class_name/ExtendedTest.gd b/addons/gdUnit4/test/core/resources/scan_testsuite_inheritance/by_class_name/ExtendedTest.gd deleted file mode 100644 index 068bba0..0000000 --- a/addons/gdUnit4/test/core/resources/scan_testsuite_inheritance/by_class_name/ExtendedTest.gd +++ /dev/null @@ -1,14 +0,0 @@ -class_name ExtendedTest -extends BaseTest - - -func before_test(): - pass - - -func after_test(): - pass - - -func test_foo2() -> void: - pass diff --git a/addons/gdUnit4/test/core/resources/scan_testsuite_inheritance/by_class_name/ExtendsExtendedTest.gd b/addons/gdUnit4/test/core/resources/scan_testsuite_inheritance/by_class_name/ExtendsExtendedTest.gd deleted file mode 100644 index 299a52d..0000000 --- a/addons/gdUnit4/test/core/resources/scan_testsuite_inheritance/by_class_name/ExtendsExtendedTest.gd +++ /dev/null @@ -1,4 +0,0 @@ -extends ExtendedTest - -func test_foo3() -> void: - pass diff --git a/addons/gdUnit4/test/core/resources/scan_testsuite_inheritance/by_class_path/BaseTest.gd b/addons/gdUnit4/test/core/resources/scan_testsuite_inheritance/by_class_path/BaseTest.gd deleted file mode 100644 index 17cfc19..0000000 --- a/addons/gdUnit4/test/core/resources/scan_testsuite_inheritance/by_class_path/BaseTest.gd +++ /dev/null @@ -1,4 +0,0 @@ -extends GdUnitTestSuite - -func test_foo1() -> void: - pass diff --git a/addons/gdUnit4/test/core/resources/scan_testsuite_inheritance/by_class_path/ExtendedTest.gd b/addons/gdUnit4/test/core/resources/scan_testsuite_inheritance/by_class_path/ExtendedTest.gd deleted file mode 100644 index eea2d4f..0000000 --- a/addons/gdUnit4/test/core/resources/scan_testsuite_inheritance/by_class_path/ExtendedTest.gd +++ /dev/null @@ -1,4 +0,0 @@ -extends "res://addons/gdUnit4/test/core/resources/scan_testsuite_inheritance/by_class_path/BaseTest.gd" - -func test_foo2() -> void: - pass diff --git a/addons/gdUnit4/test/core/resources/scan_testsuite_inheritance/by_class_path/ExtendsExtendedTest.gd b/addons/gdUnit4/test/core/resources/scan_testsuite_inheritance/by_class_path/ExtendsExtendedTest.gd deleted file mode 100644 index a14c8d3..0000000 --- a/addons/gdUnit4/test/core/resources/scan_testsuite_inheritance/by_class_path/ExtendsExtendedTest.gd +++ /dev/null @@ -1,4 +0,0 @@ -extends "res://addons/gdUnit4/test/core/resources/scan_testsuite_inheritance/by_class_path/ExtendedTest.gd" - -func test_foo3() -> void: - pass diff --git a/addons/gdUnit4/test/core/resources/scenes/drag_and_drop/DragAndDropControl.gd b/addons/gdUnit4/test/core/resources/scenes/drag_and_drop/DragAndDropControl.gd deleted file mode 100644 index 6bf00a9..0000000 --- a/addons/gdUnit4/test/core/resources/scenes/drag_and_drop/DragAndDropControl.gd +++ /dev/null @@ -1,41 +0,0 @@ -extends PanelContainer - - -# Godot calls this method to get data that can be dragged and dropped onto controls that expect drop data. -# Returns null if there is no data to drag. -# Controls that want to receive drop data should implement can_drop_data() and drop_data(). -# position is local to this control. Drag may be forced with force_drag(). -func _get_drag_data(_position: Vector2) -> Variant: - var x :TextureRect = $TextureRect - var data: = {texture = x.texture} - var drag_texture := x.duplicate() - drag_texture.size = x.size - drag_texture.position = x.global_position * -0.2 - - # set drag preview - var control := Panel.new() - control.add_child(drag_texture) - # center texture relative to mouse pos - set_drag_preview(control) - return data - - -# Godot calls this method to test if data from a control's get_drag_data() can be dropped at position. position is local to this control. -func _can_drop_data(_position: Vector2, data :Variant) -> bool: - return typeof(data) == TYPE_DICTIONARY and data.has("texture") - - -# Godot calls this method to pass you the data from a control's get_drag_data() result. -# Godot first calls can_drop_data() to test if data is allowed to drop at position where position is local to this control. -func _drop_data(_position: Vector2, data :Variant) -> void: - var drag_texture :Texture = data["texture"] - if drag_texture != null: - $TextureRect.texture = drag_texture - - -# Virtual method to be implemented by the user. Use this method to process and accept inputs on UI elements. See accept_event(). -func _gui_input(_event): - #prints("Panel _gui_input", _event.as_text()) - #if _event is InputEventMouseButton: - # prints("Panel _gui_input", _event.as_text()) - pass diff --git a/addons/gdUnit4/test/core/resources/scenes/drag_and_drop/DragAndDropControl.tscn b/addons/gdUnit4/test/core/resources/scenes/drag_and_drop/DragAndDropControl.tscn deleted file mode 100644 index e4738cf..0000000 --- a/addons/gdUnit4/test/core/resources/scenes/drag_and_drop/DragAndDropControl.tscn +++ /dev/null @@ -1,16 +0,0 @@ -[gd_scene load_steps=2 format=3 uid="uid://ca2rr3dan4vvw"] - -[ext_resource type="Script" path="res://addons/gdUnit4/test/core/resources/scenes/drag_and_drop/DragAndDropControl.gd" id="1"] - -[node name="Panel" type="PanelContainer"] -offset_left = 245.0 -offset_top = 232.0 -offset_right = 350.0 -offset_bottom = 337.0 -size_flags_horizontal = 3 -size_flags_vertical = 3 -script = ExtResource("1") - -[node name="TextureRect" type="TextureRect" parent="."] -layout_mode = 2 -expand_mode = 1 diff --git a/addons/gdUnit4/test/core/resources/scenes/drag_and_drop/DragAndDropTestScene.gd b/addons/gdUnit4/test/core/resources/scenes/drag_and_drop/DragAndDropTestScene.gd deleted file mode 100644 index eedd57a..0000000 --- a/addons/gdUnit4/test/core/resources/scenes/drag_and_drop/DragAndDropTestScene.gd +++ /dev/null @@ -1,18 +0,0 @@ -extends Control - -@onready var texture = preload("res://addons/gdUnit4/test/core/resources/scenes/drag_and_drop/icon.png") - -func _ready(): - # set initial drag texture - $left/TextureRect.texture = texture - - -# Virtual method to be implemented by the user. Use this method to process and accept inputs on UI elements. See accept_event(). -func _gui_input(_event): - #prints("Game _gui_input", _event.as_text()) - pass - - -func _on_Button_button_down(): - # print("BUTTON DOWN") - pass diff --git a/addons/gdUnit4/test/core/resources/scenes/drag_and_drop/DragAndDropTestScene.tscn b/addons/gdUnit4/test/core/resources/scenes/drag_and_drop/DragAndDropTestScene.tscn deleted file mode 100644 index aa61b7c..0000000 --- a/addons/gdUnit4/test/core/resources/scenes/drag_and_drop/DragAndDropTestScene.tscn +++ /dev/null @@ -1,43 +0,0 @@ -[gd_scene load_steps=3 format=3 uid="uid://skueh3d5qn46"] - -[ext_resource type="Script" path="res://addons/gdUnit4/test/core/resources/scenes/drag_and_drop/DragAndDropTestScene.gd" id="1"] -[ext_resource type="PackedScene" uid="uid://ca2rr3dan4vvw" path="res://addons/gdUnit4/test/core/resources/scenes/drag_and_drop/DragAndDropControl.tscn" id="2_u5ccv"] - -[node name="DragAndDropScene" type="Control"] -layout_mode = 3 -anchors_preset = 15 -anchor_right = 1.0 -anchor_bottom = 1.0 -grow_horizontal = 2 -grow_vertical = 2 -size_flags_horizontal = 3 -size_flags_vertical = 3 -script = ExtResource("1") - -[node name="left" parent="." instance=ExtResource("2_u5ccv")] -layout_mode = 0 -offset_left = 250.0 -offset_top = 240.0 -offset_right = 355.0 -offset_bottom = 345.0 -auto_translate = false -localize_numeral_system = false -metadata/_edit_use_anchors_ = true - -[node name="right" parent="." instance=ExtResource("2_u5ccv")] -layout_mode = 0 -offset_left = 370.0 -offset_top = 240.0 -offset_right = 475.0 -offset_bottom = 345.0 - -[node name="Button" type="Button" parent="."] -layout_mode = 0 -offset_left = 243.0 -offset_top = 40.0 -offset_right = 479.0 -offset_bottom = 200.0 -text = "BUTTON" -metadata/_edit_use_anchors_ = true - -[connection signal="button_down" from="Button" to="." method="_on_Button_button_down"] diff --git a/addons/gdUnit4/test/core/resources/scenes/drag_and_drop/icon.png b/addons/gdUnit4/test/core/resources/scenes/drag_and_drop/icon.png deleted file mode 100644 index eeac2924071bb62e97d1f372ca66c0e7e1a256f0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 13817 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 bool: - return true - - -func _ready(): - add_child(Player.new(), true) diff --git a/addons/gdUnit4/test/core/resources/scenes/simple_scene.tscn b/addons/gdUnit4/test/core/resources/scenes/simple_scene.tscn deleted file mode 100644 index a7648ad..0000000 --- a/addons/gdUnit4/test/core/resources/scenes/simple_scene.tscn +++ /dev/null @@ -1,11 +0,0 @@ -[gd_scene load_steps=3 format=3 uid="uid://cn8ucy2rheu0f"] - -[ext_resource type="Texture2D" uid="uid://t80a6k3vyrrd" path="res://icon.png" id="1"] -[ext_resource type="Script" path="res://addons/gdUnit4/test/core/resources/scenes/simple_scene.gd" id="2"] - -[node name="Node2D" type="Node2D"] -script = ExtResource("2") - -[node name="Sprite2D" type="Sprite2D" parent="."] -position = Vector2(504, 252) -texture = ExtResource("1") diff --git a/addons/gdUnit4/test/core/resources/script_with_class_name.gd b/addons/gdUnit4/test/core/resources/script_with_class_name.gd deleted file mode 100644 index c2e0fbb..0000000 --- a/addons/gdUnit4/test/core/resources/script_with_class_name.gd +++ /dev/null @@ -1,6 +0,0 @@ -class_name ScriptWithClassName -extends Resource - -# Called when the node enters the scene tree for the first time. -func _ready(): - pass # Replace with function body. diff --git a/addons/gdUnit4/test/core/resources/script_without_class_name.gd b/addons/gdUnit4/test/core/resources/script_without_class_name.gd deleted file mode 100644 index 2a74876..0000000 --- a/addons/gdUnit4/test/core/resources/script_without_class_name.gd +++ /dev/null @@ -1,5 +0,0 @@ -extends RefCounted - -# Called when the node enters the scene tree for the first time. -func _ready(): - pass # Replace with function body. diff --git a/addons/gdUnit4/test/core/resources/sources/test_person.gd b/addons/gdUnit4/test/core/resources/sources/test_person.gd deleted file mode 100644 index c4a86ca..0000000 --- a/addons/gdUnit4/test/core/resources/sources/test_person.gd +++ /dev/null @@ -1,17 +0,0 @@ -extends RefCounted - -var _first_name :String -var _last_name :String - -func _init(first_name_ :String,last_name_ :String): - _first_name = first_name_ - _last_name = last_name_ - -func first_name() -> String: - return _first_name - -func last_name() -> String: - return _last_name - -func fully_name() -> String: - return _first_name + " " + _last_name diff --git a/addons/gdUnit4/test/core/resources/test_script_with_arguments.gd b/addons/gdUnit4/test/core/resources/test_script_with_arguments.gd deleted file mode 100644 index b9f5455..0000000 --- a/addons/gdUnit4/test/core/resources/test_script_with_arguments.gd +++ /dev/null @@ -1,49 +0,0 @@ -extends Node - - -func test_no_args(): - pass - -@warning_ignore("unused_parameter") -func test_with_timeout(timeout=2000): - pass - - -@warning_ignore("unused_parameter") -func test_with_fuzzer(fuzzer := Fuzzers.rangei(-10, 22)): - pass - - -@warning_ignore("unused_parameter") -func test_with_fuzzer_iterations(fuzzer := Fuzzers.rangei(-10, 22), fuzzer_iterations = 10): - pass - - -@warning_ignore("unused_parameter") -func test_with_multible_fuzzers(fuzzer_a := Fuzzers.rangei(-10, 22), fuzzer_b := Fuzzers.rangei(23, 42), fuzzer_iterations = 10): - pass - - -@warning_ignore("unused_parameter") -func test_multiline_arguments_a(fuzzer_a := Fuzzers.rangei(-10, 22), fuzzer_b := Fuzzers.rangei(23, 42), - fuzzer_iterations = 42): - pass - - -@warning_ignore("unused_parameter") -func test_multiline_arguments_b( - fuzzer_a := Fuzzers.rangei(-10, 22), - fuzzer_b := Fuzzers.rangei(23, 42), - fuzzer_iterations = 23 - ): - pass - - -@warning_ignore("unused_parameter") -func test_multiline_arguments_c( - timeout=2000, - fuzzer_a := Fuzzers.rangei(-10, 22), - fuzzer_b := Fuzzers.rangei(23, 42), - fuzzer_iterations = 33 - ) -> void: - pass diff --git a/addons/gdUnit4/test/core/resources/testsuites/GdUnitFuzzerTest.resource b/addons/gdUnit4/test/core/resources/testsuites/GdUnitFuzzerTest.resource deleted file mode 100644 index 4ffc7b2..0000000 --- a/addons/gdUnit4/test/core/resources/testsuites/GdUnitFuzzerTest.resource +++ /dev/null @@ -1,48 +0,0 @@ -# this test suite simulates long running test cases -extends GdUnitTestSuite -@warning_ignore('unused_parameter') - -var _stack : Array - -func before(): - # init the stack - _stack = [] - -func before_test(): - # clean the stack before every test run - _stack.clear() - - -@warning_ignore('unused_parameter') -func test_multi_yielding_with_fuzzer(fuzzer := Fuzzers.rangei(0, 1000), fuzzer_iterations = 10): - # verify the used stack is cleaned by 'before_test' - assert_array(_stack).is_empty() - _stack.push_back(1) - - prints("test iteration %d" % fuzzer.iteration_index()) - prints("4") - await get_tree().process_frame - prints("3") - await get_tree().process_frame - prints("2") - await get_tree().process_frame - prints("1") - await get_tree().process_frame - prints("Go") - -@warning_ignore('unused_parameter') -func test_multi_yielding_with_fuzzer_fail_after_3_iterations(fuzzer := Fuzzers.rangei(0, 1000), fuzzer_iterations = 10): - prints("test iteration %d" % fuzzer.iteration_index()) - # should never be greater than 3 because we interuppted after three iterations - assert_int(fuzzer.iteration_index()).is_less_equal(3) - prints("4") - await get_tree().process_frame - prints("3") - await get_tree().process_frame - prints("2") - await get_tree().process_frame - prints("1") - await get_tree().process_frame - prints("Go") - if fuzzer.iteration_index() >= 3: - assert_bool(true).is_false() diff --git a/addons/gdUnit4/test/core/resources/testsuites/NotATestSuite.cs b/addons/gdUnit4/test/core/resources/testsuites/NotATestSuite.cs deleted file mode 100644 index a7e4323..0000000 --- a/addons/gdUnit4/test/core/resources/testsuites/NotATestSuite.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace GdUnit4.Tests.Resources -{ - using static Assertions; - - // will be ignored because of missing `[TestSuite]` anotation - public partial class NotATestSuite - { - [TestCase] - public void TestFoo() - { - AssertBool(true).IsEqual(false); - } - } -} diff --git a/addons/gdUnit4/test/core/resources/testsuites/TestCaseSkipped.resource b/addons/gdUnit4/test/core/resources/testsuites/TestCaseSkipped.resource deleted file mode 100644 index 8482bbd..0000000 --- a/addons/gdUnit4/test/core/resources/testsuites/TestCaseSkipped.resource +++ /dev/null @@ -1,11 +0,0 @@ -extends GdUnitTestSuite - - -@warning_ignore('unused_parameter') -func test_case1(timeout = 1000, do_skip=1==1, skip_reason="do not run this"): - pass - - -@warning_ignore('unused_parameter') -func test_case2(skip_reason="ignored"): - pass diff --git a/addons/gdUnit4/test/core/resources/testsuites/TestSuiteAllStagesSuccess.resource b/addons/gdUnit4/test/core/resources/testsuites/TestSuiteAllStagesSuccess.resource deleted file mode 100644 index 31ae79c..0000000 --- a/addons/gdUnit4/test/core/resources/testsuites/TestSuiteAllStagesSuccess.resource +++ /dev/null @@ -1,20 +0,0 @@ -# this test suite ends with success, no failures or errors -extends GdUnitTestSuite - -func before(): - assert_str("suite before").is_equal("suite before") - -func after(): - assert_str("suite after").is_equal("suite after") - -func before_test(): - assert_str("test before").is_equal("test before") - -func after_test(): - assert_str("test after").is_equal("test after") - -func test_case1(): - assert_str("test_case1").is_equal("test_case1") - -func test_case2(): - assert_str("test_case2").is_equal("test_case2") diff --git a/addons/gdUnit4/test/core/resources/testsuites/TestSuiteErrorOnTestTimeout.resource b/addons/gdUnit4/test/core/resources/testsuites/TestSuiteErrorOnTestTimeout.resource deleted file mode 100644 index b91b3ee..0000000 --- a/addons/gdUnit4/test/core/resources/testsuites/TestSuiteErrorOnTestTimeout.resource +++ /dev/null @@ -1,24 +0,0 @@ -# this test suite ends with error on testcase1 by a timeout -extends GdUnitTestSuite - -func before(): - assert_str("suite before").is_equal("suite before") - -func after(): - assert_str("suite after").is_equal("suite after") - -func before_test(): - assert_str("test before").is_equal("test before") - -func after_test(): - assert_str("test after").is_equal("test after") - -# configure test with timeout of 2s -@warning_ignore('unused_parameter') -func test_case1(timeout=2000): - assert_str("test_case1").is_equal("test_case1") - # wait 3s to let the test fail by timeout - await await_millis(3000) - -func test_case2(): - assert_str("test_case2").is_equal("test_case2") diff --git a/addons/gdUnit4/test/core/resources/testsuites/TestSuiteFailAddChildStageBefore.resource b/addons/gdUnit4/test/core/resources/testsuites/TestSuiteFailAddChildStageBefore.resource deleted file mode 100644 index 265aca0..0000000 --- a/addons/gdUnit4/test/core/resources/testsuites/TestSuiteFailAddChildStageBefore.resource +++ /dev/null @@ -1,11 +0,0 @@ -# this test suite fails if (https://github.com/MikeSchulze/gdUnit4/issues/106) not fixed on iterating over testcases -extends GdUnitTestSuite - -func before(): - add_child(auto_free(Node.new())) - -func test_case1(): - assert_str("test_case1").is_equal("test_case1") - -func test_case2(): - assert_str("test_case2").is_equal("test_case2") diff --git a/addons/gdUnit4/test/core/resources/testsuites/TestSuiteFailAndOrpahnsDetected.resource b/addons/gdUnit4/test/core/resources/testsuites/TestSuiteFailAndOrpahnsDetected.resource deleted file mode 100644 index 6d14009..0000000 --- a/addons/gdUnit4/test/core/resources/testsuites/TestSuiteFailAndOrpahnsDetected.resource +++ /dev/null @@ -1,37 +0,0 @@ -# this test suite fails on multiple stages and detects orphans -extends GdUnitTestSuite - -var _orphans := Array() - -func before(): - # create a node where never freed (orphan) - _orphans.append(Node.new()) - -func before_test(): - # create two node where never freed (orphan) - _orphans.append(Node.new()) - _orphans.append(Node.new()) - - -# ends with warning and 3 orphan detected -func test_case1(): - # create three node where never freed (orphan) - _orphans.append(Node.new()) - _orphans.append(Node.new()) - _orphans.append(Node.new()) - -# ends with error and 4 orphan detected -func test_case2(): - # create four node where never freed (orphan) - _orphans.append(Node.new()) - _orphans.append(Node.new()) - _orphans.append(Node.new()) - _orphans.append(Node.new()) - assert_str("test_case2").override_failure_message("faild on test_case2()").is_empty() - -# we manually freeing the orphans from the simulated testsuite to prevent memory leaks here -func _notification(what): - if what == NOTIFICATION_PREDELETE: - for orphan in _orphans: - orphan.free() - _orphans.clear() diff --git a/addons/gdUnit4/test/core/resources/testsuites/TestSuiteFailOnMultipeStages.resource b/addons/gdUnit4/test/core/resources/testsuites/TestSuiteFailOnMultipeStages.resource deleted file mode 100644 index 7d22794..0000000 --- a/addons/gdUnit4/test/core/resources/testsuites/TestSuiteFailOnMultipeStages.resource +++ /dev/null @@ -1,21 +0,0 @@ -# this test suite fails on multiple stages -extends GdUnitTestSuite - -func before(): - assert_str("suite before").is_equal("suite before") - -func after(): - assert_str("suite after").override_failure_message("failed on after()").is_empty() - -func before_test(): - assert_str("test before").override_failure_message("failed on before_test()").is_empty() - -func after_test(): - assert_str("test after").is_equal("test after") - -func test_case1(): - assert_str("test_case1").override_failure_message("failed 1 on test_case1()").is_empty() - assert_str("test_case1").override_failure_message("failed 2 on test_case1()").is_empty() - -func test_case2(): - assert_str("test_case2").is_equal("test_case2") diff --git a/addons/gdUnit4/test/core/resources/testsuites/TestSuiteFailOnStageAfter.resource b/addons/gdUnit4/test/core/resources/testsuites/TestSuiteFailOnStageAfter.resource deleted file mode 100644 index a84e88b..0000000 --- a/addons/gdUnit4/test/core/resources/testsuites/TestSuiteFailOnStageAfter.resource +++ /dev/null @@ -1,20 +0,0 @@ -# this test suite fails on stage after() -extends GdUnitTestSuite - -func before(): - assert_str("suite before").is_equal("suite before") - -func after(): - assert_str("suite after").override_failure_message("failed on after()").is_empty() - -func before_test(): - assert_str("test before").is_equal("test before") - -func after_test(): - assert_str("test after").is_equal("test after") - -func test_case1(): - assert_str("test_case1").is_equal("test_case1") - -func test_case2(): - assert_str("test_case2").is_equal("test_case2") diff --git a/addons/gdUnit4/test/core/resources/testsuites/TestSuiteFailOnStageAfterTest.resource b/addons/gdUnit4/test/core/resources/testsuites/TestSuiteFailOnStageAfterTest.resource deleted file mode 100644 index 3bd86b2..0000000 --- a/addons/gdUnit4/test/core/resources/testsuites/TestSuiteFailOnStageAfterTest.resource +++ /dev/null @@ -1,20 +0,0 @@ -# this test suite fails on stage after_test() -extends GdUnitTestSuite - -func before(): - assert_str("suite before").is_equal("suite before") - -func after(): - assert_str("suite after").is_equal("suite after") - -func before_test(): - assert_str("test before").is_equal("test before") - -func after_test(): - assert_str("test after").override_failure_message("failed on after_test()").is_empty() - -func test_case1(): - assert_str("test_case1").is_equal("test_case1") - -func test_case2(): - assert_str("test_case2").is_equal("test_case2") diff --git a/addons/gdUnit4/test/core/resources/testsuites/TestSuiteFailOnStageBefore.resource b/addons/gdUnit4/test/core/resources/testsuites/TestSuiteFailOnStageBefore.resource deleted file mode 100644 index a30bafa..0000000 --- a/addons/gdUnit4/test/core/resources/testsuites/TestSuiteFailOnStageBefore.resource +++ /dev/null @@ -1,20 +0,0 @@ -# this test suite fails on stage before() -extends GdUnitTestSuite - -func before(): - assert_str("suite before").override_failure_message("failed on before()").is_empty() - -func after(): - assert_str("suite after").is_equal("suite after") - -func before_test(): - assert_str("test before").is_equal("test before") - -func after_test(): - assert_str("test after").is_equal("test after") - -func test_case1(): - assert_str("test_case1").is_equal("test_case1") - -func test_case2(): - assert_str("test_case2").is_equal("test_case2") diff --git a/addons/gdUnit4/test/core/resources/testsuites/TestSuiteFailOnStageBeforeTest.resource b/addons/gdUnit4/test/core/resources/testsuites/TestSuiteFailOnStageBeforeTest.resource deleted file mode 100644 index b0b900d..0000000 --- a/addons/gdUnit4/test/core/resources/testsuites/TestSuiteFailOnStageBeforeTest.resource +++ /dev/null @@ -1,20 +0,0 @@ -# this test suite fails on stage before_test() -extends GdUnitTestSuite - -func before(): - assert_str("suite before").is_equal("suite before") - -func after(): - assert_str("suite after").is_equal("suite after") - -func before_test(): - assert_str("test before").override_failure_message("failed on before_test()").is_empty() - -func after_test(): - assert_str("test after").is_equal("test after") - -func test_case1(): - assert_str("test_case1").is_equal("test_case1") - -func test_case2(): - assert_str("test_case2").is_equal("test_case2") diff --git a/addons/gdUnit4/test/core/resources/testsuites/TestSuiteFailOnStageTestCase1.resource b/addons/gdUnit4/test/core/resources/testsuites/TestSuiteFailOnStageTestCase1.resource deleted file mode 100644 index 40a9a7a..0000000 --- a/addons/gdUnit4/test/core/resources/testsuites/TestSuiteFailOnStageTestCase1.resource +++ /dev/null @@ -1,20 +0,0 @@ -# this test suite fails on a single test case -extends GdUnitTestSuite - -func before(): - assert_str("suite before").is_equal("suite before") - -func after(): - assert_str("suite after").is_equal("suite after") - -func before_test(): - assert_str("test before").is_equal("test before") - -func after_test(): - assert_str("test after").is_equal("test after") - -func test_case1(): - assert_str("test_case1").override_failure_message("failed on test_case1()").is_empty() - -func test_case2(): - assert_str("test_case2").is_equal("test_case2") diff --git a/addons/gdUnit4/test/core/resources/testsuites/TestSuiteFuzzedMetricsTest.resource b/addons/gdUnit4/test/core/resources/testsuites/TestSuiteFuzzedMetricsTest.resource deleted file mode 100644 index ff0ccce..0000000 --- a/addons/gdUnit4/test/core/resources/testsuites/TestSuiteFuzzedMetricsTest.resource +++ /dev/null @@ -1,82 +0,0 @@ -# this test suite simulates long running test cases -extends GdUnitTestSuite -@warning_ignore('unused_parameter') - - -class TestCaseStatistics: - var _testcase_before_called := 0 - var _testcase_after_called := 0 - var _test_called := 0 - var _expected_calls :int - - func _init(expected_calls :int): - _expected_calls = expected_calls - - func count_test_before_test(): - _testcase_before_called +=1 - - func count_test_after_test(): - _testcase_after_called +=1 - - func count_test(): - _test_called += 1 - - -var _metrics = { - "test_execute_3times" : TestCaseStatistics.new(3), - "test_execute_5times" : TestCaseStatistics.new(5) -} - -var _stack : Array -var _before_called := 0 -var _after_called := 0 - - -func before(): - _before_called += 1 - # init the stack - _stack = [] - - -func after(): - _after_called += 1 - assert_that(_before_called)\ - .override_failure_message("Expecting 'before' is called only one times")\ - .is_equal(1) - assert_that(_after_called)\ - .override_failure_message("Expecting 'after' is called only one times")\ - .is_equal(1) - - for test_case in _metrics.keys(): - var statistics := _metrics[test_case] as TestCaseStatistics - assert_int(statistics._testcase_before_called)\ - .override_failure_message("Expect before_test called %s times but is %s for test case %s" % [statistics._expected_calls, statistics._testcase_before_called, test_case])\ - .is_equal(statistics._expected_calls) - assert_int(statistics._test_called)\ - .override_failure_message("Expect test called %s times but is %s for test case %s" % [statistics._expected_calls, statistics._test_called, test_case])\ - .is_equal(statistics._expected_calls) - assert_int(statistics._testcase_after_called)\ - .override_failure_message("Expect after_test called %s times but is %s for test case %s" % [statistics._expected_calls, statistics._testcase_after_called, test_case])\ - .is_equal(statistics._expected_calls) - - -func before_test(): - _metrics[__active_test_case].count_test_before_test() - # clean the stack before every test run - _stack.clear() - - -func after_test(): - _metrics[__active_test_case].count_test_after_test() - - -@warning_ignore('unused_parameter') -func test_execute_3times(fuzzer := Fuzzers.rangei(0, 1000), fuzzer_iterations = 3): - _metrics[__active_test_case].count_test() - pass - - -@warning_ignore('unused_parameter') -func test_execute_5times(fuzzer := Fuzzers.rangei(0, 1000), fuzzer_iterations = 5): - _metrics[__active_test_case].count_test() - pass diff --git a/addons/gdUnit4/test/core/resources/testsuites/TestSuiteInvalidParameterizedTests.resource b/addons/gdUnit4/test/core/resources/testsuites/TestSuiteInvalidParameterizedTests.resource deleted file mode 100644 index bdf871a..0000000 --- a/addons/gdUnit4/test/core/resources/testsuites/TestSuiteInvalidParameterizedTests.resource +++ /dev/null @@ -1,56 +0,0 @@ -class_name TestSuiteInvalidParameterizedTests -extends GdUnitTestSuite - -func test_no_parameters(): - assert_that(true).is_equal(true) - -@warning_ignore('unused_parameter') -func test_parameterized_success(a: int, b :int, c :int, expected :int, test_parameters := [ - [1, 2, 3, 6], - [3, 4, 5, 12], - [6, 7, 8, 21] ]): - - assert_that(a+b+c).is_equal(expected) - -@warning_ignore('unused_parameter') -func test_parameterized_failed(a: int, b :int, c :int, expected :int, test_parameters := [ - [1, 2, 3, 6], - [3, 4, 5, 11], - [6, 7, 8, 21] ]): - - assert_that(a+b+c).is_equal(expected) - -@warning_ignore('unused_parameter') -func test_parameterized_to_less_args(a: int, b :int, expected :int, test_parameters := [ - [1, 2, 3, 6], - [3, 4, 5, 11], - [6, 7, 8, 21] ]): - pass - -@warning_ignore('unused_parameter') -func test_parameterized_to_many_args(a: int, b :int, c :int, d :int, expected :int, test_parameters := [ - [1, 2, 3, 6], - [3, 4, 5, 11], - [6, 7, 8, 21] ]): - pass - -@warning_ignore('unused_parameter') -func test_parameterized_to_less_args_at_index_1(a: int, b :int, expected :int, test_parameters := [ - [1, 2, 6], - [3, 4, 5, 11], - [6, 7, 21] ]): - pass - -@warning_ignore('unused_parameter') -func test_parameterized_invalid_struct(a: int, b :int, expected :int, test_parameters := [ - [1, 2, 6], - ["foo"], - [6, 7, 21] ]): - pass - -@warning_ignore('unused_parameter') -func test_parameterized_invalid_args(a: int, b :int, expected :int, test_parameters := [ - [1, 2, 6], - [3, "4", 11], - [6, 7, 21] ]): - pass diff --git a/addons/gdUnit4/test/core/resources/testsuites/TestSuiteParameterizedMetricsTest.resource b/addons/gdUnit4/test/core/resources/testsuites/TestSuiteParameterizedMetricsTest.resource deleted file mode 100644 index 08c2d42..0000000 --- a/addons/gdUnit4/test/core/resources/testsuites/TestSuiteParameterizedMetricsTest.resource +++ /dev/null @@ -1,85 +0,0 @@ -extends GdUnitTestSuite - -@warning_ignore('unused_parameter') -@warning_ignore('return_value_discarded') - - -class TestCaseStatistics: - var _testcase_before_called := 0 - var _testcase_after_called := 0 - var _test_called := 0 - var _expected_calls :int - - func _init(expected_calls :int): - _expected_calls = expected_calls - - func count_test_before_test(): - _testcase_before_called +=1 - - func count_test_after_test(): - _testcase_after_called +=1 - - func count_test(): - _test_called += 1 - - -var _metrics = { - "test_parameterized_2times" : TestCaseStatistics.new(2), - "test_parameterized_5times" : TestCaseStatistics.new(5) -} - -var _before_called := 0 -var _after_called := 0 - - -func before(): - _before_called += 1 - - -func after(): - _after_called += 1 - assert_that(_before_called)\ - .override_failure_message("Expecting 'before' is called only one times")\ - .is_equal(1) - assert_that(_after_called)\ - .override_failure_message("Expecting 'after' is called only one times")\ - .is_equal(1) - - for test_case in _metrics.keys(): - var statistics := _metrics[test_case] as TestCaseStatistics - assert_int(statistics._testcase_before_called)\ - .override_failure_message("Expect before_test called %s times but is %s for test case %s" % [statistics._expected_calls, statistics._testcase_before_called, test_case])\ - .is_equal(statistics._expected_calls) - assert_int(statistics._test_called)\ - .override_failure_message("Expect test called %s times but is %s for test case %s" % [statistics._expected_calls, statistics._test_called, test_case])\ - .is_equal(statistics._expected_calls) - assert_int(statistics._testcase_after_called)\ - .override_failure_message("Expect after_test called %s times but is %s for test case %s" % [statistics._expected_calls, statistics._testcase_after_called, test_case])\ - .is_equal(statistics._expected_calls) - - -func before_test(): - _metrics[__active_test_case].count_test_before_test() - - -func after_test(): - _metrics[__active_test_case].count_test_after_test() - - -@warning_ignore('unused_parameter') -func test_parameterized_2times(a: int, expected :bool, test_parameters := [ - [0, false], - [1, true]]): - - _metrics[__active_test_case].count_test() - - -@warning_ignore('unused_parameter') -func test_parameterized_5times(a: int, expected :bool, test_parameters := [ - [0, false], - [1, true], - [0, false], - [1, true], - [1, true]]): - - _metrics[__active_test_case].count_test() diff --git a/addons/gdUnit4/test/core/resources/testsuites/TestSuiteParameterizedTests.resource b/addons/gdUnit4/test/core/resources/testsuites/TestSuiteParameterizedTests.resource deleted file mode 100644 index 3bb5409..0000000 --- a/addons/gdUnit4/test/core/resources/testsuites/TestSuiteParameterizedTests.resource +++ /dev/null @@ -1,148 +0,0 @@ -extends GdUnitTestSuite - -@warning_ignore('unused_parameter') -@warning_ignore('return_value_discarded') - - -class TestCaseStatistics: - var _testcase_before_called := 0 - var _testcase_after_called := 0 - var _expected_testcase_before :int - var _expected_testcase_after :int - - func _init(testcase_before_calls := 0, testcase_after_calls := 0): - _expected_testcase_before = testcase_before_calls - _expected_testcase_after = testcase_after_calls - - func count_test_before_test(): - _testcase_before_called +=1 - - func count_test_after_test(): - _testcase_after_called +=1 - - -var _metrics = { - "test_parameterized_bool_value" : TestCaseStatistics.new(2, 2), - "test_parameterized_int_values" : TestCaseStatistics.new(3, 3) -} - -var _before_called := 0 -var _after_called := 0 - - -func before(): - _before_called += 1 - - -func after(): - _after_called += 1 - assert_that(_before_called)\ - .override_failure_message("Expecting 'before' is called only one times")\ - .is_equal(1) - assert_that(_after_called)\ - .override_failure_message("Expecting 'after' is called only one times")\ - .is_equal(1) - - for test_case in _metrics.keys(): - var statistics := _metrics[test_case] as TestCaseStatistics - assert_int(statistics._testcase_before_called)\ - .override_failure_message("Expect before_test called %s times but is %s for test case %s" % [statistics._expected_testcase_before, statistics._testcase_before_called, test_case])\ - .is_equal(statistics._expected_testcase_before) - assert_int(statistics._testcase_after_called)\ - .override_failure_message("Expect after_test called %s times but is %s for test case %s" % [statistics._expected_testcase_after, statistics._testcase_after_called, test_case])\ - .is_equal(statistics._expected_testcase_after) - - -func before_test(): - if _metrics.has(__active_test_case): - _metrics[__active_test_case].count_test_before_test() - - -func after_test(): - if _metrics.has(__active_test_case): - _metrics[__active_test_case].count_test_after_test() - - -@warning_ignore('unused_parameter') -func test_parameterized_bool_value(a: int, expected :bool, test_parameters := [ - [0, false], - [1, true]]): - - assert_that(bool(a)).is_equal(expected) - -@warning_ignore('unused_parameter') -func test_parameterized_int_values(a: int, b :int, c :int, expected :int, test_parameters := [ - [1, 2, 3, 6], - [3, 4, 5, 12], - [6, 7, 8, 21] ]): - - assert_that(a+b+c).is_equal(expected) - -@warning_ignore('unused_parameter') -func test_parameterized_int_values_fail(a: int, b :int, c :int, expected :int, test_parameters := [ - [1, 2, 3, 6], - [3, 4, 5, 11], - [6, 7, 8, 22] ]): - - assert_that(a+b+c).is_equal(expected) - -@warning_ignore('unused_parameter') -func test_parameterized_float_values(a: float, b :float, expected :float, test_parameters := [ - [2.2, 2.2, 4.4], - [2.2, 2.3, 4.5], - [3.3, 2.2, 5.5] ]): - - assert_float(a+b).is_equal(expected) - -@warning_ignore('unused_parameter') -func test_parameterized_string_values(a: String, b :String, expected :String, test_parameters := [ - ["2.2", "2.2", "2.22.2"], - ["foo", "bar", "foobar"], - ["a", "b", "ab"] ]): - - assert_that(a+b).is_equal(expected) - -@warning_ignore('unused_parameter') -func test_parameterized_Vector2_values(a: Vector2, b :Vector2, expected :Vector2, test_parameters := [ - [Vector2.ONE, Vector2.ONE, Vector2(2, 2)], - [Vector2.LEFT, Vector2.RIGHT, Vector2.ZERO], - [Vector2.ZERO, Vector2.LEFT, Vector2.LEFT] ]): - - assert_that(a+b).is_equal(expected) - -@warning_ignore('unused_parameter') -func test_parameterized_Vector3_values(a: Vector3, b :Vector3, expected :Vector3, test_parameters := [ - [Vector3.ONE, Vector3.ONE, Vector3(2, 2, 2)], - [Vector3.LEFT, Vector3.RIGHT, Vector3.ZERO], - [Vector3.ZERO, Vector3.LEFT, Vector3.LEFT] ]): - - assert_that(a+b).is_equal(expected) - -class TestObj extends Resource: - var _value :String - - func _init(value :String): - _value = value - - func _to_string() -> String: - return _value - -@warning_ignore('unused_parameter') -func test_parameterized_obj_values(a: Object, b :Object, expected :String, test_parameters := [ - [TestObj.new("abc"), TestObj.new("def"), "abcdef"]]): - - assert_that(a.to_string()+b.to_string()).is_equal(expected) - - -@warning_ignore('unused_parameter') -func test_dictionary_div_number_types( - value : Dictionary, - expected : Dictionary, - test_parameters : Array = [ - [{ top = 50.0, bottom = 50.0, left = 50.0, right = 50.0}, { top = 50, bottom = 50, left = 50, right = 50}], - [{ top = 50.0, bottom = 50.0, left = 50.0, right = 50.0}, { top = 50.0, bottom = 50.0, left = 50.0, right = 50.0}], - [{ top = 50, bottom = 50, left = 50, right = 50}, { top = 50.0, bottom = 50.0, left = 50.0, right = 50.0}], - [{ top = 50, bottom = 50, left = 50, right = 50}, { top = 50, bottom = 50, left = 50, right = 50}], - ] - ) -> void: - assert_that(value).is_equal(expected) diff --git a/addons/gdUnit4/test/core/resources/testsuites/TestSuiteSkipped.resource b/addons/gdUnit4/test/core/resources/testsuites/TestSuiteSkipped.resource deleted file mode 100644 index 994bca0..0000000 --- a/addons/gdUnit4/test/core/resources/testsuites/TestSuiteSkipped.resource +++ /dev/null @@ -1,20 +0,0 @@ -extends GdUnitTestSuite - - -func is_skipped() -> bool: - return true - - -@warning_ignore('unused_parameter') -func before(do_skip=is_skipped(), skip_reason="do not run this"): - pass - - -@warning_ignore('unused_parameter') -func test_case1(timeout = 1000, do_skip=1==1, skip_reason="do not run this"): - pass - - -@warning_ignore('unused_parameter') -func test_case2(skip_reason="ignored"): - pass diff --git a/addons/gdUnit4/test/core/resources/testsuites/TestSuiteWithoutTests.gd b/addons/gdUnit4/test/core/resources/testsuites/TestSuiteWithoutTests.gd deleted file mode 100644 index 9d1564e..0000000 --- a/addons/gdUnit4/test/core/resources/testsuites/TestSuiteWithoutTests.gd +++ /dev/null @@ -1,9 +0,0 @@ -extends GdUnitTestSuite - - -func before(): - pass - - -func foo(): - pass diff --git a/addons/gdUnit4/test/core/templates/test_suite/GdUnitTestSuiteTemplateTest.gd b/addons/gdUnit4/test/core/templates/test_suite/GdUnitTestSuiteTemplateTest.gd deleted file mode 100644 index 50fc582..0000000 --- a/addons/gdUnit4/test/core/templates/test_suite/GdUnitTestSuiteTemplateTest.gd +++ /dev/null @@ -1,97 +0,0 @@ -# GdUnit generated TestSuite -#warning-ignore-all:unused_argument -#warning-ignore-all:return_value_discarded -class_name GdUnitTestSuiteTemplateTest -extends GdUnitTestSuite - -# TestSuite generated from -const __source = 'res://addons/gdUnit4/src/core/templates/test_suite/GdUnitTestSuiteTemplate.gd' - -const CUSTOM_TEMPLATE = """ - # GdUnit generated TestSuite - class_name ${suite_class_name} - extends GdUnitTestSuite - @warning_ignore('unused_parameter') - @warning_ignore('return_value_discarded') - - func before() -> void: - var ${source_var}_1 := ${source_class}.new() - var ${source_var}_2 = load("${source_resource_path}") -""" - - -func after() -> void: - GdUnitTestSuiteTemplate.reset_to_default(GdUnitTestSuiteTemplate.TEMPLATE_ID_GD) - - -func test_default_template() -> void: - assert_str(GdUnitTestSuiteTemplate.default_template(GdUnitTestSuiteTemplate.TEMPLATE_ID_GD)).is_equal(GdUnitTestSuiteTemplate.default_GD_template()) - - -func test_build_template_default() -> void: - var template := GdUnitTestSuiteTemplate.build_template("res://addons/gdUnit4/test/core/resources/script_with_class_name.gd") - var expected := """ - # GdUnit generated TestSuite - class_name ScriptWithClassNameTest - extends GdUnitTestSuite - @warning_ignore('unused_parameter') - @warning_ignore('return_value_discarded') - - # TestSuite generated from - const __source = 'res://addons/gdUnit4/test/core/resources/script_with_class_name.gd' - """.dedent().trim_prefix("\n") - assert_str(template).is_equal(expected) - - -# checked source with class_name definition -func test_build_template_custom1() -> void: - GdUnitTestSuiteTemplate.save_template(GdUnitTestSuiteTemplate.TEMPLATE_ID_GD, CUSTOM_TEMPLATE) - var template := GdUnitTestSuiteTemplate.build_template("res://addons/gdUnit4/test/core/resources/script_with_class_name.gd") - var expected := """ - # GdUnit generated TestSuite - class_name ScriptWithClassNameTest - extends GdUnitTestSuite - @warning_ignore('unused_parameter') - @warning_ignore('return_value_discarded') - - func before() -> void: - var script_with_class_name_1 := ScriptWithClassName.new() - var script_with_class_name_2 = load("res://addons/gdUnit4/test/core/resources/script_with_class_name.gd") - """.dedent().trim_prefix("\n") - assert_str(template).is_equal(expected) - - -# checked source without class_name definition -func test_build_template_custom2() -> void: - GdUnitTestSuiteTemplate.save_template(GdUnitTestSuiteTemplate.TEMPLATE_ID_GD, CUSTOM_TEMPLATE) - var template := GdUnitTestSuiteTemplate.build_template("res://addons/gdUnit4/test/core/resources/script_without_class_name.gd") - var expected := """ - # GdUnit generated TestSuite - class_name ScriptWithoutClassNameTest - extends GdUnitTestSuite - @warning_ignore('unused_parameter') - @warning_ignore('return_value_discarded') - - func before() -> void: - var script_without_class_name_1 := ScriptWithoutClassName.new() - var script_without_class_name_2 = load("res://addons/gdUnit4/test/core/resources/script_without_class_name.gd") - """.dedent().trim_prefix("\n") - assert_str(template).is_equal(expected) - - -# checked source with class_name definition pascal_case -func test_build_template_custom3() -> void: - GdUnitTestSuiteTemplate.save_template(GdUnitTestSuiteTemplate.TEMPLATE_ID_GD, CUSTOM_TEMPLATE) - var template := GdUnitTestSuiteTemplate.build_template("res://addons/gdUnit4/test/core/resources/naming_conventions/PascalCaseWithClassName.gd") - var expected := """ - # GdUnit generated TestSuite - class_name PascalCaseWithClassNameTest - extends GdUnitTestSuite - @warning_ignore('unused_parameter') - @warning_ignore('return_value_discarded') - - func before() -> void: - var pascal_case_with_class_name_1 := PascalCaseWithClassName.new() - var pascal_case_with_class_name_2 = load("res://addons/gdUnit4/test/core/resources/naming_conventions/PascalCaseWithClassName.gd") - """.dedent().trim_prefix("\n") - assert_str(template).is_equal(expected) diff --git a/addons/gdUnit4/test/extractors/GdUnitFuncValueExtractorTest.gd b/addons/gdUnit4/test/extractors/GdUnitFuncValueExtractorTest.gd deleted file mode 100644 index e79b0fc..0000000 --- a/addons/gdUnit4/test/extractors/GdUnitFuncValueExtractorTest.gd +++ /dev/null @@ -1,67 +0,0 @@ -# GdUnit generated TestSuite -class_name GdUnitFuncValueExtractorTest -extends GdUnitTestSuite - -# TestSuite generated from -const __source = 'res://addons/gdUnit4/src/extractors/GdUnitFuncValueExtractor.gd' -const GdUnitFuncValueExtractor = preload("res://addons/gdUnit4/src/extractors/GdUnitFuncValueExtractor.gd") - - -class TestNode extends Resource: - var _parent = null - var _children := Array() - - func _init(name :String,parent = null): - set_name(name) - _parent = parent - if _parent: - _parent._children.append(self) - - - func _notification(what): - if what == NOTIFICATION_PREDELETE: - _parent = null - _children.clear() - - - func get_parent() -> TestNode: - return _parent - - - func get_children() -> Array: - return _children - - - -func test_extract_value_success() -> void: - var node = auto_free(TestNode.new("node_a")) - - assert_str(GdUnitFuncValueExtractor.new("get_name", []).extract_value(node)).is_equal("node_a") - - -func test_extract_value_func_not_exists() -> void: - var node = TestNode.new("node_a") - - assert_str(GdUnitFuncValueExtractor.new("get_foo", []).extract_value(node)).is_equal("n.a.") - - -func test_extract_value_on_null_value() -> void: - assert_str(GdUnitFuncValueExtractor.new("get_foo", []).extract_value(null)).is_null() - - -func test_extract_value_chanined() -> void: - var parent = TestNode.new("parent") - var node = auto_free(TestNode.new("node_a", parent)) - - assert_str(GdUnitFuncValueExtractor.new("get_name", []).extract_value(node)).is_equal("node_a") - assert_str(GdUnitFuncValueExtractor.new("get_parent.get_name", []).extract_value(node)).is_equal("parent") - - -func test_extract_value_chanined_array_values() -> void: - var parent = TestNode.new("parent") - auto_free(TestNode.new("node_a", parent)) - auto_free(TestNode.new("node_b", parent)) - auto_free(TestNode.new("node_c", parent)) - - assert_array(GdUnitFuncValueExtractor.new("get_children.get_name", []).extract_value(parent))\ - .contains_exactly(["node_a", "node_b", "node_c"]) diff --git a/addons/gdUnit4/test/fuzzers/GdUnitFuzzerValueInjectionTest.gd b/addons/gdUnit4/test/fuzzers/GdUnitFuzzerValueInjectionTest.gd deleted file mode 100644 index f4dc7ba..0000000 --- a/addons/gdUnit4/test/fuzzers/GdUnitFuzzerValueInjectionTest.gd +++ /dev/null @@ -1,161 +0,0 @@ -extends GdUnitTestSuite - -var _current_iterations : Dictionary -var _expected_iterations: Dictionary - -# a simple test fuzzer where provided a hard coded value set -class TestFuzzer extends Fuzzer: - var _data := [0, 1, 2, 3, 4, 5, 6, 23, 8, 9] - - - func next_value(): - return _data.pop_front() - - -func max_value() -> int: - return 10 - - -func min_value() -> int: - return 1 - - -func fuzzer() -> Fuzzer: - return Fuzzers.rangei(min_value(), max_value()) - - -func before(): - # define expected iteration count - _expected_iterations = { - "test_fuzzer_has_same_instance_peer_iteration" : 10, - "test_multiple_fuzzers_inject_value_with_seed" : 10, - "test_fuzzer_iterations_default" : Fuzzer.ITERATION_DEFAULT_COUNT, - "test_fuzzer_iterations_custom_value" : 234, - "test_fuzzer_inject_value" : 100, - "test_multiline_fuzzer_args": 23, - } - # inital values - _current_iterations = { - "test_fuzzer_has_same_instance_peer_iteration" : 0, - "test_multiple_fuzzers_inject_value_with_seed" : 0, - "test_fuzzer_iterations_default" : 0, - "test_fuzzer_iterations_custom_value" : 0, - "test_fuzzer_inject_value" : 0, - "test_multiline_fuzzer_args": 0, - } - - -func after(): - for test_case in _expected_iterations.keys(): - var current = _current_iterations[test_case] - var expected = _expected_iterations[test_case] - - assert_int(current).override_failure_message("Expecting %s itertions but is %s checked test case %s" % [expected, current, test_case]).is_equal(expected) - -var _fuzzer_instance_before : Fuzzer = null - - -@warning_ignore("shadowed_variable", "unused_parameter") -func test_fuzzer_has_same_instance_peer_iteration(fuzzer=TestFuzzer.new(), fuzzer_iterations = 10): - _current_iterations["test_fuzzer_has_same_instance_peer_iteration"] += 1 - assert_object(fuzzer).is_not_null() - if _fuzzer_instance_before != null: - assert_that(fuzzer).is_same(_fuzzer_instance_before) - _fuzzer_instance_before = fuzzer - - -@warning_ignore("shadowed_variable", "unused_parameter") -func test_fuzzer_iterations_default(fuzzer := Fuzzers.rangei(-23, 22)): - _current_iterations["test_fuzzer_iterations_default"] += 1 - assert_object(fuzzer).is_not_null() - - -@warning_ignore("shadowed_variable", "unused_parameter") -func test_fuzzer_iterations_custom_value(fuzzer := Fuzzers.rangei(-23, 22), fuzzer_iterations = 234, fuzzer_seed = 100): - _current_iterations["test_fuzzer_iterations_custom_value"] += 1 - - -@warning_ignore("shadowed_variable", "unused_parameter") -func test_fuzzer_inject_value(fuzzer := Fuzzers.rangei(-23, 22), fuzzer_iterations = 100): - _current_iterations["test_fuzzer_inject_value"] += 1 - assert_object(fuzzer).is_not_null() - assert_int(fuzzer.next_value()).is_between(-23, 22) - - -@warning_ignore("shadowed_variable", "unused_parameter") -func test_fuzzer_with_timeout(fuzzer := Fuzzers.rangei(-23, 22), fuzzer_iterations = 20, timeout = 100): - discard_error_interupted_by_timeout() - assert_int(fuzzer.next_value()).is_between(-23, 22) - - if fuzzer.iteration_index() == 10: - await await_millis(100) - # we not expect more than 10 iterations it should be interuptead by a timeout - assert_int(fuzzer.iteration_index()).is_less_equal(10) - -var expected_value := [22, 3, -14, -16, 21, 20, 4, -23, -19, -5] - - -@warning_ignore("shadowed_variable", "unused_parameter") -func test_fuzzer_inject_value_with_seed(fuzzer := Fuzzers.rangei(-23, 22), fuzzer_iterations = 10, fuzzer_seed = 187772): - assert_object(fuzzer).is_not_null() - var iteration_index = fuzzer.iteration_index()-1 - var current = fuzzer.next_value() - var expected = expected_value[iteration_index] - assert_int(iteration_index).is_between(0, 9).is_less(10) - assert_int(current)\ - .override_failure_message("Expect value %s checked test iteration %s\n but was %s" % [expected, iteration_index, current])\ - .is_equal(expected) - -var expected_value_a := [22, -14, 21, 4, -19, -11, 5, 21, -6, -9] -var expected_value_b := [35, 38, 34, 39, 35, 41, 37, 35, 34, 39] - - -@warning_ignore("shadowed_variable", "unused_parameter") -func test_multiple_fuzzers_inject_value_with_seed(fuzzer_a := Fuzzers.rangei(-23, 22), fuzzer_b := Fuzzers.rangei(33, 44), fuzzer_iterations = 10, fuzzer_seed = 187772): - _current_iterations["test_multiple_fuzzers_inject_value_with_seed"] += 1 - assert_object(fuzzer_a).is_not_null() - assert_object(fuzzer_b).is_not_null() - var iteration_index_a = fuzzer_a.iteration_index()-1 - var current_a = fuzzer_a.next_value() - var expected_a = expected_value_a[iteration_index_a] - assert_int(iteration_index_a).is_between(0, 9).is_less(10) - assert_int(current_a).is_between(-23, 22) - assert_int(current_a)\ - .override_failure_message("Expect value %s checked test iteration %s\n but was %s" % [expected_a, iteration_index_a, current_a])\ - .is_equal(expected_a) - var iteration_index_b = fuzzer_b.iteration_index()-1 - var current_b = fuzzer_b.next_value() - var expected_b = expected_value_b[iteration_index_b] - assert_int(iteration_index_b).is_between(0, 9).is_less(10) - assert_int(current_b).is_between(33, 44) - assert_int(current_b)\ - .override_failure_message("Expect value %s checked test iteration %s\n but was %s" % [expected_b, iteration_index_b, current_b])\ - .is_equal(expected_b) - - -@warning_ignore("shadowed_variable", "unused_parameter") -func test_fuzzer_error_after_eight_iterations(fuzzer=TestFuzzer.new(), fuzzer_iterations = 10): - assert_object(fuzzer).is_not_null() - # should fail after 8 iterations - if fuzzer.iteration_index() == 8: - assert_failure(func(): assert_int(fuzzer.next_value()).is_between(0, 9)) \ - .is_failed() \ - .has_message("Expecting:\n '23'\n in range between\n '0' <> '9'") - else: - assert_int(fuzzer.next_value()).is_between(0, 9) - - -@warning_ignore("shadowed_variable", "unused_parameter") -func test_fuzzer_custom_func(fuzzer=fuzzer()): - assert_object(fuzzer).is_not_null() - assert_int(fuzzer.next_value()).is_between(1, 10) - - -@warning_ignore("shadowed_variable", "unused_parameter") -func test_multiline_fuzzer_args( - fuzzer_a := Fuzzers.rangev2(Vector2(-47, -47), Vector2(47, 47)), - fuzzer_b := Fuzzers.rangei(0, 9), - fuzzer_iterations = 23): - assert_object(fuzzer_a).is_not_null() - assert_object(fuzzer_b).is_not_null() - _current_iterations["test_multiline_fuzzer_args"] += 1 diff --git a/addons/gdUnit4/test/fuzzers/StringFuzzerTest.gd b/addons/gdUnit4/test/fuzzers/StringFuzzerTest.gd deleted file mode 100644 index 388dc97..0000000 --- a/addons/gdUnit4/test/fuzzers/StringFuzzerTest.gd +++ /dev/null @@ -1,34 +0,0 @@ -# GdUnit generated TestSuite -class_name StringFuzzerTest -extends GdUnitTestSuite - -# TestSuite generated from -const __source = 'res://addons/gdUnit4/src/fuzzers/StringFuzzer.gd' - - -func test_extract_charset() -> void: - assert_str(StringFuzzer.extract_charset("abc").get_string_from_ascii()).is_equal("abc") - assert_str(StringFuzzer.extract_charset("abcDXG").get_string_from_ascii()).is_equal("abcDXG") - assert_str(StringFuzzer.extract_charset("a-c").get_string_from_ascii()).is_equal("abc") - assert_str(StringFuzzer.extract_charset("a-z").get_string_from_ascii()).is_equal("abcdefghijklmnopqrstuvwxyz") - assert_str(StringFuzzer.extract_charset("A-Z").get_string_from_ascii()).is_equal("ABCDEFGHIJKLMNOPQRSTUVWXYZ") - - # range token at start - assert_str(StringFuzzer.extract_charset("-a-dA-D2-8+_").get_string_from_ascii()).is_equal("-abcdABCD2345678+_") - # range token at end - assert_str(StringFuzzer.extract_charset("a-dA-D2-8+_-").get_string_from_ascii()).is_equal("abcdABCD2345678+_-") - # range token in the middle - assert_str(StringFuzzer.extract_charset("a-d-A-D2-8+_").get_string_from_ascii()).is_equal("abcd-ABCD2345678+_") - - -func test_next_value() -> void: - var pattern := "a-cD-X+2-5" - var fuzzer := StringFuzzer.new(4, 128, pattern) - var r := RegEx.new() - r.compile("[%s]+" % pattern) - for i in 100: - var value :String = fuzzer.next_value() - # verify the generated value has a length in the configured min/max range - assert_int(value.length()).is_between(4, 128) - # using regex to remove_at all expected chars to verify the value only containing expected chars by is empty - assert_str(r.sub(value, "")).is_empty() diff --git a/addons/gdUnit4/test/fuzzers/TestExternalFuzzer.gd b/addons/gdUnit4/test/fuzzers/TestExternalFuzzer.gd deleted file mode 100644 index c88917f..0000000 --- a/addons/gdUnit4/test/fuzzers/TestExternalFuzzer.gd +++ /dev/null @@ -1,10 +0,0 @@ -class_name TestExternalFuzzer -extends Fuzzer - - -func _init(): - pass - - -func next_value() -> Variant: - return {} diff --git a/addons/gdUnit4/test/fuzzers/TestFuzzers.gd b/addons/gdUnit4/test/fuzzers/TestFuzzers.gd deleted file mode 100644 index 3700678..0000000 --- a/addons/gdUnit4/test/fuzzers/TestFuzzers.gd +++ /dev/null @@ -1,27 +0,0 @@ -extends RefCounted - -const MIN_VALUE := -10 -const MAX_VALUE := 22 - -class NestedFuzzer extends Fuzzer: - - func _init(): - pass - - func next_value() -> Variant: - return {} - - static func _s_max_value() -> int: - return MAX_VALUE - - -func min_value() -> int: - return MIN_VALUE - - -func get_fuzzer() -> Fuzzer: - return Fuzzers.rangei(min_value(), NestedFuzzer._s_max_value()) - - -func non_fuzzer() -> Resource: - return Image.new() diff --git a/addons/gdUnit4/test/matchers/AnyArgumentMatcherTest.gd b/addons/gdUnit4/test/matchers/AnyArgumentMatcherTest.gd deleted file mode 100644 index 7707986..0000000 --- a/addons/gdUnit4/test/matchers/AnyArgumentMatcherTest.gd +++ /dev/null @@ -1,29 +0,0 @@ -# GdUnit generated TestSuite -class_name AnyArgumentMatcherTest -extends GdUnitTestSuite - -# TestSuite generated from -const __source = 'res://addons/gdUnit4/src/matchers/AnyArgumentMatcher.gd' - - -func test_is_match(): - var matcher := AnyArgumentMatcher.new() - - assert_bool(matcher.is_match(null)).is_true() - assert_bool(matcher.is_match("")).is_true() - assert_bool(matcher.is_match("abc")).is_true() - assert_bool(matcher.is_match(true)).is_true() - assert_bool(matcher.is_match(false)).is_true() - assert_bool(matcher.is_match(0)).is_true() - assert_bool(matcher.is_match(100010)).is_true() - assert_bool(matcher.is_match(1.2)).is_true() - assert_bool(matcher.is_match(RefCounted.new())).is_true() - assert_bool(matcher.is_match(auto_free(Node.new()))).is_true() - - -func test_any(): - assert_object(any()).is_instanceof(AnyArgumentMatcher) - - -func test_to_string() -> void: - assert_str(str(any())).is_equal("any()") diff --git a/addons/gdUnit4/test/matchers/AnyBuildInTypeArgumentMatcherTest.gd b/addons/gdUnit4/test/matchers/AnyBuildInTypeArgumentMatcherTest.gd deleted file mode 100644 index a5448f5..0000000 --- a/addons/gdUnit4/test/matchers/AnyBuildInTypeArgumentMatcherTest.gd +++ /dev/null @@ -1,228 +0,0 @@ -# GdUnit generated TestSuite -class_name AnyBuildInTypeArgumentMatcherTest -extends GdUnitTestSuite - -# TestSuite generated from -const __source = 'res://addons/gdUnit4/src/matchers/AnyBuildInTypeArgumentMatcher.gd' - - -func test_is_match_bool() -> void: - assert_object(any_bool()).is_instanceof(AnyBuildInTypeArgumentMatcher) - - var matcher := any_bool() - assert_bool(matcher.is_match(true)).is_true() - assert_bool(matcher.is_match(false)).is_true() - assert_bool(matcher.is_match(null)).is_false() - assert_bool(matcher.is_match("")).is_false() - assert_bool(matcher.is_match(0)).is_false() - assert_bool(matcher.is_match(0.2)).is_false() - assert_bool(matcher.is_match(auto_free(Node.new()))).is_false() - - -func test_is_match_int() -> void: - assert_object(any_int()).is_instanceof(AnyBuildInTypeArgumentMatcher) - - var matcher := any_int() - assert_bool(matcher.is_match(0)).is_true() - assert_bool(matcher.is_match(1000)).is_true() - assert_bool(matcher.is_match(null)).is_false() - assert_bool(matcher.is_match("")).is_false() - assert_bool(matcher.is_match([])).is_false() - assert_bool(matcher.is_match(0.2)).is_false() - assert_bool(matcher.is_match(auto_free(Node.new()))).is_false() - - -func test_is_match_float() -> void: - assert_object(any_float()).is_instanceof(AnyBuildInTypeArgumentMatcher) - - var matcher := any_float() - assert_bool(matcher.is_match(.0)).is_true() - assert_bool(matcher.is_match(0.0)).is_true() - assert_bool(matcher.is_match(null)).is_false() - assert_bool(matcher.is_match("")).is_false() - assert_bool(matcher.is_match([])).is_false() - assert_bool(matcher.is_match(1000)).is_false() - assert_bool(matcher.is_match(auto_free(Node.new()))).is_false() - - -func test_is_match_string() -> void: - assert_object(any_string()).is_instanceof(AnyBuildInTypeArgumentMatcher) - - var matcher := any_string() - assert_bool(matcher.is_match("")).is_true() - assert_bool(matcher.is_match("abc")).is_true() - assert_bool(matcher.is_match(0)).is_false() - assert_bool(matcher.is_match(1000)).is_false() - assert_bool(matcher.is_match(null)).is_false() - assert_bool(matcher.is_match([])).is_false() - assert_bool(matcher.is_match(0.2)).is_false() - assert_bool(matcher.is_match(auto_free(Node.new()))).is_false() - - -func test_is_match_color() -> void: - assert_object(any_color()).is_instanceof(AnyBuildInTypeArgumentMatcher) - - var matcher := any_color() - assert_bool(matcher.is_match("")).is_false() - assert_bool(matcher.is_match("abc")).is_false() - assert_bool(matcher.is_match(0)).is_false() - assert_bool(matcher.is_match(1000)).is_false() - assert_bool(matcher.is_match(null)).is_false() - assert_bool(matcher.is_match(Color.ALICE_BLUE)).is_true() - assert_bool(matcher.is_match(Color.RED)).is_true() - - -func test_is_match_vector() -> void: - assert_object(any_vector()).is_instanceof(AnyBuildInTypeArgumentMatcher) - - var matcher := any_vector() - assert_bool(matcher.is_match(Vector2.ONE)).is_true() - assert_bool(matcher.is_match(Vector2i.ONE)).is_true() - assert_bool(matcher.is_match(Vector3.ONE)).is_true() - assert_bool(matcher.is_match(Vector3i.ONE)).is_true() - assert_bool(matcher.is_match(Vector4.ONE)).is_true() - assert_bool(matcher.is_match(Vector4i.ONE)).is_true() - - assert_bool(matcher.is_match("")).is_false() - assert_bool(matcher.is_match("abc")).is_false() - assert_bool(matcher.is_match(0)).is_false() - assert_bool(matcher.is_match(1000)).is_false() - assert_bool(matcher.is_match(null)).is_false() - - -func test_is_match_vector2() -> void: - assert_object(any_vector2()).is_instanceof(AnyBuildInTypeArgumentMatcher) - - var matcher := any_vector2() - assert_bool(matcher.is_match(Vector2.ONE)).is_true() - assert_bool(matcher.is_match("")).is_false() - assert_bool(matcher.is_match("abc")).is_false() - assert_bool(matcher.is_match(0)).is_false() - assert_bool(matcher.is_match(1000)).is_false() - assert_bool(matcher.is_match(null)).is_false() - assert_bool(matcher.is_match(Vector2i.ONE)).is_false() - assert_bool(matcher.is_match(Vector3.ONE)).is_false() - assert_bool(matcher.is_match(Vector3i.ONE)).is_false() - assert_bool(matcher.is_match(Vector4.ONE)).is_false() - assert_bool(matcher.is_match(Vector4i.ONE)).is_false() - - -func test_is_match_vector2i() -> void: - assert_object(any_vector2i()).is_instanceof(AnyBuildInTypeArgumentMatcher) - - var matcher := any_vector2i() - assert_bool(matcher.is_match(Vector2i.ONE)).is_true() - assert_bool(matcher.is_match("")).is_false() - assert_bool(matcher.is_match("abc")).is_false() - assert_bool(matcher.is_match(0)).is_false() - assert_bool(matcher.is_match(1000)).is_false() - assert_bool(matcher.is_match(null)).is_false() - assert_bool(matcher.is_match(Vector2.ONE)).is_false() - assert_bool(matcher.is_match(Vector3.ONE)).is_false() - assert_bool(matcher.is_match(Vector3i.ONE)).is_false() - assert_bool(matcher.is_match(Vector4.ONE)).is_false() - assert_bool(matcher.is_match(Vector4i.ONE)).is_false() - - -func test_is_match_vector3() -> void: - assert_object(any_vector3()).is_instanceof(AnyBuildInTypeArgumentMatcher) - - var matcher := any_vector3() - assert_bool(matcher.is_match(Vector3.ONE)).is_true() - assert_bool(matcher.is_match("")).is_false() - assert_bool(matcher.is_match("abc")).is_false() - assert_bool(matcher.is_match(0)).is_false() - assert_bool(matcher.is_match(1000)).is_false() - assert_bool(matcher.is_match(null)).is_false() - assert_bool(matcher.is_match(Vector2.ONE)).is_false() - assert_bool(matcher.is_match(Vector2i.ONE)).is_false() - assert_bool(matcher.is_match(Vector3i.ONE)).is_false() - assert_bool(matcher.is_match(Vector4.ONE)).is_false() - assert_bool(matcher.is_match(Vector4i.ONE)).is_false() - - -func test_is_match_vector3i() -> void: - assert_object(any_vector3i()).is_instanceof(AnyBuildInTypeArgumentMatcher) - - var matcher := any_vector3i() - assert_bool(matcher.is_match(Vector3i.ONE)).is_true() - assert_bool(matcher.is_match("")).is_false() - assert_bool(matcher.is_match("abc")).is_false() - assert_bool(matcher.is_match(0)).is_false() - assert_bool(matcher.is_match(1000)).is_false() - assert_bool(matcher.is_match(null)).is_false() - assert_bool(matcher.is_match(Vector2.ONE)).is_false() - assert_bool(matcher.is_match(Vector2i.ONE)).is_false() - assert_bool(matcher.is_match(Vector3.ONE)).is_false() - assert_bool(matcher.is_match(Vector4.ONE)).is_false() - assert_bool(matcher.is_match(Vector4i.ONE)).is_false() - - -func test_is_match_vector4() -> void: - assert_object(any_vector4()).is_instanceof(AnyBuildInTypeArgumentMatcher) - - var matcher := any_vector4() - assert_bool(matcher.is_match(Vector4.ONE)).is_true() - assert_bool(matcher.is_match("")).is_false() - assert_bool(matcher.is_match("abc")).is_false() - assert_bool(matcher.is_match(0)).is_false() - assert_bool(matcher.is_match(1000)).is_false() - assert_bool(matcher.is_match(null)).is_false() - assert_bool(matcher.is_match(Vector2.ONE)).is_false() - assert_bool(matcher.is_match(Vector2i.ONE)).is_false() - assert_bool(matcher.is_match(Vector3.ONE)).is_false() - assert_bool(matcher.is_match(Vector3i.ONE)).is_false() - assert_bool(matcher.is_match(Vector4i.ONE)).is_false() - - -func test_is_match_vector4i() -> void: - assert_object(any_vector4i()).is_instanceof(AnyBuildInTypeArgumentMatcher) - - var matcher := any_vector4i() - assert_bool(matcher.is_match(Vector4i.ONE)).is_true() - assert_bool(matcher.is_match("")).is_false() - assert_bool(matcher.is_match("abc")).is_false() - assert_bool(matcher.is_match(0)).is_false() - assert_bool(matcher.is_match(1000)).is_false() - assert_bool(matcher.is_match(null)).is_false() - assert_bool(matcher.is_match(Vector2.ONE)).is_false() - assert_bool(matcher.is_match(Vector2i.ONE)).is_false() - assert_bool(matcher.is_match(Vector3.ONE)).is_false() - assert_bool(matcher.is_match(Vector3i.ONE)).is_false() - assert_bool(matcher.is_match(Vector4.ONE)).is_false() - - -func test_to_string() -> void: - assert_str(str(any_bool())).is_equal("any_bool()") - assert_str(str(any_int())).is_equal("any_int()") - assert_str(str(any_float())).is_equal("any_float()") - assert_str(str(any_string())).is_equal("any_string()") - assert_str(str(any_color())).is_equal("any_color()") - assert_str(str(any_vector())).is_equal("any_vector()") - assert_str(str(any_vector2())).is_equal("any_vector2()") - assert_str(str(any_vector2i())).is_equal("any_vector2i()") - assert_str(str(any_vector3())).is_equal("any_vector3()") - assert_str(str(any_vector3i())).is_equal("any_vector3i()") - assert_str(str(any_vector4())).is_equal("any_vector4()") - assert_str(str(any_vector4i())).is_equal("any_vector4i()") - assert_str(str(any_rect2())).is_equal("any_rect2()") - assert_str(str(any_plane())).is_equal("any_plane()") - assert_str(str(any_quat())).is_equal("any_quat()") - assert_str(str(any_quat())).is_equal("any_quat()") - assert_str(str(any_basis())).is_equal("any_basis()") - assert_str(str(any_transform_2d())).is_equal("any_transform_2d()") - assert_str(str(any_transform_3d())).is_equal("any_transform_3d()") - assert_str(str(any_node_path())).is_equal("any_node_path()") - assert_str(str(any_rid())).is_equal("any_rid()") - assert_str(str(any_object())).is_equal("any_object()") - assert_str(str(any_dictionary())).is_equal("any_dictionary()") - assert_str(str(any_array())).is_equal("any_array()") - assert_str(str(any_packed_byte_array())).is_equal("any_packed_byte_array()") - assert_str(str(any_packed_int32_array())).is_equal("any_packed_int32_array()") - assert_str(str(any_packed_int64_array())).is_equal("any_packed_int64_array()") - assert_str(str(any_packed_float32_array())).is_equal("any_packed_float32_array()") - assert_str(str(any_packed_float64_array())).is_equal("any_packed_float64_array()") - assert_str(str(any_packed_string_array())).is_equal("any_packed_string_array()") - assert_str(str(any_packed_vector2_array())).is_equal("any_packed_vector2_array()") - assert_str(str(any_packed_vector3_array())).is_equal("any_packed_vector3_array()") - assert_str(str(any_packed_color_array())).is_equal("any_packed_color_array()") diff --git a/addons/gdUnit4/test/matchers/AnyClazzArgumentMatcherTest.gd b/addons/gdUnit4/test/matchers/AnyClazzArgumentMatcherTest.gd deleted file mode 100644 index 3670c05..0000000 --- a/addons/gdUnit4/test/matchers/AnyClazzArgumentMatcherTest.gd +++ /dev/null @@ -1,43 +0,0 @@ -# GdUnit generated TestSuite -class_name AnyClazzArgumentMatcherTest -extends GdUnitTestSuite - -# TestSuite generated from -const __source = 'res://addons/gdUnit4/src/matchers/AnyClazzArgumentMatcher.gd' - - -func test_is_match_reference(): - var matcher := AnyClazzArgumentMatcher.new(RefCounted) - - assert_bool(matcher.is_match(Resource.new())).is_true() - assert_bool(matcher.is_match(RefCounted.new())).is_true() - assert_bool(matcher.is_match(auto_free(Node.new()))).is_false() - assert_bool(matcher.is_match(null)).is_false() - assert_bool(matcher.is_match(0)).is_false() - assert_bool(matcher.is_match(false)).is_false() - assert_bool(matcher.is_match(true)).is_false() - - -func test_is_match_node(): - var matcher := AnyClazzArgumentMatcher.new(Node) - - assert_bool(matcher.is_match(auto_free(Node.new()))).is_true() - assert_bool(matcher.is_match(auto_free(AnimationPlayer.new()))).is_true() - assert_bool(matcher.is_match(auto_free(Timer.new()))).is_true() - assert_bool(matcher.is_match(Resource.new())).is_false() - assert_bool(matcher.is_match(RefCounted.new())).is_false() - assert_bool(matcher.is_match(null)).is_false() - assert_bool(matcher.is_match(0)).is_false() - assert_bool(matcher.is_match(false)).is_false() - assert_bool(matcher.is_match(true)).is_false() - - -func test_any_class(): - assert_object(any_class(Node)).is_instanceof(AnyClazzArgumentMatcher) - - -func test_to_string() -> void: - assert_str(str(any_class(Node))).is_equal("any_class()") - assert_str(str(any_class(Object))).is_equal("any_class()") - assert_str(str(any_class(RefCounted))).is_equal("any_class()") - assert_str(str(any_class(GdObjects))).is_equal("any_class()") diff --git a/addons/gdUnit4/test/matchers/ChainedArgumentMatcherTest.gd b/addons/gdUnit4/test/matchers/ChainedArgumentMatcherTest.gd deleted file mode 100644 index 547a4cb..0000000 --- a/addons/gdUnit4/test/matchers/ChainedArgumentMatcherTest.gd +++ /dev/null @@ -1,36 +0,0 @@ -# GdUnit generated TestSuite -class_name ChainedArgumentMatcherTest -extends GdUnitTestSuite - -# TestSuite generated from -const __source = 'res://addons/gdUnit4/src/matchers/ChainedArgumentMatcher.gd' - - -func test_is_match_one_arg(): - var matchers = [ - EqualsArgumentMatcher.new("foo") - ] - var matcher := ChainedArgumentMatcher.new(matchers) - - assert_bool(matcher.is_match(["foo"])).is_true() - assert_bool(matcher.is_match(["bar"])).is_false() - - -func test_is_match_two_arg(): - var matchers = [ - EqualsArgumentMatcher.new("foo"), - EqualsArgumentMatcher.new("value1") - ] - var matcher := ChainedArgumentMatcher.new(matchers) - - assert_bool(matcher.is_match(["foo", "value1"])).is_true() - assert_bool(matcher.is_match(["foo", "value2"])).is_false() - assert_bool(matcher.is_match(["bar", "value1"])).is_false() - - -func test_is_match_different_arg_and_matcher(): - var matchers = [ - EqualsArgumentMatcher.new("foo") - ] - var matcher := ChainedArgumentMatcher.new(matchers) - assert_bool(matcher.is_match(["foo", "value"])).is_false() diff --git a/addons/gdUnit4/test/matchers/CustomArgumentMatcherTest.gd b/addons/gdUnit4/test/matchers/CustomArgumentMatcherTest.gd deleted file mode 100644 index 5aa945a..0000000 --- a/addons/gdUnit4/test/matchers/CustomArgumentMatcherTest.gd +++ /dev/null @@ -1,26 +0,0 @@ -extends GdUnitTestSuite - - -class CustomArgumentMatcher extends GdUnitArgumentMatcher: - var _peek :int - - func _init(peek :int): - _peek = peek - - func is_match(value) -> bool: - return value > _peek - - -func test_custom_matcher(): - var mocked_test_class : CustomArgumentMatcherTestClass = mock(CustomArgumentMatcherTestClass) - - mocked_test_class.set_value(1000) - mocked_test_class.set_value(1001) - mocked_test_class.set_value(1002) - mocked_test_class.set_value(2002) - - # counts 1001, 1002, 2002 = 3 times - verify(mocked_test_class, 3).set_value(CustomArgumentMatcher.new(1000)) - # counts 2002 = 1 times - verify(mocked_test_class, 1).set_value(CustomArgumentMatcher.new(2000)) - diff --git a/addons/gdUnit4/test/matchers/GdUnitArgumentMatchersTest.gd b/addons/gdUnit4/test/matchers/GdUnitArgumentMatchersTest.gd deleted file mode 100644 index 7115972..0000000 --- a/addons/gdUnit4/test/matchers/GdUnitArgumentMatchersTest.gd +++ /dev/null @@ -1,16 +0,0 @@ -# GdUnit generated TestSuite -class_name GdUnitArgumentMatchersTest -extends GdUnitTestSuite - -# TestSuite generated from -const __source = 'res://addons/gdUnit4/src/matchers/GdUnitArgumentMatchers.gd' - - -func test_arguments_to_chained_matcher(): - var matcher := GdUnitArgumentMatchers.to_matcher(["foo", false, 1]) - - assert_object(matcher).is_instanceof(ChainedArgumentMatcher) - assert_bool(matcher.is_match(["foo", false, 1])).is_true() - assert_bool(matcher.is_match(["foo", false, 2])).is_false() - assert_bool(matcher.is_match(["foo", true, 1])).is_false() - assert_bool(matcher.is_match(["bar", false, 1])).is_false() diff --git a/addons/gdUnit4/test/matchers/resources/CustomArgumentMatcherTestClass.gd b/addons/gdUnit4/test/matchers/resources/CustomArgumentMatcherTestClass.gd deleted file mode 100644 index 30d06d9..0000000 --- a/addons/gdUnit4/test/matchers/resources/CustomArgumentMatcherTestClass.gd +++ /dev/null @@ -1,8 +0,0 @@ -class_name CustomArgumentMatcherTestClass -extends RefCounted - -var _value :int - - -func set_value(value :int): - _value = value diff --git a/addons/gdUnit4/test/mocker/CustomEnums.gd b/addons/gdUnit4/test/mocker/CustomEnums.gd deleted file mode 100644 index 0ed0f91..0000000 --- a/addons/gdUnit4/test/mocker/CustomEnums.gd +++ /dev/null @@ -1,8 +0,0 @@ -class_name CustomEnums -extends RefCounted - - -enum TEST_ENUM { - FOO = 11, - BAR = 22 -} diff --git a/addons/gdUnit4/test/mocker/GdUnitMockBuilderTest.gd b/addons/gdUnit4/test/mocker/GdUnitMockBuilderTest.gd deleted file mode 100644 index a7f1c8d..0000000 --- a/addons/gdUnit4/test/mocker/GdUnitMockBuilderTest.gd +++ /dev/null @@ -1,282 +0,0 @@ -# GdUnit generated TestSuite -class_name GdUnitMockBuilderTest -extends GdUnitTestSuite - -# TestSuite generated from -const __source = 'res://addons/gdUnit4/src/mocking/GdUnitMockBuilder.gd' - - -# helper to get function descriptor -func get_function_description(clazz_name :String, method_name :String) -> GdFunctionDescriptor: - var method_list :Array = ClassDB.class_get_method_list(clazz_name) - for method_descriptor in method_list: - if method_descriptor["name"] == method_name: - return GdFunctionDescriptor.extract_from(method_descriptor) - return null - - -func test_double_return_typed_function_without_arg() -> void: - var doubler := GdUnitMockFunctionDoubler.new(false) - # String get_class() const - var fd := get_function_description("Object", "get_class") - var expected := [ - '@warning_ignore("untyped_declaration")' if Engine.get_version_info().hex >= 0x40200 else '', - '@warning_ignore("native_method_override")', - '@warning_ignore("shadowed_variable")', - 'func get_class() -> String:', - ' var args :Array = ["get_class", ]', - '', - ' if __is_prepare_return_value():', - ' __save_function_return_value(args)', - ' return ""', - ' if __is_verify_interactions():', - ' __verify_interactions(args)', - ' return ""', - ' else:', - ' __save_function_interaction(args)', - '', - ' if __do_call_real_func("get_class", args):', - ' return super()', - ' return __get_mocked_return_value_or_default(args, "")', - '', - ''] - assert_array(doubler.double(fd)).contains_exactly(expected) - - -func test_double_return_typed_function_with_args() -> void: - var doubler := GdUnitMockFunctionDoubler.new(false) - # bool is_connected(signal: String, callable_: Callable)) const - var fd := get_function_description("Object", "is_connected") - var expected := [ - '@warning_ignore("untyped_declaration")' if Engine.get_version_info().hex >= 0x40200 else '', - '@warning_ignore("native_method_override")', - '@warning_ignore("shadowed_variable")', - 'func is_connected(signal_, callable_) -> bool:', - ' var args :Array = ["is_connected", signal_, callable_]', - '', - ' if __is_prepare_return_value():', - ' __save_function_return_value(args)', - ' return false', - ' if __is_verify_interactions():', - ' __verify_interactions(args)', - ' return false', - ' else:', - ' __save_function_interaction(args)', - '', - ' if __do_call_real_func("is_connected", args):', - ' return super(signal_, callable_)', - ' return __get_mocked_return_value_or_default(args, false)', - '', - ''] - assert_array(doubler.double(fd)).contains_exactly(expected) - - -func test_double_return_untyped_function_with_args() -> void: - var doubler := GdUnitMockFunctionDoubler.new(false) - - # void disconnect(signal: StringName, callable: Callable) - var fd := get_function_description("Object", "disconnect") - var expected := [ - '@warning_ignore("untyped_declaration")' if Engine.get_version_info().hex >= 0x40200 else '', - '@warning_ignore("native_method_override")', - '@warning_ignore("shadowed_variable")', - 'func disconnect(signal_, callable_) -> void:', - ' var args :Array = ["disconnect", signal_, callable_]', - '', - ' if __is_prepare_return_value():', - ' if false:', - ' push_error("Mocking a void function \'disconnect() -> void:\' is not allowed.")', - ' return', - ' if __is_verify_interactions():', - ' __verify_interactions(args)', - ' return', - ' else:', - ' __save_function_interaction(args)', - '', - ' if __do_call_real_func("disconnect"):', - ' super(signal_, callable_)', - '', - ''] - assert_array(doubler.double(fd)).contains_exactly(expected) - - -func test_double_int_function_with_varargs() -> void: - var doubler := GdUnitMockFunctionDoubler.new(false) - # Error emit_signal(signal: StringName, ...) vararg - var fd := get_function_description("Object", "emit_signal") - var expected := [ - '@warning_ignore("untyped_declaration")' if Engine.get_version_info().hex >= 0x40200 else '', - '@warning_ignore("native_method_override")', - '@warning_ignore("int_as_enum_without_match")', - '@warning_ignore("int_as_enum_without_cast")', - '@warning_ignore("shadowed_variable")', - 'func emit_signal(signal_, vararg0_="__null__", vararg1_="__null__", vararg2_="__null__", vararg3_="__null__", vararg4_="__null__", vararg5_="__null__", vararg6_="__null__", vararg7_="__null__", vararg8_="__null__", vararg9_="__null__") -> Error:', - ' var varargs :Array = __filter_vargs([vararg0_, vararg1_, vararg2_, vararg3_, vararg4_, vararg5_, vararg6_, vararg7_, vararg8_, vararg9_])', - ' var args :Array = ["emit_signal", signal_] + varargs', - '', - ' if __is_prepare_return_value():', - ' if false:', - ' push_error("Mocking a void function \'emit_signal() -> void:\' is not allowed.")', - ' __save_function_return_value(args)', - ' return OK', - ' if __is_verify_interactions():', - ' __verify_interactions(args)', - ' return OK', - ' else:', - ' __save_function_interaction(args)', - '', - ' if __do_call_real_func("emit_signal", args):', - ' match varargs.size():', - ' 0: return super(signal_)', - ' 1: return super(signal_, varargs[0])', - ' 2: return super(signal_, varargs[0], varargs[1])', - ' 3: return super(signal_, varargs[0], varargs[1], varargs[2])', - ' 4: return super(signal_, varargs[0], varargs[1], varargs[2], varargs[3])', - ' 5: return super(signal_, varargs[0], varargs[1], varargs[2], varargs[3], varargs[4])', - ' 6: return super(signal_, varargs[0], varargs[1], varargs[2], varargs[3], varargs[4], varargs[5])', - ' 7: return super(signal_, varargs[0], varargs[1], varargs[2], varargs[3], varargs[4], varargs[5], varargs[6])', - ' 8: return super(signal_, varargs[0], varargs[1], varargs[2], varargs[3], varargs[4], varargs[5], varargs[6], varargs[7])', - ' 9: return super(signal_, varargs[0], varargs[1], varargs[2], varargs[3], varargs[4], varargs[5], varargs[6], varargs[7], varargs[8])', - ' 10: return super(signal_, varargs[0], varargs[1], varargs[2], varargs[3], varargs[4], varargs[5], varargs[6], varargs[7], varargs[8], varargs[9])', - ' return __get_mocked_return_value_or_default(args, OK)', - '', - ''] - assert_array(doubler.double(fd)).contains_exactly(expected) - - -func test_double_untyped_function_with_varargs() -> void: - var doubler := GdUnitMockFunctionDoubler.new(false) - - # void emit_custom(signal_name, args ...) vararg const - var fd := GdFunctionDescriptor.new("emit_custom", 10, false, false, false, TYPE_NIL, "", - [GdFunctionArgument.new("signal_", TYPE_SIGNAL)], - GdFunctionDescriptor._build_varargs(true)) - var expected := [ - '@warning_ignore("untyped_declaration")' if Engine.get_version_info().hex >= 0x40200 else '', - '@warning_ignore("shadowed_variable")', - 'func emit_custom(signal_, vararg0_="__null__", vararg1_="__null__", vararg2_="__null__", vararg3_="__null__", vararg4_="__null__", vararg5_="__null__", vararg6_="__null__", vararg7_="__null__", vararg8_="__null__", vararg9_="__null__") -> void:', - ' var varargs :Array = __filter_vargs([vararg0_, vararg1_, vararg2_, vararg3_, vararg4_, vararg5_, vararg6_, vararg7_, vararg8_, vararg9_])', - ' var args :Array = ["emit_custom", signal_] + varargs', - '', - ' if __is_prepare_return_value():', - ' if false:', - ' push_error("Mocking a void function \'emit_custom() -> void:\' is not allowed.")', - ' __save_function_return_value(args)', - ' return null', - ' if __is_verify_interactions():', - ' __verify_interactions(args)', - ' return null', - ' else:', - ' __save_function_interaction(args)', - '', - ' if __do_call_real_func("emit_custom", args):', - ' match varargs.size():', - ' 0: return super(signal_)', - ' 1: return super(signal_, varargs[0])', - ' 2: return super(signal_, varargs[0], varargs[1])', - ' 3: return super(signal_, varargs[0], varargs[1], varargs[2])', - ' 4: return super(signal_, varargs[0], varargs[1], varargs[2], varargs[3])', - ' 5: return super(signal_, varargs[0], varargs[1], varargs[2], varargs[3], varargs[4])', - ' 6: return super(signal_, varargs[0], varargs[1], varargs[2], varargs[3], varargs[4], varargs[5])', - ' 7: return super(signal_, varargs[0], varargs[1], varargs[2], varargs[3], varargs[4], varargs[5], varargs[6])', - ' 8: return super(signal_, varargs[0], varargs[1], varargs[2], varargs[3], varargs[4], varargs[5], varargs[6], varargs[7])', - ' 9: return super(signal_, varargs[0], varargs[1], varargs[2], varargs[3], varargs[4], varargs[5], varargs[6], varargs[7], varargs[8])', - ' 10: return super(signal_, varargs[0], varargs[1], varargs[2], varargs[3], varargs[4], varargs[5], varargs[6], varargs[7], varargs[8], varargs[9])', - ' return __get_mocked_return_value_or_default(args, null)', - '', - ''] - assert_array(doubler.double(fd)).contains_exactly(expected) - - -func test_double_virtual_script_function_without_arg() -> void: - var doubler := GdUnitMockFunctionDoubler.new(false) - - # void _ready() virtual - var fd := get_function_description("Node", "_ready") - var expected := [ - '@warning_ignore("untyped_declaration")' if Engine.get_version_info().hex >= 0x40200 else '', - '@warning_ignore("native_method_override")', - '@warning_ignore("shadowed_variable")', - 'func _ready() -> void:', - ' var args :Array = ["_ready", ]', - '', - ' if __is_prepare_return_value():', - ' if false:', - ' push_error("Mocking a void function \'_ready() -> void:\' is not allowed.")', - ' return', - ' if __is_verify_interactions():', - ' __verify_interactions(args)', - ' return', - ' else:', - ' __save_function_interaction(args)', - '', - ' if __do_call_real_func("_ready"):', - ' super()', - '', - ''] - assert_array(doubler.double(fd)).contains_exactly(expected) - - -func test_double_virtual_script_function_with_arg() -> void: - var doubler := GdUnitMockFunctionDoubler.new(false) - - # void _input(event: InputEvent) virtual - var fd := get_function_description("Node", "_input") - var expected := [ - '@warning_ignore("untyped_declaration")' if Engine.get_version_info().hex >= 0x40200 else '', - '@warning_ignore("native_method_override")', - '@warning_ignore("shadowed_variable")', - 'func _input(event_) -> void:', - ' var args :Array = ["_input", event_]', - '', - ' if __is_prepare_return_value():', - ' if false:', - ' push_error("Mocking a void function \'_input() -> void:\' is not allowed.")', - ' return', - ' if __is_verify_interactions():', - ' __verify_interactions(args)', - ' return', - ' else:', - ' __save_function_interaction(args)', - '', - ' if __do_call_real_func("_input"):', - ' super(event_)', - '', - ''] - assert_array(doubler.double(fd)).contains_exactly(expected) - - -func test_mock_on_script_path_without_class_name() -> void: - var instance = load("res://addons/gdUnit4/test/mocker/resources/ClassWithoutNameA.gd").new() - var script := GdUnitMockBuilder.mock_on_script(instance, "res://addons/gdUnit4/test/mocker/resources/ClassWithoutNameA.gd", [], true); - assert_that(script.resource_name).is_equal("MockClassWithoutNameA.gd") - assert_that(script.get_instance_base_type()).is_equal("Resource") - # finally check the mocked script is valid - assert_int(script.reload()).is_equal(OK) - - -func test_mock_on_script_path_with_custom_class_name() -> void: - # the class contains a class_name definition - var instance = load("res://addons/gdUnit4/test/mocker/resources/ClassWithCustomClassName.gd").new() - var script := GdUnitMockBuilder.mock_on_script(instance, "res://addons/gdUnit4/test/mocker/resources/ClassWithCustomClassName.gd", [], false); - assert_that(script.resource_name).is_equal("MockGdUnitTestCustomClassName.gd") - assert_that(script.get_instance_base_type()).is_equal("Resource") - # finally check the mocked script is valid - assert_int(script.reload()).is_equal(OK) - - -func test_mock_on_class_with_class_name() -> void: - var script := GdUnitMockBuilder.mock_on_script(ClassWithNameA.new(), ClassWithNameA, [], false); - assert_that(script.resource_name).is_equal("MockClassWithNameA.gd") - assert_that(script.get_instance_base_type()).is_equal("Resource") - # finally check the mocked script is valid - assert_int(script.reload()).is_equal(OK) - - -func test_mock_on_class_with_custom_class_name() -> void: - # the class contains a class_name definition - var script := GdUnitMockBuilder.mock_on_script(GdUnit_Test_CustomClassName.new(), GdUnit_Test_CustomClassName, [], false); - assert_that(script.resource_name).is_equal("MockGdUnitTestCustomClassName.gd") - assert_that(script.get_instance_base_type()).is_equal("Resource") - # finally check the mocked script is valid - assert_int(script.reload()).is_equal(OK) diff --git a/addons/gdUnit4/test/mocker/GdUnitMockerTest.gd b/addons/gdUnit4/test/mocker/GdUnitMockerTest.gd deleted file mode 100644 index b579d25..0000000 --- a/addons/gdUnit4/test/mocker/GdUnitMockerTest.gd +++ /dev/null @@ -1,1148 +0,0 @@ -class_name GdUnitMockerTest -extends GdUnitTestSuite - - -var resource_path := "res://addons/gdUnit4/test/mocker/resources/" - -var _saved_report_error_settings - - -func before(): - # disable error pushing for testing - _saved_report_error_settings = ProjectSettings.get_setting(GdUnitSettings.REPORT_PUSH_ERRORS) - ProjectSettings.set_setting(GdUnitSettings.REPORT_PUSH_ERRORS, false) - - -func after(): - ProjectSettings.set_setting(GdUnitSettings.REPORT_PUSH_ERRORS, _saved_report_error_settings) - - -func test_mock_instance_id_is_unique(): - var m1 = mock(RefCounted) - var m2 = mock(RefCounted) - # test the internal instance id is unique - assert_that(m1.__instance_id()).is_not_equal(m2.__instance_id()) - assert_object(m1).is_not_same(m2) - - -func test_is_mockable_godot_classes(): - # verify enigne classes - for clazz_name in ClassDB.get_class_list(): - # mocking is not allowed for: - # singleton classes - # unregistered classes in ClassDB - # protected classes (name starts with underscore) - var is_mockable :bool = not Engine.has_singleton(clazz_name) and ClassDB.can_instantiate(clazz_name) and clazz_name.find("_") != 0 - assert_that(GdUnitMockBuilder.is_mockable(clazz_name)) \ - .override_failure_message("Class '%s' expect mockable %s" % [clazz_name, is_mockable]) \ - .is_equal(is_mockable) - - -func test_is_mockable_by_class_type(): - assert_that(GdUnitMockBuilder.is_mockable(Node)).is_true() - assert_that(GdUnitMockBuilder.is_mockable(CSGBox3D)).is_true() - - -func test_is_mockable_custom_class_type(): - assert_that(GdUnitMockBuilder.is_mockable(CustomResourceTestClass)).is_true() - assert_that(GdUnitMockBuilder.is_mockable(CustomNodeTestClass)).is_true() - - -func test_is_mockable_by_script_path(): - assert_that(GdUnitMockBuilder.is_mockable(resource_path + "CustomResourceTestClass.gd")).is_true() - assert_that(GdUnitMockBuilder.is_mockable(resource_path + "CustomNodeTestClass.gd")).is_true() - # verify for non scripts - assert_that(GdUnitMockBuilder.is_mockable(resource_path + "capsuleshape2d.tres")).is_false() - - -func test_is_mockable__overriden_func_get_class(): - # test with class type - assert_that(GdUnitMockBuilder.is_mockable(OverridenGetClassTestClass))\ - .override_failure_message("The class 'CustomResourceTestClass' should be mockable when 'func get_class()' is overriden")\ - .is_true() - # test with resource path - assert_that(GdUnitMockBuilder.is_mockable(resource_path + "OverridenGetClassTestClass.gd"))\ - .override_failure_message("The class 'CustomResourceTestClass' should be mockable when 'func get_class()' is overriden")\ - .is_true() - - -@warning_ignore("unused_parameter") -func test_mock_godot_class_fullcheck(fuzzer=GodotClassNameFuzzer.new(), fuzzer_iterations=200): - var clazz_name = fuzzer.next_value() - # try to create a mock - if GdUnitMockBuilder.is_mockable(clazz_name): - var m = mock(clazz_name, CALL_REAL_FUNC) - assert_that(m)\ - .override_failure_message("The class %s should be mockable" % clazz_name)\ - .is_not_null() - - -func test_mock_by_script_path(): - assert_that(mock(resource_path + "CustomResourceTestClass.gd")).is_not_null() - assert_that(mock(resource_path + "CustomNodeTestClass.gd")).is_not_null() - - -func test_mock_class__overriden_func_get_class(): - assert_that(mock(OverridenGetClassTestClass)).is_not_null() - assert_that(mock(resource_path + "OverridenGetClassTestClass.gd")).is_not_null() - - -func test_mock_fail(): - # not godot class - assert_that(mock("CustomResourceTestClass")).is_null() - # invalid path to script - assert_that(mock("invalid/CustomResourceTestClass.gd")).is_null() - # try to mocking an existing instance is not allowed - assert_that(mock(CustomResourceTestClass.new())).is_null() - - -func test_mock_special_classes(): - var m = mock("JavaClass") as JavaClass - assert_that(m).is_not_null() - - -func test_mock_Node(): - var mocked_node = mock(Node) - assert_that(mocked_node).is_not_null() - - # test we have initial no interactions checked this mock - verify_no_interactions(mocked_node) - - # verify we have never called 'get_child_count()' - verify(mocked_node, 0).get_child_count() - - # call 'get_child_count()' once - mocked_node.get_child_count() - # verify we have called at once - verify(mocked_node).get_child_count() - - # call function 'get_child_count' a second time - mocked_node.get_child_count() - # verify we have called at twice - verify(mocked_node, 2).get_child_count() - - # test mocked function returns default typed value - assert_that(mocked_node.get_child_count()).is_equal(0) - # now mock return value for function 'foo' to 'overwriten value' - do_return(24).on(mocked_node).get_child_count() - # verify the return value is overwritten - assert_that(mocked_node.get_child_count()).is_equal(24) - - -func test_mock_source_with_class_name_by_resource_path() -> void: - var resource_path_ := 'res://addons/gdUnit4/test/mocker/resources/GD-256/world.gd' - var m = mock(resource_path_) - var head :String = m.get_script().source_code.substr(0, 200) - assert_str(head)\ - .contains("class_name DoubledMunderwoodPathingWorld")\ - .contains("extends '%s'" % resource_path_) - - -func test_mock_source_with_class_name_by_class() -> void: - var resource_path_ := 'res://addons/gdUnit4/test/mocker/resources/GD-256/world.gd' - var m = mock(Munderwood_Pathing_World) - var head :String = m.get_script().source_code.substr(0, 200) - assert_str(head)\ - .contains("class_name DoubledMunderwoodPathingWorld")\ - .contains("extends '%s'" % resource_path_) - - -func test_mock_extends_godot_class() -> void: - var m = mock(World3D) - var head :String = m.get_script().source_code.substr(0, 200) - assert_str(head)\ - .contains("class_name DoubledWorld")\ - .contains("extends World3D") - - -var _test_signal_args := Array() -func _emit_ready(a, b, c = null): - _test_signal_args = [a, b, c] - - -func test_mock_Node_func_vararg(): - # setup - var mocked_node = mock(Node) - - # mock return value - do_return(ERR_CANT_CONNECT).on(mocked_node).rpc("test", "arg1", "arg2", "invalid") - do_return(ERR_CANT_OPEN).on(mocked_node).rpc("test", "arg1", "argX", any_string()) - do_return(ERR_CANT_CREATE).on(mocked_node).rpc("test", "arg1", "argX", any_int()) - do_return(OK).on(mocked_node).rpc("test", "arg1", "argX", "arg3") - # verify - assert_that(mocked_node.rpc("test", "arg1", "arg2", "arg3")).is_equal(OK) - assert_that(mocked_node.rpc("test", "arg1", "arg2", "invalid")).is_equal(ERR_CANT_CONNECT) - assert_that(mocked_node.rpc("test", "arg1", "argX", "arg3")).is_equal(OK) - assert_that(mocked_node.rpc("test", "arg1", "argX", "other")).is_equal(ERR_CANT_OPEN) - assert_that(mocked_node.rpc("test", "arg1", "argX", 42)).is_equal(ERR_CANT_CREATE) - - -func test_mock_Node_func_vararg_call_real_func(): - # setup - var mocked_node = mock(Node, CALL_REAL_FUNC) - assert_that(mocked_node).is_not_null() - assert_that(_test_signal_args).is_empty() - mocked_node.connect("ready", _emit_ready) - - # test emit it - mocked_node.emit_signal("ready", "aa", "bb", "cc") - - # verify is emitted - verify(mocked_node).emit_signal("ready", "aa", "bb", "cc") - await get_tree().process_frame - assert_that(_test_signal_args).is_equal(["aa", "bb", "cc"]) - - # test emit it - mocked_node.emit_signal("ready", "aa", "xxx") - - # verify is emitted - verify(mocked_node).emit_signal("ready", "aa", "xxx") - await get_tree().process_frame - assert_that(_test_signal_args).is_equal(["aa", "xxx", null]) - - -class ClassWithSignal: - signal test_signal_a - signal test_signal_b - - func foo(arg :int) -> void: - if arg == 0: - emit_signal("test_signal_a", "aa") - else: - emit_signal("test_signal_b", "bb", true) - - func bar(arg :int) -> bool: - if arg == 0: - emit_signal("test_signal_a", "aa") - else: - emit_signal("test_signal_b", "bb", true) - return true - - -func _test_mock_verify_emit_signal(): - var mocked_node = mock(ClassWithSignal, CALL_REAL_FUNC) - assert_that(mocked_node).is_not_null() - - mocked_node.foo(0) - verify(mocked_node, 1).emit_signal("test_signal_a", "aa") - verify(mocked_node, 0).emit_signal("test_signal_b", "bb", true) - reset(mocked_node) - - mocked_node.foo(1) - verify(mocked_node, 0).emit_signal("test_signal_a", "aa") - verify(mocked_node, 1).emit_signal("test_signal_b", "bb", true) - reset(mocked_node) - - mocked_node.bar(0) - verify(mocked_node, 1).emit_signal("test_signal_a", "aa") - verify(mocked_node, 0).emit_signal("test_signal_b", "bb", true) - reset(mocked_node) - - mocked_node.bar(1) - verify(mocked_node, 0).emit_signal("test_signal_a", "aa") - verify(mocked_node, 1).emit_signal("test_signal_b", "bb", true) - - -func test_mock_custom_class_by_class_name(): - var m = mock(CustomResourceTestClass) - assert_that(m).is_not_null() - - # test we have initial no interactions checked this mock - verify_no_interactions(m) - # test mocked function returns default typed value - assert_that(m.foo()).is_equal("") - - # now mock return value for function 'foo' to 'overwriten value' - do_return("overriden value").on(m).foo() - # verify the return value is overwritten - assert_that(m.foo()).is_equal("overriden value") - - # now mock return values by custom arguments - do_return("arg_1").on(m).bar(1) - do_return("arg_2").on(m).bar(2) - - assert_that(m.bar(1)).is_equal("arg_1") - assert_that(m.bar(2)).is_equal("arg_2") - - -func test_mock_custom_class_by_resource_path(): - var m = mock("res://addons/gdUnit4/test/mocker/resources/CustomResourceTestClass.gd") - assert_that(m).is_not_null() - - # test we have initial no interactions checked this mock - verify_no_interactions(m) - # test mocked function returns default typed value - assert_that(m.foo()).is_equal("") - - # now mock return value for function 'foo' to 'overwriten value' - do_return("overriden value").on(m).foo() - # verify the return value is overwritten - assert_that(m.foo()).is_equal("overriden value") - - # now mock return values by custom arguments - do_return("arg_1").on(m).bar(1) - do_return("arg_2").on(m).bar(2) - - assert_that(m.bar(1)).is_equal("arg_1") - assert_that(m.bar(2)).is_equal("arg_2") - - -func test_mock_custom_class_func_foo_use_real_func(): - var m = mock(CustomResourceTestClass, CALL_REAL_FUNC) - assert_that(m).is_not_null() - # test mocked function returns value from real function - assert_that(m.foo()).is_equal("foo") - # now mock return value for function 'foo' to 'overwriten value' - do_return("overridden value").on(m).foo() - # verify the return value is overwritten - assert_that(m.foo()).is_equal("overridden value") - - -func test_mock_custom_class_void_func(): - var m = mock(CustomResourceTestClass) - assert_that(m).is_not_null() - # test mocked void function returns null by default - assert_that(m.foo_void()).is_null() - # try now mock return value for a void function. results into an error - do_return("overridden value").on(m).foo_void() - # verify it has no affect for void func - assert_that(m.foo_void()).is_null() - - -func test_mock_custom_class_void_func_real_func(): - var m = mock(CustomResourceTestClass, CALL_REAL_FUNC) - assert_that(m).is_not_null() - # test mocked void function returns null by default - assert_that(m.foo_void()).is_null() - # try now mock return value for a void function. results into an error - do_return("overridden value").on(m).foo_void() - # verify it has no affect for void func - assert_that(m.foo_void()).is_null() - - -func test_mock_custom_class_func_foo_call_times(): - var m = mock(CustomResourceTestClass) - assert_that(m).is_not_null() - verify(m, 0).foo() - m.foo() - verify(m, 1).foo() - m.foo() - verify(m, 2).foo() - m.foo() - m.foo() - verify(m, 4).foo() - - -func test_mock_custom_class_func_foo_call_times_real_func(): - var m = mock(CustomResourceTestClass, CALL_REAL_FUNC) - assert_that(m).is_not_null() - verify(m, 0).foo() - m.foo() - verify(m, 1).foo() - m.foo() - verify(m, 2).foo() - m.foo() - m.foo() - verify(m, 4).foo() - - -func test_mock_custom_class_func_foo_full_test(): - var m = mock(CustomResourceTestClass) - assert_that(m).is_not_null() - verify(m, 0).foo() - assert_that(m.foo()).is_equal("") - verify(m, 1).foo() - do_return("new value").on(m).foo() - verify(m, 1).foo() - assert_that(m.foo()).is_equal("new value") - verify(m, 2).foo() - - -func test_mock_custom_class_func_foo_full_test_real_func(): - var m = mock(CustomResourceTestClass, CALL_REAL_FUNC) - assert_that(m).is_not_null() - verify(m, 0).foo() - assert_that(m.foo()).is_equal("foo") - verify(m, 1).foo() - do_return("new value").on(m).foo() - verify(m, 1).foo() - assert_that(m.foo()).is_equal("new value") - verify(m, 2).foo() - - -func test_mock_custom_class_func_bar(): - var m = mock(CustomResourceTestClass) - assert_that(m).is_not_null() - assert_that(m.bar(10)).is_equal("") - # verify 'bar' with args [10] is called one time at this point - verify(m, 1).bar(10) - # verify 'bar' with args [10, 20] is never called at this point - verify(m, 0).bar(10, 29) - # verify 'bar' with args [23] is never called at this point - verify(m, 0).bar(23) - - # now mock return value for function 'bar' with args [10] to 'overwriten value' - do_return("overridden value").on(m).bar(10) - # verify the return value is overwritten - assert_that(m.bar(10)).is_equal("overridden value") - # finally verify function call times - verify(m, 2).bar(10) - verify(m, 0).bar(10, 29) - verify(m, 0).bar(23) - - -func test_mock_custom_class_func_bar_real_func(): - var m = mock(CustomResourceTestClass, CALL_REAL_FUNC) - assert_that(m).is_not_null() - assert_that(m.bar(10)).is_equal("test_33") - # verify 'bar' with args [10] is called one time at this point - verify(m, 1).bar(10) - # verify 'bar' with args [10, 20] is never called at this point - verify(m, 0).bar(10, 29) - # verify 'bar' with args [23] is never called at this point - verify(m, 0).bar(23) - - # now mock return value for function 'bar' with args [10] to 'overwriten value' - do_return("overridden value").on(m).bar(10) - # verify the return value is overwritten - assert_that(m.bar(10)).is_equal("overridden value") - # verify the real implementation is used - assert_that(m.bar(10, 29)).is_equal("test_39") - assert_that(m.bar(10, 20, "other")).is_equal("other_30") - # finally verify function call times - verify(m, 2).bar(10) - verify(m, 1).bar(10, 29) - verify(m, 0).bar(10, 20) - verify(m, 1).bar(10, 20, "other") - - -func test_mock_custom_class_func_return_type_enum(): - var m = mock(ClassWithEnumReturnTypes) - assert_that(m).is_not_null() - verify(m, 0).get_enum() - - # verify enum return default ClassWithEnumReturnTypes.TEST_ENUM.FOO - assert_that(m.get_enum()).is_equal(ClassWithEnumReturnTypes.TEST_ENUM.FOO) - do_return(ClassWithEnumReturnTypes.TEST_ENUM.BAR).on(m).get_enum() - assert_that(m.get_enum()).is_equal(ClassWithEnumReturnTypes.TEST_ENUM.BAR) - verify(m, 2).get_enum() - - # with call real functions - var m2 = mock(ClassWithEnumReturnTypes, CALL_REAL_FUNC) - assert_that(m2).is_not_null() - - # verify enum return type - assert_that(m2.get_enum()).is_equal(ClassWithEnumReturnTypes.TEST_ENUM.FOO) - do_return(ClassWithEnumReturnTypes.TEST_ENUM.BAR).on(m2).get_enum() - assert_that(m2.get_enum()).is_equal(ClassWithEnumReturnTypes.TEST_ENUM.BAR) - - -func test_mock_custom_class_func_return_type_internal_class_enum(): - var m = mock(ClassWithEnumReturnTypes) - assert_that(m).is_not_null() - verify(m, 0).get_inner_class_enum() - - # verify enum return default - assert_that(m.get_inner_class_enum()).is_equal(ClassWithEnumReturnTypes.InnerClass.TEST_ENUM.FOO) - do_return(ClassWithEnumReturnTypes.InnerClass.TEST_ENUM.BAR).on(m).get_inner_class_enum() - assert_that(m.get_inner_class_enum()).is_equal(ClassWithEnumReturnTypes.InnerClass.TEST_ENUM.BAR) - verify(m, 2).get_inner_class_enum() - - # with call real functions - var m2= mock(ClassWithEnumReturnTypes, CALL_REAL_FUNC) - assert_that(m2).is_not_null() - - # verify enum return type - assert_that(m2.get_inner_class_enum()).is_equal(ClassWithEnumReturnTypes.InnerClass.TEST_ENUM.FOO) - do_return(ClassWithEnumReturnTypes.InnerClass.TEST_ENUM.BAR).on(m2).get_inner_class_enum() - assert_that(m2.get_inner_class_enum()).is_equal(ClassWithEnumReturnTypes.InnerClass.TEST_ENUM.BAR) - - -func test_mock_custom_class_func_return_type_external_class_enum(): - var m = mock(ClassWithEnumReturnTypes) - assert_that(m).is_not_null() - verify(m, 0).get_external_class_enum() - - # verify enum return default - assert_that(m.get_external_class_enum()).is_equal(CustomEnums.TEST_ENUM.FOO) - do_return(CustomEnums.TEST_ENUM.BAR).on(m).get_external_class_enum() - assert_that(m.get_external_class_enum()).is_equal(CustomEnums.TEST_ENUM.BAR) - verify(m, 2).get_external_class_enum() - - # with call real functions - var m2 = mock(ClassWithEnumReturnTypes, CALL_REAL_FUNC) - assert_that(m2).is_not_null() - - # verify enum return type - assert_that(m2.get_external_class_enum()).is_equal(CustomEnums.TEST_ENUM.FOO) - do_return(CustomEnums.TEST_ENUM.BAR).on(m2).get_external_class_enum() - assert_that(m2.get_external_class_enum()).is_equal(CustomEnums.TEST_ENUM.BAR) - - -func test_mock_custom_class_extends_Node(): - var m = mock(CustomNodeTestClass) - assert_that(m).is_not_null() - - # test mocked function returns null as default - assert_that(m.get_child_count()).is_equal(0) - assert_that(m.get_children()).contains_exactly([]) - # test seters has no affect - var node = auto_free(Node.new()) - m.add_child(node) - assert_that(m.get_child_count()).is_equal(0) - assert_that(m.get_children()).contains_exactly([]) - verify(m, 1).add_child(node) - verify(m, 2).get_child_count() - verify(m, 2).get_children() - - -func test_mock_custom_class_extends_Node_real_func(): - var m = mock(CustomNodeTestClass, CALL_REAL_FUNC) - assert_that(m).is_not_null() - # test mocked function returns default mock value - assert_that(m.get_child_count()).is_equal(0) - assert_that(m.get_children()).is_equal([]) - # test real seters used - var nodeA = auto_free(Node.new()) - var nodeB = auto_free(Node.new()) - var nodeC = auto_free(Node.new()) - m.add_child(nodeA) - m.add_child(nodeB) - assert_that(m.get_child_count()).is_equal(2) - assert_that(m.get_children()).contains_exactly([nodeA, nodeB]) - verify(m, 1).add_child(nodeA) - verify(m, 1).add_child(nodeB) - verify(m, 0).add_child(nodeC) - verify(m, 2).get_child_count() - verify(m, 2).get_children() - - -func test_mock_custom_class_extends_other_custom_class(): - var m = mock(CustomClassExtendsCustomClass) - assert_that(mock).is_not_null() - - # foo() form parent class - verify(m, 0).foo() - # foo2() overriden - verify(m, 0).foo2() - # bar2() from class - verify(m, 0).bar2() - - assert_that(m.foo()).is_empty() - assert_that(m.foo2()).is_null() - assert_that(m.bar2()).is_empty() - - verify(m, 1).foo() - verify(m, 1).foo2() - verify(m, 1).bar2() - - # override returns - do_return("abc1").on(m).foo() - do_return("abc2").on(m).foo2() - do_return("abc3").on(m).bar2() - - assert_that(m.foo()).is_equal("abc1") - assert_that(m.foo2()).is_equal("abc2") - assert_that(m.bar2()).is_equal("abc3") - - -func test_mock_custom_class_extends_other_custom_class_call_real_func(): - var m = mock(CustomClassExtendsCustomClass, CALL_REAL_FUNC) - assert_that(m).is_not_null() - - # foo() form parent class - verify(m, 0).foo() - # foo2() overriden - verify(m, 0).foo2() - # bar2() from class - verify(m, 0).bar2() - - assert_that(m.foo()).is_equal("foo") - assert_that(m.foo2()).is_equal("foo2 overriden") - assert_that(m.bar2()).is_equal("test_65") - - verify(m, 1).foo() - verify(m, 1).foo2() - verify(m, 1).bar2() - - # override returns - do_return("abc1").on(m).foo() - do_return("abc2").on(m).foo2() - do_return("abc3").on(m).bar2() - - assert_that(m.foo()).is_equal("abc1") - assert_that(m.foo2()).is_equal("abc2") - assert_that(m.bar2()).is_equal("abc3") - - -func test_mock_static_func(): - var m = mock(CustomNodeTestClass) - assert_that(m).is_not_null() - # initial not called - verify(m, 0).static_test() - verify(m, 0).static_test_void() - - assert_that(m.static_test()).is_equal("") - assert_that(m.static_test_void()).is_null() - - verify(m, 1).static_test() - verify(m, 1).static_test_void() - m.static_test() - m.static_test_void() - m.static_test_void() - verify(m, 2).static_test() - verify(m, 3).static_test_void() - - -func test_mock_static_func_real_func(): - var m = mock(CustomNodeTestClass, CALL_REAL_FUNC) - assert_that(m).is_not_null() - # initial not called - verify(m, 0).static_test() - verify(m, 0).static_test_void() - - assert_that(m.static_test()).is_equal(CustomNodeTestClass.STATIC_FUNC_RETURN_VALUE) - assert_that(m.static_test_void()).is_null() - - verify(m, 1).static_test() - verify(m, 1).static_test_void() - m.static_test() - m.static_test_void() - m.static_test_void() - verify(m, 2).static_test() - verify(m, 3).static_test_void() - - -func test_mock_custom_class_assert_has_no_side_affect(): - var m = mock(CustomNodeTestClass) - assert_that(m).is_not_null() - var node = Node.new() - # verify the assertions has no side affect checked mocked object - verify(m, 0).add_child(node) - # expect no change checked childrens - assert_that(m.get_children()).contains_exactly([]) - - m.add_child(node) - # try thre times 'assert_called' to see it has no affect to the mock - verify(m, 1).add_child(node) - verify(m, 1).add_child(node) - verify(m, 1).add_child(node) - assert_that(m.get_children()).contains_exactly([]) - # needs to be manually freed - node.free() - - -func test_mock_custom_class_assert_has_no_side_affect_real_func(): - var m = mock(CustomNodeTestClass, CALL_REAL_FUNC) - assert_that(m).is_not_null() - var node = Node.new() - # verify the assertions has no side affect checked mocked object - verify(m, 0).add_child(node) - # expect no change checked childrens - assert_that(m.get_children()).contains_exactly([]) - - m.add_child(node) - # try thre times 'assert_called' to see it has no affect to the mock - verify(m, 1).add_child(node) - verify(m, 1).add_child(node) - verify(m, 1).add_child(node) - assert_that(m.get_children()).contains_exactly([node]) - - -# This test verifies a function is calling other internally functions -# to collect the access times and the override return value is working as expected -func test_mock_advanced_func_path(): - var m = mock(AdvancedTestClass, CALL_REAL_FUNC) - # initial nothing is called - verify(m, 0).select(AdvancedTestClass.A) - verify(m, 0).select(AdvancedTestClass.B) - verify(m, 0).select(AdvancedTestClass.C) - verify(m, 0).a() - verify(m, 0).b() - verify(m, 0).c() - - # the function select() swiches based checked input argument to function a(), b() or c() - # call select where called internally func a() and returned "a" - assert_that(m.select(AdvancedTestClass.A)).is_equal("a") - # verify when call select() is also calling original func a() - verify(m, 1).select(AdvancedTestClass.A) - verify(m, 1).a() - - # call select again wiht overriden return value for func a() - do_return("overridden a func").on(m).a() - assert_that(m.select(AdvancedTestClass.A)).is_equal("overridden a func") - - # verify at this time select() and a() is called two times - verify(m, 2).select(AdvancedTestClass.A) - verify(m, 0).select(AdvancedTestClass.B) - verify(m, 0).select(AdvancedTestClass.C) - verify(m, 2).a() - verify(m, 0).b() - verify(m, 0).c() - - # finally use select to switch to internally func c() - assert_that(m.select(AdvancedTestClass.C)).is_equal("c") - verify(m, 2).select(AdvancedTestClass.A) - verify(m, 0).select(AdvancedTestClass.B) - verify(m, 1).select(AdvancedTestClass.C) - verify(m, 2).a() - verify(m, 0).b() - verify(m, 1).c() - - -func _test_mock_godot_class_calls_sub_function(): - var m = mock(MeshInstance3D, CALL_REAL_FUNC) - verify(m, 0)._mesh_changed() - m.set_mesh(QuadMesh.new()) - verify(m, 1).set_mesh(any_class(Mesh)) - verify(m, 1)._mesh_changed() - - -func test_mock_class_with_inner_classs(): - var mock_advanced = mock(AdvancedTestClass) - assert_that(mock_advanced).is_not_null() - - var mock_a := mock(AdvancedTestClass.SoundData) as AdvancedTestClass.SoundData - assert_object(mock_a).is_not_null() - - var mock_b := mock(AdvancedTestClass.AtmosphereData) as AdvancedTestClass.AtmosphereData - assert_object(mock_b).is_not_null() - - var mock_c := mock(AdvancedTestClass.Area4D) as AdvancedTestClass.Area4D - assert_object(mock_c).is_not_null() - - -func test_do_return(): - var mocked_node = mock(Node) - - # is return 0 by default - mocked_node.get_child_count() - # configure to return 10 when 'get_child_count()' is called - do_return(10).on(mocked_node).get_child_count() - # will now return 10 - assert_int(mocked_node.get_child_count()).is_equal(10) - - # is return 'null' by default - var node = mocked_node.get_child(0) - assert_object(node).is_null() - - # configure to return a mocked 'Camera3D' for child 0 - do_return(mock(Camera3D)).on(mocked_node).get_child(0) - # configure to return a mocked 'Area3D' for child 1 - do_return(mock(Area3D)).on(mocked_node).get_child(1) - - # will now return the Camera3D node - var node0 = mocked_node.get_child(0) - assert_object(node0).is_instanceof(Camera3D) - # will now return the Area3D node - var node1 = mocked_node.get_child(1) - assert_object(node1).is_instanceof(Area3D) - - -func test_matching_is_sorted(): - var mocked_node = mock(Node) - do_return(null).on(mocked_node).get_child(any(), false) - do_return(null).on(mocked_node).get_child(1, false) - do_return(null).on(mocked_node).get_child(10, false) - do_return(null).on(mocked_node).get_child(any(), true) - do_return(null).on(mocked_node).get_child(3, true) - - # get the sorted mocked args as array - var mocked_args :Array = mocked_node.__mocked_return_values.get("get_child").keys() - assert_array(mocked_args).has_size(5) - - # we expect all argument matchers are sorted to the end - var first_arguments = mocked_args.map(func (v): return v[0]) - assert_int(first_arguments[0]).is_equal(3) - assert_int(first_arguments[1]).is_equal(10) - assert_int(first_arguments[2]).is_equal(1) - assert_object(first_arguments[3]).is_instanceof(GdUnitArgumentMatcher) - assert_object(first_arguments[4]).is_instanceof(GdUnitArgumentMatcher) - - -func test_do_return_with_matchers(): - var mocked_node = mock(Node) - var childN :Node = auto_free(Node2D.new()) - var child1 :Node = auto_free(Node2D.new()) - var child10 :Node = auto_free(Node2D.new()) - - # for any index return childN by using any() matcher - do_return(childN).on(mocked_node).get_child(any(), false) - # for index 1 and 10 do return 'child1' and 'child10' - do_return(child1).on(mocked_node).get_child(1, false) - do_return(child10).on(mocked_node).get_child(10, false) - # for any index and flag true, we return null by using the 'any_int' matcher - do_return(null).on(mocked_node).get_child(any_int(), true) - - assert_that(mocked_node.get_child(0, true)).is_null() - assert_that(mocked_node.get_child(1, true)).is_null() - assert_that(mocked_node.get_child(2, true)).is_null() - assert_that(mocked_node.get_child(10, true)).is_null() - assert_that(mocked_node.get_child(0)).is_same(childN) - assert_that(mocked_node.get_child(1)).is_same(child1) - assert_that(mocked_node.get_child(2)).is_same(childN) - assert_that(mocked_node.get_child(3)).is_same(childN) - assert_that(mocked_node.get_child(4)).is_same(childN) - assert_that(mocked_node.get_child(5)).is_same(childN) - assert_that(mocked_node.get_child(6)).is_same(childN) - assert_that(mocked_node.get_child(7)).is_same(childN) - assert_that(mocked_node.get_child(8)).is_same(childN) - assert_that(mocked_node.get_child(9)).is_same(childN) - assert_that(mocked_node.get_child(10)).is_same(child10) - - -func test_example_verify(): - var mocked_node = mock(Node) - - # verify we have no interactions currently checked this instance - verify_no_interactions(mocked_node) - - # call with different arguments - mocked_node.set_process(false) # 1 times - mocked_node.set_process(true) # 1 times - mocked_node.set_process(true) # 2 times - - # verify how often we called the function with different argument - verify(mocked_node, 2).set_process(true) # in sum two times with true - verify(mocked_node, 1).set_process(false)# in sum one time with false - - # verify total sum by using an argument matcher - verify(mocked_node, 3).set_process(any_bool()) - - -func test_verify_fail(): - var mocked_node = mock(Node) - - # interact two time - mocked_node.set_process(true) # 1 times - mocked_node.set_process(true) # 2 times - - # verify we interacts two times - verify(mocked_node, 2).set_process(true) - - # verify should fail because we interacts two times and not one - var expected_error := """ - Expecting interaction on: - 'set_process(true :bool)' 1 time's - But found interactions on: - 'set_process(true :bool)' 2 time's""" \ - .dedent().trim_prefix("\n").replace("\r", "") - assert_failure(func(): verify(mocked_node, 1).set_process(true)) \ - .is_failed() \ - .has_message(expected_error) - - -func test_verify_func_interaction_wiht_PoolStringArray(): - var mocked = mock(ClassWithPoolStringArrayFunc) - - mocked.set_values(PackedStringArray()) - - verify(mocked).set_values(PackedStringArray()) - verify_no_more_interactions(mocked) - - -func test_verify_func_interaction_wiht_PoolStringArray_fail(): - var mocked = mock(ClassWithPoolStringArrayFunc) - - mocked.set_values(PackedStringArray()) - - # try to verify with default array type instead of PackedStringArray type - var expected_error := """ - Expecting interaction on: - 'set_values([] :Array)' 1 time's - But found interactions on: - 'set_values([] :PackedStringArray)' 1 time's""" \ - .dedent().trim_prefix("\n").replace("\r", "") - assert_failure(func(): verify(mocked, 1).set_values([])) \ - .is_failed() \ - .has_message(expected_error) - - reset(mocked) - # try again with called two times and different args - mocked.set_values(PackedStringArray()) - mocked.set_values(PackedStringArray(["a", "b"])) - mocked.set_values([1, 2]) - expected_error = """ - Expecting interaction on: - 'set_values([] :Array)' 1 time's - But found interactions on: - 'set_values([] :PackedStringArray)' 1 time's - 'set_values(["a", "b"] :PackedStringArray)' 1 time's - 'set_values([1, 2] :Array)' 1 time's""" \ - .dedent().trim_prefix("\n").replace("\r", "") - assert_failure(func(): verify(mocked, 1).set_values([])) \ - .is_failed() \ - .has_message(expected_error) - - -func test_reset(): - var mocked_node = mock(Node) - - # call with different arguments - mocked_node.set_process(false) # 1 times - mocked_node.set_process(true) # 1 times - mocked_node.set_process(true) # 2 times - - verify(mocked_node, 2).set_process(true) - verify(mocked_node, 1).set_process(false) - - # now reset the mock - reset(mocked_node) - # verify all counters have been reset - verify_no_interactions(mocked_node) - - -func test_verify_no_interactions(): - var mocked_node = mock(Node) - - # verify we have no interactions checked this mock - verify_no_interactions(mocked_node) - - -func test_verify_no_interactions_fails(): - var mocked_node = mock(Node) - - # interact - mocked_node.set_process(false) # 1 times - mocked_node.set_process(true) # 1 times - mocked_node.set_process(true) # 2 times - - var expected_error =""" - Expecting no more interactions! - But found interactions on: - 'set_process(false :bool)' 1 time's - 'set_process(true :bool)' 2 time's""" \ - .dedent().trim_prefix("\n") - # it should fail because we have interactions - assert_failure(func(): verify_no_interactions(mocked_node)) \ - .is_failed() \ - .has_message(expected_error) - - -func test_verify_no_more_interactions(): - var mocked_node = mock(Node) - - mocked_node.is_ancestor_of(null) - mocked_node.set_process(false) - mocked_node.set_process(true) - mocked_node.set_process(true) - - # verify for called functions - verify(mocked_node, 1).is_ancestor_of(null) - verify(mocked_node, 2).set_process(true) - verify(mocked_node, 1).set_process(false) - - # There should be no more interactions checked this mock - verify_no_more_interactions(mocked_node) - - -func test_verify_no_more_interactions_but_has(): - var mocked_node = mock(Node) - - mocked_node.is_ancestor_of(null) - mocked_node.set_process(false) - mocked_node.set_process(true) - mocked_node.set_process(true) - - # now we simulate extra calls that we are not explicit verify - mocked_node.is_inside_tree() - mocked_node.is_inside_tree() - # a function with default agrs - mocked_node.find_child("mask") - # same function again with custom agrs - mocked_node.find_child("mask", false, false) - - # verify 'all' exclusive the 'extra calls' functions - verify(mocked_node, 1).is_ancestor_of(null) - verify(mocked_node, 2).set_process(true) - verify(mocked_node, 1).set_process(false) - - # now use 'verify_no_more_interactions' to check we have no more interactions checked this mock - # but should fail with a collecion of all not validated interactions - var expected_error =""" - Expecting no more interactions! - But found interactions on: - 'is_inside_tree()' 2 time's - 'find_child(mask :String, true :bool, true :bool)' 1 time's - 'find_child(mask :String, false :bool, false :bool)' 1 time's""" \ - .dedent().trim_prefix("\n") - assert_failure(func(): verify_no_more_interactions(mocked_node)) \ - .is_failed() \ - .has_message(expected_error) - - -func test_mock_snake_case_named_class_by_resource_path(): - var mock_a = mock("res://addons/gdUnit4/test/mocker/resources/snake_case.gd") - assert_object(mock_a).is_not_null() - - mock_a.custom_func() - verify(mock_a).custom_func() - verify_no_more_interactions(mock_a) - - var mock_b = mock("res://addons/gdUnit4/test/mocker/resources/snake_case_class_name.gd") - assert_object(mock_b).is_not_null() - - mock_b.custom_func() - verify(mock_b).custom_func() - verify_no_more_interactions(mock_b) - - -func test_mock_snake_case_named_godot_class_by_name(): - # try checked Godot class - var mocked_tcp_server = mock("TCPServer") - assert_object(mocked_tcp_server).is_not_null() - - mocked_tcp_server.is_listening() - mocked_tcp_server.is_connection_available() - verify(mocked_tcp_server).is_listening() - verify(mocked_tcp_server).is_connection_available() - verify_no_more_interactions(mocked_tcp_server) - - -func test_mock_snake_case_named_class_by_class(): - var m = mock(snake_case_class_name) - assert_object(m).is_not_null() - - m.custom_func() - verify(m).custom_func() - verify_no_more_interactions(m) - - # try checked Godot class - var mocked_tcp_server = mock(TCPServer) - assert_object(mocked_tcp_server).is_not_null() - - mocked_tcp_server.is_listening() - mocked_tcp_server.is_connection_available() - verify(mocked_tcp_server).is_listening() - verify(mocked_tcp_server).is_connection_available() - verify_no_more_interactions(mocked_tcp_server) - - -func test_mock_func_with_default_build_in_type(): - var m = mock(ClassWithDefaultBuildIntTypes) - assert_object(m).is_not_null() - # call with default arg - m.foo("abc") - m.bar("def") - verify(m).foo("abc", Color.RED) - verify(m).bar("def", Vector3.FORWARD, AABB()) - verify_no_more_interactions(m) - - # call with custom color arg - m.foo("abc", Color.BLUE) - m.bar("def", Vector3.DOWN, AABB(Vector3.ONE, Vector3.ZERO)) - verify(m).foo("abc", Color.BLUE) - verify(m).bar("def", Vector3.DOWN, AABB(Vector3.ONE, Vector3.ZERO)) - verify_no_more_interactions(m) - - -func test_mock_virtual_function_is_not_called_twice() -> void: - # this test verifies the special handling of virtual functions by Godot - # virtual functions are handeld in a special way - # node.cpp - # case NOTIFICATION_READY: { - # - # if (get_script_instance()) { - # - # Variant::CallError err; - # get_script_instance()->call_multilevel_reversed(SceneStringNames::get_singleton()->_ready,NULL,0); - # } - - var m = mock(ClassWithOverridenVirtuals, CALL_REAL_FUNC) - assert_object(m).is_not_null() - - # inital constructor - assert_that(m._x).is_equal("_init") - - # add_child calls internally by "default" _ready() where is a virtual function - add_child(m) - - # verify _ready func is only once called - assert_that(m._x).is_equal("_ready") - - # now simulate an input event calls '_input' - var action = InputEventKey.new() - action.pressed = false - action.keycode = KEY_ENTER - get_tree().root.push_input(action) - assert_that(m._x).is_equal("ui_accept") - - -func test_mock_scene_by_path(): - var mocked_scene = mock("res://addons/gdUnit4/test/mocker/resources/scenes/TestScene.tscn") - assert_object(mocked_scene).is_not_null() - assert_object(mocked_scene.get_script()).is_not_null() - assert_str(mocked_scene.get_script().resource_name).is_equal("MockTestScene.gd") - # check is mocked scene registered for auto freeing - assert_bool(GdUnitMemoryObserver.is_marked_auto_free(mocked_scene)).is_true() - - -func test_mock_scene_by_resource(): - var resource := load("res://addons/gdUnit4/test/mocker/resources/scenes/TestScene.tscn") - var mocked_scene = mock(resource) - assert_object(mocked_scene).is_not_null() - assert_object(mocked_scene.get_script()).is_not_null() - assert_str(mocked_scene.get_script().resource_name).is_equal("MockTestScene.gd") - # check is mocked scene registered for auto freeing - assert_bool(GdUnitMemoryObserver.is_marked_auto_free(mocked_scene)).is_true() - - -func test_mock_scene_by_instance(): - var resource := load("res://addons/gdUnit4/test/mocker/resources/scenes/TestScene.tscn") - var instance :Control = auto_free(resource.instantiate()) - var mocked_scene = mock(instance) - # must fail mock an instance is not allowed - assert_object(mocked_scene).is_null() - - -func test_mock_scene_by_path_fail_has_no_script_attached(): - var mocked_scene = mock("res://addons/gdUnit4/test/mocker/resources/scenes/TestSceneWithoutScript.tscn") - assert_object(mocked_scene).is_null() - - -func test_mock_scene_variables_is_set(): - var mocked_scene = mock("res://addons/gdUnit4/test/mocker/resources/scenes/TestScene.tscn") - assert_object(mocked_scene).is_not_null() - - # Add as child to a node to trigger _ready to initalize all variables - add_child(mocked_scene) - assert_object(mocked_scene._box1).is_not_null() - assert_object(mocked_scene._box2).is_not_null() - assert_object(mocked_scene._box3).is_not_null() - - # check signals are connected - assert_bool(mocked_scene.is_connected("panel_color_change", Callable(mocked_scene, "_on_panel_color_changed"))) - - # check exports - assert_str(mocked_scene._initial_color.to_html()).is_equal(Color.RED.to_html()) - - -func test_mock_scene_execute_func_yielded() -> void: - var mocked_scene = mock("res://addons/gdUnit4/test/mocker/resources/scenes/TestScene.tscn") - assert_object(mocked_scene).is_not_null() - add_child(mocked_scene) - # execute the 'color_cycle' func where emits three signals - # using yield to wait for function is completed - var result = await mocked_scene.color_cycle() - # verify the return value of 'color_cycle' - assert_str(result).is_equal("black") - - verify(mocked_scene)._on_panel_color_changed(mocked_scene._box1, Color.RED) - verify(mocked_scene)._on_panel_color_changed(mocked_scene._box1, Color.BLUE) - verify(mocked_scene)._on_panel_color_changed(mocked_scene._box1, Color.GREEN) - - -class Base: - func _init(_value :String): - pass - - -class Foo extends Base: - func _init(): - super("test") - pass - - -func test_mock_with_inheritance_method() -> void: - var foo = mock(Foo) - assert_object(foo).is_not_null() diff --git a/addons/gdUnit4/test/mocker/GodotClassNameFuzzer.gd b/addons/gdUnit4/test/mocker/GodotClassNameFuzzer.gd deleted file mode 100644 index a48f428..0000000 --- a/addons/gdUnit4/test/mocker/GodotClassNameFuzzer.gd +++ /dev/null @@ -1,43 +0,0 @@ -# fuzzer to get available godot class names -class_name GodotClassNameFuzzer -extends Fuzzer - -var class_names := [] -const EXCLUDED_CLASSES = [ - "JavaClass", - "_ClassDB", - "MainLoop", - "JNISingleton", - "SceneTree", - "WebRTC", - "WebRTCPeerConnection", - "Tween", - "TextServerAdvanced", - "InputEventShortcut", - # GD-110 - missing enum `Vector3.Axis` - "Sprite3D", "AnimatedSprite3D", -] - - -func _init(no_singleton :bool = false,only_instancialbe :bool = false): - #class_names = ClassDB.get_class_list() - for clazz_name in ClassDB.get_class_list(): - #https://github.com/godotengine/godot/issues/67643 - if clazz_name.contains("Extension"): - continue - if no_singleton and Engine.has_singleton(clazz_name): - continue - if only_instancialbe and not ClassDB.can_instantiate(clazz_name): - continue - # exclude special classes - if EXCLUDED_CLASSES.has(clazz_name): - continue - # exlude Godot 3.5 *Tweener classes where produces and error - # `ERROR: Can't create empty IntervalTweener. Use get_tree().tween_property() or tween_property() instead.` - if clazz_name.find("Tweener") != -1: - continue - class_names.push_back(clazz_name) - - -func next_value(): - return class_names[randi() % class_names.size()] diff --git a/addons/gdUnit4/test/mocker/resources/AdvancedTestClass.gd b/addons/gdUnit4/test/mocker/resources/AdvancedTestClass.gd deleted file mode 100644 index 3d039c0..0000000 --- a/addons/gdUnit4/test/mocker/resources/AdvancedTestClass.gd +++ /dev/null @@ -1,84 +0,0 @@ -# this class used to mock testing with inner classes and default arguments in functions -class_name AdvancedTestClass -extends Resource - -class SoundData: - @warning_ignore("unused_private_class_variable") - var _sample :String - @warning_ignore("unused_private_class_variable") - var _randomnes :float - -class AtmosphereData: - enum { - WATER, - AIR, - SMOKY, - } - var _toxigen :float - var _type :int - - func _init(type := AIR, toxigen := 0.0): - _type = type - _toxigen = toxigen -# some comment, and an row staring with an space to simmulate invalid formatting - - - # seter func with default values - func set_data(type := AIR, toxigen := 0.0) : - _type = type - _toxigen = toxigen - - static func to_atmosphere(_value :Dictionary) -> AtmosphereData: - return null - -class Area4D extends Resource: - - const SOUND := 1 - const ATMOSPHERE := 2 - var _meta := Dictionary() - - func _init(_x :int, atmospere :AtmosphereData = null): - _meta[ATMOSPHERE] = atmospere - - func get_sound() -> SoundData: - # sounds are optional - if _meta.has(SOUND): - return _meta[SOUND] as SoundData - return null - - func get_atmoshere() -> AtmosphereData: - return _meta[ATMOSPHERE] as AtmosphereData - -var _areas : = {} - -func _init(): - # add default atmoshere - _areas["default"] = Area4D.new(1, AtmosphereData.new()) - -func get_area(name :String, default :Area4D = null) -> Area4D: - return _areas.get(name, default) - - -# test spy is called sub functions select() -> a(), b(), c() -enum { - A, B, C -} - -func a() -> String: - return "a" - -func b() -> String: - return "b" - -func c() -> String: - return "c" - -func select( type :int) -> String: - match type: - A: return a() - B: return b() - C: return c() - _: return "" - -static func to_foo() -> String: - return "foo" diff --git a/addons/gdUnit4/test/mocker/resources/ClassWithCustomClassName.gd b/addons/gdUnit4/test/mocker/resources/ClassWithCustomClassName.gd deleted file mode 100644 index a51a32c..0000000 --- a/addons/gdUnit4/test/mocker/resources/ClassWithCustomClassName.gd +++ /dev/null @@ -1,3 +0,0 @@ -class_name GdUnit_Test_CustomClassName -extends Resource - diff --git a/addons/gdUnit4/test/mocker/resources/ClassWithCustomFormattings.gd b/addons/gdUnit4/test/mocker/resources/ClassWithCustomFormattings.gd deleted file mode 100644 index c0b53c5..0000000 --- a/addons/gdUnit4/test/mocker/resources/ClassWithCustomFormattings.gd +++ /dev/null @@ -1,52 +0,0 @@ -extends Object - -var _message - -@warning_ignore("unused_parameter") -func _init(message:String, path:String="", load_on_init:bool=false, - set_auto_save:bool=false, set_network_sync:bool=false -) -> void: - _message = message - - -@warning_ignore("unused_parameter") -func a1(set_name:String, path:String="", load_on_init:bool=false, - set_auto_save:bool=false, set_network_sync:bool=false -) -> void: - pass - - -@warning_ignore("unused_parameter") -func a2(set_name:String, path:String="", load_on_init:bool=false, - set_auto_save:bool=false, set_network_sync:bool=false -) -> void: - pass - - -@warning_ignore("unused_parameter") -func a3(set_name:String, path:String="", load_on_init:bool=false, - set_auto_save:bool=false, set_network_sync:bool=false -) : - pass - - -@warning_ignore("unused_parameter") -func a4(set_name:String, - path:String="", - load_on_init:bool=false, - set_auto_save:bool=false, - set_network_sync:bool=false -): - pass - - -@warning_ignore("unused_parameter") -func a5( - value : Array, - expected : String, - test_parameters : Array = [ - [ ["a"], "a" ], - [ ["a", "very", "long", "argument"], "a very long argument" ], - ] -): - pass diff --git a/addons/gdUnit4/test/mocker/resources/ClassWithDefaultBuildIntTypes.gd b/addons/gdUnit4/test/mocker/resources/ClassWithDefaultBuildIntTypes.gd deleted file mode 100644 index f5c1e25..0000000 --- a/addons/gdUnit4/test/mocker/resources/ClassWithDefaultBuildIntTypes.gd +++ /dev/null @@ -1,8 +0,0 @@ -class_name ClassWithDefaultBuildIntTypes -extends RefCounted - -func foo(_value :String, _color := Color.RED): - pass - -func bar(_value :String, _direction := Vector3.FORWARD, _aabb := AABB()): - pass diff --git a/addons/gdUnit4/test/mocker/resources/ClassWithEnumReturnTypes.gd b/addons/gdUnit4/test/mocker/resources/ClassWithEnumReturnTypes.gd deleted file mode 100644 index 3d1cc73..0000000 --- a/addons/gdUnit4/test/mocker/resources/ClassWithEnumReturnTypes.gd +++ /dev/null @@ -1,26 +0,0 @@ -class_name ClassWithEnumReturnTypes -extends Resource - - -class InnerClass: - enum TEST_ENUM { FOO = 111, BAR = 222 } - -enum TEST_ENUM { FOO = 1, BAR = 2 } - -const NOT_AN_ENUM := 1 - -const X := { FOO=1, BAR=2 } - - -func get_enum() -> TEST_ENUM: - return TEST_ENUM.FOO - - -# function signature with an external enum reference -func get_external_class_enum() -> CustomEnums.TEST_ENUM: - return CustomEnums.TEST_ENUM.FOO - - -# function signature with an inner class enum reference -func get_inner_class_enum() -> InnerClass.TEST_ENUM: - return InnerClass.TEST_ENUM.FOO diff --git a/addons/gdUnit4/test/mocker/resources/ClassWithNameA.gd b/addons/gdUnit4/test/mocker/resources/ClassWithNameA.gd deleted file mode 100644 index dbdde7b..0000000 --- a/addons/gdUnit4/test/mocker/resources/ClassWithNameA.gd +++ /dev/null @@ -1,5 +0,0 @@ -class_name ClassWithNameA -extends Resource - -class InnerClass extends Resource: - pass diff --git a/addons/gdUnit4/test/mocker/resources/ClassWithNameB.gd b/addons/gdUnit4/test/mocker/resources/ClassWithNameB.gd deleted file mode 100644 index fb50778..0000000 --- a/addons/gdUnit4/test/mocker/resources/ClassWithNameB.gd +++ /dev/null @@ -1,8 +0,0 @@ -# some comments and empty lines - -# similate bad formated class -# - -class_name ClassWithNameB -extends Resource - diff --git a/addons/gdUnit4/test/mocker/resources/ClassWithOverridenVirtuals.gd b/addons/gdUnit4/test/mocker/resources/ClassWithOverridenVirtuals.gd deleted file mode 100644 index 2b6dd4e..0000000 --- a/addons/gdUnit4/test/mocker/resources/ClassWithOverridenVirtuals.gd +++ /dev/null @@ -1,21 +0,0 @@ -class_name ClassWithOverridenVirtuals -extends Node - -var _x := "default" - - -func _init(): - _x = "_init" - - -# Called when the node enters the scene tree for the first time. -func _ready(): - if _x == "_init": - _x = "" - _x += "_ready" - - -func _input(event): - _x = "_input" - if event.is_action_released("ui_accept"): - _x = "ui_accept" diff --git a/addons/gdUnit4/test/mocker/resources/ClassWithPoolStringArrayFunc.gd b/addons/gdUnit4/test/mocker/resources/ClassWithPoolStringArrayFunc.gd deleted file mode 100644 index 869dabb..0000000 --- a/addons/gdUnit4/test/mocker/resources/ClassWithPoolStringArrayFunc.gd +++ /dev/null @@ -1,7 +0,0 @@ -class_name ClassWithPoolStringArrayFunc -extends RefCounted - -var _values :PackedStringArray - -func set_values(values :PackedStringArray): - _values = values diff --git a/addons/gdUnit4/test/mocker/resources/ClassWithVariables.gd b/addons/gdUnit4/test/mocker/resources/ClassWithVariables.gd deleted file mode 100644 index bcc058b..0000000 --- a/addons/gdUnit4/test/mocker/resources/ClassWithVariables.gd +++ /dev/null @@ -1,40 +0,0 @@ -@tool -class_name ClassWithVariables -extends Node - -enum{ - A, - B, - C -} - -enum ETYPE { - EA, - EB - } - -# Declare member variables here. Examples: -var a = 2 -var b = "text" - -# Declare some const variables -const T1 = 1 - -const T2 = 2 - -signal source_changed( text ) - -@onready var name_label = load("res://addons/gdUnit4/test/mocker/resources/ClassWithNameA.gd") - -@export var path: NodePath = ".." - -class ClassA: - var x = 1 - # some comment - func foo()->String: - return "" - - -func foo(_value :int = T1): - var _c = a + b - pass diff --git a/addons/gdUnit4/test/mocker/resources/ClassWithoutNameA.gd b/addons/gdUnit4/test/mocker/resources/ClassWithoutNameA.gd deleted file mode 100644 index 2156f64..0000000 --- a/addons/gdUnit4/test/mocker/resources/ClassWithoutNameA.gd +++ /dev/null @@ -1,4 +0,0 @@ -# some comment - -extends Resource - diff --git a/addons/gdUnit4/test/mocker/resources/ClassWithoutNameAndNotExtends.gd b/addons/gdUnit4/test/mocker/resources/ClassWithoutNameAndNotExtends.gd deleted file mode 100644 index 4b0a4e5..0000000 --- a/addons/gdUnit4/test/mocker/resources/ClassWithoutNameAndNotExtends.gd +++ /dev/null @@ -1,5 +0,0 @@ -# some comment - -func foo(): - pass - diff --git a/addons/gdUnit4/test/mocker/resources/CustomClassExtendsCustomClass.gd b/addons/gdUnit4/test/mocker/resources/CustomClassExtendsCustomClass.gd deleted file mode 100644 index a3e16cc..0000000 --- a/addons/gdUnit4/test/mocker/resources/CustomClassExtendsCustomClass.gd +++ /dev/null @@ -1,9 +0,0 @@ -class_name CustomClassExtendsCustomClass -extends CustomResourceTestClass - -# override -func foo2(): - return "foo2 overriden" - -func bar2() -> String: - return bar(23, 42) diff --git a/addons/gdUnit4/test/mocker/resources/CustomNodeTestClass.gd b/addons/gdUnit4/test/mocker/resources/CustomNodeTestClass.gd deleted file mode 100644 index 4aec1e5..0000000 --- a/addons/gdUnit4/test/mocker/resources/CustomNodeTestClass.gd +++ /dev/null @@ -1,30 +0,0 @@ -class_name CustomNodeTestClass -extends Node - -const STATIC_FUNC_RETURN_VALUE = "i'm a static function" - -enum { - ENUM_A, - ENUM_B -} - -#func get_path() -> NodePath: -# return NodePath() - -#func duplicate(flags_=15) -> Node: -# return self - -# added a custom static func for mock testing -static func static_test() -> String: - return STATIC_FUNC_RETURN_VALUE - -static func static_test_void() -> void: - pass - -func get_value( type := ENUM_A) -> int: - match type: - ENUM_A: - return 0 - ENUM_B: - return 1 - return -1 diff --git a/addons/gdUnit4/test/mocker/resources/CustomResourceTestClass.gd b/addons/gdUnit4/test/mocker/resources/CustomResourceTestClass.gd deleted file mode 100644 index 84f7541..0000000 --- a/addons/gdUnit4/test/mocker/resources/CustomResourceTestClass.gd +++ /dev/null @@ -1,17 +0,0 @@ -class_name CustomResourceTestClass -extends Resource - -func foo() -> String: - return "foo" - -func foo2(): - return "foo2" - -func foo_void() -> void: - pass - -func bar(arg1 :int, arg2 :int = 23, name :String = "test") -> String: - return "%s_%d" % [name, arg1+arg2] - -func foo5(): - pass diff --git a/addons/gdUnit4/test/mocker/resources/DeepStubTestClass.gd b/addons/gdUnit4/test/mocker/resources/DeepStubTestClass.gd deleted file mode 100644 index ae272cb..0000000 --- a/addons/gdUnit4/test/mocker/resources/DeepStubTestClass.gd +++ /dev/null @@ -1,16 +0,0 @@ -class_name DeepStubTestClass - -class XShape: - var _shape : Shape3D = BoxShape3D.new() - - func get_shape() -> Shape3D: - return _shape - - -var _shape :XShape - -func add(shape :XShape): - _shape = shape - -func validate() -> bool: - return _shape.get_shape().get_margin() == 0.0 diff --git a/addons/gdUnit4/test/mocker/resources/GD-256/world.gd b/addons/gdUnit4/test/mocker/resources/GD-256/world.gd deleted file mode 100644 index e1a1fd8..0000000 --- a/addons/gdUnit4/test/mocker/resources/GD-256/world.gd +++ /dev/null @@ -1,5 +0,0 @@ -class_name Munderwood_Pathing_World -extends Node - -func foo() -> String: - return "test" diff --git a/addons/gdUnit4/test/mocker/resources/OverridenGetClassTestClass.gd b/addons/gdUnit4/test/mocker/resources/OverridenGetClassTestClass.gd deleted file mode 100644 index 5ae8a1a..0000000 --- a/addons/gdUnit4/test/mocker/resources/OverridenGetClassTestClass.gd +++ /dev/null @@ -1,11 +0,0 @@ -class_name OverridenGetClassTestClass -extends Resource - - -@warning_ignore("native_method_override") -func get_class() -> String: - return "OverridenGetClassTestClass" - -func foo() -> String: - prints("foo") - return "foo" diff --git a/addons/gdUnit4/test/mocker/resources/TestPersion.gd b/addons/gdUnit4/test/mocker/resources/TestPersion.gd deleted file mode 100644 index 70b3ffb..0000000 --- a/addons/gdUnit4/test/mocker/resources/TestPersion.gd +++ /dev/null @@ -1,22 +0,0 @@ -extends Object - -var _name -var _value -var _address :Address - -class Address: - var _street :String - var _code :int - - func _init(street :String, code :int): - _street = street - _code = code - - -func _init(name_ :String, street :String, code :int): - _name = name_ - _value = 1024 - _address = Address.new(street, code) - -func name() -> String: - return _name diff --git a/addons/gdUnit4/test/mocker/resources/capsuleshape2d.tres b/addons/gdUnit4/test/mocker/resources/capsuleshape2d.tres deleted file mode 100644 index de1187d..0000000 --- a/addons/gdUnit4/test/mocker/resources/capsuleshape2d.tres +++ /dev/null @@ -1,3 +0,0 @@ -[gd_resource type="CapsuleShape2D" format=2] - -[resource] diff --git a/addons/gdUnit4/test/mocker/resources/scenes/Spell.gd b/addons/gdUnit4/test/mocker/resources/scenes/Spell.gd deleted file mode 100644 index ce21871..0000000 --- a/addons/gdUnit4/test/mocker/resources/scenes/Spell.gd +++ /dev/null @@ -1,39 +0,0 @@ -class_name Spell -extends Node - -signal spell_explode - -const SPELL_LIVE_TIME = 1000 - -@warning_ignore("unused_private_class_variable") -var _spell_fired :bool = false -var _spell_live_time :float = 0 -var _spell_pos :Vector3 = Vector3.ZERO - -# helper counter for testing simulate_frames -@warning_ignore("unused_private_class_variable") -var _debug_process_counted := 0 - -func _ready(): - set_name("Spell") - -# only comment in for debugging reasons -#func _notification(what): -# prints("Spell", GdObjects.notification_as_string(what)) - -func _process(delta :float): - # added pseudo yield to check `simulate_frames` works wih custom yielding - await get_tree().process_frame - _spell_live_time += delta * 1000 - if _spell_live_time < SPELL_LIVE_TIME: - move(delta) - else: - explode() - -func move(delta :float) -> void: - #await get_tree().create_timer(0.1).timeout - _spell_pos.x += delta - -func explode() -> void: - emit_signal("spell_explode", self) - diff --git a/addons/gdUnit4/test/mocker/resources/scenes/TestScene.gd b/addons/gdUnit4/test/mocker/resources/scenes/TestScene.gd deleted file mode 100644 index a182fa2..0000000 --- a/addons/gdUnit4/test/mocker/resources/scenes/TestScene.gd +++ /dev/null @@ -1,104 +0,0 @@ -extends Control - -signal panel_color_change(box, color) - -const COLOR_CYCLE := [Color.ROYAL_BLUE, Color.CHARTREUSE, Color.YELLOW_GREEN] - -@onready var _box1 = $VBoxContainer/PanelContainer/HBoxContainer/Panel1 -@onready var _box2 = $VBoxContainer/PanelContainer/HBoxContainer/Panel2 -@onready var _box3 = $VBoxContainer/PanelContainer/HBoxContainer/Panel3 - -@warning_ignore("unused_private_class_variable") -@export var _initial_color := Color.RED - -@warning_ignore("unused_private_class_variable") -var _nullable :Object - -func _ready(): - connect("panel_color_change", _on_panel_color_changed) - # we call this function to verify the _ready is only once called - # this is need to verify `add_child` is calling the original implementation only once - only_one_time_call() - - -func only_one_time_call() -> void: - pass - - -#func _notification(what): -# prints("TestScene", GdObjects.notification_as_string(what)) - - -func _on_test_pressed(button_id :int): - var box :ColorRect - match button_id: - 1: box = _box1 - 2: box = _box2 - 3: box = _box3 - emit_signal("panel_color_change", box, Color.RED) - # special case for button 3 we wait 1s to change to gray - if button_id == 3: - await get_tree().create_timer(1).timeout - emit_signal("panel_color_change", box, Color.GRAY) - - -func _on_panel_color_changed(box :ColorRect, color :Color): - box.color = color - - -func create_timer(timeout :float) -> Timer: - var timer :Timer = Timer.new() - add_child(timer) - timer.connect("timeout",Callable(self,"_on_timeout").bind(timer)) - timer.set_one_shot(true) - timer.start(timeout) - return timer - - -func _on_timeout(timer :Timer): - remove_child(timer) - timer.queue_free() - - -func color_cycle() -> String: - prints("color_cycle") - await create_timer(0.500).timeout - emit_signal("panel_color_change", _box1, Color.RED) - prints("timer1") - await create_timer(0.500).timeout - emit_signal("panel_color_change", _box1, Color.BLUE) - prints("timer2") - await create_timer(0.500).timeout - emit_signal("panel_color_change", _box1, Color.GREEN) - prints("cycle end") - return "black" - - -func start_color_cycle(): - color_cycle() - - -# used for manuall spy checked created spy -func _create_spell() -> Spell: - return Spell.new() - - -func create_spell() -> Spell: - var spell := _create_spell() - spell.connect("spell_explode", Callable(self, "_destroy_spell")) - return spell - - -func _destroy_spell(spell :Spell) -> void: - #prints("_destroy_spell", spell) - remove_child(spell) - spell.queue_free() - - -func _input(event): - if event.is_action_released("ui_accept"): - add_child(create_spell()) - - -func add(a: int, b :int) -> int: - return a + b diff --git a/addons/gdUnit4/test/mocker/resources/scenes/TestScene.tscn b/addons/gdUnit4/test/mocker/resources/scenes/TestScene.tscn deleted file mode 100644 index 8e668a2..0000000 --- a/addons/gdUnit4/test/mocker/resources/scenes/TestScene.tscn +++ /dev/null @@ -1,104 +0,0 @@ -[gd_scene load_steps=2 format=3 uid="uid://bf24pr1xj60o6"] - -[ext_resource type="Script" path="res://addons/gdUnit4/test/mocker/resources/scenes/TestScene.gd" id="1"] - -[node name="Control" type="Control"] -layout_mode = 3 -anchors_preset = 15 -anchor_right = 1.0 -anchor_bottom = 1.0 -grow_horizontal = 2 -grow_vertical = 2 -size_flags_horizontal = 3 -size_flags_vertical = 3 -script = ExtResource("1") - -[node name="VBoxContainer" type="VBoxContainer" parent="."] -layout_mode = 1 -anchors_preset = 15 -anchor_right = 1.0 -anchor_bottom = 1.0 -grow_horizontal = 2 -grow_vertical = 2 - -[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer"] -custom_minimum_size = Vector2(0, 40) -layout_mode = 2 - -[node name="test1" type="Button" parent="VBoxContainer/HBoxContainer"] -custom_minimum_size = Vector2(100, 0) -layout_mode = 2 -text = "Test 1" - -[node name="test2" type="Button" parent="VBoxContainer/HBoxContainer"] -custom_minimum_size = Vector2(100, 0) -layout_mode = 2 -text = "Test 2" - -[node name="test3" type="Button" parent="VBoxContainer/HBoxContainer"] -custom_minimum_size = Vector2(100, 0) -layout_mode = 2 -text = "Test 3" - -[node name="PanelContainer" type="TabContainer" parent="VBoxContainer"] -layout_mode = 2 -size_flags_horizontal = 3 -size_flags_vertical = 3 - -[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer/PanelContainer"] -layout_mode = 2 - -[node name="Panel1" type="ColorRect" parent="VBoxContainer/PanelContainer/HBoxContainer"] -custom_minimum_size = Vector2(100, 0) -layout_mode = 2 - -[node name="Label" type="Label" parent="VBoxContainer/PanelContainer/HBoxContainer/Panel1"] -layout_mode = 1 -anchors_preset = 10 -anchor_right = 1.0 -offset_bottom = 14.0 -grow_horizontal = 2 -text = "Panel 1" - -[node name="Panel2" type="ColorRect" parent="VBoxContainer/PanelContainer/HBoxContainer"] -custom_minimum_size = Vector2(100, 0) -layout_mode = 2 - -[node name="Label" type="Label" parent="VBoxContainer/PanelContainer/HBoxContainer/Panel2"] -layout_mode = 1 -anchors_preset = 10 -anchor_right = 1.0 -offset_bottom = 14.0 -grow_horizontal = 2 -text = "Panel 2" - -[node name="Panel3" type="ColorRect" parent="VBoxContainer/PanelContainer/HBoxContainer"] -custom_minimum_size = Vector2(100, 0) -layout_mode = 2 - -[node name="Label" type="Label" parent="VBoxContainer/PanelContainer/HBoxContainer/Panel3"] -layout_mode = 1 -anchors_preset = 10 -anchor_right = 1.0 -offset_bottom = 14.0 -grow_horizontal = 2 -text = "Panel 3" - -[node name="Line2D" type="Line2D" parent="VBoxContainer"] -points = PackedVector2Array(0, 0, 20, 0) -width = 30.0 -default_color = Color(1, 0.0509804, 0.192157, 1) - -[node name="Line2D2" type="Line2D" parent="VBoxContainer"] -points = PackedVector2Array(20, 0, 40, 0) -width = 30.0 -default_color = Color(0.0392157, 1, 0.278431, 1) - -[node name="Line2D3" type="Line2D" parent="VBoxContainer"] -points = PackedVector2Array(40, 0, 60, 0) -width = 30.0 -default_color = Color(1, 0.0392157, 0.247059, 1) - -[connection signal="pressed" from="VBoxContainer/HBoxContainer/test1" to="." method="_on_test_pressed" binds= [1]] -[connection signal="pressed" from="VBoxContainer/HBoxContainer/test2" to="." method="_on_test_pressed" binds= [2]] -[connection signal="pressed" from="VBoxContainer/HBoxContainer/test3" to="." method="_on_test_pressed" binds= [3]] diff --git a/addons/gdUnit4/test/mocker/resources/scenes/TestSceneWithoutScript.tscn b/addons/gdUnit4/test/mocker/resources/scenes/TestSceneWithoutScript.tscn deleted file mode 100644 index 8bd646d..0000000 --- a/addons/gdUnit4/test/mocker/resources/scenes/TestSceneWithoutScript.tscn +++ /dev/null @@ -1,67 +0,0 @@ -[gd_scene format=3 uid="uid://bvp8uaof31fhm"] - -[node name="Control" type="Control"] -layout_mode = 3 -anchors_preset = 15 -anchor_right = 1.0 -anchor_bottom = 1.0 -size_flags_horizontal = 3 -size_flags_vertical = 3 - -[node name="VBoxContainer" type="VBoxContainer" parent="."] -layout_mode = 0 -anchor_right = 1.0 -anchor_bottom = 1.0 - -[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer"] -layout_mode = 2 - -[node name="test1" type="Button" parent="VBoxContainer/HBoxContainer"] -layout_mode = 2 -text = "Test 1" - -[node name="test2" type="Button" parent="VBoxContainer/HBoxContainer"] -layout_mode = 2 -text = "Test 2" - -[node name="test3" type="Button" parent="VBoxContainer/HBoxContainer"] -layout_mode = 2 -text = "Test 3" - -[node name="PanelContainer" type="TabContainer" parent="VBoxContainer"] -layout_mode = 2 -size_flags_horizontal = 3 -size_flags_vertical = 3 - -[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer/PanelContainer"] -layout_mode = 2 - -[node name="Panel1" type="ColorRect" parent="VBoxContainer/PanelContainer/HBoxContainer"] -layout_mode = 2 -color = Color(0.694118, 0.207843, 0.207843, 1) - -[node name="Label" type="Label" parent="VBoxContainer/PanelContainer/HBoxContainer/Panel1"] -layout_mode = 0 -anchor_right = 1.0 -offset_bottom = 14.0 -text = "Panel 1" - -[node name="Panel2" type="ColorRect" parent="VBoxContainer/PanelContainer/HBoxContainer"] -layout_mode = 2 -color = Color(0.219608, 0.662745, 0.380392, 1) - -[node name="Label" type="Label" parent="VBoxContainer/PanelContainer/HBoxContainer/Panel2"] -layout_mode = 0 -anchor_right = 1.0 -offset_bottom = 14.0 -text = "Panel 2" - -[node name="Panel3" type="ColorRect" parent="VBoxContainer/PanelContainer/HBoxContainer"] -layout_mode = 2 -color = Color(0.12549, 0.286275, 0.776471, 1) - -[node name="Label" type="Label" parent="VBoxContainer/PanelContainer/HBoxContainer/Panel3"] -layout_mode = 0 -anchor_right = 1.0 -offset_bottom = 14.0 -text = "Panel 3" diff --git a/addons/gdUnit4/test/mocker/resources/snake_case.gd b/addons/gdUnit4/test/mocker/resources/snake_case.gd deleted file mode 100644 index 37c1eae..0000000 --- a/addons/gdUnit4/test/mocker/resources/snake_case.gd +++ /dev/null @@ -1,4 +0,0 @@ -extends RefCounted - -func custom_func(): - pass diff --git a/addons/gdUnit4/test/mocker/resources/snake_case_class_name.gd b/addons/gdUnit4/test/mocker/resources/snake_case_class_name.gd deleted file mode 100644 index 8eccbe6..0000000 --- a/addons/gdUnit4/test/mocker/resources/snake_case_class_name.gd +++ /dev/null @@ -1,6 +0,0 @@ -class_name snake_case_class_name -extends RefCounted - - -func custom_func(): - pass diff --git a/addons/gdUnit4/test/monitor/ErrorLogEntryTest.gd b/addons/gdUnit4/test/monitor/ErrorLogEntryTest.gd deleted file mode 100644 index 22cad49..0000000 --- a/addons/gdUnit4/test/monitor/ErrorLogEntryTest.gd +++ /dev/null @@ -1,27 +0,0 @@ -# GdUnit generated TestSuite -class_name ErrorLogEntryTest -extends GdUnitTestSuite -@warning_ignore('unused_parameter') -@warning_ignore('return_value_discarded') - -# TestSuite generated from -const __source = 'res://addons/gdUnit4/src/monitor/ErrorLogEntry.gd' - -const error_report = """ - USER ERROR: this is an error - at: push_error (core/variant/variant_utility.cpp:880) - """ -const script_error = """ - USER SCRIPT ERROR: Trying to call a function on a previously freed instance. - at: GdUnitScriptTypeTest.test_xx (res://addons/gdUnit4/test/GdUnitScriptTypeTest.gd:22) -""" - - -func test_parse_script_error_line_number() -> void: - var line := ErrorLogEntry._parse_error_line_number(script_error.dedent()) - assert_int(line).is_equal(22) - - -func test_parse_push_error_line_number() -> void: - var line := ErrorLogEntry._parse_error_line_number(error_report.dedent()) - assert_int(line).is_equal(-1) diff --git a/addons/gdUnit4/test/monitor/GodotGdErrorMonitorTest.gd b/addons/gdUnit4/test/monitor/GodotGdErrorMonitorTest.gd deleted file mode 100644 index 48e7fec..0000000 --- a/addons/gdUnit4/test/monitor/GodotGdErrorMonitorTest.gd +++ /dev/null @@ -1,125 +0,0 @@ -# GdUnit generated TestSuite -#warning-ignore-all:unused_argument -#warning-ignore-all:return_value_discarded -class_name GodotGdErrorMonitorTest -extends GdUnitTestSuite - -# TestSuite generated from -const __source = 'res://addons/gdUnit4/src/monitor/GodotGdErrorMonitor.gd' - - -const error_report = """ - USER ERROR: this is an error - at: push_error (core/variant/variant_utility.cpp:880) - """ -const script_error = """ - USER SCRIPT ERROR: Trying to call a function on a previously freed instance. - at: GdUnitScriptTypeTest.test_xx (res://addons/gdUnit4/test/GdUnitScriptTypeTest.gd:22) -""" - - -var _save_is_report_push_errors :bool -var _save_is_report_script_errors :bool - - -func before(): - _save_is_report_push_errors = GdUnitSettings.is_report_push_errors() - _save_is_report_script_errors = GdUnitSettings.is_report_script_errors() - # disable default error reporting for testing - ProjectSettings.set_setting(GdUnitSettings.REPORT_PUSH_ERRORS, false) - ProjectSettings.set_setting(GdUnitSettings.REPORT_SCRIPT_ERRORS, false) - - -func after(): - ProjectSettings.set_setting(GdUnitSettings.REPORT_PUSH_ERRORS, _save_is_report_push_errors) - ProjectSettings.set_setting(GdUnitSettings.REPORT_SCRIPT_ERRORS, _save_is_report_script_errors) - - -func write_log(content :String) -> String: - var log_file := create_temp_file("/test_logs/", "test.log") - log_file.store_string(content) - log_file.flush() - return log_file.get_path_absolute() - - -func test_scan_for_push_errors() -> void: - var monitor := mock(GodotGdErrorMonitor, CALL_REAL_FUNC) as GodotGdErrorMonitor - monitor._godot_log_file = write_log(error_report) - monitor._report_enabled = true - - # with disabled push_error reporting - do_return(false).on(monitor)._is_report_push_errors() - await monitor.scan() - assert_array(monitor.to_reports()).is_empty() - - # with enabled push_error reporting - do_return(true).on(monitor)._is_report_push_errors() - - var entry := ErrorLogEntry.new(ErrorLogEntry.TYPE.PUSH_ERROR, -1, - "this is an error", - "at: push_error (core/variant/variant_utility.cpp:880)") - var expected_report := GodotGdErrorMonitor._to_report(entry) - monitor._eof = 0 - await monitor.scan() - assert_array(monitor.to_reports()).contains_exactly([expected_report]) - - -func test_scan_for_script_errors() -> void: - var log_file := write_log(script_error) - var monitor := mock(GodotGdErrorMonitor, CALL_REAL_FUNC) as GodotGdErrorMonitor - monitor._godot_log_file = log_file - monitor._report_enabled = true - - # with disabled push_error reporting - do_return(false).on(monitor)._is_report_script_errors() - await monitor.scan() - assert_array(monitor.to_reports()).is_empty() - - # with enabled push_error reporting - do_return(true).on(monitor)._is_report_script_errors() - - var entry := ErrorLogEntry.new(ErrorLogEntry.TYPE.PUSH_ERROR, 22, - "Trying to call a function on a previously freed instance.", - "at: GdUnitScriptTypeTest.test_xx (res://addons/gdUnit4/test/GdUnitScriptTypeTest.gd:22)") - var expected_report := GodotGdErrorMonitor._to_report(entry) - monitor._eof = 0 - await monitor.scan() - assert_array(monitor.to_reports()).contains_exactly([expected_report]) - - -func test_custom_log_path() -> void: - # save original log_path - var log_path :String = ProjectSettings.get_setting("debug/file_logging/log_path") - # set custom log path - var custom_log_path := "user://logs/test-run.log" - FileAccess.open(custom_log_path, FileAccess.WRITE).store_line("test-log") - ProjectSettings.set_setting("debug/file_logging/log_path", custom_log_path) - var monitor := GodotGdErrorMonitor.new() - - assert_that(monitor._godot_log_file).is_equal(custom_log_path) - # restore orignal log_path - ProjectSettings.set_setting("debug/file_logging/log_path", log_path) - - -func test_integration_test() -> void: - var monitor := GodotGdErrorMonitor.new() - monitor._report_enabled = true - # no errors reported - monitor.start() - monitor.stop() - await monitor.scan(true) - assert_array(monitor.to_reports()).is_empty() - - # push error - monitor.start() - push_error("Test GodotGdErrorMonitor 'push_error' reporting") - push_warning("Test GodotGdErrorMonitor 'push_warning' reporting") - monitor.stop() - await monitor.scan(true) - var reports := monitor.to_reports() - assert_array(reports).has_size(2) - if not reports.is_empty(): - assert_str(reports[0].message()).contains("Test GodotGdErrorMonitor 'push_error' reporting") - assert_str(reports[1].message()).contains("Test GodotGdErrorMonitor 'push_warning' reporting") - else: - fail("Expect reporting runtime errors") diff --git a/addons/gdUnit4/test/mono/ExampleTestSuite.cs b/addons/gdUnit4/test/mono/ExampleTestSuite.cs deleted file mode 100644 index cc4c603..0000000 --- a/addons/gdUnit4/test/mono/ExampleTestSuite.cs +++ /dev/null @@ -1,38 +0,0 @@ -// GdUnit generated TestSuite -using Godot; -using GdUnit4; -using System; - -namespace GdUnit4 -{ - using static Assertions; - using static Utils; - - [TestSuite] - public partial class ExampleTestSuite - { - [TestCase] - public void IsFoo() - { - AssertThat("Foo").IsEqual("Foo"); - } - - [TestCase('A', Variant.Type.Int)] - [TestCase(SByte.MaxValue, Variant.Type.Int)] - [TestCase(Byte.MaxValue, Variant.Type.Int)] - [TestCase(Int16.MaxValue, Variant.Type.Int)] - [TestCase(UInt16.MaxValue, Variant.Type.Int)] - [TestCase(Int32.MaxValue, Variant.Type.Int)] - [TestCase(UInt32.MaxValue, Variant.Type.Int)] - [TestCase(Int64.MaxValue, Variant.Type.Int)] - [TestCase(UInt64.MaxValue, Variant.Type.Int)] - [TestCase(Single.MaxValue, Variant.Type.Float)] - [TestCase(Double.MaxValue, Variant.Type.Float)] - [TestCase("HalloWorld", Variant.Type.String)] - [TestCase(true, Variant.Type.Bool)] - public void ParameterizedTest(dynamic? value, Variant.Type type) { - Godot.Variant v = value == null ? new Variant() : Godot.Variant.CreateFrom(value); - AssertObject(v.VariantType).IsEqual(type); - } - } -} diff --git a/addons/gdUnit4/test/mono/GdUnit4CSharpApiLoaderTest.gd b/addons/gdUnit4/test/mono/GdUnit4CSharpApiLoaderTest.gd deleted file mode 100644 index 86134cf..0000000 --- a/addons/gdUnit4/test/mono/GdUnit4CSharpApiLoaderTest.gd +++ /dev/null @@ -1,71 +0,0 @@ -# GdUnit generated TestSuite -#warning-ignore-all:unused_argument -#warning-ignore-all:return_value_discarded -class_name GdUnit4CSharpApiLoaderTest -extends GdUnitTestSuite - -# TestSuite generated from -const GdUnitTools := preload("res://addons/gdUnit4/src/core/GdUnitTools.gd") -const __source = 'res://addons/gdUnit4/src/mono/GdUnit4CSharpApiLoader.gd' - - -@warning_ignore("unused_parameter") -func before(do_skip = not GdUnit4CSharpApiLoader.is_mono_supported(), skip_reason = "Do run only for Godot Mono version"): - pass - - -@warning_ignore("unused_parameter") -func test_is_engine_version_supported(version :int, expected :bool, test_parameters := [ - [0x40000, false], - [0x40001, false], - [0x40002, false], - [0x40100, false], - [0x40101, false], - [0x40102, false], - [0x40100, false], - [0x40200, true], - [0x40201, true]]) -> void: - - assert_that(GdUnit4CSharpApiLoader.is_engine_version_supported(version)).is_equal(expected) - - -func test_api_version() -> void: - assert_str(GdUnit4CSharpApiLoader.version()).starts_with("4.2") - - -func test_create_test_suite() -> void: - var temp := create_temp_dir("examples") - var result := GdUnitFileAccess.copy_file("res://addons/gdUnit4/test/resources/core/sources/TestPerson.cs", temp) - assert_result(result).is_success() - - var example_source_cs = result.value() as String - var source := load(example_source_cs) - var test_suite_path := GdUnitTestSuiteScanner.resolve_test_suite_path(source.resource_path, "test") - result = GdUnit4CSharpApiLoader.create_test_suite(source.resource_path, 18, test_suite_path) - - assert_result(result).is_success() - var info := result.value() as Dictionary - assert_str(info.get("path")).is_equal("user://tmp/test/examples/TestPersonTest.cs") - assert_int(info.get("line")).is_equal(16) - - -func test_parse_test_suite() -> void: - var test_suite := GdUnit4CSharpApiLoader.parse_test_suite("res://addons/gdUnit4/test/mono/GdUnit4CSharpApiTest.cs") - assert_that(test_suite).is_not_null() - assert_that(test_suite.get("IsCsTestSuite")).is_true() - test_suite.free() - - -class TestRunListener extends Node: - pass - - -func test_executor() -> void: - var listener :TestRunListener = auto_free(TestRunListener.new()) - var executor = GdUnit4CSharpApiLoader.create_executor(listener) - assert_that(executor).is_not_null() - - var test_suite := GdUnit4CSharpApiLoader.parse_test_suite("res://addons/gdUnit4/test/mono/GdUnit4CSharpApiTest.cs") - assert_that(executor.IsExecutable(test_suite)).is_true() - - test_suite.free() diff --git a/addons/gdUnit4/test/mono/GdUnit4CSharpApiTest.cs b/addons/gdUnit4/test/mono/GdUnit4CSharpApiTest.cs deleted file mode 100644 index 584503e..0000000 --- a/addons/gdUnit4/test/mono/GdUnit4CSharpApiTest.cs +++ /dev/null @@ -1,22 +0,0 @@ -namespace GdUnit4 -{ - using static Assertions; - - [TestSuite] - public partial class GdUnit4CSharpApiTest - { - - [TestCase] - public void IsTestSuite() - { - AssertThat(GdUnit4CSharpApi.IsTestSuite("res://addons/gdUnit4/src/mono/GdUnit4CSharpApi.cs")).IsFalse(); - AssertThat(GdUnit4CSharpApi.IsTestSuite("res://addons/gdUnit4/test/mono/ExampleTestSuite.cs")).IsTrue(); - } - - [TestCase] - public void GetVersion() - { - AssertThat(GdUnit4CSharpApi.Version()).IsEqual("4.2.2.0"); - } - } -} diff --git a/addons/gdUnit4/test/network/GdUnitTcpServerTest.gd b/addons/gdUnit4/test/network/GdUnitTcpServerTest.gd deleted file mode 100644 index c00d522..0000000 --- a/addons/gdUnit4/test/network/GdUnitTcpServerTest.gd +++ /dev/null @@ -1,89 +0,0 @@ -# GdUnit generated TestSuite -class_name GdUnitTcpServerTest -extends GdUnitTestSuite -@warning_ignore('unused_parameter') -@warning_ignore('return_value_discarded') - -# TestSuite generated from -const __source = 'res://addons/gdUnit4/src/network/GdUnitTcpServer.gd' - -const DLM := GdUnitServerConstants.JSON_RESPONSE_DELIMITER - - -func test_read_next_data_packages() -> void: - var server = mock(TCPServer) - var stream = mock(StreamPeerTCP) - - do_return(stream).on(server).take_connection() - - var connection :GdUnitTcpServer.TcpConnection = auto_free(GdUnitTcpServer.TcpConnection.new(server)) - - # single package - var data = DLM + "aaaa" + DLM - var data_packages := connection._read_next_data_packages(data.to_utf8_buffer()) - assert_array(data_packages).contains_exactly(["aaaa"]) - - # many package - data = DLM + "aaaa" + DLM + "bbbb" + DLM + "cccc" + DLM + "dddd" + DLM + "eeee" + DLM - data_packages = connection._read_next_data_packages(data.to_utf8_buffer()) - assert_array(data_packages).contains_exactly(["aaaa", "bbbb", "cccc", "dddd", "eeee"]) - - # with splitted package - data_packages.clear() - var data1 := DLM + "aaaa" + DLM + "bbbb" + DLM + "cc" - var data2 := "cc" + DLM + "dd" - var data3 := "dd" + DLM + "eeee" + DLM - data_packages.append_array(connection._read_next_data_packages(data1.to_utf8_buffer())) - data_packages.append_array(connection._read_next_data_packages(data2.to_utf8_buffer())) - data_packages.append_array(connection._read_next_data_packages(data3.to_utf8_buffer())) - assert_array(data_packages).contains_exactly(["aaaa", "bbbb", "cccc", "dddd", "eeee"]) - - -func test_receive_packages() -> void: - var server = mock(TCPServer) - var stream = mock(StreamPeerTCP) - - do_return(stream).on(server).take_connection() - - var connection :GdUnitTcpServer.TcpConnection = auto_free(GdUnitTcpServer.TcpConnection.new(server)) - var test_server :GdUnitTcpServer = auto_free(GdUnitTcpServer.new()) - test_server.add_child(connection) - # create a signal collector to catch all signals emitted on the test server during `receive_packages()` - var signal_collector_ := signal_collector(test_server) - - # mock send RPCMessage - var data := DLM + RPCMessage.of("Test Message").serialize() + DLM - var package_data = [0, data.to_ascii_buffer()] - do_return(data.length()).on(stream).get_available_bytes() - do_return(package_data).on(stream).get_partial_data(data.length()) - - # do receive next packages - connection.receive_packages() - - # expect the RPCMessage is received and emitted - assert_that(signal_collector_.is_emitted("rpc_data", [RPCMessage.of("Test Message")])).is_true() - - -# TODO refactor out and provide as public interface to can be reuse on other tests -class TestGdUnitSignalCollector: - var _signalCollector :GdUnitSignalCollector - var _emitter :Variant - - - func _init(emitter :Variant): - _emitter = emitter - _signalCollector = GdUnitSignalCollector.new() - _signalCollector.register_emitter(emitter) - - - func is_emitted(signal_name :String, expected_args :Array) -> bool: - return _signalCollector.match(_emitter, signal_name, expected_args) - - - func _notification(what): - if what == NOTIFICATION_PREDELETE: - _signalCollector.unregister_emitter(_emitter) - - -func signal_collector(instance :Variant) -> TestGdUnitSignalCollector: - return TestGdUnitSignalCollector.new(instance) diff --git a/addons/gdUnit4/test/report/JUnitXmlReportTest.gd b/addons/gdUnit4/test/report/JUnitXmlReportTest.gd deleted file mode 100644 index 17ab5ac..0000000 --- a/addons/gdUnit4/test/report/JUnitXmlReportTest.gd +++ /dev/null @@ -1,28 +0,0 @@ -# GdUnit generated TestSuite -#warning-ignore-all:unused_argument -#warning-ignore-all:return_value_discarded -class_name JUnitXmlReportTest -extends GdUnitTestSuite - -# TestSuite generated from -const __source = 'res://addons/gdUnit4/src/report/JUnitXmlReport.gd' - - -func test_to_time() -> void: - assert_str(JUnitXmlReport.to_time(0)).is_equal("0.000") - assert_str(JUnitXmlReport.to_time(1)).is_equal("0.001") - assert_str(JUnitXmlReport.to_time(10)).is_equal("0.010") - assert_str(JUnitXmlReport.to_time(100)).is_equal("0.100") - assert_str(JUnitXmlReport.to_time(1000)).is_equal("1.000") - assert_str(JUnitXmlReport.to_time(10123)).is_equal("10.123") - - -func test_to_type() -> void: - assert_str(JUnitXmlReport.to_type(GdUnitReport.SUCCESS)).is_equal("SUCCESS") - assert_str(JUnitXmlReport.to_type(GdUnitReport.WARN)).is_equal("WARN") - assert_str(JUnitXmlReport.to_type(GdUnitReport.FAILURE)).is_equal("FAILURE") - assert_str(JUnitXmlReport.to_type(GdUnitReport.ORPHAN)).is_equal("ORPHAN") - assert_str(JUnitXmlReport.to_type(GdUnitReport.TERMINATED)).is_equal("TERMINATED") - assert_str(JUnitXmlReport.to_type(GdUnitReport.INTERUPTED)).is_equal("INTERUPTED") - assert_str(JUnitXmlReport.to_type(GdUnitReport.ABORT)).is_equal("ABORT") - assert_str(JUnitXmlReport.to_type(1000)).is_equal("UNKNOWN") diff --git a/addons/gdUnit4/test/report/XmlElementTest.gd b/addons/gdUnit4/test/report/XmlElementTest.gd deleted file mode 100644 index a8c9cc0..0000000 --- a/addons/gdUnit4/test/report/XmlElementTest.gd +++ /dev/null @@ -1,212 +0,0 @@ -# GdUnit generated TestSuite -#warning-ignore-all:unused_argument -#warning-ignore-all:return_value_discarded -class_name XmlElementTest -extends GdUnitTestSuite - -# TestSuite generated from -const __source = 'res://addons/gdUnit4/src/report/XmlElement.gd' - - -func test_attribute() -> void: - var element := XmlElement.new("testsuites")\ - .attribute(JUnitXmlReport.ATTR_ID, "1")\ - .attribute(JUnitXmlReport.ATTR_NAME, "foo") - var expected = \ -""" - -""".replace("\r", "") - assert_str(element.to_xml()).is_equal(expected) - element.dispose() - -func test_empty() -> void: - var element := XmlElement.new("testsuites") - var expected = \ -""" - -""".replace("\r", "") - assert_str(element.to_xml()).is_equal(expected) - element.dispose() - - -func test_add_child() -> void: - var child := XmlElement.new("foo")\ - .attribute(JUnitXmlReport.ATTR_ID, "1")\ - .attribute(JUnitXmlReport.ATTR_NAME, "foo") - var element := XmlElement.new("bar")\ - .attribute(JUnitXmlReport.ATTR_ID, "1")\ - .attribute(JUnitXmlReport.ATTR_NAME, "bar")\ - .add_child(child) - var expected = \ -""" - - - -""".replace("\r", "") - assert_str(element.to_xml()).is_equal(expected) - element.dispose() - - -func test_add_childs() -> void: - var child_a := XmlElement.new("foo_a")\ - .attribute(JUnitXmlReport.ATTR_ID, 1)\ - .attribute(JUnitXmlReport.ATTR_NAME, "foo_a") - var child_b := XmlElement.new("foo_b")\ - .attribute(JUnitXmlReport.ATTR_ID, 2)\ - .attribute(JUnitXmlReport.ATTR_NAME, "foo_b") - var element := XmlElement.new("bar")\ - .attribute(JUnitXmlReport.ATTR_ID, "1")\ - .attribute(JUnitXmlReport.ATTR_NAME, "bar")\ - .add_childs([child_a, child_b]) - var expected = \ -""" - - - - - -""".replace("\r", "") - assert_str(element.to_xml()).is_equal(expected) - element.dispose() - - -func test_add_text() -> void: - var element := XmlElement.new("testsuites")\ - .text("This is a message") - var expected = \ -""" - - -""".replace("\r", "") - assert_str(element.to_xml()).is_equal(expected) - element.dispose() - - -func test_complex_example() -> void: - var testsuite1 := XmlElement.new("testsuite")\ - .attribute(JUnitXmlReport.ATTR_ID, "1")\ - .attribute(JUnitXmlReport.ATTR_NAME, "bar") - for test_case in [1,2,3,4,5]: - var test := XmlElement.new("testcase")\ - .attribute(JUnitXmlReport.ATTR_ID, str(test_case))\ - .attribute(JUnitXmlReport.ATTR_NAME, "test_case_%d" % test_case) - testsuite1.add_child(test) - var testsuite2 := XmlElement.new("testsuite")\ - .attribute(JUnitXmlReport.ATTR_ID, "2")\ - .attribute(JUnitXmlReport.ATTR_NAME, "bar2") - for test_case in [1,2,3]: - var test := XmlElement.new("testcase")\ - .attribute(JUnitXmlReport.ATTR_ID, str(test_case))\ - .attribute(JUnitXmlReport.ATTR_NAME, "test_case_%d" % test_case) - if test_case == 2: - var failure := XmlElement.new("failure")\ - .attribute(JUnitXmlReport.ATTR_MESSAGE, "test_case.gd:12")\ - .attribute(JUnitXmlReport.ATTR_TYPE, "FAILURE")\ - .text("This is a failure\nExpecting true but was false\n") - test.add_child(failure) - testsuite2.add_child(test) - var root := XmlElement.new("testsuites")\ - .attribute(JUnitXmlReport.ATTR_ID, "ID-XXX")\ - .attribute(JUnitXmlReport.ATTR_NAME, "report_foo")\ - .attribute(JUnitXmlReport.ATTR_TESTS, 42)\ - .attribute(JUnitXmlReport.ATTR_FAILURES, 1)\ - .attribute(JUnitXmlReport.ATTR_TIME, "1.22")\ - .add_childs([testsuite1, testsuite2]) - var expected = \ -""" - - - - - - - - - - - - - - - - - - - - - - - - -""".replace("\r", "") - assert_str(root.to_xml()).is_equal(expected) - root.dispose() - - -func test_dispose() -> void: - var testsuite1 := XmlElement.new("testsuite")\ - .attribute(JUnitXmlReport.ATTR_ID, "1")\ - .attribute(JUnitXmlReport.ATTR_NAME, "bar") - var testsuite1_expected_tests := Array() - for test_case in [1,2,3,4,5]: - var test := XmlElement.new("testcase")\ - .attribute(JUnitXmlReport.ATTR_ID, str(test_case))\ - .attribute(JUnitXmlReport.ATTR_NAME, "test_case_%d" % test_case) - testsuite1.add_child(test) - testsuite1_expected_tests.append(test) - var testsuite2 := XmlElement.new("testsuite")\ - .attribute(JUnitXmlReport.ATTR_ID, "2")\ - .attribute(JUnitXmlReport.ATTR_NAME, "bar2") - var testsuite2_expected_tests := Array() - for test_case in [1,2,3]: - var test := XmlElement.new("testcase")\ - .attribute(JUnitXmlReport.ATTR_ID, str(test_case))\ - .attribute(JUnitXmlReport.ATTR_NAME, "test_case_%d" % test_case) - testsuite2_expected_tests.append(test) - if test_case == 2: - var failure := XmlElement.new("failure")\ - .attribute(JUnitXmlReport.ATTR_MESSAGE, "test_case.gd:12")\ - .attribute(JUnitXmlReport.ATTR_TYPE, "FAILURE")\ - .text("This is a failure\nExpecting true but was false\n") - test.add_child(failure) - testsuite2.add_child(test) - var root := XmlElement.new("testsuites")\ - .attribute(JUnitXmlReport.ATTR_ID, "ID-XXX")\ - .attribute(JUnitXmlReport.ATTR_NAME, "report_foo")\ - .attribute(JUnitXmlReport.ATTR_TESTS, 42)\ - .attribute(JUnitXmlReport.ATTR_FAILURES, 1)\ - .attribute(JUnitXmlReport.ATTR_TIME, "1.22")\ - .add_childs([testsuite1, testsuite2]) - - assert_that(root._parent).is_null() - assert_array(root._childs).contains_exactly([testsuite1, testsuite2]) - assert_dict(root._attributes).has_size(5) - - assert_that(testsuite1._parent).is_equal(root) - assert_array(testsuite1._childs).contains_exactly(testsuite1_expected_tests) - assert_dict(testsuite1._attributes).has_size(2) - testsuite1_expected_tests.clear() - - assert_that(testsuite2._parent).is_equal(root) - assert_array(testsuite2._childs).contains_exactly(testsuite2_expected_tests) - assert_dict(testsuite2._attributes).has_size(2) - testsuite2_expected_tests.clear() - - # free all references - root.dispose() - assert_that(root._parent).is_null() - assert_array(root._childs).is_empty() - assert_dict(root._attributes).is_empty() - - assert_that(testsuite1._parent).is_null() - assert_array(testsuite1._childs).is_empty() - assert_dict(testsuite1._attributes).is_empty() - - assert_that(testsuite2._parent).is_null() - assert_array(testsuite2._childs).is_empty() - assert_dict(testsuite2._attributes).is_empty() diff --git a/addons/gdUnit4/test/resources/core/City.gd b/addons/gdUnit4/test/resources/core/City.gd deleted file mode 100644 index a93e5d2..0000000 --- a/addons/gdUnit4/test/resources/core/City.gd +++ /dev/null @@ -1,8 +0,0 @@ -class_name City -extends Node - -func name() -> String: - return "" - -func location() -> String: - return "" diff --git a/addons/gdUnit4/test/resources/core/CustomClass.gd b/addons/gdUnit4/test/resources/core/CustomClass.gd deleted file mode 100644 index 3b0a172..0000000 --- a/addons/gdUnit4/test/resources/core/CustomClass.gd +++ /dev/null @@ -1,22 +0,0 @@ -# example class with inner classes -class_name CustomClass -extends RefCounted - - -# an inner class -class InnerClassA extends Node: - var x - -# an inner class inherits form another inner class -class InnerClassB extends InnerClassA: - var y - -# an inner class -class InnerClassC: - - func foo() -> String: - return "foo" - -class InnerClassD: - class InnerInnerClassA: - var x diff --git a/addons/gdUnit4/test/resources/core/GeneratedPersonTest.gd b/addons/gdUnit4/test/resources/core/GeneratedPersonTest.gd deleted file mode 100644 index 8aca0da..0000000 --- a/addons/gdUnit4/test/resources/core/GeneratedPersonTest.gd +++ /dev/null @@ -1,8 +0,0 @@ -class_name GeneratedPersonTest -extends GdUnitTestSuite - -# TestSuite generated from", -const __source = "res://addons/gdUnit4/test/resources/core/Person.gd" - -func test_name(): - assert_that(Person.new().name()).is_equal("Hoschi") diff --git a/addons/gdUnit4/test/resources/core/Person.gd b/addons/gdUnit4/test/resources/core/Person.gd deleted file mode 100644 index a05ea93..0000000 --- a/addons/gdUnit4/test/resources/core/Person.gd +++ /dev/null @@ -1,15 +0,0 @@ -class_name Person -extends Resource - - -func name() -> String: - return "Hoschi" - -func last_name() -> String: - return "Horst" - -func age() -> int: - return 42 - -func street() -> String: - return "Route 66" diff --git a/addons/gdUnit4/test/resources/core/Udo.gd b/addons/gdUnit4/test/resources/core/Udo.gd deleted file mode 100644 index 4432427..0000000 --- a/addons/gdUnit4/test/resources/core/Udo.gd +++ /dev/null @@ -1,6 +0,0 @@ -class_name Udo -extends Person - - -func _ready(): - pass # Replace with function body. diff --git a/addons/gdUnit4/test/resources/core/sources/TestPerson.cs b/addons/gdUnit4/test/resources/core/sources/TestPerson.cs deleted file mode 100644 index 20bf4f0..0000000 --- a/addons/gdUnit4/test/resources/core/sources/TestPerson.cs +++ /dev/null @@ -1,28 +0,0 @@ -using Godot; - -namespace Example.Test.Resources -{ - public partial class TestPerson - { - - public TestPerson(string firstName, string lastName) - { - FirstName = firstName; - LastName = lastName; - } - - public string FirstName { get; } - - public string LastName { get; } - - public string FullName => FirstName + " " + LastName; - - public string FullName2() => FirstName + " " + LastName; - - public string FullName3() - { - return FirstName + " " + LastName; - } - - } -} diff --git a/addons/gdUnit4/test/resources/issues/gd-166/issue.gd b/addons/gdUnit4/test/resources/issues/gd-166/issue.gd deleted file mode 100644 index 1123f6b..0000000 --- a/addons/gdUnit4/test/resources/issues/gd-166/issue.gd +++ /dev/null @@ -1,21 +0,0 @@ -extends Object - -const Type = preload("types.gd") - -var type = -1 : - get: - return type - set(value): - type = value - _set_type_name(value) - -var type_name - - -func _set_type(t:int): - type = t - - -func _set_type_name(type_ :int): - type_name = Type.to_str(type_) - print("type was set to %s" % type_name) diff --git a/addons/gdUnit4/test/resources/issues/gd-166/types.gd b/addons/gdUnit4/test/resources/issues/gd-166/types.gd deleted file mode 100644 index df685ea..0000000 --- a/addons/gdUnit4/test/resources/issues/gd-166/types.gd +++ /dev/null @@ -1,9 +0,0 @@ -extends Object - -enum { FOO, BAR, BAZ } - -static func to_str(type:int): - match type: - FOO: return "FOO" - BAR: return "BAR" - BAZ: return "BAZ" diff --git a/addons/gdUnit4/test/spy/GdUnitSpyBuilderTest.gd b/addons/gdUnit4/test/spy/GdUnitSpyBuilderTest.gd deleted file mode 100644 index 7b2ab21..0000000 --- a/addons/gdUnit4/test/spy/GdUnitSpyBuilderTest.gd +++ /dev/null @@ -1,332 +0,0 @@ -# GdUnit generated TestSuite -class_name GdUnitSpyBuilderTest -extends GdUnitTestSuite - -# TestSuite generated from -const __source = 'res://addons/gdUnit4/src/spy/GdUnitSpyBuilder.gd' - - -# helper to get function descriptor -func get_function_description(clazz_name :String, method_name :String) -> GdFunctionDescriptor: - var method_list :Array = ClassDB.class_get_method_list(clazz_name) - for method_descriptor in method_list: - if method_descriptor["name"] == method_name: - return GdFunctionDescriptor.extract_from(method_descriptor) - return null - - -func test_double__init() -> void: - var doubler := GdUnitSpyFunctionDoubler.new(false) - # void _init() virtual - var fd := get_function_description("Object", "_init") - var expected := [ - 'func _init() -> void:', - ' super()', - ' pass', - ''] - assert_array(doubler.double(fd)).contains_exactly(expected) - - -func test_double_return_typed_function_without_arg() -> void: - var doubler := GdUnitSpyFunctionDoubler.new(false) - # String get_class() const - var fd := get_function_description("Object", "get_class") - var expected := [ - '@warning_ignore("untyped_declaration")' if Engine.get_version_info().hex >= 0x40200 else '', - '@warning_ignore("native_method_override")', - '@warning_ignore("shadowed_variable")', - 'func get_class() -> String:', - ' var args :Array = ["get_class", ]', - '', - ' if __is_verify_interactions():', - ' __verify_interactions(args)', - ' return ""', - ' else:', - ' __save_function_interaction(args)', - '', - ' if __do_call_real_func("get_class"):', - ' return super()', - ' return ""', - '', - ''] - assert_array(doubler.double(fd)).contains_exactly(expected) - - -func test_double_return_typed_function_with_args() -> void: - var doubler := GdUnitSpyFunctionDoubler.new(false) - # bool is_connected(signal: String,Callable(target: Object,method: String)) const - var fd := get_function_description("Object", "is_connected") - var expected := [ - '@warning_ignore("untyped_declaration")' if Engine.get_version_info().hex >= 0x40200 else '', - '@warning_ignore("native_method_override")', - '@warning_ignore("shadowed_variable")', - 'func is_connected(signal_, callable_) -> bool:', - ' var args :Array = ["is_connected", signal_, callable_]', - '', - ' if __is_verify_interactions():', - ' __verify_interactions(args)', - ' return false', - ' else:', - ' __save_function_interaction(args)', - '', - ' if __do_call_real_func("is_connected"):', - ' return super(signal_, callable_)', - ' return false', - '', - ''] - assert_array(doubler.double(fd)).contains_exactly(expected) - - -func test_double_return_void_function_with_args() -> void: - var doubler := GdUnitSpyFunctionDoubler.new(false) - # void disconnect(signal: StringName, callable: Callable) - var fd := get_function_description("Object", "disconnect") - var expected := [ - '@warning_ignore("untyped_declaration")' if Engine.get_version_info().hex >= 0x40200 else '', - '@warning_ignore("native_method_override")', - '@warning_ignore("shadowed_variable")', - 'func disconnect(signal_, callable_) -> void:', - ' var args :Array = ["disconnect", signal_, callable_]', - '', - ' if __is_verify_interactions():', - ' __verify_interactions(args)', - ' return', - ' else:', - ' __save_function_interaction(args)', - '', - ' if __do_call_real_func("disconnect"):', - ' super(signal_, callable_)', - '', - ''] - assert_array(doubler.double(fd)).contains_exactly(expected) - - -func test_double_return_void_function_without_args() -> void: - var doubler := GdUnitSpyFunctionDoubler.new(false) - # void free() - var fd := get_function_description("Object", "free") - var expected := [ - '@warning_ignore("untyped_declaration")' if Engine.get_version_info().hex >= 0x40200 else '', - '@warning_ignore("native_method_override")', - '@warning_ignore("shadowed_variable")', - 'func free() -> void:', - ' var args :Array = ["free", ]', - '', - ' if __is_verify_interactions():', - ' __verify_interactions(args)', - ' return', - ' else:', - ' __save_function_interaction(args)', - '', - ' if __do_call_real_func("free"):', - ' super()', - '', - ''] - assert_array(doubler.double(fd)).contains_exactly(expected) - - -func test_double_return_typed_function_with_args_and_varargs() -> void: - var doubler := GdUnitSpyFunctionDoubler.new(false) - # Error emit_signal(signal: StringName, ...) vararg - var fd := get_function_description("Object", "emit_signal") - var expected := [ - '@warning_ignore("untyped_declaration")' if Engine.get_version_info().hex >= 0x40200 else '', - '@warning_ignore("native_method_override")', - '@warning_ignore("int_as_enum_without_match")', - '@warning_ignore("int_as_enum_without_cast")', - '@warning_ignore("shadowed_variable")', - 'func emit_signal(signal_, vararg0_="__null__", vararg1_="__null__", vararg2_="__null__", vararg3_="__null__", vararg4_="__null__", vararg5_="__null__", vararg6_="__null__", vararg7_="__null__", vararg8_="__null__", vararg9_="__null__") -> Error:', - ' var varargs :Array = __filter_vargs([vararg0_, vararg1_, vararg2_, vararg3_, vararg4_, vararg5_, vararg6_, vararg7_, vararg8_, vararg9_])', - ' var args :Array = ["emit_signal", signal_] + varargs', - '', - ' if __is_verify_interactions():', - ' __verify_interactions(args)', - ' return OK', - ' else:', - ' __save_function_interaction(args)', - '', - ' return __call_func("emit_signal", [signal_] + varargs)', - '', - ''] - assert_array(doubler.double(fd)).contains_exactly(expected) - - -func test_double_return_void_function_only_varargs() -> void: - var doubler := GdUnitSpyFunctionDoubler.new(false) - # void bar(s...) vararg - var fd := GdFunctionDescriptor.new( "bar", 23, false, false, false, TYPE_NIL, "void", [], GdFunctionDescriptor._build_varargs(true)) - var expected := [ - '@warning_ignore("untyped_declaration")' if Engine.get_version_info().hex >= 0x40200 else '', - '@warning_ignore("shadowed_variable")', - 'func bar(vararg0_="__null__", vararg1_="__null__", vararg2_="__null__", vararg3_="__null__", vararg4_="__null__", vararg5_="__null__", vararg6_="__null__", vararg7_="__null__", vararg8_="__null__", vararg9_="__null__") -> void:', - ' var varargs :Array = __filter_vargs([vararg0_, vararg1_, vararg2_, vararg3_, vararg4_, vararg5_, vararg6_, vararg7_, vararg8_, vararg9_])', - ' var args :Array = ["bar", ] + varargs', - '', - ' if __is_verify_interactions():', - ' __verify_interactions(args)', - ' return', - ' else:', - ' __save_function_interaction(args)', - '', - ' __call_func("bar", [] + varargs)', - '', - ''] - assert_array(doubler.double(fd)).contains_exactly(expected) - - -func test_double_return_typed_function_only_varargs() -> void: - var doubler := GdUnitSpyFunctionDoubler.new(false) - # String bar(s...) vararg - var fd := GdFunctionDescriptor.new( "bar", 23, false, false, false, TYPE_STRING, "String", [], GdFunctionDescriptor._build_varargs(true)) - var expected := [ - '@warning_ignore("untyped_declaration")' if Engine.get_version_info().hex >= 0x40200 else '', - '@warning_ignore("shadowed_variable")', - 'func bar(vararg0_="__null__", vararg1_="__null__", vararg2_="__null__", vararg3_="__null__", vararg4_="__null__", vararg5_="__null__", vararg6_="__null__", vararg7_="__null__", vararg8_="__null__", vararg9_="__null__") -> String:', - ' var varargs :Array = __filter_vargs([vararg0_, vararg1_, vararg2_, vararg3_, vararg4_, vararg5_, vararg6_, vararg7_, vararg8_, vararg9_])', - ' var args :Array = ["bar", ] + varargs', - '', - ' if __is_verify_interactions():', - ' __verify_interactions(args)', - ' return ""', - ' else:', - ' __save_function_interaction(args)', - '', - ' return __call_func("bar", [] + varargs)', - '', - ''] - assert_array(doubler.double(fd)).contains_exactly(expected) - - -func test_double_static_return_void_function_without_args() -> void: - var doubler := GdUnitSpyFunctionDoubler.new(false) - # void foo() - var fd := GdFunctionDescriptor.new( "foo", 23, false, true, false, TYPE_NIL, "", []) - var expected := [ - '@warning_ignore("untyped_declaration")' if Engine.get_version_info().hex >= 0x40200 else '', - '@warning_ignore("shadowed_variable")', - 'static func foo() -> void:', - ' var args :Array = ["foo", ]', - '', - ' if __instance().__is_verify_interactions():', - ' __instance().__verify_interactions(args)', - ' return', - ' else:', - ' __instance().__save_function_interaction(args)', - '', - ' if __instance().__do_call_real_func("foo"):', - ' super()', - '', - ''] - assert_array(doubler.double(fd)).contains_exactly(expected) - - -func test_double_static_return_void_function_with_args() -> void: - var doubler := GdUnitSpyFunctionDoubler.new(false) - var fd := GdFunctionDescriptor.new( "foo", 23, false, true, false, TYPE_NIL, "", [ - GdFunctionArgument.new("arg1", TYPE_BOOL), - GdFunctionArgument.new("arg2", TYPE_STRING, '"default"') - ]) - var expected := [ - '@warning_ignore("untyped_declaration")' if Engine.get_version_info().hex >= 0x40200 else '', - '@warning_ignore("shadowed_variable")', - 'static func foo(arg1, arg2="default") -> void:', - ' var args :Array = ["foo", arg1, arg2]', - '', - ' if __instance().__is_verify_interactions():', - ' __instance().__verify_interactions(args)', - ' return', - ' else:', - ' __instance().__save_function_interaction(args)', - '', - ' if __instance().__do_call_real_func("foo"):', - ' super(arg1, arg2)', - '', - ''] - assert_array(doubler.double(fd)).contains_exactly(expected) - - -func test_double_static_script_function_with_args_return_bool() -> void: - var doubler := GdUnitSpyFunctionDoubler.new(false) - - var fd := GdFunctionDescriptor.new( "foo", 23, false, true, false, TYPE_BOOL, "", [ - GdFunctionArgument.new("arg1", TYPE_BOOL), - GdFunctionArgument.new("arg2", TYPE_STRING, '"default"') - ]) - var expected := [ - '@warning_ignore("untyped_declaration")' if Engine.get_version_info().hex >= 0x40200 else '', - '@warning_ignore("shadowed_variable")', - 'static func foo(arg1, arg2="default") -> bool:', - ' var args :Array = ["foo", arg1, arg2]', - '', - ' if __instance().__is_verify_interactions():', - ' __instance().__verify_interactions(args)', - ' return false', - ' else:', - ' __instance().__save_function_interaction(args)', - '', - ' if __instance().__do_call_real_func("foo"):', - ' return super(arg1, arg2)', - ' return false', - '', - ''] - assert_array(doubler.double(fd)).contains_exactly(expected) - - -func test_double_virtual_return_void_function_with_arg() -> void: - var doubler := GdUnitSpyFunctionDoubler.new(false) - # void _input(event: InputEvent) virtual - var fd := get_function_description("Node", "_input") - var expected := [ - '@warning_ignore("untyped_declaration")' if Engine.get_version_info().hex >= 0x40200 else '', - '@warning_ignore("native_method_override")', - '@warning_ignore("shadowed_variable")', - 'func _input(event_) -> void:', - ' var args :Array = ["_input", event_]', - '', - ' if __is_verify_interactions():', - ' __verify_interactions(args)', - ' return', - ' else:', - ' __save_function_interaction(args)', - '', - ' if __do_call_real_func("_input"):', - ' super(event_)', - '', - ''] - assert_array(doubler.double(fd)).contains_exactly(expected) - - -func test_double_virtual_return_void_function_without_arg() -> void: - var doubler := GdUnitSpyFunctionDoubler.new(false) - # void _ready() virtual - var fd := get_function_description("Node", "_ready") - var expected := [ - '@warning_ignore("untyped_declaration")' if Engine.get_version_info().hex >= 0x40200 else '', - '@warning_ignore("native_method_override")', - '@warning_ignore("shadowed_variable")', - 'func _ready() -> void:', - ' var args :Array = ["_ready", ]', - '', - ' if __is_verify_interactions():', - ' __verify_interactions(args)', - ' return', - ' else:', - ' __save_function_interaction(args)', - '', - ' if __do_call_real_func("_ready"):', - ' super()', - '', - ''] - assert_array(doubler.double(fd)).contains_exactly(expected) - - -class NodeWithOutVirtualFunc extends Node: - func _ready(): - pass - - #func _input(event :InputEvent) -> void: - -func test_spy_on_script_respect_virtual_functions(): - var do_spy = auto_free(GdUnitSpyBuilder.spy_on_script(auto_free(NodeWithOutVirtualFunc.new()), [], true).new()) - assert_that(do_spy.has_method("_ready")).is_true() - assert_that(do_spy.has_method("_input")).is_false() diff --git a/addons/gdUnit4/test/spy/GdUnitSpyTest.gd b/addons/gdUnit4/test/spy/GdUnitSpyTest.gd deleted file mode 100644 index d52485c..0000000 --- a/addons/gdUnit4/test/spy/GdUnitSpyTest.gd +++ /dev/null @@ -1,621 +0,0 @@ -class_name GdUnitSpyTest -extends GdUnitTestSuite - - -func test_spy_instance_id_is_unique(): - var m1 = spy(RefCounted.new()) - var m2 = spy(RefCounted.new()) - # test the internal instance id is unique - assert_that(m1.__instance_id()).is_not_equal(m2.__instance_id()) - assert_object(m1).is_not_same(m2) - - -func test_cant_spy_is_not_a_instance(): - # returns null because spy needs an 'real' instance to by spy checked - var spy_node = spy(Node) - assert_object(spy_node).is_null() - - -func test_spy_on_Node(): - var instance :Node = auto_free(Node.new()) - var spy_node = spy(instance) - - # verify we have no interactions currently checked this instance - verify_no_interactions(spy_node) - - assert_object(spy_node)\ - .is_not_null()\ - .is_instanceof(Node)\ - .is_not_same(instance) - - # call first time - spy_node.set_process(false) - - # verify is called one times - verify(spy_node).set_process(false) - # just double check that verify has no affect to the counter - verify(spy_node).set_process(false) - - # call a scond time - spy_node.set_process(false) - # verify is called two times - verify(spy_node, 2).set_process(false) - verify(spy_node, 2).set_process(false) - - -func test_spy_source_with_class_name_by_resource_path() -> void: - var instance = auto_free(load('res://addons/gdUnit4/test/mocker/resources/GD-256/world.gd').new()) - var m = spy(instance) - var head :String = m.get_script().source_code.substr(0, 200) - assert_str(head)\ - .contains("class_name DoubledMunderwoodPathingWorld")\ - .contains("extends 'res://addons/gdUnit4/test/mocker/resources/GD-256/world.gd'") - - -func test_spy_source_with_class_name_by_class() -> void: - var m = spy(auto_free(Munderwood_Pathing_World.new())) - var head :String = m.get_script().source_code.substr(0, 200) - assert_str(head)\ - .contains("class_name DoubledMunderwoodPathingWorld")\ - .contains("extends 'res://addons/gdUnit4/test/mocker/resources/GD-256/world.gd'") - - -func test_spy_extends_godot_class() -> void: - var m = spy(auto_free(World3D.new())) - var head :String = m.get_script().source_code.substr(0, 200) - assert_str(head)\ - .contains("class_name DoubledWorld")\ - .contains("extends World3D") - - -func test_spy_on_custom_class(): - var instance :AdvancedTestClass = auto_free(AdvancedTestClass.new()) - var spy_instance = spy(instance) - - # verify we have currently no interactions - verify_no_interactions(spy_instance) - - assert_object(spy_instance)\ - .is_not_null()\ - .is_instanceof(AdvancedTestClass)\ - .is_not_same(instance) - - spy_instance.setup_local_to_scene() - verify(spy_instance, 1).setup_local_to_scene() - - # call first time script func with different arguments - spy_instance.get_area("test_a") - spy_instance.get_area("test_b") - spy_instance.get_area("test_c") - - # verify is each called only one time for different arguments - verify(spy_instance, 1).get_area("test_a") - verify(spy_instance, 1).get_area("test_b") - verify(spy_instance, 1).get_area("test_c") - # an second call with arg "test_c" - spy_instance.get_area("test_c") - verify(spy_instance, 1).get_area("test_a") - verify(spy_instance, 1).get_area("test_b") - verify(spy_instance, 2).get_area("test_c") - - # verify if a not used argument not counted - verify(spy_instance, 0).get_area("test_no") - - -# GD-291 https://github.com/MikeSchulze/gdUnit4/issues/291 -func test_spy_class_with_custom_formattings() -> void: - var resource = load("res://addons/gdUnit4/test/mocker/resources/ClassWithCustomFormattings.gd") - var do_spy = spy(auto_free(resource.new("test"))) - do_spy.a1("set_name", "", true) - verify(do_spy, 1).a1("set_name", "", true) - verify_no_more_interactions(do_spy) - assert_failure(func(): verify_no_interactions(do_spy))\ - .is_failed() \ - .has_line(112) - - -func test_spy_copied_class_members(): - var instance = auto_free(load("res://addons/gdUnit4/test/mocker/resources/TestPersion.gd").new("user-x", "street", 56616)) - assert_that(instance._name).is_equal("user-x") - assert_that(instance._value).is_equal(1024) - assert_that(instance._address._street).is_equal("street") - assert_that(instance._address._code).is_equal(56616) - - # spy it - var spy_instance = spy(instance) - reset(spy_instance) - - # verify members are inital copied - assert_that(spy_instance._name).is_equal("user-x") - assert_that(spy_instance._value).is_equal(1024) - assert_that(spy_instance._address._street).is_equal("street") - assert_that(spy_instance._address._code).is_equal(56616) - - spy_instance._value = 2048 - assert_that(instance._value).is_equal(1024) - assert_that(spy_instance._value).is_equal(2048) - - -func test_spy_copied_class_members_on_node(): - var node :Node = auto_free(Node.new()) - # checked a fresh node the name is empty and results into a error when copied at spy - # E 0:00:01.518 set_name: Condition "name == """ is true. - # C++ Source> scene/main/node.cpp:934 @ set_name() - # we set a placeholder instead - assert_that(spy(node).name).is_equal("") - - node.set_name("foo") - assert_that(spy(node).name).is_equal("foo") - - -func test_spy_on_inner_class(): - var instance :AdvancedTestClass.AtmosphereData = auto_free(AdvancedTestClass.AtmosphereData.new()) - var spy_instance :AdvancedTestClass.AtmosphereData = spy(instance) - - # verify we have currently no interactions - verify_no_interactions(spy_instance) - - assert_object(spy_instance)\ - .is_not_null()\ - .is_instanceof(AdvancedTestClass.AtmosphereData)\ - .is_not_same(instance) - - spy_instance.set_data(AdvancedTestClass.AtmosphereData.SMOKY, 1.2) - spy_instance.set_data(AdvancedTestClass.AtmosphereData.SMOKY, 1.3) - verify(spy_instance, 1).set_data(AdvancedTestClass.AtmosphereData.SMOKY, 1.2) - verify(spy_instance, 1).set_data(AdvancedTestClass.AtmosphereData.SMOKY, 1.3) - - -func test_spy_on_singleton(): - await assert_error(func () -> void: - var spy_node_ = spy(Input) - assert_object(spy_node_).is_null() - await await_idle_frame()).is_push_error("Spy on a Singleton is not allowed! 'Input'") - - -func test_example_verify(): - var instance :Node = auto_free(Node.new()) - var spy_node = spy(instance) - - # verify we have no interactions currently checked this instance - verify_no_interactions(spy_node) - - # call with different arguments - spy_node.set_process(false) # 1 times - spy_node.set_process(true) # 1 times - spy_node.set_process(true) # 2 times - - # verify how often we called the function with different argument - verify(spy_node, 2).set_process(true) # in sum two times with true - verify(spy_node, 1).set_process(false)# in sum one time with false - - # verify total sum by using an argument matcher - verify(spy_node, 3).set_process(any_bool()) - - -func test_verify_fail(): - var instance :Node = auto_free(Node.new()) - var spy_node = spy(instance) - - # interact two time - spy_node.set_process(true) # 1 times - spy_node.set_process(true) # 2 times - - # verify we interacts two times - verify(spy_node, 2).set_process(true) - - # verify should fail because we interacts two times and not one - var expected_error := """ - Expecting interaction on: - 'set_process(true :bool)' 1 time's - But found interactions on: - 'set_process(true :bool)' 2 time's""" \ - .dedent().trim_prefix("\n") - assert_failure(func(): verify(spy_node, 1).set_process(true)) \ - .is_failed() \ - .has_line(214) \ - .has_message(expected_error) - - -func test_verify_func_interaction_wiht_PoolStringArray(): - var spy_instance :ClassWithPoolStringArrayFunc = spy(ClassWithPoolStringArrayFunc.new()) - - spy_instance.set_values(PackedStringArray()) - - verify(spy_instance).set_values(PackedStringArray()) - verify_no_more_interactions(spy_instance) - - -func test_verify_func_interaction_wiht_PackedStringArray_fail(): - var spy_instance :ClassWithPoolStringArrayFunc = spy(ClassWithPoolStringArrayFunc.new()) - - spy_instance.set_values(PackedStringArray()) - - # try to verify with default array type instead of PackedStringArray type - var expected_error := """ - Expecting interaction on: - 'set_values([] :Array)' 1 time's - But found interactions on: - 'set_values([] :PackedStringArray)' 1 time's""" \ - .dedent().trim_prefix("\n") - assert_failure(func(): verify(spy_instance, 1).set_values([])) \ - .is_failed() \ - .has_line(241) \ - .has_message(expected_error) - - reset(spy_instance) - # try again with called two times and different args - spy_instance.set_values(PackedStringArray()) - spy_instance.set_values(PackedStringArray(["a", "b"])) - spy_instance.set_values([1, 2]) - expected_error = """ - Expecting interaction on: - 'set_values([] :Array)' 1 time's - But found interactions on: - 'set_values([] :PackedStringArray)' 1 time's - 'set_values(["a", "b"] :PackedStringArray)' 1 time's - 'set_values([1, 2] :Array)' 1 time's""" \ - .dedent().trim_prefix("\n") - assert_failure(func(): verify(spy_instance, 1).set_values([])) \ - .is_failed() \ - .has_line(259) \ - .has_message(expected_error) - - -func test_reset(): - var instance :Node = auto_free(Node.new()) - var spy_node = spy(instance) - - # call with different arguments - spy_node.set_process(false) # 1 times - spy_node.set_process(true) # 1 times - spy_node.set_process(true) # 2 times - - verify(spy_node, 2).set_process(true) - verify(spy_node, 1).set_process(false) - - # now reset the spy - reset(spy_node) - # verify all counters have been reset - verify_no_interactions(spy_node) - - -func test_verify_no_interactions(): - var instance :Node = auto_free(Node.new()) - var spy_node = spy(instance) - - # verify we have no interactions checked this mock - verify_no_interactions(spy_node) - - -func test_verify_no_interactions_fails(): - var instance :Node = auto_free(Node.new()) - var spy_node = spy(instance) - - # interact - spy_node.set_process(false) # 1 times - spy_node.set_process(true) # 1 times - spy_node.set_process(true) # 2 times - - var expected_error =""" - Expecting no more interactions! - But found interactions on: - 'set_process(false :bool)' 1 time's - 'set_process(true :bool)' 2 time's""" \ - .dedent().trim_prefix("\n") - # it should fail because we have interactions - assert_failure(func(): verify_no_interactions(spy_node)) \ - .is_failed() \ - .has_line(307) \ - .has_message(expected_error) - - -func test_verify_no_more_interactions(): - var instance :Node = auto_free(Node.new()) - var spy_node = spy(instance) - - spy_node.is_ancestor_of(instance) - spy_node.set_process(false) - spy_node.set_process(true) - spy_node.set_process(true) - - # verify for called functions - verify(spy_node, 1).is_ancestor_of(instance) - verify(spy_node, 2).set_process(true) - verify(spy_node, 1).set_process(false) - - # There should be no more interactions checked this mock - verify_no_more_interactions(spy_node) - - -func test_verify_no_more_interactions_but_has(): - var instance :Node = auto_free(Node.new()) - var spy_node = spy(instance) - - spy_node.is_ancestor_of(instance) - spy_node.set_process(false) - spy_node.set_process(true) - spy_node.set_process(true) - - # now we simulate extra calls that we are not explicit verify - spy_node.is_inside_tree() - spy_node.is_inside_tree() - # a function with default agrs - spy_node.find_child("mask") - # same function again with custom agrs - spy_node.find_child("mask", false, false) - - # verify 'all' exclusive the 'extra calls' functions - verify(spy_node, 1).is_ancestor_of(instance) - verify(spy_node, 2).set_process(true) - verify(spy_node, 1).set_process(false) - - # now use 'verify_no_more_interactions' to check we have no more interactions checked this mock - # but should fail with a collecion of all not validated interactions - var expected_error =""" - Expecting no more interactions! - But found interactions on: - 'is_inside_tree()' 2 time's - 'find_child(mask :String, true :bool, true :bool)' 1 time's - 'find_child(mask :String, false :bool, false :bool)' 1 time's""" \ - .dedent().trim_prefix("\n") - assert_failure(func(): verify_no_more_interactions(spy_node)) \ - .is_failed() \ - .has_line(362) \ - .has_message(expected_error) - - -class ClassWithStaticFunctions: - - static func foo() -> void: - pass - - static func bar(): - pass - - -func test_create_spy_static_func_untyped(): - var instance = spy(ClassWithStaticFunctions.new()) - assert_object(instance).is_not_null() - - -func test_spy_snake_case_named_class_by_resource_path(): - var instance_a = load("res://addons/gdUnit4/test/mocker/resources/snake_case.gd").new() - var spy_a = spy(instance_a) - assert_object(spy_a).is_not_null() - - spy_a.custom_func() - verify(spy_a).custom_func() - verify_no_more_interactions(spy_a) - - var instance_b = load("res://addons/gdUnit4/test/mocker/resources/snake_case_class_name.gd").new() - var spy_b = spy(instance_b) - assert_object(spy_b).is_not_null() - - spy_b.custom_func() - verify(spy_b).custom_func() - verify_no_more_interactions(spy_b) - - -func test_spy_snake_case_named_class_by_class(): - var do_spy = spy(snake_case_class_name.new()) - assert_object(do_spy).is_not_null() - - do_spy.custom_func() - verify(do_spy).custom_func() - verify_no_more_interactions(do_spy) - - # try checked Godot class - var spy_tcp_server = spy(TCPServer.new()) - assert_object(spy_tcp_server).is_not_null() - - spy_tcp_server.is_listening() - spy_tcp_server.is_connection_available() - verify(spy_tcp_server).is_listening() - verify(spy_tcp_server).is_connection_available() - verify_no_more_interactions(spy_tcp_server) - - -const Issue = preload("res://addons/gdUnit4/test/resources/issues/gd-166/issue.gd") -const Type = preload("res://addons/gdUnit4/test/resources/issues/gd-166/types.gd") - - -func test_spy_preload_class_GD_166() -> void: - var instance = auto_free(Issue.new()) - var spy_instance = spy(instance) - - spy_instance.type = Type.FOO - verify(spy_instance, 1)._set_type_name(Type.FOO) - assert_int(spy_instance.type).is_equal(Type.FOO) - assert_str(spy_instance.type_name).is_equal("FOO") - - -var _test_signal_args := Array() -func _emit_ready(a, b, c = null): - _test_signal_args = [a, b, c] - - -# https://github.com/MikeSchulze/gdUnit4/issues/38 -func test_spy_Node_use_real_func_vararg(): - # setup - var instance := Node.new() - instance.connect("ready", _emit_ready) - assert_that(_test_signal_args).is_empty() - var spy_node = spy(auto_free(instance)) - assert_that(spy_node).is_not_null() - - # test emit it - spy_node.emit_signal("ready", "aa", "bb", "cc") - # verify is emitted - verify(spy_node).emit_signal("ready", "aa", "bb", "cc") - await get_tree().process_frame - assert_that(_test_signal_args).is_equal(["aa", "bb", "cc"]) - - # test emit it - spy_node.emit_signal("ready", "aa", "xxx") - # verify is emitted - verify(spy_node).emit_signal("ready", "aa", "xxx") - await get_tree().process_frame - assert_that(_test_signal_args).is_equal(["aa", "xxx", null]) - - -class ClassWithSignal: - signal test_signal_a - signal test_signal_b - - func foo(arg :int) -> void: - if arg == 0: - emit_signal("test_signal_a", "aa") - else: - emit_signal("test_signal_b", "bb", true) - - func bar(arg :int) -> bool: - if arg == 0: - emit_signal("test_signal_a", "aa") - else: - emit_signal("test_signal_b", "bb", true) - return true - - -# https://github.com/MikeSchulze/gdUnit4/issues/14 -func _test_spy_verify_emit_signal(): - var spy_instance = spy(ClassWithSignal.new()) - assert_that(spy_instance).is_not_null() - - spy_instance.foo(0) - verify(spy_instance, 1).emit_signal("test_signal_a", "aa") - verify(spy_instance, 0).emit_signal("test_signal_b", "bb", true) - reset(spy_instance) - - spy_instance.foo(1) - verify(spy_instance, 0).emit_signal("test_signal_a", "aa") - verify(spy_instance, 1).emit_signal("test_signal_b", "bb", true) - reset(spy_instance) - - spy_instance.bar(0) - verify(spy_instance, 1).emit_signal("test_signal_a", "aa") - verify(spy_instance, 0).emit_signal("test_signal_b", "bb", true) - reset(spy_instance) - - spy_instance.bar(1) - verify(spy_instance, 0).emit_signal("test_signal_a", "aa") - verify(spy_instance, 1).emit_signal("test_signal_b", "bb", true) - - -func test_spy_func_with_default_build_in_type(): - var spy_instance :ClassWithDefaultBuildIntTypes = spy(ClassWithDefaultBuildIntTypes.new()) - assert_object(spy_instance).is_not_null() - # call with default arg - spy_instance.foo("abc") - spy_instance.bar("def") - verify(spy_instance).foo("abc", Color.RED) - verify(spy_instance).bar("def", Vector3.FORWARD, AABB()) - verify_no_more_interactions(spy_instance) - - # call with custom args - spy_instance.foo("abc", Color.BLUE) - spy_instance.bar("def", Vector3.DOWN, AABB(Vector3.ONE, Vector3.ZERO)) - verify(spy_instance).foo("abc", Color.BLUE) - verify(spy_instance).bar("def", Vector3.DOWN, AABB(Vector3.ONE, Vector3.ZERO)) - verify_no_more_interactions(spy_instance) - - -func test_spy_scene_by_resource_path(): - var spy_scene = spy("res://addons/gdUnit4/test/mocker/resources/scenes/TestScene.tscn") - assert_object(spy_scene)\ - .is_not_null()\ - .is_not_instanceof(PackedScene)\ - .is_instanceof(Control) - assert_str(spy_scene.get_script().resource_name).is_equal("SpyTestScene.gd") - # check is spyed scene registered for auto freeing - assert_bool(GdUnitMemoryObserver.is_marked_auto_free(spy_scene)).is_true() - - -func test_spy_on_PackedScene(): - var resource := load("res://addons/gdUnit4/test/mocker/resources/scenes/TestScene.tscn") - var original_script = resource.get_script() - assert_object(resource).is_instanceof(PackedScene) - - var spy_scene = spy(resource) - - assert_object(spy_scene)\ - .is_not_null()\ - .is_not_instanceof(PackedScene)\ - .is_not_same(resource) - assert_object(spy_scene.get_script())\ - .is_not_null()\ - .is_instanceof(GDScript)\ - .is_not_same(original_script) - assert_str(spy_scene.get_script().resource_name).is_equal("SpyTestScene.gd") - # check is spyed scene registered for auto freeing - assert_bool(GdUnitMemoryObserver.is_marked_auto_free(spy_scene)).is_true() - - -func test_spy_scene_by_instance(): - var resource := load("res://addons/gdUnit4/test/mocker/resources/scenes/TestScene.tscn") - var instance :Control = resource.instantiate() - var original_script = instance.get_script() - var spy_scene = spy(instance) - - assert_object(spy_scene)\ - .is_not_null()\ - .is_same(instance)\ - .is_instanceof(Control) - assert_object(spy_scene.get_script())\ - .is_not_null()\ - .is_instanceof(GDScript)\ - .is_not_same(original_script) - assert_str(spy_scene.get_script().resource_name).is_equal("SpyTestScene.gd") - # check is mocked scene registered for auto freeing - assert_bool(GdUnitMemoryObserver.is_marked_auto_free(spy_scene)).is_true() - - -func test_spy_scene_by_path_fail_has_no_script_attached(): - var resource := load("res://addons/gdUnit4/test/mocker/resources/scenes/TestSceneWithoutScript.tscn") - var instance :Control = auto_free(resource.instantiate()) - - # has to fail and return null - var spy_scene = spy(instance) - assert_object(spy_scene).is_null() - - -func test_spy_scene_initalize(): - var spy_scene = spy("res://addons/gdUnit4/test/mocker/resources/scenes/TestScene.tscn") - assert_object(spy_scene).is_not_null() - - # Add as child to a scene tree to trigger _ready to initalize all variables - add_child(spy_scene) - # ensure _ready is recoreded and onyl once called - verify(spy_scene, 1)._ready() - verify(spy_scene, 1).only_one_time_call() - assert_object(spy_scene._box1).is_not_null() - assert_object(spy_scene._box2).is_not_null() - assert_object(spy_scene._box3).is_not_null() - - # check signals are connected - assert_bool(spy_scene.is_connected("panel_color_change",Callable(spy_scene,"_on_panel_color_changed"))) - - # check exports - assert_str(spy_scene._initial_color.to_html()).is_equal(Color.RED.to_html()) - - -class CustomNode extends Node: - - func _ready(): - # we call this function to verify the _ready is only once called - # this is need to verify `add_child` is calling the original implementation only once - only_one_time_call() - - func only_one_time_call() -> void: - pass - - -func test_spy_ready_called_once(): - var spy_node = spy(auto_free(CustomNode.new())) - - # Add as child to a scene tree to trigger _ready to initalize all variables - add_child(spy_node) - - # ensure _ready is recoreded and onyl once called - verify(spy_node, 1)._ready() - verify(spy_node, 1).only_one_time_call() diff --git a/addons/gdUnit4/test/ui/GdUnitFontsTest.gd b/addons/gdUnit4/test/ui/GdUnitFontsTest.gd deleted file mode 100644 index 96220de..0000000 --- a/addons/gdUnit4/test/ui/GdUnitFontsTest.gd +++ /dev/null @@ -1,17 +0,0 @@ -# GdUnit generated TestSuite -class_name GdUnitFontsTest -extends GdUnitTestSuite -@warning_ignore('unused_parameter') -@warning_ignore('return_value_discarded') - -# TestSuite generated from -const __source = 'res://addons/gdUnit4/src/ui/GdUnitFonts.gd' - - -func test_load_and_resize_font() -> void: - var font8 = GdUnitFonts.load_and_resize_font(GdUnitFonts.FONT_MONO, 8) - var font16 = GdUnitFonts.load_and_resize_font(GdUnitFonts.FONT_MONO, 16) - - assert_object(font8).is_not_null().is_not_same(font16) - assert_that(font8.fixed_size).is_equal(8) - assert_that(font16.fixed_size).is_equal(16) diff --git a/addons/gdUnit4/test/ui/parts/InspectorProgressBarTest.gd b/addons/gdUnit4/test/ui/parts/InspectorProgressBarTest.gd deleted file mode 100644 index 530e91f..0000000 --- a/addons/gdUnit4/test/ui/parts/InspectorProgressBarTest.gd +++ /dev/null @@ -1,84 +0,0 @@ -# GdUnit generated TestSuite -class_name InspectorProgressBarTest -extends GdUnitTestSuite -@warning_ignore('unused_parameter') -@warning_ignore('return_value_discarded') - -# TestSuite generated from -const __source = 'res://addons/gdUnit4/src/ui/parts/InspectorProgressBar.gd' - -var _runner :GdUnitSceneRunner -var _progress :ProgressBar -var _status :Label -var _style :StyleBoxFlat - - -func before_test(): - _runner = scene_runner('res://addons/gdUnit4/src/ui/parts/InspectorProgressBar.tscn') - _progress = _runner.get_property("bar") - _status = _runner.get_property("status") - _style = _runner.get_property("style") - # inital state - assert_that(_status.text).is_equal("0:0") - assert_that(_progress.value).is_equal(0) - assert_that(_progress.max_value).is_equal(0) - _runner.invoke("_on_gdunit_event", GdUnitInit.new(10, 42)) - - -func test_progress_init() -> void: - _runner.invoke("_on_gdunit_event", GdUnitInit.new(10, 230)) - assert_that(_progress.value).is_equal(0) - assert_that(_progress.max_value).is_equal(230) - assert_that(_status.text).is_equal("0:230") - assert_that(_style.bg_color).is_equal(Color.DARK_GREEN) - - -func test_progress_success() -> void: - _runner.invoke("_on_gdunit_event", GdUnitInit.new(10, 42)) - var expected_progess_index := 0 - # simulate execution of 20 success test runs - for index in 20: - _runner.invoke("_on_gdunit_event", GdUnitEvent.new().test_after("res://test/testA.gd", "TestSuiteA", "test_a%d" % index, {})) - expected_progess_index += 1 - assert_that(_progress.value).is_equal(expected_progess_index) - assert_that(_status.text).is_equal("%d:42" % expected_progess_index) - assert_that(_style.bg_color).is_equal(Color.DARK_GREEN) - - # simulate execution of parameterized test with 10 iterations - for index in 10: - _runner.invoke("_on_gdunit_event", GdUnitEvent.new().test_after("res://test/testA.gd", "TestSuiteA", "test_parameterized:%d (params)" % index, {})) - assert_that(_progress.value).is_equal(expected_progess_index) - # final test end event - _runner.invoke("_on_gdunit_event", GdUnitEvent.new().test_after("res://test/testA.gd", "TestSuiteA", "test_parameterized", {})) - # we expect only one progress step after a parameterized test has been executed, regardless of the iterations - expected_progess_index += 1 - assert_that(_progress.value).is_equal(expected_progess_index) - assert_that(_status.text).is_equal("%d:42" % expected_progess_index) - assert_that(_style.bg_color).is_equal(Color.DARK_GREEN) - - # verify the max progress state is not affected - assert_that(_progress.max_value).is_equal(42) - - -@warning_ignore("unused_parameter") -func test_progress_failed(test_name :String, is_failed :bool, is_error :bool, expected_color :Color, test_parameters = [ - ["test_a", false, false, Color.DARK_GREEN], - ["test_b", false, false, Color.DARK_GREEN], - ["test_c", false, false, Color.DARK_GREEN], - ["test_d", true, false, Color.DARK_RED], - ["test_e", true, false, Color.DARK_RED], -]) -> void: - var statistics = { - GdUnitEvent.ORPHAN_NODES: 0, - GdUnitEvent.ELAPSED_TIME: 100, - GdUnitEvent.WARNINGS: false, - GdUnitEvent.ERRORS: is_error, - GdUnitEvent.ERROR_COUNT: 1 if is_error else 0, - GdUnitEvent.FAILED: is_failed, - GdUnitEvent.FAILED_COUNT: 1 if is_failed else 0, - GdUnitEvent.SKIPPED: false, - GdUnitEvent.SKIPPED_COUNT: 0, - } - - _runner.invoke("_on_gdunit_event", GdUnitEvent.new().test_after("res://test/testA.gd", "TestSuiteA", test_name, statistics)) - assert_that(_style.bg_color).is_equal(expected_color) diff --git a/addons/gdUnit4/test/ui/parts/InspectorTreeMainPanelPerformanceTest.gd b/addons/gdUnit4/test/ui/parts/InspectorTreeMainPanelPerformanceTest.gd deleted file mode 100644 index 3bbdfb6..0000000 --- a/addons/gdUnit4/test/ui/parts/InspectorTreeMainPanelPerformanceTest.gd +++ /dev/null @@ -1,168 +0,0 @@ -# GdUnit generated TestSuite -class_name InspectorTreeMainPanelPerformanceTest -extends GdUnitTestSuite - -# TestSuite generated from -const __source = 'res://addons/gdUnit4/src/ui/parts/InspectorTreeMainPanel.gd' - -# this test-suite contains only empty test to run as performance indicator - - -func test_01() -> void: - pass - - -func test_02() -> void: - pass - - -func test_03() -> void: - pass - - -func test_04() -> void: - pass - - -func test_05() -> void: - pass - - -func test_06() -> void: - pass - - -func test_07() -> void: - pass - - -func test_08() -> void: - pass - - -func test_09() -> void: - pass - - -func test_10() -> void: - pass - - -func test_11() -> void: - pass - - -func test_12() -> void: - pass - - -func test_13() -> void: - pass - - -func test_14() -> void: - pass - - -func test_15() -> void: - pass - - -func test_16() -> void: - pass - - -func test_17() -> void: - pass - - -func test_18() -> void: - pass - - -func test_19() -> void: - pass - - -func test_20() -> void: - pass - - -func test_21() -> void: - pass - - -func test_22() -> void: - pass - - -func test_23() -> void: - pass - - -func test_24() -> void: - pass - - -func test_25() -> void: - pass - - -func test_26() -> void: - pass - - -func test_27() -> void: - pass - - -func test_28() -> void: - pass - - -func test_29() -> void: - pass - - -func test_30() -> void: - pass - - -func test_31() -> void: - pass - - -func test_32() -> void: - pass - - -func test_33() -> void: - pass - - -func test_34() -> void: - pass - - -func test_35() -> void: - pass - - -func test_36() -> void: - pass - - -func test_37() -> void: - pass - - -func test_38() -> void: - pass - - -func test_39() -> void: - pass - - -func test_40() -> void: - pass diff --git a/addons/gdUnit4/test/ui/parts/InspectorTreeMainPanelTest.gd b/addons/gdUnit4/test/ui/parts/InspectorTreeMainPanelTest.gd deleted file mode 100644 index b5a5bf9..0000000 --- a/addons/gdUnit4/test/ui/parts/InspectorTreeMainPanelTest.gd +++ /dev/null @@ -1,303 +0,0 @@ -# GdUnit generated TestSuite -class_name InspectorTreeMainPanelTest -extends GdUnitTestSuite - -# TestSuite generated from -const __source = 'res://addons/gdUnit4/src/ui/parts/InspectorTreeMainPanel.gd' - -var TEST_SUITE_A :String -var TEST_SUITE_B :String -var TEST_SUITE_C :String - -var _inspector - - -func before_test(): - _inspector = load("res://addons/gdUnit4/src/ui/parts/InspectorTreePanel.tscn").instantiate() - add_child(_inspector) - _inspector.init_tree() - - # load a testsuite - for test_suite in setup_test_env(): - _inspector.add_test_suite(toDto(test_suite)) - # verify no failures are exists - assert_array(_inspector.collect_failures_and_errors()).is_empty() - - -func after_test(): - _inspector.cleanup_tree() - remove_child(_inspector) - _inspector.free() - - -func toDto(test_suite :Node) -> GdUnitTestSuiteDto: - var dto := GdUnitTestSuiteDto.new() - return dto.deserialize(dto.serialize(test_suite)) as GdUnitTestSuiteDto - - -func setup_test_env() -> Array: - var test_suite_a := GdUnitTestResourceLoader.load_test_suite("res://addons/gdUnit4/test/ui/parts/resources/foo/ExampleTestSuiteA.resource") - var test_suite_b := GdUnitTestResourceLoader.load_test_suite("res://addons/gdUnit4/test/ui/parts/resources/foo/ExampleTestSuiteB.resource") - var test_suite_c := GdUnitTestResourceLoader.load_test_suite("res://addons/gdUnit4/test/ui/parts/resources/foo/ExampleTestSuiteC.resource") - TEST_SUITE_A = test_suite_a.get_script().resource_path - TEST_SUITE_B = test_suite_b.get_script().resource_path - TEST_SUITE_C = test_suite_c.get_script().resource_path - return Array([auto_free(test_suite_a), auto_free(test_suite_b), auto_free(test_suite_c)]) - - -func mark_as_failure(inspector, test_cases :Array) -> void: - var tree_root :TreeItem = inspector._tree_root - assert_object(tree_root).is_not_null() - # mark all test as failed - for parent in tree_root.get_children(): - inspector.set_state_succeded(parent) - for item in parent.get_children(): - if test_cases.has(item.get_text(0)): - inspector.set_state_failed(parent) - inspector.set_state_failed(item) - else: - inspector.set_state_succeded(item) - item = item.get_next() - parent = parent.get_next() - -func get_item_state(parent :TreeItem, item_name :String) -> int: - var item = _inspector._find_by_name(parent, item_name) - return item.get_meta(_inspector.META_GDUNIT_STATE) - -func test_collect_failures_and_errors() -> void: - # mark some test as failed - mark_as_failure(_inspector, ["test_aa", "test_ad", "test_cb", "test_cc", "test_ce"]) - - assert_array(_inspector.collect_failures_and_errors())\ - .extract("get_text", [0])\ - .contains_exactly(["test_aa", "test_ad", "test_cb", "test_cc", "test_ce"]) - -func test_select_first_failure() -> void: - # test initial nothing is selected - assert_object(_inspector._tree.get_selected()).is_null() - - # we have no failures or errors - _inspector.collect_failures_and_errors() - _inspector.select_first_failure() - assert_object(_inspector._tree.get_selected()).is_null() - - # add failures - mark_as_failure(_inspector, ["test_aa", "test_ad", "test_cb", "test_cc", "test_ce"]) - _inspector.collect_failures_and_errors() - # select first failure - _inspector.select_first_failure() - assert_str(_inspector._tree.get_selected().get_text(0)).is_equal("test_aa") - -func test_select_last_failure() -> void: - # test initial nothing is selected - assert_object(_inspector._tree.get_selected()).is_null() - - # we have no failures or errors - _inspector.collect_failures_and_errors() - _inspector.select_last_failure() - assert_object(_inspector._tree.get_selected()).is_null() - - # add failures - mark_as_failure(_inspector, ["test_aa", "test_ad", "test_cb", "test_cc", "test_ce"]) - _inspector.collect_failures_and_errors() - # select last failure - _inspector.select_last_failure() - assert_str(_inspector._tree.get_selected().get_text(0)).is_equal("test_ce") - - -func test_clear_failures() -> void: - assert_array(_inspector._current_failures).is_empty() - - mark_as_failure(_inspector, ["test_aa", "test_ad", "test_cb", "test_cc", "test_ce"]) - _inspector.collect_failures_and_errors() - assert_array(_inspector._current_failures).is_not_empty() - - # clear it - _inspector.clear_failures() - assert_array(_inspector._current_failures).is_empty() - -func test_select_next_failure() -> void: - # test initial nothing is selected - assert_object(_inspector._tree.get_selected()).is_null() - - # first time select next but no failure exists - _inspector.select_next_failure() - assert_str(_inspector._tree.get_selected()).is_null() - - # add failures - mark_as_failure(_inspector, ["test_aa", "test_ad", "test_cb", "test_cc", "test_ce"]) - _inspector.collect_failures_and_errors() - - # first time select next than select first failure - _inspector.select_next_failure() - assert_str(_inspector._tree.get_selected().get_text(0)).is_equal("test_aa") - _inspector.select_next_failure() - assert_str(_inspector._tree.get_selected().get_text(0)).is_equal("test_ad") - _inspector.select_next_failure() - assert_str(_inspector._tree.get_selected().get_text(0)).is_equal("test_cb") - _inspector.select_next_failure() - assert_str(_inspector._tree.get_selected().get_text(0)).is_equal("test_cc") - _inspector.select_next_failure() - assert_str(_inspector._tree.get_selected().get_text(0)).is_equal("test_ce") - # if current last failure selected than select first as next - _inspector.select_next_failure() - assert_str(_inspector._tree.get_selected().get_text(0)).is_equal("test_aa") - _inspector.select_next_failure() - assert_str(_inspector._tree.get_selected().get_text(0)).is_equal("test_ad") - -func test_select_previous_failure() -> void: - # test initial nothing is selected - assert_object(_inspector._tree.get_selected()).is_null() - - # first time select previous but no failure exists - _inspector.select_previous_failure() - assert_str(_inspector._tree.get_selected()).is_null() - - # add failures - mark_as_failure(_inspector, ["test_aa", "test_ad", "test_cb", "test_cc", "test_ce"]) - _inspector.collect_failures_and_errors() - - # first time select previous than select last failure - _inspector.select_previous_failure() - assert_str(_inspector._tree.get_selected().get_text(0)).is_equal("test_ce") - _inspector.select_previous_failure() - assert_str(_inspector._tree.get_selected().get_text(0)).is_equal("test_cc") - _inspector.select_previous_failure() - assert_str(_inspector._tree.get_selected().get_text(0)).is_equal("test_cb") - _inspector.select_previous_failure() - assert_str(_inspector._tree.get_selected().get_text(0)).is_equal("test_ad") - _inspector.select_previous_failure() - assert_str(_inspector._tree.get_selected().get_text(0)).is_equal("test_aa") - # if current first failure selected than select last as next - _inspector.select_previous_failure() - assert_str(_inspector._tree.get_selected().get_text(0)).is_equal("test_ce") - _inspector.select_previous_failure() - assert_str(_inspector._tree.get_selected().get_text(0)).is_equal("test_cc") - -func test_find_item_for_test_suites() -> void: - var suite_a: TreeItem = _inspector._find_item(_inspector._tree_root, TEST_SUITE_A) - assert_str(suite_a.get_meta(_inspector.META_GDUNIT_NAME)).is_equal("ExampleTestSuiteA") - - var suite_b: TreeItem = _inspector._find_item(_inspector._tree_root, TEST_SUITE_B) - assert_str(suite_b.get_meta(_inspector.META_GDUNIT_NAME)).is_equal("ExampleTestSuiteB") - -func test_find_item_for_test_cases() -> void: - var case_aa: TreeItem = _inspector._find_item(_inspector._tree_root, TEST_SUITE_A, "test_aa") - assert_str(case_aa.get_meta(_inspector.META_GDUNIT_NAME)).is_equal("test_aa") - - var case_ce: TreeItem = _inspector._find_item(_inspector._tree_root, TEST_SUITE_C, "test_ce") - assert_str(case_ce.get_meta(_inspector.META_GDUNIT_NAME)).is_equal("test_ce") - -func test_suite_text_shows_amount_of_cases() -> void: - var suite_a: TreeItem = _inspector._find_item(_inspector._tree_root, TEST_SUITE_A) - assert_str(suite_a.get_text(0)).is_equal("(0/5) ExampleTestSuiteA") - - var suite_b: TreeItem = _inspector._find_item(_inspector._tree_root, TEST_SUITE_B) - assert_str(suite_b.get_text(0)).is_equal("(0/3) ExampleTestSuiteB") - -func test_suite_text_responds_to_test_case_events() -> void: - var suite_a: TreeItem = _inspector._find_item(_inspector._tree_root, TEST_SUITE_A) - - var success_aa := GdUnitEvent.new().test_after(TEST_SUITE_A, "ExampleTestSuiteA", "test_aa") - _inspector._on_gdunit_event(success_aa) - assert_str(suite_a.get_text(0)).is_equal("(1/5) ExampleTestSuiteA") - - var error_ad := GdUnitEvent.new().test_after(TEST_SUITE_A, "ExampleTestSuiteA", "test_ad", {GdUnitEvent.ERRORS: true}) - _inspector._on_gdunit_event(error_ad) - assert_str(suite_a.get_text(0)).is_equal("(1/5) ExampleTestSuiteA") - - var failure_ab := GdUnitEvent.new().test_after(TEST_SUITE_A, "ExampleTestSuiteA", "test_ab", {GdUnitEvent.FAILED: true}) - _inspector._on_gdunit_event(failure_ab) - assert_str(suite_a.get_text(0)).is_equal("(1/5) ExampleTestSuiteA") - - var skipped_ac := GdUnitEvent.new().test_after(TEST_SUITE_A, "ExampleTestSuiteA", "test_ac", {GdUnitEvent.SKIPPED: true}) - _inspector._on_gdunit_event(skipped_ac) - assert_str(suite_a.get_text(0)).is_equal("(1/5) ExampleTestSuiteA") - - var success_ae := GdUnitEvent.new().test_after(TEST_SUITE_A, "ExampleTestSuiteA", "test_ae") - _inspector._on_gdunit_event(success_ae) - assert_str(suite_a.get_text(0)).is_equal("(2/5) ExampleTestSuiteA") - -# test coverage for issue GD-117 -func test_update_test_case_on_multiple_test_suite_with_same_name() -> void: - # add a second test suite where has same name as TEST_SUITE_A - var test_suite = auto_free(GdUnitTestResourceLoader.load_test_suite("res://addons/gdUnit4/test/ui/parts/resources/bar/ExampleTestSuiteA.resource")) - var test_suite_aa_path = test_suite.get_script().resource_path - _inspector.add_test_suite(toDto(test_suite)) - - # verify the items exists checked the tree - assert_str(TEST_SUITE_A).is_not_equal(test_suite_aa_path) - var suite_a: TreeItem = _inspector._find_item(_inspector._tree_root, TEST_SUITE_A) - var suite_aa: TreeItem = _inspector._find_item(_inspector._tree_root, test_suite_aa_path) - assert_object(suite_a).is_not_same(suite_aa) - assert_str(suite_a.get_meta(_inspector.META_RESOURCE_PATH)).is_equal(TEST_SUITE_A) - assert_str(suite_aa.get_meta(_inspector.META_RESOURCE_PATH)).is_equal(test_suite_aa_path) - - # verify inital state - assert_str(suite_a.get_text(0)).is_equal("(0/5) ExampleTestSuiteA") - assert_int(get_item_state(suite_a, "test_aa")).is_equal(_inspector.STATE.INITIAL) - assert_str(suite_aa.get_text(0)).is_equal("(0/5) ExampleTestSuiteA") - - # set test starting checked TEST_SUITE_A - _inspector._on_gdunit_event(GdUnitEvent.new().test_before(TEST_SUITE_A, "ExampleTestSuiteA", "test_aa")) - _inspector._on_gdunit_event(GdUnitEvent.new().test_before(TEST_SUITE_A, "ExampleTestSuiteA", "test_ab")) - assert_str(suite_a.get_text(0)).is_equal("(0/5) ExampleTestSuiteA") - assert_int(get_item_state(suite_a, "test_aa")).is_equal(_inspector.STATE.RUNNING) - assert_int(get_item_state(suite_a, "test_ab")).is_equal(_inspector.STATE.RUNNING) - # test test_suite_aa_path is not affected - assert_str(suite_aa.get_text(0)).is_equal("(0/5) ExampleTestSuiteA") - assert_int(get_item_state(suite_aa, "test_aa")).is_equal(_inspector.STATE.INITIAL) - assert_int(get_item_state(suite_aa, "test_ab")).is_equal(_inspector.STATE.INITIAL) - - # finish the tests with success - _inspector._on_gdunit_event(GdUnitEvent.new().test_after(TEST_SUITE_A, "ExampleTestSuiteA", "test_aa")) - _inspector._on_gdunit_event(GdUnitEvent.new().test_after(TEST_SUITE_A, "ExampleTestSuiteA", "test_ab")) - - # verify updated state checked TEST_SUITE_A - assert_str(suite_a.get_text(0)).is_equal("(2/5) ExampleTestSuiteA") - assert_int(get_item_state(suite_a, "test_aa")).is_equal(_inspector.STATE.SUCCESS) - assert_int(get_item_state(suite_a, "test_ab")).is_equal(_inspector.STATE.SUCCESS) - # test test_suite_aa_path is not affected - assert_str(suite_aa.get_text(0)).is_equal("(0/5) ExampleTestSuiteA") - assert_int(get_item_state(suite_aa, "test_aa")).is_equal(_inspector.STATE.INITIAL) - assert_int(get_item_state(suite_aa, "test_ab")).is_equal(_inspector.STATE.INITIAL) - - -# Test coverage for issue GD-278: GdUnit Inspector: Test marks as passed if both warning and error -func test_update_icon_state() -> void: - var TEST_SUITE_PATH = "res://addons/gdUnit4/test/core/resources/testsuites/TestSuiteFailAndOrpahnsDetected.resource" - var TEST_SUITE_NAME = "TestSuiteFailAndOrpahnsDetected" - var test_suite = auto_free(GdUnitTestResourceLoader.load_test_suite(TEST_SUITE_PATH)) - _inspector.add_test_suite(toDto(test_suite)) - - var suite: TreeItem = _inspector._find_item(_inspector._tree_root, TEST_SUITE_PATH) - - # Verify the inital state - assert_str(suite.get_text(0)).is_equal("(0/2) "+ TEST_SUITE_NAME) - assert_str(suite.get_meta(_inspector.META_RESOURCE_PATH)).is_equal(TEST_SUITE_PATH) - assert_int(get_item_state(_inspector._tree_root, TEST_SUITE_NAME)).is_equal(_inspector.STATE.INITIAL) - assert_int(get_item_state(suite, "test_case1")).is_equal(_inspector.STATE.INITIAL) - assert_int(get_item_state(suite, "test_case2")).is_equal(_inspector.STATE.INITIAL) - - # Set tests to running - _inspector._on_gdunit_event(GdUnitEvent.new().test_before(TEST_SUITE_PATH, TEST_SUITE_NAME, "test_case1")) - _inspector._on_gdunit_event(GdUnitEvent.new().test_before(TEST_SUITE_PATH, TEST_SUITE_NAME, "test_case2")) - # Verify all items on state running. - assert_str(suite.get_text(0)).is_equal("(0/2) " + TEST_SUITE_NAME) - assert_int(get_item_state(suite, "test_case1")).is_equal(_inspector.STATE.RUNNING) - assert_int(get_item_state(suite, "test_case2")).is_equal(_inspector.STATE.RUNNING) - - # Simulate test processed. - # test_case1 succeeded - _inspector._on_gdunit_event(GdUnitEvent.new().test_after(TEST_SUITE_PATH, TEST_SUITE_NAME, "test_case1")) - # test_case2 is failing by an orphan warning and an failure - _inspector._on_gdunit_event(GdUnitEvent.new()\ - .test_after(TEST_SUITE_PATH, TEST_SUITE_NAME, "test_case2", {GdUnitEvent.FAILED: true})) - # We check whether a test event with a warning does not overwrite a higher object status, e.g. an error. - _inspector._on_gdunit_event(GdUnitEvent.new()\ - .test_after(TEST_SUITE_PATH, TEST_SUITE_NAME, "test_case2", {GdUnitEvent.WARNINGS: true})) - - # Verify the final state - assert_str(suite.get_text(0)).is_equal("(1/2) " + TEST_SUITE_NAME) - assert_int(get_item_state(suite, "test_case1")).is_equal(_inspector.STATE.SUCCESS) - assert_int(get_item_state(suite, "test_case2")).is_equal(_inspector.STATE.FAILED) diff --git a/addons/gdUnit4/test/ui/parts/resources/bar/ExampleTestSuiteA.resource b/addons/gdUnit4/test/ui/parts/resources/bar/ExampleTestSuiteA.resource deleted file mode 100644 index f192f7c..0000000 --- a/addons/gdUnit4/test/ui/parts/resources/bar/ExampleTestSuiteA.resource +++ /dev/null @@ -1,17 +0,0 @@ -extends GdUnitTestSuite - - -func test_aa() -> void: - pass - -func test_ab() -> void: - pass - -func test_ac() -> void: - pass - -func test_ad() -> void: - pass - -func test_ae() -> void: - pass diff --git a/addons/gdUnit4/test/ui/parts/resources/foo/ExampleTestSuiteA.resource b/addons/gdUnit4/test/ui/parts/resources/foo/ExampleTestSuiteA.resource deleted file mode 100644 index 2019b4b..0000000 --- a/addons/gdUnit4/test/ui/parts/resources/foo/ExampleTestSuiteA.resource +++ /dev/null @@ -1,18 +0,0 @@ -class_name ExampleTestSuiteA -extends GdUnitTestSuite - - -func test_aa() -> void: - pass - -func test_ab() -> void: - pass - -func test_ac() -> void: - pass - -func test_ad() -> void: - pass - -func test_ae() -> void: - pass diff --git a/addons/gdUnit4/test/ui/parts/resources/foo/ExampleTestSuiteB.resource b/addons/gdUnit4/test/ui/parts/resources/foo/ExampleTestSuiteB.resource deleted file mode 100644 index c0bf271..0000000 --- a/addons/gdUnit4/test/ui/parts/resources/foo/ExampleTestSuiteB.resource +++ /dev/null @@ -1,12 +0,0 @@ -class_name ExampleTestSuiteB -extends GdUnitTestSuite - - -func test_ba() -> void: - pass - -func test_bb() -> void: - pass - -func test_bc() -> void: - pass diff --git a/addons/gdUnit4/test/ui/parts/resources/foo/ExampleTestSuiteC.resource b/addons/gdUnit4/test/ui/parts/resources/foo/ExampleTestSuiteC.resource deleted file mode 100644 index e96bc44..0000000 --- a/addons/gdUnit4/test/ui/parts/resources/foo/ExampleTestSuiteC.resource +++ /dev/null @@ -1,18 +0,0 @@ -class_name ExampleTestSuiteC -extends GdUnitTestSuite - - -func test_ca() -> void: - pass - -func test_cb() -> void: - pass - -func test_cc() -> void: - pass - -func test_cd() -> void: - pass - -func test_ce() -> void: - pass diff --git a/addons/gdUnit4/test/ui/templates/TestSuiteTemplateTest.gd b/addons/gdUnit4/test/ui/templates/TestSuiteTemplateTest.gd deleted file mode 100644 index 26e77a7..0000000 --- a/addons/gdUnit4/test/ui/templates/TestSuiteTemplateTest.gd +++ /dev/null @@ -1,36 +0,0 @@ -# GdUnit generated TestSuite -#warning-ignore-all:unused_argument -#warning-ignore-all:return_value_discarded -class_name TestSuiteTemplateTest -extends GdUnitTestSuite - -# TestSuite generated from -const __source = 'res://addons/gdUnit4/src/ui/templates/TestSuiteTemplate.gd' - - -func test_show() -> void: - var template = spy("res://addons/gdUnit4/src/ui/templates/TestSuiteTemplate.tscn") - scene_runner(template) - - # verify the followup functions are called by _ready() - verify(template)._ready() - verify(template).setup_editor_colors() - verify(template).setup_supported_types() - verify(template).load_template(GdUnitTestSuiteTemplate.TEMPLATE_ID_GD) - verify(template).setup_tags_help() - - -func test_load_template_gd() -> void: - var runner := scene_runner("res://addons/gdUnit4/src/ui/templates/TestSuiteTemplate.tscn") - runner.invoke("load_template", GdUnitTestSuiteTemplate.TEMPLATE_ID_GD) - - assert_int(runner.get_property("_selected_template")).is_equal(GdUnitTestSuiteTemplate.TEMPLATE_ID_GD) - assert_str(runner.get_property("_template_editor").text).is_equal(GdUnitTestSuiteTemplate.default_GD_template().replace("\r", "")) - - -func test_load_template_cs() -> void: - var runner := scene_runner("res://addons/gdUnit4/src/ui/templates/TestSuiteTemplate.tscn") - runner.invoke("load_template", GdUnitTestSuiteTemplate.TEMPLATE_ID_CS) - - assert_int(runner.get_property("_selected_template")).is_equal(GdUnitTestSuiteTemplate.TEMPLATE_ID_CS) - assert_str(runner.get_property("_template_editor").text).is_equal(GdUnitTestSuiteTemplate.default_CS_template().replace("\r", "")) diff --git a/addons/gdUnit4/test/update/GdMarkDownReaderTest.gd b/addons/gdUnit4/test/update/GdMarkDownReaderTest.gd deleted file mode 100644 index c68b87c..0000000 --- a/addons/gdUnit4/test/update/GdMarkDownReaderTest.gd +++ /dev/null @@ -1,122 +0,0 @@ -# GdUnit generated TestSuite -class_name GdMarkDownReaderTest -extends GdUnitTestSuite - -# TestSuite generated from -const __source = 'res://addons/gdUnit4/src/update/GdMarkDownReader.gd' -const GdUnitUpdateClient = preload("res://addons/gdUnit4/src/update/GdUnitUpdateClient.gd") - -var _reader = preload("res://addons/gdUnit4/src/update/GdMarkDownReader.gd").new() -var _client :GdUnitUpdateClient - - -func before(): - _client = GdUnitUpdateClient.new() - add_child(_client) - _reader.set_http_client(_client) - - -func after(): - _client.queue_free() - - -func test_tobbcode() -> void: - var source := resource_as_string("res://addons/gdUnit4/test/update/resources/markdown_example.txt") - var expected := resource_as_string("res://addons/gdUnit4/test/update/resources/bbcode_example.txt") - assert_str(await _reader.to_bbcode(source)).is_equal(expected) - - -func test_tobbcode_table() -> void: - var source := resource_as_string("res://addons/gdUnit4/test/update/resources/markdown_table.txt") - var expected := resource_as_string("res://addons/gdUnit4/test/update/resources/bbcode_table.txt") - assert_str(await _reader.to_bbcode(source)).is_equal(expected) - - -func test_tobbcode_html_headers() -> void: - var source := resource_as_string("res://addons/gdUnit4/test/update/resources/html_header.txt") - var expected := resource_as_string("res://addons/gdUnit4/test/update/resources/bbcode_header.txt") - assert_str(await _reader.to_bbcode(source)).is_equal(expected) - - -func test_tobbcode_md_headers() -> void: - var source := resource_as_string("res://addons/gdUnit4/test/update/resources/md_header.txt") - var expected := resource_as_string("res://addons/gdUnit4/test/update/resources/bbcode_md_header.txt") - assert_str(await _reader.to_bbcode(source)).is_equal(expected) - - -func test_tobbcode_list() -> void: - assert_str(await _reader.to_bbcode("- item")).is_equal("[img=12x12]res://addons/gdUnit4/src/update/assets/dot1.png[/img] item\n") - assert_str(await _reader.to_bbcode(" - item")).is_equal(" [img=12x12]res://addons/gdUnit4/src/update/assets/dot2.png[/img] item\n") - assert_str(await _reader.to_bbcode(" - item")).is_equal(" [img=12x12]res://addons/gdUnit4/src/update/assets/dot1.png[/img] item\n") - assert_str(await _reader.to_bbcode(" - item")).is_equal(" [img=12x12]res://addons/gdUnit4/src/update/assets/dot2.png[/img] item\n") - - -func test_to_bbcode_embeded_text() -> void: - assert_str(await _reader.to_bbcode("> some text")).is_equal("[img=50x14]res://addons/gdUnit4/src/update/assets/embedded.png[/img][i] some text[/i]\n") - - -func test_process_image() -> void: - #regex("!\\[(.*?)\\]\\((.*?)(( )+(.*?))?\\)") - var reg_ex :RegEx = _reader.md_replace_patterns[24][0] - - # without tooltip - assert_str(await _reader.process_image(reg_ex, "![alt text](res://addons/gdUnit4/test/update/resources/icon48.png)"))\ - .is_equal("[img]res://addons/gdUnit4/test/update/resources/icon48.png[/img]") - # with tooltip - assert_str(await _reader.process_image(reg_ex, "![alt text](res://addons/gdUnit4/test/update/resources/icon48.png \"Logo Title Text 1\")"))\ - .is_equal("[img]res://addons/gdUnit4/test/update/resources/icon48.png[/img]") - # multiy lines - var input := """ - ![alt text](res://addons/gdUnit4/test/update/resources/icon48.png) - - ![alt text](res://addons/gdUnit4/test/update/resources/icon23.png \"Logo Title Text 1\") - - """.dedent() - var expected := """ - [img]res://addons/gdUnit4/test/update/resources/icon48.png[/img] - - [img]res://addons/gdUnit4/test/update/resources/icon23.png[/img] - - """.dedent() - assert_str(await _reader.process_image(reg_ex, input))\ - .is_equal(expected) - - -func test_process_image_by_reference() -> void: - #regex("!\\[(.*?)\\]\\((.*?)(( )+(.*?))?\\)") - var reg_ex :RegEx = _reader.md_replace_patterns[23][0] - var input := """ - ![alt text1][logo-1] - - [logo-1]:https://github.com/adam-p/markdown-here/raw/master/src/common/images/icon48.png "Logo Title Text 2" - - ![alt text2][logo-1] - - """.dedent() - - var expected := """ - ![alt text1](https://github.com/adam-p/markdown-here/raw/master/src/common/images/icon48.png) - - - ![alt text2](https://github.com/adam-p/markdown-here/raw/master/src/common/images/icon48.png) - - """.replace("\r", "").dedent() - - # without tooltip - assert_str(_reader.process_image_references(reg_ex, input))\ - .is_equal(expected) - - -func test_process_external_image_save_as_png() -> void: - var input := """ - [img]https://github.com/adam-p/markdown-here/raw/master/src/common/images/icon48.png[/img] - [img]https://github.com/MikeSchulze/gdUnit4/assets/347037/3205c9f1-1746-4716-aa6d-e3a1808b761d[/img] - """.dedent() - - var output := await _reader._process_external_image_resources(input) - assert_str(output).is_equal(""" - [img]res://addons/gdUnit4/tmp-update/icon48.png[/img] - [img]res://addons/gdUnit4/tmp-update/3205c9f1-1746-4716-aa6d-e3a1808b761d.png[/img] - """.dedent()) - assert_file("res://addons/gdUnit4/tmp-update/icon48.png").exists() - assert_file("res://addons/gdUnit4/tmp-update/3205c9f1-1746-4716-aa6d-e3a1808b761d.png").exists() diff --git a/addons/gdUnit4/test/update/GdUnitPatcherTest.gd b/addons/gdUnit4/test/update/GdUnitPatcherTest.gd deleted file mode 100644 index f4ac204..0000000 --- a/addons/gdUnit4/test/update/GdUnitPatcherTest.gd +++ /dev/null @@ -1,114 +0,0 @@ -# GdUnit generated TestSuite -class_name GdUnitPatcherTest -extends GdUnitTestSuite - -# TestSuite generated from -const __source = 'res://addons/gdUnit4/src/update/GdUnitPatcher.gd' - -const _patches := "res://addons/gdUnit4/test/update/resources/patches/" - -var _patcher :GdUnitPatcher - - -func before(): - _patcher = auto_free(GdUnitPatcher.new()) - - -func before_test(): - Engine.set_meta(GdUnitPatch.PATCH_VERSION, []) - _patcher._patches.clear() - - -func test__collect_patch_versions_no_patches() -> void: - # using higher version than patches exists in patch folder - assert_array(_patcher._collect_patch_versions(_patches, GdUnit4Version.new(3,0,0))).is_empty() - - -func test__collect_patch_versions_current_eq_latest_version() -> void: - # using equal version than highst available patch - assert_array(_patcher._collect_patch_versions(_patches, GdUnit4Version.new(1,1,4))).is_empty() - - -func test__collect_patch_versions_current_lower_latest_version() -> void: - # using one version lower than highst available patch - assert_array(_patcher._collect_patch_versions(_patches, GdUnit4Version.new(0,9,9)))\ - .contains_exactly(["res://addons/gdUnit4/test/update/resources/patches/v1.1.4"]) - - # using two versions lower than highst available patch - assert_array(_patcher._collect_patch_versions(_patches, GdUnit4Version.new(0,9,8)))\ - .contains_exactly([ - "res://addons/gdUnit4/test/update/resources/patches/v0.9.9", - "res://addons/gdUnit4/test/update/resources/patches/v1.1.4"]) - - # using three versions lower than highst available patch - assert_array(_patcher._collect_patch_versions(_patches, GdUnit4Version.new(0,9,5)))\ - .contains_exactly([ - "res://addons/gdUnit4/test/update/resources/patches/v0.9.6", - "res://addons/gdUnit4/test/update/resources/patches/v0.9.9", - "res://addons/gdUnit4/test/update/resources/patches/v1.1.4"]) - - -func test_scan_patches() -> void: - _patcher._scan(_patches, GdUnit4Version.new(0,9,6)) - assert_dict(_patcher._patches)\ - .contains_key_value("res://addons/gdUnit4/test/update/resources/patches/v0.9.9", PackedStringArray(["patch_a.gd", "patch_b.gd"]))\ - .contains_key_value("res://addons/gdUnit4/test/update/resources/patches/v1.1.4", PackedStringArray(["patch_a.gd"])) - assert_int(_patcher.patch_count()).is_equal(3) - - _patcher._patches.clear() - _patcher._scan(_patches, GdUnit4Version.new(0,9,5)) - assert_dict(_patcher._patches)\ - .contains_key_value("res://addons/gdUnit4/test/update/resources/patches/v0.9.6", PackedStringArray(["patch_x.gd"]))\ - .contains_key_value("res://addons/gdUnit4/test/update/resources/patches/v0.9.9", PackedStringArray(["patch_a.gd", "patch_b.gd"]))\ - .contains_key_value("res://addons/gdUnit4/test/update/resources/patches/v1.1.4", PackedStringArray(["patch_a.gd"])) - assert_int(_patcher.patch_count()).is_equal(4) - - -func test_execute_no_patches() -> void: - assert_array(Engine.get_meta(GdUnitPatch.PATCH_VERSION)).is_empty() - - _patcher.execute() - assert_array(Engine.get_meta(GdUnitPatch.PATCH_VERSION)).is_empty() - - -func test_execute_v_095() -> void: - assert_array(Engine.get_meta(GdUnitPatch.PATCH_VERSION)).is_empty() - _patcher._scan(_patches, GdUnit4Version.parse("v0.9.5")) - - _patcher.execute() - assert_array(Engine.get_meta(GdUnitPatch.PATCH_VERSION)).is_equal([ - GdUnit4Version.parse("v0.9.6"), - GdUnit4Version.parse("v0.9.9-a"), - GdUnit4Version.parse("v0.9.9-b"), - GdUnit4Version.parse("v1.1.4"), - ]) - - -func test_execute_v_096() -> void: - assert_array(Engine.get_meta(GdUnitPatch.PATCH_VERSION)).is_empty() - _patcher._scan(_patches, GdUnit4Version.parse("v0.9.6")) - - _patcher.execute() - assert_array(Engine.get_meta(GdUnitPatch.PATCH_VERSION)).is_equal([ - GdUnit4Version.parse("v0.9.9-a"), - GdUnit4Version.parse("v0.9.9-b"), - GdUnit4Version.parse("v1.1.4"), - ]) - - -func test_execute_v_099() -> void: - assert_array(Engine.get_meta(GdUnitPatch.PATCH_VERSION)).is_empty() - _patcher._scan(_patches, GdUnit4Version.new(0,9,9)) - - _patcher.execute() - assert_array(Engine.get_meta(GdUnitPatch.PATCH_VERSION)).is_equal([ - GdUnit4Version.parse("v1.1.4"), - ]) - - -func test_execute_v_150() -> void: - assert_array(Engine.get_meta(GdUnitPatch.PATCH_VERSION)).is_empty() - _patcher._scan(_patches, GdUnit4Version.parse("v1.5.0")) - - _patcher.execute() - assert_array(Engine.get_meta(GdUnitPatch.PATCH_VERSION)).is_empty() diff --git a/addons/gdUnit4/test/update/GdUnitUpdateTest.gd b/addons/gdUnit4/test/update/GdUnitUpdateTest.gd deleted file mode 100644 index 4dfff50..0000000 --- a/addons/gdUnit4/test/update/GdUnitUpdateTest.gd +++ /dev/null @@ -1,18 +0,0 @@ -# GdUnit generated TestSuite -class_name GdUnitUpdateTest -extends GdUnitTestSuite - -# TestSuite generated from -const GdUnitUpdate = preload('res://addons/gdUnit4/src/update/GdUnitUpdate.gd') - - -func after_test(): - clean_temp_dir() - - -func test__prepare_update_deletes_old_content() -> void: - var _update :GdUnitUpdate = auto_free(GdUnitUpdate.new()) - - - - diff --git a/addons/gdUnit4/test/update/bbcodeView.gd b/addons/gdUnit4/test/update/bbcodeView.gd deleted file mode 100644 index 12934f1..0000000 --- a/addons/gdUnit4/test/update/bbcodeView.gd +++ /dev/null @@ -1,48 +0,0 @@ -extends Control - -const GdUnitTools := preload("res://addons/gdUnit4/src/core/GdUnitTools.gd") -const GdMarkDownReader := preload("res://addons/gdUnit4/src/update/GdMarkDownReader.gd") -const GdUnitUpdateClient := preload("res://addons/gdUnit4/src/update/GdUnitUpdateClient.gd") - -@onready var _input :TextEdit = $HSplitContainer/TextEdit -@onready var _text :RichTextLabel = $HSplitContainer/RichTextLabel - -@onready var _update_client :GdUnitUpdateClient = $GdUnitUpdateClient - -var _md_reader := GdMarkDownReader.new() - - -func _ready(): - _md_reader.set_http_client(_update_client) - var source := GdUnitFileAccess.resource_as_string("res://addons/gdUnit4/test/update/resources/markdown_example.txt") - _input.text = source - await set_bbcode(source) - - -func set_bbcode(text :String) : - var bbcode = await _md_reader.to_bbcode(text) - _text.clear() - _text.append_text(bbcode) - _text.queue_redraw() - - -func _on_TextEdit_text_changed(): - await set_bbcode(_input.get_text()) - - -func _on_RichTextLabel_meta_clicked(meta :String): - var properties = str_to_var(meta) - prints("meta_clicked", properties) - if properties.has("url"): - OS.shell_open(properties.get("url")) - - -func _on_RichTextLabel_meta_hover_started(meta :String): - var properties = str_to_var(meta) - prints("hover_started", properties) - if properties.has("tool_tip"): - _text.set_tooltip(properties.get("tool_tip")) - - -func _on_RichTextLabel_meta_hover_ended(_meta :String): - _text.set_tooltip("") diff --git a/addons/gdUnit4/test/update/bbcodeView.tscn b/addons/gdUnit4/test/update/bbcodeView.tscn deleted file mode 100644 index e096c51..0000000 --- a/addons/gdUnit4/test/update/bbcodeView.tscn +++ /dev/null @@ -1,39 +0,0 @@ -[gd_scene load_steps=3 format=3 uid="uid://c1rwx6anh3u3m"] - -[ext_resource type="Script" path="res://addons/gdUnit4/test/update/bbcodeView.gd" id="1"] -[ext_resource type="Script" path="res://addons/gdUnit4/src/update/GdUnitUpdateClient.gd" id="6"] - -[node name="Control" type="Control"] -layout_mode = 3 -anchors_preset = 15 -anchor_right = 1.0 -anchor_bottom = 1.0 -grow_horizontal = 2 -grow_vertical = 2 -script = ExtResource("1") - -[node name="HSplitContainer" type="HSplitContainer" parent="."] -layout_mode = 1 -anchors_preset = 15 -anchor_right = 1.0 -anchor_bottom = 1.0 -grow_horizontal = 2 -grow_vertical = 2 -split_offset = 600 - -[node name="TextEdit" type="TextEdit" parent="HSplitContainer"] -layout_mode = 2 - -[node name="RichTextLabel" type="RichTextLabel" parent="HSplitContainer"] -use_parent_material = true -layout_mode = 2 -tooltip_text = "test" -bbcode_enabled = true - -[node name="GdUnitUpdateClient" type="Node" parent="."] -script = ExtResource("6") - -[connection signal="text_changed" from="HSplitContainer/TextEdit" to="." method="_on_TextEdit_text_changed"] -[connection signal="meta_clicked" from="HSplitContainer/RichTextLabel" to="." method="_on_RichTextLabel_meta_clicked"] -[connection signal="meta_hover_ended" from="HSplitContainer/RichTextLabel" to="." method="_on_RichTextLabel_meta_hover_ended"] -[connection signal="meta_hover_started" from="HSplitContainer/RichTextLabel" to="." method="_on_RichTextLabel_meta_hover_started"] diff --git a/addons/gdUnit4/test/update/resources/bbcode_example.txt b/addons/gdUnit4/test/update/resources/bbcode_example.txt deleted file mode 100644 index 921f91c..0000000 --- a/addons/gdUnit4/test/update/resources/bbcode_example.txt +++ /dev/null @@ -1,28 +0,0 @@ -[font_size=28]GdUnit3 v0.9.4 - Release Candidate[/font_size] -[img=4000x2]res://addons/gdUnit4/src/update/assets/horizontal-line2.png[/img] - - -[font_size=24]Improvements[/font_size] - -[img=12x12]res://addons/gdUnit4/src/update/assets/dot1.png[/img] Added project settings to configure: -[img=12x12]res://addons/gdUnit4/src/update/assets/dot1.png[/img] [b]Verbose Orphans[/b] to enable/disable report detected orphans -[img]res://addons/gdUnit4/tmp-update/119266895-e09d1900-bbec-11eb-91e9-45409ba2edb2.png[/img] -[img=12x12]res://addons/gdUnit4/src/update/assets/dot1.png[/img] [b]Server Connection Timeout Minites[/b] to set test server connection timeout in minutes -[img=12x12]res://addons/gdUnit4/src/update/assets/dot1.png[/img] [b]Test Timeout Seconds[/b] to set the default test case timeout in seconds -[img]res://addons/gdUnit4/tmp-update/119266875-d1b66680-bbec-11eb-856f-8fac9b0ed31c.png[/img] - -test seperator -[img=4000x2]res://addons/gdUnit4/src/update/assets/horizontal-line2.png[/img] - - -[font_size=24]Bugfixes[/font_size] - -[img=12x12]res://addons/gdUnit4/src/update/assets/dot1.png[/img] GdUnit inspecor: - [img=12x12]res://addons/gdUnit4/src/update/assets/dot2.png[/img] Fixed invalid test case state visualisation for detected orphan nodes (#63) - [img=12x12]res://addons/gdUnit4/src/update/assets/dot2.png[/img] Fixed a ui bug to auto select the first report failure after a test run - [img=12x12]res://addons/gdUnit4/src/update/assets/dot2.png[/img] Fixed invalid visualisation state and error counter (#66) -[img=12x12]res://addons/gdUnit4/src/update/assets/dot1.png[/img] TestSuite: - [img=12x12]res://addons/gdUnit4/src/update/assets/dot2.png[/img] Using asserts on stage after() now reporting -[img=12x12]res://addons/gdUnit4/src/update/assets/dot1.png[/img] Core: - [img=12x12]res://addons/gdUnit4/src/update/assets/dot2.png[/img] The GdUnit network layer was replaced by a new TCP server/client architecture to enable network-related testing (#64 ) - diff --git a/addons/gdUnit4/test/update/resources/bbcode_header.txt b/addons/gdUnit4/test/update/resources/bbcode_header.txt deleted file mode 100644 index 4874ceb..0000000 --- a/addons/gdUnit4/test/update/resources/bbcode_header.txt +++ /dev/null @@ -1,40 +0,0 @@ -[font_size=32]Header 1 Text[/font_size] -[img=4000x2]res://addons/gdUnit4/src/update/assets/horizontal-line2.png[/img] - -[font_size=32][center]Header 1 Centered Text[/center][/font_size] -[img=4000x2]res://addons/gdUnit4/src/update/assets/horizontal-line2.png[/img] - - -[font_size=28]Header 2 Text[/font_size] -[img=4000x2]res://addons/gdUnit4/src/update/assets/horizontal-line2.png[/img] - -[font_size=32][center]Header 2 Centered Text[/center][/font_size] -[img=4000x2]res://addons/gdUnit4/src/update/assets/horizontal-line2.png[/img] - - -[font_size=32][center]Header 2 Centered Text -Multiline Test -Is here[/center][/font_size] -[img=4000x2]res://addons/gdUnit4/src/update/assets/horizontal-line2.png[/img] - - -[font_size=24]Header 3 Text[/font_size] - -[font_size=24][center]Header 3 Centered Text[/center][/font_size] - - -[font_size=20]Header 4 Text[/font_size] - -[font_size=20][center]Header 4 Centered Text[/center][/font_size] - - -[font_size=16]Header 5 Text[/font_size] - -[font_size=16][center]Header 5 Centered Text[/center][/font_size] - - -[font_size=12]Header 6 Text[/font_size] - -[font_size=12][center]Header 6 Centered Text[/center][/font_size] - - diff --git a/addons/gdUnit4/test/update/resources/bbcode_md_header.txt b/addons/gdUnit4/test/update/resources/bbcode_md_header.txt deleted file mode 100644 index 09399f3..0000000 --- a/addons/gdUnit4/test/update/resources/bbcode_md_header.txt +++ /dev/null @@ -1,20 +0,0 @@ -[font_size=32]Header 1 Text[/font_size] -[img=4000x2]res://addons/gdUnit4/src/update/assets/horizontal-line2.png[/img] - - -[font_size=28]Header 2 Text[/font_size] -[img=4000x2]res://addons/gdUnit4/src/update/assets/horizontal-line2.png[/img] - - -[font_size=24]Header 3 Text[/font_size] - - -[font_size=20]Header 4 Text[/font_size] - - -[font_size=16]Header 5 Text[/font_size] - - -[font_size=12]Header 6 Text[/font_size] - - diff --git a/addons/gdUnit4/test/update/resources/bbcode_table.txt b/addons/gdUnit4/test/update/resources/bbcode_table.txt deleted file mode 100644 index e7fc778..0000000 --- a/addons/gdUnit4/test/update/resources/bbcode_table.txt +++ /dev/null @@ -1,9 +0,0 @@ -[table=2] -[cell][b] type [/b][/cell]|[cell][b] description[/b][/cell] -[cell]--------------[/cell]|[cell]--------------------------------------------[/cell] -[cell]any_color() [/cell]|[cell] Argument matcher to match any Color value[/cell] -[cell]any_vector2() [/cell]|[cell] Argument matcher to match any Vector2 value[/cell] -[cell]any_vector3() [/cell]|[cell] Argument matcher to match any Vector3 value[/cell] -[cell]any_rect2() [/cell]|[cell] Argument matcher to match any Rect2 value[/cell] -[/table] - diff --git a/addons/gdUnit4/test/update/resources/html_header.txt b/addons/gdUnit4/test/update/resources/html_header.txt deleted file mode 100644 index d9206b4..0000000 --- a/addons/gdUnit4/test/update/resources/html_header.txt +++ /dev/null @@ -1,21 +0,0 @@ -

Header 1 Text

-

Header 1 Centered Text

- -

Header 2 Text

-

Header 2 Centered Text

- -

Header 2 Centered Text -Multiline Test -Is here

- -

Header 3 Text

-

Header 3 Centered Text

- -

Header 4 Text

-

Header 4 Centered Text

- -
Header 5 Text
-
Header 5 Centered Text
- -
Header 6 Text
-
Header 6 Centered Text
diff --git a/addons/gdUnit4/test/update/resources/icon48.png b/addons/gdUnit4/test/update/resources/icon48.png deleted file mode 100644 index 9f8d934a63e62664379b1e87dea83e23282a294c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 916 zcmV;F18e+=P)004R>004l5008;`004mK004C`008P>0026e000+ooVrmw00006 zVoOIv00000008+zyMF)x010qNS#tmYZn^*fZn^>1-mw<|000McNliru;Q|U4Hy;5N zqksSa0_90WK~!ko?U_AD96=a{*IxwVDTqk!jtf>cf}O2R1TpcVMS_J^LSi@-G!`Ky zg_V`1U;-ym5L*!hAtwG^p@qaqjHs1`Rtbo3oqBl&X5lzocW-z0C*cO3#yi*dk)8Qw zzR|RM#_RCM6g-0>&4=+WHYbQf%GLu2o`Gvvg?6t*2wVj1unO0ZaUzh-8g#fNB7=9p znh2eZDgm8u@Dz#_1uupt@HIEMTLL=k&}mmh2JeJ*qu_1}=xoTzUSctLiJY7pX2HD@ z(Ak16BM}+A3%0BV_gX+_8@dzWPo?5HZ)2ZJ3DFJP`3Cn(Kqoa4O}=pw+*g4CKp->j zFnmGae-^k^Byj7OfY@3L!l_MxQy9E!>)@5ZlKfr`48gHQfnykg8d)Ef{1RBs`F=Gd z;K)GW2og}8Q{a{ZA&~WZAs9g5PzwAa!~ld0Ds->}^zriHOx0*K`S`=5`6`pWQ7{Ga z*p(Ip3IYXzfaYH~|Z~`{mBeDl8 zprV1h0((Zm`B{Tqfj=+_^>=($0nJYPq_Q8{3>9~LRu7Z#+vzHFCJ!u?9(AWH<$>o+ z^)k8-FH|*k4SJ=|Vc+dKAkM_U7u9v(=RFeQ;$s1t90qTK1^pt!fJ3_iwJ>W^ zjjvz|B6$Xnz!Y4WRN+~ub-J7mL)t{-9LB_X*d%xe#^Br}cp8S4y`$othZ76?qeJM0 z(wyL;L)|N02Me9J6EP>0uM*-l5!KgXYjG3WMxx0w!nDDr#Wtd-$`RMrXz`zg2|0<) zdZRZ8d>@a^-B<`NCh-K6d2)vThis is an **embedded section**. ->The section continues here - ->This is another **embedded section**. ->This section also continues in the second like ->- aba ->This line isn’t embedded any more. ->- tets -> - aha -> - akaka - - - - ------- lists ------ -* an asterisk starts an unordered list -* and this is another item in the list -+ or you can also use the + character -- or the - character - -To start an ordered list, write this: - -1. this starts a list *with* numbers -* this will show as number "2" -* this will show as number "3." -9. any number, +, -, or * will keep the list going. - * just indent by 4 spaces (or tab) to make a sub-list - 1. keep indenting for more sub lists - * here i'm back to the second level - - - -- Asserts: - - Added new `assert_vector2` to verify Vector2 values (#69 ) - - Added new `assert_vector3` to verify Vector3 values (#69 ) - - ahss - - kaka - - kaka - - kaka - - lll - - kkk - - -- Fuzzers: - - Added `rangev2` to generate random Vector2 values - - Added `rangev3` to generate random Vector3 values - - one or more fuzzers are now allowed for a test case (#71) -- GitHub Action - - Added GitHub action to automatic trigger selftest on push events (tests against Godot 3.2.3, 3.3, 3.3.1, 3.3.2) (#74 ) - - - ------- check lists ------ -[ ] A -[x] B -[ ] C - ------- code ------ -This is `code`. - -``This is all `code`.`` - -```javascript -var s = "JavaScript syntax highlighting"; -alert(s); -``` - -```python -s = "Python syntax highlighting" -print s -``` - -``` -No language indicated, so no syntax highlighting. -But let's throw in a tag. -``` - ------- links ------ -Here is a [Link](https://example.com/ "Optional link title"). - ------- image ------ -Inline-style: -![alt text](res://addons/gdUnit4/test/update/resources/icon48.png "Logo Title Text 1") - -![alt text](https://upload.wikimedia.org/wikipedia/commons/4/47/PNG_transparency_demonstration_1.png) - - -Reference-style: -![alt text][logo] - -[logo]:res://addons/gdUnit4/test/update/resources/icon48.png "Logo Title Text 2" - - ------- Horizontal Rules ------ - ---- -Hyphens -*** -Asterisks -___ -Underscores - - - ------- table ------ -|Column 1|Column 2| -|--------|--------| -| A | B | -| C | D | - -Column 1|Column 2 ---------|-------- -A | B -C | D - - ------- foodnodes ------ -You can easily place footnotes [^2] in the continuous text [^1]. -[^1]: Here you can find the text for the footnote. -[^2]: **Footnotes** themselves can also be *formatted*. -And these even include several lines. - - ------- asterisk ------ -This *is* an \*example with an asterisk\**. -This _is_ an \_example with an asterisk\_. - ------- bold ------ -test -**test** -__test__ - ------- italic ------ -test -*test* -_test_ - ------- italic + bold ------ -***Italic and Bold Text*** -___Italic and Bold Text___ - ------- stroke ------ -test -~test~ -~~test~~ diff --git a/addons/gdUnit4/test/update/resources/markdown_example.txt b/addons/gdUnit4/test/update/resources/markdown_example.txt deleted file mode 100644 index 54dd073..0000000 --- a/addons/gdUnit4/test/update/resources/markdown_example.txt +++ /dev/null @@ -1,22 +0,0 @@ -## GdUnit3 v0.9.4 - Release Candidate - -### Improvements -- Added project settings to configure: - - Verbose Orphans to enable/disable report detected orphans -![image](https://user-images.githubusercontent.com/347037/119266895-e09d1900-bbec-11eb-91e9-45409ba2edb2.png) - - Server Connection Timeout Minites to set test server connection timeout in minutes - - Test Timeout Seconds to set the default test case timeout in seconds -![image](https://user-images.githubusercontent.com/347037/119266875-d1b66680-bbec-11eb-856f-8fac9b0ed31c.png) - -test seperator ---- - -### Bugfixes -- GdUnit inspecor: - - Fixed invalid test case state visualisation for detected orphan nodes (#63) - - Fixed a ui bug to auto select the first report failure after a test run - - Fixed invalid visualisation state and error counter (#66) -- TestSuite: - - Using asserts on stage after() now reporting -- Core: - - The GdUnit network layer was replaced by a new TCP server/client architecture to enable network-related testing (#64 ) diff --git a/addons/gdUnit4/test/update/resources/markdown_table.txt b/addons/gdUnit4/test/update/resources/markdown_table.txt deleted file mode 100644 index 6fbdad6..0000000 --- a/addons/gdUnit4/test/update/resources/markdown_table.txt +++ /dev/null @@ -1,6 +0,0 @@ - type | description --- | -- -any_color() | Argument matcher to match any Color value -any_vector2() | Argument matcher to match any Vector2 value -any_vector3() | Argument matcher to match any Vector3 value -any_rect2() | Argument matcher to match any Rect2 value diff --git a/addons/gdUnit4/test/update/resources/md_header.txt b/addons/gdUnit4/test/update/resources/md_header.txt deleted file mode 100644 index b7669b0..0000000 --- a/addons/gdUnit4/test/update/resources/md_header.txt +++ /dev/null @@ -1,11 +0,0 @@ -# Header 1 Text - -## Header 2 Text - -### Header 3 Text - -#### Header 4 Text - -##### Header 5 Text - -###### Header 6 Text diff --git a/addons/gdUnit4/test/update/resources/patches/v0.9.5/patch_y.gd b/addons/gdUnit4/test/update/resources/patches/v0.9.5/patch_y.gd deleted file mode 100644 index ee555ec..0000000 --- a/addons/gdUnit4/test/update/resources/patches/v0.9.5/patch_y.gd +++ /dev/null @@ -1,12 +0,0 @@ -extends GdUnitPatch - -func _init(): - super(GdUnit4Version.parse("v0.9.5")) - -func execute() -> bool: - var patches := Array() - if Engine.has_meta(PATCH_VERSION): - patches = Engine.get_meta(PATCH_VERSION) - patches.append(version()) - Engine.set_meta(PATCH_VERSION, patches) - return true diff --git a/addons/gdUnit4/test/update/resources/patches/v0.9.6/patch_x.gd b/addons/gdUnit4/test/update/resources/patches/v0.9.6/patch_x.gd deleted file mode 100644 index a71e1d4..0000000 --- a/addons/gdUnit4/test/update/resources/patches/v0.9.6/patch_x.gd +++ /dev/null @@ -1,12 +0,0 @@ -extends GdUnitPatch - -func _init(): - super(GdUnit4Version.parse("v0.9.6")) - -func execute() -> bool: - var patches := Array() - if Engine.has_meta(PATCH_VERSION): - patches = Engine.get_meta(PATCH_VERSION) - patches.append(version()) - Engine.set_meta(PATCH_VERSION, patches) - return true diff --git a/addons/gdUnit4/test/update/resources/patches/v0.9.9/patch_a.gd b/addons/gdUnit4/test/update/resources/patches/v0.9.9/patch_a.gd deleted file mode 100644 index 908571b..0000000 --- a/addons/gdUnit4/test/update/resources/patches/v0.9.9/patch_a.gd +++ /dev/null @@ -1,12 +0,0 @@ -extends GdUnitPatch - -func _init(): - super(GdUnit4Version.parse("v0.9.9-a")) - -func execute() -> bool: - var patches := Array() - if Engine.has_meta(PATCH_VERSION): - patches = Engine.get_meta(PATCH_VERSION) - patches.append(version()) - Engine.set_meta(PATCH_VERSION, patches) - return true diff --git a/addons/gdUnit4/test/update/resources/patches/v0.9.9/patch_b.gd b/addons/gdUnit4/test/update/resources/patches/v0.9.9/patch_b.gd deleted file mode 100644 index 9338997..0000000 --- a/addons/gdUnit4/test/update/resources/patches/v0.9.9/patch_b.gd +++ /dev/null @@ -1,12 +0,0 @@ -extends GdUnitPatch - -func _init(): - super(GdUnit4Version.parse("v0.9.9-b")) - -func execute() -> bool: - var patches := Array() - if Engine.has_meta(PATCH_VERSION): - patches = Engine.get_meta(PATCH_VERSION) - patches.append(version()) - Engine.set_meta(PATCH_VERSION, patches) - return true diff --git a/addons/gdUnit4/test/update/resources/patches/v1.1.4/patch_a.gd b/addons/gdUnit4/test/update/resources/patches/v1.1.4/patch_a.gd deleted file mode 100644 index 3098ba7..0000000 --- a/addons/gdUnit4/test/update/resources/patches/v1.1.4/patch_a.gd +++ /dev/null @@ -1,12 +0,0 @@ -extends GdUnitPatch - -func _init(): - super(GdUnit4Version.parse("v1.1.4")) - -func execute() -> bool: - var patches := Array() - if Engine.has_meta(PATCH_VERSION): - patches = Engine.get_meta(PATCH_VERSION) - patches.append(version()) - Engine.set_meta(PATCH_VERSION, patches) - return true diff --git a/addons/gdUnit4/test/update/resources/update.zip b/addons/gdUnit4/test/update/resources/update.zip deleted file mode 100644 index f58a8540415c2df2ac282c9c9ef35419e84d9950..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1194 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 literal 0 HcmV?d00001 diff --git a/addons/gdUnit4/src/ui/assets/icon.png.import b/resources/icons/gameicon.png.import similarity index 68% rename from addons/gdUnit4/src/ui/assets/icon.png.import rename to resources/icons/gameicon.png.import index 4bd6b84..8978ffb 100644 --- a/addons/gdUnit4/src/ui/assets/icon.png.import +++ b/resources/icons/gameicon.png.import @@ -2,16 +2,16 @@ importer="texture" type="CompressedTexture2D" -uid="uid://c7sk0yhd52lg3" -path="res://.godot/imported/icon.png-3b59f326b0d0310df661a9bddfa24566.ctex" +uid="uid://c12b76nv4y3kv" +path="res://.godot/imported/gameicon.png-493cd866ff935ca1233be4506c17d2c0.ctex" metadata={ "vram_texture": false } [deps] -source_file="res://addons/gdUnit4/src/ui/assets/icon.png" -dest_files=["res://.godot/imported/icon.png-3b59f326b0d0310df661a9bddfa24566.ctex"] +source_file="res://resources/icons/gameicon.png" +dest_files=["res://.godot/imported/gameicon.png-493cd866ff935ca1233be4506c17d2c0.ctex"] [params]

@b*n$NX0V*me^ZSnU&OKIFJBpqo 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 diff --git a/addons/gdUnit4/src/update/assets/fonts/static/RobotoMono-LightItalic.ttf.import b/addons/gdUnit4/src/update/assets/fonts/static/RobotoMono-LightItalic.ttf.import deleted file mode 100644 index 4216e5d..0000000 --- a/addons/gdUnit4/src/update/assets/fonts/static/RobotoMono-LightItalic.ttf.import +++ /dev/null @@ -1,33 +0,0 @@ -[remap] - -importer="font_data_dynamic" -type="FontFile" -uid="uid://crphq80yxhgic" -path="res://.godot/imported/RobotoMono-LightItalic.ttf-473f0d613e289d058b8c392d0c5242bc.fontdata" - -[deps] - -source_file="res://addons/gdUnit4/src/update/assets/fonts/static/RobotoMono-LightItalic.ttf" -dest_files=["res://.godot/imported/RobotoMono-LightItalic.ttf-473f0d613e289d058b8c392d0c5242bc.fontdata"] - -[params] - -Rendering=null -antialiasing=1 -generate_mipmaps=false -multichannel_signed_distance_field=false -msdf_pixel_range=8 -msdf_size=48 -allow_system_fallback=true -force_autohinter=false -hinting=1 -subpixel_positioning=1 -oversampling=0.0 -Fallbacks=null -fallbacks=[] -Compress=null -compress=true -preload=[] -language_support={} -script_support={} -opentype_features={} diff --git a/addons/gdUnit4/src/update/assets/fonts/static/RobotoMono-Medium.ttf b/addons/gdUnit4/src/update/assets/fonts/static/RobotoMono-Medium.ttf deleted file mode 100644 index 8461be77a38bdc0e47b678f1623df33379535d8f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 86820 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 Ce6P0v diff --git a/addons/gdUnit4/src/update/assets/fonts/static/RobotoMono-Medium.ttf.import b/addons/gdUnit4/src/update/assets/fonts/static/RobotoMono-Medium.ttf.import deleted file mode 100644 index 342bafe..0000000 --- a/addons/gdUnit4/src/update/assets/fonts/static/RobotoMono-Medium.ttf.import +++ /dev/null @@ -1,33 +0,0 @@ -[remap] - -importer="font_data_dynamic" -type="FontFile" -uid="uid://csdprnrpwr3xp" -path="res://.godot/imported/RobotoMono-Medium.ttf-f165ecef77d89557a95acac0927f13c4.fontdata" - -[deps] - -source_file="res://addons/gdUnit4/src/update/assets/fonts/static/RobotoMono-Medium.ttf" -dest_files=["res://.godot/imported/RobotoMono-Medium.ttf-f165ecef77d89557a95acac0927f13c4.fontdata"] - -[params] - -Rendering=null -antialiasing=1 -generate_mipmaps=false -multichannel_signed_distance_field=false -msdf_pixel_range=8 -msdf_size=48 -allow_system_fallback=true -force_autohinter=false -hinting=1 -subpixel_positioning=1 -oversampling=0.0 -Fallbacks=null -fallbacks=[] -Compress=null -compress=true -preload=[] -language_support={} -script_support={} -opentype_features={} diff --git a/addons/gdUnit4/src/update/assets/fonts/static/RobotoMono-MediumItalic.ttf b/addons/gdUnit4/src/update/assets/fonts/static/RobotoMono-MediumItalic.ttf deleted file mode 100644 index a3bfaa115a3f3a30eddad5c6c3aafb9b5ee2624b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 93948 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%0D@A?Vb^R#DS|+Cj3`jOj(eO`fft;vZ)JVx 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 CrTcsU diff --git a/addons/gdUnit4/src/update/assets/fonts/static/RobotoMono-MediumItalic.ttf.import b/addons/gdUnit4/src/update/assets/fonts/static/RobotoMono-MediumItalic.ttf.import deleted file mode 100644 index 604eddb..0000000 --- a/addons/gdUnit4/src/update/assets/fonts/static/RobotoMono-MediumItalic.ttf.import +++ /dev/null @@ -1,33 +0,0 @@ -[remap] - -importer="font_data_dynamic" -type="FontFile" -uid="uid://cdxcb7jq16o5k" -path="res://.godot/imported/RobotoMono-MediumItalic.ttf-40c40d791914284c8092585e839f0cd1.fontdata" - -[deps] - -source_file="res://addons/gdUnit4/src/update/assets/fonts/static/RobotoMono-MediumItalic.ttf" -dest_files=["res://.godot/imported/RobotoMono-MediumItalic.ttf-40c40d791914284c8092585e839f0cd1.fontdata"] - -[params] - -Rendering=null -antialiasing=1 -generate_mipmaps=false -multichannel_signed_distance_field=false -msdf_pixel_range=8 -msdf_size=48 -allow_system_fallback=true -force_autohinter=false -hinting=1 -subpixel_positioning=1 -oversampling=0.0 -Fallbacks=null -fallbacks=[] -Compress=null -compress=true -preload=[] -language_support={} -script_support={} -opentype_features={} diff --git a/addons/gdUnit4/src/update/assets/fonts/static/RobotoMono-Regular.ttf b/addons/gdUnit4/src/update/assets/fonts/static/RobotoMono-Regular.ttf deleted file mode 100644 index 7c4ce36a442d14eeb12444ad707c2afa19422fd8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 86908 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