├── LICENSE ├── addons └── gdUnit3 │ ├── bin │ ├── GdUnitBuildTool.gd │ ├── GdUnitCmdTool.gd │ └── GdUnitCopyLog.gd │ ├── plugin.cfg │ ├── plugin.gd │ ├── runtest.sh │ └── src │ ├── Comparator.gd │ ├── Fuzzers.gd │ ├── GdUnitArrayAssert.gd │ ├── GdUnitAssert.gd │ ├── GdUnitAwaiter.gd │ ├── GdUnitBoolAssert.gd │ ├── GdUnitConstants.gd │ ├── GdUnitDictionaryAssert.gd │ ├── GdUnitFileAssert.gd │ ├── GdUnitFloatAssert.gd │ ├── GdUnitFuncAssert.gd │ ├── GdUnitIntAssert.gd │ ├── GdUnitObjectAssert.gd │ ├── GdUnitResultAssert.gd │ ├── GdUnitSceneRunner.gd │ ├── GdUnitSignalAssert.gd │ ├── GdUnitStringAssert.gd │ ├── GdUnitTestSuite.gd │ ├── GdUnitTuple.gd │ ├── GdUnitValueExtractor.gd │ ├── GdUnitVector2Assert.gd │ ├── GdUnitVector3Assert.gd │ ├── asserts │ ├── CallBackValueProvider.gd │ ├── DefaultValueProvider.gd │ ├── GdAssertMessages.gd │ ├── GdAssertReports.gd │ ├── GdUnitArrayAssertImpl.gd │ ├── GdUnitAssertImpl.gd │ ├── GdUnitBoolAssertImpl.gd │ ├── GdUnitDictionaryAssertImpl.gd │ ├── GdUnitFileAssertImpl.gd │ ├── GdUnitFloatAssertImpl.gd │ ├── GdUnitFuncAssertImpl.gd │ ├── GdUnitIntAssertImpl.gd │ ├── GdUnitObjectAssertImpl.gd │ ├── GdUnitResultAssertImpl.gd │ ├── GdUnitSignalAssertImpl.gd │ ├── GdUnitStringAssertImpl.gd │ ├── GdUnitVector2AssertImpl.gd │ ├── GdUnitVector3AssertImpl.gd │ └── ValueProvider.gd │ ├── cmd │ ├── CmdArgumentParser.gd │ ├── CmdCommand.gd │ ├── CmdCommandHandler.gd │ ├── CmdConsole.gd │ ├── CmdOption.gd │ └── CmdOptions.gd │ ├── core │ ├── GdDiffTool.gd │ ├── GdFunctionDoubler.gd │ ├── GdObjects.gd │ ├── GdUnit3Version.gd │ ├── GdUnitClassDoubler.gd │ ├── GdUnitExecutor.gd │ ├── GdUnitMemoryPool.gd │ ├── GdUnitObjectInteractions.gd │ ├── GdUnitObjectInteractionsTemplate.gd │ ├── GdUnitProperty.gd │ ├── GdUnitRunner.gd │ ├── GdUnitRunner.tscn │ ├── GdUnitRunnerConfig.gd │ ├── GdUnitSceneRunnerImpl.gd │ ├── GdUnitScriptType.gd │ ├── GdUnitSettings.gd │ ├── GdUnitSignalAwaiter.gd │ ├── GdUnitSingleton.gd │ ├── GdUnitTestSuiteBuilder.gd │ ├── GdUnitTools.gd │ ├── LocalTime.gd │ ├── Result.gd │ ├── TimeUnit.gd │ ├── _TestCase.gd │ ├── _TestSuiteScanner.gd │ ├── event │ │ ├── GdUnitEvent.gd │ │ ├── GdUnitEventInit.gd │ │ ├── GdUnitEventStop.gd │ │ └── SignalHandler.gd │ ├── parse │ │ ├── GdClassDescriptor.gd │ │ ├── GdFunctionArgument.gd │ │ ├── GdFunctionDescriptor.gd │ │ ├── GdScriptParser.gd │ │ └── GdTestParameterSet.gd │ ├── report │ │ ├── GdUnitReport.gd │ │ ├── GdUnitReportCollector.gd │ │ └── GdUnitReportConsumer.gd │ ├── scan_project.gd │ └── templates │ │ └── test_suite │ │ ├── GdUnitTestSuiteDefaultTemplate.gd │ │ └── GdUnitTestSuiteTemplate.gd │ ├── extractors │ └── GdUnitFuncValueExtractor.gd │ ├── fuzzers │ ├── Fuzzer.gd │ ├── FuzzerTool.gd │ ├── IntFuzzer.gd │ ├── StringFuzzer.gd │ ├── Vector2Fuzzer.gd │ └── Vector3Fuzzer.gd │ ├── matchers │ ├── AnyArgumentMatcher.gd │ ├── AnyBuildInTypeArgumentMatcher.gd │ ├── AnyClazzArgumentMatcher.gd │ ├── ChainedArgumentMatcher.gd │ ├── EqualsArgumentMatcher.gd │ ├── GdUnitArgumentMatcher.gd │ └── GdUnitArgumentMatchers.gd │ ├── mocking │ ├── GdUnitMock.gd │ ├── GdUnitMockBuilder.gd │ └── GdUnitMockImpl.gd │ ├── monitor │ ├── GdUnitMemMonitor.gd │ ├── GdUnitMonitor.gd │ └── GodotGdErrorMonitor.gd │ ├── mono │ ├── GdUnit3MonoAPI.cs │ └── GdUnit3MonoAPI.gd │ ├── network │ ├── GdUnitServer.gd │ ├── GdUnitServer.tscn │ ├── GdUnitServerConstants.gd │ ├── GdUnitTask.gd │ ├── GdUnitTcpClient.gd │ ├── GdUnitTcpServer.gd │ └── rpc │ │ ├── RPC.gd │ │ ├── RPCClientConnect.gd │ │ ├── RPCClientDisconnect.gd │ │ ├── RPCData.gd │ │ ├── RPCGdUnitEvent.gd │ │ ├── RPCGdUnitTestSuite.gd │ │ ├── RPCMessage.gd │ │ └── dtos │ │ ├── GdUnitResourceDto.gd │ │ ├── GdUnitTestCaseDto.gd │ │ └── GdUnitTestSuiteDto.gd │ ├── report │ ├── GdUnitByPathReport.gd │ ├── GdUnitHtmlPatterns.gd │ ├── GdUnitHtmlReport.gd │ ├── GdUnitReportSummary.gd │ ├── GdUnitTestCaseReport.gd │ ├── GdUnitTestSuiteReport.gd │ ├── JUnitXmlReport.gd │ ├── XmlElement.gd │ └── template │ │ ├── css │ │ ├── breadcrumb.css │ │ ├── icon.png │ │ └── style.css │ │ ├── folder_report.html │ │ ├── index.html │ │ └── suite_report.html │ ├── spy │ ├── GdUnitSpyBuilder.gd │ └── GdUnitSpyImpl.gd │ ├── ui │ ├── GdUnitConsole.gd │ ├── GdUnitConsole.tscn │ ├── GdUnitFonts.gd │ ├── GdUnitInspector.gd │ ├── GdUnitInspector.tscn │ ├── GdUnitToolsDialog.gd │ ├── GdUnitToolsDialog.tscn │ ├── assets │ │ ├── Play.svg │ │ ├── PlayDebug.svg │ │ ├── Stop.svg │ │ ├── TestCase.svg │ │ ├── TestCaseError.svg │ │ ├── TestCaseFailed.svg │ │ ├── TestCaseSuccess.svg │ │ ├── TestCase_error_orphan.tres │ │ ├── TestCase_failed_orphan.tres │ │ ├── TestCase_success_orphan.tres │ │ ├── TestSuite.svg │ │ ├── clock.svg │ │ ├── debug.svg │ │ ├── errors.svg │ │ ├── failures.svg │ │ ├── orphan │ │ │ ├── TestCaseError1.svg │ │ │ ├── TestCaseError2.svg │ │ │ ├── TestCaseFailed.svg │ │ │ ├── TestCaseFailed1.svg │ │ │ ├── TestCaseFailed2.svg │ │ │ ├── TestCaseSuccess1.svg │ │ │ ├── TestCaseSuccess2.svg │ │ │ ├── orphan_animated_icon.tres │ │ │ ├── orphan_green.svg │ │ │ ├── orphan_red1.svg │ │ │ ├── orphan_red2.svg │ │ │ ├── orphan_red3.svg │ │ │ ├── orphan_red4.svg │ │ │ ├── orphan_red5.svg │ │ │ ├── orphan_red6.svg │ │ │ └── orphan_red7.svg │ │ ├── running.png │ │ ├── spinner.tres │ │ └── spinner │ │ │ ├── Progress1.svg │ │ │ ├── Progress2.svg │ │ │ ├── Progress3.svg │ │ │ ├── Progress4.svg │ │ │ ├── Progress5.svg │ │ │ ├── Progress6.svg │ │ │ ├── Progress7.svg │ │ │ └── Progress8.svg │ ├── parts │ │ ├── InspectorMonitor.gd │ │ ├── InspectorMonitor.tscn │ │ ├── InspectorProgressBar.gd │ │ ├── InspectorProgressBar.tscn │ │ ├── InspectorStatusBar.gd │ │ ├── InspectorStatusBar.tscn │ │ ├── InspectorToolBar.gd │ │ ├── InspectorToolBar.tscn │ │ ├── InspectorTreeMainPanel.gd │ │ ├── InspectorTreePanel.tscn │ │ ├── RichTextEffectBackground.gd │ │ └── RichTextLabelExt.gd │ └── templates │ │ ├── TestSuiteTemplate.gd │ │ └── TestSuiteTemplate.tscn │ └── update │ ├── GdMarkDownReader.gd │ ├── GdUnitPatch.gd │ ├── GdUnitPatcher.gd │ ├── GdUnitUpdate.gd │ ├── GdUnitUpdate.tscn │ ├── GdUnitUpdateClient.gd │ ├── assets │ ├── border_bottom.png │ ├── border_top.png │ ├── dot1.png │ ├── dot2.png │ ├── embedded.png │ ├── fonts │ │ ├── LICENSE.txt │ │ ├── README.txt │ │ ├── RobotoMono-14-bold-italics.tres │ │ ├── RobotoMono-14-bold.tres │ │ ├── RobotoMono-14-italics.tres │ │ ├── RobotoMono-14.tres │ │ ├── RobotoMono-code.tres │ │ ├── RobotoMono-h1.tres │ │ ├── RobotoMono-h2.tres │ │ ├── RobotoMono-h3.tres │ │ ├── RobotoMono-h4.tres │ │ ├── RobotoMono-h5.tres │ │ └── static │ │ │ ├── RobotoMono-Bold.ttf │ │ │ ├── RobotoMono-BoldItalic.ttf │ │ │ ├── RobotoMono-ExtraLight.ttf │ │ │ ├── RobotoMono-ExtraLightItalic.ttf │ │ │ ├── RobotoMono-Italic.ttf │ │ │ ├── RobotoMono-Light.ttf │ │ │ ├── RobotoMono-LightItalic.ttf │ │ │ ├── RobotoMono-Medium.ttf │ │ │ ├── RobotoMono-MediumItalic.ttf │ │ │ ├── RobotoMono-Regular.ttf │ │ │ ├── RobotoMono-SemiBold.ttf │ │ │ ├── RobotoMono-SemiBoldItalic.ttf │ │ │ ├── RobotoMono-Thin.ttf │ │ │ └── RobotoMono-ThinItalic.ttf │ ├── horizontal-line2.png │ └── progress-background.png │ ├── bbcodeView.tscn │ └── patches │ └── v1.1.0 │ ├── patch_gd_170.gd │ └── patch_gd_172.gd ├── runtest.cmd └── workspace.code-workspace /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Mike Schulze 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /addons/gdUnit3/plugin.cfg: -------------------------------------------------------------------------------- 1 | [plugin] 2 | 3 | name="gdUnit3" 4 | description="Unit Testing Framework for Godot Scripts" 5 | author="Mike Schulze" 6 | version="2.4.3" 7 | script="plugin.gd" 8 | -------------------------------------------------------------------------------- /addons/gdUnit3/plugin.gd: -------------------------------------------------------------------------------- 1 | tool 2 | extends EditorPlugin 3 | 4 | var _gd_inspector :Node 5 | var _server_node 6 | var _gd_console :Node 7 | var _update_tool :Node 8 | var _singleton :GdUnitSingleton = GdUnitSingleton.new() 9 | 10 | func _enter_tree(): 11 | Engine.set_meta("GdUnitEditorPlugin", self) 12 | GdUnitSettings.setup() 13 | # show possible update notification when is enabled 14 | if GdUnitSettings.is_update_notification_enabled(): 15 | _update_tool = load("res://addons/gdUnit3/src/update/GdUnitUpdate.tscn").instance() 16 | add_control_to_container(EditorPlugin.CONTAINER_TOOLBAR, _update_tool) 17 | 18 | # install SignalHandler singleton 19 | GdUnitSingleton.add_singleton(SignalHandler.SINGLETON_NAME, "res://addons/gdUnit3/src/core/event/SignalHandler.gd") 20 | # install the GdUnit inspector 21 | _gd_inspector = load("res://addons/gdUnit3/src/ui/GdUnitInspector.tscn").instance() 22 | _gd_inspector.set_editor_interface(get_editor_interface()) 23 | add_control_to_dock(EditorPlugin.DOCK_SLOT_LEFT_UR, _gd_inspector) 24 | # install the GdUnit Console 25 | _gd_console = load("res://addons/gdUnit3/src/ui/GdUnitConsole.tscn").instance() 26 | add_control_to_bottom_panel(_gd_console, "gdUnitConsole") 27 | # needs to wait before we can add a child to the root 28 | yield(get_tree(), "idle_frame") 29 | _server_node = load("res://addons/gdUnit3/src/network/GdUnitServer.tscn").instance() 30 | add_child(_server_node) 31 | var err := _gd_inspector.connect("gdunit_runner_stop", _server_node, "_on_gdunit_runner_stop") 32 | if err != OK: 33 | prints("ERROR", GdUnitTools.error_as_string(err)) 34 | prints("Loading GdUnit3 Plugin success") 35 | 36 | func _exit_tree(): 37 | if is_instance_valid(_gd_inspector): 38 | remove_control_from_docks(_gd_inspector) 39 | _gd_inspector.free() 40 | if is_instance_valid(_gd_console): 41 | remove_control_from_bottom_panel(_gd_console) 42 | _gd_console.free() 43 | if is_instance_valid(_server_node): 44 | remove_child(_server_node) 45 | _server_node.free() 46 | 47 | # Delete and release the update tool only when it is not in use, otherwise it will interrupt the execution of the update 48 | if is_instance_valid(_update_tool) and not _update_tool.is_update_in_progress(): 49 | remove_control_from_container(EditorPlugin.CONTAINER_TOOLBAR, _update_tool) 50 | _update_tool.free() 51 | GdUnitSingleton.remove_singleton(SignalHandler.SINGLETON_NAME) 52 | if Engine.has_meta("GdUnitEditorPlugin"): 53 | Engine.remove_meta("GdUnitEditorPlugin") 54 | prints("Unload GdUnit3 Plugin success") 55 | -------------------------------------------------------------------------------- /addons/gdUnit3/runtest.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | if [ -z "$GODOT_BIN" ]; then 4 | echo "'GODOT_BIN' is not set." 5 | echo "Please set the environment variable 'export GODOT_BIN=/Applications/Godot.app/Contents/MacOS/Godot'" 6 | exit 1 7 | fi 8 | 9 | # we not use no-window because of issue https://github.com/godotengine/godot/issues/55379 10 | #$GODOT_BIN --no-window -s -d ./addons/gdUnit3/bin/GdUnitCmdTool.gd $* 11 | $GODOT_BIN -s -d ./addons/gdUnit3/bin/GdUnitCmdTool.gd $* 12 | exit_code=$? 13 | $GODOT_BIN --no-window --quiet -s -d ./addons/gdUnit3/bin/GdUnitCopyLog.gd $* > /dev/null 14 | exit $exit_code 15 | -------------------------------------------------------------------------------- /addons/gdUnit3/src/Comparator.gd: -------------------------------------------------------------------------------- 1 | class_name Comparator 2 | extends Resource 3 | 4 | enum { 5 | EQUAL, 6 | LESS_THAN, 7 | LESS_EQUAL, 8 | GREATER_THAN, 9 | GREATER_EQUAL, 10 | BETWEEN_EQUAL, 11 | NOT_BETWEEN_EQUAL, 12 | } 13 | -------------------------------------------------------------------------------- /addons/gdUnit3/src/Fuzzers.gd: -------------------------------------------------------------------------------- 1 | class_name Fuzzers 2 | extends Resource 3 | 4 | static func rand_str(min_length :int, max_length, charset := StringFuzzer.DEFAULT_CHARSET) -> Fuzzer: 5 | return StringFuzzer.new(min_length, max_length, charset) 6 | 7 | # Generates an random integer in a range form to 8 | static func rangei(from: int, to: int) -> Fuzzer: 9 | return IntFuzzer.new(from, to) 10 | 11 | # Generates an random Vector2 in a range form to 12 | static func rangev2(from: Vector2, to: Vector2) -> Fuzzer: 13 | return Vector2Fuzzer.new(from, to) 14 | 15 | # Generates an random Vector3 in a range form to 16 | static func rangev3(from: Vector3, to: Vector3) -> Fuzzer: 17 | return Vector3Fuzzer.new(from, to) 18 | 19 | # Generates an integer in a range form to that can be divided exactly by 2 20 | static func eveni(from: int, to: int) -> Fuzzer: 21 | return IntFuzzer.new(from, to, IntFuzzer.EVEN) 22 | 23 | # Generates an integer in a range form to that cannot be divided exactly by 2 24 | static func oddi(from: int, to: int) -> Fuzzer: 25 | return IntFuzzer.new(from, to, IntFuzzer.ODD) 26 | -------------------------------------------------------------------------------- /addons/gdUnit3/src/GdUnitArrayAssert.gd: -------------------------------------------------------------------------------- 1 | # An Assertion Tool to verify array values 2 | class_name GdUnitArrayAssert 3 | extends GdUnitAssert 4 | 5 | 6 | # Verifies that the current value is null. 7 | func is_null() -> GdUnitArrayAssert: 8 | return self 9 | 10 | # Verifies that the current value is not null. 11 | func is_not_null() -> GdUnitArrayAssert: 12 | return self 13 | 14 | # Verifies that the current Array is equal to the given one. 15 | func is_equal(expected) -> GdUnitArrayAssert: 16 | return self 17 | 18 | # Verifies that the current Array is equal to the given one, ignoring case considerations. 19 | func is_equal_ignoring_case(expected) -> GdUnitArrayAssert: 20 | return self 21 | 22 | # Verifies that the current Array is not equal to the given one. 23 | func is_not_equal(expected) -> GdUnitArrayAssert: 24 | return self 25 | 26 | # Verifies that the current Array is not equal to the given one, ignoring case considerations. 27 | func is_not_equal_ignoring_case(expected) -> GdUnitArrayAssert: 28 | return self 29 | 30 | # Verifies that the current Array is empty, it has a size of 0. 31 | func is_empty() -> GdUnitArrayAssert: 32 | return self 33 | 34 | # Verifies that the current Array is not empty, it has a size of minimum 1. 35 | func is_not_empty() -> GdUnitArrayAssert: 36 | return self 37 | 38 | # Verifies that the current Array has a size of given value. 39 | func has_size(expectd: int) -> GdUnitArrayAssert: 40 | return self 41 | 42 | # Verifies that the current Array contains the given values, in any order. 43 | func contains(expected) -> GdUnitArrayAssert: 44 | return self 45 | 46 | # Verifies that the current Array contains exactly only the given values and nothing else, in same order. 47 | func contains_exactly(expected) -> GdUnitArrayAssert: 48 | return self 49 | 50 | # Verifies that the current Array contains exactly only the given values and nothing else, in any order. 51 | func contains_exactly_in_any_order(expected) -> GdUnitArrayAssert: 52 | return self 53 | 54 | # Extracts all values by given function name and optional arguments into a new ArrayAssert 55 | # If the elements not accessible by `func_name` the value is converted to `"n.a"`, expecting null values 56 | func extract(func_name: String, args := Array()) -> GdUnitArrayAssert: 57 | return self 58 | 59 | # Extracts all values by given extractor's into a new ArrayAssert 60 | # If the elements not extractable than the value is converted to `"n.a"`, expecting null values 61 | func extractv( 62 | extractor0 :GdUnitValueExtractor, 63 | extractor1 :GdUnitValueExtractor = null, 64 | extractor2 :GdUnitValueExtractor = null, 65 | extractor3 :GdUnitValueExtractor = null, 66 | extractor4 :GdUnitValueExtractor = null, 67 | extractor5 :GdUnitValueExtractor = null, 68 | extractor6 :GdUnitValueExtractor = null, 69 | extractor7 :GdUnitValueExtractor = null, 70 | extractor8 :GdUnitValueExtractor = null, 71 | extractor9 :GdUnitValueExtractor = null) -> GdUnitArrayAssert: 72 | return self 73 | -------------------------------------------------------------------------------- /addons/gdUnit3/src/GdUnitAssert.gd: -------------------------------------------------------------------------------- 1 | # Base interface of all GdUnit asserts 2 | class_name GdUnitAssert 3 | extends Reference 4 | 5 | # assert expects ends with success 6 | const EXPECT_SUCCESS:int = 0 7 | # assert expects ends with error 8 | const EXPECT_FAIL:int = 1 9 | 10 | 11 | # Verifies that the current value is null. 12 | func is_null(): 13 | return self 14 | 15 | # Verifies that the current value is not null. 16 | func is_not_null(): 17 | return self 18 | 19 | # Verifies that the current value is equal to expected one. 20 | func is_equal(expected): 21 | return self 22 | 23 | # Verifies that the current value is not equal to expected one. 24 | func is_not_equal(expected): 25 | return self 26 | 27 | func test_fail(): 28 | return self 29 | 30 | # Verifies the failure message is equal to expected one. 31 | func has_failure_message(expected: String): 32 | return self 33 | 34 | # Verifies that the failure starts with the given prefix. 35 | func starts_with_failure_message(expected: String): 36 | return self 37 | 38 | # Overrides the default failure message by given custom message. 39 | func override_failure_message(message :String): 40 | return self 41 | -------------------------------------------------------------------------------- /addons/gdUnit3/src/GdUnitAwaiter.gd: -------------------------------------------------------------------------------- 1 | class_name GdUnitAwaiter 2 | extends Reference 3 | 4 | 5 | # Waits for a specified signal in an interval of 50ms sent from the , and terminates with an error after the specified timeout has elapsed. 6 | # source: the object from which the signal is emitted 7 | # signal_name: signal name 8 | # args: the expected signal arguments as an array 9 | # timeout: the timeout in ms, default is set to 2000ms 10 | static func await_signal_on(test_suite :WeakRef, source :Object, signal_name :String, args :Array = [], timeout_millis :int = 2000) -> GDScriptFunctionState: 11 | var line_number := GdUnitAssertImpl._get_line_number() 12 | # fail fast if the given source instance invalid 13 | if not is_instance_valid(source): 14 | GdUnitAssertImpl.new(test_suite.get_ref(), signal_name)\ 15 | .report_error(GdAssertMessages.error_await_signal_on_invalid_instance(source, signal_name, args), line_number) 16 | return await_idle_frame() 17 | var awaiter = GdUnitSignalAwaiter.new(timeout_millis) 18 | var value = yield(awaiter.on_signal(source, signal_name, args), "completed") 19 | if awaiter.is_interrupted(): 20 | var failure = "await_signal_on(%s, %s) timed out after %sms" % [signal_name, args, timeout_millis] 21 | GdUnitAssertImpl.new(test_suite.get_ref(), signal_name).report_error(failure, line_number) 22 | return value 23 | 24 | # Waits for a specified signal sent from the between idle frames and aborts with an error after the specified timeout has elapsed 25 | # source: the object from which the signal is emitted 26 | # signal_name: signal name 27 | # args: the expected signal arguments as an array 28 | # timeout: the timeout in ms, default is set to 2000ms 29 | static func await_signal_idle_frames(test_suite :WeakRef, source :Object, signal_name :String, args :Array = [], timeout_millis :int = 2000) -> GDScriptFunctionState: 30 | var line_number := GdUnitAssertImpl._get_line_number() 31 | # fail fast if the given source instance invalid 32 | if not is_instance_valid(source): 33 | GdUnitAssertImpl.new(test_suite.get_ref(), signal_name)\ 34 | .report_error(GdAssertMessages.error_await_signal_on_invalid_instance(source, signal_name, args), line_number) 35 | return await_idle_frame() 36 | var awaiter = GdUnitSignalAwaiter.new(timeout_millis, true) 37 | yield(awaiter.on_signal(source, signal_name, args), "completed") 38 | if awaiter.is_interrupted(): 39 | var failure = "await_signal_idle_frames(%s, %s) timed out after %sms" % [signal_name, args, timeout_millis] 40 | GdUnitAssertImpl.new(test_suite.get_ref(), signal_name).report_error(failure, line_number) 41 | return 42 | 43 | # Waits for for a given amount of milliseconds 44 | # example: 45 | # # waits for 100ms 46 | # yield(GdUnitAwaiter.await_millis(myNode, 100), "completed") 47 | # use this waiter and not `yield(get_tree().create_timer(), "timeout") to prevent errors when a test case is timed out 48 | static func await_millis(parent: Node, milliSec :int) -> GDScriptFunctionState: 49 | var timer :Timer = parent.auto_free(Timer.new()) 50 | parent.add_child(timer) 51 | timer.set_one_shot(true) 52 | timer.start(milliSec * 0.001) 53 | return yield(timer, "timeout") 54 | 55 | # Waits until the next idle frame 56 | static func await_idle_frame() -> GDScriptFunctionState: 57 | return yield(Engine.get_main_loop(), "idle_frame") 58 | -------------------------------------------------------------------------------- /addons/gdUnit3/src/GdUnitBoolAssert.gd: -------------------------------------------------------------------------------- 1 | # An Assertion Tool to verify boolean values 2 | class_name GdUnitBoolAssert 3 | extends GdUnitAssert 4 | 5 | 6 | # Verifies that the current value is null. 7 | func is_null() -> GdUnitBoolAssert: 8 | return self 9 | 10 | # Verifies that the current value is not null. 11 | func is_not_null() -> GdUnitBoolAssert: 12 | return self 13 | 14 | # Verifies that the current value is equal to the given one. 15 | func is_equal(expected) -> GdUnitBoolAssert: 16 | return self 17 | 18 | # Verifies that the current value is not equal to the given one. 19 | func is_not_equal(expected) -> GdUnitBoolAssert: 20 | return self 21 | 22 | # Verifies that the current value is true. 23 | func is_true() -> GdUnitBoolAssert: 24 | return self 25 | 26 | # Verifies that the current value is false. 27 | func is_false() -> GdUnitBoolAssert: 28 | return self 29 | 30 | func override_failure_message(message :String) -> GdUnitBoolAssert: 31 | return self 32 | -------------------------------------------------------------------------------- /addons/gdUnit3/src/GdUnitConstants.gd: -------------------------------------------------------------------------------- 1 | class_name GdUnitConstants 2 | extends Reference 3 | 4 | const NO_ARG = "<--null-->" 5 | -------------------------------------------------------------------------------- /addons/gdUnit3/src/GdUnitDictionaryAssert.gd: -------------------------------------------------------------------------------- 1 | # An Assertion Tool to verify dictionary 2 | class_name GdUnitDictionaryAssert 3 | extends GdUnitAssert 4 | 5 | 6 | # Verifies that the current value is null. 7 | func is_null() -> GdUnitDictionaryAssert: 8 | return self 9 | 10 | # Verifies that the current value is not null. 11 | func is_not_null() -> GdUnitDictionaryAssert: 12 | return self 13 | 14 | # Verifies that the current dictionary is equal to the given one, ignoring order. 15 | func is_equal(expected) -> GdUnitDictionaryAssert: 16 | return self 17 | 18 | # Verifies that the current dictionary is not equal to the given one, ignoring order. 19 | func is_not_equal(expected) -> GdUnitDictionaryAssert: 20 | return self 21 | 22 | # Verifies that the current dictionary is empty, it has a size of 0. 23 | func is_empty() -> GdUnitDictionaryAssert: 24 | return self 25 | 26 | # Verifies that the current dictionary is not empty, it has a size of minimum 1. 27 | func is_not_empty() -> GdUnitDictionaryAssert: 28 | return self 29 | 30 | # Verifies that the current dictionary has a size of given value. 31 | func has_size(expected: int) -> GdUnitDictionaryAssert: 32 | return self 33 | 34 | # Verifies that the current dictionary contains the given key(s). 35 | func contains_keys(expected :Array) -> GdUnitDictionaryAssert: 36 | return self 37 | 38 | # Verifies that the current dictionary not contains the given key(s). 39 | func contains_not_keys(expected :Array) -> GdUnitDictionaryAssert: 40 | return self 41 | 42 | # Verifies that the current dictionary contains the given key and value. 43 | func contains_key_value(key, value) -> GdUnitDictionaryAssert: 44 | return self 45 | -------------------------------------------------------------------------------- /addons/gdUnit3/src/GdUnitFileAssert.gd: -------------------------------------------------------------------------------- 1 | class_name GdUnitFileAssert 2 | extends GdUnitAssert 3 | 4 | 5 | func is_file() -> GdUnitFileAssert: 6 | return self 7 | 8 | func exists() -> GdUnitFileAssert: 9 | return self 10 | 11 | func is_script() -> GdUnitFileAssert: 12 | return self 13 | 14 | func contains_exactly(expected_rows :Array) -> GdUnitFileAssert: 15 | return self 16 | -------------------------------------------------------------------------------- /addons/gdUnit3/src/GdUnitFloatAssert.gd: -------------------------------------------------------------------------------- 1 | # An Assertion Tool to verify float values 2 | class_name GdUnitFloatAssert 3 | extends GdUnitAssert 4 | 5 | # Verifies that the current value is equal to expected one. 6 | func is_equal(expected :float) -> GdUnitFloatAssert: 7 | return self 8 | 9 | # Verifies that the current value is not equal to expected one. 10 | func is_not_equal(expected :float) -> GdUnitFloatAssert: 11 | return self 12 | 13 | # Verifies that the current and expected value are approximately equal. 14 | func is_equal_approx(expected :float, approx :float) -> GdUnitFloatAssert: 15 | return self 16 | 17 | # Verifies that the current value is less than the given one. 18 | func is_less(expected :float) -> GdUnitFloatAssert: 19 | return self 20 | 21 | # Verifies that the current value is less than or equal the given one. 22 | func is_less_equal(expected :float) -> GdUnitFloatAssert: 23 | return self 24 | 25 | # Verifies that the current value is greater than the given one. 26 | func is_greater(expected :float) -> GdUnitFloatAssert: 27 | return self 28 | 29 | # Verifies that the current value is greater than or equal the given one. 30 | func is_greater_equal(expected :float) -> GdUnitFloatAssert: 31 | return self 32 | 33 | # Verifies that the current value is negative. 34 | func is_negative() -> GdUnitFloatAssert: 35 | return self 36 | 37 | # Verifies that the current value is not negative. 38 | func is_not_negative() -> GdUnitFloatAssert: 39 | return self 40 | 41 | # Verifies that the current value is equal to zero. 42 | func is_zero() -> GdUnitFloatAssert: 43 | return self 44 | 45 | # Verifies that the current value is not equal to zero. 46 | func is_not_zero() -> GdUnitFloatAssert: 47 | return self 48 | 49 | # Verifies that the current value is in the given set of values. 50 | func is_in(expected :Array) -> GdUnitFloatAssert: 51 | return self 52 | 53 | # Verifies that the current value is not in the given set of values. 54 | func is_not_in(expected :Array) -> GdUnitFloatAssert: 55 | return self 56 | 57 | # Verifies that the current value is between the given boundaries (inclusive). 58 | func is_between(from :float, to :float) -> GdUnitFloatAssert: 59 | return self 60 | 61 | -------------------------------------------------------------------------------- /addons/gdUnit3/src/GdUnitFuncAssert.gd: -------------------------------------------------------------------------------- 1 | # An Assertion Tool to verify function callback values 2 | class_name GdUnitFuncAssert 3 | extends GdUnitAssert 4 | 5 | 6 | # Verifies that the current value is null. 7 | func is_null() -> GdUnitAssert: 8 | return self 9 | 10 | # Verifies that the current value is not null. 11 | func is_not_null() -> GdUnitAssert: 12 | return self 13 | 14 | # Verifies that the current value is equal to the given one. 15 | func is_equal(expected) -> GdUnitAssert: 16 | return self 17 | 18 | # Verifies that the current value is not equal to the given one. 19 | func is_not_equal(expected) -> GdUnitAssert: 20 | return self 21 | 22 | # Verifies that the current value is true. 23 | func is_true() -> GdUnitAssert: 24 | return self 25 | 26 | # Verifies that the current value is false. 27 | func is_false() -> GdUnitAssert: 28 | return self 29 | 30 | # Verifies the failure message is equal to expected one. 31 | func has_failure_message(expected: String): 32 | return self 33 | 34 | # Verifies that the failure starts with the given prefix. 35 | func starts_with_failure_message(expected: String): 36 | return self 37 | 38 | # Overrides the default failure message by given custom message. 39 | func override_failure_message(message :String): 40 | return self 41 | 42 | # Sets the timeout in ms to wait the function returnd the expected value, if the time over a failure is emitted 43 | # e.g. 44 | # do wait until 5s the function `is_state` is returns 10 45 | # assert_func(instance, "is_state").wait_until(5000).is_equal(10) 46 | func wait_until(timeout :int) -> GdUnitAssert: 47 | return self 48 | -------------------------------------------------------------------------------- /addons/gdUnit3/src/GdUnitIntAssert.gd: -------------------------------------------------------------------------------- 1 | # An Assertion Tool to verify integer values 2 | class_name GdUnitIntAssert 3 | extends GdUnitAssert 4 | 5 | # Verifies that the current value is equal to expected one. 6 | func is_equal(expected :int) -> GdUnitIntAssert: 7 | return self 8 | 9 | # Verifies that the current value is not equal to expected one. 10 | func is_not_equal(expected :int) -> GdUnitIntAssert: 11 | return self 12 | 13 | # Verifies that the current value is less than the given one. 14 | func is_less(expected :int) -> GdUnitIntAssert: 15 | return self 16 | 17 | # Verifies that the current value is less than or equal the given one. 18 | func is_less_equal(expected :int) -> GdUnitIntAssert: 19 | return self 20 | 21 | # Verifies that the current value is greater than the given one. 22 | func is_greater(expected :int) -> GdUnitIntAssert: 23 | return self 24 | 25 | # Verifies that the current value is greater than or equal the given one. 26 | func is_greater_equal(expected :int) -> GdUnitIntAssert: 27 | return self 28 | 29 | # Verifies that the current value is even. 30 | func is_even() -> GdUnitIntAssert: 31 | return self 32 | 33 | # Verifies that the current value is odd. 34 | func is_odd() -> GdUnitIntAssert: 35 | return self 36 | 37 | # Verifies that the current value is negative. 38 | func is_negative() -> GdUnitIntAssert: 39 | return self 40 | 41 | # Verifies that the current value is not negative. 42 | func is_not_negative() -> GdUnitIntAssert: 43 | return self 44 | 45 | # Verifies that the current value is equal to zero. 46 | func is_zero() -> GdUnitIntAssert: 47 | return self 48 | 49 | # Verifies that the current value is not equal to zero. 50 | func is_not_zero() -> GdUnitIntAssert: 51 | return self 52 | 53 | # Verifies that the current value is in the given set of values. 54 | func is_in(expected :Array) -> GdUnitIntAssert: 55 | return self 56 | 57 | # Verifies that the current value is not in the given set of values. 58 | func is_not_in(expected :Array) -> GdUnitIntAssert: 59 | return self 60 | 61 | # Verifies that the current value is between the given boundaries (inclusive). 62 | func is_between(from :int, to :int) -> GdUnitIntAssert: 63 | return self 64 | 65 | -------------------------------------------------------------------------------- /addons/gdUnit3/src/GdUnitObjectAssert.gd: -------------------------------------------------------------------------------- 1 | # An Assertion Tool to verify Object values 2 | class_name GdUnitObjectAssert 3 | extends GdUnitAssert 4 | 5 | # Verifies that the current value is equal to expected one. 6 | func is_equal(expected) -> GdUnitObjectAssert: 7 | return self 8 | 9 | # Verifies that the current value is not equal to expected one. 10 | func is_not_equal(expected) -> GdUnitObjectAssert: 11 | return self 12 | 13 | # Verifies that the current value is null. 14 | func is_null() -> GdUnitObjectAssert: 15 | return self 16 | 17 | # Verifies that the current value is not null. 18 | func is_not_null() -> GdUnitObjectAssert: 19 | return self 20 | 21 | # Verifies that the current value is the same as the given one. 22 | func is_same(expected) -> GdUnitObjectAssert: 23 | return self 24 | 25 | # Verifies that the current value is not the same as the given one. 26 | func is_not_same(expected) -> GdUnitObjectAssert: 27 | return self 28 | 29 | # Verifies that the current value is an instance of the given type. 30 | func is_instanceof(expected) -> GdUnitObjectAssert: 31 | return self 32 | 33 | # Verifies that the current value is not an instance of the given type. 34 | func is_not_instanceof(expected) -> GdUnitObjectAssert: 35 | return self 36 | -------------------------------------------------------------------------------- /addons/gdUnit3/src/GdUnitResultAssert.gd: -------------------------------------------------------------------------------- 1 | # An Assertion Tool to verify Results 2 | class_name GdUnitResultAssert 3 | extends GdUnitAssert 4 | 5 | # Verifies that the current value is null. 6 | func is_null() -> GdUnitResultAssert: 7 | return self 8 | 9 | # Verifies that the current value is not null. 10 | func is_not_null() -> GdUnitResultAssert: 11 | return self 12 | 13 | # Verifies that the result is ends up with empty 14 | func is_empty() -> GdUnitResultAssert: 15 | return self 16 | 17 | # Verifies that the result is ends up with success 18 | func is_success() -> GdUnitResultAssert: 19 | return self 20 | 21 | # Verifies that the result is ends up with warning 22 | func is_warning() -> GdUnitResultAssert: 23 | return self 24 | 25 | # Verifies that the result is ends up with error 26 | func is_error() -> GdUnitResultAssert: 27 | return self 28 | 29 | # Verifies that the result contains the given message 30 | func contains_message(expected :String) -> GdUnitResultAssert: 31 | return self 32 | 33 | # Verifies that the result contains the given value 34 | func is_value(expected) -> GdUnitResultAssert: 35 | return self 36 | -------------------------------------------------------------------------------- /addons/gdUnit3/src/GdUnitSignalAssert.gd: -------------------------------------------------------------------------------- 1 | # An Assertion Tool to verify for emitted signals until a waiting time 2 | class_name GdUnitSignalAssert 3 | extends GdUnitAssert 4 | 5 | # Verifies that given signal is emitted until waiting time 6 | func is_emitted(name :String, args := []) -> GdUnitSignalAssert: 7 | return self 8 | 9 | # Verifies that given signal is NOT emitted until waiting time 10 | func is_not_emitted(name :String, args := []) -> GdUnitSignalAssert: 11 | return self 12 | 13 | # Verifies the signal exists on the emitter 14 | func is_signal_exists(name :String) -> GdUnitSignalAssert: 15 | return self 16 | 17 | # Verifies the failure message is equal to expected one. 18 | func has_failure_message(expected: String) -> GdUnitSignalAssert: 19 | return self 20 | 21 | # Verifies that the failure starts with the given prefix. 22 | func starts_with_failure_message(expected: String) -> GdUnitSignalAssert: 23 | return self 24 | 25 | # Overrides the default failure message by given custom message. 26 | func override_failure_message(message :String) -> GdUnitSignalAssert: 27 | return self 28 | 29 | # Sets the assert signal timeout in ms, if the time over a failure is reported 30 | # e.g. 31 | # do wait until 5s the instance has emitted the signal `signal_a` 32 | # assert_signal(instance).wait_until(5000).is_emitted("signal_a") 33 | func wait_until(timeout :int) -> GdUnitSignalAssert: 34 | return self 35 | -------------------------------------------------------------------------------- /addons/gdUnit3/src/GdUnitStringAssert.gd: -------------------------------------------------------------------------------- 1 | # An Assertion Tool to verify String values 2 | class_name GdUnitStringAssert 3 | extends GdUnitAssert 4 | 5 | # Verifies that the current String is equal to the given one. 6 | func is_equal(expected) -> GdUnitStringAssert: 7 | return self 8 | 9 | # Verifies that the current String is equal to the given one, ignoring case considerations. 10 | func is_equal_ignoring_case(expected) -> GdUnitStringAssert: 11 | return self 12 | 13 | # Verifies that the current String is not equal to the given one. 14 | func is_not_equal(expected) -> GdUnitStringAssert: 15 | return self 16 | 17 | # Verifies that the current String is not equal to the given one, ignoring case considerations. 18 | func is_not_equal_ignoring_case(expected) -> GdUnitStringAssert: 19 | return self 20 | 21 | # Verifies that the current String is empty, it has a length of 0. 22 | func is_empty() -> GdUnitStringAssert: 23 | return self 24 | 25 | # Verifies that the current String is not empty, it has a length of minimum 1. 26 | func is_not_empty() -> GdUnitStringAssert: 27 | return self 28 | 29 | # Verifies that the current String contains the given String. 30 | func contains(expected: String) -> GdUnitStringAssert: 31 | return self 32 | 33 | # Verifies that the current String does not contain the given String. 34 | func not_contains(expected: String) -> GdUnitStringAssert: 35 | return self 36 | 37 | # Verifies that the current String does not contain the given String, ignoring case considerations. 38 | func contains_ignoring_case(expected: String) -> GdUnitStringAssert: 39 | return self 40 | 41 | # Verifies that the current String does not contain the given String, ignoring case considerations. 42 | func not_contains_ignoring_case(expected: String) -> GdUnitStringAssert: 43 | return self 44 | 45 | # Verifies that the current String starts with the given prefix. 46 | func starts_with(expected: String) -> GdUnitStringAssert: 47 | return self 48 | 49 | # Verifies that the current String ends with the given suffix. 50 | func ends_with(expected: String) -> GdUnitStringAssert: 51 | return self 52 | 53 | # Verifies that the current String has the expected length by used comparator. 54 | func has_length(lenght: int, comparator: int = Comparator.EQUAL) -> GdUnitStringAssert: 55 | return self 56 | -------------------------------------------------------------------------------- /addons/gdUnit3/src/GdUnitTuple.gd: -------------------------------------------------------------------------------- 1 | # A tuple implementation to hold two or many values 2 | class_name GdUnitTuple 3 | extends Reference 4 | 5 | const NO_ARG = GdUnitConstants.NO_ARG 6 | 7 | var __values :Array = Array() 8 | 9 | func _init(arg0, arg1, arg2=NO_ARG, arg3=NO_ARG, arg4=NO_ARG, arg5=NO_ARG, arg6=NO_ARG, arg7=NO_ARG, arg8=NO_ARG, arg9=NO_ARG): 10 | __values = GdObjects.array_filter_value([arg0,arg1,arg2,arg3,arg4,arg5,arg6,arg7,arg8,arg9], NO_ARG) 11 | 12 | func values() -> Array: 13 | return __values 14 | 15 | func _to_string(): 16 | return "tuple(%s)" % str(__values) 17 | -------------------------------------------------------------------------------- /addons/gdUnit3/src/GdUnitValueExtractor.gd: -------------------------------------------------------------------------------- 1 | # This is the base interface for value extraction 2 | class_name GdUnitValueExtractor 3 | extends Reference 4 | 5 | # Extracts a value by given implementation 6 | func extract_value(value): 7 | push_error("Uninplemented func 'extract_value'") 8 | return value 9 | -------------------------------------------------------------------------------- /addons/gdUnit3/src/GdUnitVector2Assert.gd: -------------------------------------------------------------------------------- 1 | class_name GdUnitVector2Assert 2 | extends GdUnitAssert 3 | 4 | # Verifies that the current value is equal to expected one. 5 | func is_equal(expected :Vector2) -> GdUnitVector2Assert: 6 | return self 7 | 8 | # Verifies that the current value is not equal to expected one. 9 | func is_not_equal(expected :Vector2) -> GdUnitVector2Assert: 10 | return self 11 | 12 | # Verifies that the current and expected value are approximately equal. 13 | func is_equal_approx(expected :Vector2, approx :Vector2) -> GdUnitVector2Assert: 14 | return self 15 | 16 | # Verifies that the current value is less than the given one. 17 | func is_less(expected :Vector2) -> GdUnitVector2Assert: 18 | return self 19 | 20 | # Verifies that the current value is less than or equal the given one. 21 | func is_less_equal(expected :Vector2) -> GdUnitVector2Assert: 22 | return self 23 | 24 | # Verifies that the current value is greater than the given one. 25 | func is_greater(expected :Vector2) -> GdUnitVector2Assert: 26 | return self 27 | 28 | # Verifies that the current value is greater than or equal the given one. 29 | func is_greater_equal(expected :Vector2) -> GdUnitVector2Assert: 30 | return self 31 | 32 | # Verifies that the current value is between the given boundaries (inclusive). 33 | func is_between(from :Vector2, to :Vector2) -> GdUnitVector2Assert: 34 | return self 35 | 36 | # Verifies that the current value is not between the given boundaries (inclusive). 37 | func is_not_between(from :Vector2, to :Vector2) -> GdUnitVector2Assert: 38 | return self 39 | -------------------------------------------------------------------------------- /addons/gdUnit3/src/GdUnitVector3Assert.gd: -------------------------------------------------------------------------------- 1 | class_name GdUnitVector3Assert 2 | extends GdUnitAssert 3 | 4 | # Verifies that the current value is equal to expected one. 5 | func is_equal(expected :Vector3) -> GdUnitVector3Assert: 6 | return self 7 | 8 | # Verifies that the current value is not equal to expected one. 9 | func is_not_equal(expected :Vector3) -> GdUnitVector3Assert: 10 | return self 11 | 12 | # Verifies that the current and expected value are approximately equal. 13 | func is_equal_approx(expected :Vector3, approx :Vector3) -> GdUnitVector3Assert: 14 | return self 15 | 16 | # Verifies that the current value is less than the given one. 17 | func is_less(expected :Vector3) -> GdUnitVector3Assert: 18 | return self 19 | 20 | # Verifies that the current value is less than or equal the given one. 21 | func is_less_equal(expected :Vector3) -> GdUnitVector3Assert: 22 | return self 23 | 24 | # Verifies that the current value is greater than the given one. 25 | func is_greater(expected :Vector3) -> GdUnitVector3Assert: 26 | return self 27 | 28 | # Verifies that the current value is greater than or equal the given one. 29 | func is_greater_equal(expected :Vector3) -> GdUnitVector3Assert: 30 | return self 31 | 32 | # Verifies that the current value is between the given boundaries (inclusive). 33 | func is_between(from :Vector3, to :Vector3) -> GdUnitVector3Assert: 34 | return self 35 | 36 | # Verifies that the current value is not between the given boundaries (inclusive). 37 | func is_not_between(from :Vector3, to :Vector3) -> GdUnitVector3Assert: 38 | return self 39 | -------------------------------------------------------------------------------- /addons/gdUnit3/src/asserts/CallBackValueProvider.gd: -------------------------------------------------------------------------------- 1 | # a value provider unsing a callback to get `next` value from a certain function 2 | class_name CallBackValueProvider 3 | extends ValueProvider 4 | 5 | var _fr :FuncRef 6 | var _args :Array 7 | 8 | func _init(instance :Object, func_name :String, args :Array = Array(), force_error :=true): 9 | _fr = funcref(instance, func_name); 10 | _args = args 11 | if force_error and not _fr.is_valid(): 12 | push_error("Can't find function '%s' on instance %s" % [func_name, instance]) 13 | 14 | func get_value(): 15 | if not _fr.is_valid(): 16 | return null 17 | return _fr.call_func() if _args.empty() else _fr.call_funcv(_args) 18 | -------------------------------------------------------------------------------- /addons/gdUnit3/src/asserts/DefaultValueProvider.gd: -------------------------------------------------------------------------------- 1 | # default value provider, simple returns the initial value 2 | class_name DefaultValueProvider 3 | extends ValueProvider 4 | 5 | var _value 6 | 7 | func _init(value): 8 | _value = value 9 | 10 | func get_value(): 11 | return _value 12 | -------------------------------------------------------------------------------- /addons/gdUnit3/src/asserts/GdAssertReports.gd: -------------------------------------------------------------------------------- 1 | class_name GdAssertReports 2 | extends Reference 3 | 4 | const LAST_ERROR = "last_assert_error_message" 5 | const LAST_ERROR_LINE = "last_assert_error_line" 6 | 7 | # if a test success but we expect to fail map to an error 8 | static func report_success(gd_assert :GdUnitAssert) -> GdUnitAssert: 9 | Engine.remove_meta(LAST_ERROR) 10 | if not gd_assert._expect_fail || gd_assert._is_failed: 11 | return gd_assert 12 | var error_msg := GdAssertMessages._error("Expecting to fail!") 13 | gd_assert.send_report(GdUnitReport.new().create(GdUnitReport.SUCCESS, gd_assert._get_line_number(), error_msg)) 14 | return gd_assert 15 | 16 | static func report_warning(gd_assert :GdUnitAssert, message :String, line_number :int) -> GdUnitAssert: 17 | gd_assert.send_report(GdUnitReport.new().create(GdUnitReport.WARN, line_number, message)) 18 | return gd_assert 19 | 20 | static func report_error(message:String, gd_assert :GdUnitAssert, line_number :int) -> GdUnitAssert: 21 | if gd_assert != null: 22 | gd_assert._is_failed = true 23 | gd_assert._current_error_message = message 24 | # use this kind of hack to enable validate error message for expected failure testing 25 | Engine.set_meta(LAST_ERROR, message) 26 | # reset we expect to fail 27 | expect_fail(false) 28 | # if we expect to fail we handle as success test 29 | if gd_assert._expect_fail: 30 | return gd_assert 31 | gd_assert.send_report(GdUnitReport.new().create(GdUnitReport.FAILURE, line_number, message)) 32 | return gd_assert 33 | 34 | static func reset_last_error_line_number() -> void: 35 | Engine.remove_meta(LAST_ERROR_LINE) 36 | 37 | static func set_last_error_line_number(line_number :int) -> void: 38 | Engine.set_meta(LAST_ERROR_LINE, line_number) 39 | 40 | static func get_last_error_line_number() -> int: 41 | if Engine.has_meta(LAST_ERROR_LINE): 42 | return Engine.get_meta(LAST_ERROR_LINE) 43 | return -1 44 | 45 | static func expect_fail(enabled :bool = true): 46 | Engine.set_meta("report_failures", enabled) 47 | 48 | static func is_expect_fail() -> bool: 49 | if Engine.has_meta("report_failures"): 50 | return Engine.get_meta("report_failures") 51 | return false 52 | 53 | static func current_failure() -> String: 54 | return Engine.get_meta(LAST_ERROR) 55 | -------------------------------------------------------------------------------- /addons/gdUnit3/src/asserts/GdUnitBoolAssertImpl.gd: -------------------------------------------------------------------------------- 1 | class_name GdUnitBoolAssertImpl 2 | extends GdUnitBoolAssert 3 | 4 | var _base: GdUnitAssert 5 | 6 | func _init(caller :Object, current, expect_result: int): 7 | _base = GdUnitAssertImpl.new(caller, current, expect_result) 8 | if not _base.__validate_value_type(current, TYPE_BOOL): 9 | report_error("GdUnitBoolAssert inital error, unexpected type <%s>" % GdObjects.typeof_as_string(current)) 10 | 11 | func __current(): 12 | return _base.__current() 13 | 14 | func report_success() -> GdUnitBoolAssert: 15 | _base.report_success() 16 | return self 17 | 18 | func report_error(error :String) -> GdUnitBoolAssert: 19 | _base.report_error(error) 20 | return self 21 | 22 | # -------- Base Assert wrapping ------------------------------------------------ 23 | func has_failure_message(expected: String) -> GdUnitBoolAssert: 24 | _base.has_failure_message(expected) 25 | return self 26 | 27 | func starts_with_failure_message(expected: String) -> GdUnitBoolAssert: 28 | _base.starts_with_failure_message(expected) 29 | return self 30 | 31 | func override_failure_message(message :String) -> GdUnitBoolAssert: 32 | _base.override_failure_message(message) 33 | return self 34 | 35 | func _notification(event): 36 | if event == NOTIFICATION_PREDELETE: 37 | if _base != null: 38 | _base.notification(event) 39 | _base = null 40 | #------------------------------------------------------------------------------- 41 | # Verifies that the current value is null. 42 | func is_null() -> GdUnitBoolAssert: 43 | _base.is_null() 44 | return self 45 | 46 | # Verifies that the current value is not null. 47 | func is_not_null() -> GdUnitBoolAssert: 48 | _base.is_not_null() 49 | return self 50 | 51 | func is_equal(expected) -> GdUnitBoolAssert: 52 | _base.is_equal(expected) 53 | return self 54 | 55 | func is_not_equal(expected) -> GdUnitBoolAssert: 56 | _base.is_not_equal(expected) 57 | return self 58 | 59 | func is_true() -> GdUnitBoolAssert: 60 | if __current() != true: 61 | return report_error(GdAssertMessages.error_is_true(__current())) 62 | return report_success() 63 | 64 | func is_false() -> GdUnitBoolAssert: 65 | if __current() == true || __current() == null: 66 | return report_error(GdAssertMessages.error_is_false(__current())) 67 | return report_success() 68 | -------------------------------------------------------------------------------- /addons/gdUnit3/src/asserts/GdUnitFileAssertImpl.gd: -------------------------------------------------------------------------------- 1 | class_name GdUnitFileAssertImpl 2 | extends GdUnitFileAssert 3 | 4 | var _base: GdUnitAssert 5 | var _caller :Object 6 | 7 | func _init(caller :Object, current, expect_result: int): 8 | _caller = caller 9 | _base = GdUnitAssertImpl.new(caller, current, expect_result) 10 | if not _base.__validate_value_type(current, TYPE_STRING): 11 | report_error("GdUnitFileAssert inital error, unexpected type <%s>" % GdObjects.typeof_as_string(current)) 12 | 13 | func __current() -> String: 14 | return _base.__current() as String 15 | 16 | func report_success() -> GdUnitFileAssert: 17 | _base.report_success() 18 | return self 19 | 20 | func report_error(error :String) -> GdUnitFileAssert: 21 | _base.report_error(error) 22 | return self 23 | 24 | # -------- Base Assert wrapping ------------------------------------------------ 25 | func has_failure_message(expected: String) -> GdUnitFileAssert: 26 | _base.has_failure_message(expected) 27 | return self 28 | 29 | func starts_with_failure_message(expected: String) -> GdUnitFileAssert: 30 | _base.starts_with_failure_message(expected) 31 | return self 32 | 33 | func override_failure_message(message :String) -> GdUnitFileAssert: 34 | _base.override_failure_message(message) 35 | return self 36 | 37 | func _notification(event): 38 | if event == NOTIFICATION_PREDELETE: 39 | if _base != null: 40 | _base.notification(event) 41 | _base = null 42 | #------------------------------------------------------------------------------- 43 | 44 | func is_equal(expected) -> GdUnitFileAssert: 45 | _base.is_equal(expected) 46 | return self 47 | 48 | func is_not_equal(expected) -> GdUnitFileAssert: 49 | _base.is_not_equal(expected) 50 | return self 51 | 52 | func is_file() -> GdUnitFileAssert: 53 | var current := __current() 54 | var file := File.new() 55 | var ret = file.open(current, File.READ) 56 | if ret != OK: 57 | return report_error("Is not a file '%s', error code %s" % [current, ret]) 58 | return report_success() 59 | 60 | func exists() -> GdUnitFileAssert: 61 | var current := __current() 62 | var file := File.new() 63 | if not file.file_exists(current): 64 | return report_error("The file '%s' not exists" %current) 65 | return report_success() 66 | 67 | func is_script() -> GdUnitFileAssert: 68 | var current := __current() 69 | var file := File.new() 70 | var ret = file.open(current, File.READ) 71 | if ret != OK: 72 | return report_error("Can't acces the file '%s'! Error code %s" % [current, ret]) 73 | 74 | var script = load(current) 75 | if not script is GDScript: 76 | return report_error("The file '%s' is not a GdScript" % current) 77 | return report_success() 78 | 79 | func contains_exactly(expected_rows :Array) -> GdUnitFileAssert: 80 | var current := __current() 81 | var file := File.new() 82 | var ret = file.open(current, File.READ) 83 | if ret != OK: 84 | return report_error("Can't acces the file '%s'! Error code %s" % [current, ret]) 85 | 86 | var script = load(current) 87 | if script is GDScript: 88 | var instance = script.new() 89 | var source_code = GdScriptParser.to_unix_format(instance.get_script().source_code) 90 | GdUnitTools.free_instance(instance) 91 | var rows := Array(source_code.split("\n")) 92 | GdUnitArrayAssertImpl.new(_caller, rows, GdUnitAssert.EXPECT_SUCCESS).contains_exactly(expected_rows) 93 | return self 94 | -------------------------------------------------------------------------------- /addons/gdUnit3/src/asserts/ValueProvider.gd: -------------------------------------------------------------------------------- 1 | # base interface for assert value provider 2 | class_name ValueProvider 3 | extends Reference 4 | 5 | func get_value(): 6 | pass 7 | -------------------------------------------------------------------------------- /addons/gdUnit3/src/cmd/CmdArgumentParser.gd: -------------------------------------------------------------------------------- 1 | class_name CmdArgumentParser 2 | extends Reference 3 | 4 | var _options :CmdOptions 5 | var _tool_name :String 6 | var _parsed_commands :Dictionary = Dictionary() 7 | 8 | func _init(options :CmdOptions, tool_name :String): 9 | _options = options 10 | _tool_name = tool_name 11 | 12 | func parse(args :Array, ignore_unknown_cmd := false) -> Result: 13 | _parsed_commands.clear() 14 | 15 | # parse until first program argument 16 | while not args.empty(): 17 | var arg :String = args.pop_front() 18 | if arg.find(_tool_name) != -1: 19 | break 20 | 21 | if args.empty(): 22 | return Result.empty() 23 | 24 | # now parse all arguments 25 | while not args.empty(): 26 | var cmd :String = args.pop_front() 27 | var option := _options.get_option(cmd) 28 | 29 | if option: 30 | if _parse_cmd_arguments(option, args) == -1: 31 | return Result.error("The '%s' command requires an argument!" % option.short_command()) 32 | elif not ignore_unknown_cmd: 33 | return Result.error("Unknown '%s' command!" % cmd) 34 | return Result.success(_parsed_commands.values()) 35 | 36 | func options() -> CmdOptions: 37 | return _options 38 | 39 | func _parse_cmd_arguments(option :CmdOption, args :Array) -> int: 40 | var command_name := option.short_command() 41 | var command :CmdCommand = _parsed_commands.get(command_name, CmdCommand.new(command_name)) 42 | 43 | if option.has_argument(): 44 | if not option.is_argument_optional() and args.empty(): 45 | return -1 46 | if _is_next_value_argument(args): 47 | command.add_argument(args.pop_front()) 48 | elif not option.is_argument_optional(): 49 | return -1 50 | _parsed_commands[command_name] = command 51 | return 0 52 | 53 | func _is_next_value_argument(args :Array) -> bool: 54 | if args.empty(): 55 | return false 56 | return _options.get_option(args[0]) == null 57 | -------------------------------------------------------------------------------- /addons/gdUnit3/src/cmd/CmdCommand.gd: -------------------------------------------------------------------------------- 1 | class_name CmdCommand 2 | extends Reference 3 | 4 | var _name :String 5 | var _arguments :PoolStringArray 6 | 7 | func _init(name :String, arguments := PoolStringArray()): 8 | _name = name 9 | _arguments = arguments 10 | 11 | func name() -> String: 12 | return _name 13 | 14 | func arguments() -> PoolStringArray: 15 | return _arguments 16 | 17 | func add_argument(arg :String) -> void: 18 | _arguments.append(arg) 19 | 20 | func _to_string(): 21 | return "%s:%s" % [_name, _arguments.join(", ")] 22 | -------------------------------------------------------------------------------- /addons/gdUnit3/src/cmd/CmdOption.gd: -------------------------------------------------------------------------------- 1 | class_name CmdOption 2 | extends Reference 3 | 4 | 5 | var _commands :PoolStringArray 6 | var _help :String 7 | var _description :String 8 | var _type :int 9 | var _arg_optional :bool = false 10 | 11 | # constructs a command option by given arguments 12 | # commands : a string with comma separated list of available commands begining with the short form 13 | # help: a help text show howto use 14 | # description: a full description of the command 15 | # type: the argument type 16 | # arg_optional: defines of the argument optional 17 | func _init(commands :String, help :String, description :String, type :int = TYPE_NIL, arg_optional :bool = false): 18 | _commands = commands.replace(" ", "").replace("\t", "").split(",") 19 | _help = help 20 | _description = description 21 | _type = type 22 | _arg_optional = arg_optional 23 | 24 | func commands() -> PoolStringArray: 25 | return _commands 26 | 27 | func short_command() -> String: 28 | return _commands[0] 29 | 30 | func help() -> String: 31 | return _help 32 | 33 | func description() -> String: 34 | return _description 35 | 36 | func type() -> int: 37 | return _type 38 | 39 | func is_argument_optional() -> bool: 40 | return _arg_optional 41 | 42 | func has_argument() -> bool: 43 | return _type != TYPE_NIL 44 | 45 | func describe() -> String: 46 | if help().empty(): 47 | return " %-32s %s \n" % [commands(), description()] 48 | return " %-32s %s \n %-32s %s\n" % [commands(), description(), "", help()] 49 | 50 | func _to_string(): 51 | return describe() 52 | -------------------------------------------------------------------------------- /addons/gdUnit3/src/cmd/CmdOptions.gd: -------------------------------------------------------------------------------- 1 | class_name CmdOptions 2 | extends Reference 3 | 4 | 5 | var _default_options :Array 6 | var _advanced_options :Array 7 | 8 | 9 | func _init(options :Array = Array(), advanced_options :Array = Array()): 10 | # default help options 11 | _default_options = options 12 | _advanced_options = advanced_options 13 | 14 | func default_options() -> Array: 15 | return _default_options 16 | 17 | func advanced_options() -> Array: 18 | return _advanced_options 19 | 20 | func options() -> Array: 21 | return default_options() + advanced_options() 22 | 23 | func get_option(cmd :String) -> CmdOption: 24 | for option in options(): 25 | if Array(option.commands()).has(cmd): 26 | return option 27 | return null 28 | -------------------------------------------------------------------------------- /addons/gdUnit3/src/core/GdFunctionDoubler.gd: -------------------------------------------------------------------------------- 1 | class_name GdFunctionDoubler 2 | extends Reference 3 | 4 | const DEFAULT_TYPED_RETURN_VALUES := { 5 | TYPE_NIL: "null", 6 | TYPE_BOOL: "false", 7 | TYPE_INT: "0", 8 | TYPE_REAL: "0.0", 9 | TYPE_STRING: "\"\"", 10 | TYPE_VECTOR2: "Vector2.ZERO", 11 | TYPE_RECT2: "Rect2()", 12 | TYPE_VECTOR3: "Vector3.ZERO", 13 | TYPE_TRANSFORM2D: "Transform2D()", 14 | TYPE_PLANE: "Plane()", 15 | TYPE_QUAT: "Quat()", 16 | TYPE_AABB: "AABB()", 17 | TYPE_BASIS: "Basis()", 18 | TYPE_TRANSFORM: "Transform()", 19 | TYPE_COLOR: "Color()", 20 | TYPE_NODE_PATH: "NodePath()", 21 | TYPE_RID: "RID()", 22 | TYPE_OBJECT: "null", 23 | TYPE_DICTIONARY: "Dictionary()", 24 | TYPE_ARRAY: "Array()", 25 | TYPE_RAW_ARRAY: "PoolByteArray()", 26 | TYPE_INT_ARRAY: "PoolIntArray()", 27 | TYPE_REAL_ARRAY: "PoolRealArray()", 28 | TYPE_STRING_ARRAY: "PoolStringArray()", 29 | TYPE_VECTOR2_ARRAY: "PoolVector2Array()", 30 | TYPE_VECTOR3_ARRAY: "PoolVector3Array()", 31 | TYPE_COLOR_ARRAY: "PoolColorArray()", 32 | } 33 | 34 | static func return_value(type :int) -> String: 35 | if DEFAULT_TYPED_RETURN_VALUES.has(type): 36 | return DEFAULT_TYPED_RETURN_VALUES.get(type) 37 | return "void" 38 | 39 | func double(func_descriptor :GdFunctionDescriptor) -> PoolStringArray: 40 | push_error("FunctionDoubler#double() is not implemented!") 41 | return PoolStringArray() 42 | 43 | func extract_arg_names(argument_signatures :Array) -> PoolStringArray: 44 | var arg_names := PoolStringArray() 45 | for arg in argument_signatures: 46 | arg_names.append(arg._name) 47 | return arg_names 48 | 49 | static func extract_constructor_args(args :Array) -> PoolStringArray: 50 | var constructor_args := PoolStringArray() 51 | for arg in args: 52 | var a := arg as GdFunctionArgument 53 | var arg_name := a._name 54 | var default_value = get_default(a) 55 | constructor_args.append(arg_name + "=" + default_value) 56 | return constructor_args 57 | 58 | static func get_default(arg :GdFunctionArgument): 59 | if arg.default() != GdFunctionArgument.UNDEFINED: 60 | return arg.default() 61 | else: 62 | var arg_type := GdObjects.string_to_type(arg._type) 63 | return return_value(arg_type) 64 | -------------------------------------------------------------------------------- /addons/gdUnit3/src/core/GdUnit3Version.gd: -------------------------------------------------------------------------------- 1 | class_name GdUnit3Version 2 | extends Reference 3 | 4 | const VERSION_PATTERN = "[center][color=#9887c4]gd[/color][color=#7a57d6]Unit[/color][color=#9887c4]3[/color] [color=#9887c4]${version}[/color][/center]" 5 | 6 | var _major :int 7 | var _minor :int 8 | var _patch :int 9 | 10 | func _init(major :int, minor :int, patch :int): 11 | _major = major 12 | _minor = minor 13 | _patch = patch 14 | 15 | static func parse(value :String) -> GdUnit3Version: 16 | var regex := RegEx.new() 17 | regex.compile("[a-zA-Z:,-]+") 18 | var cleaned := regex.sub(value, "", true) 19 | var parts := cleaned.split(".") 20 | var major := int(parts[0]) 21 | var minor := int(parts[1]) 22 | var patch := int(parts[2]) if parts.size() > 2 else 0 23 | return load("res://addons/gdUnit3/src/core/GdUnit3Version.gd").new(major, minor, patch) 24 | 25 | static func current() -> GdUnit3Version: 26 | var config = ConfigFile.new() 27 | config.load('addons/gdUnit3/plugin.cfg') 28 | return parse(config.get_value('plugin', 'version')) 29 | 30 | func equals(other :GdUnit3Version) -> bool: 31 | return _major == other._major and _minor == other._minor and _patch == other._patch 32 | 33 | func is_greater(other :GdUnit3Version) -> bool: 34 | if _major > other._major: 35 | return true 36 | if _major == other._major and _minor > other._minor: 37 | return true 38 | return _major == other._major and _minor == other._minor and _patch > other._patch 39 | 40 | static func init_version_label(label :Control) -> void: 41 | var config = ConfigFile.new() 42 | config.load('addons/gdUnit3/plugin.cfg') 43 | var version = config.get_value('plugin', 'version') 44 | if label is RichTextLabel: 45 | label.bbcode_text = VERSION_PATTERN.replace('${version}', version) 46 | else: 47 | label.text = "gdUnit3 " + version 48 | 49 | func _to_string() -> String: 50 | return "v%d.%d.%d" % [_major, _minor, _patch] 51 | -------------------------------------------------------------------------------- /addons/gdUnit3/src/core/GdUnitMemoryPool.gd: -------------------------------------------------------------------------------- 1 | class_name GdUnitMemoryPool 2 | extends Node 3 | 4 | const META_PARAM := "MEMORY_POOL" 5 | 6 | enum { 7 | SUITE_SETUP, 8 | TEST_SETUP, 9 | TEST_EXECUTE, 10 | } 11 | 12 | var _monitors := { 13 | SUITE_SETUP : GdUnitMemMonitor.new("SUITE_SETUP"), 14 | TEST_SETUP : GdUnitMemMonitor.new("TEST_SETUP"), 15 | TEST_EXECUTE : GdUnitMemMonitor.new("TEST_EXECUTE"), 16 | } 17 | 18 | var _monitored_pool_order := Array() 19 | var _current :int 20 | var _orphan_detection_enabled :bool = true 21 | 22 | func _init(): 23 | set_name("GdUnitMemoryPool-%d" % get_instance_id()) 24 | configure(GdUnitSettings.is_verbose_orphans()) 25 | 26 | func configure(orphan_detection :bool): 27 | _orphan_detection_enabled = orphan_detection 28 | if not _orphan_detection_enabled: 29 | prints("!!! Reporting orphan nodes is disabled. Please check GdUnit settings.") 30 | 31 | func set_pool(obj :Object, pool_id :int, reset_monitor: bool = false) -> void: 32 | _current = pool_id 33 | obj.set_meta(META_PARAM, pool_id) 34 | var monitor := get_monitor(_current) 35 | if reset_monitor: 36 | monitor.reset() 37 | monitor.start() 38 | 39 | func monitor_stop() -> void: 40 | var monitor := get_monitor(_current) 41 | monitor.stop() 42 | 43 | func free_pool() -> void: 44 | GdUnitTools.run_auto_free(_current) 45 | 46 | func get_monitor(pool_id :int) -> GdUnitMemMonitor: 47 | return _monitors.get(pool_id) 48 | 49 | func orphan_nodes() -> int: 50 | if _orphan_detection_enabled: 51 | return _monitors.get(_current).orphan_nodes() 52 | return 0 53 | -------------------------------------------------------------------------------- /addons/gdUnit3/src/core/GdUnitObjectInteractions.gd: -------------------------------------------------------------------------------- 1 | class_name GdUnitObjectInteractions 2 | extends Reference 3 | 4 | static func verify(obj :Object, times, expect_result :int): 5 | if not _is_mock_or_spy( obj, "__verify"): 6 | return obj 7 | return obj.__do_verify_interactions(times, expect_result) 8 | 9 | static func verify_no_interactions(caller :Object, obj :Object, expect_result :int) -> GdUnitAssert: 10 | var gd_assert := GdUnitAssertImpl.new(caller, "", expect_result) 11 | if not _is_mock_or_spy( obj, "__verify"): 12 | return gd_assert.report_success() 13 | var summary :Dictionary = obj.__verify_no_interactions() 14 | if summary.empty(): 15 | return gd_assert.report_success() 16 | return gd_assert.report_error(GdAssertMessages.error_no_more_interactions(summary)) 17 | 18 | static func verify_no_more_interactions(caller :Object, obj :Object, expect_result :int) -> GdUnitAssert: 19 | var gd_assert := GdUnitAssertImpl.new(caller, "", expect_result) 20 | if not _is_mock_or_spy( obj, "__verify_no_more_interactions"): 21 | return gd_assert 22 | var summary :Dictionary = obj.__verify_no_more_interactions() 23 | if summary.empty(): 24 | return gd_assert 25 | return gd_assert.report_error(GdAssertMessages.error_no_more_interactions(summary)) 26 | 27 | static func reset(obj :Object) -> Object: 28 | if not _is_mock_or_spy( obj, "__reset"): 29 | return obj 30 | obj.__reset_interactions() 31 | return obj 32 | 33 | static func _is_mock_or_spy(obj :Object, func_sig :String) -> bool: 34 | if obj is GDScript and not obj.get_script().has_script_method(func_sig): 35 | push_error("Error: You try to use a non mock or spy!") 36 | return false 37 | return true 38 | -------------------------------------------------------------------------------- /addons/gdUnit3/src/core/GdUnitObjectInteractionsTemplate.gd: -------------------------------------------------------------------------------- 1 | class_name GdUnitObjectInteractionsTemplate 2 | 3 | var __expected_interactions :int = -1 4 | var __expect_result :int 5 | var __saved_interactions := Dictionary() 6 | var __verified_interactions := Array() 7 | var __caller :Object 8 | 9 | func __save_function_interaction(args :Array) -> void: 10 | var matcher := GdUnitArgumentMatchers.to_matcher(args, true) 11 | for key in __saved_interactions.keys(): 12 | if matcher.is_match(key): 13 | __saved_interactions[key] += 1 14 | return 15 | __saved_interactions[args] = 1 16 | 17 | func __is_verify_interactions() -> bool: 18 | return __expected_interactions != -1 19 | 20 | func __do_verify_interactions(times :int = 1, expect_result :int = GdUnitAssert.EXPECT_SUCCESS) -> Object: 21 | __expected_interactions = times 22 | __expect_result = expect_result 23 | return self 24 | 25 | func __verify_interactions(args :Array): 26 | var summary := Dictionary() 27 | var total_interactions := 0 28 | var matcher := GdUnitArgumentMatchers.to_matcher(args, true) 29 | for key in __saved_interactions.keys(): 30 | if matcher.is_match(key): 31 | var interactions :int = __saved_interactions.get(key, 0) 32 | total_interactions += interactions 33 | summary[key] = interactions 34 | # add as verified 35 | __verified_interactions.append(key) 36 | 37 | var gd_assert := GdUnitAssertImpl.new(__caller, "", __expect_result) 38 | if total_interactions != __expected_interactions: 39 | var expected_summary = {args : __expected_interactions} 40 | var error_message :String 41 | # if no interactions macht collect not verified interactions for failure report 42 | if summary.empty(): 43 | var current_summary = __verify_no_more_interactions() 44 | error_message = GdAssertMessages.error_validate_interactions(current_summary, expected_summary) 45 | else: 46 | error_message = GdAssertMessages.error_validate_interactions(summary, expected_summary) 47 | gd_assert.report_error(error_message) 48 | else: 49 | gd_assert.report_success() 50 | __expected_interactions = -1 51 | 52 | func __verify_no_interactions() -> Dictionary: 53 | var summary := Dictionary() 54 | if not __saved_interactions.empty(): 55 | for func_call in __saved_interactions.keys(): 56 | summary[func_call] = __saved_interactions[func_call] 57 | return summary 58 | 59 | func __verify_no_more_interactions() -> Dictionary: 60 | var summary := Dictionary() 61 | var called_functions :Array = __saved_interactions.keys() 62 | if called_functions != __verified_interactions: 63 | # collect the not verified functions 64 | var called_but_not_verified := called_functions.duplicate() 65 | for verified_function in __verified_interactions: 66 | called_but_not_verified.erase(verified_function) 67 | 68 | for not_verified in called_but_not_verified: 69 | summary[not_verified] = __saved_interactions[not_verified] 70 | return summary 71 | 72 | func __reset_interactions() -> void: 73 | __saved_interactions.clear() 74 | 75 | func __filter_vargs(arg_values :Array) -> Array: 76 | var filtered := Array() 77 | for arg in arg_values: 78 | if typeof(arg) == TYPE_STRING and arg == GdObjects.TYPE_VARARG_PLACEHOLDER_VALUE: 79 | continue 80 | filtered.append(arg) 81 | return filtered 82 | 83 | func __set_caller(caller :Object) -> void: 84 | __caller = caller 85 | -------------------------------------------------------------------------------- /addons/gdUnit3/src/core/GdUnitProperty.gd: -------------------------------------------------------------------------------- 1 | class_name GdUnitProperty 2 | extends Reference 3 | 4 | var _name :String 5 | var _help :String 6 | var _type :int 7 | var _value 8 | var _value_set :PoolStringArray 9 | var _default 10 | 11 | func _init(name :String, type :int, value, default_value, help :="", value_set := PoolStringArray() ): 12 | _name = name 13 | _type = type 14 | _value = value 15 | _value_set = value_set 16 | _default = default_value 17 | _help = help 18 | 19 | func name() -> String: 20 | return _name 21 | 22 | func type() -> int: 23 | return _type 24 | 25 | func value(): 26 | return _value 27 | 28 | func value_set() -> PoolStringArray: 29 | return _value_set 30 | 31 | func is_selectable_value() -> bool: 32 | return not _value_set.empty() 33 | 34 | func set_value(value) -> void: 35 | match _type: 36 | TYPE_STRING: 37 | _value = str(value) 38 | TYPE_BOOL: 39 | _value = bool(value) 40 | TYPE_INT: 41 | _value = int(value) 42 | TYPE_REAL: 43 | _value = float(value) 44 | 45 | func default(): 46 | return _default 47 | 48 | func category() -> String: 49 | var elements := _name.split("/") 50 | if elements.size() > 3: 51 | return elements[2] 52 | return "" 53 | 54 | func help() -> String: 55 | return _help 56 | 57 | func _to_string() -> String: 58 | return "%-64s %-10s %-10s (%s) help:%s set:%s" % [name(), type(), value(), default(), help(), _value_set] 59 | -------------------------------------------------------------------------------- /addons/gdUnit3/src/core/GdUnitRunner.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=4 format=2] 2 | 3 | [ext_resource path="res://addons/gdUnit3/src/core/GdUnitRunner.gd" type="Script" id=1] 4 | [ext_resource path="res://addons/gdUnit3/src/network/GdUnitTcpClient.gd" type="Script" id=2] 5 | [ext_resource path="res://addons/gdUnit3/src/core/GdUnitExecutor.gd" type="Script" id=3] 6 | 7 | [node name="Control" type="Node"] 8 | script = ExtResource( 1 ) 9 | 10 | [node name="GdUnitExecutor" type="Node" parent="."] 11 | script = ExtResource( 3 ) 12 | 13 | [node name="GdUnitTcpClient" type="Node" parent="."] 14 | script = ExtResource( 2 ) 15 | 16 | [connection signal="send_event" from="GdUnitExecutor" to="." method="_on_Executor_send_event"] 17 | -------------------------------------------------------------------------------- /addons/gdUnit3/src/core/GdUnitScriptType.gd: -------------------------------------------------------------------------------- 1 | class_name GdUnitScriptType 2 | extends Reference 3 | 4 | const UNKNOWN := "" 5 | const CS := "cs" 6 | const GD := "gd" 7 | const NATIVE := "gdns" 8 | const VS := "vs" 9 | 10 | static func type_of(script :Script) -> String: 11 | if script == null: 12 | return UNKNOWN 13 | if GdObjects.is_gd_script(script): 14 | return GD 15 | if GdObjects.is_vs_script(script): 16 | return VS 17 | if GdObjects.is_native_script(script): 18 | return NATIVE 19 | if GdObjects.is_cs_script(script): 20 | return CS 21 | return UNKNOWN 22 | -------------------------------------------------------------------------------- /addons/gdUnit3/src/core/GdUnitSignalAwaiter.gd: -------------------------------------------------------------------------------- 1 | class_name GdUnitSignalAwaiter 2 | extends Reference 3 | 4 | const NO_ARG = GdUnitConstants.NO_ARG 5 | 6 | signal signal_emitted(action) 7 | 8 | var TIMER_AWAKE = Reference.new() 9 | var TIMER_INTERRUPTED = Reference.new() 10 | var _wait_on_idle_frame = false 11 | var _interrupted := false 12 | var _time_left := 0 13 | var _timeout_millis 14 | 15 | func _init(timeout_millis :int, wait_on_idle_frame := false): 16 | _timeout_millis = timeout_millis 17 | _wait_on_idle_frame = wait_on_idle_frame 18 | 19 | func _on_sleep_awakening(): 20 | call_deferred("emit_signal", "signal_emitted", TIMER_AWAKE) 21 | 22 | func _on_timeout(): 23 | call_deferred("emit_signal", "signal_emitted", TIMER_INTERRUPTED) 24 | 25 | 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): 26 | var signal_args = GdObjects.array_filter_value([arg0,arg1,arg2,arg3,arg4,arg5,arg6,arg7,arg8,arg9], NO_ARG) 27 | call_deferred("emit_signal", "signal_emitted", signal_args) 28 | 29 | func is_interrupted() -> bool: 30 | return _interrupted 31 | 32 | func elapsed_time() -> int: 33 | return _time_left 34 | 35 | func on_signal(source :Object, signal_name :String, expected_signal_args :Array): 36 | # register on signal to wait for 37 | source.connect(signal_name, self, "_on_signal_emmited") 38 | # install timeout timer 39 | var timer = Timer.new() 40 | Engine.get_main_loop().root.add_child(timer) 41 | timer.set_one_shot(true) 42 | timer.connect("timeout", self, "_on_timeout") 43 | timer.start(_timeout_millis * 0.001 * Engine.get_time_scale()) 44 | # install sleep timer with a time of 50ms between the signal received checks 45 | # the sleep timer is need to give engine main loop time to process 46 | # if _wait_on_idle_frame set than we skip wait time and wait instead for next idle frame 47 | var sleep_time = 0.0001 if _wait_on_idle_frame else 0.05 48 | var sleep := Timer.new() 49 | Engine.get_main_loop().root.add_child(sleep) 50 | sleep.connect("timeout", self, "_on_sleep_awakening") 51 | sleep.start(sleep_time) 52 | 53 | # holds the emited value 54 | var value 55 | # wait for signal is emitted or a timeout is happen 56 | while true: 57 | if _wait_on_idle_frame: 58 | yield(Engine.get_main_loop(), "idle_frame") 59 | value = yield(self, "signal_emitted") 60 | if value is Reference and value == TIMER_INTERRUPTED: 61 | _interrupted = true 62 | break 63 | if value is Reference and value == TIMER_AWAKE: 64 | continue 65 | if not (value is Array): 66 | value = [value] 67 | if expected_signal_args.size() == 0 or GdObjects.equals(value, expected_signal_args): 68 | break 69 | 70 | # stop/cleanup timers 71 | _time_left = timer.time_left 72 | timer.stop() 73 | Engine.get_main_loop().root.remove_child(timer) 74 | timer.free() 75 | sleep.stop() 76 | Engine.get_main_loop().root.remove_child(sleep) 77 | sleep.free() 78 | if value is Array and value.size() == 1: 79 | return value[0] 80 | return value 81 | -------------------------------------------------------------------------------- /addons/gdUnit3/src/core/GdUnitSingleton.gd: -------------------------------------------------------------------------------- 1 | ################################################################################ 2 | # Provides access to a global accessible singleton 3 | # 4 | # This is a workarount to the existing auto load singleton because of some bugs 5 | # around plugin handling 6 | ################################################################################ 7 | class_name GdUnitSingleton 8 | extends Resource 9 | 10 | const _singletons :Dictionary = Dictionary() 11 | 12 | static func get_singleton(name: String) -> Object: 13 | if _singletons.has(name): 14 | return _singletons[name] 15 | push_error("No singleton instance with '" + name + "' found.") 16 | return null 17 | 18 | static func add_singleton(name: String, path: String) -> Object: 19 | var singleton:Object = load(path).new() 20 | if singleton.has_method("set_name"): 21 | singleton.set_name(name) 22 | _singletons[name] = singleton 23 | #print_debug("Added singleton ", name, " ",singleton) 24 | return singleton 25 | 26 | static func get_or_create_singleton(name: String, path: String) -> Object: 27 | if _singletons.has(name): 28 | return _singletons[name] 29 | return add_singleton(name, path) 30 | 31 | static func remove_singleton(name: String) -> void: 32 | if _singletons.has(name): 33 | #print_debug("Remove singleton '" + name + "'") 34 | _singletons.erase(name) 35 | return 36 | push_error("Remove singleton '" + name + "' failed. No global instance found.") 37 | -------------------------------------------------------------------------------- /addons/gdUnit3/src/core/GdUnitTestSuiteBuilder.gd: -------------------------------------------------------------------------------- 1 | class_name GdUnitTestSuiteBuilder 2 | extends Resource 3 | 4 | #https://github.com/godotengine/godot/blob/3.2/editor/plugins/script_editor_plugin.h 5 | enum EDITOR_ACTIONS { 6 | FILE_NEW, 7 | FILE_NEW_TEXTFILE, 8 | FILE_OPEN, 9 | FILE_REOPEN_CLOSED, 10 | FILE_OPEN_RECENT, 11 | FILE_SAVE, 12 | FILE_SAVE_AS, 13 | FILE_SAVE_ALL, 14 | FILE_THEME, 15 | FILE_RUN, 16 | FILE_CLOSE, 17 | CLOSE_DOCS 18 | } 19 | 20 | 21 | func create(source :Script, line_number :int) -> Result: 22 | var test_suite_path := _TestSuiteScanner.resolve_test_suite_path(source.resource_path, GdUnitSettings.test_root_folder()) 23 | _save_and_close_script(test_suite_path) 24 | 25 | if GdObjects.is_cs_script(source): 26 | return GdUnit3MonoAPI.create_test_suite(source.resource_path, line_number+1, test_suite_path) 27 | 28 | var parser := GdScriptParser.new() 29 | var lines := source.source_code.split("\n") 30 | var current_line := lines[line_number] 31 | var func_name := parser.parse_func_name(current_line) 32 | if func_name.empty(): 33 | return Result.error("No function found at line: %d." % line_number) 34 | return _TestSuiteScanner.create_test_case(test_suite_path, func_name, source.resource_path) 35 | 36 | 37 | static func _save_and_close_script(script_path :String): 38 | if not Engine.has_meta("GdUnitEditorPlugin"): 39 | return 40 | var plugin :EditorPlugin = Engine.get_meta("GdUnitEditorPlugin") 41 | var script_editor :ScriptEditor = plugin.get_editor_interface().get_script_editor() 42 | # is already opened? 43 | var open_file_index = 0 44 | for open_script in script_editor.get_open_scripts(): 45 | if open_script.resource_path == script_path: 46 | # select the script in the editor 47 | script_editor._script_selected(open_file_index) 48 | 49 | # seams to be the save is async and collidates with external changes 50 | # https://github.com/godotengine/godot/issues/50163 51 | if not script_path.ends_with(".cs"): 52 | # needs to be saved first to store current editor changes 53 | script_editor._menu_option(EDITOR_ACTIONS.FILE_SAVE) 54 | # finally needs to be closed to can modify and reload 55 | script_editor._menu_option(EDITOR_ACTIONS.FILE_CLOSE) 56 | return 57 | open_file_index += 1 58 | -------------------------------------------------------------------------------- /addons/gdUnit3/src/core/LocalTime.gd: -------------------------------------------------------------------------------- 1 | # This class provides Date/Time functionallity to Godot 2 | class_name LocalTime 3 | extends Resource 4 | 5 | const SCRIPT_PATH = "res://addons/gdUnit3/src/core/LocalTime.gd" 6 | 7 | const SECONDS_PER_MINUTE:int = 60 8 | const MINUTES_PER_HOUR:int = 60 9 | const HOURS_PER_DAY:int = 24 10 | const MILLIS_PER_SECOND:int = 1000 11 | const MILLIS_PER_MINUTE:int = MILLIS_PER_SECOND * SECONDS_PER_MINUTE 12 | const MILLIS_PER_HOUR:int = MILLIS_PER_MINUTE * MINUTES_PER_HOUR 13 | 14 | var _time:int 15 | var _hour:int 16 | var _minute:int 17 | var _second:int 18 | var _millisecond:int 19 | 20 | 21 | static func _create(time_ms:int): 22 | var _instance = load(SCRIPT_PATH) 23 | return _instance.new(time_ms) 24 | 25 | static func now() -> LocalTime: 26 | var time := OS.get_system_time_msecs() 27 | return _create(time) 28 | #return local_time(time.get("hour"), time.get("minute"), time.get("second"), 0) 29 | 30 | static func of_unix_time(time_ms:int) -> LocalTime: 31 | return _create(time_ms) 32 | 33 | static func local_time(hours:int, minutes:int, seconds:int, millis:int) -> LocalTime: 34 | return _create(MILLIS_PER_HOUR * hours\ 35 | + MILLIS_PER_MINUTE * minutes\ 36 | + MILLIS_PER_SECOND * seconds\ 37 | + millis) 38 | 39 | func elapsed_since() -> String: 40 | return elapsed(OS.get_system_time_msecs() - _time) 41 | 42 | func elapsed_since_ms() -> int: 43 | return OS.get_system_time_msecs() - _time 44 | 45 | func plus( time_unit, value:int) -> LocalTime: 46 | var addValue:int = 0 47 | match time_unit: 48 | TimeUnit.MILLIS: 49 | addValue = value 50 | TimeUnit.SECOND: 51 | addValue = value * MILLIS_PER_SECOND 52 | TimeUnit.MINUTE: 53 | addValue = value * MILLIS_PER_MINUTE 54 | TimeUnit.HOUR: 55 | addValue = value * MILLIS_PER_HOUR 56 | 57 | _init(_time + addValue) 58 | return self 59 | 60 | static func elapsed(time_ms:int) -> String: 61 | var local_time = _create(time_ms) 62 | if local_time._hour > 0: 63 | return "%dh %dmin %ds %dms" % [local_time._hour, local_time._minute, local_time._second, local_time._millisecond] 64 | if local_time._minute > 0: 65 | return "%dmin %ds %dms" % [local_time._minute, local_time._second, local_time._millisecond] 66 | if local_time._second > 0: 67 | return "%ds %dms" % [local_time._second, local_time._millisecond] 68 | return "%dms" % local_time._millisecond 69 | 70 | # create from epoch timestamp in ms 71 | func _init(time:int): 72 | _time = time 73 | _hour = (time / MILLIS_PER_HOUR) % 24 74 | _minute = (time / MILLIS_PER_MINUTE) % 60 75 | _second = (time / MILLIS_PER_SECOND) % 60 76 | _millisecond = time % 1000 77 | 78 | func hour() -> int: 79 | return _hour 80 | 81 | func minute() -> int: 82 | return _minute 83 | 84 | func second() -> int: 85 | return _second 86 | 87 | func millis() -> int: 88 | return _millisecond 89 | 90 | func _to_string() -> String: 91 | return "%02d:%02d:%02d.%03d" % [_hour, _minute, _second, _millisecond] 92 | -------------------------------------------------------------------------------- /addons/gdUnit3/src/core/Result.gd: -------------------------------------------------------------------------------- 1 | class_name Result 2 | extends Reference 3 | 4 | enum { 5 | SUCCESS, 6 | WARN, 7 | ERROR, 8 | EMPTY 9 | } 10 | 11 | var _state 12 | var _warn_message := "" 13 | var _error_message := "" 14 | var _value = null 15 | 16 | static func __instance() -> Result: 17 | return load("res://addons/gdUnit3/src/core/Result.gd").new() 18 | 19 | static func success(value) -> Result: 20 | assert(value != null, "The value must not be NULL") 21 | var result := __instance() 22 | result._value = value 23 | result._state = SUCCESS 24 | return result 25 | 26 | static func warn(warn_message :String, value = null) -> Result: 27 | assert(not warn_message.empty(), "The message must not be empty") 28 | var result := __instance() 29 | result._value = value 30 | result._warn_message = warn_message 31 | result._state = WARN 32 | return result 33 | 34 | static func error(error_message :String, error :int = 0) -> Result: 35 | assert(not error_message.empty(), "The message must not be empty") 36 | var result := __instance() 37 | result._value = null 38 | result._error_message = error_message 39 | result._state = ERROR 40 | return result 41 | 42 | static func empty() -> Result: 43 | var result := __instance() 44 | result._state = EMPTY 45 | return result 46 | 47 | 48 | func is_success() -> bool: 49 | return _state == SUCCESS 50 | 51 | func is_warn() -> bool: 52 | return _state == WARN 53 | 54 | func is_error() -> bool: 55 | return _state == ERROR 56 | 57 | func is_empty() -> bool: 58 | return _state == EMPTY 59 | 60 | func value(): 61 | return _value 62 | 63 | func or_else(value): 64 | if not is_success(): 65 | return value 66 | return value() 67 | 68 | func error_message() -> String: 69 | return _error_message 70 | 71 | func warn_message() -> String: 72 | return _warn_message 73 | 74 | func _to_string() -> String: 75 | return str(serialize(self)) 76 | 77 | static func serialize(result :Result) -> Dictionary: 78 | if result == null: 79 | push_error("Can't serialize a Null object from type Result") 80 | return { 81 | "state" : result._state, 82 | "value" : var2str(result._value), 83 | "warn_msg" : result._warn_message, 84 | "err_msg" : result._error_message 85 | } 86 | 87 | static func deserialize(config :Dictionary) -> Result: 88 | var result := __instance() 89 | result._value = str2var(config.get("value", "")) 90 | result._warn_message = config.get("warn_msg", null) 91 | result._error_message = config.get("err_msg", null) 92 | result._state = config.get("state") 93 | return result 94 | 95 | -------------------------------------------------------------------------------- /addons/gdUnit3/src/core/TimeUnit.gd: -------------------------------------------------------------------------------- 1 | class_name TimeUnit 2 | extends Resource 3 | 4 | enum { 5 | MILLIS = 1, 6 | SECOND = 2, 7 | MINUTE = 3, 8 | HOUR = 4, 9 | DAY = 5, 10 | MONTH = 6, 11 | YEAR = 7 12 | } 13 | -------------------------------------------------------------------------------- /addons/gdUnit3/src/core/event/GdUnitEventInit.gd: -------------------------------------------------------------------------------- 1 | class_name GdUnitInit 2 | extends GdUnitEvent 3 | 4 | 5 | var _total_testsuites :int 6 | 7 | func _init(total_testsuites :int, total_tests :int) -> void: 8 | _event_type = INIT 9 | _total_testsuites = total_testsuites 10 | _total_count = total_tests 11 | 12 | 13 | func total_test_suites() -> int: 14 | return _total_testsuites 15 | 16 | func total_tests() -> int: 17 | return _total_count 18 | -------------------------------------------------------------------------------- /addons/gdUnit3/src/core/event/GdUnitEventStop.gd: -------------------------------------------------------------------------------- 1 | class_name GdUnitStop 2 | extends GdUnitEvent 3 | 4 | 5 | func _init() -> void: 6 | _event_type = STOP 7 | 8 | -------------------------------------------------------------------------------- /addons/gdUnit3/src/core/event/SignalHandler.gd: -------------------------------------------------------------------------------- 1 | tool 2 | class_name SignalHandler 3 | extends Resource 4 | 5 | const SINGLETON_NAME := "SignalHandler" 6 | 7 | signal client_connected 8 | signal client_disconnected 9 | signal client_terminated 10 | signal send_message 11 | signal test_suite_event 12 | signal test_suite_added 13 | 14 | 15 | func client_connected(client_id :int) -> void: 16 | emit_signal("client_connected", client_id) 17 | 18 | func register_on_client_connected(target :Object, eventFunc :String) -> void: 19 | var err = connect("client_connected", target, eventFunc, [], CONNECT_REFERENCE_COUNTED) 20 | if err != OK: 21 | push_error("Can't connect signal to %s:%s.\n%s" % [target, eventFunc, GdUnitTools.error_as_string(err)]) 22 | 23 | func client_disconnected(client_id :int) -> void: 24 | emit_signal("client_disconnected", client_id) 25 | 26 | func register_on_client_disconnected(target :Object, eventFunc :String) -> void: 27 | var err = connect("client_disconnected", target, eventFunc, [], CONNECT_REFERENCE_COUNTED) 28 | if err != OK: 29 | push_error("Can't connect signal to %s:%s.\n%s" % [target, eventFunc, GdUnitTools.error_as_string(err)]) 30 | 31 | func send_message(message :String) -> void: 32 | emit_signal("send_message", message) 33 | 34 | func register_on_message(target:Object, eventFunc:String) -> void: 35 | var err = connect("send_message", target, eventFunc, [], CONNECT_REFERENCE_COUNTED) 36 | if err != OK: 37 | push_error("Can't connect signal to %s:%s.\n%s" % [target, eventFunc, GdUnitTools.error_as_string(err)]) 38 | 39 | # add test suite to current running 40 | func send_add_test_suite(test_suite :GdUnitTestSuiteDto) -> void: 41 | emit_signal("test_suite_added", test_suite) 42 | 43 | func register_on_test_suite_added(target :Object, eventFunc :String) -> void: 44 | var err = connect("test_suite_added", target, eventFunc, [], CONNECT_REFERENCE_COUNTED) 45 | if err != OK: 46 | push_error("Can't connect signal to %s:%s.\n%s" % [target, eventFunc, GdUnitTools.error_as_string(err)]) 47 | 48 | 49 | # - test suite events 50 | func send_event(event:GdUnitEvent) -> void: 51 | emit_signal("test_suite_event", event) 52 | 53 | func register_on_gdunit_events(target:Object, eventFunc:String) -> void: 54 | if is_connected("test_suite_event", target, eventFunc): 55 | return 56 | var err = connect("test_suite_event", target, eventFunc) 57 | if err != OK: 58 | push_error("Can't connect signal to %s:%s.\n%s" % [target, eventFunc, GdUnitTools.error_as_string(err)]) 59 | 60 | func unregister_on_gdunit_events(target:Object, eventFunc:String) -> void: 61 | if is_connected("test_suite_event", target, eventFunc): 62 | disconnect("test_suite_event", target, eventFunc) 63 | -------------------------------------------------------------------------------- /addons/gdUnit3/src/core/parse/GdClassDescriptor.gd: -------------------------------------------------------------------------------- 1 | class_name GdClassDescriptor 2 | extends Reference 3 | 4 | 5 | var _name :String 6 | var _parent = null 7 | var _is_inner_class :bool 8 | var _functions 9 | 10 | func _init(name :String, is_inner_class :bool, functions :Array): 11 | _name = name 12 | _is_inner_class = is_inner_class 13 | _functions = functions 14 | 15 | func set_parent_clazz(parent :GdClassDescriptor): 16 | _parent = parent 17 | 18 | func name() -> String: 19 | return _name 20 | 21 | func parent() -> GdClassDescriptor: 22 | return _parent 23 | 24 | func is_inner_class() -> bool: 25 | return _is_inner_class 26 | 27 | func functions() -> Array: 28 | return _functions 29 | -------------------------------------------------------------------------------- /addons/gdUnit3/src/core/parse/GdFunctionArgument.gd: -------------------------------------------------------------------------------- 1 | class_name GdFunctionArgument 2 | extends Reference 3 | 4 | var _name: String 5 | var _type: String 6 | var _default_value 7 | 8 | const UNDEFINED = "<-NO_ARG->" 9 | const ARG_PARAMETERIZED_TEST = "test_parameters" 10 | 11 | func _init(name :String, type :String ="", default_value = UNDEFINED): 12 | _name = name 13 | _type = type 14 | _default_value = default_value 15 | 16 | func name() -> String: 17 | return _name 18 | 19 | func default(): 20 | return _default_value 21 | 22 | func type() -> String: 23 | return _type 24 | 25 | func is_parameter_set() -> bool: 26 | return _name == ARG_PARAMETERIZED_TEST 27 | 28 | static func get_parameter_set(parameters :Array) -> GdFunctionArgument: 29 | for current in parameters: 30 | if current != null and current.is_parameter_set(): 31 | return current 32 | return null 33 | 34 | func _to_string() -> String: 35 | var s = _name 36 | if not _type.empty(): 37 | s += ":" + _type 38 | if _default_value != UNDEFINED: 39 | s += "=" + str(_default_value) 40 | return s 41 | -------------------------------------------------------------------------------- /addons/gdUnit3/src/core/parse/GdTestParameterSet.gd: -------------------------------------------------------------------------------- 1 | class_name GdTestParameterSet 2 | extends Reference 3 | 4 | const CLASS_TEMPLATE = """ 5 | class_name _ParameterExtractor extends '${clazz_path}' 6 | 7 | func __extract_test_parameters() -> Array: 8 | return ${test_params} 9 | 10 | """ 11 | 12 | # validates the given arguments are complete and matches to required input fields of the test function 13 | static func validate(input_arguments :Array, input_value_set :Array) -> String: 14 | # check given parameter set with test case arguments 15 | var expected_arg_count = input_arguments.size() - 1 16 | for input_values in input_value_set: 17 | var parameter_set_index := input_value_set.find(input_values) 18 | if input_values is Array: 19 | var current_arg_count = input_values.size() 20 | if current_arg_count != expected_arg_count: 21 | 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] 22 | var error := validate_parameter_types(input_arguments, input_values, parameter_set_index) 23 | if not error.empty(): 24 | return error 25 | else: 26 | 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 27 | return "" 28 | 29 | static func validate_parameter_types(input_arguments :Array, input_values :Array, parameter_set_index :int) -> String: 30 | for i in input_arguments.size(): 31 | var input_param :GdFunctionArgument = input_arguments[i] 32 | # only check the test input arguments 33 | if input_param.is_parameter_set(): 34 | continue 35 | var input_param_type := GdObjects.string_as_typeof(input_param.type()) 36 | var input_value = input_values[i] 37 | var input_value_type := typeof(input_value) 38 | # allow only equal types and object == null 39 | if input_param_type == TYPE_OBJECT and input_value_type == TYPE_NIL: 40 | continue 41 | if input_param_type != input_value_type: 42 | 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] 43 | return "" 44 | 45 | # extracts the arguments from the given test case, using kind of reflection solution 46 | # to restore the parameters from a string representation to real instance type 47 | static func extract_test_parameters(source :GDScript, fd :GdFunctionDescriptor) -> Array: 48 | var parameter_arg := GdFunctionArgument.get_parameter_set(fd.args()) 49 | var source_code = CLASS_TEMPLATE\ 50 | .replace("${clazz_path}", source.resource_path)\ 51 | .replace("${test_params}", parameter_arg.default()) 52 | var script = GDScript.new() 53 | script.source_code = source_code 54 | # enable this lines only for debuging 55 | #script.resource_path = GdUnitTools.create_temp_dir("parameter_extract") + "/%s__.gd" % fd.name() 56 | #Directory.new().remove(script.resource_path) 57 | #ResourceSaver.save(script.resource_path, script) 58 | var result = script.reload() 59 | if result != OK: 60 | push_error("Extracting test parameters failed! Script loading error: %s" % result) 61 | return [] 62 | var instance = script.new() 63 | instance.queue_free() 64 | return instance.call("__extract_test_parameters") 65 | -------------------------------------------------------------------------------- /addons/gdUnit3/src/core/report/GdUnitReport.gd: -------------------------------------------------------------------------------- 1 | class_name GdUnitReport 2 | extends Resource 3 | 4 | # report type 5 | enum { 6 | SUCCESS, 7 | WARN, 8 | FAILURE, 9 | ORPHAN, 10 | TERMINATED, 11 | INTERUPTED, 12 | ABORT 13 | } 14 | 15 | var _type :int 16 | var _line_number :int 17 | var _message :String 18 | 19 | func create(type, line_number :int, message :String) -> GdUnitReport: 20 | _type = type 21 | _line_number = line_number 22 | _message = message 23 | return self 24 | 25 | func type() -> int: 26 | return _type 27 | 28 | func line_number() -> int: 29 | return _line_number 30 | 31 | func message() -> String: 32 | return _message 33 | 34 | func is_warning() -> bool: 35 | return _type == WARN 36 | 37 | func is_failure() -> bool: 38 | return _type == FAILURE 39 | 40 | func is_error() -> bool: 41 | return _type == TERMINATED or _type == INTERUPTED or _type == ABORT 42 | 43 | func _to_string(): 44 | if _line_number == -1: 45 | return "[color=green]line [/color][color=aqua]:[/color] %s" % [_message] 46 | return "[color=green]line [/color][color=aqua]%d:[/color] %s" % [_line_number, _message] 47 | 48 | func serialize() -> Dictionary: 49 | return { 50 | "type" :_type, 51 | "line_number" :_line_number, 52 | "message" :_message 53 | } 54 | 55 | func deserialize(serialized:Dictionary) -> GdUnitReport: 56 | _type = serialized["type"] 57 | _line_number = serialized["line_number"] 58 | _message = serialized["message"] 59 | return self 60 | -------------------------------------------------------------------------------- /addons/gdUnit3/src/core/report/GdUnitReportCollector.gd: -------------------------------------------------------------------------------- 1 | # collects all reports seperated as warnings and failures/errors 2 | class_name GdUnitReportCollector 3 | extends GdUnitReportConsumer 4 | 5 | const STAGE_TEST_SUITE_BEFORE = 1 6 | const STAGE_TEST_SUITE_AFTER = 2 7 | const STAGE_TEST_CASE_BEFORE = 4 8 | const STAGE_TEST_CASE_EXECUTE = 8 9 | const STAGE_TEST_CASE_AFTER = 16 10 | 11 | var ALL_REPORT_STATES := [STAGE_TEST_SUITE_BEFORE, STAGE_TEST_SUITE_AFTER, STAGE_TEST_CASE_BEFORE, STAGE_TEST_CASE_EXECUTE, STAGE_TEST_CASE_AFTER] 12 | var _current_stage :int 13 | 14 | 15 | var _reports_by_state :Dictionary = { 16 | STAGE_TEST_SUITE_BEFORE : Array(), 17 | STAGE_TEST_SUITE_AFTER : Array(), 18 | STAGE_TEST_CASE_BEFORE : Array(), 19 | STAGE_TEST_CASE_AFTER : Array(), 20 | STAGE_TEST_CASE_EXECUTE : Array(), 21 | } 22 | 23 | func get_reports_by_state(execution_state :int) -> Array: 24 | return _reports_by_state.get(execution_state) 25 | 26 | func add_report(execution_state :int, report :GdUnitReport) -> void: 27 | get_reports_by_state(execution_state).append(report) 28 | 29 | func push_front(execution_state :int, report :GdUnitReport) -> void: 30 | get_reports_by_state(execution_state).push_front(report) 31 | 32 | func pop_front(execution_state :int) -> GdUnitReport: 33 | return get_reports_by_state(execution_state).pop_front() 34 | 35 | func clear_reports(execution_states :int) -> void: 36 | for state in ALL_REPORT_STATES: 37 | if execution_states&state == state: 38 | get_reports_by_state(state).clear() 39 | 40 | func get_reports(execution_states :int) -> Array: 41 | var reports :Array = Array() 42 | for state in ALL_REPORT_STATES: 43 | if execution_states&state == state: 44 | GdUnitTools.append_array(reports, get_reports_by_state(state)) 45 | return reports 46 | 47 | func has_errors(execution_states :int) -> bool: 48 | for state in ALL_REPORT_STATES: 49 | if execution_states&state == state: 50 | for report in get_reports_by_state(state): 51 | if report.is_error(): 52 | return true 53 | return false 54 | 55 | func count_errors(execution_states :int) -> int: 56 | var count := 0 57 | for state in ALL_REPORT_STATES: 58 | if execution_states&state == state: 59 | for report in get_reports_by_state(state): 60 | if report.is_error(): 61 | count += 1 62 | return count 63 | 64 | func has_failures(execution_states :int) -> bool: 65 | for state in ALL_REPORT_STATES: 66 | if execution_states&state == state: 67 | for report in get_reports_by_state(state): 68 | if report.type() == GdUnitReport.FAILURE: 69 | return true 70 | return false 71 | 72 | func count_failures(execution_states :int) -> int: 73 | var count := 0 74 | for state in ALL_REPORT_STATES: 75 | if execution_states&state == state: 76 | for report in get_reports_by_state(state): 77 | if report.type() == GdUnitReport.FAILURE: 78 | count += 1 79 | return count 80 | 81 | func has_warnings(execution_states :int) -> bool: 82 | for state in ALL_REPORT_STATES: 83 | if execution_states&state == state: 84 | for report in get_reports_by_state(state): 85 | if report.type() == GdUnitReport.WARN: 86 | return true 87 | return false 88 | 89 | func set_stage(stage :int) -> void: 90 | _current_stage = stage 91 | 92 | func consume(report :GdUnitReport) -> void: 93 | add_report(_current_stage, report) 94 | -------------------------------------------------------------------------------- /addons/gdUnit3/src/core/report/GdUnitReportConsumer.gd: -------------------------------------------------------------------------------- 1 | # Report consumer interace to collect reports 2 | class_name GdUnitReportConsumer 3 | extends Reference 4 | 5 | const META_PARAM := "gdunit.report.consumer" 6 | 7 | # must be implemented to collect reports 8 | func consume(report :GdUnitReport) -> void: 9 | pass 10 | 11 | # register a report provider to enable consuming reports 12 | func register_report_provider(provider :Object): 13 | provider.set_meta(META_PARAM, self) 14 | -------------------------------------------------------------------------------- /addons/gdUnit3/src/core/scan_project.gd: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env -S godot -s 2 | tool 3 | extends SceneTree 4 | 5 | enum { 6 | INIT, 7 | SCAN, 8 | QUIT 9 | } 10 | 11 | var _counter = 0 12 | var WAIT_TIME_IN_MS = 1.000 13 | var _state = INIT 14 | 15 | func _init(): 16 | set_auto_accept_quit(true) 17 | print("Scan for project changes ...") 18 | _state = SCAN 19 | 20 | func _idle(delta): 21 | yield(root.get_tree(), "idle_frame") 22 | if _state != SCAN: 23 | return 24 | _counter += delta 25 | if _counter >= WAIT_TIME_IN_MS: 26 | prints("Scan for project changes done", root.get_tree()) 27 | _state = QUIT 28 | fix_cache_bug() 29 | exit() 30 | 31 | # see https://github.com/godotengine/godot/issues/62820 for more details 32 | func fix_cache_bug(): 33 | for node in root.get_children(): 34 | if node.name.begins_with("EditorNode"): 35 | for child in node.get_children(): 36 | if child is EditorResourcePreview: 37 | var prewview := child as EditorResourcePreview 38 | prewview.check_for_invalidation("res://") 39 | yield(root.get_tree(), "idle_frame") 40 | 41 | func exit(): 42 | prints("Exit") 43 | key_pressed(KEY_Q, true) 44 | key_pressed(KEY_ENTER) 45 | 46 | func key_pressed(key_code :int, command := false): 47 | key_press(key_code, command) 48 | key_release(key_code, command) 49 | 50 | func key_press(key_code :int, command := false): 51 | var action = InputEventKey.new() 52 | action.pressed = true 53 | action.scancode = key_code 54 | action.command = command 55 | input_event(action) 56 | 57 | func key_release(key_code :int,command := false): 58 | var action = InputEventKey.new() 59 | action.pressed = false 60 | action.scancode = key_code 61 | action.command = command 62 | input_event(action) 63 | -------------------------------------------------------------------------------- /addons/gdUnit3/src/core/templates/test_suite/GdUnitTestSuiteDefaultTemplate.gd: -------------------------------------------------------------------------------- 1 | class_name GdUnitTestSuiteDefaultTemplate 2 | extends Reference 3 | 4 | 5 | const DEFAULT_TEMP_TS_GD = \ 6 | """# GdUnit generated TestSuite 7 | #warning-ignore-all:unused_argument 8 | #warning-ignore-all:return_value_discarded 9 | class_name ${suite_class_name} 10 | extends GdUnitTestSuite 11 | 12 | # TestSuite generated from 13 | const __source = '${source_resource_path}' 14 | """ 15 | 16 | const DEFAULT_TEMP_TS_CS = \ 17 | """// GdUnit generated TestSuite 18 | 19 | using Godot; 20 | using GdUnit3; 21 | 22 | namespace ${name_space} 23 | { 24 | using static Assertions; 25 | using static Utils; 26 | 27 | [TestSuite] 28 | public class ${suite_class_name} 29 | { 30 | // TestSuite generated from 31 | private const string sourceClazzPath = "${source_resource_path}"; 32 | 33 | } 34 | } 35 | """ 36 | -------------------------------------------------------------------------------- /addons/gdUnit3/src/extractors/GdUnitFuncValueExtractor.gd: -------------------------------------------------------------------------------- 1 | # This class defines a value extractor by given function name and args 2 | class_name GdUnitFuncValueExtractor 3 | extends GdUnitValueExtractor 4 | 5 | var _func_names :Array 6 | var _args :Array 7 | 8 | func _init(func_name :String, args :Array): 9 | _func_names = func_name.split(".") 10 | _args = args 11 | 12 | func func_names() -> Array: 13 | return _func_names 14 | 15 | func args() -> Array: 16 | return _args 17 | 18 | # Extracts a value by given `func_name` and `args`, 19 | # Allows to use a chained list of functions setarated ba a dot. 20 | # e.g. "func_a.func_b.name" 21 | # do calls instance.func_a().func_b().name() and returns finally the name 22 | # If a function returns an array, all elements will by collected in a array 23 | # e.g. "get_children.get_name" on a node 24 | # do calls node.get_children() for all childs get_name() and returns all names in an array 25 | # 26 | # if the value not a Object or not accesible be `func_name` the value is converted to `"n.a."` 27 | # expecing null values 28 | func extract_value(value): 29 | if value == null: 30 | return null 31 | for func_name in func_names(): 32 | if GdObjects.is_array_type(value): 33 | var values := Array() 34 | for element in Array(value): 35 | values.append(_call_func(element, func_name)) 36 | value = values 37 | else: 38 | value = _call_func(value, func_name) 39 | if typeof(value) == TYPE_STRING and value == "n.a.": 40 | return value 41 | return value 42 | 43 | func _call_func(value, func_name :String): 44 | # for array types we need to call explicit by function name, using funcref is only supported for Objects 45 | # TODO extend to all array functions 46 | if GdObjects.is_array_type(value) and func_name == "empty": 47 | return value.empty() 48 | 49 | if not (value is Object): 50 | if GdUnitSettings.is_verbose_assert_warnings(): 51 | push_warning("Extracting value from element '%s' by func '%s' failed! Converting to \"n.a.\"" % [value, func_name]) 52 | return "n.a." 53 | var extract := funcref(value, func_name) 54 | if extract.is_valid(): 55 | return value.call(func_name) if args().empty() else value.callv(func_name, args()) 56 | else: 57 | if GdUnitSettings.is_verbose_assert_warnings(): 58 | push_warning("Extracting value from element '%s' by func '%s' failed! Converting to \"n.a.\"" % [value, func_name]) 59 | return "n.a." 60 | -------------------------------------------------------------------------------- /addons/gdUnit3/src/fuzzers/Fuzzer.gd: -------------------------------------------------------------------------------- 1 | # Base interface for fuzz testing 2 | # https://en.wikipedia.org/wiki/Fuzzing 3 | class_name Fuzzer 4 | extends Resource 5 | # To run a test with a specific fuzzer you have to add defailt argument on your test case 6 | # all arguments are optional [] 7 | # syntax: 8 | # func test_foo([fuzzer = ], [fuzzer_iterations=], [fuzzer_seed=]) 9 | # example: 10 | # # runs the test 'test_foo' 10 times with a random int value generated by the IntFuzzer 11 | # func test_foo(fuzzer = Fuzzers.randomInt(), fuzzer_iterations=10) 12 | # 13 | # # runs the test 'test_foo2' 1000 times as default with a random seed='101010101' 14 | # func test_foo2(fuzzer = Fuzzers.randomInt(), fuzzer_seed=101010101) 15 | 16 | const ITERATION_DEFAULT_COUNT = 1000 17 | const ARGUMENT_FUZZER_INSTANCE := "fuzzer" 18 | const ARGUMENT_ITERATIONS := "fuzzer_iterations" 19 | const ARGUMENT_SEED := "fuzzer_seed" 20 | 21 | var _iteration_index :int = 0 22 | var _iteration_limit :int = ITERATION_DEFAULT_COUNT 23 | 24 | # generates the next fuzz value 25 | # needs to be implement 26 | func next_value(): 27 | push_error("Invalid vall. Fuzzer not implemented 'next_value()'") 28 | return null 29 | 30 | # returns the current iteration index 31 | func iteration_index() -> int: 32 | return _iteration_index 33 | 34 | # returns the amount of iterations where the fuzzer will be run 35 | func iteration_limit() -> int: 36 | return _iteration_limit 37 | -------------------------------------------------------------------------------- /addons/gdUnit3/src/fuzzers/FuzzerTool.gd: -------------------------------------------------------------------------------- 1 | class_name FuzzerTool 2 | extends Resource 3 | 4 | static func create_fuzzer(source:GDScript, function: String) -> Fuzzer: 5 | var fuzzer_func = function.split("=") 6 | var source_code = "# warnings-disable\n" \ 7 | + source.source_code \ 8 | + "\n" \ 9 | + "func __fuzzer():\n" \ 10 | + " return ${fuzzer_func}\n"\ 11 | .replace("${fuzzer_func}", fuzzer_func[1]) 12 | var script = GDScript.new() 13 | script.source_code = source_code 14 | var result = script.reload() 15 | if result != OK: 16 | prints("script loading error", result) 17 | return null 18 | 19 | var instance = script.new() 20 | var f := funcref(instance, "__fuzzer") 21 | if not f.is_valid(): 22 | prints("Error", script, f) 23 | return null 24 | var fuzzer = f.call_func() 25 | instance.free() 26 | if fuzzer is Fuzzer: 27 | return fuzzer as Fuzzer 28 | else: 29 | return null 30 | -------------------------------------------------------------------------------- /addons/gdUnit3/src/fuzzers/IntFuzzer.gd: -------------------------------------------------------------------------------- 1 | class_name IntFuzzer 2 | extends Fuzzer 3 | 4 | enum { 5 | NORMAL, 6 | EVEN, 7 | ODD 8 | } 9 | 10 | var _from :int = 0 11 | var _to : int = 0 12 | var _mode : int = NORMAL 13 | 14 | func _init(from: int, to: int, mode :int = NORMAL): 15 | assert(from <= to, "Invalid range!") 16 | _from = from 17 | _to = to 18 | _mode = mode 19 | 20 | func next_value() -> int: 21 | var value = rand_range(_from, _to) as int 22 | match _mode: 23 | NORMAL: 24 | return value 25 | EVEN: 26 | return (value / 2) * 2 27 | ODD: 28 | return (value / 2) * 2 + 1 29 | _: 30 | return value 31 | -------------------------------------------------------------------------------- /addons/gdUnit3/src/fuzzers/StringFuzzer.gd: -------------------------------------------------------------------------------- 1 | class_name StringFuzzer 2 | extends Fuzzer 3 | 4 | 5 | const DEFAULT_CHARSET = "a-zA-Z0-9+-_" 6 | 7 | var _min_length :int 8 | var _max_length :int 9 | var _charset :PoolByteArray 10 | 11 | 12 | func _init(min_length :int, max_length :int, pattern :String = DEFAULT_CHARSET): 13 | assert(min_length>0 and min_length < max_length) 14 | assert(not null or not pattern.empty()) 15 | _min_length = min_length 16 | _max_length = max_length 17 | _charset = extract_charset(pattern) 18 | 19 | static func extract_charset(pattern :String) -> PoolByteArray: 20 | var reg := RegEx.new() 21 | if reg.compile(pattern) != OK: 22 | push_error("Invalid pattern to generate Strings! Use e.g 'a-zA-Z0-9+-_'") 23 | return PoolByteArray() 24 | 25 | var charset := Array() 26 | var char_before := -1 27 | var index := 0 28 | while index < pattern.length(): 29 | var char_current := pattern.ord_at(index) 30 | # - range token at first or last pos? 31 | if char_current == 45 and (index == 0 or index == pattern.length()-1): 32 | charset.append(char_current) 33 | index += 1 34 | continue 35 | index += 1 36 | # range starts 37 | if char_current == 45 and char_before != -1: 38 | var char_next := pattern.ord_at(index) 39 | var characters := build_chars(char_before, char_next) 40 | for character in characters: 41 | charset.append(character) 42 | char_before = -1 43 | index += 1 44 | continue 45 | char_before = char_current 46 | charset.append(char_current) 47 | return PoolByteArray(charset) 48 | 49 | static func build_chars(from :int, to :int) -> Array: 50 | var characters := Array() 51 | for character in range(from+1, to+1): 52 | characters.append(character) 53 | return characters 54 | 55 | func next_value(): 56 | var value := PoolByteArray() 57 | var max_char = len(_charset) 58 | var length := max(_min_length, randi() % _max_length) 59 | for i in length: 60 | value.append(_charset[randi() % max_char]) 61 | return value.get_string_from_ascii() 62 | -------------------------------------------------------------------------------- /addons/gdUnit3/src/fuzzers/Vector2Fuzzer.gd: -------------------------------------------------------------------------------- 1 | class_name Vector2Fuzzer 2 | extends Fuzzer 3 | 4 | 5 | var _from :Vector2 6 | var _to : Vector2 7 | 8 | func _init(from: Vector2, to: Vector2): 9 | assert(from <= to, "Invalid range!") 10 | _from = from 11 | _to = to 12 | 13 | func next_value() -> Vector2: 14 | var x = rand_range(_from.x, _to.x) 15 | var y = rand_range(_from.y, _to.y) 16 | return Vector2(x, y) 17 | -------------------------------------------------------------------------------- /addons/gdUnit3/src/fuzzers/Vector3Fuzzer.gd: -------------------------------------------------------------------------------- 1 | class_name Vector3Fuzzer 2 | extends Fuzzer 3 | 4 | 5 | var _from :Vector3 6 | var _to : Vector3 7 | 8 | func _init(from: Vector3, to: Vector3): 9 | assert(from <= to, "Invalid range!") 10 | _from = from 11 | _to = to 12 | 13 | func next_value() -> Vector3: 14 | var x = rand_range(_from.x, _to.x) 15 | var y = rand_range(_from.y, _to.y) 16 | var z = rand_range(_from.z, _to.z) 17 | return Vector3(x, y, z) 18 | -------------------------------------------------------------------------------- /addons/gdUnit3/src/matchers/AnyArgumentMatcher.gd: -------------------------------------------------------------------------------- 1 | class_name AnyArgumentMatcher 2 | extends GdUnitArgumentMatcher 3 | 4 | func is_match(value) -> bool: 5 | return true 6 | -------------------------------------------------------------------------------- /addons/gdUnit3/src/matchers/AnyBuildInTypeArgumentMatcher.gd: -------------------------------------------------------------------------------- 1 | class_name AnyBuildInTypeArgumentMatcher 2 | extends GdUnitArgumentMatcher 3 | 4 | var _type :int 5 | 6 | func _init(type :int): 7 | _type = type 8 | 9 | func is_match(value) -> bool: 10 | return typeof(value) == _type 11 | -------------------------------------------------------------------------------- /addons/gdUnit3/src/matchers/AnyClazzArgumentMatcher.gd: -------------------------------------------------------------------------------- 1 | class_name AnyClazzArgumentMatcher 2 | extends GdUnitArgumentMatcher 3 | 4 | var _clazz 5 | 6 | func _init(clazz :Object): 7 | _clazz = clazz 8 | 9 | func is_match(value) -> bool: 10 | if is_instance_valid(value) and GdObjects.is_script(_clazz): 11 | return value.get_script() == _clazz 12 | return GdObjects.is_instanceof(value, _clazz) 13 | -------------------------------------------------------------------------------- /addons/gdUnit3/src/matchers/ChainedArgumentMatcher.gd: -------------------------------------------------------------------------------- 1 | class_name ChainedArgumentMatcher 2 | extends GdUnitArgumentMatcher 3 | 4 | var _matchers :Array 5 | 6 | func _init(matchers :Array): 7 | _matchers = matchers 8 | 9 | func is_match(arguments :Array) -> bool: 10 | if arguments.size() != _matchers.size(): 11 | return false 12 | 13 | for index in arguments.size(): 14 | var arg = arguments[index] 15 | var matcher = _matchers[index] as GdUnitArgumentMatcher 16 | 17 | if not matcher.is_match(arg): 18 | return false 19 | return true 20 | -------------------------------------------------------------------------------- /addons/gdUnit3/src/matchers/EqualsArgumentMatcher.gd: -------------------------------------------------------------------------------- 1 | class_name EqualsArgumentMatcher 2 | extends GdUnitArgumentMatcher 3 | 4 | var _current 5 | var _auto_deep_check_mode 6 | 7 | func _init(current, auto_deep_check_mode := false): 8 | _current = current 9 | _auto_deep_check_mode = auto_deep_check_mode 10 | 11 | func is_match(value) -> bool: 12 | # is auto deep compare mode requested 13 | var deep_check := deep_check_on(value) 14 | var case_sensitive_check := true 15 | return GdObjects.equals(_current, value, case_sensitive_check, deep_check) 16 | 17 | func deep_check_on(value) -> bool: 18 | var deep_check := false 19 | if _auto_deep_check_mode and is_instance_valid(value): 20 | # we do deep check on all InputEvent's 21 | deep_check = value is InputEvent 22 | return deep_check 23 | -------------------------------------------------------------------------------- /addons/gdUnit3/src/matchers/GdUnitArgumentMatcher.gd: -------------------------------------------------------------------------------- 1 | # base class of all argument matchers 2 | class_name GdUnitArgumentMatcher 3 | extends Reference 4 | 5 | 6 | func is_match(value) -> bool: 7 | return true 8 | -------------------------------------------------------------------------------- /addons/gdUnit3/src/matchers/GdUnitArgumentMatchers.gd: -------------------------------------------------------------------------------- 1 | class_name GdUnitArgumentMatchers 2 | extends Reference 3 | 4 | const TYPE_ANY = TYPE_MAX + 100 5 | const _instances = Dictionary() 6 | 7 | 8 | func _init(): 9 | for build_in_type in GdObjects.all_types(): 10 | _instances[build_in_type] = AnyBuildInTypeArgumentMatcher.new(build_in_type) 11 | _instances[TYPE_ANY] = AnyArgumentMatcher.new() 12 | 13 | static func to_matcher(arguments :Array, auto_deep_check_mode := false) -> ChainedArgumentMatcher: 14 | var matchers := Array() 15 | for arg in arguments: 16 | # argument is already a matcher 17 | if arg is GdUnitArgumentMatcher: 18 | matchers.append(arg) 19 | else: 20 | # pass argument into equals matcher 21 | matchers.append(EqualsArgumentMatcher.new(arg, auto_deep_check_mode)) 22 | return ChainedArgumentMatcher.new(matchers) 23 | 24 | static func any() -> GdUnitArgumentMatcher: 25 | return _instances[TYPE_ANY] 26 | 27 | static func by_type(type :int) -> GdUnitArgumentMatcher: 28 | return _instances[type] 29 | 30 | static func any_class(clazz) -> GdUnitArgumentMatcher: 31 | return AnyClazzArgumentMatcher.new(clazz) 32 | 33 | func _notification(what): 34 | if what == NOTIFICATION_PREDELETE: 35 | _instances.clear() 36 | -------------------------------------------------------------------------------- /addons/gdUnit3/src/mocking/GdUnitMock.gd: -------------------------------------------------------------------------------- 1 | class_name GdUnitMock 2 | extends Reference 3 | 4 | # do call the real implementation 5 | const CALL_REAL_FUNC = "CALL_REAL_FUNC" 6 | # do return a default value for primitive types or null 7 | const RETURN_DEFAULTS = "RETURN_DEFAULTS" 8 | # do return a default value for primitive types and a fully mocked value for Object types 9 | # builds full deep mocked object 10 | const RETURN_DEEP_STUB = "RETURN_DEEP_STUB" 11 | 12 | var _value 13 | 14 | func _init(value): 15 | _value = value 16 | 17 | func on(obj :Object): 18 | if not _is_mock_or_spy( obj, "__do_return"): 19 | return obj 20 | return obj.__do_return(_value) 21 | 22 | static func _is_mock_or_spy(obj :Object, func_sig :String) -> bool: 23 | if obj is GDScript and not obj.get_script().has_script_method(func_sig): 24 | push_error("Error: You try to use a non mock or spy!") 25 | return false 26 | return true 27 | -------------------------------------------------------------------------------- /addons/gdUnit3/src/mocking/GdUnitMockImpl.gd: -------------------------------------------------------------------------------- 1 | # warnings-disable 2 | # warning-ignore:unused_argument 3 | class_name GdUnitMockImpl 4 | 5 | ################################################################################ 6 | # internal mocking stuff 7 | ################################################################################ 8 | var __working_mode :String 9 | 10 | var __do_return_value = null 11 | var __saved_return_values := Dictionary() 12 | 13 | # self reference holder, use this kind of hack to store static function calls 14 | # it is important to manually free by '__release_double' otherwise it ends up in orphan instance 15 | const __self := [] 16 | 17 | func __set_singleton(): 18 | # store self need to mock static functions 19 | __self.append(self) 20 | 21 | func __release_double(): 22 | # we need to release the self reference manually to prevent orphan nodes 23 | __self.clear() 24 | 25 | func __is_prepare_return_value() -> bool: 26 | return __do_return_value != null 27 | 28 | func __save_function_return_value(args :Array): 29 | __saved_return_values[args] = __do_return_value 30 | __do_return_value = null 31 | return __saved_return_values[args] 32 | 33 | func __set_mode(mode :String): 34 | __working_mode = mode 35 | return self 36 | 37 | func __do_return(value): 38 | __do_return_value = value 39 | return self 40 | -------------------------------------------------------------------------------- /addons/gdUnit3/src/monitor/GdUnitMemMonitor.gd: -------------------------------------------------------------------------------- 1 | class_name GdUnitMemMonitor 2 | extends GdUnitMonitor 3 | 4 | var _orphan_nodes_start :int 5 | var _orphan_nodes_end :int 6 | var _orphan_total :int 7 | 8 | func _init(name :String = "").("MemMonitor:" + name): 9 | _orphan_nodes_start = 0 10 | _orphan_nodes_end = 0 11 | _orphan_total = 0 12 | 13 | func reset(): 14 | _orphan_total = 0 15 | 16 | func start(): 17 | _orphan_nodes_start = Performance.get_monitor(Performance.OBJECT_ORPHAN_NODE_COUNT) 18 | 19 | func stop(): 20 | _orphan_nodes_end = Performance.get_monitor(Performance.OBJECT_ORPHAN_NODE_COUNT) 21 | _orphan_total += _orphan_nodes_end - _orphan_nodes_start 22 | 23 | func orphan_nodes() -> int: 24 | return _orphan_total 25 | 26 | -------------------------------------------------------------------------------- /addons/gdUnit3/src/monitor/GdUnitMonitor.gd: -------------------------------------------------------------------------------- 1 | # GdUnit Monitoring Base Class 2 | class_name GdUnitMonitor 3 | extends Resource 4 | 5 | var _id :String 6 | 7 | # constructs new Monitor with given id 8 | func _init(id :String): 9 | _id = id 10 | 11 | # Returns the id of the monitor to uniqe identify 12 | func id() -> String: 13 | return _id 14 | 15 | # starts monitoring 16 | func start(): 17 | pass 18 | 19 | # stops monitoring 20 | func stop(): 21 | pass 22 | -------------------------------------------------------------------------------- /addons/gdUnit3/src/monitor/GodotGdErrorMonitor.gd: -------------------------------------------------------------------------------- 1 | class_name GodotGdErrorMonitor 2 | extends GdUnitMonitor 3 | 4 | var _godot_log_file :String 5 | var _file :File 6 | var _eof :int 7 | var _report_enabled := false 8 | 9 | func _init().("GodotGdErrorMonitor"): 10 | _godot_log_file = GdUnitSettings.get_log_path() 11 | _report_enabled = is_reporting_enabled() 12 | 13 | func start(): 14 | if _report_enabled: 15 | _file = File.new() 16 | if _file.open(_godot_log_file, File.READ) != OK: 17 | push_error("Can't access Godot logfile '%s', error reporting will be disabled." % _godot_log_file) 18 | _report_enabled = false 19 | _file.seek_end(0) 20 | _eof = _file.get_len() 21 | _file.close() 22 | 23 | func stop(): 24 | pass 25 | 26 | func reports() -> Array: 27 | if _report_enabled: 28 | return _scan_for_errors() 29 | return [] 30 | 31 | func is_reporting_enabled() -> bool: 32 | return _is_report_script_errors() or _is_report_push_errors() 33 | 34 | func _collect_seek_log() -> String: 35 | _file = File.new() 36 | _file.open(_godot_log_file, File.READ) 37 | _file.seek(_eof) 38 | var current_log := "" 39 | var line := _file.get_line() 40 | while line != null and not _file.eof_reached(): 41 | current_log += line + "\n" 42 | line = _file.get_line() 43 | _file.close() 44 | return current_log 45 | 46 | func _scan_for_errors() -> Array: 47 | var _reports :Array = Array() 48 | var loggs := _collect_seek_log().split("\n") 49 | for index in loggs.size(): 50 | var message := loggs[index] as String 51 | if _is_report_script_errors() and message.find("**SCRIPT ERROR**:") != -1: 52 | var error = message + "\n" + loggs[index+1] 53 | var line := _parse_error_line_number(error) 54 | _reports.append(GdUnitReport.new().create(GdUnitReport.FAILURE, line, error)) 55 | if _is_report_push_errors() and message.find("**ERROR**:") != -1: 56 | var error = message + "\n" + loggs[index+1] 57 | _reports.append(GdUnitReport.new().create(GdUnitReport.FAILURE, -1, error)) 58 | return _reports 59 | 60 | func _parse_error_line_number(error :String) -> int: 61 | var _regex := RegEx.new() 62 | _regex.compile("(At: res:\\/\\/.*\\:(\\d+):)") 63 | var matches := _regex.search_all(error) 64 | if matches != null and matches.size() > 0: 65 | return matches[0].get_string(2) as int 66 | return -1 67 | 68 | func _is_report_push_errors() -> bool: 69 | return GdUnitSettings.is_report_push_errors() 70 | 71 | func _is_report_script_errors() -> bool: 72 | return GdUnitSettings.is_report_script_errors() 73 | -------------------------------------------------------------------------------- /addons/gdUnit3/src/mono/GdUnit3MonoAPI.cs: -------------------------------------------------------------------------------- 1 | 2 | // GdUnit3 c# API wrapper 3 | public class GdUnit3MonoAPI : GdUnit3.GdUnit3MonoAPI 4 | { 5 | } 6 | -------------------------------------------------------------------------------- /addons/gdUnit3/src/mono/GdUnit3MonoAPI.gd: -------------------------------------------------------------------------------- 1 | extends Reference 2 | class_name GdUnit3MonoAPI 3 | 4 | const _instance : = [] 5 | 6 | static func instance() : 7 | if not _instance.empty(): 8 | return _instance[0] 9 | var instance = load("res://addons/gdUnit3/src/mono/GdUnit3MonoAPI.cs").new() 10 | _instance.append(instance) 11 | return _instance[0] 12 | 13 | static func create_test_suite(source_path :String, line_number :int, test_suite_path :String) -> Result: 14 | if not GdUnitTools.is_mono_supported(): 15 | return Result.error("Can't create test suite. No c# support found.") 16 | var result := instance().CreateTestSuite(source_path, line_number, test_suite_path) as Dictionary 17 | if result.has("error"): 18 | return Result.error(result.get("error")) 19 | return Result.success(result) 20 | 21 | static func is_test_suite(resource_path :String) -> bool: 22 | if not is_csharp_file(resource_path) or not GdUnitTools.is_mono_supported(): 23 | return false 24 | if resource_path.empty(): 25 | if GdUnitSettings.is_report_push_errors(): 26 | push_error("Can't create test suite. Missing resource path.") 27 | return false 28 | return instance().IsTestSuite(resource_path) 29 | 30 | static func parse_test_suite(source_path :String) -> Node: 31 | if not GdUnitTools.is_mono_supported(): 32 | if GdUnitSettings.is_report_push_errors(): 33 | push_error("Can't create test suite. No c# support found.") 34 | return null 35 | return instance().ParseTestSuite(source_path) 36 | 37 | static func create_executor(listener :Node) -> Node: 38 | if not GdUnitTools.is_mono_supported(): 39 | return null 40 | return instance().Executor(listener) 41 | 42 | static func is_csharp_file(resource_path :String) -> bool: 43 | var ext := resource_path.get_extension() 44 | return ext == "cs" and GdUnitTools.is_mono_supported() 45 | -------------------------------------------------------------------------------- /addons/gdUnit3/src/network/GdUnitServer.gd: -------------------------------------------------------------------------------- 1 | tool 2 | extends Node 3 | 4 | onready var _server :GdUnitTcpServer = $TcpServer 5 | #onready var _error_handler :GdUnitPushErrorHandler = $ErrorHandler 6 | 7 | var _signal_handler :SignalHandler 8 | 9 | 10 | # holds tasks to execute by key = task_name and value = GdUnitTask 11 | var _tasks := Dictionary() 12 | 13 | func _ready(): 14 | _signal_handler = GdUnitSingleton.get_or_create_singleton(SignalHandler.SINGLETON_NAME, "res://addons/gdUnit3/src/core/event/SignalHandler.gd") 15 | var result := _server.start() 16 | if result.is_error(): 17 | push_error(result.error_message()) 18 | return 19 | var server_port :int = result.value() 20 | Engine.set_meta("gdunit_server_port", server_port) 21 | 22 | _server.connect("client_connected", self, "_on_client_connected") 23 | _server.connect("client_disconnected", self, "_on_client_disconnected") 24 | _server.connect("rpc_data", self, "_receive_rpc_data") 25 | 26 | #register_tasks(_error_handler.get_tasks()) 27 | 28 | #remote func sync_rpc_id_request(request :Dictionary): 29 | # #prints(" ->exec", request.get(GdUnitTask.TASK_NAME)) 30 | # var client_id := get_tree().get_rpc_sender_id() 31 | # var result := execute_task(request.get(GdUnitTask.TASK_NAME), request.get(GdUnitTask.TASK_ARGS)) 32 | # #prints(" <-send response:", request.get(GdUnitTask.TASK_NAME), typeof(result.value())) 33 | # rpc_id(client_id, "sync_rpc_id_request_response", Result.serialize(result)) 34 | 35 | #remote func async_rpc_id_request(request :Dictionary): 36 | # #prints(" ->exec", request.get(GdUnitTask.TASK_NAME)) 37 | # var client_id := get_tree().get_rpc_sender_id() 38 | # execute_task(request.get(GdUnitTask.TASK_NAME), request.get(GdUnitTask.TASK_ARGS)) 39 | 40 | #func register_tasks(tasks :Array) -> void: 41 | # for task in tasks: 42 | # register_task(task as GdUnitTask) 43 | 44 | #func register_task(task :GdUnitTask) -> void: 45 | # if _tasks.has(task.name()): 46 | # push_error("An task with name '%s' is already registered." % task.name()) 47 | # return 48 | # _tasks[task.name()] = task 49 | 50 | #func execute_task(task_name :String, task_args :Array) -> Result: 51 | # if not _tasks.has(task_name): 52 | # push_error("Invalid task: can't find a registerd task by name '%s'" % task_name) 53 | # return null 54 | # var task :GdUnitTask = _tasks.get(task_name) 55 | # return task.execute(task_args) 56 | 57 | 58 | func _on_client_connected(client_id :int) -> void: 59 | _signal_handler.client_connected(client_id) 60 | 61 | func _on_client_disconnected(client_id :int) -> void: 62 | _signal_handler.client_disconnected(client_id) 63 | 64 | func _on_gdunit_runner_stop(client_id :int): 65 | if _server: 66 | _server.disconnect_client(client_id) 67 | 68 | func _receive_rpc_data(rpc :RPC) -> void: 69 | if rpc is RPCMessage: 70 | _signal_handler.send_message(rpc.message()) 71 | return 72 | if rpc is RPCGdUnitEvent: 73 | _signal_handler.send_event(rpc.event()) 74 | return 75 | if rpc is RPCGdUnitTestSuite: 76 | _signal_handler.send_add_test_suite(rpc.dto()) 77 | -------------------------------------------------------------------------------- /addons/gdUnit3/src/network/GdUnitServer.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=3 format=2] 2 | 3 | [ext_resource path="res://addons/gdUnit3/src/network/GdUnitServer.gd" type="Script" id=1] 4 | [ext_resource path="res://addons/gdUnit3/src/network/GdUnitTcpServer.gd" type="Script" id=2] 5 | 6 | [node name="Control" type="Node"] 7 | script = ExtResource( 1 ) 8 | 9 | [node name="TcpServer" type="Node" parent="."] 10 | script = ExtResource( 2 ) 11 | -------------------------------------------------------------------------------- /addons/gdUnit3/src/network/GdUnitServerConstants.gd: -------------------------------------------------------------------------------- 1 | class_name GdUnitServerConstants 2 | extends Reference 3 | 4 | const DEFAULT_SERVER_START_RETRY_TIMES = 5 5 | const GD_TEST_SERVER_PORT :int = 31002 6 | const JSON_RESPONSE_DELIMITER = "<>" 7 | -------------------------------------------------------------------------------- /addons/gdUnit3/src/network/GdUnitTask.gd: -------------------------------------------------------------------------------- 1 | class_name GdUnitTask 2 | extends Reference 3 | 4 | const TASK_NAME = "task_name" 5 | const TASK_ARGS = "task_args" 6 | 7 | var _task_name :String 8 | var _fref :FuncRef 9 | 10 | func _init(task_name :String, instance :Object, func_name :String): 11 | _task_name = task_name 12 | if not instance.has_method(func_name): 13 | push_error("Can't create GdUnitTask, Invalid func name '%s' for instance '%s'" % [instance, func_name]) 14 | _fref = funcref(instance, func_name) 15 | 16 | func name() -> String: 17 | return _task_name 18 | 19 | func execute(args :Array) -> Result: 20 | if args.empty(): 21 | return _fref.call_func() 22 | return _fref.call_funcv(args) 23 | -------------------------------------------------------------------------------- /addons/gdUnit3/src/network/rpc/RPC.gd: -------------------------------------------------------------------------------- 1 | class_name RPC 2 | extends Reference 3 | 4 | func serialize() -> String: 5 | return JSON.print(inst2dict(self)) 6 | 7 | # using untyped version see comments below 8 | static func deserialize(json :String): 9 | var result: = JSON.parse(json) 10 | if not typeof(result.result) == TYPE_DICTIONARY: 11 | push_error("Can't deserialize JSON, error at line %d: %s \n json:%s" % [result.error_line, result.error_string, json]) 12 | return null 13 | return dict2inst(result.result) 14 | 15 | # this results in orpan node, for more details https://github.com/godotengine/godot/issues/50069 16 | #func deserialize2(data :Dictionary) -> RPC: 17 | # return dict2inst(data) as RPC 18 | -------------------------------------------------------------------------------- /addons/gdUnit3/src/network/rpc/RPCClientConnect.gd: -------------------------------------------------------------------------------- 1 | class_name RPCClientConnect 2 | extends RPC 3 | 4 | var _client_id :int 5 | 6 | func with_id(client_id :int) -> RPCClientConnect: 7 | _client_id = client_id 8 | return self 9 | 10 | func client_id() -> int: 11 | return _client_id 12 | -------------------------------------------------------------------------------- /addons/gdUnit3/src/network/rpc/RPCClientDisconnect.gd: -------------------------------------------------------------------------------- 1 | class_name RPCClientDisconnect 2 | extends RPC 3 | 4 | var _client_id :int 5 | 6 | func with_id(client_id :int) -> RPCClientDisconnect: 7 | _client_id = client_id 8 | return self 9 | 10 | func client_id() -> int: 11 | return _client_id 12 | -------------------------------------------------------------------------------- /addons/gdUnit3/src/network/rpc/RPCData.gd: -------------------------------------------------------------------------------- 1 | class_name RPCData 2 | extends RPC 3 | 4 | var _value 5 | 6 | func with_data(value) -> RPCData: 7 | _value = value 8 | return self 9 | 10 | func data() : 11 | return _value 12 | -------------------------------------------------------------------------------- /addons/gdUnit3/src/network/rpc/RPCGdUnitEvent.gd: -------------------------------------------------------------------------------- 1 | class_name RPCGdUnitEvent 2 | extends RPC 3 | 4 | var _event :Dictionary 5 | 6 | static func of(event :GdUnitEvent) -> RPCGdUnitEvent: 7 | var rpc = load("res://addons/gdUnit3/src/network/rpc/RPCGdUnitEvent.gd").new() 8 | rpc._event = event.serialize() 9 | return rpc 10 | 11 | func event() -> GdUnitEvent: 12 | return GdUnitEvent.new().deserialize(_event) 13 | 14 | func to_string(): 15 | return "RPCGdUnitEvent: " + str(_event) 16 | -------------------------------------------------------------------------------- /addons/gdUnit3/src/network/rpc/RPCGdUnitTestSuite.gd: -------------------------------------------------------------------------------- 1 | class_name RPCGdUnitTestSuite 2 | extends RPC 3 | 4 | var _data :Dictionary 5 | 6 | static func of(test_suite) -> RPCGdUnitTestSuite: 7 | var rpc = load("res://addons/gdUnit3/src/network/rpc/RPCGdUnitTestSuite.gd").new() 8 | rpc._data = GdUnitTestSuiteDto.new().serialize(test_suite) 9 | return rpc 10 | 11 | func dto() -> GdUnitResourceDto: 12 | return GdUnitTestSuiteDto.new().deserialize(_data) 13 | 14 | func to_string(): 15 | return "RPCGdUnitTestSuite: " + str(_data) 16 | -------------------------------------------------------------------------------- /addons/gdUnit3/src/network/rpc/RPCMessage.gd: -------------------------------------------------------------------------------- 1 | class_name RPCMessage 2 | extends RPC 3 | 4 | var _message :String 5 | 6 | static func of(message :String) -> RPCMessage: 7 | var rpc = load("res://addons/gdUnit3/src/network/rpc/RPCMessage.gd").new() 8 | rpc._message = message 9 | return rpc 10 | 11 | func message() -> String: 12 | return _message 13 | 14 | func to_string(): 15 | return "RPCMessage: " + _message 16 | -------------------------------------------------------------------------------- /addons/gdUnit3/src/network/rpc/dtos/GdUnitResourceDto.gd: -------------------------------------------------------------------------------- 1 | class_name GdUnitResourceDto 2 | extends Resource 3 | 4 | var _name :String 5 | var _path :String 6 | 7 | func serialize(resource) -> Dictionary: 8 | var serialized := Dictionary() 9 | serialized["name"] = resource.get_name() 10 | serialized["resource_path"] = resource.ResourcePath() 11 | return serialized 12 | 13 | func deserialize(data :Dictionary) -> GdUnitResourceDto: 14 | _name = data.get("name", "n.a.") 15 | _path = data.get("resource_path", "") 16 | return self 17 | 18 | func name() -> String: 19 | return _name 20 | 21 | func path() -> String: 22 | return _path 23 | -------------------------------------------------------------------------------- /addons/gdUnit3/src/network/rpc/dtos/GdUnitTestCaseDto.gd: -------------------------------------------------------------------------------- 1 | class_name GdUnitTestCaseDto 2 | extends GdUnitResourceDto 3 | 4 | var _line_number :int = -1 5 | var _test_case_names :PoolStringArray = [] 6 | 7 | func serialize(test_case) -> Dictionary: 8 | var serialized := .serialize(test_case) 9 | if test_case.has_method("line_number"): 10 | serialized["line_number"] = test_case.line_number() 11 | else: 12 | serialized["line_number"] = test_case.get("LineNumber") 13 | if test_case.has_method("test_case_names"): 14 | serialized["test_case_names"] = test_case.test_case_names() 15 | return serialized 16 | 17 | func deserialize(data :Dictionary) -> GdUnitResourceDto: 18 | .deserialize(data) 19 | _line_number = data.get("line_number", -1) 20 | _test_case_names = data.get("test_case_names", []) 21 | return self 22 | 23 | func line_number() -> int: 24 | return _line_number 25 | 26 | func test_case_names() -> PoolStringArray: 27 | return _test_case_names 28 | -------------------------------------------------------------------------------- /addons/gdUnit3/src/network/rpc/dtos/GdUnitTestSuiteDto.gd: -------------------------------------------------------------------------------- 1 | class_name GdUnitTestSuiteDto 2 | extends GdUnitResourceDto 3 | 4 | var _test_cases_by_name := Dictionary() 5 | 6 | func serialize(test_suite :Node) -> Dictionary: 7 | var serialized := .serialize(test_suite) 8 | var test_cases := Array() 9 | serialized["test_cases"] = test_cases 10 | for test_case in test_suite.get_children(): 11 | test_cases.append(GdUnitTestCaseDto.new().serialize(test_case)) 12 | return serialized 13 | 14 | func deserialize(data :Dictionary) -> GdUnitResourceDto: 15 | .deserialize(data) 16 | var test_cases :Array = data.get("test_cases", Array()) 17 | for test_case in test_cases: 18 | add_test_case(GdUnitTestCaseDto.new().deserialize(test_case)) 19 | return self 20 | 21 | func add_test_case(test_case :GdUnitTestCaseDto): 22 | _test_cases_by_name[test_case.name()] = test_case 23 | 24 | func test_case_count() -> int: 25 | return _test_cases_by_name.size() 26 | 27 | func test_cases() -> Array: 28 | return _test_cases_by_name.values() 29 | -------------------------------------------------------------------------------- /addons/gdUnit3/src/report/GdUnitByPathReport.gd: -------------------------------------------------------------------------------- 1 | class_name GdUnitByPathReport 2 | extends GdUnitReportSummary 3 | 4 | func _init(path :String, reports :Array): 5 | _resource_path = path 6 | _reports = reports 7 | 8 | static func sort_reports_by_path(reports :Array) -> Dictionary: 9 | var by_path := Dictionary() 10 | for report in reports: 11 | var suite_path :String = report.path() 12 | var suite_report :Array = by_path.get(suite_path, Array()) 13 | suite_report.append(report) 14 | by_path[suite_path] = suite_report 15 | return by_path 16 | 17 | func path() -> String: 18 | return _resource_path.replace("res://", "") 19 | 20 | func create_record(report_link :String) -> String: 21 | return GdUnitHtmlPatterns.build(GdUnitHtmlPatterns.TABLE_RECORD_PATH, self, report_link) 22 | 23 | func write(report_dir :String) -> String: 24 | var template := GdUnitHtmlPatterns.load_template("res://addons/gdUnit3/src/report/template/folder_report.html") 25 | var path_report := GdUnitHtmlPatterns.build(template, self, "") 26 | path_report = apply_testsuite_reports(report_dir, path_report, _reports) 27 | 28 | var output_path := "%s/path/%s.html" % [report_dir, path().replace("/", ".")] 29 | var dir := output_path.get_base_dir() 30 | var dest_dir := Directory.new() 31 | if not dest_dir.dir_exists(dir): 32 | dest_dir.make_dir_recursive(dir) 33 | 34 | var file := File.new() 35 | file.open(output_path, File.WRITE) 36 | file.store_string(path_report) 37 | file.close() 38 | return output_path 39 | 40 | static func apply_testsuite_reports(report_dir :String, template :String, reports :Array) -> String: 41 | var table_records := PoolStringArray() 42 | 43 | for report in reports: 44 | var report_link = report.output_path(report_dir).replace(report_dir, "..") 45 | table_records.append(report.create_record(report_link)) 46 | return template.replace(GdUnitHtmlPatterns.TABLE_BY_TESTSUITES, table_records.join("\n")) 47 | -------------------------------------------------------------------------------- /addons/gdUnit3/src/report/GdUnitHtmlReport.gd: -------------------------------------------------------------------------------- 1 | class_name GdUnitHtmlReport 2 | extends GdUnitReportSummary 3 | 4 | const REPORT_DIR_PREFIX = "report_" 5 | 6 | var _report_path :String 7 | var _iteration :int 8 | 9 | func _init(path :String): 10 | _iteration = GdUnitTools.find_last_path_index(path, REPORT_DIR_PREFIX) + 1 11 | _report_path = "%s/%s%d" % [path, REPORT_DIR_PREFIX, _iteration] 12 | 13 | func add_testsuite_report(suite_report :GdUnitTestSuiteReport): 14 | _reports.append(suite_report) 15 | 16 | func add_testcase_report(resource_path :String, suite_report :GdUnitTestCaseReport) -> void: 17 | for report in _reports: 18 | if report.resource_path() == resource_path: 19 | report.add_report(suite_report) 20 | 21 | func update_test_suite_report(resource_path :String, duration :int) -> void: 22 | for report in _reports: 23 | if report.resource_path() == resource_path: 24 | report.set_duration(duration) 25 | 26 | func update_testcase_report(resource_path :String, test_report :GdUnitTestCaseReport): 27 | for report in _reports: 28 | if report.resource_path() == resource_path: 29 | report.update(test_report) 30 | 31 | func write() -> String: 32 | var template := GdUnitHtmlPatterns.load_template("res://addons/gdUnit3/src/report/template/index.html") 33 | var to_write = GdUnitHtmlPatterns.build(template, self, "") 34 | to_write = apply_path_reports(_report_path, to_write, _reports) 35 | to_write = apply_testsuite_reports(_report_path, to_write, _reports) 36 | 37 | # write report 38 | var index_file := "%s/index.html" % _report_path 39 | var file := File.new() 40 | file.open(index_file, File.WRITE) 41 | file.store_string(to_write) 42 | file.close() 43 | GdUnitTools.copy_directory("res://addons/gdUnit3/src/report/template/css/", _report_path + "/css") 44 | return index_file 45 | 46 | func delete_history(max_reports :int) -> int: 47 | return GdUnitTools.delete_path_index_lower_equals_than(_report_path.get_base_dir(), REPORT_DIR_PREFIX, _iteration-max_reports) 48 | 49 | static func apply_path_reports(report_dir :String, template :String, reports :Array) -> String: 50 | var path_report_mapping := GdUnitByPathReport.sort_reports_by_path(reports) 51 | var table_records := PoolStringArray() 52 | var paths := path_report_mapping.keys() 53 | paths.sort() 54 | for path in paths: 55 | var report := GdUnitByPathReport.new(path, path_report_mapping.get(path)) 56 | var report_link :String = report.write(report_dir).replace(report_dir, ".") 57 | table_records.append(report.create_record(report_link)) 58 | return template.replace(GdUnitHtmlPatterns.TABLE_BY_PATHS, table_records.join("\n")) 59 | 60 | static func apply_testsuite_reports(report_dir :String, template :String, reports :Array) -> String: 61 | var table_records := PoolStringArray() 62 | 63 | for report in reports: 64 | var report_link :String = report.write(report_dir).replace(report_dir, ".") 65 | table_records.append(report.create_record(report_link)) 66 | return template.replace(GdUnitHtmlPatterns.TABLE_BY_TESTSUITES, table_records.join("\n")) 67 | 68 | func iteration() -> int: 69 | return _iteration 70 | -------------------------------------------------------------------------------- /addons/gdUnit3/src/report/GdUnitReportSummary.gd: -------------------------------------------------------------------------------- 1 | class_name GdUnitReportSummary 2 | extends Reference 3 | 4 | var _resource_path :String 5 | var _name :String 6 | var _suite_count := 0 7 | var _test_count := 0 8 | var _failure_count := 0 9 | var _error_count := 0 10 | var _orphan_count := 0 11 | var _skipped_count := 0 12 | var _duration := 0 13 | var _reports:Array = Array() 14 | 15 | func name() -> String: 16 | return _name 17 | 18 | func path() -> String: 19 | return _resource_path.get_base_dir().replace("res://", "") 20 | 21 | func resource_path() -> String: 22 | return _resource_path 23 | 24 | func suite_count() -> int: 25 | return _reports.size() 26 | 27 | func test_count() -> int: 28 | var count := _test_count 29 | for report in _reports: 30 | count += report.test_count() 31 | return count 32 | 33 | func error_count() -> int: 34 | var count := _error_count 35 | for report in _reports: 36 | count += report.error_count() 37 | return count 38 | 39 | func failure_count() -> int: 40 | var count := _failure_count 41 | for report in _reports: 42 | count += report.failure_count() 43 | return count 44 | 45 | func skipped_count() -> int: 46 | var count := _skipped_count 47 | for report in _reports: 48 | count += report.skipped_count() 49 | return count 50 | 51 | func orphan_count() -> int: 52 | var count := _orphan_count 53 | for report in _reports: 54 | count += report.orphan_count() 55 | return count 56 | 57 | func duration() -> int: 58 | var count := _duration 59 | for report in _reports: 60 | count += report.duration() 61 | return count 62 | 63 | func reports() -> Array: 64 | return _reports 65 | 66 | func add_report(report :GdUnitReportSummary) -> void: 67 | _reports.append(report) 68 | 69 | func report_state() -> String: 70 | return calculate_state(error_count(), failure_count(), orphan_count()) 71 | 72 | func succes_rate() -> String: 73 | return calculate_succes_rate(test_count(), error_count(), failure_count()) 74 | 75 | static func calculate_state(error_count :int, failure_count :int, orphan_count :int) -> String: 76 | if error_count > 0: 77 | return "error" 78 | if failure_count > 0: 79 | return "failure" 80 | if orphan_count > 0: 81 | return "warning" 82 | return "success" 83 | 84 | static func calculate_succes_rate(test_count :int, error_count: int, failure_count: int) -> String: 85 | if failure_count == 0: 86 | return "100%" 87 | return "%d" % ((test_count-failure_count-error_count) * 100 / test_count) + "%" 88 | 89 | func create_summary(report_dir :String) -> String: 90 | return "" 91 | -------------------------------------------------------------------------------- /addons/gdUnit3/src/report/GdUnitTestCaseReport.gd: -------------------------------------------------------------------------------- 1 | class_name GdUnitTestCaseReport 2 | extends GdUnitReportSummary 3 | 4 | var _suite_name :String 5 | var _failure_reports :Array 6 | var _rtf :RichTextLabel 7 | 8 | func _init(rtf :RichTextLabel, resource_path :String, suite_name :String, test_name :String, is_error := false, is_failed := false, orphans :int = 0, is_skipped := false, failure_reports :Array = [], duration :int = 0): 9 | _rtf = rtf 10 | _resource_path = resource_path 11 | _suite_name = suite_name 12 | _name = test_name 13 | _test_count = 1 14 | _error_count = is_error 15 | _failure_count = is_failed 16 | _orphan_count = orphans 17 | _skipped_count = is_skipped 18 | _failure_reports = failure_reports 19 | _duration = duration 20 | 21 | func suite_name() -> String: 22 | return _suite_name 23 | 24 | func failure_report() -> String: 25 | var html_report := "" 26 | for r in _failure_reports: 27 | var report: GdUnitReport = r 28 | # TODO convert rtf to html 29 | html_report += convert_rtf_to_text(report._to_string()) 30 | return html_report 31 | 32 | func convert_rtf_to_text(bbcode :String) -> String: 33 | _rtf.clear() 34 | _rtf.parse_bbcode(bbcode) 35 | var as_text: = _rtf.text 36 | var converted := PoolStringArray() 37 | var lines := as_text.split("\n") 38 | for line in lines: 39 | converted.append("

%s

" % line) 40 | return converted.join("\n") 41 | 42 | func create_record(report_dir :String) -> String: 43 | return GdUnitHtmlPatterns.TABLE_RECORD_TESTCASE\ 44 | .replace(GdUnitHtmlPatterns.REPORT_STATE, report_state())\ 45 | .replace(GdUnitHtmlPatterns.TESTCASE_NAME, name())\ 46 | .replace(GdUnitHtmlPatterns.SKIPPED_COUNT, str(skipped_count()))\ 47 | .replace(GdUnitHtmlPatterns.ORPHAN_COUNT, str(orphan_count()))\ 48 | .replace(GdUnitHtmlPatterns.DURATION, LocalTime.elapsed(_duration))\ 49 | .replace(GdUnitHtmlPatterns.FAILURE_REPORT, failure_report()) 50 | 51 | func update(report :GdUnitTestCaseReport) -> void: 52 | _error_count += report.error_count() 53 | _failure_count += report.failure_count() 54 | _orphan_count += report.orphan_count() 55 | _skipped_count += report.skipped_count() 56 | _failure_reports += report._failure_reports 57 | _duration += report.duration() 58 | -------------------------------------------------------------------------------- /addons/gdUnit3/src/report/GdUnitTestSuiteReport.gd: -------------------------------------------------------------------------------- 1 | class_name GdUnitTestSuiteReport 2 | extends GdUnitReportSummary 3 | 4 | var _time_stamp :int 5 | 6 | func _init(resource_path :String, name :String): 7 | _resource_path = resource_path 8 | _name = name 9 | _time_stamp = OS.get_unix_time() 10 | 11 | func create_record(report_link :String) -> String: 12 | return GdUnitHtmlPatterns.build(GdUnitHtmlPatterns.TABLE_RECORD_TESTSUITE, self, report_link) 13 | 14 | func output_path(report_dir :String) -> String: 15 | return "%s/test_suites/%s.%s.html" % [report_dir, path().replace("/", "."), name()] 16 | 17 | func path_as_link() -> String: 18 | return "../path/%s.html" % path().replace("/", ".") 19 | 20 | func write(report_dir :String) -> String: 21 | var template := GdUnitHtmlPatterns.load_template("res://addons/gdUnit3/src/report/template/suite_report.html") 22 | template = GdUnitHtmlPatterns.build(template, self, "")\ 23 | .replace(GdUnitHtmlPatterns.BREADCRUMP_PATH_LINK, path_as_link()) 24 | 25 | var report_output_path := output_path(report_dir) 26 | var test_report_table := PoolStringArray() 27 | for test_report in _reports: 28 | test_report_table.append(test_report.create_record(report_output_path)) 29 | 30 | template = template.replace(GdUnitHtmlPatterns.TABLE_BY_TESTCASES, test_report_table.join("\n")) 31 | 32 | var dir := report_output_path.get_base_dir() 33 | var dest_dir := Directory.new() 34 | if not dest_dir.dir_exists(dir): 35 | dest_dir.make_dir_recursive(dir) 36 | 37 | var file := File.new() 38 | file.open(report_output_path, File.WRITE) 39 | file.store_string(template) 40 | file.close() 41 | return report_output_path 42 | 43 | func set_duration(duration :int) -> void: 44 | _duration = duration 45 | 46 | func time_stamp() -> int: 47 | return _time_stamp 48 | 49 | func duration() -> int: 50 | return _duration 51 | 52 | func set_skipped(skipped :int) -> void: 53 | _skipped_count = skipped 54 | 55 | func set_orphans(orphans :int) -> void: 56 | _orphan_count = orphans 57 | 58 | func set_failed(failed :bool) -> void: 59 | if failed: 60 | _failure_count += 1 61 | 62 | func update(test_report :GdUnitTestCaseReport) -> void: 63 | for report in _reports: 64 | if report.name() == test_report.name(): 65 | report.update(test_report) 66 | -------------------------------------------------------------------------------- /addons/gdUnit3/src/report/XmlElement.gd: -------------------------------------------------------------------------------- 1 | class_name XmlElement 2 | extends Reference 3 | 4 | var _name :String 5 | var _attributes :Dictionary = {} 6 | var _childs :Array = [] 7 | var _parent = null 8 | var _text :String = "" 9 | 10 | func _init(name :String): 11 | _name = name 12 | 13 | func dispose(): 14 | for child in _childs: 15 | child.dispose() 16 | _childs.clear() 17 | _attributes.clear() 18 | _parent = null 19 | 20 | func attribute(name :String, value) -> XmlElement: 21 | _attributes[name] = str(value) 22 | return self 23 | 24 | func text(text :String) -> XmlElement: 25 | _text = text if text.ends_with("\n") else text + "\n" 26 | return self 27 | 28 | func add_child(child :XmlElement) -> XmlElement: 29 | _childs.append(child) 30 | child._parent = self 31 | return self 32 | 33 | func add_childs(childs :Array) -> XmlElement: 34 | for child in childs: 35 | add_child(child) 36 | return self 37 | 38 | func _indentation() -> String: 39 | return "" if _parent == null else _parent._indentation() + " " 40 | 41 | func to_xml() -> String: 42 | var attributes := "" 43 | for key in _attributes.keys(): 44 | attributes += ' {attr}="{value}"'.format({"attr": key, "value": _attributes.get(key)}) 45 | 46 | var childs = "" 47 | for child in _childs: 48 | childs += child.to_xml() 49 | 50 | return "{_indentation}<{name}{attributes}>\n{childs}{text}{_indentation}\n"\ 51 | .format({"name": _name, 52 | "attributes": attributes, 53 | "childs": childs, 54 | "_indentation": _indentation(), 55 | "text": cdata(_text)}) 56 | 57 | func cdata(text :String) -> String: 58 | return "" if text.empty() else "\n".format({"text" : text}) 59 | -------------------------------------------------------------------------------- /addons/gdUnit3/src/report/template/css/breadcrumb.css: -------------------------------------------------------------------------------- 1 | 2 | .breadcrumb { 3 | display: flex; 4 | border-radius: 6px; 5 | overflow: hidden; 6 | height: 45px; 7 | z-index: 1; 8 | background-color: #055d9c; 9 | font-weight: bold; 10 | font-size: 16px; 11 | margin-top: 0px; 12 | margin-bottom: 20px; 13 | box-shadow: 0 0 3px black; 14 | } 15 | 16 | .breadcrumb a { 17 | position: relative; 18 | display: flex; 19 | -ms-flex-positive: 1; 20 | flex-grow: 1; 21 | text-decoration: none; 22 | margin: auto; 23 | height: 100%; 24 | color: white; 25 | } 26 | 27 | .breadcrumb a:first-child { 28 | padding-left: 5.2px; 29 | } 30 | 31 | .breadcrumb a:last-child { 32 | padding-right: 5.2px; 33 | } 34 | 35 | .breadcrumb a:after { 36 | content: ""; 37 | position: absolute; 38 | display: inline-block; 39 | width: 45px; 40 | height: 45px; 41 | top: 0; 42 | right: -20px; 43 | background-color: #055d9c; 44 | border-top-right-radius: 5px; 45 | transform: scale(0.707) rotate(45deg); 46 | box-shadow: 2px -2px rgba(0,0,0,0.25); 47 | z-index: 1; 48 | } 49 | 50 | .breadcrumb a:last-child:after { 51 | content: none; 52 | } 53 | 54 | .breadcrumb a.active, .breadcrumb a:hover { 55 | background: #347bad; 56 | color: white; 57 | text-decoration: underline; 58 | } 59 | 60 | .breadcrumb a.active:after, .breadcrumb a:hover:after { 61 | background: #347bad; 62 | } 63 | 64 | .breadcrumb span { 65 | margin:inherit; 66 | z-index: 2; 67 | } 68 | -------------------------------------------------------------------------------- /addons/gdUnit3/src/report/template/css/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MikeSchulze/gdUnit3/87a1189b6693257563fb4b761f8b161322a8deb6/addons/gdUnit3/src/report/template/css/icon.png -------------------------------------------------------------------------------- /addons/gdUnit3/src/report/template/folder_report.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Testsuite Results 7 | 8 | 9 | 10 | 11 | 12 | 15 | 16 |
17 |
18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 |
TestSuites${suite_count}
Tests${test_count}
Skipped${skipped_count}
Failures${failure_count}
Orphans${orphan_count}
Duration${duration}
45 |
46 |
47 |

Success Rate

48 | 49 | 50 | 51 | 52 |
${success_percent}
53 |
54 |
55 |

History

56 |

Comming Next

57 |
58 |
59 | 60 | 64 | 65 |
66 |
67 | 68 | 69 | 70 |
71 |
72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | ${report_table_testsuites} 86 | 87 |
TestsSkippedFailuresOrphansDurationSuccess rate
88 |
89 |
90 |
91 |
92 |
93 | 94 |
95 |

Generated byGdUnit3 at ${buid_date}

96 |
97 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /addons/gdUnit3/src/spy/GdUnitSpyImpl.gd: -------------------------------------------------------------------------------- 1 | # warnings-disable 2 | # warning-ignore:unused_argument 3 | class_name GdUnitSpyImpl 4 | 5 | var __instance_delegator 6 | 7 | # self reference holder, use this kind of hack to store static function calls 8 | # it is important to manually free by '__release_double' otherwise it ends up in orphan instance 9 | const __self := [] 10 | 11 | func __set_singleton(instance): 12 | # store self need to mock static functions 13 | __self.append(self) 14 | __instance_delegator = instance 15 | 16 | func __release_double(): 17 | # we need to release the self reference manually to prevent orphan nodes 18 | __self.clear() 19 | __instance_delegator = null 20 | 21 | -------------------------------------------------------------------------------- /addons/gdUnit3/src/ui/GdUnitFonts.gd: -------------------------------------------------------------------------------- 1 | class_name GdUnitFonts 2 | extends Reference 3 | 4 | const FONT_MONO = "res://addons/gdUnit3/src/update/assets/fonts/static/RobotoMono-Regular.ttf" 5 | const FONT_MONO_BOLT = "res://addons/gdUnit3/src/update/assets/fonts/static/RobotoMono-Bold.ttf" 6 | const FONT_MONO_BOLT_ITALIC = "res://addons/gdUnit3/src/update/assets/fonts/static/RobotoMono-BoldItalic.ttf" 7 | const FONT_MONO_ITALIC = "res://addons/gdUnit3/src/update/assets/fonts/static/RobotoMono-Italic.ttf" 8 | 9 | 10 | static func init_fonts(item: CanvasItem) -> float: 11 | # add a defauld fallback font 12 | item.set("custom_fonts/font", create_font(FONT_MONO, 16)) 13 | if Engine.editor_hint: 14 | var plugin :EditorPlugin = Engine.get_meta("GdUnitEditorPlugin") 15 | var settings := plugin.get_editor_interface().get_editor_settings() 16 | var scale_factor := plugin.get_editor_interface().get_editor_scale() 17 | var font_size = settings.get_setting("interface/editor/main_font_size") 18 | font_size *= scale_factor 19 | var font_mono := create_font(FONT_MONO, font_size) 20 | item.set("custom_fonts/font", font_mono) 21 | item.set("custom_fonts/mono_font", font_mono) 22 | item.set("custom_fonts/normal_font", font_mono) 23 | item.set("custom_fonts/bold_font", create_font(FONT_MONO_BOLT, font_size)) 24 | item.set("custom_fonts/bold_italics_font", create_font(FONT_MONO_BOLT_ITALIC, font_size)) 25 | item.set("custom_fonts/italics_font", create_font(FONT_MONO_ITALIC, font_size)) 26 | return font_size 27 | return 16.0 28 | 29 | static func create_font(font_resource: String, size: float) -> Font: 30 | var font = DynamicFont.new() 31 | font.font_data = load(font_resource) 32 | font.size = size 33 | return font 34 | -------------------------------------------------------------------------------- /addons/gdUnit3/src/ui/assets/Play.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /addons/gdUnit3/src/ui/assets/Stop.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /addons/gdUnit3/src/ui/assets/TestCase.svg: -------------------------------------------------------------------------------- 1 | 2 | 16 | 18 | 19 | 21 | image/svg+xml 22 | 24 | 25 | 26 | 27 | 28 | 30 | 51 | 58 | 62 | 63 | -------------------------------------------------------------------------------- /addons/gdUnit3/src/ui/assets/TestCaseError.svg: -------------------------------------------------------------------------------- 1 | 2 | 16 | 18 | 19 | 21 | image/svg+xml 22 | 24 | 25 | 26 | 27 | 28 | 30 | 50 | 57 | 61 | 65 | 66 | -------------------------------------------------------------------------------- /addons/gdUnit3/src/ui/assets/TestCaseFailed.svg: -------------------------------------------------------------------------------- 1 | 2 | 16 | 18 | 19 | 21 | image/svg+xml 22 | 24 | 25 | 26 | 27 | 28 | 30 | 50 | 57 | 61 | 66 | 67 | -------------------------------------------------------------------------------- /addons/gdUnit3/src/ui/assets/TestCaseSuccess.svg: -------------------------------------------------------------------------------- 1 | 2 | 16 | 18 | 19 | 21 | image/svg+xml 22 | 24 | 25 | 26 | 27 | 28 | 30 | 50 | 57 | 61 | 66 | 67 | -------------------------------------------------------------------------------- /addons/gdUnit3/src/ui/assets/TestCase_error_orphan.tres: -------------------------------------------------------------------------------- 1 | [gd_resource type="AnimatedTexture" load_steps=3 format=2] 2 | 3 | [ext_resource path="res://addons/gdUnit3/src/ui/assets/orphan/TestCaseError1.svg" type="Texture" id=1] 4 | [ext_resource path="res://addons/gdUnit3/src/ui/assets/orphan/TestCaseError2.svg" type="Texture" id=2] 5 | 6 | [resource] 7 | flags = 4 8 | frames = 2 9 | fps = 1.1 10 | frame_0/texture = ExtResource( 1 ) 11 | frame_1/texture = ExtResource( 2 ) 12 | frame_1/delay_sec = 0.0 13 | -------------------------------------------------------------------------------- /addons/gdUnit3/src/ui/assets/TestCase_failed_orphan.tres: -------------------------------------------------------------------------------- 1 | [gd_resource type="AnimatedTexture" load_steps=3 format=2] 2 | 3 | [ext_resource path="res://addons/gdUnit3/src/ui/assets/orphan/TestCaseFailed2.svg" type="Texture" id=1] 4 | [ext_resource path="res://addons/gdUnit3/src/ui/assets/orphan/TestCaseFailed1.svg" type="Texture" id=2] 5 | 6 | [resource] 7 | flags = 4 8 | frames = 2 9 | fps = 1.1 10 | frame_0/texture = ExtResource( 2 ) 11 | frame_1/texture = ExtResource( 1 ) 12 | frame_1/delay_sec = 0.0 13 | -------------------------------------------------------------------------------- /addons/gdUnit3/src/ui/assets/TestCase_success_orphan.tres: -------------------------------------------------------------------------------- 1 | [gd_resource type="AnimatedTexture" load_steps=3 format=2] 2 | 3 | [ext_resource path="res://addons/gdUnit3/src/ui/assets/orphan/TestCaseSuccess1.svg" type="Texture" id=1] 4 | [ext_resource path="res://addons/gdUnit3/src/ui/assets/orphan/TestCaseSuccess2.svg" type="Texture" id=2] 5 | 6 | [resource] 7 | flags = 4 8 | frames = 2 9 | fps = 1.1 10 | frame_0/texture = ExtResource( 1 ) 11 | frame_1/texture = ExtResource( 2 ) 12 | frame_1/delay_sec = 0.0 13 | -------------------------------------------------------------------------------- /addons/gdUnit3/src/ui/assets/debug.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /addons/gdUnit3/src/ui/assets/errors.svg: -------------------------------------------------------------------------------- 1 | 2 | 16 | 18 | 19 | 21 | image/svg+xml 22 | 24 | 25 | 26 | 27 | 28 | 30 | 52 | 57 | 58 | -------------------------------------------------------------------------------- /addons/gdUnit3/src/ui/assets/failures.svg: -------------------------------------------------------------------------------- 1 | 2 | 16 | 18 | 19 | 21 | image/svg+xml 22 | 24 | 25 | 26 | 27 | 28 | 30 | 51 | 56 | 57 | -------------------------------------------------------------------------------- /addons/gdUnit3/src/ui/assets/orphan/TestCaseFailed.svg: -------------------------------------------------------------------------------- 1 | 2 | 16 | 18 | 19 | 21 | image/svg+xml 22 | 24 | 25 | 26 | 27 | 28 | 30 | 50 | 57 | 61 | 66 | 67 | -------------------------------------------------------------------------------- /addons/gdUnit3/src/ui/assets/orphan/orphan_animated_icon.tres: -------------------------------------------------------------------------------- 1 | [gd_resource type="AnimatedTexture" load_steps=7 format=2] 2 | 3 | [ext_resource path="res://addons/gdUnit3/src/ui/assets/orphan/orphan_red4.svg" type="Texture" id=1] 4 | [ext_resource path="res://addons/gdUnit3/src/ui/assets/orphan/orphan_red5.svg" type="Texture" id=2] 5 | [ext_resource path="res://addons/gdUnit3/src/ui/assets/orphan/orphan_red6.svg" type="Texture" id=3] 6 | [ext_resource path="res://addons/gdUnit3/src/ui/assets/orphan/orphan_red3.svg" type="Texture" id=4] 7 | [ext_resource path="res://addons/gdUnit3/src/ui/assets/orphan/orphan_red2.svg" type="Texture" id=5] 8 | [ext_resource path="res://addons/gdUnit3/src/ui/assets/orphan/orphan_red7.svg" type="Texture" id=6] 9 | 10 | [resource] 11 | flags = 4 12 | frames = 10 13 | fps = 9.0 14 | frame_0/texture = ExtResource( 5 ) 15 | frame_1/texture = ExtResource( 4 ) 16 | frame_1/delay_sec = 0.0 17 | frame_2/texture = ExtResource( 1 ) 18 | frame_2/delay_sec = 0.0 19 | frame_3/texture = ExtResource( 2 ) 20 | frame_3/delay_sec = 0.0 21 | frame_4/texture = ExtResource( 3 ) 22 | frame_4/delay_sec = 0.0 23 | frame_5/texture = ExtResource( 6 ) 24 | frame_5/delay_sec = 0.5 25 | frame_6/texture = ExtResource( 3 ) 26 | frame_6/delay_sec = 0.0 27 | frame_7/texture = ExtResource( 2 ) 28 | frame_7/delay_sec = 0.0 29 | frame_8/texture = ExtResource( 1 ) 30 | frame_8/delay_sec = 0.0 31 | frame_9/texture = ExtResource( 4 ) 32 | frame_9/delay_sec = 0.0 33 | -------------------------------------------------------------------------------- /addons/gdUnit3/src/ui/assets/running.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MikeSchulze/gdUnit3/87a1189b6693257563fb4b761f8b161322a8deb6/addons/gdUnit3/src/ui/assets/running.png -------------------------------------------------------------------------------- /addons/gdUnit3/src/ui/assets/spinner.tres: -------------------------------------------------------------------------------- 1 | [gd_resource type="AnimatedTexture" load_steps=9 format=2] 2 | 3 | [ext_resource path="res://addons/gdUnit3/src/ui/assets/spinner/Progress7.svg" type="Texture" id=1] 4 | [ext_resource path="res://addons/gdUnit3/src/ui/assets/spinner/Progress5.svg" type="Texture" id=2] 5 | [ext_resource path="res://addons/gdUnit3/src/ui/assets/spinner/Progress1.svg" type="Texture" id=3] 6 | [ext_resource path="res://addons/gdUnit3/src/ui/assets/spinner/Progress8.svg" type="Texture" id=4] 7 | [ext_resource path="res://addons/gdUnit3/src/ui/assets/spinner/Progress2.svg" type="Texture" id=5] 8 | [ext_resource path="res://addons/gdUnit3/src/ui/assets/spinner/Progress6.svg" type="Texture" id=6] 9 | [ext_resource path="res://addons/gdUnit3/src/ui/assets/spinner/Progress3.svg" type="Texture" id=7] 10 | [ext_resource path="res://addons/gdUnit3/src/ui/assets/spinner/Progress4.svg" type="Texture" id=8] 11 | 12 | [resource] 13 | flags = 4 14 | frames = 8 15 | fps = 29.1 16 | frame_0/texture = ExtResource( 3 ) 17 | frame_1/texture = ExtResource( 5 ) 18 | frame_1/delay_sec = 0.0 19 | frame_2/texture = ExtResource( 7 ) 20 | frame_2/delay_sec = 0.0 21 | frame_3/texture = ExtResource( 8 ) 22 | frame_3/delay_sec = 0.0 23 | frame_4/texture = ExtResource( 2 ) 24 | frame_4/delay_sec = 0.0 25 | frame_5/texture = ExtResource( 6 ) 26 | frame_5/delay_sec = 0.0 27 | frame_6/texture = ExtResource( 1 ) 28 | frame_6/delay_sec = 0.0 29 | frame_7/texture = ExtResource( 4 ) 30 | frame_7/delay_sec = 0.0 31 | -------------------------------------------------------------------------------- /addons/gdUnit3/src/ui/assets/spinner/Progress1.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /addons/gdUnit3/src/ui/assets/spinner/Progress2.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /addons/gdUnit3/src/ui/assets/spinner/Progress3.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /addons/gdUnit3/src/ui/assets/spinner/Progress4.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /addons/gdUnit3/src/ui/assets/spinner/Progress5.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /addons/gdUnit3/src/ui/assets/spinner/Progress6.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /addons/gdUnit3/src/ui/assets/spinner/Progress7.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /addons/gdUnit3/src/ui/assets/spinner/Progress8.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /addons/gdUnit3/src/ui/parts/InspectorMonitor.gd: -------------------------------------------------------------------------------- 1 | tool 2 | extends PanelContainer 3 | 4 | signal jump_to_orphan_nodes 5 | 6 | onready var ICON_GREEN = load("res://addons/gdUnit3/src/ui/assets/orphan/orphan_green.svg") 7 | onready var ICON_RED = load("res://addons/gdUnit3/src/ui/assets/orphan/orphan_animated_icon.tres") 8 | 9 | onready var _time = $GridContainer/Time/value 10 | onready var _orphans = $GridContainer/Orphan/value 11 | onready var _orphan_button := $GridContainer/Orphan/ToolButton 12 | 13 | onready var _signal_handler :SignalHandler = GdUnitSingleton.get_singleton(SignalHandler.SINGLETON_NAME) 14 | 15 | var total_elapsed_time := 0 16 | var total_orphans := 0 17 | 18 | func _ready(): 19 | _signal_handler.register_on_gdunit_events(self, "_on_event") 20 | _time.text = "" 21 | _orphans.text = "0" 22 | 23 | func status_changed(elapsed_time :int, orphan_nodes :int): 24 | total_elapsed_time += elapsed_time 25 | total_orphans += orphan_nodes 26 | _time.text = LocalTime.elapsed(total_elapsed_time) 27 | _orphans.text = str(total_orphans) 28 | if total_orphans > 0: 29 | _orphan_button.icon = ICON_RED 30 | 31 | func _on_event(event :GdUnitEvent) -> void: 32 | match event.type(): 33 | GdUnitEvent.INIT: 34 | _orphan_button.icon = ICON_GREEN 35 | total_elapsed_time = 0 36 | total_orphans = 0 37 | status_changed(0, 0) 38 | GdUnitEvent.TESTCASE_BEFORE: 39 | pass 40 | GdUnitEvent.TESTCASE_AFTER: 41 | status_changed(0, event.orphan_nodes()) 42 | GdUnitEvent.TESTSUITE_BEFORE: 43 | pass 44 | GdUnitEvent.TESTSUITE_AFTER: 45 | status_changed(event.elapsed_time(), event.orphan_nodes()) 46 | 47 | func _on_ToolButton_pressed(): 48 | emit_signal("jump_to_orphan_nodes") 49 | -------------------------------------------------------------------------------- /addons/gdUnit3/src/ui/parts/InspectorMonitor.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=4 format=2] 2 | 3 | [ext_resource path="res://addons/gdUnit3/src/ui/assets/orphan/orphan_green.svg" type="Texture" id=1] 4 | [ext_resource path="res://addons/gdUnit3/src/ui/assets/clock.svg" type="Texture" id=2] 5 | [ext_resource path="res://addons/gdUnit3/src/ui/parts/InspectorMonitor.gd" type="Script" id=3] 6 | 7 | [node name="Monitor" type="PanelContainer"] 8 | anchor_right = 1.0 9 | anchor_bottom = 1.0 10 | margin_right = -793.0 11 | margin_bottom = -564.0 12 | rect_clip_content = true 13 | size_flags_horizontal = 9 14 | size_flags_vertical = 9 15 | script = ExtResource( 3 ) 16 | __meta__ = { 17 | "_edit_use_anchors_": false 18 | } 19 | 20 | [node name="GridContainer" type="GridContainer" parent="."] 21 | margin_left = 7.0 22 | margin_top = 7.0 23 | margin_right = 224.0 24 | margin_bottom = 29.0 25 | rect_clip_content = true 26 | size_flags_horizontal = 9 27 | columns = 2 28 | 29 | [node name="Time" type="GridContainer" parent="GridContainer"] 30 | margin_right = 63.0 31 | margin_bottom = 22.0 32 | rect_clip_content = true 33 | columns = 2 34 | 35 | [node name="ToolButton" type="ToolButton" parent="GridContainer/Time"] 36 | margin_right = 59.0 37 | margin_bottom = 22.0 38 | hint_tooltip = "Shows the total elapsed time of test execution." 39 | size_flags_horizontal = 3 40 | text = "Time" 41 | icon = ExtResource( 2 ) 42 | align = 0 43 | 44 | [node name="value" type="Label" parent="GridContainer/Time"] 45 | use_parent_material = true 46 | margin_left = 63.0 47 | margin_right = 63.0 48 | margin_bottom = 22.0 49 | size_flags_horizontal = 3 50 | size_flags_vertical = 1 51 | align = 2 52 | max_lines_visible = 1 53 | __meta__ = { 54 | "_edit_use_anchors_": false 55 | } 56 | 57 | [node name="Orphan" type="GridContainer" parent="GridContainer"] 58 | margin_left = 67.0 59 | margin_right = 160.0 60 | margin_bottom = 22.0 61 | rect_clip_content = true 62 | size_flags_horizontal = 9 63 | columns = 2 64 | 65 | [node name="ToolButton" type="ToolButton" parent="GridContainer/Orphan"] 66 | margin_right = 81.0 67 | margin_bottom = 22.0 68 | rect_clip_content = true 69 | hint_tooltip = "Shows the total detected orphan nodes. 70 | 71 | (Click) to jump to test." 72 | size_flags_horizontal = 9 73 | size_flags_vertical = 3 74 | text = "Orphans" 75 | icon = ExtResource( 1 ) 76 | align = 0 77 | 78 | [node name="value" type="Label" parent="GridContainer/Orphan"] 79 | use_parent_material = true 80 | margin_left = 85.0 81 | margin_right = 93.0 82 | margin_bottom = 22.0 83 | size_flags_horizontal = 3 84 | size_flags_vertical = 1 85 | text = "0" 86 | align = 2 87 | max_lines_visible = 1 88 | __meta__ = { 89 | "_edit_use_anchors_": false 90 | } 91 | [connection signal="pressed" from="GridContainer/Orphan/ToolButton" to="." method="_on_ToolButton_pressed"] 92 | -------------------------------------------------------------------------------- /addons/gdUnit3/src/ui/parts/InspectorProgressBar.gd: -------------------------------------------------------------------------------- 1 | tool 2 | extends ProgressBar 3 | 4 | onready var bar = $"." 5 | onready var status = $Label 6 | onready var style :StyleBoxFlat = bar.get("custom_styles/fg") 7 | 8 | onready var _signal_handler :SignalHandler = GdUnitSingleton.get_singleton(SignalHandler.SINGLETON_NAME) 9 | 10 | func _ready(): 11 | _signal_handler.register_on_gdunit_events(self, "_on_event") 12 | style.bg_color = Color.darkgreen 13 | var plugin := EditorPlugin.new() 14 | var settings := plugin.get_editor_interface().get_editor_settings() 15 | var font_size = settings.get_setting("interface/editor/main_font_size") 16 | bar.rect_min_size.y = font_size + 4 * plugin.get_editor_interface().get_editor_scale() 17 | plugin.free() 18 | 19 | func progress_init(max_value :int) -> void: 20 | bar.value = 0 21 | bar.max_value = max_value 22 | style.bg_color = Color.darkgreen 23 | 24 | func progress_update(value :int, failed :int, max_value :int = -1) -> void: 25 | bar.value += value 26 | status.text = str(bar.value) + ":" + str(bar.max_value) 27 | # if faild change color to red 28 | if failed > 0: 29 | var style:StyleBoxFlat = bar.get("custom_styles/fg") 30 | style.bg_color = Color.darkred 31 | 32 | func _on_event(event :GdUnitEvent) -> void: 33 | match event.type(): 34 | GdUnitEvent.INIT: 35 | progress_init(event.total_count()) 36 | GdUnitEvent.TESTCASE_BEFORE: 37 | pass 38 | GdUnitEvent.TESTCASE_AFTER: 39 | progress_update(1, event.is_failed()) 40 | GdUnitEvent.TESTSUITE_BEFORE: 41 | pass 42 | GdUnitEvent.TESTSUITE_AFTER: 43 | progress_update(0, event.is_failed()) 44 | pass 45 | -------------------------------------------------------------------------------- /addons/gdUnit3/src/ui/parts/InspectorProgressBar.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=3 format=2] 2 | 3 | [ext_resource path="res://addons/gdUnit3/src/ui/parts/InspectorProgressBar.gd" type="Script" id=1] 4 | 5 | [sub_resource type="StyleBoxFlat" id=1] 6 | bg_color = Color( 0, 0.39, 0, 1 ) 7 | 8 | [node name="ProgressBar" type="ProgressBar"] 9 | anchor_right = 1.0 10 | anchor_bottom = 1.0 11 | grow_horizontal = 2 12 | grow_vertical = 2 13 | rect_min_size = Vector2( 0, 22 ) 14 | size_flags_horizontal = 11 15 | size_flags_vertical = 9 16 | custom_styles/fg = SubResource( 1 ) 17 | min_value = -1.0 18 | max_value = 0.0 19 | value = -1.0 20 | rounded = true 21 | allow_lesser = true 22 | percent_visible = false 23 | script = ExtResource( 1 ) 24 | __meta__ = { 25 | "_edit_use_anchors_": false 26 | } 27 | 28 | [node name="Label" type="Label" parent="."] 29 | use_parent_material = true 30 | anchor_left = 0.5 31 | anchor_top = 0.5 32 | anchor_right = 0.5 33 | anchor_bottom = 0.5 34 | margin_left = -10.0 35 | margin_top = -7.0 36 | margin_right = 10.0 37 | margin_bottom = 7.0 38 | grow_horizontal = 2 39 | grow_vertical = 2 40 | size_flags_vertical = 3 41 | text = "0:0" 42 | align = 1 43 | valign = 1 44 | max_lines_visible = 1 45 | __meta__ = { 46 | "_edit_use_anchors_": false 47 | } 48 | -------------------------------------------------------------------------------- /addons/gdUnit3/src/ui/parts/InspectorStatusBar.gd: -------------------------------------------------------------------------------- 1 | tool 2 | extends PanelContainer 3 | 4 | signal failure_next 5 | signal failure_prevous 6 | 7 | onready var _errors = $GridContainer/Errors/value 8 | onready var _failures = $GridContainer/Failures/value 9 | onready var _button_failure_up := $GridContainer/Failures/buttons/failure_up 10 | onready var _button_failure_down := $GridContainer/Failures/buttons/failure_down 11 | 12 | onready var _signal_handler :SignalHandler = GdUnitSingleton.get_singleton(SignalHandler.SINGLETON_NAME) 13 | 14 | var total_failed := 0 15 | var total_errors := 0 16 | 17 | func _ready(): 18 | _signal_handler.register_on_gdunit_events(self, "_on_event") 19 | _failures.text = "0" 20 | _errors.text = "0" 21 | 22 | var editor :EditorPlugin = Engine.get_meta("GdUnitEditorPlugin") 23 | var editiorTheme := editor.get_editor_interface().get_base_control().theme 24 | _button_failure_up.icon = editiorTheme.get_icon("ArrowUp", "EditorIcons") 25 | _button_failure_down.icon = editiorTheme.get_icon("ArrowDown", "EditorIcons") 26 | 27 | 28 | func status_changed(errors :int, failed :int): 29 | total_failed += failed 30 | total_errors += errors 31 | _failures.text = str(total_failed) 32 | _errors.text = str(total_errors) 33 | 34 | func _on_event(event :GdUnitEvent) -> void: 35 | match event.type(): 36 | GdUnitEvent.INIT: 37 | total_failed = 0 38 | total_errors = 0 39 | status_changed(0, 0) 40 | GdUnitEvent.TESTCASE_BEFORE: 41 | pass 42 | GdUnitEvent.TESTCASE_AFTER: 43 | if event.is_error(): 44 | status_changed(event.error_count(), 0) 45 | else: 46 | status_changed(0, event.failed_count()) 47 | GdUnitEvent.TESTSUITE_BEFORE: 48 | pass 49 | GdUnitEvent.TESTSUITE_AFTER: 50 | if event.is_error(): 51 | status_changed(event.error_count(), 0) 52 | else: 53 | status_changed(0, event.failed_count()) 54 | 55 | func _on_failure_up_pressed(): 56 | emit_signal("failure_prevous") 57 | 58 | func _on_failure_down_pressed(): 59 | emit_signal("failure_next") 60 | -------------------------------------------------------------------------------- /addons/gdUnit3/src/ui/parts/InspectorToolBar.gd: -------------------------------------------------------------------------------- 1 | tool 2 | extends HBoxContainer 3 | 4 | signal run_pressed 5 | signal stop_pressed 6 | 7 | 8 | onready var debug_icon_image :Texture = load("res://addons/gdUnit3/src/ui/assets/PlayDebug.svg") 9 | onready var _version_label := $Tools/CenterContainer/version 10 | onready var _button_wiki := $Tools/help 11 | onready var _tool_button := $Tools/tool 12 | 13 | onready var _button_run := $Tools/run 14 | onready var _button_run_debug := $Tools/debug 15 | onready var _button_stop := $Tools/stop 16 | 17 | func _ready(): 18 | GdUnit3Version.init_version_label(_version_label) 19 | var editor :EditorPlugin = Engine.get_meta("GdUnitEditorPlugin") 20 | var editiorTheme := editor.get_editor_interface().get_base_control().theme 21 | _button_run.icon = editiorTheme.get_icon("Play", "EditorIcons") 22 | _button_stop.icon = editiorTheme.get_icon("Stop", "EditorIcons") 23 | _tool_button.icon = editiorTheme.get_icon("Tools", "EditorIcons") 24 | _button_wiki.icon = editiorTheme.get_icon("HelpSearch", "EditorIcons") 25 | var scale = editor.get_editor_interface().get_editor_scale() 26 | var texture := ImageTexture.new() 27 | texture.create_from_image(debug_icon_image.get_data()) 28 | _button_run_debug.icon = texture 29 | texture.set_size_override(Vector2.ONE * scale * 16) 30 | 31 | func _on_run_pressed(debug :bool=false): 32 | emit_signal("run_pressed", debug) 33 | 34 | func _on_stop_pressed(): 35 | emit_signal("stop_pressed") 36 | 37 | func _on_GdUnit_gdunit_runner_start(): 38 | _button_run.disabled = true 39 | _button_run_debug.disabled = true 40 | _button_stop.disabled = false 41 | 42 | func _on_GdUnit_gdunit_runner_stop(client_id :int): 43 | _button_run.disabled = false 44 | _button_run_debug.disabled = false 45 | _button_stop.disabled = true 46 | 47 | func _on_wiki_pressed(): 48 | OS.shell_open("https://mikeschulze.github.io/gdUnit3/") 49 | 50 | 51 | func _on_btn_tool_pressed(): 52 | var tool_popup = load("res://addons/gdUnit3/src/ui/GdUnitToolsDialog.tscn").instance() 53 | #tool_popup.request_ready() 54 | add_child(tool_popup) 55 | 56 | -------------------------------------------------------------------------------- /addons/gdUnit3/src/ui/parts/RichTextLabelExt.gd: -------------------------------------------------------------------------------- 1 | # Custom RichTextLabel with custom background colors 2 | # MIT License 3 | # Copyright (c) 2023 Mike Schulze 4 | # https://github.com/MikeSchulze/gdUnit3/blob/master/LICENSE 5 | 6 | tool 7 | extends RichTextLabel 8 | class_name RichTextLabelExt 9 | 10 | var _effect :RichTextEffectBackground = RichTextEffectBackground.new() 11 | var _indent :int 12 | var _max_indent : int = 0 13 | 14 | func _ready(): 15 | _update_ui_settings() 16 | _effect.set_source(self) 17 | # clear effects otherwies a duplicate will result in errors 18 | set_effects([]) 19 | install_effect(_effect) 20 | 21 | func _notification(what): 22 | if what == EditorSettings.NOTIFICATION_EDITOR_SETTINGS_CHANGED: 23 | _update_ui_settings() 24 | if _effect: 25 | _effect._notification(what) 26 | 27 | func _update_ui_settings(): 28 | GdUnitFonts.init_fonts(self) 29 | updateMinSize() 30 | 31 | func set_bbcode(code) -> void: 32 | .parse_bbcode(code) 33 | _effect.reset() 34 | updateMinSize() 35 | 36 | func append_bbcode(bbcode :String): 37 | # using own ident implementation because the original has issues with ident + leading tabs 38 | if _indent != 0: 39 | var line = text.split("\n")[-1] 40 | if line.length() == 0: 41 | for i in _indent: 42 | bbcode = bbcode.indent("\t") 43 | var error := .append_bbcode(bbcode) 44 | _effect.reset() 45 | updateMinSize() 46 | return error 47 | 48 | func push_indent(indent :int) -> void: 49 | if _effect: 50 | _indent += indent 51 | if _indent > _max_indent: 52 | _max_indent = _indent 53 | 54 | func pop_indent(indent :int) -> void: 55 | if _effect: 56 | _indent -= indent 57 | 58 | # updates the label minmum size by the longest line content 59 | # to fit the full text to on line, to avoid line wrapping 60 | func updateMinSize() -> void: 61 | # reset curren min size 62 | rect_min_size.x = 0 63 | var font := get("custom_fonts/font") as Font 64 | var lines := get_text().split("\n") 65 | # calculate additional indent characters 66 | var indent_chars = _max_indent * get_tab_size() 67 | var extra_chars = ("%+" + str(indent_chars) + "s") % " " 68 | 69 | for line in lines: 70 | var line_size := font.get_string_size(line + extra_chars) 71 | if rect_min_size < line_size: 72 | rect_min_size = line_size 73 | # add extra spacing of 40px for possible scrollbar 74 | rect_min_size.x += 40 75 | 76 | -------------------------------------------------------------------------------- /addons/gdUnit3/src/update/GdUnitPatch.gd: -------------------------------------------------------------------------------- 1 | class_name GdUnitPatch 2 | extends Reference 3 | 4 | const PATCH_VERSION = "patch_version" 5 | 6 | var _version :GdUnit3Version 7 | 8 | func _init(version :GdUnit3Version): 9 | _version = version 10 | 11 | func version() -> GdUnit3Version: 12 | return _version 13 | 14 | # this function needs to be implement 15 | func execute() -> bool: 16 | push_error("The function 'execute()' is not implemented at %s" % self) 17 | return false 18 | -------------------------------------------------------------------------------- /addons/gdUnit3/src/update/GdUnitPatcher.gd: -------------------------------------------------------------------------------- 1 | class_name GdUnitPatcher 2 | extends Reference 3 | 4 | 5 | const _base_dir := "res://addons/gdUnit3/src/update/patches/" 6 | 7 | var _patches := Dictionary() 8 | 9 | func scan(current :GdUnit3Version) -> void: 10 | _scan(_base_dir, current) 11 | 12 | func _scan(scan_path :String, current :GdUnit3Version) -> void: 13 | _patches = Dictionary() 14 | var patch_paths := _collect_patch_versions(scan_path, current) 15 | for path in patch_paths: 16 | prints("scan for patches on '%s'" % path) 17 | _patches[path] = _scan_patches(path) 18 | 19 | func patch_count() -> int: 20 | var count := 0 21 | for key in _patches.keys(): 22 | count += _patches[key].size() 23 | return count 24 | 25 | func execute() -> void: 26 | for key in _patches.keys(): 27 | var patch_root :String = key 28 | for path in _patches[key]: 29 | var patch :GdUnitPatch = load(patch_root + "/" + path).new() 30 | if patch: 31 | prints("execute patch", patch.version(), patch.get_script().resource_path) 32 | if not patch.execute(): 33 | prints("error on execution patch %s" % patch_root + "/" + path) 34 | 35 | func _collect_patch_versions(scan_path :String, current :GdUnit3Version) -> PoolStringArray: 36 | var patches := Array() 37 | var dir := Directory.new() 38 | if not dir.dir_exists(scan_path): 39 | return PoolStringArray() 40 | if dir.open(scan_path) == OK: 41 | dir.list_dir_begin() 42 | var next := "." 43 | while next != "": 44 | next = dir.get_next() 45 | if next.empty() or next == "." or next == "..": 46 | continue 47 | var version := GdUnit3Version.parse(next) 48 | if version.is_greater(current): 49 | patches.append(scan_path + next) 50 | patches.sort() 51 | return PoolStringArray(patches) 52 | 53 | func _scan_patches(path :String) -> PoolStringArray: 54 | var patches := Array() 55 | var dir := Directory.new() 56 | if dir.open(path) == OK: 57 | dir.list_dir_begin() 58 | var next := "." 59 | while next != "": 60 | next = dir.get_next() 61 | if next.empty() or next == "." or next == "..": 62 | continue 63 | patches.append(next) 64 | # make sorted from lowest to high version 65 | patches.sort() 66 | return PoolStringArray(patches) 67 | -------------------------------------------------------------------------------- /addons/gdUnit3/src/update/GdUnitUpdateClient.gd: -------------------------------------------------------------------------------- 1 | tool 2 | class_name GdUnitUpdateClient 3 | extends Node 4 | 5 | signal request_completed(response) 6 | 7 | class HttpResponse: 8 | var _code :int 9 | var _body :PoolByteArray 10 | 11 | func _init(code :int, body :PoolByteArray) -> void: 12 | _code = code 13 | _body = body 14 | 15 | func code() -> int: 16 | return _code 17 | 18 | func response(): 19 | return parse_json(_body.get_string_from_utf8()) 20 | 21 | func body(): 22 | return _body 23 | 24 | var _http_request :HTTPRequest = HTTPRequest.new() 25 | 26 | func _ready(): 27 | add_child(_http_request) 28 | _http_request.connect("request_completed", self, "_on_request_completed") 29 | 30 | func _notification(what): 31 | if what == NOTIFICATION_PREDELETE: 32 | if is_instance_valid(_http_request): 33 | _http_request.queue_free() 34 | 35 | #func list_tags() -> void: 36 | # _http_request.connect("request_completed", self, "_response_request_tags") 37 | # var error = _http_request.request("https://api.github.com/repos/MikeSchulze/gdUnit3/tags") 38 | # if error != OK: 39 | # push_error("An error occurred in the HTTP request.") 40 | 41 | func request_latest_version() -> HttpResponse: 42 | yield(get_tree(), "idle_frame") 43 | var error = _http_request.request("https://api.github.com/repos/MikeSchulze/gdUnit3/tags") 44 | if error != OK: 45 | var message = "request_latest_version failed: %d" % error 46 | return HttpResponse.new(error, message.to_utf8()) 47 | return yield(self, "request_completed") 48 | 49 | func request_releases() -> HttpResponse: 50 | yield(get_tree(), "idle_frame") 51 | var error = _http_request.request("https://api.github.com/repos/MikeSchulze/gdUnit3/releases") 52 | if error != OK: 53 | var message = "request_releases failed: %d" % error 54 | return HttpResponse.new(error, message.to_utf8()) 55 | return yield(self, "request_completed") 56 | 57 | func request_image(url :String) -> HttpResponse: 58 | yield(get_tree(), "idle_frame") 59 | var error = _http_request.request(url) 60 | if error != OK: 61 | var message = "request_image failed: %d" % error 62 | return HttpResponse.new(error, message.to_utf8()) 63 | return yield(self, "request_completed") 64 | 65 | func request_zip_package(url :String, file :String) -> HttpResponse: 66 | yield(get_tree(), "idle_frame") 67 | _http_request.set_download_file(file) 68 | var error = _http_request.request(url) 69 | if error != OK: 70 | var message = "request_zip_package failed: %d" % error 71 | return HttpResponse.new(error, message.to_utf8()) 72 | return yield(self, "request_completed") 73 | 74 | func _on_request_completed(result :int, response_code :int, headers :PoolStringArray, body :PoolByteArray): 75 | if _http_request.get_http_client_status() != HTTPClient.STATUS_DISCONNECTED: 76 | _http_request.set_download_file("") 77 | emit_signal("request_completed", HttpResponse.new(response_code, body)) 78 | -------------------------------------------------------------------------------- /addons/gdUnit3/src/update/assets/border_bottom.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MikeSchulze/gdUnit3/87a1189b6693257563fb4b761f8b161322a8deb6/addons/gdUnit3/src/update/assets/border_bottom.png -------------------------------------------------------------------------------- /addons/gdUnit3/src/update/assets/border_top.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MikeSchulze/gdUnit3/87a1189b6693257563fb4b761f8b161322a8deb6/addons/gdUnit3/src/update/assets/border_top.png -------------------------------------------------------------------------------- /addons/gdUnit3/src/update/assets/dot1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MikeSchulze/gdUnit3/87a1189b6693257563fb4b761f8b161322a8deb6/addons/gdUnit3/src/update/assets/dot1.png -------------------------------------------------------------------------------- /addons/gdUnit3/src/update/assets/dot2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MikeSchulze/gdUnit3/87a1189b6693257563fb4b761f8b161322a8deb6/addons/gdUnit3/src/update/assets/dot2.png -------------------------------------------------------------------------------- /addons/gdUnit3/src/update/assets/embedded.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MikeSchulze/gdUnit3/87a1189b6693257563fb4b761f8b161322a8deb6/addons/gdUnit3/src/update/assets/embedded.png -------------------------------------------------------------------------------- /addons/gdUnit3/src/update/assets/fonts/README.txt: -------------------------------------------------------------------------------- 1 | Roboto Mono Variable Font 2 | ========================= 3 | 4 | This download contains Roboto Mono as both variable fonts and static fonts. 5 | 6 | Roboto Mono is a variable font with this axis: 7 | wght 8 | 9 | This means all the styles are contained in these files: 10 | RobotoMono-VariableFont_wght.ttf 11 | RobotoMono-Italic-VariableFont_wght.ttf 12 | 13 | If your app fully supports variable fonts, you can now pick intermediate styles 14 | that aren’t available as static fonts. Not all apps support variable fonts, and 15 | in those cases you can use the static font files for Roboto Mono: 16 | static/RobotoMono-Thin.ttf 17 | static/RobotoMono-ExtraLight.ttf 18 | static/RobotoMono-Light.ttf 19 | static/RobotoMono-Regular.ttf 20 | static/RobotoMono-Medium.ttf 21 | static/RobotoMono-SemiBold.ttf 22 | static/RobotoMono-Bold.ttf 23 | static/RobotoMono-ThinItalic.ttf 24 | static/RobotoMono-ExtraLightItalic.ttf 25 | static/RobotoMono-LightItalic.ttf 26 | static/RobotoMono-Italic.ttf 27 | static/RobotoMono-MediumItalic.ttf 28 | static/RobotoMono-SemiBoldItalic.ttf 29 | static/RobotoMono-BoldItalic.ttf 30 | 31 | Get started 32 | ----------- 33 | 34 | 1. Install the font files you want to use 35 | 36 | 2. Use your app's font picker to view the font family and all the 37 | available styles 38 | 39 | Learn more about variable fonts 40 | ------------------------------- 41 | 42 | https://developers.google.com/web/fundamentals/design-and-ux/typography/variable-fonts 43 | https://variablefonts.typenetwork.com 44 | https://medium.com/variable-fonts 45 | 46 | In desktop apps 47 | 48 | https://theblog.adobe.com/can-variable-fonts-illustrator-cc 49 | https://helpx.adobe.com/nz/photoshop/using/fonts.html#variable_fonts 50 | 51 | Online 52 | 53 | https://developers.google.com/fonts/docs/getting_started 54 | https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Fonts/Variable_Fonts_Guide 55 | https://developer.microsoft.com/en-us/microsoft-edge/testdrive/demos/variable-fonts 56 | 57 | Installing fonts 58 | 59 | MacOS: https://support.apple.com/en-us/HT201749 60 | Linux: https://www.google.com/search?q=how+to+install+a+font+on+gnu%2Blinux 61 | Windows: https://support.microsoft.com/en-us/help/314960/how-to-install-or-remove-a-font-in-windows 62 | 63 | Android Apps 64 | 65 | https://developers.google.com/fonts/docs/android 66 | https://developer.android.com/guide/topics/ui/look-and-feel/downloadable-fonts 67 | 68 | License 69 | ------- 70 | Please read the full license text (LICENSE.txt) to understand the permissions, 71 | restrictions and requirements for usage, redistribution, and modification. 72 | 73 | You can use them freely in your products & projects - print or digital, 74 | commercial or otherwise. However, you can't sell the fonts on their own. 75 | 76 | This isn't legal advice, please consider consulting a lawyer and see the full 77 | license for all details. 78 | -------------------------------------------------------------------------------- /addons/gdUnit3/src/update/assets/fonts/RobotoMono-14-bold-italics.tres: -------------------------------------------------------------------------------- 1 | [gd_resource type="DynamicFont" load_steps=2 format=2] 2 | 3 | [ext_resource path="res://addons/gdUnit3/src/update/assets/fonts/static/RobotoMono-BoldItalic.ttf" type="DynamicFontData" id=1] 4 | 5 | [resource] 6 | size = 14 7 | font_data = ExtResource( 1 ) 8 | -------------------------------------------------------------------------------- /addons/gdUnit3/src/update/assets/fonts/RobotoMono-14-bold.tres: -------------------------------------------------------------------------------- 1 | [gd_resource type="DynamicFont" load_steps=2 format=2] 2 | 3 | [ext_resource path="res://addons/gdUnit3/src/update/assets/fonts/static/RobotoMono-Bold.ttf" type="DynamicFontData" id=1] 4 | 5 | [resource] 6 | size = 14 7 | font_data = ExtResource( 1 ) 8 | -------------------------------------------------------------------------------- /addons/gdUnit3/src/update/assets/fonts/RobotoMono-14-italics.tres: -------------------------------------------------------------------------------- 1 | [gd_resource type="DynamicFont" load_steps=2 format=2] 2 | 3 | [ext_resource path="res://addons/gdUnit3/src/update/assets/fonts/static/RobotoMono-Italic.ttf" type="DynamicFontData" id=1] 4 | 5 | [resource] 6 | size = 14 7 | font_data = ExtResource( 1 ) 8 | -------------------------------------------------------------------------------- /addons/gdUnit3/src/update/assets/fonts/RobotoMono-14.tres: -------------------------------------------------------------------------------- 1 | [gd_resource type="DynamicFont" load_steps=2 format=2] 2 | 3 | [ext_resource path="res://addons/gdUnit3/src/update/assets/fonts/static/RobotoMono-Regular.ttf" type="DynamicFontData" id=1] 4 | 5 | [resource] 6 | size = 14 7 | font_data = ExtResource( 1 ) 8 | -------------------------------------------------------------------------------- /addons/gdUnit3/src/update/assets/fonts/RobotoMono-code.tres: -------------------------------------------------------------------------------- 1 | [gd_resource type="DynamicFont" load_steps=2 format=2] 2 | 3 | [ext_resource path="res://addons/gdUnit3/src/update/assets/fonts/static/RobotoMono-Light.ttf" type="DynamicFontData" id=1] 4 | 5 | [resource] 6 | size = 14 7 | font_data = ExtResource( 1 ) 8 | -------------------------------------------------------------------------------- /addons/gdUnit3/src/update/assets/fonts/RobotoMono-h1.tres: -------------------------------------------------------------------------------- 1 | [gd_resource type="DynamicFont" load_steps=2 format=2] 2 | 3 | [ext_resource path="res://addons/gdUnit3/src/update/assets/fonts/static/RobotoMono-Bold.ttf" type="DynamicFontData" id=1] 4 | 5 | [resource] 6 | size = 24 7 | font_data = ExtResource( 1 ) 8 | -------------------------------------------------------------------------------- /addons/gdUnit3/src/update/assets/fonts/RobotoMono-h2.tres: -------------------------------------------------------------------------------- 1 | [gd_resource type="DynamicFont" load_steps=2 format=2] 2 | 3 | [ext_resource path="res://addons/gdUnit3/src/update/assets/fonts/static/RobotoMono-Bold.ttf" type="DynamicFontData" id=1] 4 | 5 | [resource] 6 | size = 22 7 | font_data = ExtResource( 1 ) 8 | -------------------------------------------------------------------------------- /addons/gdUnit3/src/update/assets/fonts/RobotoMono-h3.tres: -------------------------------------------------------------------------------- 1 | [gd_resource type="DynamicFont" load_steps=2 format=2] 2 | 3 | [ext_resource path="res://addons/gdUnit3/src/update/assets/fonts/static/RobotoMono-Bold.ttf" type="DynamicFontData" id=1] 4 | 5 | [resource] 6 | size = 20 7 | font_data = ExtResource( 1 ) 8 | -------------------------------------------------------------------------------- /addons/gdUnit3/src/update/assets/fonts/RobotoMono-h4.tres: -------------------------------------------------------------------------------- 1 | [gd_resource type="DynamicFont" load_steps=2 format=2] 2 | 3 | [ext_resource path="res://addons/gdUnit3/src/update/assets/fonts/static/RobotoMono-Bold.ttf" type="DynamicFontData" id=1] 4 | 5 | [resource] 6 | size = 18 7 | font_data = ExtResource( 1 ) 8 | -------------------------------------------------------------------------------- /addons/gdUnit3/src/update/assets/fonts/RobotoMono-h5.tres: -------------------------------------------------------------------------------- 1 | [gd_resource type="DynamicFont" load_steps=2 format=2] 2 | 3 | [ext_resource path="res://addons/gdUnit3/src/update/assets/fonts/static/RobotoMono-Bold.ttf" type="DynamicFontData" id=1] 4 | 5 | [resource] 6 | font_data = ExtResource( 1 ) 7 | -------------------------------------------------------------------------------- /addons/gdUnit3/src/update/assets/fonts/static/RobotoMono-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MikeSchulze/gdUnit3/87a1189b6693257563fb4b761f8b161322a8deb6/addons/gdUnit3/src/update/assets/fonts/static/RobotoMono-Bold.ttf -------------------------------------------------------------------------------- /addons/gdUnit3/src/update/assets/fonts/static/RobotoMono-BoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MikeSchulze/gdUnit3/87a1189b6693257563fb4b761f8b161322a8deb6/addons/gdUnit3/src/update/assets/fonts/static/RobotoMono-BoldItalic.ttf -------------------------------------------------------------------------------- /addons/gdUnit3/src/update/assets/fonts/static/RobotoMono-ExtraLight.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MikeSchulze/gdUnit3/87a1189b6693257563fb4b761f8b161322a8deb6/addons/gdUnit3/src/update/assets/fonts/static/RobotoMono-ExtraLight.ttf -------------------------------------------------------------------------------- /addons/gdUnit3/src/update/assets/fonts/static/RobotoMono-ExtraLightItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MikeSchulze/gdUnit3/87a1189b6693257563fb4b761f8b161322a8deb6/addons/gdUnit3/src/update/assets/fonts/static/RobotoMono-ExtraLightItalic.ttf -------------------------------------------------------------------------------- /addons/gdUnit3/src/update/assets/fonts/static/RobotoMono-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MikeSchulze/gdUnit3/87a1189b6693257563fb4b761f8b161322a8deb6/addons/gdUnit3/src/update/assets/fonts/static/RobotoMono-Italic.ttf -------------------------------------------------------------------------------- /addons/gdUnit3/src/update/assets/fonts/static/RobotoMono-Light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MikeSchulze/gdUnit3/87a1189b6693257563fb4b761f8b161322a8deb6/addons/gdUnit3/src/update/assets/fonts/static/RobotoMono-Light.ttf -------------------------------------------------------------------------------- /addons/gdUnit3/src/update/assets/fonts/static/RobotoMono-LightItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MikeSchulze/gdUnit3/87a1189b6693257563fb4b761f8b161322a8deb6/addons/gdUnit3/src/update/assets/fonts/static/RobotoMono-LightItalic.ttf -------------------------------------------------------------------------------- /addons/gdUnit3/src/update/assets/fonts/static/RobotoMono-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MikeSchulze/gdUnit3/87a1189b6693257563fb4b761f8b161322a8deb6/addons/gdUnit3/src/update/assets/fonts/static/RobotoMono-Medium.ttf -------------------------------------------------------------------------------- /addons/gdUnit3/src/update/assets/fonts/static/RobotoMono-MediumItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MikeSchulze/gdUnit3/87a1189b6693257563fb4b761f8b161322a8deb6/addons/gdUnit3/src/update/assets/fonts/static/RobotoMono-MediumItalic.ttf -------------------------------------------------------------------------------- /addons/gdUnit3/src/update/assets/fonts/static/RobotoMono-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MikeSchulze/gdUnit3/87a1189b6693257563fb4b761f8b161322a8deb6/addons/gdUnit3/src/update/assets/fonts/static/RobotoMono-Regular.ttf -------------------------------------------------------------------------------- /addons/gdUnit3/src/update/assets/fonts/static/RobotoMono-SemiBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MikeSchulze/gdUnit3/87a1189b6693257563fb4b761f8b161322a8deb6/addons/gdUnit3/src/update/assets/fonts/static/RobotoMono-SemiBold.ttf -------------------------------------------------------------------------------- /addons/gdUnit3/src/update/assets/fonts/static/RobotoMono-SemiBoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MikeSchulze/gdUnit3/87a1189b6693257563fb4b761f8b161322a8deb6/addons/gdUnit3/src/update/assets/fonts/static/RobotoMono-SemiBoldItalic.ttf -------------------------------------------------------------------------------- /addons/gdUnit3/src/update/assets/fonts/static/RobotoMono-Thin.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MikeSchulze/gdUnit3/87a1189b6693257563fb4b761f8b161322a8deb6/addons/gdUnit3/src/update/assets/fonts/static/RobotoMono-Thin.ttf -------------------------------------------------------------------------------- /addons/gdUnit3/src/update/assets/fonts/static/RobotoMono-ThinItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MikeSchulze/gdUnit3/87a1189b6693257563fb4b761f8b161322a8deb6/addons/gdUnit3/src/update/assets/fonts/static/RobotoMono-ThinItalic.ttf -------------------------------------------------------------------------------- /addons/gdUnit3/src/update/assets/horizontal-line2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MikeSchulze/gdUnit3/87a1189b6693257563fb4b761f8b161322a8deb6/addons/gdUnit3/src/update/assets/horizontal-line2.png -------------------------------------------------------------------------------- /addons/gdUnit3/src/update/assets/progress-background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MikeSchulze/gdUnit3/87a1189b6693257563fb4b761f8b161322a8deb6/addons/gdUnit3/src/update/assets/progress-background.png -------------------------------------------------------------------------------- /addons/gdUnit3/src/update/bbcodeView.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=7 format=2] 2 | 3 | [ext_resource path="res://addons/gdUnit3/test/update/bbcodeView.gd" type="Script" id=1] 4 | [ext_resource path="res://addons/gdUnit3/src/update/assets/fonts/RobotoMono-14.tres" type="DynamicFont" id=2] 5 | [ext_resource path="res://addons/gdUnit3/src/update/assets/fonts/RobotoMono-14-bold.tres" type="DynamicFont" id=3] 6 | [ext_resource path="res://addons/gdUnit3/src/update/assets/fonts/RobotoMono-14-italics.tres" type="DynamicFont" id=4] 7 | [ext_resource path="res://addons/gdUnit3/src/update/assets/fonts/RobotoMono-14-bold-italics.tres" type="DynamicFont" id=5] 8 | [ext_resource path="res://addons/gdUnit3/src/update/GdUnitUpdateClient.gd" type="Script" id=6] 9 | 10 | [node name="Control" type="Control"] 11 | anchor_right = 1.0 12 | anchor_bottom = 1.0 13 | script = ExtResource( 1 ) 14 | __meta__ = { 15 | "_edit_use_anchors_": false 16 | } 17 | 18 | [node name="HSplitContainer" type="HSplitContainer" parent="."] 19 | anchor_right = 1.0 20 | anchor_bottom = 1.0 21 | __meta__ = { 22 | "_edit_use_anchors_": false 23 | } 24 | 25 | [node name="TextEdit" type="TextEdit" parent="HSplitContainer"] 26 | margin_right = 300.0 27 | margin_bottom = 600.0 28 | rect_min_size = Vector2( 300, 0 ) 29 | __meta__ = { 30 | "_edit_use_anchors_": false 31 | } 32 | 33 | [node name="RichTextLabel" type="RichTextLabel" parent="HSplitContainer"] 34 | use_parent_material = true 35 | margin_left = 312.0 36 | margin_right = 1024.0 37 | margin_bottom = 600.0 38 | hint_tooltip = "test" 39 | custom_fonts/mono_font = ExtResource( 2 ) 40 | custom_fonts/bold_italics_font = ExtResource( 5 ) 41 | custom_fonts/italics_font = ExtResource( 4 ) 42 | custom_fonts/bold_font = ExtResource( 3 ) 43 | custom_fonts/normal_font = ExtResource( 2 ) 44 | custom_colors/default_color = Color( 1, 1, 1, 1 ) 45 | custom_constants/table_vseparation = 0 46 | custom_constants/line_separation = 0 47 | bbcode_enabled = true 48 | __meta__ = { 49 | "_edit_use_anchors_": false 50 | } 51 | 52 | [node name="GdUnitUpdateClient" type="Node" parent="."] 53 | script = ExtResource( 6 ) 54 | 55 | [connection signal="text_changed" from="HSplitContainer/TextEdit" to="." method="_on_TextEdit_text_changed"] 56 | [connection signal="meta_clicked" from="HSplitContainer/RichTextLabel" to="." method="_on_RichTextLabel_meta_clicked"] 57 | [connection signal="meta_hover_ended" from="HSplitContainer/RichTextLabel" to="." method="_on_RichTextLabel_meta_hover_ended"] 58 | [connection signal="meta_hover_started" from="HSplitContainer/RichTextLabel" to="." method="_on_RichTextLabel_meta_hover_started"] 59 | -------------------------------------------------------------------------------- /addons/gdUnit3/src/update/patches/v1.1.0/patch_gd_170.gd: -------------------------------------------------------------------------------- 1 | extends GdUnitPatch 2 | 3 | # common settings 4 | const OLD_UPDATE_NOTIFICATION_ENABLED = GdUnitSettings.COMMON_SETTINGS + "/update_notification_enabled" 5 | const OLD_SERVER_TIMEOUT = GdUnitSettings.COMMON_SETTINGS + "/server_connection_timeout_minutes" 6 | 7 | # test settings 8 | const OLD_TEST_TIMEOUT = GdUnitSettings.COMMON_SETTINGS + "/test_timeout_seconds" 9 | const OLD_TEST_ROOT_FOLDER = GdUnitSettings.COMMON_SETTINGS + "/test_root_folder" 10 | 11 | func _init() .(GdUnit3Version.parse("v1.1.0")): 12 | pass 13 | 14 | func execute() -> bool: 15 | # migrate existing test properties to a group 'common' 16 | GdUnitSettings.migrate_property(OLD_UPDATE_NOTIFICATION_ENABLED, GdUnitSettings.UPDATE_NOTIFICATION_ENABLED) 17 | GdUnitSettings.migrate_property(OLD_SERVER_TIMEOUT, GdUnitSettings.SERVER_TIMEOUT) 18 | 19 | # migrate existing test properties to a group 'test' 20 | GdUnitSettings.migrate_property(OLD_TEST_TIMEOUT, GdUnitSettings.TEST_TIMEOUT) 21 | GdUnitSettings.migrate_property(OLD_TEST_ROOT_FOLDER, GdUnitSettings.TEST_ROOT_FOLDER) 22 | return true 23 | -------------------------------------------------------------------------------- /addons/gdUnit3/src/update/patches/v1.1.0/patch_gd_172.gd: -------------------------------------------------------------------------------- 1 | extends GdUnitPatch 2 | 3 | func _init() .(GdUnit3Version.parse("v1.1.0")): 4 | pass 5 | 6 | func execute() -> bool: 7 | # migrate existing test-suite template by replacing old tags with new ones 8 | var old_template := GdUnitTestSuiteTemplate.load_template(GdUnitTestSuiteTemplate.TEMPLATE_ID_GD) 9 | var migrated_template := old_template\ 10 | .replace("${class_name}", GdUnitTestSuiteTemplate.TAG_TEST_SUITE_CLASS)\ 11 | .replace("${source_path}", GdUnitTestSuiteTemplate.TAG_SOURCE_RESOURCE_PATH) 12 | GdUnitTestSuiteTemplate.save_template(GdUnitTestSuiteTemplate.TEMPLATE_ID_GD, migrated_template) 13 | prints("Succesfull migrated test-suite template") 14 | return true 15 | -------------------------------------------------------------------------------- /runtest.cmd: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | CLS 3 | 4 | IF NOT DEFINED GODOT_BIN ( 5 | ECHO "GODOT_BIN is not set." 6 | ECHO "Please set the environment variable 'setx GODOT_BIN '" 7 | EXIT /b -1 8 | ) 9 | 10 | REM scan if Godot mono used and compile c# classes 11 | for /f "tokens=5 delims=. " %%i in ('%GODOT_BIN% --version') do set GODOT_TYPE=%%i 12 | IF "%GODOT_TYPE%" == "mono" ( 13 | ECHO "Godot mono detected" 14 | ECHO Compiling c# classes ... Please Wait 15 | dotnet build --debug 16 | ECHO done %errorlevel% 17 | ) 18 | 19 | %GODOT_BIN% --no-window -s -d .\addons\gdUnit3\bin\GdUnitCmdTool.gd %* 20 | SET exit_code=%errorlevel% 21 | %GODOT_BIN% --no-window --quiet -s -d .\addons\gdUnit3\bin\GdUnitCopyLog.gd %* 22 | 23 | ECHO %exit_code% 24 | 25 | EXIT /B %exit_code% 26 | -------------------------------------------------------------------------------- /workspace.code-workspace: -------------------------------------------------------------------------------- 1 | { 2 | "folders": [ 3 | { 4 | "path": "." 5 | } 6 | ], 7 | "settings": {} 8 | } --------------------------------------------------------------------------------