diff --git a/.github/workflows/gut_unit_tests.yml b/.github/workflows/gut_unit_tests.yml new file mode 100644 index 0000000..3582667 --- /dev/null +++ b/.github/workflows/gut_unit_tests.yml @@ -0,0 +1,21 @@ +name: Gut + +on: + pull_request: + push: + branches: [ master, main ] + +jobs: + gut: + runs-on: ubuntu-latest + container: + image: + barichello/godot-ci:3.5.1 + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Gut (gut.sh) + run: | + cd demo/ + godot -s --path $PWD addons/gut/gut_cmdln.gd -gdir=res://tests -glog=1 -ginclude_subdirs -gexit diff --git a/demo/.gut_editor_shortcuts.cfg b/demo/.gut_editor_shortcuts.cfg new file mode 100644 index 0000000..7e6e72e --- /dev/null +++ b/demo/.gut_editor_shortcuts.cfg @@ -0,0 +1,17 @@ +[main] + +run_all=Object(ShortCut,"resource_local_to_scene":false,"resource_name":"","shortcut":Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":true,"meta":false,"command":false,"pressed":false,"scancode":49,"physical_scancode":0,"unicode":0,"echo":false,"script":null) +,"script":null) + +run_current_script=Object(ShortCut,"resource_local_to_scene":false,"resource_name":"","shortcut":Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":true,"meta":false,"command":false,"pressed":false,"scancode":50,"physical_scancode":0,"unicode":0,"echo":false,"script":null) +,"script":null) + +run_current_inner=Object(ShortCut,"resource_local_to_scene":false,"resource_name":"","shortcut":Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":true,"meta":false,"command":false,"pressed":false,"scancode":51,"physical_scancode":0,"unicode":0,"echo":false,"script":null) +,"script":null) + +run_current_test=Object(ShortCut,"resource_local_to_scene":false,"resource_name":"","shortcut":Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":true,"meta":false,"command":false,"pressed":false,"scancode":52,"physical_scancode":0,"unicode":0,"echo":false,"script":null) +,"script":null) + +panel_button=Object(ShortCut,"resource_local_to_scene":false,"resource_name":"","shortcut":Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":true,"meta":false,"command":false,"pressed":false,"scancode":48,"physical_scancode":0,"unicode":0,"echo":false,"script":null) +,"script":null) + diff --git a/demo/BigFont.tres b/demo/BigFont.tres new file mode 100644 index 0000000..2360313 --- /dev/null +++ b/demo/BigFont.tres @@ -0,0 +1,8 @@ +[gd_resource type="DynamicFont" load_steps=2 format=2] + +[sub_resource type="DynamicFontData" id=9] +font_path = "res://addons/gut/fonts/LobsterTwo-BoldItalic.ttf" + +[resource] +size = 40 +font_data = SubResource( 9 ) diff --git a/demo/BigFontTheme.tres b/demo/BigFontTheme.tres new file mode 100644 index 0000000..8de6260 --- /dev/null +++ b/demo/BigFontTheme.tres @@ -0,0 +1,6 @@ +[gd_resource type="Theme" load_steps=2 format=2] + +[ext_resource path="res://BigFont.tres" type="DynamicFont" id=1] + +[resource] +default_font = ExtResource( 1 ) diff --git a/demo/Main.gd b/demo/Main.gd index 958900f..7964767 100644 --- a/demo/Main.gd +++ b/demo/Main.gd @@ -1,19 +1,19 @@ extends Control func _enter_tree(): - var _error : int = $Database.connect("output_received", self, "_on_output_received") - _error = $Database.connect("texture_received", self, "_on_texture_received") + var _error : int = $Database.connect("output_received", self, "_on_output_received") + _error = $Database.connect("texture_received", self, "_on_texture_received") func _on_output_received(text : String) -> void: - var label := Label.new() - $MarginContainer/VBoxContainer/ScrollContainer/VBoxContainer.add_child(label) + var label := Label.new() + $MarginContainer/VBoxContainer/ScrollContainer/VBoxContainer.add_child(label) - label.text = text - label.set("custom_colors/font_color", Color.limegreen) - label.autowrap = true + label.text = text + label.set("custom_colors/font_color", Color.limegreen) + label.autowrap = true func _on_texture_received(texture : Texture) -> void: - var texture_rect := TextureRect.new() - $MarginContainer/VBoxContainer/ScrollContainer/VBoxContainer.add_child(texture_rect) + var texture_rect := TextureRect.new() + $MarginContainer/VBoxContainer/ScrollContainer/VBoxContainer.add_child(texture_rect) - texture_rect.texture = texture + texture_rect.texture = texture diff --git a/demo/Main.tscn b/demo/Main.tscn index 35bf434..d9f4101 100644 --- a/demo/Main.tscn +++ b/demo/Main.tscn @@ -1,64 +1,12 @@ -[gd_scene load_steps=4 format=2] +[gd_scene load_steps=2 format=2] -[ext_resource path="res://database.gd" type="Script" id=1] -[ext_resource path="res://Main.gd" type="Script" id=2] +[ext_resource path="res://addons/gut/plugin_control.gd" type="Script" id=3] -[sub_resource type="StyleBoxFlat" id=1] -content_margin_left = 24.0 -content_margin_right = 24.0 -content_margin_top = 12.0 -content_margin_bottom = 12.0 -bg_color = Color( 0, 0, 0, 1 ) - -[node name="Main" type="Control"] -anchor_right = 1.0 -anchor_bottom = 1.0 -script = ExtResource( 2 ) -__meta__ = { -"_edit_use_anchors_": false -} - -[node name="Database" type="Node" parent="."] -script = ExtResource( 1 ) - -[node name="MarginContainer" type="MarginContainer" parent="."] +[node name="Gut" type="Control"] anchor_right = 1.0 anchor_bottom = 1.0 -custom_constants/margin_right = 24 -custom_constants/margin_top = 24 -custom_constants/margin_left = 24 -custom_constants/margin_bottom = 24 -__meta__ = { -"_edit_use_anchors_": false -} - -[node name="VBoxContainer" type="VBoxContainer" parent="MarginContainer"] -margin_left = 24.0 -margin_top = 24.0 -margin_right = 1000.0 -margin_bottom = 576.0 -custom_constants/separation = 24 - -[node name="Label" type="Label" parent="MarginContainer/VBoxContainer"] -margin_right = 976.0 -margin_bottom = 14.0 -text = "Godot SQLite Demo" -align = 1 - -[node name="ScrollContainer" type="ScrollContainer" parent="MarginContainer/VBoxContainer"] -margin_top = 38.0 -margin_right = 976.0 -margin_bottom = 552.0 -size_flags_horizontal = 3 -size_flags_vertical = 3 -custom_styles/bg = SubResource( 1 ) -__meta__ = { -"_edit_use_anchors_": false -} - -[node name="VBoxContainer" type="VBoxContainer" parent="MarginContainer/VBoxContainer/ScrollContainer"] -margin_left = 24.0 -margin_top = 12.0 -margin_right = 952.0 -margin_bottom = 12.0 -size_flags_horizontal = 3 +rect_min_size = Vector2( 740, 250 ) +script = ExtResource( 3 ) +_run_on_load = true +_include_subdirectories = true +_directory1 = "res://tests/unit" diff --git a/demo/addons/gut/GutScene.gd b/demo/addons/gut/GutScene.gd new file mode 100644 index 0000000..2183a81 --- /dev/null +++ b/demo/addons/gut/GutScene.gd @@ -0,0 +1,513 @@ +extends Panel + +onready var _script_list = $ScriptsList +onready var _nav_container = $VBox/BottomPanel/VBox/HBox/Navigation +onready var _nav = { + container = _nav_container, + prev = _nav_container.get_node('VBox/HBox/Previous'), + next = _nav_container.get_node('VBox/HBox/Next'), + run = _nav_container.get_node('VBox/HBox/Run'), + current_script = _nav_container.get_node('VBox/CurrentScript'), + run_single = _nav_container.get_node('VBox/HBox/RunSingleScript') +} + +onready var _progress_container = $VBox/BottomPanel/VBox/HBox/Progress +onready var _progress = { + script = _progress_container.get_node("ScriptProgress"), + script_xy = _progress_container.get_node("ScriptProgress/xy"), + test = _progress_container.get_node("TestProgress"), + test_xy = _progress_container.get_node("TestProgress/xy") +} +onready var _summary = { + control = $VBox/TitleBar/HBox/Summary, + failing = $VBox/TitleBar/HBox/Summary/Failing, # defunct? + passing = $VBox/TitleBar/HBox/Summary/Passing, # defunct? + asserts = $VBox/TitleBar/HBox/Summary/AssertCount, + fail_count = 0, # defunct? + pass_count = 0, # defunct? + test_count = 0, + passing_test_count = 0 +} + +onready var _extras = $ExtraOptions +onready var _ignore_pauses = $ExtraOptions/IgnorePause +onready var _continue_button = $VBox/BottomPanel/VBox/HBox/Continue/Continue +onready var _text_box = $VBox/TextDisplay/RichTextLabel +onready var _text_box_container = $VBox/TextDisplay +onready var _log_level_slider = $VBox/BottomPanel/VBox/HBox2/LogLevelSlider +onready var _resize_handle = $ResizeHandle +onready var _current_script = $VBox/BottomPanel/VBox/HBox2/CurrentScriptLabel +onready var _title_replacement = $VBox/TitleBar/HBox/TitleReplacement + +onready var _titlebar = { + bar = $VBox/TitleBar, + time = $VBox/TitleBar/HBox/Time, + label = $VBox/TitleBar/HBox/Title +} + +onready var _user_files = $UserFileViewer + +var _mouse = { + down = false, + in_title = false, + down_pos = null, + in_handle = false +} + +var _is_running = false +var _start_time = 0.0 +var _time = 0.0 + +const DEFAULT_TITLE = 'GUT' +var _pre_maximize_rect = null +var _font_size = 20 +var _compact_mode = false + +var min_sizes = { + compact = Vector2(330, 100), + full = Vector2(740, 300), +} + +signal end_pause +signal ignore_pause +signal log_level_changed +signal run_script +signal run_single_script + +func _ready(): + if(Engine.editor_hint): + return + + _current_script.text = '' + _pre_maximize_rect = get_rect() + _hide_scripts() + _update_controls() + _nav.current_script.set_text("No scripts available") + set_title() + clear_summary() + _titlebar.time.set_text("t: 0.0") + + _extras.visible = false + update() + + set_font_size(_font_size) + set_font('CourierPrime') + + _user_files.set_position(Vector2(10, 30)) + +func elapsed_time_as_str(): + return str("%.1f" % (_time / 1000.0), 's') + +func _process(_delta): + if(_is_running): + _time = OS.get_ticks_msec() - _start_time + _titlebar.time.set_text(str('t: ', elapsed_time_as_str())) + +func _draw(): # needs get_size() + # Draw the lines in the corner to show where you can + # drag to resize the dialog + var grab_margin = 3 + var line_space = 3 + var grab_line_color = Color(.4, .4, .4) + if(_resize_handle.visible): + for i in range(1, 10): + var x = rect_size - Vector2(i * line_space, grab_margin) + var y = rect_size - Vector2(grab_margin, i * line_space) + draw_line(x, y, grab_line_color, 1, true) + +func _on_Maximize_draw(): + # draw the maximize square thing. + var btn = $VBox/TitleBar/HBox/Maximize + btn.set_text('') + var w = btn.get_size().x + var h = btn.get_size().y + btn.draw_rect(Rect2(0, 2, w, h -2), Color(0, 0, 0, 1)) + btn.draw_rect(Rect2(2, 6, w - 4, h - 8), Color(1,1,1,1)) + +func _on_ShowExtras_draw(): + var btn = $VBox/BottomPanel/VBox/HBox/Continue/ShowExtras + btn.set_text('') + var start_x = 20 + var start_y = 15 + var pad = 5 + var color = Color(.1, .1, .1, 1) + var width = 2 + for i in range(3): + var y = start_y + pad * i + btn.draw_line(Vector2(start_x, y), Vector2(btn.get_size().x - start_x, y), color, width, true) + +# #################### +# GUI Events +# #################### +func _on_Run_pressed(): + _run_mode() + emit_signal('run_script', get_selected_index()) + +func _on_CurrentScript_pressed(): + _toggle_scripts() + +func _on_Previous_pressed(): + _select_script(get_selected_index() - 1) + +func _on_Next_pressed(): + _select_script(get_selected_index() + 1) + +func _on_LogLevelSlider_value_changed(_value): + emit_signal('log_level_changed', _log_level_slider.value) + +func _on_Continue_pressed(): + _continue_button.disabled = true + emit_signal('end_pause') + +func _on_IgnorePause_pressed(): + var checked = _ignore_pauses.is_pressed() + emit_signal('ignore_pause', checked) + if(checked): + emit_signal('end_pause') + _continue_button.disabled = true + +func _on_RunSingleScript_pressed(): + _run_mode() + emit_signal('run_single_script', get_selected_index()) + +func _on_ScriptsList_item_selected(index): + var tmr = $ScriptsList/DoubleClickTimer + if(!tmr.is_stopped()): + _run_mode() + emit_signal('run_single_script', get_selected_index()) + tmr.stop() + else: + tmr.start() + + _select_script(index) + +func _on_TitleBar_mouse_entered(): + _mouse.in_title = true + +func _on_TitleBar_mouse_exited(): + _mouse.in_title = false + +func _input(event): + if(event is InputEventMouseButton): + if(event.button_index == 1): + _mouse.down = event.pressed + if(_mouse.down): + _mouse.down_pos = event.position + + if(_mouse.in_title): + if(event is InputEventMouseMotion and _mouse.down): + set_position(get_position() + (event.position - _mouse.down_pos)) + _mouse.down_pos = event.position + _pre_maximize_rect = get_rect() + + if(_mouse.in_handle): + if(event is InputEventMouseMotion and _mouse.down): + var new_size = rect_size + event.position - _mouse.down_pos + var new_mouse_down_pos = event.position + rect_size = new_size + _mouse.down_pos = new_mouse_down_pos + _pre_maximize_rect = get_rect() + +func _on_ResizeHandle_mouse_entered(): + _mouse.in_handle = true + +func _on_ResizeHandle_mouse_exited(): + _mouse.in_handle = false + +func _on_RichTextLabel_gui_input(ev): + pass + # leaving this b/c it is wired up and might have to send + # more signals through + +func _on_Copy_pressed(): + OS.clipboard = _text_box.text + +func _on_ShowExtras_toggled(button_pressed): + _extras.visible = button_pressed + +func _on_Maximize_pressed(): + if(get_rect() == _pre_maximize_rect): + compact_mode(false) + maximize() + else: + compact_mode(false) + rect_size = _pre_maximize_rect.size + rect_position = _pre_maximize_rect.position +func _on_Minimize_pressed(): + + compact_mode(!_compact_mode) + + +func _on_Minimize_draw(): + # draw the maximize square thing. + var btn = $VBox/TitleBar/HBox/Minimize + btn.set_text('') + var w = btn.get_size().x + var h = btn.get_size().y + btn.draw_rect(Rect2(0, h-3, w, 3), Color(0, 0, 0, 1)) + +func _on_UserFiles_pressed(): + _user_files.show_open() + + +# #################### +# Private +# #################### +func _run_mode(is_running=true): + if(is_running): + _start_time = OS.get_ticks_msec() + _time = 0.0 + clear_summary() + _is_running = is_running + + _hide_scripts() + _nav.prev.disabled = is_running + _nav.next.disabled = is_running + _nav.run.disabled = is_running + _nav.current_script.disabled = is_running + _nav.run_single.disabled = is_running + +func _select_script(index): + var text = _script_list.get_item_text(index) + var max_len = 50 + if(text.length() > max_len): + text = '...' + text.right(text.length() - (max_len - 5)) + _nav.current_script.set_text(text) + _script_list.select(index) + _update_controls() + +func _toggle_scripts(): + if(_script_list.visible): + _hide_scripts() + else: + _show_scripts() + +func _show_scripts(): + _script_list.show() + +func _hide_scripts(): + _script_list.hide() + +func _update_controls(): + var is_empty = _script_list.get_selected_items().size() == 0 + if(is_empty): + _nav.next.disabled = true + _nav.prev.disabled = true + else: + var index = get_selected_index() + _nav.prev.disabled = index <= 0 + _nav.next.disabled = index >= _script_list.get_item_count() - 1 + + _nav.run.disabled = is_empty + _nav.current_script.disabled = is_empty + _nav.run_single.disabled = is_empty + +func _update_summary(): + if(!_summary): + return + + var total = _summary.fail_count + _summary.pass_count + _summary.control.visible = !total == 0 + # this now shows tests but I didn't rename everything + _summary.asserts.text = str(_summary.passing_test_count, '/', _summary.test_count, ' tests passed') +# #################### +# Public +# #################### +func run_mode(is_running=true): + _run_mode(is_running) + +func set_scripts(scripts): + _script_list.clear() + for i in range(scripts.size()): + _script_list.add_item(scripts[i]) + _select_script(0) + _update_controls() + +func select_script(index): + _select_script(index) + +func get_selected_index(): + return _script_list.get_selected_items()[0] + +func get_log_level(): + return _log_level_slider.value + +func set_log_level(value): + var new_value = value + if(new_value == null): + new_value = 0 + # !! For some reason, _log_level_slider was null, but this wasn't, so + # here's another hardcoded node path. + $VBox/BottomPanel/VBox/HBox2/LogLevelSlider.value = new_value + +func set_ignore_pause(should): + _ignore_pauses.pressed = should + +func get_ignore_pause(): + return _ignore_pauses.pressed + +func get_text_box(): + # due to some timing issue, this cannot return _text_box but can return + # this. + return $VBox/TextDisplay/RichTextLabel + +func end_run(): + _run_mode(false) + _update_controls() + +func set_progress_script_max(value): + var max_val = max(value, 1) + _progress.script.set_max(max_val) + _progress.script_xy.set_text(str('0/', max_val)) + +func set_progress_script_value(value): + _progress.script.set_value(value) + var txt = str(value, '/', _progress.test.get_max()) + _progress.script_xy.set_text(txt) + +func set_progress_test_max(value): + var max_val = max(value, 1) + _progress.test.set_max(max_val) + _progress.test_xy.set_text(str('0/', max_val)) + +func set_progress_test_value(value): + _progress.test.set_value(value) + var txt = str(value, '/', _progress.test.get_max()) + _progress.test_xy.set_text(txt) + +func clear_progress(): + _progress.test.set_value(0) + _progress.script.set_value(0) + +func pause(): + _continue_button.disabled = false + +func set_title(title=null): + if(title == null): + _titlebar.label.set_text(DEFAULT_TITLE) + else: + _titlebar.label.set_text(title) + +func add_passing(amount=1): + if(!_summary): + return + _summary.pass_count += amount + _update_summary() + +func add_failing(amount=1): + if(!_summary): + return + _summary.fail_count += amount + _update_summary() + +func add_test(passing): + if(!_summary): + return + _summary.test_count += 1 + if(passing): + _summary.passing_test_count += 1 + _update_summary() + +func clear_summary(): + _summary.fail_count = 0 + _summary.pass_count = 0 + _update_summary() + +func maximize(): + if(is_inside_tree()): + var vp_size_offset = get_tree().root.get_viewport().get_visible_rect().size + rect_size = vp_size_offset / get_scale() + set_position(Vector2(0, 0)) + +func clear_text(): + _text_box.bbcode_text = '' + +func scroll_to_bottom(): + pass + #_text_box.cursor_set_line(_gui.get_text_box().get_line_count()) + +func _set_font_size_for_rtl(rtl, new_size): + if(rtl.get('custom_fonts/normal_font') != null): + rtl.get('custom_fonts/bold_italics_font').size = new_size + rtl.get('custom_fonts/bold_font').size = new_size + rtl.get('custom_fonts/italics_font').size = new_size + rtl.get('custom_fonts/normal_font').size = new_size + + +func _set_fonts_for_rtl(rtl, base_font_name): + pass + + +func set_font_size(new_size): + _font_size = new_size + _set_font_size_for_rtl(_text_box, new_size) + _set_font_size_for_rtl(_user_files.get_rich_text_label(), new_size) + + +func _set_font(rtl, font_name, custom_name): + if(font_name == null): + rtl.set('custom_fonts/' + custom_name, null) + else: + var dyn_font = DynamicFont.new() + var font_data = DynamicFontData.new() + font_data.font_path = 'res://addons/gut/fonts/' + font_name + '.ttf' + font_data.antialiased = true + dyn_font.font_data = font_data + rtl.set('custom_fonts/' + custom_name, dyn_font) + +func _set_all_fonts_in_ftl(ftl, base_name): + if(base_name == 'Default'): + _set_font(ftl, null, 'normal_font') + _set_font(ftl, null, 'bold_font') + _set_font(ftl, null, 'italics_font') + _set_font(ftl, null, 'bold_italics_font') + else: + _set_font(ftl, base_name + '-Regular', 'normal_font') + _set_font(ftl, base_name + '-Bold', 'bold_font') + _set_font(ftl, base_name + '-Italic', 'italics_font') + _set_font(ftl, base_name + '-BoldItalic', 'bold_italics_font') + set_font_size(_font_size) + +func set_font(base_name): + _set_all_fonts_in_ftl(_text_box, base_name) + _set_all_fonts_in_ftl(_user_files.get_rich_text_label(), base_name) + +func set_default_font_color(color): + _text_box.set('custom_colors/default_color', color) + +func set_background_color(color): + _text_box_container.color = color + +func get_waiting_label(): + return $VBox/TextDisplay/WaitingLabel + +func compact_mode(should): + if(_compact_mode == should): + return + + _compact_mode = should + _text_box_container.visible = !should + _nav.container.visible = !should + _log_level_slider.visible = !should + $VBox/BottomPanel/VBox/HBox/Continue/ShowExtras.visible = !should + _titlebar.label.visible = !should + _resize_handle.visible = !should + _current_script.visible = !should + _title_replacement.visible = should + + if(should): + rect_min_size = min_sizes.compact + rect_size = rect_min_size + else: + rect_min_size = min_sizes.full + rect_size = min_sizes.full + + goto_bottom_right_corner() + + +func set_script_path(text): + _current_script.text = text + + +func goto_bottom_right_corner(): + rect_position = get_tree().root.get_viewport().get_visible_rect().size - rect_size diff --git a/demo/addons/gut/GutScene.tscn b/demo/addons/gut/GutScene.tscn new file mode 100644 index 0000000..90ae24f --- /dev/null +++ b/demo/addons/gut/GutScene.tscn @@ -0,0 +1,636 @@ +[gd_scene load_steps=16 format=2] + +[ext_resource path="res://addons/gut/GutScene.gd" type="Script" id=1] +[ext_resource path="res://addons/gut/fonts/AnonymousPro-Italic.ttf" type="DynamicFontData" id=2] +[ext_resource path="res://addons/gut/fonts/AnonymousPro-Regular.ttf" type="DynamicFontData" id=3] +[ext_resource path="res://addons/gut/fonts/AnonymousPro-BoldItalic.ttf" type="DynamicFontData" id=4] +[ext_resource path="res://addons/gut/fonts/AnonymousPro-Bold.ttf" type="DynamicFontData" id=5] +[ext_resource path="res://addons/gut/UserFileViewer.tscn" type="PackedScene" id=6] +[ext_resource path="res://addons/gut/gui/GutSceneTheme.tres" type="Theme" id=7] + +[sub_resource type="StyleBoxFlat" id=1] +bg_color = Color( 0.192157, 0.192157, 0.227451, 1 ) +corner_radius_top_left = 10 +corner_radius_top_right = 10 + +[sub_resource type="StyleBoxFlat" id=2] +bg_color = Color( 1, 1, 1, 1 ) +border_color = Color( 0, 0, 0, 1 ) +corner_radius_top_left = 5 +corner_radius_top_right = 5 + +[sub_resource type="Theme" id=3] +resource_local_to_scene = true +Panel/styles/panel = SubResource( 2 ) +Panel/styles/panelf = null +Panel/styles/panelnc = null + +[sub_resource type="DynamicFont" id=4] +font_data = ExtResource( 4 ) + +[sub_resource type="DynamicFont" id=5] +font_data = ExtResource( 2 ) + +[sub_resource type="DynamicFont" id=6] +font_data = ExtResource( 5 ) + +[sub_resource type="DynamicFont" id=7] +font_data = ExtResource( 3 ) + +[sub_resource type="StyleBoxFlat" id=8] +bg_color = Color( 0.192157, 0.192157, 0.227451, 1 ) +corner_radius_top_left = 20 +corner_radius_top_right = 20 + +[node name="Gut" type="Panel"] +margin_right = 740.0 +margin_bottom = 300.0 +rect_min_size = Vector2( 740, 300 ) +theme = ExtResource( 7 ) +custom_styles/panel = SubResource( 1 ) +script = ExtResource( 1 ) + +[node name="UserFileViewer" parent="." instance=ExtResource( 6 )] +margin_top = 388.0 +margin_bottom = 818.0 + +[node name="VBox" type="VBoxContainer" parent="."] +anchor_right = 1.0 +anchor_bottom = 1.0 +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="TitleBar" type="Panel" parent="VBox"] +margin_right = 740.0 +margin_bottom = 30.0 +rect_min_size = Vector2( 0, 30 ) +theme = SubResource( 3 ) +__meta__ = { +"_edit_group_": true, +"_edit_use_anchors_": false +} + +[node name="HBox" type="HBoxContainer" parent="VBox/TitleBar"] +anchor_right = 1.0 +anchor_bottom = 1.0 +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="Summary" type="Control" parent="VBox/TitleBar/HBox"] +margin_right = 110.0 +margin_bottom = 30.0 +rect_min_size = Vector2( 110, 0 ) +mouse_filter = 2 +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="Passing" type="Label" parent="VBox/TitleBar/HBox/Summary"] +visible = false +margin_left = 5.0 +margin_top = 7.0 +margin_right = 45.0 +margin_bottom = 21.0 +custom_colors/font_color = Color( 0, 0, 0, 1 ) +text = "0" +align = 1 +valign = 1 +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="Failing" type="Label" parent="VBox/TitleBar/HBox/Summary"] +visible = false +margin_left = 100.0 +margin_top = 7.0 +margin_right = 140.0 +margin_bottom = 21.0 +custom_colors/font_color = Color( 0, 0, 0, 1 ) +text = "0" +align = 1 +valign = 1 + +[node name="AssertCount" type="Label" parent="VBox/TitleBar/HBox/Summary"] +margin_left = 5.0 +margin_top = 7.0 +margin_right = 165.0 +margin_bottom = 21.0 +custom_colors/font_color = Color( 0, 0, 0, 1 ) +text = "Assert count" +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="TitleReplacement" type="CenterContainer" parent="VBox/TitleBar/HBox"] +visible = false +margin_left = 114.0 +margin_right = 352.0 +margin_bottom = 30.0 +rect_min_size = Vector2( 5, 0 ) +mouse_filter = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 + +[node name="Title" type="Label" parent="VBox/TitleBar/HBox"] +margin_left = 114.0 +margin_right = 594.0 +margin_bottom = 30.0 +size_flags_horizontal = 3 +size_flags_vertical = 7 +custom_colors/font_color = Color( 0, 0, 0, 1 ) +text = "Gut" +align = 1 +valign = 1 +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="Time" type="Label" parent="VBox/TitleBar/HBox"] +margin_left = 598.0 +margin_top = 7.0 +margin_right = 654.0 +margin_bottom = 22.0 +custom_colors/font_color = Color( 0, 0, 0, 1 ) +text = "9999.99" +valign = 1 +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="CC" type="CenterContainer" parent="VBox/TitleBar/HBox"] +margin_left = 658.0 +margin_right = 663.0 +margin_bottom = 30.0 +rect_min_size = Vector2( 5, 0 ) +mouse_filter = 2 + +[node name="Minimize" type="Button" parent="VBox/TitleBar/HBox"] +margin_left = 667.0 +margin_right = 697.0 +margin_bottom = 30.0 +rect_min_size = Vector2( 30, 0 ) +custom_colors/font_color = Color( 0, 0, 0, 1 ) +text = "N" +flat = true +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="Maximize" type="Button" parent="VBox/TitleBar/HBox"] +margin_left = 701.0 +margin_right = 731.0 +margin_bottom = 30.0 +rect_min_size = Vector2( 30, 0 ) +custom_colors/font_color = Color( 0, 0, 0, 1 ) +text = "X" +flat = true +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="CC2" type="CenterContainer" parent="VBox/TitleBar/HBox"] +margin_left = 735.0 +margin_right = 740.0 +margin_bottom = 30.0 +rect_min_size = Vector2( 5, 0 ) +mouse_filter = 2 + +[node name="TextDisplay" type="ColorRect" parent="VBox"] +margin_top = 34.0 +margin_right = 740.0 +margin_bottom = 176.0 +size_flags_horizontal = 3 +size_flags_vertical = 3 +color = Color( 0, 0, 0, 1 ) +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="RichTextLabel" type="RichTextLabel" parent="VBox/TextDisplay"] +anchor_right = 1.0 +anchor_bottom = 1.0 +margin_left = 10.0 +rect_min_size = Vector2( 0, 116 ) +focus_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 +custom_fonts/bold_italics_font = SubResource( 4 ) +custom_fonts/italics_font = SubResource( 5 ) +custom_fonts/bold_font = SubResource( 6 ) +custom_fonts/normal_font = SubResource( 7 ) +bbcode_enabled = true +scroll_following = true +selection_enabled = true +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="WaitingLabel" type="RichTextLabel" parent="VBox/TextDisplay"] +anchor_top = 1.0 +anchor_right = 1.0 +anchor_bottom = 1.0 +margin_top = -25.0 +bbcode_enabled = true +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="BottomPanel" type="ColorRect" parent="VBox"] +margin_top = 180.0 +margin_right = 740.0 +margin_bottom = 300.0 +rect_min_size = Vector2( 0, 120 ) +size_flags_horizontal = 9 +size_flags_vertical = 9 +color = Color( 1, 1, 1, 0 ) + +[node name="VBox" type="VBoxContainer" parent="VBox/BottomPanel"] +anchor_right = 1.0 +anchor_bottom = 1.0 +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="HBox" type="HBoxContainer" parent="VBox/BottomPanel/VBox"] +margin_right = 740.0 +margin_bottom = 80.0 +size_flags_horizontal = 3 + +[node name="CC1" type="CenterContainer" parent="VBox/BottomPanel/VBox/HBox"] +margin_right = 5.0 +margin_bottom = 80.0 +rect_min_size = Vector2( 5, 0 ) + +[node name="Progress" type="VBoxContainer" parent="VBox/BottomPanel/VBox/HBox"] +margin_left = 9.0 +margin_right = 179.0 +margin_bottom = 80.0 +rect_min_size = Vector2( 170, 0 ) +alignment = 1 + +[node name="TestProgress" type="ProgressBar" parent="VBox/BottomPanel/VBox/HBox/Progress"] +margin_top = 11.0 +margin_right = 100.0 +margin_bottom = 36.0 +rect_min_size = Vector2( 100, 25 ) +hint_tooltip = "Test progress for the current script." +size_flags_horizontal = 0 +step = 1.0 +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="Label" type="Label" parent="VBox/BottomPanel/VBox/HBox/Progress/TestProgress"] +margin_left = 107.5 +margin_top = 3.0 +margin_right = 172.5 +margin_bottom = 18.0 +text = "Tests" +valign = 1 +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="xy" type="Label" parent="VBox/BottomPanel/VBox/HBox/Progress/TestProgress"] +visible = false +anchor_right = 1.0 +anchor_bottom = 1.0 +text = "0/0" +align = 1 +valign = 1 +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="ScriptProgress" type="ProgressBar" parent="VBox/BottomPanel/VBox/HBox/Progress"] +margin_top = 40.0 +margin_right = 100.0 +margin_bottom = 65.0 +rect_min_size = Vector2( 100, 25 ) +hint_tooltip = "Overall progress of executing tests." +size_flags_horizontal = 0 +step = 1.0 +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="Label" type="Label" parent="VBox/BottomPanel/VBox/HBox/Progress/ScriptProgress"] +margin_left = 107.0 +margin_top = 3.5 +margin_right = 172.0 +margin_bottom = 18.5 +text = "Scripts" +valign = 1 +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="xy" type="Label" parent="VBox/BottomPanel/VBox/HBox/Progress/ScriptProgress"] +visible = false +anchor_right = 1.0 +anchor_bottom = 1.0 +text = "0/0" +align = 1 +valign = 1 +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="CenterContainer" type="CenterContainer" parent="VBox/BottomPanel/VBox/HBox/Progress"] +margin_top = 69.0 +margin_right = 170.0 +margin_bottom = 69.0 + +[node name="CC2" type="CenterContainer" parent="VBox/BottomPanel/VBox/HBox"] +margin_left = 183.0 +margin_right = 226.0 +margin_bottom = 80.0 +rect_min_size = Vector2( 5, 0 ) +size_flags_horizontal = 3 + +[node name="Navigation" type="Panel" parent="VBox/BottomPanel/VBox/HBox"] +self_modulate = Color( 1, 1, 1, 0 ) +margin_left = 230.0 +margin_right = 580.0 +margin_bottom = 80.0 +rect_min_size = Vector2( 350, 80 ) +__meta__ = { +"_edit_group_": true, +"_edit_use_anchors_": false +} + +[node name="VBox" type="VBoxContainer" parent="VBox/BottomPanel/VBox/HBox/Navigation"] +anchor_right = 1.0 +anchor_bottom = 1.0 +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="CurrentScript" type="Button" parent="VBox/BottomPanel/VBox/HBox/Navigation/VBox"] +margin_right = 350.0 +margin_bottom = 38.0 +hint_tooltip = "Select a script to run. You can run just this script, or this script and all scripts after using the run buttons." +size_flags_horizontal = 3 +size_flags_vertical = 3 +text = "res://test/unit/test_gut.gd" +clip_text = true +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="HBox" type="HBoxContainer" parent="VBox/BottomPanel/VBox/HBox/Navigation/VBox"] +margin_top = 42.0 +margin_right = 350.0 +margin_bottom = 80.0 +size_flags_horizontal = 3 +size_flags_vertical = 3 + +[node name="Previous" type="Button" parent="VBox/BottomPanel/VBox/HBox/Navigation/VBox/HBox"] +margin_right = 84.0 +margin_bottom = 38.0 +hint_tooltip = "Previous script in the list." +size_flags_horizontal = 3 +size_flags_vertical = 3 +text = "|<" +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="Next" type="Button" parent="VBox/BottomPanel/VBox/HBox/Navigation/VBox/HBox"] +margin_left = 88.0 +margin_right = 173.0 +margin_bottom = 38.0 +hint_tooltip = "Next script in the list. +" +size_flags_horizontal = 3 +size_flags_vertical = 3 +text = ">|" +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="Run" type="Button" parent="VBox/BottomPanel/VBox/HBox/Navigation/VBox/HBox"] +margin_left = 177.0 +margin_right = 261.0 +margin_bottom = 38.0 +hint_tooltip = "Run the currently selected item and all after it." +size_flags_horizontal = 3 +size_flags_vertical = 3 +text = ">" +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="RunSingleScript" type="Button" parent="VBox/BottomPanel/VBox/HBox/Navigation/VBox/HBox"] +margin_left = 265.0 +margin_right = 350.0 +margin_bottom = 38.0 +hint_tooltip = "Run the currently selected item. + +If the selected item has Inner Test Classes +then they will all be run. If the selected item +is an Inner Test Class then only it will be run." +size_flags_horizontal = 3 +size_flags_vertical = 3 +text = "> (1)" +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="CC3" type="CenterContainer" parent="VBox/BottomPanel/VBox/HBox"] +margin_left = 584.0 +margin_right = 627.0 +margin_bottom = 80.0 +rect_min_size = Vector2( 5, 0 ) +size_flags_horizontal = 3 + +[node name="Continue" type="VBoxContainer" parent="VBox/BottomPanel/VBox/HBox"] +self_modulate = Color( 1, 1, 1, 0 ) +margin_left = 631.0 +margin_right = 731.0 +margin_bottom = 80.0 +alignment = 1 + +[node name="ShowExtras" type="Button" parent="VBox/BottomPanel/VBox/HBox/Continue"] +margin_right = 50.0 +margin_bottom = 35.0 +rect_min_size = Vector2( 50, 35 ) +rect_pivot_offset = Vector2( 35, 20 ) +hint_tooltip = "Show/hide additional options." +size_flags_horizontal = 0 +toggle_mode = true +text = "_" +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="Continue" type="Button" parent="VBox/BottomPanel/VBox/HBox/Continue"] +margin_top = 39.0 +margin_right = 100.0 +margin_bottom = 79.0 +rect_min_size = Vector2( 100, 40 ) +hint_tooltip = "When a pause_before_teardown is encountered this button will be enabled and must be pressed to continue running tests." +disabled = true +text = "Continue" +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="CC4" type="CenterContainer" parent="VBox/BottomPanel/VBox/HBox"] +margin_left = 735.0 +margin_right = 740.0 +margin_bottom = 80.0 +rect_min_size = Vector2( 5, 0 ) + +[node name="HBox2" type="HBoxContainer" parent="VBox/BottomPanel/VBox"] +margin_top = 84.0 +margin_right = 740.0 +margin_bottom = 114.0 + +[node name="CC" type="CenterContainer" parent="VBox/BottomPanel/VBox/HBox2"] +margin_right = 5.0 +margin_bottom = 30.0 +rect_min_size = Vector2( 5, 0 ) + +[node name="LogLevelSlider" type="HSlider" parent="VBox/BottomPanel/VBox/HBox2"] +margin_left = 9.0 +margin_right = 109.0 +margin_bottom = 30.0 +rect_min_size = Vector2( 100, 30 ) +size_flags_vertical = 3 +max_value = 2.0 +tick_count = 3 +ticks_on_borders = true +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="Label" type="Label" parent="VBox/BottomPanel/VBox/HBox2/LogLevelSlider"] +margin_left = 4.0 +margin_top = -17.0 +margin_right = 85.0 +margin_bottom = 7.0 +text = "Log Level" +valign = 1 +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="CenterContainer" type="CenterContainer" parent="VBox/BottomPanel/VBox/HBox2"] +margin_left = 113.0 +margin_right = 163.0 +margin_bottom = 30.0 +rect_min_size = Vector2( 50, 0 ) + +[node name="CurrentScriptLabel" type="Label" parent="VBox/BottomPanel/VBox/HBox2"] +margin_left = 167.0 +margin_top = 7.0 +margin_right = 740.0 +margin_bottom = 22.0 +size_flags_horizontal = 3 +size_flags_vertical = 6 +text = "res://test/unit/test_something.gd" +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="ScriptsList" type="ItemList" parent="."] +visible = false +anchor_bottom = 1.0 +margin_left = 179.0 +margin_top = 40.0 +margin_right = 619.0 +margin_bottom = -110.0 +allow_reselect = true +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="DoubleClickTimer" type="Timer" parent="ScriptsList"] +wait_time = 0.3 +one_shot = true + +[node name="ExtraOptions" type="Panel" parent="."] +visible = false +anchor_left = 1.0 +anchor_top = 1.0 +anchor_right = 1.0 +anchor_bottom = 1.0 +margin_left = -212.0 +margin_top = -260.0 +margin_right = -2.0 +margin_bottom = -106.0 +custom_styles/panel = SubResource( 8 ) +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="IgnorePause" type="CheckBox" parent="ExtraOptions"] +margin_left = 17.5 +margin_top = 4.5 +margin_right = 162.5 +margin_bottom = 29.5 +rect_scale = Vector2( 1.2, 1.2 ) +hint_tooltip = "Ignore all calls to pause_before_teardown." +text = "Ignore Pauses" +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="Copy" type="Button" parent="ExtraOptions"] +margin_left = 15.0 +margin_top = 40.0 +margin_right = 195.0 +margin_bottom = 80.0 +hint_tooltip = "Copy all output to the clipboard." +text = "Copy to Clipboard" +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="UserFiles" type="Button" parent="ExtraOptions"] +margin_left = 15.0 +margin_top = 90.0 +margin_right = 195.0 +margin_bottom = 130.0 +hint_tooltip = "Copy all output to the clipboard." +text = "View User Files" +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="ResizeHandle" type="Control" parent="."] +anchor_left = 1.0 +anchor_top = 1.0 +anchor_right = 1.0 +anchor_bottom = 1.0 +margin_left = -40.0 +margin_top = -40.0 +__meta__ = { +"_edit_use_anchors_": false +} + +[connection signal="mouse_entered" from="VBox/TitleBar" to="." method="_on_TitleBar_mouse_entered"] +[connection signal="mouse_exited" from="VBox/TitleBar" to="." method="_on_TitleBar_mouse_exited"] +[connection signal="draw" from="VBox/TitleBar/HBox/Minimize" to="." method="_on_Minimize_draw"] +[connection signal="pressed" from="VBox/TitleBar/HBox/Minimize" to="." method="_on_Minimize_pressed"] +[connection signal="draw" from="VBox/TitleBar/HBox/Maximize" to="." method="_on_Maximize_draw"] +[connection signal="pressed" from="VBox/TitleBar/HBox/Maximize" to="." method="_on_Maximize_pressed"] +[connection signal="gui_input" from="VBox/TextDisplay/RichTextLabel" to="." method="_on_RichTextLabel_gui_input"] +[connection signal="pressed" from="VBox/BottomPanel/VBox/HBox/Navigation/VBox/CurrentScript" to="." method="_on_CurrentScript_pressed"] +[connection signal="pressed" from="VBox/BottomPanel/VBox/HBox/Navigation/VBox/HBox/Previous" to="." method="_on_Previous_pressed"] +[connection signal="pressed" from="VBox/BottomPanel/VBox/HBox/Navigation/VBox/HBox/Next" to="." method="_on_Next_pressed"] +[connection signal="pressed" from="VBox/BottomPanel/VBox/HBox/Navigation/VBox/HBox/Run" to="." method="_on_Run_pressed"] +[connection signal="pressed" from="VBox/BottomPanel/VBox/HBox/Navigation/VBox/HBox/RunSingleScript" to="." method="_on_RunSingleScript_pressed"] +[connection signal="draw" from="VBox/BottomPanel/VBox/HBox/Continue/ShowExtras" to="." method="_on_ShowExtras_draw"] +[connection signal="toggled" from="VBox/BottomPanel/VBox/HBox/Continue/ShowExtras" to="." method="_on_ShowExtras_toggled"] +[connection signal="pressed" from="VBox/BottomPanel/VBox/HBox/Continue/Continue" to="." method="_on_Continue_pressed"] +[connection signal="value_changed" from="VBox/BottomPanel/VBox/HBox2/LogLevelSlider" to="." method="_on_LogLevelSlider_value_changed"] +[connection signal="item_selected" from="ScriptsList" to="." method="_on_ScriptsList_item_selected"] +[connection signal="pressed" from="ExtraOptions/IgnorePause" to="." method="_on_IgnorePause_pressed"] +[connection signal="pressed" from="ExtraOptions/Copy" to="." method="_on_Copy_pressed"] +[connection signal="pressed" from="ExtraOptions/UserFiles" to="." method="_on_UserFiles_pressed"] +[connection signal="mouse_entered" from="ResizeHandle" to="." method="_on_ResizeHandle_mouse_entered"] +[connection signal="mouse_exited" from="ResizeHandle" to="." method="_on_ResizeHandle_mouse_exited"] diff --git a/demo/addons/gut/LICENSE.md b/demo/addons/gut/LICENSE.md new file mode 100644 index 0000000..a38ac23 --- /dev/null +++ b/demo/addons/gut/LICENSE.md @@ -0,0 +1,22 @@ +The MIT License (MIT) +===================== + +Copyright (c) 2018 Tom "Butch" Wesley + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/demo/addons/gut/UserFileViewer.gd b/demo/addons/gut/UserFileViewer.gd new file mode 100644 index 0000000..9713a94 --- /dev/null +++ b/demo/addons/gut/UserFileViewer.gd @@ -0,0 +1,55 @@ +extends WindowDialog + +onready var rtl = $TextDisplay/RichTextLabel +var _has_opened_file = false + +func _get_file_as_text(path): + var to_return = null + var f = File.new() + var result = f.open(path, f.READ) + if(result == OK): + to_return = f.get_as_text() + f.close() + else: + to_return = str('ERROR: Could not open file. Error code ', result) + return to_return + +func _ready(): + rtl.clear() + +func _on_OpenFile_pressed(): + $FileDialog.popup_centered() + +func _on_FileDialog_file_selected(path): + show_file(path) + +func _on_Close_pressed(): + self.hide() + +func show_file(path): + var text = _get_file_as_text(path) + if(text == ''): + text = '' + rtl.set_text(text) + self.window_title = path + +func show_open(): + self.popup_centered() + $FileDialog.popup_centered() + +func _on_FileDialog_popup_hide(): + if(rtl.text.length() == 0): + self.hide() + +func get_rich_text_label(): + return $TextDisplay/RichTextLabel + +func _on_Home_pressed(): + rtl.scroll_to_line(0) + +func _on_End_pressed(): + rtl.scroll_to_line(rtl.get_line_count() -1) + + +func _on_Copy_pressed(): + OS.clipboard = rtl.text diff --git a/demo/addons/gut/UserFileViewer.tscn b/demo/addons/gut/UserFileViewer.tscn new file mode 100644 index 0000000..15481b6 --- /dev/null +++ b/demo/addons/gut/UserFileViewer.tscn @@ -0,0 +1,126 @@ +[gd_scene load_steps=2 format=2] + +[ext_resource path="res://addons/gut/UserFileViewer.gd" type="Script" id=1] + +[node name="UserFileViewer" type="WindowDialog"] +margin_top = 20.0 +margin_right = 800.0 +margin_bottom = 450.0 +rect_min_size = Vector2( 800, 180 ) +popup_exclusive = true +window_title = "View File" +resizable = true +script = ExtResource( 1 ) +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="FileDialog" type="FileDialog" parent="."] +margin_right = 416.0 +margin_bottom = 184.0 +rect_min_size = Vector2( 400, 140 ) +rect_scale = Vector2( 2, 2 ) +popup_exclusive = true +window_title = "Open a File" +resizable = true +mode = 0 +access = 1 +show_hidden_files = true +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="TextDisplay" type="ColorRect" parent="."] +anchor_right = 1.0 +anchor_bottom = 1.0 +margin_left = 8.0 +margin_right = -10.0 +margin_bottom = -65.0 +color = Color( 0.2, 0.188235, 0.188235, 1 ) +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="RichTextLabel" type="RichTextLabel" parent="TextDisplay"] +anchor_right = 1.0 +anchor_bottom = 1.0 +focus_mode = 2 +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. + +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. + +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." +selection_enabled = true +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="OpenFile" type="Button" parent="."] +anchor_left = 1.0 +anchor_top = 1.0 +anchor_right = 1.0 +anchor_bottom = 1.0 +margin_left = -158.0 +margin_top = -50.0 +margin_right = -84.0 +margin_bottom = -30.0 +rect_scale = Vector2( 2, 2 ) +text = "Open File" + +[node name="Home" type="Button" parent="."] +anchor_left = 1.0 +anchor_top = 1.0 +anchor_right = 1.0 +anchor_bottom = 1.0 +margin_left = -478.0 +margin_top = -50.0 +margin_right = -404.0 +margin_bottom = -30.0 +rect_scale = Vector2( 2, 2 ) +text = "Home" +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="Copy" type="Button" parent="."] +anchor_top = 1.0 +anchor_bottom = 1.0 +margin_left = 160.0 +margin_top = -50.0 +margin_right = 234.0 +margin_bottom = -30.0 +rect_scale = Vector2( 2, 2 ) +text = "Copy" +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="End" type="Button" parent="."] +anchor_left = 1.0 +anchor_top = 1.0 +anchor_right = 1.0 +anchor_bottom = 1.0 +margin_left = -318.0 +margin_top = -50.0 +margin_right = -244.0 +margin_bottom = -30.0 +rect_scale = Vector2( 2, 2 ) +text = "End" + +[node name="Close" type="Button" parent="."] +anchor_top = 1.0 +anchor_bottom = 1.0 +margin_left = 10.0 +margin_top = -50.0 +margin_right = 80.0 +margin_bottom = -30.0 +rect_scale = Vector2( 2, 2 ) +text = "Close" + +[connection signal="file_selected" from="FileDialog" to="." method="_on_FileDialog_file_selected"] +[connection signal="popup_hide" from="FileDialog" to="." method="_on_FileDialog_popup_hide"] +[connection signal="pressed" from="OpenFile" to="." method="_on_OpenFile_pressed"] +[connection signal="pressed" from="Home" to="." method="_on_Home_pressed"] +[connection signal="pressed" from="Copy" to="." method="_on_Copy_pressed"] +[connection signal="pressed" from="End" to="." method="_on_End_pressed"] +[connection signal="pressed" from="Close" to="." method="_on_Close_pressed"] diff --git a/demo/addons/gut/autofree.gd b/demo/addons/gut/autofree.gd new file mode 100644 index 0000000..80b4e89 --- /dev/null +++ b/demo/addons/gut/autofree.gd @@ -0,0 +1,59 @@ +# ############################################################################## +#(G)odot (U)nit (T)est class +# +# ############################################################################## +# The MIT License (MIT) +# ===================== +# +# Copyright (c) 2020 Tom "Butch" Wesley +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# +# ############################################################################## +# Class used to keep track of objects to be freed and utilities to free them. +# ############################################################################## +var _to_free = [] +var _to_queue_free = [] + +func add_free(thing): + if(typeof(thing) == TYPE_OBJECT): + if(!thing is Reference): + _to_free.append(thing) + +func add_queue_free(thing): + _to_queue_free.append(thing) + +func get_queue_free_count(): + return _to_queue_free.size() + +func get_free_count(): + return _to_free.size() + +func free_all(): + for i in range(_to_free.size()): + if(is_instance_valid(_to_free[i])): + _to_free[i].free() + _to_free.clear() + + for i in range(_to_queue_free.size()): + if(is_instance_valid(_to_queue_free[i])): + _to_queue_free[i].queue_free() + _to_queue_free.clear() + + diff --git a/demo/addons/gut/comparator.gd b/demo/addons/gut/comparator.gd new file mode 100644 index 0000000..b34ef44 --- /dev/null +++ b/demo/addons/gut/comparator.gd @@ -0,0 +1,129 @@ +var _utils = load('res://addons/gut/utils.gd').get_instance() +var _strutils = _utils.Strutils.new() +var _max_length = 100 +var _should_compare_int_to_float = true + +const MISSING = '|__missing__gut__compare__value__|' +const DICTIONARY_DISCLAIMER = 'Dictionaries are compared-by-ref. See assert_eq in wiki.' + +func _cannot_comapre_text(v1, v2): + return str('Cannot compare ', _strutils.types[typeof(v1)], ' with ', + _strutils.types[typeof(v2)], '.') + +func _make_missing_string(text): + return '' + +func _create_missing_result(v1, v2, text): + var to_return = null + var v1_str = format_value(v1) + var v2_str = format_value(v2) + + if(typeof(v1) == TYPE_STRING and v1 == MISSING): + v1_str = _make_missing_string(text) + to_return = _utils.CompareResult.new() + elif(typeof(v2) == TYPE_STRING and v2 == MISSING): + v2_str = _make_missing_string(text) + to_return = _utils.CompareResult.new() + + if(to_return != null): + to_return.summary = str(v1_str, ' != ', v2_str) + to_return.are_equal = false + + return to_return + + +func simple(v1, v2, missing_string=''): + var missing_result = _create_missing_result(v1, v2, missing_string) + if(missing_result != null): + return missing_result + + var result = _utils.CompareResult.new() + var cmp_str = null + var extra = '' + + if(_should_compare_int_to_float and [2, 3].has(typeof(v1)) and [2, 3].has(typeof(v2))): + result.are_equal = v1 == v2 + + elif(_utils.are_datatypes_same(v1, v2)): + result.are_equal = v1 == v2 + if(typeof(v1) == TYPE_DICTIONARY): + if(result.are_equal): + extra = '. Same dictionary ref. ' + else: + extra = '. Different dictionary refs. ' + extra += DICTIONARY_DISCLAIMER + + if(typeof(v1) == TYPE_ARRAY): + var array_result = _utils.DiffTool.new(v1, v2, _utils.DIFF.SHALLOW) + result.summary = array_result.get_short_summary() + if(!array_result.are_equal()): + extra = ".\n" + array_result.get_short_summary() + + else: + cmp_str = '!=' + result.are_equal = false + extra = str('. ', _cannot_comapre_text(v1, v2)) + + cmp_str = get_compare_symbol(result.are_equal) + result.summary = str(format_value(v1), ' ', cmp_str, ' ', format_value(v2), extra) + + return result + + +func shallow(v1, v2): + var result = null + + if(_utils.are_datatypes_same(v1, v2)): + if(typeof(v1) in [TYPE_ARRAY, TYPE_DICTIONARY]): + result = _utils.DiffTool.new(v1, v2, _utils.DIFF.SHALLOW) + else: + result = simple(v1, v2) + else: + result = simple(v1, v2) + + return result + + +func deep(v1, v2): + var result = null + + if(_utils.are_datatypes_same(v1, v2)): + if(typeof(v1) in [TYPE_ARRAY, TYPE_DICTIONARY]): + result = _utils.DiffTool.new(v1, v2, _utils.DIFF.DEEP) + else: + result = simple(v1, v2) + else: + result = simple(v1, v2) + + return result + + +func format_value(val, max_val_length=_max_length): + return _strutils.truncate_string(_strutils.type2str(val), max_val_length) + + +func compare(v1, v2, diff_type=_utils.DIFF.SIMPLE): + var result = null + if(diff_type == _utils.DIFF.SIMPLE): + result = simple(v1, v2) + elif(diff_type == _utils.DIFF.SHALLOW): + result = shallow(v1, v2) + elif(diff_type == _utils.DIFF.DEEP): + result = deep(v1, v2) + + return result + + +func get_should_compare_int_to_float(): + return _should_compare_int_to_float + + +func set_should_compare_int_to_float(should_compare_int_float): + _should_compare_int_to_float = should_compare_int_float + + +func get_compare_symbol(is_equal): + if(is_equal): + return '==' + else: + return '!=' diff --git a/demo/addons/gut/compare_result.gd b/demo/addons/gut/compare_result.gd new file mode 100644 index 0000000..be6aebd --- /dev/null +++ b/demo/addons/gut/compare_result.gd @@ -0,0 +1,47 @@ +var are_equal = null setget set_are_equal, get_are_equal +var summary = null setget set_summary, get_summary +var max_differences = 30 setget set_max_differences, get_max_differences +var differences = {} setget set_differences, get_differences + +func _block_set(which, val): + push_error(str('cannot set ', which, ', value [', val, '] ignored.')) + +func _to_string(): + return str(get_summary()) # could be null, gotta str it. + +func get_are_equal(): + return are_equal + +func set_are_equal(r_eq): + are_equal = r_eq + +func get_summary(): + return summary + +func set_summary(smry): + summary = smry + +func get_total_count(): + pass + +func get_different_count(): + pass + +func get_short_summary(): + return summary + +func get_max_differences(): + return max_differences + +func set_max_differences(max_diff): + max_differences = max_diff + +func get_differences(): + return differences + +func set_differences(diffs): + _block_set('differences', diffs) + +func get_brackets(): + return null + diff --git a/demo/addons/gut/diff_formatter.gd b/demo/addons/gut/diff_formatter.gd new file mode 100644 index 0000000..fd954af --- /dev/null +++ b/demo/addons/gut/diff_formatter.gd @@ -0,0 +1,64 @@ +var _utils = load('res://addons/gut/utils.gd').get_instance() +var _strutils = _utils.Strutils.new() +const INDENT = ' ' +var _max_to_display = 30 +const ABSOLUTE_MAX_DISPLAYED = 10000 +const UNLIMITED = -1 + + +func _single_diff(diff, depth=0): + var to_return = "" + var brackets = diff.get_brackets() + + if(brackets != null and !diff.are_equal): + to_return = '' + to_return += str(brackets.open, "\n", + _strutils.indent_text(differences_to_s(diff.differences, depth), depth+1, INDENT), "\n", + brackets.close) + else: + to_return = str(diff) + + return to_return + + +func make_it(diff): + var to_return = '' + if(diff.are_equal): + to_return = diff.summary + else: + if(_max_to_display == ABSOLUTE_MAX_DISPLAYED): + to_return = str(diff.get_value_1(), ' != ', diff.get_value_2()) + else: + to_return = diff.get_short_summary() + to_return += str("\n", _strutils.indent_text(_single_diff(diff, 0), 1, ' ')) + return to_return + + +func differences_to_s(differences, depth=0): + var to_return = '' + var keys = differences.keys() + keys.sort() + var limit = min(_max_to_display, differences.size()) + + for i in range(limit): + var key = keys[i] + to_return += str(key, ": ", _single_diff(differences[key], depth)) + + if(i != limit -1): + to_return += "\n" + + if(differences.size() > _max_to_display): + to_return += str("\n\n... ", differences.size() - _max_to_display, " more.") + + return to_return + + +func get_max_to_display(): + return _max_to_display + + +func set_max_to_display(max_to_display): + _max_to_display = max_to_display + if(_max_to_display == UNLIMITED): + _max_to_display = ABSOLUTE_MAX_DISPLAYED + diff --git a/demo/addons/gut/diff_tool.gd b/demo/addons/gut/diff_tool.gd new file mode 100644 index 0000000..9dbbd1c --- /dev/null +++ b/demo/addons/gut/diff_tool.gd @@ -0,0 +1,162 @@ +extends 'res://addons/gut/compare_result.gd' +const INDENT = ' ' +enum { + DEEP, + SHALLOW, + SIMPLE +} + +var _utils = load('res://addons/gut/utils.gd').get_instance() +var _strutils = _utils.Strutils.new() +var _compare = _utils.Comparator.new() +var DiffTool = load('res://addons/gut/diff_tool.gd') + +var _value_1 = null +var _value_2 = null +var _total_count = 0 +var _diff_type = null +var _brackets = null +var _valid = true +var _desc_things = 'somethings' + +# -------- comapre_result.gd "interface" --------------------- +func set_are_equal(val): + _block_set('are_equal', val) + +func get_are_equal(): + return are_equal() + +func set_summary(val): + _block_set('summary', val) + +func get_summary(): + return summarize() + +func get_different_count(): + return differences.size() + +func get_total_count(): + return _total_count + +func get_short_summary(): + var text = str(_strutils.truncate_string(str(_value_1), 50), + ' ', _compare.get_compare_symbol(are_equal()), ' ', + _strutils.truncate_string(str(_value_2), 50)) + if(!are_equal()): + text += str(' ', get_different_count(), ' of ', get_total_count(), + ' ', _desc_things, ' do not match.') + return text + +func get_brackets(): + return _brackets +# -------- comapre_result.gd "interface" --------------------- + + +func _invalidate(): + _valid = false + differences = null + + +func _init(v1, v2, diff_type=DEEP): + _value_1 = v1 + _value_2 = v2 + _diff_type = diff_type + _compare.set_should_compare_int_to_float(false) + _find_differences(_value_1, _value_2) + + +func _find_differences(v1, v2): + if(_utils.are_datatypes_same(v1, v2)): + if(typeof(v1) == TYPE_ARRAY): + _brackets = {'open':'[', 'close':']'} + _desc_things = 'indexes' + _diff_array(v1, v2) + elif(typeof(v2) == TYPE_DICTIONARY): + _brackets = {'open':'{', 'close':'}'} + _desc_things = 'keys' + _diff_dictionary(v1, v2) + else: + _invalidate() + _utils.get_logger().error('Only Arrays and Dictionaries are supported.') + else: + _invalidate() + _utils.get_logger().error('Only Arrays and Dictionaries are supported.') + + +func _diff_array(a1, a2): + _total_count = max(a1.size(), a2.size()) + for i in range(a1.size()): + var result = null + if(i < a2.size()): + if(_diff_type == DEEP): + result = _compare.deep(a1[i], a2[i]) + else: + result = _compare.simple(a1[i], a2[i]) + else: + result = _compare.simple(a1[i], _compare.MISSING, 'index') + + if(!result.are_equal): + differences[i] = result + + if(a1.size() < a2.size()): + for i in range(a1.size(), a2.size()): + differences[i] = _compare.simple(_compare.MISSING, a2[i], 'index') + + +func _diff_dictionary(d1, d2): + var d1_keys = d1.keys() + var d2_keys = d2.keys() + + # Process all the keys in d1 + _total_count += d1_keys.size() + for key in d1_keys: + if(!d2.has(key)): + differences[key] = _compare.simple(d1[key], _compare.MISSING, 'key') + else: + d2_keys.remove(d2_keys.find(key)) + + var result = null + if(_diff_type == DEEP): + result = _compare.deep(d1[key], d2[key]) + else: + result = _compare.simple(d1[key], d2[key]) + + if(!result.are_equal): + differences[key] = result + + # Process all the keys in d2 that didn't exist in d1 + _total_count += d2_keys.size() + for i in range(d2_keys.size()): + differences[d2_keys[i]] = _compare.simple(_compare.MISSING, d2[d2_keys[i]], 'key') + + +func summarize(): + var summary = '' + + if(are_equal()): + summary = get_short_summary() + else: + var formatter = load('res://addons/gut/diff_formatter.gd').new() + formatter.set_max_to_display(max_differences) + summary = formatter.make_it(self) + + return summary + + +func are_equal(): + if(!_valid): + return null + else: + return differences.size() == 0 + + +func get_diff_type(): + return _diff_type + + +func get_value_1(): + return _value_1 + + +func get_value_2(): + return _value_2 diff --git a/demo/addons/gut/double_templates/function_template.txt b/demo/addons/gut/double_templates/function_template.txt new file mode 100644 index 0000000..666952e --- /dev/null +++ b/demo/addons/gut/double_templates/function_template.txt @@ -0,0 +1,6 @@ +{func_decleration} + __gut_spy('{method_name}', {param_array}) + if(__gut_should_call_super('{method_name}', {param_array})): + return {super_call} + else: + return __gut_get_stubbed_return('{method_name}', {param_array}) diff --git a/demo/addons/gut/double_templates/init_template.txt b/demo/addons/gut/double_templates/init_template.txt new file mode 100644 index 0000000..8a0cb95 --- /dev/null +++ b/demo/addons/gut/double_templates/init_template.txt @@ -0,0 +1,3 @@ +{func_decleration}{super_params}: + __gut_init() + __gut_spy('{method_name}', {param_array}) diff --git a/demo/addons/gut/double_templates/script_template.txt b/demo/addons/gut/double_templates/script_template.txt new file mode 100644 index 0000000..2071207 --- /dev/null +++ b/demo/addons/gut/double_templates/script_template.txt @@ -0,0 +1,58 @@ +# ############################################################################## +# Start Script +# ############################################################################## +{extends} + +{constants} + +{properties} +# ------------------------------------------------------------------------------ +# GUT Double properties and methods +# ------------------------------------------------------------------------------ +var __gut_metadata_ = { + path = '{path}', + subpath = '{subpath}', + stubber = __gut_instance_from_id({stubber_id}), + spy = __gut_instance_from_id({spy_id}), + gut = __gut_instance_from_id({gut_id}), + from_singleton = '{singleton_name}', + is_partial = {is_partial} +} + +func __gut_instance_from_id(inst_id): + if(inst_id == -1): + return null + else: + return instance_from_id(inst_id) + +func __gut_should_call_super(method_name, called_with): + if(__gut_metadata_.stubber != null): + return __gut_metadata_.stubber.should_call_super(self, method_name, called_with) + else: + return false + +var __gut_utils_ = load('res://addons/gut/utils.gd').get_instance() + +func __gut_spy(method_name, called_with): + if(__gut_metadata_.spy != null): + __gut_metadata_.spy.add_call(self, method_name, called_with) + +func __gut_get_stubbed_return(method_name, called_with): + if(__gut_metadata_.stubber != null): + return __gut_metadata_.stubber.get_return(self, method_name, called_with) + else: + return null + +func __gut_default_val(method_name, p_index): + if(__gut_metadata_.stubber != null): + return __gut_metadata_.stubber.get_default_value(self, method_name, p_index) + else: + return null + +func __gut_init(): + if(__gut_metadata_.gut != null): + __gut_metadata_.gut.get_autofree().add_free(self) + +# ------------------------------------------------------------------------------ +# Methods start here +# ------------------------------------------------------------------------------ diff --git a/demo/addons/gut/doubler.gd b/demo/addons/gut/doubler.gd new file mode 100644 index 0000000..ce865e5 --- /dev/null +++ b/demo/addons/gut/doubler.gd @@ -0,0 +1,751 @@ +# ############################################################################## +#(G)odot (U)nit (T)est class +# +# ############################################################################## +# The MIT License (MIT) +# ===================== +# +# Copyright (c) 2020 Tom "Butch" Wesley +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# +# ############################################################################## +# Description +# ----------- +# ############################################################################## + +# ------------------------------------------------------------------------------ +# Utility class to hold the local and built in methods separately. Add all local +# methods FIRST, then add built ins. +# ------------------------------------------------------------------------------ +class ScriptMethods: + # List of methods that should not be overloaded when they are not defined + # in the class being doubled. These either break things if they are + # overloaded or do not have a "super" equivalent so we can't just pass + # through. + var _blacklist = [ + 'has_method', + 'get_script', + 'get', + '_notification', + 'get_path', + '_enter_tree', + '_exit_tree', + '_process', + '_draw', + '_physics_process', + '_input', + '_unhandled_input', + '_unhandled_key_input', + '_set', + '_get', # probably + 'emit_signal', # can't handle extra parameters to be sent with signal. + 'draw_mesh', # issue with one parameter, value is `Null((..), (..), (..))`` + '_to_string', # nonexistant function ._to_string + '_get_minimum_size', # Nonexistent function _get_minimum_size + ] + + + var built_ins = [] + var local_methods = [] + var _method_names = [] + + func is_blacklisted(method_meta): + return _blacklist.find(method_meta.name) != -1 + + func _add_name_if_does_not_have(method_name): + var should_add = _method_names.find(method_name) == -1 + if(should_add): + _method_names.append(method_name) + return should_add + + func add_built_in_method(method_meta): + var did_add = _add_name_if_does_not_have(method_meta.name) + if(did_add and !is_blacklisted(method_meta)): + built_ins.append(method_meta) + + func add_local_method(method_meta): + var did_add = _add_name_if_does_not_have(method_meta.name) + if(did_add): + local_methods.append(method_meta) + + func to_s(): + var text = "Locals\n" + for i in range(local_methods.size()): + text += str(" ", local_methods[i].name, "\n") + text += "Built-Ins\n" + for i in range(built_ins.size()): + text += str(" ", built_ins[i].name, "\n") + return text + +# ------------------------------------------------------------------------------ +# Helper class to deal with objects and inner classes. +# ------------------------------------------------------------------------------ +class ObjectInfo: + var _path = null + var _subpaths = [] + var _utils = load('res://addons/gut/utils.gd').get_instance() + var _lgr = _utils.get_logger() + var _method_strategy = null + var make_partial_double = false + var scene_path = null + var _native_class = null + var _native_class_name = null + var _singleton_instance = null + var _singleton_name = null + + func _init(path, subpath=null): + _path = path + if(subpath != null): + _subpaths = Array(subpath.split('/')) + + # Returns an instance of the class/inner class + func instantiate(): + var to_return = null + + if(_singleton_instance != null): + to_return = _singleton_instance + elif(is_native()): + to_return = _native_class.new() + else: + to_return = get_loaded_class().new() + + return to_return + + + # Can't call it get_class because that is reserved so it gets this ugly name. + # Loads up the class and then any inner classes to give back a reference to + # the desired Inner class (if there is any) + func get_loaded_class(): + var LoadedClass = load(_path) + for i in range(_subpaths.size()): + LoadedClass = LoadedClass.get(_subpaths[i]) + return LoadedClass + + + func to_s(): + return str(_path, '[', get_subpath(), ']') + + + func get_path(): + return _path + + + func get_subpath(): + return PoolStringArray(_subpaths).join('/') + + + func has_subpath(): + return _subpaths.size() != 0 + + + func get_method_strategy(): + return _method_strategy + + + func set_method_strategy(method_strategy): + _method_strategy = method_strategy + + + func is_native(): + return _native_class != null + + + func set_native_class(native_class): + _native_class = native_class + var inst = native_class.new() + _native_class_name = inst.get_class() + _path = _native_class_name + if(!inst is Reference): + inst.free() + + + func get_native_class_name(): + return _native_class_name + + + func get_singleton_instance(): + return _singleton_instance + + + func get_singleton_name(): + return _singleton_name + + + func set_singleton_name(singleton_name): + _singleton_name = singleton_name + _singleton_instance = _utils.get_singleton_by_name(_singleton_name) + + + func is_singleton(): + return _singleton_instance != null + + + func get_extends_text(): + var extend = null + if(is_singleton()): + extend = str("# Double of singleton ", _singleton_name, ", base class is Reference") + elif(is_native()): + var native = get_native_class_name() + if(native.begins_with('_')): + native = native.substr(1) + extend = str("extends ", native) + else: + extend = str("extends '", get_path(), "'") + + if(has_subpath()): + extend += str('.', get_subpath().replace('/', '.')) + + return extend + + + func get_constants_text(): + if(!is_singleton()): + return "" + + # do not include constants defined in the super class which for + # singletons stubs is Reference. + var exclude_constants = Array(ClassDB.class_get_integer_constant_list("Reference")) + var text = str("# -----\n# ", _singleton_name, " Constants\n# -----\n") + var constants = ClassDB.class_get_integer_constant_list(_singleton_name) + for c in constants: + if(!exclude_constants.has(c)): + var value = ClassDB.class_get_integer_constant(_singleton_name, c) + text += str("const ", c, " = ", value, "\n") + + return text + + func get_properties_text(): + if(!is_singleton()): + return "" + + var text = str("# -----\n# ", _singleton_name, " Properties\n# -----\n") + var props = ClassDB.class_get_property_list(_singleton_name) + for prop in props: + var accessors = {"setter":null, "getter":null} + var prop_text = str("var ", prop["name"]) + + var getter_name = "get_" + prop["name"] + if(ClassDB.class_has_method(_singleton_name, getter_name)): + accessors.getter = getter_name + else: + getter_name = "is_" + prop["name"] + if(ClassDB.class_has_method(_singleton_name, getter_name)): + accessors.getter = getter_name + + var setter_name = "set_" + prop["name"] + if(ClassDB.class_has_method(_singleton_name, setter_name)): + accessors.setter = setter_name + + var setget_text = "" + if(accessors.setter != null and accessors.getter != null): + setget_text = str("setget ", accessors.setter, ", ", accessors.getter) + else: + # never seen this message show up, but it should show up if we + # get misbehaving singleton. + _lgr.error(str("Could not find setget methods for property: ", + _singleton_name, ".", prop["name"])) + + text += str(prop_text, " ", setget_text, "\n") + + return text + + +# ------------------------------------------------------------------------------ +# Allows for interacting with a file but only creating a string. This was done +# to ease the transition from files being created for doubles to loading +# doubles from a string. This allows the files to be created for debugging +# purposes since reading a file is easier than reading a dumped out string. +# ------------------------------------------------------------------------------ +class FileOrString: + extends File + + var _do_file = false + var _contents = '' + var _path = null + + func open(path, mode): + _path = path + if(_do_file): + return .open(path, mode) + else: + return OK + + func close(): + if(_do_file): + return .close() + + func store_string(s): + if(_do_file): + .store_string(s) + _contents += s + + func get_contents(): + return _contents + + func get_path(): + return _path + + func load_it(): + if(_contents != ''): + var script = GDScript.new() + script.set_source_code(get_contents()) + script.reload() + return script + else: + return load(_path) + +# ------------------------------------------------------------------------------ +# A stroke of genius if I do say so. This allows for doubling a scene without +# having to write any files. By overloading the "instance" method we can +# make whatever we want. +# ------------------------------------------------------------------------------ +class PackedSceneDouble: + extends PackedScene + var _script = null + var _scene = null + + func set_script_obj(obj): + _script = obj + + func instance(edit_state=0): + var inst = _scene.instance(edit_state) + if(_script != null): + inst.set_script(_script) + return inst + + func load_scene(path): + _scene = load(path) + + + + +# ------------------------------------------------------------------------------ +# START Doubler +# ------------------------------------------------------------------------------ +var _utils = load('res://addons/gut/utils.gd').get_instance() + +var _ignored_methods = _utils.OneToMany.new() +var _stubber = _utils.Stubber.new() +var _lgr = _utils.get_logger() +var _method_maker = _utils.MethodMaker.new() + +var _output_dir = 'user://gut_temp_directory' +var _double_count = 0 # used in making files names unique +var _spy = null +var _gut = null +var _strategy = null +var _base_script_text = _utils.get_file_as_text('res://addons/gut/double_templates/script_template.txt') +var _make_files = false +# used by tests for debugging purposes. +var _print_source = false + +func _init(strategy=_utils.DOUBLE_STRATEGY.PARTIAL): + set_logger(_utils.get_logger()) + _strategy = strategy + +# ############### +# Private +# ############### +func _get_indented_line(indents, text): + var to_return = '' + for _i in range(indents): + to_return += "\t" + return str(to_return, text, "\n") + + +func _stub_to_call_super(obj_info, method_name): + if(_utils.non_super_methods.has(method_name)): + return + + var path = obj_info.get_path() + if(obj_info.is_singleton()): + path = obj_info.get_singleton_name() + elif(obj_info.scene_path != null): + path = obj_info.scene_path + + var params = _utils.StubParams.new(path, method_name, obj_info.get_subpath()) + params.to_call_super() + _stubber.add_stub(params) + + +func _get_base_script_text(obj_info, override_path, script_methods): + var path = obj_info.get_path() + if(override_path != null): + path = override_path + + var stubber_id = -1 + if(_stubber != null): + stubber_id = _stubber.get_instance_id() + + var spy_id = -1 + if(_spy != null): + spy_id = _spy.get_instance_id() + + var gut_id = -1 + if(_gut != null): + gut_id = _gut.get_instance_id() + + var values = { + # Top sections + "extends":obj_info.get_extends_text(), + "constants":obj_info.get_constants_text(), + "properties":obj_info.get_properties_text(), + + # metadata values + "path":path, + "subpath":obj_info.get_subpath(), + "stubber_id":stubber_id, + "spy_id":spy_id, + "gut_id":gut_id, + "singleton_name":_utils.nvl(obj_info.get_singleton_name(), ''), + "is_partial":str(obj_info.make_partial_double).to_lower() + } + + return _base_script_text.format(values) + + +func _write_file(obj_info, dest_path, override_path=null): + var script_methods = _get_methods(obj_info) + var base_script = _get_base_script_text(obj_info, override_path, script_methods) + var super_name = "" + var path = "" + + if(obj_info.is_singleton()): + super_name = obj_info.get_singleton_name() + else: + path = obj_info.get_path() + + var f = FileOrString.new() + f._do_file = _make_files + var f_result = f.open(dest_path, f.WRITE) + + if(f_result != OK): + _lgr.error(str('Error creating file ', dest_path)) + _lgr.error(str('Could not create double for :', obj_info.to_s())) + return + + f.store_string(base_script) + + for i in range(script_methods.local_methods.size()): + f.store_string(_get_func_text(script_methods.local_methods[i], path, super_name)) + + for i in range(script_methods.built_ins.size()): + _stub_to_call_super(obj_info, script_methods.built_ins[i].name) + f.store_string(_get_func_text(script_methods.built_ins[i], path, super_name)) + + f.close() + if(_print_source): + print(f.get_contents()) + return f + + +func _double_scene_and_script(scene_info): + var to_return = PackedSceneDouble.new() + to_return.load_scene(scene_info.get_path()) + + var inst = load(scene_info.get_path()).instance() + var script_path = null + if(inst.get_script()): + script_path = inst.get_script().get_path() + inst.free() + + if(script_path): + var oi = ObjectInfo.new(script_path) + oi.set_method_strategy(scene_info.get_method_strategy()) + oi.make_partial_double = scene_info.make_partial_double + oi.scene_path = scene_info.get_path() + to_return.set_script_obj(_double(oi, scene_info.get_path()).load_it()) + + return to_return + + +func _get_methods(object_info): + var obj = object_info.instantiate() + # any method in the script or super script + var script_methods = ScriptMethods.new() + var methods = obj.get_method_list() + + if(!object_info.is_singleton() and !(obj is Reference)): + obj.free() + + # first pass is for local methods only + for i in range(methods.size()): + if(object_info.is_singleton()): + #print(methods[i].name, " :: ", methods[i].flags, " :: ", methods[i].id) + #print(" ", methods[i]) + + # It appears that the ID for methods upstream from a singleton are + # below 200. Initially it was thought that singleton specific methods + # were above 1000. This was true for Input but not for OS. I've + # changed the condition to be > 200 instead of > 1000. It will take + # some investigation to figure out if this is right, but it works + # for now. Someone either find an issue and open a bug, or this will + # just exist like this. Sorry future me (or someone else). + if(methods[i].id > 200 and methods[i].flags in [1, 9]): + script_methods.add_local_method(methods[i]) + + # 65 is a magic number for methods in script, though documentation + # says 64. This picks up local overloads of base class methods too. + # See MethodFlags in @GlobalScope + elif(methods[i].flags == 65 and !_ignored_methods.has(object_info.get_path(), methods[i]['name'])): + script_methods.add_local_method(methods[i]) + + if(object_info.get_method_strategy() == _utils.DOUBLE_STRATEGY.FULL): + # second pass is for anything not local + for j in range(methods.size()): + # 65 is a magic number for methods in script, though documentation + # says 64. This picks up local overloads of base class methods too. + if(methods[j].flags != 65 and !_ignored_methods.has(object_info.get_path(), methods[j]['name'])): + script_methods.add_built_in_method(methods[j]) + + return script_methods + + +func _get_inst_id_ref_str(inst): + var ref_str = 'null' + if(inst): + ref_str = str('instance_from_id(', inst.get_instance_id(),')') + return ref_str + + +func _get_func_text(method_hash, path, super=""): + var override_count = null; + if(_stubber != null): + override_count = _stubber.get_parameter_count(path, method_hash.name) + + var text = _method_maker.get_function_text(method_hash, path, override_count, super) + "\n" + + return text + +# returns the path to write the double file to +func _get_temp_path(object_info): + var file_name = null + var extension = null + + if(object_info.is_singleton()): + file_name = str(object_info.get_singleton_instance()) + extension = "gd" + elif(object_info.is_native()): + file_name = object_info.get_native_class_name() + extension = 'gd' + else: + file_name = object_info.get_path().get_file().get_basename() + extension = object_info.get_path().get_extension() + + if(object_info.has_subpath()): + file_name += '__' + object_info.get_subpath().replace('/', '__') + + file_name += str('__dbl', _double_count, '__.', extension) + + var to_return = _output_dir.plus_file(file_name) + return to_return + + +func _double(obj_info, override_path=null): + var temp_path = _get_temp_path(obj_info) + var result = _write_file(obj_info, temp_path, override_path) + _double_count += 1 + return result + + +func _double_script(path, make_partial, strategy): + var oi = ObjectInfo.new(path) + oi.make_partial_double = make_partial + oi.set_method_strategy(strategy) + return _double(oi).load_it() + + +func _double_inner(path, subpath, make_partial, strategy): + var oi = ObjectInfo.new(path, subpath) + oi.set_method_strategy(strategy) + oi.make_partial_double = make_partial + return _double(oi).load_it() + + +func _double_scene(path, make_partial, strategy): + var oi = ObjectInfo.new(path) + oi.set_method_strategy(strategy) + oi.make_partial_double = make_partial + return _double_scene_and_script(oi) + + +func _double_gdnative(native_class, make_partial, strategy): + var oi = ObjectInfo.new(null) + oi.set_native_class(native_class) + oi.set_method_strategy(strategy) + oi.make_partial_double = make_partial + return _double(oi).load_it() + + +func _double_singleton(singleton_name, make_partial, strategy): + var oi = ObjectInfo.new(null) + oi.set_singleton_name(singleton_name) + oi.set_method_strategy(_utils.DOUBLE_STRATEGY.PARTIAL) + oi.make_partial_double = make_partial + return _double(oi).load_it() + +# ############### +# Public +# ############### +func get_output_dir(): + return _output_dir + + +func set_output_dir(output_dir): + if(output_dir != null): + _output_dir = output_dir + if(_make_files): + var d = Directory.new() + d.make_dir_recursive(output_dir) + + +func get_spy(): + return _spy + + +func set_spy(spy): + _spy = spy + + +func get_stubber(): + return _stubber + + +func set_stubber(stubber): + _stubber = stubber + + +func get_logger(): + return _lgr + + +func set_logger(logger): + _lgr = logger + _method_maker.set_logger(logger) + + +func get_strategy(): + return _strategy + + +func set_strategy(strategy): + _strategy = strategy + + +func get_gut(): + return _gut + + +func set_gut(gut): + _gut = gut + + +func partial_double_scene(path, strategy=_strategy): + return _double_scene(path, true, strategy) + + +# double a scene +func double_scene(path, strategy=_strategy): + return _double_scene(path, false, strategy) + + +# double a script/object +func double(path, strategy=_strategy): + return _double_script(path, false, strategy) + + +func partial_double(path, strategy=_strategy): + return _double_script(path, true, strategy) + + +func partial_double_inner(path, subpath, strategy=_strategy): + return _double_inner(path, subpath, true, strategy) + + +# double an inner class in a script +func double_inner(path, subpath, strategy=_strategy): + return _double_inner(path, subpath, false, strategy) + + +# must always use FULL strategy since this is a native class and you won't get +# any methods if you don't use FULL +func double_gdnative(native_class): + return _double_gdnative(native_class, false, _utils.DOUBLE_STRATEGY.FULL) + + +# must always use FULL strategy since this is a native class and you won't get +# any methods if you don't use FULL +func partial_double_gdnative(native_class): + return _double_gdnative(native_class, true, _utils.DOUBLE_STRATEGY.FULL) + + +func double_singleton(name): + return _double_singleton(name, false, _utils.DOUBLE_STRATEGY.PARTIAL) + + +func partial_double_singleton(name): + return _double_singleton(name, true, _utils.DOUBLE_STRATEGY.PARTIAL) + + +func clear_output_directory(): + if(!_make_files): + return false + + var did = false + if(_output_dir.find('user://') == 0): + var d = Directory.new() + var result = d.open(_output_dir) + # BIG GOTCHA HERE. If it cannot open the dir w/ erro 31, then the + # directory becomes res:// and things go on normally and gut clears out + # out res:// which is SUPER BAD. + if(result == OK): + d.list_dir_begin(true) + var f = d.get_next() + while(f != ''): + d.remove(f) + f = d.get_next() + did = true + return did + +func delete_output_directory(): + var did = clear_output_directory() + if(did): + var d = Directory.new() + d.remove(_output_dir) + + +func add_ignored_method(path, method_name): + _ignored_methods.add(path, method_name) + + +func get_ignored_methods(): + return _ignored_methods + + +func get_make_files(): + return _make_files + + +func set_make_files(make_files): + _make_files = make_files + set_output_dir(_output_dir) + +func get_method_maker(): + return _method_maker diff --git a/demo/addons/gut/fonts/AnonymousPro-Bold.ttf b/demo/addons/gut/fonts/AnonymousPro-Bold.ttf new file mode 100644 index 0000000..1d4bf2b Binary files /dev/null and b/demo/addons/gut/fonts/AnonymousPro-Bold.ttf differ diff --git a/demo/addons/gut/fonts/AnonymousPro-BoldItalic.ttf b/demo/addons/gut/fonts/AnonymousPro-BoldItalic.ttf new file mode 100644 index 0000000..12863ca Binary files /dev/null and b/demo/addons/gut/fonts/AnonymousPro-BoldItalic.ttf differ diff --git a/demo/addons/gut/fonts/AnonymousPro-Italic.ttf b/demo/addons/gut/fonts/AnonymousPro-Italic.ttf new file mode 100644 index 0000000..f6870b7 Binary files /dev/null and b/demo/addons/gut/fonts/AnonymousPro-Italic.ttf differ diff --git a/demo/addons/gut/fonts/AnonymousPro-Regular.ttf b/demo/addons/gut/fonts/AnonymousPro-Regular.ttf new file mode 100644 index 0000000..57aa893 Binary files /dev/null and b/demo/addons/gut/fonts/AnonymousPro-Regular.ttf differ diff --git a/demo/addons/gut/fonts/CourierPrime-Bold.ttf b/demo/addons/gut/fonts/CourierPrime-Bold.ttf new file mode 100644 index 0000000..91d6de4 Binary files /dev/null and b/demo/addons/gut/fonts/CourierPrime-Bold.ttf differ diff --git a/demo/addons/gut/fonts/CourierPrime-BoldItalic.ttf b/demo/addons/gut/fonts/CourierPrime-BoldItalic.ttf new file mode 100644 index 0000000..0afaa98 Binary files /dev/null and b/demo/addons/gut/fonts/CourierPrime-BoldItalic.ttf differ diff --git a/demo/addons/gut/fonts/CourierPrime-Italic.ttf b/demo/addons/gut/fonts/CourierPrime-Italic.ttf new file mode 100644 index 0000000..f8a20bd Binary files /dev/null and b/demo/addons/gut/fonts/CourierPrime-Italic.ttf differ diff --git a/demo/addons/gut/fonts/CourierPrime-Regular.ttf b/demo/addons/gut/fonts/CourierPrime-Regular.ttf new file mode 100644 index 0000000..4f638f6 Binary files /dev/null and b/demo/addons/gut/fonts/CourierPrime-Regular.ttf differ diff --git a/demo/addons/gut/fonts/LobsterTwo-Bold.ttf b/demo/addons/gut/fonts/LobsterTwo-Bold.ttf new file mode 100644 index 0000000..2e979fb Binary files /dev/null and b/demo/addons/gut/fonts/LobsterTwo-Bold.ttf differ diff --git a/demo/addons/gut/fonts/LobsterTwo-BoldItalic.ttf b/demo/addons/gut/fonts/LobsterTwo-BoldItalic.ttf new file mode 100644 index 0000000..8bbf8d8 Binary files /dev/null and b/demo/addons/gut/fonts/LobsterTwo-BoldItalic.ttf differ diff --git a/demo/addons/gut/fonts/LobsterTwo-Italic.ttf b/demo/addons/gut/fonts/LobsterTwo-Italic.ttf new file mode 100644 index 0000000..b88ec17 Binary files /dev/null and b/demo/addons/gut/fonts/LobsterTwo-Italic.ttf differ diff --git a/demo/addons/gut/fonts/LobsterTwo-Regular.ttf b/demo/addons/gut/fonts/LobsterTwo-Regular.ttf new file mode 100644 index 0000000..556c45e Binary files /dev/null and b/demo/addons/gut/fonts/LobsterTwo-Regular.ttf differ diff --git a/demo/addons/gut/fonts/OFL.txt b/demo/addons/gut/fonts/OFL.txt new file mode 100644 index 0000000..3ed0152 --- /dev/null +++ b/demo/addons/gut/fonts/OFL.txt @@ -0,0 +1,94 @@ +Copyright (c) 2009, Mark Simonson (http://www.ms-studio.com, mark@marksimonson.com), +with Reserved Font Name Anonymous Pro. + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/demo/addons/gut/get_native_script.gd b/demo/addons/gut/get_native_script.gd new file mode 100644 index 0000000..7055fc3 --- /dev/null +++ b/demo/addons/gut/get_native_script.gd @@ -0,0 +1,6 @@ +# Since NativeScript does not exist if GDNative is not included in the build +# of Godot this script is conditionally loaded only when NativeScript exists. +# You can then get a reference to NativeScript for use in `is` checks by calling +# get_it. +static func get_it(): + return NativeScript diff --git a/demo/addons/gut/gui/BottomPanelShortcuts.gd b/demo/addons/gut/gui/BottomPanelShortcuts.gd new file mode 100644 index 0000000..86fbf8d --- /dev/null +++ b/demo/addons/gut/gui/BottomPanelShortcuts.gd @@ -0,0 +1,82 @@ +tool +extends WindowDialog + +onready var _ctrls = { + run_all = $Layout/CRunAll/ShortcutButton, + run_current_script = $Layout/CRunCurrentScript/ShortcutButton, + run_current_inner = $Layout/CRunCurrentInner/ShortcutButton, + run_current_test = $Layout/CRunCurrentTest/ShortcutButton, + panel_button = $Layout/CPanelButton/ShortcutButton, +} + +func _ready(): + for key in _ctrls: + var sc_button = _ctrls[key] + sc_button.connect('start_edit', self, '_on_edit_start', [sc_button]) + sc_button.connect('end_edit', self, '_on_edit_end') + + + # show dialog when running scene from editor. + if(get_parent() == get_tree().root): + popup_centered() + +# ------------ +# Events +# ------------ +func _on_Hide_pressed(): + hide() + +func _on_edit_start(which): + for key in _ctrls: + var sc_button = _ctrls[key] + if(sc_button != which): + sc_button.disable_set(true) + sc_button.disable_clear(true) + +func _on_edit_end(): + for key in _ctrls: + var sc_button = _ctrls[key] + sc_button.disable_set(false) + sc_button.disable_clear(false) + +# ------------ +# Public +# ------------ +func get_run_all(): + return _ctrls.run_all.get_shortcut() + +func get_run_current_script(): + return _ctrls.run_current_script.get_shortcut() + +func get_run_current_inner(): + return _ctrls.run_current_inner.get_shortcut() + +func get_run_current_test(): + return _ctrls.run_current_test.get_shortcut() + +func get_panel_button(): + return _ctrls.panel_button.get_shortcut() + + +func save_shortcuts(path): + var f = ConfigFile.new() + + f.set_value('main', 'run_all', _ctrls.run_all.get_shortcut()) + f.set_value('main', 'run_current_script', _ctrls.run_current_script.get_shortcut()) + f.set_value('main', 'run_current_inner', _ctrls.run_current_inner.get_shortcut()) + f.set_value('main', 'run_current_test', _ctrls.run_current_test.get_shortcut()) + f.set_value('main', 'panel_button', _ctrls.panel_button.get_shortcut()) + + f.save(path) + + +func load_shortcuts(path): + var emptyShortcut = ShortCut.new() + var f = ConfigFile.new() + f.load(path) + + _ctrls.run_all.set_shortcut(f.get_value('main', 'run_all', emptyShortcut)) + _ctrls.run_current_script.set_shortcut(f.get_value('main', 'run_current_script', emptyShortcut)) + _ctrls.run_current_inner.set_shortcut(f.get_value('main', 'run_current_inner', emptyShortcut)) + _ctrls.run_current_test.set_shortcut(f.get_value('main', 'run_current_test', emptyShortcut)) + _ctrls.panel_button.set_shortcut(f.get_value('main', 'panel_button', emptyShortcut)) diff --git a/demo/addons/gut/gui/BottomPanelShortcuts.tscn b/demo/addons/gut/gui/BottomPanelShortcuts.tscn new file mode 100644 index 0000000..5e2e5d9 --- /dev/null +++ b/demo/addons/gut/gui/BottomPanelShortcuts.tscn @@ -0,0 +1,232 @@ +[gd_scene load_steps=3 format=2] + +[ext_resource path="res://addons/gut/gui/ShortcutButton.tscn" type="PackedScene" id=1] +[ext_resource path="res://addons/gut/gui/BottomPanelShortcuts.gd" type="Script" id=2] + +[node name="BottomPanelShortcuts" type="WindowDialog"] +visible = true +anchor_right = 0.234 +anchor_bottom = 0.328 +margin_right = 195.384 +margin_bottom = 62.2 +rect_min_size = Vector2( 435, 305 ) +popup_exclusive = true +window_title = "GUT Shortcuts" +resizable = true +script = ExtResource( 2 ) +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="Layout" type="VBoxContainer" parent="."] +anchor_right = 1.0 +anchor_bottom = 1.0 +margin_left = 5.0 +margin_right = -5.0 +margin_bottom = 2.0 +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="TopPad" type="CenterContainer" parent="Layout"] +margin_right = 425.0 +margin_bottom = 5.0 +rect_min_size = Vector2( 0, 5 ) + +[node name="Label2" type="Label" parent="Layout"] +margin_top = 9.0 +margin_right = 425.0 +margin_bottom = 29.0 +rect_min_size = Vector2( 0, 20 ) +text = "Always Active" +align = 1 +valign = 1 +autowrap = true + +[node name="ColorRect" type="ColorRect" parent="Layout/Label2"] +show_behind_parent = true +anchor_right = 1.0 +anchor_bottom = 1.0 +color = Color( 0, 0, 0, 0.196078 ) +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="CPanelButton" type="HBoxContainer" parent="Layout"] +margin_top = 33.0 +margin_right = 425.0 +margin_bottom = 58.0 + +[node name="Label" type="Label" parent="Layout/CPanelButton"] +margin_right = 138.0 +margin_bottom = 25.0 +rect_min_size = Vector2( 50, 0 ) +size_flags_vertical = 7 +text = "Show/Hide GUT Panel" +valign = 1 + +[node name="ShortcutButton" parent="Layout/CPanelButton" instance=ExtResource( 1 )] +anchor_right = 0.0 +anchor_bottom = 0.0 +margin_left = 142.0 +margin_right = 425.0 +margin_bottom = 25.0 +size_flags_horizontal = 3 + +[node name="GutPanelPad" type="CenterContainer" parent="Layout"] +margin_top = 62.0 +margin_right = 425.0 +margin_bottom = 67.0 +rect_min_size = Vector2( 0, 5 ) + +[node name="Label" type="Label" parent="Layout"] +margin_top = 71.0 +margin_right = 425.0 +margin_bottom = 91.0 +rect_min_size = Vector2( 0, 20 ) +text = "Only Active When GUT Panel Shown" +align = 1 +valign = 1 +autowrap = true + +[node name="ColorRect2" type="ColorRect" parent="Layout/Label"] +show_behind_parent = true +anchor_right = 1.0 +anchor_bottom = 1.0 +color = Color( 0, 0, 0, 0.196078 ) +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="TopPad2" type="CenterContainer" parent="Layout"] +margin_top = 95.0 +margin_right = 425.0 +margin_bottom = 100.0 +rect_min_size = Vector2( 0, 5 ) + +[node name="CRunAll" type="HBoxContainer" parent="Layout"] +margin_top = 104.0 +margin_right = 425.0 +margin_bottom = 129.0 + +[node name="Label" type="Label" parent="Layout/CRunAll"] +margin_right = 50.0 +margin_bottom = 25.0 +rect_min_size = Vector2( 50, 0 ) +size_flags_vertical = 7 +text = "Run All" +valign = 1 + +[node name="ShortcutButton" parent="Layout/CRunAll" instance=ExtResource( 1 )] +anchor_right = 0.0 +anchor_bottom = 0.0 +margin_left = 54.0 +margin_right = 425.0 +margin_bottom = 25.0 +size_flags_horizontal = 3 + +[node name="CRunCurrentScript" type="HBoxContainer" parent="Layout"] +margin_top = 133.0 +margin_right = 425.0 +margin_bottom = 158.0 + +[node name="Label" type="Label" parent="Layout/CRunCurrentScript"] +margin_right = 115.0 +margin_bottom = 25.0 +rect_min_size = Vector2( 50, 0 ) +size_flags_vertical = 7 +text = "Run Current Script" +valign = 1 + +[node name="ShortcutButton" parent="Layout/CRunCurrentScript" instance=ExtResource( 1 )] +anchor_right = 0.0 +anchor_bottom = 0.0 +margin_left = 119.0 +margin_right = 425.0 +margin_bottom = 25.0 +size_flags_horizontal = 3 + +[node name="CRunCurrentInner" type="HBoxContainer" parent="Layout"] +margin_top = 162.0 +margin_right = 425.0 +margin_bottom = 187.0 + +[node name="Label" type="Label" parent="Layout/CRunCurrentInner"] +margin_right = 150.0 +margin_bottom = 25.0 +rect_min_size = Vector2( 50, 0 ) +size_flags_vertical = 7 +text = "Run Current Inner Class" +valign = 1 + +[node name="ShortcutButton" parent="Layout/CRunCurrentInner" instance=ExtResource( 1 )] +anchor_right = 0.0 +anchor_bottom = 0.0 +margin_left = 154.0 +margin_right = 425.0 +margin_bottom = 25.0 +size_flags_horizontal = 3 + +[node name="CRunCurrentTest" type="HBoxContainer" parent="Layout"] +margin_top = 191.0 +margin_right = 425.0 +margin_bottom = 216.0 + +[node name="Label" type="Label" parent="Layout/CRunCurrentTest"] +margin_right = 106.0 +margin_bottom = 25.0 +rect_min_size = Vector2( 50, 0 ) +size_flags_vertical = 7 +text = "Run Current Test" +valign = 1 + +[node name="ShortcutButton" parent="Layout/CRunCurrentTest" instance=ExtResource( 1 )] +anchor_right = 0.0 +anchor_bottom = 0.0 +margin_left = 110.0 +margin_right = 425.0 +margin_bottom = 25.0 +size_flags_horizontal = 3 + +[node name="CenterContainer2" type="CenterContainer" parent="Layout"] +margin_top = 220.0 +margin_right = 425.0 +margin_bottom = 241.0 +rect_min_size = Vector2( 0, 5 ) +size_flags_horizontal = 3 +size_flags_vertical = 3 + +[node name="ShiftDisclaimer" type="Label" parent="Layout"] +margin_top = 245.0 +margin_right = 425.0 +margin_bottom = 259.0 +text = "\"Shift\" cannot be the only modifier for a shortcut." +align = 2 +autowrap = true + +[node name="HBoxContainer" type="HBoxContainer" parent="Layout"] +margin_top = 263.0 +margin_right = 425.0 +margin_bottom = 293.0 + +[node name="CenterContainer" type="CenterContainer" parent="Layout/HBoxContainer"] +margin_right = 361.0 +margin_bottom = 30.0 +size_flags_horizontal = 3 +size_flags_vertical = 3 + +[node name="Hide" type="Button" parent="Layout/HBoxContainer"] +margin_left = 365.0 +margin_right = 425.0 +margin_bottom = 30.0 +rect_min_size = Vector2( 60, 30 ) +text = "Close" + +[node name="BottomPad" type="CenterContainer" parent="Layout"] +margin_top = 297.0 +margin_right = 425.0 +margin_bottom = 307.0 +rect_min_size = Vector2( 0, 10 ) +size_flags_horizontal = 3 + +[connection signal="pressed" from="Layout/HBoxContainer/Hide" to="." method="_on_Hide_pressed"] diff --git a/demo/addons/gut/gui/GutBottomPanel.gd b/demo/addons/gut/gui/GutBottomPanel.gd new file mode 100644 index 0000000..e7b5037 --- /dev/null +++ b/demo/addons/gut/gui/GutBottomPanel.gd @@ -0,0 +1,370 @@ +tool +extends Control + +const RUNNER_JSON_PATH = 'res://.gut_editor_config.json' +const RESULT_FILE = 'user://.gut_editor.bbcode' +const RESULT_JSON = 'user://.gut_editor.json' +const SHORTCUTS_PATH = 'res://.gut_editor_shortcuts.cfg' + +var TestScript = load('res://addons/gut/test.gd') +var GutConfigGui = load('res://addons/gut/gui/gut_config_gui.gd') +var ScriptTextEditors = load('res://addons/gut/gui/script_text_editor_controls.gd') + +var _interface = null; +var _is_running = false; +var _gut_config = load('res://addons/gut/gut_config.gd').new() +var _gut_config_gui = null +var _gut_plugin = null +var _light_color = Color(0, 0, 0, .5) +var _panel_button = null +var _last_selected_path = null + + +onready var _ctrls = { + output = $layout/RSplit/CResults/Tabs/OutputText.get_rich_text_edit(), + output_ctrl = $layout/RSplit/CResults/Tabs/OutputText, + run_button = $layout/ControlBar/RunAll, + shortcuts_button = $layout/ControlBar/Shortcuts, + + settings_button = $layout/ControlBar/Settings, + run_results_button = $layout/ControlBar/RunResultsBtn, + output_button = $layout/ControlBar/OutputBtn, + + settings = $layout/RSplit/sc/Settings, + shortcut_dialog = $BottomPanelShortcuts, + light = $layout/RSplit/CResults/ControlBar/Light, + results = { + bar = $layout/RSplit/CResults/ControlBar, + passing = $layout/RSplit/CResults/ControlBar/Passing/value, + failing = $layout/RSplit/CResults/ControlBar/Failing/value, + pending = $layout/RSplit/CResults/ControlBar/Pending/value, + errors = $layout/RSplit/CResults/ControlBar/Errors/value, + warnings = $layout/RSplit/CResults/ControlBar/Warnings/value, + orphans = $layout/RSplit/CResults/ControlBar/Orphans/value + }, + run_at_cursor = $layout/ControlBar/RunAtCursor, + run_results = $layout/RSplit/CResults/Tabs/RunResults +} + + +func _init(): + _gut_config.load_panel_options(RUNNER_JSON_PATH) + + +func _ready(): + _ctrls.results.bar.connect('draw', self, '_on_results_bar_draw', [_ctrls.results.bar]) + hide_settings(!_ctrls.settings_button.pressed) + _gut_config_gui = GutConfigGui.new(_ctrls.settings) + _gut_config_gui.set_options(_gut_config.options) + + _apply_options_to_controls() + + _ctrls.shortcuts_button.icon = get_icon('ShortCut', 'EditorIcons') + _ctrls.settings_button.icon = get_icon('Tools', 'EditorIcons') + _ctrls.run_results_button.icon = get_icon('AnimationTrackGroup', 'EditorIcons') # Tree + _ctrls.output_button.icon = get_icon('Font', 'EditorIcons') + + _ctrls.run_results.set_output_control(_ctrls.output_ctrl) + _ctrls.run_results.set_font( + _gut_config.options.panel_options.font_name, + _gut_config.options.panel_options.font_size) + + var check_import = load('res://addons/gut/images/red.png') + if(check_import == null): + _ctrls.run_results.add_centered_text("GUT got some new images that are not imported yet. Please restart Godot.") + print('GUT got some new images that are not imported yet. Please restart Godot.') + else: + _ctrls.run_results.add_centered_text("Let's run some tests!") + + +func _apply_options_to_controls(): + hide_settings(_gut_config.options.panel_options.hide_settings) + hide_result_tree(_gut_config.options.panel_options.hide_result_tree) + hide_output_text(_gut_config.options.panel_options.hide_output_text) + + _ctrls.output_ctrl.set_use_colors(_gut_config.options.panel_options.use_colors) + _ctrls.output_ctrl.set_all_fonts(_gut_config.options.panel_options.font_name) + _ctrls.output_ctrl.set_font_size(_gut_config.options.panel_options.font_size) + + _ctrls.run_results.set_font( + _gut_config.options.panel_options.font_name, + _gut_config.options.panel_options.font_size) + _ctrls.run_results.set_show_orphans(!_gut_config.options.hide_orphans) + + +func _process(delta): + if(_is_running): + if(!_interface.is_playing_scene()): + _is_running = false + _ctrls.output_ctrl.add_text("\ndone") + load_result_output() + _gut_plugin.make_bottom_panel_item_visible(self) + +# --------------- +# Private +# --------------- + +func load_shortcuts(): + _ctrls.shortcut_dialog.load_shortcuts(SHORTCUTS_PATH) + _apply_shortcuts() + + +func _is_test_script(script): + var from = script.get_base_script() + while(from and from.resource_path != 'res://addons/gut/test.gd'): + from = from.get_base_script() + + return from != null + + +func _show_errors(errs): + _ctrls.output_ctrl.clear() + var text = "Cannot run tests, you have a configuration error:\n" + for e in errs: + text += str('* ', e, "\n") + text += "Check your settings ----->" + _ctrls.output_ctrl.add_text(text) + hide_output_text(false) + hide_settings(false) + + +func _save_config(): + _gut_config.options = _gut_config_gui.get_options(_gut_config.options) + _gut_config.options.panel_options.hide_settings = !_ctrls.settings_button.pressed + _gut_config.options.panel_options.hide_result_tree = !_ctrls.run_results_button.pressed + _gut_config.options.panel_options.hide_output_text = !_ctrls.output_button.pressed + _gut_config.options.panel_options.use_colors = _ctrls.output_ctrl.get_use_colors() + + var w_result = _gut_config.write_options(RUNNER_JSON_PATH) + if(w_result != OK): + push_error(str('Could not write options to ', RUNNER_JSON_PATH, ': ', w_result)) + return; + + +func _run_tests(): + var issues = _gut_config_gui.get_config_issues() + if(issues.size() > 0): + _show_errors(issues) + return + + write_file(RESULT_FILE, 'Run in progress') + _save_config() + _apply_options_to_controls() + + _ctrls.output_ctrl.clear() + _ctrls.run_results.clear() + _ctrls.run_results.add_centered_text('Running...') + + _interface.play_custom_scene('res://addons/gut/gui/GutRunner.tscn') + _is_running = true + _ctrls.output_ctrl.add_text('Running...') + + +func _apply_shortcuts(): + _ctrls.run_button.shortcut = _ctrls.shortcut_dialog.get_run_all() + + _ctrls.run_at_cursor.get_script_button().shortcut = \ + _ctrls.shortcut_dialog.get_run_current_script() + _ctrls.run_at_cursor.get_inner_button().shortcut = \ + _ctrls.shortcut_dialog.get_run_current_inner() + _ctrls.run_at_cursor.get_test_button().shortcut = \ + _ctrls.shortcut_dialog.get_run_current_test() + + _panel_button.shortcut = _ctrls.shortcut_dialog.get_panel_button() + + +func _run_all(): + _gut_config.options.selected = null + _gut_config.options.inner_class = null + _gut_config.options.unit_test_name = null + + _run_tests() + + +# --------------- +# Events +# --------------- +func _on_results_bar_draw(bar): + bar.draw_rect(Rect2(Vector2(0, 0), bar.rect_size), Color(0, 0, 0, .2)) + + +func _on_Light_draw(): + var l = _ctrls.light + l.draw_circle(Vector2(l.rect_size.x / 2, l.rect_size.y / 2), l.rect_size.x / 2, _light_color) + + +func _on_editor_script_changed(script): + if(script): + set_current_script(script) + + +func _on_RunAll_pressed(): + _run_all() + + +func _on_Shortcuts_pressed(): + _ctrls.shortcut_dialog.popup_centered() + + +func _on_BottomPanelShortcuts_popup_hide(): + _apply_shortcuts() + _ctrls.shortcut_dialog.save_shortcuts(SHORTCUTS_PATH) + + +func _on_RunAtCursor_run_tests(what): + _gut_config.options.selected = what.script + _gut_config.options.inner_class = what.inner_class + _gut_config.options.unit_test_name = what.test_method + + _run_tests() + + +func _on_Settings_pressed(): + hide_settings(!_ctrls.settings_button.pressed) + _save_config() + + +func _on_OutputBtn_pressed(): + hide_output_text(!_ctrls.output_button.pressed) + _save_config() + + +func _on_RunResultsBtn_pressed(): + hide_result_tree(! _ctrls.run_results_button.pressed) + _save_config() + + +# Currently not used, but will be when I figure out how to put +# colors into the text results +func _on_UseColors_pressed(): + pass + +# --------------- +# Public +# --------------- +func hide_result_tree(should): + _ctrls.run_results.visible = !should + _ctrls.run_results_button.pressed = !should + + +func hide_settings(should): + var s_scroll = _ctrls.settings.get_parent() + s_scroll.visible = !should + + # collapse only collapses the first control, so we move + # settings around to be the collapsed one + if(should): + s_scroll.get_parent().move_child(s_scroll, 0) + else: + s_scroll.get_parent().move_child(s_scroll, 1) + + $layout/RSplit.collapsed = should + _ctrls.settings_button.pressed = !should + + +func hide_output_text(should): + $layout/RSplit/CResults/Tabs/OutputText.visible = !should + _ctrls.output_button.pressed = !should + + +func load_result_output(): + _ctrls.output_ctrl.load_file(RESULT_FILE) + + var summary = get_file_as_text(RESULT_JSON) + var results = JSON.parse(summary) + if(results.error != OK): + return + + _ctrls.run_results.load_json_results(results.result) + + var summary_json = results.result['test_scripts']['props'] + _ctrls.results.passing.text = str(summary_json.passing) + _ctrls.results.passing.get_parent().visible = true + + _ctrls.results.failing.text = str(summary_json.failures) + _ctrls.results.failing.get_parent().visible = true + + _ctrls.results.pending.text = str(summary_json.pending) + _ctrls.results.pending.get_parent().visible = _ctrls.results.pending.text != '0' + + _ctrls.results.errors.text = str(summary_json.errors) + _ctrls.results.errors.get_parent().visible = _ctrls.results.errors.text != '0' + + _ctrls.results.warnings.text = str(summary_json.warnings) + _ctrls.results.warnings.get_parent().visible = _ctrls.results.warnings.text != '0' + + _ctrls.results.orphans.text = str(summary_json.orphans) + _ctrls.results.orphans.get_parent().visible = _ctrls.results.orphans.text != '0' and !_gut_config.options.hide_orphans + + if(summary_json.tests == 0): + _light_color = Color(1, 0, 0, .75) + elif(summary_json.failures != 0): + _light_color = Color(1, 0, 0, .75) + elif(summary_json.pending != 0): + _light_color = Color(1, 1, 0, .75) + else: + _light_color = Color(0, 1, 0, .75) + _ctrls.light.visible = true + _ctrls.light.update() + + +func set_current_script(script): + if(script): + if(_is_test_script(script)): + var file = script.resource_path.get_file() + _last_selected_path = script.resource_path.get_file() + _ctrls.run_at_cursor.activate_for_script(script.resource_path) + + +func set_interface(value): + _interface = value + _interface.get_script_editor().connect("editor_script_changed", self, '_on_editor_script_changed') + + var ste = ScriptTextEditors.new(_interface.get_script_editor()) + _ctrls.run_results.set_interface(_interface) + _ctrls.run_results.set_script_text_editors(ste) + _ctrls.run_at_cursor.set_script_text_editors(ste) + set_current_script(_interface.get_script_editor().get_current_script()) + + +func set_plugin(value): + _gut_plugin = value + + +func set_panel_button(value): + _panel_button = value + +# ------------------------------------------------------------------------------ +# Write a file. +# ------------------------------------------------------------------------------ +func write_file(path, content): + var f = File.new() + var result = f.open(path, f.WRITE) + if(result == OK): + f.store_string(content) + f.close() + return result + + +# ------------------------------------------------------------------------------ +# Returns the text of a file or an empty string if the file could not be opened. +# ------------------------------------------------------------------------------ +func get_file_as_text(path): + var to_return = '' + var f = File.new() + var result = f.open(path, f.READ) + if(result == OK): + to_return = f.get_as_text() + f.close() + return to_return + + +# ------------------------------------------------------------------------------ +# return if_null if value is null otherwise return value +# ------------------------------------------------------------------------------ +func nvl(value, if_null): + if(value == null): + return if_null + else: + return value + diff --git a/demo/addons/gut/gui/GutBottomPanel.tscn b/demo/addons/gut/gui/GutBottomPanel.tscn new file mode 100644 index 0000000..7ec6649 --- /dev/null +++ b/demo/addons/gut/gui/GutBottomPanel.tscn @@ -0,0 +1,385 @@ +[gd_scene load_steps=11 format=2] + +[ext_resource path="res://addons/gut/gui/GutBottomPanel.gd" type="Script" id=1] +[ext_resource path="res://addons/gut/gui/BottomPanelShortcuts.tscn" type="PackedScene" id=2] +[ext_resource path="res://addons/gut/gui/RunAtCursor.tscn" type="PackedScene" id=3] +[ext_resource path="res://addons/gut/gui/play.png" type="Texture" id=4] +[ext_resource path="res://addons/gut/gui/RunResults.tscn" type="PackedScene" id=5] +[ext_resource path="res://addons/gut/gui/OutputText.tscn" type="PackedScene" id=6] + +[sub_resource type="InputEventKey" id=8] +control = true +scancode = 49 + +[sub_resource type="ShortCut" id=9] +shortcut = SubResource( 8 ) + +[sub_resource type="Image" id=10] +data = { +"data": PoolByteArray( 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ), +"format": "LumAlpha8", +"height": 16, +"mipmaps": false, +"width": 16 +} + +[sub_resource type="ImageTexture" id=2] +flags = 4 +flags = 4 +image = SubResource( 10 ) +size = Vector2( 16, 16 ) + +[node name="GutBottomPanel" type="Control"] +anchor_left = -0.0025866 +anchor_top = -0.00176575 +anchor_right = 0.997413 +anchor_bottom = 0.998234 +margin_left = 2.64868 +margin_top = 1.05945 +margin_right = 2.64862 +margin_bottom = 1.05945 +rect_min_size = Vector2( 0, 300 ) +script = ExtResource( 1 ) + +[node name="layout" type="VBoxContainer" parent="."] +anchor_right = 1.0 +anchor_bottom = 1.0 +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="ControlBar" type="HBoxContainer" parent="layout"] +margin_right = 1023.0 +margin_bottom = 40.0 +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="RunAll" type="Button" parent="layout/ControlBar"] +margin_right = 150.0 +margin_bottom = 40.0 +rect_min_size = Vector2( 150, 0 ) +hint_tooltip = "Run all test scripts in the suite." +size_flags_vertical = 11 +shortcut = SubResource( 9 ) +text = "Run All" +icon = ExtResource( 4 ) +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="Label" type="Label" parent="layout/ControlBar"] +margin_left = 154.0 +margin_top = 13.0 +margin_right = 213.0 +margin_bottom = 27.0 +hint_tooltip = "When a test script is edited, buttons are displayed to +run the opened script or an Inner-Test-Class or a +single test. The buttons change based on the location +of the cursor in the file. + +These buttons will remain active when editing other +items so that you can run tests without having to switch +back to the test script. + +You can assign keyboard shortcuts for these buttons +using the \"shortcuts\" button in the GUT panel." +mouse_filter = 1 +text = "Current: " + +[node name="RunAtCursor" parent="layout/ControlBar" instance=ExtResource( 3 )] +anchor_right = 0.0 +anchor_bottom = 0.0 +margin_left = 217.0 +margin_right = 548.0 +margin_bottom = 40.0 +rect_min_size = Vector2( 0, 40 ) + +[node name="CenterContainer2" type="CenterContainer" parent="layout/ControlBar"] +margin_left = 552.0 +margin_right = 883.0 +margin_bottom = 40.0 +size_flags_horizontal = 3 + +[node name="Sep1" type="ColorRect" parent="layout/ControlBar"] +margin_left = 887.0 +margin_right = 889.0 +margin_bottom = 40.0 +rect_min_size = Vector2( 2, 0 ) + +[node name="RunResultsBtn" type="ToolButton" parent="layout/ControlBar"] +margin_left = 893.0 +margin_right = 921.0 +margin_bottom = 40.0 +hint_tooltip = "Show/Hide Results Tree Panel." +toggle_mode = true +pressed = true +icon = SubResource( 2 ) + +[node name="OutputBtn" type="ToolButton" parent="layout/ControlBar"] +margin_left = 925.0 +margin_right = 953.0 +margin_bottom = 40.0 +hint_tooltip = "Show/Hide Output Panel." +toggle_mode = true +pressed = true +icon = SubResource( 2 ) + +[node name="Settings" type="ToolButton" parent="layout/ControlBar"] +margin_left = 957.0 +margin_right = 985.0 +margin_bottom = 40.0 +hint_tooltip = "Show/Hide Settings Panel." +toggle_mode = true +icon = SubResource( 2 ) + +[node name="Sep2" type="ColorRect" parent="layout/ControlBar"] +margin_left = 989.0 +margin_right = 991.0 +margin_bottom = 40.0 +rect_min_size = Vector2( 2, 0 ) + +[node name="Shortcuts" type="ToolButton" parent="layout/ControlBar"] +margin_left = 995.0 +margin_right = 1023.0 +margin_bottom = 40.0 +hint_tooltip = "Set shortcuts for GUT buttons. Shortcuts do not work when the GUT panel is not visible." +size_flags_vertical = 11 +icon = SubResource( 2 ) + +[node name="RSplit" type="HSplitContainer" parent="layout"] +margin_top = 44.0 +margin_right = 1023.0 +margin_bottom = 599.0 +size_flags_horizontal = 3 +size_flags_vertical = 3 +collapsed = true + +[node name="sc" type="ScrollContainer" parent="layout/RSplit"] +visible = false +margin_left = 593.0 +margin_right = 1093.0 +margin_bottom = 555.0 +rect_min_size = Vector2( 500, 0 ) +mouse_filter = 1 +size_flags_vertical = 3 + +[node name="Settings" type="VBoxContainer" parent="layout/RSplit/sc"] +margin_right = 500.0 +margin_bottom = 908.0 +size_flags_horizontal = 3 +size_flags_vertical = 3 + +[node name="CResults" type="VBoxContainer" parent="layout/RSplit"] +margin_right = 1023.0 +margin_bottom = 555.0 +size_flags_horizontal = 3 +size_flags_vertical = 3 + +[node name="ControlBar" type="HBoxContainer" parent="layout/RSplit/CResults"] +margin_right = 1023.0 +margin_bottom = 35.0 +rect_min_size = Vector2( 0, 35 ) + +[node name="Light" type="Control" parent="layout/RSplit/CResults/ControlBar"] +visible = false +margin_right = 30.0 +margin_bottom = 35.0 +rect_min_size = Vector2( 30, 30 ) + +[node name="Passing" type="HBoxContainer" parent="layout/RSplit/CResults/ControlBar"] +visible = false +margin_left = 34.0 +margin_right = 107.0 +margin_bottom = 35.0 + +[node name="Sep" type="ColorRect" parent="layout/RSplit/CResults/ControlBar/Passing"] +margin_right = 2.0 +margin_bottom = 35.0 +rect_min_size = Vector2( 2, 0 ) + +[node name="label" type="Label" parent="layout/RSplit/CResults/ControlBar/Passing"] +margin_left = 6.0 +margin_top = 10.0 +margin_right = 54.0 +margin_bottom = 24.0 +text = "Passing" + +[node name="value" type="Label" parent="layout/RSplit/CResults/ControlBar/Passing"] +margin_left = 58.0 +margin_top = 10.0 +margin_right = 73.0 +margin_bottom = 24.0 +text = "---" + +[node name="Failing" type="HBoxContainer" parent="layout/RSplit/CResults/ControlBar"] +visible = false +margin_left = 34.0 +margin_right = 100.0 +margin_bottom = 35.0 + +[node name="Sep" type="ColorRect" parent="layout/RSplit/CResults/ControlBar/Failing"] +margin_right = 2.0 +margin_bottom = 35.0 +rect_min_size = Vector2( 2, 0 ) + +[node name="label" type="Label" parent="layout/RSplit/CResults/ControlBar/Failing"] +margin_left = 6.0 +margin_top = 10.0 +margin_right = 47.0 +margin_bottom = 24.0 +text = "Failing" + +[node name="value" type="Label" parent="layout/RSplit/CResults/ControlBar/Failing"] +margin_left = 51.0 +margin_top = 10.0 +margin_right = 66.0 +margin_bottom = 24.0 +text = "---" + +[node name="Pending" type="HBoxContainer" parent="layout/RSplit/CResults/ControlBar"] +visible = false +margin_left = 34.0 +margin_right = 110.0 +margin_bottom = 35.0 + +[node name="Sep" type="ColorRect" parent="layout/RSplit/CResults/ControlBar/Pending"] +margin_right = 2.0 +margin_bottom = 35.0 +rect_min_size = Vector2( 2, 0 ) + +[node name="label" type="Label" parent="layout/RSplit/CResults/ControlBar/Pending"] +margin_left = 6.0 +margin_top = 10.0 +margin_right = 57.0 +margin_bottom = 24.0 +text = "Pending" + +[node name="value" type="Label" parent="layout/RSplit/CResults/ControlBar/Pending"] +margin_left = 61.0 +margin_top = 10.0 +margin_right = 76.0 +margin_bottom = 24.0 +text = "---" + +[node name="Orphans" type="HBoxContainer" parent="layout/RSplit/CResults/ControlBar"] +visible = false +margin_left = 34.0 +margin_right = 110.0 +margin_bottom = 35.0 + +[node name="Sep" type="ColorRect" parent="layout/RSplit/CResults/ControlBar/Orphans"] +margin_right = 2.0 +margin_bottom = 35.0 +rect_min_size = Vector2( 2, 0 ) + +[node name="label" type="Label" parent="layout/RSplit/CResults/ControlBar/Orphans"] +margin_left = 6.0 +margin_top = 10.0 +margin_right = 57.0 +margin_bottom = 24.0 +text = "Orphans" + +[node name="value" type="Label" parent="layout/RSplit/CResults/ControlBar/Orphans"] +margin_left = 61.0 +margin_top = 10.0 +margin_right = 76.0 +margin_bottom = 24.0 +text = "---" + +[node name="Errors" type="HBoxContainer" parent="layout/RSplit/CResults/ControlBar"] +visible = false +margin_left = 34.0 +margin_right = 96.0 +margin_bottom = 35.0 + +[node name="Sep" type="ColorRect" parent="layout/RSplit/CResults/ControlBar/Errors"] +margin_right = 2.0 +margin_bottom = 35.0 +rect_min_size = Vector2( 2, 0 ) + +[node name="label" type="Label" parent="layout/RSplit/CResults/ControlBar/Errors"] +margin_left = 6.0 +margin_top = 10.0 +margin_right = 43.0 +margin_bottom = 24.0 +hint_tooltip = "The number of GUT errors generated. This does not include engine errors." +text = "Errors" + +[node name="value" type="Label" parent="layout/RSplit/CResults/ControlBar/Errors"] +margin_left = 47.0 +margin_top = 10.0 +margin_right = 62.0 +margin_bottom = 24.0 +text = "---" + +[node name="Warnings" type="HBoxContainer" parent="layout/RSplit/CResults/ControlBar"] +visible = false +margin_left = 34.0 +margin_right = 118.0 +margin_bottom = 35.0 + +[node name="Sep" type="ColorRect" parent="layout/RSplit/CResults/ControlBar/Warnings"] +margin_right = 2.0 +margin_bottom = 35.0 +rect_min_size = Vector2( 2, 0 ) + +[node name="label" type="Label" parent="layout/RSplit/CResults/ControlBar/Warnings"] +margin_left = 6.0 +margin_top = 10.0 +margin_right = 65.0 +margin_bottom = 24.0 +text = "Warnings" +__meta__ = { +"_editor_description_": "The number of GUT Warnings generated. This does not include engine warnings." +} + +[node name="value" type="Label" parent="layout/RSplit/CResults/ControlBar/Warnings"] +margin_left = 69.0 +margin_top = 10.0 +margin_right = 84.0 +margin_bottom = 24.0 +text = "---" + +[node name="CenterContainer" type="CenterContainer" parent="layout/RSplit/CResults/ControlBar"] +margin_right = 1023.0 +margin_bottom = 35.0 +size_flags_horizontal = 3 + +[node name="Tabs" type="HSplitContainer" parent="layout/RSplit/CResults"] +margin_top = 39.0 +margin_right = 1023.0 +margin_bottom = 555.0 +size_flags_horizontal = 3 +size_flags_vertical = 3 + +[node name="RunResults" parent="layout/RSplit/CResults/Tabs" instance=ExtResource( 5 )] +margin_right = 505.0 +margin_bottom = 516.0 +size_flags_horizontal = 3 +size_flags_vertical = 3 + +[node name="OutputText" parent="layout/RSplit/CResults/Tabs" instance=ExtResource( 6 )] +margin_left = 517.0 +margin_right = 1023.0 +margin_bottom = 516.0 + +[node name="BottomPanelShortcuts" parent="." instance=ExtResource( 2 )] +visible = false +anchor_left = -0.000517324 +anchor_top = 0.000882874 +anchor_right = 0.233483 +anchor_bottom = 0.328883 +margin_left = 10.0649 +margin_top = -173.752 +margin_right = 31.6969 +margin_bottom = -125.552 + +[connection signal="pressed" from="layout/ControlBar/RunAll" to="." method="_on_RunAll_pressed"] +[connection signal="run_tests" from="layout/ControlBar/RunAtCursor" to="." method="_on_RunAtCursor_run_tests"] +[connection signal="pressed" from="layout/ControlBar/RunResultsBtn" to="." method="_on_RunResultsBtn_pressed"] +[connection signal="pressed" from="layout/ControlBar/OutputBtn" to="." method="_on_OutputBtn_pressed"] +[connection signal="pressed" from="layout/ControlBar/Settings" to="." method="_on_Settings_pressed"] +[connection signal="pressed" from="layout/ControlBar/Shortcuts" to="." method="_on_Shortcuts_pressed"] +[connection signal="draw" from="layout/RSplit/CResults/ControlBar/Light" to="." method="_on_Light_draw"] +[connection signal="popup_hide" from="BottomPanelShortcuts" to="." method="_on_BottomPanelShortcuts_popup_hide"] diff --git a/demo/addons/gut/gui/GutRunner.gd b/demo/addons/gut/gui/GutRunner.gd new file mode 100644 index 0000000..608fc26 --- /dev/null +++ b/demo/addons/gut/gui/GutRunner.gd @@ -0,0 +1,95 @@ +extends Node2D + +var Gut = load('res://addons/gut/gut.gd') +var ResultExporter = load('res://addons/gut/result_exporter.gd') +var GutConfig = load('res://addons/gut/gut_config.gd') + +const RUNNER_JSON_PATH = 'res://.gut_editor_config.json' +const RESULT_FILE = 'user://.gut_editor.bbcode' +const RESULT_JSON = 'user://.gut_editor.json' + +var _gut_config = null +var _gut = null; +var _wrote_results = false +# Flag for when this is being used at the command line. Otherwise it is +# assumed this is being used by the panel and being launched with +# play_custom_scene +var _cmdln_mode = false + +onready var _gut_layer = $GutLayer + + +func _ready(): + if(_gut_config == null): + _gut_config = GutConfig.new() + _gut_config.load_panel_options(RUNNER_JSON_PATH) + + # The command line will call run_tests on its own. When used from the panel + # we have to kick off the tests ourselves b/c there's no way I know of to + # interact with the scene that was run via play_custom_scene. + if(!_cmdln_mode): + call_deferred('run_tests') + + +func run_tests(): + if(_gut == null): + _gut = Gut.new() + + _gut.set_add_children_to(self) + if(_gut_config.options.gut_on_top): + _gut_layer.add_child(_gut) + else: + add_child(_gut) + + if(!_cmdln_mode): + _gut.connect('tests_finished', self, '_on_tests_finished', + [_gut_config.options.should_exit, _gut_config.options.should_exit_on_success]) + + _gut_config.config_gut(_gut) + if(_gut_config.options.gut_on_top): + _gut.get_gui().goto_bottom_right_corner() + + var run_rest_of_scripts = _gut_config.options.unit_test_name == '' + _gut.test_scripts(run_rest_of_scripts) + + +func _write_results(): + var content = _gut.get_logger().get_gui_bbcode() + + var f = File.new() + var result = f.open(RESULT_FILE, f.WRITE) + if(result == OK): + f.store_string(content) + f.close() + else: + print('ERROR Could not save bbcode, result = ', result) + + var exporter = ResultExporter.new() + var f_result = exporter.write_json_file(_gut, RESULT_JSON) + _wrote_results = true + + +func _exit_tree(): + if(!_wrote_results and !_cmdln_mode): + _write_results() + + +func _on_tests_finished(should_exit, should_exit_on_success): + _write_results() + + if(should_exit): + get_tree().quit() + elif(should_exit_on_success and _gut.get_fail_count() == 0): + get_tree().quit() + + +func get_gut(): + if(_gut == null): + _gut = Gut.new() + return _gut + +func set_gut_config(which): + _gut_config = which + +func set_cmdln_mode(is_it): + _cmdln_mode = is_it diff --git a/demo/addons/gut/gui/GutRunner.tscn b/demo/addons/gut/gui/GutRunner.tscn new file mode 100644 index 0000000..077e411 --- /dev/null +++ b/demo/addons/gut/gui/GutRunner.tscn @@ -0,0 +1,9 @@ +[gd_scene load_steps=2 format=2] + +[ext_resource path="res://addons/gut/gui/GutRunner.gd" type="Script" id=1] + +[node name="GutRunner" type="Node2D"] +script = ExtResource( 1 ) + +[node name="GutLayer" type="CanvasLayer" parent="."] +layer = 128 diff --git a/demo/addons/gut/gui/GutSceneTheme.tres b/demo/addons/gut/gui/GutSceneTheme.tres new file mode 100644 index 0000000..565a6af --- /dev/null +++ b/demo/addons/gut/gui/GutSceneTheme.tres @@ -0,0 +1,11 @@ +[gd_resource type="Theme" load_steps=3 format=2] + +[sub_resource type="DynamicFontData" id=9] +font_path = "res://addons/gut/fonts/AnonymousPro-Regular.ttf" + +[sub_resource type="DynamicFont" id=10] +size = 14 +font_data = SubResource( 9 ) + +[resource] +default_font = SubResource( 10 ) diff --git a/demo/addons/gut/gui/OutputText.gd b/demo/addons/gut/gui/OutputText.gd new file mode 100644 index 0000000..e5cf2b6 --- /dev/null +++ b/demo/addons/gut/gui/OutputText.gd @@ -0,0 +1,291 @@ +extends VBoxContainer +tool + +class SearchResults: + const L = TextEdit.SEARCH_RESULT_LINE + const C = TextEdit.SEARCH_RESULT_COLUMN + + var positions = [] + var te = null + var _last_term = '' + + func _search_te(text, start_position, flags=0): + var start_pos = start_position + if(start_pos[L] < 0 or start_pos[L] > te.get_line_count()): + start_pos[L] = 0 + if(start_pos[C] < 0): + start_pos[L] = 0 + + var result = te.search(text, flags, start_pos[L], start_pos[C]) + if(result.size() == 2 and result[L] == start_position[L] and + result[C] == start_position[C] and text == _last_term): + if(flags == TextEdit.SEARCH_BACKWARDS): + result[C] -= 1 + else: + result[C] += 1 + result = _search_te(text, result, flags) + elif(result.size() == 2): + te.scroll_vertical = result[L] + te.select(result[L], result[C], result[L], result[C] + text.length()) + te.cursor_set_column(result[C]) + te.cursor_set_line(result[L]) + te.center_viewport_to_cursor() + + _last_term = text + te.center_viewport_to_cursor() + return result + + func _cursor_to_pos(): + var to_return = [0, 0] + to_return[L] = te.cursor_get_line() + to_return[C] = te.cursor_get_column() + return to_return + + func find_next(term): + return _search_te(term, _cursor_to_pos()) + + func find_prev(term): + var new_pos = _search_te(term, _cursor_to_pos(), TextEdit.SEARCH_BACKWARDS) + return new_pos + + func get_next_pos(): + pass + + func get_prev_pos(): + pass + + func clear(): + pass + + func find_all(text): + var c_pos = [0, 0] + var found = true + var last_pos = [0, 0] + positions.clear() + + while(found): + c_pos = te.search(text, 0, c_pos[L], c_pos[C]) + + if(c_pos.size() > 0 and + (c_pos[L] > last_pos[L] or + (c_pos[L] == last_pos[L] and c_pos[C] > last_pos[C]))): + positions.append([c_pos[L], c_pos[C]]) + c_pos[C] += 1 + last_pos = c_pos + else: + found = false + + + +onready var _ctrls = { + output = $Output, + + copy_button = $Toolbar/CopyButton, + use_colors = $Toolbar/UseColors, + clear_button = $Toolbar/ClearButton, + word_wrap = $Toolbar/WordWrap, + show_search = $Toolbar/ShowSearch, + + search_bar = { + bar = $Search, + search_term = $Search/SearchTerm, + } +} +var _sr = SearchResults.new() + +func _test_running_setup(): + _ctrls.use_colors.text = 'use colors' + _ctrls.show_search.text = 'search' + _ctrls.word_wrap.text = 'ww' + + set_all_fonts("CourierPrime") + set_font_size(20) + + load_file('user://.gut_editor.bbcode') + + +func _ready(): + _sr.te = _ctrls.output + _ctrls.use_colors.icon = get_icon('RichTextEffect', 'EditorIcons') + _ctrls.show_search.icon = get_icon('Search', 'EditorIcons') + _ctrls.word_wrap.icon = get_icon('Loop', 'EditorIcons') + + _setup_colors() + if(get_parent() == get_tree().root): + _test_running_setup() + + +# ------------------ +# Private +# ------------------ +func _setup_colors(): + _ctrls.output.clear_colors() + var keywords = [ + ['Failed', Color.red], + ['Passed', Color.green], + ['Pending', Color.yellow], + ['Orphans', Color.yellow], + ['WARNING', Color.yellow], + ['ERROR', Color.red] + ] + + for keyword in keywords: + _ctrls.output.add_keyword_color(keyword[0], keyword[1]) + + var f_color = _ctrls.output.get_color("font_color") + _ctrls.output.add_color_override("font_color_readonly", f_color) + _ctrls.output.add_color_override("function_color", f_color) + _ctrls.output.add_color_override("member_variable_color", f_color) + _ctrls.output.update() + + +func _set_font(font_name, custom_name): + var rtl = _ctrls.output + if(font_name == null): + rtl.set('custom_fonts/' + custom_name, null) + else: + var dyn_font = DynamicFont.new() + var font_data = DynamicFontData.new() + font_data.font_path = 'res://addons/gut/fonts/' + font_name + '.ttf' + font_data.antialiased = true + dyn_font.font_data = font_data + rtl.set('custom_fonts/' + custom_name, dyn_font) + + +# ------------------ +# Events +# ------------------ +func _on_CopyButton_pressed(): + copy_to_clipboard() + + +func _on_UseColors_pressed(): + _ctrls.output.syntax_highlighting = _ctrls.use_colors.pressed + + +func _on_ClearButton_pressed(): + clear() + + +func _on_ShowSearch_pressed(): + show_search(_ctrls.show_search.pressed) + + +func _on_SearchTerm_focus_entered(): + _ctrls.search_bar.search_term.call_deferred('select_all') + +func _on_SearchNext_pressed(): + _sr.find_next(_ctrls.search_bar.search_term.text) + + +func _on_SearchPrev_pressed(): + _sr.find_prev(_ctrls.search_bar.search_term.text) + + +func _on_SearchTerm_text_changed(new_text): + if(new_text == ''): + _ctrls.output.deselect() + else: + _sr.find_next(new_text) + + +func _on_SearchTerm_text_entered(new_text): + if(Input.is_physical_key_pressed(KEY_SHIFT)): + _sr.find_prev(new_text) + else: + _sr.find_next(new_text) + + +func _on_SearchTerm_gui_input(event): + if(event is InputEventKey and !event.pressed and event.scancode == KEY_ESCAPE): + show_search(false) + +func _on_WordWrap_pressed(): + _ctrls.output.wrap_enabled = _ctrls.word_wrap.pressed + _ctrls.output.update() + +# ------------------ +# Public +# ------------------ +func show_search(should): + _ctrls.search_bar.bar.visible = should + if(should): + _ctrls.search_bar.search_term.grab_focus() + _ctrls.search_bar.search_term.select_all() + _ctrls.show_search.pressed = should + + +func search(text, start_pos, highlight=true): + return _sr.find_next(text) + + +func copy_to_clipboard(): + var selected = _ctrls.output.get_selection_text() + if(selected != ''): + OS.clipboard = selected + else: + OS.clipboard = _ctrls.output.text + + +func clear(): + _ctrls.output.text = '' + + +func set_all_fonts(base_name): + if(base_name == 'Default'): + _set_font(null, 'font') +# _set_font(null, 'normal_font') +# _set_font(null, 'bold_font') +# _set_font(null, 'italics_font') +# _set_font(null, 'bold_italics_font') + else: + _set_font(base_name + '-Regular', 'font') +# _set_font(base_name + '-Regular', 'normal_font') +# _set_font(base_name + '-Bold', 'bold_font') +# _set_font(base_name + '-Italic', 'italics_font') +# _set_font(base_name + '-BoldItalic', 'bold_italics_font') + + +func set_font_size(new_size): + var rtl = _ctrls.output + if(rtl.get('custom_fonts/font') != null): + rtl.get('custom_fonts/font').size = new_size +# rtl.get('custom_fonts/bold_italics_font').size = new_size +# rtl.get('custom_fonts/bold_font').size = new_size +# rtl.get('custom_fonts/italics_font').size = new_size +# rtl.get('custom_fonts/normal_font').size = new_size + + +func set_use_colors(value): + pass + + +func get_use_colors(): + return false; + + +func get_rich_text_edit(): + return _ctrls.output + + +func load_file(path): + var f = File.new() + var result = f.open(path, f.READ) + if(result != OK): + return + + var t = f.get_as_text() + f.close() + _ctrls.output.text = t + _ctrls.output.scroll_vertical = _ctrls.output.get_line_count() + _ctrls.output.set_deferred('scroll_vertical', _ctrls.output.get_line_count()) + + +func add_text(text): + if(is_inside_tree()): + _ctrls.output.text += text + + +func scroll_to_line(line): + _ctrls.output.scroll_vertical = line + _ctrls.output.cursor_set_line(line) diff --git a/demo/addons/gut/gui/OutputText.tscn b/demo/addons/gut/gui/OutputText.tscn new file mode 100644 index 0000000..d7c693a --- /dev/null +++ b/demo/addons/gut/gui/OutputText.tscn @@ -0,0 +1,123 @@ +[gd_scene load_steps=4 format=2] + +[ext_resource path="res://addons/gut/gui/OutputText.gd" type="Script" id=1] + +[sub_resource type="Image" id=3] +data = { +"data": PoolByteArray( 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ), +"format": "LumAlpha8", +"height": 16, +"mipmaps": false, +"width": 16 +} + +[sub_resource type="ImageTexture" id=2] +flags = 4 +flags = 4 +image = SubResource( 3 ) +size = Vector2( 16, 16 ) + +[node name="OutputText" type="VBoxContainer"] +margin_right = 862.0 +margin_bottom = 523.0 +size_flags_horizontal = 3 +size_flags_vertical = 3 +script = ExtResource( 1 ) + +[node name="Toolbar" type="HBoxContainer" parent="."] +margin_right = 862.0 +margin_bottom = 24.0 +size_flags_horizontal = 3 + +[node name="ShowSearch" type="ToolButton" parent="Toolbar"] +margin_right = 28.0 +margin_bottom = 24.0 +toggle_mode = true +icon = SubResource( 2 ) + +[node name="UseColors" type="ToolButton" parent="Toolbar"] +margin_left = 32.0 +margin_right = 60.0 +margin_bottom = 24.0 +hint_tooltip = "Colorize output. + It's not the same as everywhere else (long story), + but it is better than nothing." +toggle_mode = true +pressed = true +icon = SubResource( 2 ) + +[node name="WordWrap" type="ToolButton" parent="Toolbar"] +margin_left = 64.0 +margin_right = 92.0 +margin_bottom = 24.0 +hint_tooltip = "Word wrap" +toggle_mode = true +icon = SubResource( 2 ) + +[node name="CenterContainer" type="CenterContainer" parent="Toolbar"] +margin_left = 96.0 +margin_right = 743.0 +margin_bottom = 24.0 +size_flags_horizontal = 3 + +[node name="CopyButton" type="Button" parent="Toolbar"] +margin_left = 747.0 +margin_right = 798.0 +margin_bottom = 24.0 +hint_tooltip = "Copy to clipboard" +text = " Copy " + +[node name="ClearButton" type="Button" parent="Toolbar"] +margin_left = 802.0 +margin_right = 862.0 +margin_bottom = 24.0 +text = " Clear " + +[node name="Output" type="TextEdit" parent="."] +margin_top = 28.0 +margin_right = 862.0 +margin_bottom = 523.0 +size_flags_horizontal = 3 +size_flags_vertical = 3 +readonly = true +highlight_current_line = true +syntax_highlighting = true +show_line_numbers = true +smooth_scrolling = true + +[node name="Search" type="HBoxContainer" parent="."] +visible = false +margin_top = 499.0 +margin_right = 862.0 +margin_bottom = 523.0 + +[node name="SearchTerm" type="LineEdit" parent="Search"] +margin_right = 804.0 +margin_bottom = 24.0 +size_flags_horizontal = 3 + +[node name="SearchNext" type="Button" parent="Search"] +margin_left = 808.0 +margin_right = 862.0 +margin_bottom = 24.0 +hint_tooltip = "Find next (enter)" +text = "Next" + +[node name="SearchPrev" type="Button" parent="Search"] +margin_left = 808.0 +margin_right = 820.0 +margin_bottom = 20.0 +hint_tooltip = "Find previous (shift + enter)" +text = "Prev" + +[connection signal="pressed" from="Toolbar/ShowSearch" to="." method="_on_ShowSearch_pressed"] +[connection signal="pressed" from="Toolbar/UseColors" to="." method="_on_UseColors_pressed"] +[connection signal="pressed" from="Toolbar/WordWrap" to="." method="_on_WordWrap_pressed"] +[connection signal="pressed" from="Toolbar/CopyButton" to="." method="_on_CopyButton_pressed"] +[connection signal="pressed" from="Toolbar/ClearButton" to="." method="_on_ClearButton_pressed"] +[connection signal="focus_entered" from="Search/SearchTerm" to="." method="_on_SearchTerm_focus_entered"] +[connection signal="gui_input" from="Search/SearchTerm" to="." method="_on_SearchTerm_gui_input"] +[connection signal="text_changed" from="Search/SearchTerm" to="." method="_on_SearchTerm_text_changed"] +[connection signal="text_entered" from="Search/SearchTerm" to="." method="_on_SearchTerm_text_entered"] +[connection signal="pressed" from="Search/SearchNext" to="." method="_on_SearchNext_pressed"] +[connection signal="pressed" from="Search/SearchPrev" to="." method="_on_SearchPrev_pressed"] diff --git a/demo/addons/gut/gui/RunAtCursor.gd b/demo/addons/gut/gui/RunAtCursor.gd new file mode 100644 index 0000000..dc83c3e --- /dev/null +++ b/demo/addons/gut/gui/RunAtCursor.gd @@ -0,0 +1,153 @@ +tool +extends Control + + +var ScriptTextEditors = load('res://addons/gut/gui/script_text_editor_controls.gd') + +onready var _ctrls = { + btn_script = $HBox/BtnRunScript, + btn_inner = $HBox/BtnRunInnerClass, + btn_method = $HBox/BtnRunMethod, + lbl_none = $HBox/LblNoneSelected, + arrow_1 = $HBox/Arrow1, + arrow_2 = $HBox/Arrow2 +} + +var _editors = null +var _cur_editor = null +var _last_line = -1 +var _cur_script_path = null +var _last_info = null + +signal run_tests(what) + + +func _ready(): + _ctrls.lbl_none.visible = true + _ctrls.btn_script.visible = false + _ctrls.btn_inner.visible = false + _ctrls.btn_method.visible = false + +# ---------------- +# Private +# ---------------- +func _set_editor(which): + _last_line = -1 + if(_cur_editor != null and _cur_editor.get_ref()): + _cur_editor.get_ref().disconnect('cursor_changed', self, '_on_cursor_changed') + + if(which != null): + _cur_editor = weakref(which) + which.connect('cursor_changed', self, '_on_cursor_changed', [which]) + + _last_line = which.cursor_get_line() + _last_info = _editors.get_line_info() + _update_buttons(_last_info) + + +func _update_buttons(info): + _ctrls.lbl_none.visible = _cur_script_path == null + _ctrls.btn_script.visible = _cur_script_path != null + + _ctrls.btn_inner.visible = info.inner_class != null + _ctrls.arrow_1.visible = info.inner_class != null + _ctrls.btn_inner.text = str(info.inner_class) + _ctrls.btn_inner.hint_tooltip = str("Run all tests in Inner-Test-Class ", info.inner_class) + + _ctrls.btn_method.visible = info.test_method != null + _ctrls.arrow_2.visible = info.test_method != null + _ctrls.btn_method.text = str(info.test_method) + _ctrls.btn_method.hint_tooltip = str("Run test ", info.test_method) + + # The button's new size won't take effect until the next frame. + # This appears to be what was causing the button to not be clickable the + # first time. + call_deferred("_update_rect_size") + + +func _update_rect_size(): + rect_min_size.x = _ctrls.btn_method.rect_size.x + _ctrls.btn_method.rect_position.x + +# ---------------- +# Events +# ---------------- +func _on_cursor_changed(which): + if(which.cursor_get_line() != _last_line): + _last_line = which.cursor_get_line() + _last_info = _editors.get_line_info() + _update_buttons(_last_info) + + +func _on_BtnRunScript_pressed(): + var info = _last_info.duplicate() + info.script = _cur_script_path.get_file() + info.inner_class = null + info.test_method = null + emit_signal("run_tests", info) + + +func _on_BtnRunInnerClass_pressed(): + var info = _last_info.duplicate() + info.script = _cur_script_path.get_file() + info.test_method = null + emit_signal("run_tests", info) + + +func _on_BtnRunMethod_pressed(): + var info = _last_info.duplicate() + info.script = _cur_script_path.get_file() + emit_signal("run_tests", info) + + +# ---------------- +# Public +# ---------------- +func set_script_text_editors(value): + _editors = value + + +func activate_for_script(path): + _ctrls.btn_script.visible = true + _ctrls.btn_script.text = path.get_file() + _ctrls.btn_script.hint_tooltip = str("Run all tests in script ", path) + _cur_script_path = path + _editors.refresh() + _set_editor(_editors.get_current_text_edit()) + + +func get_script_button(): + return _ctrls.btn_script + + +func get_inner_button(): + return _ctrls.btn_inner + + +func get_test_button(): + return _ctrls.btn_method + + +# not used, thought was configurable but it's just the script prefix +func set_method_prefix(value): + _editors.set_method_prefix(value) + + +# not used, thought was configurable but it's just the script prefix +func set_inner_class_prefix(value): + _editors.set_inner_class_prefix(value) + + +# Mashed this function in here b/c it has _editors. Probably should be +# somewhere else (possibly in script_text_editor_controls). +func search_current_editor_for_text(txt): + var te = _editors.get_current_text_edit() + var result = te.search(txt, 0, 0, 0) + var to_return = -1 + + if result.size() > 0: + to_return = result[TextEdit.SEARCH_RESULT_LINE] + + return to_return + + + diff --git a/demo/addons/gut/gui/RunAtCursor.tscn b/demo/addons/gut/gui/RunAtCursor.tscn new file mode 100644 index 0000000..b4662df --- /dev/null +++ b/demo/addons/gut/gui/RunAtCursor.tscn @@ -0,0 +1,80 @@ +[gd_scene load_steps=4 format=2] + +[ext_resource path="res://addons/gut/gui/RunAtCursor.gd" type="Script" id=1] +[ext_resource path="res://addons/gut/gui/play.png" type="Texture" id=2] +[ext_resource path="res://addons/gut/gui/arrow.png" type="Texture" id=3] + +[node name="RunAtCursor" type="Control"] +anchor_right = 1.0 +anchor_bottom = 1.0 +margin_right = 1.0 +margin_bottom = -527.0 +size_flags_horizontal = 3 +size_flags_vertical = 3 +script = ExtResource( 1 ) +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="HBox" type="HBoxContainer" parent="."] +anchor_right = 1.0 +anchor_bottom = 1.0 +size_flags_horizontal = 3 +size_flags_vertical = 3 +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="LblNoneSelected" type="Label" parent="HBox"] +margin_top = 29.0 +margin_right = 50.0 +margin_bottom = 43.0 +text = "" + +[node name="BtnRunScript" type="Button" parent="HBox"] +visible = false +margin_left = 54.0 +margin_right = 140.0 +margin_bottom = 73.0 +text = "