Update Gut for Godot 4

Copied from Gut repo godot_4 branch commit:
ba19a4c1b6f88160641a67a39729144046c6391f
This commit is contained in:
Leroy Hopson 2023-01-08 08:26:17 +13:00
parent 44f7e3801c
commit b5d3c6c9a5
No known key found for this signature in database
GPG key ID: D2747312A6DB51AA
105 changed files with 8545 additions and 5750 deletions

View file

@ -0,0 +1,82 @@
@tool
extends Window
@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', _on_edit_start.bind(sc_button))
sc_button.connect('end_edit', _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))

View file

@ -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="Window"]
visible = true
anchor_right = 0.234
anchor_bottom = 0.328
offset_right = 195.384
offset_bottom = 62.2
custom_minimum_size = Vector2( 435, 305 )
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
offset_left = 5.0
offset_right = -5.0
offset_bottom = 2.0
__meta__ = {
"_edit_use_anchors_": false
}
[node name="TopPad" type="CenterContainer" parent="Layout"]
offset_right = 425.0
offset_bottom = 5.0
custom_minimum_size = Vector2( 0, 5 )
[node name="Label2" type="Label" parent="Layout"]
offset_top = 9.0
offset_right = 425.0
offset_bottom = 29.0
custom_minimum_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"]
offset_top = 33.0
offset_right = 425.0
offset_bottom = 58.0
[node name="Label" type="Label" parent="Layout/CPanelButton"]
offset_right = 138.0
offset_bottom = 25.0
custom_minimum_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
offset_left = 142.0
offset_right = 425.0
offset_bottom = 25.0
size_flags_horizontal = 3
[node name="GutPanelPad" type="CenterContainer" parent="Layout"]
offset_top = 62.0
offset_right = 425.0
offset_bottom = 67.0
custom_minimum_size = Vector2( 0, 5 )
[node name="Label" type="Label" parent="Layout"]
offset_top = 71.0
offset_right = 425.0
offset_bottom = 91.0
custom_minimum_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"]
offset_top = 95.0
offset_right = 425.0
offset_bottom = 100.0
custom_minimum_size = Vector2( 0, 5 )
[node name="CRunAll" type="HBoxContainer" parent="Layout"]
offset_top = 104.0
offset_right = 425.0
offset_bottom = 129.0
[node name="Label" type="Label" parent="Layout/CRunAll"]
offset_right = 50.0
offset_bottom = 25.0
custom_minimum_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
offset_left = 54.0
offset_right = 425.0
offset_bottom = 25.0
size_flags_horizontal = 3
[node name="CRunCurrentScript" type="HBoxContainer" parent="Layout"]
offset_top = 133.0
offset_right = 425.0
offset_bottom = 158.0
[node name="Label" type="Label" parent="Layout/CRunCurrentScript"]
offset_right = 115.0
offset_bottom = 25.0
custom_minimum_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
offset_left = 119.0
offset_right = 425.0
offset_bottom = 25.0
size_flags_horizontal = 3
[node name="CRunCurrentInner" type="HBoxContainer" parent="Layout"]
offset_top = 162.0
offset_right = 425.0
offset_bottom = 187.0
[node name="Label" type="Label" parent="Layout/CRunCurrentInner"]
offset_right = 150.0
offset_bottom = 25.0
custom_minimum_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
offset_left = 154.0
offset_right = 425.0
offset_bottom = 25.0
size_flags_horizontal = 3
[node name="CRunCurrentTest" type="HBoxContainer" parent="Layout"]
offset_top = 191.0
offset_right = 425.0
offset_bottom = 216.0
[node name="Label" type="Label" parent="Layout/CRunCurrentTest"]
offset_right = 106.0
offset_bottom = 25.0
custom_minimum_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
offset_left = 110.0
offset_right = 425.0
offset_bottom = 25.0
size_flags_horizontal = 3
[node name="CenterContainer2" type="CenterContainer" parent="Layout"]
offset_top = 220.0
offset_right = 425.0
offset_bottom = 241.0
custom_minimum_size = Vector2( 0, 5 )
size_flags_horizontal = 3
size_flags_vertical = 3
[node name="ShiftDisclaimer" type="Label" parent="Layout"]
offset_top = 245.0
offset_right = 425.0
offset_bottom = 259.0
text = "\"Shift\" cannot be the only modifier for a shortcut."
align = 2
autowrap = true
[node name="HBoxContainer" type="HBoxContainer" parent="Layout"]
offset_top = 263.0
offset_right = 425.0
offset_bottom = 293.0
[node name="CenterContainer" type="CenterContainer" parent="Layout/HBoxContainer"]
offset_right = 361.0
offset_bottom = 30.0
size_flags_horizontal = 3
size_flags_vertical = 3
[node name="Hide" type="Button" parent="Layout/HBoxContainer"]
offset_left = 365.0
offset_right = 425.0
offset_bottom = 30.0
custom_minimum_size = Vector2( 60, 30 )
text = "Close"
[node name="BottomPad" type="CenterContainer" parent="Layout"]
offset_top = 297.0
offset_right = 425.0
offset_bottom = 307.0
custom_minimum_size = Vector2( 0, 10 )
size_flags_horizontal = 3
[connection signal="pressed" from="Layout/HBoxContainer/Hide" to="." method="_on_Hide_pressed"]

View file

@ -0,0 +1,367 @@
@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/TabBar/OutputText.get_rich_text_edit(),
output_ctrl = $layout/RSplit/CResults/TabBar/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/Light3D,
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/TabBar/RunResults
}
func _init():
_gut_config.load_panel_options(RUNNER_JSON_PATH)
func _ready():
_ctrls.results.bar.connect('draw', _on_results_bar_draw.bind(_ctrls.results.bar))
hide_settings(!_ctrls.settings_button.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_theme_icon('Shortcut', 'EditorIcons')
_ctrls.settings_button.icon = get_theme_icon('Tools', 'EditorIcons')
_ctrls.run_results_button.icon = get_theme_icon('AnimationTrackGroup', 'EditorIcons') # Tree
_ctrls.output_button.icon = get_theme_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.button_pressed
_gut_config.options.panel_options.hide_result_tree = !_ctrls.run_results_button.button_pressed
_gut_config.options.panel_options.hide_output_text = !_ctrls.output_button.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.size), Color(0, 0, 0, .2))
func _on_Light_draw():
var l = _ctrls.light
l.draw_circle(Vector2(l.size.x / 2, l.size.y / 2), l.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_bottom_panel_shortcuts_visibility_changed():
_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.button_pressed)
_save_config()
func _on_OutputBtn_pressed():
hide_output_text(!_ctrls.output_button.button_pressed)
_save_config()
func _on_RunResultsBtn_pressed():
hide_result_tree(! _ctrls.run_results_button.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.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.button_pressed = !should
func hide_output_text(should):
$layout/RSplit/CResults/TabBar/OutputText.visible = !should
_ctrls.output_button.button_pressed = !should
func load_result_output():
_ctrls.output_ctrl.load_file(RESULT_FILE)
var summary = get_file_as_text(RESULT_JSON)
var test_json_conv = JSON.new()
if (test_json_conv.parse(summary) != OK):
return
var results = test_json_conv.get_data()
_ctrls.run_results.load_json_results(results)
var summary_json = results['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.queue_redraw()
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",Callable(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 = FileAccess.open(path, FileAccess.WRITE)
if(f != null):
f.store_string(content)
f = null;
return FileAccess.get_open_error()
# ------------------------------------------------------------------------------
# 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 = FileAccess.open(path, FileAccess.READ)
if(f != null):
to_return = f.get_as_text()
f = null
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

View file

@ -0,0 +1,339 @@
[gd_scene load_steps=10 format=3 uid="uid://b3bostcslstem"]
[ext_resource type="Script" path="res://addons/gut/gui/GutBottomPanel.gd" id="1"]
[ext_resource type="PackedScene" path="res://addons/gut/gui/BottomPanelShortcuts.tscn" id="2"]
[ext_resource type="PackedScene" path="res://addons/gut/gui/RunAtCursor.tscn" id="3"]
[ext_resource type="Texture2D" path="res://addons/gut/gui/play.png" id="4"]
[ext_resource type="PackedScene" path="res://addons/gut/gui/RunResults.tscn" id="5"]
[ext_resource type="PackedScene" path="res://addons/gut/gui/OutputText.tscn" id="6"]
[sub_resource type="Shortcut" id="9"]
[sub_resource type="Image" id="Image_r56ab"]
data = {
"data": PackedByteArray(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 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"]
image = SubResource("Image_r56ab")
[node name="GutBottomPanel" type="Control"]
custom_minimum_size = Vector2(250, 250)
anchor_left = -0.0025866
anchor_top = -0.00176575
anchor_right = 0.997413
anchor_bottom = 0.998234
offset_left = 2.64868
offset_top = 1.05945
offset_right = 2.64862
offset_bottom = 1.05945
script = ExtResource("1")
[node name="layout" type="VBoxContainer" parent="."]
anchor_right = 1.0
anchor_bottom = 1.0
[node name="ControlBar" type="HBoxContainer" parent="layout"]
offset_right = 1023.0
offset_bottom = 31.0
[node name="RunAll" type="Button" parent="layout/ControlBar"]
offset_right = 85.0
offset_bottom = 31.0
size_flags_vertical = 11
hint_tooltip = "Run all test scripts in the suite."
shortcut = SubResource("9")
text = "Run All"
icon = ExtResource("4")
[node name="Label" type="Label" parent="layout/ControlBar"]
offset_left = 89.0
offset_top = 2.0
offset_right = 162.0
offset_bottom = 28.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
offset_left = 166.0
offset_right = 532.0
offset_bottom = 31.0
[node name="CenterContainer2" type="CenterContainer" parent="layout/ControlBar"]
offset_left = 536.0
offset_right = 903.0
offset_bottom = 31.0
size_flags_horizontal = 3
[node name="Sep1" type="ColorRect" parent="layout/ControlBar"]
offset_left = 907.0
offset_right = 907.0
offset_bottom = 31.0
[node name="RunResultsBtn" type="Button" parent="layout/ControlBar"]
offset_left = 911.0
offset_right = 935.0
offset_bottom = 31.0
hint_tooltip = "Show/Hide Results Tree Panel."
toggle_mode = true
icon = SubResource("2")
[node name="OutputBtn" type="Button" parent="layout/ControlBar"]
offset_left = 939.0
offset_right = 963.0
offset_bottom = 31.0
hint_tooltip = "Show/Hide Output Panel."
toggle_mode = true
icon = SubResource("2")
[node name="Settings" type="Button" parent="layout/ControlBar"]
offset_left = 967.0
offset_right = 991.0
offset_bottom = 31.0
hint_tooltip = "Show/Hide Settings Panel."
toggle_mode = true
icon = SubResource("2")
[node name="Sep2" type="ColorRect" parent="layout/ControlBar"]
offset_left = 995.0
offset_right = 995.0
offset_bottom = 31.0
[node name="Shortcuts" type="Button" parent="layout/ControlBar"]
offset_left = 999.0
offset_right = 1023.0
offset_bottom = 31.0
size_flags_vertical = 11
hint_tooltip = "Set shortcuts for GUT buttons. Shortcuts do not work when the GUT panel is not visible."
icon = SubResource("2")
[node name="RSplit" type="HSplitContainer" parent="layout"]
offset_top = 35.0
offset_right = 1023.0
offset_bottom = 599.0
size_flags_horizontal = 3
size_flags_vertical = 3
collapsed = true
[node name="sc" type="ScrollContainer" parent="layout/RSplit"]
visible = false
offset_left = 593.0
offset_right = 1093.0
offset_bottom = 555.0
size_flags_vertical = 3
[node name="Settings" type="VBoxContainer" parent="layout/RSplit/sc"]
offset_right = 500.0
offset_bottom = 555.0
size_flags_horizontal = 3
size_flags_vertical = 3
[node name="CResults" type="VBoxContainer" parent="layout/RSplit"]
offset_right = 1023.0
offset_bottom = 564.0
size_flags_horizontal = 3
size_flags_vertical = 3
[node name="ControlBar" type="HBoxContainer" parent="layout/RSplit/CResults"]
offset_right = 1023.0
[node name="Light3D" type="Control" parent="layout/RSplit/CResults/ControlBar"]
visible = false
offset_right = 30.0
offset_bottom = 35.0
[node name="Passing" type="HBoxContainer" parent="layout/RSplit/CResults/ControlBar"]
visible = false
offset_left = 34.0
offset_right = 107.0
offset_bottom = 35.0
[node name="Sep" type="ColorRect" parent="layout/RSplit/CResults/ControlBar/Passing"]
offset_right = 2.0
offset_bottom = 35.0
[node name="label" type="Label" parent="layout/RSplit/CResults/ControlBar/Passing"]
offset_left = 6.0
offset_top = 10.0
offset_right = 54.0
offset_bottom = 24.0
text = "Passing"
[node name="value" type="Label" parent="layout/RSplit/CResults/ControlBar/Passing"]
offset_left = 58.0
offset_top = 10.0
offset_right = 73.0
offset_bottom = 24.0
text = "---"
[node name="Failing" type="HBoxContainer" parent="layout/RSplit/CResults/ControlBar"]
visible = false
offset_left = 34.0
offset_right = 100.0
offset_bottom = 35.0
[node name="Sep" type="ColorRect" parent="layout/RSplit/CResults/ControlBar/Failing"]
offset_right = 2.0
offset_bottom = 35.0
[node name="label" type="Label" parent="layout/RSplit/CResults/ControlBar/Failing"]
offset_left = 6.0
offset_top = 10.0
offset_right = 47.0
offset_bottom = 24.0
text = "Failing"
[node name="value" type="Label" parent="layout/RSplit/CResults/ControlBar/Failing"]
offset_left = 51.0
offset_top = 10.0
offset_right = 66.0
offset_bottom = 24.0
text = "---"
[node name="Pending" type="HBoxContainer" parent="layout/RSplit/CResults/ControlBar"]
visible = false
offset_left = 34.0
offset_right = 110.0
offset_bottom = 35.0
[node name="Sep" type="ColorRect" parent="layout/RSplit/CResults/ControlBar/Pending"]
offset_right = 2.0
offset_bottom = 35.0
[node name="label" type="Label" parent="layout/RSplit/CResults/ControlBar/Pending"]
offset_left = 6.0
offset_top = 10.0
offset_right = 57.0
offset_bottom = 24.0
text = "Pending"
[node name="value" type="Label" parent="layout/RSplit/CResults/ControlBar/Pending"]
offset_left = 61.0
offset_top = 10.0
offset_right = 76.0
offset_bottom = 24.0
text = "---"
[node name="Orphans" type="HBoxContainer" parent="layout/RSplit/CResults/ControlBar"]
visible = false
offset_left = 34.0
offset_right = 110.0
offset_bottom = 35.0
[node name="Sep" type="ColorRect" parent="layout/RSplit/CResults/ControlBar/Orphans"]
offset_right = 2.0
offset_bottom = 35.0
[node name="label" type="Label" parent="layout/RSplit/CResults/ControlBar/Orphans"]
offset_left = 6.0
offset_top = 10.0
offset_right = 57.0
offset_bottom = 24.0
text = "Orphans"
[node name="value" type="Label" parent="layout/RSplit/CResults/ControlBar/Orphans"]
offset_left = 61.0
offset_top = 10.0
offset_right = 76.0
offset_bottom = 24.0
text = "---"
[node name="Errors" type="HBoxContainer" parent="layout/RSplit/CResults/ControlBar"]
visible = false
offset_left = 34.0
offset_right = 96.0
offset_bottom = 35.0
[node name="Sep" type="ColorRect" parent="layout/RSplit/CResults/ControlBar/Errors"]
offset_right = 2.0
offset_bottom = 35.0
[node name="label" type="Label" parent="layout/RSplit/CResults/ControlBar/Errors"]
offset_left = 6.0
offset_top = 10.0
offset_right = 43.0
offset_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"]
offset_left = 47.0
offset_top = 10.0
offset_right = 62.0
offset_bottom = 24.0
text = "---"
[node name="Warnings" type="HBoxContainer" parent="layout/RSplit/CResults/ControlBar"]
visible = false
offset_left = 34.0
offset_right = 118.0
offset_bottom = 35.0
[node name="Sep" type="ColorRect" parent="layout/RSplit/CResults/ControlBar/Warnings"]
offset_right = 2.0
offset_bottom = 35.0
[node name="label" type="Label" parent="layout/RSplit/CResults/ControlBar/Warnings"]
offset_left = 6.0
offset_top = 10.0
offset_right = 65.0
offset_bottom = 24.0
text = "Warnings"
[node name="value" type="Label" parent="layout/RSplit/CResults/ControlBar/Warnings"]
offset_left = 69.0
offset_top = 10.0
offset_right = 84.0
offset_bottom = 24.0
text = "---"
[node name="CenterContainer" type="CenterContainer" parent="layout/RSplit/CResults/ControlBar"]
offset_right = 1023.0
size_flags_horizontal = 3
[node name="TabBar" type="HSplitContainer" parent="layout/RSplit/CResults"]
offset_top = 4.0
offset_right = 1023.0
offset_bottom = 564.0
size_flags_horizontal = 3
size_flags_vertical = 3
[node name="RunResults" parent="layout/RSplit/CResults/TabBar" instance=ExtResource("5")]
offset_right = 505.0
offset_bottom = 560.0
size_flags_horizontal = 3
size_flags_vertical = 3
[node name="OutputText" parent="layout/RSplit/CResults/TabBar" instance=ExtResource("6")]
offset_left = 517.0
offset_right = 1023.0
offset_bottom = 560.0
[node name="BottomPanelShortcuts" parent="." instance=ExtResource("2")]
visible = false
[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/Light3D" to="." method="_on_Light_draw"]
[connection signal="visibility_changed" from="BottomPanelShortcuts" to="." method="_on_bottom_panel_shortcuts_visibility_changed"]

View file

@ -1,15 +1,15 @@
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")
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"
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 _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
@ -17,80 +17,100 @@ var _wrote_results = false
var _cmdln_mode = false
@onready var _gut_layer = $GutLayer
@onready var _gui = $GutLayer/GutScene
var auto_run_tests = true
func _ready():
if _gut_config == null:
if(_gut_config == null):
_gut_config = GutConfig.new()
_gut_config.load_options(RUNNER_JSON_PATH)
_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")
if(!_cmdln_mode and auto_run_tests):
call_deferred('run_tests')
func run_tests():
if _gut == null:
_gut = Gut.new()
func run_tests(show_gui=true):
_gut.set_add_children_to(self)
if _gut_config.options.gut_on_top:
if(_gut == null):
get_gut()
_setup_gui(show_gui)
_gut.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]
)
if(!_cmdln_mode):
_gut.end_run.connect(_on_tests_finished.bind(_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 == ''
var run_rest_of_scripts = _gut_config.options.unit_test_name == ""
_gut.test_scripts(run_rest_of_scripts)
func _write_results():
# bbcode_text appears to be empty. I'm not 100% sure why. Until that is
# figured out we have to just get the text which stinks.
var content = _gut.get_gui().get_text_box().text
func _setup_gui(show_gui):
if(show_gui):
_gui.gut = _gut
var printer = _gut.logger.get_printer('gui')
printer.set_textbox(_gui.get_textbox())
else:
_gut.logger.disable_printer('gui', true)
_gui.visible = false
var f = File.new()
var result = f.open(RESULT_FILE, f.WRITE)
if result == OK:
var opts = _gut_config.options
_gui.set_font_size(opts.font_size)
_gui.set_font(opts.font_name)
if(opts.font_color != null and opts.font_color.is_valid_html_color()):
_gui.set_default_font_color(Color(opts.font_color))
if(opts.background_color != null and opts.background_color.is_valid_html_color()):
_gui.set_background_color(Color(opts.background_color))
#_tester.set_modulate(Color(1.0, 1.0, 1.0, min(1.0, float(opts.opacity) / 100)))
# if(opts.should_maximize):
# _tester.maximize()
#if(opts.compact_mode):
# _tester.get_gui().compact_mode(true)
func _write_results():
var content = _gui.get_textbox().text #_gut.logger.get_gui_bbcode()
var f = FileAccess.open(RESULT_FILE, FileAccess.WRITE)
if(f != null):
f.store_string(content)
f.close()
else:
print("ERROR Could not save bbcode, result = ", result)
push_error('Could not save bbcode, result = ', FileAccess.get_open_error())
var exporter = ResultExporter.new()
var f_result = exporter.write_summary_file(_gut, RESULT_JSON)
var f_result = exporter.write_json_file(_gut, RESULT_JSON)
_wrote_results = true
func _exit_tree():
if !_wrote_results and !_cmdln_mode:
if(!_wrote_results and !_cmdln_mode):
_write_results()
func _on_tests_finished(should_exit, should_exit_on_success):
_write_results()
if should_exit:
if(should_exit):
get_tree().quit()
elif should_exit_on_success and _gut.get_fail_count() == 0:
elif(should_exit_on_success and _gut.get_fail_count() == 0):
get_tree().quit()
func get_gut():
if _gut == null:
if(_gut == null):
_gut = Gut.new()
return _gut

View file

@ -1,9 +1,12 @@
[gd_scene load_steps=2 format=2]
[gd_scene load_steps=3 format=3 uid="uid://bqy3ikt6vu4b5"]
[ext_resource path="res://addons/gut/gui/GutRunner.gd" type="Script" id=1]
[ext_resource type="Script" path="res://addons/gut/gui/GutRunner.gd" id="1"]
[ext_resource type="PackedScene" uid="uid://m28heqtswbuq" path="res://addons/gut/GutScene.tscn" id="2_6ruxb"]
[node name="GutRunner" type="Node2D"]
script = ExtResource( 1 )
script = ExtResource("1")
[node name="GutLayer" type="CanvasLayer" parent="."]
layer = 128
[node name="GutScene" parent="GutLayer" instance=ExtResource("2_6ruxb")]

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,303 @@
extends VBoxContainer
@tool
class SearchResults:
var L = 0
var C = 0
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)
L = result.y
C = result.x
elif(result.size() == 2):
te.scroll_vertical = result[L]
te.select(result[L], result[C], result[L], result[C] + text.length())
te.set_caret_column(result[C])
te.set_caret_line(result[L])
te.center_viewport_to_caret()
L = result.y
C = result.x
_last_term = text
te.center_viewport_to_caret()
return result
func _cursor_to_pos():
var to_return = [0, 0]
to_return[L] = te.get_caret_line()
to_return[C] = te.get_caret_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_theme_icon('RichTextEffect', 'EditorIcons')
_ctrls.show_search.icon = get_theme_icon('Search', 'EditorIcons')
_ctrls.word_wrap.icon = get_theme_icon('Loop', 'EditorIcons')
_setup_colors()
if(get_parent() == get_tree().root):
_test_running_setup()
# ------------------
# Private
# ------------------
func _setup_colors():
_ctrls.output.clear()
var keywords = [
['Failed', Color.RED],
['Passed', Color.GREEN],
['Pending', Color.YELLOW],
['Orphans', Color.YELLOW],
['WARNING', Color.YELLOW],
['ERROR', Color.RED]
]
for keyword in keywords:
if (_ctrls.output.syntax_highlighter == null) :
_ctrls.output.syntax_highlighter = CodeHighlighter.new()
_ctrls.output.syntax_highlighter.add_keyword_color(keyword[0], keyword[1])
var f_color = null
if (_ctrls.output.theme == null) :
f_color = get_theme_color("font_color")
else :
f_color = _ctrls.output.theme.font_color
_ctrls.output.add_theme_color_override("font_color_readonly", f_color)
_ctrls.output.add_theme_color_override("function_color", f_color)
_ctrls.output.add_theme_color_override("member_variable_color", f_color)
_ctrls.output.queue_redraw()
func _set_font(font_name, custom_name):
var rtl = _ctrls.output
if(font_name == null):
rtl.set('custom_fonts/' + custom_name, null)
else:
pass
# cuasing issues in 4.0
# var dyn_font = FontFile.new()
# var font_data = FontFile.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_highlighter = _ctrls.use_colors.button_pressed
func _on_ClearButton_pressed():
clear()
func _on_ShowSearch_pressed():
show_search(_ctrls.show_search.button_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.queue_redraw()
# ------------------
# 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.button_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 = FileAccess.open(path, FileAccess.READ)
if(f == null):
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.set_caret_line(line)

View file

@ -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": PackedByteArray( 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 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"]
offset_right = 862.0
offset_bottom = 523.0
size_flags_horizontal = 3
size_flags_vertical = 3
script = ExtResource( 1 )
[node name="Toolbar" type="HBoxContainer" parent="."]
offset_right = 862.0
offset_bottom = 24.0
size_flags_horizontal = 3
[node name="ShowSearch" type="Button" parent="Toolbar"]
offset_right = 28.0
offset_bottom = 24.0
toggle_mode = true
icon = SubResource( 2 )
[node name="UseColors" type="Button" parent="Toolbar"]
offset_left = 32.0
offset_right = 60.0
offset_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="Button" parent="Toolbar"]
offset_left = 64.0
offset_right = 92.0
offset_bottom = 24.0
hint_tooltip = "Word wrap"
toggle_mode = true
icon = SubResource( 2 )
[node name="CenterContainer" type="CenterContainer" parent="Toolbar"]
offset_left = 96.0
offset_right = 743.0
offset_bottom = 24.0
size_flags_horizontal = 3
[node name="CopyButton" type="Button" parent="Toolbar"]
offset_left = 747.0
offset_right = 798.0
offset_bottom = 24.0
hint_tooltip = "Copy to clipboard"
text = " Copy "
[node name="ClearButton" type="Button" parent="Toolbar"]
offset_left = 802.0
offset_right = 862.0
offset_bottom = 24.0
text = " Clear "
[node name="Output" type="TextEdit" parent="."]
offset_top = 28.0
offset_right = 862.0
offset_bottom = 523.0
size_flags_horizontal = 3
size_flags_vertical = 3
readonly = true
highlight_current_line = true
syntax_highlighter = true
show_line_numbers = true
smooth_scrolling = true
[node name="Search" type="HBoxContainer" parent="."]
visible = false
offset_top = 499.0
offset_right = 862.0
offset_bottom = 523.0
[node name="SearchTerm" type="LineEdit" parent="Search"]
offset_right = 804.0
offset_bottom = 24.0
size_flags_horizontal = 3
[node name="SearchNext" type="Button" parent="Search"]
offset_left = 808.0
offset_right = 862.0
offset_bottom = 24.0
hint_tooltip = "Find next (enter)"
text = "Next"
[node name="SearchPrev" type="Button" parent="Search"]
offset_left = 808.0
offset_right = 820.0
offset_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_submitted" 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"]

View file

@ -0,0 +1,152 @@
@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',Callable(self,'_on_cursor_changed'))
if(which != null):
_cur_editor = weakref(which)
which.connect('cursor_changed',Callable(self,'_on_cursor_changed'),[which])
_last_line = which.get_caret_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_size")
func _update_size():
custom_minimum_size.x = _ctrls.btn_method.size.x + _ctrls.btn_method.rect_position.x
# ----------------
# Events
# ----------------
func _on_cursor_changed(which):
if(which.get_caret_line() != _last_line):
_last_line = which.get_caret_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

View file

@ -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="Texture2D" id=2]
[ext_resource path="res://addons/gut/gui/arrow.png" type="Texture2D" id=3]
[node name="RunAtCursor" type="Control"]
anchor_right = 1.0
anchor_bottom = 1.0
offset_right = 1.0
offset_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"]
offset_top = 29.0
offset_right = 50.0
offset_bottom = 43.0
text = "<None>"
[node name="BtnRunScript" type="Button" parent="HBox"]
visible = false
offset_left = 54.0
offset_right = 140.0
offset_bottom = 73.0
text = "<script>"
icon = ExtResource( 2 )
[node name="Arrow1" type="TextureButton" parent="HBox"]
visible = false
offset_left = 54.0
offset_right = 78.0
offset_bottom = 73.0
custom_minimum_size = Vector2( 24, 0 )
texture_normal = ExtResource( 3 )
expand = true
stretch_mode = 3
[node name="BtnRunInnerClass" type="Button" parent="HBox"]
visible = false
offset_left = 134.0
offset_right = 243.0
offset_bottom = 73.0
text = "<inner class>"
icon = ExtResource( 2 )
[node name="Arrow2" type="TextureButton" parent="HBox"]
visible = false
offset_left = 54.0
offset_right = 78.0
offset_bottom = 73.0
custom_minimum_size = Vector2( 24, 0 )
texture_normal = ExtResource( 3 )
expand = true
stretch_mode = 3
[node name="BtnRunMethod" type="Button" parent="HBox"]
visible = false
offset_left = 247.0
offset_right = 337.0
offset_bottom = 73.0
text = "<method>"
icon = ExtResource( 2 )
[connection signal="pressed" from="HBox/BtnRunScript" to="." method="_on_BtnRunScript_pressed"]
[connection signal="pressed" from="HBox/BtnRunInnerClass" to="." method="_on_BtnRunInnerClass_pressed"]
[connection signal="pressed" from="HBox/BtnRunMethod" to="." method="_on_BtnRunMethod_pressed"]

View file

@ -0,0 +1,513 @@
extends Control
@tool
var _interface = null
var _utils = load('res://addons/gut/utils.gd').new()
var _hide_passing = true
var _font = null
var _font_size = null
var _root = null
var _max_icon_width = 10
var _editors = null # script_text_editor_controls.gd
var _show_orphans = true
var _output_control = null
const _col_1_bg_color = Color(0, 0, 0, .1)
var _icons = {
red = load('res://addons/gut/images/red.png'),
green = load('res://addons/gut/images/green.png'),
yellow = load('res://addons/gut/images/yellow.png'),
}
signal search_for_text(text)
@onready var _ctrls = {
tree = $VBox/Output/Scroll/Tree,
lbl_overlay = $VBox/Output/OverlayMessage,
chk_hide_passing = $VBox/Toolbar/HidePassing,
toolbar = {
toolbar = $VBox/Toolbar,
collapse = $VBox/Toolbar/Collapse,
collapse_all = $VBox/Toolbar/CollapseAll,
expand = $VBox/Toolbar/Expand,
expand_all = $VBox/Toolbar/ExpandAll,
hide_passing = $VBox/Toolbar/HidePassing,
show_script = $VBox/Toolbar/ShowScript,
scroll_output = $VBox/Toolbar/ScrollOutput
}
}
func _test_running_setup():
_hide_passing = true
_show_orphans = true
var _gut_config = load('res://addons/gut/gut_config.gd').new()
_gut_config.load_panel_options('res://.gut_editor_config.json')
set_font(
_gut_config.options.panel_options.font_name,
_gut_config.options.panel_options.font_size)
_ctrls.toolbar.hide_passing.text = '[hp]'
load_json_file('user://.gut_editor.json')
func _set_toolbutton_icon(btn, icon_name, text):
if(Engine.is_editor_hint()):
btn.icon = get_theme_icon(icon_name, 'EditorIcons')
else:
btn.text = str('[', text, ']')
func _ready():
var f = null
if ($FontSampler.get_label_settings() == null) :
f = get_theme_default_font()
else :
f = $FontSampler.get_label_settings().font
var s_size = f.get_string_size("000 of 000 passed")
_root = _ctrls.tree.create_item()
_ctrls.tree.set_hide_root(true)
_ctrls.tree.columns = 2
_ctrls.tree.set_column_expand(0, true)
_ctrls.tree.set_column_expand(1, false)
_ctrls.tree.set_column_custom_minimum_width(1, s_size.x)
_set_toolbutton_icon(_ctrls.toolbar.collapse, 'CollapseTree', 'c')
_set_toolbutton_icon(_ctrls.toolbar.collapse_all, 'CollapseTree', 'c')
_set_toolbutton_icon(_ctrls.toolbar.expand, 'ExpandTree', 'e')
_set_toolbutton_icon(_ctrls.toolbar.expand_all, 'ExpandTree', 'e')
_set_toolbutton_icon(_ctrls.toolbar.show_script, 'Script', 'ss')
_set_toolbutton_icon(_ctrls.toolbar.scroll_output, 'Font', 'so')
_ctrls.toolbar.hide_passing.set('custom_icons/checked', get_theme_icon('GuiVisibilityHidden', 'EditorIcons'))
_ctrls.toolbar.hide_passing.set('custom_icons/unchecked', get_theme_icon('GuiVisibilityVisible', 'EditorIcons'))
if(get_parent() == get_tree().root):
_test_running_setup()
call_deferred('_update_min_width')
func _update_min_width():
custom_minimum_size.x = _ctrls.toolbar.toolbar.size.x
func _open_file(path, line_number):
if(_interface == null):
print('Too soon, wait a bit and try again.')
return
var r = load(path)
if(line_number != -1):
_interface.edit_script(r, line_number)
else:
_interface.edit_script(r)
if(_ctrls.toolbar.show_script.pressed):
_interface.set_main_screen_editor('Script')
func _add_script_tree_item(script_path, script_json):
var path_info = _get_path_and_inner_class_name_from_test_path(script_path)
# print('* adding script ', path_info)
var item_text = script_path
var parent = _root
if(path_info.inner_class != ''):
parent = _find_script_item_with_path(path_info.path)
item_text = path_info.inner_class
if(parent == null):
parent = _add_script_tree_item(path_info.path, {})
var item = _ctrls.tree.create_item(parent)
item.set_text(0, item_text)
var meta = {
"type":"script",
"path":path_info.path,
"inner_class":path_info.inner_class,
"json":script_json}
item.set_metadata(0, meta)
item.set_custom_bg_color(1, _col_1_bg_color)
return item
func _add_assert_item(text, icon, parent_item):
# print(' * adding assert')
var assert_item = _ctrls.tree.create_item(parent_item)
assert_item.set_icon_max_width(0, _max_icon_width)
assert_item.set_text(0, text)
assert_item.set_metadata(0, {"type":"assert"})
assert_item.set_icon(0, icon)
assert_item.set_custom_bg_color(1, _col_1_bg_color)
return assert_item
func _add_test_tree_item(test_name, test_json, script_item):
# print(' * adding test ', test_name)
var no_orphans_to_show = !_show_orphans or (_show_orphans and test_json.orphans == 0)
if(_hide_passing and test_json['status'] == 'pass' and no_orphans_to_show):
return
var item = _ctrls.tree.create_item(script_item)
var status = test_json['status']
var meta = {"type":"test", "json":test_json}
item.set_text(0, test_name)
item.set_text(1, status)
item.set_text_alignment(1, HORIZONTAL_ALIGNMENT_RIGHT)
item.set_custom_bg_color(1, _col_1_bg_color)
item.set_metadata(0, meta)
item.set_icon_max_width(0, _max_icon_width)
var orphan_text = 'orphans'
if(test_json.orphans == 1):
orphan_text = 'orphan'
orphan_text = str(test_json.orphans, ' ', orphan_text)
if(status == 'pass' and no_orphans_to_show):
item.set_icon(0, _icons.green)
elif(status == 'pass' and !no_orphans_to_show):
item.set_icon(0, _icons.yellow)
item.set_text(1, orphan_text)
elif(status == 'fail'):
item.set_icon(0, _icons.red)
else:
item.set_icon(0, _icons.yellow)
if(!_hide_passing):
for passing in test_json.passing:
_add_assert_item('pass: ' + passing, _icons.green, item)
for failure in test_json.failing:
_add_assert_item("fail: " + failure.replace("\n", ''), _icons.red, item)
for pending in test_json.pending:
_add_assert_item("pending: " + pending.replace("\n", ''), _icons.yellow, item)
if(status != 'pass' and !no_orphans_to_show):
_add_assert_item(orphan_text, _icons.yellow, item)
return item
func _load_result_tree(j):
var scripts = j['test_scripts']['scripts']
var script_keys = scripts.keys()
# if we made it here, the json is valid and we did something, otherwise the
# 'nothing to see here' should be visible.
clear_centered_text()
var _last_script_item = null
for key in script_keys:
var tests = scripts[key]['tests']
var test_keys = tests.keys()
var s_item = _add_script_tree_item(key, scripts[key])
var bad_count = 0
for test_key in test_keys:
var t_item = _add_test_tree_item(test_key, tests[test_key], s_item)
if(tests[test_key].status != 'pass'):
bad_count += 1
elif(t_item != null):
t_item.collapsed = true
# get_children returns the first child or null. its a dumb name.
if(s_item.get_children() == null):
# var m = s_item.get_metadata(0)
# print('!! Deleting ', m.path, ' ', m.inner_class)
s_item.free()
else:
var total_text = str(test_keys.size(), ' passed')
# s_item.set_text_alignment(1, s_item.ALIGN_LEFT)
if(bad_count == 0):
s_item.collapsed = true
else:
total_text = str(test_keys.size() - bad_count, ' of ', test_keys.size(), ' passed')
s_item.set_text(1, total_text)
_free_childless_scripts()
_show_all_passed()
func _free_childless_scripts():
var items = _root.get_children()
for item in items:
var next_item = item.get_next()
if(item.get_children() == null):
item.free()
item = next_item
func _find_script_item_with_path(path):
var items = _root.get_children()
var to_return = null
var idx = 0
while(idx < items.size() and to_return == null):
var item = items[idx]
if(item.get_metadata(0).path == path):
to_return = item
else:
idx += 1
return to_return
func _get_line_number_from_assert_msg(msg):
var line = -1
if(msg.find('at line') > 0):
line = msg.split("at line")[-1].split(" ")[-1].to_int()
return line
func _get_path_and_inner_class_name_from_test_path(path):
var to_return = {
path = '',
inner_class = ''
}
to_return.path = path
if !path.ends_with('.gd'):
var loc = path.find('.gd')
to_return.inner_class = path.split('.')[-1]
to_return.path = path.substr(0, loc + 3)
return to_return
func _handle_tree_item_select(item, force_scroll):
var item_type = item.get_metadata(0).type
var path = '';
var line = -1;
var method_name = ''
var inner_class = ''
if(item_type == 'test'):
var s_item = item.get_parent()
path = s_item.get_metadata(0)['path']
inner_class = s_item.get_metadata(0)['inner_class']
line = -1
method_name = item.get_text(0)
elif(item_type == 'assert'):
var s_item = item.get_parent().get_parent()
path = s_item.get_metadata(0)['path']
inner_class = s_item.get_metadata(0)['inner_class']
line = _get_line_number_from_assert_msg(item.get_text(0))
method_name = item.get_parent().get_text(0)
elif(item_type == 'script'):
path = item.get_metadata(0)['path']
if(item.get_parent() != _root):
inner_class = item.get_text(0)
line = -1
method_name = ''
else:
return
var path_info = _get_path_and_inner_class_name_from_test_path(path)
if(force_scroll or _ctrls.toolbar.show_script.pressed):
_goto_code(path, line, method_name, inner_class)
if(force_scroll or _ctrls.toolbar.scroll_output.pressed):
_goto_output(path, method_name, inner_class)
# starts at beginning of text edit and searches for each search term, moving
# through the text as it goes; ensuring that, when done, it found the first
# occurance of the last srting that happend after the first occurance of
# each string before it. (Generic way of searching for a method name in an
# inner class that may have be a duplicate of a method name in a different
# inner class)
func _get_line_number_for_seq_search(search_strings, te):
# var te = _editors.get_current_text_edit()
var result = null
var line = Vector2i(-1, -1)
var s_flags = 0
var i = 0
var string_found = true
while(i < search_strings.size() and string_found):
result = te.search(search_strings[i], s_flags, line.y, line.x)
if(result.x != -1):
line = result
else:
string_found = false
i += 1
return line.y
func _goto_code(path, line, method_name='', inner_class =''):
if(_interface == null):
print('going to ', [path, line, method_name, inner_class])
return
_open_file(path, line)
if(line == -1):
var search_strings = []
if(inner_class != ''):
search_strings.append(inner_class)
if(method_name != ''):
search_strings.append(method_name)
line = _get_line_number_for_seq_search(search_strings, _editors.get_current_text_edit())
if(line != -1):
_interface.get_script_editor().goto_line(line)
func _goto_output(path, method_name, inner_class):
if(_output_control == null):
return
var search_strings = [path]
if(inner_class != ''):
search_strings.append(inner_class)
if(method_name != ''):
search_strings.append(method_name)
var line = _get_line_number_for_seq_search(search_strings, _output_control.get_rich_text_edit())
if(line != -1):
_output_control.scroll_to_line(line)
func _show_all_passed():
if(_root.get_children() == null):
add_centered_text('Everything passed!')
func _set_collapsed_on_all(item, value):
if(item == _root):
var node = _root.get_children()
while(node != null):
node.call_recursive('set_collapsed', value)
node = node.get_next()
else:
item.call_recursive('set_collapsed', value)
# --------------
# Events
# --------------
func _on_Tree_item_selected():
# do not force scroll
var item = _ctrls.tree.get_selected()
_handle_tree_item_select(item, false)
# it just looks better if the left is always selected.
if(item.is_selected(1)):
item.deselect(1)
item.select(0)
func _on_Tree_item_activated():
# force scroll
print('double clicked')
_handle_tree_item_select(_ctrls.tree.get_selected(), true)
func _on_Collapse_pressed():
collapse_selected()
func _on_Expand_pressed():
expand_selected()
func _on_CollapseAll_pressed():
collapse_all()
func _on_ExpandAll_pressed():
expand_all()
func _on_Hide_Passing_pressed():
_hide_passing = _ctrls.toolbar.hide_passing.button_pressed
# --------------
# Public
# --------------
func load_json_file(path):
var text = _utils.get_file_as_text(path)
if(text != ''):
var test_json_conv = JSON.new()
test_json_conv.parse(text)
var result = test_json_conv.get_data()
if(result.error != OK):
add_centered_text(str(path, " has invalid json in it \n",
'Error ', result.error, "@", result.error_line, "\n",
result.error_string))
return
load_json_results(result.result)
else:
add_centered_text(str(path, ' was empty or does not exist.'))
func load_json_results(j):
clear()
add_centered_text('Nothing Here')
_load_result_tree(j)
func add_centered_text(t):
_ctrls.lbl_overlay.text = t
func clear_centered_text():
_ctrls.lbl_overlay.text = ''
func clear():
_ctrls.tree.clear()
_root = _ctrls.tree.create_item()
clear_centered_text()
func set_interface(which):
_interface = which
func set_script_text_editors(value):
_editors = value
func collapse_all():
_set_collapsed_on_all(_root, true)
func expand_all():
_set_collapsed_on_all(_root, false)
func collapse_selected():
var item = _ctrls.tree.get_selected()
if(item != null):
_set_collapsed_on_all(item, true)
func expand_selected():
var item = _ctrls.tree.get_selected()
if(item != null):
_set_collapsed_on_all(item, false)
func set_show_orphans(should):
_show_orphans = should
func set_font(font_name, size):
pass
# var dyn_font = FontFile.new()
# var font_data = FontFile.new()
# font_data.font_path = 'res://addons/gut/fonts/' + font_name + '-Regular.ttf'
# font_data.antialiased = true
# dyn_font.font_data = font_data
#
# _font = dyn_font
# _font.size = size
# _font_size = size
func set_output_control(value):
_output_control = value

View file

@ -0,0 +1,165 @@
[gd_scene load_steps=4 format=2]
[ext_resource path="res://addons/gut/gui/RunResults.gd" type="Script" id=1]
[sub_resource type="Image" id=3]
data = {
"data": PackedByteArray( 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 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="RunResults" type="Control"]
offset_right = 595.0
offset_bottom = 459.0
custom_minimum_size = Vector2( 302, 0 )
script = ExtResource( 1 )
[node name="VBox" type="VBoxContainer" parent="."]
anchor_right = 1.0
anchor_bottom = 1.0
[node name="Toolbar" type="HBoxContainer" parent="VBox"]
offset_right = 296.0
offset_bottom = 24.0
size_flags_horizontal = 0
[node name="Expand" type="Button" parent="VBox/Toolbar"]
offset_right = 28.0
offset_bottom = 24.0
hint_tooltip = "Expand selected item and all children."
icon = SubResource( 2 )
[node name="Collapse" type="Button" parent="VBox/Toolbar"]
offset_left = 32.0
offset_right = 60.0
offset_bottom = 24.0
hint_tooltip = "Collapse selected item and all children."
icon = SubResource( 2 )
[node name="Sep" type="ColorRect" parent="VBox/Toolbar"]
offset_left = 64.0
offset_right = 66.0
offset_bottom = 24.0
custom_minimum_size = Vector2( 2, 0 )
[node name="LblAll" type="Label" parent="VBox/Toolbar"]
offset_left = 70.0
offset_top = 5.0
offset_right = 91.0
offset_bottom = 19.0
text = "All:"
align = 1
[node name="ExpandAll" type="Button" parent="VBox/Toolbar"]
offset_left = 95.0
offset_right = 123.0
offset_bottom = 24.0
hint_tooltip = "Expand All."
icon = SubResource( 2 )
[node name="CollapseAll" type="Button" parent="VBox/Toolbar"]
offset_left = 127.0
offset_right = 155.0
offset_bottom = 24.0
hint_tooltip = "Collapse all."
icon = SubResource( 2 )
[node name="Sep2" type="ColorRect" parent="VBox/Toolbar"]
offset_left = 159.0
offset_right = 161.0
offset_bottom = 24.0
custom_minimum_size = Vector2( 2, 0 )
[node name="HidePassing" type="CheckBox" parent="VBox/Toolbar"]
offset_left = 165.0
offset_right = 189.0
offset_bottom = 24.0
hint_tooltip = "Show/Hide passing tests. Takes effect on next run."
size_flags_horizontal = 4
custom_icons/checked = SubResource( 2 )
custom_icons/unchecked = SubResource( 2 )
pressed = true
__meta__ = {
"_editor_description_": ""
}
[node name="Sep3" type="ColorRect" parent="VBox/Toolbar"]
offset_left = 193.0
offset_right = 195.0
offset_bottom = 24.0
custom_minimum_size = Vector2( 2, 0 )
[node name="LblSync" type="Label" parent="VBox/Toolbar"]
offset_left = 199.0
offset_top = 5.0
offset_right = 232.0
offset_bottom = 19.0
text = "Sync:"
align = 1
[node name="ShowScript" type="Button" parent="VBox/Toolbar"]
offset_left = 236.0
offset_right = 264.0
offset_bottom = 24.0
hint_tooltip = "Open script and scroll to line when a tree item is clicked."
toggle_mode = true
pressed = true
icon = SubResource( 2 )
[node name="ScrollOutput" type="Button" parent="VBox/Toolbar"]
offset_left = 268.0
offset_right = 296.0
offset_bottom = 24.0
hint_tooltip = "Scroll to related line in the output panel when tree item clicked."
toggle_mode = true
pressed = true
icon = SubResource( 2 )
[node name="Output" type="Panel" parent="VBox"]
self_modulate = Color( 1, 1, 1, 0.541176 )
offset_top = 28.0
offset_right = 595.0
offset_bottom = 459.0
size_flags_horizontal = 3
size_flags_vertical = 3
[node name="Scroll" type="ScrollContainer" parent="VBox/Output"]
anchor_right = 1.0
anchor_bottom = 1.0
[node name="Tree" type="Tree" parent="VBox/Output/Scroll"]
offset_right = 595.0
offset_bottom = 431.0
size_flags_horizontal = 3
size_flags_vertical = 3
columns = 2
hide_root = true
[node name="OverlayMessage" type="Label" parent="VBox/Output"]
anchor_right = 1.0
anchor_bottom = 1.0
align = 1
valign = 1
[node name="FontSampler" type="Label" parent="."]
visible = false
offset_right = 40.0
offset_bottom = 14.0
text = "000 of 000 passed"
[connection signal="pressed" from="VBox/Toolbar/Expand" to="." method="_on_Expand_pressed"]
[connection signal="pressed" from="VBox/Toolbar/Collapse" to="." method="_on_Collapse_pressed"]
[connection signal="pressed" from="VBox/Toolbar/ExpandAll" to="." method="_on_ExpandAll_pressed"]
[connection signal="pressed" from="VBox/Toolbar/CollapseAll" to="." method="_on_CollapseAll_pressed"]
[connection signal="pressed" from="VBox/Toolbar/HidePassing" to="." method="_on_Hide_Passing_pressed"]
[connection signal="item_activated" from="VBox/Output/Scroll/Tree" to="." method="_on_Tree_item_activated"]
[connection signal="item_selected" from="VBox/Output/Scroll/Tree" to="." method="_on_Tree_item_selected"]

View file

@ -0,0 +1,7 @@
[gd_scene format=2]
[node name="Settings" type="VBoxContainer"]
offset_right = 388.0
offset_bottom = 586.0
size_flags_horizontal = 3
size_flags_vertical = 3

View file

@ -0,0 +1,144 @@
@tool
extends Control
@onready var _ctrls = {
shortcut_label = $Layout/lblShortcut,
set_button = $Layout/SetButton,
save_button = $Layout/SaveButton,
cancel_button = $Layout/CancelButton,
clear_button = $Layout/ClearButton
}
signal changed
signal start_edit
signal end_edit
const NO_SHORTCUT = '<None>'
var _source_event = InputEventKey.new()
var _pre_edit_event = null
var _key_disp = NO_SHORTCUT
var _modifier_keys = [KEY_ALT, KEY_CTRL, KEY_META, KEY_SHIFT]
# Called when the node enters the scene tree for the first time.
func _ready():
set_process_unhandled_key_input(false)
func _display_shortcut():
if(_key_disp == ''):
_key_disp = NO_SHORTCUT
_ctrls.shortcut_label.text = _key_disp
func _is_shift_only_modifier():
return _source_event.shift_pressed and \
!(_source_event.alt_pressed or _source_event.command_pressed or \
_source_event.ctrl_pressed or _source_event.meta_pressed) and \
!_is_modifier(_source_event.keycode)
func _has_modifier(event):
return event.alt_pressed or event.command_pressed or \
event.ctrl_pressed or event.meta_pressed or \
event.shift_pressed
func _is_modifier(keycode):
return _modifier_keys.has(keycode)
func _edit_mode(should):
set_process_unhandled_key_input(should)
_ctrls.set_button.visible = !should
_ctrls.save_button.visible = should
_ctrls.save_button.disabled = should
_ctrls.cancel_button.visible = should
_ctrls.clear_button.visible = !should
if(should and to_s() == ''):
_ctrls.shortcut_label.text = 'press buttons'
else:
_ctrls.shortcut_label.text = to_s()
if(should):
emit_signal("start_edit")
else:
emit_signal("end_edit")
# ---------------
# Events
# ---------------
func _unhandled_key_input(event):
if(event is InputEventKey):
if(event.pressed):
if(_has_modifier(event) and !_is_modifier(event.get_keycode_with_modifiers())):
_source_event = event
_key_disp = OS.get_keycode_string(event.get_keycode_with_modifiers())
else:
_source_event = InputEventKey.new()
_key_disp = NO_SHORTCUT
_display_shortcut()
_ctrls.save_button.disabled = !is_valid()
func _on_SetButton_pressed():
_pre_edit_event = _source_event.duplicate(true)
_edit_mode(true)
func _on_SaveButton_pressed():
_edit_mode(false)
_pre_edit_event = null
emit_signal('changed')
func _on_CancelButton_pressed():
_edit_mode(false)
_source_event = _pre_edit_event
_key_disp = to_s()
_display_shortcut()
func _on_ClearButton_pressed():
clear_shortcut()
# ---------------
# Public
# ---------------
func to_s():
return OS.get_keycode_string(_source_event.get_keycode_with_modifiers())
func is_valid():
return _has_modifier(_source_event) and !_is_shift_only_modifier()
func get_shortcut():
var to_return = Shortcut.new()
to_return.events.append(_source_event)
return to_return
func set_shortcut(sc):
if(sc == null or sc.events == null || sc.events.size() <= 0):
clear_shortcut()
else:
_source_event = sc.events[0]
_key_disp = to_s()
_display_shortcut()
func clear_shortcut():
_source_event = InputEventKey.new()
_key_disp = NO_SHORTCUT
_display_shortcut()
func disable_set(should):
_ctrls.set_button.disabled = should
func disable_clear(should):
_ctrls.clear_button.disabled = should

View file

@ -0,0 +1,80 @@
[gd_scene load_steps=2 format=2]
[ext_resource path="res://addons/gut/gui/ShortcutButton.gd" type="Script" id=1]
[node name="ShortcutButton" type="Control"]
anchor_right = 0.123
anchor_bottom = 0.04
offset_right = 33.048
offset_bottom = 1.0
custom_minimum_size = Vector2( 125, 25 )
script = ExtResource( 1 )
__meta__ = {
"_edit_use_anchors_": false
}
[node name="Layout" type="HBoxContainer" parent="."]
anchor_right = 1.0
anchor_bottom = 1.0
__meta__ = {
"_edit_use_anchors_": false
}
[node name="lblShortcut" type="Label" parent="Layout"]
offset_right = 50.0
offset_bottom = 25.0
size_flags_horizontal = 3
size_flags_vertical = 7
text = "<None>"
align = 2
valign = 1
[node name="CenterContainer" type="CenterContainer" parent="Layout"]
offset_left = 54.0
offset_right = 64.0
offset_bottom = 25.0
custom_minimum_size = Vector2( 10, 0 )
[node name="SetButton" type="Button" parent="Layout"]
offset_left = 68.0
offset_right = 128.0
offset_bottom = 25.0
custom_minimum_size = Vector2( 60, 0 )
text = "Set"
__meta__ = {
"_edit_use_anchors_": false
}
[node name="SaveButton" type="Button" parent="Layout"]
visible = false
offset_left = 82.0
offset_right = 142.0
offset_bottom = 25.0
custom_minimum_size = Vector2( 60, 0 )
text = "Save"
__meta__ = {
"_edit_use_anchors_": false
}
[node name="CancelButton" type="Button" parent="Layout"]
visible = false
offset_left = 82.0
offset_right = 142.0
offset_bottom = 25.0
custom_minimum_size = Vector2( 60, 0 )
text = "Cancel"
__meta__ = {
"_edit_use_anchors_": false
}
[node name="ClearButton" type="Button" parent="Layout"]
offset_left = 132.0
offset_right = 192.0
offset_bottom = 25.0
custom_minimum_size = Vector2( 60, 0 )
text = "Clear"
[connection signal="pressed" from="Layout/SetButton" to="." method="_on_SetButton_pressed"]
[connection signal="pressed" from="Layout/SaveButton" to="." method="_on_SaveButton_pressed"]
[connection signal="pressed" from="Layout/CancelButton" to="." method="_on_CancelButton_pressed"]
[connection signal="pressed" from="Layout/ClearButton" to="." method="_on_ClearButton_pressed"]

BIN
addons/gut/gui/arrow.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 122 B

View file

@ -0,0 +1,34 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://6wra5rxmfsrl"
path="res://.godot/imported/arrow.png-2b5b2d838b5b3467cf300ac2da1630d9.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/gut/gui/arrow.png"
dest_files=["res://.godot/imported/arrow.png-2b5b2d838b5b3467cf300ac2da1630d9.ctex"]
[params]
compress/mode=0
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/bptc_ldr=0
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1

View file

@ -0,0 +1,426 @@
# ------------------------------------------------------------------------------
# ------------------------------------------------------------------------------
class DirectoryCtrl:
extends HBoxContainer
var text = '':
get:
return _txt_path.text
set(val):
_txt_path.text = val
var _txt_path = LineEdit.new()
var _btn_dir = Button.new()
var _dialog = FileDialog.new()
func _init():
_btn_dir.text = '...'
_btn_dir.connect('pressed',Callable(self,'_on_dir_button_pressed'))
_txt_path.size_flags_horizontal = _txt_path.SIZE_EXPAND_FILL
_dialog.mode = _dialog.FILE_MODE_OPEN_DIR
_dialog.unresizable = false
_dialog.connect("dir_selected",Callable(self,'_on_selected'))
_dialog.connect("file_selected",Callable(self,'_on_selected'))
_dialog.size = Vector2(1000, 700)
func _on_selected(path):
text = path
func _on_dir_button_pressed():
_dialog.current_dir = _txt_path.text
_dialog.popup_centered()
func _ready():
add_child(_txt_path)
add_child(_btn_dir)
add_child(_dialog)
func get_line_edit():
return _txt_path
# ------------------------------------------------------------------------------
# ------------------------------------------------------------------------------
class FileCtrl:
extends DirectoryCtrl
func _init():
_dialog.mode = _dialog.FILE_MODE_OPEN_FILE
# ------------------------------------------------------------------------------
# ------------------------------------------------------------------------------
class Vector2Ctrl:
extends VBoxContainer
var value = Vector2(-1, -1) :
get:
return get_value()
set(val):
set_value(val)
var disabled = false :
get:
return get_disabled()
set(val):
set_disabled(val)
var x_spin = SpinBox.new()
var y_spin = SpinBox.new()
func _init():
add_child(_make_one('x: ', x_spin))
add_child(_make_one('y: ', y_spin))
func _make_one(txt, spinner):
var hbox = HBoxContainer.new()
var lbl = Label.new()
lbl.text = txt
hbox.add_child(lbl)
hbox.add_child(spinner)
spinner.min_value = -1
spinner.max_value = 10000
spinner.size_flags_horizontal = spinner.SIZE_EXPAND_FILL
return hbox
func set_value(v):
if(v != null):
x_spin.value = v[0]
y_spin.value = v[1]
# Returns array instead of vector2 b/c that is what is stored in
# in the dictionary and what is expected everywhere else.
func get_value():
return [x_spin.value, y_spin.value]
func set_disabled(should):
get_parent().visible = !should
x_spin.visible = !should
y_spin.visible = !should
func get_disabled():
pass
# ------------------------------------------------------------------------------
# ------------------------------------------------------------------------------
var _base_container = null
var _base_control = null
const DIRS_TO_LIST = 6
var _cfg_ctrls = {}
var _avail_fonts = ['AnonymousPro', 'CourierPrime', 'LobsterTwo', 'Default']
func _init(cont):
_base_container = cont
_base_control = HBoxContainer.new()
_base_control.size_flags_horizontal = _base_control.SIZE_EXPAND_FILL
_base_control.mouse_filter = _base_control.MOUSE_FILTER_PASS
# I don't remember what this is all about at all. Could be
# garbage. Decided to spend more time typing this message
# than figuring it out.
var lbl = Label.new()
lbl.size_flags_horizontal = lbl.SIZE_EXPAND_FILL
lbl.mouse_filter = lbl.MOUSE_FILTER_STOP
_base_control.add_child(lbl)
# ------------------
# Private
# ------------------
func _new_row(key, disp_text, value_ctrl, hint):
var ctrl = _base_control.duplicate()
var lbl = ctrl.get_child(0)
lbl.hint_tooltip = hint
lbl.text = disp_text
_base_container.add_child(ctrl)
_cfg_ctrls[key] = value_ctrl
ctrl.add_child(value_ctrl)
var rpad = CenterContainer.new()
# rpad.custom_minimum_size.x = 5
ctrl.add_child(rpad)
return ctrl
func _add_title(text):
var row = _base_control.duplicate()
var lbl = row.get_child(0)
lbl.text = text
# lbl.align = Label.ALIGNMENT_CENTER
_base_container.add_child(row)
row.connect('draw', _on_title_cell_draw.bind(row))
func _add_number(key, value, disp_text, v_min, v_max, hint=''):
var value_ctrl = SpinBox.new()
value_ctrl.value = value
value_ctrl.size_flags_horizontal = value_ctrl.SIZE_EXPAND_FILL
value_ctrl.min_value = v_min
value_ctrl.max_value = v_max
_wire_select_on_focus(value_ctrl.get_line_edit())
_new_row(key, disp_text, value_ctrl, hint)
func _add_select(key, value, values, disp_text, hint=''):
var value_ctrl = OptionButton.new()
var select_idx = 0
for i in range(values.size()):
value_ctrl.add_item(values[i])
if(value == values[i]):
select_idx = i
value_ctrl.selected = select_idx
value_ctrl.size_flags_horizontal = value_ctrl.SIZE_EXPAND_FILL
_new_row(key, disp_text, value_ctrl, hint)
func _add_value(key, value, disp_text, hint=''):
var value_ctrl = LineEdit.new()
value_ctrl.size_flags_horizontal = value_ctrl.SIZE_EXPAND_FILL
value_ctrl.text = value
_wire_select_on_focus(value_ctrl)
_new_row(key, disp_text, value_ctrl, hint)
func _add_boolean(key, value, disp_text, hint=''):
var value_ctrl = CheckBox.new()
value_ctrl.button_pressed = value
_new_row(key, disp_text, value_ctrl, hint)
func _add_directory(key, value, disp_text, hint=''):
var value_ctrl = DirectoryCtrl.new()
value_ctrl.size_flags_horizontal = value_ctrl.SIZE_EXPAND_FILL
value_ctrl.text = value
_wire_select_on_focus(value_ctrl.get_line_edit())
_new_row(key, disp_text, value_ctrl, hint)
func _add_file(key, value, disp_text, hint=''):
var value_ctrl = FileCtrl.new()
value_ctrl.size_flags_horizontal = value_ctrl.SIZE_EXPAND_FILL
value_ctrl.text = value
_wire_select_on_focus(value_ctrl.get_line_edit())
_new_row(key, disp_text, value_ctrl, hint)
func _add_color(key, value, disp_text, hint=''):
var value_ctrl = ColorPickerButton.new()
value_ctrl.size_flags_horizontal = value_ctrl.SIZE_EXPAND_FILL
value_ctrl.color = value
_new_row(key, disp_text, value_ctrl, hint)
func _add_vector2(key, value, disp_text, hint=''):
var value_ctrl = Vector2Ctrl.new()
value_ctrl.size_flags_horizontal = value_ctrl.SIZE_EXPAND_FILL
value_ctrl.value = value
_wire_select_on_focus(value_ctrl.x_spin.get_line_edit())
_wire_select_on_focus(value_ctrl.y_spin.get_line_edit())
_new_row(key, disp_text, value_ctrl, hint)
# -----------------------------
# ------------------
# Events
# ------------------
func _wire_select_on_focus(which):
pass
which.connect('focus_entered', _on_ctrl_focus_highlight.bind(which))
which.connect('focus_exited', _on_ctrl_focus_unhighlight.bind(which))
func _on_ctrl_focus_highlight(which):
if(which.has_method('select_all')):
which.call_deferred('select_all')
func _on_ctrl_focus_unhighlight(which):
if(which.has_method('select')):
which.select(0, 0)
func _on_title_cell_draw(which):
which.draw_rect(Rect2(Vector2(0, 0), which.size), Color(0, 0, 0, .15))
# ------------------
# Public
# ------------------
func get_config_issues():
var to_return = []
var has_directory = false
for i in range(DIRS_TO_LIST):
var key = str('directory_', i)
var path = _cfg_ctrls[key].text
if(path != null and path != ''):
has_directory = true
if(!DirAccess.dir_exists(path)):
to_return.append(str('Test directory ', path, ' does not exist.'))
if(!has_directory):
to_return.append('You do not have any directories set.')
if(!_cfg_ctrls['suffix'].text.ends_with('.gd')):
to_return.append("Script suffix must end in '.gd'")
return to_return
func set_options(options):
_add_title("Settings")
_add_number("log_level", options.log_level, "Log Level", 0, 3,
"Detail level for log messages.\n" + \
"\t0: Errors and failures only.\n" + \
"\t1: Adds all test names + warnings + info\n" + \
"\t2: Shows all asserts\n" + \
"\t3: Adds more stuff probably, maybe not.")
_add_boolean('ignore_pause', options.ignore_pause, 'Ignore Pause',
"Ignore calls to pause_before_teardown")
_add_boolean('hide_orphans', options.hide_orphans, 'Hide Orphans',
'Do not display orphan counts in output.')
_add_boolean('should_exit', options.should_exit, 'Exit on Finish',
"Exit when tests finished.")
_add_boolean('should_exit_on_success', options.should_exit_on_success, 'Exit on Success',
"Exit if there are no failures. Does nothing if 'Exit on Finish' is enabled.")
_add_title("Panel Output")
_add_select('output_font_name', options.panel_options.font_name, _avail_fonts, 'Font',
"The name of the font to use when running tests and in the output panel to the left.")
_add_number('output_font_size', options.panel_options.font_size, 'Font Size', 5, 100,
"The font size to use when running tests and in the output panel to the left.")
_add_title('Runner Window')
_add_boolean("gut_on_top", options.gut_on_top, "On Top",
"The GUT Runner appears above children added during tests.")
_add_number('opacity', options.opacity, 'Opacity', 0, 100,
"The opacity of GUT when tests are running.")
_add_boolean('should_maximize', options.should_maximize, 'Maximize',
"Maximize GUT when tests are being run.")
_add_boolean('compact_mode', options.compact_mode, 'Compact Mode',
'The runner will be in compact mode. This overrides Maximize.')
_add_title('Runner Appearance')
_add_select('font_name', options.font_name, _avail_fonts, 'Font',
"The font to use for text output in the Gut Runner.")
_add_number('font_size', options.font_size, 'Font Size', 5, 100,
"The font size for text output in the Gut Runner.")
_add_color('font_color', options.font_color, 'Font Color',
"The font color for text output in the Gut Runner.")
_add_color('background_color', options.background_color, 'Background Color',
"The background color for text output in the Gut Runner.")
_add_boolean('disable_colors', options.disable_colors, 'Disable Formatting',
'Disable formatting and colors used in the Runner. Does not affect panel output.')
_add_title('Test Directories')
_add_boolean('include_subdirs', options.include_subdirs, 'Include Subdirs',
"Include subdirectories of the directories configured below.")
for i in range(DIRS_TO_LIST):
var value = ''
if(options.dirs.size() > i):
value = options.dirs[i]
_add_directory(str('directory_', i), value, str('Directory ', i))
_add_title("XML Output")
_add_value("junit_xml_file", options.junit_xml_file, "Output Path3D",
"Path3D and filename where GUT should create a JUnit compliant XML file. " +
"This file will contain the results of the last test run. To avoid " +
"overriding the file use Include Timestamp.")
_add_boolean("junit_xml_timestamp", options.junit_xml_timestamp, "Include Timestamp",
"Include a timestamp in the filename so that each run gets its own xml file.")
_add_title('Hooks')
_add_file('pre_run_script', options.pre_run_script, 'Pre-Run Hook',
'This script will be run by GUT before any tests are run.')
_add_file('post_run_script', options.post_run_script, 'Post-Run Hook',
'This script will be run by GUT after all tests are run.')
_add_title('Misc')
_add_value('prefix', options.prefix, 'Script Prefix',
"The filename prefix for all test scripts.")
_add_value('suffix', options.suffix, 'Script Suffix',
"Script suffix, including .gd extension. For example '_foo.gd'.")
_add_number('paint_after', options.paint_after, 'Paint After', 0.0, 1.0,
"How long GUT will wait before pausing for 1 frame to paint the screen. 0 is never.")
# since _add_number doesn't set step property, it will default to a step of
# 1 and cast values to int. Give it a .5 step and re-set the value.
_cfg_ctrls.paint_after.step = .05
_cfg_ctrls.paint_after.value = options.paint_after
print('paint after = ', options.paint_after)
func get_options(base_opts):
var to_return = base_opts.duplicate()
# Settings
to_return.log_level = _cfg_ctrls.log_level.value
to_return.ignore_pause = _cfg_ctrls.ignore_pause.button_pressed
to_return.hide_orphans = _cfg_ctrls.hide_orphans.button_pressed
to_return.should_exit = _cfg_ctrls.should_exit.button_pressed
to_return.should_exit_on_success = _cfg_ctrls.should_exit_on_success.button_pressed
#Output
to_return.panel_options.font_name = _cfg_ctrls.output_font_name.get_item_text(
_cfg_ctrls.output_font_name.selected)
to_return.panel_options.font_size = _cfg_ctrls.output_font_size.value
# Runner Appearance
to_return.font_name = _cfg_ctrls.font_name.get_item_text(
_cfg_ctrls.font_name.selected)
to_return.font_size = _cfg_ctrls.font_size.value
to_return.should_maximize = _cfg_ctrls.should_maximize.button_pressed
to_return.compact_mode = _cfg_ctrls.compact_mode.button_pressed
to_return.opacity = _cfg_ctrls.opacity.value
to_return.background_color = _cfg_ctrls.background_color.color.to_html()
to_return.font_color = _cfg_ctrls.font_color.color.to_html()
to_return.disable_colors = _cfg_ctrls.disable_colors.button_pressed
to_return.gut_on_top = _cfg_ctrls.gut_on_top.button_pressed
to_return.paint_after = _cfg_ctrls.paint_after.value
# Directories
to_return.include_subdirs = _cfg_ctrls.include_subdirs.button_pressed
var dirs = []
for i in range(DIRS_TO_LIST):
var key = str('directory_', i)
var val = _cfg_ctrls[key].text
if(val != '' and val != null):
dirs.append(val)
to_return.dirs = dirs
# XML Output
to_return.junit_xml_file = _cfg_ctrls.junit_xml_file.text
to_return.junit_xml_timestamp = _cfg_ctrls.junit_xml_timestamp.button_pressed
# Hooks
to_return.pre_run_script = _cfg_ctrls.pre_run_script.text
to_return.post_run_script = _cfg_ctrls.post_run_script.text
# Misc
to_return.prefix = _cfg_ctrls.prefix.text
to_return.suffix = _cfg_ctrls.suffix.text
return to_return

BIN
addons/gut/gui/play.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 143 B

View file

@ -0,0 +1,34 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://cr6tvdv0ve6cv"
path="res://.godot/imported/play.png-5c90e88e8136487a183a099d67a7de24.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/gut/gui/play.png"
dest_files=["res://.godot/imported/play.png-5c90e88e8136487a183a099d67a7de24.ctex"]
[params]
compress/mode=0
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/bptc_ldr=0
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1

View file

@ -0,0 +1,230 @@
# Holds weakrefs to a ScriptTextEditor and related children nodes
# that might be useful. Though the TextEdit is really the only one, but
# since the tree may change, the first TextEdit under a CodeTextEditor is
# the one we use...so we hold a ref to the CodeTextEditor too.
class ScriptEditorControlRef:
var _text_edit = null
var _script_editor = null
var _code_editor = null
func _init(script_edit):
_script_editor = weakref(script_edit)
_populate_controls()
func _populate_controls():
# who knows if the tree will change so get the first instance of each
# type of control we care about. Chances are there won't be more than
# one of these in the future, but their position in the tree may change.
_code_editor = weakref(_get_first_child_named('CodeTextEditor', _script_editor.get_ref()))
_text_edit = weakref(_get_first_child_named("CodeEdit", _code_editor.get_ref()))
func _get_first_child_named(obj_name, parent_obj):
if(parent_obj == null):
return null
var kids = parent_obj.get_children()
var index = 0
var to_return = null
while(index < kids.size() and to_return == null):
if(str(kids[index]).find(str("[", obj_name)) != -1):
to_return = kids[index]
else:
to_return = _get_first_child_named(obj_name, kids[index])
if(to_return == null):
index += 1
return to_return
func get_script_text_edit():
return _script_editor.get_ref()
func get_text_edit():
# ScriptTextEditors that are loaded when the project is opened
# do not have their children populated yet. So if we may have to
# _populate_controls again if we don't have one.
if(_text_edit == null):
_populate_controls()
return _text_edit.get_ref()
func get_script_editor():
return _script_editor
func is_visible():
var to_return = false
if(_script_editor.get_ref()):
to_return = _script_editor.get_ref().visible
return to_return
# ##############################################################################
#
# ##############################################################################
# Used to make searching for the controls easier and faster.
var _script_editors_parent = null
# reference the ScriptEditor instance
var _script_editor = null
# Array of ScriptEditorControlRef containing all the opened ScriptTextEditors
# and related controls at the time of the last refresh.
var _script_editor_controls = []
var _method_prefix = 'test_'
var _inner_class_prefix = 'Test'
func _init(script_edit):
_script_editor = script_edit
refresh()
func _is_script_editor(obj):
return str(obj).find('[ScriptTextEditor') != -1
# Find the first ScriptTextEditor and then get its parent. Done this way
# because who knows if the parent object will change. This is somewhat
# future proofed.
func _find_script_editors_parent():
var _first_editor = _get_first_child_of_type_name("ScriptTextEditor", _script_editor)
if(_first_editor != null):
_script_editors_parent = _first_editor.get_parent()
func _populate_editors():
if(_script_editors_parent == null):
return
_script_editor_controls.clear()
for child in _script_editors_parent.get_children():
if(_is_script_editor(child)):
var ref = ScriptEditorControlRef.new(child)
_script_editor_controls.append(ref)
# Yes, this is the same as the one above but with a different name. This was
# easier than trying to find a place where it could be used by both.
func _get_first_child_of_type_name(obj_name, parent_obj):
if(parent_obj == null):
return null
var kids = parent_obj.get_children()
var index = 0
var to_return = null
while(index < kids.size() and to_return == null):
if(str(kids[index]).find(str("[", obj_name)) != -1):
to_return = kids[index]
else:
to_return = _get_first_child_of_type_name(obj_name, kids[index])
if(to_return == null):
index += 1
return to_return
func _get_func_name_from_line(text):
text = text.strip_edges()
var left = text.split("(")[0]
var func_name = left.split(" ")[1]
return func_name
func _get_class_name_from_line(text):
text = text.strip_edges()
var right = text.split(" ")[1]
var the_name = right.rstrip(":")
return the_name
func refresh():
if(_script_editors_parent == null):
_find_script_editors_parent()
if(_script_editors_parent != null):
_populate_editors()
func get_current_text_edit():
var cur_script_editor = null
var idx = 0
while(idx < _script_editor_controls.size() and cur_script_editor == null):
if(_script_editor_controls[idx].is_visible()):
cur_script_editor = _script_editor_controls[idx]
else:
idx += 1
var to_return = null
if(cur_script_editor != null):
to_return = cur_script_editor.get_text_edit()
return to_return
func get_script_editor_controls():
var to_return = []
for ctrl_ref in _script_editor_controls:
to_return.append(ctrl_ref.get_script_text_edit())
return to_return
func get_line_info():
var editor = get_current_text_edit()
if(editor == null):
return
var info = {
script = null,
inner_class = null,
test_method = null
}
var line = editor.get_caret_line()
var done_func = false
var done_inner = false
while(line > 0 and (!done_func or !done_inner)):
if(editor.can_fold_line(line)):
var text = editor.get_line(line)
var strip_text = text.strip_edges(true, false) # only left
if(!done_func and strip_text.begins_with("func ")):
var func_name = _get_func_name_from_line(text)
if(func_name.begins_with(_method_prefix)):
info.test_method = func_name
done_func = true
# If the func line is left justified then there won't be any
# inner classes above it.
if(strip_text == text):
done_inner = true
if(!done_inner and strip_text.begins_with("class")):
var inner_name = _get_class_name_from_line(text)
if(inner_name.begins_with(_inner_class_prefix)):
info.inner_class = inner_name
done_inner = true
# if we found an inner class then we are already past
# any test the cursor could be in.
done_func = true
line -= 1
return info
func get_method_prefix():
return _method_prefix
func set_method_prefix(method_prefix):
_method_prefix = method_prefix
func get_inner_class_prefix():
return _inner_class_prefix
func set_inner_class_prefix(inner_class_prefix):
_inner_class_prefix = inner_class_prefix