└── addons └── gut ├── GutScene.gd ├── GutScene.tscn ├── LICENSE.md ├── UserFileViewer.gd ├── UserFileViewer.tscn ├── autofree.gd ├── awaiter.gd ├── cli ├── change_project_warnings.gd ├── gut_cli.gd └── optparse.gd ├── collected_script.gd ├── collected_test.gd ├── comparator.gd ├── compare_result.gd ├── diff_formatter.gd ├── diff_tool.gd ├── double_templates ├── function_template.txt ├── init_template.txt └── script_template.txt ├── double_tools.gd ├── doubler.gd ├── dynamic_gdscript.gd ├── fonts ├── AnonymousPro-Bold.ttf ├── AnonymousPro-Bold.ttf.import ├── AnonymousPro-BoldItalic.ttf ├── AnonymousPro-BoldItalic.ttf.import ├── AnonymousPro-Italic.ttf ├── AnonymousPro-Italic.ttf.import ├── AnonymousPro-Regular.ttf ├── AnonymousPro-Regular.ttf.import ├── CourierPrime-Bold.ttf ├── CourierPrime-Bold.ttf.import ├── CourierPrime-BoldItalic.ttf ├── CourierPrime-BoldItalic.ttf.import ├── CourierPrime-Italic.ttf ├── CourierPrime-Italic.ttf.import ├── CourierPrime-Regular.ttf ├── CourierPrime-Regular.ttf.import ├── LobsterTwo-Bold.ttf ├── LobsterTwo-Bold.ttf.import ├── LobsterTwo-BoldItalic.ttf ├── LobsterTwo-BoldItalic.ttf.import ├── LobsterTwo-Italic.ttf ├── LobsterTwo-Italic.ttf.import ├── LobsterTwo-Regular.ttf ├── LobsterTwo-Regular.ttf.import └── OFL.txt ├── gui ├── BottomPanelShortcuts.gd ├── BottomPanelShortcuts.tscn ├── GutBottomPanel.gd ├── GutBottomPanel.tscn ├── GutControl.gd ├── GutControl.tscn ├── GutRunner.gd ├── GutRunner.tscn ├── GutSceneTheme.tres ├── MinGui.tscn ├── NormalGui.tscn ├── OutputText.gd ├── OutputText.tscn ├── ResizeHandle.gd ├── ResizeHandle.tscn ├── ResultsTree.gd ├── ResultsTree.tscn ├── RunAtCursor.gd ├── RunAtCursor.tscn ├── RunResults.gd ├── RunResults.tscn ├── Settings.tscn ├── ShortcutButton.gd ├── ShortcutButton.tscn ├── arrow.png ├── arrow.png.import ├── editor_globals.gd ├── gut_config_gui.gd ├── gut_gui.gd ├── gut_user_preferences.gd ├── panel_controls.gd ├── play.png ├── play.png.import ├── run_from_editor.gd ├── run_from_editor.tscn └── script_text_editor_controls.gd ├── gut.gd ├── gut_cmdln.gd ├── gut_config.gd ├── gut_loader.gd ├── gut_loader_the_scene.tscn ├── gut_plugin.gd ├── gut_to_move.gd ├── gut_vscode_debugger.gd ├── hook_script.gd ├── icon.png ├── icon.png.import ├── images ├── Folder.svg ├── Folder.svg.import ├── Script.svg ├── Script.svg.import ├── green.png ├── green.png.import ├── red.png ├── red.png.import ├── yellow.png └── yellow.png.import ├── inner_class_registry.gd ├── input_factory.gd ├── input_sender.gd ├── junit_xml_export.gd ├── lazy_loader.gd ├── logger.gd ├── method_maker.gd ├── one_to_many.gd ├── orphan_counter.gd ├── parameter_factory.gd ├── parameter_handler.gd ├── plugin.cfg ├── printers.gd ├── result_exporter.gd ├── script_parser.gd ├── signal_watcher.gd ├── source_code_pro.fnt ├── source_code_pro.fnt.import ├── spy.gd ├── strutils.gd ├── stub_params.gd ├── stubber.gd ├── summary.gd ├── test.gd ├── test_collector.gd ├── thing_counter.gd ├── utils.gd ├── version_conversion.gd ├── version_numbers.gd └── warnings_manager.gd /addons/gut/GutScene.gd: -------------------------------------------------------------------------------- 1 | extends Node2D 2 | # ############################################################################## 3 | # This is a wrapper around the normal and compact gui controls and serves as 4 | # the interface between gut.gd and the gui. The GutRunner creates an instance 5 | # of this and then this takes care of managing the different GUI controls. 6 | # ############################################################################## 7 | @onready var _normal_gui = $Normal 8 | @onready var _compact_gui = $Compact 9 | 10 | var gut = null : 11 | set(val): 12 | gut = val 13 | _set_gut(val) 14 | 15 | 16 | func _ready(): 17 | _normal_gui.switch_modes.connect(use_compact_mode.bind(true)) 18 | _compact_gui.switch_modes.connect(use_compact_mode.bind(false)) 19 | 20 | _normal_gui.set_title("GUT") 21 | _compact_gui.set_title("GUT") 22 | 23 | _normal_gui.align_right() 24 | _compact_gui.to_bottom_right() 25 | 26 | use_compact_mode(false) 27 | 28 | if(get_parent() == get_tree().root): 29 | _test_running_setup() 30 | 31 | func _test_running_setup(): 32 | set_font_size(100) 33 | _normal_gui.get_textbox().text = "hello world, how are you doing?" 34 | 35 | # ------------------------ 36 | # Private 37 | # ------------------------ 38 | func _set_gut(val): 39 | if(_normal_gui.get_gut() == val): 40 | return 41 | _normal_gui.set_gut(val) 42 | _compact_gui.set_gut(val) 43 | 44 | val.start_run.connect(_on_gut_start_run) 45 | val.end_run.connect(_on_gut_end_run) 46 | val.start_pause_before_teardown.connect(_on_gut_pause) 47 | val.end_pause_before_teardown.connect(_on_pause_end) 48 | 49 | func _set_both_titles(text): 50 | _normal_gui.set_title(text) 51 | _compact_gui.set_title(text) 52 | 53 | 54 | # ------------------------ 55 | # Events 56 | # ------------------------ 57 | func _on_gut_start_run(): 58 | _set_both_titles('Running') 59 | 60 | func _on_gut_end_run(): 61 | _set_both_titles('Finished') 62 | 63 | func _on_gut_pause(): 64 | _set_both_titles('-- Paused --') 65 | 66 | func _on_pause_end(): 67 | _set_both_titles('Running') 68 | 69 | 70 | # ------------------------ 71 | # Public 72 | # ------------------------ 73 | func get_textbox(): 74 | return _normal_gui.get_textbox() 75 | 76 | 77 | func set_font_size(new_size): 78 | var rtl = _normal_gui.get_textbox() 79 | 80 | rtl.set('theme_override_font_sizes/bold_italics_font_size', new_size) 81 | rtl.set('theme_override_font_sizes/bold_font_size', new_size) 82 | rtl.set('theme_override_font_sizes/italics_font_size', new_size) 83 | rtl.set('theme_override_font_sizes/normal_font_size', new_size) 84 | 85 | 86 | func set_font(font_name): 87 | _set_all_fonts_in_rtl(_normal_gui.get_textbox(), font_name) 88 | 89 | 90 | func _set_font(rtl, font_name, custom_name): 91 | if(font_name == null): 92 | rtl.remove_theme_font_override(custom_name) 93 | else: 94 | var dyn_font = FontFile.new() 95 | dyn_font.load_dynamic_font('res://addons/gut/fonts/' + font_name + '.ttf') 96 | rtl.add_theme_font_override(custom_name, dyn_font) 97 | 98 | 99 | func _set_all_fonts_in_rtl(rtl, base_name): 100 | if(base_name == 'Default'): 101 | _set_font(rtl, null, 'normal_font') 102 | _set_font(rtl, null, 'bold_font') 103 | _set_font(rtl, null, 'italics_font') 104 | _set_font(rtl, null, 'bold_italics_font') 105 | else: 106 | _set_font(rtl, base_name + '-Regular', 'normal_font') 107 | _set_font(rtl, base_name + '-Bold', 'bold_font') 108 | _set_font(rtl, base_name + '-Italic', 'italics_font') 109 | _set_font(rtl, base_name + '-BoldItalic', 'bold_italics_font') 110 | 111 | 112 | func set_default_font_color(color): 113 | _normal_gui.get_textbox().set('custom_colors/default_color', color) 114 | 115 | 116 | func set_background_color(color): 117 | _normal_gui.set_bg_color(color) 118 | 119 | 120 | func use_compact_mode(should=true): 121 | _compact_gui.visible = should 122 | _normal_gui.visible = !should 123 | 124 | 125 | func set_opacity(val): 126 | _normal_gui.modulate.a = val 127 | _compact_gui.modulate.a = val 128 | 129 | func set_title(text): 130 | _set_both_titles(text) 131 | -------------------------------------------------------------------------------- /addons/gut/GutScene.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=4 format=3 uid="uid://m28heqtswbuq"] 2 | 3 | [ext_resource type="Script" path="res://addons/gut/GutScene.gd" id="1_b4m8y"] 4 | [ext_resource type="PackedScene" uid="uid://duxblir3vu8x7" path="res://addons/gut/gui/NormalGui.tscn" id="2_j6ywb"] 5 | [ext_resource type="PackedScene" uid="uid://cnqqdfsn80ise" path="res://addons/gut/gui/MinGui.tscn" id="3_3glw1"] 6 | 7 | [node name="GutScene" type="Node2D"] 8 | script = ExtResource("1_b4m8y") 9 | 10 | [node name="Normal" parent="." instance=ExtResource("2_j6ywb")] 11 | 12 | [node name="Compact" parent="." instance=ExtResource("3_3glw1")] 13 | offset_left = 5.0 14 | offset_top = 273.0 15 | offset_right = 265.0 16 | offset_bottom = 403.0 17 | -------------------------------------------------------------------------------- /addons/gut/LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | ===================== 3 | 4 | Copyright (c) 2018 Tom "Butch" Wesley 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /addons/gut/UserFileViewer.gd: -------------------------------------------------------------------------------- 1 | extends Window 2 | 3 | @onready var rtl = $TextDisplay/RichTextLabel 4 | 5 | func _get_file_as_text(path): 6 | var to_return = null 7 | var f = FileAccess.open(path, FileAccess.READ) 8 | if(f != null): 9 | to_return = f.get_as_text() 10 | else: 11 | to_return = str('ERROR: Could not open file. Error code ', FileAccess.get_open_error()) 12 | return to_return 13 | 14 | func _ready(): 15 | rtl.clear() 16 | 17 | func _on_OpenFile_pressed(): 18 | $FileDialog.popup_centered() 19 | 20 | func _on_FileDialog_file_selected(path): 21 | show_file(path) 22 | 23 | func _on_Close_pressed(): 24 | self.hide() 25 | 26 | func show_file(path): 27 | var text = _get_file_as_text(path) 28 | if(text == ''): 29 | text = '' 30 | rtl.set_text(text) 31 | self.window_title = path 32 | 33 | func show_open(): 34 | self.popup_centered() 35 | $FileDialog.popup_centered() 36 | 37 | func get_rich_text_label(): 38 | return $TextDisplay/RichTextLabel 39 | 40 | func _on_Home_pressed(): 41 | rtl.scroll_to_line(0) 42 | 43 | func _on_End_pressed(): 44 | rtl.scroll_to_line(rtl.get_line_count() -1) 45 | 46 | func _on_Copy_pressed(): 47 | return 48 | # OS.clipboard = rtl.text 49 | 50 | func _on_file_dialog_visibility_changed(): 51 | if rtl.text.length() == 0 and not $FileDialog.visible: 52 | self.hide() 53 | -------------------------------------------------------------------------------- /addons/gut/UserFileViewer.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=2 format=3 uid="uid://bsm7wtt1gie4v"] 2 | 3 | [ext_resource type="Script" path="res://addons/gut/UserFileViewer.gd" id="1"] 4 | 5 | [node name="UserFileViewer" type="Window"] 6 | exclusive = true 7 | script = ExtResource("1") 8 | 9 | [node name="FileDialog" type="FileDialog" parent="."] 10 | access = 1 11 | show_hidden_files = true 12 | __meta__ = { 13 | "_edit_use_anchors_": false 14 | } 15 | 16 | [node name="TextDisplay" type="ColorRect" parent="."] 17 | anchor_right = 1.0 18 | anchor_bottom = 1.0 19 | offset_left = 8.0 20 | offset_right = -10.0 21 | offset_bottom = -65.0 22 | color = Color(0.2, 0.188235, 0.188235, 1) 23 | 24 | [node name="RichTextLabel" type="RichTextLabel" parent="TextDisplay"] 25 | anchor_right = 1.0 26 | anchor_bottom = 1.0 27 | focus_mode = 2 28 | text = "In publishing and graphic design, Lorem ipsum is a placeholder text commonly used to demonstrate the visual form of a document or a typeface without relying on meaningful content. Lorem ipsum may be used before final copy is available, but it may also be used to temporarily replace copy in a process called greeking, which allows designers to consider form without the meaning of the text influencing the design. 29 | 30 | Lorem ipsum is typically a corrupted version of De finibus bonorum et malorum, a first-century BCE text by the Roman statesman and philosopher Cicero, with words altered, added, and removed to make it nonsensical, improper Latin. 31 | 32 | Versions of the Lorem ipsum text have been used in typesetting at least since the 1960s, when it was popularized by advertisements for Letraset transfer sheets. Lorem ipsum was introduced to the digital world in the mid-1980s when Aldus employed it in graphic and word-processing templates for its desktop publishing program PageMaker. Other popular word processors including Pages and Microsoft Word have since adopted Lorem ipsum as well." 33 | selection_enabled = true 34 | 35 | [node name="OpenFile" type="Button" parent="."] 36 | anchor_left = 1.0 37 | anchor_top = 1.0 38 | anchor_right = 1.0 39 | anchor_bottom = 1.0 40 | offset_left = -158.0 41 | offset_top = -50.0 42 | offset_right = -84.0 43 | offset_bottom = -30.0 44 | text = "Open File" 45 | 46 | [node name="Home" type="Button" parent="."] 47 | anchor_left = 1.0 48 | anchor_top = 1.0 49 | anchor_right = 1.0 50 | anchor_bottom = 1.0 51 | offset_left = -478.0 52 | offset_top = -50.0 53 | offset_right = -404.0 54 | offset_bottom = -30.0 55 | text = "Home" 56 | 57 | [node name="Copy" type="Button" parent="."] 58 | anchor_top = 1.0 59 | anchor_bottom = 1.0 60 | offset_left = 160.0 61 | offset_top = -50.0 62 | offset_right = 234.0 63 | offset_bottom = -30.0 64 | text = "Copy" 65 | 66 | [node name="End" type="Button" parent="."] 67 | anchor_left = 1.0 68 | anchor_top = 1.0 69 | anchor_right = 1.0 70 | anchor_bottom = 1.0 71 | offset_left = -318.0 72 | offset_top = -50.0 73 | offset_right = -244.0 74 | offset_bottom = -30.0 75 | text = "End" 76 | 77 | [node name="Close" type="Button" parent="."] 78 | anchor_top = 1.0 79 | anchor_bottom = 1.0 80 | offset_left = 10.0 81 | offset_top = -50.0 82 | offset_right = 80.0 83 | offset_bottom = -30.0 84 | text = "Close" 85 | 86 | [connection signal="file_selected" from="FileDialog" to="." method="_on_FileDialog_file_selected"] 87 | [connection signal="visibility_changed" from="FileDialog" to="." method="_on_file_dialog_visibility_changed"] 88 | [connection signal="pressed" from="OpenFile" to="." method="_on_OpenFile_pressed"] 89 | [connection signal="pressed" from="Home" to="." method="_on_Home_pressed"] 90 | [connection signal="pressed" from="Copy" to="." method="_on_Copy_pressed"] 91 | [connection signal="pressed" from="End" to="." method="_on_End_pressed"] 92 | [connection signal="pressed" from="Close" to="." method="_on_Close_pressed"] 93 | -------------------------------------------------------------------------------- /addons/gut/autofree.gd: -------------------------------------------------------------------------------- 1 | # ############################################################################## 2 | #(G)odot (U)nit (T)est class 3 | # 4 | # ############################################################################## 5 | # The MIT License (MIT) 6 | # ===================== 7 | # 8 | # Copyright (c) 2025 Tom "Butch" Wesley 9 | # 10 | # Permission is hereby granted, free of charge, to any person obtaining a copy 11 | # of this software and associated documentation files (the "Software"), to deal 12 | # in the Software without restriction, including without limitation the rights 13 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | # copies of the Software, and to permit persons to whom the Software is 15 | # furnished to do so, subject to the following conditions: 16 | # 17 | # The above copyright notice and this permission notice shall be included in 18 | # all copies or substantial portions of the Software. 19 | # 20 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 26 | # THE SOFTWARE. 27 | # 28 | # ############################################################################## 29 | # Class used to keep track of objects to be freed and utilities to free them. 30 | # ############################################################################## 31 | var _to_free = [] 32 | var _to_queue_free = [] 33 | 34 | func add_free(thing): 35 | if(typeof(thing) == TYPE_OBJECT): 36 | if(!thing is RefCounted): 37 | _to_free.append(thing) 38 | 39 | func add_queue_free(thing): 40 | _to_queue_free.append(thing) 41 | 42 | func get_queue_free_count(): 43 | return _to_queue_free.size() 44 | 45 | func get_free_count(): 46 | return _to_free.size() 47 | 48 | func free_all(): 49 | for i in range(_to_free.size()): 50 | if(is_instance_valid(_to_free[i])): 51 | _to_free[i].free() 52 | _to_free.clear() 53 | 54 | for i in range(_to_queue_free.size()): 55 | if(is_instance_valid(_to_queue_free[i])): 56 | _to_queue_free[i].queue_free() 57 | _to_queue_free.clear() 58 | -------------------------------------------------------------------------------- /addons/gut/awaiter.gd: -------------------------------------------------------------------------------- 1 | extends Node 2 | 3 | signal timeout 4 | signal wait_started 5 | 6 | var _wait_time := 0.0 7 | var _wait_process_frames := 0 8 | var _wait_physics_frames := 0 9 | var _signal_to_wait_on = null 10 | 11 | var _predicate_method = null 12 | var _waiting_for_predicate_to_be = null 13 | 14 | var _predicate_time_between := 0.0 15 | var _predicate_time_between_elpased := 0.0 16 | 17 | var _did_last_wait_timeout = false 18 | var did_last_wait_timeout = false : 19 | get: return _did_last_wait_timeout 20 | set(val): push_error("Cannot set did_last_wait_timeout") 21 | 22 | var _elapsed_time := 0.0 23 | var _elapsed_frames := 0 24 | 25 | func _ready() -> void: 26 | get_tree().process_frame.connect(_on_tree_process_frame) 27 | get_tree().physics_frame.connect(_on_tree_physics_frame) 28 | 29 | 30 | func _on_tree_process_frame(): 31 | # Count frames here instead of in _process so that tree order never 32 | # makes a difference and the count/signaling happens outside of 33 | # _process being called. 34 | if(_wait_process_frames > 0): 35 | _elapsed_frames += 1 36 | if(_elapsed_frames > _wait_process_frames): 37 | _end_wait() 38 | 39 | 40 | func _on_tree_physics_frame(): 41 | # Count frames here instead of in _physics_process so that tree order never 42 | # makes a difference and the count/signaling happens outside of 43 | # _physics_process being called. 44 | if(_wait_physics_frames != 0): 45 | _elapsed_frames += 1 46 | if(_elapsed_frames > _wait_physics_frames): 47 | _end_wait() 48 | 49 | 50 | func _physics_process(delta): 51 | if(_wait_time != 0.0): 52 | _elapsed_time += delta 53 | if(_elapsed_time >= _wait_time): 54 | _end_wait() 55 | 56 | if(_predicate_method != null): 57 | _predicate_time_between_elpased += delta 58 | if(_predicate_time_between_elpased >= _predicate_time_between): 59 | _predicate_time_between_elpased = 0.0 60 | var result = _predicate_method.call() 61 | if(_waiting_for_predicate_to_be == false): 62 | if(typeof(result) != TYPE_BOOL or result != true): 63 | _end_wait() 64 | else: 65 | if(typeof(result) == TYPE_BOOL and result == _waiting_for_predicate_to_be): 66 | _end_wait() 67 | 68 | 69 | func _end_wait(): 70 | # Check for time before checking for frames so that the extra frames added 71 | # when waiting on a signal do not cause a false negative for timing out. 72 | if(_wait_time > 0): 73 | _did_last_wait_timeout = _elapsed_time >= _wait_time 74 | elif(_wait_physics_frames > 0): 75 | _did_last_wait_timeout = _elapsed_frames >= _wait_physics_frames 76 | elif(_wait_process_frames > 0): 77 | _did_last_wait_timeout = _elapsed_frames >= _wait_process_frames 78 | 79 | if(_signal_to_wait_on != null and _signal_to_wait_on.is_connected(_signal_callback)): 80 | _signal_to_wait_on.disconnect(_signal_callback) 81 | 82 | _wait_process_frames = 0 83 | _wait_time = 0.0 84 | _wait_physics_frames = 0 85 | _signal_to_wait_on = null 86 | _predicate_method = null 87 | _elapsed_time = 0.0 88 | _elapsed_frames = 0 89 | timeout.emit() 90 | 91 | 92 | const ARG_NOT_SET = '_*_argument_*_is_*_not_set_*_' 93 | func _signal_callback( 94 | _arg1=ARG_NOT_SET, _arg2=ARG_NOT_SET, _arg3=ARG_NOT_SET, 95 | _arg4=ARG_NOT_SET, _arg5=ARG_NOT_SET, _arg6=ARG_NOT_SET, 96 | _arg7=ARG_NOT_SET, _arg8=ARG_NOT_SET, _arg9=ARG_NOT_SET): 97 | 98 | _signal_to_wait_on.disconnect(_signal_callback) 99 | # DO NOT _end_wait here. For other parts of the test to get the signal that 100 | # was waited on, we have to wait for another frames. For example, the 101 | # signal_watcher doesn't get the signal in time if we don't do this. 102 | _wait_process_frames = 1 103 | 104 | 105 | func wait_seconds(x): 106 | _did_last_wait_timeout = false 107 | _wait_time = x 108 | wait_started.emit() 109 | 110 | 111 | func wait_process_frames(x): 112 | _did_last_wait_timeout = false 113 | _wait_process_frames = x 114 | wait_started.emit() 115 | 116 | 117 | func wait_physics_frames(x): 118 | _did_last_wait_timeout = false 119 | _wait_physics_frames = x 120 | wait_started.emit() 121 | 122 | 123 | func wait_for_signal(the_signal, max_time): 124 | _did_last_wait_timeout = false 125 | the_signal.connect(_signal_callback) 126 | _signal_to_wait_on = the_signal 127 | _wait_time = max_time 128 | wait_started.emit() 129 | 130 | 131 | func wait_until(predicate_function: Callable, max_time, time_between_calls:=0.0): 132 | _predicate_time_between = time_between_calls 133 | _predicate_method = predicate_function 134 | _wait_time = max_time 135 | 136 | _waiting_for_predicate_to_be = true 137 | _predicate_time_between_elpased = 0.0 138 | _did_last_wait_timeout = false 139 | 140 | wait_started.emit() 141 | 142 | 143 | func wait_while(predicate_function: Callable, max_time, time_between_calls:=0.0): 144 | _predicate_time_between = time_between_calls 145 | _predicate_method = predicate_function 146 | _wait_time = max_time 147 | 148 | _waiting_for_predicate_to_be = false 149 | _predicate_time_between_elpased = 0.0 150 | _did_last_wait_timeout = false 151 | 152 | wait_started.emit() 153 | 154 | func is_waiting(): 155 | return _wait_time != 0.0 || _wait_physics_frames != 0 156 | -------------------------------------------------------------------------------- /addons/gut/collected_script.gd: -------------------------------------------------------------------------------- 1 | # ------------------------------------------------------------------------------ 2 | # This holds all the meta information for a test script. It contains the 3 | # name of the inner class and an array of CollectedTests. This does not parse 4 | # anything, it just holds the data about parsed scripts and tests. The 5 | # TestCollector is responsible for populating this object. 6 | # 7 | # This class also facilitates all the exporting and importing of tests. 8 | # ------------------------------------------------------------------------------ 9 | var CollectedTest = GutUtils.CollectedTest 10 | 11 | var _lgr = null 12 | 13 | # One entry per test found in the script. Added externally by TestCollector 14 | var tests = [] 15 | # One entry for before_all and after_all (maybe add before_each and after_each). 16 | # These are added by Gut when running before_all and after_all for the script. 17 | var setup_teardown_tests = [] 18 | var inner_class_name:StringName 19 | var path:String 20 | 21 | 22 | # Set externally by test_collector after it can verify that the script was 23 | # actually loaded. This could probably be changed to just hold the GutTest 24 | # script that was loaded, cutting down on complexity elsewhere. 25 | var is_loaded = false 26 | 27 | # Set by Gut when it decides that a script should be skipped. 28 | # Right now this is whenever the script has the variable skip_script declared. 29 | # the value of skip_script is put into skip_reason. 30 | var was_skipped = false 31 | var skip_reason = '' 32 | var was_run = false 33 | 34 | 35 | var name = '' : 36 | get: return path 37 | set(val):pass 38 | 39 | 40 | func _init(logger=null): 41 | _lgr = logger 42 | 43 | 44 | func get_new(): 45 | return load_script().new() 46 | 47 | 48 | func load_script(): 49 | var to_return = load(path) 50 | 51 | if(inner_class_name != null and inner_class_name != ''): 52 | # If we wanted to do inner classes in inner classses 53 | # then this would have to become some kind of loop or recursive 54 | # call to go all the way down the chain or this class would 55 | # have to change to hold onto the loaded class instead of 56 | # just path information. 57 | to_return = to_return.get(inner_class_name) 58 | 59 | return to_return 60 | 61 | # script.gd.InnerClass 62 | func get_filename_and_inner(): 63 | var to_return = get_filename() 64 | if(inner_class_name != ''): 65 | to_return += '.' + String(inner_class_name) 66 | return to_return 67 | 68 | 69 | # res://foo/bar.gd.FooBar 70 | func get_full_name(): 71 | var to_return = path 72 | if(inner_class_name != ''): 73 | to_return += '.' + String(inner_class_name) 74 | return to_return 75 | 76 | 77 | func get_filename(): 78 | return path.get_file() 79 | 80 | 81 | func has_inner_class(): 82 | return inner_class_name != '' 83 | 84 | 85 | # Note: although this no longer needs to export the inner_class names since 86 | # they are pulled from metadata now, it is easier to leave that in 87 | # so we don't have to cut the export down to unique script names. 88 | func export_to(config_file, section): 89 | config_file.set_value(section, 'path', path) 90 | config_file.set_value(section, 'inner_class', inner_class_name) 91 | var names = [] 92 | for i in range(tests.size()): 93 | names.append(tests[i].name) 94 | config_file.set_value(section, 'tests', names) 95 | 96 | 97 | func _remap_path(source_path): 98 | var to_return = source_path 99 | if(!FileAccess.file_exists(source_path)): 100 | _lgr.debug('Checking for remap for: ' + source_path) 101 | var remap_path = source_path.get_basename() + '.gd.remap' 102 | if(FileAccess.file_exists(remap_path)): 103 | var cf = ConfigFile.new() 104 | cf.load(remap_path) 105 | to_return = cf.get_value('remap', 'path') 106 | else: 107 | _lgr.warn('Could not find remap file ' + remap_path) 108 | return to_return 109 | 110 | 111 | func import_from(config_file, section): 112 | path = config_file.get_value(section, 'path') 113 | path = _remap_path(path) 114 | # Null is an acceptable value, but you can't pass null as a default to 115 | # get_value since it thinks you didn't send a default...then it spits 116 | # out red text. This works around that. 117 | var inner_name = config_file.get_value(section, 'inner_class', 'Placeholder') 118 | if(inner_name != 'Placeholder'): 119 | inner_class_name = inner_name 120 | else: # just being explicit 121 | inner_class_name = StringName("") 122 | 123 | 124 | func get_test_named(test_name): 125 | return GutUtils.search_array(tests, 'name', test_name) 126 | 127 | 128 | func get_ran_test_count(): 129 | var count = 0 130 | for t in tests: 131 | if(t.was_run): 132 | count += 1 133 | return count 134 | 135 | 136 | func get_assert_count(): 137 | var count = 0 138 | for t in tests: 139 | count += t.pass_texts.size() 140 | count += t.fail_texts.size() 141 | for t in setup_teardown_tests: 142 | count += t.pass_texts.size() 143 | count += t.fail_texts.size() 144 | return count 145 | 146 | 147 | func get_pass_count(): 148 | var count = 0 149 | for t in tests: 150 | count += t.pass_texts.size() 151 | for t in setup_teardown_tests: 152 | count += t.pass_texts.size() 153 | return count 154 | 155 | 156 | func get_fail_count(): 157 | var count = 0 158 | for t in tests: 159 | count += t.fail_texts.size() 160 | for t in setup_teardown_tests: 161 | count += t.fail_texts.size() 162 | return count 163 | 164 | 165 | func get_pending_count(): 166 | var count = 0 167 | for t in tests: 168 | count += t.pending_texts.size() 169 | return count 170 | 171 | 172 | func get_passing_test_count(): 173 | var count = 0 174 | for t in tests: 175 | if(t.is_passing()): 176 | count += 1 177 | return count 178 | 179 | 180 | func get_failing_test_count(): 181 | var count = 0 182 | for t in tests: 183 | if(t.is_failing()): 184 | count += 1 185 | return count 186 | 187 | 188 | func get_risky_count(): 189 | var count = 0 190 | if(was_skipped): 191 | count = 1 192 | else: 193 | for t in tests: 194 | if(t.is_risky()): 195 | count += 1 196 | return count 197 | 198 | 199 | func to_s(): 200 | var to_return = path 201 | if(inner_class_name != null): 202 | to_return += str('.', inner_class_name) 203 | to_return += "\n" 204 | for i in range(tests.size()): 205 | to_return += str(' ', tests[i].to_s()) 206 | return to_return 207 | -------------------------------------------------------------------------------- /addons/gut/collected_test.gd: -------------------------------------------------------------------------------- 1 | # ------------------------------------------------------------------------------ 2 | # Used to keep track of info about each test ran. 3 | # ------------------------------------------------------------------------------ 4 | # the name of the function 5 | var name = "" 6 | 7 | # flag to know if the name has been printed yet. Used by the logger. 8 | var has_printed_name = false 9 | 10 | # the number of arguments the method has 11 | var arg_count = 0 12 | 13 | # the time it took to execute the test in seconds 14 | var time_taken : float = 0 15 | 16 | # The number of asserts in the test. Converted to a property for backwards 17 | # compatibility. This now reflects the text sizes instead of being a value 18 | # that can be altered externally. 19 | var assert_count = 0 : 20 | get: return pass_texts.size() + fail_texts.size() 21 | set(val): pass 22 | 23 | # Converted to propety for backwards compatibility. This now cannot be set 24 | # externally 25 | var pending = false : 26 | get: return is_pending() 27 | set(val): pass 28 | 29 | # the line number when the test fails 30 | var line_number = -1 31 | 32 | # Set internally by Gut using whatever reason Gut wants to use to set this. 33 | # Gut will skip these marked true and the test will be listed as risky. 34 | var should_skip = false # -- Currently not used by GUT don't believe ^ 35 | 36 | var pass_texts = [] 37 | var fail_texts = [] 38 | var pending_texts = [] 39 | var orphans = 0 40 | 41 | var was_run = false 42 | 43 | 44 | func did_pass(): 45 | return is_passing() 46 | 47 | 48 | func add_fail(fail_text): 49 | fail_texts.append(fail_text) 50 | 51 | 52 | func add_pending(pending_text): 53 | pending_texts.append(pending_text) 54 | 55 | 56 | func add_pass(passing_text): 57 | pass_texts.append(passing_text) 58 | 59 | 60 | # must have passed an assert and not have any other status to be passing 61 | func is_passing(): 62 | return pass_texts.size() > 0 and fail_texts.size() == 0 and pending_texts.size() == 0 63 | 64 | 65 | # failing takes precedence over everything else, so any failures makes the 66 | # test a failure. 67 | func is_failing(): 68 | return fail_texts.size() > 0 69 | 70 | 71 | # test is only pending if pending was called and the test is not failing. 72 | func is_pending(): 73 | return pending_texts.size() > 0 and fail_texts.size() == 0 74 | 75 | 76 | func is_risky(): 77 | return should_skip or (was_run and !did_something()) 78 | 79 | 80 | func did_something(): 81 | return is_passing() or is_failing() or is_pending() 82 | 83 | 84 | func get_status_text(): 85 | var to_return = GutUtils.TEST_STATUSES.NO_ASSERTS 86 | 87 | if(should_skip): 88 | to_return = GutUtils.TEST_STATUSES.SKIPPED 89 | elif(!was_run): 90 | to_return = GutUtils.TEST_STATUSES.NOT_RUN 91 | elif(pending_texts.size() > 0): 92 | to_return = GutUtils.TEST_STATUSES.PENDING 93 | elif(fail_texts.size() > 0): 94 | to_return = GutUtils.TEST_STATUSES.FAILED 95 | elif(pass_texts.size() > 0): 96 | to_return = GutUtils.TEST_STATUSES.PASSED 97 | 98 | return to_return 99 | 100 | 101 | # Deprecated 102 | func get_status(): 103 | return get_status_text() 104 | 105 | 106 | func to_s(): 107 | var pad = ' ' 108 | var to_return = str(name, "[", get_status_text(), "]\n") 109 | 110 | for i in range(fail_texts.size()): 111 | to_return += str(pad, 'Fail: ', fail_texts[i]) 112 | for i in range(pending_texts.size()): 113 | to_return += str(pad, 'Pending: ', pending_texts[i], "\n") 114 | for i in range(pass_texts.size()): 115 | to_return += str(pad, 'Pass: ', pass_texts[i], "\n") 116 | return to_return 117 | 118 | 119 | -------------------------------------------------------------------------------- /addons/gut/comparator.gd: -------------------------------------------------------------------------------- 1 | var _strutils = GutUtils.Strutils.new() 2 | var _max_length = 100 3 | var _should_compare_int_to_float = true 4 | 5 | const MISSING = '|__missing__gut__compare__value__|' 6 | 7 | 8 | func _cannot_compare_text(v1, v2): 9 | return str('Cannot compare ', _strutils.types[typeof(v1)], ' with ', 10 | _strutils.types[typeof(v2)], '.') 11 | 12 | 13 | func _make_missing_string(text): 14 | return '' 15 | 16 | 17 | func _create_missing_result(v1, v2, text): 18 | var to_return = null 19 | var v1_str = format_value(v1) 20 | var v2_str = format_value(v2) 21 | 22 | if(typeof(v1) == TYPE_STRING and v1 == MISSING): 23 | v1_str = _make_missing_string(text) 24 | to_return = GutUtils.CompareResult.new() 25 | elif(typeof(v2) == TYPE_STRING and v2 == MISSING): 26 | v2_str = _make_missing_string(text) 27 | to_return = GutUtils.CompareResult.new() 28 | 29 | if(to_return != null): 30 | to_return.summary = str(v1_str, ' != ', v2_str) 31 | to_return.are_equal = false 32 | 33 | return to_return 34 | 35 | 36 | func simple(v1, v2, missing_string=''): 37 | var missing_result = _create_missing_result(v1, v2, missing_string) 38 | if(missing_result != null): 39 | return missing_result 40 | 41 | var result = GutUtils.CompareResult.new() 42 | var cmp_str = null 43 | var extra = '' 44 | 45 | var tv1 = typeof(v1) 46 | var tv2 = typeof(v2) 47 | 48 | # print(tv1, '::', tv2, ' ', _strutils.types[tv1], '::', _strutils.types[tv2]) 49 | if(_should_compare_int_to_float and [TYPE_INT, TYPE_FLOAT].has(tv1) and [TYPE_INT, TYPE_FLOAT].has(tv2)): 50 | result.are_equal = v1 == v2 51 | elif([TYPE_STRING, TYPE_STRING_NAME].has(tv1) and [TYPE_STRING, TYPE_STRING_NAME].has(tv2)): 52 | result.are_equal = v1 == v2 53 | elif(GutUtils.are_datatypes_same(v1, v2)): 54 | result.are_equal = v1 == v2 55 | 56 | if(typeof(v1) == TYPE_DICTIONARY or typeof(v1) == TYPE_ARRAY): 57 | var sub_result = GutUtils.DiffTool.new(v1, v2, GutUtils.DIFF.DEEP) 58 | result.summary = sub_result.get_short_summary() 59 | if(!sub_result.are_equal): 60 | extra = ".\n" + sub_result.get_short_summary() 61 | else: 62 | cmp_str = '!=' 63 | result.are_equal = false 64 | extra = str('. ', _cannot_compare_text(v1, v2)) 65 | 66 | cmp_str = get_compare_symbol(result.are_equal) 67 | result.summary = str(format_value(v1), ' ', cmp_str, ' ', format_value(v2), extra) 68 | 69 | return result 70 | 71 | 72 | func shallow(v1, v2): 73 | var result = null 74 | if(GutUtils.are_datatypes_same(v1, v2)): 75 | if(typeof(v1) in [TYPE_ARRAY, TYPE_DICTIONARY]): 76 | result = GutUtils.DiffTool.new(v1, v2, GutUtils.DIFF.DEEP) 77 | else: 78 | result = simple(v1, v2) 79 | else: 80 | result = simple(v1, v2) 81 | 82 | return result 83 | 84 | 85 | func deep(v1, v2): 86 | var result = null 87 | 88 | if(GutUtils.are_datatypes_same(v1, v2)): 89 | if(typeof(v1) in [TYPE_ARRAY, TYPE_DICTIONARY]): 90 | result = GutUtils.DiffTool.new(v1, v2, GutUtils.DIFF.DEEP) 91 | else: 92 | result = simple(v1, v2) 93 | else: 94 | result = simple(v1, v2) 95 | 96 | return result 97 | 98 | 99 | func format_value(val, max_val_length=_max_length): 100 | return _strutils.truncate_string(_strutils.type2str(val), max_val_length) 101 | 102 | 103 | func compare(v1, v2, diff_type=GutUtils.DIFF.SIMPLE): 104 | var result = null 105 | if(diff_type == GutUtils.DIFF.SIMPLE): 106 | result = simple(v1, v2) 107 | elif(diff_type == GutUtils.DIFF.DEEP): 108 | result = deep(v1, v2) 109 | 110 | return result 111 | 112 | 113 | func get_should_compare_int_to_float(): 114 | return _should_compare_int_to_float 115 | 116 | 117 | func set_should_compare_int_to_float(should_compare_int_float): 118 | _should_compare_int_to_float = should_compare_int_float 119 | 120 | 121 | func get_compare_symbol(is_equal): 122 | if(is_equal): 123 | return '==' 124 | else: 125 | return '!=' 126 | -------------------------------------------------------------------------------- /addons/gut/compare_result.gd: -------------------------------------------------------------------------------- 1 | var _are_equal = false 2 | var are_equal = false : 3 | get: 4 | return get_are_equal() 5 | set(val): 6 | set_are_equal(val) 7 | 8 | var _summary = null 9 | var summary = null : 10 | get: 11 | return get_summary() 12 | set(val): 13 | set_summary(val) 14 | 15 | var _max_differences = 30 16 | var max_differences = 30 : 17 | get: 18 | return get_max_differences() 19 | set(val): 20 | set_max_differences(val) 21 | 22 | var _differences = {} 23 | var differences : 24 | get: 25 | return get_differences() 26 | set(val): 27 | set_differences(val) 28 | 29 | func _block_set(which, val): 30 | push_error(str('cannot set ', which, ', value [', val, '] ignored.')) 31 | 32 | func _to_string(): 33 | return str(get_summary()) # could be null, gotta str it. 34 | 35 | func get_are_equal(): 36 | return _are_equal 37 | 38 | func set_are_equal(r_eq): 39 | _are_equal = r_eq 40 | 41 | func get_summary(): 42 | return _summary 43 | 44 | func set_summary(smry): 45 | _summary = smry 46 | 47 | func get_total_count(): 48 | pass 49 | 50 | func get_different_count(): 51 | pass 52 | 53 | func get_short_summary(): 54 | return summary 55 | 56 | func get_max_differences(): 57 | return _max_differences 58 | 59 | func set_max_differences(max_diff): 60 | _max_differences = max_diff 61 | 62 | func get_differences(): 63 | return _differences 64 | 65 | func set_differences(diffs): 66 | _block_set('differences', diffs) 67 | 68 | func get_brackets(): 69 | return null 70 | 71 | -------------------------------------------------------------------------------- /addons/gut/diff_formatter.gd: -------------------------------------------------------------------------------- 1 | var _strutils = GutUtils.Strutils.new() 2 | const INDENT = ' ' 3 | var _max_to_display = 30 4 | const ABSOLUTE_MAX_DISPLAYED = 10000 5 | const UNLIMITED = -1 6 | 7 | 8 | func _single_diff(diff, depth=0): 9 | var to_return = "" 10 | var brackets = diff.get_brackets() 11 | 12 | if(brackets != null and !diff.are_equal): 13 | to_return = '' 14 | to_return += str(brackets.open, "\n", 15 | _strutils.indent_text(differences_to_s(diff.differences, depth), depth+1, INDENT), "\n", 16 | brackets.close) 17 | else: 18 | to_return = str(diff) 19 | 20 | return to_return 21 | 22 | 23 | func make_it(diff): 24 | var to_return = '' 25 | if(diff.are_equal): 26 | to_return = diff.summary 27 | else: 28 | if(_max_to_display == ABSOLUTE_MAX_DISPLAYED): 29 | to_return = str(diff.get_value_1(), ' != ', diff.get_value_2()) 30 | else: 31 | to_return = diff.get_short_summary() 32 | to_return += str("\n", _strutils.indent_text(_single_diff(diff, 0), 1, ' ')) 33 | return to_return 34 | 35 | 36 | func differences_to_s(differences, depth=0): 37 | var to_return = '' 38 | var keys = differences.keys() 39 | keys.sort() 40 | var limit = min(_max_to_display, differences.size()) 41 | 42 | for i in range(limit): 43 | var key = keys[i] 44 | to_return += str(key, ": ", _single_diff(differences[key], depth)) 45 | 46 | if(i != limit -1): 47 | to_return += "\n" 48 | 49 | if(differences.size() > _max_to_display): 50 | to_return += str("\n\n... ", differences.size() - _max_to_display, " more.") 51 | 52 | return to_return 53 | 54 | 55 | func get_max_to_display(): 56 | return _max_to_display 57 | 58 | 59 | func set_max_to_display(max_to_display): 60 | _max_to_display = max_to_display 61 | if(_max_to_display == UNLIMITED): 62 | _max_to_display = ABSOLUTE_MAX_DISPLAYED 63 | 64 | -------------------------------------------------------------------------------- /addons/gut/diff_tool.gd: -------------------------------------------------------------------------------- 1 | extends 'res://addons/gut/compare_result.gd' 2 | const INDENT = ' ' 3 | enum { 4 | DEEP, 5 | SIMPLE 6 | } 7 | 8 | var _strutils = GutUtils.Strutils.new() 9 | var _compare = GutUtils.Comparator.new() 10 | var DiffTool = load('res://addons/gut/diff_tool.gd') 11 | 12 | var _value_1 = null 13 | var _value_2 = null 14 | var _total_count = 0 15 | var _diff_type = null 16 | var _brackets = null 17 | var _valid = true 18 | var _desc_things = 'somethings' 19 | 20 | # -------- comapre_result.gd "interface" --------------------- 21 | func set_are_equal(val): 22 | _block_set('are_equal', val) 23 | 24 | func get_are_equal(): 25 | if(!_valid): 26 | return null 27 | else: 28 | return differences.size() == 0 29 | 30 | 31 | func set_summary(val): 32 | _block_set('summary', val) 33 | 34 | func get_summary(): 35 | return summarize() 36 | 37 | func get_different_count(): 38 | return differences.size() 39 | 40 | func get_total_count(): 41 | return _total_count 42 | 43 | func get_short_summary(): 44 | var text = str(_strutils.truncate_string(str(_value_1), 50), 45 | ' ', _compare.get_compare_symbol(are_equal), ' ', 46 | _strutils.truncate_string(str(_value_2), 50)) 47 | if(!are_equal): 48 | text += str(' ', get_different_count(), ' of ', get_total_count(), 49 | ' ', _desc_things, ' do not match.') 50 | return text 51 | 52 | func get_brackets(): 53 | return _brackets 54 | # -------- comapre_result.gd "interface" --------------------- 55 | 56 | 57 | func _invalidate(): 58 | _valid = false 59 | differences = null 60 | 61 | 62 | func _init(v1,v2,diff_type=DEEP): 63 | _value_1 = v1 64 | _value_2 = v2 65 | _diff_type = diff_type 66 | _compare.set_should_compare_int_to_float(false) 67 | _find_differences(_value_1, _value_2) 68 | 69 | 70 | func _find_differences(v1, v2): 71 | if(GutUtils.are_datatypes_same(v1, v2)): 72 | if(typeof(v1) == TYPE_ARRAY): 73 | _brackets = {'open':'[', 'close':']'} 74 | _desc_things = 'indexes' 75 | _diff_array(v1, v2) 76 | elif(typeof(v2) == TYPE_DICTIONARY): 77 | _brackets = {'open':'{', 'close':'}'} 78 | _desc_things = 'keys' 79 | _diff_dictionary(v1, v2) 80 | else: 81 | _invalidate() 82 | GutUtils.get_logger().error('Only Arrays and Dictionaries are supported.') 83 | else: 84 | _invalidate() 85 | GutUtils.get_logger().error('Only Arrays and Dictionaries are supported.') 86 | 87 | 88 | func _diff_array(a1, a2): 89 | _total_count = max(a1.size(), a2.size()) 90 | for i in range(a1.size()): 91 | var result = null 92 | if(i < a2.size()): 93 | if(_diff_type == DEEP): 94 | result = _compare.deep(a1[i], a2[i]) 95 | else: 96 | result = _compare.simple(a1[i], a2[i]) 97 | else: 98 | result = _compare.simple(a1[i], _compare.MISSING, 'index') 99 | 100 | if(!result.are_equal): 101 | differences[i] = result 102 | 103 | if(a1.size() < a2.size()): 104 | for i in range(a1.size(), a2.size()): 105 | differences[i] = _compare.simple(_compare.MISSING, a2[i], 'index') 106 | 107 | 108 | func _diff_dictionary(d1, d2): 109 | var d1_keys = d1.keys() 110 | var d2_keys = d2.keys() 111 | 112 | # Process all the keys in d1 113 | _total_count += d1_keys.size() 114 | for key in d1_keys: 115 | if(!d2.has(key)): 116 | differences[key] = _compare.simple(d1[key], _compare.MISSING, 'key') 117 | else: 118 | d2_keys.remove_at(d2_keys.find(key)) 119 | 120 | var result = null 121 | if(_diff_type == DEEP): 122 | result = _compare.deep(d1[key], d2[key]) 123 | else: 124 | result = _compare.simple(d1[key], d2[key]) 125 | 126 | if(!result.are_equal): 127 | differences[key] = result 128 | 129 | # Process all the keys in d2 that didn't exist in d1 130 | _total_count += d2_keys.size() 131 | for i in range(d2_keys.size()): 132 | differences[d2_keys[i]] = _compare.simple(_compare.MISSING, d2[d2_keys[i]], 'key') 133 | 134 | 135 | func summarize(): 136 | var summary = '' 137 | 138 | if(are_equal): 139 | summary = get_short_summary() 140 | else: 141 | var formatter = load('res://addons/gut/diff_formatter.gd').new() 142 | formatter.set_max_to_display(max_differences) 143 | summary = formatter.make_it(self) 144 | 145 | return summary 146 | 147 | 148 | func get_diff_type(): 149 | return _diff_type 150 | 151 | 152 | func get_value_1(): 153 | return _value_1 154 | 155 | 156 | func get_value_2(): 157 | return _value_2 158 | -------------------------------------------------------------------------------- /addons/gut/double_templates/function_template.txt: -------------------------------------------------------------------------------- 1 | {func_decleration} 2 | {vararg_warning}__gutdbl.spy_on('{method_name}', {param_array}) 3 | if(__gutdbl.is_stubbed_to_call_super('{method_name}', {param_array})): 4 | return {super_call} 5 | else: 6 | return await __gutdbl.handle_other_stubs('{method_name}', {param_array}) 7 | -------------------------------------------------------------------------------- /addons/gut/double_templates/init_template.txt: -------------------------------------------------------------------------------- 1 | {func_decleration}: 2 | super({super_params}) 3 | __gutdbl.spy_on('{method_name}', {param_array}) 4 | 5 | -------------------------------------------------------------------------------- /addons/gut/double_templates/script_template.txt: -------------------------------------------------------------------------------- 1 | # ############################################################################## 2 | # Gut Doubled Script 3 | # ############################################################################## 4 | {extends} 5 | 6 | {constants} 7 | 8 | {properties} 9 | 10 | # ------------------------------------------------------------------------------ 11 | # GUT stuff 12 | # ------------------------------------------------------------------------------ 13 | var __gutdbl_values = { 14 | double = self, 15 | thepath = '{path}', 16 | subpath = '{subpath}', 17 | stubber = {stubber_id}, 18 | spy = {spy_id}, 19 | gut = {gut_id}, 20 | from_singleton = '{singleton_name}', 21 | is_partial = {is_partial}, 22 | doubled_methods = {doubled_methods}, 23 | } 24 | var __gutdbl = load('res://addons/gut/double_tools.gd').new(__gutdbl_values) 25 | 26 | # Here so other things can check for a method to know if this is a double. 27 | func __gutdbl_check_method__(): 28 | pass 29 | 30 | # ------------------------------------------------------------------------------ 31 | # Doubled Methods 32 | # ------------------------------------------------------------------------------ 33 | -------------------------------------------------------------------------------- /addons/gut/double_tools.gd: -------------------------------------------------------------------------------- 1 | var thepath = '' 2 | var subpath = '' 3 | var stubber = null 4 | var spy = null 5 | var gut = null 6 | var from_singleton = null 7 | var is_partial = null 8 | var double = null 9 | 10 | const NO_DEFAULT_VALUE = '!__gut__no__default__value__!' 11 | func _init(values=null): 12 | if(values != null): 13 | double = values.double 14 | thepath = values.thepath 15 | subpath = values.subpath 16 | stubber = from_id(values.stubber) 17 | spy = from_id(values.spy) 18 | gut = from_id(values.gut) 19 | from_singleton = values.from_singleton 20 | is_partial = values.is_partial 21 | 22 | if(gut != null): 23 | gut.get_autofree().add_free(double) 24 | 25 | 26 | func _get_stubbed_method_to_call(method_name, called_with): 27 | var method = stubber.get_call_this(double, method_name, called_with) 28 | if(method != null): 29 | method = method.bindv(called_with) 30 | return method 31 | return method 32 | 33 | 34 | func from_id(inst_id): 35 | if(inst_id == -1): 36 | return null 37 | else: 38 | return instance_from_id(inst_id) 39 | 40 | 41 | func is_stubbed_to_call_super(method_name, called_with): 42 | if(stubber != null): 43 | return stubber.should_call_super(double, method_name, called_with) 44 | else: 45 | return false 46 | 47 | 48 | func handle_other_stubs(method_name, called_with): 49 | if(stubber == null): 50 | return 51 | 52 | var method = _get_stubbed_method_to_call(method_name, called_with) 53 | if(method != null): 54 | return await method.call() 55 | else: 56 | return stubber.get_return(double, method_name, called_with) 57 | 58 | 59 | func spy_on(method_name, called_with): 60 | if(spy != null): 61 | spy.add_call(double, method_name, called_with) 62 | 63 | 64 | func default_val(method_name, p_index, default_val=NO_DEFAULT_VALUE): 65 | if(stubber != null): 66 | return stubber.get_default_value(double, method_name, p_index) 67 | else: 68 | return null 69 | 70 | 71 | func vararg_warning(): 72 | if(gut != null): 73 | gut.get_logger().warn( 74 | "This method contains a vararg argument and the paramter count was not stubbed. " + \ 75 | "GUT adds extra parameters to this method which should fill most needs. " + \ 76 | "It is recommended that you stub param_count for this object's class to ensure " + \ 77 | "that there are not any parameter count mismatch errors.") 78 | -------------------------------------------------------------------------------- /addons/gut/dynamic_gdscript.gd: -------------------------------------------------------------------------------- 1 | @tool 2 | var default_script_name_no_extension = 'gut_dynamic_script' 3 | var default_script_resource_path = 'res://addons/gut/not_a_real_file/' 4 | var default_script_extension = "gd" 5 | 6 | var _created_script_count = 0 7 | 8 | 9 | # Creates a loaded script from the passed in source. This loaded script is 10 | # returned unless there is an error. When an error occcurs the error number 11 | # is returned instead. 12 | func create_script_from_source(source, override_path=null): 13 | _created_script_count += 1 14 | var r_path = str(default_script_resource_path, 15 | default_script_name_no_extension, '_', _created_script_count, ".", 16 | default_script_extension) 17 | 18 | if(override_path != null): 19 | r_path = override_path 20 | 21 | var DynamicScript = GDScript.new() 22 | DynamicScript.source_code = source.dedent() 23 | # The resource_path must be unique or Godot thinks it is trying 24 | # to load something it has already loaded and generates an error like 25 | # ERROR: Another resource is loaded from path 'workaround for godot 26 | # issue #65263' (possible cyclic resource inclusion). 27 | DynamicScript.resource_path = r_path 28 | var result = DynamicScript.reload() 29 | if(result != OK): 30 | DynamicScript = result 31 | 32 | return DynamicScript 33 | 34 | -------------------------------------------------------------------------------- /addons/gut/fonts/AnonymousPro-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitwes/Gut/ded7a878d6515bdf5a9ace992a0dee93cdd77df9/addons/gut/fonts/AnonymousPro-Bold.ttf -------------------------------------------------------------------------------- /addons/gut/fonts/AnonymousPro-Bold.ttf.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="font_data_dynamic" 4 | type="FontFile" 5 | uid="uid://c8axnpxc0nrk4" 6 | path="res://.godot/imported/AnonymousPro-Bold.ttf-9d8fef4d357af5b52cd60afbe608aa49.fontdata" 7 | 8 | [deps] 9 | 10 | source_file="res://addons/gut/fonts/AnonymousPro-Bold.ttf" 11 | dest_files=["res://.godot/imported/AnonymousPro-Bold.ttf-9d8fef4d357af5b52cd60afbe608aa49.fontdata"] 12 | 13 | [params] 14 | 15 | Rendering=null 16 | antialiasing=1 17 | generate_mipmaps=false 18 | disable_embedded_bitmaps=true 19 | multichannel_signed_distance_field=false 20 | msdf_pixel_range=8 21 | msdf_size=48 22 | allow_system_fallback=true 23 | force_autohinter=false 24 | hinting=1 25 | subpixel_positioning=1 26 | oversampling=0.0 27 | Fallbacks=null 28 | fallbacks=[] 29 | Compress=null 30 | compress=true 31 | preload=[] 32 | language_support={} 33 | script_support={} 34 | opentype_features={} 35 | -------------------------------------------------------------------------------- /addons/gut/fonts/AnonymousPro-BoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitwes/Gut/ded7a878d6515bdf5a9ace992a0dee93cdd77df9/addons/gut/fonts/AnonymousPro-BoldItalic.ttf -------------------------------------------------------------------------------- /addons/gut/fonts/AnonymousPro-BoldItalic.ttf.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="font_data_dynamic" 4 | type="FontFile" 5 | uid="uid://msst1l2s2s" 6 | path="res://.godot/imported/AnonymousPro-BoldItalic.ttf-4274bf704d3d6b9cd32c4f0754d8c83d.fontdata" 7 | 8 | [deps] 9 | 10 | source_file="res://addons/gut/fonts/AnonymousPro-BoldItalic.ttf" 11 | dest_files=["res://.godot/imported/AnonymousPro-BoldItalic.ttf-4274bf704d3d6b9cd32c4f0754d8c83d.fontdata"] 12 | 13 | [params] 14 | 15 | Rendering=null 16 | antialiasing=1 17 | generate_mipmaps=false 18 | disable_embedded_bitmaps=true 19 | multichannel_signed_distance_field=false 20 | msdf_pixel_range=8 21 | msdf_size=48 22 | allow_system_fallback=true 23 | force_autohinter=false 24 | hinting=1 25 | subpixel_positioning=1 26 | oversampling=0.0 27 | Fallbacks=null 28 | fallbacks=[] 29 | Compress=null 30 | compress=true 31 | preload=[] 32 | language_support={} 33 | script_support={} 34 | opentype_features={} 35 | -------------------------------------------------------------------------------- /addons/gut/fonts/AnonymousPro-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitwes/Gut/ded7a878d6515bdf5a9ace992a0dee93cdd77df9/addons/gut/fonts/AnonymousPro-Italic.ttf -------------------------------------------------------------------------------- /addons/gut/fonts/AnonymousPro-Italic.ttf.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="font_data_dynamic" 4 | type="FontFile" 5 | uid="uid://hf5rdg67jcwc" 6 | path="res://.godot/imported/AnonymousPro-Italic.ttf-9989590b02137b799e13d570de2a42c1.fontdata" 7 | 8 | [deps] 9 | 10 | source_file="res://addons/gut/fonts/AnonymousPro-Italic.ttf" 11 | dest_files=["res://.godot/imported/AnonymousPro-Italic.ttf-9989590b02137b799e13d570de2a42c1.fontdata"] 12 | 13 | [params] 14 | 15 | Rendering=null 16 | antialiasing=1 17 | generate_mipmaps=false 18 | disable_embedded_bitmaps=true 19 | multichannel_signed_distance_field=false 20 | msdf_pixel_range=8 21 | msdf_size=48 22 | allow_system_fallback=true 23 | force_autohinter=false 24 | hinting=1 25 | subpixel_positioning=1 26 | oversampling=0.0 27 | Fallbacks=null 28 | fallbacks=[] 29 | Compress=null 30 | compress=true 31 | preload=[] 32 | language_support={} 33 | script_support={} 34 | opentype_features={} 35 | -------------------------------------------------------------------------------- /addons/gut/fonts/AnonymousPro-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitwes/Gut/ded7a878d6515bdf5a9ace992a0dee93cdd77df9/addons/gut/fonts/AnonymousPro-Regular.ttf -------------------------------------------------------------------------------- /addons/gut/fonts/AnonymousPro-Regular.ttf.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="font_data_dynamic" 4 | type="FontFile" 5 | uid="uid://c6c7gnx36opr0" 6 | path="res://.godot/imported/AnonymousPro-Regular.ttf-856c843fd6f89964d2ca8d8ff1724f13.fontdata" 7 | 8 | [deps] 9 | 10 | source_file="res://addons/gut/fonts/AnonymousPro-Regular.ttf" 11 | dest_files=["res://.godot/imported/AnonymousPro-Regular.ttf-856c843fd6f89964d2ca8d8ff1724f13.fontdata"] 12 | 13 | [params] 14 | 15 | Rendering=null 16 | antialiasing=1 17 | generate_mipmaps=false 18 | disable_embedded_bitmaps=true 19 | multichannel_signed_distance_field=false 20 | msdf_pixel_range=8 21 | msdf_size=48 22 | allow_system_fallback=true 23 | force_autohinter=false 24 | hinting=1 25 | subpixel_positioning=1 26 | oversampling=0.0 27 | Fallbacks=null 28 | fallbacks=[] 29 | Compress=null 30 | compress=true 31 | preload=[] 32 | language_support={} 33 | script_support={} 34 | opentype_features={} 35 | -------------------------------------------------------------------------------- /addons/gut/fonts/CourierPrime-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitwes/Gut/ded7a878d6515bdf5a9ace992a0dee93cdd77df9/addons/gut/fonts/CourierPrime-Bold.ttf -------------------------------------------------------------------------------- /addons/gut/fonts/CourierPrime-Bold.ttf.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="font_data_dynamic" 4 | type="FontFile" 5 | uid="uid://bhjgpy1dovmyq" 6 | path="res://.godot/imported/CourierPrime-Bold.ttf-1f003c66d63ebed70964e7756f4fa235.fontdata" 7 | 8 | [deps] 9 | 10 | source_file="res://addons/gut/fonts/CourierPrime-Bold.ttf" 11 | dest_files=["res://.godot/imported/CourierPrime-Bold.ttf-1f003c66d63ebed70964e7756f4fa235.fontdata"] 12 | 13 | [params] 14 | 15 | Rendering=null 16 | antialiasing=1 17 | generate_mipmaps=false 18 | disable_embedded_bitmaps=true 19 | multichannel_signed_distance_field=false 20 | msdf_pixel_range=8 21 | msdf_size=48 22 | allow_system_fallback=true 23 | force_autohinter=false 24 | hinting=1 25 | subpixel_positioning=1 26 | oversampling=0.0 27 | Fallbacks=null 28 | fallbacks=[] 29 | Compress=null 30 | compress=true 31 | preload=[] 32 | language_support={} 33 | script_support={} 34 | opentype_features={} 35 | -------------------------------------------------------------------------------- /addons/gut/fonts/CourierPrime-BoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitwes/Gut/ded7a878d6515bdf5a9ace992a0dee93cdd77df9/addons/gut/fonts/CourierPrime-BoldItalic.ttf -------------------------------------------------------------------------------- /addons/gut/fonts/CourierPrime-BoldItalic.ttf.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="font_data_dynamic" 4 | type="FontFile" 5 | uid="uid://n6mxiov5sbgc" 6 | path="res://.godot/imported/CourierPrime-BoldItalic.ttf-65ebcc61dd5e1dfa8f96313da4ad7019.fontdata" 7 | 8 | [deps] 9 | 10 | source_file="res://addons/gut/fonts/CourierPrime-BoldItalic.ttf" 11 | dest_files=["res://.godot/imported/CourierPrime-BoldItalic.ttf-65ebcc61dd5e1dfa8f96313da4ad7019.fontdata"] 12 | 13 | [params] 14 | 15 | Rendering=null 16 | antialiasing=1 17 | generate_mipmaps=false 18 | disable_embedded_bitmaps=true 19 | multichannel_signed_distance_field=false 20 | msdf_pixel_range=8 21 | msdf_size=48 22 | allow_system_fallback=true 23 | force_autohinter=false 24 | hinting=1 25 | subpixel_positioning=1 26 | oversampling=0.0 27 | Fallbacks=null 28 | fallbacks=[] 29 | Compress=null 30 | compress=true 31 | preload=[] 32 | language_support={} 33 | script_support={} 34 | opentype_features={} 35 | -------------------------------------------------------------------------------- /addons/gut/fonts/CourierPrime-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitwes/Gut/ded7a878d6515bdf5a9ace992a0dee93cdd77df9/addons/gut/fonts/CourierPrime-Italic.ttf -------------------------------------------------------------------------------- /addons/gut/fonts/CourierPrime-Italic.ttf.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="font_data_dynamic" 4 | type="FontFile" 5 | uid="uid://mcht266g817e" 6 | path="res://.godot/imported/CourierPrime-Italic.ttf-baa9156a73770735a0f72fb20b907112.fontdata" 7 | 8 | [deps] 9 | 10 | source_file="res://addons/gut/fonts/CourierPrime-Italic.ttf" 11 | dest_files=["res://.godot/imported/CourierPrime-Italic.ttf-baa9156a73770735a0f72fb20b907112.fontdata"] 12 | 13 | [params] 14 | 15 | Rendering=null 16 | antialiasing=1 17 | generate_mipmaps=false 18 | disable_embedded_bitmaps=true 19 | multichannel_signed_distance_field=false 20 | msdf_pixel_range=8 21 | msdf_size=48 22 | allow_system_fallback=true 23 | force_autohinter=false 24 | hinting=1 25 | subpixel_positioning=1 26 | oversampling=0.0 27 | Fallbacks=null 28 | fallbacks=[] 29 | Compress=null 30 | compress=true 31 | preload=[] 32 | language_support={} 33 | script_support={} 34 | opentype_features={} 35 | -------------------------------------------------------------------------------- /addons/gut/fonts/CourierPrime-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitwes/Gut/ded7a878d6515bdf5a9ace992a0dee93cdd77df9/addons/gut/fonts/CourierPrime-Regular.ttf -------------------------------------------------------------------------------- /addons/gut/fonts/CourierPrime-Regular.ttf.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="font_data_dynamic" 4 | type="FontFile" 5 | uid="uid://bnh0lslf4yh87" 6 | path="res://.godot/imported/CourierPrime-Regular.ttf-3babe7e4a7a588dfc9a84c14b4f1fe23.fontdata" 7 | 8 | [deps] 9 | 10 | source_file="res://addons/gut/fonts/CourierPrime-Regular.ttf" 11 | dest_files=["res://.godot/imported/CourierPrime-Regular.ttf-3babe7e4a7a588dfc9a84c14b4f1fe23.fontdata"] 12 | 13 | [params] 14 | 15 | Rendering=null 16 | antialiasing=1 17 | generate_mipmaps=false 18 | disable_embedded_bitmaps=true 19 | multichannel_signed_distance_field=false 20 | msdf_pixel_range=8 21 | msdf_size=48 22 | allow_system_fallback=true 23 | force_autohinter=false 24 | hinting=1 25 | subpixel_positioning=1 26 | oversampling=0.0 27 | Fallbacks=null 28 | fallbacks=[] 29 | Compress=null 30 | compress=true 31 | preload=[] 32 | language_support={} 33 | script_support={} 34 | opentype_features={} 35 | -------------------------------------------------------------------------------- /addons/gut/fonts/LobsterTwo-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitwes/Gut/ded7a878d6515bdf5a9ace992a0dee93cdd77df9/addons/gut/fonts/LobsterTwo-Bold.ttf -------------------------------------------------------------------------------- /addons/gut/fonts/LobsterTwo-Bold.ttf.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="font_data_dynamic" 4 | type="FontFile" 5 | uid="uid://cmiuntu71oyl3" 6 | path="res://.godot/imported/LobsterTwo-Bold.ttf-7c7f734103b58a32491a4788186f3dcb.fontdata" 7 | 8 | [deps] 9 | 10 | source_file="res://addons/gut/fonts/LobsterTwo-Bold.ttf" 11 | dest_files=["res://.godot/imported/LobsterTwo-Bold.ttf-7c7f734103b58a32491a4788186f3dcb.fontdata"] 12 | 13 | [params] 14 | 15 | Rendering=null 16 | antialiasing=1 17 | generate_mipmaps=false 18 | disable_embedded_bitmaps=true 19 | multichannel_signed_distance_field=false 20 | msdf_pixel_range=8 21 | msdf_size=48 22 | allow_system_fallback=true 23 | force_autohinter=false 24 | hinting=1 25 | subpixel_positioning=1 26 | oversampling=0.0 27 | Fallbacks=null 28 | fallbacks=[] 29 | Compress=null 30 | compress=true 31 | preload=[] 32 | language_support={} 33 | script_support={} 34 | opentype_features={} 35 | -------------------------------------------------------------------------------- /addons/gut/fonts/LobsterTwo-BoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitwes/Gut/ded7a878d6515bdf5a9ace992a0dee93cdd77df9/addons/gut/fonts/LobsterTwo-BoldItalic.ttf -------------------------------------------------------------------------------- /addons/gut/fonts/LobsterTwo-BoldItalic.ttf.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="font_data_dynamic" 4 | type="FontFile" 5 | uid="uid://bll38n2ct6qme" 6 | path="res://.godot/imported/LobsterTwo-BoldItalic.ttf-227406a33e84448e6aa974176016de19.fontdata" 7 | 8 | [deps] 9 | 10 | source_file="res://addons/gut/fonts/LobsterTwo-BoldItalic.ttf" 11 | dest_files=["res://.godot/imported/LobsterTwo-BoldItalic.ttf-227406a33e84448e6aa974176016de19.fontdata"] 12 | 13 | [params] 14 | 15 | Rendering=null 16 | antialiasing=1 17 | generate_mipmaps=false 18 | disable_embedded_bitmaps=true 19 | multichannel_signed_distance_field=false 20 | msdf_pixel_range=8 21 | msdf_size=48 22 | allow_system_fallback=true 23 | force_autohinter=false 24 | hinting=1 25 | subpixel_positioning=1 26 | oversampling=0.0 27 | Fallbacks=null 28 | fallbacks=[] 29 | Compress=null 30 | compress=true 31 | preload=[] 32 | language_support={} 33 | script_support={} 34 | opentype_features={} 35 | -------------------------------------------------------------------------------- /addons/gut/fonts/LobsterTwo-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitwes/Gut/ded7a878d6515bdf5a9ace992a0dee93cdd77df9/addons/gut/fonts/LobsterTwo-Italic.ttf -------------------------------------------------------------------------------- /addons/gut/fonts/LobsterTwo-Italic.ttf.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="font_data_dynamic" 4 | type="FontFile" 5 | uid="uid://dis65h8wxc3f2" 6 | path="res://.godot/imported/LobsterTwo-Italic.ttf-f93abf6c25390c85ad5fb6c4ee75159e.fontdata" 7 | 8 | [deps] 9 | 10 | source_file="res://addons/gut/fonts/LobsterTwo-Italic.ttf" 11 | dest_files=["res://.godot/imported/LobsterTwo-Italic.ttf-f93abf6c25390c85ad5fb6c4ee75159e.fontdata"] 12 | 13 | [params] 14 | 15 | Rendering=null 16 | antialiasing=1 17 | generate_mipmaps=false 18 | disable_embedded_bitmaps=true 19 | multichannel_signed_distance_field=false 20 | msdf_pixel_range=8 21 | msdf_size=48 22 | allow_system_fallback=true 23 | force_autohinter=false 24 | hinting=1 25 | subpixel_positioning=1 26 | oversampling=0.0 27 | Fallbacks=null 28 | fallbacks=[] 29 | Compress=null 30 | compress=true 31 | preload=[] 32 | language_support={} 33 | script_support={} 34 | opentype_features={} 35 | -------------------------------------------------------------------------------- /addons/gut/fonts/LobsterTwo-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitwes/Gut/ded7a878d6515bdf5a9ace992a0dee93cdd77df9/addons/gut/fonts/LobsterTwo-Regular.ttf -------------------------------------------------------------------------------- /addons/gut/fonts/LobsterTwo-Regular.ttf.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="font_data_dynamic" 4 | type="FontFile" 5 | uid="uid://5e8msj0ih2pv" 6 | path="res://.godot/imported/LobsterTwo-Regular.ttf-f3fcfa01cd671c8da433dd875d0fe04b.fontdata" 7 | 8 | [deps] 9 | 10 | source_file="res://addons/gut/fonts/LobsterTwo-Regular.ttf" 11 | dest_files=["res://.godot/imported/LobsterTwo-Regular.ttf-f3fcfa01cd671c8da433dd875d0fe04b.fontdata"] 12 | 13 | [params] 14 | 15 | Rendering=null 16 | antialiasing=1 17 | generate_mipmaps=false 18 | disable_embedded_bitmaps=true 19 | multichannel_signed_distance_field=false 20 | msdf_pixel_range=8 21 | msdf_size=48 22 | allow_system_fallback=true 23 | force_autohinter=false 24 | hinting=1 25 | subpixel_positioning=1 26 | oversampling=0.0 27 | Fallbacks=null 28 | fallbacks=[] 29 | Compress=null 30 | compress=true 31 | preload=[] 32 | language_support={} 33 | script_support={} 34 | opentype_features={} 35 | -------------------------------------------------------------------------------- /addons/gut/fonts/OFL.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2009, Mark Simonson (http://www.ms-studio.com, mark@marksimonson.com), 2 | with Reserved Font Name Anonymous Pro. 3 | 4 | This Font Software is licensed under the SIL Open Font License, Version 1.1. 5 | This license is copied below, and is also available with a FAQ at: 6 | http://scripts.sil.org/OFL 7 | 8 | 9 | ----------------------------------------------------------- 10 | SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 11 | ----------------------------------------------------------- 12 | 13 | PREAMBLE 14 | The goals of the Open Font License (OFL) are to stimulate worldwide 15 | development of collaborative font projects, to support the font creation 16 | efforts of academic and linguistic communities, and to provide a free and 17 | open framework in which fonts may be shared and improved in partnership 18 | with others. 19 | 20 | The OFL allows the licensed fonts to be used, studied, modified and 21 | redistributed freely as long as they are not sold by themselves. The 22 | fonts, including any derivative works, can be bundled, embedded, 23 | redistributed and/or sold with any software provided that any reserved 24 | names are not used by derivative works. The fonts and derivatives, 25 | however, cannot be released under any other type of license. The 26 | requirement for fonts to remain under this license does not apply 27 | to any document created using the fonts or their derivatives. 28 | 29 | DEFINITIONS 30 | "Font Software" refers to the set of files released by the Copyright 31 | Holder(s) under this license and clearly marked as such. This may 32 | include source files, build scripts and documentation. 33 | 34 | "Reserved Font Name" refers to any names specified as such after the 35 | copyright statement(s). 36 | 37 | "Original Version" refers to the collection of Font Software components as 38 | distributed by the Copyright Holder(s). 39 | 40 | "Modified Version" refers to any derivative made by adding to, deleting, 41 | or substituting -- in part or in whole -- any of the components of the 42 | Original Version, by changing formats or by porting the Font Software to a 43 | new environment. 44 | 45 | "Author" refers to any designer, engineer, programmer, technical 46 | writer or other person who contributed to the Font Software. 47 | 48 | PERMISSION & CONDITIONS 49 | Permission is hereby granted, free of charge, to any person obtaining 50 | a copy of the Font Software, to use, study, copy, merge, embed, modify, 51 | redistribute, and sell modified and unmodified copies of the Font 52 | Software, subject to the following conditions: 53 | 54 | 1) Neither the Font Software nor any of its individual components, 55 | in Original or Modified Versions, may be sold by itself. 56 | 57 | 2) Original or Modified Versions of the Font Software may be bundled, 58 | redistributed and/or sold with any software, provided that each copy 59 | contains the above copyright notice and this license. These can be 60 | included either as stand-alone text files, human-readable headers or 61 | in the appropriate machine-readable metadata fields within text or 62 | binary files as long as those fields can be easily viewed by the user. 63 | 64 | 3) No Modified Version of the Font Software may use the Reserved Font 65 | Name(s) unless explicit written permission is granted by the corresponding 66 | Copyright Holder. This restriction only applies to the primary font name as 67 | presented to the users. 68 | 69 | 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font 70 | Software shall not be used to promote, endorse or advertise any 71 | Modified Version, except to acknowledge the contribution(s) of the 72 | Copyright Holder(s) and the Author(s) or with their explicit written 73 | permission. 74 | 75 | 5) The Font Software, modified or unmodified, in part or in whole, 76 | must be distributed entirely under this license, and must not be 77 | distributed under any other license. The requirement for fonts to 78 | remain under this license does not apply to any document created 79 | using the Font Software. 80 | 81 | TERMINATION 82 | This license becomes null and void if any of the above conditions are 83 | not met. 84 | 85 | DISCLAIMER 86 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 87 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF 88 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT 89 | OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE 90 | COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 91 | INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL 92 | DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 93 | FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM 94 | OTHER DEALINGS IN THE FONT SOFTWARE. 95 | -------------------------------------------------------------------------------- /addons/gut/gui/BottomPanelShortcuts.gd: -------------------------------------------------------------------------------- 1 | @tool 2 | extends Window 3 | 4 | var GutEditorGlobals = load('res://addons/gut/gui/editor_globals.gd') 5 | var default_path = GutEditorGlobals.editor_shortcuts_path 6 | 7 | @onready var _ctrls = { 8 | run_all = $Layout/CRunAll/ShortcutButton, 9 | run_current_script = $Layout/CRunCurrentScript/ShortcutButton, 10 | run_current_inner = $Layout/CRunCurrentInner/ShortcutButton, 11 | run_current_test = $Layout/CRunCurrentTest/ShortcutButton, 12 | panel_button = $Layout/CPanelButton/ShortcutButton, 13 | } 14 | 15 | var _user_prefs = GutEditorGlobals.user_prefs 16 | 17 | func _ready(): 18 | for key in _ctrls: 19 | var sc_button = _ctrls[key] 20 | sc_button.connect('start_edit', _on_edit_start.bind(sc_button)) 21 | sc_button.connect('end_edit', _on_edit_end) 22 | 23 | # show dialog when running scene from editor. 24 | if(get_parent() == get_tree().root): 25 | popup_centered() 26 | 27 | 28 | func _cancel_all(): 29 | _ctrls.run_all.cancel() 30 | _ctrls.run_current_script.cancel() 31 | _ctrls.run_current_inner.cancel() 32 | _ctrls.run_current_test.cancel() 33 | _ctrls.panel_button.cancel() 34 | 35 | # ------------ 36 | # Events 37 | # ------------ 38 | func _on_Hide_pressed(): 39 | hide() 40 | 41 | 42 | func _on_edit_start(which): 43 | for key in _ctrls: 44 | var sc_button = _ctrls[key] 45 | if(sc_button != which): 46 | sc_button.disable_set(true) 47 | sc_button.disable_clear(true) 48 | 49 | 50 | func _on_edit_end(): 51 | for key in _ctrls: 52 | var sc_button = _ctrls[key] 53 | sc_button.disable_set(false) 54 | sc_button.disable_clear(false) 55 | 56 | 57 | func _on_popup_hide(): 58 | _cancel_all() 59 | 60 | # ------------ 61 | # Public 62 | # ------------ 63 | func get_run_all(): 64 | return _ctrls.run_all.get_shortcut() 65 | 66 | func get_run_current_script(): 67 | return _ctrls.run_current_script.get_shortcut() 68 | 69 | func get_run_current_inner(): 70 | return _ctrls.run_current_inner.get_shortcut() 71 | 72 | func get_run_current_test(): 73 | return _ctrls.run_current_test.get_shortcut() 74 | 75 | func get_panel_button(): 76 | return _ctrls.panel_button.get_shortcut() 77 | 78 | func _set_pref_value(pref, button): 79 | pref.value = {shortcut = button.get_shortcut().events} 80 | 81 | 82 | func save_shortcuts(): 83 | save_shortcuts_to_file(default_path) 84 | 85 | 86 | func save_shortcuts_to_editor_settings(): 87 | _set_pref_value(_user_prefs.shortcut_run_all, _ctrls.run_all) 88 | _set_pref_value(_user_prefs.shortcut_run_current_script, _ctrls.run_current_script) 89 | _set_pref_value(_user_prefs.shortcut_run_current_inner, _ctrls.run_current_inner) 90 | _set_pref_value(_user_prefs.shortcut_run_current_test, _ctrls.run_current_test) 91 | _set_pref_value(_user_prefs.shortcut_panel_button, _ctrls.panel_button) 92 | 93 | _user_prefs.save_it() 94 | 95 | 96 | func save_shortcuts_to_file(path): 97 | var f = ConfigFile.new() 98 | f.set_value('main', 'run_all', _ctrls.run_all.get_shortcut()) 99 | f.set_value('main', 'run_current_script', _ctrls.run_current_script.get_shortcut()) 100 | f.set_value('main', 'run_current_inner', _ctrls.run_current_inner.get_shortcut()) 101 | f.set_value('main', 'run_current_test', _ctrls.run_current_test.get_shortcut()) 102 | f.set_value('main', 'panel_button', _ctrls.panel_button.get_shortcut()) 103 | f.save(path) 104 | 105 | 106 | func _load_shortcut_from_pref(user_pref): 107 | var to_return = Shortcut.new() 108 | # value with be _user_prefs.EMPTY which is a string when the value 109 | # has not been set. 110 | if(typeof(user_pref.value) == TYPE_DICTIONARY): 111 | to_return.events.append(user_pref.value.shortcut[0]) 112 | # to_return = user_pref.value 113 | return to_return 114 | 115 | 116 | func load_shortcuts(): 117 | load_shortcuts_from_file(default_path) 118 | 119 | 120 | func load_shortcuts_from_editor_settings(): 121 | var empty = Shortcut.new() 122 | 123 | _ctrls.run_all.set_shortcut(_load_shortcut_from_pref(_user_prefs.shortcut_run_all)) 124 | _ctrls.run_current_script.set_shortcut(_load_shortcut_from_pref(_user_prefs.shortcut_run_current_script)) 125 | _ctrls.run_current_inner.set_shortcut(_load_shortcut_from_pref(_user_prefs.shortcut_run_current_inner)) 126 | _ctrls.run_current_test.set_shortcut(_load_shortcut_from_pref(_user_prefs.shortcut_run_current_test)) 127 | _ctrls.panel_button.set_shortcut(_load_shortcut_from_pref(_user_prefs.shortcut_panel_button)) 128 | 129 | 130 | func load_shortcuts_from_file(path): 131 | var f = ConfigFile.new() 132 | var empty = Shortcut.new() 133 | 134 | f.load(path) 135 | _ctrls.run_all.set_shortcut(f.get_value('main', 'run_all', empty)) 136 | _ctrls.run_current_script.set_shortcut(f.get_value('main', 'run_current_script', empty)) 137 | _ctrls.run_current_inner.set_shortcut(f.get_value('main', 'run_current_inner', empty)) 138 | _ctrls.run_current_test.set_shortcut(f.get_value('main', 'run_current_test', empty)) 139 | _ctrls.panel_button.set_shortcut(f.get_value('main', 'panel_button', empty)) 140 | -------------------------------------------------------------------------------- /addons/gut/gui/BottomPanelShortcuts.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=3 format=3 uid="uid://bsk32dh41b4gs"] 2 | 3 | [ext_resource type="PackedScene" uid="uid://sfb1fw8j6ufu" path="res://addons/gut/gui/ShortcutButton.tscn" id="1"] 4 | [ext_resource type="Script" path="res://addons/gut/gui/BottomPanelShortcuts.gd" id="2"] 5 | 6 | [node name="BottomPanelShortcuts" type="Popup"] 7 | title = "Shortcuts" 8 | size = Vector2i(500, 350) 9 | visible = true 10 | exclusive = true 11 | unresizable = false 12 | borderless = false 13 | script = ExtResource("2") 14 | 15 | [node name="Layout" type="VBoxContainer" parent="."] 16 | anchors_preset = 15 17 | anchor_right = 1.0 18 | anchor_bottom = 1.0 19 | offset_left = 5.0 20 | offset_right = -5.0 21 | offset_bottom = 2.0 22 | 23 | [node name="TopPad" type="CenterContainer" parent="Layout"] 24 | custom_minimum_size = Vector2(0, 5) 25 | layout_mode = 2 26 | 27 | [node name="Label2" type="Label" parent="Layout"] 28 | custom_minimum_size = Vector2(0, 20) 29 | layout_mode = 2 30 | text = "Always Active" 31 | 32 | [node name="ColorRect" type="ColorRect" parent="Layout/Label2"] 33 | show_behind_parent = true 34 | layout_mode = 0 35 | anchor_right = 1.0 36 | anchor_bottom = 1.0 37 | color = Color(0, 0, 0, 0.196078) 38 | 39 | [node name="CPanelButton" type="HBoxContainer" parent="Layout"] 40 | layout_mode = 2 41 | 42 | [node name="Label" type="Label" parent="Layout/CPanelButton"] 43 | custom_minimum_size = Vector2(50, 0) 44 | layout_mode = 2 45 | size_flags_vertical = 7 46 | text = "Show/Hide GUT Panel" 47 | 48 | [node name="ShortcutButton" parent="Layout/CPanelButton" instance=ExtResource("1")] 49 | layout_mode = 2 50 | size_flags_horizontal = 3 51 | 52 | [node name="GutPanelPad" type="CenterContainer" parent="Layout"] 53 | custom_minimum_size = Vector2(0, 5) 54 | layout_mode = 2 55 | 56 | [node name="Label" type="Label" parent="Layout"] 57 | custom_minimum_size = Vector2(0, 20) 58 | layout_mode = 2 59 | text = "Only Active When GUT Panel Shown" 60 | 61 | [node name="ColorRect2" type="ColorRect" parent="Layout/Label"] 62 | show_behind_parent = true 63 | layout_mode = 0 64 | anchor_right = 1.0 65 | anchor_bottom = 1.0 66 | color = Color(0, 0, 0, 0.196078) 67 | 68 | [node name="TopPad2" type="CenterContainer" parent="Layout"] 69 | custom_minimum_size = Vector2(0, 5) 70 | layout_mode = 2 71 | 72 | [node name="CRunAll" type="HBoxContainer" parent="Layout"] 73 | layout_mode = 2 74 | 75 | [node name="Label" type="Label" parent="Layout/CRunAll"] 76 | custom_minimum_size = Vector2(50, 0) 77 | layout_mode = 2 78 | size_flags_vertical = 7 79 | text = "Run All" 80 | 81 | [node name="ShortcutButton" parent="Layout/CRunAll" instance=ExtResource("1")] 82 | layout_mode = 2 83 | size_flags_horizontal = 3 84 | 85 | [node name="CRunCurrentScript" type="HBoxContainer" parent="Layout"] 86 | layout_mode = 2 87 | 88 | [node name="Label" type="Label" parent="Layout/CRunCurrentScript"] 89 | custom_minimum_size = Vector2(50, 0) 90 | layout_mode = 2 91 | size_flags_vertical = 7 92 | text = "Run Current Script" 93 | 94 | [node name="ShortcutButton" parent="Layout/CRunCurrentScript" instance=ExtResource("1")] 95 | layout_mode = 2 96 | size_flags_horizontal = 3 97 | 98 | [node name="CRunCurrentInner" type="HBoxContainer" parent="Layout"] 99 | layout_mode = 2 100 | 101 | [node name="Label" type="Label" parent="Layout/CRunCurrentInner"] 102 | custom_minimum_size = Vector2(50, 0) 103 | layout_mode = 2 104 | size_flags_vertical = 7 105 | text = "Run Current Inner Class" 106 | 107 | [node name="ShortcutButton" parent="Layout/CRunCurrentInner" instance=ExtResource("1")] 108 | layout_mode = 2 109 | size_flags_horizontal = 3 110 | 111 | [node name="CRunCurrentTest" type="HBoxContainer" parent="Layout"] 112 | layout_mode = 2 113 | 114 | [node name="Label" type="Label" parent="Layout/CRunCurrentTest"] 115 | custom_minimum_size = Vector2(50, 0) 116 | layout_mode = 2 117 | size_flags_vertical = 7 118 | text = "Run Current Test" 119 | 120 | [node name="ShortcutButton" parent="Layout/CRunCurrentTest" instance=ExtResource("1")] 121 | layout_mode = 2 122 | size_flags_horizontal = 3 123 | 124 | [node name="CenterContainer2" type="CenterContainer" parent="Layout"] 125 | custom_minimum_size = Vector2(0, 5) 126 | layout_mode = 2 127 | size_flags_horizontal = 3 128 | size_flags_vertical = 3 129 | 130 | [node name="ShiftDisclaimer" type="Label" parent="Layout"] 131 | layout_mode = 2 132 | text = "\"Shift\" cannot be the only modifier for a shortcut." 133 | 134 | [node name="HBoxContainer" type="HBoxContainer" parent="Layout"] 135 | layout_mode = 2 136 | 137 | [node name="CenterContainer" type="CenterContainer" parent="Layout/HBoxContainer"] 138 | layout_mode = 2 139 | size_flags_horizontal = 3 140 | size_flags_vertical = 3 141 | 142 | [node name="Hide" type="Button" parent="Layout/HBoxContainer"] 143 | custom_minimum_size = Vector2(60, 30) 144 | layout_mode = 2 145 | text = "Close" 146 | 147 | [node name="BottomPad" type="CenterContainer" parent="Layout"] 148 | custom_minimum_size = Vector2(0, 10) 149 | layout_mode = 2 150 | size_flags_horizontal = 3 151 | 152 | [connection signal="popup_hide" from="." to="." method="_on_popup_hide"] 153 | [connection signal="pressed" from="Layout/HBoxContainer/Hide" to="." method="_on_Hide_pressed"] 154 | -------------------------------------------------------------------------------- /addons/gut/gui/GutControl.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=2 format=3 uid="uid://4jb53yqktyfg"] 2 | 3 | [ext_resource type="Script" path="res://addons/gut/gui/GutControl.gd" id="1_eprql"] 4 | 5 | [node name="GutControl" type="Control"] 6 | layout_mode = 3 7 | anchors_preset = 0 8 | offset_right = 295.0 9 | offset_bottom = 419.0 10 | script = ExtResource("1_eprql") 11 | 12 | [node name="Bg" type="ColorRect" parent="."] 13 | layout_mode = 1 14 | anchors_preset = 15 15 | anchor_right = 1.0 16 | anchor_bottom = 1.0 17 | grow_horizontal = 2 18 | grow_vertical = 2 19 | color = Color(0.36, 0.36, 0.36, 1) 20 | 21 | [node name="VBox" type="VBoxContainer" parent="."] 22 | layout_mode = 1 23 | anchors_preset = 15 24 | anchor_right = 1.0 25 | anchor_bottom = 1.0 26 | grow_horizontal = 2 27 | grow_vertical = 2 28 | 29 | [node name="Tabs" type="TabContainer" parent="VBox"] 30 | layout_mode = 2 31 | size_flags_vertical = 3 32 | 33 | [node name="Tests" type="Tree" parent="VBox/Tabs"] 34 | layout_mode = 2 35 | size_flags_vertical = 3 36 | hide_root = true 37 | 38 | [node name="SettingsScroll" type="ScrollContainer" parent="VBox/Tabs"] 39 | visible = false 40 | layout_mode = 2 41 | size_flags_vertical = 3 42 | 43 | [node name="Settings" type="VBoxContainer" parent="VBox/Tabs/SettingsScroll"] 44 | layout_mode = 2 45 | size_flags_horizontal = 3 46 | size_flags_vertical = 3 47 | 48 | [node name="Buttons" type="HBoxContainer" parent="VBox"] 49 | layout_mode = 2 50 | 51 | [node name="RunTests" type="Button" parent="VBox/Buttons"] 52 | layout_mode = 2 53 | size_flags_horizontal = 3 54 | text = "Run All" 55 | 56 | [node name="RunSelected" type="Button" parent="VBox/Buttons"] 57 | layout_mode = 2 58 | size_flags_horizontal = 3 59 | text = "Run Selected" 60 | 61 | [connection signal="item_activated" from="VBox/Tabs/Tests" to="." method="_on_tests_item_activated"] 62 | [connection signal="pressed" from="VBox/Buttons/RunTests" to="." method="_on_run_tests_pressed"] 63 | [connection signal="pressed" from="VBox/Buttons/RunSelected" to="." method="_on_run_selected_pressed"] 64 | -------------------------------------------------------------------------------- /addons/gut/gui/GutRunner.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=3 format=3 uid="uid://bqy3ikt6vu4b5"] 2 | 3 | [ext_resource type="Script" path="res://addons/gut/gui/GutRunner.gd" id="1"] 4 | [ext_resource type="PackedScene" uid="uid://m28heqtswbuq" path="res://addons/gut/GutScene.tscn" id="2_6ruxb"] 5 | 6 | [node name="GutRunner" type="Node2D"] 7 | script = ExtResource("1") 8 | 9 | [node name="GutLayer" type="CanvasLayer" parent="."] 10 | layer = 128 11 | 12 | [node name="GutScene" parent="GutLayer" instance=ExtResource("2_6ruxb")] 13 | -------------------------------------------------------------------------------- /addons/gut/gui/GutSceneTheme.tres: -------------------------------------------------------------------------------- 1 | [gd_resource type="Theme" load_steps=2 format=3 uid="uid://cstkhwkpajvqu"] 2 | 3 | [ext_resource type="FontFile" uid="uid://c6c7gnx36opr0" path="res://addons/gut/fonts/AnonymousPro-Regular.ttf" id="1_df57p"] 4 | 5 | [resource] 6 | default_font = ExtResource("1_df57p") 7 | Label/fonts/font = ExtResource("1_df57p") 8 | -------------------------------------------------------------------------------- /addons/gut/gui/MinGui.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=5 format=3 uid="uid://cnqqdfsn80ise"] 2 | 3 | [ext_resource type="Theme" uid="uid://cstkhwkpajvqu" path="res://addons/gut/gui/GutSceneTheme.tres" id="1_farmq"] 4 | [ext_resource type="FontFile" uid="uid://bnh0lslf4yh87" path="res://addons/gut/fonts/CourierPrime-Regular.ttf" id="2_a2e2l"] 5 | [ext_resource type="Script" path="res://addons/gut/gui/gut_gui.gd" id="2_eokrf"] 6 | [ext_resource type="PackedScene" uid="uid://bvrqqgjpyouse" path="res://addons/gut/gui/ResizeHandle.tscn" id="4_xrhva"] 7 | 8 | [node name="Min" type="Panel"] 9 | clip_contents = true 10 | custom_minimum_size = Vector2(280, 145) 11 | offset_right = 280.0 12 | offset_bottom = 145.0 13 | theme = ExtResource("1_farmq") 14 | script = ExtResource("2_eokrf") 15 | 16 | [node name="MainBox" type="VBoxContainer" parent="."] 17 | layout_mode = 1 18 | anchors_preset = 15 19 | anchor_right = 1.0 20 | anchor_bottom = 1.0 21 | grow_horizontal = 2 22 | grow_vertical = 2 23 | metadata/_edit_layout_mode = 1 24 | 25 | [node name="TitleBar" type="Panel" parent="MainBox"] 26 | custom_minimum_size = Vector2(0, 25) 27 | layout_mode = 2 28 | 29 | [node name="TitleBox" type="HBoxContainer" parent="MainBox/TitleBar"] 30 | layout_mode = 0 31 | anchor_right = 1.0 32 | anchor_bottom = 1.0 33 | offset_top = 2.0 34 | offset_bottom = 3.0 35 | grow_horizontal = 2 36 | grow_vertical = 2 37 | metadata/_edit_layout_mode = 1 38 | 39 | [node name="Spacer1" type="CenterContainer" parent="MainBox/TitleBar/TitleBox"] 40 | layout_mode = 2 41 | size_flags_horizontal = 3 42 | 43 | [node name="Title" type="Label" parent="MainBox/TitleBar/TitleBox"] 44 | layout_mode = 2 45 | text = "Title" 46 | 47 | [node name="Spacer2" type="CenterContainer" parent="MainBox/TitleBar/TitleBox"] 48 | layout_mode = 2 49 | size_flags_horizontal = 3 50 | 51 | [node name="TimeLabel" type="Label" parent="MainBox/TitleBar/TitleBox"] 52 | layout_mode = 2 53 | text = "0.000s" 54 | 55 | [node name="Body" type="HBoxContainer" parent="MainBox"] 56 | layout_mode = 2 57 | size_flags_vertical = 3 58 | 59 | [node name="LeftMargin" type="CenterContainer" parent="MainBox/Body"] 60 | custom_minimum_size = Vector2(5, 0) 61 | layout_mode = 2 62 | 63 | [node name="BodyRows" type="VBoxContainer" parent="MainBox/Body"] 64 | layout_mode = 2 65 | size_flags_horizontal = 3 66 | 67 | [node name="ProgressBars" type="HBoxContainer" parent="MainBox/Body/BodyRows"] 68 | layout_mode = 2 69 | size_flags_horizontal = 3 70 | 71 | [node name="HBoxContainer" type="HBoxContainer" parent="MainBox/Body/BodyRows/ProgressBars"] 72 | layout_mode = 2 73 | size_flags_horizontal = 3 74 | 75 | [node name="Label" type="Label" parent="MainBox/Body/BodyRows/ProgressBars/HBoxContainer"] 76 | layout_mode = 2 77 | text = "T:" 78 | 79 | [node name="ProgressTest" type="ProgressBar" parent="MainBox/Body/BodyRows/ProgressBars/HBoxContainer"] 80 | custom_minimum_size = Vector2(100, 0) 81 | layout_mode = 2 82 | size_flags_horizontal = 3 83 | value = 25.0 84 | 85 | [node name="HBoxContainer2" type="HBoxContainer" parent="MainBox/Body/BodyRows/ProgressBars"] 86 | layout_mode = 2 87 | size_flags_horizontal = 3 88 | 89 | [node name="Label" type="Label" parent="MainBox/Body/BodyRows/ProgressBars/HBoxContainer2"] 90 | layout_mode = 2 91 | text = "S:" 92 | 93 | [node name="ProgressScript" type="ProgressBar" parent="MainBox/Body/BodyRows/ProgressBars/HBoxContainer2"] 94 | custom_minimum_size = Vector2(100, 0) 95 | layout_mode = 2 96 | size_flags_horizontal = 3 97 | value = 75.0 98 | 99 | [node name="PathDisplay" type="VBoxContainer" parent="MainBox/Body/BodyRows"] 100 | clip_contents = true 101 | layout_mode = 2 102 | size_flags_vertical = 3 103 | 104 | [node name="Path" type="Label" parent="MainBox/Body/BodyRows/PathDisplay"] 105 | layout_mode = 2 106 | theme_override_fonts/font = ExtResource("2_a2e2l") 107 | theme_override_font_sizes/font_size = 14 108 | text = "res://test/integration/whatever" 109 | clip_text = true 110 | text_overrun_behavior = 3 111 | 112 | [node name="HBoxContainer" type="HBoxContainer" parent="MainBox/Body/BodyRows/PathDisplay"] 113 | clip_contents = true 114 | layout_mode = 2 115 | 116 | [node name="S3" type="CenterContainer" parent="MainBox/Body/BodyRows/PathDisplay/HBoxContainer"] 117 | custom_minimum_size = Vector2(5, 0) 118 | layout_mode = 2 119 | 120 | [node name="File" type="Label" parent="MainBox/Body/BodyRows/PathDisplay/HBoxContainer"] 121 | layout_mode = 2 122 | size_flags_horizontal = 3 123 | theme_override_fonts/font = ExtResource("2_a2e2l") 124 | theme_override_font_sizes/font_size = 14 125 | text = "test_this_thing.gd" 126 | text_overrun_behavior = 3 127 | 128 | [node name="Footer" type="HBoxContainer" parent="MainBox/Body/BodyRows"] 129 | layout_mode = 2 130 | 131 | [node name="HandleLeft" parent="MainBox/Body/BodyRows/Footer" node_paths=PackedStringArray("resize_control") instance=ExtResource("4_xrhva")] 132 | layout_mode = 2 133 | orientation = 0 134 | resize_control = NodePath("../../../../..") 135 | vertical_resize = false 136 | 137 | [node name="SwitchModes" type="Button" parent="MainBox/Body/BodyRows/Footer"] 138 | layout_mode = 2 139 | text = "Expand" 140 | 141 | [node name="CenterContainer" type="CenterContainer" parent="MainBox/Body/BodyRows/Footer"] 142 | layout_mode = 2 143 | size_flags_horizontal = 3 144 | 145 | [node name="Continue" type="Button" parent="MainBox/Body/BodyRows/Footer"] 146 | layout_mode = 2 147 | text = "Continue 148 | " 149 | 150 | [node name="HandleRight" parent="MainBox/Body/BodyRows/Footer" node_paths=PackedStringArray("resize_control") instance=ExtResource("4_xrhva")] 151 | layout_mode = 2 152 | resize_control = NodePath("../../../../..") 153 | vertical_resize = false 154 | 155 | [node name="RightMargin" type="CenterContainer" parent="MainBox/Body"] 156 | custom_minimum_size = Vector2(5, 0) 157 | layout_mode = 2 158 | 159 | [node name="CenterContainer" type="CenterContainer" parent="MainBox"] 160 | custom_minimum_size = Vector2(2.08165e-12, 2) 161 | layout_mode = 2 162 | -------------------------------------------------------------------------------- /addons/gut/gui/ResizeHandle.gd: -------------------------------------------------------------------------------- 1 | @tool 2 | extends ColorRect 3 | # ############################################################################# 4 | # Resize Handle control. Place onto a control. Set the orientation, then 5 | # set the control that this should resize. Then you can resize the control 6 | # by dragging this thing around. It's pretty neat. 7 | # ############################################################################# 8 | enum ORIENTATION { 9 | LEFT, 10 | RIGHT 11 | } 12 | 13 | @export var orientation := ORIENTATION.RIGHT : 14 | get: return orientation 15 | set(val): 16 | orientation = val 17 | queue_redraw() 18 | @export var resize_control : Control = null 19 | @export var vertical_resize := true 20 | 21 | var _line_width = .5 22 | var _line_color = Color(.4, .4, .4) 23 | var _active_line_color = Color(.3, .3, .3) 24 | var _invalid_line_color = Color(1, 0, 0) 25 | 26 | var _line_space = 3 27 | var _num_lines = 8 28 | 29 | var _mouse_down = false 30 | # Called when the node enters the scene tree for the first time. 31 | 32 | 33 | func _draw(): 34 | var c = _line_color 35 | if(resize_control == null): 36 | c = _invalid_line_color 37 | elif(_mouse_down): 38 | c = _active_line_color 39 | 40 | if(orientation == ORIENTATION.LEFT): 41 | _draw_resize_handle_left(c) 42 | else: 43 | _draw_resize_handle_right(c) 44 | 45 | 46 | func _gui_input(event): 47 | if(resize_control == null): 48 | return 49 | 50 | if(orientation == ORIENTATION.LEFT): 51 | _handle_left_input(event) 52 | else: 53 | _handle_right_input(event) 54 | 55 | 56 | # Draw the lines in the corner to show where you can 57 | # drag to resize the dialog 58 | func _draw_resize_handle_right(draw_color): 59 | var br = size 60 | 61 | for i in range(_num_lines): 62 | var start = br - Vector2(i * _line_space, 0) 63 | var end = br - Vector2(0, i * _line_space) 64 | draw_line(start, end, draw_color, _line_width, true) 65 | 66 | 67 | func _draw_resize_handle_left(draw_color): 68 | var bl = Vector2(0, size.y) 69 | 70 | for i in range(_num_lines): 71 | var start = bl + Vector2(i * _line_space, 0) 72 | var end = bl - Vector2(0, i * _line_space) 73 | draw_line(start, end, draw_color, _line_width, true) 74 | 75 | 76 | func _handle_right_input(event : InputEvent): 77 | if(event is InputEventMouseMotion): 78 | if(_mouse_down and 79 | event.global_position.x > 0 and 80 | event.global_position.y < DisplayServer.window_get_size().y): 81 | 82 | if(vertical_resize): 83 | resize_control.size.y += event.relative.y 84 | resize_control.size.x += event.relative.x 85 | elif(event is InputEventMouseButton): 86 | if(event.button_index == MOUSE_BUTTON_LEFT): 87 | _mouse_down = event.pressed 88 | queue_redraw() 89 | 90 | 91 | func _handle_left_input(event : InputEvent): 92 | if(event is InputEventMouseMotion): 93 | if(_mouse_down and 94 | event.global_position.x > 0 and 95 | event.global_position.y < DisplayServer.window_get_size().y): 96 | 97 | var start_size = resize_control.size 98 | resize_control.size.x -= event.relative.x 99 | if(resize_control.size.x != start_size.x): 100 | resize_control.global_position.x += event.relative.x 101 | 102 | if(vertical_resize): 103 | resize_control.size.y += event.relative.y 104 | elif(event is InputEventMouseButton): 105 | if(event.button_index == MOUSE_BUTTON_LEFT): 106 | _mouse_down = event.pressed 107 | queue_redraw() 108 | -------------------------------------------------------------------------------- /addons/gut/gui/ResizeHandle.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=2 format=3 uid="uid://bvrqqgjpyouse"] 2 | 3 | [ext_resource type="Script" path="res://addons/gut/gui/ResizeHandle.gd" id="1_oi5ed"] 4 | 5 | [node name="ResizeHandle" type="ColorRect"] 6 | custom_minimum_size = Vector2(20, 20) 7 | color = Color(1, 1, 1, 0) 8 | script = ExtResource("1_oi5ed") 9 | -------------------------------------------------------------------------------- /addons/gut/gui/ResultsTree.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=2 format=3 uid="uid://dls5r5f6157nq"] 2 | 3 | [ext_resource type="Script" path="res://addons/gut/gui/ResultsTree.gd" id="1_b4uub"] 4 | 5 | [node name="ResultsTree" type="VBoxContainer"] 6 | custom_minimum_size = Vector2(10, 10) 7 | anchors_preset = 15 8 | anchor_right = 1.0 9 | anchor_bottom = 1.0 10 | offset_right = -70.0 11 | offset_bottom = -104.0 12 | grow_horizontal = 2 13 | grow_vertical = 2 14 | size_flags_horizontal = 3 15 | size_flags_vertical = 3 16 | script = ExtResource("1_b4uub") 17 | 18 | [node name="Tree" type="Tree" parent="."] 19 | layout_mode = 2 20 | size_flags_horizontal = 3 21 | size_flags_vertical = 3 22 | columns = 2 23 | hide_root = true 24 | 25 | [node name="TextOverlay" type="Label" parent="Tree"] 26 | visible = false 27 | layout_mode = 1 28 | anchors_preset = 15 29 | anchor_right = 1.0 30 | anchor_bottom = 1.0 31 | grow_horizontal = 2 32 | grow_vertical = 2 33 | -------------------------------------------------------------------------------- /addons/gut/gui/RunAtCursor.gd: -------------------------------------------------------------------------------- 1 | @tool 2 | extends Control 3 | 4 | 5 | var ScriptTextEditors = load('res://addons/gut/gui/script_text_editor_controls.gd') 6 | 7 | @onready var _ctrls = { 8 | btn_script = $HBox/BtnRunScript, 9 | btn_inner = $HBox/BtnRunInnerClass, 10 | btn_method = $HBox/BtnRunMethod, 11 | lbl_none = $HBox/LblNoneSelected, 12 | arrow_1 = $HBox/Arrow1, 13 | arrow_2 = $HBox/Arrow2 14 | } 15 | 16 | var _editors = null 17 | var _cur_editor = null 18 | var _last_line = -1 19 | var _cur_script_path = null 20 | var _last_info = { 21 | script = null, 22 | inner_class = null, 23 | test_method = null 24 | } 25 | 26 | 27 | signal run_tests(what) 28 | 29 | 30 | func _ready(): 31 | _ctrls.lbl_none.visible = true 32 | _ctrls.btn_script.visible = false 33 | _ctrls.btn_inner.visible = false 34 | _ctrls.btn_method.visible = false 35 | _ctrls.arrow_1.visible = false 36 | _ctrls.arrow_2.visible = false 37 | 38 | # ---------------- 39 | # Private 40 | # ---------------- 41 | func _set_editor(which): 42 | _last_line = -1 43 | if(_cur_editor != null and _cur_editor.get_ref()): 44 | # _cur_editor.get_ref().disconnect('cursor_changed',Callable(self,'_on_cursor_changed')) 45 | _cur_editor.get_ref().caret_changed.disconnect(_on_cursor_changed) 46 | 47 | if(which != null): 48 | _cur_editor = weakref(which) 49 | which.caret_changed.connect(_on_cursor_changed.bind(which)) 50 | # which.connect('cursor_changed',Callable(self,'_on_cursor_changed'),[which]) 51 | 52 | _last_line = which.get_caret_line() 53 | _last_info = _editors.get_line_info() 54 | _update_buttons(_last_info) 55 | 56 | 57 | func _update_buttons(info): 58 | _ctrls.lbl_none.visible = _cur_script_path == null 59 | _ctrls.btn_script.visible = _cur_script_path != null 60 | 61 | _ctrls.btn_inner.visible = info.inner_class != null 62 | _ctrls.arrow_1.visible = info.inner_class != null 63 | _ctrls.btn_inner.text = str(info.inner_class) 64 | _ctrls.btn_inner.tooltip_text = str("Run all tests in Inner-Test-Class ", info.inner_class) 65 | 66 | _ctrls.btn_method.visible = info.test_method != null 67 | _ctrls.arrow_2.visible = info.test_method != null 68 | _ctrls.btn_method.text = str(info.test_method) 69 | _ctrls.btn_method.tooltip_text = str("Run test ", info.test_method) 70 | 71 | # The button's new size won't take effect until the next frame. 72 | # This appears to be what was causing the button to not be clickable the 73 | # first time. 74 | call_deferred("_update_size") 75 | 76 | func _update_size(): 77 | custom_minimum_size.x = _ctrls.btn_method.size.x + _ctrls.btn_method.position.x 78 | 79 | # ---------------- 80 | # Events 81 | # ---------------- 82 | func _on_cursor_changed(which): 83 | if(which.get_caret_line() != _last_line): 84 | _last_line = which.get_caret_line() 85 | _last_info = _editors.get_line_info() 86 | _update_buttons(_last_info) 87 | 88 | 89 | func _on_BtnRunScript_pressed(): 90 | var info = _last_info.duplicate() 91 | info.script = _cur_script_path.get_file() 92 | info.inner_class = null 93 | info.test_method = null 94 | emit_signal("run_tests", info) 95 | 96 | 97 | func _on_BtnRunInnerClass_pressed(): 98 | var info = _last_info.duplicate() 99 | info.script = _cur_script_path.get_file() 100 | info.test_method = null 101 | emit_signal("run_tests", info) 102 | 103 | 104 | func _on_BtnRunMethod_pressed(): 105 | var info = _last_info.duplicate() 106 | info.script = _cur_script_path.get_file() 107 | emit_signal("run_tests", info) 108 | 109 | 110 | # ---------------- 111 | # Public 112 | # ---------------- 113 | func set_script_text_editors(value): 114 | _editors = value 115 | 116 | 117 | func activate_for_script(path): 118 | _ctrls.btn_script.visible = true 119 | _ctrls.btn_script.text = path.get_file() 120 | _ctrls.btn_script.tooltip_text = str("Run all tests in script ", path) 121 | _cur_script_path = path 122 | _editors.refresh() 123 | # We have to wait a beat for the visibility to change on 124 | # the editors, otherwise we always get the first one. 125 | await get_tree().process_frame 126 | _set_editor(_editors.get_current_text_edit()) 127 | 128 | 129 | func get_script_button(): 130 | return _ctrls.btn_script 131 | 132 | 133 | func get_inner_button(): 134 | return _ctrls.btn_inner 135 | 136 | 137 | func get_test_button(): 138 | return _ctrls.btn_method 139 | 140 | 141 | # not used, thought was configurable but it's just the script prefix 142 | func set_method_prefix(value): 143 | _editors.set_method_prefix(value) 144 | 145 | 146 | # not used, thought was configurable but it's just the script prefix 147 | func set_inner_class_prefix(value): 148 | _editors.set_inner_class_prefix(value) 149 | 150 | 151 | # Mashed this function in here b/c it has _editors. Probably should be 152 | # somewhere else (possibly in script_text_editor_controls). 153 | func search_current_editor_for_text(txt): 154 | var te = _editors.get_current_text_edit() 155 | var result = te.search(txt, 0, 0, 0) 156 | var to_return = -1 157 | 158 | return to_return 159 | -------------------------------------------------------------------------------- /addons/gut/gui/RunAtCursor.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=4 format=3 uid="uid://0yunjxtaa8iw"] 2 | 3 | [ext_resource type="Script" path="res://addons/gut/gui/RunAtCursor.gd" id="1"] 4 | [ext_resource type="Texture2D" uid="uid://cr6tvdv0ve6cv" path="res://addons/gut/gui/play.png" id="2"] 5 | [ext_resource type="Texture2D" uid="uid://6wra5rxmfsrl" path="res://addons/gut/gui/arrow.png" id="3"] 6 | 7 | [node name="RunAtCursor" type="Control"] 8 | layout_mode = 3 9 | anchors_preset = 15 10 | anchor_right = 1.0 11 | anchor_bottom = 1.0 12 | offset_right = 1.0 13 | offset_bottom = -527.0 14 | grow_horizontal = 2 15 | grow_vertical = 2 16 | size_flags_horizontal = 3 17 | size_flags_vertical = 3 18 | script = ExtResource("1") 19 | 20 | [node name="HBox" type="HBoxContainer" parent="."] 21 | layout_mode = 0 22 | anchor_right = 1.0 23 | anchor_bottom = 1.0 24 | size_flags_horizontal = 3 25 | size_flags_vertical = 3 26 | 27 | [node name="LblNoneSelected" type="Label" parent="HBox"] 28 | layout_mode = 2 29 | text = "" 30 | 31 | [node name="BtnRunScript" type="Button" parent="HBox"] 32 | visible = false 33 | layout_mode = 2 34 | text = "